@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make various UX improvements to charts so they're closer to making visual sense

Summary: Ref T13279. Fix some tabular stuff, draw areas better, make the "compose()" API more consistent, unfatal the demo chart, unfatal the project burndown, make the project chart do something roughly physical.

Test Plan: Looked at charts, saw fewer obvious horrors.

Subscribers: yelirekim

Maniphest Tasks: T13279

Differential Revision: https://secure.phabricator.com/D20817

+122 -101
+8 -8
resources/celerity/map.php
··· 390 390 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123', 391 391 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a', 392 392 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b', 393 - 'rsrc/js/application/fact/Chart.js' => 'ddb9dd1f', 393 + 'rsrc/js/application/fact/Chart.js' => '52e3ff03', 394 394 'rsrc/js/application/fact/ChartCurtainView.js' => '86954222', 395 395 'rsrc/js/application/fact/ChartFunctionLabel.js' => '81de1dab', 396 396 'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22', ··· 699 699 'javelin-behavior-user-menu' => '60cd9241', 700 700 'javelin-behavior-view-placeholder' => 'a9942052', 701 701 'javelin-behavior-workflow' => '9623adc1', 702 - 'javelin-chart' => 'ddb9dd1f', 702 + 'javelin-chart' => '52e3ff03', 703 703 'javelin-chart-curtain-view' => '86954222', 704 704 'javelin-chart-function-label' => '81de1dab', 705 705 'javelin-color' => '78f811c9', ··· 1369 1369 'javelin-dom', 1370 1370 'javelin-fx', 1371 1371 ), 1372 + '52e3ff03' => array( 1373 + 'phui-chart-css', 1374 + 'd3', 1375 + 'javelin-chart-curtain-view', 1376 + 'javelin-chart-function-label', 1377 + ), 1372 1378 '541f81c3' => array( 1373 1379 'javelin-install', 1374 1380 ), ··· 2065 2071 'javelin-behavior', 2066 2072 'javelin-uri', 2067 2073 'phabricator-notification', 2068 - ), 2069 - 'ddb9dd1f' => array( 2070 - 'phui-chart-css', 2071 - 'd3', 2072 - 'javelin-chart-curtain-view', 2073 - 'javelin-chart-function-label', 2074 2074 ), 2075 2075 'dfa1d313' => array( 2076 2076 'javelin-behavior',
+2
src/__phutil_library_map__.php
··· 3104 3104 'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php', 3105 3105 'PhabricatorDefaultSyntaxStyle' => 'infrastructure/syntax/PhabricatorDefaultSyntaxStyle.php', 3106 3106 'PhabricatorDefaultUnlockEngine' => 'applications/system/engine/PhabricatorDefaultUnlockEngine.php', 3107 + 'PhabricatorDemoChartEngine' => 'applications/fact/engine/PhabricatorDemoChartEngine.php', 3107 3108 'PhabricatorDestructibleCodex' => 'applications/system/codex/PhabricatorDestructibleCodex.php', 3108 3109 'PhabricatorDestructibleCodexInterface' => 'applications/system/interface/PhabricatorDestructibleCodexInterface.php', 3109 3110 'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php', ··· 9434 9435 'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 9435 9436 'PhabricatorDefaultSyntaxStyle' => 'PhabricatorSyntaxStyle', 9436 9437 'PhabricatorDefaultUnlockEngine' => 'PhabricatorUnlockEngine', 9438 + 'PhabricatorDemoChartEngine' => 'PhabricatorChartEngine', 9437 9439 'PhabricatorDestructibleCodex' => 'Phobject', 9438 9440 'PhabricatorDestructionEngine' => 'Phobject', 9439 9441 'PhabricatorDestructionEngineExtension' => 'Phobject',
+2 -43
src/applications/fact/controller/PhabricatorFactChartController.php
··· 62 62 private function newDemoChart() { 63 63 $viewer = $this->getViewer(); 64 64 65 - $argvs = array(); 66 - 67 - $argvs[] = array('fact', 'tasks.count.create'); 68 - 69 - $argvs[] = array('constant', 360); 70 - 71 - $argvs[] = array('fact', 'tasks.open-count.create'); 72 - 73 - $argvs[] = array( 74 - 'sum', 75 - array( 76 - 'accumulate', 77 - array('fact', 'tasks.count.create'), 78 - ), 79 - array( 80 - 'accumulate', 81 - array('fact', 'tasks.open-count.create'), 82 - ), 83 - ); 84 - 85 - $argvs[] = array( 86 - 'compose', 87 - array('scale', 0.001), 88 - array('cos'), 89 - array('scale', 100), 90 - array('shift', 800), 91 - ); 92 - 93 - $datasets = array(); 94 - foreach ($argvs as $argv) { 95 - $datasets[] = PhabricatorChartDataset::newFromDictionary( 96 - array( 97 - 'function' => $argv, 98 - )); 99 - } 100 - 101 - $chart = id(new PhabricatorFactChart()) 102 - ->setDatasets($datasets); 103 - 104 - $engine = id(new PhabricatorChartRenderingEngine()) 65 + $chart = id(new PhabricatorDemoChartEngine()) 105 66 ->setViewer($viewer) 106 - ->setChart($chart); 107 - 108 - $chart = $engine->getStoredChart(); 67 + ->newStoredChart(); 109 68 110 69 return id(new AphrontRedirectResponse())->setURI($chart->getURI()); 111 70 }
+7 -3
src/applications/fact/engine/PhabricatorChartEngine.php
··· 63 63 64 64 abstract protected function newChart(PhabricatorFactChart $chart, array $map); 65 65 66 - final public function buildChartPanel() { 66 + final public function newStoredChart() { 67 67 $viewer = $this->getViewer(); 68 68 69 69 $parameters = $this->getEngineParameters(); ··· 76 76 ->setViewer($viewer) 77 77 ->setChart($chart); 78 78 79 - $chart = $rendering_engine->getStoredChart(); 79 + return $rendering_engine->getStoredChart(); 80 + } 81 + 82 + final public function buildChartPanel() { 83 + $chart = $this->newStoredChart(); 80 84 81 85 $panel_type = id(new PhabricatorDashboardChartPanelType()) 82 86 ->getPanelTypeKey(); ··· 91 95 final protected function newFunction($name /* , ... */) { 92 96 $argv = func_get_args(); 93 97 return id(new PhabricatorComposeChartFunction()) 94 - ->setArguments(array($argv)); 98 + ->setArguments($argv); 95 99 } 96 100 97 101 }
+3
src/applications/fact/engine/PhabricatorChartRenderingEngine.php
··· 178 178 $rows[] = array( 179 179 $xv, 180 180 $yv, 181 + null, 182 + null, 183 + null, 181 184 ); 182 185 } else { 183 186 foreach ($point_refs as $ref => $ref_data) {
+44
src/applications/fact/engine/PhabricatorDemoChartEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorDemoChartEngine 4 + extends PhabricatorChartEngine { 5 + 6 + const CHARTENGINEKEY = 'facts.demo'; 7 + 8 + protected function newChart(PhabricatorFactChart $chart, array $map) { 9 + $viewer = $this->getViewer(); 10 + 11 + $functions = array(); 12 + 13 + $function = $this->newFunction( 14 + array('scale', 0.0001), 15 + array('cos'), 16 + array('scale', 128), 17 + array('shift', 256)); 18 + 19 + $function->getFunctionLabel() 20 + ->setName(pht('cos(x)')) 21 + ->setColor('rgba(0, 200, 0, 1)') 22 + ->setFillColor('rgba(0, 200, 0, 0.15)'); 23 + 24 + $functions[] = $function; 25 + 26 + $function = $this->newFunction( 27 + array('constant', 345)); 28 + 29 + $function->getFunctionLabel() 30 + ->setName(pht('constant(345)')) 31 + ->setColor('rgba(0, 0, 200, 1)') 32 + ->setFillColor('rgba(0, 0, 200, 0.15)'); 33 + 34 + $functions[] = $function; 35 + 36 + $datasets = array(); 37 + 38 + $datasets[] = id(new PhabricatorChartStackedAreaDataset()) 39 + ->setFunctions($functions); 40 + 41 + $chart->attachDatasets($datasets); 42 + } 43 + 44 + }
+42 -46
src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php
··· 34 34 $function = $this->newFunction( 35 35 array( 36 36 'accumulate', 37 - array('fact', 'tasks.open-count.assign.project', $project_phid), 38 - ), 39 - array( 40 - 'min', 41 - 0, 37 + array( 38 + 'compose', 39 + array('fact', 'tasks.open-count.assign.project', $project_phid), 40 + array('min', 0), 41 + ), 42 42 )); 43 43 44 44 $function->getFunctionLabel() 45 45 ->setName(pht('Tasks Moved Into Project')) 46 - ->setColor('rgba(0, 200, 200, 1)') 47 - ->setFillColor('rgba(0, 200, 200, 0.15)'); 46 + ->setColor('rgba(128, 128, 200, 1)') 47 + ->setFillColor('rgba(128, 128, 200, 0.15)'); 48 48 49 49 $functions[] = $function; 50 50 51 51 $function = $this->newFunction( 52 52 array( 53 53 'accumulate', 54 - array('fact', 'tasks.open-count.status.project', $project_phid), 55 - ), 56 - array( 57 - 'min', 58 - 0, 54 + array('fact', 'tasks.open-count.create.project', $project_phid), 59 55 )); 60 56 61 57 $function->getFunctionLabel() 62 - ->setName(pht('Tasks Reopened')) 63 - ->setColor('rgba(200, 0, 200, 1)') 64 - ->setFillColor('rgba(200, 0, 200, 0.15)'); 58 + ->setName(pht('Tasks Created')) 59 + ->setColor('rgba(0, 0, 200, 1)') 60 + ->setFillColor('rgba(0, 0, 200, 0.15)'); 65 61 66 62 $functions[] = $function; 67 63 68 64 $function = $this->newFunction( 69 - 'sum', 70 65 array( 71 66 'accumulate', 72 - array('fact', 'tasks.open-count.create.project', $project_phid), 73 - ), 74 - array( 75 67 array( 76 - 'accumulate', 77 - array('fact', 'tasks.open-count.status.project', $project_phid), 78 - ), 79 - array( 80 - 'max', 81 - 0, 82 - ), 83 - ), 84 - array( 85 - array( 86 - 'accumulate', 68 + 'compose', 87 69 array('fact', 'tasks.open-count.assign.project', $project_phid), 88 - ), 89 - array( 90 - 'max', 91 - 0, 70 + array('max', 0), 92 71 ), 93 72 )); 94 73 95 74 $function->getFunctionLabel() 96 - ->setName(pht('Tasks Created')) 97 - ->setColor('rgba(0, 0, 200, 1)') 98 - ->setFillColor('rgba(0, 0, 200, 0.15)'); 75 + ->setName(pht('Tasks Moved Out of Project')) 76 + ->setColor('rgba(128, 200, 128, 1)') 77 + ->setFillColor('rgba(128, 200, 128, 0.15)'); 78 + 79 + $functions[] = $function; 80 + 81 + $function = $this->newFunction( 82 + array( 83 + 'accumulate', 84 + array('fact', 'tasks.open-count.status.project', $project_phid), 85 + )); 86 + 87 + $function->getFunctionLabel() 88 + ->setName(pht('Tasks Closed')) 89 + ->setColor('rgba(0, 200, 0, 1)') 90 + ->setFillColor('rgba(0, 200, 0, 0.15)'); 99 91 100 92 $functions[] = $function; 101 93 } 102 94 } else { 103 95 $function = $this->newFunction( 104 - 'accumulate', 105 - array('fact', 'tasks.open-count.create')); 96 + array( 97 + 'accumulate', 98 + array('fact', 'tasks.open-count.create'), 99 + )); 106 100 107 101 $function->getFunctionLabel() 108 102 ->setName(pht('Tasks Created')) 109 - ->setColor('rgba(0, 200, 200, 1)') 110 - ->setFillColor('rgba(0, 200, 200, 0.15)'); 103 + ->setColor('rgba(0, 0, 200, 1)') 104 + ->setFillColor('rgba(0, 0, 200, 0.15)'); 111 105 112 106 $functions[] = $function; 113 107 114 108 $function = $this->newFunction( 115 - 'accumulate', 116 - array('fact', 'tasks.open-count.status')); 109 + array( 110 + 'accumulate', 111 + array('fact', 'tasks.open-count.status'), 112 + )); 117 113 118 114 $function->getFunctionLabel() 119 - ->setName(pht('Tasks Closed / Reopened')) 120 - ->setColor('rgba(200, 0, 200, 1)') 121 - ->setFillColor('rgba(200, 0, 200, 0.15)'); 115 + ->setName(pht('Tasks Closed')) 116 + ->setColor('rgba(0, 200, 0, 1)') 117 + ->setFillColor('rgba(0, 200, 0, 0.15)'); 122 118 123 119 $functions[] = $function; 124 120 }
+14 -1
webroot/rsrc/js/application/fact/Chart.js
··· 139 139 140 140 var area = d3.area() 141 141 .x(function(d) { return x(to_date(d.x)); }) 142 - .y0(function(d) { return y(d.y0); }) 142 + .y0(function(d) { 143 + // When the area is positive, draw it above the X axis. When the area 144 + // is negative, draw it below the X axis. We currently avoid having 145 + // functions which cross the X axis by clever construction. 146 + if (d.y0 >= 0 && d.y1 >= 0) { 147 + return y(d.y0); 148 + } 149 + 150 + if (d.y0 <= 0 && d.y1 <= 0) { 151 + return y(d.y0); 152 + } 153 + 154 + return y(0); 155 + }) 143 156 .y1(function(d) { return y(d.y1); }); 144 157 145 158 var line = d3.line()