Changeset View
Changeset View
Standalone View
Standalone View
src/applications/fact/chart/PhabricatorChartStackedAreaDataset.php
- This file was added.
<?php | |||||
final class PhabricatorChartStackedAreaDataset | |||||
extends PhabricatorChartDataset { | |||||
const DATASETKEY = 'stacked-area'; | |||||
protected function newWireFormat(PhabricatorChartDataQuery $data_query) { | |||||
$functions = $this->getFunctions(); | |||||
$function_points = array(); | |||||
foreach ($functions as $function_idx => $function) { | |||||
$function_points[$function_idx] = array(); | |||||
$datapoints = $function->newDatapoints($data_query); | |||||
foreach ($datapoints as $point) { | |||||
$x = $point['x']; | |||||
$function_points[$function_idx][$x] = $point; | |||||
} | |||||
} | |||||
$raw_points = $function_points; | |||||
// 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(); | |||||
foreach ($function_points as $function_idx => $points) { | |||||
foreach ($points as $x => $point) { | |||||
$must_define[$x] = $x; | |||||
} | |||||
} | |||||
ksort($must_define); | |||||
foreach ($functions as $function_idx => $function) { | |||||
$missing = array(); | |||||
foreach ($must_define as $x) { | |||||
if (!isset($function_points[$function_idx][$x])) { | |||||
$missing[$x] = true; | |||||
} | |||||
} | |||||
if (!$missing) { | |||||
continue; | |||||
} | |||||
$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" | |||||
amckinley: `foreach ($missing as $x => $_)`? | |||||
// which is defined. | |||||
while ($cursor + 1 < $length && $values[$cursor + 1] < $x) { | |||||
Not Done Inline ActionsThis looks like a typo for "before which 'x' is defined". Maybe:
amckinley: This //looks// like a typo for "before which 'x' is defined". Maybe:
> Move the cursor forward… | |||||
$cursor++; | |||||
} | |||||
Not Done Inline ActionsI feel like we're going to be tracking down edge cases in a lot of this code for a long time. Did you use the D3 implementation as a reference, or was this just on the fly? Nothing looks like it's obviously going to break, but I wonder if we're doing a lot of work upfront without knowing the limits of the way D3 does things. (Clearly the right answer is to use PHP to invoke Node.js server-side to evaluate D3's implementation). amckinley: I feel like we're going to be tracking down edge cases in a lot of this code for a long time. | |||||
// 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. | |||||
Not Done Inline ActionsI would change this ordering to match the evaluation order below: "if to the left", "if between", "if to the right". amckinley: I would change this ordering to match the evaluation order below: "if to the left", "if… | |||||
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']; | |||||
} | |||||
$function_points[$function_idx][$x] = array( | |||||
'x' => $x, | |||||
'y' => $y, | |||||
); | |||||
} | |||||
ksort($function_points[$function_idx]); | |||||
} | |||||
$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; | |||||
} | |||||
} | |||||
$series[] = $bounds; | |||||
} | |||||
$events = array(); | |||||
foreach ($raw_points as $function_idx => $points) { | |||||
$event_list = array(); | |||||
foreach ($points as $point) { | |||||
$event_list[] = $point; | |||||
} | |||||
$events[] = $event_list; | |||||
} | |||||
$result = array( | |||||
'type' => $this->getDatasetTypeKey(), | |||||
'data' => $series, | |||||
'events' => $events, | |||||
'color' => array( | |||||
'blue', | |||||
'cyan', | |||||
'green', | |||||
), | |||||
); | |||||
return $result; | |||||
} | |||||
} |
foreach ($missing as $x => $_)?