Passed
Push — master ( 66221b...0610e5 )
by Mark
22:08 queued 10:21
created

Chart::writePlotSeriesLabel()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 3.0015

Importance

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