See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 403]
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Chart\Renderer; 4 5 use AccBarPlot; 6 use AccLinePlot; 7 use BarPlot; 8 use ContourPlot; 9 use Graph; 10 use GroupBarPlot; 11 use LinePlot; 12 use PhpOffice\PhpSpreadsheet\Chart\Chart; 13 use PhpOffice\PhpSpreadsheet\Style\NumberFormat; 14 use PieGraph; 15 use PiePlot; 16 use PiePlot3D; 17 use PiePlotC; 18 use RadarGraph; 19 use RadarPlot; 20 use ScatterPlot; 21 use Spline; 22 use StockPlot; 23 24 /** 25 * Base class for different Jpgraph implementations as charts renderer. 26 */ 27 abstract class JpGraphRendererBase implements IRenderer 28 { 29 private static $width = 640; 30 31 private static $height = 480; 32 33 private static $colourSet = [ 34 'mediumpurple1', 'palegreen3', 'gold1', 'cadetblue1', 35 'darkmagenta', 'coral', 'dodgerblue3', 'eggplant', 36 'mediumblue', 'magenta', 'sandybrown', 'cyan', 37 'firebrick1', 'forestgreen', 'deeppink4', 'darkolivegreen', 38 'goldenrod2', 39 ]; 40 41 private static $markSet; 42 43 private $chart; 44 45 private $graph; 46 47 private static $plotColour = 0; 48 49 private static $plotMark = 0; 50 51 /** 52 * Create a new jpgraph. 53 */ 54 public function __construct(Chart $chart) 55 { 56 static::init(); 57 $this->graph = null; 58 $this->chart = $chart; 59 60 self::$markSet = [ 61 'diamond' => MARK_DIAMOND, 62 'square' => MARK_SQUARE, 63 'triangle' => MARK_UTRIANGLE, 64 'x' => MARK_X, 65 'star' => MARK_STAR, 66 'dot' => MARK_FILLEDCIRCLE, 67 'dash' => MARK_DTRIANGLE, 68 'circle' => MARK_CIRCLE, 69 'plus' => MARK_CROSS, 70 ]; 71 } 72 73 /** 74 * This method should be overriden in descendants to do real JpGraph library initialization. 75 */ 76 abstract protected static function init(): void; 77 78 private function formatPointMarker($seriesPlot, $markerID) 79 { 80 $plotMarkKeys = array_keys(self::$markSet); 81 if ($markerID === null) { 82 // Use default plot marker (next marker in the series) 83 self::$plotMark %= count(self::$markSet); 84 $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); 85 } elseif ($markerID !== 'none') { 86 // Use specified plot marker (if it exists) 87 if (isset(self::$markSet[$markerID])) { 88 $seriesPlot->mark->SetType(self::$markSet[$markerID]); 89 } else { 90 // If the specified plot marker doesn't exist, use default plot marker (next marker in the series) 91 self::$plotMark %= count(self::$markSet); 92 $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); 93 } 94 } else { 95 // Hide plot marker 96 $seriesPlot->mark->Hide(); 97 } 98 $seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]); 99 $seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]); 100 $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); 101 102 return $seriesPlot; 103 } 104 105 private function formatDataSetLabels($groupID, $datasetLabels, $rotation = '') 106 { 107 $datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode() ?? ''; 108 // Retrieve any label formatting code 109 $datasetLabelFormatCode = stripslashes($datasetLabelFormatCode); 110 111 $testCurrentIndex = 0; 112 foreach ($datasetLabels as $i => $datasetLabel) { 113 if (is_array($datasetLabel)) { 114 if ($rotation == 'bar') { 115 $datasetLabels[$i] = implode(' ', $datasetLabel); 116 } else { 117 $datasetLabel = array_reverse($datasetLabel); 118 $datasetLabels[$i] = implode("\n", $datasetLabel); 119 } 120 } else { 121 // Format labels according to any formatting code 122 if ($datasetLabelFormatCode !== null) { 123 $datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode); 124 } 125 } 126 ++$testCurrentIndex; 127 } 128 129 return $datasetLabels; 130 } 131 132 private function percentageSumCalculation($groupID, $seriesCount) 133 { 134 $sumValues = []; 135 // Adjust our values to a percentage value across all series in the group 136 for ($i = 0; $i < $seriesCount; ++$i) { 137 if ($i == 0) { 138 $sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); 139 } else { 140 $nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); 141 foreach ($nextValues as $k => $value) { 142 if (isset($sumValues[$k])) { 143 $sumValues[$k] += $value; 144 } else { 145 $sumValues[$k] = $value; 146 } 147 } 148 } 149 } 150 151 return $sumValues; 152 } 153 154 private function percentageAdjustValues($dataValues, $sumValues) 155 { 156 foreach ($dataValues as $k => $dataValue) { 157 $dataValues[$k] = $dataValue / $sumValues[$k] * 100; 158 } 159 160 return $dataValues; 161 } 162 163 private function getCaption($captionElement) 164 { 165 // Read any caption 166 $caption = ($captionElement !== null) ? $captionElement->getCaption() : null; 167 // Test if we have a title caption to display 168 if ($caption !== null) { 169 // If we do, it could be a plain string or an array 170 if (is_array($caption)) { 171 // Implode an array to a plain string 172 $caption = implode('', $caption); 173 } 174 } 175 176 return $caption; 177 } 178 179 private function renderTitle(): void 180 { 181 $title = $this->getCaption($this->chart->getTitle()); 182 if ($title !== null) { 183 $this->graph->title->Set($title); 184 } 185 } 186 187 private function renderLegend(): void 188 { 189 $legend = $this->chart->getLegend(); 190 if ($legend !== null) { 191 $legendPosition = $legend->getPosition(); 192 switch ($legendPosition) { 193 case 'r': 194 $this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); // right 195 $this->graph->legend->SetColumns(1); 196 197 break; 198 case 'l': 199 $this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); // left 200 $this->graph->legend->SetColumns(1); 201 202 break; 203 case 't': 204 $this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); // top 205 206 break; 207 case 'b': 208 $this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); // bottom 209 210 break; 211 default: 212 $this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); // top-right 213 $this->graph->legend->SetColumns(1); 214 215 break; 216 } 217 } else { 218 $this->graph->legend->Hide(); 219 } 220 } 221 222 private function renderCartesianPlotArea($type = 'textlin'): void 223 { 224 $this->graph = new Graph(self::$width, self::$height); 225 $this->graph->SetScale($type); 226 227 $this->renderTitle(); 228 229 // Rotate for bar rather than column chart 230 $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection(); 231 $reverse = $rotation == 'bar'; 232 233 $xAxisLabel = $this->chart->getXAxisLabel(); 234 if ($xAxisLabel !== null) { 235 $title = $this->getCaption($xAxisLabel); 236 if ($title !== null) { 237 $this->graph->xaxis->SetTitle($title, 'center'); 238 $this->graph->xaxis->title->SetMargin(35); 239 if ($reverse) { 240 $this->graph->xaxis->title->SetAngle(90); 241 $this->graph->xaxis->title->SetMargin(90); 242 } 243 } 244 } 245 246 $yAxisLabel = $this->chart->getYAxisLabel(); 247 if ($yAxisLabel !== null) { 248 $title = $this->getCaption($yAxisLabel); 249 if ($title !== null) { 250 $this->graph->yaxis->SetTitle($title, 'center'); 251 if ($reverse) { 252 $this->graph->yaxis->title->SetAngle(0); 253 $this->graph->yaxis->title->SetMargin(-55); 254 } 255 } 256 } 257 } 258 259 private function renderPiePlotArea(): void 260 { 261 $this->graph = new PieGraph(self::$width, self::$height); 262 263 $this->renderTitle(); 264 } 265 266 private function renderRadarPlotArea(): void 267 { 268 $this->graph = new RadarGraph(self::$width, self::$height); 269 $this->graph->SetScale('lin'); 270 271 $this->renderTitle(); 272 } 273 274 private function renderPlotLine($groupID, $filled = false, $combination = false): void 275 { 276 $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); 277 278 $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0]; 279 $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount(); 280 if ($labelCount > 0) { 281 $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); 282 $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels); 283 $this->graph->xaxis->SetTickLabels($datasetLabels); 284 } 285 286 $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); 287 $seriesPlots = []; 288 if ($grouping == 'percentStacked') { 289 $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); 290 } else { 291 $sumValues = []; 292 } 293 294 // Loop through each data series in turn 295 for ($i = 0; $i < $seriesCount; ++$i) { 296 $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$i]; 297 $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues(); 298 $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointMarker(); 299 300 if ($grouping == 'percentStacked') { 301 $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); 302 } 303 304 // Fill in any missing values in the $dataValues array 305 $testCurrentIndex = 0; 306 foreach ($dataValues as $k => $dataValue) { 307 while ($k != $testCurrentIndex) { 308 $dataValues[$testCurrentIndex] = null; 309 ++$testCurrentIndex; 310 } 311 ++$testCurrentIndex; 312 } 313 314 $seriesPlot = new LinePlot($dataValues); 315 if ($combination) { 316 $seriesPlot->SetBarCenter(); 317 } 318 319 if ($filled) { 320 $seriesPlot->SetFilled(true); 321 $seriesPlot->SetColor('black'); 322 $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); 323 } else { 324 // Set the appropriate plot marker 325 $this->formatPointMarker($seriesPlot, $marker); 326 } 327 $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($index)->getDataValue(); 328 $seriesPlot->SetLegend($dataLabel); 329 330 $seriesPlots[] = $seriesPlot; 331 } 332 333 if ($grouping == 'standard') { 334 $groupPlot = $seriesPlots; 335 } else { 336 $groupPlot = new AccLinePlot($seriesPlots); 337 } 338 $this->graph->Add($groupPlot); 339 } 340 341 private function renderPlotBar($groupID, $dimensions = '2d'): void 342 { 343 $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection(); 344 // Rotate for bar rather than column chart 345 if (($groupID == 0) && ($rotation == 'bar')) { 346 $this->graph->Set90AndMargin(); 347 } 348 $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); 349 350 $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0]; 351 $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount(); 352 if ($labelCount > 0) { 353 $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); 354 $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $rotation); 355 // Rotate for bar rather than column chart 356 if ($rotation == 'bar') { 357 $datasetLabels = array_reverse($datasetLabels); 358 $this->graph->yaxis->SetPos('max'); 359 $this->graph->yaxis->SetLabelAlign('center', 'top'); 360 $this->graph->yaxis->SetLabelSide(SIDE_RIGHT); 361 } 362 $this->graph->xaxis->SetTickLabels($datasetLabels); 363 } 364 365 $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); 366 $seriesPlots = []; 367 if ($grouping == 'percentStacked') { 368 $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); 369 } else { 370 $sumValues = []; 371 } 372 373 // Loop through each data series in turn 374 for ($j = 0; $j < $seriesCount; ++$j) { 375 $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$j]; 376 $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues(); 377 if ($grouping == 'percentStacked') { 378 $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); 379 } 380 381 // Fill in any missing values in the $dataValues array 382 $testCurrentIndex = 0; 383 foreach ($dataValues as $k => $dataValue) { 384 while ($k != $testCurrentIndex) { 385 $dataValues[$testCurrentIndex] = null; 386 ++$testCurrentIndex; 387 } 388 ++$testCurrentIndex; 389 } 390 391 // Reverse the $dataValues order for bar rather than column chart 392 if ($rotation == 'bar') { 393 $dataValues = array_reverse($dataValues); 394 } 395 $seriesPlot = new BarPlot($dataValues); 396 $seriesPlot->SetColor('black'); 397 $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); 398 if ($dimensions == '3d') { 399 $seriesPlot->SetShadow(); 400 } 401 if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) { 402 $dataLabel = ''; 403 } else { 404 $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue(); 405 } 406 $seriesPlot->SetLegend($dataLabel); 407 408 $seriesPlots[] = $seriesPlot; 409 } 410 // Reverse the plot order for bar rather than column chart 411 if (($rotation == 'bar') && ($grouping != 'percentStacked')) { 412 $seriesPlots = array_reverse($seriesPlots); 413 } 414 415 if ($grouping == 'clustered') { 416 $groupPlot = new GroupBarPlot($seriesPlots); 417 } elseif ($grouping == 'standard') { 418 $groupPlot = new GroupBarPlot($seriesPlots); 419 } else { 420 $groupPlot = new AccBarPlot($seriesPlots); 421 if ($dimensions == '3d') { 422 $groupPlot->SetShadow(); 423 } 424 } 425 426 $this->graph->Add($groupPlot); 427 } 428 429 private function renderPlotScatter($groupID, $bubble): void 430 { 431 $scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); 432 433 $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); 434 435 // Loop through each data series in turn 436 for ($i = 0; $i < $seriesCount; ++$i) { 437 $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); 438 $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); 439 440 foreach ($dataValuesY as $k => $dataValueY) { 441 $dataValuesY[$k] = $k; 442 } 443 444 $seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY); 445 if ($scatterStyle == 'lineMarker') { 446 $seriesPlot->SetLinkPoints(); 447 $seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]); 448 } elseif ($scatterStyle == 'smoothMarker') { 449 $spline = new Spline($dataValuesY, $dataValuesX); 450 [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20); 451 $lplot = new LinePlot($splineDataX, $splineDataY); 452 $lplot->SetColor(self::$colourSet[self::$plotColour]); 453 454 $this->graph->Add($lplot); 455 } 456 457 if ($bubble) { 458 $this->formatPointMarker($seriesPlot, 'dot'); 459 $seriesPlot->mark->SetColor('black'); 460 $seriesPlot->mark->SetSize($bubbleSize); 461 } else { 462 $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); 463 $this->formatPointMarker($seriesPlot, $marker); 464 } 465 $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); 466 $seriesPlot->SetLegend($dataLabel); 467 468 $this->graph->Add($seriesPlot); 469 } 470 } 471 472 private function renderPlotRadar($groupID): void 473 { 474 $radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); 475 476 $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); 477 478 // Loop through each data series in turn 479 for ($i = 0; $i < $seriesCount; ++$i) { 480 $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); 481 $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); 482 $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); 483 484 $dataValues = []; 485 foreach ($dataValuesY as $k => $dataValueY) { 486 $dataValues[$k] = implode(' ', array_reverse($dataValueY)); 487 } 488 $tmp = array_shift($dataValues); 489 $dataValues[] = $tmp; 490 $tmp = array_shift($dataValuesX); 491 $dataValuesX[] = $tmp; 492 493 $this->graph->SetTitles(array_reverse($dataValues)); 494 495 $seriesPlot = new RadarPlot(array_reverse($dataValuesX)); 496 497 $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); 498 $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); 499 if ($radarStyle == 'filled') { 500 $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]); 501 } 502 $this->formatPointMarker($seriesPlot, $marker); 503 $seriesPlot->SetLegend($dataLabel); 504 505 $this->graph->Add($seriesPlot); 506 } 507 } 508 509 private function renderPlotContour($groupID): void 510 { 511 $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); 512 513 $dataValues = []; 514 // Loop through each data series in turn 515 for ($i = 0; $i < $seriesCount; ++$i) { 516 $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); 517 518 $dataValues[$i] = $dataValuesX; 519 } 520 $seriesPlot = new ContourPlot($dataValues); 521 522 $this->graph->Add($seriesPlot); 523 } 524 525 private function renderPlotStock($groupID): void 526 { 527 $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); 528 $plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder(); 529 530 $dataValues = []; 531 // Loop through each data series in turn and build the plot arrays 532 foreach ($plotOrder as $i => $v) { 533 $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v); 534 if ($dataValuesX === false) { 535 continue; 536 } 537 $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues(); 538 foreach ($dataValuesX as $j => $dataValueX) { 539 $dataValues[$plotOrder[$i]][$j] = $dataValueX; 540 } 541 } 542 if (empty($dataValues)) { 543 return; 544 } 545 546 $dataValuesPlot = []; 547 // Flatten the plot arrays to a single dimensional array to work with jpgraph 548 $jMax = count($dataValues[0]); 549 for ($j = 0; $j < $jMax; ++$j) { 550 for ($i = 0; $i < $seriesCount; ++$i) { 551 $dataValuesPlot[] = $dataValues[$i][$j] ?? null; 552 } 553 } 554 555 // Set the x-axis labels 556 $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); 557 if ($labelCount > 0) { 558 $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); 559 $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels); 560 $this->graph->xaxis->SetTickLabels($datasetLabels); 561 } 562 563 $seriesPlot = new StockPlot($dataValuesPlot); 564 $seriesPlot->SetWidth(20); 565 566 $this->graph->Add($seriesPlot); 567 } 568 569 private function renderAreaChart($groupCount): void 570 { 571 $this->renderCartesianPlotArea(); 572 573 for ($i = 0; $i < $groupCount; ++$i) { 574 $this->renderPlotLine($i, true, false); 575 } 576 } 577 578 private function renderLineChart($groupCount): void 579 { 580 $this->renderCartesianPlotArea(); 581 582 for ($i = 0; $i < $groupCount; ++$i) { 583 $this->renderPlotLine($i, false, false); 584 } 585 } 586 587 private function renderBarChart($groupCount, $dimensions = '2d'): void 588 { 589 $this->renderCartesianPlotArea(); 590 591 for ($i = 0; $i < $groupCount; ++$i) { 592 $this->renderPlotBar($i, $dimensions); 593 } 594 } 595 596 private function renderScatterChart($groupCount): void 597 { 598 $this->renderCartesianPlotArea('linlin'); 599 600 for ($i = 0; $i < $groupCount; ++$i) { 601 $this->renderPlotScatter($i, false); 602 } 603 } 604 605 private function renderBubbleChart($groupCount): void 606 { 607 $this->renderCartesianPlotArea('linlin'); 608 609 for ($i = 0; $i < $groupCount; ++$i) { 610 $this->renderPlotScatter($i, true); 611 } 612 } 613 614 private function renderPieChart($groupCount, $dimensions = '2d', $doughnut = false, $multiplePlots = false): void 615 { 616 $this->renderPiePlotArea(); 617 618 $iLimit = ($multiplePlots) ? $groupCount : 1; 619 for ($groupID = 0; $groupID < $iLimit; ++$groupID) { 620 $exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); 621 $datasetLabels = []; 622 if ($groupID == 0) { 623 $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); 624 if ($labelCount > 0) { 625 $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); 626 $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels); 627 } 628 } 629 630 $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); 631 // For pie charts, we only display the first series: doughnut charts generally display all series 632 $jLimit = ($multiplePlots) ? $seriesCount : 1; 633 // Loop through each data series in turn 634 for ($j = 0; $j < $jLimit; ++$j) { 635 $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues(); 636 637 // Fill in any missing values in the $dataValues array 638 $testCurrentIndex = 0; 639 foreach ($dataValues as $k => $dataValue) { 640 while ($k != $testCurrentIndex) { 641 $dataValues[$testCurrentIndex] = null; 642 ++$testCurrentIndex; 643 } 644 ++$testCurrentIndex; 645 } 646 647 if ($dimensions == '3d') { 648 $seriesPlot = new PiePlot3D($dataValues); 649 } else { 650 if ($doughnut) { 651 $seriesPlot = new PiePlotC($dataValues); 652 } else { 653 $seriesPlot = new PiePlot($dataValues); 654 } 655 } 656 657 if ($multiplePlots) { 658 $seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4)); 659 } 660 661 if ($doughnut && method_exists($seriesPlot, 'SetMidColor')) { 662 $seriesPlot->SetMidColor('white'); 663 } 664 665 $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); 666 if (count($datasetLabels) > 0) { 667 $seriesPlot->SetLabels(array_fill(0, count($datasetLabels), '')); 668 } 669 if ($dimensions != '3d') { 670 $seriesPlot->SetGuideLines(false); 671 } 672 if ($j == 0) { 673 if ($exploded) { 674 $seriesPlot->ExplodeAll(); 675 } 676 $seriesPlot->SetLegends($datasetLabels); 677 } 678 679 $this->graph->Add($seriesPlot); 680 } 681 } 682 } 683 684 private function renderRadarChart($groupCount): void 685 { 686 $this->renderRadarPlotArea(); 687 688 for ($groupID = 0; $groupID < $groupCount; ++$groupID) { 689 $this->renderPlotRadar($groupID); 690 } 691 } 692 693 private function renderStockChart($groupCount): void 694 { 695 $this->renderCartesianPlotArea('intint'); 696 697 for ($groupID = 0; $groupID < $groupCount; ++$groupID) { 698 $this->renderPlotStock($groupID); 699 } 700 } 701 702 private function renderContourChart($groupCount): void 703 { 704 $this->renderCartesianPlotArea('intint'); 705 706 for ($i = 0; $i < $groupCount; ++$i) { 707 $this->renderPlotContour($i); 708 } 709 } 710 711 private function renderCombinationChart($groupCount, $outputDestination) 712 { 713 $this->renderCartesianPlotArea(); 714 715 for ($i = 0; $i < $groupCount; ++$i) { 716 $dimensions = null; 717 $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); 718 switch ($chartType) { 719 case 'area3DChart': 720 case 'areaChart': 721 $this->renderPlotLine($i, true, true); 722 723 break; 724 case 'bar3DChart': 725 $dimensions = '3d'; 726 // no break 727 case 'barChart': 728 $this->renderPlotBar($i, $dimensions); 729 730 break; 731 case 'line3DChart': 732 case 'lineChart': 733 $this->renderPlotLine($i, false, true); 734 735 break; 736 case 'scatterChart': 737 $this->renderPlotScatter($i, false); 738 739 break; 740 case 'bubbleChart': 741 $this->renderPlotScatter($i, true); 742 743 break; 744 default: 745 $this->graph = null; 746 747 return false; 748 } 749 } 750 751 $this->renderLegend(); 752 753 $this->graph->Stroke($outputDestination); 754 755 return true; 756 } 757 758 public function render($outputDestination) 759 { 760 self::$plotColour = 0; 761 762 $groupCount = $this->chart->getPlotArea()->getPlotGroupCount(); 763 764 $dimensions = null; 765 if ($groupCount == 1) { 766 $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType(); 767 } else { 768 $chartTypes = []; 769 for ($i = 0; $i < $groupCount; ++$i) { 770 $chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); 771 } 772 $chartTypes = array_unique($chartTypes); 773 if (count($chartTypes) == 1) { 774 $chartType = array_pop($chartTypes); 775 } elseif (count($chartTypes) == 0) { 776 echo 'Chart is not yet implemented<br />'; 777 778 return false; 779 } else { 780 return $this->renderCombinationChart($groupCount, $outputDestination); 781 } 782 } 783 784 switch ($chartType) { 785 case 'area3DChart': 786 $dimensions = '3d'; 787 // no break 788 case 'areaChart': 789 $this->renderAreaChart($groupCount); 790 791 break; 792 case 'bar3DChart': 793 $dimensions = '3d'; 794 // no break 795 case 'barChart': 796 $this->renderBarChart($groupCount, $dimensions); 797 798 break; 799 case 'line3DChart': 800 $dimensions = '3d'; 801 // no break 802 case 'lineChart': 803 $this->renderLineChart($groupCount); 804 805 break; 806 case 'pie3DChart': 807 $dimensions = '3d'; 808 // no break 809 case 'pieChart': 810 $this->renderPieChart($groupCount, $dimensions, false, false); 811 812 break; 813 case 'doughnut3DChart': 814 $dimensions = '3d'; 815 // no break 816 case 'doughnutChart': 817 $this->renderPieChart($groupCount, $dimensions, true, true); 818 819 break; 820 case 'scatterChart': 821 $this->renderScatterChart($groupCount); 822 823 break; 824 case 'bubbleChart': 825 $this->renderBubbleChart($groupCount); 826 827 break; 828 case 'radarChart': 829 $this->renderRadarChart($groupCount); 830 831 break; 832 case 'surface3DChart': 833 case 'surfaceChart': 834 $this->renderContourChart($groupCount); 835 836 break; 837 case 'stockChart': 838 $this->renderStockChart($groupCount); 839 840 break; 841 default: 842 echo $chartType . ' is not yet implemented<br />'; 843 844 return false; 845 } 846 $this->renderLegend(); 847 848 $this->graph->Stroke($outputDestination); 849 850 return true; 851 } 852 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body