@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.

Stack chart functions in a more physical way

Summary:
Ref T13279. See that task for some discussion.

The accumulations of some of the datasets may be negative (e.g., if more tasks are moved out of a project than into it) which can lead to negative area in the stacked chart.

Introduce `min(...)` and `max(...)` to separate a function into points above or below some line, then mangle the areas to pick the negative and positive regions apart so they at least have a plausible physical interpretation and none of the areas are negative.

This is presumably not a final version, I'm just trying to produce a chart that isn't a sequence of overlapping regions with negative areas that is "technically" correct but not really possible to interpret.

Test Plan: {F6439195}

Reviewers: amckinley

Reviewed By: amckinley

Subscribers: yelirekim

Maniphest Tasks: T13279

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

+131 -18
+4
src/__phutil_library_map__.php
··· 3622 3622 'PhabricatorMarkupInterface' => 'infrastructure/markup/PhabricatorMarkupInterface.php', 3623 3623 'PhabricatorMarkupOneOff' => 'infrastructure/markup/PhabricatorMarkupOneOff.php', 3624 3624 'PhabricatorMarkupPreviewController' => 'infrastructure/markup/PhabricatorMarkupPreviewController.php', 3625 + 'PhabricatorMaxChartFunction' => 'applications/fact/chart/PhabricatorMaxChartFunction.php', 3625 3626 'PhabricatorMemeEngine' => 'applications/macro/engine/PhabricatorMemeEngine.php', 3626 3627 'PhabricatorMemeRemarkupRule' => 'applications/macro/markup/PhabricatorMemeRemarkupRule.php', 3627 3628 'PhabricatorMentionRemarkupRule' => 'applications/people/markup/PhabricatorMentionRemarkupRule.php', ··· 3676 3677 'PhabricatorMetronome' => 'infrastructure/util/PhabricatorMetronome.php', 3677 3678 'PhabricatorMetronomeTestCase' => 'infrastructure/util/__tests__/PhabricatorMetronomeTestCase.php', 3678 3679 'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php', 3680 + 'PhabricatorMinChartFunction' => 'applications/fact/chart/PhabricatorMinChartFunction.php', 3679 3681 'PhabricatorModularTransaction' => 'applications/transactions/storage/PhabricatorModularTransaction.php', 3680 3682 'PhabricatorModularTransactionType' => 'applications/transactions/storage/PhabricatorModularTransactionType.php', 3681 3683 'PhabricatorMonogramDatasourceEngineExtension' => 'applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php', ··· 9748 9750 'PhabricatorMarkupInterface', 9749 9751 ), 9750 9752 'PhabricatorMarkupPreviewController' => 'PhabricatorController', 9753 + 'PhabricatorMaxChartFunction' => 'PhabricatorChartFunction', 9751 9754 'PhabricatorMemeEngine' => 'Phobject', 9752 9755 'PhabricatorMemeRemarkupRule' => 'PhutilRemarkupRule', 9753 9756 'PhabricatorMentionRemarkupRule' => 'PhutilRemarkupRule', ··· 9814 9817 'PhabricatorMetronome' => 'Phobject', 9815 9818 'PhabricatorMetronomeTestCase' => 'PhabricatorTestCase', 9816 9819 'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock', 9820 + 'PhabricatorMinChartFunction' => 'PhabricatorChartFunction', 9817 9821 'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction', 9818 9822 'PhabricatorModularTransactionType' => 'Phobject', 9819 9823 'PhabricatorMonogramDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension',
+6 -2
src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php
··· 9 9 PhabricatorChartDataQuery $data_query) { 10 10 $functions = $this->getFunctions(); 11 11 12 + $reversed_functions = array_reverse($functions, true); 13 + 12 14 $function_points = array(); 13 - foreach ($functions as $function_idx => $function) { 15 + foreach ($reversed_functions as $function_idx => $function) { 14 16 $function_points[$function_idx] = array(); 15 17 16 18 $datapoints = $function->newDatapoints($data_query); ··· 36 38 } 37 39 ksort($must_define); 38 40 39 - foreach ($functions as $function_idx => $function) { 41 + foreach ($reversed_functions as $function_idx => $function) { 40 42 $missing = array(); 41 43 foreach ($must_define as $x) { 42 44 if (!isset($function_points[$function_idx][$x])) { ··· 135 137 136 138 $series[] = $bounds; 137 139 } 140 + 141 + $series = array_reverse($series); 138 142 139 143 $events = array(); 140 144 foreach ($raw_points as $function_idx => $points) {
+40
src/applications/fact/chart/PhabricatorMaxChartFunction.php
··· 1 + <?php 2 + 3 + final class PhabricatorMaxChartFunction 4 + extends PhabricatorChartFunction { 5 + 6 + const FUNCTIONKEY = 'max'; 7 + 8 + protected function newArguments() { 9 + return array( 10 + $this->newArgument() 11 + ->setName('x') 12 + ->setType('function'), 13 + $this->newArgument() 14 + ->setName('max') 15 + ->setType('number'), 16 + ); 17 + } 18 + 19 + public function getDomain() { 20 + return $this->getArgument('x')->getDomain(); 21 + } 22 + 23 + public function newInputValues(PhabricatorChartDataQuery $query) { 24 + return $this->getArgument('x')->newInputValues($query); 25 + } 26 + 27 + public function evaluateFunction(array $xv) { 28 + $yv = $this->getArgument('x')->evaluateFunction($xv); 29 + $max = $this->getArgument('max'); 30 + 31 + foreach ($yv as $k => $y) { 32 + if ($y > $max) { 33 + $yv[$k] = null; 34 + } 35 + } 36 + 37 + return $yv; 38 + } 39 + 40 + }
+40
src/applications/fact/chart/PhabricatorMinChartFunction.php
··· 1 + <?php 2 + 3 + final class PhabricatorMinChartFunction 4 + extends PhabricatorChartFunction { 5 + 6 + const FUNCTIONKEY = 'min'; 7 + 8 + protected function newArguments() { 9 + return array( 10 + $this->newArgument() 11 + ->setName('x') 12 + ->setType('function'), 13 + $this->newArgument() 14 + ->setName('min') 15 + ->setType('number'), 16 + ); 17 + } 18 + 19 + public function getDomain() { 20 + return $this->getArgument('x')->getDomain(); 21 + } 22 + 23 + public function newInputValues(PhabricatorChartDataQuery $query) { 24 + return $this->getArgument('x')->newInputValues($query); 25 + } 26 + 27 + public function evaluateFunction(array $xv) { 28 + $yv = $this->getArgument('x')->evaluateFunction($xv); 29 + $min = $this->getArgument('min'); 30 + 31 + foreach ($yv as $k => $y) { 32 + if ($y < $min) { 33 + $yv[$k] = null; 34 + } 35 + } 36 + 37 + return $yv; 38 + } 39 + 40 + }
+1 -1
src/applications/fact/daemon/PhabricatorFactDaemon.php
··· 15 15 } 16 16 17 17 $this->log(pht('Zzz...')); 18 - $this->sleep(60 * 5); 18 + $this->sleep(15); 19 19 } 20 20 } 21 21
+40 -15
src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php
··· 32 32 if ($project_phids) { 33 33 foreach ($project_phids as $project_phid) { 34 34 $function = $this->newFunction( 35 - 'accumulate', 36 - array('fact', 'tasks.open-count.create.project', $project_phid)); 35 + 'min', 36 + array( 37 + 'accumulate', 38 + array('fact', 'tasks.open-count.assign.project', $project_phid), 39 + ), 40 + 0); 37 41 38 42 $function->getFunctionLabel() 39 - ->setName(pht('Tasks Created')) 40 - ->setColor('rgba(0, 0, 200, 1)') 41 - ->setFillColor('rgba(0, 0, 200, 0.15)'); 43 + ->setName(pht('Tasks Moved Into Project')) 44 + ->setColor('rgba(0, 200, 200, 1)') 45 + ->setFillColor('rgba(0, 200, 200, 0.15)'); 42 46 43 47 $functions[] = $function; 44 48 45 - 46 49 $function = $this->newFunction( 47 - 'accumulate', 48 - array('fact', 'tasks.open-count.status.project', $project_phid)); 50 + 'min', 51 + array( 52 + 'accumulate', 53 + array('fact', 'tasks.open-count.status.project', $project_phid), 54 + ), 55 + 0); 49 56 50 57 $function->getFunctionLabel() 51 - ->setName(pht('Tasks Closed / Reopened')) 58 + ->setName(pht('Tasks Reopened')) 52 59 ->setColor('rgba(200, 0, 200, 1)') 53 60 ->setFillColor('rgba(200, 0, 200, 0.15)'); 54 61 55 62 $functions[] = $function; 56 63 57 - 58 64 $function = $this->newFunction( 59 - 'accumulate', 60 - array('fact', 'tasks.open-count.assign.project', $project_phid)); 65 + 'sum', 66 + array( 67 + 'accumulate', 68 + array('fact', 'tasks.open-count.create.project', $project_phid), 69 + ), 70 + array( 71 + 'max', 72 + array( 73 + 'accumulate', 74 + array('fact', 'tasks.open-count.status.project', $project_phid), 75 + ), 76 + 0, 77 + ), 78 + array( 79 + 'max', 80 + array( 81 + 'accumulate', 82 + array('fact', 'tasks.open-count.assign.project', $project_phid), 83 + ), 84 + 0, 85 + )); 61 86 62 87 $function->getFunctionLabel() 63 - ->setName(pht('Tasks Rescoped')) 64 - ->setColor('rgba(0, 200, 200, 1)') 65 - ->setFillColor('rgba(0, 200, 200, 0.15)'); 88 + ->setName(pht('Tasks Created')) 89 + ->setColor('rgba(0, 0, 200, 1)') 90 + ->setFillColor('rgba(0, 0, 200, 0.15)'); 66 91 67 92 $functions[] = $function; 68 93 }