Chart::writePlotSeriesValuesElement()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

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