Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15407456
D3357.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
9 KB
Referenced Files
None
Subscribers
None
D3357.diff
View Options
Index: externals/raphael/g.raphael.dot.js
===================================================================
--- /dev/null
+++ externals/raphael/g.raphael.dot.js
@@ -0,0 +1,13 @@
+/**
+ * @requires raphael-core
+ * @requires raphael-g
+ * @provides raphael-g-dot
+ * @do-not-minify
+ */
+/*!
+ * g.Raphael 0.5 - Charting library, based on Raphaël
+ *
+ * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ */
+(function(){var b=function(g,f,e,d){return"hsb("+[Math.min((1-g/f)*0.4,1),e||0.75,d||0.75]+")"};function a(e,N,M,d,j,B,A,t,I){var v=this;function U(g){+g[0]&&(g[0]=v.axis(N+s,M+s,d-2*s,E,p,I.axisxstep||Math.floor((d-2*s)/20),2,I.axisxlabels||null,I.axisxtype||"t",null,e));+g[1]&&(g[1]=v.axis(N+d-s,M+j-s,j-2*s,D,o,I.axisystep||Math.floor((j-2*s)/20),3,I.axisylabels||null,I.axisytype||"t",null,e));+g[2]&&(g[2]=v.axis(N+s,M+j-s+H,d-2*s,E,p,I.axisxstep||Math.floor((d-2*s)/20),0,I.axisxlabels||null,I.axisxtype||"t",null,e));+g[3]&&(g[3]=v.axis(N+s-H,M+j-s,j-2*s,D,o,I.axisystep||Math.floor((j-2*s)/20),1,I.axisylabels||null,I.axisytype||"t",null,e))}I=I||{};var z=v.snapEnds(Math.min.apply(Math,B),Math.max.apply(Math,B),B.length-1),E=z.from,p=z.to,s=I.gutter||10,L=v.snapEnds(Math.min.apply(Math,A),Math.max.apply(Math,A),A.length-1),D=L.from,o=L.to,C=Math.max(B.length,A.length,t.length),w=e[I.symbol]||"circle",J=e.set(),u=e.set(),G=I.max||100,r=Math.max.apply(Math,t),q=[],Q=Math.sqrt(r/Math.PI)*2/G;for(var S=0;S<C;S++){q[S]=Math.min(Math.sqrt(t[S]/Math.PI)*2/Q,G)}s=Math.max.apply(Math,q.concat(s));var F=e.set(),H=Math.max.apply(Math,q);if(I.axis){var n=(I.axis+"").split(/[,\s]+/);U.call(v,n);var T=[],V=[];for(var S=0,K=n.length;S<K;S++){var W=n[S].all?n[S].all.getBBox()[["height","width"][S%2]]:0;T[S]=W+s;V[S]=W}s=Math.max.apply(Math,T.concat(s));for(var S=0,K=n.length;S<K;S++){if(n[S].all){n[S].remove();n[S]=1}}U.call(v,n);for(var S=0,K=n.length;S<K;S++){if(n[S].all){F.push(n[S].all)}}J.axis=F}var P=(d-s*2)/((p-E)||1),O=(j-s*2)/((o-D)||1);for(var S=0,K=A.length;S<K;S++){var h=e.raphael.is(w,"array")?w[S]:w,m=N+s+(B[S]-E)*P,l=M+j-s-(A[S]-D)*O;h&&q[S]&&u.push(e[h](m,l,q[S]).attr({fill:I.heat?b(q[S],H):v.colors[0],"fill-opacity":I.opacity?q[S]/G:1,stroke:"none"}))}var f=e.set();for(var S=0,K=A.length;S<K;S++){var m=N+s+(B[S]-E)*P,l=M+j-s-(A[S]-D)*O;f.push(e.circle(m,l,H).attr(v.shim));I.href&&I.href[S]&&f[S].attr({href:I.href[S]});f[S].r=+q[S].toFixed(3);f[S].x=+m.toFixed(3);f[S].y=+l.toFixed(3);f[S].X=B[S];f[S].Y=A[S];f[S].value=t[S]||0;f[S].dot=u[S]}J.covers=f;J.series=u;J.push(u,F,f);J.hover=function(i,g){f.mouseover(i).mouseout(g);return this};J.click=function(g){f.click(g);return this};J.each=function(k){if(!e.raphael.is(k,"function")){return this}for(var g=f.length;g--;){k.call(f[g])}return this};J.href=function(x){var k;for(var g=f.length;g--;){k=f[g];if(k.X==x.x&&k.Y==x.y&&k.value==x.value){k.attr({href:x.href})}}};return J}var c=function(){};c.prototype=Raphael.g;a.prototype=new c;Raphael.fn.dotchart=function(e,k,g,d,j,i,f,h){return new a(this,e,k,g,d,j,i,f,h)}})();
\ No newline at end of file
Index: src/__celerity_resource_map__.php
===================================================================
--- src/__celerity_resource_map__.php
+++ src/__celerity_resource_map__.php
@@ -1668,6 +1668,19 @@
),
'disk' => '/rsrc/js/application/projects/behavior-project-create.js',
),
+ 'javelin-behavior-punchcard' =>
+ array(
+ 'uri' => '/res/2d086de7/rsrc/js/application/people/behavior_punchcard.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ 0 => 'javelin-behavior',
+ 1 => 'javelin-dom',
+ 2 => 'javelin-vector',
+ 3 => 'raphael-g-dot',
+ ),
+ 'disk' => '/rsrc/js/application/people/behavior_punchcard.js',
+ ),
'javelin-behavior-refresh-csrf' =>
array(
'uri' => '/res/88beba4c/rsrc/js/application/core/behavior-refresh-csrf.js',
@@ -2885,6 +2898,17 @@
),
'disk' => '/rsrc/js/raphael/g.raphael.js',
),
+ 'raphael-g-dot' =>
+ array(
+ 'uri' => '/res/12eb50ec/rsrc/js/raphael/g.raphael.dot.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ 0 => 'raphael-core',
+ 1 => 'raphael-g',
+ ),
+ 'disk' => '/rsrc/js/raphael/g.raphael.dot.js',
+ ),
'raphael-g-line' =>
array(
'uri' => '/res/a59c8556/rsrc/js/raphael/g.raphael.line.js',
Index: src/applications/people/controller/PhabricatorPeopleProfileController.php
===================================================================
--- src/applications/people/controller/PhabricatorPeopleProfileController.php
+++ src/applications/people/controller/PhabricatorPeopleProfileController.php
@@ -206,22 +206,107 @@
</table>
</div>
</div>';
+ $content .= $this->renderActivityAnalysis($user);
return $content;
}
- private function renderUserFeed(PhabricatorUser $user) {
- $viewer = $this->getRequest()->getUser();
+ private function renderActivityAnalysis(PhabricatorUser $user) {
+
+ $stories = $this->getStories($user, 500);
+ $counts = $this->countStories($stories);
+
+ $behavior = $this->analyzeActivity($counts);
+ switch ($behavior) {
+ case self::DIURNAL:
+ $behavior_str = "//This creature appears to be diurnal.//";
+ break;
+ case self::NOCTURNAL;
+ $behavior_str = "//This creature appears to be nocturnal.//";
+ break;
+ case self::CATHEMERAL;
+ $behavior_str = "//This creature appears to be " .
+ "[[http://en.wikipedia.org/wiki/Cathemeral | cathemeral]].//";
+ break;
+ }
+ $engine = PhabricatorMarkupEngine::newProfileMarkupEngine();
+ $behavior_str = $engine->markupText($behavior_str);
- $query = new PhabricatorFeedQuery();
- $query->setFilterPHIDs(
+ $id = celerity_generate_unique_node_id();
+ $punchcard_node = phutil_render_tag(
+ 'div',
array(
- $user->getPHID(),
- ));
- $query->setLimit(100);
- $query->setViewer($viewer);
- $stories = $query->execute();
+ 'id' => $id,
+ 'style' => 'border: 1px solid #6f6f6f; '.
+ 'margin: 1em 2em; '.
+ 'height: 280px; '.
+ 'width: 700px; ',
+ ),
+ '');
+
+ require_celerity_resource('javelin-behavior-punchcard');
+
+ Javelin::initBehavior('punchcard', array(
+ 'hardpoint' => $id,
+ 'counts' => $counts,
+ ));
+
+ return
+ '<div class="phabricator-profile-info-group">
+ <h1 class="phabricator-profile-info-header">Sightings In The Wild</h1>
+ <div class="phabricator-profile-info-pane">
+ <table class="phabricator-profile-info-table">
+ <tr>
+ <th>Behavior Pattern</th>
+ <td>'.$behavior_str.'</td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ '.$punchcard_node;
+ }
+
+ // Tabulate stories by day of week and hour of day.
+ private function countStories($stories) {
+
+ $counts = array_fill(1, 7, array_fill(0, 24, 0));
+ foreach ($stories as $story) {
+ $epoch = $story->getEpoch();
+ ++$counts[intval(date('N', $epoch))][intval(date('G', $epoch))];
+ }
+ return $counts;
+ }
+
+ const DIURNAL = 1;
+ const NOCTURNAL = 2;
+ const CATHEMERAL = 3;
+
+ private function analyzeActivity($counts) {
+ $day_count = 0;
+ $night_count = 0;
+ foreach ($counts as $day) {
+ foreach ($day as $hour => $count) {
+ if (($hour >= 8) && ($hour < 20)) {
+ $day_count += $count;
+ } else {
+ $night_count += $count;
+ }
+ }
+ }
+
+ if ($day_count / ($day_count + $night_count) > 0.66) {
+ return self::DIURNAL;
+ } else if ($day_count / ($day_count + $night_count) < 0.66) {
+ return self::NOCTURNAL;
+ } else {
+ return self::CATHEMERAL;
+ }
+ }
+
+ private function renderUserFeed(PhabricatorUser $user) {
+ $viewer = $this->getRequest()->getUser();
+ $stories = $this->getStories($user, 100);
$builder = new PhabricatorFeedBuilder($stories);
$builder->setUser($viewer);
$view = $builder->buildView();
@@ -234,4 +319,17 @@
</div>
</div>';
}
+
+ private function getStories(PhabricatorUser $user, $limit) {
+ $viewer = $this->getRequest()->getUser();
+
+ $query = new PhabricatorFeedQuery();
+ $query->setFilterPHIDs(
+ array(
+ $user->getPHID(),
+ ));
+ $query->setLimit($limit);
+ $query->setViewer($viewer);
+ return $query->execute();
+ }
}
Index: webroot/rsrc/js/application/people/behavior_punchcard.js
===================================================================
--- /dev/null
+++ webroot/rsrc/js/application/people/behavior_punchcard.js
@@ -0,0 +1,58 @@
+/**
+ * @provides javelin-behavior-punchcard
+ * @requires javelin-behavior
+ * javelin-dom
+ * javelin-vector
+ * raphael-g-dot
+ */
+
+JX.behavior('punchcard', function(config) {
+
+ var h = JX.$(config.hardpoint);
+ var p = JX.Vector.getPos(h);
+ var d = JX.Vector.getDim(h);
+ var mx = 10;
+ var my = 10;
+
+ var r = Raphael(p.x, p.y, d.x, d.y);
+ // TODO(mshang): Internationalize this.
+ var axisy = ["Sun", "Sat", "Fri", "Thu", "Wed", "Tue", "Mon"];
+ var axisx = ["12a", "1a", "2a", "3a", "4a", "5a", "6a", "7a",
+ "8a", "9a", "10a", "11a", "12p", "1p", "2p", "3p",
+ "4p", "5p", "6p", "7p", "8p", "9p", "10p", "11p"];
+
+ var counts = [];
+ var days = [];
+ var hours = [];
+
+ for (var ii in config.counts) {
+ for (var jj = 0; jj < config.counts[ii].length; jj++) {
+ counts = counts.concat(config.counts[ii][jj]);
+ days = days.concat(-parseInt(ii));
+ hours = hours.concat(jj);
+ }
+ }
+
+ r.dotchart(
+ mx,
+ my,
+ d.x - (2 * mx),
+ d.y - (2 * my),
+ hours,
+ days,
+ counts,
+ {
+ symbol: "o",
+ max: 10,
+ heat: true,
+ axis: "0 0 1 1",
+ axisxstep: 23,
+ axisystep: 6,
+ axisxlabels: axisx,
+ axisxtype: " ",
+ axisylabels: axisy,
+ axisytype: " ",
+ }
+ );
+});
+
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Mar 19, 5:35 PM (3 w, 1 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7529846
Default Alt Text
D3357.diff (9 KB)
Attached To
Mode
D3357: GitHub-style punchcards
Attached
Detach File
Event Timeline
Log In to Comment