Completed
Push — master ( 80e270...da3bcc )
by Mark
38s queued 38s
created

Chart::writePlotGroup()   F

Complexity

Conditions 88
Paths > 20000

Size

Total Lines 309
Code Lines 215

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 203
CRAP Score 88.5943

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 215
dl 0
loc 309
ccs 203
cts 212
cp 0.9575
rs 0
c 1
b 0
f 0
cc 88
nc 806088721
nop 6
crap 88.5943

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 68
    public function writeChart(\PhpOffice\PhpSpreadsheet\Chart\Chart $chart, $calculateCellValues = true)
34
    {
35
        // Create XML writer
36 68
        $objWriter = null;
37 68
        if ($this->getParentWriter()->getUseDiskCaching()) {
38
            $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
39
        } else {
40 68
            $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
41
        }
42
        //    Ensure that data series values are up-to-date before we save
43 68
        if ($calculateCellValues) {
44 68
            $chart->refresh();
45
        }
46
47
        // XML header
48 68
        $objWriter->startDocument('1.0', 'UTF-8', 'yes');
49
50
        // c:chartSpace
51 68
        $objWriter->startElement('c:chartSpace');
52 68
        $objWriter->writeAttribute('xmlns:c', Namespaces::CHART);
53 68
        $objWriter->writeAttribute('xmlns:a', Namespaces::DRAWINGML);
54 68
        $objWriter->writeAttribute('xmlns:r', Namespaces::SCHEMA_OFFICE_DOCUMENT);
55
56 68
        $objWriter->startElement('c:date1904');
57 68
        $objWriter->writeAttribute('val', '0');
58 68
        $objWriter->endElement();
59 68
        $objWriter->startElement('c:lang');
60 68
        $objWriter->writeAttribute('val', 'en-GB');
61 68
        $objWriter->endElement();
62 68
        $objWriter->startElement('c:roundedCorners');
63 68
        $objWriter->writeAttribute('val', $chart->getRoundedCorners() ? '1' : '0');
64 68
        $objWriter->endElement();
65
66 68
        $this->writeAlternateContent($objWriter);
67
68 68
        $objWriter->startElement('c:chart');
69
70 68
        $this->writeTitle($objWriter, $chart->getTitle());
71
72 68
        $objWriter->startElement('c:autoTitleDeleted');
73 68
        $objWriter->writeAttribute('val', (string) (int) $chart->getAutoTitleDeleted());
74 68
        $objWriter->endElement();
75
76 68
        $objWriter->startElement('c:view3D');
77 68
        $surface2D = false;
78 68
        $plotArea = $chart->getPlotArea();
79 68
        if ($plotArea !== null) {
80 68
            $seriesArray = $plotArea->getPlotGroup();
81 68
            foreach ($seriesArray as $series) {
82 68
                if ($series->getPlotType() === DataSeries::TYPE_SURFACECHART) {
83 2
                    $surface2D = true;
84
85 2
                    break;
86
                }
87
            }
88
        }
89 68
        $this->writeView3D($objWriter, $chart->getRotX(), 'c:rotX', $surface2D, 90);
90 68
        $this->writeView3D($objWriter, $chart->getRotY(), 'c:rotY', $surface2D);
91 68
        $this->writeView3D($objWriter, $chart->getRAngAx(), 'c:rAngAx', $surface2D);
92 68
        $this->writeView3D($objWriter, $chart->getPerspective(), 'c:perspective', $surface2D);
93 68
        $objWriter->endElement(); // view3D
94
95 68
        $this->writePlotArea($objWriter, $chart->getPlotArea(), $chart->getXAxisLabel(), $chart->getYAxisLabel(), $chart->getChartAxisX(), $chart->getChartAxisY());
96
97 68
        $this->writeLegend($objWriter, $chart->getLegend());
98
99 68
        $objWriter->startElement('c:plotVisOnly');
100 68
        $objWriter->writeAttribute('val', (string) (int) $chart->getPlotVisibleOnly());
101 68
        $objWriter->endElement();
102
103 68
        $objWriter->startElement('c:dispBlanksAs');
104 68
        $objWriter->writeAttribute('val', $chart->getDisplayBlanksAs());
105 68
        $objWriter->endElement();
106
107 68
        $objWriter->startElement('c:showDLblsOverMax');
108 68
        $objWriter->writeAttribute('val', '0');
109 68
        $objWriter->endElement();
110
111 68
        $objWriter->endElement(); // c:chart
112 68
        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 68
        $this->writePrintSettings($objWriter);
120
121 68
        $objWriter->endElement(); // c:chartSpace
122
123
        // Return
124 68
        return $objWriter->getData();
125
    }
126
127 68
    private function writeView3D(XMLWriter $objWriter, ?int $value, string $tag, bool $surface2D, int $default = 0): void
128
    {
129 68
        if ($value === null && $surface2D) {
130 1
            $value = $default;
131
        }
132 68
        if ($value !== null) {
133 2
            $objWriter->startElement($tag);
134 2
            $objWriter->writeAttribute('val', "$value");
135 2
            $objWriter->endElement();
136
        }
137
    }
138
139
    /**
140
     * Write Chart Title.
141
     */
142 68
    private function writeTitle(XMLWriter $objWriter, ?Title $title = null): void
143
    {
144 68
        if ($title === null) {
145 10
            return;
146
        }
147
148 65
        $objWriter->startElement('c:title');
149 65
        $objWriter->startElement('c:tx');
150 65
        $objWriter->startElement('c:rich');
151
152 65
        $objWriter->startElement('a:bodyPr');
153 65
        $objWriter->endElement();
154
155 65
        $objWriter->startElement('a:lstStyle');
156 65
        $objWriter->endElement();
157
158 65
        $objWriter->startElement('a:p');
159 65
        $objWriter->startElement('a:pPr');
160 65
        $objWriter->startElement('a:defRPr');
161 65
        $objWriter->endElement();
162 65
        $objWriter->endElement();
163
164 65
        $caption = $title->getCaption();
165 65
        if ((is_array($caption)) && (count($caption) > 0)) {
166 28
            $caption = $caption[0];
167
        }
168 65
        $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a');
169
170 65
        $objWriter->endElement();
171 65
        $objWriter->endElement();
172 65
        $objWriter->endElement();
173
174 65
        $this->writeLayout($objWriter, $title->getLayout());
175
176 65
        $objWriter->startElement('c:overlay');
177 65
        $objWriter->writeAttribute('val', '0');
178 65
        $objWriter->endElement();
179
180 65
        $objWriter->endElement();
181
    }
182
183
    /**
184
     * Write Chart Legend.
185
     */
186 68
    private function writeLegend(XMLWriter $objWriter, ?Legend $legend = null): void
187
    {
188 68
        if ($legend === null) {
189 10
            return;
190
        }
191
192 64
        $objWriter->startElement('c:legend');
193
194 64
        $objWriter->startElement('c:legendPos');
195 64
        $objWriter->writeAttribute('val', $legend->getPosition());
196 64
        $objWriter->endElement();
197
198 64
        $this->writeLayout($objWriter, $legend->getLayout());
199
200 64
        $objWriter->startElement('c:overlay');
201 64
        $objWriter->writeAttribute('val', ($legend->getOverlay()) ? '1' : '0');
202 64
        $objWriter->endElement();
203
204 64
        $objWriter->startElement('c:txPr');
205 64
        $objWriter->startElement('a:bodyPr');
206 64
        $objWriter->endElement();
207
208 64
        $objWriter->startElement('a:lstStyle');
209 64
        $objWriter->endElement();
210
211 64
        $objWriter->startElement('a:p');
212 64
        $objWriter->startElement('a:pPr');
213 64
        $objWriter->writeAttribute('rtl', '0');
214
215 64
        $objWriter->startElement('a:defRPr');
216 64
        $objWriter->endElement();
217 64
        $objWriter->endElement();
218
219 64
        $objWriter->startElement('a:endParaRPr');
220 64
        $objWriter->writeAttribute('lang', 'en-US');
221 64
        $objWriter->endElement();
222
223 64
        $objWriter->endElement();
224 64
        $objWriter->endElement();
225
226 64
        $objWriter->endElement();
227
    }
