From db57af0c7f11db427b0203fe56d1ebbbf9626cfb Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 14 Jul 2022 08:30:36 -0700 Subject: [PATCH] Fix Chart Problems and Memory Leak in Xlsx Writer (#2930) This was supposed to be mopping up some longstanding chart issues. But one of the sample files exposed a memory leak in Xlsx Writer, unrelated to charts. Since that is my best sample file for this problem, I would like to fix both problems at the same time. Xlsx Writer for Worksheets calls getRowDimension for all rows on the sheet. As it happens, the sample file had data in the last rows after a huge gap of rows without any data. It correctly did not write anything for the unused rows. However, the call to getRowDimension actually creates a new RowDimension object if it doesn't already exist, and so it wound up creating over a million totally unneeded objects. This caused it to run out of memory when I tried to make a copy of the 8K input file. The logic is changed to call getRowDimension if and only if (there is data in the row or the RowDimension object already exists). It still has to loop through a million rows, but it no longer allocates the unneeded storage. As for the Chart problems - fix #1797. This is where the file that caused the memory leak originated. Many of its problems were already resolved by the earlier large set of changes to Charts. However, there were a few new properties that needed to be added to Layout to make things complete - numberFormat code and source-linked, and dLblPos (position for labels); and autoTitleDeleted needs to be added to Charts. Also fix #2077, by allowing the format to be specified in the Layout rather than the DataSeriesValues constructor. --- samples/templates/32readwriteLineChart5.xlsx | Bin 0 -> 8344 bytes src/PhpSpreadsheet/Chart/Chart.php | 15 +++ src/PhpSpreadsheet/Chart/Layout.php | 56 +++++++++ src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 16 +++ src/PhpSpreadsheet/Worksheet/Worksheet.php | 5 + src/PhpSpreadsheet/Writer/Xlsx/Chart.php | 13 ++- src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 97 ++++++++-------- .../Chart/Issue2077Test.php | 108 ++++++++++++++++++ .../PhpSpreadsheetTests/Chart/LayoutTest.php | 6 + 9 files changed, 268 insertions(+), 48 deletions(-) create mode 100644 samples/templates/32readwriteLineChart5.xlsx create mode 100644 tests/PhpSpreadsheetTests/Chart/Issue2077Test.php diff --git a/samples/templates/32readwriteLineChart5.xlsx b/samples/templates/32readwriteLineChart5.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..430cc1d78bb11ba06435263adcee7c9b8b446b97 GIT binary patch literal 8344 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 literal 0 HcmV?d00001 diff --git a/src/PhpSpreadsheet/Chart/Chart.php b/src/PhpSpreadsheet/Chart/Chart.php index 3f4dd2a7..3f89e6fb 100644 --- a/src/PhpSpreadsheet/Chart/Chart.php +++ b/src/PhpSpreadsheet/Chart/Chart.php @@ -141,6 +141,9 @@ class Chart /** @var bool */ private $oneCellAnchor = false; + /** @var bool */ + private $autoTitleDeleted = false; + /** * Create a new Chart. * majorGridlines and minorGridlines are deprecated, moved to Axis. @@ -732,4 +735,16 @@ class Chart return $this; } + + public function getAutoTitleDeleted(): bool + { + return $this->autoTitleDeleted; + } + + public function setAutoTitleDeleted(bool $autoTitleDeleted): self + { + $this->autoTitleDeleted = $autoTitleDeleted; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Chart/Layout.php b/src/PhpSpreadsheet/Chart/Layout.php index 03a0ca3e..3dabcc63 100644 --- a/src/PhpSpreadsheet/Chart/Layout.php +++ b/src/PhpSpreadsheet/Chart/Layout.php @@ -53,6 +53,19 @@ class Layout */ private $height; + /** + * Position - t=top. + * + * @var string + */ + private $dLblPos = ''; + + /** @var string */ + private $numFmtCode = ''; + + /** @var bool */ + private $numFmtLinked = false; + /** * show legend key * Specifies that legend keys should be shown in data labels. @@ -143,6 +156,12 @@ class Layout if (isset($layout['h'])) { $this->height = (float) $layout['h']; } + if (isset($layout['dLblPos'])) { + $this->dLblPos = (string) $layout['dLblPos']; + } + if (isset($layout['numFmtCode'])) { + $this->numFmtCode = (string) $layout['numFmtCode']; + } $this->initBoolean($layout, 'showLegendKey'); $this->initBoolean($layout, 'showVal'); $this->initBoolean($layout, 'showCatName'); @@ -150,6 +169,7 @@ class Layout $this->initBoolean($layout, 'showPercent'); $this->initBoolean($layout, 'showBubbleSize'); $this->initBoolean($layout, 'showLeaderLines'); + $this->initBoolean($layout, 'numFmtLinked'); $this->initColor($layout, 'labelFillColor'); $this->initColor($layout, 'labelBorderColor'); $this->initColor($layout, 'labelFontColor'); @@ -484,4 +504,40 @@ class Layout return $this; } + + public function getDLblPos(): string + { + return $this->dLblPos; + } + + public function setDLblPos(string $dLblPos): self + { + $this->dLblPos = $dLblPos; + + return $this; + } + + public function getNumFmtCode(): string + { + return $this->numFmtCode; + } + + public function setNumFmtCode(string $numFmtCode): self + { + $this->numFmtCode = $numFmtCode; + + return $this; + } + + public function getNumFmtLinked(): bool + { + return $this->numFmtLinked; + } + + public function setNumFmtLinked(bool $numFmtLinked): self + { + $this->numFmtLinked = $numFmtLinked; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index 55365a17..eb425646 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -72,12 +72,18 @@ class Chart $rotX = $rotY = $rAngAx = $perspective = null; $xAxis = new Axis(); $yAxis = new Axis(); + $autoTitleDeleted = null; foreach ($chartElementsC as $chartElementKey => $chartElement) { switch ($chartElementKey) { case 'chart': foreach ($chartElement as $chartDetailsKey => $chartDetails) { $chartDetailsC = $chartDetails->children($this->cNamespace); switch ($chartDetailsKey) { + case 'autoTitleDeleted': + /** @var bool */ + $autoTitleDeleted = self::getAttribute($chartElementsC->chart->autoTitleDeleted, 'val', 'boolean'); + + break; case 'view3D': $rotX = self::getAttribute($chartDetails->rotX, 'val', 'integer'); $rotY = self::getAttribute($chartDetails->rotY, 'val', 'integer'); @@ -324,6 +330,9 @@ class Chart } } $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel, $xAxis, $yAxis); + if (is_bool($autoTitleDeleted)) { + $chart->setAutoTitleDeleted($autoTitleDeleted); + } if (is_int($rotX)) { $chart->setRotX($rotX); } @@ -967,6 +976,13 @@ class Chart { $plotAttributes = []; if (isset($chartDetail->dLbls)) { + if (isset($chartDetail->dLbls->dLblPos)) { + $plotAttributes['dLblPos'] = self::getAttribute($chartDetail->dLbls->dLblPos, 'val', 'string'); + } + if (isset($chartDetail->dLbls->numFmt)) { + $plotAttributes['numFmtCode'] = self::getAttribute($chartDetail->dLbls->numFmt, 'formatCode', 'string'); + $plotAttributes['numFmtLinked'] = self::getAttribute($chartDetail->dLbls->numFmt, 'sourceLinked', 'boolean'); + } if (isset($chartDetail->dLbls->showLegendKey)) { $plotAttributes['showLegendKey'] = self::getAttribute($chartDetail->dLbls->showLegendKey, 'val', 'string'); } diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index be717053..45111f3c 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -1413,6 +1413,11 @@ class Worksheet implements IComparable return $this->rowDimensions[$row]; } + public function rowDimensionExists(int $row): bool + { + return isset($this->rowDimensions[$row]); + } + /** * Get column dimension at a specific column. * diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php index 8d01038b..08935721 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -68,7 +68,7 @@ class Chart extends WriterPart $this->writeTitle($objWriter, $chart->getTitle()); $objWriter->startElement('c:autoTitleDeleted'); - $objWriter->writeAttribute('val', '0'); + $objWriter->writeAttribute('val', (string) (int) $chart->getAutoTitleDeleted()); $objWriter->endElement(); $objWriter->startElement('c:view3D'); @@ -420,6 +420,17 @@ class Chart extends WriterPart $objWriter->endElement(); // c:txPr } + if ($chartLayout->getNumFmtCode() !== '') { + $objWriter->startElement('c:numFmt'); + $objWriter->writeAttribute('formatCode', $chartLayout->getnumFmtCode()); + $objWriter->writeAttribute('sourceLinked', (string) (int) $chartLayout->getnumFmtLinked()); + $objWriter->endElement(); // c:numFmt + } + if ($chartLayout->getDLblPos() !== '') { + $objWriter->startElement('c:dLblPos'); + $objWriter->writeAttribute('val', $chartLayout->getDLblPos()); + $objWriter->endElement(); // c:dLblPos + } $this->writeDataLabelsBool($objWriter, 'showLegendKey', $chartLayout->getShowLegendKey()); $this->writeDataLabelsBool($objWriter, 'showVal', $chartLayout->getShowVal()); $this->writeDataLabelsBool($objWriter, 'showCatName', $chartLayout->getShowCatName()); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index f6c0c035..4b0cb632 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -1155,58 +1155,61 @@ class Worksheet extends WriterPart $currentRow = 0; while ($currentRow++ < $highestRow) { - // Get row dimension - $rowDimension = $worksheet->getRowDimension($currentRow); + $isRowSet = isset($cellsByRow[$currentRow]); + if ($isRowSet || $worksheet->rowDimensionExists($currentRow)) { + // Get row dimension + $rowDimension = $worksheet->getRowDimension($currentRow); - // Write current row? - $writeCurrentRow = isset($cellsByRow[$currentRow]) || $rowDimension->getRowHeight() >= 0 || $rowDimension->getVisible() === false || $rowDimension->getCollapsed() === true || $rowDimension->getOutlineLevel() > 0 || $rowDimension->getXfIndex() !== null; + // Write current row? + $writeCurrentRow = $isRowSet || $rowDimension->getRowHeight() >= 0 || $rowDimension->getVisible() === false || $rowDimension->getCollapsed() === true || $rowDimension->getOutlineLevel() > 0 || $rowDimension->getXfIndex() !== null; - if ($writeCurrentRow) { - // Start a new row - $objWriter->startElement('row'); - $objWriter->writeAttribute('r', $currentRow); - $objWriter->writeAttribute('spans', '1:' . $colCount); + if ($writeCurrentRow) { + // Start a new row + $objWriter->startElement('row'); + $objWriter->writeAttribute('r', $currentRow); + $objWriter->writeAttribute('spans', '1:' . $colCount); - // Row dimensions - if ($rowDimension->getRowHeight() >= 0) { - $objWriter->writeAttribute('customHeight', '1'); - $objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight())); - } - - // Row visibility - if (!$rowDimension->getVisible() === true) { - $objWriter->writeAttribute('hidden', 'true'); - } - - // Collapsed - if ($rowDimension->getCollapsed() === true) { - $objWriter->writeAttribute('collapsed', 'true'); - } - - // Outline level - if ($rowDimension->getOutlineLevel() > 0) { - $objWriter->writeAttribute('outlineLevel', $rowDimension->getOutlineLevel()); - } - - // Style - if ($rowDimension->getXfIndex() !== null) { - $objWriter->writeAttribute('s', $rowDimension->getXfIndex()); - $objWriter->writeAttribute('customFormat', '1'); - } - - // Write cells - if (isset($cellsByRow[$currentRow])) { - // We have a comma-separated list of column names (with a trailing entry); split to an array - $columnsInRow = explode(',', $cellsByRow[$currentRow]); - array_pop($columnsInRow); - foreach ($columnsInRow as $column) { - // Write cell - $this->writeCell($objWriter, $worksheet, "{$column}{$currentRow}", $aFlippedStringTable); + // Row dimensions + if ($rowDimension->getRowHeight() >= 0) { + $objWriter->writeAttribute('customHeight', '1'); + $objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight())); } - } - // End row - $objWriter->endElement(); + // Row visibility + if (!$rowDimension->getVisible() === true) { + $objWriter->writeAttribute('hidden', 'true'); + } + + // Collapsed + if ($rowDimension->getCollapsed() === true) { + $objWriter->writeAttribute('collapsed', 'true'); + } + + // Outline level + if ($rowDimension->getOutlineLevel() > 0) { + $objWriter->writeAttribute('outlineLevel', $rowDimension->getOutlineLevel()); + } + + // Style + if ($rowDimension->getXfIndex() !== null) { + $objWriter->writeAttribute('s', $rowDimension->getXfIndex()); + $objWriter->writeAttribute('customFormat', '1'); + } + + // Write cells + if (isset($cellsByRow[$currentRow])) { + // We have a comma-separated list of column names (with a trailing entry); split to an array + $columnsInRow = explode(',', $cellsByRow[$currentRow]); + array_pop($columnsInRow); + foreach ($columnsInRow as $column) { + // Write cell + $this->writeCell($objWriter, $worksheet, "{$column}{$currentRow}", $aFlippedStringTable); + } + } + + // End row + $objWriter->endElement(); + } } } diff --git a/tests/PhpSpreadsheetTests/Chart/Issue2077Test.php b/tests/PhpSpreadsheetTests/Chart/Issue2077Test.php new file mode 100644 index 00000000..2d1688c6 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Chart/Issue2077Test.php @@ -0,0 +1,108 @@ +getActiveSheet(); + $worksheet->fromArray( + [ + ['', '2010', '2011', '2012'], + ['Q1', 12, 15, 21], + ['Q2', 56, 73, 86], + ['Q3', 52, 61, 69], + ['Q4', 30, 32, 60], + ] + ); + + // Set the Labels for each data series we want to plot + $dataSeriesLabels1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$B$1', null, 1), // 2011 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$C$1', null, 1), // 2012 + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, 'Worksheet!$D$1', null, 1), // 2013 + ]; + + // Set the X-Axis Labels + $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 + // TODO I think the third parameter can be set,but I didn't succeed + $dataSeriesValues1 = [ + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', NumberFormat::FORMAT_NUMBER_00, 4), + new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$D$2:$D$5', NumberFormat::FORMAT_PERCENTAGE_00, 4), + ]; + + // 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); + // Set the layout to show percentage with 2 decimal points + $layout1->setShowPercent(true); + $layout1->setNumFmtCode(NumberFormat::FORMAT_PERCENTAGE_00); + + // 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'); + + $yAxisLabel = new Title('Value ($k)'); + // Create the chart + $chart1 = new Chart( + 'chart1', // name + $title1, // title + $legend1, // legend + $plotArea1, // plotArea + true, // plotVisibleOnly + 'gap', // displayBlanksAs + null, // xAxisLabel + $yAxisLabel + ); + + // 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); + + $writer = new XlsxWriter($spreadsheet); + $writer->setIncludeCharts(true); + $writerChart = new XlsxWriter\Chart($writer); + $data = $writerChart->writeChart($chart1); + self::assertStringContainsString('', $data); + + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Chart/LayoutTest.php b/tests/PhpSpreadsheetTests/Chart/LayoutTest.php index fbc878e1..5df139e3 100644 --- a/tests/PhpSpreadsheetTests/Chart/LayoutTest.php +++ b/tests/PhpSpreadsheetTests/Chart/LayoutTest.php @@ -42,6 +42,9 @@ class LayoutTest extends TestCase 'w' => 3.0, 'h' => 4.0, 'showVal' => true, + 'dLblPos' => 't', + 'numFmtCode' => '0.00%', + 'numFmtLinked' => true, 'labelFillColor' => $fillColor, 'labelBorderColor' => $borderColor, 'labelFontColor' => $fontColor, @@ -56,6 +59,9 @@ class LayoutTest extends TestCase ->setWidth(3.0) ->setHeight(4.0) ->setShowVal(true) + ->setDLblPos('t') + ->setNumFmtCode('0.00%') + ->setNumFmtLinked(true) ->setLabelFillColor($fillColor) ->setLabelBorderColor($borderColor) ->setLabelFontColor($fontColor);