Passed
Pull Request — master (#4414)
by Owen
14:43
created

Chart::writePlotSeriesValues()   D

Complexity

Conditions 18
Paths 12

Size

Total Lines 84
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 52
CRAP Score 18.0021

Importance

Changes 0
Metric Value
eloc 54
c 0
b 0
f 0
dl 0
loc 84
ccs 52
cts 53
cp 0.9811
rs 4.8666
cc 18
nc 12
nop 4
crap 18.0021

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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