From b933174f65ddf7e009215f3d621a4e462b797cc8 Mon Sep 17 00:00:00 2001 From: Progi1984 Date: Mon, 26 Jul 2021 13:55:34 +0200 Subject: [PATCH] Support for managing missing values in Chart --- docs/changes/1.0.0.md | 3 + .../libreoffice_chart_displayblankas.png | Bin 0 -> 18773 bytes docs/usage/presentation.md | 2 +- docs/usage/shapes/chart.md | 25 ++++ samples/Sample_05_Chart_Line.php | 38 ++++-- src/PhpPresentation/Shape/Chart.php | 37 ++++++ src/PhpPresentation/Shape/Chart/Series.php | 19 ++- .../Writer/ODPresentation/ObjectsChart.php | 78 +++++++------ .../Writer/PowerPoint2007/PptCharts.php | 5 + .../PhpPresentation/Tests/Shape/ChartTest.php | 39 +++++-- .../ODPresentation/ObjectsChartTest.php | 108 +++++++++++++++++- .../Writer/PowerPoint2007/PptChartsTest.php | 42 +++++++ 12 files changed, 335 insertions(+), 61 deletions(-) create mode 100644 docs/images/libreoffice_chart_displayblankas.png diff --git a/docs/changes/1.0.0.md b/docs/changes/1.0.0.md index aea01fef3..230992c33 100644 --- a/docs/changes/1.0.0.md +++ b/docs/changes/1.0.0.md @@ -40,6 +40,9 @@ - Support for rotation for axis label - @Progi1986 GH-410 - ODPresentation Writer - PowerPoint2007 Writer +- Support for managing missing values in Chart - @TonisOrmisson GH-581 & @Progi1986 GH-659 + - ODPresentation Writer + - PowerPoint2007 Writer ## Project Management - Migrated from Travis CI to Github Actions - @Progi1984 GH-635 diff --git a/docs/images/libreoffice_chart_displayblankas.png b/docs/images/libreoffice_chart_displayblankas.png new file mode 100644 index 0000000000000000000000000000000000000000..c37331d778709bcaedc22a8bda0e912569d4ca26 GIT binary patch literal 18773 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 literal 0 HcmV?d00001 diff --git a/docs/usage/presentation.md b/docs/usage/presentation.md index 2680474c1..7e34e4684 100644 --- a/docs/usage/presentation.md +++ b/docs/usage/presentation.md @@ -138,7 +138,7 @@ $state = $presentation->isMarkedAsFinal(); You can define the type of slideshow you can with the method `setSlideshowType`. -![Slideshow type](/PHPPresentation/images/presentation_slideshow_type.png) +![Slideshow type](/images/presentation_slideshow_type.png) Differents types are available: diff --git a/docs/usage/shapes/chart.md b/docs/usage/shapes/chart.md index ace7e3f12..0e6c8b315 100644 --- a/docs/usage/shapes/chart.md +++ b/docs/usage/shapes/chart.md @@ -8,6 +8,31 @@ To create a chart, use `createChartShape` method of Slide. $chartShape = $slide->createChartShape(); ``` +## Customization + +### Manage how blank values are displayed + +You can define how blank values are displayed with the method `setDisplayBlankAs`. + +![Slideshow type](/images/libreoffice_chart_displayblankas.png) + +Differents types are available: + +* `Chart::BLANKAS_GAP` for **Leave a gap** +* `Chart::BLANKAS_ZERO` for **Assume zero** (default) +* `Chart::BLANKAS_SPAN` for **Continue line** + +``` php +setDisplayBlankAs(Chart::BLANKAS_GAP); +// Get the behavior +echo $chart->getDisplayBlankAs(); +``` + ## Parts ### Axis diff --git a/samples/Sample_05_Chart_Line.php b/samples/Sample_05_Chart_Line.php index 84b232b64..c0c59ef44 100644 --- a/samples/Sample_05_Chart_Line.php +++ b/samples/Sample_05_Chart_Line.php @@ -3,11 +3,15 @@ include_once 'Sample_Header.php'; use PhpOffice\PhpPresentation\PhpPresentation; +use PhpOffice\PhpPresentation\Shape\Chart; +use PhpOffice\PhpPresentation\Shape\Chart\Gridlines; +use PhpOffice\PhpPresentation\Shape\Chart\Marker; use PhpOffice\PhpPresentation\Shape\Chart\Series; use PhpOffice\PhpPresentation\Shape\Chart\Type\Line; use PhpOffice\PhpPresentation\Style\Border; use PhpOffice\PhpPresentation\Style\Color; use PhpOffice\PhpPresentation\Style\Fill; +use PhpOffice\PhpPresentation\Style\Outline; use PhpOffice\PhpPresentation\Style\Shadow; // Create new PHPPresentation object @@ -73,7 +77,7 @@ $currentSlide = createTemplatedSlide($objPHPPresentation); // Create a line chart (that should be inserted in a shape) -$oOutline = new \PhpOffice\PhpPresentation\Style\Outline(); +$oOutline = new Outline(); $oOutline->getFill()->setFillType(Fill::FILL_SOLID); $oOutline->getFill()->setStartColor(new Color(Color::COLOR_YELLOW)); $oOutline->setWidth(2); @@ -82,7 +86,7 @@ $lineChart1 = clone $lineChart; $series1 = $lineChart1->getSeries(); $series1[0]->setOutline($oOutline); -$series1[0]->getMarker()->setSymbol(\PhpOffice\PhpPresentation\Shape\Chart\Marker::SYMBOL_DIAMOND); +$series1[0]->getMarker()->setSymbol(Marker::SYMBOL_DIAMOND); $series1[0]->getMarker()->setSize(7); $lineChart1->setSeries($series1); @@ -106,7 +110,7 @@ $lineChart2 = clone $lineChart; $series2 = $lineChart2->getSeries(); $series2[0]->getFont()->setSize(25); -$series2[0]->getMarker()->setSymbol(\PhpOffice\PhpPresentation\Shape\Chart\Marker::SYMBOL_TRIANGLE); +$series2[0]->getMarker()->setSymbol(Marker::SYMBOL_TRIANGLE); $series2[0]->getMarker()->setSize(10); $lineChart2->setSeries($series2); @@ -129,11 +133,11 @@ echo date('H:i:s') . ' Create a line chart (that should be inserted in a chart shape)' . EOL; $lineChart3 = clone $lineChart; -$oGridLines1 = new \PhpOffice\PhpPresentation\Shape\Chart\Gridlines(); +$oGridLines1 = new Gridlines(); $oGridLines1->getOutline()->setWidth(10); $oGridLines1->getOutline()->getFill()->setFillType(Fill::FILL_SOLID)->setStartColor(new Color(Color::COLOR_BLUE)); -$oGridLines2 = new \PhpOffice\PhpPresentation\Shape\Chart\Gridlines(); +$oGridLines2 = new Gridlines(); $oGridLines2->getOutline()->setWidth(1); $oGridLines2->getOutline()->getFill()->setFillType(Fill::FILL_SOLID)->setStartColor(new Color(Color::COLOR_DARKGREEN)); @@ -156,12 +160,12 @@ echo date('H:i:s') . ' Create a line chart (that should be inserted in a chart shape)' . EOL; $lineChart4 = clone $lineChart; -$oOutlineAxisX = new \PhpOffice\PhpPresentation\Style\Outline(); +$oOutlineAxisX = new Outline(); $oOutlineAxisX->setWidth(2); $oOutlineAxisX->getFill()->setFillType(Fill::FILL_SOLID); $oOutlineAxisX->getFill()->getStartColor()->setRGB('012345'); -$oOutlineAxisY = new \PhpOffice\PhpPresentation\Style\Outline(); +$oOutlineAxisY = new Outline(); $oOutlineAxisY->setWidth(5); $oOutlineAxisY->getFill()->setFillType(Fill::FILL_SOLID); $oOutlineAxisY->getFill()->getStartColor()->setRGB('ABCDEF'); @@ -190,6 +194,26 @@ $shape5->getPlotArea()->getAxisY()->setMinBounds(5); $shape5->getPlotArea()->getAxisY()->setMaxBounds(20); $currentSlide->addShape($shape5); + +// Create templated slide +echo EOL . date('H:i:s') . ' Create templated slide #6' . EOL; +$currentSlide = createTemplatedSlide($objPHPPresentation); + +// Create a shape (chart) +echo date('H:i:s') . ' Create a shape (chart6)' . EOL; +echo date('H:i:s') . ' Feature : DisplayBlankAs' . EOL; +$shape6 = clone $shape; +$lineChart6 = clone $lineChart; +$series6 = clone $series; +$seriesData6 = $seriesData; +$seriesData6['Thursday'] = null; + +$series6->setValues($seriesData6); +$lineChart6->setSeries([$series6]); +$shape6->getPlotArea()->setType($lineChart6); +$shape6->setDisplayBlankAs(Chart::BLANKAS_GAP); +$currentSlide->addShape($shape6); + // Save file echo EOL . write($objPHPPresentation, basename(__FILE__, '.php'), $writers); diff --git a/src/PhpPresentation/Shape/Chart.php b/src/PhpPresentation/Shape/Chart.php index d840ccb3b..7cebcb3b2 100644 --- a/src/PhpPresentation/Shape/Chart.php +++ b/src/PhpPresentation/Shape/Chart.php @@ -29,6 +29,10 @@ */ class Chart extends AbstractGraphic implements ComparableInterface { + public const BLANKAS_GAP = 'gap'; + public const BLANKAS_ZERO = 'zero'; + public const BLANKAS_SPAN = 'span'; + /** * Title. * @@ -64,6 +68,13 @@ class Chart extends AbstractGraphic implements ComparableInterface */ private $includeSpreadsheet = false; + /** + * How to display blank (missing) values? Not set by default. + * + * @var string + */ + private $displayBlankAs = self::BLANKAS_ZERO; + /** * Create a new Chart. */ @@ -89,6 +100,16 @@ public function __clone() $this->view3D = clone $this->view3D; } + /** + * How missing/blank values are displayed on chart (dispBlanksAs property) + * + * @return string + */ + public function getDisplayBlankAs(): string + { + return $this->displayBlankAs; + } + /** * Get Title. * @@ -139,6 +160,22 @@ public function hasIncludedSpreadsheet(): bool return $this->includeSpreadsheet; } + /** + * Define a way to display missing/blank values (dispBlanksAs property) + * + * @param string $value + * + * @return self + */ + public function setDisplayBlankAs(string $value): self + { + if (in_array($value, [self::BLANKAS_GAP, self::BLANKAS_SPAN, self::BLANKAS_ZERO])) { + $this->displayBlankAs = $value; + } + + return $this; + } + /** * Is the spreadsheet included for editing data ? * diff --git a/src/PhpPresentation/Shape/Chart/Series.php b/src/PhpPresentation/Shape/Chart/Series.php index 15616fa2d..b60d03f24 100644 --- a/src/PhpPresentation/Shape/Chart/Series.php +++ b/src/PhpPresentation/Shape/Chart/Series.php @@ -132,7 +132,7 @@ class Series implements ComparableInterface /** * Values (key/value). * - * @var array + * @var array */ private $values = []; @@ -144,7 +144,8 @@ class Series implements ComparableInterface private $hashIndex; /** - * @param array $values + * @param string $title + * @param array $values */ public function __construct(string $title = 'Series Title', array $values = []) { @@ -152,9 +153,10 @@ public function __construct(string $title = 'Series Title', array $values = []) $this->font = new Font(); $this->font->setName('Calibri'); $this->font->setSize(9); + $this->marker = new Marker(); + $this->title = $title; $this->values = $values; - $this->marker = new Marker(); } /** @@ -239,7 +241,7 @@ public function getDataPointFills(): array /** * Get Values. * - * @return array + * @return array */ public function getValues(): array { @@ -249,7 +251,7 @@ public function getValues(): array /** * Set Values. * - * @param array $values + * @param array $values */ public function setValues(array $values = []): self { @@ -260,8 +262,13 @@ public function setValues(array $values = []): self /** * Add Value. + * + * @param string $key + * @param string|null $value + * + * @return self */ - public function addValue(string $key, string $value): self + public function addValue(string $key, ?string $value): self { $this->values[$key] = $value; diff --git a/src/PhpPresentation/Writer/ODPresentation/ObjectsChart.php b/src/PhpPresentation/Writer/ODPresentation/ObjectsChart.php index ce2473c08..fc53d0e23 100644 --- a/src/PhpPresentation/Writer/ODPresentation/ObjectsChart.php +++ b/src/PhpPresentation/Writer/ODPresentation/ObjectsChart.php @@ -544,6 +544,17 @@ private function writePlotAreaStyle(Chart $chart): void $this->xmlContent->writeAttribute('chart:three-dimensional', 'true'); $this->xmlContent->writeAttribute('chart:right-angled-axes', 'true'); } + switch ($chart->getDisplayBlankAs()) { + case Chart::BLANKAS_ZERO: + $this->xmlContent->writeAttribute('chart:treat-empty-cells', 'use-zero'); + break; + case Chart::BLANKAS_GAP: + $this->xmlContent->writeAttribute('chart:treat-empty-cells', 'leave-gap'); + break; + case Chart::BLANKAS_SPAN: + $this->xmlContent->writeAttribute('chart:treat-empty-cells', 'ignore'); + break; + } if ($chartType instanceof AbstractTypeBar) { $chartVertical = 'false'; if (AbstractTypeBar::DIRECTION_HORIZONTAL == $chartType->getBarDirection()) { @@ -809,40 +820,6 @@ private function writeTable(): void // > table:table-header-columns $this->xmlContent->endElement(); - // table:table-rows - $this->xmlContent->startElement('table:table-rows'); - if (empty($this->arrayData)) { - $this->xmlContent->startElement('table:table-row'); - $this->xmlContent->startElement('table:table-cell'); - $this->xmlContent->endElement(); - $this->xmlContent->endElement(); - } else { - foreach ($this->arrayData as $row) { - // table:table-row - $this->xmlContent->startElement('table:table-row'); - foreach ($row as $cell) { - // table:table-cell - $this->xmlContent->startElement('table:table-cell'); - - $cellNumeric = is_numeric($cell); - $this->xmlContent->writeAttributeIf(!$cellNumeric, 'office:value-type', 'string'); - $this->xmlContent->writeAttributeIf($cellNumeric, 'office:value-type', 'float'); - $this->xmlContent->writeAttributeIf($cellNumeric, 'office:value', $cell); - // text:p - $this->xmlContent->startElement('text:p'); - $this->xmlContent->text($cell); - // > text:p - $this->xmlContent->endElement(); - // > table:table-cell - $this->xmlContent->endElement(); - } - // > table:table-row - $this->xmlContent->endElement(); - } - } - // > table:table-rows - $this->xmlContent->endElement(); - // table:table-header-rows $this->xmlContent->startElement('table:table-header-rows'); // table:table-row @@ -874,6 +851,39 @@ private function writeTable(): void // > table:table-header-rows $this->xmlContent->endElement(); + // table:table-rows + $this->xmlContent->startElement('table:table-rows'); + if (empty($this->arrayData)) { + $this->xmlContent->startElement('table:table-row'); + $this->xmlContent->startElement('table:table-cell'); + $this->xmlContent->endElement(); + $this->xmlContent->endElement(); + } else { + foreach ($this->arrayData as $row) { + // table:table-row + $this->xmlContent->startElement('table:table-row'); + foreach ($row as $cell) { + // table:table-cell + $this->xmlContent->startElement('table:table-cell'); + + $cellValueTypeFloat = is_null($cell) ? true : is_numeric($cell); + $this->xmlContent->writeAttributeIf(!$cellValueTypeFloat, 'office:value-type', 'string'); + $this->xmlContent->writeAttributeIf($cellValueTypeFloat, 'office:value-type', 'float'); + $this->xmlContent->writeAttributeIf($cellValueTypeFloat, 'office:value', is_null($cell) ? 'NaN' : $cell); + // text:p + $this->xmlContent->startElement('text:p'); + $this->xmlContent->text(is_null($cell) ? 'NaN' : $cell); + $this->xmlContent->endElement(); + // > table:table-cell + $this->xmlContent->endElement(); + } + // > table:table-row + $this->xmlContent->endElement(); + } + } + // > table:table-rows + $this->xmlContent->endElement(); + // > table:table $this->xmlContent->endElement(); } diff --git a/src/PhpPresentation/Writer/PowerPoint2007/PptCharts.php b/src/PhpPresentation/Writer/PowerPoint2007/PptCharts.php index a90b16ade..a031d0bc6 100644 --- a/src/PhpPresentation/Writer/PowerPoint2007/PptCharts.php +++ b/src/PhpPresentation/Writer/PowerPoint2007/PptCharts.php @@ -146,6 +146,11 @@ public function writeChart(Chart $chart): string $objWriter->writeAttribute('val', '1'); $objWriter->endElement(); + // c:dispBlanksAs + $objWriter->startElement('c:dispBlanksAs'); + $objWriter->writeAttribute('val', $chart->getDisplayBlankAs()); + $objWriter->endElement(); + $objWriter->endElement(); // c:spPr diff --git a/tests/PhpPresentation/Tests/Shape/ChartTest.php b/tests/PhpPresentation/Tests/Shape/ChartTest.php index 0195aa19f..a042fa8df 100644 --- a/tests/PhpPresentation/Tests/Shape/ChartTest.php +++ b/tests/PhpPresentation/Tests/Shape/ChartTest.php @@ -32,10 +32,10 @@ public function testConstruct(): void { $object = new Chart(); - $this->assertInstanceOf('PhpOffice\\PhpPresentation\\Shape\\Chart\\Title', $object->getTitle()); - $this->assertInstanceOf('PhpOffice\\PhpPresentation\\Shape\\Chart\\Legend', $object->getLegend()); - $this->assertInstanceOf('PhpOffice\\PhpPresentation\\Shape\\Chart\\PlotArea', $object->getPlotArea()); - $this->assertInstanceOf('PhpOffice\\PhpPresentation\\Shape\\Chart\\View3D', $object->getView3D()); + $this->assertInstanceOf(Chart\Title::class, $object->getTitle()); + $this->assertInstanceOf(Chart\Legend::class, $object->getLegend()); + $this->assertInstanceOf(Chart\PlotArea::class, $object->getPlotArea()); + $this->assertInstanceOf(Chart\View3D::class, $object->getView3D()); } public function testClone(): void @@ -44,11 +44,26 @@ public function testClone(): void $oClone = clone $object; - $this->assertInstanceOf('PhpOffice\\PhpPresentation\\Shape\\Chart', $oClone); - $this->assertInstanceOf('PhpOffice\\PhpPresentation\\Shape\\Chart\\Title', $oClone->getTitle()); - $this->assertInstanceOf('PhpOffice\\PhpPresentation\\Shape\\Chart\\Legend', $oClone->getLegend()); - $this->assertInstanceOf('PhpOffice\\PhpPresentation\\Shape\\Chart\\PlotArea', $oClone->getPlotArea()); - $this->assertInstanceOf('PhpOffice\\PhpPresentation\\Shape\\Chart\\View3D', $oClone->getView3D()); + $this->assertInstanceOf(Chart::class, $oClone); + $this->assertInstanceOf(Chart\Title::class, $oClone->getTitle()); + $this->assertInstanceOf(Chart\Legend::class, $oClone->getLegend()); + $this->assertInstanceOf(Chart\PlotArea::class, $oClone->getPlotArea()); + $this->assertInstanceOf(Chart\View3D::class, $oClone->getView3D()); + } + + public function testDisplayBlankAs(): void + { + $object = new Chart(); + + $this->assertEquals(Chart::BLANKAS_ZERO, $object->getDisplayBlankAs()); + $this->assertInstanceOf(Chart::class, $object->setDisplayBlankAs(Chart::BLANKAS_GAP)); + $this->assertEquals(Chart::BLANKAS_GAP, $object->getDisplayBlankAs()); + $this->assertInstanceOf(Chart::class, $object->setDisplayBlankAs(Chart::BLANKAS_ZERO)); + $this->assertEquals(Chart::BLANKAS_ZERO, $object->getDisplayBlankAs()); + $this->assertInstanceOf(Chart::class, $object->setDisplayBlankAs(Chart::BLANKAS_SPAN)); + $this->assertEquals(Chart::BLANKAS_SPAN, $object->getDisplayBlankAs()); + $this->assertInstanceOf(Chart::class, $object->setDisplayBlankAs('Unauthorized value')); + $this->assertEquals(Chart::BLANKAS_SPAN, $object->getDisplayBlankAs()); } public function testIncludeSpreadsheet(): void @@ -56,11 +71,11 @@ public function testIncludeSpreadsheet(): void $object = new Chart(); $this->assertFalse($object->hasIncludedSpreadsheet()); - $this->assertInstanceOf('PhpOffice\\PhpPresentation\\Shape\\Chart', $object->setIncludeSpreadsheet()); + $this->assertInstanceOf(Chart::class, $object->setIncludeSpreadsheet()); $this->assertFalse($object->hasIncludedSpreadsheet()); - $this->assertInstanceOf('PhpOffice\\PhpPresentation\\Shape\\Chart', $object->setIncludeSpreadsheet(false)); + $this->assertInstanceOf(Chart::class, $object->setIncludeSpreadsheet(false)); $this->assertFalse($object->hasIncludedSpreadsheet()); - $this->assertInstanceOf('PhpOffice\\PhpPresentation\\Shape\\Chart', $object->setIncludeSpreadsheet(true)); + $this->assertInstanceOf(Chart::class, $object->setIncludeSpreadsheet(true)); $this->assertTrue($object->hasIncludedSpreadsheet()); } } diff --git a/tests/PhpPresentation/Tests/Writer/ODPresentation/ObjectsChartTest.php b/tests/PhpPresentation/Tests/Writer/ODPresentation/ObjectsChartTest.php index 3ca0b8c0d..89b149341 100644 --- a/tests/PhpPresentation/Tests/Writer/ODPresentation/ObjectsChartTest.php +++ b/tests/PhpPresentation/Tests/Writer/ODPresentation/ObjectsChartTest.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpPresentation\Tests\Writer\ODPresentation; use PhpOffice\Common\Drawing as CommonDrawing; +use PhpOffice\PhpPresentation\Shape\Chart; use PhpOffice\PhpPresentation\Shape\Chart\Gridlines; use PhpOffice\PhpPresentation\Shape\Chart\Legend; use PhpOffice\PhpPresentation\Shape\Chart\Marker; @@ -166,6 +167,46 @@ public function testAxisVisibility(): void $this->assertIsSchemaOpenDocumentNotValid('1.2'); } + public function testChartDisplayBlankAs(): void + { + $oSeries = new Series('Downloads', $this->seriesData); + + $oLine = new Line(); + $oLine->addSeries($oSeries); + + $oShape = $this->oPresentation->getActiveSlide()->createChartShape(); + $oShape->getPlotArea()->setType($oLine); + $oShape->setDisplayBlankAs(Chart::BLANKAS_ZERO); + + $element = '/office:document-content/office:automatic-styles/style:style[@style:name=\'stylePlotArea\']/style:chart-properties'; + $this->assertZipFileExists('Object 1/content.xml'); + $this->assertZipXmlElementExists('Object 1/content.xml', $element); + $this->assertZipXmlAttributeExists('Object 1/content.xml', $element, 'chart:treat-empty-cells'); + $this->assertZipXmlAttributeEquals('Object 1/content.xml', $element, 'chart:treat-empty-cells', 'use-zero'); + // chart:title : Element chart failed to validate attributes + $this->assertIsSchemaOpenDocumentNotValid('1.2'); + + $this->resetPresentationFile(); + $oShape->setDisplayBlankAs(Chart::BLANKAS_SPAN); + + $this->assertZipFileExists('Object 1/content.xml'); + $this->assertZipXmlElementExists('Object 1/content.xml', $element); + $this->assertZipXmlAttributeExists('Object 1/content.xml', $element, 'chart:treat-empty-cells'); + $this->assertZipXmlAttributeEquals('Object 1/content.xml', $element, 'chart:treat-empty-cells', 'ignore'); + // chart:title : Element chart failed to validate attributes + $this->assertIsSchemaOpenDocumentNotValid('1.2'); + + $this->resetPresentationFile(); + $oShape->setDisplayBlankAs(Chart::BLANKAS_GAP); + + $this->assertZipFileExists('Object 1/content.xml'); + $this->assertZipXmlElementExists('Object 1/content.xml', $element); + $this->assertZipXmlAttributeExists('Object 1/content.xml', $element, 'chart:treat-empty-cells'); + $this->assertZipXmlAttributeEquals('Object 1/content.xml', $element, 'chart:treat-empty-cells', 'leave-gap'); + // chart:title : Element chart failed to validate attributes + $this->assertIsSchemaOpenDocumentNotValid('1.2'); + } + public function testLegend(): void { $oSeries = new Series('Series', ['Jan' => '1', 'Feb' => '5', 'Mar' => '2']); @@ -238,7 +279,72 @@ public function testLegend(): void $this->assertIsSchemaOpenDocumentNotValid('1.2'); } - public function testSeries(): void + public function testSeriesValues(): void + { + $series = new Series('Series', ['Jan' => null]); + + $pie = new Pie(); + $pie->addSeries($series); + + $chart = $this->oPresentation->getActiveSlide()->createChartShape(); + $chart->getPlotArea()->setType($pie); + + $element = '/office:document-content/office:body/office:chart/chart:chart/table:table/table:table-rows/table:table-row/table:table-cell[2]'; + + $this->assertZipXmlElementExists('Object 1/content.xml', $element); + $this->assertZipXmlAttributeExists('Object 1/content.xml', $element, 'office:value-type'); + $this->assertZipXmlAttributeEquals('Object 1/content.xml', $element, 'office:value-type', 'float'); + $this->assertZipXmlAttributeExists('Object 1/content.xml', $element, 'office:value'); + $this->assertZipXmlAttributeEquals('Object 1/content.xml', $element, 'office:value', 'NaN'); + + $element = '/office:document-content/office:body/office:chart/chart:chart/table:table/table:table-rows/table:table-row/table:table-cell[2]/text:p'; + + $this->assertZipXmlElementExists('Object 1/content.xml', $element); + $this->assertZipXmlElementEquals('Object 1/content.xml', $element, 'NaN'); + // chart:title : Element chart failed to validate attributes + $this->assertIsSchemaOpenDocumentNotValid('1.2'); + + $this->resetPresentationFile(); + + $series = new Series('Series', ['Jan' => '12.3']); + $chart->getPlotArea()->getType()->setSeries([$series]); + + $element = '/office:document-content/office:body/office:chart/chart:chart/table:table/table:table-rows/table:table-row/table:table-cell[2]'; + + $this->assertZipXmlElementExists('Object 1/content.xml', $element); + $this->assertZipXmlAttributeExists('Object 1/content.xml', $element, 'office:value-type'); + $this->assertZipXmlAttributeEquals('Object 1/content.xml', $element, 'office:value-type', 'float'); + $this->assertZipXmlAttributeExists('Object 1/content.xml', $element, 'office:value'); + $this->assertZipXmlAttributeEquals('Object 1/content.xml', $element, 'office:value', '12.3'); + + $element = '/office:document-content/office:body/office:chart/chart:chart/table:table/table:table-rows/table:table-row/table:table-cell[2]/text:p'; + + $this->assertZipXmlElementExists('Object 1/content.xml', $element); + $this->assertZipXmlElementEquals('Object 1/content.xml', $element, '12.3'); + // chart:title : Element chart failed to validate attributes + $this->assertIsSchemaOpenDocumentNotValid('1.2'); + + $this->resetPresentationFile(); + + $series = new Series('Series', ['Jan' => 'data']); + $chart->getPlotArea()->getType()->setSeries([$series]); + + $element = '/office:document-content/office:body/office:chart/chart:chart/table:table/table:table-rows/table:table-row/table:table-cell[2]'; + + $this->assertZipXmlElementExists('Object 1/content.xml', $element); + $this->assertZipXmlAttributeExists('Object 1/content.xml', $element, 'office:value-type'); + $this->assertZipXmlAttributeEquals('Object 1/content.xml', $element, 'office:value-type', 'string'); + $this->assertZipXmlAttributeNotExists('Object 1/content.xml', $element, 'office:value'); + + $element = '/office:document-content/office:body/office:chart/chart:chart/table:table/table:table-rows/table:table-row/table:table-cell[2]/text:p'; + + $this->assertZipXmlElementExists('Object 1/content.xml', $element); + $this->assertZipXmlElementEquals('Object 1/content.xml', $element, 'data'); + // chart:title : Element chart failed to validate attributes + $this->assertIsSchemaOpenDocumentNotValid('1.2'); + } + + public function testSeriesShowConfig(): void { $oSeries = new Series('Series', ['Jan' => '1', 'Feb' => '5', 'Mar' => '2']); $oPie = new Pie(); diff --git a/tests/PhpPresentation/Tests/Writer/PowerPoint2007/PptChartsTest.php b/tests/PhpPresentation/Tests/Writer/PowerPoint2007/PptChartsTest.php index fbb794f3e..41de5a90d 100644 --- a/tests/PhpPresentation/Tests/Writer/PowerPoint2007/PptChartsTest.php +++ b/tests/PhpPresentation/Tests/Writer/PowerPoint2007/PptChartsTest.php @@ -4,6 +4,7 @@ use Exception; use PhpOffice\Common\Drawing; +use PhpOffice\PhpPresentation\Shape\Chart; use PhpOffice\PhpPresentation\Shape\Chart\Axis; use PhpOffice\PhpPresentation\Shape\Chart\Gridlines; use PhpOffice\PhpPresentation\Shape\Chart\Marker; @@ -39,6 +40,47 @@ class PptChartsTest extends PhpPresentationTestCase 'E' => '2', ]; + public function testChartDisplayBlankAs(): void + { + $oSeries = new Series('Downloads', $this->seriesData); + + $oLine = new Line(); + $oLine->addSeries($oSeries); + + $oShape = $this->oPresentation->getActiveSlide()->createChartShape(); + $oShape->getPlotArea()->setType($oLine); + $oShape->setDisplayBlankAs(Chart::BLANKAS_ZERO); + + $element = '/c:chartSpace/c:chart/c:dispBlanksAs'; + + $this->assertZipFileExists('ppt/charts/' . $oShape->getIndexedFilename()); + $this->assertZipXmlElementExists('ppt/charts/' . $oShape->getIndexedFilename(), $element); + $this->assertZipXmlAttributeExists('ppt/charts/' . $oShape->getIndexedFilename(), $element, 'val'); + $this->assertZipXmlAttributeEquals('ppt/charts/' . $oShape->getIndexedFilename(), $element, 'val', Chart::BLANKAS_ZERO); + + $this->assertIsSchemaECMA376Valid(); + + $this->resetPresentationFile(); + $oShape->setDisplayBlankAs(Chart::BLANKAS_SPAN); + + $this->assertZipFileExists('ppt/charts/' . $oShape->getIndexedFilename()); + $this->assertZipXmlElementExists('ppt/charts/' . $oShape->getIndexedFilename(), $element); + $this->assertZipXmlAttributeExists('ppt/charts/' . $oShape->getIndexedFilename(), $element, 'val'); + $this->assertZipXmlAttributeEquals('ppt/charts/' . $oShape->getIndexedFilename(), $element, 'val', Chart::BLANKAS_SPAN); + + $this->assertIsSchemaECMA376Valid(); + + $this->resetPresentationFile(); + $oShape->setDisplayBlankAs(Chart::BLANKAS_GAP); + + $this->assertZipFileExists('ppt/charts/' . $oShape->getIndexedFilename()); + $this->assertZipXmlElementExists('ppt/charts/' . $oShape->getIndexedFilename(), $element); + $this->assertZipXmlAttributeExists('ppt/charts/' . $oShape->getIndexedFilename(), $element, 'val'); + $this->assertZipXmlAttributeEquals('ppt/charts/' . $oShape->getIndexedFilename(), $element, 'val', Chart::BLANKAS_GAP); + + $this->assertIsSchemaECMA376Valid(); + } + public function testChartIncludeSpreadsheet(): void { $oSlide = $this->oPresentation->getActiveSlide();