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\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
    private ?array $dataValues;
58
 
59
    /**
60
     * Fill color (can be array with colors if dataseries have custom colors).
61
     *
62
     * @var null|ChartColor|ChartColor[]
63
     */
64
    private $fillColor;
65
 
66
    private bool $scatterLines = true;
67
 
68
    private bool $bubble3D = false;
69
 
70
    private ?Layout $labelLayout = null;
71
 
72
    /** @var TrendLine[] */
73
    private array $trendLines = [];
74
 
75
    /**
76
     * Create a new DataSeriesValues object.
77
     *
78
     * @param null|ChartColor|ChartColor[]|string|string[] $fillColor
79
     */
80
    public function __construct(
81
        string $dataType = self::DATASERIES_TYPE_NUMBER,
82
        ?string $dataSource = null,
83
        ?string $formatCode = null,
84
        int $pointCount = 0,
85
        ?array $dataValues = [],
86
        ?string $marker = null,
87
        null|ChartColor|array|string $fillColor = null,
88
        int|string $pointSize = 3
89
    ) {
90
        parent::__construct();
91
        $this->markerFillColor = new ChartColor();
92
        $this->markerBorderColor = new ChartColor();
93
        $this->setDataType($dataType);
94
        $this->dataSource = $dataSource;
95
        $this->formatCode = $formatCode;
96
        $this->pointCount = $pointCount;
97
        $this->dataValues = $dataValues;
98
        $this->pointMarker = $marker;
99
        if ($fillColor !== null) {
100
            $this->setFillColor($fillColor);
101
        }
102
        if (is_numeric($pointSize)) {
103
            $this->pointSize = (int) $pointSize;
104
        }
105
    }
106
 
107
    /**
108
     * Get Series Data Type.
109
     */
110
    public function getDataType(): string
111
    {
112
        return $this->dataType;
113
    }
114
 
115
    /**
116
     * Set Series Data Type.
117
     *
118
     * @param string $dataType Datatype of this data series
119
     *                                Typical values are:
120
     *                                    DataSeriesValues::DATASERIES_TYPE_STRING
121
     *                                        Normally used for axis point values
122
     *                                    DataSeriesValues::DATASERIES_TYPE_NUMBER
123
     *                                        Normally used for chart data values
124
     *
125
     * @return $this
126
     */
127
    public function setDataType(string $dataType): static
128
    {
129
        if (!in_array($dataType, self::DATA_TYPE_VALUES)) {
130
            throw new Exception('Invalid datatype for chart data series values');
131
        }
132
        $this->dataType = $dataType;
133
 
134
        return $this;
135
    }
136
 
137
    /**
138
     * Get Series Data Source (formula).
139
     */
140
    public function getDataSource(): ?string
141
    {
142
        return $this->dataSource;
143
    }
144
 
145
    /**
146
     * Set Series Data Source (formula).
147
     *
148
     * @return $this
149
     */
150
    public function setDataSource(?string $dataSource): static
151
    {
152
        $this->dataSource = $dataSource;
153
 
154
        return $this;
155
    }
156
 
157
    /**
158
     * Get Point Marker.
159
     */
160
    public function getPointMarker(): ?string
161
    {
162
        return $this->pointMarker;
163
    }
164
 
165
    /**
166
     * Set Point Marker.
167
     *
168
     * @return $this
169
     */
170
    public function setPointMarker(string $marker): static
171
    {
172
        $this->pointMarker = $marker;
173
 
174
        return $this;
175
    }
176
 
177
    public function getMarkerFillColor(): ChartColor
178
    {
179
        return $this->markerFillColor;
180
    }
181
 
182
    public function getMarkerBorderColor(): ChartColor
183
    {
184
        return $this->markerBorderColor;
185
    }
186
 
187
    /**
188
     * Get Point Size.
189
     */
190
    public function getPointSize(): int
191
    {
192
        return $this->pointSize;
193
    }
194
 
195
    /**
196
     * Set Point Size.
197
     *
198
     * @return $this
199
     */
200
    public function setPointSize(int $size = 3): static
201
    {
202
        $this->pointSize = $size;
203
 
204
        return $this;
205
    }
206
 
207
    /**
208
     * Get Series Format Code.
209
     */
210
    public function getFormatCode(): ?string
211
    {
212
        return $this->formatCode;
213
    }
214
 
215
    /**
216
     * Set Series Format Code.
217
     *
218
     * @return $this
219
     */
220
    public function setFormatCode(string $formatCode): static
221
    {
222
        $this->formatCode = $formatCode;
223
 
224
        return $this;
225
    }
226
 
227
    /**
228
     * Get Series Point Count.
229
     */
230
    public function getPointCount(): int
231
    {
232
        return $this->pointCount;
233
    }
234
 
235
    /**
236
     * Get fill color object.
237
     *
238
     * @return null|ChartColor|ChartColor[]
239
     */
240
    public function getFillColorObject()
