Page MenuHomePhabricator

D20454.diff
No OneTemporary

D20454.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2107,6 +2107,7 @@
'PhabricatorAccessLog' => 'infrastructure/log/PhabricatorAccessLog.php',
'PhabricatorAccessLogConfigOptions' => 'applications/config/option/PhabricatorAccessLogConfigOptions.php',
'PhabricatorAccessibilitySetting' => 'applications/settings/setting/PhabricatorAccessibilitySetting.php',
+ 'PhabricatorAccumulateChartFunction' => 'applications/fact/chart/PhabricatorAccumulateChartFunction.php',
'PhabricatorActionListView' => 'view/layout/PhabricatorActionListView.php',
'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php',
'PhabricatorActivitySettingsPanel' => 'applications/settings/panel/PhabricatorActivitySettingsPanel.php',
@@ -2695,6 +2696,7 @@
'PhabricatorCommitSearchEngine' => 'applications/audit/query/PhabricatorCommitSearchEngine.php',
'PhabricatorCommitTagsField' => 'applications/repository/customfield/PhabricatorCommitTagsField.php',
'PhabricatorCommonPasswords' => 'applications/auth/constants/PhabricatorCommonPasswords.php',
+ 'PhabricatorComposeChartFunction' => 'applications/fact/chart/PhabricatorComposeChartFunction.php',
'PhabricatorConduitAPIController' => 'applications/conduit/controller/PhabricatorConduitAPIController.php',
'PhabricatorConduitApplication' => 'applications/conduit/application/PhabricatorConduitApplication.php',
'PhabricatorConduitCallManagementWorkflow' => 'applications/conduit/management/PhabricatorConduitCallManagementWorkflow.php',
@@ -3425,6 +3427,7 @@
'PhabricatorHeraldContentSource' => 'applications/herald/contentsource/PhabricatorHeraldContentSource.php',
'PhabricatorHexdumpDocumentEngine' => 'applications/files/document/PhabricatorHexdumpDocumentEngine.php',
'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php',
+ 'PhabricatorHigherOrderChartFunction' => 'applications/fact/chart/PhabricatorHigherOrderChartFunction.php',
'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php',
'PhabricatorHomeConstants' => 'applications/home/constants/PhabricatorHomeConstants.php',
'PhabricatorHomeController' => 'applications/home/controller/PhabricatorHomeController.php',
@@ -4725,6 +4728,7 @@
'PhabricatorSubscriptionsUIEventListener' => 'applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php',
'PhabricatorSubscriptionsUnsubscribeEmailCommand' => 'applications/subscriptions/command/PhabricatorSubscriptionsUnsubscribeEmailCommand.php',
'PhabricatorSubtypeEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorSubtypeEditEngineExtension.php',
+ 'PhabricatorSumChartFunction' => 'applications/fact/chart/PhabricatorSumChartFunction.php',
'PhabricatorSupportApplication' => 'applications/support/application/PhabricatorSupportApplication.php',
'PhabricatorSyntaxHighlighter' => 'infrastructure/markup/PhabricatorSyntaxHighlighter.php',
'PhabricatorSyntaxHighlightingConfigOptions' => 'applications/config/option/PhabricatorSyntaxHighlightingConfigOptions.php',
@@ -4952,7 +4956,6 @@
'PhabricatorWorkingCopyDiscoveryTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php',
'PhabricatorWorkingCopyPullTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyPullTestCase.php',
'PhabricatorWorkingCopyTestCase' => 'applications/repository/engine/__tests__/PhabricatorWorkingCopyTestCase.php',
- 'PhabricatorXChartFunction' => 'applications/fact/chart/PhabricatorXChartFunction.php',
'PhabricatorXHPASTDAO' => 'applications/phpast/storage/PhabricatorXHPASTDAO.php',
'PhabricatorXHPASTParseTree' => 'applications/phpast/storage/PhabricatorXHPASTParseTree.php',
'PhabricatorXHPASTViewController' => 'applications/phpast/controller/PhabricatorXHPASTViewController.php',
@@ -7987,6 +7990,7 @@
'PhabricatorAccessLog' => 'Phobject',
'PhabricatorAccessLogConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorAccessibilitySetting' => 'PhabricatorSelectSetting',
+ 'PhabricatorAccumulateChartFunction' => 'PhabricatorChartFunction',
'PhabricatorActionListView' => 'AphrontTagView',
'PhabricatorActionView' => 'AphrontView',
'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel',
@@ -8691,6 +8695,7 @@
'PhabricatorCommitSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorCommitTagsField' => 'PhabricatorCommitCustomField',
'PhabricatorCommonPasswords' => 'Phobject',
+ 'PhabricatorComposeChartFunction' => 'PhabricatorHigherOrderChartFunction',
'PhabricatorConduitAPIController' => 'PhabricatorConduitController',
'PhabricatorConduitApplication' => 'PhabricatorApplication',
'PhabricatorConduitCallManagementWorkflow' => 'PhabricatorConduitManagementWorkflow',
@@ -9520,6 +9525,7 @@
'PhabricatorHeraldContentSource' => 'PhabricatorContentSource',
'PhabricatorHexdumpDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
+ 'PhabricatorHigherOrderChartFunction' => 'PhabricatorChartFunction',
'PhabricatorHomeApplication' => 'PhabricatorApplication',
'PhabricatorHomeConstants' => 'PhabricatorHomeController',
'PhabricatorHomeController' => 'PhabricatorController',
@@ -11053,6 +11059,7 @@
'PhabricatorSubscriptionsUIEventListener' => 'PhabricatorEventListener',
'PhabricatorSubscriptionsUnsubscribeEmailCommand' => 'MetaMTAEmailTransactionCommand',
'PhabricatorSubtypeEditEngineExtension' => 'PhabricatorEditEngineExtension',
+ 'PhabricatorSumChartFunction' => 'PhabricatorHigherOrderChartFunction',
'PhabricatorSupportApplication' => 'PhabricatorApplication',
'PhabricatorSyntaxHighlighter' => 'Phobject',
'PhabricatorSyntaxHighlightingConfigOptions' => 'PhabricatorApplicationConfigOptions',
@@ -11323,7 +11330,6 @@
'PhabricatorWorkingCopyDiscoveryTestCase' => 'PhabricatorWorkingCopyTestCase',
'PhabricatorWorkingCopyPullTestCase' => 'PhabricatorWorkingCopyTestCase',
'PhabricatorWorkingCopyTestCase' => 'PhabricatorTestCase',
- 'PhabricatorXChartFunction' => 'PhabricatorChartFunction',
'PhabricatorXHPASTDAO' => 'PhabricatorLiskDAO',
'PhabricatorXHPASTParseTree' => 'PhabricatorXHPASTDAO',
'PhabricatorXHPASTViewController' => 'PhabricatorController',
diff --git a/src/applications/fact/chart/PhabricatorAccumulateChartFunction.php b/src/applications/fact/chart/PhabricatorAccumulateChartFunction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/fact/chart/PhabricatorAccumulateChartFunction.php
@@ -0,0 +1,81 @@
+<?php
+
+final class PhabricatorAccumulateChartFunction
+ extends PhabricatorChartFunction {
+
+ const FUNCTIONKEY = 'accumulate';
+
+ protected function newArguments() {
+ return array(
+ $this->newArgument()
+ ->setName('x')
+ ->setType('function'),
+ );
+ }
+
+ public function getDomain() {
+ return $this->getArgument('x')->getDomain();
+ }
+
+ public function newInputValues(PhabricatorChartDataQuery $query) {
+ return $this->getArgument('x')->newInputValues($query);
+ }
+
+ public function evaluateFunction(array $xv) {
+ // First, we're going to accumulate the underlying function. Then
+ // we'll map the inputs through the accumulation.
+
+ $datasource = $this->getArgument('x');
+
+ // Use an unconstrained query to pull all the data from the underlying
+ // source. We need to accumulate data since the beginning of time to
+ // figure out the right Y-intercept -- otherwise, we'll always start at
+ // "0" wherever our domain begins.
+ $empty_query = new PhabricatorChartDataQuery();
+
+ $datasource_xv = $datasource->newInputValues($empty_query);
+ if (!$datasource_xv) {
+ // TODO: Maybe this should just be an error?
+ $datasource_xv = $xv;
+ }
+
+ $yv = $datasource->evaluateFunction($datasource_xv);
+
+ $map = array_combine($datasource_xv, $yv);
+
+ $accumulator = 0;
+ foreach ($map as $x => $y) {
+ $accumulator += $y;
+ $map[$x] = $accumulator;
+ }
+
+ // The value of "accumulate(x)" is the largest datapoint in the map which
+ // is no larger than "x".
+
+ $map_x = array_keys($map);
+ $idx = -1;
+ $max = count($map_x) - 1;
+
+ $yv = array();
+
+ $value = 0;
+ foreach ($xv as $x) {
+ // While the next "x" we need to evaluate the function at lies to the
+ // right of the next datapoint, move the current datapoint forward until
+ // we're at the rightmost datapoint which is not larger than "x".
+ while ($idx < $max) {
+ if ($map_x[$idx + 1] > $x) {
+ break;
+ }
+
+ $idx++;
+ $value = $map[$map_x[$idx]];
+ }
+
+ $yv[] = $value;
+ }
+
+ return $yv;
+ }
+
+}
diff --git a/src/applications/fact/chart/PhabricatorChartDataQuery.php b/src/applications/fact/chart/PhabricatorChartDataQuery.php
--- a/src/applications/fact/chart/PhabricatorChartDataQuery.php
+++ b/src/applications/fact/chart/PhabricatorChartDataQuery.php
@@ -34,4 +34,48 @@
return $this->limit;
}
+ public function selectInputValues(array $xv) {
+ $result = array();
+
+ $x_min = $this->getMinimumValue();
+ $x_max = $this->getMaximumValue();
+ $limit = $this->getLimit();
+
+ if ($x_min !== null) {
+ foreach ($xv as $key => $x) {
+ if ($x < $x_min) {
+ unset($xv[$key]);
+ }
+ }
+ }
+
+ if ($x_max !== null) {
+ foreach ($xv as $key => $x) {
+ if ($x > $x_max) {
+ unset($xv[$key]);
+ }
+ }
+ }
+
+ // If we have too many data points, throw away some of the data.
+
+ // TODO: This doesn't work especially well right now.
+
+ if ($limit !== null) {
+ $count = count($xv);
+ if ($count > $limit) {
+ $ii = 0;
+ $every = ceil($count / $limit);
+ foreach ($xv as $key => $x) {
+ $ii++;
+ if (($ii % $every) && ($ii != $count)) {
+ unset($xv[$key]);
+ }
+ }
+ }
+ }
+
+ return array_values($xv);
+ }
+
}
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
@@ -3,11 +3,7 @@
abstract class PhabricatorChartFunction
extends Phobject {
- private $xAxis;
- private $yAxis;
-
private $argumentParser;
- private $sourceFunction;
final public function getFunctionKey() {
return $this->getPhobjectClassConstant('FUNCTIONKEY', 32);
@@ -44,125 +40,115 @@
$parser->setHaveAllArguments(true);
$parser->parseArguments();
- $source_argument = $parser->getSourceFunctionArgument();
- if ($source_argument) {
- $source_function = $this->getArgument($source_argument->getName());
- $this->setSourceFunction($source_function);
- }
-
return $this;
}
- abstract protected function newArguments();
+ public function getSubfunctions() {
+ $result = array();
+ $result[] = $this;
- final protected function newArgument() {
- return new PhabricatorChartFunctionArgument();
- }
+ foreach ($this->getFunctionArguments() as $argument) {
+ foreach ($argument->getSubfunctions() as $subfunction) {
+ $result[] = $subfunction;
+ }
+ }
- final protected function getArgument($key) {
- return $this->getArgumentParser()->getArgumentValue($key);
+ return $result;
}
- final protected function getArgumentParser() {
- if (!$this->argumentParser) {
- $parser = id(new PhabricatorChartFunctionArgumentParser())
- ->setFunction($this);
+ public function getFunctionArguments() {
+ $results = array();
- $this->argumentParser = $parser;
+ $parser = $this->getArgumentParser();
+ foreach ($parser->getAllArguments() as $argument) {
+ if ($argument->getType() !== 'function') {
+ continue;
+ }
+
+ $name = $argument->getName();
+ $value = $this->getArgument($name);
+
+ if (!is_array($value)) {
+ $results[] = $value;
+ } else {
+ foreach ($value as $arg_value) {
+ $results[] = $arg_value;
+ }
+ }
}
- return $this->argumentParser;
- }
- public function loadData() {
- return;
+ return $results;
}
- protected function setSourceFunction(PhabricatorChartFunction $source) {
- $this->sourceFunction = $source;
- return $this;
- }
+ public function newDatapoints(PhabricatorChartDataQuery $query) {
+ $xv = $this->newInputValues($query);
- protected function getSourceFunction() {
- return $this->sourceFunction;
- }
+ if ($xv === null) {
+ $xv = $this->newDefaultInputValues($query);
+ }
- final public function setXAxis(PhabricatorChartAxis $x_axis) {
- $this->xAxis = $x_axis;
- return $this;
- }
+ $xv = $query->selectInputValues($xv);
- final public function getXAxis() {
- return $this->xAxis;
- }
+ $n = count($xv);
+ $yv = $this->evaluateFunction($xv);
- final public function setYAxis(PhabricatorChartAxis $y_axis) {
- $this->yAxis = $y_axis;
- return $this;
- }
+ $points = array();
+ for ($ii = 0; $ii < $n; $ii++) {
+ $y = $yv[$ii];
- final public function getYAxis() {
- return $this->yAxis;
- }
+ if ($y === null) {
+ continue;
+ }
- protected function canEvaluateFunction() {
- return false;
- }
+ $points[] = array(
+ 'x' => $xv[$ii],
+ 'y' => $y,
+ );
+ }
- protected function evaluateFunction($x) {
- throw new PhutilMethodNotImplementedException();
+ return $points;
}
- public function hasDomain() {
- if ($this->canEvaluateFunction()) {
- return false;
- }
+ abstract protected function newArguments();
- throw new PhutilMethodNotImplementedException();
+ final protected function newArgument() {
+ return new PhabricatorChartFunctionArgument();
}
- public function getDatapoints(PhabricatorChartDataQuery $query) {
- if ($this->canEvaluateFunction()) {
- $points = $this->newSourceDatapoints($query);
- foreach ($points as $key => $point) {
- $y = $point['y'];
- $y = $this->evaluateFunction($y);
- $points[$key]['y'] = $y;
- }
+ final protected function getArgument($key) {
+ return $this->getArgumentParser()->getArgumentValue($key);
+ }
- return $points;
- }
+ final protected function getArgumentParser() {
+ if (!$this->argumentParser) {
+ $parser = id(new PhabricatorChartFunctionArgumentParser())
+ ->setFunction($this);
- return $this->newDatapoints($query);
+ $this->argumentParser = $parser;
+ }
+ return $this->argumentParser;
}
- protected function newDatapoints(PhabricatorChartDataQuery $query) {
- throw new PhutilMethodNotImplementedException();
+ abstract public function evaluateFunction(array $xv);
+
+ public function getDomain() {
+ return null;
}
- protected function newSourceDatapoints(PhabricatorChartDataQuery $query) {
- $source = $this->getSourceFunction();
- if ($source) {
- return $source->getDatapoints($query);
- }
+ public function newInputValues(PhabricatorChartDataQuery $query) {
+ return null;
+ }
- return $this->newDefaultDatapoints($query);
+ public function loadData() {
+ return;
}
- protected function newDefaultDatapoints(PhabricatorChartDataQuery $query) {
+ protected function newDefaultInputValues(PhabricatorChartDataQuery $query) {
$x_min = $query->getMinimumValue();
$x_max = $query->getMaximumValue();
$limit = $query->getLimit();
- $points = array();
- $steps = $this->newLinearSteps($x_min, $x_max, $limit);
- foreach ($steps as $step) {
- $points[] = array(
- 'x' => $step,
- 'y' => $step,
- );
- }
-
- return $points;
+ return $this->newLinearSteps($x_min, $x_max, $limit);
}
protected function newLinearSteps($src, $dst, $count) {
@@ -213,5 +199,4 @@
return $steps;
}
-
}
diff --git a/src/applications/fact/chart/PhabricatorChartFunctionArgument.php b/src/applications/fact/chart/PhabricatorChartFunctionArgument.php
--- a/src/applications/fact/chart/PhabricatorChartFunctionArgument.php
+++ b/src/applications/fact/chart/PhabricatorChartFunctionArgument.php
@@ -5,7 +5,7 @@
private $name;
private $type;
- private $isSourceFunction;
+ private $repeatable;
public function setName($name) {
$this->name = $name;
@@ -16,6 +16,15 @@
return $this->name;
}
+ public function setRepeatable($repeatable) {
+ $this->repeatable = $repeatable;
+ return $this;
+ }
+
+ public function getRepeatable() {
+ return $this->repeatable;
+ }
+
public function setType($type) {
$types = array(
'fact-key' => true,
@@ -40,15 +49,6 @@
return $this->type;
}
- public function setIsSourceFunction($is_source_function) {
- $this->isSourceFunction = $is_source_function;
- return $this;
- }
-
- public function getIsSourceFunction() {
- return $this->isSourceFunction;
- }
-
public function newValue($value) {
switch ($this->getType()) {
case 'fact-key':
diff --git a/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php b/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php
--- a/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php
+++ b/src/applications/fact/chart/PhabricatorChartFunctionArgumentParser.php
@@ -11,6 +11,7 @@
private $argumentMap = array();
private $argumentPosition = 0;
private $argumentValues = array();
+ private $repeatableArgument = null;
public function setFunction(PhabricatorChartFunction $function) {
$this->function = $function;
@@ -55,6 +56,32 @@
$name));
}
+ if ($this->repeatableArgument) {
+ if ($spec->getRepeatable()) {
+ throw new Exception(
+ pht(
+ 'Chart function "%s" emitted multiple repeatable argument '.
+ 'specifications ("%s" and "%s"). Only one argument may be '.
+ 'repeatable and it must be the last argument.',
+ $this->getFunctionArgumentSignature(),
+ $name,
+ $this->repeatableArgument->getName()));
+ } else {
+ throw new Exception(
+ pht(
+ 'Chart function "%s" emitted a repeatable argument ("%s"), then '.
+ 'another argument ("%s"). No arguments are permitted after a '.
+ 'repeatable argument.',
+ $this->getFunctionArgumentSignature(),
+ $this->repeatableArgument->getName(),
+ $name));
+ }
+ }
+
+ if ($spec->getRepeatable()) {
+ $this->repeatableArgument = $spec;
+ }
+
$this->argumentMap[$name] = $spec;
$this->unparsedArguments[] = $spec;
@@ -72,12 +99,26 @@
return $this;
}
+ public function getAllArguments() {
+ return array_values($this->argumentMap);
+ }
+
public function parseArguments() {
$have_count = count($this->rawArguments);
$want_count = count($this->argumentMap);
if ($this->haveAllArguments) {
- if ($want_count !== $have_count) {
+ if ($this->repeatableArgument) {
+ if ($want_count > $have_count) {
+ throw new Exception(
+ pht(
+ 'Function "%s" expects %s or more argument(s), but only %s '.
+ 'argument(s) were provided.',
+ $this->getFunctionArgumentSignature(),
+ $want_count,
+ $have_count));
+ }
+ } else if ($want_count !== $have_count) {
throw new Exception(
pht(
'Function "%s" expects %s argument(s), but %s argument(s) were '.
@@ -105,6 +146,14 @@
$raw_argument = array_shift($this->unconsumedArguments);
$this->argumentPosition++;
+ $is_repeatable = $argument->getRepeatable();
+
+ // If this argument is repeatable and we have more arguments, add it
+ // back to the end of the list so we can continue parsing.
+ if ($is_repeatable && $this->unconsumedArguments) {
+ $this->unparsedArguments[] = $argument;
+ }
+
try {
$value = $argument->newValue($raw_argument);
} catch (Exception $ex) {
@@ -118,7 +167,14 @@
$ex->getMessage()));
}
- $this->argumentValues[$name] = $value;
+ if ($is_repeatable) {
+ if (!isset($this->argumentValues[$name])) {
+ $this->argumentValues[$name] = array();
+ }
+ $this->argumentValues[$name][] = $value;
+ } else {
+ $this->argumentValues[$name] = $value;
+ }
}
}
@@ -141,7 +197,7 @@
$argument_list[] = $key;
}
- if (!$this->haveAllArguments) {
+ if (!$this->haveAllArguments || $this->repeatableArgument) {
$argument_list[] = '...';
}
@@ -151,43 +207,4 @@
implode(', ', $argument_list));
}
- public function getSourceFunctionArgument() {
- $required_type = 'function';
-
- $sources = array();
- foreach ($this->argumentMap as $key => $argument) {
- if (!$argument->getIsSourceFunction()) {
- continue;
- }
-
- if ($argument->getType() !== $required_type) {
- throw new Exception(
- pht(
- 'Function "%s" defines an argument "%s" which is marked as a '.
- 'source function, but the type of this argument is not "%s".',
- $this->getFunctionArgumentSignature(),
- $argument->getName(),
- $required_type));
- }
-
- $sources[$key] = $argument;
- }
-
- if (!$sources) {
- return null;
- }
-
- if (count($sources) > 1) {
- throw new Exception(
- pht(
- 'Function "%s" defines more than one argument as a source '.
- 'function (arguments: %s). Functions must have zero or one '.
- 'source function.',
- $this->getFunctionArgumentSignature(),
- implode(', ', array_keys($sources))));
- }
-
- return head($sources);
- }
-
}
diff --git a/src/applications/fact/chart/PhabricatorComposeChartFunction.php b/src/applications/fact/chart/PhabricatorComposeChartFunction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/fact/chart/PhabricatorComposeChartFunction.php
@@ -0,0 +1,73 @@
+<?php
+
+final class PhabricatorComposeChartFunction
+ extends PhabricatorHigherOrderChartFunction {
+
+ const FUNCTIONKEY = 'compose';
+
+ protected function newArguments() {
+ return array(
+ $this->newArgument()
+ ->setName('f')
+ ->setType('function')
+ ->setRepeatable(true),
+ );
+ }
+
+ public function evaluateFunction(array $xv) {
+ $original_positions = array_keys($xv);
+ $remaining_positions = $original_positions;
+ foreach ($this->getFunctionArguments() as $function) {
+ $xv = $function->evaluateFunction($xv);
+
+ // If a function evaluates to "null" at some positions, we want to return
+ // "null" at those positions and stop evaluating the function.
+
+ // We also want to pass "evaluateFunction()" a natural list containing
+ // only values it should evaluate: keys should not be important and we
+ // should not pass "null". This simplifies implementation of functions.
+
+ // To do this, first create a map from original input positions to
+ // function return values.
+ $xv = array_combine($remaining_positions, $xv);
+
+ // If a function evaluated to "null" at any position where we evaluated
+ // it, the result will be "null". We remove the position from the
+ // vector so we stop evaluating it.
+ foreach ($xv as $x => $y) {
+ if ($y !== null) {
+ continue;
+ }
+
+ unset($xv[$x]);
+ }
+
+ // Store the remaining original input positions for the next round, then
+ // throw away the array keys so we're passing the next function a natural
+ // list with only non-"null" values.
+ $remaining_positions = array_keys($xv);
+ $xv = array_values($xv);
+
+ // If we have no more inputs to evaluate, we can bail out early rather
+ // than passing empty vectors to functions for evaluation.
+ if (!$xv) {
+ break;
+ }
+ }
+
+
+ $yv = array();
+ $xv = array_combine($remaining_positions, $xv);
+ foreach ($original_positions as $position) {
+ if (isset($xv[$position])) {
+ $y = $xv[$position];
+ } else {
+ $y = null;
+ }
+ $yv[$position] = $y;
+ }
+
+ return $yv;
+ }
+
+}
diff --git a/src/applications/fact/chart/PhabricatorConstantChartFunction.php b/src/applications/fact/chart/PhabricatorConstantChartFunction.php
--- a/src/applications/fact/chart/PhabricatorConstantChartFunction.php
+++ b/src/applications/fact/chart/PhabricatorConstantChartFunction.php
@@ -13,12 +13,16 @@
);
}
- protected function canEvaluateFunction() {
- return true;
- }
+ public function evaluateFunction(array $xv) {
+ $n = $this->getArgument('n');
+
+ $yv = array();
+
+ foreach ($xv as $x) {
+ $yv[] = $n;
+ }
- protected function evaluateFunction($x) {
- return $this->getArgument('n');
+ return $yv;
}
}
diff --git a/src/applications/fact/chart/PhabricatorCosChartFunction.php b/src/applications/fact/chart/PhabricatorCosChartFunction.php
--- a/src/applications/fact/chart/PhabricatorCosChartFunction.php
+++ b/src/applications/fact/chart/PhabricatorCosChartFunction.php
@@ -6,20 +6,17 @@
const FUNCTIONKEY = 'cos';
protected function newArguments() {
- return array(
- $this->newArgument()
- ->setName('x')
- ->setType('function')
- ->setIsSourceFunction(true),
- );
+ return array();
}
- protected function canEvaluateFunction() {
- return true;
- }
+ public function evaluateFunction(array $xv) {
+ $yv = array();
+
+ foreach ($xv as $x) {
+ $yv[] = cos(deg2rad($x));
+ }
- protected function evaluateFunction($x) {
- return cos(deg2rad($x));
+ return $yv;
}
}
diff --git a/src/applications/fact/chart/PhabricatorFactChartFunction.php b/src/applications/fact/chart/PhabricatorFactChartFunction.php
--- a/src/applications/fact/chart/PhabricatorFactChartFunction.php
+++ b/src/applications/fact/chart/PhabricatorFactChartFunction.php
@@ -6,7 +6,7 @@
const FUNCTIONKEY = 'fact';
private $fact;
- private $datapoints;
+ private $map;
protected function newArguments() {
$key_argument = $this->newArgument()
@@ -44,73 +44,46 @@
return;
}
- $points = array();
+ $map = array();
+ foreach ($data as $row) {
+ $value = (int)$row['value'];
+ $epoch = (int)$row['epoch'];
- $sum = 0;
- foreach ($data as $key => $row) {
- $sum += (int)$row['value'];
- $points[] = array(
- 'x' => (int)$row['epoch'],
- 'y' => $sum,
- );
+ if (!isset($map[$epoch])) {
+ $map[$epoch] = 0;
+ }
+
+ $map[$epoch] += $value;
}
- $this->datapoints = $points;
+ $this->map = $map;
}
- public function getDatapoints(PhabricatorChartDataQuery $query) {
- $points = $this->datapoints;
- if (!$points) {
- return array();
- }
+ public function getDomain() {
+ return array(
+ head_key($this->map),
+ last_key($this->map),
+ );
+ }
- $x_min = $query->getMinimumValue();
- $x_max = $query->getMaximumValue();
- $limit = $query->getLimit();
+ public function newInputValues(PhabricatorChartDataQuery $query) {
+ return array_keys($this->map);
+ }
- if ($x_min !== null) {
- foreach ($points as $key => $point) {
- if ($point['x'] < $x_min) {
- unset($points[$key]);
- }
- }
- }
+ public function evaluateFunction(array $xv) {
+ $map = $this->map;
- if ($x_max !== null) {
- foreach ($points as $key => $point) {
- if ($point['x'] > $x_max) {
- unset($points[$key]);
- }
- }
- }
+ $yv = array();
- // If we have too many data points, throw away some of the data.
- if ($limit !== null) {
- $count = count($points);
- if ($count > $limit) {
- $ii = 0;
- $every = ceil($count / $limit);
- foreach ($points as $key => $point) {
- $ii++;
- if (($ii % $every) && ($ii != $count)) {
- unset($points[$key]);
- }
- }
+ foreach ($xv as $x) {
+ if (isset($map[$x])) {
+ $yv[] = $map[$x];
+ } else {
+ $yv[] = null;
}
}
- return $points;
- }
-
- public function hasDomain() {
- return true;
- }
-
- public function getDomain() {
- // TODO: We can examine the data to fit a better domain.
-
- $now = PhabricatorTime::getNow();
- return array($now - phutil_units('90 days in seconds'), $now);
+ return $yv;
}
}
diff --git a/src/applications/fact/chart/PhabricatorHigherOrderChartFunction.php b/src/applications/fact/chart/PhabricatorHigherOrderChartFunction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/fact/chart/PhabricatorHigherOrderChartFunction.php
@@ -0,0 +1,56 @@
+<?php
+
+abstract class PhabricatorHigherOrderChartFunction
+ extends PhabricatorChartFunction {
+
+ public function getDomain() {
+ $minv = array();
+ $maxv = array();
+ foreach ($this->getFunctionArguments() as $function) {
+ $domain = $function->getDomain();
+ if ($domain !== null) {
+ list($min, $max) = $domain;
+ $minv[] = $min;
+ $maxv[] = $max;
+ }
+ }
+
+ if (!$minv && !$maxv) {
+ return null;
+ }
+
+ $min = null;
+ $max = null;
+
+ if ($minv) {
+ $min = min($minv);
+ }
+
+ if ($maxv) {
+ $max = max($maxv);
+ }
+
+ return array($min, $max);
+ }
+
+ public function newInputValues(PhabricatorChartDataQuery $query) {
+ $map = array();
+ foreach ($this->getFunctionArguments() as $function) {
+ $xv = $function->newInputValues($query);
+ if ($xv !== null) {
+ foreach ($xv as $x) {
+ $map[$x] = true;
+ }
+ }
+ }
+
+ if (!$map) {
+ return null;
+ }
+
+ ksort($map);
+
+ return array_keys($map);
+ }
+
+}
diff --git a/src/applications/fact/chart/PhabricatorScaleChartFunction.php b/src/applications/fact/chart/PhabricatorScaleChartFunction.php
--- a/src/applications/fact/chart/PhabricatorScaleChartFunction.php
+++ b/src/applications/fact/chart/PhabricatorScaleChartFunction.php
@@ -7,22 +7,22 @@
protected function newArguments() {
return array(
- $this->newArgument()
- ->setName('x')
- ->setType('function')
- ->setIsSourceFunction(true),
$this->newArgument()
->setName('scale')
->setType('number'),
);
}
- protected function canEvaluateFunction() {
- return true;
- }
+ public function evaluateFunction(array $xv) {
+ $scale = $this->getArgument('scale');
+
+ $yv = array();
+
+ foreach ($xv as $x) {
+ $yv[] = $x * $scale;
+ }
- protected function evaluateFunction($x) {
- return $x * $this->getArgument('scale');
+ return $yv;
}
}
diff --git a/src/applications/fact/chart/PhabricatorShiftChartFunction.php b/src/applications/fact/chart/PhabricatorShiftChartFunction.php
--- a/src/applications/fact/chart/PhabricatorShiftChartFunction.php
+++ b/src/applications/fact/chart/PhabricatorShiftChartFunction.php
@@ -7,22 +7,22 @@
protected function newArguments() {
return array(
- $this->newArgument()
- ->setName('x')
- ->setType('function')
- ->setIsSourceFunction(true),
$this->newArgument()
->setName('shift')
->setType('number'),
);
}
- protected function canEvaluateFunction() {
- return true;
- }
+ public function evaluateFunction(array $xv) {
+ $shift = $this->getArgument('shift');
+
+ $yv = array();
+
+ foreach ($xv as $x) {
+ $yv[] = $x + $shift;
+ }
- protected function evaluateFunction($x) {
- return $x * $this->getArgument('shift');
+ return $yv;
}
}
diff --git a/src/applications/fact/chart/PhabricatorSinChartFunction.php b/src/applications/fact/chart/PhabricatorSinChartFunction.php
--- a/src/applications/fact/chart/PhabricatorSinChartFunction.php
+++ b/src/applications/fact/chart/PhabricatorSinChartFunction.php
@@ -6,20 +6,17 @@
const FUNCTIONKEY = 'sin';
protected function newArguments() {
- return array(
- $this->newArgument()
- ->setName('x')
- ->setType('function')
- ->setIsSourceFunction(true),
- );
+ return array();
}
- protected function canEvaluateFunction() {
- return true;
- }
+ public function evaluateFunction(array $xv) {
+ $yv = array();
+
+ foreach ($xv as $x) {
+ $yv[] = sin(deg2rad($x));
+ }
- protected function evaluateFunction($x) {
- return sin(deg2rad($x));
+ return $yv;
}
}
diff --git a/src/applications/fact/chart/PhabricatorSumChartFunction.php b/src/applications/fact/chart/PhabricatorSumChartFunction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/fact/chart/PhabricatorSumChartFunction.php
@@ -0,0 +1,40 @@
+<?php
+
+final class PhabricatorSumChartFunction
+ extends PhabricatorHigherOrderChartFunction {
+
+ const FUNCTIONKEY = 'sum';
+
+ protected function newArguments() {
+ return array(
+ $this->newArgument()
+ ->setName('f')
+ ->setType('function')
+ ->setRepeatable(true),
+ );
+ }
+
+ public function evaluateFunction(array $xv) {
+ $fv = array();
+ foreach ($this->getFunctionArguments() as $function) {
+ $fv[] = $function->evaluateFunction($xv);
+ }
+
+ $n = count($xv);
+ $yv = array_fill(0, $n, null);
+
+ foreach ($fv as $f) {
+ for ($ii = 0; $ii < $n; $ii++) {
+ if ($f[$ii] !== null) {
+ if (!isset($yv[$ii])) {
+ $yv[$ii] = 0;
+ }
+ $yv[$ii] += $f[$ii];
+ }
+ }
+ }
+
+ return $yv;
+ }
+
+}
diff --git a/src/applications/fact/chart/PhabricatorXChartFunction.php b/src/applications/fact/chart/PhabricatorXChartFunction.php
deleted file mode 100644
--- a/src/applications/fact/chart/PhabricatorXChartFunction.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-final class PhabricatorXChartFunction
- extends PhabricatorChartFunction {
-
- const FUNCTIONKEY = 'x';
-
- protected function newArguments() {
- return array();
- }
-
- protected function canEvaluateFunction() {
- return true;
- }
-
- protected function evaluateFunction($x) {
- return $x;
- }
-
-}
diff --git a/src/applications/fact/controller/PhabricatorFactChartController.php b/src/applications/fact/controller/PhabricatorFactChartController.php
--- a/src/applications/fact/controller/PhabricatorFactChartController.php
+++ b/src/applications/fact/controller/PhabricatorFactChartController.php
@@ -12,43 +12,50 @@
$is_chart_mode = ($mode === 'chart');
$is_draw_mode = ($mode === 'draw');
+ $argvs = array();
+
+ $argvs[] = array('fact', 'tasks.count.create');
+
+ $argvs[] = array('constant', 360);
+
+ $argvs[] = array('fact', 'tasks.open-count.create');
+
+ $argvs[] = array(
+ 'sum',
+ array(
+ 'accumulate',
+ array('fact', 'tasks.count.create'),
+ ),
+ array(
+ 'accumulate',
+ array('fact', 'tasks.open-count.create'),
+ ),
+ );
+
+ $argvs[] = array(
+ 'compose',
+ array('scale', 0.001),
+ array('cos'),
+ array('scale', 100),
+ array('shift', 800),
+ );
+
$functions = array();
+ foreach ($argvs as $argv) {
+ $functions[] = id(new PhabricatorComposeChartFunction())
+ ->setArguments(array($argv));
+ }
- $functions[] = id(new PhabricatorFactChartFunction())
- ->setArguments(array('tasks.count.create'));
-
- $functions[] = id(new PhabricatorFactChartFunction())
- ->setArguments(array('tasks.open-count.create'));
-
- $x_function = id(new PhabricatorXChartFunction())
- ->setArguments(array());
-
- $functions[] = id(new PhabricatorConstantChartFunction())
- ->setArguments(array(360));
-
- $functions[] = id(new PhabricatorSinChartFunction())
- ->setArguments(array($x_function));
-
- $cos_function = id(new PhabricatorCosChartFunction())
- ->setArguments(array($x_function));
-
- $functions[] = id(new PhabricatorShiftChartFunction())
- ->setArguments(
- array(
- array(
- 'scale',
- array(
- 'cos',
- array(
- 'scale',
- array('x'),
- 0.001,
- ),
- ),
- 10,
- ),
- 200,
- ));
+ $subfunctions = array();
+ foreach ($functions as $function) {
+ foreach ($function->getSubfunctions() as $subfunction) {
+ $subfunctions[] = $subfunction;
+ }
+ }
+
+ foreach ($subfunctions as $subfunction) {
+ $subfunction->loadData();
+ }
list($domain_min, $domain_max) = $this->getDomain($functions);
@@ -63,11 +70,7 @@
$datasets = array();
foreach ($functions as $function) {
- $function->setXAxis($axis);
-
- $function->loadData();
-
- $points = $function->getDatapoints($data_query);
+ $points = $function->newDatapoints($data_query);
$x = array();
$y = array();
@@ -157,19 +160,18 @@
private function getDomain(array $functions) {
$domain_min_list = null;
$domain_max_list = null;
+
foreach ($functions as $function) {
- if ($function->hasDomain()) {
- $domain = $function->getDomain();
+ $domain = $function->getDomain();
- list($domain_min, $domain_max) = $domain;
+ list($function_min, $function_max) = $domain;
- if ($domain_min !== null) {
- $domain_min_list[] = $domain_min;
- }
+ if ($function_min !== null) {
+ $domain_min_list[] = $function_min;
+ }
- if ($domain_max !== null) {
- $domain_max_list[] = $domain_max;
- }
+ if ($function_max !== null) {
+ $domain_max_list[] = $function_max;
}
}

File Metadata

Mime Type
text/plain
Expires
Thu, May 16, 1:21 AM (2 w, 4 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/cs/yx/2hvg6mehioyuihfh
Default Alt Text
D20454.diff (37 KB)

Event Timeline