More Bubble Chart Fixes (#2856)

Continuing the work from #2828, #2841, #2846, and #2852. This is probably my last change in this area for a while.

Bubble charts can have bubbles of different sizes. Phpspreadsheet had not supported this. Openpyxl comes with sample code to generate such a chart. I was especially drawn to that solution because its namespace usage would have been unexpected before 2852. And it turned out to come with other surprises - use of absolute paths in the .rels files (PhpSpreadsheet expected only relative), use of a one-cell anchor to place the chart (PhpSpreadsheet expected two-cell anchor or absolute positioning), plaintext in the legend (Phpspreadsheet expected RichText), no cached values for chart data. Excel handles the file okay, and this PR makes sure PhpSpreadsheet does as well. This file is now Samples/Templates/32readwriteBubbleChart2, and is used for both generating a sample output file and in formal tests. A new sample in the 33* series demonstrates how to code these.
This commit is contained in:
oleibman 2022-05-28 18:45:47 -07:00 committed by GitHub
parent 273d16e6a3
commit ab52991dc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 408 additions and 39 deletions

View File

@ -4547,7 +4547,7 @@ parameters:
- -
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#"
count: 43 count: 42
path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php path: src/PhpSpreadsheet/Writer/Xlsx/Chart.php
- -

View File

@ -0,0 +1,124 @@
<?php
use PhpOffice\PhpSpreadsheet\Chart\Chart;
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
use PhpOffice\PhpSpreadsheet\Chart\Legend as ChartLegend;
use PhpOffice\PhpSpreadsheet\Chart\PlotArea;
use PhpOffice\PhpSpreadsheet\Chart\Title;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
require __DIR__ . '/../Header.php';
$spreadsheet = new Spreadsheet();
$worksheet = $spreadsheet->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);

Binary file not shown.

View File

