Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
4
 
5
use PhpOffice\PhpSpreadsheet\Chart\Axis;
6
use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
7
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
8
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
9
use PhpOffice\PhpSpreadsheet\Chart\Layout;
10
use PhpOffice\PhpSpreadsheet\Chart\Legend;
11
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
12
use PhpOffice\PhpSpreadsheet\Chart\Properties;
13
use PhpOffice\PhpSpreadsheet\Chart\Title;
14
use PhpOffice\PhpSpreadsheet\Chart\TrendLine;
15
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
16
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
17
use PhpOffice\PhpSpreadsheet\Style\Font;
18
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
19
 
20
class Chart extends WriterPart
21
{
22
    /**
23
     * @var int
24
     */
25
    private $seriesIndex;
26
 
27
    /**
28
     * Write charts to XML format.
29
     *
30
     * @param mixed $calculateCellValues
31
     *
32
     * @return string XML Output
33
     */
34
    public function writeChart(\PhpOffice\PhpSpreadsheet\Chart\Chart $chart, $calculateCellValues = true)
35
    {
36
        // Create XML writer
37
        $objWriter = null;
38
        if ($this->getParentWriter()->getUseDiskCaching()) {
39
            $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
40
        } else {
41
            $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
42
        }
43
        //    Ensure that data series values are up-to-date before we save
44
        if ($calculateCellValues) {
45
            $chart->refresh();
46
        }
47
 
48
        // XML header
49
        $objWriter->startDocument('1.0', 'UTF-8', 'yes');
50
 
51
        // c:chartSpace
52
        $objWriter->startElement('c:chartSpace');
53
        $objWriter->writeAttribute('xmlns:c', Namespaces::CHART);
54
        $objWriter->writeAttribute('xmlns:a', Namespaces::DRAWINGML);
55
        $objWriter->writeAttribute('xmlns:r', Namespaces::SCHEMA_OFFICE_DOCUMENT);
56
 
57
        $objWriter->startElement('c:date1904');
58
        $objWriter->writeAttribute('val', '0');
59
        $objWriter->endElement();
60
        $objWriter->startElement('c:lang');
61
        $objWriter->writeAttribute('val', 'en-GB');
62
        $objWriter->endElement();
63
        $objWriter->startElement('c:roundedCorners');
64
        $objWriter->writeAttribute('val', $chart->getRoundedCorners() ? '1' : '0');
65
        $objWriter->endElement();
66
 
67
        $this->writeAlternateContent($objWriter);
68
 
69
        $objWriter->startElement('c:chart');
70
 
71
        $this->writeTitle($objWriter, $chart->getTitle());
72
 
73
        $objWriter->startElement('c:autoTitleDeleted');
74
        $objWriter->writeAttribute('val', (string) (int) $chart->getAutoTitleDeleted());
75
        $objWriter->endElement();
76
 
77
        $objWriter->startElement('c:view3D');
78
        $surface2D = false;
79
        $plotArea = $chart->getPlotArea();
80
        if ($plotArea !== null) {
81
            $seriesArray = $plotArea->getPlotGroup();
82
            foreach ($seriesArray as $series) {
83
                if ($series->getPlotType() === DataSeries::TYPE_SURFACECHART) {
84
                    $surface2D = true;
85
 
86
                    break;
87
                }
88
            }
89
        }
90
        $this->writeView3D($objWriter, $chart->getRotX(), 'c:rotX', $surface2D, 90);
91
        $this->writeView3D($objWriter, $chart->getRotY(), 'c:rotY', $surface2D);
92
        $this->writeView3D($objWriter, $chart->getRAngAx(), 'c:rAngAx', $surface2D);
93
        $this->writeView3D($objWriter, $chart->getPerspective(), 'c:perspective', $surface2D);
94
        $objWriter->endElement(); // view3D
95
 
96
        $this->writePlotArea($objWriter, $chart->getPlotArea(), $chart->getXAxisLabel(), $chart->getYAxisLabel(), $chart->getChartAxisX(), $chart->getChartAxisY());
97
 
98
        $this->writeLegend($objWriter, $chart->getLegend());
99
 
100
        $objWriter->startElement('c:plotVisOnly');
101
        $objWriter->writeAttribute('val', (string) (int) $chart->getPlotVisibleOnly());
102
        $objWriter->endElement();
103
 
104
        $objWriter->startElement('c:dispBlanksAs');
105
        $objWriter->writeAttribute('val', $chart->getDisplayBlanksAs());
106
        $objWriter->endElement();
107
 
108
        $objWriter->startElement('c:showDLblsOverMax');
109
        $objWriter->writeAttribute('val', '0');
110
        $objWriter->endElement();
111
 
112
        $objWriter->endElement(); // c:chart
113
 
114
        $objWriter->startElement('c:spPr');
115
        if ($chart->getNoFill()) {
116
            $objWriter->startElement('a:noFill');
117
            $objWriter->endElement(); // a:noFill
118
        }
119
        $fillColor = $chart->getFillColor();
120
        if ($fillColor->isUsable()) {
121
            $this->writeColor($objWriter, $fillColor);
122
        }
123
        $borderLines = $chart->getBorderLines();
124
        $this->writeLineStyles($objWriter, $borderLines);
125
        $this->writeEffects($objWriter, $borderLines);
126
        $objWriter->endElement(); // c:spPr
127
 
128
        $this->writePrintSettings($objWriter);
129
 
130
        $objWriter->endElement(); // c:chartSpace
131
 
132
        // Return
133
        return $objWriter->getData();
134
    }
135
 
136
    private function writeView3D(XMLWriter $objWriter, ?int $value, string $tag, bool $surface2D, int $default = 0): void
137
    {
138
        if ($value === null && $surface2D) {
139
            $value = $default;
140
        }
141
        if ($value !== null) {
142
            $objWriter->startElement($tag);
143
            $objWriter->writeAttribute('val', "$value");
144
            $objWriter->endElement();
145
        }
146
    }
147
 
148
    /**
149
     * Write Chart Title.
150
     */
151
    private function writeTitle(XMLWriter $objWriter, ?Title $title = null): void
152
    {
153
        if ($title === null) {
154
            return;
155
        }
156
 
157
        $objWriter->startElement('c:title');
158
        $objWriter->startElement('c:tx');
159
        $objWriter->startElement('c:rich');
160
 
161
        $objWriter->startElement('a:bodyPr');
162
        $objWriter->endElement();
163
 
164
        $objWriter->startElement('a:lstStyle');
165
        $objWriter->endElement();
166
 
167
        $objWriter->startElement('a:p');
168
        $objWriter->startElement('a:pPr');
169
        $objWriter->startElement('a:defRPr');
170
        $objWriter->endElement();
171
        $objWriter->endElement();
172
 
173
        $caption = $title->getCaption();
174
        if ((is_array($caption)) && (count($caption) > 0)) {
175
            $caption = $caption[0];
176
        }
177
        $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a');
178
 
179
        $objWriter->endElement();
180
        $objWriter->endElement();
181
        $objWriter->endElement();
182
 
183
        $this->writeLayout($objWriter, $title->getLayout());
184
 
185
        $objWriter->startElement('c:overlay');
186
        $objWriter->writeAttribute('val', ($title->getOverlay()) ? '1' : '0');
187
        $objWriter->endElement();
188
 
189
        $objWriter->endElement();
190
    }
191
 
192
    /**
193
     * Write Chart Legend.
194
     */
195
    private function writeLegend(XMLWriter $objWriter, ?Legend $legend = null): void
196
    {
197
        if ($legend === null) {
198
            return;
199
        }
200
 
201
        $objWriter->startElement('c:legend');
202
 
203
        $objWriter->startElement('c:legendPos');
204
        $objWriter->writeAttribute('val', $legend->getPosition());
205
        $objWriter->endElement();
206
 
207
        $this->writeLayout($objWriter, $legend->getLayout());
208
 
209
        $objWriter->startElement('c:overlay');
210
        $objWriter->writeAttribute('val', ($legend->getOverlay()) ? '1' : '0');
211
        $objWriter->endElement();
212
 
213
        $objWriter->startElement('c:spPr');
214
        $fillColor = $legend->getFillColor();
215
        if ($fillColor->isUsable()) {
216
            $this->writeColor($objWriter, $fillColor);
217
        }
218
        $borderLines = $legend->getBorderLines();
219
        $this->writeLineStyles($objWriter, $borderLines);
220
        $this->writeEffects($objWriter, $borderLines);
221
        $objWriter->endElement(); // c:spPr
222
 
223
        $legendText = $legend->getLegendText();
224
        $objWriter->startElement('c:txPr');
225
        $objWriter->startElement('a:bodyPr');
226
        $objWriter->endElement();
227
 
228
        $objWriter->startElement('a:lstStyle');
229
        $objWriter->endElement();
230
 
231
        $objWriter->startElement('a:p');
232
        $objWriter->startElement('a:pPr');
233
        $objWriter->writeAttribute('rtl', '0');
234
 
235
        $objWriter->startElement('a:defRPr');
236
        if ($legendText !== null) {
237
            $this->writeColor($objWriter, $legendText->getFillColorObject());
238
            $this->writeEffects($objWriter, $legendText);
239
        }
240
        $objWriter->endElement(); // a:defRpr
241
        $objWriter->endElement(); // a:pPr
242
 
243
        $objWriter->startElement('a:endParaRPr');
244
        $objWriter->writeAttribute('lang', 'en-US');
245
        $objWriter->endElement(); // a:endParaRPr
246
 
247
        $objWriter->endElement(); // a:p
248
        $objWriter->endElement(); // c:txPr
249
 
250
        $objWriter->endElement(); // c:legend
251
    }
252
 
253
    /**
254
     * Write Chart Plot Area.
255
     */
256
    private function writePlotArea(XMLWriter $objWriter, ?PlotArea $plotArea, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null): void
257
    {
258
        if ($plotArea === null) {
259
            return;
260
        }
261
 
262
        $id1 = $id2 = $id3 = '0';
263
        $this->seriesIndex = 0;
264
        $objWriter->startElement('c:plotArea');
265
 
266
        $layout = $plotArea->getLayout();
267
 
268
        $this->writeLayout($objWriter, $layout);
269
 
270
        $chartTypes = self::getChartType($plotArea);
271
        $catIsMultiLevelSeries = $valIsMultiLevelSeries = false;
272
        $plotGroupingType = '';
273
        $chartType = null;
274
        foreach ($chartTypes as $chartType) {
275
            $objWriter->startElement('c:' . $chartType);
276
 
277
            $groupCount = $plotArea->getPlotGroupCount();
278
            $plotGroup = null;
279
            for ($i = 0; $i < $groupCount; ++$i) {
280
                $plotGroup = $plotArea->getPlotGroupByIndex($i);
281
                $groupType = $plotGroup->getPlotType();
282
                if ($groupType == $chartType) {
283
                    $plotStyle = $plotGroup->getPlotStyle();
284
                    if (!empty($plotStyle) && $groupType === DataSeries::TYPE_RADARCHART) {
285
                        $objWriter->startElement('c:radarStyle');
286
                        $objWriter->writeAttribute('val', $plotStyle);
287
                        $objWriter->endElement();
288
                    } elseif (!empty($plotStyle) && $groupType === DataSeries::TYPE_SCATTERCHART) {
289
                        $objWriter->startElement('c:scatterStyle');
290
                        $objWriter->writeAttribute('val', $plotStyle);
291
                        $objWriter->endElement();
292
                    } elseif ($groupType === DataSeries::TYPE_SURFACECHART_3D || $groupType === DataSeries::TYPE_SURFACECHART) {
293
                        $objWriter->startElement('c:wireframe');
294
                        $objWriter->writeAttribute('val', $plotStyle ? '1' : '0');
295
                        $objWriter->endElement();
296
                    }
297
 
298
                    $this->writePlotGroup($plotGroup, $chartType, $objWriter, $catIsMultiLevelSeries, $valIsMultiLevelSeries, $plotGroupingType);
299
                }
300
            }
301
 
302
            $this->writeDataLabels($objWriter, $layout);
303
 
304
            if ($chartType === DataSeries::TYPE_LINECHART && $plotGroup) {
305
                //    Line only, Line3D can't be smoothed
306
                $objWriter->startElement('c:smooth');
307
                $objWriter->writeAttribute('val', (string) (int) $plotGroup->getSmoothLine());
308
                $objWriter->endElement();
309
            } elseif (($chartType === DataSeries::TYPE_BARCHART) || ($chartType === DataSeries::TYPE_BARCHART_3D)) {
310
                $objWriter->startElement('c:gapWidth');
311
                $objWriter->writeAttribute('val', '150');
312
                $objWriter->endElement();
313
 
314
                if ($plotGroupingType == 'percentStacked' || $plotGroupingType == 'stacked') {
315
                    $objWriter->startElement('c:overlap');
316
                    $objWriter->writeAttribute('val', '100');
317
                    $objWriter->endElement();
318
                }
319
            } elseif ($chartType === DataSeries::TYPE_BUBBLECHART) {
320
                $scale = ($plotGroup === null) ? '' : (string) $plotGroup->getPlotStyle();
321
                if ($scale !== '') {
322
                    $objWriter->startElement('c:bubbleScale');
323
                    $objWriter->writeAttribute('val', $scale);
324
                    $objWriter->endElement();
325
                }
326
 
327
                $objWriter->startElement('c:showNegBubbles');
328
                $objWriter->writeAttribute('val', '0');
329
                $objWriter->endElement();
330
            } elseif ($chartType === DataSeries::TYPE_STOCKCHART) {
331
                $objWriter->startElement('c:hiLowLines');
332
                $objWriter->endElement();
333
 
334
                $gapWidth = $plotArea->getGapWidth();
335
                $upBars = $plotArea->getUseUpBars();
336
                $downBars = $plotArea->getUseDownBars();
337
                if ($gapWidth !== null || $upBars || $downBars) {
338
                    $objWriter->startElement('c:upDownBars');
339
                    if ($gapWidth !== null) {
340
                        $objWriter->startElement('c:gapWidth');
341
                        $objWriter->writeAttribute('val', "$gapWidth");
342
                        $objWriter->endElement();
343
                    }
344
                    if ($upBars) {
345
                        $objWriter->startElement('c:upBars');
346
                        $objWriter->endElement();
347
                    }
348
                    if ($downBars) {
349
                        $objWriter->startElement('c:downBars');
350
                        $objWriter->endElement();
351
                    }
352
                    $objWriter->endElement(); // c:upDownBars
353
                }
354
            }
355
 
356
            //    Generate 3 unique numbers to use for axId values
357
            $id1 = '110438656';
358
            $id2 = '110444544';
359
            $id3 = '110365312'; // used in Surface Chart
360
 
361
            if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
362
                $objWriter->startElement('c:axId');
363
                $objWriter->writeAttribute('val', $id1);
364
                $objWriter->endElement();
365
                $objWriter->startElement('c:axId');
366
                $objWriter->writeAttribute('val', $id2);
367
                $objWriter->endElement();
368
                if ($chartType === DataSeries::TYPE_SURFACECHART_3D || $chartType === DataSeries::TYPE_SURFACECHART) {
369
                    $objWriter->startElement('c:axId');
370
                    $objWriter->writeAttribute('val', $id3);
371
                    $objWriter->endElement();
372
                }
373
            } else {
374
                $objWriter->startElement('c:firstSliceAng');
375
                $objWriter->writeAttribute('val', '0');
376
                $objWriter->endElement();
377
 
378
                if ($chartType === DataSeries::TYPE_DONUTCHART) {
379
                    $objWriter->startElement('c:holeSize');
380
                    $objWriter->writeAttribute('val', '50');
381
                    $objWriter->endElement();
382
                }
383
            }
384
 
385
            $objWriter->endElement();
386
        }
387
 
388
        if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
389
            if ($chartType === DataSeries::TYPE_BUBBLECHART) {
390
                $this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id2, $id1, $catIsMultiLevelSeries, $xAxis ?? new Axis());
391
            } else {
392
                $this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $xAxis ?? new Axis());
393
            }
394
 
395
            $this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $yAxis ?? new Axis());
396
            if ($chartType === DataSeries::TYPE_SURFACECHART_3D || $chartType === DataSeries::TYPE_SURFACECHART) {
397
                $this->writeSerAxis($objWriter, $id2, $id3);
398
            }
399
        }
400
        $stops = $plotArea->getGradientFillStops();
401
        if ($plotArea->getNoFill() || !empty($stops)) {
402
            $objWriter->startElement('c:spPr');
403
            if ($plotArea->getNoFill()) {
404
                $objWriter->startElement('a:noFill');
405
                $objWriter->endElement(); // a:noFill
406
            }
407
            if (!empty($stops)) {
408
                $objWriter->startElement('a:gradFill');
409
                $objWriter->startElement('a:gsLst');
410
                foreach ($stops as $stop) {
411
                    $objWriter->startElement('a:gs');
412
                    $objWriter->writeAttribute('pos', (string) (Properties::PERCENTAGE_MULTIPLIER * (float) $stop[0]));
413
                    $this->writeColor($objWriter, $stop[1], false);
414
                    $objWriter->endElement(); // a:gs
415
                }
416
                $objWriter->endElement(); // a:gsLst
417
                $angle = $plotArea->getGradientFillAngle();
418
                if ($angle !== null) {
419
                    $objWriter->startElement('a:lin');
420
                    $objWriter->writeAttribute('ang', Properties::angleToXml($angle));
421
                    $objWriter->endElement(); // a:lin
422
                }
423
                $objWriter->endElement(); // a:gradFill
424
            }
425
            $objWriter->endElement(); // c:spPr
426
        }
427
 
428
        $objWriter->endElement(); // c:plotArea
429
    }
430
 
431
    private function writeDataLabelsBool(XMLWriter $objWriter, string $name, ?bool $value): void
432
    {
433
        if ($value !== null) {
434
            $objWriter->startElement("c:$name");
435
            $objWriter->writeAttribute('val', $value ? '1' : '0');
436
            $objWriter->endElement();
437
        }
438
    }
439
 
440
    /**
441
     * Write Data Labels.
442
     */
443
    private function writeDataLabels(XMLWriter $objWriter, ?Layout $chartLayout = null): void
444
    {
445
        if (!isset($chartLayout)) {
446
            return;
447
        }
448
        $objWriter->startElement('c:dLbls');
449
 
450
        $fillColor = $chartLayout->getLabelFillColor();
451
        $borderColor = $chartLayout->getLabelBorderColor();
452
        if ($fillColor && $fillColor->isUsable()) {
453
            $objWriter->startElement('c:spPr');
454
            $this->writeColor($objWriter, $fillColor);
455
            if ($borderColor && $borderColor->isUsable()) {
456
                $objWriter->startElement('a:ln');
457
                $this->writeColor($objWriter, $borderColor);
458
                $objWriter->endElement(); // a:ln
459
            }
460
            $objWriter->endElement(); // c:spPr
461
        }
462
        $labelFont = $chartLayout->getLabelFont();
463
        if ($labelFont !== null) {
464
            $objWriter->startElement('c:txPr');
465
 
466
            $objWriter->startElement('a:bodyPr');
467
            $objWriter->writeAttribute('wrap', 'square');
468
            $objWriter->writeAttribute('lIns', '38100');
469
            $objWriter->writeAttribute('tIns', '19050');
470
            $objWriter->writeAttribute('rIns', '38100');
471
            $objWriter->writeAttribute('bIns', '19050');
472
            $objWriter->writeAttribute('anchor', 'ctr');
473
            $objWriter->startElement('a:spAutoFit');
474
            $objWriter->endElement(); // a:spAutoFit
475
            $objWriter->endElement(); // a:bodyPr
476
 
477
            $objWriter->startElement('a:lstStyle');
478
            $objWriter->endElement(); // a:lstStyle
479
            $this->writeLabelFont($objWriter, $labelFont, $chartLayout->getLabelEffects());
480
 
481
            $objWriter->endElement(); // c:txPr
482
        }
483
 
484
        if ($chartLayout->getNumFmtCode() !== '') {
485
            $objWriter->startElement('c:numFmt');
486
            $objWriter->writeAttribute('formatCode', $chartLayout->getnumFmtCode());
487
            $objWriter->writeAttribute('sourceLinked', (string) (int) $chartLayout->getnumFmtLinked());
488
            $objWriter->endElement(); // c:numFmt
489
        }
490
        if ($chartLayout->getDLblPos() !== '') {
491
            $objWriter->startElement('c:dLblPos');
492
            $objWriter->writeAttribute('val', $chartLayout->getDLblPos());
493
            $objWriter->endElement(); // c:dLblPos
494
        }
495
        $this->writeDataLabelsBool($objWriter, 'showLegendKey', $chartLayout->getShowLegendKey());
496
        $this->writeDataLabelsBool($objWriter, 'showVal', $chartLayout->getShowVal());
497
        $this->writeDataLabelsBool($objWriter, 'showCatName', $chartLayout->getShowCatName());
498
        $this->writeDataLabelsBool($objWriter, 'showSerName', $chartLayout->getShowSerName());
499
        $this->writeDataLabelsBool($objWriter, 'showPercent', $chartLayout->getShowPercent());
500
        $this->writeDataLabelsBool($objWriter, 'showBubbleSize', $chartLayout->getShowBubbleSize());
501
        $this->writeDataLabelsBool($objWriter, 'showLeaderLines', $chartLayout->getShowLeaderLines());
502
 
503
        $objWriter->endElement(); // c:dLbls
504
    }
