Add tooltips to bar charts
Add a class to format and populate tooltips from graph data sets and implement those tooltips in the ChartController.
This commit is contained in:
parent
055a107b90
commit
4b55bcf8b6
|
@ -16,6 +16,13 @@ use Icinga\Chart\Render\RenderContext;
|
|||
*/
|
||||
class BarGraph extends Styleable implements Drawable
|
||||
{
|
||||
/**
|
||||
* The dataset order
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $order = 0;
|
||||
|
||||
/**
|
||||
* The width of the bars.
|
||||
*
|
||||
|
@ -30,14 +37,37 @@ class BarGraph extends Styleable implements Drawable
|
|||
*/
|
||||
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 datapoints
|
||||
* @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)
|
||||
{
|
||||
public function __construct(
|
||||
array $dataSet,
|
||||
array &$graphs,
|
||||
$order,
|
||||
array $tooltips = null
|
||||
) {
|
||||
$this->order = $order;
|
||||
$this->dataSet = $dataSet;
|
||||
$this->tooltips = $tooltips;
|
||||
$this->graphs = $graphs;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,6 +86,30 @@ class BarGraph extends Styleable implements Drawable
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a single rectangle
|
||||
*
|
||||
* @param array $point The
|
||||
* @param null $index
|
||||
* @param string $fill The fill color to use
|
||||
* @param $strokeWidth
|
||||
*
|
||||
* @return Rect
|
||||
*/
|
||||
private function drawSingleBar($point, $index = null, $fill, $strokeWidth)
|
||||
{
|
||||
$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
|
||||
*
|
||||
|
@ -68,23 +122,33 @@ class BarGraph extends Styleable implements Drawable
|
|||
$doc = $ctx->getDocument();
|
||||
$group = $doc->createElement('g');
|
||||
$idx = 0;
|
||||
foreach ($this->dataSet as $point) {
|
||||
$rect = new Rect($point[0] - 2, $point[1], 4, 100 - $point[1]);
|
||||
$rect->setFill($this->fill);
|
||||
$rect->setStrokeWidth($this->strokeWidth);
|
||||
$rect->setStrokeColor('black');
|
||||
$rect->setAttribute('data-icinga-graph-index', $idx++);
|
||||
$rect->setAttribute('data-icinga-graph-type', 'bar');
|
||||
$rect->setAdditionalStyle('clip-path: url(#clip);');
|
||||
/*$rect->setAnimation(
|
||||
new Animation(
|
||||
'y',
|
||||
$ctx->yToAbsolute(100),
|
||||
$ctx->yToAbsolute($point[1]),
|
||||
rand(1, 1.5)/2
|
||||
)
|
||||
);*/
|
||||
$group->appendChild($rect->toSvg($ctx));
|
||||
foreach ($this->dataSet as $x => $point) {
|
||||
// add white background bar, to prevent other bars from altering transparency effects
|
||||
$bar = $this->drawSingleBar($point, $idx++, 'white', $this->strokeWidth, $idx)->toSvg($ctx);
|
||||
$group->appendChild($bar);
|
||||
|
||||
// draw actual bar
|
||||
$bar = $this->drawSingleBar($point, null, $this->fill, $this->strokeWidth, $idx)->toSvg($ctx);
|
||||
$bar->setAttribute('class', 'chart-data');
|
||||
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;
|
||||
$bar->setAttribute(
|
||||
'title',
|
||||
$this->tooltips[$x]->renderNoHtml($this->order, $data, $format)
|
||||
);
|
||||
$bar->setAttribute(
|
||||
'title-rich',
|
||||
$this->tooltips[$x]->render($this->order, $data, $format)
|
||||
);
|
||||
}
|
||||
$group->appendChild($bar);
|
||||
}
|
||||
return $group;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,10 @@ class StackedGraph implements Drawable
|
|||
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];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
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></b><br> {value} of {sum} {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));
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ use Icinga\Chart\Axis;
|
|||
use Icinga\Chart\Graph\BarGraph;
|
||||
use Icinga\Chart\Graph\LineGraph;
|
||||
use Icinga\Chart\Graph\StackedGraph;
|
||||
use Icinga\Chart\Graph\Tooltip;
|
||||
use Icinga\Chart\Primitive\Canvas;
|
||||
use Icinga\Chart\Primitive\Rect;
|
||||
use Icinga\Chart\Primitive\Path;
|
||||
|
@ -74,6 +75,16 @@ class GridChart extends Chart
|
|||
*/
|
||||
private $stacks = array();
|
||||
|
||||
/**
|
||||
* An associative array containing all Tooltips used to render the titles
|
||||
*
|
||||
* Each tooltip represents the summary for all y-values of a certain x-value
|
||||
* in the grid chart
|
||||
*
|
||||
* @var Tooltip
|
||||
*/
|
||||
private $tooltips = array();
|
||||
|
||||
/**
|
||||
* Check if the current dataset has the proper structure for this chart.
|
||||
*
|
||||
|
@ -169,6 +180,26 @@ class GridChart extends Chart
|
|||
$this->legend->addDataset($graph);
|
||||
}
|
||||
}
|
||||
$this->initTooltips($data);
|
||||
}
|
||||
|
||||
|
||||
private function initTooltips($data)
|
||||
{
|
||||
foreach ($data as &$graph) {
|
||||
foreach ($graph['data'] as $x => $point) {
|
||||
if (!array_key_exists($x, $this->tooltips)) {
|
||||
$this->tooltips[$x] = new Tooltip(
|
||||
array(
|
||||
'color' => $graph['color'],
|
||||
|
||||
)
|
||||
|
||||
);
|
||||
}
|
||||
$this->tooltips[$x]->addDataPoint($point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -353,11 +384,16 @@ class GridChart extends Chart
|
|||
foreach ($this->graphs as $axisName => $graphs) {
|
||||
$axis = $this->axis[$axisName];
|
||||
$graphObj = null;
|
||||
foreach ($graphs as $graph) {
|
||||
foreach ($graphs as $dataset => $graph) {
|
||||
// determine the type and create a graph object for it
|
||||
switch ($graph['graphType']) {
|
||||
case self::TYPE_BAR:
|
||||
$graphObj = new BarGraph($axis->transform($graph['data']));
|
||||
$graphObj = new BarGraph(
|
||||
$axis->transform($graph['data']),
|
||||
$graphs,
|
||||
$dataset,
|
||||
$this->tooltips
|
||||
);
|
||||
break;
|
||||
case self::TYPE_LINE:
|
||||
$graphObj = new LineGraph($axis->transform($graph['data']));
|
||||
|
|
|
@ -154,30 +154,35 @@ class Monitoring_ChartController extends Controller
|
|||
->setXAxis(new \Icinga\Chart\Unit\StaticAxis())
|
||||
->setAxisMin(null, 0);
|
||||
|
||||
$tooltip = t('<b>{title}:</b><br />{value} of {sum} services are {label}');
|
||||
$this->view->chart->drawBars(
|
||||
array(
|
||||
'label' => t('Ok'),
|
||||
'color' => '#44bb77',
|
||||
'stack' => 'stack1',
|
||||
'data' => $okBars
|
||||
'data' => $okBars,
|
||||
'tooltip' => $tooltip
|
||||
),
|
||||
array(
|
||||
'label' => t('Warning'),
|
||||
'color' => '#ffaa44',
|
||||
'stack' => 'stack1',
|
||||
'data' => $warningBars
|
||||
'data' => $warningBars,
|
||||
'tooltip' => $tooltip
|
||||
),
|
||||
array(
|
||||
'label' => t('Critical'),
|
||||
'color' => '#ff5566',
|
||||
'stack' => 'stack1',
|
||||
'data' => $critBars
|
||||
'data' => $critBars,
|
||||
'tooltip' => $tooltip
|
||||
),
|
||||
array(
|
||||
'label' => t('Unknown'),
|
||||
'color' => '#dd66ff',
|
||||
'stack' => 'stack1',
|
||||
'data' => $unknownBars
|
||||
'data' => $unknownBars,
|
||||
'tooltip' => $tooltip
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -201,6 +206,7 @@ class Monitoring_ChartController extends Controller
|
|||
$hostgroup->hosts_unreachable_unhandled
|
||||
);
|
||||
}
|
||||
$tooltip = t('<b>{title}:</b><br /> {value} of {sum} hosts are {label}');
|
||||
$this->view->chart = new GridChart();
|
||||
$this->view->chart->alignTopLeft();
|
||||
$this->view->chart->setAxisLabel('', t('Hosts'))
|
||||
|
@ -211,19 +217,22 @@ class Monitoring_ChartController extends Controller
|
|||
'label' => t('Up'),
|
||||
'color' => '#44bb77',
|
||||
'stack' => 'stack1',
|
||||
'data' => $upBars
|
||||
'data' => $upBars,
|
||||
'tooltip' => $tooltip
|
||||
),
|
||||
array(
|
||||
'label' => t('Down'),
|
||||
'color' => '#ff5566',
|
||||
'stack' => 'stack1',
|
||||
'data' => $downBars
|
||||
'data' => $downBars,
|
||||
'tooltip' => $tooltip
|
||||
),
|
||||
array(
|
||||
'label' => t('Unreachable'),
|
||||
'color' => '#dd66ff',
|
||||
'stack' => 'stack1',
|
||||
'data' => $unreachableBars
|
||||
'data' => $unreachableBars,
|
||||
'tooltip' => $tooltip
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue