Passed
Push — master ( 41da84...3cb7b3 )
by
unknown
13:24 queued 13s
created

Chart::writePlotSeriesLabel()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 3.0015

Importance

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