228
229
    /**
230
     * Write Chart Plot Area.
231
     */
232 68
    private function writePlotArea(XMLWriter $objWriter, ?PlotArea $plotArea, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null): void
233
    {
234 68
        if ($plotArea === null) {
235
            return;
236
        }
237
238 68
        $id1 = $id2 = $id3 = '0';
239 68
        $this->seriesIndex = 0;
240 68
        $objWriter->startElement('c:plotArea');
241
242 68
        $layout = $plotArea->getLayout();
243
244 68
        $this->writeLayout($objWriter, $layout);
245
246 68
        $chartTypes = self::getChartType($plotArea);
247 68
        $catIsMultiLevelSeries = $valIsMultiLevelSeries = false;
248 68
        $plotGroupingType = '';
249 68
        $chartType = null;
250 68
        foreach ($chartTypes as $chartType) {
251 68
            $objWriter->startElement('c:' . $chartType);
252
253 68
            $groupCount = $plotArea->getPlotGroupCount();
254 68
            $plotGroup = null;
255 68
            for ($i = 0; $i < $groupCount; ++$i) {
256 68
                $plotGroup = $plotArea->getPlotGroupByIndex($i);
257 68
                $groupType = $plotGroup->getPlotType();
258 68
                if ($groupType == $chartType) {
259 68
                    $plotStyle = $plotGroup->getPlotStyle();
260 68
                    if (!empty($plotStyle) && $groupType === DataSeries::TYPE_RADARCHART) {
261 2
                        $objWriter->startElement('c:radarStyle');
262 2
                        $objWriter->writeAttribute('val', $plotStyle);
263 2
                        $objWriter->endElement();
264 67
                    } elseif (!empty($plotStyle) && $groupType === DataSeries::TYPE_SCATTERCHART) {
265 26
                        $objWriter->startElement('c:scatterStyle');
266 26
                        $objWriter->writeAttribute('val', $plotStyle);
267 26
                        $objWriter->endElement();
268 47
                    } 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 68
                    $this->writePlotGroup($plotGroup, $chartType, $objWriter, $catIsMultiLevelSeries, $valIsMultiLevelSeries, $plotGroupingType);
275
                }
276
            }
277
278 68
            $this->writeDataLabels($objWriter, $layout);
279
280 68
            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 59
            } elseif (($chartType === DataSeries::TYPE_BARCHART) || ($chartType === DataSeries::TYPE_BARCHART_3D)) {
286 19
                $objWriter->startElement('c:gapWidth');
287 19
                $objWriter->writeAttribute('val', '150');
288 19
                $objWriter->endElement();
289
290 19
                if ($plotGroupingType == 'percentStacked' || $plotGroupingType == 'stacked') {
291 2
                    $objWriter->startElement('c:overlap');
292 2
                    $objWriter->writeAttribute('val', '100');
293 19
                    $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 68
            $id1 = '110438656';
327 68
            $id2 = '110444544';
328 68
            $id3 = '110365312'; // used in Surface Chart
329
330 68
            if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
331 63
                $objWriter->startElement('c:axId');
332 63
                $objWriter->writeAttribute('val', $id1);
333 63
                $objWriter->endElement();
334 63
                $objWriter->startElement('c:axId');
335 63
                $objWriter->writeAttribute('val', $id2);
336 63
                $objWriter->endElement();
337 63
                if ($chartType === DataSeries::TYPE_SURFACECHART_3D || $chartType === DataSeries::TYPE_SURFACECHART) {
338 2
                    $objWriter->startElement('c:axId');
339 2
                    $objWriter->writeAttribute('val', $id3);
340 63
                    $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 68
            $objWriter->endElement();
355
        }
356
357 68
        if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
358 63
            if ($chartType === DataSeries::TYPE_BUBBLECHART) {
359 2
                $this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id2, $id1, $catIsMultiLevelSeries, $xAxis ?? new Axis());
360
            } else {
361 62
                $this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $xAxis ?? new Axis());
362
            }
363
364 63
            $this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $yAxis ?? new Axis());
365 63
            if ($chartType === DataSeries::TYPE_SURFACECHART_3D || $chartType === DataSeries::TYPE_SURFACECHART) {
366 2
                $this->writeSerAxis($objWriter, $id2, $id3);
367
            }
368
        }
369 68
        $stops = $plotArea->getGradientFillStops();
370 68
        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 68
        $objWriter->endElement(); // c:plotArea
398
    }
399
400 34
    private function writeDataLabelsBool(XMLWriter $objWriter, string $name, ?bool $value): void
401
    {
402 34
        if ($value !== null) {
403 31
            $objWriter->startElement("c:$name");
404 31
            $objWriter->writeAttribute('val', $value ? '1' : '0');
405 31
            $objWriter->endElement();
406
        }
407
    }
408
409
    /**
410
     * Write Data Labels.
411
     */
412 68
    private function writeDataLabels(XMLWriter $objWriter, ?Layout $chartLayout = null): void
413
    {
414 68
        if (!isset($chartLayout)) {
415 35
            return;
416
        }
417 34
        $objWriter->startElement('c:dLbls');
418
419 34
        $fillColor = $chartLayout->getLabelFillColor();
420 34
        $borderColor = $chartLayout->getLabelBorderColor();
421 34
        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 34
        $fontColor = $chartLayout->getLabelFontColor();
432 34
        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 34
        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 34
        if ($chartLayout->getDLblPos() !== '') {
467 2
            $objWriter->startElement('c:dLblPos');
468 2
            $objWriter->writeAttribute('val', $chartLayout->getDLblPos());
469 2
            $objWriter->endElement(); // c:dLblPos
470
        }
471 34
        $this->writeDataLabelsBool($objWriter, 'showLegendKey', $chartLayout->getShowLegendKey());
472 34
        $this->writeDataLabelsBool($objWriter, 'showVal', $chartLayout->getShowVal());
473 34
        $this->writeDataLabelsBool($objWriter, 'showCatName', $chartLayout->getShowCatName());
474 34
        $this->writeDataLabelsBool($objWriter, 'showSerName', $chartLayout->getShowSerName());
475 34
        $this->writeDataLabelsBool($objWriter, 'showPercent', $chartLayout->getShowPercent());
476 34
        $this->writeDataLabelsBool($objWriter, 'showBubbleSize', $chartLayout->getShowBubbleSize());
477 34
        $this->writeDataLabelsBool($objWriter, 'showLeaderLines', $chartLayout->getShowLeaderLines());
478
479 34
        $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 62
    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 62
        $axisType = $yAxis->getAxisType();
494 62
        if ($axisType !== '') {
495 25
            $objWriter->startElement("c:$axisType");
496 39
        } elseif ($yAxis->getAxisIsNumericFormat()) {
497 6
            $objWriter->startElement('c:' . Axis::AXIS_TYPE_VALUE);
498
        } else {
499 33
            $objWriter->startElement('c:' . Axis::AXIS_TYPE_CATEGORY);
500
        }
501 62
        $majorGridlines = $yAxis->getMajorGridlines();
502 62
        $minorGridlines = $yAxis->getMinorGridlines();
503
504 62
        if ($id1 !== '0') {
505 62
            $objWriter->startElement('c:axId');
506 62
            $objWriter->writeAttribute('val', $id1);
507 62
            $objWriter->endElement();
508
        }
509
510 62
        $objWriter->startElement('c:scaling');
511 62
        if ($yAxis->getAxisOptionsProperty('maximum') !== null) {
512 5
            $objWriter->startElement('c:max');
513 5
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('maximum'));
514 5
            $objWriter->endElement();
515
        }
516 62
        if ($yAxis->getAxisOptionsProperty('minimum') !== null) {
517 5
            $objWriter->startElement('c:min');
518 5
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minimum'));
519 5
            $objWriter->endElement();
520
        }
