diff options
Diffstat (limited to '')
-rw-r--r-- | library/Icinga/Chart/Primitive/Animatable.php | 43 | ||||
-rw-r--r-- | library/Icinga/Chart/Primitive/Animation.php | 87 | ||||
-rw-r--r-- | library/Icinga/Chart/Primitive/Canvas.php | 140 | ||||
-rw-r--r-- | library/Icinga/Chart/Primitive/Circle.php | 84 | ||||
-rw-r--r-- | library/Icinga/Chart/Primitive/Drawable.php | 22 | ||||
-rw-r--r-- | library/Icinga/Chart/Primitive/Line.php | 103 | ||||
-rw-r--r-- | library/Icinga/Chart/Primitive/Path.php | 187 | ||||
-rw-r--r-- | library/Icinga/Chart/Primitive/PieSlice.php | 307 | ||||
-rw-r--r-- | library/Icinga/Chart/Primitive/RawElement.php | 43 | ||||
-rw-r--r-- | library/Icinga/Chart/Primitive/Rect.php | 119 | ||||
-rw-r--r-- | library/Icinga/Chart/Primitive/Styleable.php | 161 | ||||
-rw-r--r-- | library/Icinga/Chart/Primitive/Text.php | 184 |
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; + } +} |