summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Chart/SVGRenderer.php
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--library/Icinga/Chart/SVGRenderer.php331
1 files changed, 331 insertions, 0 deletions
diff --git a/library/Icinga/Chart/SVGRenderer.php b/library/Icinga/Chart/SVGRenderer.php
new file mode 100644
index 0000000..d3891f2
--- /dev/null
+++ b/library/Icinga/Chart/SVGRenderer.php
@@ -0,0 +1,331 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart;
+
+use DOMNode;
+use DOMElement;
+use DOMDocument;
+use DOMImplementation;
+use Icinga\Chart\Render\LayoutBox;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Primitive\Canvas;
+
+/**
+ * SVG Renderer component.
+ *
+ * Creates the basic DOM tree of the SVG to use
+ */
+class SVGRenderer
+{
+ const X_ASPECT_RATIO_MIN = 'xMin';
+
+ const X_ASPECT_RATIO_MID = 'xMid';
+
+ const X_ASPECT_RATIO_MAX = 'xMax';
+
+ const Y_ASPECT_RATIO_MIN = 'YMin';
+
+ const Y_ASPECT_RATIO_MID = 'YMid';
+
+ const Y_ASPECT_RATIO_MAX = 'YMax';
+
+ const ASPECT_RATIO_PAD = 'meet';
+
+ const ASPECT_RATIO_CUTOFF = 'slice';
+
+ /**
+ * The XML-document
+ *
+ * @var DOMDocument
+ */
+ private $document;
+
+ /**
+ * The SVG-element
+ *
+ * @var DOMNode
+ */
+ private $svg;
+
+ /**
+ * The description of this SVG, useful for screen readers
+ *
+ * @var string
+ */
+ private $ariaDescription;
+
+ /**
+ * The title of this SVG, useful for screen readers
+ *
+ * @var string
+ */
+ private $ariaTitle;
+
+ /**
+ * The aria role used by this svg element
+ *
+ * @var string
+ */
+ private $ariaRole = 'img';
+
+ /**
+ * The root layer for all elements
+ *
+ * @var Canvas
+ */
+ private $rootCanvas;
+
+ /**
+ * The width of this renderer
+ *
+ * @var int
+ */
+ private $width = 100;
+
+ /**
+ * The height of this renderer
+ *
+ * @var int
+ */
+ private $height = 100;
+
+ /**
+ * Whether the aspect ratio is preversed
+ *
+ * @var bool
+ */
+ private $preserveAspectRatio = false;
+
+ /**
+ * Horizontal alignment of SVG element
+ *
+ * @var string
+ */
+ private $xAspectRatio = self::X_ASPECT_RATIO_MID;
+
+ /**
+ * Vertical alignment of SVG element
+ *
+ * @var string
+ */
+ private $yAspectRatio = self::Y_ASPECT_RATIO_MID;
+
+ /**
+ * Define whether aspect differences should be handled using padding (default) or cutoff
+ *
+ * @var string
+ */
+ private $xFillMode = "meet";
+
+
+ /**
+ * Create the root document and the SVG root node
+ */
+ private function createRootDocument()
+ {
+ $implementation = new DOMImplementation();
+ $docType = $implementation->createDocumentType(
+ 'svg',
+ '-//W3C//DTD SVG 1.1//EN',
+ 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'
+ );
+
+ $this->document = $implementation->createDocument(null, '', $docType);
+ $this->svg = $this->createOuterBox();
+ $this->document->appendChild($this->svg);
+ }
+
+ /**
+ * Create the outer SVG box containing the root svg element and namespace and return it
+ *
+ * @return DOMElement The SVG root node
+ */
+ private function createOuterBox()
+ {
+ $ctx = $this->createRenderContext();
+ $svg = $this->document->createElement('svg');
+ $svg->setAttribute('xmlns', 'http://www.w3.org/2000/svg');
+ $svg->setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $svg->setAttribute('role', $this->ariaRole);
+ $svg->setAttribute('width', '100%');
+ $svg->setAttribute('height', '100%');
+ $svg->setAttribute(
+ 'viewBox',
+ sprintf(
+ '0 0 %s %s',
+ $ctx->getNrOfUnitsX(),
+ $ctx->getNrOfUnitsY()
+ )
+ );
+ if ($this->preserveAspectRatio) {
+ $svg->setAttribute(
+ 'preserveAspectRatio',
+ sprintf(
+ '%s%s %s',
+ $this->xAspectRatio,
+ $this->yAspectRatio,
+ $this->xFillMode
+ )
+ );
+ }
+ return $svg;
+ }
+
+ /**
+ * Add aria title and description
+ *
+ * Adds an aria title and desc element to the given SVG node, which are used to describe this SVG by accessibility
+ * tools such as screen readers.
+ *
+ * @param DOMNode $svg The SVG DOMNode to which the aria attributes should be attached
+ * @param $title The title text
+ * @param $description The description text
+ */
+ private function addAriaDescription(DOMNode $svg, $titleText, $descriptionText)
+ {
+ $doc = $svg->ownerDocument;
+
+ $titleId = $descId = '';
+ if (isset($this->ariaTitle)) {
+ $titleId = 'aria-title-' . $this->stripNonAlphanumeric($titleText);
+ $title = $doc->createElement('title');
+ $title->setAttribute('id', $titleId);
+
+ $title->appendChild($doc->createTextNode($titleText));
+ $svg->appendChild($title);
+ }
+
+ if (isset($this->ariaDescription)) {
+ $descId = 'aria-desc-' . $this->stripNonAlphanumeric($descriptionText);
+ $desc = $doc->createElement('desc');
+ $desc->setAttribute('id', $descId);
+
+ $desc->appendChild($doc->createTextNode($descriptionText));
+ $svg->appendChild($desc);
+ }
+
+ $svg->setAttribute('aria-labelledby', join(' ', array($titleId, $descId)));
+ }
+
+ /**
+ * Initialises the XML-document, SVG-element and this figure's root canvas
+ *
+ * @param int $width The width ratio
+ * @param int $height The height ratio
+ */
+ public function __construct($width, $height)
+ {
+ $this->width = $width;
+ $this->height = $height;
+ $this->rootCanvas = new Canvas('root', new LayoutBox(0, 0));
+ }
+
+ /**
+ * Render the SVG-document
+ *
+ * @return string The resulting XML structure
+ */
+ public function render()
+ {
+ $this->createRootDocument();
+ $ctx = $this->createRenderContext();
+ $this->addAriaDescription($this->svg, $this->ariaTitle, $this->ariaDescription);
+ $this->svg->appendChild($this->rootCanvas->toSvg($ctx));
+ $this->document->formatOutput = true;
+ $this->document->encoding = 'UTF-8';
+ return $this->document->saveXML();
+ }
+
+ /**
+ * Create a render context that will be used for rendering elements
+ *
+ * @return RenderContext The created RenderContext instance
+ */
+ public function createRenderContext()
+ {
+ return new RenderContext($this->document, $this->width, $this->height);
+ }
+
+ /**
+ * Return the root canvas of this rendered
+ *
+ * @return Canvas The canvas that will be the uppermost element in this figure
+ */
+ public function getCanvas()
+ {
+ return $this->rootCanvas;
+ }
+
+ /**
+ * Preserve the aspect ratio of the rendered object
+ *
+ * Do not deform the content of the SVG when the aspect ratio of the viewBox
+ * differs from the aspect ratio of the SVG element, but add padding or cutoff
+ * instead
+ *
+ * @param bool $preserve Whether the aspect ratio should be preserved
+ */
+ public function preserveAspectRatio($preserve = true)
+ {
+ $this->preserveAspectRatio = $preserve;
+ }
+
+ /**
+ * Change the horizontal alignment of the SVG element
+ *
+ * Change the horizontal alignment of the svg, when preserveAspectRatio is used and
+ * padding is present. Defaults to
+ */
+ public function setXAspectRatioAlignment($alignment)
+ {
+ $this->xAspectRatio = $alignment;
+ }
+
+ /**
+ * Change the vertical alignment of the SVG element
+ *
+ * Change the vertical alignment of the svg, when preserveAspectRatio is used and
+ * padding is present.
+ */
+ public function setYAspectRatioAlignment($alignment)
+ {
+ $this->yAspectRatio = $alignment;
+ }
+
+ /**
+ * Set the aria description, that is used as a title for this SVG in screen readers
+ *
+ * @param $text
+ */
+ public function setAriaTitle($text)
+ {
+ $this->ariaTitle = $text;
+ }
+
+ /**
+ * Set the aria description, that is used to describe this SVG in screen readers
+ *
+ * @param $text
+ */
+ public function setAriaDescription($text)
+ {
+ $this->ariaDescription = $text;
+ }
+
+ /**
+ * Set the aria role, that is used to describe the purpose of this SVG in screen readers
+ *
+ * @param $text
+ */
+ public function setAriaRole($text)
+ {
+ $this->ariaRole = $text;
+ }
+
+
+ private function stripNonAlphanumeric($str)
+ {
+ return preg_replace('/[^A-Za-z]+/', '', $str);
+ }
+}