From 294b60a918d21f239b1b6dadddc8494eeef22cc2 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Mon, 13 Nov 2023 06:53:03 -0800 Subject: [PATCH] Two Problems with Html Chart Rendering - Minor Break (#3787) * Two Problems with Html Chart Rendering - Minor Break Several problems are noted in #3783. This PR addresses those problems which make the rendering unsatisfactory (see following paragraphs). It does not address some items where the rendering is IMO satisfactory although it doesn't match Excel. In particular, the use of a different color palette and the rotation of charts are not addressed. I will leave the issue open for now because of those. As for the items which are addressed, in some cases the Html was omitting a chart altogether. This is because it had been extending the column range for charts only when it decided that extending the row range was needed. The code is changed to now extend the column range whenever the chart begins beyond the current column range of the sheet. Also, the rendering always produced a fixed-size image, so saving as Html could result in charts overlaying each other or other parts of the spreadsheet. New properties `renderedWidth` and `renderedHeight` are added to Chart, along with setters and getters. Writer/Html is changed to set these values using the chart's top left and bottom right cells to try to determine the actual size that is needed. Users can also set these properties outside of Writer/Html if they wish. Thanks to @f1mishutka for determining the source of this problem and suggesting an approach to resolving it. Because the size of the rendered image in Html/Pdf is changed, this could be considered a breaking change. To restore the prior behavior, do the following for all charts before saving as Html: ```php $chart->setRenderedWidth(640.0); $chart->setRenderedHeight(480.0); ``` * Update CHANGELOG.md --- CHANGELOG.md | 6 +- samples/templates/36writeMultiple1.xlsx | Bin 0 -> 21837 bytes src/PhpSpreadsheet/Chart/Chart.php | 34 ++++++++++ .../Chart/Renderer/JpGraphRendererBase.php | 23 +++++-- src/PhpSpreadsheet/Shared/Drawing.php | 2 +- src/PhpSpreadsheet/Writer/Html.php | 61 +++++++++++++++--- 6 files changed, 108 insertions(+), 18 deletions(-) create mode 100644 samples/templates/36writeMultiple1.xlsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 407abdd093..6decabdc31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Drop support for PHP 7.4, according to https://phpspreadsheet.readthedocs.io/en/latest/#php-version-support [PR #3713](https://github.com/PHPOffice/PhpSpreadsheet/pull/3713) - RLM Added to NumberFormatter Currency. This happens depending on release of ICU which Php is using (it does not yet happen with any official release). PhpSpreadsheet will continue to use the value returned by Php, but a method is added to keep the result unchanged from release to release. [Issue #3571](https://github.com/PHPOffice/PhpSpreadsheet/issues/3571) [PR #3640](https://github.com/PHPOffice/PhpSpreadsheet/pull/3640) - `toFormattedString` will now always return a string. This was introduced with 1.28.0, but was not properly documented at the time. This can affect the results of `toArray`, `namedRangeToArray`, and `rangeToArray`. [PR #3304](https://github.com/PHPOffice/PhpSpreadsheet/pull/3304) +- Value of constants FORMAT_CURRENCY_EUR and FORMAT_CURRENCY_USD was changed in 1.28.0, but was not properly documented at the time. [Issue #3577](https://github.com/PHPOffice/PhpSpreadsheet/issues/3577) +- Html Writer will attempt to use Chart coordinates to determine image size. [Issue #3783](https://github.com/PHPOffice/PhpSpreadsheet/issues/3783) [PR #3787](https://github.com/PHPOffice/PhpSpreadsheet/pull/3787) ### Deprecated @@ -69,6 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Load Tables even with READ_DATA_ONLY. [PR #3726](https://github.com/PHPOffice/PhpSpreadsheet/pull/3726) - Theme File Missing but Referenced in Spreadsheet. [Issue #3770](https://github.com/PHPOffice/PhpSpreadsheet/issues/3770) [PR #3772](https://github.com/PHPOffice/PhpSpreadsheet/pull/3772) - Slk Shared Formulas. [Issue #2267](https://github.com/PHPOffice/PhpSpreadsheet/issues/2267) [PR #3776](https://github.com/PHPOffice/PhpSpreadsheet/pull/3776) +- Html omitting some charts. [Issue #3767](https://github.com/PHPOffice/PhpSpreadsheet/issues/3767) [PR #3771](https://github.com/PHPOffice/PhpSpreadsheet/pull/3771) ## 1.29.0 - 2023-06-15 @@ -148,10 +151,11 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Improved support for locale settings and currency codes when matching formatted strings to numerics in the Calculation Engine [PR #3373](https://github.com/PHPOffice/PhpSpreadsheet/pull/3373) and [PR #3374](https://github.com/PHPOffice/PhpSpreadsheet/pull/3374) - Improved support for locale settings and matching in the Advanced Value Binder [PR #3376](https://github.com/PHPOffice/PhpSpreadsheet/pull/3376) - `toFormattedString` will now always return a string. This can affect the results of `toArray`, `namedRangeToArray`, and `rangeToArray`. [PR #3304](https://github.com/PHPOffice/PhpSpreadsheet/pull/3304) +- Value of constants FORMAT_CURRENCY_EUR and FORMAT_CURRENCY_USD is changed. [Issue #3577](https://github.com/PHPOffice/PhpSpreadsheet/issues/3577) [PR #3377](https://github.com/PHPOffice/PhpSpreadsheet/pull/3377) ### Deprecated -- Rationalisation of Pre-defined Currency Format Masks +- Rationalisation of Pre-defined Currency Format Masks [PR #3377](https://github.com/PHPOffice/PhpSpreadsheet/pull/3377) ### Removed diff --git a/samples/templates/36writeMultiple1.xlsx b/samples/templates/36writeMultiple1.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..ddbd5124a25ad61278b5ab4fbd51c5794abc852d GIT binary patch literal 21837 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 KyF}R^|NS3$AaOSU literal 0 HcmV?d00001 diff --git a/src/PhpSpreadsheet/Chart/Chart.php b/src/PhpSpreadsheet/Chart/Chart.php index e078b80a6d..b86b19cdd0 100644 --- a/src/PhpSpreadsheet/Chart/Chart.php +++ b/src/PhpSpreadsheet/Chart/Chart.php @@ -132,6 +132,16 @@ class Chart private ChartColor $fillColor; + /** + * Rendered width in pixels. + */ + private ?float $renderedWidth = null; + + /** + * Rendered height in pixels. + */ + private ?float $renderedHeight = null; + /** * Create a new Chart. * majorGridlines and minorGridlines are deprecated, moved to Axis. @@ -791,4 +801,28 @@ public function getFillColor(): ChartColor { return $this->fillColor; } + + public function setRenderedWidth(?float $width): self + { + $this->renderedWidth = $width; + + return $this; + } + + public function getRenderedWidth(): ?float + { + return $this->renderedWidth; + } + + public function setRenderedHeight(?float $height): self + { + $this->renderedHeight = $height; + + return $this; + } + + public function getRenderedHeight(): ?float + { + return $this->renderedHeight; + } } diff --git a/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php b/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php index 40ce338d93..27cf3b165d 100644 --- a/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php +++ b/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php @@ -26,9 +26,9 @@ */ abstract class JpGraphRendererBase implements IRenderer { - private static $width = 640; + private const DEFAULT_WIDTH = 640.0; - private static $height = 480; + private const DEFAULT_HEIGHT = 480.0; private static $colourSet = [ 'mediumpurple1', 'palegreen3', 'gold1', 'cadetblue1', @@ -70,6 +70,16 @@ public function __construct(Chart $chart) ]; } + private function getGraphWidth(): float + { + return $this->chart->getRenderedWidth() ?? self::DEFAULT_WIDTH; + } + + private function getGraphHeight(): float + { + return $this->chart->getRenderedHeight() ?? self::DEFAULT_HEIGHT; + } + /** * This method should be overriden in descendants to do real JpGraph library initialization. */ @@ -221,7 +231,7 @@ private function renderLegend(): void private function renderCartesianPlotArea(string $type = 'textlin'): void { - $this->graph = new Graph(self::$width, self::$height); + $this->graph = new Graph($this->getGraphWidth(), $this->getGraphHeight()); $this->graph->SetScale($type); $this->renderTitle(); @@ -258,14 +268,14 @@ private function renderCartesianPlotArea(string $type = 'textlin'): void private function renderPiePlotArea(): void { - $this->graph = new PieGraph(self::$width, self::$height); + $this->graph = new PieGraph($this->getGraphWidth(), $this->getGraphHeight()); $this->renderTitle(); } private function renderRadarPlotArea(): void { - $this->graph = new RadarGraph(self::$width, self::$height); + $this->graph = new RadarGraph($this->getGraphWidth(), $this->getGraphHeight()); $this->graph->SetScale('lin'); $this->renderTitle(); @@ -460,7 +470,6 @@ private function renderPlotScatter(int $groupID, bool $bubble): void $dataValuesY[$k] = $k; } } - //var_dump($dataValuesY, $dataValuesX, $bubbleSize); $seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY); if ($scatterStyle == 'lineMarker') { @@ -468,7 +477,7 @@ private function renderPlotScatter(int $groupID, bool $bubble): void $seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]); } elseif ($scatterStyle == 'smoothMarker') { $spline = new Spline($dataValuesY, $dataValuesX); - [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20); + [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * $this->getGraphWidth() / 20); $lplot = new LinePlot($splineDataX, $splineDataY); $lplot->SetColor(self::$colourSet[self::$plotColour]); diff --git a/src/PhpSpreadsheet/Shared/Drawing.php b/src/PhpSpreadsheet/Shared/Drawing.php index edaad86153..f7ce022c51 100644 --- a/src/PhpSpreadsheet/Shared/Drawing.php +++ b/src/PhpSpreadsheet/Shared/Drawing.php @@ -110,7 +110,7 @@ public static function pixelsToPoints($pixelValue): float /** * Convert points to pixels. * - * @param int $pointValue Value in points + * @param float|int $pointValue Value in points * * @return int Value in pixels */ diff --git a/src/PhpSpreadsheet/Writer/Html.php b/src/PhpSpreadsheet/Writer/Html.php index 2b38abfc4e..ecb99219d1 100644 --- a/src/PhpSpreadsheet/Writer/Html.php +++ b/src/PhpSpreadsheet/Writer/Html.php @@ -31,6 +31,10 @@ class Html extends BaseWriter { + private const DEFAULT_CELL_WIDTH_POINTS = 42; + + private const DEFAULT_CELL_WIDTH_PIXELS = 56; + /** * Spreadsheet object. */ @@ -580,9 +584,9 @@ private function extendRowsForCharts(Worksheet $worksheet, int $row): array $chartCol = Coordinate::columnIndexFromString($chartTL[0]); if ($chartTL[1] > $rowMax) { $rowMax = $chartTL[1]; - if ($chartCol > Coordinate::columnIndexFromString($colMax)) { - $colMax = $chartTL[0]; - } + } + if ($chartCol > Coordinate::columnIndexFromString($colMax)) { + $colMax = $chartTL[0]; } } } @@ -601,9 +605,9 @@ private function extendRowsForChartsAndImages(Worksheet $worksheet, int $row): s $imageCol = Coordinate::columnIndexFromString($imageTL[0]); if ($imageTL[1] > $rowMax) { $rowMax = $imageTL[1]; - if ($imageCol > Coordinate::columnIndexFromString($colMax)) { - $colMax = $imageTL[0]; - } + } + if ($imageCol > Coordinate::columnIndexFromString($colMax)) { + $colMax = $imageTL[0]; } } @@ -745,7 +749,15 @@ private function writeChartInCell(Worksheet $worksheet, string $coordinates): st $chartCoordinates = $chart->getTopLeftPosition(); if ($chartCoordinates['cell'] == $coordinates) { $chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png'; - if (!$chart->render($chartFileName)) { + $renderedWidth = $chart->getRenderedWidth(); + $renderedHeight = $chart->getRenderedHeight(); + if ($renderedWidth === null || $renderedHeight === null) { + $this->adjustRendererPositions($chart, $worksheet); + } + $renderSuccessful = $chart->render($chartFileName); + $chart->setRenderedWidth($renderedWidth); + $chart->setRenderedHeight($renderedHeight); + if (!$renderSuccessful) { return ''; } @@ -770,6 +782,37 @@ private function writeChartInCell(Worksheet $worksheet, string $coordinates): st return $html; } + private function adjustRendererPositions(Chart $chart, Worksheet $sheet): void + { + $topLeft = $chart->getTopLeftPosition(); + $bottomRight = $chart->getBottomRightPosition(); + $tlCell = $topLeft['cell']; + $brCell = $bottomRight['cell']; + if ($tlCell !== '' && $brCell !== '') { + $tlCoordinate = Coordinate::indexesFromString($tlCell); + $brCoordinate = Coordinate::indexesFromString($brCell); + $totalHeight = 0.0; + $totalWidth = 0.0; + $defaultRowHeight = $sheet->getDefaultRowDimension()->getRowHeight(); + $defaultRowHeight = SharedDrawing::pointsToPixels(($defaultRowHeight >= 0) ? $defaultRowHeight : SharedFont::getDefaultRowHeightByFont($this->defaultFont)); + if ($tlCoordinate[1] <= $brCoordinate[1] && $tlCoordinate[0] <= $brCoordinate[0]) { + for ($row = $tlCoordinate[1]; $row <= $brCoordinate[1]; ++$row) { + $height = $sheet->getRowDimension($row)->getRowHeight('pt'); + $totalHeight += ($height >= 0) ? $height : $defaultRowHeight; + } + $rightEdge = $brCoordinate[2]; + ++$rightEdge; + for ($column = $tlCoordinate[2]; $column !== $rightEdge; ++$column) { + $width = $sheet->getColumnDimension($column)->getWidth(); + $width = ($width < 0) ? self::DEFAULT_CELL_WIDTH_PIXELS : SharedDrawing::cellDimensionToPixels($sheet->getColumnDimension($column)->getWidth(), $this->defaultFont); + $totalWidth += $width; + } + $chart->setRenderedWidth($totalWidth); + $chart->setRenderedHeight($totalHeight); + } + } + } + /** * Generate CSS styles. * @@ -844,8 +887,8 @@ private function buildCssPerSheet(Worksheet $sheet, array &$css): void $highestColumnIndex = Coordinate::columnIndexFromString($sheet->getHighestColumn()) - 1; $column = -1; while ($column++ < $highestColumnIndex) { - $this->columnWidths[$sheetIndex][$column] = 42; // approximation - $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = '42pt'; + $this->columnWidths[$sheetIndex][$column] = self::DEFAULT_CELL_WIDTH_POINTS; // approximation + $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = self::DEFAULT_CELL_WIDTH_POINTS . 'pt'; } // col elements, loop through columnDimensions and set width