Add more meaningful tooltip labels to InlinePies

Show current title, a well-formated value and a label for each area in a
tooltip and add the ability to customize tooltip labels of InlinePies

refs #6117
This commit is contained in:
Matthias Jentsch 2014-06-18 13:53:38 +02:00
parent 521cc0cac4
commit be0c5d4b23
4 changed files with 229 additions and 18 deletions

View File

@ -32,6 +32,8 @@ namespace Icinga\Web\Widget\Chart;
use Icinga\Web\Widget\AbstractWidget;
use Icinga\Web\Url;
use Icinga\Util\Format;
use Icinga\Logger\Logger;
/**
* A SVG-PieChart intended to be displayed as a small icon next to labels, to offer a better visualization of the
@ -44,14 +46,32 @@ use Icinga\Web\Url;
*/
class InlinePie extends AbstractWidget
{
const NUMBER_FORMAT_TIME = 'time';
const NUMBER_FORMAT_BYTES = 'bytes';
const NUMBER_FORMAT_RATIO = 'ratio';
/**
* The template string used for rendering this widget
* The template string used for rendering this widget
*
* @var string
*/
private $template =<<<'EOD'
<span class="sparkline" sparkTitle="{title}" sparkWidth="{width}" sparkHeight="{height}" style="{style}"
sparkSliceColors="[{colors}]" values="{data}" sparkType="pie"></span>
<span
class="sparkline"
sparkTitle="{title}"
sparkWidth="{width}"
sparkHeight="{height}"
sparkBorderWidth="{borderWidth}"
sparkBorderColor="{borderColor}"
sparkTooltipChartTitle="{title}"
style="{style}"
labels="{labels}"
formatted="{formatted}"
values="{data}"
tooltipFormat="{tooltipFormat}"
sparkSliceColors="[{colors}]"
sparkType="pie"></span>
<noscript>
<img class="inlinepie"
title="{title}" src="{url}" style="width: {width}px; height: {height}px; {style}"
@ -86,6 +106,20 @@ EOD;
*/
private $height = 28;
/**
* PieChart border width
*
* @var float
*/
private $borderWidth = 1.25;
/**
* The color of the border
*
* @var string
*/
private $borderColor = '#888';
/**
* The title of the chart
*
@ -103,10 +137,31 @@ EOD;
/**
* The data displayed by the pie-chart
*
* @var
* @var array
*/
private $data;
/**
* The labels to display for each data set
*
* @var array
*/
private $labels;
/**
* The format string used to display tooltips
*
* @var string
*/
private $tooltipFormat = '<b>{{title}}</b></br> {{label}}: {{formatted}} ({{percent}}%)';
/**
* The number format used to render numeric values in tooltips
*
* @var array
*/
private $format = self::NUMBER_FORMAT_BYTES;
/**
* Set the data to be displayed.
*
@ -127,7 +182,12 @@ EOD;
*/
public function setLabels($labels = null)
{
$this->url->setParam('labels', implode(',', $labels));
if ($labels != null) {
$this->url->setParam('labels', implode(',', $labels));
} else {
$this->url->removeKey('labels');
}
$this->labels = $labels;
return $this;
}
@ -146,6 +206,34 @@ EOD;
}
}
/**
* Set the used number format
*
* @param $format string 'bytes' or 'time'
*/
public function setNumberFormat($format)
{
$this->format = $format;
}
/**
* A format string used to render the content of the piechar tooltips
*
* Placeholders using curly braces '{FOO}' are replace with their specific values. Available
* values are:
* <ul>
* <li><b>label</b>: The description for the current value </li>
* <li><b>formatted</b>: A string representing the formatted value </li>
* <li><b>value</b>: The raw (non-formatted) value used to render the piechart </li>
* <li><b>percent</b>: The percentage of the current value </li>
* </ul>
* Note: Changes will only affect JavaScript sparklines and not the SVG charts used for fallback
*/
public function setTooltipFormat($format)
{
$this->tooltipFormat = $format;
}
/**
* @param $height
*
@ -157,6 +245,26 @@ EOD;
return $this;
}
/**
* Set the border width of the pie chart
*
* @param float $width Width in px
*/
public function setBorderWidth($width)
{
$this->borderWidth = $width;
}
/**
* Set the color of the pie chart border
*
* @param string $col The color string
*/
public function setBorderColor($col)
{
$this->borderColor = $col;
}
/**
* @param $width
*
@ -191,10 +299,12 @@ EOD;
/**
* Create a new InlinePie
*
* @param array $data The data displayed by the slices
* @param array $colors The colors displayed by the slices
* @param array $data The data displayed by the slices
* @param array $colors The colors displayed by the slices
* @param array $labels The labels to display for each slice
* @param string $unit The number format
*/
public function __construct(array $data, array $colors = null)
public function __construct(array $data, array $colors = null, array $labels = null, $unit = self::NUMBER_FORMAT_BYTES)
{
$this->url = Url::fromPath('svg/chart.php');
if (array_key_exists('data', $data)) {
@ -215,6 +325,15 @@ EOD;
}
}
/**
* Create a serialization containing the current label array
*
* @return string A serialized array of labels
*/
private function createLabelString () {
return isset($this->labels) && is_array($this->labels) ? implode(',', $this->labels) : '';
}
/**
* Renders this widget via the given view and returns the
* HTML as a string
@ -224,19 +343,53 @@ EOD;
public function render()
{
$template = $this->template;
// Locale-ignorant string cast:
$data = array();
foreach ($this->data as $dat) {
$data[] = sprintf('%F', $dat);
}
$template = preg_replace('{{url}}', $this->url, $template);
// style
$template = preg_replace('{{width}}', $this->width, $template);
$template = preg_replace('{{height}}', $this->height, $template);
$template = preg_replace('{{title}}', $this->title, $template);
$template = preg_replace('{{style}}', $this->style, $template);
$template = preg_replace('{{data}}', implode(',', $data), $template);
$template = preg_replace('{{colors}}', implode(',', $this->colors), $template);
$template = preg_replace('{{borderWidth}}', $this->borderWidth, $template);
$template = preg_replace('{{borderColor}}', $this->borderColor, $template);
// values
$data = array();
foreach ($this->data as $dat) {
// Locale-ignorant string cast:
$data[] = sprintf('%F', $dat);
}
$formatted = array();
foreach ($this->data as $key => $value) {
$formatted[$key] = $this->formatValue($value);
}
$template = preg_replace('{{data}}', implode(',', $data), $template);
$template = preg_replace('{{formatted}}', implode(',', $formatted), $template);
$template = preg_replace('{{labels}}', $this->createLabelString(), $template);
$template = preg_replace('{{tooltipFormat}}', $this->tooltipFormat, $template);
return $template;
}
/**
* Format the given value depending on the current value of numberFormat
*
* @param float $value The value to format
*
* @return string The formatted value
*/
private function formatValue($value)
{
if ($this->format === self::NUMBER_FORMAT_BYTES) {
return Format::bytes($value);
} else if ($this->format === self::NUMBER_FORMAT_TIME) {
return Format::duration($value);
} else if ($this->format === self::NUMBER_FORMAT_RATIO) {
return $value;
} else {
Logger::warning('Unknown format string "' . $this->format . '" for InlinePie, value not formatted.');
return $value;
}
}
}
// @codeCoverageIgnoreEnd

