Page MenuHomePhabricator

D3357.diff
No OneTemporary

D3357.diff

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

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)

Event Timeline