@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
3abstract class PhabricatorChartFunction
4 extends Phobject {
5
6 private $argumentParser;
7 private $functionLabel;
8
9 final public function getFunctionKey() {
10 return $this->getPhobjectClassConstant('FUNCTIONKEY', 32);
11 }
12
13 /**
14 * Get all available PhabricatorChartFunction subclasses
15 *
16 * @return array<PhabricatorChartFunction> All Phabricator*ChartFunction
17 * classes which implement getFunctionKey()
18 */
19 final public static function getAllFunctions() {
20 return id(new PhutilClassMapQuery())
21 ->setAncestorClass(self::class)
22 ->setUniqueMethod('getFunctionKey')
23 ->execute();
24 }
25
26 final public function setArguments(array $arguments) {
27 $parser = $this->getArgumentParser();
28 $parser->setRawArguments($arguments);
29
30 $specs = $this->newArguments();
31
32 if (!is_array($specs)) {
33 throw new Exception(
34 pht(
35 'Expected "newArguments()" in class "%s" to return a list of '.
36 'argument specifications, got %s.',
37 get_class($this),
38 phutil_describe_type($specs)));
39 }
40
41 assert_instances_of($specs, PhabricatorChartFunctionArgument::class);
42
43 foreach ($specs as $spec) {
44 $parser->addArgument($spec);
45 }
46
47 $parser->setHaveAllArguments(true);
48 $parser->parseArguments();
49
50 return $this;
51 }
52
53 public function setFunctionLabel(PhabricatorChartFunctionLabel $label) {
54 $this->functionLabel = $label;
55 return $this;
56 }
57
58 /**
59 * @return PhabricatorChartFunctionLabel
60 */
61 public function getFunctionLabel() {
62 if (!$this->functionLabel) {
63 $this->functionLabel = id(new PhabricatorChartFunctionLabel())
64 ->setName(pht('Unlabeled Function'))
65 ->setColor('rgba(255, 0, 0, 1)')
66 ->setFillColor('rgba(255, 0, 0, 0.15)');
67 }
68
69 return $this->functionLabel;
70 }
71
72 final public function getKey() {
73 return $this->getFunctionLabel()->getKey();
74 }
75
76 final public static function newFromDictionary(array $map) {
77 PhutilTypeSpec::checkMap(
78 $map,
79 array(
80 'function' => 'string',
81 'arguments' => 'list<wild>',
82 ));
83
84 $functions = self::getAllFunctions();
85
86 $function_name = $map['function'];
87 if (!isset($functions[$function_name])) {
88 throw new Exception(
89 pht(
90 'Attempting to build function "%s" from dictionary, but that '.
91 'function is unknown. Known functions are: %s.',
92 $function_name,
93 implode(', ', array_keys($functions))));
94 }
95
96 $function = id(clone $functions[$function_name])
97 ->setArguments($map['arguments']);
98
99 return $function;
100 }
101
102 public function getSubfunctions() {
103 $result = array();
104 $result[] = $this;
105
106 foreach ($this->getFunctionArguments() as $argument) {
107 foreach ($argument->getSubfunctions() as $subfunction) {
108 $result[] = $subfunction;
109 }
110 }
111
112 return $result;
113 }
114
115 public function getFunctionArguments() {
116 $results = array();
117
118 $parser = $this->getArgumentParser();
119 foreach ($parser->getAllArguments() as $argument) {
120 if ($argument->getType() !== 'function') {
121 continue;
122 }
123
124 $name = $argument->getName();
125 $value = $this->getArgument($name);
126
127 if (!is_array($value)) {
128 $results[] = $value;
129 } else {
130 foreach ($value as $arg_value) {
131 $results[] = $arg_value;
132 }
133 }
134 }
135
136 return $results;
137 }
138
139 /**
140 * Return arrays of x,y data points for a two-dimensional chart
141 * @param PhabricatorChartDataQuery $query
142 * @return array<array<int,int>> x => epoch value; y => incremental number
143 */
144 public function newDatapoints(PhabricatorChartDataQuery $query) {
145 $xv = $this->newInputValues($query);
146
147 if ($xv === null) {
148 $xv = $this->newDefaultInputValues($query);
149 }
150
151 $xv = $query->selectInputValues($xv);
152
153 $n = count($xv);
154 $yv = $this->evaluateFunction($xv);
155
156 $points = array();
157 for ($ii = 0; $ii < $n; $ii++) {
158 $y = $yv[$ii];
159
160 if ($y === null) {
161 continue;
162 }
163
164 $points[] = array(
165 'x' => $xv[$ii],
166 'y' => $y,
167 );
168 }
169
170 return $points;
171 }
172
173 abstract protected function newArguments();
174
175 final protected function newArgument() {
176 return new PhabricatorChartFunctionArgument();
177 }
178
179 final protected function getArgument($key) {
180 return $this->getArgumentParser()->getArgumentValue($key);
181 }
182
183 final protected function getArgumentParser() {
184 if (!$this->argumentParser) {
185 $parser = id(new PhabricatorChartFunctionArgumentParser())
186 ->setFunction($this);
187
188 $this->argumentParser = $parser;
189 }
190 return $this->argumentParser;
191 }
192
193 abstract public function evaluateFunction(array $xv);
194 abstract public function getDataRefs(array $xv);
195 abstract public function loadRefs(array $refs);
196
197 public function getDomain() {
198 return null;
199 }
200
201 public function newInputValues(PhabricatorChartDataQuery $query) {
202 return null;
203 }
204
205 public function loadData() {
206 return;
207 }
208
209 protected function newDefaultInputValues(PhabricatorChartDataQuery $query) {
210 $x_min = $query->getMinimumValue();
211 $x_max = $query->getMaximumValue();
212 $limit = $query->getLimit();
213
214 return $this->newLinearSteps($x_min, $x_max, $limit);
215 }
216
217 protected function newLinearSteps($src, $dst, $count) {
218 $count = (int)$count;
219 $src = (int)$src;
220 $dst = (int)$dst;
221
222 if ($count === 0) {
223 throw new Exception(
224 pht('Can not generate zero linear steps between two values!'));
225 }
226
227 if ($src === $dst) {
228 return array($src);
229 }
230
231 if ($count === 1) {
232 return array($src);
233 }
234
235 $is_reversed = ($src > $dst);
236 if ($is_reversed) {
237 $min = (float)$dst;
238 $max = (float)$src;
239 } else {
240 $min = (float)$src;
241 $max = (float)$dst;
242 }
243
244 $step = (float)($max - $min) / (float)($count - 1);
245
246 $steps = array();
247 for ($cursor = $min; $cursor <= $max; $cursor += $step) {
248 $x = (int)round($cursor);
249
250 if (isset($steps[$x])) {
251 continue;
252 }
253
254 $steps[$x] = $x;
255 }
256
257 $steps = array_values($steps);
258
259 if ($is_reversed) {
260 $steps = array_reverse($steps);
261 }
262
263 return $steps;
264 }
265}