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:
Matthias Jentsch 2014-09-02 12:24:29 +02:00
parent 055a107b90
commit 4b55bcf8b6
5 changed files with 286 additions and 29 deletions

View File

@ -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;
}

View File

@ -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];
}

View File

@ -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));
}
}

View File

@ -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']));

View File

@ -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
)
);
}