521 62
        if (!empty($yAxis->getAxisOptionsProperty('orientation'))) {
522 62
            $objWriter->startElement('c:orientation');
523 62
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('orientation'));
524 62
            $objWriter->endElement();
525
        }
526 62
        $objWriter->endElement(); // c:scaling
527
528 62
        $objWriter->startElement('c:delete');
529 62
        $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('hidden') ?? '0');
530 62
        $objWriter->endElement();
531
532 62
        $objWriter->startElement('c:axPos');
533 62
        $objWriter->writeAttribute('val', 'b');
534 62
        $objWriter->endElement();
535
536 62
        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 62
        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 62
        if ($xAxisLabel !== null) {
555 4
            $objWriter->startElement('c:title');
556 4
            $objWriter->startElement('c:tx');
557 4
            $objWriter->startElement('c:rich');
558
559 4
            $objWriter->startElement('a:bodyPr');
560 4
            $objWriter->endElement();
561
562 4
            $objWriter->startElement('a:lstStyle');
563 4
            $objWriter->endElement();
564
565 4
            $objWriter->startElement('a:p');
566
567 4
            $caption = $xAxisLabel->getCaption();
568 4
            if (is_array($caption)) {
569 2
                $caption = $caption[0];
570
            }
571 4
            $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a');
572
573 4
            $objWriter->endElement();
574 4
            $objWriter->endElement();
575 4
            $objWriter->endElement();
576
577 4
            $layout = $xAxisLabel->getLayout();
578 4
            $this->writeLayout($objWriter, $layout);
579
580 4
            $objWriter->startElement('c:overlay');
581 4
            $objWriter->writeAttribute('val', '0');
582 4
            $objWriter->endElement();
583
584 4
            $objWriter->endElement();
585
        }
586
587 62
        $objWriter->startElement('c:numFmt');
588 62
        $objWriter->writeAttribute('formatCode', $yAxis->getAxisNumberFormat());
589 62
        $objWriter->writeAttribute('sourceLinked', $yAxis->getAxisNumberSourceLinked());
590 62
        $objWriter->endElement();
591
592 62
        if (!empty($yAxis->getAxisOptionsProperty('major_tick_mark'))) {
593 62
            $objWriter->startElement('c:majorTickMark');
594 62
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('major_tick_mark'));
595 62
            $objWriter->endElement();
596
        }
597
598 62
        if (!empty($yAxis->getAxisOptionsProperty('minor_tick_mark'))) {
599 62
            $objWriter->startElement('c:minorTickMark');
600 62
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minor_tick_mark'));
601 62
            $objWriter->endElement();
602
        }
603
604 62
        if (!empty($yAxis->getAxisOptionsProperty('axis_labels'))) {
605 62
            $objWriter->startElement('c:tickLblPos');
606 62
            $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('axis_labels'));
607 62
            $objWriter->endElement();
608
        }
609
610 62
        $textRotation = $yAxis->getAxisOptionsProperty('textRotation');
611 62
        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 62
        $objWriter->startElement('c:spPr');
628 62
        $this->writeColor($objWriter, $yAxis->getFillColorObject());
629 62
        $this->writeLineStyles($objWriter, $yAxis);
630 62
        $this->writeEffects($objWriter, $yAxis);
631 62
        $objWriter->endElement(); // spPr
632
633 62
        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 62
        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 62
        if ($id2 !== '0') {
646 62
            $objWriter->startElement('c:crossAx');
647 62
            $objWriter->writeAttribute('val', $id2);
648 62
            $objWriter->endElement();
649
650 62
            if (!empty($yAxis->getAxisOptionsProperty('horizontal_crosses'))) {
651 62
                $objWriter->startElement('c:crosses');
652 62
                $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('horizontal_crosses'));
653 62
                $objWriter->endElement();
654
            }
655
        }
656
657 62
        $objWriter->startElement('c:auto');
658
        // LineChart with dateAx wants '0'
659 62
        $objWriter->writeAttribute('val', ($axisType === Axis::AXIS_TYPE_DATE) ? '0' : '1');
660 62
        $objWriter->endElement();
661
662 62
        $objWriter->startElement('c:lblAlgn');
663 62
        $objWriter->writeAttribute('val', 'ctr');
664 62
        $objWriter->endElement();
665
666 62
        $objWriter->startElement('c:lblOffset');
667 62
        $objWriter->writeAttribute('val', '100');
668 62
        $objWriter->endElement();
669
670 62
        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 62
        if ($isMultiLevelSeries) {
695 14
            $objWriter->startElement('c:noMultiLvlLbl');
696 14
            $objWriter->writeAttribute('val', '0');
697 14
            $objWriter->endElement();
698
        }
699 62
        $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 63
    private function writeValueAxis(XMLWriter $objWriter, ?Title $yAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries, Axis $xAxis): void
711
    {
712 63
        $objWriter->startElement('c:' . Axis::AXIS_TYPE_VALUE);
713 63
        $majorGridlines = $xAxis->getMajorGridlines();
714 63
        $minorGridlines = $xAxis->getMinorGridlines();
715
716 63
        if ($id2 !== '0') {
717 63
            $objWriter->startElement('c:axId');
718 63
            $objWriter->writeAttribute('val', $id2);
719 63
            $objWriter->endElement();
720
        }
721
722 63
        $objWriter->startElement('c:scaling');
723
724 63
        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 63
        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 63
        if (!empty($xAxis->getAxisOptionsProperty('orientation'))) {
737 63
            $objWriter->startElement('c:orientation');
738 63
            $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('orientation'));
739 63
            $objWriter->endElement();
740
        }
741
742 63
        $objWriter->endElement(); // c:scaling
743
744 63
        $objWriter->startElement('c:delete');
745 63
        $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('hidden') ?? '0');
746 63
        $objWriter->endElement();
747
748 63
        $objWriter->startElement('c:axPos');
749 63
        $objWriter->writeAttribute('val', 'l');
750 63
        $objWriter->endElement();
751
752 63
        if ($majorGridlines !== null) {
753 27
            $objWriter->startElement('c:majorGridlines');
754 27
            $objWriter->startElement('c:spPr');
755 27
            $this->writeLineStyles($objWriter, $majorGridlines);
756 27
            $this->writeEffects($objWriter, $majorGridlines);
757 27
            $objWriter->endElement(); //end spPr
758 27
            $objWriter->endElement(); //end majorGridLines
759
        }
760
761 63
        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 63
        if ($yAxisLabel !== null) {
771 33
            $objWriter->startElement('c:title');
772 33
            $objWriter->startElement('c:tx');
773 33
            $objWriter->startElement('c:rich');
774
775 33
            $objWriter->startElement('a:bodyPr');
776 33
            $objWriter->endElement();
777
778 33
            $objWriter->startElement('a:lstStyle');
779 33
            $objWriter->endElement();
780
781 33
            $objWriter->startElement('a:p');
782
783 33
            $caption = $yAxisLabel->getCaption();
784 33
            if (is_array($caption)) {
785 7
                $caption = $caption[0];
786
            }
787 33
            $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a');
788
789 33
            $objWriter->endElement();
790 33
            $objWriter->endElement();
791 33
            $objWriter->endElement();
792
793 33
            if ($groupType !== DataSeries::TYPE_BUBBLECHART) {
794 33
                $layout = $yAxisLabel->getLayout();
795 33
                $this->writeLayout($objWriter, $layout);
796
            }
797
798 33
            $objWriter->startElement('c:overlay');
799 33
            $objWriter->writeAttribute('val', '0');
800 33
            $objWriter->endElement();
801
802 33
            $objWriter->endElement();
803
        }
804
805 63
        $objWriter->startElement('c:numFmt');
806 63
        $objWriter->writeAttribute('formatCode', $xAxis->getAxisNumberFormat());
807 63
        $objWriter->writeAttribute('sourceLinked', $xAxis->getAxisNumberSourceLinked());
808 63
        $objWriter->endElement();
