Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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