summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Chart/Primitive
diff options
context:
space:
mode:
Diffstat (limited to 'library/Icinga/Chart/Primitive')
-rw-r--r--library/Icinga/Chart/Primitive/Animatable.php43
-rw-r--r--library/Icinga/Chart/Primitive/Animation.php87
-rw-r--r--library/Icinga/Chart/Primitive/Canvas.php140
-rw-r--r--library/Icinga/Chart/Primitive/Circle.php84
-rw-r--r--library/Icinga/Chart/Primitive/Drawable.php22
-rw-r--r--library/Icinga/Chart/Primitive/Line.php103
-rw-r--r--library/Icinga/Chart/Primitive/Path.php187
-rw-r--r--library/Icinga/Chart/Primitive/PieSlice.php307
-rw-r--r--library/Icinga/Chart/Primitive/RawElement.php43
-rw-r--r--library/Icinga/Chart/Primitive/Rect.php119
-rw-r--r--library/Icinga/Chart/Primitive/Styleable.php161
-rw-r--r--library/Icinga/Chart/Primitive/Text.php184
12 files changed, 1480 insertions, 0 deletions
diff --git a/library/Icinga/Chart/Primitive/Animatable.php b/library/Icinga/Chart/Primitive/Animatable.php
new file mode 100644
index 0000000..69ba0e1
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Animatable.php
@@ -0,0 +1,43 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Base interface for animatable objects
+ */
+abstract class Animatable extends Styleable
+{
+ /**
+ * The animation object set
+ *
+ * @var Animation
+ */
+ public $animation = null;
+
+ /**
+ * Set the animation for this object
+ *
+ * @param Animation $anim The animation to use
+ */
+ public function setAnimation(Animation $anim)
+ {
+ $this->animation = $anim;
+ }
+
+ /**
+ * Append the animation to the given element
+ *
+ * @param DOMElement $dom The element to append the animation to
+ * @param RenderContext $ctx The context to use for rendering the animation object
+ */
+ protected function appendAnimation(DOMElement $dom, RenderContext $ctx)
+ {
+ if ($this->animation) {
+ $dom->appendChild($this->animation->toSvg($ctx));
+ }
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Animation.php b/library/Icinga/Chart/Primitive/Animation.php
new file mode 100644
index 0000000..e620fa7
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Animation.php
@@ -0,0 +1,87 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Drawable for the SVG animate tag
+ */
+class Animation implements Drawable
+{
+ /**
+ * The attribute to animate
+ *
+ * @var string
+ */
+ private $attribute;
+
+ /**
+ * The 'from' value
+ *
+ * @var mixed
+ */
+ private $from;
+
+ /**
+ * The to value
+ *
+ * @var mixed
+ */
+ private $to;
+
+ /**
+ * The begin value (in seconds)
+ *
+ * @var float
+ */
+ private $begin = 0;
+
+ /**
+ * The duration value (in seconds)
+ *
+ * @var float
+ */
+ private $duration = 0.5;
+
+ /**
+ * Create an animation object
+ *
+ * @param string $attribute The attribute to animate
+ * @param string $from The from value for the animation
+ * @param string $to The to value for the animation
+ * @param float $duration The duration of the duration
+ * @param float $begin The begin of the duration
+ */
+ public function __construct($attribute, $from, $to, $duration = 0.5, $begin = 0.0)
+ {
+ $this->attribute = $attribute;
+ $this->from = $from;
+ $this->to = $to;
+ $this->duration = $duration;
+ $this->begin = $begin;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+
+ $animate = $ctx->getDocument()->createElement('animate');
+ $animate->setAttribute('attributeName', $this->attribute);
+ $animate->setAttribute('attributeType', 'XML');
+ $animate->setAttribute('from', $this->from);
+ $animate->setAttribute('to', $this->to);
+ $animate->setAttribute('begin', $this->begin . 's');
+ $animate->setAttribute('dur', $this->duration . 's');
+ $animate->setAttribute('fill', "freeze");
+
+ return $animate;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Canvas.php b/library/Icinga/Chart/Primitive/Canvas.php
new file mode 100644
index 0000000..32f06bf
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Canvas.php
@@ -0,0 +1,140 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\LayoutBox;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Canvas SVG component that encapsulates grouping and padding and allows rendering
+ * multiple elements in a group
+ *
+ */
+class Canvas implements Drawable
+{
+ /**
+ * The name of the canvas, will be used as the id
+ *
+ * @var string
+ */
+ private $name;
+
+ /**
+ * An array of child elements of this Canvas
+ *
+ * @var array
+ */
+ private $children = array();
+
+ /**
+ * When true, this canvas is encapsulated in a clipPath tag and not drawn
+ *
+ * @var bool
+ */
+ private $isClipPath = false;
+
+ /**
+ * The LayoutBox of this Canvas
+ *
+ * @var LayoutBox
+ */
+ private $rect;
+
+ /**
+ * The aria role used to describe this canvas' purpose in the accessibility tree
+ *
+ * @var string
+ */
+ private $ariaRole;
+
+ /**
+ * Create this canvas
+ *
+ * @param String $name The name of this canvas
+ * @param LayoutBox $rect The layout and size of this canvas
+ */
+ public function __construct($name, LayoutBox $rect)
+ {
+ $this->rect = $rect;
+ $this->name = $name;
+ }
+
+ /**
+ * Convert this canvas to a clipPath element
+ */
+ public function toClipPath()
+ {
+ $this->isClipPath = true;
+ }
+
+ /**
+ * Return the layout of this canvas
+ *
+ * @return LayoutBox
+ */
+ public function getLayout()
+ {
+ return $this->rect;
+ }
+
+ /**
+ * Add an element to this canvas
+ *
+ * @param Drawable $child
+ */
+ public function addElement(Drawable $child)
+ {
+ $this->children[] = $child;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $doc = $ctx->getDocument();
+ if ($this->isClipPath) {
+ $outer = $doc->createElement('defs');
+ $innerContainer = $element = $doc->createElement('clipPath');
+ $outer->appendChild($element);
+ } else {
+ $outer = $element = $doc->createElement('g');
+ $innerContainer = $doc->createElement('g');
+ $innerContainer->setAttribute('x', 0);
+ $innerContainer->setAttribute('y', 0);
+ $innerContainer->setAttribute('id', $this->name . '_inner');
+ $innerContainer->setAttribute('transform', $this->rect->getInnerTransform($ctx));
+ $element->appendChild($innerContainer);
+ }
+
+ $element->setAttribute('id', $this->name);
+ foreach ($this->children as $child) {
+ $innerContainer->appendChild($child->toSvg($ctx));
+ }
+
+ if (isset($this->ariaRole)) {
+ $outer->setAttribute('role', $this->ariaRole);
+ }
+ return $outer;
+ }
+
+ /**
+ * Set the aria role used to determine the meaning of this canvas in the accessibility tree
+ *
+ * The role 'presentation' will indicate that the purpose of this canvas is entirely decorative, while the role
+ * 'img' will indicate that the canvas contains an image, with a possible title or a description. For other
+ * possible roles, see http://www.w3.org/TR/wai-aria/roles
+ *
+ * @param $role string The aria role to set
+ */
+ public function setAriaRole($role)
+ {
+ $this->ariaRole = $role;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Circle.php b/library/Icinga/Chart/Primitive/Circle.php
new file mode 100644
index 0000000..f98ffac
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Circle.php
@@ -0,0 +1,84 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Chart\Primitive;
+
+use DOMDocument;
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Format;
+
+/**
+ * Drawable for svg circles
+ */
+class Circle extends Styleable implements Drawable
+{
+ /**
+ * The circles x position
+ *
+ * @var int
+ */
+ private $x;
+
+ /**
+ * The circles y position
+ *
+ * @var int
+ */
+ private $y;
+
+ /**
+ * The circles radius
+ *
+ * @var int
+ */
+ private $radius;
+
+ /**
+ * Construct the circle
+ *
+ * @param int $x The x position of the circle
+ * @param int $y The y position of the circle
+ * @param int $radius The radius of the circle
+ */
+ public function __construct($x, $y, $radius)
+ {
+ $this->x = $x;
+ $this->y = $y;
+ $this->radius = $radius;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $coords = $ctx->toAbsolute($this->x, $this->y);
+ $circle = $ctx->getDocument()->createElement('circle');
+ $circle->setAttribute('cx', Format::formatSVGNumber($coords[0]));
+ $circle->setAttribute('cy', Format::formatSVGNumber($coords[1]));
+ $circle->setAttribute('r', $this->radius);
+
+ $id = $this->id ?? uniqid('circle-');
+ $circle->setAttribute('id', $id);
+ $this->setId($id);
+
+ $this->applyAttributes($circle);
+
+ $style = new DOMDocument();
+ $style->loadHTML($this->getStyle());
+
+ $circle->appendChild(
+ $circle->ownerDocument->importNode(
+ $style->getElementsByTagName('style')->item(0),
+ true
+ )
+ );
+
+ return $circle;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Drawable.php b/library/Icinga/Chart/Primitive/Drawable.php
new file mode 100644
index 0000000..5b4355c
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Drawable.php
@@ -0,0 +1,22 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Drawable element for creating svg out of components
+ */
+interface Drawable
+{
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx);
+}
diff --git a/library/Icinga/Chart/Primitive/Line.php b/library/Icinga/Chart/Primitive/Line.php
new file mode 100644
index 0000000..d83cbea
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Line.php
@@ -0,0 +1,103 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMDocument;
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Format;
+
+/**
+ * Drawable for the svg line element
+ */
+class Line extends Styleable implements Drawable
+{
+
+ /**
+ * The default stroke width
+ *
+ * @var int
+ */
+ public $strokeWidth = 1;
+
+ /**
+ * The line's start x coordinate
+ *
+ * @var int
+ */
+ private $xStart = 0;
+
+ /**
+ * The line's end x coordinate
+ *
+ * @var int
+ */
+ private $xEnd = 0;
+
+ /**
+ * The line's start y coordinate
+ *
+ * @var int
+ */
+ private $yStart = 0;
+
+ /**
+ * The line's end y coordinate
+ *
+ * @var int
+ */
+ private $yEnd = 0;
+
+ /**
+ * Create a line object starting at the first coordinate and ending at the second one
+ *
+ * @param int $x1 The line's start x coordinate
+ * @param int $y1 The line's start y coordinate
+ * @param int $x2 The line's end x coordinate
+ * @param int $y2 The line's end y coordinate
+ */
+ public function __construct($x1, $y1, $x2, $y2)
+ {
+ $this->xStart = $x1;
+ $this->xEnd = $x2;
+ $this->yStart = $y1;
+ $this->yEnd = $y2;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $doc = $ctx->getDocument();
+ list($x1, $y1) = $ctx->toAbsolute($this->xStart, $this->yStart);
+ list($x2, $y2) = $ctx->toAbsolute($this->xEnd, $this->yEnd);
+ $line = $doc->createElement('line');
+ $line->setAttribute('x1', Format::formatSVGNumber($x1));
+ $line->setAttribute('x2', Format::formatSVGNumber($x2));
+ $line->setAttribute('y1', Format::formatSVGNumber($y1));
+ $line->setAttribute('y2', Format::formatSVGNumber($y2));
+
+ $id = $this->id ?? uniqid('line-');
+ $line->setAttribute('id', $id);
+ $this->setId($id);
+
+ $this->applyAttributes($line);
+
+ $style = new DOMDocument();
+ $style->loadHTML($this->getStyle());
+
+ $line->appendChild(
+ $line->ownerDocument->importNode(
+ $style->getElementsByTagName('style')->item(0),
+ true
+ )
+ );
+
+ return $line;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Path.php b/library/Icinga/Chart/Primitive/Path.php
new file mode 100644
index 0000000..b9d5f7b
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Path.php
@@ -0,0 +1,187 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMDocument;
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Format;
+
+/**
+ * Drawable for creating a svg path element
+ */
+class Path extends Styleable implements Drawable
+{
+ /**
+ * Syntax template for moving
+ *
+ * @see http://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
+ */
+ const TPL_MOVE = 'M %s %s ';
+
+ /**
+ * Syntax template for bezier curve
+ *
+ * @see http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands
+ */
+ const TPL_BEZIER = 'S %s %s ';
+
+ /**
+ * Syntax template for straight lines
+ *
+ * @see http://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands
+ */
+ const TPL_STRAIGHT = 'L %s %s ';
+
+ /**
+ * The default stroke width
+ *
+ * @var int
+ */
+ public $strokeWidth = 1;
+
+ /**
+ * True to treat coordinates as absolute values
+ *
+ * @var bool
+ */
+ protected $isAbsolute = false;
+
+ /**
+ * The points to draw, in the order they are drawn
+ *
+ * @var array
+ */
+ protected $points = array();
+
+ /**
+ * True to draw the path discrete, i.e. make hard steps between points
+ *
+ * @var bool
+ */
+ protected $discrete = false;
+
+ /**
+ * Create the path using the given points
+ *
+ * @param array $points Either a single [x, y] point or an array of x, y points
+ */
+ public function __construct(array $points)
+ {
+ $this->append($points);
+ }
+
+ /**
+ * Append a single point or an array of points to this path
+ *
+ * @param array $points Either a single [x, y] point or an array of x, y points
+ *
+ * @return $this Fluid interface
+ */
+ public function append(array $points)
+ {
+ if (count($points) === 0) {
+ return $this;
+ }
+ if (!is_array($points[0])) {
+ $points = array($points);
+ }
+ $this->points = array_merge($this->points, $points);
+ return $this;
+ }
+
+ /**
+ * Prepend a single point or an array of points to this path
+ *
+ * @param array $points Either a single [x, y] point or an array of x, y points
+ *
+ * @return $this Fluid interface
+ */
+ public function prepend(array $points)
+ {
+ if (count($points) === 0) {
+ return $this;
+ }
+ if (!is_array($points[0])) {
+ $points = array($points);
+ }
+ $this->points = array_merge($points, $this->points);
+ return $this;
+ }
+
+ /**
+ * Set this path to be discrete
+ *
+ * @param boolean $bool True to draw discrete or false to draw straight lines between points
+ *
+ * @return $this Fluid interface
+ */
+ public function setDiscrete($bool)
+ {
+ $this->discrete = $bool;
+ return $this;
+ }
+
+ /**
+ * Mark this path as containing absolute coordinates
+ *
+ * @return $this Fluid interface
+ */
+ public function toAbsolute()
+ {
+ $this->isAbsolute = true;
+ return $this;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $doc = $ctx->getDocument();
+ $group = $doc->createElement('g');
+
+ $pathDescription = '';
+ $tpl = self::TPL_MOVE;
+ $lastPoint = null;
+ foreach ($this->points as $point) {
+ if (!$this->isAbsolute) {
+ $point = $ctx->toAbsolute($point[0], $point[1]);
+ }
+ $point[0] = Format::formatSVGNumber($point[0]);
+ $point[1] = Format::formatSVGNumber($point[1]);
+ if ($lastPoint && $this->discrete) {
+ $pathDescription .= sprintf($tpl, $point[0], $lastPoint[1]);
+ }
+ $pathDescription .= vsprintf($tpl, $point);
+ $lastPoint = $point;
+ $tpl = self::TPL_STRAIGHT;
+ }
+
+ $path = $doc->createElement('path');
+
+ $id = $this->id ?? uniqid('path-');
+ $path->setAttribute('id', $id);
+ $this->setId($id);
+
+ $path->setAttribute('d', $pathDescription);
+
+ $this->applyAttributes($path);
+ $style = new DOMDocument();
+ $style->loadHTML($this->getStyle());
+
+ $path->appendChild(
+ $path->ownerDocument->importNode(
+ $style->getElementsByTagName('style')->item(0),
+ true
+ )
+ );
+
+ $group->appendChild($path);
+ return $group;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/PieSlice.php b/library/Icinga/Chart/Primitive/PieSlice.php
new file mode 100644
index 0000000..f898435
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/PieSlice.php
@@ -0,0 +1,307 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMDocument;
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Format;
+
+/**
+ * Component for drawing a pie slice
+ */
+class PieSlice extends Animatable implements Drawable
+{
+ /**
+ * The radius of this pieslice relative to the canvas
+ *
+ * @var int
+ */
+ private $radius = 50;
+
+ /**
+ * The start radian of the pie slice
+ *
+ * @var float
+ */
+ private $startRadian = 0;
+
+ /**
+ * The end radian of the pie slice
+ *
+ * @var float
+ */
+ private $endRadian = 0;
+
+ /**
+ * The x position of the pie slice's center
+ *
+ * @var int
+ */
+ private $x;
+
+ /**
+ * The y position of the pie slice's center
+ *
+ * @var int
+ */
+ private $y;
+
+ /**
+ * The caption of the pie slice, empty string means no caption
+ *
+ * @var string
+ */
+ private $caption = "";
+
+ /**
+ * The offset of the caption, shifting the indicator from the center of the pie slice
+ *
+ * This is required for nested pie slices.
+ *
+ * @var int
+ */
+ private $captionOffset = 0;
+
+ /**
+ * The minimum radius the label must respect
+ *
+ * @var int
+ */
+ private $outerCaptionBound = 0;
+
+ /**
+ * An optional group element to add labels to when rendering
+ *
+ * @var DOMElement
+ */
+ private $labelGroup;
+
+ /**
+ * Create a pie slice
+ *
+ * @param int $radius The radius of the slice
+ * @param int $percent The percentage the slice represents
+ * @param int $percentStart The percentage where this slice starts
+ */
+ public function __construct($radius, $percent, $percentStart = 0)
+ {
+ $this->x = $this->y = $this->radius = $radius;
+
+ $this->startRadian = M_PI * $percentStart/50;
+ $this->endRadian = M_PI * ($percent + $percentStart)/50;
+ }
+
+ /**
+ * Create the path for the pie slice
+ *
+ * @param int $x The x position of the pie slice
+ * @param int $y The y position of the pie slice
+ * @param int $r The absolute radius of the pie slice
+ *
+ * @return string A SVG path string
+ */
+ private function getPieSlicePath($x, $y, $r)
+ {
+ // The coordinate system is mirrored on the Y axis, so we have to flip cos and sin
+ $xStart = $x + ($r * sin($this->startRadian));
+ $yStart = $y - ($r * cos($this->startRadian));
+
+ if ($this->endRadian - $this->startRadian == 2*M_PI) {
+ // To draw a full circle, adjust arc endpoint by a small (unvisible) value
+ $this->endRadian -= 0.001;
+ $pathString = 'M ' . Format::formatSVGNumber($xStart) . ' ' . Format::formatSVGNumber($yStart);
+ } else {
+ // Start at the center of the pieslice
+ $pathString = 'M ' . $x . ' ' . $y;
+ // Draw a straight line to the upper part of the arc
+ $pathString .= ' L ' . Format::formatSVGNumber($xStart) . ' ' . Format::formatSVGNumber($yStart);
+ }
+
+ // Instead of directly connecting the upper part of the arc (leaving a triangle), draw a bow with the radius
+ $pathString .= ' A ' . Format::formatSVGNumber($r) . ' ' . Format::formatSVGNumber($r);
+ // These are the flags for the bow, see the SVG path documentation for details
+ // http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
+ $pathString .= ' 0 ' . (($this->endRadian - $this->startRadian > M_PI) ? '1' : '0 ') . ' 1';
+
+ // xEnd and yEnd are the lower point of the arc
+ $xEnd = $x + ($r * sin($this->endRadian));
+ $yEnd = $y - ($r * cos($this->endRadian));
+ $pathString .= ' ' . Format::formatSVGNumber($xEnd) . ' ' . Format::formatSVGNumber($yEnd);
+
+ return $pathString;
+ }
+
+ /**
+ * Draw the label handler and the text for this pie slice
+ *
+ * @param RenderContext $ctx The rendering context to use for coordinate translation
+ * @param int $r The radius of the pie in absolute coordinates
+ *
+ * @return DOMElement The group DOMElement containing the handle and label
+ */
+ private function drawDescriptionLabel(RenderContext $ctx, $r)
+ {
+ $group = $ctx->getDocument()->createElement('g');
+ $rOuter = ($ctx->xToAbsolute($this->outerCaptionBound) + $ctx->yToAbsolute($this->outerCaptionBound)) / 2;
+ $addOffset = $rOuter - $r ;
+ if ($addOffset < 0) {
+ $addOffset = 0;
+ }
+ list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
+ $midRadius = $this->startRadian + ($this->endRadian - $this->startRadian) / 2;
+ list($offsetX, $offsetY) = $ctx->toAbsolute($this->captionOffset, $this->captionOffset);
+
+ $midX = $x + intval(($offsetX + $r)/2 * sin($midRadius));
+ $midY = $y - intval(($offsetY + $r)/2 * cos($midRadius));
+
+ // Draw the handle
+ $path = new Path(array($midX, $midY));
+
+ $midX += ($addOffset + $r/3) * ($midRadius > M_PI ? -1 : 1);
+ $path->append(array($midX, $midY))->toAbsolute();
+
+ $midX += intval($r/2 * sin(M_PI/9)) * ($midRadius > M_PI ? -1 : 1);
+ $midY -= intval($r/2 * cos(M_PI/3)) * ($midRadius < M_PI*1.4 && $midRadius > M_PI/3 ? -1 : 1);
+
+ if ($ctx->yToRelative($midY) > 100) {
+ $midY = $ctx->yToAbsolute(100);
+ } elseif ($ctx->yToRelative($midY) < 0) {
+ $midY = $ctx->yToAbsolute($ctx->yToRelative(100+$midY));
+ }
+
+ $path->append(array($midX , $midY));
+ $rel = $ctx->toRelative($midX, $midY);
+
+ // Draw the text box
+ $text = new Text($rel[0]+1.5, $rel[1], $this->caption);
+ $text->setFontSize('5em');
+ $text->setAlignment(($midRadius > M_PI ? Text::ALIGN_END : Text::ALIGN_START));
+
+ $group->appendChild($path->toSvg($ctx));
+ $group->appendChild($text->toSvg($ctx));
+
+ return $group;
+ }
+
+ /**
+ * Set the x position of the pie slice
+ *
+ * @param int $x The new x position
+ *
+ * @return $this Fluid interface
+ */
+ public function setX($x)
+ {
+ $this->x = $x;
+ return $this;
+ }
+
+ /**
+ * Set the y position of the pie slice
+ *
+ * @param int $y The new y position
+ *
+ * @return $this Fluid interface
+ */
+ public function setY($y)
+ {
+ $this->y = $y;
+ return $this;
+ }
+
+ /**
+ * Set a root element to be used for drawing labels
+ *
+ * @param DOMElement $group The label group
+ *
+ * @return $this Fluid interface
+ */
+ public function setLabelGroup(DOMElement $group)
+ {
+ $this->labelGroup = $group;
+ return $this;
+ }
+
+ /**
+ * Set the caption for this label
+ *
+ * @param string $caption The caption for this element
+ *
+ * @return $this Fluid interface
+ */
+ public function setCaption($caption)
+ {
+ $this->caption = $caption;
+ return $this;
+ }
+
+ /**
+ * Set the internal offset of the caption handle
+ *
+ * @param int $offset The offset for the caption handle
+ *
+ * @return $this Fluid interface
+ */
+ public function setCaptionOffset($offset)
+ {
+ $this->captionOffset = $offset;
+ return $this;
+ }
+
+ /**
+ * Set the minimum radius to be used for drawing labels
+ *
+ * @param int $bound The offset for the caption text
+ *
+ * @return $this Fluid interface
+ */
+ public function setOuterCaptionBound($bound)
+ {
+ $this->outerCaptionBound = $bound;
+ return $this;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $doc = $ctx->getDocument();
+ $group = $doc->createElement('g');
+ $r = ($ctx->xToAbsolute($this->radius) + $ctx->yToAbsolute($this->radius)) / 2;
+ list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
+
+ $slicePath = $doc->createElement('path');
+
+ $slicePath->setAttribute('d', $this->getPieSlicePath($x, $y, $r));
+ $slicePath->setAttribute('data-icinga-graph-type', 'pieslice');
+
+ $id = $this->id ?? uniqid('slice-');
+ $slicePath->setAttribute('id', $id);
+ $this->setId($id);
+
+ $style = new DOMDocument();
+ $style->loadHTML($this->getStyle());
+
+ $slicePath->appendChild(
+ $slicePath->ownerDocument->importNode(
+ $style->getElementsByTagName('style')->item(0),
+ true
+ )
+ );
+
+ $this->applyAttributes($slicePath);
+ $group->appendChild($slicePath);
+ if ($this->caption != "") {
+ $lblGroup = ($this->labelGroup ? $this->labelGroup : $group);
+ $lblGroup->appendChild($this->drawDescriptionLabel($ctx, $r));
+ }
+ return $group;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/RawElement.php b/library/Icinga/Chart/Primitive/RawElement.php
new file mode 100644
index 0000000..721b6e0
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/RawElement.php
@@ -0,0 +1,43 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Wrapper for raw elements to be added as Drawable's
+ */
+class RawElement implements Drawable
+{
+
+ /**
+ * The DOMElement wrapped by this Drawable
+ *
+ * @var DOMElement
+ */
+ private $domEl;
+
+ /**
+ * Create this RawElement
+ *
+ * @param DOMElement $el The element to wrap here
+ */
+ public function __construct(DOMElement $el)
+ {
+ $this->domEl = $el;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ return $this->domEl;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Rect.php b/library/Icinga/Chart/Primitive/Rect.php
new file mode 100644
index 0000000..0c0835f
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Rect.php
@@ -0,0 +1,119 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use DOMDocument;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Format;
+
+/**
+ * Drawable representing the SVG rect element
+ */
+class Rect extends Animatable implements Drawable
+{
+ /**
+ * The x position
+ *
+ * @var int
+ */
+ private $x;
+
+ /**
+ * The y position
+ *
+ * @var int
+ */
+ private $y;
+
+ /**
+ * The width of this rect
+ *
+ * @var int
+ */
+ private $width;
+
+ /**
+ * The height of this rect
+ *
+ * @var int
+ */
+ private $height;
+
+ /**
+ * Whether to keep the ratio
+ *
+ * @var bool
+ */
+ private $keepRatio = false;
+
+ /**
+ * Create this rect
+ *
+ * @param int $x The x position of the rect
+ * @param int $y The y position of the rectangle
+ * @param int $width The width of the rectangle
+ * @param int $height The height of the rectangle
+ */
+ public function __construct($x, $y, $width, $height)
+ {
+ $this->x = $x;
+ $this->y = $y;
+ $this->width = $width;
+ $this->height = $height;
+ }
+
+ /**
+ * Call to let the rectangle keep the ratio
+ */
+ public function keepRatio()
+ {
+ $this->keepRatio = true;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $doc = $ctx->getDocument();
+ $rect = $doc->createElement('rect');
+
+ list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
+ if ($this->keepRatio) {
+ $ctx->keepRatio();
+ }
+ list($width, $height) = $ctx->toAbsolute($this->width, $this->height);
+ if ($this->keepRatio) {
+ $ctx->ignoreRatio();
+ }
+ $rect->setAttribute('x', Format::formatSVGNumber($x));
+ $rect->setAttribute('y', Format::formatSVGNumber($y));
+ $rect->setAttribute('width', Format::formatSVGNumber($width));
+ $rect->setAttribute('height', Format::formatSVGNumber($height));
+
+ $id = $this->id ?? uniqid('rect-');
+ $rect->setAttribute('id', $id);
+ $this->setId($id);
+
+ $this->applyAttributes($rect);
+ $this->appendAnimation($rect, $ctx);
+
+ $style = new DOMDocument();
+ $style->loadHTML($this->getStyle());
+
+ $rect->appendChild(
+ $rect->ownerDocument->importNode(
+ $style->getElementsByTagName('style')->item(0),
+ true
+ )
+ );
+
+ return $rect;
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Styleable.php b/library/Icinga/Chart/Primitive/Styleable.php
new file mode 100644
index 0000000..15025bf
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Styleable.php
@@ -0,0 +1,161 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Chart\Primitive;
+
+use DOMElement;
+use Icinga\Util\Csp;
+use ipl\Web\Style;
+
+/**
+ * Base class for stylable drawables
+ */
+class Styleable
+{
+
+ /**
+ * The stroke width to use
+ *
+ * @var int|float
+ */
+ public $strokeWidth = 0;
+
+ /**
+ * The stroke color to use
+ *
+ * @var string
+ */
+ public $strokeColor = '#000';
+
+ /**
+ * The fill color to use
+ *
+ * @var string
+ */
+ public $fill = 'none';
+
+ /**
+ * Additional styles to be appended to the style attribute
+ *
+ * @var array<string, string>
+ */
+ public $additionalStyle = [];
+
+ /**
+ * The id of this element
+ *
+ * @var ?string
+ */
+ public $id = null;
+
+ /**
+ * Additional attributes to be set
+ *
+ * @var array
+ */
+ public $attributes = array();
+
+ /**
+ * Set the stroke width for this drawable
+ *
+ * @param int|float $width The stroke with unit
+ *
+ * @return $this Fluid interface
+ */
+ public function setStrokeWidth($width)
+ {
+ $this->strokeWidth = $width;
+ return $this;
+ }
+
+ /**
+ * Set the color for the stroke or none for no stroke
+ *
+ * @param string $color The color to set for the stroke
+ *
+ * @return $this Fluid interface
+ */
+ public function setStrokeColor($color)
+ {
+ $this->strokeColor = $color ? $color : 'none';
+ return $this;
+ }
+
+ /**
+ * Set additional styles for this drawable
+ *
+ * @param array<string, string> $styles The styles to set additionally
+ *
+ * @return $this Fluid interface
+ */
+ public function setAdditionalStyle($styles)
+ {
+ $this->additionalStyle = $styles;
+ return $this;
+ }
+
+ /**
+ * Set the fill for this styleable
+ *
+ * @param string $color The color to use for filling or null to use no fill
+ *
+ * @return $this Fluid interface
+ */
+ public function setFill($color = null)
+ {
+ $this->fill = $color ? $color : 'none';
+ return $this;
+ }
+
+ /**
+ * Set the id for this element
+ *
+ * @param string $id The id to set for this element
+ *
+ * @return $this Fluid interface
+ */
+ public function setId($id)
+ {
+ $this->id = $id;
+
+ return $this;
+ }
+
+ /**
+ * Return the ruleset used for styling the DOMNode
+ *
+ * @return Style A ruleset containing styles
+ */
+ public function getStyle()
+ {
+ $styles = $this->additionalStyle;
+ $styles['fill'] = $this->fill;
+ $styles['stroke'] = $this->strokeColor;
+ $styles['stroke-width'] = (string) $this->strokeWidth;
+
+ return (new Style())
+ ->setNonce(Csp::getStyleNonce())
+ ->add("#$this->id", $styles);
+ }
+
+ /**
+ * Add an additional attribute to this element
+ */
+ public function setAttribute($key, $value)
+ {
+ $this->attributes[$key] = $value;
+ }
+
+ /**
+ * Apply attribute to a DOMElement
+ *
+ * @param DOMElement $el Element to apply attributes
+ */
+ protected function applyAttributes(DOMElement $el)
+ {
+ foreach ($this->attributes as $name => $value) {
+ $el->setAttribute($name, $value);
+ }
+ }
+}
diff --git a/library/Icinga/Chart/Primitive/Text.php b/library/Icinga/Chart/Primitive/Text.php
new file mode 100644
index 0000000..f6bf365
--- /dev/null
+++ b/library/Icinga/Chart/Primitive/Text.php
@@ -0,0 +1,184 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Chart\Primitive;
+
+use DOMDocument;
+use DOMElement;
+use DOMText;
+use Icinga\Chart\Render\RenderContext;
+use Icinga\Chart\Format;
+use ipl\Html\HtmlDocument;
+
+/**
+ * Wrapper for the SVG text element
+ */
+class Text extends Styleable implements Drawable
+{
+ /**
+ * Align the text to end at the x and y position
+ */
+ const ALIGN_END = 'end';
+
+ /**
+ * Align the text to start at the x and y position
+ */
+ const ALIGN_START = 'start';
+
+ /**
+ * Align the text to be centered at the x and y position
+ */
+ const ALIGN_MIDDLE = 'middle';
+
+ /**
+ * The x position of the Text
+ *
+ * @var int
+ */
+ private $x;
+
+ /**
+ * The y position of the Text
+ *
+ * @var int
+ */
+ private $y;
+
+ /**
+ * The text content
+ *
+ * @var string
+ */
+ private $text;
+
+ /**
+ * The size of the font
+ *
+ * @var string
+ */
+ private $fontSize = '1.5em';
+
+ /**
+ * The weight of the font
+ *
+ * @var string
+ */
+ private $fontWeight = 'normal';
+
+ /**
+ * The default fill color
+ *
+ * @var string
+ */
+ public $fill = '#000';
+
+ /**
+ * The alignment of the text
+ *
+ * @var string
+ */
+ private $alignment = self::ALIGN_START;
+
+ /**
+ * Set the font-stretch property of the text
+ */
+ private $fontStretch = 'semi-condensed';
+
+ /**
+ * Construct a new text drawable
+ *
+ * @param int $x The x position of the text
+ * @param int $y The y position of the text
+ * @param string $text The text this component should contain
+ * @param string $fontSize The font size of the text
+ */
+ public function __construct($x, $y, $text, $fontSize = '1.5em')
+ {
+ $this->x = $x;
+ $this->y = $y;
+ $this->text = $text;
+ $this->fontSize = $fontSize;
+
+ $this->setAdditionalStyle([
+ 'font-size' => $this->fontSize,
+ 'font-family' => 'Ubuntu, Calibri, Trebuchet MS, Helvetica, Verdana, sans-serif',
+ 'font-weight' => $this->fontWeight,
+ 'font-stretch' => $this->fontStretch,
+ 'font-style' => 'normal',
+ 'text-anchor' => $this->alignment
+ ]);
+ }
+
+ /**
+ * Set the font size of the svg text element
+ *
+ * @param string $size The font size including a unit
+ *
+ * @return $this Fluid interface
+ */
+ public function setFontSize($size)
+ {
+ $this->fontSize = $size;
+ return $this;
+ }
+
+ /**
+ * Set the text alignment with one of the ALIGN_* constants
+ *
+ * @param String $align Value how to align
+ *
+ * @return $this Fluid interface
+ */
+ public function setAlignment($align)
+ {
+ $this->alignment = $align;
+ return $this;
+ }
+
+ /**
+ * Set the weight of the current font
+ *
+ * @param string $weight The weight of the string
+ *
+ * @return $this Fluid interface
+ */
+ public function setFontWeight($weight)
+ {
+ $this->fontWeight = $weight;
+ return $this;
+ }
+
+ /**
+ * Create the SVG representation from this Drawable
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement The SVG Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ list($x, $y) = $ctx->toAbsolute($this->x, $this->y);
+ $text = $ctx->getDocument()->createElement('text');
+ $text->setAttribute('x', Format::formatSVGNumber($x - 15));
+
+ $id = $this->id ?? uniqid('text-');
+ $text->setAttribute('id', $id);
+ $this->setId($id);
+
+ $text->setAttribute('y', Format::formatSVGNumber($y));
+ $text->appendChild(new DOMText($this->text));
+
+ $style = new DOMDocument();
+ $style->loadHTML($this->getStyle());
+
+ $text->appendChild(
+ $text->ownerDocument->importNode(
+ $style->getElementsByTagName('style')->item(0),
+ true
+ )
+ );
+
+ return $text;
+ }
+}