Completed
Push — master ( a3489b...087532 )
by Mark
33s queued 30s
created

Chart::writePlotGroup()   F

Complexity

Conditions 88
Paths > 20000

Size

Total Lines 307
Code Lines 213

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 204
CRAP Score 88.1811

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 213
c 1
b 0
f 0
dl 0
loc 307
ccs 204
cts 210
cp 0.9714
rs 0
cc 88
nc 723144721
nop 6
crap 88.1811

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