From 04f46676584abcc144a441aa516a8e2a2035eb1b Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Tue, 14 Jun 2022 08:45:12 -0700 Subject: [PATCH] Expand Chart Support for schemeClr and prstClr (#2879) * Expand Chart Support for schemeClr and prstClr Fix #2219. Address some, but not all, issues mentioned in #2863. For Pie Charts, fill color is stored in XML as part of dPt node, which had been ignored by Reader/Xlsx/Chart. Add support for it, including when specified as schemeClr or prstClr rather than srgbClr. Add support for prstClr in other cases where schemeClr is supported. * Update Change Log Add this PR. --- CHANGELOG.md | 2 +- samples/templates/32readwriteAreaChart4.xlsx | Bin 0 -> 12474 bytes src/PhpSpreadsheet/Chart/DataSeriesValues.php | 25 ++- src/PhpSpreadsheet/Chart/Properties.php | 5 + src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 90 ++++++---- src/PhpSpreadsheet/Writer/Xlsx/Chart.php | 34 +++- .../Chart/Charts32XmlTest.php | 95 ++++++----- .../PhpSpreadsheetTests/Chart/PieFillTest.php | 160 ++++++++++++++++++ 8 files changed, 325 insertions(+), 86 deletions(-) create mode 100644 samples/templates/32readwriteAreaChart4.xlsx create mode 100644 tests/PhpSpreadsheetTests/Chart/PieFillTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 91a12675..4664f049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Time interval formatting [Issue #2768](https://github.com/PHPOffice/PhpSpreadsheet/issues/2768) [PR #2772](https://github.com/PHPOffice/PhpSpreadsheet/pull/2772) - Copy from Xls(x) to Html/Pdf loses drawings [PR #2788](https://github.com/PHPOffice/PhpSpreadsheet/pull/2788) - Html Reader converting cell containing 0 to null string [Issue #2810](https://github.com/PHPOffice/PhpSpreadsheet/issues/2810) [PR #2813](https://github.com/PHPOffice/PhpSpreadsheet/pull/2813) -- Many fixes for Charts, especially, but not limited to, Scatter, Bubble, and Surface charts. [Issue #2762](https://github.com/PHPOffice/PhpSpreadsheet/issues/2762) [Issue #2299](https://github.com/PHPOffice/PhpSpreadsheet/issues/2299) [Issue #2700](https://github.com/PHPOffice/PhpSpreadsheet/issues/2700) [Issue #2817](https://github.com/PHPOffice/PhpSpreadsheet/issues/2817) [Issue #2763](https://github.com/PHPOffice/PhpSpreadsheet/issues/2763) [PR #2828](https://github.com/PHPOffice/PhpSpreadsheet/pull/2828) [PR #2841](https://github.com/PHPOffice/PhpSpreadsheet/pull/2841) [PR #2846](https://github.com/PHPOffice/PhpSpreadsheet/pull/2846) [PR #2852](https://github.com/PHPOffice/PhpSpreadsheet/pull/2852) +- Many fixes for Charts, especially, but not limited to, Scatter, Bubble, and Surface charts. [Issue #2762](https://github.com/PHPOffice/PhpSpreadsheet/issues/2762) [Issue #2299](https://github.com/PHPOffice/PhpSpreadsheet/issues/2299) [Issue #2700](https://github.com/PHPOffice/PhpSpreadsheet/issues/2700) [Issue #2817](https://github.com/PHPOffice/PhpSpreadsheet/issues/2817) [Issue #2763](https://github.com/PHPOffice/PhpSpreadsheet/issues/2763) [Issue #2219](https://github.com/PHPOffice/PhpSpreadsheet/issues/2219) [PR #2828](https://github.com/PHPOffice/PhpSpreadsheet/pull/2828) [PR #2841](https://github.com/PHPOffice/PhpSpreadsheet/pull/2841) [PR #2846](https://github.com/PHPOffice/PhpSpreadsheet/pull/2846) [PR #2852](https://github.com/PHPOffice/PhpSpreadsheet/pull/2852) [PR #2856](https://github.com/PHPOffice/PhpSpreadsheet/pull/2856) [PR #2865](https://github.com/PHPOffice/PhpSpreadsheet/pull/2865) [PR #2872](https://github.com/PHPOffice/PhpSpreadsheet/pull/2872) [PR #2879](https://github.com/PHPOffice/PhpSpreadsheet/pull/2879) ## 1.23.0 - 2022-04-24 diff --git a/samples/templates/32readwriteAreaChart4.xlsx b/samples/templates/32readwriteAreaChart4.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..4548bf2e0dec273a37ceaf11b27e1a914c8978c5 GIT binary patch literal 12474 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 U5@^0Zw_jus05G`}qyIVjKiJn^X8-^I literal 0 HcmV?d00001 diff --git a/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/src/PhpSpreadsheet/Chart/DataSeriesValues.php index 6747934a..0a2f5a85 100644 --- a/src/PhpSpreadsheet/Chart/DataSeriesValues.php +++ b/src/PhpSpreadsheet/Chart/DataSeriesValues.php @@ -76,6 +76,9 @@ class DataSeriesValues /** @var string */ private $schemeClr = ''; + /** @var string */ + private $prstClr = ''; + /** * Line Width. * @@ -262,7 +265,7 @@ class DataSeriesValues /** * Set fill color for series. * - * @param null|string|string[] $color HEX color or array with HEX colors + * @param string|string[] $color HEX color or array with HEX colors * * @return DataSeriesValues */ @@ -270,10 +273,14 @@ class DataSeriesValues { if (is_array($color)) { foreach ($color as $colorValue) { - $this->validateColor($colorValue); + if (substr($colorValue, 0, 1) !== '*' && substr($colorValue, 0, 1) !== '/') { + $this->validateColor($colorValue); + } } } else { - $this->validateColor("$color"); + if (substr($color, 0, 1) !== '*' && substr($color, 0, 1) !== '/') { + $this->validateColor("$color"); + } } $this->fillColor = $color; @@ -470,4 +477,16 @@ class DataSeriesValues return $this; } + + public function getPrstClr(): string + { + return $this->prstClr; + } + + public function setPrstClr(string $prstClr): self + { + $this->prstClr = $prstClr; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Chart/Properties.php b/src/PhpSpreadsheet/Chart/Properties.php index 01a83915..6db04809 100644 --- a/src/PhpSpreadsheet/Chart/Properties.php +++ b/src/PhpSpreadsheet/Chart/Properties.php @@ -14,6 +14,11 @@ abstract class Properties EXCEL_COLOR_TYPE_STANDARD = 'prstClr'; const EXCEL_COLOR_TYPE_SCHEME = 'schemeClr'; const EXCEL_COLOR_TYPE_ARGB = 'srgbClr'; + const EXCEL_COLOR_TYPES = [ + self::EXCEL_COLOR_TYPE_ARGB, + self::EXCEL_COLOR_TYPE_SCHEME, + self::EXCEL_COLOR_TYPE_STANDARD, + ]; const AXIS_LABELS_LOW = 'low'; diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index 55a150b7..8e3d6386 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -359,7 +359,9 @@ class Chart $pointSize = null; $noFill = false; $schemeClr = ''; + $prstClr = ''; $bubble3D = false; + $dPtColors = []; foreach ($seriesDetails as $seriesKey => $seriesDetail) { switch ($seriesKey) { case 'idx': @@ -383,7 +385,24 @@ class Chart $noFill = true; } if (isset($children->solidFill)) { - $this->readColor($children->solidFill, $srgbClr, $schemeClr); + $this->readColor($children->solidFill, $srgbClr, $schemeClr, $prstClr); + } + + break; + case 'dPt': + $dptIdx = (int) self::getAttribute($seriesDetail->idx, 'val', 'string'); + if (isset($seriesDetail->spPr)) { + $children = $seriesDetail->spPr->children($this->aNamespace); + if (isset($children->solidFill)) { + $arrayColors = $this->readColor($children->solidFill); + if ($arrayColors['type'] === 'srgbClr') { + $dptColors[$dptIdx] = $arrayColors['value']; + } elseif ($arrayColors['type'] === 'prstClr') { + $dptColors[$dptIdx] = '/' . $arrayColors['value']; + } else { + $dptColors[$dptIdx] = '*' . $arrayColors['value']; + } + } } break; @@ -394,7 +413,7 @@ class Chart if (count($seriesDetail->spPr) === 1) { $ln = $seriesDetail->spPr->children($this->aNamespace); if (isset($ln->solidFill)) { - $this->readColor($ln->solidFill, $srgbClr, $schemeClr); + $this->readColor($ln->solidFill, $srgbClr, $schemeClr, $prstClr); } } @@ -461,6 +480,16 @@ class Chart if (isset($seriesValues[$seriesIndex])) { $seriesValues[$seriesIndex]->setSchemeClr($schemeClr); } + } elseif ($prstClr) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->setPrstClr($prstClr); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->setPrstClr($prstClr); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->setPrstClr($prstClr); + } } if ($bubble3D) { if (isset($seriesLabel[$seriesIndex])) { @@ -473,6 +502,17 @@ class Chart $seriesValues[$seriesIndex]->setBubble3D($bubble3D); } } + if (!empty($dptColors)) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->setFillColor($dptColors); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->setFillColor($dptColors); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->setFillColor($dptColors); + } + } } } /** @phpstan-ignore-next-line */ @@ -1001,39 +1041,31 @@ class Chart 'innerShdw', ]; - private function readColor(SimpleXMLElement $colorXml, ?string &$srgbClr = null, ?string &$schemeClr = null): array + private function readColor(SimpleXMLElement $colorXml, ?string &$srgbClr = null, ?string &$schemeClr = null, ?string &$prstClr = null): array { $result = [ 'type' => null, 'value' => null, 'alpha' => null, ]; - if (isset($colorXml->srgbClr)) { - $result['type'] = Properties::EXCEL_COLOR_TYPE_ARGB; - $result['value'] = $srgbClr = self::getAttribute($colorXml->srgbClr, 'val', 'string'); - if (isset($colorXml->srgbClr->alpha)) { - /** @var string */ - $alpha = self::getAttribute($colorXml->srgbClr->alpha, 'val', 'string'); - $alpha = Properties::alphaFromXml($alpha); - $result['alpha'] = $alpha; - } - } elseif (isset($colorXml->schemeClr)) { - $result['type'] = Properties::EXCEL_COLOR_TYPE_SCHEME; - $result['value'] = $schemeClr = self::getAttribute($colorXml->schemeClr, 'val', 'string'); - if (isset($colorXml->schemeClr->alpha)) { - /** @var string */ - $alpha = self::getAttribute($colorXml->schemeClr->alpha, 'val', 'string'); - $alpha = Properties::alphaFromXml($alpha); - $result['alpha'] = $alpha; - } - } elseif (isset($colorXml->prstClr)) { - $result['type'] = Properties::EXCEL_COLOR_TYPE_STANDARD; - $result['value'] = self::getAttribute($colorXml->prstClr, 'val', 'string'); - if (isset($colorXml->prstClr->alpha)) { - /** @var string */ - $alpha = self::getAttribute($colorXml->prstClr->alpha, 'val', 'string'); - $alpha = Properties::alphaFromXml($alpha); - $result['alpha'] = $alpha; + foreach (Properties::EXCEL_COLOR_TYPES as $type) { + if (isset($colorXml->$type)) { + $result['type'] = $type; + $result['value'] = self::getAttribute($colorXml->$type, 'val', 'string'); + if ($type === Properties::EXCEL_COLOR_TYPE_ARGB) { + $srgbClr = $result['value']; + } elseif ($type === Properties::EXCEL_COLOR_TYPE_SCHEME) { + $schemeClr = $result['value']; + } elseif ($type === Properties::EXCEL_COLOR_TYPE_STANDARD) { + $prstClr = $result['value']; + } + if (isset($colorXml->$type->alpha)) { + $alpha = (int) self::getAttribute($colorXml->$type->alpha, 'val', 'string'); + $alpha = 100 - (int) ($alpha / 1000); + $result['alpha'] = $alpha; + } + + break; } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index e24afbac..dda395ac 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -942,6 +942,9 @@ class Chart extends WriterPart */ private function writePlotSeriesValuesElement(XMLWriter $objWriter, $val = 3, $fillColor = 'FF9900'): void { + if ($fillColor === '') { + return; + } $objWriter->startElement('c:dPt'); $objWriter->startElement('c:idx'); $objWriter->writeAttribute('val', $val); @@ -953,8 +956,16 @@ class Chart extends WriterPart $objWriter->startElement('c:spPr'); $objWriter->startElement('a:solidFill'); - $objWriter->startElement('a:srgbClr'); - $objWriter->writeAttribute('val', $fillColor); + if (substr($fillColor, 0, 1) === '*') { + $objWriter->startElement('a:schemeClr'); + $objWriter->writeAttribute('val', substr($fillColor, 1)); + } elseif (substr($fillColor, 0, 1) === '/') { + $objWriter->startElement('a:prstClr'); + $objWriter->writeAttribute('val', substr($fillColor, 1)); + } else { + $objWriter->startElement('a:srgbClr'); + $objWriter->writeAttribute('val', $fillColor); + } $objWriter->endElement(); $objWriter->endElement(); $objWriter->endElement(); @@ -1039,7 +1050,7 @@ class Chart extends WriterPart $fillColorValues = $plotSeriesValues->getFillColor(); if ($fillColorValues !== null && is_array($fillColorValues)) { foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) { - $this->writePlotSeriesValuesElement($objWriter, $dataKey, ($fillColorValues[$dataKey] ?? 'FF9900')); + $this->writePlotSeriesValuesElement($objWriter, $dataKey, $fillColorValues[$dataKey] ?? ''); } } else { $this->writePlotSeriesValuesElement($objWriter); @@ -1061,7 +1072,7 @@ class Chart extends WriterPart $groupType == DataSeries::TYPE_LINECHART || $groupType == DataSeries::TYPE_STOCKCHART || ($groupType === DataSeries::TYPE_SCATTERCHART && $plotSeriesValues !== false && !$plotSeriesValues->getScatterLines()) - || ($plotSeriesValues !== false && $plotSeriesValues->getSchemeClr()) + || ($plotSeriesValues !== false && ($plotSeriesValues->getSchemeClr() || $plotSeriesValues->getPrstClr())) ) { $plotLineWidth = 12700; if ($plotSeriesValues) { @@ -1069,10 +1080,21 @@ class Chart extends WriterPart } $objWriter->startElement('c:spPr'); - $schemeClr = $plotLabel ? $plotLabel->getSchemeClr() : null; + $schemeClr = $typeClr = ''; + if ($plotLabel) { + $schemeClr = $plotLabel->getSchemeClr(); + if ($schemeClr) { + $typeClr = 'schemeClr'; + } else { + $schemeClr = $plotLabel->getPrstClr(); + if ($schemeClr) { + $typeClr = 'prstClr'; + } + } + } if ($schemeClr) { $objWriter->startElement('a:solidFill'); - $objWriter->startElement('a:schemeClr'); + $objWriter->startElement("a:$typeClr"); $objWriter->writeAttribute('val', $schemeClr); $objWriter->endElement(); $objWriter->endElement(); diff --git a/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php b/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php index a193ca7d..3123278f 100644 --- a/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php +++ b/tests/PhpSpreadsheetTests/Chart/Charts32XmlTest.php @@ -4,7 +4,6 @@ namespace PhpOffice\PhpSpreadsheetTests\Chart; use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; -use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter; use PHPUnit\Framework\TestCase; @@ -13,17 +12,6 @@ class Charts32XmlTest extends TestCase // These tests can only be performed by examining xml. private const DIRECTORY = 'samples' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR; - /** @var string */ - private $outputFileName = ''; - - protected function tearDown(): void - { - if ($this->outputFileName !== '') { - unlink($this->outputFileName); - $this->outputFileName = ''; - } - } - /** * @dataProvider providerScatterCharts */ @@ -33,25 +21,21 @@ class Charts32XmlTest extends TestCase $reader = new XlsxReader(); $reader->setIncludeCharts(true); $spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getActiveSheet(); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); $writer = new XlsxWriter($spreadsheet); $writer->setIncludeCharts(true); - $this->outputFileName = File::temporaryFilename(); - $writer->save($this->outputFileName); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart); $spreadsheet->disconnectWorksheets(); - $file = 'zip://'; - $file .= $this->outputFileName; - $file .= '#xl/charts/chart2.xml'; - $data = file_get_contents($file); - // confirm that file contains expected tags - if ($data === false) { - self::fail('Unable to read file'); - } else { - self::assertSame(1, substr_count($data, '')); - self::assertSame($expectedCount, substr_count($data, '')); - } + self::assertSame(1, substr_count($data, '')); + self::assertSame($expectedCount, substr_count($data, '')); } public function providerScatterCharts(): array @@ -69,23 +53,20 @@ class Charts32XmlTest extends TestCase $reader = new XlsxReader(); $reader->setIncludeCharts(true); $spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getActiveSheet(); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); $writer = new XlsxWriter($spreadsheet); $writer->setIncludeCharts(true); - $this->outputFileName = File::temporaryFilename(); - $writer->save($this->outputFileName); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart); $spreadsheet->disconnectWorksheets(); - $file = 'zip://'; - $file .= $this->outputFileName; - $file .= '#xl/charts/chart1.xml'; - $data = file_get_contents($file); // confirm that file contains expected tags - if ($data === false) { - self::fail('Unable to read file'); - } else { - self::assertSame(0, substr_count($data, '')); - } + self::assertSame(0, substr_count($data, '')); } /** @@ -116,18 +97,11 @@ class Charts32XmlTest extends TestCase $writer = new XlsxWriter($spreadsheet); $writer->setIncludeCharts(true); - $this->outputFileName = File::temporaryFilename(); - $writer->save($this->outputFileName); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart); $spreadsheet->disconnectWorksheets(); - $file = 'zip://'; - $file .= $this->outputFileName; - $file .= '#xl/charts/chart2.xml'; - $data = file_get_contents($file); - // confirm that file contains expected tags - if ($data === false) { - self::fail('Unable to read file'); - } elseif ($numeric === true) { + if ($numeric === true) { self::assertSame(0, substr_count($data, '')); self::assertSame(2, substr_count($data, '')); } else { @@ -144,4 +118,31 @@ class Charts32XmlTest extends TestCase [null], ]; } + + public function testAreaPrstClr(): void + { + $file = self::DIRECTORY . '32readwriteAreaChart4.xlsx'; + $reader = new XlsxReader(); + $reader->setIncludeCharts(true); + $spreadsheet = $reader->load($file); + $sheet = $spreadsheet->getActiveSheet(); + $charts = $sheet->getChartCollection(); + self::assertCount(1, $charts); + $chart = $charts[0]; + self::assertNotNull($chart); + + $writer = new XlsxWriter($spreadsheet); + $writer->setIncludeCharts(true); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart); + $spreadsheet->disconnectWorksheets(); + + self::assertSame( + 1, + substr_count( + $data, + '' + ) + ); + } } diff --git a/tests/PhpSpreadsheetTests/Chart/PieFillTest.php b/tests/PhpSpreadsheetTests/Chart/PieFillTest.php new file mode 100644 index 00000000..452fcb93 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/PieFillTest.php @@ -0,0 +1,160 @@ +setIncludeCharts(true); + } + + public function writeCharts(XlsxWriter $writer): void + { + $writer->setIncludeCharts(true); + } + + public function testPieFill(): void + { + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + $worksheet->fromArray( + [ + ['', 2010, 2011, 2012], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 0], + ] + ); + // Custom colors for dataSeries (gray, blue, red, orange) + $colors = [ + 'cccccc', + '*accent1', // use schemeClr, was '00abb8', + '/green', // use prstClr, was 'b8292f', + 'eb8500', + ]; + + // Set the Labels for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $dataSeriesLabels1 = [ + new DataSeriesValues( + DataSeriesValues::DATASERIES_TYPE_STRING, + 'Worksheet!$C$1', + null, + 1 + ), // 2011 + ]; + // Set the X-Axis Labels + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + $xAxisTickValues1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$A$2:$A$5', null, 4), // Q1 to Q4 + ]; + // Set the Data values for each data series we want to plot + // Datatype + // Cell reference for data + // Format Code + // Number of datapoints in series + // Data values + // Data Marker + // Custom Colors + $dataSeriesValues1Element = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4); + $dataSeriesValues1Element->setFillColor($colors); + $dataSeriesValues1 = [$dataSeriesValues1Element]; + + // Build the dataseries + $series1 = new DataSeries( + DataSeries::TYPE_PIECHART, // plotType + null, // plotGrouping (Pie charts don't have any grouping) + range(0, count($dataSeriesValues1) - 1), // plotOrder + $dataSeriesLabels1, // plotLabel + $xAxisTickValues1, // plotCategory + $dataSeriesValues1 // plotValues + ); + + // Set up a layout object for the Pie chart + $layout1 = new Layout(); + $layout1->setShowVal(true); + $layout1->setShowPercent(true); + + // Set the series in the plot area + $plotArea1 = new PlotArea($layout1, [$series1]); + // Set the chart legend + $legend1 = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false); + + $title1 = new Title('Test Pie Chart'); + + // Create the chart + $chart1 = new Chart( + 'chart1', // name + $title1, // title + $legend1, // legend + $plotArea1, // plotArea + true, // plotVisibleOnly + DataSeries::EMPTY_AS_GAP, // displayBlanksAs + null, // xAxisLabel + null // no Y-Axis for Pie Chart + ); + + // Set the position where the chart should appear in the worksheet + $chart1->setTopLeftPosition('A7'); + $chart1->setBottomRightPosition('H20'); + + // Add the chart to the worksheet + $worksheet->addChart($chart1); + + /** @var callable */ + $callableReader = [$this, 'readCharts']; + /** @var callable */ + $callableWriter = [$this, 'writeCharts']; + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx', $callableReader, $callableWriter); + $spreadsheet->disconnectWorksheets(); + + $sheet = $reloadedSpreadsheet->getActiveSheet(); + $charts2 = $sheet->getChartCollection(); + self::assertCount(1, $charts2); + $chart2 = $charts2[0]; + self::assertNotNull($chart2); + $plotArea2 = $chart2->getPlotArea(); + $dataSeries2 = $plotArea2->getPlotGroup(); + self::assertCount(1, $dataSeries2); + $plotValues = $dataSeries2[0]->getPlotValues(); + self::assertCount(1, $plotValues); + $fillColors = $plotValues[0]->getFillColor(); + self::assertSame($colors, $fillColors); + + $writer = new XlsxWriter($reloadedSpreadsheet); + $writer->setIncludeCharts(true); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart2); + self::assertSame(1, substr_count($data, '')); + self::assertSame(1, substr_count($data, '')); + self::assertSame(1, substr_count($data, '')); + self::assertSame(1, substr_count($data, '')); + self::assertSame(4, substr_count($data, '')); + + $reloadedSpreadsheet->disconnectWorksheets(); + } +}