View File

@ -18,11 +18,10 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
if (!$perfdata->isPercentage() && $perfdata->getMaximumValue() === null) {
continue;
}
$pieChart = new InlinePie($this->calculatePieChartData($perfdata));
$pieChart = $this->createInlinePie($perfdata);
if ($compact) {
$pieChart->setTitle(
htmlspecialchars($label) . ': ' . htmlspecialchars($this->formatPerfdataValue($perfdata))
htmlspecialchars($label) /* . ': ' . htmlspecialchars($this->formatPerfdataValue($perfdata) */
);
if (!$float) {
$result .= $pieChart->render();
@ -31,6 +30,9 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
}
} else {
$pieChart->setTitle(htmlspecialchars($label));
if (! $perfdata->isPercentage()) {
$pieChart->setTooltipFormat('{{label}}: {{formatted}} ({{percent}}%)');
}
$pieChart->setStyle('float: left; margin: 0.2em 0.5em 0.2em 0.5em;');
$table[] = '<tr><th>' . $pieChart->render()
. htmlspecialchars($label)
@ -82,4 +84,22 @@ class Zend_View_Helper_Perfdata extends Zend_View_Helper_Abstract
return $perfdata->getValue();
}
protected function createInlinePie(Perfdata $perfdata)
{
$pieChart = new InlinePie($this->calculatePieChartData($perfdata));
$pieChart->setHeight(32)->setWidth(32);
if ($perfdata->isBytes()) {
$pieChart->setLabels(array(t('Used'), t('Used'), t('Used'), t('Free')));
$pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_BYTES);
} else if ($perfdata->isSeconds()) {
$pieChart->setLabels(array(t('Runtime'), t('Runtime'), t('Runtime'), t('Tolerance')));
$pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_TIME);
} else {
$pieChart->setLabels(array(t('Packet Loss'), t('Packet Loss'), t('Packet Loss'), t('Packet Return')));
$pieChart->setTooltipFormat('{{label}}: {{formatted}}%');
$pieChart->setNumberFormat(InlinePie::NUMBER_FORMAT_RATIO);
}
return $pieChart;
}
}

View File

@ -70,8 +70,33 @@
$('input.autofocus', el).focus();
// replace all sparklines
$('span.sparkline', el).sparkline('html', { enableTagOptions: true });
$('span.sparkline', el).each(function(i, element) {
// read custom options
var $spark = $(element);
var labels = $spark.attr('labels').split(',');
var formatted = $spark.attr('formatted').split(',');
var tooltipChartTitle = $spark.attr('sparkTooltipChartTitle') || '';
var format = $spark.attr('tooltipformat');
$spark.sparkline(
'html',
{
enableTagOptions: true,
tooltipFormatter: function (sparkline, options, fields) {
var out = format;
var replace = {
title: tooltipChartTitle,
label: labels[fields.offset] ? labels[fields.offset] : fields.offset,
formatted: formatted[fields.offset] ? formatted[fields.offset] : '',
value: fields.value,
percent: Math.round(fields.percent * 100) / 100
};
$.each(replace, function(key, value) {
out = out.replace('{{' + key + '}}', value);
});
return out;
}
});
});
var searchField = $('#menu input.search', el);
// Remember initial search field value if any
if (searchField.length && searchField.val().length) {

View File

@ -53,6 +53,19 @@
return hours + ':' + minutes + ':' + seconds;
},
/**
* Format the given byte-value into a human-readable string
*
* @param {number} The amount of bytes to format
* @returns {string} The formatted string
*/
formatBytes: function (bytes) {
var log2 = Math.log(bytes) / Math.LN2;
var pot = Math.floor(log2 / 10);
var unit = (['b', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB'])[pot];
return ((bytes / Math.pow(1024, pot)).toFixed(2)) + ' ' + unit;
},
/**
* Return whether the given element is visible in the users view
*