Passed
Push — master ( 2661ad...b3452a )
by
unknown
13:20 queued 15s
created

Chart::writeTitle()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 42
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 5

Importance

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