Skip to content

Commit 44248cd

Browse files
authored
Fix/sheets xlsx chart (#1761)
* Add support for Google Sheets Exported XLSX Charts Google Sheets XLSX charts use oneCellAnchor positioning and the data series do not have the *Cache elements with cached values. * update CHANGELOG * Add support for Google Sheets Exported XLSX Charts Google Sheets XLSX charts use oneCellAnchor positioning and the data series do not have the *Cache elements with cached values. Because the reader had been assuming *Cache elements existed as children of strRef and numRef, errors about the node being deleted were thrown when reading Xlsx exported from Google Sheets. Co-authored-by: Darren Maczka <dkm@utk.edu>
1 parent 304904d commit 44248cd

File tree

5 files changed

+107
-13
lines changed

5 files changed

+107
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
5858

5959
### Fixed
6060

61+
- Resolve Google Sheets Xlsx charts issue. Google Sheets uses oneCellAnchor positioning and does not include *Cache values in the exported Xlsx.
6162
- Fix for Xls Reader when SST has a bad length [#1592](https://github.com/PHPOffice/PhpSpreadsheet/issues/1592)
6263
- Resolve Xlsx loader issue whe hyperlinks don't have a destination
6364
- Resolve issues when printer settings resources IDs clash with drawing IDs

src/PhpSpreadsheet/Reader/Xlsx.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,13 +1155,27 @@ public function load($pFilename)
11551155
$this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks);
11561156

11571157
$objDrawing->setWorksheet($docSheet);
1158-
} else {
1159-
// ? Can charts be positioned with a oneCellAnchor ?
1158+
} elseif ($this->includeCharts && $oneCellAnchor->graphicFrame) {
1159+
// Exported XLSX from Google Sheets positions charts with a oneCellAnchor
11601160
$coordinates = Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1);
11611161
$offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff);
11621162
$offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff);
11631163
$width = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx'));
11641164
$height = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy'));
1165+
1166+
$graphic = $oneCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic;
1167+
/** @var SimpleXMLElement $chartRef */
1168+
$chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart;
1169+
$thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
1170+
1171+
$chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [
1172+
'fromCoordinate' => $coordinates,
1173+
'fromOffsetX' => $offsetX,
1174+
'fromOffsetY' => $offsetY,
1175+
'width' => $width,
1176+
'height' => $height,
1177+
'worksheetTitle' => $docSheet->getTitle(),
1178+
];
11651179
}
11661180
}
11671181
}
@@ -1508,7 +1522,10 @@ public function load($pFilename)
15081522
$excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart);
15091523
$objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet']));
15101524
$objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
1511-
$objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']);
1525+
if (array_key_exists('toCoordinate', $chartDetails[$chartPositionRef])) {
1526+
// For oneCellAnchor positioned charts, toCoordinate is not in the data. Does it need to be calculated?
1527+
$objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']);
1528+
}
15121529
}
15131530
}
15141531
}

src/PhpSpreadsheet/Reader/Xlsx/Chart.php

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -328,26 +328,51 @@ private static function chartDataSeriesValueSet($seriesDetail, $namespacesChartM
328328
{
329329
if (isset($seriesDetail->strRef)) {
330330
$seriesSource = (string) $seriesDetail->strRef->f;
331-
$seriesData = self::chartDataSeriesValues($seriesDetail->strRef->strCache->children($namespacesChartMeta['c']), 's');
331+
$seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker);
332332

333-
return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker);
333+
if (isset($seriesDetail->strRef->strCache)) {
334+
$seriesData = self::chartDataSeriesValues($seriesDetail->strRef->strCache->children($namespacesChartMeta['c']), 's');
335+
$seriesValues
336+
->setFormatCode($seriesData['formatCode'])
337+
->setDataValues($seriesData['dataValues']);
338+
}
339+
340+
return $seriesValues;
334341
} elseif (isset($seriesDetail->numRef)) {
335342
$seriesSource = (string) $seriesDetail->numRef->f;
336-
$seriesData = self::chartDataSeriesValues($seriesDetail->numRef->numCache->children($namespacesChartMeta['c']));
343+
$seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, null, null, $marker);
344+
if (isset($seriesDetail->strRef->strCache)) {
345+
$seriesData = self::chartDataSeriesValues($seriesDetail->numRef->numCache->children($namespacesChartMeta['c']));
346+
$seriesValues
347+
->setFormatCode($seriesData['formatCode'])
348+
->setDataValues($seriesData['dataValues']);
349+
}
337350

338-
return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker);
351+
return $seriesValues;
339352
} elseif (isset($seriesDetail->multiLvlStrRef)) {
340353
$seriesSource = (string) $seriesDetail->multiLvlStrRef->f;
341-
$seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($namespacesChartMeta['c']), 's');
342-
$seriesData['pointCount'] = count($seriesData['dataValues']);
354+
$seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker);
343355

344-
return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker);
356+
if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) {
357+
$seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($namespacesChartMeta['c']), 's');
358+
$seriesValues
359+
->setFormatCode($seriesData['formatCode'])
360+
->setDataValues($seriesData['dataValues']);
361+
}
362+
363+
return $seriesValues;
345364
} elseif (isset($seriesDetail->multiLvlNumRef)) {
346365
$seriesSource = (string) $seriesDetail->multiLvlNumRef->f;
347-
$seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($namespacesChartMeta['c']), 's');
348-
$seriesData['pointCount'] = count($seriesData['dataValues']);
366+
$seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker);
367+
368+
if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) {
369+
$seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($namespacesChartMeta['c']), 's');
370+
$seriesValues
371+
->setFormatCode($seriesData['formatCode'])
372+
->setDataValues($seriesData['dataValues']);
373+
}
349374

350-
return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker);
375+
return $seriesValues;
351376
}
352377

353378
return null;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheetTests\Reader;
4+
5+
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
6+
use PhpOffice\PhpSpreadsheet\IOFactory;
7+
use PHPUnit\Framework\TestCase;
8+
9+
class SheetsXlsxChartTest extends TestCase
10+
{
11+
public function testLoadSheetsXlsxChart(): void
12+
{
13+
$filename = 'tests/data/Reader/XLSX/sheetsChartsTest.xlsx';
14+
$reader = IOFactory::createReader('Xlsx')->setIncludeCharts(true);
15+
$spreadsheet = $reader->load($filename);
16+
$worksheet = $spreadsheet->getActiveSheet();
17+
18+
$charts = $worksheet->getChartCollection();
19+
self::assertEquals(2, $worksheet->getChartCount());
20+
self::assertCount(2, $charts);
21+
22+
$chart1 = $charts[0];
23+
$pa1 = $chart1->getPlotArea();
24+
self::assertEquals(2, $pa1->getPlotSeriesCount());
25+
26+
$pg1 = $pa1->getPlotGroup()[0];
27+
28+
self::assertEquals(DataSeries::TYPE_LINECHART, $pg1->getPlotType());
29+
self::assertCount(2, $pg1->getPlotLabels());
30+
self::assertCount(2, $pg1->getPlotValues());
31+
self::assertCount(2, $pg1->getPlotCategories());
32+
33+
$chart2 = $charts[1];
34+
$pa1 = $chart2->getPlotArea();
35+
self::assertEquals(2, $pa1->getPlotSeriesCount());
36+
37+
$pg1 = $pa1->getPlotGroupByIndex(0);
38+
//Before a refresh, data values are empty
39+
foreach ($pg1->getPlotValues() as $dv) {
40+
self::assertEmpty($dv->getPointCount());
41+
}
42+
$pg1->refresh($worksheet);
43+
foreach ($pg1->getPlotValues() as $dv) {
44+
self::assertEquals(9, $dv->getPointCount());
45+
}
46+
self::assertEquals(DataSeries::TYPE_SCATTERCHART, $pg1->getPlotType());
47+
self::assertCount(2, $pg1->getPlotLabels());
48+
self::assertCount(2, $pg1->getPlotValues());
49+
self::assertCount(2, $pg1->getPlotCategories());
50+
}
51+
}
11.8 KB
Binary file not shown.

0 commit comments

Comments
 (0)