diff --git a/CHANGELOG.md b/CHANGELOG.md index ebfd3263..302cbf98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This release added drawing shapes (arc, curve, line, polyline, rect, oval) eleme - RTF Writer: Support for sections, margins, and borders - @ivanlanin GH-249 - Section: Ability to set paper size, e.g. A4, A3, and Legal - @ivanlanin GH-249 - General: New `PhpWord::save()` method to encapsulate `IOFactory` - @ivanlanin +- Element: Basic 2D charts (pie, doughnut, bar, line, area, scatter, radar) - @ivanlanin GH-278 ### Bugfixes diff --git a/README.md b/README.md index 97e5b642..5a432cba 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ With PHPWord, you can create DOCX, ODT, or RTF documents dynamically using your - Insert list items as bulleted, numbered, or multilevel - Insert hyperlinks - Insert footnotes and endnotes +- Insert drawing shapes (arc, curve, line, polyline, rect, oval) +- Insert charts (pie, doughnut, bar, line, area, scatter, radar) - Create document from templates - Use XSL 1.0 style sheets to transform main document part of OOXML template - ... and many more features on progress diff --git a/docs/elements.rst b/docs/elements.rst index a01c9fd4..ee827326 100644 --- a/docs/elements.rst +++ b/docs/elements.rst @@ -49,6 +49,8 @@ column shows the containers while the rows lists the elements. +-------+-----------------+-----------+----------+----------+---------+------------+------------+ | 20 | Shape | v | v | v | v | v | v | +-------+-----------------+-----------+----------+----------+---------+------------+------------+ +| 21 | Chart | v | - | - | - | - | - | ++-------+-----------------+-----------+----------+----------+---------+------------+------------+ Legend: @@ -399,3 +401,8 @@ Shapes ------ To be completed. + +Charts +------ + +To be completed. diff --git a/docs/intro.rst b/docs/intro.rst index 5045d4dc..241b581c 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -45,6 +45,8 @@ Features - Insert list items as bulleted, numbered, or multilevel - Insert hyperlinks - Insert footnotes and endnotes +- Insert drawing shapes (arc, curve, line, polyline, rect, oval) +- Insert charts (pie, doughnut, bar, line, area, scatter, radar) - Create document from templates - Use XSL 1.0 style sheets to transform main document part of OOXML template diff --git a/docs/src/documentation.md b/docs/src/documentation.md index 49505c7f..0b21f634 100644 --- a/docs/src/documentation.md +++ b/docs/src/documentation.md @@ -37,6 +37,7 @@ Don't forget to change `code::` directive to `code-block::` in the resulting rst - [Fields](#fields) - [Lines](#lines) - [Shapes](#shapes) + - [Charts](#charts) - [Styles](#styles) - [Section](#section) - [Font](#font) @@ -76,6 +77,8 @@ PHPWord is an open source project licensed under the terms of [LGPL version 3](h - Insert list items as bulleted, numbered, or multilevel - Insert hyperlinks - Insert footnotes and endnotes +- Insert drawing shapes (arc, curve, line, polyline, rect, oval) +- Insert charts (pie, doughnut, bar, line, area, scatter, radar) - Create document from templates - Use XSL 1.0 style sheets to transform main document part of OOXML template - ... and many more features on progress @@ -440,6 +443,7 @@ Below are the matrix of element availability in each container. The column shows | 18 | Field | v | v | v | v | v | v | | 19 | Line | v | v | v | v | v | v | | 20 | Shape | v | v | v | v | v | v | +| 21 | Chart | v | - | - | - | - | - | Legend: @@ -737,6 +741,10 @@ To be completed. To be completed. +## Charts + +To be completed. + # Styles ## Section diff --git a/samples/Sample_32_Chart.php b/samples/Sample_32_Chart.php index 52c19f3c..26d6c420 100644 --- a/samples/Sample_32_Chart.php +++ b/samples/Sample_32_Chart.php @@ -9,13 +9,23 @@ $phpWord = new \PhpOffice\PhpWord\PhpWord(); $section = $phpWord->addSection(array('colsNum' => 2)); $phpWord->addTitleStyle(1, array('size' => 14, 'bold' => true), array('keepNext' => true, 'spaceBefore' => 240)); -$charts = array('pie', 'doughnut', 'line', 'area', 'scatter', 'bar', 'radar'); -$labels = array('A', 'B', 'C', 'D', 'E'); -$data = array(1, 3, 2, 5, 4); +$chartTypes = array('pie', 'doughnut', 'bar', 'line', 'area', 'scatter', 'radar'); +$twoSeries = array('bar', 'line', 'area', 'scatter', 'radar'); +$threeSeries = array('bar', 'line'); +$categories = array('A', 'B', 'C', 'D', 'E'); +$series1 = array(1, 3, 2, 5, 4); +$series2 = array(3, 1, 7, 2, 6); +$series3 = array(8, 3, 2, 5, 4); -foreach ($charts as $chart) { - $section->addTitle(ucfirst($chart), 1); - $section->addChart($chart, $labels, $data); +foreach ($chartTypes as $chartType) { + $section->addTitle(ucfirst($chartType), 1); + $chart = $section->addChart($chartType, $categories, $series1); + if (in_array($chartType, $twoSeries)) { + $chart->addSeries($categories, $series2); + } + if (in_array($chartType, $threeSeries)) { + $chart->addSeries($categories, $series3); + } $section->addTextBreak(); } diff --git a/src/PhpWord/Element/Chart.php b/src/PhpWord/Element/Chart.php index 5a93b827..24f2bb78 100644 --- a/src/PhpWord/Element/Chart.php +++ b/src/PhpWord/Element/Chart.php @@ -39,31 +39,23 @@ class Chart extends AbstractElement private $type = 'pie'; /** - * Labels + * Series * * @var array */ - private $labels = array(); - - /** - * Data - * - * @var array - */ - private $data = array(); + private $series = array(); /** * Create new instance * * @param string $type - * @param array $labels - * @param array $data + * @param array $categories + * @param array $values */ - public function __construct($type, $labels, $data) + public function __construct($type, $categories, $values) { $this->setType($type); - $this->setLabels($labels); - $this->setData($data); + $this->addSeries($categories, $values); } /** @@ -88,42 +80,23 @@ class Chart extends AbstractElement } /** - * Get labels + * Add series + * + * @param array $categories + * @param array $values + */ + public function addSeries($categories, $values) + { + $this->series[] = array('categories' => $categories, 'values' => $values); + } + + /** + * Get series * * @return array */ - public function getLabels() + public function getSeries() { - return $this->labels; - } - - /** - * Set labels - * - * @param array $value - */ - public function setLabels($value) - { - $this->labels = $value; - } - - /** - * Get data - * - * @return array - */ - public function getData() - { - return $this->data; - } - - /** - * Set data - * - * @param array $value - */ - public function setData($value) - { - $this->data = $value; + return $this->series; } } diff --git a/src/PhpWord/Shared/XMLWriter.php b/src/PhpWord/Shared/XMLWriter.php index b2057bb9..8691ae1d 100644 --- a/src/PhpWord/Shared/XMLWriter.php +++ b/src/PhpWord/Shared/XMLWriter.php @@ -150,7 +150,7 @@ class XMLWriter * @param string|array $attributes * @param string $value */ - public function writeBlock($element, $attributes, $value = null) + public function writeElementBlock($element, $attributes, $value = null) { $this->xmlWriter->startElement($element); if (!is_array($attributes)) { diff --git a/src/PhpWord/Writer/Word2007/Element/Chart.php b/src/PhpWord/Writer/Word2007/Element/Chart.php index 454ebce5..4330e2fe 100644 --- a/src/PhpWord/Writer/Word2007/Element/Chart.php +++ b/src/PhpWord/Writer/Word2007/Element/Chart.php @@ -48,8 +48,8 @@ class Chart extends AbstractElement $xmlWriter->startElement('wp:inline'); // EMU - $xmlWriter->writeBlock('wp:extent', array('cx' => '2000000', 'cy' => '2000000')); - $xmlWriter->writeBlock('wp:docPr', array('id' => $rId, 'name' => "Chart{$rId}")); + $xmlWriter->writeElementBlock('wp:extent', array('cx' => '2000000', 'cy' => '2000000')); + $xmlWriter->writeElementBlock('wp:docPr', array('id' => $rId, 'name' => "Chart{$rId}")); $xmlWriter->startElement('a:graphic'); $xmlWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); diff --git a/src/PhpWord/Writer/Word2007/Part/Chart.php b/src/PhpWord/Writer/Word2007/Part/Chart.php index 8d922013..66e43914 100644 --- a/src/PhpWord/Writer/Word2007/Part/Chart.php +++ b/src/PhpWord/Writer/Word2007/Part/Chart.php @@ -25,7 +25,6 @@ use PhpOffice\PhpWord\Element\Chart as ChartElement; * * @since 0.12.0 * @link http://www.datypic.com/sc/ooxml/e-draw-chart_chartSpace.html - * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ class Chart extends AbstractPart { @@ -36,6 +35,11 @@ class Chart extends AbstractPart */ private $element; + /** + * Type definition + * + * @var array + */ private $types = array( 'pie' => array('type' => 'pieChart', 'colors' => 1), 'doughnut' => array('type' => 'doughnutChart', 'colors' => 1, 'hole' => 75), @@ -46,6 +50,11 @@ class Chart extends AbstractPart 'scatter' => array('type' => 'scatterChart', 'colors' => 0, 'axes' => true, 'scatter' => 'marker'), ); + /** + * Chart options + * + * @var array + */ private $options = array(); /** @@ -71,14 +80,9 @@ class Chart extends AbstractPart $xmlWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); $xmlWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); - $xmlWriter->writeBlock('c:date1904', 'val', 1); - $xmlWriter->writeBlock('c:lang', 'val', 'en-US'); - $xmlWriter->writeBlock('c:roundedCorners', 'val', 0); - $this->writeChart($xmlWriter); $this->writeShape($xmlWriter); - $xmlWriter->endElement(); // c:chartSpace return $xmlWriter->getData(); @@ -93,11 +97,9 @@ class Chart extends AbstractPart { $xmlWriter->startElement('c:chart'); - $xmlWriter->writeBlock('c:autoTitleDeleted', 'val', 1); - $xmlWriter->writeBlock('c:dispBlanksAs', 'val', 'zero'); + $xmlWriter->writeElementBlock('c:autoTitleDeleted', 'val', 1); $this->writePlotArea($xmlWriter); - // $this->writeLegend($xmlWriter); $xmlWriter->endElement(); // c:chart } @@ -125,27 +127,33 @@ class Chart extends AbstractPart // Chart $xmlWriter->startElement('c:' . $this->options['type']); - $xmlWriter->writeBlock('c:varyColors', 'val', $this->options['colors']); + $xmlWriter->writeElementBlock('c:varyColors', 'val', $this->options['colors']); + if ($type == 'area') { + $xmlWriter->writeElementBlock('c:grouping', 'val', 'standard'); + } if (isset($this->options['hole'])) { - $xmlWriter->writeBlock('c:holeSize', 'val', $this->options['hole']); + $xmlWriter->writeElementBlock('c:holeSize', 'val', $this->options['hole']); } if (isset($this->options['bar'])) { - $xmlWriter->writeBlock('c:barDir', 'val', $this->options['bar']); // bar|col + $xmlWriter->writeElementBlock('c:barDir', 'val', $this->options['bar']); // bar|col + $xmlWriter->writeElementBlock('c:grouping', 'val', 'clustered'); } if (isset($this->options['radar'])) { - $xmlWriter->writeBlock('c:radarStyle', 'val', $this->options['radar']); + $xmlWriter->writeElementBlock('c:radarStyle', 'val', $this->options['radar']); } if (isset($this->options['scatter'])) { - $xmlWriter->writeBlock('c:scatterStyle', 'val', $this->options['scatter']); - } - if (isset($this->options['axes'])) { - $xmlWriter->writeBlock('c:axId', 'val', 1); - $xmlWriter->writeBlock('c:axId', 'val', 2); + $xmlWriter->writeElementBlock('c:scatterStyle', 'val', $this->options['scatter']); } // Series $this->writeSeries($xmlWriter, isset($this->options['scatter'])); + // Axes + if (isset($this->options['axes'])) { + $xmlWriter->writeElementBlock('c:axId', 'val', 1); + $xmlWriter->writeElementBlock('c:axId', 'val', 2); + } + $xmlWriter->endElement(); // chart type // Axes @@ -165,28 +173,34 @@ class Chart extends AbstractPart */ private function writeSeries(XMLWriter $xmlWriter, $scatter = false) { - $xmlWriter->startElement('c:ser'); + $series = $this->element->getSeries(); - $xmlWriter->writeBlock('c:idx', 'val', 0); - $xmlWriter->writeBlock('c:order', 'val', 0); + $index = 0; + foreach ($series as $seriesItem) { + $categories = $seriesItem['categories']; + $values = $seriesItem['values']; - if (isset($this->options['scatter'])) { - $xmlWriter->startElement('c:spPr'); - $xmlWriter->startElement('a:ln'); - $xmlWriter->writeElement('a:noFill'); - $xmlWriter->endElement(); // a:ln - $xmlWriter->endElement(); // c:spPr + $xmlWriter->startElement('c:ser'); + + $xmlWriter->writeElementBlock('c:idx', 'val', $index); + $xmlWriter->writeElementBlock('c:order', 'val', $index); + + if (isset($this->options['scatter'])) { + $this->writeShape($xmlWriter); + } + + if ($scatter === true) { + $this->writeSeriesItem($xmlWriter, 'xVal', $categories); + $this->writeSeriesItem($xmlWriter, 'yVal', $values); + } else { + $this->writeSeriesItem($xmlWriter, 'cat', $categories); + $this->writeSeriesItem($xmlWriter, 'val', $values); + } + + $xmlWriter->endElement(); // c:ser + $index++; } - if ($scatter === true) { - $this->writeSeriesItems($xmlWriter, 'xVal', $this->element->getLabels()); - $this->writeSeriesItems($xmlWriter, 'yVal', $this->element->getData()); - } else { - $this->writeSeriesItems($xmlWriter, 'cat', $this->element->getLabels()); - $this->writeSeriesItems($xmlWriter, 'val', $this->element->getData()); - } - - $xmlWriter->endElement(); // c:ser } /** @@ -196,7 +210,7 @@ class Chart extends AbstractPart * @param string $type * @param array $values */ - private function writeSeriesItems(XMLWriter $xmlWriter, $type, $values) + private function writeSeriesItem(XMLWriter $xmlWriter, $type, $values) { $types = array( 'cat' => array('c:cat', 'c:strLit'), @@ -243,63 +257,47 @@ class Chart extends AbstractPart $xmlWriter->startElement($axisType); - $xmlWriter->writeBlock('c:axId', 'val', $axisId); - $xmlWriter->writeBlock('c:axPos', 'val', $axisPos); - $xmlWriter->writeBlock('c:crossAx', 'val', $axisCross); - $xmlWriter->writeBlock('c:auto', 'val', 1); + $xmlWriter->writeElementBlock('c:axId', 'val', $axisId); + $xmlWriter->writeElementBlock('c:axPos', 'val', $axisPos); + $xmlWriter->writeElementBlock('c:crossAx', 'val', $axisCross); + $xmlWriter->writeElementBlock('c:auto', 'val', 1); if (isset($this->options['axes'])) { - $xmlWriter->writeBlock('c:delete', 'val', 0); - $xmlWriter->writeBlock('c:majorTickMark', 'val', 'none'); - $xmlWriter->writeBlock('c:minorTickMark', 'val', 'none'); - $xmlWriter->writeBlock('c:tickLblPos', 'val', 'none'); // nextTo - // $xmlWriter->writeBlock('c:crosses', 'val', 'autoZero'); + $xmlWriter->writeElementBlock('c:delete', 'val', 0); + $xmlWriter->writeElementBlock('c:majorTickMark', 'val', 'none'); + $xmlWriter->writeElementBlock('c:minorTickMark', 'val', 'none'); + $xmlWriter->writeElementBlock('c:tickLblPos', 'val', 'none'); // nextTo + $xmlWriter->writeElementBlock('c:crosses', 'val', 'autoZero'); } if (isset($this->options['radar'])) { $xmlWriter->writeElement('c:majorGridlines'); } $xmlWriter->startElement('c:scaling'); - $xmlWriter->writeBlock('c:orientation', 'val', 'minMax'); + $xmlWriter->writeElementBlock('c:orientation', 'val', 'minMax'); $xmlWriter->endElement(); // c:scaling - $xmlWriter->startElement('c:spPr'); - $xmlWriter->writeElement('a:noFill'); - - $xmlWriter->startElement('a:ln'); - $xmlWriter->startElement('a:solidFill'); - // $xmlWriter->writeBlock('a:srgbClr', 'val', '0FF000'); - $xmlWriter->endElement(); // a:solidFill - $xmlWriter->endElement(); // a:ln - - $xmlWriter->endElement(); // c:spPr + $this->writeShape($xmlWriter, true); $xmlWriter->endElement(); // $axisType } - /** - * Write legend - * - * @link http://www.datypic.com/sc/ooxml/t-draw-chart_CT_Legend.html - */ - private function writeLegend(XMLWriter $xmlWriter) - { - $xmlWriter->startElement('c:legend'); - $xmlWriter->writeElement('c:layout'); - $xmlWriter->writeBlock('c:legendPos', 'val', 'r'); - $xmlWriter->endElement(); // c:legend - } - /** * Write shape * + * @param \PhpOffice\PhpWord\Shared\XMLWriter $xmlWriter + * @param bool $line * @link http://www.datypic.com/sc/ooxml/t-a_CT_ShapeProperties.html */ - private function writeShape(XMLWriter $xmlWriter) + private function writeShape(XMLWriter $xmlWriter, $line = false) { $xmlWriter->startElement('c:spPr'); $xmlWriter->startElement('a:ln'); - $xmlWriter->writeElement('a:noFill'); + if ($line === true) { + $xmlWriter->writeElement('a:solidFill'); + } else { + $xmlWriter->writeElement('a:noFill'); + } $xmlWriter->endElement(); // a:ln $xmlWriter->endElement(); // c:spPr } diff --git a/tests/PhpWord/Tests/Writer/RTFTest.php b/tests/PhpWord/Tests/Writer/RTFTest.php index c1448106..5b983b35 100644 --- a/tests/PhpWord/Tests/Writer/RTFTest.php +++ b/tests/PhpWord/Tests/Writer/RTFTest.php @@ -91,7 +91,7 @@ class RTFTest extends \PHPUnit_Framework_TestCase $this->assertTrue(file_exists($file)); - unlink($file); + @unlink($file); } /** diff --git a/tests/PhpWord/Tests/Writer/Word2007/ElementTest.php b/tests/PhpWord/Tests/Writer/Word2007/ElementTest.php index 2e76df12..0ba29f2f 100644 --- a/tests/PhpWord/Tests/Writer/Word2007/ElementTest.php +++ b/tests/PhpWord/Tests/Writer/Word2007/ElementTest.php @@ -148,4 +148,30 @@ class ElementTest extends \PHPUnit_Framework_TestCase $this->assertTrue($doc->elementExists($path)); } } + + /** + * Test shape elements + */ + public function testChartElement() + { + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + + $chartTypes = array('pie', 'doughnut', 'bar', 'line', 'area', 'scatter', 'radar'); + $categories = array('A', 'B', 'C', 'D', 'E'); + $series1 = array(1, 3, 2, 5, 4); + foreach ($chartTypes as $chartType) { + $section->addChart($chartType, $categories, $series1); + } + + $doc = TestHelperDOCX::getDocument($phpWord); + + $index = 0; + foreach ($chartTypes as $chartType) { + $index++; + $file = "word/charts/chart{$index}.xml"; + $path = "/c:chartSpace/c:chart/c:plotArea/c:{$chartType}Chart"; + $this->assertTrue($doc->elementExists($path, $file)); + } + } }