1
0
Fork 0
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:
epriestley 2019-05-08 12:43:55 -07:00
parent f190c42bcd
commit f91bef64f1
6 changed files with 131 additions and 18 deletions

View file

@ -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',

View file

@ -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();

View 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;
}
}

View 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;
}
}

View file

@ -15,7 +15,7 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
}
$this->log(pht('Zzz...'));
$this->sleep(60 * 5);
$this->sleep(15);
}
}

View file

@ -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;
}