Passed
Pull Request — master (#4414)
by Owen
13:11
created

Chart::writePlotSeriesValuesElement()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

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