Skip to content

Commit

Permalink
Merge branch 'feature/svg-chart-tooltips-7024'
Browse files Browse the repository at this point in the history
  • Loading branch information
majentsch committed Sep 3, 2014
2 parents 1d81211 + da85112 commit 64b9c89
Show file tree
Hide file tree
Showing 17 changed files with 803 additions and 33 deletions.
74 changes: 73 additions & 1 deletion doc/graphs.md
Expand Up @@ -238,6 +238,77 @@ the labels to show you can use the 'disableLegend()' call on the GridChart objec

![Various Line Graph Options][graph7]


### Tooltips

It is possible to specify custom tooltip format strings when creating bar charts.
Tooltips provide information about the points of each bar chart column, by aggregating
the values of all data sets with the same x-coordinate.

When no custom format string is given, a sane default format string is used, but its usually
clearer for the user to describe the data of each chart more accurately with a custom one.


**Example #9.1: Bar Charts with custom tooltips**

$this->chart->drawBars(
array(
'label' => 'Hosts critical',
'palette' => Palette::PROBLEM,
'stack' => 'stack1',
'data' => $data2,
'tooltip' => '{title}<br/> {value} of {sum} hosts are ok.'
),
array(
'label' => 'Hosts warning',
'stack' => 'stack1',
'palette' => Palette::WARNING,
'data' => $data,
'tooltip' => '{title}<br/> Oh no, {value} of {sum} hosts are down!'
)
);


As you can see, you can specify a format string for each data set, which allows you to
pass a custom message for all "down" hosts, one custom message for all "Ok" hosts and so on.
In contrast to that, the aggregation of values works on a column basis and will give you the
sum of all y-values with the same x-coordinate and not the aggregation of all values of the data set.

#### Rich Tooltips

It is also possible to use HTML in the tooltip strings to create rich tooltip markups, which can
be useful to provide extended output that spans over multiple lines. Please keep in mind that
users without JavaScript will see the tooltip with all of its html-tags stripped.

![Various Line Graph Options][graph7.1]

#### Available replacements

The available replacements depend on the used chart type, since the tooltip data is
instantiated and populated by the chart. All bar graphs have the following replacements available:

Aggregated values, are calculated from the data points of each column:

- sum: The amount of all Y-values of the current column
- max: The biggest occurring Y-value of the current column
- min: The smallest occurring Y-value of the current column


Column values are also defined by the current column, but are not
the product of any aggregation

- title: The x-value of the current column


Row values are defined by the properties the current data set, and are only useful for rendering the
generic tooltip correctly, since you could also just directly write
those values into your custom tooltip.

- label: The name of the current data set
- color: The color of this data set



## Pie Charts

### The PieChart Object
Expand Down Expand Up @@ -317,5 +388,6 @@ Rendering is straightforward, assuming $svg is the PieChart/GridChart object, yo
[graph5]: res/GraphExample#5.png
[graph6]: res/GraphExample#6.png
[graph7]: res/GraphExample#7.png
[graph7.1]: res/GraphExample#7.1.png
[graph8]: res/GraphExample#8.png
[graph9]: res/GraphExample#9.png
[graph9]: res/GraphExample#9.png
Binary file added doc/res/GraphExample#7.1.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 84 additions & 20 deletions library/Icinga/Chart/Graph/BarGraph.php
Expand Up @@ -16,6 +16,13 @@
*/
class BarGraph extends Styleable implements Drawable
{
/**
* The dataset order
*
* @var int
*/
private $order = 0;

/**
* The width of the bars.
*
Expand All @@ -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;
}

/**
Expand All @@ -56,6 +86,30 @@ public function setStyleFromConfig(array $cfg)
}
}

/**
* 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
*
Expand All @@ -68,23 +122,33 @@ public function toSvg(RenderContext $ctx)
$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;
}
Expand Down
4 changes: 4 additions & 0 deletions library/Icinga/Chart/Graph/StackedGraph.php
Expand Up @@ -41,6 +41,10 @@ public function addGraph(array &$subGraph)
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];
}
Expand Down
144 changes: 144 additions & 0 deletions library/Icinga/Chart/Graph/Tooltip.php
@@ -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));
}
}

0 comments on commit 64b9c89

Please sign in to comment.