Passed
Pull Request — master (#4369)
by Owen
12:10
created

Chart::writeTitle()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 42
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 5

Importance

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