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

PHPOffice / PhpSpreadsheet / 23217488242

17 Mar 2026 09:35PM UTC coverage: 96.904%. Remained the same
23217488242

Pull #4839

github

web-flow
Merge fb096885d into a1dacfdf7
Pull Request #4839: Some Problems with Conditional Colorscale for Html

15 of 17 new or added lines in 1 file covered. (88.24%)

1 existing line in 1 file now uncovered.

47697 of 49221 relevant lines covered (96.9%)

384.11 hits per line

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

81.68
/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalColorScale.php
1
<?php
2

3
namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting;
4

5
use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Percentiles;
6
use PhpOffice\PhpSpreadsheet\Style\Color;
7

8
class ConditionalColorScale
9
{
10
    private ?ConditionalFormatValueObject $minimumConditionalFormatValueObject = null;
11

12
    private ?ConditionalFormatValueObject $midpointConditionalFormatValueObject = null;
13

14
    private ?ConditionalFormatValueObject $maximumConditionalFormatValueObject = null;
15

16
    private ?Color $minimumColor = null;
17

18
    private ?Color $midpointColor = null;
19

20
    private ?Color $maximumColor = null;
21

22
    private ?string $sqref = null;
23

24
    /** @var mixed[] */
25
    private array $valueArray = [];
26

27
    private float $minValue = 0;
28

29
    private float $maxValue = 0;
30

31
    private float $midValue = 0;
32

33
    private ?\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet = null;
34

35
    public function getMinimumConditionalFormatValueObject(): ?ConditionalFormatValueObject
3✔
36
    {
37
        return $this->minimumConditionalFormatValueObject;
3✔
38
    }
39

40
    public function setMinimumConditionalFormatValueObject(ConditionalFormatValueObject $minimumConditionalFormatValueObject): self
9✔
41
    {
42
        $this->minimumConditionalFormatValueObject = $minimumConditionalFormatValueObject;
9✔
43

44
        return $this;
9✔
45
    }
46

47
    public function getMidpointConditionalFormatValueObject(): ?ConditionalFormatValueObject
3✔
48
    {
49
        return $this->midpointConditionalFormatValueObject;
3✔
50
    }
51

52
    public function setMidpointConditionalFormatValueObject(ConditionalFormatValueObject $midpointConditionalFormatValueObject): self
8✔
53
    {
54
        $this->midpointConditionalFormatValueObject = $midpointConditionalFormatValueObject;
8✔
55

56
        return $this;
8✔
57
    }
58

59
    public function getMaximumConditionalFormatValueObject(): ?ConditionalFormatValueObject
3✔
60
    {
61
        return $this->maximumConditionalFormatValueObject;
3✔
62
    }
63

64
    public function setMaximumConditionalFormatValueObject(ConditionalFormatValueObject $maximumConditionalFormatValueObject): self
9✔
65
    {
66
        $this->maximumConditionalFormatValueObject = $maximumConditionalFormatValueObject;
9✔
67

68
        return $this;
9✔
69
    }
70

71
    public function getMinimumColor(): ?Color
3✔
72
    {
73
        return $this->minimumColor;
3✔
74
    }
75

76
    public function setMinimumColor(Color $minimumColor): self
9✔
77
    {
78
        $this->minimumColor = $minimumColor;
9✔
79

80
        return $this;
9✔
81
    }
82

83
    public function getMidpointColor(): ?Color
3✔
84
    {
85
        return $this->midpointColor;
3✔
86
    }
87

88
    public function setMidpointColor(Color $midpointColor): self
8✔
89
    {
90
        $this->midpointColor = $midpointColor;
8✔
91

92
        return $this;
8✔
93
    }
94

95
    public function getMaximumColor(): ?Color
3✔
96
    {
97
        return $this->maximumColor;
3✔
98
    }
99

100
    public function setMaximumColor(Color $maximumColor): self
9✔
101
    {
102
        $this->maximumColor = $maximumColor;
9✔
103

104
        return $this;
9✔
105
    }
106

107
    public function getSqRef(): ?string
×
108
    {
109
        return $this->sqref;
×
110
    }
111

112
    public function setSqRef(string $sqref, \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet): self
8✔
113
    {
114
        $this->sqref = $sqref;
8✔
115
        $this->worksheet = $worksheet;
8✔
116

117
        return $this;
8✔
118
    }
119

120
    public function setScaleArray(): self
5✔
121
    {
122
        if ($this->sqref !== null && $this->worksheet !== null) {
5✔
123
            $values = $this->worksheet->rangesToArray($this->sqref, null, true, false, true);
5✔
124
            $this->valueArray = [];
5✔
125
            foreach ($values as $key => $value) {
5✔
126
                /** @var array<float|int|string> $value */
127
                foreach ($value as $k => $v) {
5✔
128
                    $this->valueArray[] = (float) $v;
5✔
129
                }
130
            }
131
            $this->prepareColorScale();
5✔
132
        }
133

134
        return $this;
5✔
135
    }
136

137
    public function getColorForValue(float $value): string
5✔
138
    {
139
        if ($this->minimumColor === null || $this->midpointColor === null || $this->maximumColor === null) {
5✔
140
            return 'FF000000';
×
141
        }
142
        $minColor = $this->minimumColor->getARGB();
5✔
143
        $midColor = $this->midpointColor->getARGB();
5✔
144
        $maxColor = $this->maximumColor->getARGB();
5✔
145

146
        if ($minColor === null || $midColor === null || $maxColor === null) {
5✔
147
            return 'FF000000';
×
148
        }
149

150
        if ($value <= $this->minValue) {
5✔
151
            return $minColor;
5✔
152
        }
153
        if ($value >= $this->maxValue) {
5✔
154
            return $maxColor;
5✔
155
        }
156
        if ($value == $this->midValue) {
5✔
157
            return $midColor;
4✔
158
        }
159
        if ($value < $this->midValue) {
5✔
160
            $blend = ($value - $this->minValue) / ($this->midValue - $this->minValue);
5✔
161
            $alpha1 = hexdec(substr($minColor, 0, 2));
5✔
162
            $alpha2 = hexdec(substr($midColor, 0, 2));
5✔
163
            $red1 = hexdec(substr($minColor, 2, 2));
5✔
164
            $red2 = hexdec(substr($midColor, 2, 2));
5✔
165
            $green1 = hexdec(substr($minColor, 4, 2));
5✔
166
            $green2 = hexdec(substr($midColor, 4, 2));
5✔
167
            $blue1 = hexdec(substr($minColor, 6, 2));
5✔
168
            $blue2 = hexdec(substr($midColor, 6, 2));
5✔
169
        } else {
170
            $blend = ($value - $this->midValue) / ($this->maxValue - $this->midValue);
5✔
171
            $alpha1 = hexdec(substr($midColor, 0, 2));
5✔
172
            $alpha2 = hexdec(substr($maxColor, 0, 2));
5✔
173
            $red1 = hexdec(substr($midColor, 2, 2));
5✔
174
            $red2 = hexdec(substr($maxColor, 2, 2));
5✔
175
            $green1 = hexdec(substr($midColor, 4, 2));
5✔
176
            $green2 = hexdec(substr($maxColor, 4, 2));
5✔
177
            $blue1 = hexdec(substr($midColor, 6, 2));
5✔
178
            $blue2 = hexdec(substr($maxColor, 6, 2));
5✔
179
        }
180
        $alpha = (int) ($alpha2 * $blend + $alpha1 * (1 - $blend));
5✔
181
        $red = (int) ($red2 * $blend + $red1 * (1 - $blend));
5✔
182
        $green = (int) ($green2 * $blend + $green1 * (1 - $blend));
5✔
183
        $blue = (int) ($blue2 * $blend + $blue1 * (1 - $blend));
5✔
184

185
        return sprintf('%02X%02X%02X%02X', $alpha, $red, $green, $blue);
5✔
186
    }
187

188
    private function getLimitValue(string $type, float $value = 0, float $formula = 0): float
5✔
189
    {
190
        if (count($this->valueArray) === 0) {
5✔
191
            return 0;
×
192
        }
193
        switch ($type) {
194
            case 'min':
5✔
195
                /** @var float|int */
196
                $temp = min($this->valueArray);
5✔
197

198
                return (float) $temp;
5✔
199
            case 'max':
5✔
200
                /** @var float|int */
201
                $temp = max($this->valueArray);
5✔
202

203
                return (float) $temp;
5✔
204
            case 'percentile':
5✔
205
                return (float) Percentiles::PERCENTILE($this->valueArray, (float) ($value / 100));
5✔
206
            case 'formula':
2✔
207
                return $formula;
2✔
208
            case 'percent':
2✔
209
                /** @var float|int */
210
                $min = min($this->valueArray);
2✔
211
                $min = (float) $min;
2✔
212
                /** @var float|int */
213
                $max = max($this->valueArray);
2✔
214
                $max = (float) $max;
2✔
215

216
                return $min + (float) ($value / 100) * ($max - $min);
2✔
217
            default:
218
                return 0;
×
219
        }
220
    }
221

222
    /**
223
     * Prepares color scale for execution, see the first if for variables that must be set beforehand.
224
     */
225
    public function prepareColorScale(): self
5✔
226
    {
227
        if ($this->minimumConditionalFormatValueObject !== null && $this->maximumConditionalFormatValueObject !== null && $this->minimumColor !== null && $this->maximumColor !== null) {
5✔
228
            if ($this->midpointConditionalFormatValueObject !== null && $this->midpointConditionalFormatValueObject->getType() !== 'None') {
5✔
229
                $this->minValue = $this->getLimitValue($this->minimumConditionalFormatValueObject->getType(), (float) $this->minimumConditionalFormatValueObject->getValue(), (float) $this->minimumConditionalFormatValueObject->getCellFormula());
5✔
230
                $this->midValue = $this->getLimitValue($this->midpointConditionalFormatValueObject->getType(), (float) $this->midpointConditionalFormatValueObject->getValue(), (float) $this->midpointConditionalFormatValueObject->getCellFormula());
5✔
231
                $this->maxValue = $this->getLimitValue($this->maximumConditionalFormatValueObject->getType(), (float) $this->maximumConditionalFormatValueObject->getValue(), (float) $this->maximumConditionalFormatValueObject->getCellFormula());
5✔
232
            } else {
233
                $this->minValue = $this->getLimitValue($this->minimumConditionalFormatValueObject->getType(), (float) $this->minimumConditionalFormatValueObject->getValue(), (float) $this->minimumConditionalFormatValueObject->getCellFormula());
×
234
                $this->maxValue = $this->getLimitValue($this->maximumConditionalFormatValueObject->getType(), (float) $this->maximumConditionalFormatValueObject->getValue(), (float) $this->maximumConditionalFormatValueObject->getCellFormula());
×
235
                $this->midValue = (float) ($this->minValue + $this->maxValue) / 2;
×
236
                $blend = 0.5;
×
237

238
                $minColor = $this->minimumColor->getARGB();
×
239
                $maxColor = $this->maximumColor->getARGB();
×
240

241
                if ($minColor !== null && $maxColor !== null) {
×
242
                    $alpha1 = hexdec(substr($minColor, 0, 2));
×
243
                    $alpha2 = hexdec(substr($maxColor, 0, 2));
×
244
                    $red1 = hexdec(substr($minColor, 2, 2));
×
245
                    $red2 = hexdec(substr($maxColor, 2, 2));
×
246
                    $green1 = hexdec(substr($minColor, 4, 2));
×
247
                    $green2 = hexdec(substr($maxColor, 4, 2));
×
248
                    $blue1 = hexdec(substr($minColor, 6, 2));
×
249
                    $blue2 = hexdec(substr($maxColor, 6, 2));
×
NEW
250
                    $this->midpointColor = new Color(strtoupper(dechex((int) ($alpha2 * $blend + $alpha1 * (1 - $blend))) . '' . dechex((int) ($red2 * $blend + $red1 * (1 - $blend))) . '' . dechex((int) ($green2 * $blend + $green1 * (1 - $blend))) . '' . dechex((int) ($blue2 * $blend + $blue1 * (1 - $blend)))));
×
251
                } else {
NEW
252
                    $this->midpointColor = null;
×
253
                }
254
            }
255
        }
256

257
        return $this;
5✔
258
    }
259

260
    /**
261
     * Checks that all needed color scale data is in place.
262
     */
263
    public function colorScaleReadyForUse(): bool
5✔
264
    {
265
        if ($this->minimumColor === null || $this->midpointColor === null || $this->maximumColor === null) {
5✔
UNCOV
266
            return false;
×
267
        }
268

269
        return true;
5✔
270
    }
271
}
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

© 2026 Coveralls, Inc