505
 
506
    /**
507
     * Write Category Axis.
508
     *
509
     * @param string $id1
510
     * @param string $id2
511
     * @param bool $isMultiLevelSeries
512
     */
513
    private function writeCategoryAxis(XMLWriter $objWriter, ?Title $xAxisLabel, $id1, $id2, $isMultiLevelSeries, Axis $yAxis): void
514
    {
515
        // N.B. writeCategoryAxis may be invoked with the last parameter($yAxis) using $xAxis for ScatterChart, etc
516
        // In that case, xAxis may contain values like the yAxis, or it may be a date axis (LINECHART).
517
        $axisType = $yAxis->getAxisType();
518
        if ($axisType !== '') {
519
            $objWriter->startElement("c:$axisType");
520
        } elseif ($yAxis->getAxisIsNumericFormat()) {
521
            $objWriter->startElement('c:' . Axis::AXIS_TYPE_VALUE);
522
        } else {
523
            $objWriter->startElement('c:' . Axis::AXIS_TYPE_CATEGORY);
524
        }
525
        $majorGridlines = $yAxis->getMajorGridlines();
526
        $minorGridlines = $yAxis->getMinorGridlines();
527
 
528
        if ($id1 !== '0') {
529
            $objWriter->startElement('c:axId');
530
            $objWriter->writeAttribute('val', $id1);
531
            $objWriter->endElement();
532
        }
533
 
534
        $objWriter->startElement('c:scaling');
535
        if ($yAxis->getAxisOptionsProperty('maximum') !== null) {
536
            $objWriter->startElement('c:max');
537
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('maximum'));
538
            $objWriter->endElement();
539
        }
540
        if ($yAxis->getAxisOptionsProperty('minimum') !== null) {
541
            $objWriter->startElement('c:min');
542
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minimum'));
543
            $objWriter->endElement();
544
        }
545
        if (!empty($yAxis->getAxisOptionsProperty('orientation'))) {
546
            $objWriter->startElement('c:orientation');
547
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('orientation'));
548
            $objWriter->endElement();
549
        }
550
        $objWriter->endElement(); // c:scaling
551
 
552
        $objWriter->startElement('c:delete');
553
        $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('hidden') ?? '0');
554
        $objWriter->endElement();
555
 
556
        $objWriter->startElement('c:axPos');
557
        $objWriter->writeAttribute('val', 'b');
558
        $objWriter->endElement();
559
 
560
        if ($majorGridlines !== null) {
561
            $objWriter->startElement('c:majorGridlines');
562
            $objWriter->startElement('c:spPr');
563
            $this->writeLineStyles($objWriter, $majorGridlines);
564
            $this->writeEffects($objWriter, $majorGridlines);
565
            $objWriter->endElement(); //end spPr
566
            $objWriter->endElement(); //end majorGridLines
567
        }
568
 
569
        if ($minorGridlines !== null && $minorGridlines->getObjectState()) {
570
            $objWriter->startElement('c:minorGridlines');
571
            $objWriter->startElement('c:spPr');
572
            $this->writeLineStyles($objWriter, $minorGridlines);
573
            $this->writeEffects($objWriter, $minorGridlines);
574
            $objWriter->endElement(); //end spPr
575
            $objWriter->endElement(); //end minorGridLines
576
        }
577
 
578
        if ($xAxisLabel !== null) {
579
            $objWriter->startElement('c:title');
580
            $objWriter->startElement('c:tx');
581
            $objWriter->startElement('c:rich');
582
 
583
            $objWriter->startElement('a:bodyPr');
584
            $objWriter->endElement();
585
 
586
            $objWriter->startElement('a:lstStyle');
587
            $objWriter->endElement();
588
 
589
            $objWriter->startElement('a:p');
590
 
591
            $caption = $xAxisLabel->getCaption();
592
            if (is_array($caption)) {
593
                $caption = $caption[0];
594
            }
595
            $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a');
596
 
597
            $objWriter->endElement();
598
            $objWriter->endElement();
599
            $objWriter->endElement();
600
 
601
            $layout = $xAxisLabel->getLayout();
602
            $this->writeLayout($objWriter, $layout);
603
 
604
            $objWriter->startElement('c:overlay');
605
            $objWriter->writeAttribute('val', '0');
606
            $objWriter->endElement();
607
 
608
            $objWriter->endElement();
609
        }
610
 
611
        $objWriter->startElement('c:numFmt');
612
        $objWriter->writeAttribute('formatCode', $yAxis->getAxisNumberFormat());
613
        $objWriter->writeAttribute('sourceLinked', $yAxis->getAxisNumberSourceLinked());
614
        $objWriter->endElement();
615
 
616
        if (!empty($yAxis->getAxisOptionsProperty('major_tick_mark'))) {
617
            $objWriter->startElement('c:majorTickMark');
618
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('major_tick_mark'));
619
            $objWriter->endElement();
620
        }
621
 
622
        if (!empty($yAxis->getAxisOptionsProperty('minor_tick_mark'))) {
623
            $objWriter->startElement('c:minorTickMark');
624
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minor_tick_mark'));
625
            $objWriter->endElement();
626
        }
627
 
628
        if (!empty($yAxis->getAxisOptionsProperty('axis_labels'))) {
629
            $objWriter->startElement('c:tickLblPos');
630
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('axis_labels'));
631
            $objWriter->endElement();
632
        }
633
 
634
        $textRotation = $yAxis->getAxisOptionsProperty('textRotation');
635
        $axisText = $yAxis->getAxisText();
636
 
637
        if ($axisText !== null || is_numeric($textRotation)) {
638
            $objWriter->startElement('c:txPr');
639
            $objWriter->startElement('a:bodyPr');
640
            if (is_numeric($textRotation)) {
641
                $objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation));
642
            }
643
            $objWriter->endElement(); // a:bodyPr
644
            $objWriter->startElement('a:lstStyle');
645
            $objWriter->endElement(); // a:lstStyle
646
            $this->writeLabelFont($objWriter, ($axisText === null) ? null : $axisText->getFont(), $axisText);
647
            $objWriter->endElement(); // c:txPr
648
        }
649
 
650
        $objWriter->startElement('c:spPr');
651
        $this->writeColor($objWriter, $yAxis->getFillColorObject());
652
        $this->writeLineStyles($objWriter, $yAxis, $yAxis->getNoFill());
653
        $this->writeEffects($objWriter, $yAxis);
654
        $objWriter->endElement(); // spPr
655
 
656
        if ($yAxis->getAxisOptionsProperty('major_unit') !== null) {
657
            $objWriter->startElement('c:majorUnit');
658
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('major_unit'));
659
            $objWriter->endElement();
660
        }
661
 
662
        if ($yAxis->getAxisOptionsProperty('minor_unit') !== null) {
663
            $objWriter->startElement('c:minorUnit');
664
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minor_unit'));
665
            $objWriter->endElement();
666
        }
667
 
668
        if ($id2 !== '0') {
669
            $objWriter->startElement('c:crossAx');
670
            $objWriter->writeAttribute('val', $id2);
671
            $objWriter->endElement();
672
 
673
            if (!empty($yAxis->getAxisOptionsProperty('horizontal_crosses'))) {
674
                $objWriter->startElement('c:crosses');
675
                $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('horizontal_crosses'));
676
                $objWriter->endElement();
677
            }
678
        }
679
 
680
        $objWriter->startElement('c:auto');
681
        // LineChart with dateAx wants '0'
682
        $objWriter->writeAttribute('val', ($axisType === Axis::AXIS_TYPE_DATE) ? '0' : '1');
683
        $objWriter->endElement();
684
 
685
        $objWriter->startElement('c:lblAlgn');
686
        $objWriter->writeAttribute('val', 'ctr');
687
        $objWriter->endElement();
688
 
689
        $objWriter->startElement('c:lblOffset');
690
        $objWriter->writeAttribute('val', '100');
691
        $objWriter->endElement();
692
 
693
        if ($axisType === Axis::AXIS_TYPE_DATE) {
694
            $property = 'baseTimeUnit';
695
            $propertyVal = $yAxis->getAxisOptionsProperty($property);
696
            if (!empty($propertyVal)) {
697
                $objWriter->startElement("c:$property");
698
                $objWriter->writeAttribute('val', $propertyVal);
699
                $objWriter->endElement();
700
            }
701
            $property = 'majorTimeUnit';
702
            $propertyVal = $yAxis->getAxisOptionsProperty($property);
703
            if (!empty($propertyVal)) {
704
                $objWriter->startElement("c:$property");
705
                $objWriter->writeAttribute('val', $propertyVal);
706
                $objWriter->endElement();
707
            }
708
            $property = 'minorTimeUnit';
709
            $propertyVal = $yAxis->getAxisOptionsProperty($property);
710
            if (!empty($propertyVal)) {
711
                $objWriter->startElement("c:$property");
712
                $objWriter->writeAttribute('val', $propertyVal);
713
                $objWriter->endElement();
714
            }
715
        }
