@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
at recaptime-dev/main 265 lines 6.2 kB view raw
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}