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:
parent
521cc0cac4
commit
be0c5d4b23
|
@ -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)
|
||||
{
|
||||
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
|
||||
*
|
||||
|
@ -193,8 +301,10 @@ EOD;
|
|||
*
|
||||
* @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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue