Passed
Pull Request — master (#3481)
by Mark
11:46
created

Chart::writeTitle()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 39
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 5

Importance

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