@ -152,6 +152,9 @@ class Chart
/** @var ?int */ /** @var ?int */
private $perspective; private $perspective;
/** @var bool */
private $oneCellAnchor = false;
/** /**
* Create a new Chart. * Create a new Chart.
* *
@ -743,4 +746,16 @@ class Chart
return $this; return $this;
} }
public function getOneCellAnchor(): bool
{
return $this->oneCellAnchor;
}
public function setOneCellAnchor(bool $oneCellAnchor): self
{
$this->oneCellAnchor = $oneCellAnchor;
return $this;
}
} }

View File

@ -107,6 +107,13 @@ class DataSeries
*/ */
private $plotValues = []; private $plotValues = [];
/**
* Plot Bubble Sizes.
*
* @var DataSeriesValues[]
*/
private $plotBubbleSizes = [];
/** /**
* Create a new DataSeries. * Create a new DataSeries.
* *
@ -339,6 +346,28 @@ class DataSeries
return false; 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. * Get Number of Plot Series.
* *

View File

@ -328,7 +328,7 @@ class DataSeriesValues
*/ */
public function isMultiLevelSeries() public function isMultiLevelSeries()
{ {
if (count($this->dataValues) > 0) { if (!empty($this->dataValues)) {
return is_array(array_values($this->dataValues)[0]); return is_array(array_values($this->dataValues)[0]);
} }

View File

@ -157,6 +157,10 @@ class Xlsx extends BaseReader
Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_DRAWING, 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. * 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 // Read the theme first, because we need the colour scheme when reading the styles
[$workbookBasename, $xmlNamespaceBase] = $this->getWorkbookBaseName(); [$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); $wbRels = $this->loadZip("xl/_rels/${workbookBasename}.rels", Namespaces::RELATIONSHIPS);
$theme = null; $theme = null;
$this->styleReader = new Styles(); $this->styleReader = new Styles();
foreach ($wbRels->Relationship as $relx) { foreach ($wbRels->Relationship as $relx) {
$rel = self::getAttributes($relx); $rel = self::getAttributes($relx);
$relTarget = (string) $rel['Target']; $relTarget = (string) $rel['Target'];
if (substr($relTarget, 0, 4) === '/xl/') {
$relTarget = substr($relTarget, 4);
}
switch ($rel['Type']) { switch ($rel['Type']) {
case "$xmlNamespaceBase/theme": case "$xmlNamespaceBase/theme":
$themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2']; $themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2'];
$themeOrderAdditional = count($themeOrderArray); $themeOrderAdditional = count($themeOrderArray);
$drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML;
$xmlTheme = $this->loadZip("xl/{$relTarget}", $drawingNS); $xmlTheme = $this->loadZip("xl/{$relTarget}", $drawingNS);
$xmlThemeName = self::getAttributes($xmlTheme); $xmlThemeName = self::getAttributes($xmlTheme);
@ -1204,12 +1212,20 @@ class Xlsx extends BaseReader
. '/_rels/' . '/_rels/'
. basename($fileWorksheet) . basename($fileWorksheet)
. '.rels'; . '.rels';
if (substr($drawingFilename, 0, 7) === 'xl//xl/') {
$drawingFilename = substr($drawingFilename, 4);
}
if ($zip->locateName($drawingFilename)) { if ($zip->locateName($drawingFilename)) {
$relsWorksheet = $this->loadZipNoNamespace($drawingFilename, Namespaces::RELATIONSHIPS); $relsWorksheet = $this->loadZipNoNamespace($drawingFilename, Namespaces::RELATIONSHIPS);
$drawings = []; $drawings = [];
foreach ($relsWorksheet->Relationship as $ele) { foreach ($relsWorksheet->Relationship as $ele) {
if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") { 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']); $images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']);
} elseif ($eleType === "$xmlNamespaceBase/chart") { } elseif ($eleType === "$xmlNamespaceBase/chart") {
if ($this->includeCharts) { 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'], 'id' => (string) $ele['Id'],
'sheet' => $docSheet->getTitle(), 'sheet' => $docSheet->getTitle(),
]; ];
@ -1326,6 +1348,7 @@ class Xlsx extends BaseReader
'width' => $width, 'width' => $width,
'height' => $height, 'height' => $height,
'worksheetTitle' => $docSheet->getTitle(), 'worksheetTitle' => $docSheet->getTitle(),
'oneCellAnchor' => true,
]; ];
} }
} }
@ -1645,7 +1668,7 @@ class Xlsx extends BaseReader
if ($this->includeCharts) { if ($this->includeCharts) {
$chartEntryRef = ltrim((string) $contentType['PartName'], '/'); $chartEntryRef = ltrim((string) $contentType['PartName'], '/');
$chartElements = $this->loadZip($chartEntryRef); $chartElements = $this->loadZip($chartEntryRef);
$chartReader = new Chart(); $chartReader = new Chart($chartNS, $drawingNS);
$objChart = $chartReader->readChart($chartElements, basename($chartEntryRef, '.xml')); $objChart = $chartReader->readChart($chartElements, basename($chartEntryRef, '.xml'));
if (isset($charts[$chartEntryRef])) { if (isset($charts[$chartEntryRef])) {
$chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id']; $chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id'];
@ -1662,6 +1685,9 @@ class Xlsx extends BaseReader
// oneCellAnchor or absoluteAnchor (e.g. Chart sheet) // oneCellAnchor or absoluteAnchor (e.g. Chart sheet)
$objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']); $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
$objChart->setBottomRightPosition('', $chartDetails[$chartPositionRef]['width'], $chartDetails[$chartPositionRef]['height']); $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 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"); return (string) preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add");
} }

View File

@ -305,7 +305,7 @@ class Chart
{ {
$multiSeriesType = null; $multiSeriesType = null;
$smoothLine = false; $smoothLine = false;
$seriesLabel = $seriesCategory = $seriesValues = $plotOrder = []; $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = $seriesBubbles = [];
$seriesDetailSet = $chartDetail->children($this->cNamespace); $seriesDetailSet = $chartDetail->children($this->cNamespace);
foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) { foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) {
@ -382,6 +382,10 @@ class Chart
case 'yVal': case 'yVal':
$seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize"); $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize");
break;
case 'bubbleSize':
$seriesBubbles[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", "$srgbClr", "$pointSize");
break; break;
case 'bubble3D': case 'bubble3D':
$bubble3D = self::getAttribute($seriesDetail, 'val', 'boolean'); $bubble3D = self::getAttribute($seriesDetail, 'val', 'boolean');
@ -435,9 +439,11 @@ class Chart
} }
} }
} }
/** @phpstan-ignore-next-line */ /** @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; return $seriesValues;
} }
if (isset($seriesDetail->v)) {
return new DataSeriesValues(
DataSeriesValues::DATASERIES_TYPE_STRING,
null,
null,
1,
[(string) $seriesDetail->v]
);
}
return null; return null;
} }

View File

@ -76,5 +76,7 @@ class Namespaces
const PURL_DRAWING = 'http://purl.oclc.org/ooxml/drawingml/main'; 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'; const PURL_WORKSHEET = 'http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet';
} }

View File

@ -284,9 +284,12 @@ class Chart extends WriterPart
$objWriter->endElement(); $objWriter->endElement();
} }
} elseif ($chartType === DataSeries::TYPE_BUBBLECHART) { } elseif ($chartType === DataSeries::TYPE_BUBBLECHART) {
$objWriter->startElement('c:bubbleScale'); $scale = ($plotGroup === null) ? '' : (string) $plotGroup->getPlotStyle();
$objWriter->writeAttribute('val', 25); if ($scale !== '') {
$objWriter->endElement(); $objWriter->startElement('c:bubbleScale');
$objWriter->writeAttribute('val', $scale);
$objWriter->endElement();
}
$objWriter->startElement('c:showNegBubbles'); $objWriter->startElement('c:showNegBubbles');
$objWriter->writeAttribute('val', 0); $objWriter->writeAttribute('val', 0);
@ -1326,7 +1329,23 @@ class Chart extends WriterPart
} }
if ($groupType === DataSeries::TYPE_BUBBLECHART) { 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(); $objWriter->endElement();
@ -1420,38 +1439,43 @@ class Chart extends WriterPart
$objWriter->writeRawData($plotSeriesValues->getDataSource()); $objWriter->writeRawData($plotSeriesValues->getDataSource());
$objWriter->endElement(); $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 (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) {
if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) { if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) {
$objWriter->startElement('c:formatCode'); $objWriter->startElement('c:formatCode');
$objWriter->writeRawData($plotSeriesValues->getFormatCode()); $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();
} }
} }
$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(); // *Ref
$objWriter->endElement();
} }
} }

View File

@ -108,6 +108,19 @@ class Drawing extends WriterPart
$objWriter->writeElement('xdr:row', (string) ($brColRow[1] - 1)); $objWriter->writeElement('xdr:row', (string) ($brColRow[1] - 1));
$objWriter->writeElement('xdr:rowOff', self::stringEmu($br['yOffset'])); $objWriter->writeElement('xdr:rowOff', self::stringEmu($br['yOffset']));
$objWriter->endElement(); $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 { } else {
$objWriter->startElement('xdr:absoluteAnchor'); $objWriter->startElement('xdr:absoluteAnchor');
$objWriter->startElement('xdr:pos'); $objWriter->startElement('xdr:pos');

View File

@ -0,0 +1,115 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
use PHPUnit\Framework\TestCase;
class ChartsOpenpyxlTest extends TestCase
{
private const DIRECTORY = 'samples' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR;
public function testBubble2(): void
{
$file = self::DIRECTORY . '32readwriteBubbleChart2.xlsx';
$reader = new XlsxReader();
$reader->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, '<tx><v>2013</v></tx>'), 'v tag for 2013');
self::assertSame(1, substr_count($data, '<tx><v>2014</v></tx>'), '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');
}
}
}