Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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