809
810 63
        if (!empty($xAxis->getAxisOptionsProperty('major_tick_mark'))) {
811 63
            $objWriter->startElement('c:majorTickMark');
812 63
            $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_tick_mark'));
813 63
            $objWriter->endElement();
814
        }
815
816 63
        if (!empty($xAxis->getAxisOptionsProperty('minor_tick_mark'))) {
817 63
            $objWriter->startElement('c:minorTickMark');
818 63
            $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_tick_mark'));
819 63
            $objWriter->endElement();
820
        }
821
822 63
        if (!empty($xAxis->getAxisOptionsProperty('axis_labels'))) {
823 63
            $objWriter->startElement('c:tickLblPos');
824 63
            $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('axis_labels'));
825 63
            $objWriter->endElement();
826
        }
827
828 63
        $textRotation = $xAxis->getAxisOptionsProperty('textRotation');
829 63
        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 63
        $objWriter->startElement('c:spPr');
846 63
        $this->writeColor($objWriter, $xAxis->getFillColorObject());
847 63
        $this->writeLineStyles($objWriter, $xAxis);
848 63
        $this->writeEffects($objWriter, $xAxis);
849 63
        $objWriter->endElement(); //end spPr
850
851 63
        if ($id1 !== '0') {
852 63
            $objWriter->startElement('c:crossAx');
853 63
            $objWriter->writeAttribute('val', $id1);
854 63
            $objWriter->endElement();
855
856 63
            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 63
                $crosses = $xAxis->getAxisOptionsProperty('horizontal_crosses');
862 63
                if ($crosses) {
863 63
                    $objWriter->startElement('c:crosses');
864 63
                    $objWriter->writeAttribute('val', $crosses);
865 63
                    $objWriter->endElement();
866
                }
867
            }
868
869 63
            $crossBetween = $xAxis->getCrossBetween();
870 63
            if ($crossBetween !== '') {
871 24
                $objWriter->startElement('c:crossBetween');
872 24
                $objWriter->writeAttribute('val', $crossBetween);
873 24
                $objWriter->endElement();
874
            }
875
876 63
            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 63
            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 63
        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 63
        $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 68
    private static function getChartType(PlotArea $plotArea): array
954
    {
955 68
        $groupCount = $plotArea->getPlotGroupCount();
956
957 68
        if ($groupCount == 1) {
958 66
            $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 68
        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 68
    private function writePlotGroup(?DataSeries $plotGroup, string $groupType, XMLWriter $objWriter, &$catIsMultiLevelSeries, &$valIsMultiLevelSeries, &$plotGroupingType): void
1003
    {
1004 68
        if ($plotGroup === null) {
1005
            return;
1006
        }
1007
1008 68
        if (($groupType == DataSeries::TYPE_BARCHART) || ($groupType == DataSeries::TYPE_BARCHART_3D)) {
1009 19
            $objWriter->startElement('c:barDir');
1010 19
            $objWriter->writeAttribute('val', $plotGroup->getPlotDirection());
1011 19
            $objWriter->endElement();
1012
        }
1013
1014 68
        $plotGroupingType = $plotGroup->getPlotGrouping();
1015 68
        if ($plotGroupingType !== null && $groupType !== DataSeries::TYPE_SURFACECHART && $groupType !== DataSeries::TYPE_SURFACECHART_3D) {
1016 37
            $objWriter->startElement('c:grouping');
1017 37
            $objWriter->writeAttribute('val', $plotGroupingType);
1018 37
            $objWriter->endElement();
1019
        }
1020
1021
        //    Get these details before the loop, because we can use the count to check for varyColors
1022 68
        $plotSeriesOrder = $plotGroup->getPlotOrder();
1023 68
        $plotSeriesCount = count($plotSeriesOrder);
1024
1025 68
        if (($groupType !== DataSeries::TYPE_RADARCHART) && ($groupType !== DataSeries::TYPE_STOCKCHART)) {
1026 66
            if ($groupType !== DataSeries::TYPE_LINECHART) {
1027 57
                if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART) || ($plotSeriesCount > 1)) {
1028 51
                    $objWriter->startElement('c:varyColors');
1029 51
                    $objWriter->writeAttribute('val', '1');
1030 51
                    $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 68
        $plotSeriesIdx = 0;
1040 68
        foreach ($plotSeriesOrder as $plotSeriesIdx => $plotSeriesRef) {
1041 68
            $objWriter->startElement('c:ser');
1042
1043 68
            $objWriter->startElement('c:idx');
1044 68
            $adder = array_key_exists(0, $plotSeriesOrder) ? $this->seriesIndex : 0;
1045 68
            $objWriter->writeAttribute('val', (string) ($adder + $plotSeriesIdx));
1046 68
            $objWriter->endElement();
1047
1048 68
            $objWriter->startElement('c:order');
1049 68
            $objWriter->writeAttribute('val', (string) ($adder + $plotSeriesRef));
1050 68
            $objWriter->endElement();
1051
1052 68
            $plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx);
1053 68
            $labelFill = null;
1054 68
            if ($plotLabel && $groupType === DataSeries::TYPE_LINECHART) {
1055 13
                $labelFill = $plotLabel->getFillColorObject();
1056 13
                $labelFill = ($labelFill instanceof ChartColor) ? $labelFill : null;
1057
            }
1058 68
            if ($plotLabel && $groupType !== DataSeries::TYPE_LINECHART) {
1059 59
                $fillColor = $plotLabel->getFillColorObject();
1060 59
                if ($fillColor !== null && !is_array($fillColor) && $fillColor->isUsable()) {
1061
                    $objWriter->startElement('c:spPr');
1062
                    $this->writeColor($objWriter, $fillColor);
1063
                    $objWriter->endElement(); // c:spPr
1064
                }
1065
            }
1066
1067
            //    Values
1068 68
            $plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesIdx);
1069
1070 68
            if ($plotSeriesValues !== false && in_array($groupType, self::CUSTOM_COLOR_TYPES, true)) {
1071 24
                $fillColorValues = $plotSeriesValues->getFillColorObject();
1072 24
                if ($fillColorValues !== null && is_array($fillColorValues)) {
1073 8
                    foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) {
1074 8
                        $this->writePlotSeriesValuesElement($objWriter, $dataKey, $fillColorValues[$dataKey] ?? null);
1075
                    }
1076
                }
1077
            }
1078 68
            if ($plotSeriesValues !== false && $plotSeriesValues->getLabelLayout()) {
1079 4
                $this->writeDataLabels($objWriter, $plotSeriesValues->getLabelLayout());
1080
            }
1081
1082
            //    Labels
1083 68
            $plotSeriesLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx);
1084 68
            if ($plotSeriesLabel && ($plotSeriesLabel->getPointCount() > 0)) {
1085 67
                $objWriter->startElement('c:tx');
1086 67
                $objWriter->startElement('c:strRef');
1087 67
                $this->writePlotSeriesLabel($plotSeriesLabel, $objWriter);
1088 67
                $objWriter->endElement();
1089 67
                $objWriter->endElement();
1090
            }
1091
1092
            //    Formatting for the points
1093
            if (
1094 68
                $plotSeriesValues !== false
1095
            ) {
1096 68
                $objWriter->startElement('c:spPr');
1097 68
                $fillObject = $labelFill ?? $plotSeriesValues->getFillColorObject();
1098 68
                $callLineStyles = true;
1099 68
                if ($fillObject instanceof ChartColor && $fillObject->isUsable()) {
1100 10
                    if ($groupType === DataSeries::TYPE_LINECHART) {
1101 3
                        $objWriter->startElement('a:ln');
1102 3
                        $callLineStyles = false;
1103
                    }
1104 10
                    $this->writeColor($objWriter, $fillObject);
1105 10
                    if (!$callLineStyles) {
1106 3
                        $objWriter->endElement(); // a:ln
1107
                    }
1108
                }
1109 68
                $nofill = $groupType === DataSeries::TYPE_STOCKCHART || (($groupType === DataSeries::TYPE_SCATTERCHART || $groupType === DataSeries::TYPE_LINECHART) && !$plotSeriesValues->getScatterLines());
1110 68
                if ($callLineStyles) {
1111 67
                    $this->writeLineStyles($objWriter, $plotSeriesValues, $nofill);
1112 67
                    $this->writeEffects($objWriter, $plotSeriesValues);
1113
                }
1114 68
                $objWriter->endElement(); // c:spPr
1115
            }
1116
1117 68
            if ($plotSeriesValues) {
1118 68
                $plotSeriesMarker = $plotSeriesValues->getPointMarker();
1119 68
                $markerFillColor = $plotSeriesValues->getMarkerFillColor();
1120 68
                $fillUsed = $markerFillColor->IsUsable();
1121 68
                $markerBorderColor = $plotSeriesValues->getMarkerBorderColor();
1122 68
                $borderUsed = $markerBorderColor->isUsable();
1123 68
                if ($plotSeriesMarker || $fillUsed || $borderUsed) {
1124 20
                    $objWriter->startElement('c:marker');
1125 20
                    $objWriter->startElement('c:symbol');
1126 20
                    if ($plotSeriesMarker) {
1127 20
                        $objWriter->writeAttribute('val', $plotSeriesMarker);
1128
                    }
1129 20
                    $objWriter->endElement();
1130
1131 20
                    if ($plotSeriesMarker !== 'none') {
1132 20
                        $objWriter->startElement('c:size');
1133 20
                        $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointSize());
1134 20
                        $objWriter->endElement(); // c:size
1135 20
                        $objWriter->startElement('c:spPr');
1136 20
                        $this->writeColor($objWriter, $markerFillColor);
1137 20
                        if ($borderUsed) {
1138 10
                            $objWriter->startElement('a:ln');
1139 10
                            $this->writeColor($objWriter, $markerBorderColor);
1140 10
                            $objWriter->endElement(); // a:ln
1141
                        }
1142 20
                        $objWriter->endElement(); // spPr
1143
                    }
1144
1145 20
                    $objWriter->endElement();
1146
                }
1147
            }
1148
1149 68
            if (($groupType === DataSeries::TYPE_BARCHART) || ($groupType === DataSeries::TYPE_BARCHART_3D) || ($groupType === DataSeries::TYPE_BUBBLECHART)) {
1150 20
                $objWriter->startElement('c:invertIfNegative');
1151 20
                $objWriter->writeAttribute('val', '0');
1152 20
                $objWriter->endElement();
1153
            }
1154
            // Trendlines
1155 68
            if ($plotSeriesValues !== false) {
1156 68
                foreach ($plotSeriesValues->getTrendLines() as $trendLine) {
1157 3
                    $trendLineType = $trendLine->getTrendLineType();
1158 3
                    $order = $trendLine->getOrder();
1159 3
                    $period = $trendLine->getPeriod();
1160 3
                    $dispRSqr = $trendLine->getDispRSqr();
1161 3
                    $dispEq = $trendLine->getDispEq();
1162 3
                    $forward = $trendLine->getForward();
1163 3
                    $backward = $trendLine->getBackward();
1164 3
                    $intercept = $trendLine->getIntercept();
1165 3
                    $name = $trendLine->getName();
1166 3
                    $trendLineColor = $trendLine->getLineColor(); // ChartColor
1167
1168 3
                    $objWriter->startElement('c:trendline'); // N.B. lowercase 'ell'
1169 3
                    if ($name !== '') {
1170 3
                        $objWriter->startElement('c:name');
1171 3
                        $objWriter->writeRawData($name);
1172 3
                        $objWriter->endElement(); // c:name
1173
                    }
1174 3
                    $objWriter->startElement('c:spPr');
1175
1176 3
                    if (!$trendLineColor->isUsable()) {
1177
                        // use dataSeriesValues line color as a backup if $trendLineColor is null
1178
                        $dsvLineColor = $plotSeriesValues->getLineColor();
1179
                        if ($dsvLineColor->isUsable()) {
1180
                            $trendLine
1181
                                ->getLineColor()
1182
                                ->setColorProperties($dsvLineColor->getValue(), $dsvLineColor->getAlpha(), $dsvLineColor->getType());
1183
                        }
1184
                    } // otherwise, hope Excel does the right thing
1185
1186 3
                    $this->writeLineStyles($objWriter, $trendLine, false); // suppress noFill
1187
1188 3
                    $objWriter->endElement(); // spPr
1189
1190 3
                    $objWriter->startElement('c:trendlineType'); // N.B lowercase 'ell'
1191 3
                    $objWriter->writeAttribute('val', $trendLineType);
1192 3
                    $objWriter->endElement(); // trendlineType
1193 3
                    if ($backward !== 0.0) {
1194 3
                        $objWriter->startElement('c:backward');
1195 3
                        $objWriter->writeAttribute('val', "$backward");
1196 3
                        $objWriter->endElement(); // c:backward
1197
                    }
1198 3
                    if ($forward !== 0.0) {
1199 3
                        $objWriter->startElement('c:forward');
1200 3
                        $objWriter->writeAttribute('val', "$forward");
1201 3
                        $objWriter->endElement(); // c:forward
1202
                    }
1203 3
                    if ($intercept !== 0.0) {
1204 3
                        $objWriter->startElement('c:intercept');
1205 3
                        $objWriter->writeAttribute('val', "$intercept");
1206 3
                        $objWriter->endElement(); // c:intercept
1207
                    }
1208 3
                    if ($trendLineType == TrendLine::TRENDLINE_POLYNOMIAL) {
1209 3
                        $objWriter->startElement('c:order');
1210 3
                        $objWriter->writeAttribute('val', $order);
1211 3
                        $objWriter->endElement(); // order
1212
                    }
1213 3
                    if ($trendLineType == TrendLine::TRENDLINE_MOVING_AVG) {
1214 3
                        $objWriter->startElement('c:period');
1215 3
                        $objWriter->writeAttribute('val', $period);
1216 3
                        $objWriter->endElement(); // period
1217
                    }
1218 3
                    $objWriter->startElement('c:dispRSqr');
1219 3
                    $objWriter->writeAttribute('val', $dispRSqr ? '1' : '0');
1220 3
                    $objWriter->endElement();
1221 3
                    $objWriter->startElement('c:dispEq');
1222 3
                    $objWriter->writeAttribute('val', $dispEq ? '1' : '0');
1223 3
                    $objWriter->endElement();
1224 3
                    if ($groupType === DataSeries::TYPE_SCATTERCHART || $groupType === DataSeries::TYPE_LINECHART) {
1225 3
                        $objWriter->startElement('c:trendlineLbl');
1226 3
                        $objWriter->startElement('c:numFmt');
1227 3
                        $objWriter->writeAttribute('formatCode', 'General');
1228 3
                        $objWriter->writeAttribute('sourceLinked', '0');
1229 3
                        $objWriter->endElement();  // numFmt
1230 3
                        $objWriter->endElement();  // trendlineLbl
1231
                    }
1232
1233 3
                    $objWriter->endElement(); // trendline
1234
                }
1235
            }
1236
1237
            //    Category Labels
1238 68
            $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesIdx);
1239 68
            if ($plotSeriesCategory && ($plotSeriesCategory->getPointCount() > 0)) {
1240 61
                $catIsMultiLevelSeries = $catIsMultiLevelSeries || $plotSeriesCategory->isMultiLevelSeries();
1241
1242 61
                if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) {
1243 9
                    $plotStyle = $plotGroup->getPlotStyle();
1244 9
                    if (is_numeric($plotStyle)) {
1245 2
                        $objWriter->startElement('c:explosion');
1246 2
                        $objWriter->writeAttribute('val', $plotStyle);
1247 2
                        $objWriter->endElement();
1248
                    }
1249
                }
1250
1251 61
                if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) {
1252 27
                    $objWriter->startElement('c:xVal');
1253
                } else {
1254 40
                    $objWriter->startElement('c:cat');
1255
                }
