From dafd78b4625466d40e7298f51b49153fe36016a5 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Sun, 19 Mar 2023 18:32:33 -0700 Subject: [PATCH] Support Border for Charts (#3462) * Support Border for Charts All chart linestyles will be supported for Chart Border. Also add fill color for Chart. Also 'nofill' for Axis (allows suppressing of vertical axis line). These are demonstrated in sample 32_readwriteChartLine5, and a new unit test member is added. This addresses some vague problems added to issue #1797 over 8 months after it was closed; there is still at least one problem, much more complicated than the 2 being addressed in this PR. * Handle Legend Borders in Same Way as Chart Borders Consistency is good, and the implementation of Chart Borders offers more possibilities than the implementation of Legend Borders had. Since Legend Borders hasn't made it to a release yet, there is no need for deprecations. --- samples/Chart/33_Chart_create_stock.php | 2 +- src/PhpSpreadsheet/Chart/Axis.php | 15 ++++++ src/PhpSpreadsheet/Chart/Chart.php | 25 +++++++++ src/PhpSpreadsheet/Chart/Legend.php | 23 +++++--- src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 36 ++++++++++--- src/PhpSpreadsheet/Writer/Xlsx/Chart.php | 35 ++++++------ .../Chart/ChartBorderTest.php | 54 +++++++++++++++++++ .../Chart/LegendColorTest.php | 4 +- 8 files changed, 160 insertions(+), 34 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Chart/ChartBorderTest.php diff --git a/samples/Chart/33_Chart_create_stock.php b/samples/Chart/33_Chart_create_stock.php index 386e360485..3195b5ace1 100644 --- a/samples/Chart/33_Chart_create_stock.php +++ b/samples/Chart/33_Chart_create_stock.php @@ -82,7 +82,7 @@ $plotArea = new PlotArea(null, [$series]); // Set the chart legend $legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false); -$legend->getBorderColor()->setColorProperties('ffc000'); +$legend->getBorderLines()->setLineColorProperties('ffc000', null, ChartColor::EXCEL_COLOR_TYPE_RGB); $legend->getFillColor()->setColorProperties('cccccc'); $legendText = new AxisText(); $legendText->getFillColorObject()->setValue('008080')->setType(ChartColor::EXCEL_COLOR_TYPE_RGB); diff --git a/src/PhpSpreadsheet/Chart/Axis.php b/src/PhpSpreadsheet/Chart/Axis.php index 91ff032a87..3d48134684 100644 --- a/src/PhpSpreadsheet/Chart/Axis.php +++ b/src/PhpSpreadsheet/Chart/Axis.php @@ -91,6 +91,9 @@ public function __construct() Properties::FORMAT_CODE_DATE_ISO8601, ]; + /** @var bool */ + private $noFill = false; + /** * Get Series Data Type. * @@ -318,4 +321,16 @@ public function setAxisText(?AxisText $axisText): self return $this; } + + public function setNoFill(bool $noFill): self + { + $this->noFill = $noFill; + + return $this; + } + + public function getNoFill(): bool + { + return $this->noFill; + } } diff --git a/src/PhpSpreadsheet/Chart/Chart.php b/src/PhpSpreadsheet/Chart/Chart.php index 2ff22a3440..38c69a4636 100644 --- a/src/PhpSpreadsheet/Chart/Chart.php +++ b/src/PhpSpreadsheet/Chart/Chart.php @@ -150,6 +150,12 @@ class Chart /** @var bool */ private $roundedCorners = false; + /** @var GridLines */ + private $borderLines; + + /** @var ChartColor */ + private $fillColor; + /** * Create a new Chart. * majorGridlines and minorGridlines are deprecated, moved to Axis. @@ -176,6 +182,8 @@ public function __construct($name, ?Title $title = null, ?Legend $legend = null, if ($minorGridlines !== null) { $this->yAxis->setMinorGridlines($minorGridlines); } + $this->fillColor = new ChartColor(); + $this->borderLines = new GridLines(); } /** @@ -786,4 +794,21 @@ public function setRoundedCorners(?bool $roundedCorners): self return $this; } + + public function getBorderLines(): GridLines + { + return $this->borderLines; + } + + public function setBorderLines(GridLines $borderLines): self + { + $this->borderLines = $borderLines; + + return $this; + } + + public function getFillColor(): ChartColor + { + return $this->fillColor; + } } diff --git a/src/PhpSpreadsheet/Chart/Legend.php b/src/PhpSpreadsheet/Chart/Legend.php index e1bc4dc9f7..04040aed6a 100644 --- a/src/PhpSpreadsheet/Chart/Legend.php +++ b/src/PhpSpreadsheet/Chart/Legend.php @@ -48,8 +48,8 @@ class Legend */ private $layout; - /** @var ChartColor */ - private $borderColor; + /** @var GridLines */ + private $borderLines; /** @var ChartColor */ private $fillColor; @@ -69,15 +69,10 @@ public function __construct($position = self::POSITION_RIGHT, ?Layout $layout = $this->setPosition($position); $this->layout = $layout; $this->setOverlay($overlay); - $this->borderColor = new ChartColor(); + $this->borderLines = new GridLines(); $this->fillColor = new ChartColor(); } - public function getBorderColor(): ChartColor - { - return $this->borderColor; - } - public function getFillColor(): ChartColor { return $this->fillColor; @@ -181,4 +176,16 @@ public function setLegendText(?AxisText $legendText): self return $this; } + + public function getBorderLines(): GridLines + { + return $this->borderLines; + } + + public function setBorderLines(GridLines $borderLines): self + { + $this->borderLines = $borderLines; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index b488c9569c..5859ed1077 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -77,16 +77,25 @@ public function readChart(SimpleXMLElement $chartElements, $chartName) $yAxis = new Axis(); $autoTitleDeleted = null; $chartNoFill = false; + $chartBorderLines = null; + $chartFillColor = null; $gradientArray = []; $gradientLin = null; $roundedCorners = false; foreach ($chartElementsC as $chartElementKey => $chartElement) { switch ($chartElementKey) { case 'spPr': - $possibleNoFill = $chartElementsC->spPr->children($this->aNamespace); - if (isset($possibleNoFill->noFill)) { + $children = $chartElementsC->spPr->children($this->aNamespace); + if (isset($children->noFill)) { $chartNoFill = true; } + if (isset($children->solidFill)) { + $chartFillColor = $this->readColor($children->solidFill); + } + if (isset($children->ln)) { + $chartBorderLines = new GridLines(); + $this->readLineStyle($chartElementsC, $chartBorderLines); + } break; case 'roundedCorners': @@ -158,6 +167,9 @@ public function readChart(SimpleXMLElement $chartElements, $chartName) $axisColorArray = $this->readColor($sppr->solidFill); $xAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']); } + if (isset($chartDetail->spPr->ln->noFill)) { + $xAxis->setNoFill(true); + } } if (isset($chartDetail->majorGridlines)) { $majorGridlines = new GridLines(); @@ -228,6 +240,9 @@ public function readChart(SimpleXMLElement $chartElements, $chartName) $axisColorArray = $this->readColor($sppr->solidFill); $whichAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']); } + if (isset($sppr->ln->noFill)) { + $whichAxis->setNoFill(true); + } } if ($whichAxis !== null && isset($chartDetail->majorGridlines)) { $majorGridlines = new GridLines(); @@ -351,7 +366,7 @@ public function readChart(SimpleXMLElement $chartElements, $chartName) $legendPos = 'r'; $legendLayout = null; $legendOverlay = false; - $legendBorderColor = null; + $legendBorderLines = null; $legendFillColor = null; $legendText = null; $addLegendText = false; @@ -375,8 +390,9 @@ public function readChart(SimpleXMLElement $chartElements, $chartName) if (isset($children->solidFill)) { $legendFillColor = $this->readColor($children->solidFill); } - if (isset($children->ln->solidFill)) { - $legendBorderColor = $this->readColor($children->ln->solidFill); + if (isset($children->ln)) { + $legendBorderLines = new GridLines(); + $this->readLineStyle($chartDetails, $legendBorderLines); } break; @@ -401,8 +417,8 @@ public function readChart(SimpleXMLElement $chartElements, $chartName) if ($legendFillColor !== null) { $legend->getFillColor()->setColorPropertiesArray($legendFillColor); } - if ($legendBorderColor !== null) { - $legend->getBorderColor()->setColorPropertiesArray($legendBorderColor); + if ($legendBorderLines !== null) { + $legend->setBorderLines($legendBorderLines); } if ($addLegendText) { $legend->setLegendText($legendText); @@ -417,6 +433,12 @@ public function readChart(SimpleXMLElement $chartElements, $chartName) if ($chartNoFill) { $chart->setNoFill(true); } + if ($chartFillColor !== null) { + $chart->getFillColor()->setColorPropertiesArray($chartFillColor); + } + if ($chartBorderLines !== null) { + $chart->setBorderLines($chartBorderLines); + } $chart->setRoundedCorners($roundedCorners); if (is_bool($autoTitleDeleted)) { $chart->setAutoTitleDeleted($autoTitleDeleted); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index a4fc68eacc..bea6f49167 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -109,12 +109,20 @@ public function writeChart(\PhpOffice\PhpSpreadsheet\Chart\Chart $chart, $calcul $objWriter->endElement(); $objWriter->endElement(); // c:chart + + $objWriter->startElement('c:spPr'); if ($chart->getNoFill()) { - $objWriter->startElement('c:spPr'); $objWriter->startElement('a:noFill'); $objWriter->endElement(); // a:noFill - $objWriter->endElement(); // c:spPr } + $fillColor = $chart->getFillColor(); + if ($fillColor->isUsable()) { + $this->writeColor($objWriter, $fillColor); + } + $borderLines = $chart->getBorderLines(); + $this->writeLineStyles($objWriter, $borderLines); + $this->writeEffects($objWriter, $borderLines); + $objWriter->endElement(); // c:spPr $this->writePrintSettings($objWriter); @@ -201,20 +209,15 @@ private function writeLegend(XMLWriter $objWriter, ?Legend $legend = null): void $objWriter->writeAttribute('val', ($legend->getOverlay()) ? '1' : '0'); $objWriter->endElement(); + $objWriter->startElement('c:spPr'); $fillColor = $legend->getFillColor(); - $borderColor = $legend->getBorderColor(); - if ($fillColor->isUsable() || $borderColor->isUsable()) { - $objWriter->startElement('c:spPr'); - if ($fillColor->isUsable()) { - $this->writeColor($objWriter, $fillColor); - } - if ($borderColor->isUsable()) { - $objWriter->startElement('a:ln'); - $this->writeColor($objWriter, $borderColor); - $objWriter->endElement(); // a:ln - } - $objWriter->endElement(); // c:spPr + if ($fillColor->isUsable()) { + $this->writeColor($objWriter, $fillColor); } + $borderLines = $legend->getBorderLines(); + $this->writeLineStyles($objWriter, $borderLines); + $this->writeEffects($objWriter, $borderLines); + $objWriter->endElement(); // c:spPr $legendText = $legend->getLegendText(); $objWriter->startElement('c:txPr'); @@ -654,7 +657,7 @@ private function writeCategoryAxis(XMLWriter $objWriter, ?Title $xAxisLabel, $id $objWriter->startElement('c:spPr'); $this->writeColor($objWriter, $yAxis->getFillColorObject()); - $this->writeLineStyles($objWriter, $yAxis); + $this->writeLineStyles($objWriter, $yAxis, $yAxis->getNoFill()); $this->writeEffects($objWriter, $yAxis); $objWriter->endElement(); // spPr @@ -880,7 +883,7 @@ private function writeValueAxis(XMLWriter $objWriter, ?Title $yAxisLabel, $group $objWriter->startElement('c:spPr'); $this->writeColor($objWriter, $xAxis->getFillColorObject()); - $this->writeLineStyles($objWriter, $xAxis); + $this->writeLineStyles($objWriter, $xAxis, $xAxis->getNoFill()); $this->writeEffects($objWriter, $xAxis); $objWriter->endElement(); //end spPr diff --git a/tests/PhpSpreadsheetTests/Chart/ChartBorderTest.php b/tests/PhpSpreadsheetTests/Chart/ChartBorderTest.php new file mode 100644 index 0000000000..f5efc09f9a --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/ChartBorderTest.php @@ -0,0 +1,54 @@ +setIncludeCharts(true); + } + + public function writeCharts(XlsxWriter $writer): void + { + $writer->setIncludeCharts(true); + } + + public function testChartBorder(): void + { + $file = self::DIRECTORY . '32readwriteLineChart5.xlsx'; + $reader = new XlsxReader(); + $reader->setIncludeCharts(true); + $spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame(1, $sheet->getChartCount()); + /** @var callable */ + $callableReader = [$this, 'readCharts']; + /** @var callable */ + $callableWriter = [$this, 'writeCharts']; + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $sheet = $reloadedSpreadsheet->getActiveSheet(); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); + self::assertSame('ffffff', $chart->getFillColor()->getValue()); + self::assertSame('srgbClr', $chart->getFillColor()->getType()); + self::assertSame('d9d9d9', $chart->getBorderLines()->getLineColorProperty('value')); + self::assertSame('srgbClr', $chart->getBorderLines()->getLineColorProperty('type')); + self::assertEqualsWithDelta(9360 / Properties::POINTS_WIDTH_MULTIPLIER, $chart->getBorderLines()->getLineStyleProperty('width'), 1.0E-8); + self::assertTrue($chart->getChartAxisY()->getNoFill()); + self::assertFalse($chart->getChartAxisX()->getNoFill()); + + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Chart/LegendColorTest.php b/tests/PhpSpreadsheetTests/Chart/LegendColorTest.php index 4d4e86b731..1bf5cb32e0 100644 --- a/tests/PhpSpreadsheetTests/Chart/LegendColorTest.php +++ b/tests/PhpSpreadsheetTests/Chart/LegendColorTest.php @@ -99,7 +99,7 @@ public function testLegend(): void $plotArea = new PlotArea(null, [$series]); // Set the chart legend $legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false); - $legend->getBorderColor()->setColorProperties('ffc000'); + $legend->getBorderLines()->setLineColorProperties('ffc000', null, ChartColor::EXCEL_COLOR_TYPE_RGB); $legend->getFillColor()->setColorProperties('cccccc'); $legendText = new AxisText(); $legendText->getFillColorObject()->setValue('008080')->setType(ChartColor::EXCEL_COLOR_TYPE_RGB); @@ -146,7 +146,7 @@ public function testLegend(): void if ($legend2 === null) { self::fail('Unexpected null legend'); } else { - self::assertSame('ffc000', $legend2->getBorderColor()->getValue()); + self::assertSame('ffc000', $legend2->getBorderLines()->getLineColorProperty('value')); self::assertSame('cccccc', $legend2->getFillColor()->getValue()); $legendText2 = $legend2->getLegendText(); if ($legendText2 === null) {