@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class PhabricatorComposeChartFunction
4 extends PhabricatorHigherOrderChartFunction {
5
6 const FUNCTIONKEY = 'compose';
7
8 protected function newArguments() {
9 return array(
10 $this->newArgument()
11 ->setName('f')
12 ->setType('function')
13 ->setRepeatable(true),
14 );
15 }
16
17 public function evaluateFunction(array $xv) {
18 $original_positions = array_keys($xv);
19 $remaining_positions = $original_positions;
20 foreach ($this->getFunctionArguments() as $function) {
21 $xv = $function->evaluateFunction($xv);
22
23 // If a function evaluates to "null" at some positions, we want to return
24 // "null" at those positions and stop evaluating the function.
25
26 // We also want to pass "evaluateFunction()" a natural list containing
27 // only values it should evaluate: keys should not be important and we
28 // should not pass "null". This simplifies implementation of functions.
29
30 // To do this, first create a map from original input positions to
31 // function return values.
32 $xv = array_combine($remaining_positions, $xv);
33
34 // If a function evaluated to "null" at any position where we evaluated
35 // it, the result will be "null". We remove the position from the
36 // vector so we stop evaluating it.
37 foreach ($xv as $x => $y) {
38 if ($y !== null) {
39 continue;
40 }
41
42 unset($xv[$x]);
43 }
44
45 // Store the remaining original input positions for the next round, then
46 // throw away the array keys so we're passing the next function a natural
47 // list with only non-"null" values.
48 $remaining_positions = array_keys($xv);
49 $xv = array_values($xv);
50
51 // If we have no more inputs to evaluate, we can bail out early rather
52 // than passing empty vectors to functions for evaluation.
53 if (!$xv) {
54 break;
55 }
56 }
57
58
59 $yv = array();
60 $xv = array_combine($remaining_positions, $xv);
61 foreach ($original_positions as $position) {
62 if (isset($xv[$position])) {
63 $y = $xv[$position];
64 } else {
65 $y = null;
66 }
67 $yv[$position] = $y;
68 }
69
70 return $yv;
71 }
72
73 public function getDataRefs(array $xv) {
74 // TODO: This is not entirely correct. The correct implementation would
75 // map "x -> y" at each stage of composition and pull and aggregate all
76 // the datapoint refs. In practice, we currently never compose functions
77 // with a data function somewhere in the middle, so just grabbing the first
78 // result is close enough.
79
80 // In the future, we may: notably, "x -> shift(-1 month) -> ..." to
81 // generate a month-over-month overlay is a sensible operation which will
82 // source data from the middle of a function composition.
83
84 foreach ($this->getFunctionArguments() as $function) {
85 return $function->getDataRefs($xv);
86 }
87
88 return array();
89 }
90
91}