Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15470144
D20454.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
37 KB
Referenced Files
None
Subscribers
None
D20454.diff
View Options
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
Details
Attached
Mime Type
text/plain
Expires
Sat, Apr 5, 8:38 PM (2 d, 4 h ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/q7/6l/cmtdlcacal7wqi6c
Default Alt Text
D20454.diff (37 KB)
Attached To
Mode
D20454: Separate the "configuration" and "evaluation" phases of chart functions
Attached
Detach File
Event Timeline
Log In to Comment