Page MenuHomePhabricator

D20818.id49637.diff
No OneTemporary

D20818.id49637.diff

diff --git a/src/applications/fact/chart/PhabricatorChartDataset.php b/src/applications/fact/chart/PhabricatorChartDataset.php
--- a/src/applications/fact/chart/PhabricatorChartDataset.php
+++ b/src/applications/fact/chart/PhabricatorChartDataset.php
@@ -59,13 +59,6 @@
return $dataset;
}
- final public function toDictionary() {
- return array(
- 'type' => $this->getDatasetTypeKey(),
- 'functions' => mpull($this->getFunctions(), 'toDictionary'),
- );
- }
-
final public function getChartDisplayData(
PhabricatorChartDataQuery $data_query) {
return $this->newChartDisplayData($data_query);
diff --git a/src/applications/fact/chart/PhabricatorChartFunction.php b/src/applications/fact/chart/PhabricatorChartFunction.php
--- a/src/applications/fact/chart/PhabricatorChartFunction.php
+++ b/src/applications/fact/chart/PhabricatorChartFunction.php
@@ -60,6 +60,10 @@
return $this->functionLabel;
}
+ final public function getKey() {
+ return $this->getFunctionLabel()->getKey();
+ }
+
final public static function newFromDictionary(array $map) {
PhutilTypeSpec::checkMap(
$map,
@@ -86,13 +90,6 @@
return $function;
}
- public function toDictionary() {
- return array(
- 'function' => $this->getFunctionKey(),
- 'arguments' => $this->getArgumentParser()->getRawArguments(),
- );
- }
-
public function getSubfunctions() {
$result = array();
$result[] = $this;
diff --git a/src/applications/fact/chart/PhabricatorChartFunctionLabel.php b/src/applications/fact/chart/PhabricatorChartFunctionLabel.php
--- a/src/applications/fact/chart/PhabricatorChartFunctionLabel.php
+++ b/src/applications/fact/chart/PhabricatorChartFunctionLabel.php
@@ -3,11 +3,21 @@
final class PhabricatorChartFunctionLabel
extends Phobject {
+ private $key;
private $name;
private $color;
private $icon;
private $fillColor;
+ public function setKey($key) {
+ $this->key = $key;
+ return $this;
+ }
+
+ public function getKey() {
+ return $this->key;
+ }
+
public function setName($name) {
$this->name = $name;
return $this;
@@ -46,6 +56,7 @@
public function toWireFormat() {
return array(
+ 'key' => $this->getKey(),
'name' => $this->getName(),
'color' => $this->getColor(),
'icon' => $this->getIcon(),
diff --git a/src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php b/src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php
--- a/src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php
+++ b/src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php
@@ -5,124 +5,89 @@
const DATASETKEY = 'stacked-area';
- protected function newChartDisplayData(
- PhabricatorChartDataQuery $data_query) {
- $functions = $this->getFunctions();
+ private $stacks;
- $reversed_functions = array_reverse($functions, true);
+ public function setStacks(array $stacks) {
+ $this->stacks = $stacks;
+ return $this;
+ }
- $function_points = array();
- foreach ($reversed_functions as $function_idx => $function) {
- $function_points[$function_idx] = array();
+ public function getStacks() {
+ return $this->stacks;
+ }
- $datapoints = $function->newDatapoints($data_query);
- foreach ($datapoints as $point) {
- $x_value = $point['x'];
- $function_points[$function_idx][$x_value] = $point;
- }
- }
+ protected function newChartDisplayData(
+ PhabricatorChartDataQuery $data_query) {
- $raw_points = $function_points;
+ $functions = $this->getFunctions();
+ $functions = mpull($functions, null, 'getKey');
- // We need to define every function we're drawing at every point where
- // any of the functions we're drawing are defined. If we don't, we'll
- // end up with weird gaps or overlaps between adjacent areas, and won't
- // know how much we need to lift each point above the baseline when
- // stacking the functions on top of one another.
+ $stacks = $this->getStacks();
- $must_define = array();
- foreach ($function_points as $function_idx => $points) {
- foreach ($points as $x => $point) {
- $must_define[$x] = $x;
- }
+ if (!$stacks) {
+ $stacks = array(
+ array_reverse(array_keys($functions), true),
+ );
}
- ksort($must_define);
- foreach ($reversed_functions as $function_idx => $function) {
- $missing = array();
- foreach ($must_define as $x) {
- if (!isset($function_points[$function_idx][$x])) {
- $missing[$x] = true;
+ $series = array();
+ $raw_points = array();
+
+ foreach ($stacks as $stack) {
+ $stack_functions = array_select_keys($functions, $stack);
+
+ $function_points = $this->getFunctionDatapoints(
+ $data_query,
+ $stack_functions);
+
+ $stack_points = $function_points;
+
+ $function_points = $this->getGeometry(
+ $data_query,
+ $function_points);
+
+ $baseline = array();
+ foreach ($function_points as $function_idx => $points) {
+ $bounds = array();
+ foreach ($points as $x => $point) {
+ if (!isset($baseline[$x])) {
+ $baseline[$x] = 0;
+ }
+
+ $y0 = $baseline[$x];
+ $baseline[$x] += $point['y'];
+ $y1 = $baseline[$x];
+
+ $bounds[] = array(
+ 'x' => $x,
+ 'y0' => $y0,
+ 'y1' => $y1,
+ );
+
+ if (isset($stack_points[$function_idx][$x])) {
+ $stack_points[$function_idx][$x]['y1'] = $y1;
+ }
}
- }
- if (!$missing) {
- continue;
+ $series[$function_idx] = $bounds;
}
- $points = $function_points[$function_idx];
-
- $values = array_keys($points);
- $cursor = -1;
- $length = count($values);
-
- foreach ($missing as $x => $ignored) {
- // Move the cursor forward until we find the last point before "x"
- // which is defined.
- while ($cursor + 1 < $length && $values[$cursor + 1] < $x) {
- $cursor++;
- }
-
- // If this new point is to the left of all defined points, we'll
- // assume the value is 0. If the point is to the right of all defined
- // points, we assume the value is the same as the last known value.
-
- // If it's between two defined points, we average them.
-
- if ($cursor < 0) {
- $y = 0;
- } else if ($cursor + 1 < $length) {
- $xmin = $values[$cursor];
- $xmax = $values[$cursor + 1];
-
- $ymin = $points[$xmin]['y'];
- $ymax = $points[$xmax]['y'];
-
- // Fill in the missing point by creating a linear interpolation
- // between the two adjacent points.
- $distance = ($x - $xmin) / ($xmax - $xmin);
- $y = $ymin + (($ymax - $ymin) * $distance);
- } else {
- $xmin = $values[$cursor];
- $y = $function_points[$function_idx][$xmin]['y'];
- }
+ $raw_points += $stack_points;
+ }
- $function_points[$function_idx][$x] = array(
- 'x' => $x,
- 'y' => $y,
- );
- }
+ $series = array_select_keys($series, array_keys($functions));
+ $series = array_values($series);
- ksort($function_points[$function_idx]);
- }
+ $raw_points = array_select_keys($raw_points, array_keys($functions));
+ $raw_points = array_values($raw_points);
$range_min = null;
$range_max = null;
- $series = array();
- $baseline = array();
- foreach ($function_points as $function_idx => $points) {
- $below = idx($function_points, $function_idx - 1);
-
- $bounds = array();
- foreach ($points as $x => $point) {
- if (!isset($baseline[$x])) {
- $baseline[$x] = 0;
- }
-
- $y0 = $baseline[$x];
- $baseline[$x] += $point['y'];
- $y1 = $baseline[$x];
-
- $bounds[] = array(
- 'x' => $x,
- 'y0' => $y0,
- 'y1' => $y1,
- );
-
- if (isset($raw_points[$function_idx][$x])) {
- $raw_points[$function_idx][$x]['y1'] = $y1;
- }
+ foreach ($series as $geometry_list) {
+ foreach ($geometry_list as $geometry_item) {
+ $y0 = $geometry_item['y0'];
+ $y1 = $geometry_item['y1'];
if ($range_min === null) {
$range_min = $y0;
@@ -134,12 +99,8 @@
}
$range_max = max($range_max, $y0, $y1);
}
-
- $series[] = $bounds;
}
- $series = array_reverse($series);
-
// We're going to group multiple events into a single point if they have
// X values that are very close to one another.
//
@@ -222,5 +183,118 @@
->setRange(new PhabricatorChartInterval($range_min, $range_max));
}
+ private function getAllXValuesAsMap(
+ PhabricatorChartDataQuery $data_query,
+ array $point_lists) {
+
+ // We need to define every function we're drawing at every point where
+ // any of the functions we're drawing are defined. If we don't, we'll
+ // end up with weird gaps or overlaps between adjacent areas, and won't
+ // know how much we need to lift each point above the baseline when
+ // stacking the functions on top of one another.
+
+ $must_define = array();
+
+ $min = $data_query->getMinimumValue();
+ $max = $data_query->getMaximumValue();
+ $must_define[$max] = $max;
+ $must_define[$min] = $min;
+
+ foreach ($point_lists as $point_list) {
+ foreach ($point_list as $x => $point) {
+ $must_define[$x] = $x;
+ }
+ }
+
+ ksort($must_define);
+
+ return $must_define;
+ }
+
+ private function getFunctionDatapoints(
+ PhabricatorChartDataQuery $data_query,
+ array $functions) {
+
+ assert_instances_of($functions, 'PhabricatorChartFunction');
+
+ $points = array();
+ foreach ($functions as $idx => $function) {
+ $points[$idx] = array();
+
+ $datapoints = $function->newDatapoints($data_query);
+ foreach ($datapoints as $point) {
+ $x_value = $point['x'];
+ $points[$idx][$x_value] = $point;
+ }
+ }
+
+ return $points;
+ }
+
+ private function getGeometry(
+ PhabricatorChartDataQuery $data_query,
+ array $point_lists) {
+
+ $must_define = $this->getAllXValuesAsMap($data_query, $point_lists);
+
+ foreach ($point_lists as $idx => $points) {
+
+ $missing = array();
+ foreach ($must_define as $x) {
+ if (!isset($points[$x])) {
+ $missing[$x] = true;
+ }
+ }
+
+ if (!$missing) {
+ continue;
+ }
+
+ $values = array_keys($points);
+ $cursor = -1;
+ $length = count($values);
+
+ foreach ($missing as $x => $ignored) {
+ // Move the cursor forward until we find the last point before "x"
+ // which is defined.
+ while ($cursor + 1 < $length && $values[$cursor + 1] < $x) {
+ $cursor++;
+ }
+
+ // If this new point is to the left of all defined points, we'll
+ // assume the value is 0. If the point is to the right of all defined
+ // points, we assume the value is the same as the last known value.
+
+ // If it's between two defined points, we average them.
+
+ if ($cursor < 0) {
+ $y = 0;
+ } else if ($cursor + 1 < $length) {
+ $xmin = $values[$cursor];
+ $xmax = $values[$cursor + 1];
+
+ $ymin = $points[$xmin]['y'];
+ $ymax = $points[$xmax]['y'];
+
+ // Fill in the missing point by creating a linear interpolation
+ // between the two adjacent points.
+ $distance = ($x - $xmin) / ($xmax - $xmin);
+ $y = $ymin + (($ymax - $ymin) * $distance);
+ } else {
+ $xmin = $values[$cursor];
+ $y = $points[$xmin]['y'];
+ }
+
+ $point_lists[$idx][$x] = array(
+ 'x' => $x,
+ 'y' => $y,
+ );
+ }
+
+ ksort($point_lists[$idx]);
+ }
+
+ return $point_lists;
+ }
}
diff --git a/src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php b/src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php
--- a/src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php
+++ b/src/applications/project/chart/PhabricatorProjectBurndownChartEngine.php
@@ -29,6 +29,8 @@
}
$functions = array();
+ $stacks = array();
+
if ($project_phids) {
foreach ($project_phids as $project_phid) {
$function = $this->newFunction(
@@ -42,12 +44,31 @@
));
$function->getFunctionLabel()
+ ->setKey('moved-in')
->setName(pht('Tasks Moved Into Project'))
->setColor('rgba(128, 128, 200, 1)')
->setFillColor('rgba(128, 128, 200, 0.15)');
$functions[] = $function;
+ $function = $this->newFunction(
+ array(
+ 'accumulate',
+ array(
+ 'compose',
+ array('fact', 'tasks.open-count.status.project', $project_phid),
+ array('min', 0),
+ ),
+ ));
+
+ $function->getFunctionLabel()
+ ->setKey('reopened')
+ ->setName(pht('Tasks Reopened'))
+ ->setColor('rgba(128, 128, 200, 1)')
+ ->setFillColor('rgba(128, 128, 200, 0.15)');
+
+ $functions[] = $function;
+
$function = $this->newFunction(
array(
'accumulate',
@@ -55,6 +76,7 @@
));
$function->getFunctionLabel()
+ ->setKey('created')
->setName(pht('Tasks Created'))
->setColor('rgba(0, 0, 200, 1)')
->setFillColor('rgba(0, 0, 200, 0.15)');
@@ -66,30 +88,39 @@
'accumulate',
array(
'compose',
- array('fact', 'tasks.open-count.assign.project', $project_phid),
+ array('fact', 'tasks.open-count.status.project', $project_phid),
array('max', 0),
),
));
$function->getFunctionLabel()
- ->setName(pht('Tasks Moved Out of Project'))
- ->setColor('rgba(128, 200, 128, 1)')
- ->setFillColor('rgba(128, 200, 128, 0.15)');
+ ->setKey('closed')
+ ->setName(pht('Tasks Closed'))
+ ->setColor('rgba(0, 200, 0, 1)')
+ ->setFillColor('rgba(0, 200, 0, 0.15)');
$functions[] = $function;
$function = $this->newFunction(
array(
'accumulate',
- array('fact', 'tasks.open-count.status.project', $project_phid),
+ array(
+ 'compose',
+ array('fact', 'tasks.open-count.assign.project', $project_phid),
+ array('max', 0),
+ ),
));
$function->getFunctionLabel()
- ->setName(pht('Tasks Closed'))
- ->setColor('rgba(0, 200, 0, 1)')
- ->setFillColor('rgba(0, 200, 0, 0.15)');
+ ->setKey('moved-out')
+ ->setName(pht('Tasks Moved Out of Project'))
+ ->setColor('rgba(128, 200, 128, 1)')
+ ->setFillColor('rgba(128, 200, 128, 0.15)');
$functions[] = $function;
+
+ $stacks[] = array('created', 'reopened', 'moved-in');
+ $stacks[] = array('closed', 'moved-out');
}
} else {
$function = $this->newFunction(
@@ -99,7 +130,8 @@
));
$function->getFunctionLabel()
- ->setName(pht('Tasks Created'))
+ ->setKey('open')
+ ->setName(pht('Open Tasks'))
->setColor('rgba(0, 0, 200, 1)')
->setFillColor('rgba(0, 0, 200, 0.15)');
@@ -112,7 +144,8 @@
));
$function->getFunctionLabel()
- ->setName(pht('Tasks Closed'))
+ ->setKey('closed')
+ ->setName(pht('Closed Tasks'))
->setColor('rgba(0, 200, 0, 1)')
->setFillColor('rgba(0, 200, 0, 0.15)');
@@ -121,9 +154,14 @@
$datasets = array();
- $datasets[] = id(new PhabricatorChartStackedAreaDataset())
+ $dataset = id(new PhabricatorChartStackedAreaDataset())
->setFunctions($functions);
+ if ($stacks) {
+ $dataset->setStacks($stacks);
+ }
+
+ $datasets[] = $dataset;
$chart->attachDatasets($datasets);
}

File Metadata

Mime Type
text/plain
Expires
Fri, Mar 21, 2:54 AM (2 w, 6 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7714836
Default Alt Text
D20818.id49637.diff (15 KB)

Event Timeline