Some Problems with Conditional Colorscale for Html

Fix #4838. Some processing of Html Conditional Formatting which worked with PhpSpreadsheet 5.3 does not work with 5.4. There are 2 problems. The first was caused by PR 4763, which moved some code out of Html Writer to an easier-to-access location. One of the intermediate variables in Html Writer was not available in the new location; its simulation turned out to be imperfect.

When evaluating Colorscale, the cells in question need to be ranked. This was done using `rangesToArray` with the formatting option set to `true`. This code, inherited from 5.3, was a mistake; it should have been set to `false`. As coded, it created problems for cells formatted as percentages (it is not clear why 5.3 did not suffer a similar fate). Making that change fixes the problem. The fix is demonstrated in the first new test `testPercentages`.

A second problem affected 5.3. The code to assign a colorscale color to a cell concatenated the alpha, red, green, and blue colors expressed in hex digits. But it did not properly handle the case when any of those was only 1 digit long. The second new test `testPercentages2` demonstrates that this problem is now fixed.
This commit is contained in:
oleibman
2026-03-17 14:33:45 -07:00
parent a1dacfdf79
commit fb096885d8
2 changed files with 115 additions and 14 deletions
@@ -120,7 +120,7 @@ class ConditionalColorScale
public function setScaleArray(): self
{
if ($this->sqref !== null && $this->worksheet !== null) {
$values = $this->worksheet->rangesToArray($this->sqref, null, true, true, true);
$values = $this->worksheet->rangesToArray($this->sqref, null, true, false, true);
$this->valueArray = [];
foreach ($values as $key => $value) {
/** @var array<float|int|string> $value */
@@ -166,20 +166,23 @@ class ConditionalColorScale
$green2 = hexdec(substr($midColor, 4, 2));
$blue1 = hexdec(substr($minColor, 6, 2));
$blue2 = hexdec(substr($midColor, 6, 2));
return 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))));
} else {
$blend = ($value - $this->midValue) / ($this->maxValue - $this->midValue);
$alpha1 = hexdec(substr($midColor, 0, 2));
$alpha2 = hexdec(substr($maxColor, 0, 2));
$red1 = hexdec(substr($midColor, 2, 2));
$red2 = hexdec(substr($maxColor, 2, 2));
$green1 = hexdec(substr($midColor, 4, 2));
$green2 = hexdec(substr($maxColor, 4, 2));
$blue1 = hexdec(substr($midColor, 6, 2));
$blue2 = hexdec(substr($maxColor, 6, 2));
}
$blend = ($value - $this->midValue) / ($this->maxValue - $this->midValue);
$alpha1 = hexdec(substr($midColor, 0, 2));
$alpha2 = hexdec(substr($maxColor, 0, 2));
$red1 = hexdec(substr($midColor, 2, 2));
$red2 = hexdec(substr($maxColor, 2, 2));
$green1 = hexdec(substr($midColor, 4, 2));
$green2 = hexdec(substr($maxColor, 4, 2));
$blue1 = hexdec(substr($midColor, 6, 2));
$blue2 = hexdec(substr($maxColor, 6, 2));
$alpha = (int) ($alpha2 * $blend + $alpha1 * (1 - $blend));
$red = (int) ($red2 * $blend + $red1 * (1 - $blend));
$green = (int) ($green2 * $blend + $green1 * (1 - $blend));
$blue = (int) ($blue2 * $blend + $blue1 * (1 - $blend));
return 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))));
return sprintf('%02X%02X%02X%02X', $alpha, $red, $green, $blue);
}
private function getLimitValue(string $type, float $value = 0, float $formula = 0): float
@@ -6,6 +6,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Writer\Html;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalColorScale;
use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject;
use PhpOffice\PhpSpreadsheet\Writer\Html as HtmlWriter;
use PHPUnit\Framework\TestCase;
@@ -13,7 +18,7 @@ class HtmlConditionalFormattingTest extends TestCase
{
private string $data = '';
protected function setUp(): void
private function populateData(): void
{
$file = 'samples/templates/BasicConditionalFormatting.xlsx';
$reader = new XlsxReader();
@@ -46,6 +51,7 @@ class HtmlConditionalFormattingTest extends TestCase
public function testConditionalFormattingHtmLOutput(): void
{
$this->populateData();
$expectedMatches = [
['B1', 'class="column1 style1 s">Jan<', 'no conditional styling for B1'],
['F2', 'background-color:#C6EFCE;">120<', 'conditional style for F2'],
@@ -62,4 +68,96 @@ class HtmlConditionalFormattingTest extends TestCase
self::assertStringContainsString($expectedString, $string, $message);
}
}
public function testPercentages(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->fromArray([
[0.0014676309754806],
[0.0058290134726582],
[0.00031478256190473],
[0.0075923486102745],
[0.0041865730298472],
]);
$sheet->getStyle('A1:A5')
->getNumberFormat()
->setFormatCode('0.00%');
$conditionals = [];
$conditional1 = new Conditional();
$colorscale = new ConditionalColorScale();
$min = new ConditionalFormatValueObject('min');
$max = new ConditionalFormatValueObject('max');
$mid = new ConditionalFormatValueObject('percentile', 50);
$colorscale
->setSqref('A1:A5', $sheet)
->setMinimumColor(new Color('83CCEB'))
->setMidpointColor(new Color('AEAEAE'))
->setMaximumColor(new Color('FF6CAD'))
->setMinimumConditionalFormatValueObject($min)
->setMidpointConditionalFormatValueObject($mid)
->setMaximumConditionalFormatValueObject($max)
->setScaleArray();
$conditional1->setColorScale($colorscale)
->setConditionType(Conditional::CONDITION_COLORSCALE);
$conditionals = [$conditional1];
$sheet->getStyle('$A$1:$A$5')
->setConditionalStyles($conditionals);
$writer = new HtmlWriter($spreadsheet);
$writer->setConditionalFormatting(true);
$html = $writer->generateHtmlAll();
self::assertStringContainsString('<td class="column0 style1 n" style="color:#000000;background-color:#8FC3D8;">0.15%</td>', $html);
self::assertStringContainsString('<td class="column0 style1 n" style="color:#000000;background-color:#D58EAD;">0.58%</td>', $html);
self::assertStringContainsString('<td class="column0 style1 n" style="color:#000000;background-color:#83CCEB;">0.03%</td>', $html);
self::assertStringContainsString('<td class="column0 style1 n" style="color:#000000;background-color:#FF6CAD;">0.76%</td>', $html);
self::assertStringContainsString('<td class="column0 style1 n" style="color:#000000;background-color:#AEAEAE;">0.42%</td>', $html);
$spreadsheet->disconnectWorksheets();
}
public function testPercentages2(): void
{
// fill color was being returned incorrectly
// because some of the colors required leading 0.
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->fromArray([
[0.0014676309754806],
[0.0058290134726582],
[0.00031478256190473],
[0.0075923486102745],
[0.0041865730298472],
]);
$sheet->getStyle('A1:A5')
->getNumberFormat()
->setFormatCode('0.00%');
$conditionals = [];
$conditional1 = new Conditional();
$colorscale = new ConditionalColorScale();
$min = new ConditionalFormatValueObject('min');
$max = new ConditionalFormatValueObject('max');
$mid = new ConditionalFormatValueObject('percentile', 50);
$colorscale
->setSqref('A1:A5', $sheet)
->setMinimumColor(new Color('FF0000'))
->setMidpointColor(new Color('00FF00'))
->setMaximumColor(new Color('0000FF'))
->setMinimumConditionalFormatValueObject($min)
->setMidpointConditionalFormatValueObject($mid)
->setMaximumConditionalFormatValueObject($max)
->setScaleArray();
$conditional1->setColorScale($colorscale)
->setConditionType(Conditional::CONDITION_COLORSCALE);
$conditionals = [$conditional1];
$sheet->getStyle('$A$1:$A$5')
->setConditionalStyles($conditionals);
$writer = new HtmlWriter($spreadsheet);
$writer->setConditionalFormatting(true);
$html = $writer->generateHtmlAll();
self::assertStringContainsString('<td class="column0 style1 n" style="color:#000000;background-color:#B34B00;">0.15%</td>', $html);
self::assertStringContainsString('<td class="column0 style1 n" style="color:#000000;background-color:#00847A;">0.58%</td>', $html);
self::assertStringContainsString('<td class="column0 style1 n" style="color:#000000;background-color:#FF0000;">0.03%</td>', $html);
self::assertStringContainsString('<td class="column0 style1 n" style="color:#000000;background-color:#0000FF;">0.76%</td>', $html);
self::assertStringContainsString('<td class="column0 style1 n" style="color:#000000;background-color:#00FF00;">0.42%</td>', $html);
$spreadsheet->disconnectWorksheets();
}
}