716
 
717
        if ($isMultiLevelSeries) {
718
            $objWriter->startElement('c:noMultiLvlLbl');
719
            $objWriter->writeAttribute('val', '0');
720
            $objWriter->endElement();
721
        }
722
        $objWriter->endElement();
723
    }
724
 
725
    /**
726
     * Write Value Axis.
727
     *
728
     * @param null|string $groupType Chart type
729
     * @param string $id1
730
     * @param string $id2
731
     * @param bool $isMultiLevelSeries
732
     */
733
    private function writeValueAxis(XMLWriter $objWriter, ?Title $yAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries, Axis $xAxis): void
734
    {
735
        $objWriter->startElement('c:' . Axis::AXIS_TYPE_VALUE);
736
        $majorGridlines = $xAxis->getMajorGridlines();
737
        $minorGridlines = $xAxis->getMinorGridlines();
738
 
739
        if ($id2 !== '0') {
740
            $objWriter->startElement('c:axId');
741
            $objWriter->writeAttribute('val', $id2);
742
            $objWriter->endElement();
743
        }
744
 
745
        $objWriter->startElement('c:scaling');
746
 
747
        if ($xAxis->getAxisOptionsProperty('maximum') !== null) {
748
            $objWriter->startElement('c:max');
749
            $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('maximum'));
750
            $objWriter->endElement();
751
        }
752
 
753
        if ($xAxis->getAxisOptionsProperty('minimum') !== null) {
754
            $objWriter->startElement('c:min');
755
            $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minimum'));
756
            $objWriter->endElement();
757
        }
758
 
759
        if (!empty($xAxis->getAxisOptionsProperty('orientation'))) {
760
            $objWriter->startElement('c:orientation');
761
            $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('orientation'));
762
            $objWriter->endElement();
763
        }
764
 
765
        $objWriter->endElement(); // c:scaling
766
 
767
        $objWriter->startElement('c:delete');
768
        $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('hidden') ?? '0');
769
        $objWriter->endElement();
770
 
771
        $objWriter->startElement('c:axPos');
772
        $objWriter->writeAttribute('val', 'l');
773
        $objWriter->endElement();
774
 
775
        if ($majorGridlines !== null) {
776
            $objWriter->startElement('c:majorGridlines');
777
            $objWriter->startElement('c:spPr');
778
            $this->writeLineStyles($objWriter, $majorGridlines);
779
            $this->writeEffects($objWriter, $majorGridlines);
780
            $objWriter->endElement(); //end spPr
781
            $objWriter->endElement(); //end majorGridLines
782
        }
783
 
784
        if ($minorGridlines !== null && $minorGridlines->getObjectState()) {
785
            $objWriter->startElement('c:minorGridlines');
786
            $objWriter->startElement('c:spPr');
787
            $this->writeLineStyles($objWriter, $minorGridlines);
788
            $this->writeEffects($objWriter, $minorGridlines);
789
            $objWriter->endElement(); //end spPr
790
            $objWriter->endElement(); //end minorGridLines
791
        }
792
 
793
        if ($yAxisLabel !== null) {
794
            $objWriter->startElement('c:title');
795
            $objWriter->startElement('c:tx');
796
            $objWriter->startElement('c:rich');
797
 
798
            $objWriter->startElement('a:bodyPr');
799
            $objWriter->endElement();
800
 
801
            $objWriter->startElement('a:lstStyle');
802
            $objWriter->endElement();
803
 
804
            $objWriter->startElement('a:p');
805
 
806
            $caption = $yAxisLabel->getCaption();
807
            if (is_array($caption)) {
808
                $caption = $caption[0];
809
            }
810
            $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a');
811
 
812
            $objWriter->endElement();
813
            $objWriter->endElement();
814
            $objWriter->endElement();
815
 
816
            if ($groupType !== DataSeries::TYPE_BUBBLECHART) {
817
                $layout = $yAxisLabel->getLayout();
818
                $this->writeLayout($objWriter, $layout);
819
            }
820
 
821
            $objWriter->startElement('c:overlay');
822
            $objWriter->writeAttribute('val', '0');
823
            $objWriter->endElement();
824
 
825
            $objWriter->endElement();
826
        }
827
 
828
        $objWriter->startElement('c:numFmt');
829
        $objWriter->writeAttribute('formatCode', $xAxis->getAxisNumberFormat());
830
        $objWriter->writeAttribute('sourceLinked', $xAxis->getAxisNumberSourceLinked());
831
        $objWriter->endElement();
832
 
833
        if (!empty($xAxis->getAxisOptionsProperty('major_tick_mark'))) {
834
            $objWriter->startElement('c:majorTickMark');
835
            $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_tick_mark'));
836
            $objWriter->endElement();
837
        }
838
 
839
        if (!empty($xAxis->getAxisOptionsProperty('minor_tick_mark'))) {
840
            $objWriter->startElement('c:minorTickMark');
841
            $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_tick_mark'));
842
            $objWriter->endElement();
843
        }
844
 
845
        if (!empty($xAxis->getAxisOptionsProperty('axis_labels'))) {
846
            $objWriter->startElement('c:tickLblPos');
847
            $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('axis_labels'));
848
            $objWriter->endElement();
849
        }
850
 
851
        $textRotation = $xAxis->getAxisOptionsProperty('textRotation');
852
        $axisText = $xAxis->getAxisText();
853
 
854
        if ($axisText !== null || is_numeric($textRotation)) {
855
            $objWriter->startElement('c:txPr');
856
            $objWriter->startElement('a:bodyPr');
857
            if (is_numeric($textRotation)) {
858
                $objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation));
859
            }
860
            $objWriter->endElement(); // a:bodyPr
861
            $objWriter->startElement('a:lstStyle');
862
            $objWriter->endElement(); // a:lstStyle
863
 
864
            $this->writeLabelFont($objWriter, ($axisText === null) ? null : $axisText->getFont(), $axisText);
865
 
866
            $objWriter->endElement(); // c:txPr
867
        }
868
 
869
        $objWriter->startElement('c:spPr');
870
        $this->writeColor($objWriter, $xAxis->getFillColorObject());
871
        $this->writeLineStyles($objWriter, $xAxis, $xAxis->getNoFill());
872
        $this->writeEffects($objWriter, $xAxis);
873
        $objWriter->endElement(); //end spPr
874
 
875
        if ($id1 !== '0') {
876
            $objWriter->startElement('c:crossAx');
877
            $objWriter->writeAttribute('val', $id1);
878
            $objWriter->endElement();
879
 
880
            if ($xAxis->getAxisOptionsProperty('horizontal_crosses_value') !== null) {
881
                $objWriter->startElement('c:crossesAt');
882
                $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses_value'));
883
                $objWriter->endElement();
884
            } else {
885
                $crosses = $xAxis->getAxisOptionsProperty('horizontal_crosses');
886
                if ($crosses) {
887
                    $objWriter->startElement('c:crosses');
888
                    $objWriter->writeAttribute('val', $crosses);
889
                    $objWriter->endElement();
890
                }
891
            }
892
 
893
            $crossBetween = $xAxis->getCrossBetween();
894
            if ($crossBetween !== '') {
895
                $objWriter->startElement('c:crossBetween');
896
                $objWriter->writeAttribute('val', $crossBetween);
897
                $objWriter->endElement();
898
            }
899
 
900
            if ($xAxis->getAxisOptionsProperty('major_unit') !== null) {
901
                $objWriter->startElement('c:majorUnit');
902
                $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_unit'));
903
                $objWriter->endElement();
904
            }
905
 
906
            if ($xAxis->getAxisOptionsProperty('minor_unit') !== null) {
907
                $objWriter->startElement('c:minorUnit');
908
                $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_unit'));
909
                $objWriter->endElement();
910
            }
911
        }
912
 
913
        if ($isMultiLevelSeries) {
914
            if ($groupType !== DataSeries::TYPE_BUBBLECHART) {
915
                $objWriter->startElement('c:noMultiLvlLbl');
916
                $objWriter->writeAttribute('val', '0');
917
                $objWriter->endElement();
918
            }
919
        }
920
 
921
        $objWriter->endElement();
922
    }
923
 
924
    /**
925
     * Write Ser Axis, for Surface chart.
926
     */
927
    private function writeSerAxis(XMLWriter $objWriter, string $id2, string $id3): void
928
    {
929
        $objWriter->startElement('c:serAx');
930
 
931
        $objWriter->startElement('c:axId');
932
        $objWriter->writeAttribute('val', $id3);
933
        $objWriter->endElement(); // axId
934
 
935
        $objWriter->startElement('c:scaling');
936
        $objWriter->startElement('c:orientation');
937
        $objWriter->writeAttribute('val', 'minMax');
938
        $objWriter->endElement(); // orientation
939
        $objWriter->endElement(); // scaling
940
 
941
        $objWriter->startElement('c:delete');
942
        $objWriter->writeAttribute('val', '0');
943
        $objWriter->endElement(); // delete
944
 
945
        $objWriter->startElement('c:axPos');
946
        $objWriter->writeAttribute('val', 'b');
947
        $objWriter->endElement(); // axPos
948
 
949
        $objWriter->startElement('c:majorTickMark');
950
        $objWriter->writeAttribute('val', 'out');
951
        $objWriter->endElement(); // majorTickMark
952
 
953
        $objWriter->startElement('c:minorTickMark');
954
        $objWriter->writeAttribute('val', 'none');
955
        $objWriter->endElement(); // minorTickMark
956
 
957
        $objWriter->startElement('c:tickLblPos');
958
        $objWriter->writeAttribute('val', 'nextTo');
959
        $objWriter->endElement(); // tickLblPos
960
 
961
        $objWriter->startElement('c:crossAx');
962
        $objWriter->writeAttribute('val', $id2);
963
        $objWriter->endElement(); // crossAx
964
 
965
        $objWriter->startElement('c:crosses');
966
        $objWriter->writeAttribute('val', 'autoZero');
967
        $objWriter->endElement(); // crosses
968
 
969
        $objWriter->endElement(); //serAx
970
    }
971
 
972
    /**
973
     * Get the data series type(s) for a chart plot series.
974
     *
975
     * @return string[]
976
     */
977
    private static function getChartType(PlotArea $plotArea): array
978
    {
979
        $groupCount = $plotArea->getPlotGroupCount();
980
 
981
        if ($groupCount == 1) {
982
            $chartType = [$plotArea->getPlotGroupByIndex(0)->getPlotType()];
983
        } else {
984
            $chartTypes = [];
985
            for ($i = 0; $i < $groupCount; ++$i) {
986
                $chartTypes[] = $plotArea->getPlotGroupByIndex($i)->getPlotType();
987
            }
988
            $chartType = array_unique($chartTypes);
989
            if (count($chartTypes) == 0) {
990
                throw new WriterException('Chart is not yet implemented');
991
            }
992
        }
993
 
994
        return $chartType;
995
    }
996
 
997
    /**
998
     * Method writing plot series values.
999
     */
1000
    private function writePlotSeriesValuesElement(XMLWriter $objWriter, int $val, ?ChartColor $fillColor): void
1001
    {
1002
        if ($fillColor === null || !$fillColor->isUsable()) {
1003
            return;
1004
        }
1005
        $objWriter->startElement('c:dPt');
1006
 
1007
        $objWriter->startElement('c:idx');
1008
        $objWriter->writeAttribute('val', "$val");
1009
        $objWriter->endElement(); // c:idx
1010
 
1011
        $objWriter->startElement('c:spPr');
1012
        $this->writeColor($objWriter, $fillColor);
1013
        $objWriter->endElement(); // c:spPr
1014
 
1015
        $objWriter->endElement(); // c:dPt
1016
    }
1017
 
1018
    /**
1019
     * Write Plot Group (series of related plots).
1020
     *
1021
     * @param string $groupType Type of plot for dataseries
1022
     * @param bool $catIsMultiLevelSeries Is category a multi-series category
1023
     * @param bool $valIsMultiLevelSeries Is value set a multi-series set
1024
     * @param string $plotGroupingType Type of grouping for multi-series values
1025
     */
1026
    private function writePlotGroup(?DataSeries $plotGroup, string $groupType, XMLWriter $objWriter, &$catIsMultiLevelSeries, &$valIsMultiLevelSeries, &$plotGroupingType): void
1027
    {
1028
        if ($plotGroup === null) {
1029
            return;
1030
        }
1031
 
1032
        if (($groupType == DataSeries::TYPE_BARCHART) || ($groupType == DataSeries::TYPE_BARCHART_3D)) {
1033
            $objWriter->startElement('c:barDir');
1034
            $objWriter->writeAttribute('val', $plotGroup->getPlotDirection());
1035
            $objWriter->endElement();
1036
        }
1037
 
1038
        $plotGroupingType = $plotGroup->getPlotGrouping();
1039
        if ($plotGroupingType !== null && $groupType !== DataSeries::TYPE_SURFACECHART && $groupType !== DataSeries::TYPE_SURFACECHART_3D) {
1040
            $objWriter->startElement('c:grouping');
1041
            $objWriter->writeAttribute('val', $plotGroupingType);
1042
            $objWriter->endElement();
1043
        }
1044
 
1045
        //    Get these details before the loop, because we can use the count to check for varyColors
1046
        $plotSeriesOrder = $plotGroup->getPlotOrder();
1047
        $plotSeriesCount = count($plotSeriesOrder);
1048
 
1049
        if (($groupType !== DataSeries::TYPE_RADARCHART) && ($groupType !== DataSeries::TYPE_STOCKCHART)) {
1050
            if ($groupType !== DataSeries::TYPE_LINECHART) {
1051
                if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART) || ($plotSeriesCount > 1)) {
1052
                    $objWriter->startElement('c:varyColors');
1053
                    $objWriter->writeAttribute('val', '1');
1054
                    $objWriter->endElement();
1055
                } else {
1056
                    $objWriter->startElement('c:varyColors');
1057
                    $objWriter->writeAttribute('val', '0');
1058
                    $objWriter->endElement();
1059
                }
1060
            }
1061
        }
1062
 
1063
        $plotSeriesIdx = 0;
1064
        foreach ($plotSeriesOrder as $plotSeriesIdx => $plotSeriesRef) {
1065
            $objWriter->startElement('c:ser');
1066
 
1067
            $objWriter->startElement('c:idx');
1068
            $adder = array_key_exists(0, $plotSeriesOrder) ? $this->seriesIndex : 0;
1069
            $objWriter->writeAttribute('val', (string) ($adder + $plotSeriesIdx));
1070
            $objWriter->endElement();
1071
 
1072
            $objWriter->startElement('c:order');
1073
            $objWriter->writeAttribute('val', (string) ($adder + $plotSeriesRef));
1074
            $objWriter->endElement();
1075
 
1076
            $plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx);
1077
            $labelFill = null;
1078
            if ($plotLabel && $groupType === DataSeries::TYPE_LINECHART) {
1079
                $labelFill = $plotLabel->getFillColorObject();
1080
                $labelFill = ($labelFill instanceof ChartColor) ? $labelFill : null;
1081
            }
1082
 
1083
            //    Values
1084
            $plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesIdx);
1085
 
1086
            if ($plotSeriesValues !== false && in_array($groupType, self::CUSTOM_COLOR_TYPES, true)) {
1087
                $fillColorValues = $plotSeriesValues->getFillColorObject();
1088
                if ($fillColorValues !== null && is_array($fillColorValues)) {
1089
                    foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) {
1090
                        $this->writePlotSeriesValuesElement($objWriter, $dataKey, $fillColorValues[$dataKey] ?? null);
1091
                    }
1092
                }
1093
            }
1094
            if ($plotSeriesValues !== false && $plotSeriesValues->getLabelLayout()) {
1095
                $this->writeDataLabels($objWriter, $plotSeriesValues->getLabelLayout());
1096
            }
1097
 
1098
            //    Labels
1099
            $plotSeriesLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx);
1100
            if ($plotSeriesLabel && ($plotSeriesLabel->getPointCount() > 0)) {
1101
                $objWriter->startElement('c:tx');
1102
                $objWriter->startElement('c:strRef');
1103
                $this->writePlotSeriesLabel($plotSeriesLabel, $objWriter);
1104
                $objWriter->endElement();
1105
                $objWriter->endElement();
1106
            }
1107
 
1108
            //    Formatting for the points
1109
            if (
1110
                $plotSeriesValues !== false
1111
            ) {
1112
                $objWriter->startElement('c:spPr');
1113
                if ($plotLabel && $groupType !== DataSeries::TYPE_LINECHART) {
1114
                    $fillColor = $plotLabel->getFillColorObject();
1115
                    if ($fillColor !== null && !is_array($fillColor) && $fillColor->isUsable()) {
1116
                        $this->writeColor($objWriter, $fillColor);
1117
                    }
1118
                }
1119
                $fillObject = $labelFill ?? $plotSeriesValues->getFillColorObject();
1120
                $callLineStyles = true;
1121
                if ($fillObject instanceof ChartColor && $fillObject->isUsable()) {
1122
                    if ($groupType === DataSeries::TYPE_LINECHART) {
1123
                        $objWriter->startElement('a:ln');
1124
                        $callLineStyles = false;
1125
                    }
1126
                    $this->writeColor($objWriter, $fillObject);
1127
                    if (!$callLineStyles) {
1128
                        $objWriter->endElement(); // a:ln
1129
                    }
1130
                }
1131
                $nofill = $groupType === DataSeries::TYPE_STOCKCHART || (($groupType === DataSeries::TYPE_SCATTERCHART || $groupType === DataSeries::TYPE_LINECHART) && !$plotSeriesValues->getScatterLines());
1132
                if ($callLineStyles) {
1133
                    $this->writeLineStyles($objWriter, $plotSeriesValues, $nofill);
1134
                    $this->writeEffects($objWriter, $plotSeriesValues);
1135
                }
1136
                $objWriter->endElement(); // c:spPr
1137
            }
1138
 
1139
            if ($plotSeriesValues) {
1140
                $plotSeriesMarker = $plotSeriesValues->getPointMarker();
1141
                $markerFillColor = $plotSeriesValues->getMarkerFillColor();
1142
                $fillUsed = $markerFillColor->IsUsable();
1143
                $markerBorderColor = $plotSeriesValues->getMarkerBorderColor();
1144
                $borderUsed = $markerBorderColor->isUsable();
1145
                if ($plotSeriesMarker || $fillUsed || $borderUsed) {
1146
                    $objWriter->startElement('c:marker');
1147
                    $objWriter->startElement('c:symbol');
1148
                    if ($plotSeriesMarker) {
1149
                        $objWriter->writeAttribute('val', $plotSeriesMarker);
1150
                    }
1151
                    $objWriter->endElement();
1152
 
1153
                    if ($plotSeriesMarker !== 'none') {
1154
                        $objWriter->startElement('c:size');
1155
                        $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointSize());
1156
                        $objWriter->endElement(); // c:size
1157
                        $objWriter->startElement('c:spPr');
1158
                        $this->writeColor($objWriter, $markerFillColor);
1159
                        if ($borderUsed) {
1160
                            $objWriter->startElement('a:ln');
1161
                            $this->writeColor($objWriter, $markerBorderColor);
1162
                            $objWriter->endElement(); // a:ln
1163
                        }
1164
                        $objWriter->endElement(); // spPr
1165
                    }
1166
 
1167
                    $objWriter->endElement();
1168
                }
1169
            }
1170
 
1171
            if (($groupType === DataSeries::TYPE_BARCHART) || ($groupType === DataSeries::TYPE_BARCHART_3D) || ($groupType === DataSeries::TYPE_BUBBLECHART)) {
1172
                $objWriter->startElement('c:invertIfNegative');
1173
                $objWriter->writeAttribute('val', '0');
1174
                $objWriter->endElement();
1175
            }
1176
            // Trendlines
1177
            if ($plotSeriesValues !== false) {
1178
                foreach ($plotSeriesValues->getTrendLines() as $trendLine) {
1179
                    $trendLineType = $trendLine->getTrendLineType();
1180
                    $order = $trendLine->getOrder();
1181
                    $period = $trendLine->getPeriod();
1182
                    $dispRSqr = $trendLine->getDispRSqr();
1183
                    $dispEq = $trendLine->getDispEq();
1184
                    $forward = $trendLine->getForward();
1185
                    $backward = $trendLine->getBackward();
1186
                    $intercept = $trendLine->getIntercept();
1187
                    $name = $trendLine->getName();
1188
                    $trendLineColor = $trendLine->getLineColor(); // ChartColor
1189
 
1190
                    $objWriter->startElement('c:trendline'); // N.B. lowercase 'ell'
1191
                    if ($name !== '') {
1192
                        $objWriter->startElement('c:name');
1193
                        $objWriter->writeRawData($name);
1194
                        $objWriter->endElement(); // c:name
1195
                    }
1196
                    $objWriter->startElement('c:spPr');
1197
 
1198
                    if (!$trendLineColor->isUsable()) {
1199
                        // use dataSeriesValues line color as a backup if $trendLineColor is null
1200
                        $dsvLineColor = $plotSeriesValues->getLineColor();
1201
                        if ($dsvLineColor->isUsable()) {
1202
                            $trendLine
1203
                                ->getLineColor()
1204
                                ->setColorProperties($dsvLineColor->getValue(), $dsvLineColor->getAlpha(), $dsvLineColor->getType());
1205
                        }
1206
                    } // otherwise, hope Excel does the right thing
1207
 
1208
                    $this->writeLineStyles($objWriter, $trendLine, false); // suppress noFill
1209
 
1210
                    $objWriter->endElement(); // spPr
1211
 
1212
                    $objWriter->startElement('c:trendlineType'); // N.B lowercase 'ell'
1213
                    $objWriter->writeAttribute('val', $trendLineType);
1214
                    $objWriter->endElement(); // trendlineType
1215
                    if ($backward !== 0.0) {
1216
                        $objWriter->startElement('c:backward');
1217
                        $objWriter->writeAttribute('val', "$backward");
1218
                        $objWriter->endElement(); // c:backward
1219
                    }
1220
                    if ($forward !== 0.0) {
1221
                        $objWriter->startElement('c:forward');
1222
                        $objWriter->writeAttribute('val', "$forward");
1223
                        $objWriter->endElement(); // c:forward
1224
                    }
1225
                    if ($intercept !== 0.0) {
1226
                        $objWriter->startElement('c:intercept');
1227
                        $objWriter->writeAttribute('val', "$intercept");
1228
                        $objWriter->endElement(); // c:intercept
1229
                    }
1230
                    if ($trendLineType == TrendLine::TRENDLINE_POLYNOMIAL) {
1231
                        $objWriter->startElement('c:order');
1232
                        $objWriter->writeAttribute('val', $order);
1233
                        $objWriter->endElement(); // order
1234
                    }
1235
                    if ($trendLineType == TrendLine::TRENDLINE_MOVING_AVG) {
1236
                        $objWriter->startElement('c:period');
1237
                        $objWriter->writeAttribute('val', $period);
1238
                        $objWriter->endElement(); // period
1239
                    }
1240
                    $objWriter->startElement('c:dispRSqr');
1241
                    $objWriter->writeAttribute('val', $dispRSqr ? '1' : '0');
1242
                    $objWriter->endElement();
1243
                    $objWriter->startElement('c:dispEq');
1244
                    $objWriter->writeAttribute('val', $dispEq ? '1' : '0');
1245
                    $objWriter->endElement();
1246
                    if ($groupType === DataSeries::TYPE_SCATTERCHART || $groupType === DataSeries::TYPE_LINECHART) {
1247
                        $objWriter->startElement('c:trendlineLbl');
1248
                        $objWriter->startElement('c:numFmt');
1249
                        $objWriter->writeAttribute('formatCode', 'General');
1250
                        $objWriter->writeAttribute('sourceLinked', '0');
1251
                        $objWriter->endElement();  // numFmt
1252
                        $objWriter->endElement();  // trendlineLbl
1253
                    }
1254
 
1255
                    $objWriter->endElement(); // trendline
1256
                }
1257
            }
1258
 
1259
            //    Category Labels
1260
            $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesIdx);
1261
            if ($plotSeriesCategory && ($plotSeriesCategory->getPointCount() > 0)) {
1262
                $catIsMultiLevelSeries = $catIsMultiLevelSeries || $plotSeriesCategory->isMultiLevelSeries();
1263
 
1264
                if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) {
1265
                    $plotStyle = $plotGroup->getPlotStyle();
1266
                    if (is_numeric($plotStyle)) {
1267
                        $objWriter->startElement('c:explosion');
1268
                        $objWriter->writeAttribute('val', $plotStyle);
1269
                        $objWriter->endElement();
1270
                    }
1271
                }
1272
 
1273
                if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) {
1274
                    $objWriter->startElement('c:xVal');
1275
                } else {
1276
                    $objWriter->startElement('c:cat');
1277
                }
1278
 
1279
                // xVals (Categories) are not always 'str'
1280
                // Test X-axis Label's Datatype to decide 'str' vs 'num'
1281
                $CategoryDatatype = $plotSeriesCategory->getDataType();
1282
                if ($CategoryDatatype == DataSeriesValues::DATASERIES_TYPE_NUMBER) {
1283
                    $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'num');
1284
                } else {
1285
                    $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'str');
1286
                }
1287
                $objWriter->endElement();
1288
            }
1289
 
1290
            //    Values
1291
            if ($plotSeriesValues) {
1292
                $valIsMultiLevelSeries = $valIsMultiLevelSeries || $plotSeriesValues->isMultiLevelSeries();
1293
 
1294
                if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) {
1295
                    $objWriter->startElement('c:yVal');
1296
                } else {
1297
                    $objWriter->startElement('c:val');
1298
                }
1299
 
1300
                $this->writePlotSeriesValues($plotSeriesValues, $objWriter, $groupType, 'num');
1301
                $objWriter->endElement();
1302
                if ($groupType === DataSeries::TYPE_SCATTERCHART && $plotGroup->getPlotStyle() === 'smoothMarker') {
1303
                    $objWriter->startElement('c:smooth');
1304
                    $objWriter->writeAttribute('val', $plotSeriesValues->getSmoothLine() ? '1' : '0');
1305
                    $objWriter->endElement();
1306
                }
1307
            }
1308
 
1309
            if ($groupType === DataSeries::TYPE_BUBBLECHART) {
1310
                if (!empty($plotGroup->getPlotBubbleSizes()[$plotSeriesIdx])) {
1311
                    $objWriter->startElement('c:bubbleSize');
1312
                    $this->writePlotSeriesValues(
1313
                        $plotGroup->getPlotBubbleSizes()[$plotSeriesIdx],
1314
                        $objWriter,
1315
                        $groupType,
1316
                        'num'
1317
                    );
1318
                    $objWriter->endElement();
1319
                    if ($plotSeriesValues !== false) {
1320
                        $objWriter->startElement('c:bubble3D');
1321
                        $objWriter->writeAttribute('val', $plotSeriesValues->getBubble3D() ? '1' : '0');
1322
                        $objWriter->endElement();
1323
                    }
1324
                } elseif ($plotSeriesValues !== false) {
1325
                    $this->writeBubbles($plotSeriesValues, $objWriter);
1326
                }
1327
            }
1328
 
1329
            $objWriter->endElement();
1330
        }
1331
 
1332
        $this->seriesIndex += $plotSeriesIdx + 1;
1333
    }
1334
 
1335
    /**
1336
     * Write Plot Series Label.
1337
     */
1338
    private function writePlotSeriesLabel(?DataSeriesValues $plotSeriesLabel, XMLWriter $objWriter): void
1339
    {
1340
        if ($plotSeriesLabel === null) {
1341
            return;
1342
        }
1343
 
1344
        $objWriter->startElement('c:f');
1345
        $objWriter->writeRawData($plotSeriesLabel->getDataSource());
1346
        $objWriter->endElement();
1347
 
1348
        $objWriter->startElement('c:strCache');
1349
        $objWriter->startElement('c:ptCount');
1350
        $objWriter->writeAttribute('val', (string) $plotSeriesLabel->getPointCount());
1351
        $objWriter->endElement();
1352
 
1353
        foreach ($plotSeriesLabel->getDataValues() as $plotLabelKey => $plotLabelValue) {
1354
            $objWriter->startElement('c:pt');
1355
            $objWriter->writeAttribute('idx', $plotLabelKey);
1356
 
1357
            $objWriter->startElement('c:v');
1358
            $objWriter->writeRawData($plotLabelValue);
1359
            $objWriter->endElement();
1360
            $objWriter->endElement();
1361
        }
1362
        $objWriter->endElement();
1363
    }
1364
 
1365
    /**
1366
     * Write Plot Series Values.
1367
     *
1368
     * @param string $groupType Type of plot for dataseries
1369
     * @param string $dataType Datatype of series values
1370
     */
1371
    private function writePlotSeriesValues(?DataSeriesValues $plotSeriesValues, XMLWriter $objWriter, $groupType, $dataType = 'str'): void