241
    {
242
        return $this->fillColor;
243
    }
244
 
245
    private function stringToChartColor(string $fillString): ChartColor
246
    {
247
        $value = $type = '';
248
        if (str_starts_with($fillString, '*')) {
249
            $type = 'schemeClr';
250
            $value = substr($fillString, 1);
251
        } elseif (str_starts_with($fillString, '/')) {
252
            $type = 'prstClr';
253
            $value = substr($fillString, 1);
254
        } elseif ($fillString !== '') {
255
            $type = 'srgbClr';
256
            $value = $fillString;
257
            $this->validateColor($value);
258
        }
259
 
260
        return new ChartColor($value, null, $type);
261
    }
262
 
263
    private function chartColorToString(ChartColor $chartColor): string
264
    {
265
        $type = (string) $chartColor->getColorProperty('type');
266
        $value = (string) $chartColor->getColorProperty('value');
267
        if ($type === '' || $value === '') {
268
            return '';
269
        }
270
        if ($type === 'schemeClr') {
271
            return "*$value";
272
        }
273
        if ($type === 'prstClr') {
274
            return "/$value";
275
        }
276
 
277
        return $value;
278
    }
279
 
280
    /**
281
     * Get fill color.
282
     *
283
     * @return string|string[] HEX color or array with HEX colors
284
     */
285
    public function getFillColor(): string|array
286
    {
287
        if ($this->fillColor === null) {
288
            return '';
289
        }
290
        if (is_array($this->fillColor)) {
291
            $array = [];
292
            foreach ($this->fillColor as $chartColor) {
293
                $array[] = $this->chartColorToString($chartColor);
294
            }
295
 
296
            return $array;
297
        }
298
 
299
        return $this->chartColorToString($this->fillColor);
300
    }
301
 
302
    /**
303
     * Set fill color for series.
304
     *
305
     * @param ChartColor|ChartColor[]|string|string[] $color HEX color or array with HEX colors
306
     *
307
     * @return   $this
308
     */
309
    public function setFillColor($color): static
310
    {
311
        if (is_array($color)) {
312
            $this->fillColor = [];
313
            foreach ($color as $fillString) {
314
                if ($fillString instanceof ChartColor) {
315
                    $this->fillColor[] = $fillString;
316
                } else {
317
                    $this->fillColor[] = $this->stringToChartColor($fillString);
318
                }
319
            }
320
        } elseif ($color instanceof ChartColor) {
321
            $this->fillColor = $color;
322
        } else {
323
            $this->fillColor = $this->stringToChartColor($color);
324
        }
325
 
326
        return $this;
327
    }
328
 
329
    /**
330
     * Method for validating hex color.
331
     *
332
     * @param string $color value for color
333
     *
334
     * @return bool true if validation was successful
335
     */
336
    private function validateColor(string $color): bool
337
    {
338
        if (!preg_match('/^[a-f0-9]{6}$/i', $color)) {
339
            throw new Exception(sprintf('Invalid hex color for chart series (color: "%s")', $color));
340
        }
341
 
342
        return true;
343
    }
344
 
345
    /**
346
     * Get line width for series.
347
     */
348
    public function getLineWidth(): null|float|int
349
    {
350
        return $this->lineStyleProperties['width'];
351
    }
352
 
353
    /**
354
     * Set line width for the series.
355
     *
356
     * @return $this
357
     */
358
    public function setLineWidth(null|float|int $width): static
359
    {
360
        $this->lineStyleProperties['width'] = $width;
361
 
362
        return $this;
363
    }
364
 
365
    /**
366
     * Identify if the Data Series is a multi-level or a simple series.
367
     */
368
    public function isMultiLevelSeries(): ?bool
369
    {
370
        if (!empty($this->dataValues)) {
371
            return is_array(array_values($this->dataValues)[0]);
372
        }
373
 
374
        return null;
375
    }
376
 
377
    /**
378
     * Return the level count of a multi-level Data Series.
379
     */
380
    public function multiLevelCount(): int
381
    {
382
        $levelCount = 0;
383
        foreach (($this->dataValues ?? []) as $dataValueSet) {
384
            $levelCount = max($levelCount, count($dataValueSet));
385
        }
386
 
387
        return $levelCount;
388
    }
389
 
390
    /**
391
     * Get Series Data Values.
392
     */
393
    public function getDataValues(): ?array
394
    {
395
        return $this->dataValues;
396
    }
397
 
398
    /**
399
     * Get the first Series Data value.
400
     */
401
    public function getDataValue(): mixed
402
    {
403
        if ($this->dataValues === null) {
404
            return null;
405
        }
406
        $count = count($this->dataValues);
407
        if ($count == 0) {
408
            return null;
409
        } elseif ($count == 1) {
410
            return $this->dataValues[0];
411
        }
412
 
413
        return $this->dataValues;
414
    }
415
 
416
    /**
417
     * Set Series Data Values.
418
     *
419
     * @return $this
420
     */
421
    public function setDataValues(array $dataValues): static
422
    {
423
        $this->dataValues = Functions::flattenArray($dataValues);
424
        $this->pointCount = count($dataValues);
425
 
426
        return $this;
427
    }
428
 
429
    public function refresh(Worksheet $worksheet, bool $flatten = true): void
430
    {
431
        if ($this->dataSource !== null) {
432
            $calcEngine = Calculation::getInstance($worksheet->getParent());
433
            $newDataValues = Calculation::unwrapResult(
434
                $calcEngine->_calculateFormulaValue(
435
                    '=' . $this->dataSource,
436
                    null,
437
                    $worksheet->getCell('A1')
438
                )
439
            );
440
            if ($flatten) {
441
                $this->dataValues = Functions::flattenArray($newDataValues);
442
                foreach ($this->dataValues as &$dataValue) {
443
                    if (is_string($dataValue) && !empty($dataValue) && $dataValue[0] == '#') {
444
                        $dataValue = 0.0;
445
                    }
446
                }
447
                unset($dataValue);
448
            } else {
449
                [, $cellRange] = Worksheet::extractSheetTitle($this->dataSource, true);
450
                $dimensions = Coordinate::rangeDimension(str_replace('$', '', $cellRange ?? ''));
451
                if (($dimensions[0] == 1) || ($dimensions[1] == 1)) {
452
                    $this->dataValues = Functions::flattenArray($newDataValues);
453
                } else {
454
                    /** @var array<int, array> */
455
                    $newDataValuesx = $newDataValues;
456
                    $newArray = array_values(array_shift($newDataValuesx) ?? []);
457
                    foreach ($newArray as $i => $newDataSet) {
458
                        $newArray[$i] = [$newDataSet];
459
                    }
460
 
461
                    foreach ($newDataValuesx as $newDataSet) {
462
                        $i = 0;
463
                        foreach ($newDataSet as $newDataVal) {
464
                            array_unshift($newArray[$i++], $newDataVal);
465
                        }
466
                    }
467
                    $this->dataValues = $newArray;
468
                }
469
            }
470
            $this->pointCount = count($this->dataValues);
471
        }
472
    }
473
 
474
    public function getScatterLines(): bool
475
    {
476
        return $this->scatterLines;
477
    }
478
 
479
    public function setScatterLines(bool $scatterLines): self
480
    {
481
        $this->scatterLines = $scatterLines;
482
 
483
        return $this;
484
    }
485
 
486
    public function getBubble3D(): bool
487
    {
488
        return $this->bubble3D;
489
    }
490
 
491
    public function setBubble3D(bool $bubble3D): self
492
    {
493
        $this->bubble3D = $bubble3D;
494
 
495
        return $this;
496
    }
497
 
498
    /**
499
     * Smooth Line. Must be specified for both DataSeries and DataSeriesValues.
500
     */
501
    private bool $smoothLine = false;
502
 
503
    /**
504
     * Get Smooth Line.
505
     */
506
    public function getSmoothLine(): bool
507
    {
508
        return $this->smoothLine;
509
    }
510
 
511
    /**
512
     * Set Smooth Line.
513
     *
514
     * @return $this
515
     */
516
    public function setSmoothLine(bool $smoothLine): static
517
    {
518
        $this->smoothLine = $smoothLine;
519
 
520
        return $this;
521
    }
522
 
523
    public function getLabelLayout(): ?Layout
524
    {
525
        return $this->labelLayout;
526
    }
527
 
528
    public function setLabelLayout(?Layout $labelLayout): self
529
    {
530
        $this->labelLayout = $labelLayout;
531
 
532
        return $this;
533
    }
534
 
535
    public function setTrendLines(array $trendLines): self
536
    {
537
        $this->trendLines = $trendLines;
538
 
539
        return $this;
540
    }
541
 
542
    public function getTrendLines(): array
543
    {
544
        return $this->trendLines;
545
    }
546
 
547
    /**
548
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
549
     */
550
    public function __clone()
551
    {
552
        parent::__clone();
553
        $this->markerFillColor = clone $this->markerFillColor;
554
        $this->markerBorderColor = clone $this->markerBorderColor;
555
        if (is_array($this->fillColor)) {
556
            $fillColor = $this->fillColor;
557
            $this->fillColor = [];
558
            foreach ($fillColor as $color) {
559
                $this->fillColor[] = clone $color;
560
            }
561
        } elseif ($this->fillColor instanceof ChartColor) {
562
            $this->fillColor = clone $this->fillColor;
563
        }
564
        $this->labelLayout = ($this->labelLayout === null) ? null : clone $this->labelLayout;
565
        $trendLines = $this->trendLines;
566
        $this->trendLines = [];
567
        foreach ($trendLines as $trendLine) {
568
            $this->trendLines[] = clone $trendLine;
569
        }
570
    }
571
}