mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-11 07:11:04 +01:00
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
This commit is contained in:
parent
f190c42bcd
commit
f91bef64f1
6 changed files with 131 additions and 18 deletions
|
@ -3622,6 +3622,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMarkupInterface' => 'infrastructure/markup/PhabricatorMarkupInterface.php',
|
||||
'PhabricatorMarkupOneOff' => 'infrastructure/markup/PhabricatorMarkupOneOff.php',
|
||||
'PhabricatorMarkupPreviewController' => 'infrastructure/markup/PhabricatorMarkupPreviewController.php',
|
||||
'PhabricatorMaxChartFunction' => 'applications/fact/chart/PhabricatorMaxChartFunction.php',
|
||||
'PhabricatorMemeEngine' => 'applications/macro/engine/PhabricatorMemeEngine.php',
|
||||
'PhabricatorMemeRemarkupRule' => 'applications/macro/markup/PhabricatorMemeRemarkupRule.php',
|
||||
'PhabricatorMentionRemarkupRule' => 'applications/people/markup/PhabricatorMentionRemarkupRule.php',
|
||||
|
@ -3676,6 +3677,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMetronome' => 'infrastructure/util/PhabricatorMetronome.php',
|
||||
'PhabricatorMetronomeTestCase' => 'infrastructure/util/__tests__/PhabricatorMetronomeTestCase.php',
|
||||
'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php',
|
||||
'PhabricatorMinChartFunction' => 'applications/fact/chart/PhabricatorMinChartFunction.php',
|
||||
'PhabricatorModularTransaction' => 'applications/transactions/storage/PhabricatorModularTransaction.php',
|
||||
'PhabricatorModularTransactionType' => 'applications/transactions/storage/PhabricatorModularTransactionType.php',
|
||||
'PhabricatorMonogramDatasourceEngineExtension' => 'applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php',
|
||||
|
@ -9748,6 +9750,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMarkupInterface',
|
||||
),
|
||||
'PhabricatorMarkupPreviewController' => 'PhabricatorController',
|
||||
'PhabricatorMaxChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorMemeEngine' => 'Phobject',
|
||||
'PhabricatorMemeRemarkupRule' => 'PhutilRemarkupRule',
|
||||
'PhabricatorMentionRemarkupRule' => 'PhutilRemarkupRule',
|
||||
|
@ -9814,6 +9817,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMetronome' => 'Phobject',
|
||||
'PhabricatorMetronomeTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock',
|
||||
'PhabricatorMinChartFunction' => 'PhabricatorChartFunction',
|
||||
'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'PhabricatorModularTransactionType' => 'Phobject',
|
||||
'PhabricatorMonogramDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension',
|
||||
|
|
|
@ -9,8 +9,10 @@ final class PhabricatorChartStackedAreaDataset
|
|||
PhabricatorChartDataQuery $data_query) {
|
||||
$functions = $this->getFunctions();
|
||||
|
||||
$reversed_functions = array_reverse($functions, true);
|
||||
|
||||
$function_points = array();
|
||||
foreach ($functions as $function_idx => $function) {
|
||||
foreach ($reversed_functions as $function_idx => $function) {
|
||||
$function_points[$function_idx] = array();
|
||||
|
||||
$datapoints = $function->newDatapoints($data_query);
|
||||
|
@ -36,7 +38,7 @@ final class PhabricatorChartStackedAreaDataset
|
|||
}
|
||||
ksort($must_define);
|
||||
|
||||
foreach ($functions as $function_idx => $function) {
|
||||
foreach ($reversed_functions as $function_idx => $function) {
|
||||
$missing = array();
|
||||
foreach ($must_define as $x) {
|
||||
if (!isset($function_points[$function_idx][$x])) {
|
||||
|
@ -136,6 +138,8 @@ final class PhabricatorChartStackedAreaDataset
|
|||
$series[] = $bounds;
|
||||
}
|
||||
|
||||
$series = array_reverse($series);
|
||||
|
||||
$events = array();
|
||||
foreach ($raw_points as $function_idx => $points) {
|
||||
$event_list = array();
|
||||
|
|
40
src/applications/fact/chart/PhabricatorMaxChartFunction.php
Normal file
40
src/applications/fact/chart/PhabricatorMaxChartFunction.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMaxChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'max';
|
||||
|
||||
protected function newArguments() {
|
||||
return array(
|
||||
$this->newArgument()
|
||||
->setName('x')
|
||||
->setType('function'),
|
||||
$this->newArgument()
|
||||
->setName('max')
|
||||
->setType('number'),
|
||||
);
|
||||
}
|
||||
|
||||
public function getDomain() {
|
||||
return $this->getArgument('x')->getDomain();
|
||||
}
|
||||
|
||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
||||
return $this->getArgument('x')->newInputValues($query);
|
||||
}
|
||||
|
||||
public function evaluateFunction(array $xv) {
|
||||
$yv = $this->getArgument('x')->evaluateFunction($xv);
|
||||
$max = $this->getArgument('max');
|
||||
|
||||
foreach ($yv as $k => $y) {
|
||||
if ($y > $max) {
|
||||
$yv[$k] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $yv;
|
||||
}
|
||||
|
||||
}
|
40
src/applications/fact/chart/PhabricatorMinChartFunction.php
Normal file
40
src/applications/fact/chart/PhabricatorMinChartFunction.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorMinChartFunction
|
||||
extends PhabricatorChartFunction {
|
||||
|
||||
const FUNCTIONKEY = 'min';
|
||||
|
||||
protected function newArguments() {
|
||||
return array(
|
||||
$this->newArgument()
|
||||
->setName('x')
|
||||
->setType('function'),
|
||||
$this->newArgument()
|
||||
->setName('min')
|
||||
->setType('number'),
|
||||
);
|
||||
}
|
||||
|
||||
public function getDomain() {
|
||||
return $this->getArgument('x')->getDomain();
|
||||
}
|
||||
|
||||
public function newInputValues(PhabricatorChartDataQuery $query) {
|
||||
return $this->getArgument('x')->newInputValues($query);
|
||||
}
|
||||
|
||||
public function evaluateFunction(array $xv) {
|
||||
$yv = $this->getArgument('x')->evaluateFunction($xv);
|
||||
$min = $this->getArgument('min');
|
||||
|
||||
foreach ($yv as $k => $y) {
|
||||
if ($y < $min) {
|
||||
$yv[$k] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $yv;
|
||||
}
|
||||
|
||||
}
|
|
@ -15,7 +15,7 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
|
|||
}
|
||||
|
||||
$this->log(pht('Zzz...'));
|
||||
$this->sleep(60 * 5);
|
||||
$this->sleep(15);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,37 +32,62 @@ final class PhabricatorProjectBurndownChartEngine
|
|||
if ($project_phids) {
|
||||
foreach ($project_phids as $project_phid) {
|
||||
$function = $this->newFunction(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.create.project', $project_phid));
|
||||
'min',
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.assign.project', $project_phid),
|
||||
),
|
||||
0);
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setName(pht('Tasks Created'))
|
||||
->setColor('rgba(0, 0, 200, 1)')
|
||||
->setFillColor('rgba(0, 0, 200, 0.15)');
|
||||
->setName(pht('Tasks Moved Into Project'))
|
||||
->setColor('rgba(0, 200, 200, 1)')
|
||||
->setFillColor('rgba(0, 200, 200, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
|
||||
|
||||
$function = $this->newFunction(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.status.project', $project_phid));
|
||||
'min',
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.status.project', $project_phid),
|
||||
),
|
||||
0);
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setName(pht('Tasks Closed / Reopened'))
|
||||
->setName(pht('Tasks Reopened'))
|
||||
->setColor('rgba(200, 0, 200, 1)')
|
||||
->setFillColor('rgba(200, 0, 200, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
|
||||
|
||||
$function = $this->newFunction(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.assign.project', $project_phid));
|
||||
'sum',
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.create.project', $project_phid),
|
||||
),
|
||||
array(
|
||||
'max',
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.status.project', $project_phid),
|
||||
),
|
||||
0,
|
||||
),
|
||||
array(
|
||||
'max',
|
||||
array(
|
||||
'accumulate',
|
||||
array('fact', 'tasks.open-count.assign.project', $project_phid),
|
||||
),
|
||||
0,
|
||||
));
|
||||
|
||||
$function->getFunctionLabel()
|
||||
->setName(pht('Tasks Rescoped'))
|
||||
->setColor('rgba(0, 200, 200, 1)')
|
||||
->setFillColor('rgba(0, 200, 200, 0.15)');
|
||||
->setName(pht('Tasks Created'))
|
||||
->setColor('rgba(0, 0, 200, 1)')
|
||||
->setFillColor('rgba(0, 0, 200, 0.15)');
|
||||
|
||||
$functions[] = $function;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue