Skip to content

Commit f9eb35d

Browse files
authored
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
1 parent 5b27082 commit f9eb35d

File tree

6 files changed

+108
-18
lines changed

6 files changed

+108
-18
lines changed

CHANGELOG.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ and this project adheres to [Semantic Versioning](https://semver.org).
3030
- 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)
3131
- 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)
3232
- `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)
33+
- 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)
34+
- 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)
3335

3436
### Deprecated
3537

@@ -69,6 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
6971
- Load Tables even with READ_DATA_ONLY. [PR #3726](https://github.com/PHPOffice/PhpSpreadsheet/pull/3726)
7072
- 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)
7173
- Slk Shared Formulas. [Issue #2267](https://github.com/PHPOffice/PhpSpreadsheet/issues/2267) [PR #3776](https://github.com/PHPOffice/PhpSpreadsheet/pull/3776)
74+
- Html omitting some charts. [Issue #3767](https://github.com/PHPOffice/PhpSpreadsheet/issues/3767) [PR #3771](https://github.com/PHPOffice/PhpSpreadsheet/pull/3771)
7275

7376
## 1.29.0 - 2023-06-15
7477

@@ -148,10 +151,11 @@ and this project adheres to [Semantic Versioning](https://semver.org).
148151
- 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)
149152
- Improved support for locale settings and matching in the Advanced Value Binder [PR #3376](https://github.com/PHPOffice/PhpSpreadsheet/pull/3376)
150153
- `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)
154+
- 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)
151155

152156
### Deprecated
153157

154-
- Rationalisation of Pre-defined Currency Format Masks
158+
- Rationalisation of Pre-defined Currency Format Masks [PR #3377](https://github.com/PHPOffice/PhpSpreadsheet/pull/3377)
155159

156160
### Removed
157161

21.3 KB
Binary file not shown.

src/PhpSpreadsheet/Chart/Chart.php

+34
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@ class Chart
132132

133133
private ChartColor $fillColor;
134134

135+
/**
136+
* Rendered width in pixels.
137+
*/
138+
private ?float $renderedWidth = null;
139+
140+
/**
141+
* Rendered height in pixels.
142+
*/
143+
private ?float $renderedHeight = null;
144+
135145
/**
136146
* Create a new Chart.
137147
* majorGridlines and minorGridlines are deprecated, moved to Axis.
@@ -791,4 +801,28 @@ public function getFillColor(): ChartColor
791801
{
792802
return $this->fillColor;
793803
}
804+
805+
public function setRenderedWidth(?float $width): self
806+
{
807+
$this->renderedWidth = $width;
808+
809+
return $this;
810+
}
811+
812+
public function getRenderedWidth(): ?float
813+
{
814+
return $this->renderedWidth;
815+
}
816+
817+
public function setRenderedHeight(?float $height): self
818+
{
819+
$this->renderedHeight = $height;
820+
821+
return $this;
822+
}
823+
824+
public function getRenderedHeight(): ?float
825+
{
826+
return $this->renderedHeight;
827+
}
794828
}

src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php

+16-7
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
*/
2727
abstract class JpGraphRendererBase implements IRenderer
2828
{
29-
private static $width = 640;
29+
private const DEFAULT_WIDTH = 640.0;
3030

31-
private static $height = 480;
31+
private const DEFAULT_HEIGHT = 480.0;
3232

3333
private static $colourSet = [
3434
'mediumpurple1', 'palegreen3', 'gold1', 'cadetblue1',
@@ -70,6 +70,16 @@ public function __construct(Chart $chart)
7070
];
7171
}
7272

73+
private function getGraphWidth(): float
74+
{
75+
return $this->chart->getRenderedWidth() ?? self::DEFAULT_WIDTH;
76+
}
77+
78+
private function getGraphHeight(): float
79+
{
80+
return $this->chart->getRenderedHeight() ?? self::DEFAULT_HEIGHT;
81+
}
82+
7383
/**
7484
* This method should be overriden in descendants to do real JpGraph library initialization.
7585
*/
@@ -221,7 +231,7 @@ private function renderLegend(): void
221231

222232
private function renderCartesianPlotArea(string $type = 'textlin'): void
223233
{
224-
$this->graph = new Graph(self::$width, self::$height);
234+
$this->graph = new Graph($this->getGraphWidth(), $this->getGraphHeight());
225235
$this->graph->SetScale($type);
226236

227237
$this->renderTitle();
@@ -258,14 +268,14 @@ private function renderCartesianPlotArea(string $type = 'textlin'): void
258268

259269
private function renderPiePlotArea(): void
260270
{
261-
$this->graph = new PieGraph(self::$width, self::$height);
271+
$this->graph = new PieGraph($this->getGraphWidth(), $this->getGraphHeight());
262272

263273
$this->renderTitle();
264274
}
265275

266276
private function renderRadarPlotArea(): void
267277
{
268-
$this->graph = new RadarGraph(self::$width, self::$height);
278+
$this->graph = new RadarGraph($this->getGraphWidth(), $this->getGraphHeight());
269279
$this->graph->SetScale('lin');
270280

271281
$this->renderTitle();
@@ -460,15 +470,14 @@ private function renderPlotScatter(int $groupID, bool $bubble): void
460470
$dataValuesY[$k] = $k;
461471
}
462472
}
463-
//var_dump($dataValuesY, $dataValuesX, $bubbleSize);
464473

465474
$seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY);
466475
if ($scatterStyle == 'lineMarker') {
467476
$seriesPlot->SetLinkPoints();
468477
$seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]);
469478
} elseif ($scatterStyle == 'smoothMarker') {
470479
$spline = new Spline($dataValuesY, $dataValuesX);
471-
[$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20);
480+
[$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * $this->getGraphWidth() / 20);
472481
$lplot = new LinePlot($splineDataX, $splineDataY);
473482
$lplot->SetColor(self::$colourSet[self::$plotColour]);
474483

src/PhpSpreadsheet/Shared/Drawing.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public static function pixelsToPoints($pixelValue): float
110110
/**
111111
* Convert points to pixels.
112112
*
113-
* @param int $pointValue Value in points
113+
* @param float|int $pointValue Value in points
114114
*
115115
* @return int Value in pixels
116116
*/

src/PhpSpreadsheet/Writer/Html.php

+52-9
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131

3232
class Html extends BaseWriter
3333
{
34+
private const DEFAULT_CELL_WIDTH_POINTS = 42;
35+
36+
private const DEFAULT_CELL_WIDTH_PIXELS = 56;
37+
3438
/**
3539
* Spreadsheet object.
3640
*/
@@ -580,9 +584,9 @@ private function extendRowsForCharts(Worksheet $worksheet, int $row): array
580584
$chartCol = Coordinate::columnIndexFromString($chartTL[0]);
581585
if ($chartTL[1] > $rowMax) {
582586
$rowMax = $chartTL[1];
583-
if ($chartCol > Coordinate::columnIndexFromString($colMax)) {
584-
$colMax = $chartTL[0];
585-
}
587+
}
588+
if ($chartCol > Coordinate::columnIndexFromString($colMax)) {
589+
$colMax = $chartTL[0];
586590
}
587591
}
588592
}
@@ -601,9 +605,9 @@ private function extendRowsForChartsAndImages(Worksheet $worksheet, int $row): s
601605
$imageCol = Coordinate::columnIndexFromString($imageTL[0]);
602606
if ($imageTL[1] > $rowMax) {
603607
$rowMax = $imageTL[1];
604-
if ($imageCol > Coordinate::columnIndexFromString($colMax)) {
605-
$colMax = $imageTL[0];
606-
}
608+
}
609+
if ($imageCol > Coordinate::columnIndexFromString($colMax)) {
610+
$colMax = $imageTL[0];
607611
}
608612
}
609613

@@ -745,7 +749,15 @@ private function writeChartInCell(Worksheet $worksheet, string $coordinates): st
745749
$chartCoordinates = $chart->getTopLeftPosition();
746750
if ($chartCoordinates['cell'] == $coordinates) {
747751
$chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png';
748-
if (!$chart->render($chartFileName)) {
752+
$renderedWidth = $chart->getRenderedWidth();
753+
$renderedHeight = $chart->getRenderedHeight();
754+
if ($renderedWidth === null || $renderedHeight === null) {
755+
$this->adjustRendererPositions($chart, $worksheet);
756+
}
757+
$renderSuccessful = $chart->render($chartFileName);
758+
$chart->setRenderedWidth($renderedWidth);
759+
$chart->setRenderedHeight($renderedHeight);
760+
if (!$renderSuccessful) {
749761
return '';
750762
}
751763

@@ -770,6 +782,37 @@ private function writeChartInCell(Worksheet $worksheet, string $coordinates): st
770782
return $html;
771783
}
772784

785+
private function adjustRendererPositions(Chart $chart, Worksheet $sheet): void
786+
{
787+
$topLeft = $chart->getTopLeftPosition();
788+
$bottomRight = $chart->getBottomRightPosition();
789+
$tlCell = $topLeft['cell'];
790+
$brCell = $bottomRight['cell'];
791+
if ($tlCell !== '' && $brCell !== '') {
792+
$tlCoordinate = Coordinate::indexesFromString($tlCell);
793+
$brCoordinate = Coordinate::indexesFromString($brCell);
794+
$totalHeight = 0.0;
795+
$totalWidth = 0.0;
796+
$defaultRowHeight = $sheet->getDefaultRowDimension()->getRowHeight();
797+
$defaultRowHeight = SharedDrawing::pointsToPixels(($defaultRowHeight >= 0) ? $defaultRowHeight : SharedFont::getDefaultRowHeightByFont($this->defaultFont));
798+
if ($tlCoordinate[1] <= $brCoordinate[1] && $tlCoordinate[0] <= $brCoordinate[0]) {
799+
for ($row = $tlCoordinate[1]; $row <= $brCoordinate[1]; ++$row) {
800+
$height = $sheet->getRowDimension($row)->getRowHeight('pt');
801+
$totalHeight += ($height >= 0) ? $height : $defaultRowHeight;
802+
}
803+
$rightEdge = $brCoordinate[2];
804+
++$rightEdge;
805+
for ($column = $tlCoordinate[2]; $column !== $rightEdge; ++$column) {
806+
$width = $sheet->getColumnDimension($column)->getWidth();
807+
$width = ($width < 0) ? self::DEFAULT_CELL_WIDTH_PIXELS : SharedDrawing::cellDimensionToPixels($sheet->getColumnDimension($column)->getWidth(), $this->defaultFont);
808+
$totalWidth += $width;
809+
}
810+
$chart->setRenderedWidth($totalWidth);
811+
$chart->setRenderedHeight($totalHeight);
812+
}
813+
}
814+
}
815+
773816
/**
774817
* Generate CSS styles.
775818
*
@@ -844,8 +887,8 @@ private function buildCssPerSheet(Worksheet $sheet, array &$css): void
844887
$highestColumnIndex = Coordinate::columnIndexFromString($sheet->getHighestColumn()) - 1;
845888
$column = -1;
846889
while ($column++ < $highestColumnIndex) {
847-
$this->columnWidths[$sheetIndex][$column] = 42; // approximation
848-
$css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = '42pt';
890+
$this->columnWidths[$sheetIndex][$column] = self::DEFAULT_CELL_WIDTH_POINTS; // approximation
891+
$css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = self::DEFAULT_CELL_WIDTH_POINTS . 'pt';
849892
}
850893

851894
// col elements, loop through columnDimensions and set width

0 commit comments

Comments
 (0)