1256
1257
                // xVals (Categories) are not always 'str'
1258
                // Test X-axis Label's Datatype to decide 'str' vs 'num'
1259 61
                $CategoryDatatype = $plotSeriesCategory->getDataType();
1260 61
                if ($CategoryDatatype == DataSeriesValues::DATASERIES_TYPE_NUMBER) {
1261 16
                    $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'num');
1262
                } else {
1263 46
                    $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'str');
1264
                }
1265 61
                $objWriter->endElement();
1266
            }
1267
1268
            //    Values
1269 68
            if ($plotSeriesValues) {
1270 68
                $valIsMultiLevelSeries = $valIsMultiLevelSeries || $plotSeriesValues->isMultiLevelSeries();
1271
1272 68
                if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) {
1273 27
                    $objWriter->startElement('c:yVal');
1274
                } else {
1275 47
                    $objWriter->startElement('c:val');
1276
                }
1277
1278 68
                $this->writePlotSeriesValues($plotSeriesValues, $objWriter, $groupType, 'num');
1279 68
                $objWriter->endElement();
1280 68
                if ($groupType === DataSeries::TYPE_SCATTERCHART && $plotGroup->getPlotStyle() === 'smoothMarker') {
1281 10
                    $objWriter->startElement('c:smooth');
1282 10
                    $objWriter->writeAttribute('val', $plotSeriesValues->getSmoothLine() ? '1' : '0');
1283 10
                    $objWriter->endElement();
1284
                }
1285
            }
1286
1287 68
            if ($groupType === DataSeries::TYPE_BUBBLECHART) {
1288 2
                if (!empty($plotGroup->getPlotBubbleSizes()[$plotSeriesIdx])) {
1289 2
                    $objWriter->startElement('c:bubbleSize');
1290 2
                    $this->writePlotSeriesValues(
1291 2
                        $plotGroup->getPlotBubbleSizes()[$plotSeriesIdx],
1292 2
                        $objWriter,
1293 2
                        $groupType,
1294 2
                        'num'
1295 2
                    );
1296 2
                    $objWriter->endElement();
1297 2
                    if ($plotSeriesValues !== false) {
1298 2
                        $objWriter->startElement('c:bubble3D');
1299 2
                        $objWriter->writeAttribute('val', $plotSeriesValues->getBubble3D() ? '1' : '0');
1300 2
                        $objWriter->endElement();
1301
                    }
1302 1
                } elseif ($plotSeriesValues !== false) {
1303 1
                    $this->writeBubbles($plotSeriesValues, $objWriter);
1304
                }
1305
            }
1306
1307 68
            $objWriter->endElement();
1308
        }
1309
1310 68
        $this->seriesIndex += $plotSeriesIdx + 1;
1311
    }
1312
1313
    /**
1314
     * Write Plot Series Label.
1315
     */
1316 67
    private function writePlotSeriesLabel(?DataSeriesValues $plotSeriesLabel, XMLWriter $objWriter): void
1317
    {
1318 67
        if ($plotSeriesLabel === null) {
1319
            return;
1320
        }
1321
1322 67
        $objWriter->startElement('c:f');
1323 67
        $objWriter->writeRawData($plotSeriesLabel->getDataSource());
1324 67
        $objWriter->endElement();
1325
1326 67
        $objWriter->startElement('c:strCache');
1327 67
        $objWriter->startElement('c:ptCount');
1328 67
        $objWriter->writeAttribute('val', (string) $plotSeriesLabel->getPointCount());
1329 67
        $objWriter->endElement();
1330
1331 67
        foreach ($plotSeriesLabel->getDataValues() as $plotLabelKey => $plotLabelValue) {
1332 67
            $objWriter->startElement('c:pt');
1333 67
            $objWriter->writeAttribute('idx', $plotLabelKey);
1334
1335 67
            $objWriter->startElement('c:v');
1336 67
            $objWriter->writeRawData($plotLabelValue);
1337 67
            $objWriter->endElement();
1338 67
            $objWriter->endElement();
1339
        }
1340 67
        $objWriter->endElement();
1341
    }
1342
1343
    /**
1344
     * Write Plot Series Values.
1345
     *
1346
     * @param string $groupType Type of plot for dataseries
1347
     * @param string $dataType Datatype of series values
1348
     */
1349 68
    private function writePlotSeriesValues(?DataSeriesValues $plotSeriesValues, XMLWriter $objWriter, $groupType, $dataType = 'str'): void
1350
    {
1351 68
        if ($plotSeriesValues === null) {
1352
            return;
1353
        }
1354
1355 68
        if ($plotSeriesValues->isMultiLevelSeries()) {
1356 14
            $levelCount = $plotSeriesValues->multiLevelCount();
1357
1358 14
            $objWriter->startElement('c:multiLvlStrRef');
1359
1360 14
            $objWriter->startElement('c:f');
1361 14
            $objWriter->writeRawData($plotSeriesValues->getDataSource());
1362 14
            $objWriter->endElement();
1363
1364 14
            $objWriter->startElement('c:multiLvlStrCache');
1365
1366 14
            $objWriter->startElement('c:ptCount');
1367 14
            $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointCount());
1368 14
            $objWriter->endElement();
1369
1370 14
            for ($level = 0; $level < $levelCount; ++$level) {
1371 14
                $objWriter->startElement('c:lvl');
1372
1373 14
                foreach ($plotSeriesValues->getDataValues() as $plotSeriesKey => $plotSeriesValue) {
1374 14
                    if (isset($plotSeriesValue[$level])) {
1375 14
                        $objWriter->startElement('c:pt');
1376 14
                        $objWriter->writeAttribute('idx', $plotSeriesKey);
1377
1378 14
                        $objWriter->startElement('c:v');
1379 14
                        $objWriter->writeRawData($plotSeriesValue[$level]);
1380 14
                        $objWriter->endElement();
1381 14
                        $objWriter->endElement();
1382
                    }
1383
                }
1384
1385 14
                $objWriter->endElement();
1386
            }
1387
1388 14
            $objWriter->endElement();
1389
1390 14
            $objWriter->endElement();
1391
        } else {
1392 68
            $objWriter->startElement('c:' . $dataType . 'Ref');
1393
1394 68
            $objWriter->startElement('c:f');
1395 68
            $objWriter->writeRawData($plotSeriesValues->getDataSource());
1396 68
            $objWriter->endElement();
1397
1398 68
            $count = $plotSeriesValues->getPointCount();
1399 68
            $source = $plotSeriesValues->getDataSource();
1400 68
            $values = $plotSeriesValues->getDataValues();
1401 68
            if ($count > 1 || ($count === 1 && "=$source" !== (string) $values[0])) {
1402 68
                $objWriter->startElement('c:' . $dataType . 'Cache');
1403
1404 68
                if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) {
1405 63
                    if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) {
1406 33
                        $objWriter->startElement('c:formatCode');
1407 33
                        $objWriter->writeRawData($plotSeriesValues->getFormatCode());
1408 33
                        $objWriter->endElement();
1409
                    }
1410
                }
1411
1412 68
                $objWriter->startElement('c:ptCount');
1413 68
                $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointCount());
1414 68
                $objWriter->endElement();
1415
1416 68
                $dataValues = $plotSeriesValues->getDataValues();
1417 68
                if (!empty($dataValues)) {
1418 68
                    foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
1419 68
                        $objWriter->startElement('c:pt');
1420 68
                        $objWriter->writeAttribute('idx', $plotSeriesKey);
1421
1422 68
                        $objWriter->startElement('c:v');
1423 68
                        $objWriter->writeRawData($plotSeriesValue);
1424 68
                        $objWriter->endElement();
1425 68
                        $objWriter->endElement();
1426
                    }
1427
                }
1428
1429 68
                $objWriter->endElement(); // *Cache
1430
            }
1431
1432 68
            $objWriter->endElement(); // *Ref
1433
        }
1434
    }
1435
1436
    private const CUSTOM_COLOR_TYPES = [
1437
        DataSeries::TYPE_BARCHART,
1438
        DataSeries::TYPE_BARCHART_3D,
1439
        DataSeries::TYPE_PIECHART,
1440
        DataSeries::TYPE_PIECHART_3D,
1441
        DataSeries::TYPE_DONUTCHART,
1442
    ];
1443
1444
    /**
1445
     * Write Bubble Chart Details.
1446
     */
1447 1
    private function writeBubbles(?DataSeriesValues $plotSeriesValues, XMLWriter $objWriter): void
1448
    {
1449 1
        if ($plotSeriesValues === null) {
1450
            return;
1451
        }
1452
1453 1
        $objWriter->startElement('c:bubbleSize');
1454 1
        $objWriter->startElement('c:numLit');
1455
1456 1
        $objWriter->startElement('c:formatCode');
1457 1
        $objWriter->writeRawData('General');
1458 1
        $objWriter->endElement();
1459
1460 1
        $objWriter->startElement('c:ptCount');
1461 1
        $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointCount());
1462 1
        $objWriter->endElement();
1463
1464 1
        $dataValues = $plotSeriesValues->getDataValues();
1465 1
        if (!empty($dataValues)) {
1466 1
            foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
1467 1
                $objWriter->startElement('c:pt');
1468 1
                $objWriter->writeAttribute('idx', $plotSeriesKey);
1469 1
                $objWriter->startElement('c:v');
1470 1
                $objWriter->writeRawData('1');
1471 1
                $objWriter->endElement();
1472 1
                $objWriter->endElement();
1473
            }
1474
        }
1475
1476 1
        $objWriter->endElement();
1477 1
        $objWriter->endElement();
1478
1479 1
        $objWriter->startElement('c:bubble3D');
1480 1
        $objWriter->writeAttribute('val', $plotSeriesValues->getBubble3D() ? '1' : '0');
1481 1
        $objWriter->endElement();
1482
    }
1483
1484
    /**
1485
     * Write Layout.
1486
     */
1487 68
    private function writeLayout(XMLWriter $objWriter, ?Layout $layout = null): void
1488
    {
1489 68
        $objWriter->startElement('c:layout');
1490
1491 68
        if ($layout !== null) {
1492 34
            $objWriter->startElement('c:manualLayout');
1493
1494 34
            $layoutTarget = $layout->getLayoutTarget();
1495 34
            if ($layoutTarget !== null) {
1496 15
                $objWriter->startElement('c:layoutTarget');
1497 15
                $objWriter->writeAttribute('val', $layoutTarget);
1498 15
                $objWriter->endElement();
1499
            }
1500
1501 34
            $xMode = $layout->getXMode();
1502 34
            if ($xMode !== null) {
1503 15
                $objWriter->startElement('c:xMode');
1504 15
                $objWriter->writeAttribute('val', $xMode);
1505 15
                $objWriter->endElement();
1506
            }
1507
1508 34
            $yMode = $layout->getYMode();
1509 34
            if ($yMode !== null) {
1510 15
                $objWriter->startElement('c:yMode');
1511 15
                $objWriter->writeAttribute('val', $yMode);
1512 15
                $objWriter->endElement();
1513
            }
1514
1515 34
            $x = $layout->getXPosition();
1516 34
            if ($x !== null) {
1517 15
                $objWriter->startElement('c:x');
1518 15
                $objWriter->writeAttribute('val', "$x");
1519 15
                $objWriter->endElement();
1520
            }
1521
1522 34
            $y = $layout->getYPosition();
1523 34
            if ($y !== null) {
1524 15
                $objWriter->startElement('c:y');
1525 15
                $objWriter->writeAttribute('val', "$y");
1526 15
                $objWriter->endElement();
1527
            }
1528
1529 34
            $w = $layout->getWidth();
1530 34
            if ($w !== null) {
1531 15
                $objWriter->startElement('c:w');
1532 15
                $objWriter->writeAttribute('val', "$w");
1533 15
                $objWriter->endElement();
1534
            }
1535
1536 34
            $h = $layout->getHeight();
1537 34
            if ($h !== null) {
1538 15
                $objWriter->startElement('c:h');
1539 15
                $objWriter->writeAttribute('val', "$h");
1540 15
                $objWriter->endElement();
1541
            }
1542
1543 34
            $objWriter->endElement();
1544
        }
1545
1546 68
        $objWriter->endElement();
1547
    }
1548
1549
    /**
1550
     * Write Alternate Content block.
1551
     */
1552 68
    private function writeAlternateContent(XMLWriter $objWriter): void
1553
    {
1554 68
        $objWriter->startElement('mc:AlternateContent');
1555 68
        $objWriter->writeAttribute('xmlns:mc', Namespaces::COMPATIBILITY);
1556
1557 68
        $objWriter->startElement('mc:Choice');
1558 68
        $objWriter->writeAttribute('Requires', 'c14');
1559 68
        $objWriter->writeAttribute('xmlns:c14', Namespaces::CHART_ALTERNATE);
1560
1561 68
        $objWriter->startElement('c14:style');
1562 68
        $objWriter->writeAttribute('val', '102');
1563 68
        $objWriter->endElement();
1564 68
        $objWriter->endElement();
1565
1566 68
        $objWriter->startElement('mc:Fallback');
1567 68
        $objWriter->startElement('c:style');
1568 68
        $objWriter->writeAttribute('val', '2');
1569 68
        $objWriter->endElement();
1570 68
        $objWriter->endElement();
1571
1572 68
        $objWriter->endElement();
1573
    }
1574
1575
    /**
1576
     * Write Printer Settings.
1577
     */
1578 68
    private function writePrintSettings(XMLWriter $objWriter): void
1579
    {
1580 68
        $objWriter->startElement('c:printSettings');
1581
1582 68
        $objWriter->startElement('c:headerFooter');
1583 68
        $objWriter->endElement();
1584
1585 68
        $objWriter->startElement('c:pageMargins');
1586 68
        $objWriter->writeAttribute('footer', '0.3');
1587 68
        $objWriter->writeAttribute('header', '0.3');
1588 68
        $objWriter->writeAttribute('r', '0.7');
1589 68
        $objWriter->writeAttribute('l', '0.7');
1590 68
        $objWriter->writeAttribute('t', '0.75');
1591 68
        $objWriter->writeAttribute('b', '0.75');
1592 68
        $objWriter->endElement();
1593
1594 68
        $objWriter->startElement('c:pageSetup');
1595 68
        $objWriter->writeAttribute('orientation', 'portrait');
1596 68
        $objWriter->endElement();
1597
1598 68
        $objWriter->endElement();
1599
    }
1600
1601 68
    private function writeEffects(XMLWriter $objWriter, Properties $yAxis): void
1602
    {
1603
        if (
1604 68
            !empty($yAxis->getSoftEdgesSize())
1605 68
            || !empty($yAxis->getShadowProperty('effect'))
1606 68
            || !empty($yAxis->getGlowProperty('size'))
1607
        ) {
1608 7
            $objWriter->startElement('a:effectLst');
1609 7
            $this->writeGlow($objWriter, $yAxis);
1610 7
            $this->writeShadow($objWriter, $yAxis);
1611 7
            $this->writeSoftEdge($objWriter, $yAxis);
1612 7
            $objWriter->endElement(); // effectLst
1613
        }
1614
    }
1615
1616 7
    private function writeShadow(XMLWriter $objWriter, Properties $xAxis): void