1372
    {
1373
        if ($plotSeriesValues === null) {
1374
            return;
1375
        }
1376
 
1377
        if ($plotSeriesValues->isMultiLevelSeries()) {
1378
            $levelCount = $plotSeriesValues->multiLevelCount();
1379
 
1380
            $objWriter->startElement('c:multiLvlStrRef');
1381
 
1382
            $objWriter->startElement('c:f');
1383
            $objWriter->writeRawData($plotSeriesValues->getDataSource());
1384
            $objWriter->endElement();
1385
 
1386
            $objWriter->startElement('c:multiLvlStrCache');
1387
 
1388
            $objWriter->startElement('c:ptCount');
1389
            $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointCount());
1390
            $objWriter->endElement();
1391
 
1392
            for ($level = 0; $level < $levelCount; ++$level) {
1393
                $objWriter->startElement('c:lvl');
1394
 
1395
                foreach ($plotSeriesValues->getDataValues() as $plotSeriesKey => $plotSeriesValue) {
1396
                    if (isset($plotSeriesValue[$level])) {
1397
                        $objWriter->startElement('c:pt');
1398
                        $objWriter->writeAttribute('idx', $plotSeriesKey);
1399
 
1400
                        $objWriter->startElement('c:v');
1401
                        $objWriter->writeRawData($plotSeriesValue[$level]);
1402
                        $objWriter->endElement();
1403
                        $objWriter->endElement();
1404
                    }
1405
                }
1406
 
1407
                $objWriter->endElement();
1408
            }
1409
 
1410
            $objWriter->endElement();
1411
 
1412
            $objWriter->endElement();
1413
        } else {
1414
            $objWriter->startElement('c:' . $dataType . 'Ref');
1415
 
1416
            $objWriter->startElement('c:f');
1417
            $objWriter->writeRawData($plotSeriesValues->getDataSource());
1418
            $objWriter->endElement();
1419
 
1420
            $count = $plotSeriesValues->getPointCount();
1421
            $source = $plotSeriesValues->getDataSource();
1422
            $values = $plotSeriesValues->getDataValues();
1423
            if ($count > 1 || ($count === 1 && array_key_exists(0, $values) && "=$source" !== (string) $values[0])) {
1424
                $objWriter->startElement('c:' . $dataType . 'Cache');
1425
 
1426
                if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) {
1427
                    if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) {
1428
                        $objWriter->startElement('c:formatCode');
1429
                        $objWriter->writeRawData($plotSeriesValues->getFormatCode());
1430
                        $objWriter->endElement();
1431
                    }
1432
                }
1433
 
1434
                $objWriter->startElement('c:ptCount');
1435
                $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointCount());
1436
                $objWriter->endElement();
1437
 
1438
                $dataValues = $plotSeriesValues->getDataValues();
1439
                if (!empty($dataValues)) {
1440
                    foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
1441
                        $objWriter->startElement('c:pt');
1442
                        $objWriter->writeAttribute('idx', $plotSeriesKey);
1443
 
1444
                        $objWriter->startElement('c:v');
1445
                        $objWriter->writeRawData($plotSeriesValue);
1446
                        $objWriter->endElement();
1447
                        $objWriter->endElement();
1448
                    }
1449
                }
1450
 
1451
                $objWriter->endElement(); // *Cache
1452
            }
1453
 
1454
            $objWriter->endElement(); // *Ref
1455
        }
1456
    }
1457
 
1458
    private const CUSTOM_COLOR_TYPES = [
1459
        DataSeries::TYPE_BARCHART,
1460
        DataSeries::TYPE_BARCHART_3D,
1461
        DataSeries::TYPE_PIECHART,
1462
        DataSeries::TYPE_PIECHART_3D,
1463
        DataSeries::TYPE_DONUTCHART,
1464
    ];
1465
 
1466
    /**
1467
     * Write Bubble Chart Details.
1468
     */
1469
    private function writeBubbles(?DataSeriesValues $plotSeriesValues, XMLWriter $objWriter): void
1470
    {
1471
        if ($plotSeriesValues === null) {
1472
            return;
1473
        }
1474
 
1475
        $objWriter->startElement('c:bubbleSize');
1476
        $objWriter->startElement('c:numLit');
1477
 
1478
        $objWriter->startElement('c:formatCode');
1479
        $objWriter->writeRawData('General');
1480
        $objWriter->endElement();
1481
 
1482
        $objWriter->startElement('c:ptCount');
1483
        $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointCount());
1484
        $objWriter->endElement();
1485
 
1486
        $dataValues = $plotSeriesValues->getDataValues();
1487
        if (!empty($dataValues)) {
1488
            foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
1489
                $objWriter->startElement('c:pt');
1490
                $objWriter->writeAttribute('idx', $plotSeriesKey);
1491
                $objWriter->startElement('c:v');
1492
                $objWriter->writeRawData('1');
1493
                $objWriter->endElement();
1494
                $objWriter->endElement();
1495
            }
1496
        }
1497
 
1498
        $objWriter->endElement();
1499
        $objWriter->endElement();
1500
 
1501
        $objWriter->startElement('c:bubble3D');
1502
        $objWriter->writeAttribute('val', $plotSeriesValues->getBubble3D() ? '1' : '0');
1503
        $objWriter->endElement();
1504
    }
1505
 
1506
    /**
1507
     * Write Layout.
1508
     */
1509
    private function writeLayout(XMLWriter $objWriter, ?Layout $layout = null): void
1510
    {
1511
        $objWriter->startElement('c:layout');
1512
 
1513
        if ($layout !== null) {
1514
            $objWriter->startElement('c:manualLayout');
1515
 
1516
            $layoutTarget = $layout->getLayoutTarget();
1517
            if ($layoutTarget !== null) {
1518
                $objWriter->startElement('c:layoutTarget');
1519
                $objWriter->writeAttribute('val', $layoutTarget);
1520
                $objWriter->endElement();
1521
            }
1522
 
1523
            $xMode = $layout->getXMode();
1524
            if ($xMode !== null) {
1525
                $objWriter->startElement('c:xMode');
1526
                $objWriter->writeAttribute('val', $xMode);
1527
                $objWriter->endElement();
1528
            }
1529
 
1530
            $yMode = $layout->getYMode();
1531
            if ($yMode !== null) {
1532
                $objWriter->startElement('c:yMode');
1533
                $objWriter->writeAttribute('val', $yMode);
1534
                $objWriter->endElement();
1535
            }
1536
 
1537
            $x = $layout->getXPosition();
1538
            if ($x !== null) {
1539
                $objWriter->startElement('c:x');
1540
                $objWriter->writeAttribute('val', "$x");
1541
                $objWriter->endElement();
1542
            }
1543
 
1544
            $y = $layout->getYPosition();
1545
            if ($y !== null) {
1546
                $objWriter->startElement('c:y');
1547
                $objWriter->writeAttribute('val', "$y");
1548
                $objWriter->endElement();
1549
            }
1550
 
1551
            $w = $layout->getWidth();
1552
            if ($w !== null) {
1553
                $objWriter->startElement('c:w');
1554
                $objWriter->writeAttribute('val', "$w");
1555
                $objWriter->endElement();
1556
            }
1557
 
1558
            $h = $layout->getHeight();
1559
            if ($h !== null) {
1560
                $objWriter->startElement('c:h');
1561
                $objWriter->writeAttribute('val', "$h");
1562
                $objWriter->endElement();
1563
            }
1564
 
1565
            $objWriter->endElement();
1566
        }
1567
 
1568
        $objWriter->endElement();
1569
    }
1570
 
1571
    /**
1572
     * Write Alternate Content block.
1573
     */
1574
    private function writeAlternateContent(XMLWriter $objWriter): void
1575
    {
1576
        $objWriter->startElement('mc:AlternateContent');
1577
        $objWriter->writeAttribute('xmlns:mc', Namespaces::COMPATIBILITY);
1578
 
1579
        $objWriter->startElement('mc:Choice');
1580
        $objWriter->writeAttribute('Requires', 'c14');
1581
        $objWriter->writeAttribute('xmlns:c14', Namespaces::CHART_ALTERNATE);
1582
 
1583
        $objWriter->startElement('c14:style');
1584
        $objWriter->writeAttribute('val', '102');
1585
        $objWriter->endElement();
1586
        $objWriter->endElement();
1587
 
1588
        $objWriter->startElement('mc:Fallback');
1589
        $objWriter->startElement('c:style');
1590
        $objWriter->writeAttribute('val', '2');
1591
        $objWriter->endElement();
1592
        $objWriter->endElement();
1593
 
1594
        $objWriter->endElement();
1595
    }
1596
 
1597
    /**
1598
     * Write Printer Settings.
1599
     */
1600
    private function writePrintSettings(XMLWriter $objWriter): void
1601
    {
1602
        $objWriter->startElement('c:printSettings');
1603
 
1604
        $objWriter->startElement('c:headerFooter');
1605
        $objWriter->endElement();
1606
 
1607
        $objWriter->startElement('c:pageMargins');
1608
        $objWriter->writeAttribute('footer', '0.3');
1609
        $objWriter->writeAttribute('header', '0.3');
1610
        $objWriter->writeAttribute('r', '0.7');
1611
        $objWriter->writeAttribute('l', '0.7');
1612
        $objWriter->writeAttribute('t', '0.75');
1613
        $objWriter->writeAttribute('b', '0.75');
1614
        $objWriter->endElement();
1615
 
1616
        $objWriter->startElement('c:pageSetup');
1617
        $objWriter->writeAttribute('orientation', 'portrait');
1618
        $objWriter->endElement();
1619
 
1620
        $objWriter->endElement();
1621
    }
1622
 
1623
    private function writeEffects(XMLWriter $objWriter, Properties $yAxis): void
1624
    {
1625
        if (
1626
            !empty($yAxis->getSoftEdgesSize())
1627
            || !empty($yAxis->getShadowProperty('effect'))
1628
            || !empty($yAxis->getGlowProperty('size'))
1629
        ) {
1630
            $objWriter->startElement('a:effectLst');
1631
            $this->writeGlow($objWriter, $yAxis);
1632
            $this->writeShadow($objWriter, $yAxis);
1633
            $this->writeSoftEdge($objWriter, $yAxis);
1634
            $objWriter->endElement(); // effectLst
1635
        }
1636
    }
1637
 
1638
    private function writeShadow(XMLWriter $objWriter, Properties $xAxis): void
1639
    {
1640
        if (empty($xAxis->getShadowProperty('effect'))) {
1641
            return;
1642
        }
1643
        /** @var string */
1644
        $effect = $xAxis->getShadowProperty('effect');
1645
        $objWriter->startElement("a:$effect");
1646
 
1647
        if (is_numeric($xAxis->getShadowProperty('blur'))) {
1648
            $objWriter->writeAttribute('blurRad', Properties::pointsToXml((float) $xAxis->getShadowProperty('blur')));
1649
        }
1650
        if (is_numeric($xAxis->getShadowProperty('distance'))) {
1651
            $objWriter->writeAttribute('dist', Properties::pointsToXml((float) $xAxis->getShadowProperty('distance')));
1652
        }
1653
        if (is_numeric($xAxis->getShadowProperty('direction'))) {
1654
            $objWriter->writeAttribute('dir', Properties::angleToXml((float) $xAxis->getShadowProperty('direction')));
1655
        }
1656
        $algn = $xAxis->getShadowProperty('algn');
1657
        if (is_string($algn) && $algn !== '') {
1658
            $objWriter->writeAttribute('algn', $algn);
1659
        }
1660
        foreach (['sx', 'sy'] as $sizeType) {
1661
            $sizeValue = $xAxis->getShadowProperty(['size', $sizeType]);
1662
            if (is_numeric($sizeValue)) {
1663
                $objWriter->writeAttribute($sizeType, Properties::tenthOfPercentToXml((float) $sizeValue));
1664
            }
1665
        }
1666
        foreach (['kx', 'ky'] as $sizeType) {
1667
            $sizeValue = $xAxis->getShadowProperty(['size', $sizeType]);
1668
            if (is_numeric($sizeValue)) {
1669
                $objWriter->writeAttribute($sizeType, Properties::angleToXml((float) $sizeValue));
1670
            }
1671
        }
1672
        $rotWithShape = $xAxis->getShadowProperty('rotWithShape');
1673
        if (is_numeric($rotWithShape)) {
1674
            $objWriter->writeAttribute('rotWithShape', (string) (int) $rotWithShape);
1675
        }
1676
 
1677
        $this->writeColor($objWriter, $xAxis->getShadowColorObject(), false);
1678
 
1679
        $objWriter->endElement();
1680
    }
1681
 
1682
    private function writeGlow(XMLWriter $objWriter, Properties $yAxis): void
1683
    {
1684
        $size = $yAxis->getGlowProperty('size');
1685
        if (empty($size)) {
1686
            return;
1687
        }
1688
        $objWriter->startElement('a:glow');
1689
        $objWriter->writeAttribute('rad', Properties::pointsToXml((float) $size));
1690
        $this->writeColor($objWriter, $yAxis->getGlowColorObject(), false);
1691
        $objWriter->endElement(); // glow
1692
    }
1693
 
1694
    private function writeSoftEdge(XMLWriter $objWriter, Properties $yAxis): void
1695
    {
1696
        $softEdgeSize = $yAxis->getSoftEdgesSize();
1697
        if (empty($softEdgeSize)) {
1698
            return;
1699
        }
1700
        $objWriter->startElement('a:softEdge');
1701
        $objWriter->writeAttribute('rad', Properties::pointsToXml((float) $softEdgeSize));
1702
        $objWriter->endElement(); //end softEdge
1703
    }
1704
 
1705
    private function writeLineStyles(XMLWriter $objWriter, Properties $gridlines, bool $noFill = false): void
1706
    {
1707
        $objWriter->startElement('a:ln');
1708
        $widthTemp = $gridlines->getLineStyleProperty('width');
1709
        if (is_numeric($widthTemp)) {
1710
            $objWriter->writeAttribute('w', Properties::pointsToXml((float) $widthTemp));
1711
        }
1712
        $this->writeNotEmpty($objWriter, 'cap', $gridlines->getLineStyleProperty('cap'));
1713
        $this->writeNotEmpty($objWriter, 'cmpd', $gridlines->getLineStyleProperty('compound'));
1714
        if ($noFill) {
1715
            $objWriter->startElement('a:noFill');
1716
            $objWriter->endElement();
1717
        } else {
1718
            $this->writeColor($objWriter, $gridlines->getLineColor());
1719
        }
1720
 
1721
        $dash = $gridlines->getLineStyleProperty('dash');
1722
        if (!empty($dash)) {
1723
            $objWriter->startElement('a:prstDash');
1724
            $this->writeNotEmpty($objWriter, 'val', $dash);
1725
            $objWriter->endElement();
1726
        }
1727
 
1728
        if ($gridlines->getLineStyleProperty('join') === 'miter') {
1729
            $objWriter->startElement('a:miter');
1730
            $objWriter->writeAttribute('lim', '800000');
1731
            $objWriter->endElement();
1732
        } elseif ($gridlines->getLineStyleProperty('join') === 'bevel') {
1733
            $objWriter->startElement('a:bevel');
1734
            $objWriter->endElement();
1735
        }
1736
 
1737
        if ($gridlines->getLineStyleProperty(['arrow', 'head', 'type'])) {
1738
            $objWriter->startElement('a:headEnd');
1739
            $objWriter->writeAttribute('type', $gridlines->getLineStyleProperty(['arrow', 'head', 'type']));
1740
            $this->writeNotEmpty($objWriter, 'w', $gridlines->getLineStyleArrowWidth('head'));
1741
            $this->writeNotEmpty($objWriter, 'len', $gridlines->getLineStyleArrowLength('head'));
1742
            $objWriter->endElement();
1743
        }
1744
 
1745
        if ($gridlines->getLineStyleProperty(['arrow', 'end', 'type'])) {
1746
            $objWriter->startElement('a:tailEnd');
1747
            $objWriter->writeAttribute('type', $gridlines->getLineStyleProperty(['arrow', 'end', 'type']));
1748
            $this->writeNotEmpty($objWriter, 'w', $gridlines->getLineStyleArrowWidth('end'));
1749
            $this->writeNotEmpty($objWriter, 'len', $gridlines->getLineStyleArrowLength('end'));
1750
            $objWriter->endElement();
1751
        }
1752
        $objWriter->endElement(); //end ln
1753
    }
1754
 
1755
    private function writeNotEmpty(XMLWriter $objWriter, string $name, ?string $value): void
1756
    {
1757
        if ($value !== null && $value !== '') {
1758
            $objWriter->writeAttribute($name, $value);
1759
        }
1760
    }
1761
 
1762
    private function writeColor(XMLWriter $objWriter, ChartColor $chartColor, bool $solidFill = true): void
1763
    {
1764
        $type = $chartColor->getType();
1765
        $value = $chartColor->getValue();
1766
        if (!empty($type) && !empty($value)) {
1767
            if ($solidFill) {
1768
                $objWriter->startElement('a:solidFill');
1769
            }
1770
            $objWriter->startElement("a:$type");
1771
            $objWriter->writeAttribute('val', $value);
1772
            $alpha = $chartColor->getAlpha();
1773
            if (is_numeric($alpha)) {
1774
                $objWriter->startElement('a:alpha');
1775
                $objWriter->writeAttribute('val', ChartColor::alphaToXml((int) $alpha));
1776
                $objWriter->endElement(); // a:alpha
1777
            }
1778
            $brightness = $chartColor->getBrightness();
1779
            if (is_numeric($brightness)) {
1780
                $brightness = (int) $brightness;
1781
                $lumOff = 100 - $brightness;
1782
                $objWriter->startElement('a:lumMod');
1783
                $objWriter->writeAttribute('val', ChartColor::alphaToXml($brightness));
1784
                $objWriter->endElement(); // a:lumMod
1785
                $objWriter->startElement('a:lumOff');
1786
                $objWriter->writeAttribute('val', ChartColor::alphaToXml($lumOff));
1787
                $objWriter->endElement(); // a:lumOff
1788
            }
1789
            $objWriter->endElement(); //a:srgbClr/schemeClr/prstClr
1790
            if ($solidFill) {
1791
                $objWriter->endElement(); //a:solidFill
1792
            }
1793
        }
1794
    }
1795
 
1796
    private function writeLabelFont(XMLWriter $objWriter, ?Font $labelFont, ?Properties $axisText): void
1797
    {
1798
        $objWriter->startElement('a:p');
1799
        $objWriter->startElement('a:pPr');
1800
        $objWriter->startElement('a:defRPr');
1801
        if ($labelFont !== null) {
1802
            $fontSize = $labelFont->getSize();
1803
            if (is_numeric($fontSize)) {
1804
                $fontSize *= (($fontSize < 100) ? 100 : 1);
1805
                $objWriter->writeAttribute('sz', (string) $fontSize);
1806
            }
1807
            if ($labelFont->getBold() === true) {
1808
                $objWriter->writeAttribute('b', '1');
1809
            }
1810
            if ($labelFont->getItalic() === true) {
1811
                $objWriter->writeAttribute('i', '1');
1812
            }
1813
            $fontColor = $labelFont->getChartColor();
1814
            if ($fontColor !== null) {
1815
                $this->writeColor($objWriter, $fontColor);
1816
            }
1817
        }
1818
        if ($axisText !== null) {
1819
            $this->writeEffects($objWriter, $axisText);
1820
        }
1821
        if ($labelFont !== null) {
1822
            if (!empty($labelFont->getLatin())) {
1823
                $objWriter->startElement('a:latin');
1824
                $objWriter->writeAttribute('typeface', $labelFont->getLatin());
1825
                $objWriter->endElement();
1826
            }
1827
            if (!empty($labelFont->getEastAsian())) {
1828
                $objWriter->startElement('a:eastAsian');
1829
                $objWriter->writeAttribute('typeface', $labelFont->getEastAsian());
1830
                $objWriter->endElement();
1831
            }
1832
            if (!empty($labelFont->getComplexScript())) {
1833
                $objWriter->startElement('a:complexScript');
1834
                $objWriter->writeAttribute('typeface', $labelFont->getComplexScript());
1835
                $objWriter->endElement();
1836
            }
1837
        }
1838
        $objWriter->endElement(); // a:defRPr
1839
        $objWriter->endElement(); // a:pPr
1840
        $objWriter->endElement(); // a:p
1841
    }
1842
}