PluginProbe
E2Pdf – Export Pdf Tool for WordPress / 1.32.49
E2Pdf – Export Pdf Tool for WordPress v1.32.49
1.32.49 1.32.48 1.32.43 1.32.40 1.32.34 1.32.32 1.32.31 1.32.26 1.32.22 1.32.23 1.32.18 1.32.17 1.32.15 trunk 1.00.00 1.00.13 1.01.01 1.02.02 1.03.07 1.04.07 1.05.03 1.06.02 1.07.11 1.08.00 1.08.06 All 72 releases
e2pdf / vendors / svggraph / StructuredData.php

StructuredData.php in E2Pdf – Export Pdf Tool for WordPress 1.32.49, at vendors/svggraph/StructuredData.php

698 lines 18.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Copyright (C) 2013-2022 Graham Breach
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 /**
19 * For more information, please contact <graham@goat1000.com>
20 */
21
22 namespace Goat1000\SVGGraph;
23
24 /**
25 * Class for structured data
26 */
27 class StructuredData implements \Countable, \ArrayAccess, \Iterator {
28
29 private $datasets = 0;
30 private $key_field = 0;
31 private $dataset_fields = [];
32 private $data;
33 private $force_assoc = false;
34 private $assoc = null;
35 private $datetime;
36 private $repeated_keys;
37 private $sort_keys;
38 private $assoc_test;
39 private $structure = [];
40 private $max_keys = [];
41 private $min_keys = [];
42 private $max_values = [];
43 private $min_values = [];
44 public $error = null;
45
46 public function __construct(&$data, $force_assoc, $datetime_keys,
47 $structure, $repeated_keys, $sort_keys, $integer_keys, $requirements,
48 $rekey_done = false)
49 {
50 if($structure !== null && !empty($structure)) {
51 // structure provided, is it valid?
52 foreach(['key', 'value'] as $field) {
53 if(!array_key_exists($field, $structure)) {
54 $this->error = $field . ' field not set for structured data';
55 return;
56 }
57 }
58
59 if(!is_array($structure['value']))
60 $structure['value'] = [$structure['value']];
61 $this->key_field = $structure['key'];
62 $this->dataset_fields = is_array($structure['value']) ?
63 $structure['value'] : [$structure['value']];
64 } else {
65 // find key and datasets
66 $keys = array_keys($data[0]);
67 $this->key_field = array_shift($keys);
68 $this->dataset_fields = $keys;
69
70 // check for more datasets
71 foreach($data as $item) {
72 foreach(array_keys($item) as $key) {
73 if($key !== $this->key_field &&
74 array_search($key, $this->dataset_fields) === false) {
75 $this->dataset_fields[] = $key;
76 }
77 }
78 }
79
80 // default structure
81 $structure = [
82 'key' => $this->key_field,
83 'value' => $this->dataset_fields
84 ];
85 }
86
87 // check any extra requirements
88 if(is_array($requirements)) {
89 $missing = [];
90 foreach($requirements as $req) {
91 if(!isset($structure[$req])) {
92 $missing[] = $req;
93 }
94 }
95 if(!empty($missing)) {
96 $this->error = 'Required field(s) [' . implode(', ', $missing) .
97 '] not set in data structure';
98 return;
99 }
100 }
101
102 $this->structure = $structure;
103 // check if it really has more than one dataset
104 if(isset($structure['datasets']) && $structure['datasets'] &&
105 is_array(current($data)) && is_array(current(current($data)))) {
106 $this->scatter2DDatasets($data);
107 } else {
108 $this->data = &$data;
109 }
110 $this->datasets = count($this->dataset_fields);
111 $this->force_assoc = $force_assoc;
112 $this->assoc_test = $integer_keys ? 'is_int' : 'is_numeric';
113
114 $do_sort = false;
115 if($datetime_keys || $this->associativeKeys()) {
116 // reindex the array to 0, 1, 2, ...
117 $this->data = array_values($this->data);
118 if($datetime_keys) {
119 if($rekey_done || $this->rekey('Goat1000\\SVGGraph\\Graph::dateConvert')) {
120 $this->datetime = true;
121 $this->assoc = false;
122 } else {
123 $this->error = 'Too many date/time conversion errors';
124 return;
125 }
126 $do_sort = true;
127 }
128 } elseif($this->key_field !== null) {
129 // if not associative, sort by key field
130 $do_sort = true;
131 }
132
133 if($do_sort && $sort_keys) {
134 $this->sort_keys = true;
135 $field_sort = new FieldSort($this->key_field);
136 $field_sort->sort($this->data);
137 }
138
139 if($this->repeatedKeys()) {
140 if($repeated_keys == 'force_assoc')
141 $this->force_assoc = true;
142 elseif($repeated_keys != 'accept')
143 $this->error = 'Repeated keys in data';
144 }
145 }
146
147 /**
148 * Converts plain Data to StructuredData
149 */
150 public static function convertFrom($values, $force_assoc, $datetime_keys,
151 $int_keys)
152 {
153 if($values instanceof Data) {
154 $new_data = [];
155 $count = count($values);
156 $structure = ['key' => 0, 'value' => []];
157
158 // set up all keys first
159 for($i = 0; $i < $count; ++$i) {
160 foreach($values[$i] as $item) {
161 $new_data[$item->key] = [$item->key];
162 }
163 }
164
165 // sort keys if they are numeric
166 if(!$force_assoc && !$values->associativeKeys())
167 ksort($new_data);
168
169 // fill in values
170 for($i = 0; $i < $count; ++$i) {
171 $dpos = $i + 1;
172 $structure['value'][] = $dpos;
173 foreach($values[$i] as $item) {
174 $new_data[$item->key][$dpos] = $item->value;
175 }
176 }
177
178 // array keys are now superfluous
179 $new_data = array_values($new_data);
180
181 $new_values = new StructuredData($new_data, $force_assoc,
182 $datetime_keys, $structure, false, false, $int_keys, null, true);
183 return $new_values;
184 }
185
186 // just return the old values
187 return $values;
188 }
189
190 /**
191 * Sets up normal structured data from scatter_2d datasets
192 */
193 private function scatter2DDatasets(&$data)
194 {
195 $newdata = [];
196 $key_field = $this->structure['key'];
197 $value_field = $this->structure['value'][0];
198
199 // update structure
200 $this->structure['key'] = 0;
201 $this->structure['value'] = [];
202 $this->key_field = 0;
203 $this->dataset_fields = [];
204 $set = 1;
205 foreach($data as $dataset) {
206 foreach($dataset as $item) {
207 if(isset($item[$key_field]) && isset($item[$value_field])) {
208 // no need to dedupe keys - no extra data and scatter_2d
209 // only supported by scatter graphs
210 $newdata[] = [0 => $item[$key_field], $set => $item[$value_field]];
211 }
212 }
213 $this->structure['value'][] = $set;
214 $this->dataset_fields[] = $set;
215 ++$set;
216 }
217 $this->data = $newdata;
218 }
219
220 /**
221 * Implement Iterator interface to prevent iteration...
222 */
223 private function notIterator()
224 {
225 throw new \Exception('Cannot iterate ' . __CLASS__);
226 }
227 #[\ReturnTypeWillChange]
228 public function current() { $this->notIterator(); }
229 #[\ReturnTypeWillChange]
230 public function key() { $this->notIterator(); }
231 #[\ReturnTypeWillChange]
232 public function next() { $this->notIterator(); }
233 #[\ReturnTypeWillChange]
234 public function rewind() { $this->notIterator(); }
235 #[\ReturnTypeWillChange]
236 public function valid() { $this->notIterator(); }
237
238 /**
239 * ArrayAccess methods
240 */
241 #[\ReturnTypeWillChange]
242 public function offsetExists($offset)
243 {
244 return array_key_exists($offset, $this->dataset_fields);
245 }
246
247 #[\ReturnTypeWillChange]
248 public function offsetGet($offset)
249 {
250 return new StructuredDataIterator($this->data, $offset, $this->structure);
251 }
252
253 /**
254 * Don't allow writing to the data
255 */
256 #[\ReturnTypeWillChange]
257 public function offsetSet($offset, $value)
258 {
259 throw new \Exception('Read-only');
260 }
261 #[\ReturnTypeWillChange]
262 public function offsetUnset($offset)
263 {
264 throw new \Exception('Read-only');
265 }
266
267 /**
268 * Countable method
269 */
270 #[\ReturnTypeWillChange]
271 public function count()
272 {
273 return $this->datasets;
274 }
275
276 /**
277 * Returns minimum data value for a dataset
278 */
279 public function getMinValue($dataset = 0)
280 {
281 if(isset($this->min_values[$dataset]))
282 return $this->min_values[$dataset];
283
284 $min = null;
285 $key = $this->dataset_fields[$dataset];
286 foreach($this->data as $item) {
287 if(isset($item[$key]) && ($min === null || $item[$key] < $min))
288 $min = $item[$key];
289 }
290
291 return ($this->min_values[$dataset] = $min);
292 }
293
294 /**
295 * Returns maximum data value for a dataset
296 */
297 public function getMaxValue($dataset = 0)
298 {
299 if(isset($this->max_values[$dataset]))
300 return $this->max_values[$dataset];
301
302 $max = null;
303 $key = $this->dataset_fields[$dataset];
304 foreach($this->data as $item) {
305 if(isset($item[$key]) && ($max === null || $item[$key] > $max))
306 $max = $item[$key];
307 }
308
309 return ($this->max_values[$dataset] = $max);
310 }
311
312 /**
313 * Returns the minimum key value
314 */
315 public function getMinKey($dataset = 0)
316 {
317 if(isset($this->min_keys[$dataset]))
318 return $this->min_keys[$dataset];
319
320 if($this->associativeKeys())
321 return ($this->min_keys[$dataset] = 0);
322
323 $min = null;
324 $key = $this->key_field;
325 $set = $this->dataset_fields[$dataset];
326 if($key === null) {
327 foreach($this->data as $k => $item) {
328 if(isset($item[$set]) && ($min === null || $k < $min))
329 $min = $k;
330 }
331 } else {
332 foreach($this->data as $item) {
333 if(isset($item[$key]) && isset($item[$set]) &&
334 ($min === null || $item[$key] < $min))
335 $min = $item[$key];
336 }
337 }
338
339 return ($this->min_keys[$dataset] = $min);
340 }
341
342 /**
343 * Returns the maximum key value for a dataset
344 */
345 public function getMaxKey($dataset = 0)
346 {
347 if(isset($this->max_keys[$dataset]))
348 return $this->max_keys[$dataset];
349
350 if($this->associativeKeys())
351 return ($this->max_keys[$dataset] = count($this->data) - 1);
352
353 $max = null;
354 $key = $this->key_field;
355 $set = $this->dataset_fields[$dataset];
356 if($key === null) {
357 foreach($this->data as $k => $item) {
358 if(isset($item[$set]) && ($max === null || $k > $max))
359 $max = $k;
360 }
361 } else {
362 foreach($this->data as $item) {
363 if(isset($item[$key]) && isset($item[$set]) &&
364 ($max === null || $item[$key] > $max))
365 $max = $item[$key];
366 }
367 }
368
369 return ($this->max_keys[$dataset] = $max);
370 }
371
372 /**
373 * Returns the key at a given index
374 */
375 public function getKey($index, $dataset = 0)
376 {
377 if(!$this->associativeKeys())
378 return $index;
379 $index = (int)round($index);
380 if(isset($this->data[$index])) {
381 if($this->key_field === null)
382 return $index;
383 $item = $this->data[$index];
384 if(isset($item[$this->key_field]))
385 return $item[$this->key_field];
386 }
387 return null;
388 }
389
390 /**
391 * Returns TRUE if the keys are associative
392 */
393 public function associativeKeys()
394 {
395 if($this->force_assoc)
396 return true;
397
398 if($this->assoc !== null)
399 return $this->assoc;
400
401 // use either is_int or is_numeric to test
402 $test = $this->assoc_test;
403 if($this->key_field === null) {
404 foreach($this->data as $k => $item) {
405 if(! $test($k))
406 return ($this->assoc = true);
407 }
408 } else {
409 foreach($this->data as $item) {
410 if(isset($item[$this->key_field]) && !$test($item[$this->key_field]))
411 return ($this->assoc = true);
412 }
413 }
414 return ($this->assoc = false);
415 }
416
417 /**
418 * Returns the number of data items in a dataset
419 * If $dataset is -1, returns number of items across all datasets
420 */
421 public function itemsCount($dataset = 0)
422 {
423 if($dataset == -1)
424 return count($this->data);
425
426 if(!isset($this->dataset_fields[$dataset]))
427 return 0;
428 $count = 0;
429 $key = $this->dataset_fields[$dataset];
430 foreach($this->data as $item)
431 if(isset($item[$key]))
432 ++$count;
433 return $count;
434 }
435
436 /**
437 * Returns TRUE if there are repeated keys
438 * (also culls items without key field)
439 */
440 public function repeatedKeys()
441 {
442 if($this->repeated_keys !== null)
443 return $this->repeated_keys;
444 if($this->key_field === null)
445 return false;
446 $keys = [];
447 foreach($this->data as $k => $item) {
448 if(!isset($item[$this->key_field]))
449 unset($this->data[$k]);
450 else
451 $keys[] = $item[$this->key_field];
452 }
453 $len = count($keys);
454 $ukeys = array_unique($keys);
455 return ($this->repeated_keys = ($len != count($ukeys)));
456 }
457
458 /**
459 * Returns the min and max sum values for some datasets
460 */
461 public function getMinMaxSumValues($start = 0, $end = null)
462 {
463 if($start >= $this->datasets || ($end !== null && $end >= $this->datasets))
464 throw new \Exception('Dataset not found');
465
466 if($end === null)
467 $end = $this->datasets - 1;
468 return $this->getMinMaxSumValuesFor(range($start, $end));
469 }
470
471 /**
472 * Returns the min/max sum values for an array of datasets
473 */
474 public function getMinMaxSumValuesFor($datasets)
475 {
476 $min_stack = [];
477 $max_stack = [];
478
479 foreach($this->data as $item) {
480 $smin = $smax = 0;
481 foreach($datasets as $dataset) {
482 $vfield = $this->dataset_fields[$dataset];
483 if(!isset($item[$vfield]))
484 continue;
485 $value = $item[$vfield];
486 if($value !== null && !is_numeric($value))
487 throw new \Exception('Non-numeric value');
488 if($value > 0)
489 $smax += $value;
490 else
491 $smin += $value;
492 }
493 $min_stack[] = $smin;
494 $max_stack[] = $smax;
495 }
496 if(!count($min_stack))
497 return [null, null];
498 return [min($min_stack), max($max_stack)];
499 }
500
501 /**
502 * Returns a structured data item
503 */
504 public function getItem($index, $dataset = 0)
505 {
506 $index = (int)round($index);
507 if(!isset($this->data[$index]))
508 return null;
509
510 $key = $this->key_field === null ? $index : null;
511 return new StructuredDataItem($this->data[$index],
512 $this->structure, $dataset, $key);
513 }
514
515 /**
516 * Returns TRUE if the data field exists, setting $value
517 */
518 public function getData($index, $name, &$value)
519 {
520 $index = (int)round($index);
521 if(!isset($this->structure[$name]) || !isset($this->data[$index]))
522 return false;
523
524 $item = $this->data[$index];
525 if($item === null)
526 return false;
527
528 $field = $this->structure[$name];
529 if(!is_array($field)) {
530 if(!isset($item[$field]))
531 return false;
532 $value = $item[$field];
533 return true;
534 }
535
536 // handle array fields
537 $vals = [];
538 $count = 0;
539 foreach($field as $f) {
540 $v = null;
541 if(isset($item[$f])) {
542 $v = $item[$f];
543 ++$count;
544 }
545 $vals[] = $v;
546 }
547
548 // return true if any fields are set
549 if($count > 0) {
550 $value = $vals;
551 return true;
552 }
553 return false;
554 }
555
556 /**
557 * Transforms the keys using a callback function
558 */
559 public function rekey($callback)
560 {
561 // use a tab character as the new key name
562 $rekey_name = "\t";
563 $invalid = 0;
564 foreach($this->data as $index => $item) {
565 $key = $item[$this->key_field];
566 $new_key = call_user_func($callback, $key);
567
568 // if the callback returns NULL, NULL the data item
569 if($new_key === null) {
570 $this->data[$index] = [$rekey_name => null];
571 ++$invalid;
572 continue;
573 }
574
575 $this->data[$index][$rekey_name] = $new_key;
576 }
577
578 // if too many invalid, probably a format error
579 if(count($this->data) && $invalid / count($this->data) > 0.05)
580 return false;
581
582 // forget previous min/max and assoc settings
583 $this->min_keys = [];
584 $this->max_keys = [];
585 $this->assoc = null;
586 $this->key_field = $this->structure['key'] = $rekey_name;
587 return true;
588 }
589
590 /**
591 * Transforms values using a callback function
592 */
593 public function revalue($datasets, $callback)
594 {
595 // $datasets is the number in the output
596 $new_values = [];
597 for($i = 0; $i < $datasets; ++$i)
598 $new_values[] = uniqid('val', false);
599
600 // call the callback for each row of values
601 foreach($this->data as $index => $item) {
602 $row = [];
603 foreach($this->dataset_fields as $field)
604 $row[] = isset($item[$field]) ? $item[$field] : null;
605
606 // get updated row
607 $new_row = call_user_func($callback, $item[$this->key_field], $row);
608 for($i = 0; $i < $datasets; ++$i)
609 $this->data[$index][$new_values[$i]] = isset($new_row[$i]) ?
610 $new_row[$i] : null;
611 }
612
613 // update structure with new value fields
614 $this->structure['value'] = $new_values;
615 $this->dataset_fields = $new_values;
616 $this->datasets = $datasets;
617
618 // forget previous min/max
619 $this->min_values = [];
620 $this->max_values = [];
621 return true;
622 }
623
624 /**
625 * Transform a row using a callback
626 */
627 public function transform($callback, $dataset = 0)
628 {
629 // build the list of fields for this dataset
630 $tstruct = [];
631 foreach($this->structure as $field => $element) {
632 if(is_array($element) && isset($element[$dataset]))
633 $tstruct[$field] = $element[$dataset];
634 else
635 $tstruct[$field] = $element;
636 }
637
638 foreach($this->data as &$row) {
639
640 // use a POD class for the data item
641 $item = new \stdClass;
642
643 foreach($tstruct as $field => $key)
644 if(isset($row[$key]))
645 $item->$field = $row[$key];
646
647 // if the callback returns null, there is no change
648 $item = $callback($item);
649 if($item === null)
650 continue;
651
652 foreach($tstruct as $field => $key)
653 if(isset($item->$field))
654 $row[$key] = $item->$field;
655 }
656 }
657
658 /**
659 * Adds a field to the structure, if it doesn't already exist
660 */
661 public function addField($name)
662 {
663 if(!array_key_exists($name, $this->structure))
664 $this->structure[$name] = uniqid($name, true);
665 }
666
667 /**
668 * Adds a row to the data at end or at $insert position
669 */
670 public function addRow(array $row, $insert = null)
671 {
672 if($insert === null || $insert >= count($this->data)) {
673 $this->data[] = $row;
674 return;
675 }
676 array_splice($this->data, $insert, 0, [$row]);
677 }
678
679 /**
680 * Returns the structure array
681 */
682 public function getStructure()
683 {
684 return $this->structure;
685 }
686
687 /**
688 * Sorts the data by the selected dataset values
689 */
690 public function sort($dataset, $reverse)
691 {
692 $key = $this->dataset_fields[$dataset];
693 $field_sort = new FieldSort($key, $reverse);
694 $field_sort->sort($this->data);
695 }
696 }
697
698