summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Chart/Graph
diff options
context:
space:
mode:
Diffstat (limited to 'library/Icinga/Chart/Graph')
-rw-r--r--library/Icinga/Chart/Graph/BarGraph.php163
-rw-r--r--library/Icinga/Chart/Graph/LineGraph.php202
-rw-r--r--library/Icinga/Chart/Graph/StackedGraph.php88
-rw-r--r--library/Icinga/Chart/Graph/Tooltip.php143
4 files changed, 596 insertions, 0 deletions
diff --git a/library/Icinga/Chart/Graph/BarGraph.php b/library/Icinga/Chart/Graph/BarGraph.php
new file mode 100644
index 0000000..be142bf
--- /dev/null
+++ b/library/Icinga/Chart/Graph/BarGraph.php
@@ -0,0 +1,163 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Graph;
+
+use DOMElement;
+use Icinga\Chart\Primitive\Animation;
+use Icinga\Chart\Primitive\Drawable;
+use Icinga\Chart\Primitive\Rect;
+use Icinga\Chart\Primitive\Styleable;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Bar graph implementation
+ */
+class BarGraph extends Styleable implements Drawable
+{
+ /**
+ * The dataset order
+ *
+ * @var int
+ */
+ private $order = 0;
+
+ /**
+ * The width of the bars.
+ *
+ * @var int
+ */
+ private $barWidth = 3;
+
+ /**
+ * The dataset to use for this bar graph
+ *
+ * @var array
+ */
+ private $dataSet;
+
+ /**
+ * The tooltips
+ *
+ * @var
+ */
+ private $tooltips;
+
+ /**
+ * All graphs
+ *
+ * @var
+ */
+ private $graphs;
+
+ /**
+ * Create a new BarGraph with the given dataset
+ *
+ * @param array $dataSet An array of data points
+ * @param int $order The graph number displayed by this BarGraph
+ * @param array $tooltips The tooltips to display for each value
+ */
+ public function __construct(
+ array $dataSet,
+ array &$graphs,
+ $order,
+ array $tooltips = null
+ ) {
+ $this->order = $order;
+ $this->dataSet = $dataSet;
+
+ $this->tooltips = $tooltips;
+ $ts = [];
+ foreach ($this->tooltips as $value) {
+ $ts[] = $value;
+ }
+ $this->tooltips = $ts;
+
+ $this->graphs = $graphs;
+ }
+
+ /**
+ * Apply configuration styles from the $cfg
+ *
+ * @param array $cfg The configuration as given in the drawBars call
+ */
+ public function setStyleFromConfig(array $cfg)
+ {
+ foreach ($cfg as $elem => $value) {
+ if ($elem === 'color') {
+ $this->setFill($value);
+ } elseif ($elem === 'width') {
+ $this->setStrokeWidth($value);
+ }
+ }
+ }
+
+ /**
+ * Draw a single rectangle
+ *
+ * @param array $point The
+ * @param string $fill The fill color to use
+ * @param $strokeWidth
+ * @param ?int $index
+ *
+ * @return Rect
+ */
+ private function drawSingleBar($point, $fill, $strokeWidth, $index = null)
+ {
+ $rect = new Rect($point[0] - ($this->barWidth / 2), $point[1], $this->barWidth, 100 - $point[1]);
+ $rect->setFill($fill);
+ $rect->setStrokeWidth($strokeWidth);
+ $rect->setStrokeColor('black');
+ if (isset($index)) {
+ $rect->setAttribute('data-icinga-graph-index', $index);
+ }
+ $rect->setAttribute('data-icinga-graph-type', 'bar');
+ $rect->setAdditionalStyle(['clip-path' => 'url(#clip)']);
+ return $rect;
+ }
+
+ /**
+ * Render this BarChart
+ *
+ * @param RenderContext $ctx The rendering context to use for drawing
+ *
+ * @return DOMElement $dom Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $doc = $ctx->getDocument();
+ $group = $doc->createElement('g');
+ $idx = 0;
+
+ if (count($this->dataSet) > 15) {
+ $this->barWidth = 2;
+ }
+ if (count($this->dataSet) > 25) {
+ $this->barWidth = 1;
+ }
+
+ foreach ($this->dataSet as $x => $point) {
+ // add white background bar, to prevent other bars from altering transparency effects
+ $bar = $this->drawSingleBar($point, 'white', $this->strokeWidth, $idx++)->toSvg($ctx);
+ $group->appendChild($bar);
+
+ // draw actual bar
+ $bar = $this->drawSingleBar($point, $this->fill, $this->strokeWidth)->toSvg($ctx);
+ if (isset($this->tooltips[$x])) {
+ $data = array(
+ 'label' => isset($this->graphs[$this->order]['label']) ?
+ strtolower($this->graphs[$this->order]['label']) : '',
+ 'color' => isset($this->graphs[$this->order]['color']) ?
+ strtolower($this->graphs[$this->order]['color']) : '#fff'
+ );
+ $format = isset($this->graphs[$this->order]['tooltip'])
+ ? $this->graphs[$this->order]['tooltip'] : null;
+ $title = $ctx->getDocument()->createElement('title');
+ $title->textContent = $this->tooltips[$x]->renderNoHtml($this->order, $data, $format);
+ $bar->appendChild($title);
+ }
+ $group->appendChild($bar);
+ }
+ return $group;
+ }
+}
diff --git a/library/Icinga/Chart/Graph/LineGraph.php b/library/Icinga/Chart/Graph/LineGraph.php
new file mode 100644
index 0000000..21f930a
--- /dev/null
+++ b/library/Icinga/Chart/Graph/LineGraph.php
@@ -0,0 +1,202 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+
+namespace Icinga\Chart\Graph;
+
+use DOMElement;
+use Icinga\Chart\Primitive\Drawable;
+use Icinga\Chart\Primitive\Path;
+use Icinga\Chart\Primitive\Circle;
+use Icinga\Chart\Primitive\Styleable;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * LineGraph implementation for drawing a set of datapoints as
+ * a connected path
+ */
+class LineGraph extends Styleable implements Drawable
+{
+ /**
+ * The dataset to use
+ *
+ * @var array
+ */
+ private $dataset;
+
+ /**
+ * True to show dots for each datapoint
+ *
+ * @var bool
+ */
+ private $showDataPoints = false;
+
+ /**
+ * When true, the path will be discrete, i.e. showing hard steps instead of a direct line
+ *
+ * @var bool
+ */
+ private $isDiscrete = false;
+
+ /**
+ * The tooltips
+ *
+ * @var
+ */
+ private $tooltips;
+
+ /** @var array */
+ private $graphs;
+
+ /** @var int */
+ private $order;
+
+ /**
+ * The default stroke width
+ * @var int
+ */
+ public $strokeWidth = 5;
+
+ /**
+ * The size of the displayed dots
+ *
+ * @var int
+ */
+ public $dotWith = 0;
+
+ /**
+ * Create a new LineGraph displaying the given dataset
+ *
+ * @param array $dataset An array of [x, y] arrays to display
+ */
+ public function __construct(
+ array $dataset,
+ array &$graphs,
+ $order,
+ array $tooltips = null
+ ) {
+ usort($dataset, array($this, 'sortByX'));
+ $this->dataset = $dataset;
+ $this->graphs = $graphs;
+
+ $this->tooltips = $tooltips;
+ $ts = [];
+ foreach ($this->tooltips as $value) {
+ $ts[] = $value;
+ }
+ $this->tooltips = $ts;
+ $this->order = $order;
+ }
+
+ /**
+ * Set datapoints to be emphased via dots
+ *
+ * @param bool $bool True to enable datapoints, otherwise false
+ */
+ public function setShowDataPoints($bool)
+ {
+ $this->showDataPoints = $bool;
+ }
+
+ /**
+ * Sort the daset by the xaxis
+ *
+ * @param array $v1
+ * @param array $v2
+ * @return int
+ */
+ private function sortByX(array $v1, array $v2)
+ {
+ if ($v1[0] === $v2[0]) {
+ return 0;
+ }
+ return ($v1[0] < $v2[0]) ? -1 : 1;
+ }
+
+ /**
+ * Configure this style
+ *
+ * @param array $cfg The configuration as given in the drawLine call
+ */
+ public function setStyleFromConfig(array $cfg)
+ {
+ $fill = false;
+ foreach ($cfg as $elem => $value) {
+ if ($elem === 'color') {
+ $this->setStrokeColor($value);
+ } elseif ($elem === 'width') {
+ $this->setStrokeWidth($value);
+ } elseif ($elem === 'showPoints') {
+ $this->setShowDataPoints($value);
+ } elseif ($elem === 'fill') {
+ $fill = $value;
+ } elseif ($elem === 'discrete') {
+ $this->isDiscrete = true;
+ }
+ }
+ if ($fill) {
+ $this->setFill($this->strokeColor);
+ $this->setStrokeColor('black');
+ }
+ }
+
+ /**
+ * Render this BarChart
+ *
+ * @param RenderContext $ctx The rendering context to use for drawing
+ *
+ * @return DOMElement $dom Element
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $path = new Path($this->dataset);
+ if ($this->isDiscrete) {
+ $path->setDiscrete(true);
+ }
+ $path->setStrokeColor($this->strokeColor);
+ $path->setStrokeWidth($this->strokeWidth);
+
+ $path->setAttribute('data-icinga-graph-type', 'line');
+ if ($this->fill !== 'none') {
+ $firstX = $this->dataset[0][0];
+ $lastX = $this->dataset[count($this->dataset)-1][0];
+ $path->prepend(array($firstX, 100))
+ ->append(array($lastX, 100));
+ $path->setFill($this->fill);
+ }
+
+ $path->setAdditionalStyle(['clip-path' => 'url(#clip)']);
+ $path->setId($this->id ?? uniqid('line-graph-'));
+ $group = $path->toSvg($ctx);
+
+ foreach ($this->dataset as $x => $point) {
+ if ($this->showDataPoints === true) {
+ $dot = new Circle($point[0], $point[1], $this->dotWith);
+ $dot->setFill($this->strokeColor);
+ $group->appendChild($dot->toSvg($ctx));
+ }
+
+ // Draw invisible circle for tooltip hovering
+ if (isset($this->tooltips[$x])) {
+ $invisible = new Circle($point[0], $point[1], 20);
+ $invisible->setFill($this->strokeColor);
+ $invisible->setAdditionalStyle(['opacity' => '0.0']);
+ $data = array(
+ 'label' => isset($this->graphs[$this->order]['label']) ?
+ strtolower($this->graphs[$this->order]['label']) : '',
+ 'color' => isset($this->graphs[$this->order]['color']) ?
+ strtolower($this->graphs[$this->order]['color']) : '#fff'
+ );
+ $format = isset($this->graphs[$this->order]['tooltip'])
+ ? $this->graphs[$this->order]['tooltip'] : null;
+ $title = $ctx->getDocument()->createElement('title');
+ $title->textContent = $this->tooltips[$x]->renderNoHtml($this->order, $data, $format);
+ $invisibleRendered = $invisible->toSvg($ctx);
+ $invisibleRendered->appendChild($title);
+ $group->appendChild($invisibleRendered);
+ }
+ }
+
+ return $group;
+ }
+}
diff --git a/library/Icinga/Chart/Graph/StackedGraph.php b/library/Icinga/Chart/Graph/StackedGraph.php
new file mode 100644
index 0000000..49801a9
--- /dev/null
+++ b/library/Icinga/Chart/Graph/StackedGraph.php
@@ -0,0 +1,88 @@
+<?php
+/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Graph;
+
+use DOMElement;
+use Icinga\Chart\Primitive\Drawable;
+use Icinga\Chart\Render\RenderContext;
+
+/**
+ * Graph implementation that stacks several graphs and displays them in a cumulative way
+ */
+class StackedGraph implements Drawable
+{
+ /**
+ * All graphs displayed in this stackedgraph
+ *
+ * @var array
+ */
+ private $stack = array();
+
+ /**
+ * An associative array containing x points as the key and an array of y values as the value
+ *
+ * @var array
+ */
+ private $points = array();
+
+ /**
+ * Add a graph to this stack and aggregate the values on the fly
+ *
+ * This modifies the dataset as a side effect
+ *
+ * @param array $subGraph
+ */
+ public function addGraph(array &$subGraph)
+ {
+ foreach ($subGraph['data'] as &$point) {
+ $x = $point[0];
+ if (!isset($this->points[$x])) {
+ $this->points[$x] = 0;
+ }
+ // store old y-value for displaying the actual (non-aggregated)
+ // value in the tooltip
+ $point[2] = $point[1];
+
+ $this->points[$x] += $point[1];
+ $point[1] = $this->points[$x];
+ }
+ }
+
+ /**
+ * Add a graph to the stack
+ *
+ * @param $graph
+ */
+ public function addToStack($graph)
+ {
+ $this->stack[] = $graph;
+ }
+
+ /**
+ * Empty the stack
+ *
+ * @return bool
+ */
+ public function stackEmpty()
+ {
+ return empty($this->stack);
+ }
+
+ /**
+ * Render this stack in the correct order
+ *
+ * @param RenderContext $ctx The context to use for rendering
+ *
+ * @return DOMElement The SVG representation of this graph
+ */
+ public function toSvg(RenderContext $ctx)
+ {
+ $group = $ctx->getDocument()->createElement('g');
+ $renderOrder = array_reverse($this->stack);
+ foreach ($renderOrder as $stackElem) {
+ $group->appendChild($stackElem->toSvg($ctx));
+ }
+ return $group;
+ }
+}
diff --git a/library/Icinga/Chart/Graph/Tooltip.php b/library/Icinga/Chart/Graph/Tooltip.php
new file mode 100644
index 0000000..7236685
--- /dev/null
+++ b/library/Icinga/Chart/Graph/Tooltip.php
@@ -0,0 +1,143 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Chart\Graph;
+
+/**
+ * A tooltip that stores and aggregates information about displayed data
+ * points of a graph and replaces them in a format string to render the description
+ * for specific data points of the graph.
+ *
+ * When render() is called, placeholders for the keys for each data entry will be replaced by
+ * the current value of this data set and the formatted string will be returned.
+ * The content of the replaced keys can change for each data set and depends on how the data
+ * is passed to this class. There are several types of properties:
+ *
+ * <ul>
+ * <li>Global properties</li>: Key-value pairs that stay the same every time render is called, and are
+ * passed to an instance in the constructor.
+ * <li>Aggregated properties</li>: Global properties that are created automatically from
+ * all attached data points.
+ * <li>Local properties</li>: Key-value pairs that only apply to a single data point and
+ * are passed to the render-function.
+ * </ul>
+ */
+class Tooltip
+{
+ /**
+ * The default format string used
+ * when no other format is specified
+ *
+ * @var string
+ */
+ private $defaultFormat;
+
+ /**
+ * All aggregated points
+ *
+ * @var array
+ */
+ private $points = array();
+
+ /**
+ * Contains all static replacements
+ *
+ * @var array
+ */
+ private $data = array(
+ 'sum' => 0
+ );
+
+ /**
+ * Used to format the displayed tooltip.
+ *
+ * @var string
+ */
+ protected $tooltipFormat;
+
+ /**
+ * Create a new tooltip with the specified default format string
+ *
+ * Allows you to set the global data for this tooltip, that is displayed every
+ * time render is called.
+ *
+ * @param array $data Map of global properties
+ * @param string $format The default format string
+ */
+ public function __construct(
+ $data = array(),
+ $format = '<b>{title}</b>: {value} {label}'
+ ) {
+ $this->data = array_merge($this->data, $data);
+ $this->defaultFormat = $format;
+ }
+
+ /**
+ * Add a single data point to update the aggregated properties for this tooltip
+ *
+ * @param $point array Contains the (x,y) values of the data set
+ */
+ public function addDataPoint($point)
+ {
+ // set x-value
+ if (!isset($this->data['title'])) {
+ $this->data['title'] = $point[0];
+ }
+
+ // aggregate y-values
+ $y = (int)$point[1];
+ if (isset($point[2])) {
+ // load original value in case value already aggregated
+ $y = (int)$point[2];
+ }
+
+ if (!isset($this->data['min']) || $this->data['min'] > $y) {
+ $this->data['min'] = $y;
+ }
+ if (!isset($this->data['max']) || $this->data['max'] < $y) {
+ $this->data['max'] = $y;
+ }
+ $this->data['sum'] += $y;
+ $this->points[] = $y;
+ }
+
+ /**
+ * Format the tooltip for a certain data point
+ *
+ * @param array $order Which data set to render
+ * @param array $data The local data for this tooltip
+ * @param string $format Use a custom format string for this data set
+ *
+ * @return mixed|string The tooltip value
+ */
+ public function render($order, $data = array(), $format = null)
+ {
+ if (isset($format)) {
+ $str = $format;
+ } else {
+ $str = $this->defaultFormat;
+ }
+ $data['value'] = $this->points[$order];
+ foreach (array_merge($this->data, $data) as $key => $value) {
+ $str = str_replace('{' . $key . '}', $value, $str);
+ }
+ return $str;
+ }
+
+ /**
+ * Format the tooltip for a certain data point but remove all
+ * occurring html tags
+ *
+ * This is useful for rendering clean tooltips on client without JavaScript
+ *
+ * @param array $order Which data set to render
+ * @param array $data The local data for this tooltip
+ * @param string $format Use a custom format string for this data set
+ *
+ * @return mixed|string The tooltip value, without any HTML tags
+ */
+ public function renderNoHtml($order, $data, $format)
+ {
+ return strip_tags($this->render($order, $data, $format));
+ }
+}