PluginProbe
E2Pdf – Export Pdf Tool for WordPress / 1.32.53
E2Pdf – Export Pdf Tool for WordPress v1.32.53
1.32.53 1.32.51 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 All 74 releases
e2pdf / vendors / svggraph / StructuredData.php

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

702 lines 18.1 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 <[email protected]>
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 if($key !== null) {
287 foreach($this->data as $item) {
288 if(isset($item[$key]) && ($min === null || $item[$key] < $min))
289 $min = $item[$key];
290 }
291 }
292
293 return ($this->min_values[$dataset] = $min);
294 }
295
296 /**
297 * Returns maximum data value for a dataset
298 */
299 public function getMaxValue($dataset = 0)
300 {
301 if(isset($this->max_values[$dataset]))
302 return $this->max_values[$dataset];
303
304 $max = null;
305 $key = $this->dataset_fields[$dataset];
306 if($key !== null) {
307 foreach($this->data as $item) {
308 if(isset($item[$key]) && ($max === null || $item[$key] > $max))
309 $max = $item[$key];
310 }
311 }
312
313 return ($this->max_values[$dataset] = $max);
314 }
315
316 /**
317 * Returns the minimum key value
318 */
319 public function getMinKey($dataset = 0)
320 {
321 if(isset($this->min_keys[$dataset]))
322 return $this->min_keys[$dataset];
323
324 if($this->associativeKeys())
325 return ($this->min_keys[$dataset] = 0);
326
327 $min = null;
328 $key = $this->key_field;
329 $set = $this->dataset_fields[$dataset];
330 if($key === null) {
331 foreach($this->data as $k => $item) {
332 if(isset($item[$set]) && ($min === null || $k < $min))
333 $min = $k;
334 }
335 } else {
336 foreach($this->data as $item) {
337 if(isset($item[$key]) && isset($item[$set]) &&
338 ($min === null || $item[$key] < $min))
339 $min = $item[$key];
340 }
341 }
342
343 return ($this->min_keys[$dataset] = $min);
344 }
345
346 /**
347 * Returns the maximum key value for a dataset
348 */
349 public function getMaxKey($dataset = 0)
350 {
351 if(isset($this->max_keys[$dataset]))
352 return $this->max_keys[$dataset];
353
354 if($this->associativeKeys())
355 return ($this->max_keys[$dataset] = count($this->data) - 1);
356
357 $max = null;
358 $key = $this->key_field;
359 $set = $this->dataset_fields[$dataset];
360 if($key === null) {
361 foreach($this->data as $k => $item) {
362 if(isset($item[$set]) && ($max === null || $k > $max))
363 $max = $k;
364 }
365 } else {
366 foreach($this->data as $item) {
367 if(isset($item[$key]) && isset($item[$set]) &&
368 ($max === null || $item[$key] > $max))
369 $max = $item[$key];
370 }
371 }
372
373 return ($this->max_keys[$dataset] = $max);
374 }
375
376 /**
377 * Returns the key at a given index
378 */
379 public function getKey($index, $dataset = 0)
380 {
381 if(!$this->associativeKeys())
382 return $index;
383 $index = (int)round($index);
384 if(isset($this->data[$index])) {
385 if($this->key_field === null)
386 return $index;
387 $item = $this->data[$index];
388 if(isset($item[$this->key_field]))
389 return $item[$this->key_field];
390 }
391 return null;
392 }
393
394 /**
395 * Returns TRUE if the keys are associative
396 */
397 public function associativeKeys()
398 {
399 if($this->force_assoc)
400 return true;
401
402 if($this->assoc !== null)
403 return $this->assoc;
404
405 // use either is_int or is_numeric to test
406 $test = $this->assoc_test;
407 if($this->key_field === null) {
408 foreach($this->data as $k => $item) {
409 if(! $test($k))
410 return ($this->assoc = true);
411 }
412 } else {
413 foreach($this->data as $item) {
414 if(isset($item[$this->key_field]) && !$test($item[$this->key_field]))
415 return ($this->assoc = true);
416 }
417 }
418 return ($this->assoc = false);
419 }
420
421 /**
422 * Returns the number of data items in a dataset
423 * If $dataset is -1, returns number of items across all datasets
424 */
425 public function itemsCount($dataset = 0)
426 {
427 if($dataset == -1)
428 return count($this->data);
429
430 if(!isset($this->dataset_fields[$dataset]))
431 return 0;
432 $count = 0;
433 $key = $this->dataset_fields[$dataset];
434 foreach($this->data as $item)
435 if(isset($item[$key]))
436 ++$count;
437 return $count;
438 }
439
440 /**
441 * Returns TRUE if there are repeated keys
442 * (also culls items without key field)
443 */
444 public function repeatedKeys()
445 {
446 if($this->repeated_keys !== null)
447 return $this->repeated_keys;
448 if($this->key_field === null)
449 return false;
450 $keys = [];
451 foreach($this->data as $k => $item) {
452 if(!isset($item[$this->key_field]))
453 unset($this->data[$k]);
454 else
455 $keys[] = $item[$this->key_field];
456 }
457 $len = count($keys);
458 $ukeys = array_unique($keys);
459 return ($this->repeated_keys = ($len != count($ukeys)));
460 }
461
462 /**
463 * Returns the min and max sum values for some datasets
464 */
465 public function getMinMaxSumValues($start = 0, $end = null)
466 {
467 if($start >= $this->datasets || ($end !== null && $end >= $this->datasets))
468 throw new \Exception('Dataset not found');
469
470 if($end === null)
471 $end = $this->datasets - 1;
472 return $this->getMinMaxSumValuesFor(range($start, $end));
473 }
474
475 /**
476 * Returns the min/max sum values for an array of datasets
477 */
478 public function getMinMaxSumValuesFor($datasets)
479 {
480 $min_stack = [];
481 $max_stack = [];
482
483 foreach($this->data as $item) {
484 $smin = $smax = 0;
485 foreach($datasets as $dataset) {
486 $vfield = $this->dataset_fields[$dataset];
487 if(!isset($item[$vfield]))
488 continue;
489 $value = $item[$vfield];
490 if($value !== null && !is_numeric($value))
491 throw new \Exception('Non-numeric value');
492 if($value > 0)
493 $smax += $value;
494 else
495 $smin += $value;
496 }
497 $min_stack[] = $smin;
498 $max_stack[] = $smax;
499 }
500 if(!count($min_stack))
501 return [null, null];
502 return [min($min_stack), max($max_stack)];
503 }
504
505 /**
506 * Returns a structured data item
507 */
508 public function getItem($index, $dataset = 0)
509 {
510 $index = (int)round($index);
511 if(!isset($this->data[$index]))
512 return null;
513
514 $key = $this->key_field === null ? $index : null;
515 return new StructuredDataItem($this->data[$index],
516 $this->structure, $dataset, $key);
517 }
518
519 /**
520 * Returns TRUE if the data field exists, setting $value
521 */
522 public function getData($index, $name, &$value)
523 {
524 $index = (int)round($index);
525 if(!isset($this->structure[$name]) || !isset($this->data[$index]))
526 return false;
527
528 $item = $this->data[$index];
529 if($item === null)
530 return false;
531
532 $field = $this->structure[$name];
533 if(!is_array($field)) {
534 if(!isset($item[$field]))
535 return false;
536 $value = $item[$field];
537 return true;
538 }
539
540 // handle array fields
541 $vals = [];
542 $count = 0;
543 foreach($field as $f) {
544 $v = null;
545 if(isset($item[$f])) {
546 $v = $item[$f];
547 ++$count;
548 }
549 $vals[] = $v;
550 }
551
552 // return true if any fields are set
553 if($count > 0) {
554 $value = $vals;
555 return true;
556 }
557 return false;
558 }
559
560 /**
561 * Transforms the keys using a callback function
562 */
563 public function rekey($callback)
564 {
565 // use a tab character as the new key name
566 $rekey_name = "\t";
567 $invalid = 0;
568 foreach($this->data as $index => $item) {
569 $key = $item[$this->key_field];
570 $new_key = call_user_func($callback, $key);
571
572 // if the callback returns NULL, NULL the data item
573 if($new_key === null) {
574 $this->data[$index] = [$rekey_name => null];
575 ++$invalid;
576 continue;
577 }
578
579 $this->data[$index][$rekey_name] = $new_key;
580 }
581
582 // if too many invalid, probably a format error
583 if(count($this->data) && $invalid / count($this->data) > 0.05)
584 return false;
585
586 // forget previous min/max and assoc settings
587 $this->min_keys = [];
588 $this->max_keys = [];
589 $this->assoc = null;
590 $this->key_field = $this->structure['key'] = $rekey_name;
591 return true;
592 }
593
594 /**
595 * Transforms values using a callback function
596 */
597 public function revalue($datasets, $callback)
598 {
599 // $datasets is the number in the output
600 $new_values = [];
601 for($i = 0; $i < $datasets; ++$i)
602 $new_values[] = uniqid('val', false);
603
604 // call the callback for each row of values
605 foreach($this->data as $index => $item) {
606 $row = [];
607 foreach($this->dataset_fields as $field)
608 $row[] = isset($item[$field]) ? $item[$field] : null;
609
610 // get updated row
611 $new_row = call_user_func($callback, $item[$this->key_field], $row);
612 for($i = 0; $i < $datasets; ++$i)
613 $this->data[$index][$new_values[$i]] = isset($new_row[$i]) ?
614 $new_row[$i] : null;
615 }
616
617 // update structure with new value fields
618 $this->structure['value'] = $new_values;
619 $this->dataset_fields = $new_values;
620 $this->datasets = $datasets;
621
622 // forget previous min/max
623 $this->min_values = [];
624 $this->max_values = [];
625 return true;
626 }
627
628 /**
629 * Transform a row using a callback
630 */
631 public function transform($callback, $dataset = 0)
632 {
633 // build the list of fields for this dataset
634 $tstruct = [];
635 foreach($this->structure as $field => $element) {
636 if(is_array($element) && isset($element[$dataset]))
637 $tstruct[$field] = $element[$dataset];
638 else
639 $tstruct[$field] = $element;
640 }
641
642 foreach($this->data as &$row) {
643
644 // use a POD class for the data item
645 $item = new \stdClass;
646
647 foreach($tstruct as $field => $key)
648 if(isset($row[$key]))
649 $item->$field = $row[$key];
650
651 // if the callback returns null, there is no change
652 $item = $callback($item);
653 if($item === null)
654 continue;
655
656 foreach($tstruct as $field => $key)
657 if(isset($item->$field))
658 $row[$key] = $item->$field;
659 }
660 }
661
662 /**
663 * Adds a field to the structure, if it doesn't already exist
664 */
665 public function addField($name)
666 {
667 if(!array_key_exists($name, $this->structure))
668 $this->structure[$name] = uniqid($name, true);
669 }
670
671 /**
672 * Adds a row to the data at end or at $insert position
673 */
674 public function addRow(array $row, $insert = null)
675 {
676 if($insert === null || $insert >= count($this->data)) {
677 $this->data[] = $row;
678 return;
679 }
680 array_splice($this->data, $insert, 0, [$row]);
681 }
682
683 /**
684 * Returns the structure array
685 */
686 public function getStructure()
687 {
688 return $this->structure;
689 }
690
691 /**
692 * Sorts the data by the selected dataset values
693 */
694 public function sort($dataset, $reverse)
695 {
696 $key = $this->dataset_fields[$dataset];
697 $field_sort = new FieldSort($key, $reverse);
698 $field_sort->sort($this->data);
699 }
700 }
701
702