Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15433226
D20818.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
15 KB
Referenced Files
None
Subscribers
None
D20818.id.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Tue, Mar 25, 10:09 PM (2 w, 1 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7714836
Default Alt Text
D20818.id.diff (15 KB)
Attached To
Mode
D20818: Support explicit stacking configuration in stacked area charts
Attached
Detach File
Event Timeline
Log In to Comment