1617
    {
1618 7
        if (empty($xAxis->getShadowProperty('effect'))) {
1619 5
            return;
1620
        }
1621
        /** @var string */
1622 3
        $effect = $xAxis->getShadowProperty('effect');
1623 3
        $objWriter->startElement("a:$effect");
1624
1625 3
        if (is_numeric($xAxis->getShadowProperty('blur'))) {
1626 3
            $objWriter->writeAttribute('blurRad', Properties::pointsToXml((float) $xAxis->getShadowProperty('blur')));
1627
        }
1628 3
        if (is_numeric($xAxis->getShadowProperty('distance'))) {
1629 3
            $objWriter->writeAttribute('dist', Properties::pointsToXml((float) $xAxis->getShadowProperty('distance')));
1630
        }
1631 3
        if (is_numeric($xAxis->getShadowProperty('direction'))) {
1632 3
            $objWriter->writeAttribute('dir', Properties::angleToXml((float) $xAxis->getShadowProperty('direction')));
1633
        }
1634 3
        $algn = $xAxis->getShadowProperty('algn');
1635 3
        if (is_string($algn) && $algn !== '') {
1636 3
            $objWriter->writeAttribute('algn', $algn);
1637
        }
1638 3
        foreach (['sx', 'sy'] as $sizeType) {
1639 3
            $sizeValue = $xAxis->getShadowProperty(['size', $sizeType]);
1640 3
            if (is_numeric($sizeValue)) {
1641 2
                $objWriter->writeAttribute($sizeType, Properties::tenthOfPercentToXml((float) $sizeValue));
1642
            }
1643
        }
1644 3
        foreach (['kx', 'ky'] as $sizeType) {
1645 3
            $sizeValue = $xAxis->getShadowProperty(['size', $sizeType]);
1646 3
            if (is_numeric($sizeValue)) {
1647 2
                $objWriter->writeAttribute($sizeType, Properties::angleToXml((float) $sizeValue));
1648
            }
1649
        }
1650 3
        $rotWithShape = $xAxis->getShadowProperty('rotWithShape');
1651 3
        if (is_numeric($rotWithShape)) {
1652 3
            $objWriter->writeAttribute('rotWithShape', (string) (int) $rotWithShape);
1653
        }
1654
1655 3
        $this->writeColor($objWriter, $xAxis->getShadowColorObject(), false);
1656
1657 3
        $objWriter->endElement();
1658
    }
1659
1660 7
    private function writeGlow(XMLWriter $objWriter, Properties $yAxis): void
1661
    {
1662 7
        $size = $yAxis->getGlowProperty('size');
1663 7
        if (empty($size)) {
1664 4
            return;
1665
        }
1666 5
        $objWriter->startElement('a:glow');
1667 5
        $objWriter->writeAttribute('rad', Properties::pointsToXml((float) $size));
1668 5
        $this->writeColor($objWriter, $yAxis->getGlowColorObject(), false);
1669 5
        $objWriter->endElement(); // glow
1670
    }
1671
1672 7
    private function writeSoftEdge(XMLWriter $objWriter, Properties $yAxis): void
1673
    {
1674 7
        $softEdgeSize = $yAxis->getSoftEdgesSize();
1675 7
        if (empty($softEdgeSize)) {
1676 6
            return;
1677
        }
1678 2
        $objWriter->startElement('a:softEdge');
1679 2
        $objWriter->writeAttribute('rad', Properties::pointsToXml((float) $softEdgeSize));
1680 2
        $objWriter->endElement(); //end softEdge
1681
    }
1682
1683 68
    private function writeLineStyles(XMLWriter $objWriter, Properties $gridlines, bool $noFill = false): void
1684
    {
1685 68
        $objWriter->startElement('a:ln');
1686 68
        $widthTemp = $gridlines->getLineStyleProperty('width');
1687 68
        if (is_numeric($widthTemp)) {
1688 27
            $objWriter->writeAttribute('w', Properties::pointsToXml((float) $widthTemp));
1689
        }
1690 68
        $this->writeNotEmpty($objWriter, 'cap', $gridlines->getLineStyleProperty('cap'));
1691 68
        $this->writeNotEmpty($objWriter, 'cmpd', $gridlines->getLineStyleProperty('compound'));
1692 68
        if ($noFill) {
1693 20
            $objWriter->startElement('a:noFill');
1694 20
            $objWriter->endElement();
1695
        } else {
1696 68
            $this->writeColor($objWriter, $gridlines->getLineColor());
1697
        }
1698
1699 68
        $dash = $gridlines->getLineStyleProperty('dash');
1700 68
        if (!empty($dash)) {
1701 17
            $objWriter->startElement('a:prstDash');
1702 17
            $this->writeNotEmpty($objWriter, 'val', $dash);
1703 17
            $objWriter->endElement();
1704
        }
1705
1706 68
        if ($gridlines->getLineStyleProperty('join') === 'miter') {
1707 12
            $objWriter->startElement('a:miter');
1708 12
            $objWriter->writeAttribute('lim', '800000');
1709 12
            $objWriter->endElement();
1710 68
        } elseif ($gridlines->getLineStyleProperty('join') === 'bevel') {
1711 5
            $objWriter->startElement('a:bevel');
1712 5
            $objWriter->endElement();
1713
        }
1714
1715 68
        if ($gridlines->getLineStyleProperty(['arrow', 'head', 'type'])) {
1716 12
            $objWriter->startElement('a:headEnd');
1717 12
            $objWriter->writeAttribute('type', $gridlines->getLineStyleProperty(['arrow', 'head', 'type']));
1718 12
            $this->writeNotEmpty($objWriter, 'w', $gridlines->getLineStyleArrowWidth('head'));
1719 12
            $this->writeNotEmpty($objWriter, 'len', $gridlines->getLineStyleArrowLength('head'));
1720 12
            $objWriter->endElement();
1721
        }
1722
1723 68
        if ($gridlines->getLineStyleProperty(['arrow', 'end', 'type'])) {
1724 12
            $objWriter->startElement('a:tailEnd');
1725 12
            $objWriter->writeAttribute('type', $gridlines->getLineStyleProperty(['arrow', 'end', 'type']));
1726 12
            $this->writeNotEmpty($objWriter, 'w', $gridlines->getLineStyleArrowWidth('end'));
1727 12
            $this->writeNotEmpty($objWriter, 'len', $gridlines->getLineStyleArrowLength('end'));
1728 12
            $objWriter->endElement();
1729
        }
1730 68
        $objWriter->endElement(); //end ln
1731
    }
1732
1733 68
    private function writeNotEmpty(XMLWriter $objWriter, string $name, ?string $value): void
1734
    {
1735 68
        if ($value !== null && $value !== '') {
1736 17
            $objWriter->writeAttribute($name, $value);
1737
        }
1738
    }
1739
1740 68
    private function writeColor(XMLWriter $objWriter, ChartColor $chartColor, bool $solidFill = true): void
1741
    {
1742 68
        $type = $chartColor->getType();
1743 68
        $value = $chartColor->getValue();
1744 68
        if (!empty($type) && !empty($value)) {
1745 43
            if ($solidFill) {
1746 37
                $objWriter->startElement('a:solidFill');
1747
            }
1748 43
            $objWriter->startElement("a:$type");
1749 43
            $objWriter->writeAttribute('val', $value);
1750 43
            $alpha = $chartColor->getAlpha();
1751 43
            if (is_numeric($alpha)) {
1752 19
                $objWriter->startElement('a:alpha');
1753 19
                $objWriter->writeAttribute('val', ChartColor::alphaToXml((int) $alpha));
1754 19
                $objWriter->endElement(); // a:alpha
1755
            }
1756 43
            $brightness = $chartColor->getBrightness();
1757 43
            if (is_numeric($brightness)) {
1758 8
                $brightness = (int) $brightness;
1759 8
                $lumOff = 100 - $brightness;
1760 8
                $objWriter->startElement('a:lumMod');
1761 8
                $objWriter->writeAttribute('val', ChartColor::alphaToXml($brightness));
1762 8
                $objWriter->endElement(); // a:lumMod
1763 8
                $objWriter->startElement('a:lumOff');
1764 8
                $objWriter->writeAttribute('val', ChartColor::alphaToXml($lumOff));
1765 8
                $objWriter->endElement(); // a:lumOff
1766
            }
1767 43
            $objWriter->endElement(); //a:srgbClr/schemeClr/prstClr
1768 43
            if ($solidFill) {
1769 37
                $objWriter->endElement(); //a:solidFill
1770
            }
1771
        }
1772
    }
1773
}
1774