1<?php 2 3namespace CpChart; 4 5use Exception; 6use RuntimeException; 7 8/** 9 * Data - class to manipulate data arrays 10 * 11 * Version : 2.1.4 12 * Made by : Jean-Damien POGOLOTTI 13 * Last Update : 19/01/2014 14 * 15 * This file can be distributed under the license you can find at : 16 * 17 * http://www.pchart.net/license 18 * 19 * You can find the whole class documentation on the pChart web site. 20 */ 21class Data 22{ 23 /** 24 * @var array 25 */ 26 public $Data = []; 27 28 /** 29 * @var array 30 */ 31 public $Palette = [ 32 "0" => ["R" => 188, "G" => 224, "B" => 46, "Alpha" => 100], 33 "1" => ["R" => 224, "G" => 100, "B" => 46, "Alpha" => 100], 34 "2" => ["R" => 224, "G" => 214, "B" => 46, "Alpha" => 100], 35 "3" => ["R" => 46, "G" => 151, "B" => 224, "Alpha" => 100], 36 "4" => ["R" => 176, "G" => 46, "B" => 224, "Alpha" => 100], 37 "5" => ["R" => 224, "G" => 46, "B" => 117, "Alpha" => 100], 38 "6" => ["R" => 92, "G" => 224, "B" => 46, "Alpha" => 100], 39 "7" => ["R" => 224, "G" => 176, "B" => 46, "Alpha" => 100] 40 ]; 41 42 public function __construct() 43 { 44 $this->Data["XAxisDisplay"] = AXIS_FORMAT_DEFAULT; 45 $this->Data["XAxisFormat"] = null; 46 $this->Data["XAxisName"] = null; 47 $this->Data["XAxisUnit"] = null; 48 $this->Data["Abscissa"] = null; 49 $this->Data["AbsicssaPosition"] = AXIS_POSITION_BOTTOM; 50 51 $this->Data["Axis"][0]["Display"] = AXIS_FORMAT_DEFAULT; 52 $this->Data["Axis"][0]["Position"] = AXIS_POSITION_LEFT; 53 $this->Data["Axis"][0]["Identity"] = AXIS_Y; 54 } 55 56 /** 57 * Add a single point or an array to the given serie 58 * @param mixed $Values 59 * @param string $SerieName 60 * @return int 61 */ 62 public function addPoints($Values, $SerieName = "Serie1") 63 { 64 if (!isset($this->Data["Series"][$SerieName])) { 65 $this->initialise($SerieName); 66 } 67 if (is_array($Values)) { 68 foreach ($Values as $Value) { 69 $this->Data["Series"][$SerieName]["Data"][] = $Value; 70 } 71 } else { 72 $this->Data["Series"][$SerieName]["Data"][] = $Values; 73 } 74 75 if ($Values != VOID) { 76 $StrippedData = $this->stripVOID($this->Data["Series"][$SerieName]["Data"]); 77 if (empty($StrippedData)) { 78 $this->Data["Series"][$SerieName]["Max"] = 0; 79 $this->Data["Series"][$SerieName]["Min"] = 0; 80 return 0; 81 } 82 $this->Data["Series"][$SerieName]["Max"] = max($StrippedData); 83 $this->Data["Series"][$SerieName]["Min"] = min($StrippedData); 84 } 85 } 86 87 /** 88 * Strip VOID values 89 * @param mixed $Values 90 * @return array 91 */ 92 public function stripVOID($Values) 93 { 94 if (!is_array($Values)) { 95 return []; 96 } 97 $Result = []; 98 foreach ($Values as $Value) { 99 if ($Value != VOID) { 100 $Result[] = $Value; 101 } 102 } 103 return $Result; 104 } 105 106 /** 107 * Return the number of values contained in a given serie 108 * @param string $Serie 109 * @return int 110 */ 111 public function getSerieCount($Serie) 112 { 113 if (isset($this->Data["Series"][$Serie]["Data"])) { 114 return sizeof($this->Data["Series"][$Serie]["Data"]); 115 } 116 return 0; 117 } 118 119 /** 120 * Remove a serie from the pData object 121 * @param mixed $Series 122 */ 123 public function removeSerie($Series) 124 { 125 if (!is_array($Series)) { 126 $Series = $this->convertToArray($Series); 127 } 128 foreach ($Series as $Serie) { 129 if (isset($this->Data["Series"][$Serie])) { 130 unset($this->Data["Series"][$Serie]); 131 } 132 } 133 } 134 135 /** 136 * Return a value from given serie & index 137 * @param string $Serie 138 * @param int $Index 139 * @return mixed 140 */ 141 public function getValueAt($Serie, $Index = 0) 142 { 143 if (isset($this->Data["Series"][$Serie]["Data"][$Index])) { 144 return $this->Data["Series"][$Serie]["Data"][$Index]; 145 } 146 return null; 147 } 148 149 /** 150 * Return the values array 151 * @param string $Serie 152 * @return mixed 153 */ 154 public function getValues($Serie) 155 { 156 if (isset($this->Data["Series"][$Serie]["Data"])) { 157 return $this->Data["Series"][$Serie]["Data"]; 158 } 159 return null; 160 } 161 162 /** 163 * Reverse the values in the given serie 164 * @param mixed $Series 165 */ 166 public function reverseSerie($Series) 167 { 168 if (!is_array($Series)) { 169 $Series = $this->convertToArray($Series); 170 } 171 foreach ($Series as $Serie) { 172 if (isset($this->Data["Series"][$Serie]["Data"])) { 173 $this->Data["Series"][$Serie]["Data"] = array_reverse( 174 $this->Data["Series"][$Serie]["Data"] 175 ); 176 } 177 } 178 } 179 180 /** 181 * Return the sum of the serie values 182 * @param string $Serie 183 * @return int|null 184 */ 185 public function getSum($Serie) 186 { 187 if (isset($this->Data["Series"][$Serie])) { 188 return array_sum($this->Data["Series"][$Serie]["Data"]); 189 } 190 return null; 191 } 192 193 /** 194 * Return the max value of a given serie 195 * @param string $Serie 196 * @return mixed 197 */ 198 public function getMax($Serie) 199 { 200 if (isset($this->Data["Series"][$Serie]["Max"])) { 201 return $this->Data["Series"][$Serie]["Max"]; 202 } 203 return null; 204 } 205 206 /** 207 * @param string $Serie 208 * @return mixed 209 */ 210 public function getMin($Serie) 211 { 212 if (isset($this->Data["Series"][$Serie]["Min"])) { 213 return $this->Data["Series"][$Serie]["Min"]; 214 } 215 return null; 216 } 217 218 /** 219 * Set the description of a given serie 220 * @param mixed $Series 221 * @param string $Shape 222 */ 223 public function setSerieShape($Series, $Shape = SERIE_SHAPE_FILLEDCIRCLE) 224 { 225 if (!is_array($Series)) { 226 $Series = $this->convertToArray($Series); 227 } 228 foreach ($Series as $Serie) { 229 if (isset($this->Data["Series"][$Serie])) { 230 $this->Data["Series"][$Serie]["Shape"] = $Shape; 231 } 232 } 233 } 234 235 /** 236 * Set the description of a given serie 237 * @param string|array $Series 238 * @param string $Description 239 */ 240 public function setSerieDescription($Series, $Description = "My serie") 241 { 242 if (!is_array($Series)) { 243 $Series = $this->convertToArray($Series); 244 } 245 foreach ($Series as $Serie) { 246 if (isset($this->Data["Series"][$Serie])) { 247 $this->Data["Series"][$Serie]["Description"] = $Description; 248 } 249 } 250 } 251 252 /** 253 * Set a serie as "drawable" while calling a rendering public function 254 * @param string|array $Series 255 * @param boolean $Drawable 256 */ 257 public function setSerieDrawable($Series, $Drawable = true) 258 { 259 if (!is_array($Series)) { 260 $Series = $this->convertToArray($Series); 261 } 262 foreach ($Series as $Serie) { 263 if (isset($this->Data["Series"][$Serie])) { 264 $this->Data["Series"][$Serie]["isDrawable"] = $Drawable; 265 } 266 } 267 } 268 269 /** 270 * Set the icon associated to a given serie 271 * @param mixed $Series 272 * @param mixed $Picture 273 */ 274 public function setSeriePicture($Series, $Picture = null) 275 { 276 if (!is_array($Series)) { 277 $Series = $this->convertToArray($Series); 278 } 279 foreach ($Series as $Serie) { 280 if (isset($this->Data["Series"][$Serie])) { 281 $this->Data["Series"][$Serie]["Picture"] = $Picture; 282 } 283 } 284 } 285 286 /** 287 * Set the name of the X Axis 288 * @param string $Name 289 */ 290 public function setXAxisName($Name) 291 { 292 $this->Data["XAxisName"] = $Name; 293 } 294 295 /** 296 * Set the display mode of the X Axis 297 * @param int $Mode 298 * @param array $Format 299 */ 300 public function setXAxisDisplay($Mode, $Format = null) 301 { 302 $this->Data["XAxisDisplay"] = $Mode; 303 $this->Data["XAxisFormat"] = $Format; 304 } 305 306 /** 307 * Set the unit that will be displayed on the X axis 308 * @param string $Unit 309 */ 310 public function setXAxisUnit($Unit) 311 { 312 $this->Data["XAxisUnit"] = $Unit; 313 } 314 315 /** 316 * Set the serie that will be used as abscissa 317 * @param string $Serie 318 */ 319 public function setAbscissa($Serie) 320 { 321 if (isset($this->Data["Series"][$Serie])) { 322 $this->Data["Abscissa"] = $Serie; 323 } 324 } 325 326 /** 327 * Set the position of the abscissa axis 328 * @param int $Position 329 */ 330 public function setAbsicssaPosition($Position = AXIS_POSITION_BOTTOM) 331 { 332 $this->Data["AbsicssaPosition"] = $Position; 333 } 334 335 /** 336 * Set the name of the abscissa axis 337 * @param string $Name 338 */ 339 public function setAbscissaName($Name) 340 { 341 $this->Data["AbscissaName"] = $Name; 342 } 343 344 /** 345 * Create a scatter group specified in X and Y data series 346 * @param string $SerieX 347 * @param string $SerieY 348 * @param int $ID 349 */ 350 public function setScatterSerie($SerieX, $SerieY, $ID = 0) 351 { 352 if (isset($this->Data["Series"][$SerieX]) && isset($this->Data["Series"][$SerieY])) { 353 $this->initScatterSerie($ID); 354 $this->Data["ScatterSeries"][$ID]["X"] = $SerieX; 355 $this->Data["ScatterSeries"][$ID]["Y"] = $SerieY; 356 } 357 } 358 359 /** 360 * Set the shape of a given sctatter serie 361 * @param int $ID 362 * @param int $Shape 363 */ 364 public function setScatterSerieShape($ID, $Shape = SERIE_SHAPE_FILLEDCIRCLE) 365 { 366 if (isset($this->Data["ScatterSeries"][$ID])) { 367 $this->Data["ScatterSeries"][$ID]["Shape"] = $Shape; 368 } 369 } 370 371 /** 372 * Set the description of a given scatter serie 373 * @param int $ID 374 * @param string $Description 375 */ 376 public function setScatterSerieDescription($ID, $Description = "My serie") 377 { 378 if (isset($this->Data["ScatterSeries"][$ID])) { 379 $this->Data["ScatterSeries"][$ID]["Description"] = $Description; 380 } 381 } 382 383 /** 384 * Set the icon associated to a given scatter serie 385 * @param int $ID 386 * @param mixed $Picture 387 */ 388 public function setScatterSeriePicture($ID, $Picture = null) 389 { 390 if (isset($this->Data["ScatterSeries"][$ID])) { 391 $this->Data["ScatterSeries"][$ID]["Picture"] = $Picture; 392 } 393 } 394 395 /** 396 * Set a scatter serie as "drawable" while calling a rendering public function 397 * @param int $ID 398 * @param boolean $Drawable 399 */ 400 public function setScatterSerieDrawable($ID, $Drawable = true) 401 { 402 if (isset($this->Data["ScatterSeries"][$ID])) { 403 $this->Data["ScatterSeries"][$ID]["isDrawable"] = $Drawable; 404 } 405 } 406 407 /** 408 * Define if a scatter serie should be draw with ticks 409 * @param int $ID 410 * @param int $Width 411 */ 412 public function setScatterSerieTicks($ID, $Width = 0) 413 { 414 if (isset($this->Data["ScatterSeries"][$ID])) { 415 $this->Data["ScatterSeries"][$ID]["Ticks"] = $Width; 416 } 417 } 418 419 /** 420 * Define if a scatter serie should be draw with a special weight 421 * @param int $ID 422 * @param int $Weight 423 */ 424 public function setScatterSerieWeight($ID, $Weight = 0) 425 { 426 if (isset($this->Data["ScatterSeries"][$ID])) { 427 $this->Data["ScatterSeries"][$ID]["Weight"] = $Weight; 428 } 429 } 430 431 /** 432 * Associate a color to a scatter serie 433 * @param int $ID 434 * @param array $Format 435 */ 436 public function setScatterSerieColor($ID, array $Format) 437 { 438 $R = isset($Format["R"]) ? $Format["R"] : 0; 439 $G = isset($Format["G"]) ? $Format["G"] : 0; 440 $B = isset($Format["B"]) ? $Format["B"] : 0; 441 $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100; 442 443 if (isset($this->Data["ScatterSeries"][$ID])) { 444 $this->Data["ScatterSeries"][$ID]["Color"]["R"] = $R; 445 $this->Data["ScatterSeries"][$ID]["Color"]["G"] = $G; 446 $this->Data["ScatterSeries"][$ID]["Color"]["B"] = $B; 447 $this->Data["ScatterSeries"][$ID]["Color"]["Alpha"] = $Alpha; 448 } 449 } 450 451 /** 452 * Compute the series limits for an individual and global point of view 453 * @return array 454 */ 455 public function limits() 456 { 457 $GlobalMin = ABSOLUTE_MAX; 458 $GlobalMax = ABSOLUTE_MIN; 459 460 foreach (array_keys($this->Data["Series"]) as $Key) { 461 if ($this->Data["Abscissa"] != $Key 462 && $this->Data["Series"][$Key]["isDrawable"] == true 463 ) { 464 if ($GlobalMin > $this->Data["Series"][$Key]["Min"]) { 465 $GlobalMin = $this->Data["Series"][$Key]["Min"]; 466 } 467 if ($GlobalMax < $this->Data["Series"][$Key]["Max"]) { 468 $GlobalMax = $this->Data["Series"][$Key]["Max"]; 469 } 470 } 471 } 472 $this->Data["Min"] = $GlobalMin; 473 $this->Data["Max"] = $GlobalMax; 474 475 return [$GlobalMin, $GlobalMax]; 476 } 477 478 /** 479 * Mark all series as drawable 480 */ 481 public function drawAll() 482 { 483 foreach (array_keys($this->Data["Series"]) as $Key) { 484 if ($this->Data["Abscissa"] != $Key) { 485 $this->Data["Series"][$Key]["isDrawable"] = true; 486 } 487 } 488 } 489 490 /** 491 * Return the average value of the given serie 492 * @param string $Serie 493 * @return int|null 494 */ 495 public function getSerieAverage($Serie) 496 { 497 if (isset($this->Data["Series"][$Serie])) { 498 $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]); 499 return array_sum($SerieData) / sizeof($SerieData); 500 } 501 502 return null; 503 } 504 505 /** 506 * Return the geometric mean of the given serie 507 * @param string $Serie 508 * @return int|null 509 */ 510 public function getGeometricMean($Serie) 511 { 512 if (isset($this->Data["Series"][$Serie])) { 513 $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]); 514 $Seriesum = 1; 515 foreach ($SerieData as $Value) { 516 $Seriesum = $Seriesum * $Value; 517 } 518 return pow($Seriesum, 1 / sizeof($SerieData)); 519 } 520 521 return null; 522 } 523 524 /** 525 * Return the harmonic mean of the given serie 526 * @param string $Serie 527 * @return int|null 528 */ 529 public function getHarmonicMean($Serie) 530 { 531 if (isset($this->Data["Series"][$Serie])) { 532 $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]); 533 $Seriesum = 0; 534 foreach ($SerieData as $Value) { 535 $Seriesum = $Seriesum + 1 / $Value; 536 } 537 return sizeof($SerieData) / $Seriesum; 538 } 539 540 return null; 541 } 542 543 /** 544 * Return the standard deviation of the given serie 545 * @param string $Serie 546 * @return double|null 547 */ 548 public function getStandardDeviation($Serie) 549 { 550 if (isset($this->Data["Series"][$Serie])) { 551 $Average = $this->getSerieAverage($Serie); 552 $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]); 553 554 $DeviationSum = 0; 555 foreach ($SerieData as $Key => $Value) { 556 $DeviationSum = $DeviationSum + ($Value - $Average) * ($Value - $Average); 557 } 558 return sqrt($DeviationSum / count($SerieData)); 559 } 560 return null; 561 } 562 563 /** 564 * Return the Coefficient of variation of the given serie 565 * @param string $Serie 566 * @return float|null 567 */ 568 public function getCoefficientOfVariation($Serie) 569 { 570 if (isset($this->Data["Series"][$Serie])) { 571 $Average = $this->getSerieAverage($Serie); 572 $StandardDeviation = $this->getStandardDeviation($Serie); 573 574 if ($StandardDeviation != 0) { 575 return $StandardDeviation / $Average; 576 } 577 } 578 return null; 579 } 580 581 /** 582 * Return the median value of the given serie 583 * @param string $Serie 584 * @return int|float 585 */ 586 public function getSerieMedian($Serie) 587 { 588 if (isset($this->Data["Series"][$Serie])) { 589 $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]); 590 sort($SerieData); 591 $SerieCenter = floor(sizeof($SerieData) / 2); 592 593 if (isset($SerieData[$SerieCenter])) { 594 return $SerieData[$SerieCenter]; 595 } 596 } 597 return null; 598 } 599 600 /** 601 * Return the x th percentil of the given serie 602 * @param string $Serie 603 * @param int $Percentil 604 * @return int|float| null 605 */ 606 public function getSeriePercentile($Serie = "Serie1", $Percentil = 95) 607 { 608 if (!isset($this->Data["Series"][$Serie]["Data"])) { 609 return null; 610 } 611 612 $Values = count($this->Data["Series"][$Serie]["Data"]) - 1; 613 if ($Values < 0) { 614 $Values = 0; 615 } 616 617 $PercentilID = floor(($Values / 100) * $Percentil + .5); 618 $SortedValues = $this->Data["Series"][$Serie]["Data"]; 619 sort($SortedValues); 620 621 if (is_numeric($SortedValues[$PercentilID])) { 622 return $SortedValues[$PercentilID]; 623 } 624 return null; 625 } 626 627 /** 628 * Add random values to a given serie 629 * @param string $SerieName 630 * @param array $Options 631 */ 632 public function addRandomValues($SerieName = "Serie1", array $Options = []) 633 { 634 $Values = isset($Options["Values"]) ? $Options["Values"] : 20; 635 $Min = isset($Options["Min"]) ? $Options["Min"] : 0; 636 $Max = isset($Options["Max"]) ? $Options["Max"] : 100; 637 $withFloat = isset($Options["withFloat"]) ? $Options["withFloat"] : false; 638 639 for ($i = 0; $i <= $Values; $i++) { 640 $Value = $withFloat ? rand($Min * 100, $Max * 100) / 100 : rand($Min, $Max); 641 $this->addPoints($Value, $SerieName); 642 } 643 } 644 645 /** 646 * Test if we have valid data 647 * @return boolean|null 648 */ 649 public function containsData() 650 { 651 if (!isset($this->Data["Series"])) { 652 return false; 653 } 654 655 foreach (array_keys($this->Data["Series"]) as $Key) { 656 if ($this->Data["Abscissa"] != $Key 657 && $this->Data["Series"][$Key]["isDrawable"] == true 658 ) { 659 return true; 660 } 661 } 662 663 return null; 664 } 665 666 /** 667 * Set the display mode of an Axis 668 * @param int $AxisID 669 * @param int $Mode 670 * @param array $Format 671 */ 672 public function setAxisDisplay($AxisID, $Mode = AXIS_FORMAT_DEFAULT, $Format = null) 673 { 674 if (isset($this->Data["Axis"][$AxisID])) { 675 $this->Data["Axis"][$AxisID]["Display"] = $Mode; 676 if ($Format != null) { 677 $this->Data["Axis"][$AxisID]["Format"] = $Format; 678 } 679 } 680 } 681 682 /** 683 * Set the position of an Axis 684 * @param int $AxisID 685 * @param int $Position 686 */ 687 public function setAxisPosition($AxisID, $Position = AXIS_POSITION_LEFT) 688 { 689 if (isset($this->Data["Axis"][$AxisID])) { 690 $this->Data["Axis"][$AxisID]["Position"] = $Position; 691 } 692 } 693 694 /** 695 * Associate an unit to an axis 696 * @param int $AxisID 697 * @param string $Unit 698 */ 699 public function setAxisUnit($AxisID, $Unit) 700 { 701 if (isset($this->Data["Axis"][$AxisID])) { 702 $this->Data["Axis"][$AxisID]["Unit"] = $Unit; 703 } 704 } 705 706 /** 707 * Associate a name to an axis 708 * @param int $AxisID 709 * @param string $Name 710 */ 711 public function setAxisName($AxisID, $Name) 712 { 713 if (isset($this->Data["Axis"][$AxisID])) { 714 $this->Data["Axis"][$AxisID]["Name"] = $Name; 715 } 716 } 717 718 /** 719 * Associate a color to an axis 720 * @param int $AxisID 721 * @param array $Format 722 */ 723 public function setAxisColor($AxisID, array $Format) 724 { 725 $R = isset($Format["R"]) ? $Format["R"] : 0; 726 $G = isset($Format["G"]) ? $Format["G"] : 0; 727 $B = isset($Format["B"]) ? $Format["B"] : 0; 728 $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100; 729 730 if (isset($this->Data["Axis"][$AxisID])) { 731 $this->Data["Axis"][$AxisID]["Color"]["R"] = $R; 732 $this->Data["Axis"][$AxisID]["Color"]["G"] = $G; 733 $this->Data["Axis"][$AxisID]["Color"]["B"] = $B; 734 $this->Data["Axis"][$AxisID]["Color"]["Alpha"] = $Alpha; 735 } 736 } 737 738 /** 739 * Design an axis as X or Y member 740 * @param int $AxisID 741 * @param int $Identity 742 */ 743 public function setAxisXY($AxisID, $Identity = AXIS_Y) 744 { 745 if (isset($this->Data["Axis"][$AxisID])) { 746 $this->Data["Axis"][$AxisID]["Identity"] = $Identity; 747 } 748 } 749 750 /** 751 * Associate one data serie with one axis 752 * @param mixed $Series 753 * @param int $AxisID 754 */ 755 public function setSerieOnAxis($Series, $AxisID) 756 { 757 if (!is_array($Series)) { 758 $Series = $this->convertToArray($Series); 759 } 760 foreach ($Series as $Serie) { 761 $PreviousAxis = $this->Data["Series"][$Serie]["Axis"]; 762 763 /* Create missing axis */ 764 if (!isset($this->Data["Axis"][$AxisID])) { 765 $this->Data["Axis"][$AxisID]["Position"] = AXIS_POSITION_LEFT; 766 $this->Data["Axis"][$AxisID]["Identity"] = AXIS_Y; 767 } 768 769 $this->Data["Series"][$Serie]["Axis"] = $AxisID; 770 771 /* Cleanup unused axis */ 772 $Found = false; 773 foreach ($this->Data["Series"] as $Values) { 774 if ($Values["Axis"] == $PreviousAxis) { 775 $Found = true; 776 } 777 } 778 if (!$Found) { 779 unset($this->Data["Axis"][$PreviousAxis]); 780 } 781 } 782 } 783 784 /** 785 * Define if a serie should be draw with ticks 786 * @param mixed $Series 787 * @param int $Width 788 */ 789 public function setSerieTicks($Series, $Width = 0) 790 { 791 if (!is_array($Series)) { 792 $Series = $this->convertToArray($Series); 793 } 794 foreach ($Series as $Serie) { 795 if (isset($this->Data["Series"][$Serie])) { 796 $this->Data["Series"][$Serie]["Ticks"] = $Width; 797 } 798 } 799 } 800 801 /** 802 * Define if a serie should be draw with a special weight 803 * @param mixed $Series 804 * @param int $Weight 805 */ 806 public function setSerieWeight($Series, $Weight = 0) 807 { 808 if (!is_array($Series)) { 809 $Series = $this->convertToArray($Series); 810 } 811 foreach ($Series as $Serie) { 812 if (isset($this->Data["Series"][$Serie])) { 813 $this->Data["Series"][$Serie]["Weight"] = $Weight; 814 } 815 } 816 } 817 818 /** 819 * Returns the palette of the given serie 820 * @param type $Serie 821 * @return null 822 */ 823 public function getSeriePalette($Serie) 824 { 825 if (!isset($this->Data["Series"][$Serie])) { 826 return null; 827 } 828 829 $Result = []; 830 $Result["R"] = $this->Data["Series"][$Serie]["Color"]["R"]; 831 $Result["G"] = $this->Data["Series"][$Serie]["Color"]["G"]; 832 $Result["B"] = $this->Data["Series"][$Serie]["Color"]["B"]; 833 $Result["Alpha"] = $this->Data["Series"][$Serie]["Color"]["Alpha"]; 834 835 return $Result; 836 } 837 838 /** 839 * Set the color of one serie 840 * @param mixed $Series 841 * @param array $Format 842 */ 843 public function setPalette($Series, array $Format = []) 844 { 845 if (!is_array($Series)) { 846 $Series = $this->convertToArray($Series); 847 } 848 849 foreach ($Series as $Key => $Serie) { 850 $R = isset($Format["R"]) ? $Format["R"] : 0; 851 $G = isset($Format["G"]) ? $Format["G"] : 0; 852 $B = isset($Format["B"]) ? $Format["B"] : 0; 853 $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100; 854 855 if (isset($this->Data["Series"][$Serie])) { 856 $OldR = $this->Data["Series"][$Serie]["Color"]["R"]; 857 $OldG = $this->Data["Series"][$Serie]["Color"]["G"]; 858 $OldB = $this->Data["Series"][$Serie]["Color"]["B"]; 859 $this->Data["Series"][$Serie]["Color"]["R"] = $R; 860 $this->Data["Series"][$Serie]["Color"]["G"] = $G; 861 $this->Data["Series"][$Serie]["Color"]["B"] = $B; 862 $this->Data["Series"][$Serie]["Color"]["Alpha"] = $Alpha; 863 864 /* Do reverse processing on the internal palette array */ 865 foreach ($this->Palette as $Key => $Value) { 866 if ($Value["R"] == $OldR && $Value["G"] == $OldG && $Value["B"] == $OldB) { 867 $this->Palette[$Key]["R"] = $R; 868 $this->Palette[$Key]["G"] = $G; 869 $this->Palette[$Key]["B"] = $B; 870 $this->Palette[$Key]["Alpha"] = $Alpha; 871 } 872 } 873 } 874 } 875 } 876 877 /** 878 * Load a palette file 879 * @param string $FileName 880 * @param boolean $Overwrite 881 * @throws Exception 882 */ 883 public function loadPalette($FileName, $Overwrite = false) 884 { 885 $path = file_exists($FileName) 886 ? $FileName 887 : sprintf('%s/../resources/palettes/%s', __DIR__, ltrim($FileName, '/')) 888 ; 889 890 $fileHandle = @fopen($path, "r"); 891 if (!$fileHandle) { 892 throw new Exception(sprintf( 893 'The requested palette "%s" was not found at path "%s"!', 894 $FileName, 895 $path 896 )); 897 } 898 899 if ($Overwrite) { 900 $this->Palette = []; 901 } 902 903 while (!feof($fileHandle)) { 904 $line = fgets($fileHandle, 4096); 905 if (false === $line) { 906 continue; 907 } 908 $row = explode(',', $line); 909 if (empty($row)) { 910 continue; 911 } 912 if (count($row) !== 4) { 913 throw new RuntimeException(sprintf( 914 'A palette row must supply R, G, B and Alpha components, %s given!', 915 var_export($row, true) 916 )); 917 } 918 list($R, $G, $B, $Alpha) = $row; 919 $ID = count($this->Palette); 920 $this->Palette[$ID] = [ 921 "R" => trim($R), 922 "G" => trim($G), 923 "B" => trim($B), 924 "Alpha" => trim($Alpha) 925 ]; 926 } 927 fclose($fileHandle); 928 929 /* Apply changes to current series */ 930 $ID = 0; 931 if (isset($this->Data["Series"])) { 932 foreach ($this->Data["Series"] as $Key => $Value) { 933 if (!isset($this->Palette[$ID])) { 934 $this->Data["Series"][$Key]["Color"] = ["R" => 0, "G" => 0, "B" => 0, "Alpha" => 0]; 935 } else { 936 $this->Data["Series"][$Key]["Color"] = $this->Palette[$ID]; 937 } 938 $ID++; 939 } 940 } 941 } 942 943 /** 944 * Initialise a given scatter serie 945 * @param int $ID 946 * @return null 947 */ 948 public function initScatterSerie($ID) 949 { 950 if (isset($this->Data["ScatterSeries"][$ID])) { 951 return null; 952 } 953 954 $this->Data["ScatterSeries"][$ID]["Description"] = "Scatter " . $ID; 955 $this->Data["ScatterSeries"][$ID]["isDrawable"] = true; 956 $this->Data["ScatterSeries"][$ID]["Picture"] = null; 957 $this->Data["ScatterSeries"][$ID]["Ticks"] = 0; 958 $this->Data["ScatterSeries"][$ID]["Weight"] = 0; 959 960 if (isset($this->Palette[$ID])) { 961 $this->Data["ScatterSeries"][$ID]["Color"] = $this->Palette[$ID]; 962 } else { 963 $this->Data["ScatterSeries"][$ID]["Color"]["R"] = rand(0, 255); 964 $this->Data["ScatterSeries"][$ID]["Color"]["G"] = rand(0, 255); 965 $this->Data["ScatterSeries"][$ID]["Color"]["B"] = rand(0, 255); 966 $this->Data["ScatterSeries"][$ID]["Color"]["Alpha"] = 100; 967 } 968 } 969 970 /** 971 * Initialise a given serie 972 * @param string $Serie 973 */ 974 public function initialise($Serie) 975 { 976 $ID = 0; 977 if (isset($this->Data["Series"])) { 978 $ID = count($this->Data["Series"]); 979 } 980 981 $this->Data["Series"][$Serie]["Description"] = $Serie; 982 $this->Data["Series"][$Serie]["isDrawable"] = true; 983 $this->Data["Series"][$Serie]["Picture"] = null; 984 $this->Data["Series"][$Serie]["Max"] = null; 985 $this->Data["Series"][$Serie]["Min"] = null; 986 $this->Data["Series"][$Serie]["Axis"] = 0; 987 $this->Data["Series"][$Serie]["Ticks"] = 0; 988 $this->Data["Series"][$Serie]["Weight"] = 0; 989 $this->Data["Series"][$Serie]["Shape"] = SERIE_SHAPE_FILLEDCIRCLE; 990 991 if (isset($this->Palette[$ID])) { 992 $this->Data["Series"][$Serie]["Color"] = $this->Palette[$ID]; 993 } else { 994 $this->Data["Series"][$Serie]["Color"]["R"] = rand(0, 255); 995 $this->Data["Series"][$Serie]["Color"]["G"] = rand(0, 255); 996 $this->Data["Series"][$Serie]["Color"]["B"] = rand(0, 255); 997 $this->Data["Series"][$Serie]["Color"]["Alpha"] = 100; 998 } 999 $this->Data["Series"][$Serie]["Data"] = []; 1000 } 1001 1002 /** 1003 * @param int $NormalizationFactor 1004 * @param mixed $UnitChange 1005 * @param int $Round 1006 */ 1007 public function normalize($NormalizationFactor = 100, $UnitChange = null, $Round = 1) 1008 { 1009 $Abscissa = $this->Data["Abscissa"]; 1010 1011 $SelectedSeries = []; 1012 $MaxVal = 0; 1013 foreach (array_keys($this->Data["Axis"]) as $AxisID) { 1014 if ($UnitChange != null) { 1015 $this->Data["Axis"][$AxisID]["Unit"] = $UnitChange; 1016 } 1017 1018 foreach ($this->Data["Series"] as $SerieName => $Serie) { 1019 if ($Serie["Axis"] == $AxisID 1020 && $Serie["isDrawable"] == true 1021 && $SerieName != $Abscissa 1022 ) { 1023 $SelectedSeries[$SerieName] = $SerieName; 1024 1025 if (count($Serie["Data"]) > $MaxVal) { 1026 $MaxVal = count($Serie["Data"]); 1027 } 1028 } 1029 } 1030 } 1031 1032 for ($i = 0; $i <= $MaxVal - 1; $i++) { 1033 $Factor = 0; 1034 foreach ($SelectedSeries as $Key => $SerieName) { 1035 $Value = $this->Data["Series"][$SerieName]["Data"][$i]; 1036 if ($Value != VOID) { 1037 $Factor = $Factor + abs($Value); 1038 } 1039 } 1040 1041 if ($Factor != 0) { 1042 $Factor = $NormalizationFactor / $Factor; 1043 1044 foreach ($SelectedSeries as $Key => $SerieName) { 1045 $Value = $this->Data["Series"][$SerieName]["Data"][$i]; 1046 1047 if ($Value != VOID && $Factor != $NormalizationFactor) { 1048 $this->Data["Series"][$SerieName]["Data"][$i] = round(abs($Value) * $Factor, $Round); 1049 } elseif ($Value == VOID || $Value == 0) { 1050 $this->Data["Series"][$SerieName]["Data"][$i] = VOID; 1051 } elseif ($Factor == $NormalizationFactor) { 1052 $this->Data["Series"][$SerieName]["Data"][$i] = $NormalizationFactor; 1053 } 1054 } 1055 } 1056 } 1057 1058 foreach ($SelectedSeries as $Key => $SerieName) { 1059 $this->Data["Series"][$SerieName]["Max"] = max( 1060 $this->stripVOID($this->Data["Series"][$SerieName]["Data"]) 1061 ); 1062 $this->Data["Series"][$SerieName]["Min"] = min( 1063 $this->stripVOID($this->Data["Series"][$SerieName]["Data"]) 1064 ); 1065 } 1066 } 1067 1068 /** 1069 * Load data from a CSV (or similar) data source 1070 * @param string $FileName 1071 * @param array $Options 1072 */ 1073 public function importFromCSV($FileName, array $Options = []) 1074 { 1075 $Delimiter = isset($Options["Delimiter"]) ? $Options["Delimiter"] : ","; 1076 $GotHeader = isset($Options["GotHeader"]) ? $Options["GotHeader"] : false; 1077 $SkipColumns = isset($Options["SkipColumns"]) ? $Options["SkipColumns"] : [-1]; 1078 $DefaultSerieName = isset($Options["DefaultSerieName"]) ? $Options["DefaultSerieName"] : "Serie"; 1079 1080 $Handle = @fopen($FileName, "r"); 1081 if ($Handle) { 1082 $HeaderParsed = false; 1083 $SerieNames = []; 1084 while (!feof($Handle)) { 1085 $Buffer = fgets($Handle, 4096); 1086 $Buffer = str_replace(chr(10), "", $Buffer); 1087 $Buffer = str_replace(chr(13), "", $Buffer); 1088 $Values = preg_split("/" . $Delimiter . "/", $Buffer); 1089 1090 if ($Buffer != "") { 1091 if ($GotHeader && !$HeaderParsed) { 1092 foreach ($Values as $Key => $Name) { 1093 if (!in_array($Key, $SkipColumns)) { 1094 $SerieNames[$Key] = $Name; 1095 } 1096 } 1097 $HeaderParsed = true; 1098 } else { 1099 if (!count($SerieNames)) { 1100 foreach ($Values as $Key => $Name) { 1101 if (!in_array($Key, $SkipColumns)) { 1102 $SerieNames[$Key] = $DefaultSerieName . $Key; 1103 } 1104 } 1105 } 1106 foreach ($Values as $Key => $Value) { 1107 if (!in_array($Key, $SkipColumns)) { 1108 $this->addPoints($Value, $SerieNames[$Key]); 1109 } 1110 } 1111 } 1112 } 1113 } 1114 fclose($Handle); 1115 } 1116 } 1117 1118 /** 1119 * Create a dataset based on a formula 1120 * 1121 * @param string $SerieName 1122 * @param string $Formula 1123 * @param array $Options 1124 * @return null 1125 */ 1126 public function createFunctionSerie($SerieName, $Formula = "", array $Options = []) 1127 { 1128 $MinX = isset($Options["MinX"]) ? $Options["MinX"] : -10; 1129 $MaxX = isset($Options["MaxX"]) ? $Options["MaxX"] : 10; 1130 $XStep = isset($Options["XStep"]) ? $Options["XStep"] : 1; 1131 $AutoDescription = isset($Options["AutoDescription"]) ? $Options["AutoDescription"] : false; 1132 $RecordAbscissa = isset($Options["RecordAbscissa"]) ? $Options["RecordAbscissa"] : false; 1133 $AbscissaSerie = isset($Options["AbscissaSerie"]) ? $Options["AbscissaSerie"] : "Abscissa"; 1134 1135 if ($Formula == "") { 1136 return null; 1137 } 1138 1139 $Result = []; 1140 $Abscissa = []; 1141 for ($i = $MinX; $i <= $MaxX; $i = $i + $XStep) { 1142 $Expression = "\$return = '!'.(" . str_replace("z", $i, $Formula) . ");"; 1143 if (@eval($Expression) === false) { 1144 $return = VOID; 1145 } 1146 if ($return == "!") { 1147 $return = VOID; 1148 } else { 1149 $return = $this->right($return, strlen($return) - 1); 1150 } 1151 if ($return == "NAN") { 1152 $return = VOID; 1153 } 1154 if ($return == "INF") { 1155 $return = VOID; 1156 } 1157 if ($return == "-INF") { 1158 $return = VOID; 1159 } 1160 1161 $Abscissa[] = $i; 1162 $Result[] = $return; 1163 } 1164 1165 $this->addPoints($Result, $SerieName); 1166 if ($AutoDescription) { 1167 $this->setSerieDescription($SerieName, $Formula); 1168 } 1169 if ($RecordAbscissa) { 1170 $this->addPoints($Abscissa, $AbscissaSerie); 1171 } 1172 } 1173 1174 /** 1175 * @param mixed $Series 1176 */ 1177 public function negateValues($Series) 1178 { 1179 if (!is_array($Series)) { 1180 $Series = $this->convertToArray($Series); 1181 } 1182 foreach ($Series as $Key => $SerieName) { 1183 if (isset($this->Data["Series"][$SerieName])) { 1184 $Data = []; 1185 foreach ($this->Data["Series"][$SerieName]["Data"] as $Key => $Value) { 1186 if ($Value == VOID) { 1187 $Data[] = VOID; 1188 } else { 1189 $Data[] = -$Value; 1190 } 1191 } 1192 $this->Data["Series"][$SerieName]["Data"] = $Data; 1193 $this->Data["Series"][$SerieName]["Max"] = max( 1194 $this->stripVOID($this->Data["Series"][$SerieName]["Data"]) 1195 ); 1196 $this->Data["Series"][$SerieName]["Min"] = min( 1197 $this->stripVOID($this->Data["Series"][$SerieName]["Data"]) 1198 ); 1199 } 1200 } 1201 } 1202 1203 /** 1204 * Return the data & configuration of the series 1205 * @return array 1206 */ 1207 public function getData() 1208 { 1209 return $this->Data; 1210 } 1211 1212 /** 1213 * Save a palette element 1214 * 1215 * @param integer $ID 1216 * @param string $Color 1217 */ 1218 public function savePalette($ID, $Color) 1219 { 1220 $this->Palette[$ID] = $Color; 1221 } 1222 1223 /** 1224 * Return the palette of the series 1225 * @return array 1226 */ 1227 public function getPalette() 1228 { 1229 return $this->Palette; 1230 } 1231 1232 /** 1233 * Called by the scaling algorithm to save the config 1234 * @param mixed $Axis 1235 */ 1236 public function saveAxisConfig($Axis) 1237 { 1238 $this->Data["Axis"] = $Axis; 1239 } 1240 1241 /** 1242 * Save the Y Margin if set 1243 * @param mixed $Value 1244 */ 1245 public function saveYMargin($Value) 1246 { 1247 $this->Data["YMargin"] = $Value; 1248 } 1249 1250 /** 1251 * Save extended configuration to the pData object 1252 * @param string $Tag 1253 * @param mixed $Values 1254 */ 1255 public function saveExtendedData($Tag, $Values) 1256 { 1257 $this->Data["Extended"][$Tag] = $Values; 1258 } 1259 1260 /** 1261 * Called by the scaling algorithm to save the orientation of the scale 1262 * @param mixed $Orientation 1263 */ 1264 public function saveOrientation($Orientation) 1265 { 1266 $this->Data["Orientation"] = $Orientation; 1267 } 1268 1269 /** 1270 * Convert a string to a single elements array 1271 * @param mixed $Value 1272 * @return array 1273 */ 1274 public function convertToArray($Value) 1275 { 1276 return [$Value]; 1277 } 1278 1279 /** 1280 * Class string wrapper 1281 * @return string 1282 */ 1283 public function __toString() 1284 { 1285 return "pData object."; 1286 } 1287 1288 /** 1289 * @param string $value 1290 * @param int $NbChar 1291 * @return string 1292 */ 1293 public function left($value, $NbChar) 1294 { 1295 return substr($value, 0, $NbChar); 1296 } 1297 1298 /** 1299 * @param string $value 1300 * @param int $NbChar 1301 * @return string 1302 */ 1303 public function right($value, $NbChar) 1304 { 1305 return substr($value, strlen($value) - $NbChar, $NbChar); 1306 } 1307 1308 /** 1309 * @param string $value 1310 * @param int $Depart 1311 * @param int $NbChar 1312 * @return string 1313 */ 1314 public function mid($value, $Depart, $NbChar) 1315 { 1316 return substr($value, $Depart - 1, $NbChar); 1317 } 1318} 1319