diff --git a/CHANGELOG.md b/CHANGELOG.md index efcb21ca3d..d429f0adbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Fixed +- Resolve Google Sheets Xlsx charts issue. Google Sheets uses oneCellAnchor positioning and does not include *Cache values in the exported Xlsx. - Fix for Xls Reader when SST has a bad length [#1592](https://github.com/PHPOffice/PhpSpreadsheet/issues/1592) - Resolve Xlsx loader issue whe hyperlinks don't have a destination - Resolve issues when printer settings resources IDs clash with drawing IDs diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index c05f77de02..c228f344c4 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -1155,13 +1155,27 @@ public function load($pFilename) $this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks); $objDrawing->setWorksheet($docSheet); - } else { - // ? Can charts be positioned with a oneCellAnchor ? + } elseif ($this->includeCharts && $oneCellAnchor->graphicFrame) { + // Exported XLSX from Google Sheets positions charts with a oneCellAnchor $coordinates = Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1); $offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff); $offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff); $width = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx')); $height = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy')); + + $graphic = $oneCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic; + /** @var SimpleXMLElement $chartRef */ + $chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart; + $thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + + $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ + 'fromCoordinate' => $coordinates, + 'fromOffsetX' => $offsetX, + 'fromOffsetY' => $offsetY, + 'width' => $width, + 'height' => $height, + 'worksheetTitle' => $docSheet->getTitle(), + ]; } } } @@ -1508,7 +1522,10 @@ public function load($pFilename) $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart); $objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet'])); $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']); - $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']); + if (array_key_exists('toCoordinate', $chartDetails[$chartPositionRef])) { + // For oneCellAnchor positioned charts, toCoordinate is not in the data. Does it need to be calculated? + $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']); + } } } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php index c9a230c215..84b2e62b80 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -328,26 +328,51 @@ private static function chartDataSeriesValueSet($seriesDetail, $namespacesChartM { if (isset($seriesDetail->strRef)) { $seriesSource = (string) $seriesDetail->strRef->f; - $seriesData = self::chartDataSeriesValues($seriesDetail->strRef->strCache->children($namespacesChartMeta['c']), 's'); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker); - return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker); + if (isset($seriesDetail->strRef->strCache)) { + $seriesData = self::chartDataSeriesValues($seriesDetail->strRef->strCache->children($namespacesChartMeta['c']), 's'); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } + + return $seriesValues; } elseif (isset($seriesDetail->numRef)) { $seriesSource = (string) $seriesDetail->numRef->f; - $seriesData = self::chartDataSeriesValues($seriesDetail->numRef->numCache->children($namespacesChartMeta['c'])); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, null, null, $marker); + if (isset($seriesDetail->strRef->strCache)) { + $seriesData = self::chartDataSeriesValues($seriesDetail->numRef->numCache->children($namespacesChartMeta['c'])); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } - return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker); + return $seriesValues; } elseif (isset($seriesDetail->multiLvlStrRef)) { $seriesSource = (string) $seriesDetail->multiLvlStrRef->f; - $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($namespacesChartMeta['c']), 's'); - $seriesData['pointCount'] = count($seriesData['dataValues']); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker); - return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker); + if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) { + $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($namespacesChartMeta['c']), 's'); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } + + return $seriesValues; } elseif (isset($seriesDetail->multiLvlNumRef)) { $seriesSource = (string) $seriesDetail->multiLvlNumRef->f; - $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($namespacesChartMeta['c']), 's'); - $seriesData['pointCount'] = count($seriesData['dataValues']); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, null, null, $marker); + + if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) { + $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($namespacesChartMeta['c']), 's'); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } - return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker); + return $seriesValues; } return null; diff --git a/tests/PhpSpreadsheetTests/Reader/SheetsXlsxChartTest.php b/tests/PhpSpreadsheetTests/Reader/SheetsXlsxChartTest.php new file mode 100644 index 0000000000..c4dc328d2d --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/SheetsXlsxChartTest.php @@ -0,0 +1,51 @@ +setIncludeCharts(true); + $spreadsheet = $reader->load($filename); + $worksheet = $spreadsheet->getActiveSheet(); + + $charts = $worksheet->getChartCollection(); + self::assertEquals(2, $worksheet->getChartCount()); + self::assertCount(2, $charts); + + $chart1 = $charts[0]; + $pa1 = $chart1->getPlotArea(); + self::assertEquals(2, $pa1->getPlotSeriesCount()); + + $pg1 = $pa1->getPlotGroup()[0]; + + self::assertEquals(DataSeries::TYPE_LINECHART, $pg1->getPlotType()); + self::assertCount(2, $pg1->getPlotLabels()); + self::assertCount(2, $pg1->getPlotValues()); + self::assertCount(2, $pg1->getPlotCategories()); + + $chart2 = $charts[1]; + $pa1 = $chart2->getPlotArea(); + self::assertEquals(2, $pa1->getPlotSeriesCount()); + + $pg1 = $pa1->getPlotGroupByIndex(0); + //Before a refresh, data values are empty + foreach ($pg1->getPlotValues() as $dv) { + self::assertEmpty($dv->getPointCount()); + } + $pg1->refresh($worksheet); + foreach ($pg1->getPlotValues() as $dv) { + self::assertEquals(9, $dv->getPointCount()); + } + self::assertEquals(DataSeries::TYPE_SCATTERCHART, $pg1->getPlotType()); + self::assertCount(2, $pg1->getPlotLabels()); + self::assertCount(2, $pg1->getPlotValues()); + self::assertCount(2, $pg1->getPlotCategories()); + } +} diff --git a/tests/data/Reader/XLSX/sheetsChartsTest.xlsx b/tests/data/Reader/XLSX/sheetsChartsTest.xlsx new file mode 100755 index 0000000000..82e00ac932 Binary files /dev/null and b/tests/data/Reader/XLSX/sheetsChartsTest.xlsx differ