diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 5a90a21b..778090a8 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -4547,7 +4547,7 @@ parameters:
-
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#"
- count: 43
+ count: 42
path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php
-
diff --git a/samples/Chart/33_Chart_create_bubble.php b/samples/Chart/33_Chart_create_bubble.php
new file mode 100644
index 00000000..33feea62
--- /dev/null
+++ b/samples/Chart/33_Chart_create_bubble.php
@@ -0,0 +1,124 @@
+getActiveSheet();
+$worksheet->fromArray(
+ [
+ ['Number of Products', 'Sales in USD', 'Market share'],
+ [14, 12200, 15],
+ [20, 60000, 33],
+ [18, 24400, 10],
+ [22, 32000, 42],
+ [],
+ [12, 8200, 18],
+ [15, 50000, 30],
+ [19, 22400, 15],
+ [25, 25000, 50],
+ ]
+);
+
+// Set the Labels for each data series we want to plot
+// Datatype
+// Cell reference for data
+// Format Code
+// Number of datapoints in series
+// Data values
+// Data Marker
+
+$dataSeriesLabels = [
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, null, null, 1, ['2013']), // 2013
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, null, null, 1, ['2014']), // 2014
+];
+
+// Set the X-Axis values
+// Datatype
+// Cell reference for data
+// Format Code
+// Number of datapoints in series
+// Data values
+// Data Marker
+$dataSeriesCategories = [
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$2:$A$5', null, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$A$7:$A$10', null, 4),
+];
+
+// Set the Y-Axis values
+// Datatype
+// Cell reference for data
+// Format Code
+// Number of datapoints in series
+// Data values
+// Data Marker
+$dataSeriesValues = [
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$2:$B$5', null, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$B$7:$B$10', null, 4),
+];
+
+// Set the Z-Axis values (bubble size)
+// Datatype
+// Cell reference for data
+// Format Code
+// Number of datapoints in series
+// Data values
+// Data Marker
+$dataSeriesBubbles = [
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$2:$C$5', null, 4),
+ new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, 'Worksheet!$C$7:$C$10', null, 4),
+];
+
+// Build the dataseries
+$series = new DataSeries(
+ DataSeries::TYPE_BUBBLECHART, // plotType
+ null, // plotGrouping
+ range(0, count($dataSeriesValues) - 1), // plotOrder
+ $dataSeriesLabels, // plotLabel
+ $dataSeriesCategories, // plotCategory
+ $dataSeriesValues // plotValues
+);
+$series->setPlotBubbleSizes($dataSeriesBubbles);
+
+// Set the series in the plot area
+$plotArea = new PlotArea(null, [$series]);
+// Set the chart legend
+$legend = new ChartLegend(ChartLegend::POSITION_RIGHT, null, false);
+
+// Create the chart
+$chart = new Chart(
+ 'chart1', // name
+ null, // title
+ $legend, // legend
+ $plotArea, // plotArea
+ true, // plotVisibleOnly
+ DataSeries::EMPTY_AS_GAP, // displayBlanksAs
+ null, // xAxisLabel
+ null // yAxisLabel
+);
+
+// Set the position where the chart should appear in the worksheet
+$chart->setTopLeftPosition('E1');
+$chart->setBottomRightPosition('M15');
+
+// Add the chart to the worksheet
+$worksheet->addChart($chart);
+$worksheet->getColumnDimension('A')->setAutoSize(true);
+$worksheet->getColumnDimension('B')->setAutoSize(true);
+$worksheet->getColumnDimension('C')->setAutoSize(true);
+
+// Save Excel 2007 file
+$filename = $helper->getFilename(__FILE__);
+$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
+$writer->setIncludeCharts(true);
+$callStartTime = microtime(true);
+$writer->save($filename);
+$helper->logWrite($writer, $filename, $callStartTime);
diff --git a/samples/templates/32readwriteBubbleChart2.xlsx b/samples/templates/32readwriteBubbleChart2.xlsx
new file mode 100644
index 00000000..206cfaea
Binary files /dev/null and b/samples/templates/32readwriteBubbleChart2.xlsx differ
diff --git a/src/PhpSpreadsheet/Chart/Chart.php b/src/PhpSpreadsheet/Chart/Chart.php
index 80d3d5f3..ec6342c5 100644
--- a/src/PhpSpreadsheet/Chart/Chart.php
+++ b/src/PhpSpreadsheet/Chart/Chart.php
@@ -152,6 +152,9 @@ class Chart
/** @var ?int */
private $perspective;
+ /** @var bool */
+ private $oneCellAnchor = false;
+
/**
* Create a new Chart.
*
@@ -743,4 +746,16 @@ class Chart
return $this;
}
+
+ public function getOneCellAnchor(): bool
+ {
+ return $this->oneCellAnchor;
+ }
+
+ public function setOneCellAnchor(bool $oneCellAnchor): self
+ {
+ $this->oneCellAnchor = $oneCellAnchor;
+
+ return $this;
+ }
}
diff --git a/src/PhpSpreadsheet/Chart/DataSeries.php b/src/PhpSpreadsheet/Chart/DataSeries.php
index 067d30e5..dca1186e 100644
--- a/src/PhpSpreadsheet/Chart/DataSeries.php
+++ b/src/PhpSpreadsheet/Chart/DataSeries.php
@@ -107,6 +107,13 @@ class DataSeries
*/
private $plotValues = [];
+ /**
+ * Plot Bubble Sizes.
+ *
+ * @var DataSeriesValues[]
+ */
+ private $plotBubbleSizes = [];
+
/**
* Create a new DataSeries.
*
@@ -339,6 +346,28 @@ class DataSeries
return false;
}
+ /**
+ * Get Plot Bubble Sizes.
+ *
+ * @return DataSeriesValues[]
+ */
+ public function getPlotBubbleSizes(): array
+ {
+ return $this->plotBubbleSizes;
+ }
+
+ /**
+ * Set Plot Bubble Sizes.
+ *
+ * @param DataSeriesValues[] $plotBubbleSizes
+ */
+ public function setPlotBubbleSizes(array $plotBubbleSizes): self
+ {
+ $this->plotBubbleSizes = $plotBubbleSizes;
+
+ return $this;
+ }
+
/**
* Get Number of Plot Series.
*
diff --git a/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/src/PhpSpreadsheet/Chart/DataSeriesValues.php
index cf3e0853..6747934a 100644
--- a/src/PhpSpreadsheet/Chart/DataSeriesValues.php
+++ b/src/PhpSpreadsheet/Chart/DataSeriesValues.php
@@ -328,7 +328,7 @@ class DataSeriesValues
*/
public function isMultiLevelSeries()
{
- if (count($this->dataValues) > 0) {
+ if (!empty($this->dataValues)) {
return is_array(array_values($this->dataValues)[0]);
}
diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php
index 455fe07a..8562339b 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx.php
@@ -157,6 +157,10 @@ class Xlsx extends BaseReader
Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_DRAWING,
];
+ private const REL_TO_CHART = [
+ Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_CHART,
+ ];
+
/**
* Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
*
@@ -408,17 +412,21 @@ class Xlsx extends BaseReader
// Read the theme first, because we need the colour scheme when reading the styles
[$workbookBasename, $xmlNamespaceBase] = $this->getWorkbookBaseName();
+ $drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML;
+ $chartNS = self::REL_TO_CHART[$xmlNamespaceBase] ?? Namespaces::CHART;
$wbRels = $this->loadZip("xl/_rels/${workbookBasename}.rels", Namespaces::RELATIONSHIPS);
$theme = null;
$this->styleReader = new Styles();
foreach ($wbRels->Relationship as $relx) {
$rel = self::getAttributes($relx);
$relTarget = (string) $rel['Target'];
+ if (substr($relTarget, 0, 4) === '/xl/') {
+ $relTarget = substr($relTarget, 4);
+ }
switch ($rel['Type']) {
case "$xmlNamespaceBase/theme":
$themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2'];
$themeOrderAdditional = count($themeOrderArray);
- $drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML;
$xmlTheme = $this->loadZip("xl/{$relTarget}", $drawingNS);
$xmlThemeName = self::getAttributes($xmlTheme);
@@ -1204,12 +1212,20 @@ class Xlsx extends BaseReader
. '/_rels/'
. basename($fileWorksheet)
. '.rels';
+ if (substr($drawingFilename, 0, 7) === 'xl//xl/') {
+ $drawingFilename = substr($drawingFilename, 4);
+ }
if ($zip->locateName($drawingFilename)) {
$relsWorksheet = $this->loadZipNoNamespace($drawingFilename, Namespaces::RELATIONSHIPS);
$drawings = [];
foreach ($relsWorksheet->Relationship as $ele) {
if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") {
- $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
+ $eleTarget = (string) $ele['Target'];
+ if (substr($eleTarget, 0, 4) === '/xl/') {
+ $drawings[(string) $ele['Id']] = substr($eleTarget, 1);
+ } else {
+ $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
+ }
}
}
@@ -1234,7 +1250,13 @@ class Xlsx extends BaseReader
$images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']);
} elseif ($eleType === "$xmlNamespaceBase/chart") {
if ($this->includeCharts) {
- $charts[self::dirAdd($fileDrawing, $ele['Target'])] = [
+ $eleTarget = (string) $ele['Target'];
+ if (substr($eleTarget, 0, 4) === '/xl/') {
+ $index = substr($eleTarget, 1);
+ } else {
+ $index = self::dirAdd($fileDrawing, $eleTarget);
+ }
+ $charts[$index] = [
'id' => (string) $ele['Id'],
'sheet' => $docSheet->getTitle(),
];
@@ -1326,6 +1348,7 @@ class Xlsx extends BaseReader
'width' => $width,
'height' => $height,
'worksheetTitle' => $docSheet->getTitle(),
+ 'oneCellAnchor' => true,
];
}
}
@@ -1645,7 +1668,7 @@ class Xlsx extends BaseReader
if ($this->includeCharts) {
$chartEntryRef = ltrim((string) $contentType['PartName'], '/');
$chartElements = $this->loadZip($chartEntryRef);
- $chartReader = new Chart();
+ $chartReader = new Chart($chartNS, $drawingNS);
$objChart = $chartReader->readChart($chartElements, basename($chartEntryRef, '.xml'));
if (isset($charts[$chartEntryRef])) {
$chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id'];
@@ -1662,6 +1685,9 @@ class Xlsx extends BaseReader
// oneCellAnchor or absoluteAnchor (e.g. Chart sheet)
$objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
$objChart->setBottomRightPosition('', $chartDetails[$chartPositionRef]['width'], $chartDetails[$chartPositionRef]['height']);
+ if (array_key_exists('oneCellAnchor', $chartDetails[$chartPositionRef])) {
+ $objChart->setOneCellAnchor($chartDetails[$chartPositionRef]['oneCellAnchor']);
+ }
}
}
}
@@ -1823,6 +1849,11 @@ class Xlsx extends BaseReader
private static function dirAdd($base, $add): string
{
+ $add = "$add";
+ if (substr($add, 0, 4) === '/xl/') {
+ $add = substr($add, 4);
+ }
+
return (string) preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add");
}
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
index f6b1edd5..98507af8 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
@@ -305,7 +305,7 @@ class Chart
{
$multiSeriesType = null;
$smoothLine = false;
- $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = [];
+ $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = $seriesBubbles = [];
$seriesDetailSet = $chartDetail->children($this->cNamespace);
foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) {
@@ -382,6 +382,10 @@ class Chart
case 'yVal':
$seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize");
+ break;
+ case 'bubbleSize':
+ $seriesBubbles[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize");
+
break;
case 'bubble3D':
$bubble3D = self::getAttribute($seriesDetail, 'val', 'boolean');
@@ -435,9 +439,11 @@ class Chart
}
}
}
-
/** @phpstan-ignore-next-line */
- return new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine);
+ $series = new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine);
+ $series->setPlotBubbleSizes($seriesBubbles);
+
+ return $series;
}
/**
@@ -494,6 +500,16 @@ class Chart
return $seriesValues;
}
+ if (isset($seriesDetail->v)) {
+ return new DataSeriesValues(
+ DataSeriesValues::DATASERIES_TYPE_STRING,
+ null,
+ null,
+ 1,
+ [(string) $seriesDetail->v]
+ );
+ }
+
return null;
}
diff --git a/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php b/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php
index eb768d09..57a88bb0 100644
--- a/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php
+++ b/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php
@@ -76,5 +76,7 @@ class Namespaces
const PURL_DRAWING = 'http://purl.oclc.org/ooxml/drawingml/main';
+ const PURL_CHART = 'http://purl.oclc.org/ooxml/drawingml/chart';
+
const PURL_WORKSHEET = 'http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet';
}
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
index 4ea26438..08d578e6 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
@@ -284,9 +284,12 @@ class Chart extends WriterPart
$objWriter->endElement();
}
} elseif ($chartType === DataSeries::TYPE_BUBBLECHART) {
- $objWriter->startElement('c:bubbleScale');
- $objWriter->writeAttribute('val', 25);
- $objWriter->endElement();
+ $scale = ($plotGroup === null) ? '' : (string) $plotGroup->getPlotStyle();
+ if ($scale !== '') {
+ $objWriter->startElement('c:bubbleScale');
+ $objWriter->writeAttribute('val', $scale);
+ $objWriter->endElement();
+ }
$objWriter->startElement('c:showNegBubbles');
$objWriter->writeAttribute('val', 0);
@@ -1326,7 +1329,23 @@ class Chart extends WriterPart
}
if ($groupType === DataSeries::TYPE_BUBBLECHART) {
- $this->writeBubbles($plotSeriesValues, $objWriter);
+ if (!empty($plotGroup->getPlotBubbleSizes()[$plotSeriesIdx])) {
+ $objWriter->startElement('c:bubbleSize');
+ $this->writePlotSeriesValues(
+ $plotGroup->getPlotBubbleSizes()[$plotSeriesIdx],
+ $objWriter,
+ $groupType,
+ 'num'
+ );
+ $objWriter->endElement();
+ if ($plotSeriesValues !== false) {
+ $objWriter->startElement('c:bubble3D');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getBubble3D() ? '1' : '0');
+ $objWriter->endElement();
+ }
+ } else {
+ $this->writeBubbles($plotSeriesValues, $objWriter);
+ }
}
$objWriter->endElement();
@@ -1420,38 +1439,43 @@ class Chart extends WriterPart
$objWriter->writeRawData($plotSeriesValues->getDataSource());
$objWriter->endElement();
- $objWriter->startElement('c:' . $dataType . 'Cache');
+ $count = $plotSeriesValues->getPointCount();
+ $source = $plotSeriesValues->getDataSource();
+ $values = $plotSeriesValues->getDataValues();
+ if ($count > 1 || ($count === 1 && "=$source" !== (string) $values[0])) {
+ $objWriter->startElement('c:' . $dataType . 'Cache');
- if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) {
- if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) {
- $objWriter->startElement('c:formatCode');
- $objWriter->writeRawData($plotSeriesValues->getFormatCode());
- $objWriter->endElement();
- }
- }
-
- $objWriter->startElement('c:ptCount');
- $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
- $objWriter->endElement();
-
- $dataValues = $plotSeriesValues->getDataValues();
- if (!empty($dataValues)) {
- if (is_array($dataValues)) {
- foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
- $objWriter->startElement('c:pt');
- $objWriter->writeAttribute('idx', $plotSeriesKey);
-
- $objWriter->startElement('c:v');
- $objWriter->writeRawData($plotSeriesValue);
- $objWriter->endElement();
+ if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) {
+ if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) {
+ $objWriter->startElement('c:formatCode');
+ $objWriter->writeRawData($plotSeriesValues->getFormatCode());
$objWriter->endElement();
}
}
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
+ $objWriter->endElement();
+
+ $dataValues = $plotSeriesValues->getDataValues();
+ if (!empty($dataValues)) {
+ if (is_array($dataValues)) {
+ foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey);
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData($plotSeriesValue);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ $objWriter->endElement(); // *Cache
}
- $objWriter->endElement();
-
- $objWriter->endElement();
+ $objWriter->endElement(); // *Ref
}
}
diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
index 33eee1e0..7693c72c 100644
--- a/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
+++ b/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
@@ -108,6 +108,19 @@ class Drawing extends WriterPart
$objWriter->writeElement('xdr:row', (string) ($brColRow[1] - 1));
$objWriter->writeElement('xdr:rowOff', self::stringEmu($br['yOffset']));
$objWriter->endElement();
+ } elseif ($chart->getOneCellAnchor()) {
+ $objWriter->startElement('xdr:oneCellAnchor');
+
+ $objWriter->startElement('xdr:from');
+ $objWriter->writeElement('xdr:col', (string) ($tlColRow[0] - 1));
+ $objWriter->writeElement('xdr:colOff', self::stringEmu($tl['xOffset']));
+ $objWriter->writeElement('xdr:row', (string) ($tlColRow[1] - 1));
+ $objWriter->writeElement('xdr:rowOff', self::stringEmu($tl['yOffset']));
+ $objWriter->endElement();
+ $objWriter->startElement('xdr:ext');
+ $objWriter->writeAttribute('cx', self::stringEmu($br['xOffset']));
+ $objWriter->writeAttribute('cy', self::stringEmu($br['yOffset']));
+ $objWriter->endElement();
} else {
$objWriter->startElement('xdr:absoluteAnchor');
$objWriter->startElement('xdr:pos');
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsOpenpyxlTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsOpenpyxlTest.php
new file mode 100644
index 00000000..a7343af5
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ChartsOpenpyxlTest.php
@@ -0,0 +1,115 @@
+setIncludeCharts(true);
+ $spreadsheet = $reader->load($file);
+ $sheet = $spreadsheet->getActiveSheet();
+ self::assertSame(1, $sheet->getChartCount());
+
+ self::assertSame('Sheet', $sheet->getTitle());
+ $charts = $sheet->getChartCollection();
+ self::assertCount(1, $charts);
+ $chart = $charts[0];
+ self::assertNotNull($chart);
+ self::assertEmpty($chart->getTitle());
+ self::assertTrue($chart->getOneCellAnchor());
+
+ $plotArea = $chart->getPlotArea();
+ $plotSeries = $plotArea->getPlotGroup();
+ self::assertCount(1, $plotSeries);
+ $dataSeries = $plotSeries[0];
+ $labels = $dataSeries->getPlotLabels();
+ self::assertCount(2, $labels);
+ self::assertSame(['2013'], $labels[0]->getDataValues());
+ self::assertSame(['2014'], $labels[1]->getDataValues());
+
+ $plotCategories = $dataSeries->getPlotCategories();
+ self::assertCount(2, $plotCategories);
+ $categories = $plotCategories[0];
+ self::assertSame('Number', $categories->getDataType());
+ self::assertSame('\'Sheet\'!$A$2:$A$5', $categories->getDataSource());
+ self::assertFalse($categories->getBubble3D());
+ $categories = $plotCategories[1];
+ self::assertCount(2, $plotCategories);
+ self::assertSame('Number', $categories->getDataType());
+ self::assertSame('\'Sheet\'!$A$7:$A$10', $categories->getDataSource());
+ self::assertFalse($categories->getBubble3D());
+
+ $plotValues = $dataSeries->getPlotValues();
+ self::assertCount(2, $plotValues);
+ $values = $plotValues[0];
+ self::assertSame('Number', $values->getDataType());
+ self::assertSame('\'Sheet\'!$B$2:$B$5', $values->getDataSource());
+ self::assertFalse($values->getBubble3D());
+ $values = $plotValues[1];
+ self::assertCount(2, $plotValues);
+ self::assertSame('Number', $values->getDataType());
+ self::assertSame('\'Sheet\'!$B$7:$B$10', $values->getDataSource());
+ self::assertFalse($values->getBubble3D());
+
+ $plotValues = $dataSeries->getPlotBubbleSizes();
+ self::assertCount(2, $plotValues);
+ $values = $plotValues[0];
+ self::assertSame('Number', $values->getDataType());
+ self::assertSame('\'Sheet\'!$C$2:$C$5', $values->getDataSource());
+ self::assertFalse($values->getBubble3D());
+ $values = $plotValues[1];
+ self::assertCount(2, $plotValues);
+ self::assertSame('Number', $values->getDataType());
+ self::assertSame('\'Sheet\'!$C$7:$C$10', $values->getDataSource());
+ self::assertFalse($values->getBubble3D());
+
+ $spreadsheet->disconnectWorksheets();
+ }
+
+ public function testXml(): void
+ {
+ $infile = self::DIRECTORY . '32readwriteBubbleChart2.xlsx';
+ $file = 'zip://';
+ $file .= $infile;
+ $file .= '#xl/charts/chart1.xml';
+ $data = file_get_contents($file);
+ // confirm that file contains expected tags
+ if ($data === false) {
+ self::fail('Unable to read file');
+ } else {
+ self::assertSame(0, substr_count($data, 'c:'), 'unusual choice of prefix');
+ self::assertSame(0, substr_count($data, 'bubbleScale'));
+ self::assertSame(1, substr_count($data, '2013'), 'v tag for 2013');
+ self::assertSame(1, substr_count($data, '2014'), 'v tag for 2014');
+ self::assertSame(0, substr_count($data, 'numCache'), 'no cached values');
+ }
+ $file = 'zip://';
+ $file .= $infile;
+ $file .= '#xl/drawings/_rels/drawing1.xml.rels';
+ $data = file_get_contents($file);
+ // confirm that file contains expected tags
+ if ($data === false) {
+ self::fail('Unable to read file');
+ } else {
+ self::assertSame(1, substr_count($data, 'Target="/xl/charts/chart1.xml"'), 'Unusual absolute address in drawing rels file');
+ }
+ $file = 'zip://';
+ $file .= $infile;
+ $file .= '#xl/worksheets/_rels/sheet1.xml.rels';
+ $data = file_get_contents($file);
+ // confirm that file contains expected tags
+ if ($data === false) {
+ self::fail('Unable to read file');
+ } else {
+ self::assertSame(1, substr_count($data, 'Target="/xl/drawings/drawing1.xml"'), 'Unusual absolute address in worksheet rels file');
+ }
+ }
+}