• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPOffice / PhpSpreadsheet / 17663639262

12 Sep 2025 03:26AM UTC coverage: 95.297% (-0.001%) from 95.298%
17663639262

Pull #4641

github

web-flow
Merge dc11b8ccc into 9b28b9e9c
Pull Request #4641: Proper Output for BASE Function

40307 of 42296 relevant lines covered (95.3%)

348.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.73
/src/PhpSpreadsheet/Chart/DataSeriesValues.php
1
<?php
2

3
namespace PhpOffice\PhpSpreadsheet\Chart;
4

5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
7
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
8
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
9

10
class DataSeriesValues extends Properties
11
{
12
    const DATASERIES_TYPE_STRING = 'String';
13
    const DATASERIES_TYPE_NUMBER = 'Number';
14

15
    private const DATA_TYPE_VALUES = [
16
        self::DATASERIES_TYPE_STRING,
17
        self::DATASERIES_TYPE_NUMBER,
18
    ];
19

20
    /**
21
     * Series Data Type.
22
     */
23
    private string $dataType;
24

25
    /**
26
     * Series Data Source.
27
     */
28
    private ?string $dataSource;
29

30
    /**
31
     * Format Code.
32
     */
33
    private ?string $formatCode;
34

35
    /**
36
     * Series Point Marker.
37
     */
38
    private ?string $pointMarker;
39

40
    private ChartColor $markerFillColor;
41

42
    private ChartColor $markerBorderColor;
43

44
    /**
45
     * Series Point Size.
46
     */
47
    private int $pointSize = 3;
48

49
    /**
50
     * Point Count (The number of datapoints in the dataseries).
51
     */
52
    private int $pointCount;
53

54
    /**
55
     * Data Values.
56
     *
57
     * @var null|mixed[]
58
     */
59
    private ?array $dataValues;
60

61
    /**
62
     * Fill color (can be array with colors if dataseries have custom colors).
63
     *
64
     * @var null|ChartColor|ChartColor[]
65
     */
66
    private $fillColor;
67

68
    private bool $scatterLines = true;
69

70
    private bool $bubble3D = false;
71

72
    private ?Layout $labelLayout = null;
73

74
    /** @var TrendLine[] */
75
    private array $trendLines = [];
76

77
    /**
78
     * Create a new DataSeriesValues object.
79
     *
80
     * @param null|mixed[] $dataValues
81
     * @param null|ChartColor|ChartColor[]|string|string[] $fillColor
82
     */
83
    public function __construct(
84
        string $dataType = self::DATASERIES_TYPE_NUMBER,
85
        ?string $dataSource = null,
86
        ?string $formatCode = null,
87
        int $pointCount = 0,
88
        ?array $dataValues = [],
89
        ?string $marker = null,
90
        null|ChartColor|array|string $fillColor = null,
91
        int|string $pointSize = 3
92
    ) {
93
        parent::__construct();
118✔
94
        $this->markerFillColor = new ChartColor();
118✔
95
        $this->markerBorderColor = new ChartColor();
118✔
96
        $this->setDataType($dataType);
118✔
97
        $this->dataSource = $dataSource;
118✔
98
        $this->formatCode = $formatCode;
118✔
99
        $this->pointCount = $pointCount;
118✔
100
        $this->dataValues = $dataValues;
118✔
101
        $this->pointMarker = $marker;
118✔
102
        if ($fillColor !== null) {
118✔
103
            $this->setFillColor($fillColor);
27✔
104
        }
105
        if (is_numeric($pointSize)) {
118✔
106
            $this->pointSize = (int) $pointSize;
115✔
107
        }
108
    }
109

110
    /**
111
     * Get Series Data Type.
112
     */
113
    public function getDataType(): string
114
    {
115
        return $this->dataType;
85✔
116
    }
117

118
    /**
119
     * Set Series Data Type.
120
     *
121
     * @param string $dataType Datatype of this data series
122
     *                                Typical values are:
123
     *                                    DataSeriesValues::DATASERIES_TYPE_STRING
124
     *                                        Normally used for axis point values
125
     *                                    DataSeriesValues::DATASERIES_TYPE_NUMBER
126
     *                                        Normally used for chart data values
127
     *
128
     * @return $this
129
     */
130
    public function setDataType(string $dataType): static
131
    {
132
        if (!in_array($dataType, self::DATA_TYPE_VALUES)) {
118✔
133
            throw new Exception('Invalid datatype for chart data series values');
1✔
134
        }
135
        $this->dataType = $dataType;
118✔
136

137
        return $this;
118✔
138
    }
139

140
    /**
141
     * Get Series Data Source (formula).
142
     */
143
    public function getDataSource(): ?string
144
    {
145
        return $this->dataSource;
93✔
146
    }
147

148
    /**
149
     * Set Series Data Source (formula).
150
     *
151
     * @return $this
152
     */
153
    public function setDataSource(?string $dataSource): static
154
    {
155
        $this->dataSource = $dataSource;
1✔
156

157
        return $this;
1✔
158
    }
159

160
    /**
161
     * Get Point Marker.
162
     */
163
    public function getPointMarker(): ?string
164
    {
165
        return $this->pointMarker;
95✔
166
    }
167

168
    /**
169
     * Set Point Marker.
170
     *
171
     * @return $this
172
     */
173
    public function setPointMarker(string $marker): static
174
    {
175
        $this->pointMarker = $marker;
4✔
176

177
        return $this;
4✔
178
    }
179

180
    public function getMarkerFillColor(): ChartColor
181
    {
182
        return $this->markerFillColor;
96✔
183
    }
184

185
    public function getMarkerBorderColor(): ChartColor
186
    {
187
        return $this->markerBorderColor;
96✔
188
    }
189

190
    /**
191
     * Get Point Size.
192
     */
193
    public function getPointSize(): int
194
    {
195
        return $this->pointSize;
27✔
196
    }
197

198
    /**
199
     * Set Point Size.
200
     *
201
     * @return $this
202
     */
203
    public function setPointSize(int $size = 3): static
204
    {
205
        $this->pointSize = $size;
4✔
206

207
        return $this;
4✔
208
    }
209

210
    /**
211
     * Get Series Format Code.
212
     */
213
    public function getFormatCode(): ?string
214
    {
215
        return $this->formatCode;
88✔
216
    }
217

218
    /**
219
     * Set Series Format Code.
220
     *
221
     * @return $this
222
     */
223
    public function setFormatCode(string $formatCode): static
224
    {
225
        $this->formatCode = $formatCode;
67✔
226

227
        return $this;
67✔
228
    }
229

230
    /**
231
     * Get Series Point Count.
232
     */
233
    public function getPointCount(): int
234
    {
235
        return $this->pointCount;
95✔
236
    }
237

238
    /**
239
     * Get fill color object.
240
     *
241
     * @return null|ChartColor|ChartColor[]
242
     */
243
    public function getFillColorObject()
244
    {
245
        return $this->fillColor;
91✔
246
    }
247

248
    private function stringToChartColor(string $fillString): ChartColor
249
    {
250
        $value = $type = '';
16✔
251
        if (str_starts_with($fillString, '*')) {
16✔
252
            $type = 'schemeClr';
5✔
253
            $value = substr($fillString, 1);
5✔
254
        } elseif (str_starts_with($fillString, '/')) {
14✔
255
            $type = 'prstClr';
3✔
256
            $value = substr($fillString, 1);
3✔
257
        } elseif ($fillString !== '') {
14✔
258
            $type = 'srgbClr';
14✔
259
            $value = $fillString;
14✔
260
            $this->validateColor($value);
14✔
261
        }
262

263
        return new ChartColor($value, null, $type);
15✔
264
    }
265

266
    private function chartColorToString(ChartColor $chartColor): string
267
    {
268
        $type = (string) $chartColor->getColorProperty('type');
4✔
269
        $value = (string) $chartColor->getColorProperty('value');
4✔
270
        if ($type === '' || $value === '') {
4✔
271
            return '';
1✔
272
        }
273
        if ($type === 'schemeClr') {
4✔
274
            return "*$value";
3✔
275
        }
276
        if ($type === 'prstClr') {
4✔
277
            return "/$value";
3✔
278
        }
279

280
        return $value;
4✔
281
    }
282

283
    /**
284
     * Get fill color.
285
     *
286
     * @return string|string[] HEX color or array with HEX colors
287
     */
288
    public function getFillColor(): string|array
289
    {
290
        if ($this->fillColor === null) {
9✔
291
            return '';
5✔
292
        }
293
        if (is_array($this->fillColor)) {
4✔
294
            $array = [];
4✔
295
            foreach ($this->fillColor as $chartColor) {
4✔
296
                $array[] = $this->chartColorToString($chartColor);
4✔
297
            }
298

299
            return $array;
4✔
300
        }
301

302
        return $this->chartColorToString($this->fillColor);
1✔
303
    }
304

305
    /**
306
     * Set fill color for series.
307
     *
308
     * @param ChartColor|ChartColor[]|string|string[] $color HEX color or array with HEX colors
309
     *
310
     * @return   $this
311
     */
312
    public function setFillColor($color): static
313
    {
314
        if (is_array($color)) {
38✔
315
            $this->fillColor = [];
16✔
316
            foreach ($color as $fillString) {
16✔
317
                if ($fillString instanceof ChartColor) {
16✔
318
                    $this->fillColor[] = $fillString;
9✔
319
                } else {
320
                    $this->fillColor[] = $this->stringToChartColor($fillString);
9✔
321
                }
322
            }
323
        } elseif ($color instanceof ChartColor) {
29✔
324
            $this->fillColor = $color;
21✔
325
        } else {
326
            $this->fillColor = $this->stringToChartColor($color);
8✔
327
        }
328

329
        return $this;
36✔
330
    }
331

332
    /**
333
     * Method for validating hex color.
334
     *
335
     * @param string $color value for color
336
     */
337
    private function validateColor(string $color): void
338
    {
339
        if (!preg_match('/^[a-f0-9]{6}$/i', $color)) {
14✔
340
            throw new Exception(sprintf('Invalid hex color for chart series (color: "%s")', $color));
2✔
341
        }
342
    }
343

344
    /**
345
     * Get line width for series.
346
     */
347
    public function getLineWidth(): null|float|int
348
    {
349
        /** @var null|float|int */
350
        $temp = $this->lineStyleProperties['width'];
6✔
351

352
        return $temp;
6✔
353
    }
354

355
    /**
356
     * Set line width for the series.
357
     *
358
     * @return $this
359
     */
360
    public function setLineWidth(null|float|int $width): static
361
    {
362
        $this->lineStyleProperties['width'] = $width;
7✔
363

364
        return $this;
7✔
365
    }
366

367
    /**
368
     * Identify if the Data Series is a multi-level or a simple series.
369
     */
370
    public function isMultiLevelSeries(): ?bool
371
    {
372
        if (!empty($this->dataValues)) {
91✔
373
            return is_array(array_values($this->dataValues)[0]);
91✔
374
        }
375

376
        return null;
2✔
377
    }
378

379
    /**
380
     * Return the level count of a multi-level Data Series.
381
     */
382
    public function multiLevelCount(): int
383
    {
384
        $levelCount = 0;
18✔
385
        foreach (($this->dataValues ?? []) as $dataValueSet) {
18✔
386
            /** @var mixed[] $dataValueSet */
387
            $levelCount = max($levelCount, count($dataValueSet));
18✔
388
        }
389

390
        return $levelCount;
18✔
391
    }
392

393
    /**
394
     * Get Series Data Values.
395
     *
396
     * @return null|mixed[]
397
     */
398
    public function getDataValues(): ?array
399
    {
400
        return $this->dataValues;
95✔
401
    }
402

403
    /**
404
     * Get the first Series Data value.
405
     */
406
    public function getDataValue(): mixed
407
    {
408
        if ($this->dataValues === null) {
6✔
409
            return null;
×
410
        }
411
        $count = count($this->dataValues);
6✔
412
        if ($count == 0) {
6✔
413
            return null;
3✔
414
        } elseif ($count == 1) {
5✔
415
            return $this->dataValues[0];
5✔
416
        }
417

418
        return $this->dataValues;
1✔
419
    }
420

421
    /**
422
     * Set Series Data Values.
423
     *
424
     * @param mixed[] $dataValues
425
     *
426
     * @return $this
427
     */
428
    public function setDataValues(array $dataValues): static
429
    {
430
        $this->dataValues = Functions::flattenArray($dataValues);
67✔
431
        $this->pointCount = count($dataValues);
67✔
432

433
        return $this;
67✔
434
    }
435

436
    public function refresh(Worksheet $worksheet, bool $flatten = true): void
437
    {
438
        if ($this->dataSource !== null) {
95✔
439
            $calcEngine = Calculation::getInstance($worksheet->getParent());
94✔
440
            $newDataValues = Calculation::unwrapResult(
94✔
441
                $calcEngine->_calculateFormulaValue(
94✔
442
                    '=' . $this->dataSource,
94✔
443
                    null,
94✔
444
                    $worksheet->getCell('A1')
94✔
445
                )
94✔
446
            );
94✔
447
            if ($flatten) {
94✔
448
                $this->dataValues = Functions::flattenArray($newDataValues);
94✔
449
                foreach ($this->dataValues as &$dataValue) {
94✔
450
                    if (is_string($dataValue) && !empty($dataValue) && $dataValue[0] == '#') {
94✔
451
                        $dataValue = 0.0;
×
452
                    }
453
                }
454
                unset($dataValue);
94✔
455
            } else {
456
                [, $cellRange] = Worksheet::extractSheetTitle($this->dataSource, true);
85✔
457
                $dimensions = Coordinate::rangeDimension(str_replace('$', '', $cellRange ?? ''));
85✔
458
                if (($dimensions[0] == 1) || ($dimensions[1] == 1)) {
85✔
459
                    $this->dataValues = Functions::flattenArray($newDataValues);
68✔
460
                } else {
461
                    /** @var array<int, mixed[]> */
462
                    $newDataValuesx = $newDataValues;
21✔
463
                    /** @var mixed[][] $newArray */
464
                    $newArray = array_values(array_shift($newDataValuesx) ?? []);
21✔
465
                    foreach ($newArray as $i => $newDataSet) {
21✔
466
                        $newArray[$i] = [$newDataSet];
21✔
467
                    }
468

469
                    foreach ($newDataValuesx as $newDataSet) {
21✔
470
                        $i = 0;
21✔
471
                        foreach ($newDataSet as $newDataVal) {
21✔
472
                            array_unshift($newArray[$i++], $newDataVal);
21✔
473
                        }
474
                    }
475
                    $this->dataValues = $newArray;
21✔
476
                }
477
            }
478
            $this->pointCount = count($this->dataValues ?? []);
94✔
479
        }
480
    }
481

482
    public function getScatterLines(): bool
483
    {
484
        return $this->scatterLines;
45✔
485
    }
486

487
    public function setScatterLines(bool $scatterLines): self
488
    {
489
        $this->scatterLines = $scatterLines;
28✔
490

491
        return $this;
28✔
492
    }
493

494
    public function getBubble3D(): bool
495
    {
496
        return $this->bubble3D;
3✔
497
    }
498

499
    public function setBubble3D(bool $bubble3D): self
500
    {
501
        $this->bubble3D = $bubble3D;
2✔
502

503
        return $this;
2✔
504
    }
505

506
    /**
507
     * Smooth Line. Must be specified for both DataSeries and DataSeriesValues.
508
     */
509
    private bool $smoothLine = false;
510

511
    /**
512
     * Get Smooth Line.
513
     */
514
    public function getSmoothLine(): bool
515
    {
516
        return $this->smoothLine;
11✔
517
    }
518

519
    /**
520
     * Set Smooth Line.
521
     *
522
     * @return $this
523
     */
524
    public function setSmoothLine(bool $smoothLine): static
525
    {
526
        $this->smoothLine = $smoothLine;
15✔
527

528
        return $this;
15✔
529
    }
530

531
    public function getLabelLayout(): ?Layout
532
    {
533
        return $this->labelLayout;
91✔
534
    }
535

536
    public function setLabelLayout(?Layout $labelLayout): self
537
    {
538
        $this->labelLayout = $labelLayout;
9✔
539

540
        return $this;
9✔
541
    }
542

543
    /** @param TrendLine[] $trendLines */
544
    public function setTrendLines(array $trendLines): self
545
    {
546
        $this->trendLines = $trendLines;
7✔
547

548
        return $this;
7✔
549
    }
550

551
    /** @return TrendLine[] */
552
    public function getTrendLines(): array
553
    {
554
        return $this->trendLines;
92✔
555
    }
556

557
    /**
558
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
559
     */
560
    public function __clone()
561
    {
562
        parent::__clone();
5✔
563
        $this->markerFillColor = clone $this->markerFillColor;
5✔
564
        $this->markerBorderColor = clone $this->markerBorderColor;
5✔
565
        if (is_array($this->fillColor)) {
5✔
566
            $fillColor = $this->fillColor;
1✔
567
            $this->fillColor = [];
1✔
568
            foreach ($fillColor as $color) {
1✔
569
                $this->fillColor[] = clone $color;
1✔
570
            }
571
        } elseif ($this->fillColor instanceof ChartColor) {
5✔
572
            $this->fillColor = clone $this->fillColor;
1✔
573
        }
574
        $this->labelLayout = ($this->labelLayout === null) ? null : clone $this->labelLayout;
5✔
575
        $trendLines = $this->trendLines;
5✔
576
        $this->trendLines = [];
5✔
577
        foreach ($trendLines as $trendLine) {
5✔
578
            $this->trendLines[] = clone $trendLine;
1✔
579
        }
580
    }
581
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc