mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Graph: shared multi series tooltip, refactoring PR #850
This commit is contained in:
@@ -3,9 +3,10 @@ define([
|
||||
'jquery',
|
||||
'kbn',
|
||||
'moment',
|
||||
'lodash'
|
||||
'lodash',
|
||||
'./grafanaGraph.tooltip'
|
||||
],
|
||||
function (angular, $, kbn, moment, _) {
|
||||
function (angular, $, kbn, moment, _, graphTooltip) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
@@ -15,8 +16,8 @@ function (angular, $, kbn, moment, _) {
|
||||
restrict: 'A',
|
||||
template: '<div> </div>',
|
||||
link: function(scope, elem) {
|
||||
var data, annotations;
|
||||
var dashboard = scope.dashboard;
|
||||
var data, annotations;
|
||||
var legendSideLastValue = null;
|
||||
|
||||
scope.$on('refresh',function() {
|
||||
@@ -174,7 +175,7 @@ function (angular, $, kbn, moment, _) {
|
||||
|
||||
function callPlot() {
|
||||
try {
|
||||
elem.flot=$.plot(elem, sortedSeries, options);
|
||||
$.plot(elem, sortedSeries, options);
|
||||
} catch (e) {
|
||||
console.log('flotcharts error', e);
|
||||
}
|
||||
@@ -343,112 +344,6 @@ function (angular, $, kbn, moment, _) {
|
||||
return "%H:%M";
|
||||
}
|
||||
|
||||
var $tooltip = $('<div id="tooltip">');
|
||||
|
||||
//this event will erase tooltip and crosshair once leaved the graph
|
||||
elem.mouseleave(function () {
|
||||
console.log('onmouse out:');
|
||||
if(scope.panel.tooltip.shared) {
|
||||
$tooltip.detach();
|
||||
elem.flot.clearCrosshair();
|
||||
elem.flot.unhighlight();
|
||||
}
|
||||
});
|
||||
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
var group, value, timestamp, seriesInfo, format, i, j, s, s_final;
|
||||
|
||||
//if tooltip shared we'll show a crosshair and will look for X and all Y series values
|
||||
//else we will take from item.
|
||||
if(scope.panel.tooltip.shared){
|
||||
//unhighligh previous points.
|
||||
elem.flot.unhighlight();
|
||||
//check if all series has same length if so, only one x index will
|
||||
//be checked and only for exact timestamp values
|
||||
var l = [];
|
||||
var series;
|
||||
for (i = 0; i < data.length; ++i) {
|
||||
series = data[i];
|
||||
l.push(series.data.length);
|
||||
}
|
||||
//if all series has the same length it is because of they share time axis
|
||||
if(_.uniq(l).length === 1) {
|
||||
s='';
|
||||
series = data[0];
|
||||
for(j=0;j<series.data.length;j++) {
|
||||
if(series.data[j][0] > pos.x){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(j>0) {
|
||||
j--; //we take previous value in time.
|
||||
}
|
||||
//now we know the current X (j) position for X and Y values
|
||||
timestamp = dashboard.formatDate(series.data[j][0]);
|
||||
var last_value=0; //needed for stacked values
|
||||
for (i = data.length-1; i >= 0; --i) {
|
||||
//stacked values should be added in reverse order
|
||||
series = data[i];
|
||||
seriesInfo = series.info;
|
||||
format = scope.panel.y_formats[seriesInfo.yaxis - 1];
|
||||
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
|
||||
value = series.data[j][1];
|
||||
} else {
|
||||
last_value+=series.data[j][1];
|
||||
value = last_value;
|
||||
}
|
||||
value = kbn.getFormatFunction(format, 2)(value,series.yaxis);
|
||||
if (seriesInfo.alias) {
|
||||
group = '<i class="icon-circle" style="color:'+series.color+';"></i>' +
|
||||
' ' + seriesInfo.alias;
|
||||
} else {
|
||||
group = kbn.query_color_dot(series.color, 15) + ' ';
|
||||
}
|
||||
//pre-pending new values
|
||||
s_final= group+ ": <b>"+value +'</b><br>'+ s;
|
||||
s=s_final;
|
||||
//higligth point
|
||||
elem.flot.highlight(i,j);
|
||||
}
|
||||
|
||||
$tooltip.html('<small style="font-size:0.7em;">Time@ <b>'+
|
||||
timestamp + '</b><br><hr>' + s + '</small>').place_tt(pos.pageX, pos.pageY);
|
||||
return;
|
||||
}else {
|
||||
console.log('WARNING: tootltip shared can not be shown becouse of from '
|
||||
+data.length+' series has different length '+_.uniq(l));
|
||||
$tooltip.detach();
|
||||
}
|
||||
}
|
||||
if (item) {
|
||||
seriesInfo = item.series.info;
|
||||
format = scope.panel.y_formats[seriesInfo.yaxis - 1];
|
||||
|
||||
if (seriesInfo.alias) {
|
||||
group = '<small style="font-size:0.9em;">' +
|
||||
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
|
||||
seriesInfo.alias +
|
||||
'</small><br>';
|
||||
} else {
|
||||
group = kbn.query_color_dot(item.series.color, 15) + ' ';
|
||||
}
|
||||
|
||||
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
|
||||
value = item.datapoint[1] - item.datapoint[2];
|
||||
}
|
||||
else {
|
||||
value = item.datapoint[1];
|
||||
}
|
||||
|
||||
value = kbn.valueFormats[format](value, item.series.yaxis.tickDecimals);
|
||||
timestamp = dashboard.formatDate(item.datapoint[0]);
|
||||
|
||||
$tooltip.html(group + value + " @ " + timestamp).place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
|
||||
function render_panel_as_graphite_png(url) {
|
||||
url += '&width=' + elem.width();
|
||||
url += '&height=' + elem.css('height').replace('px', '');
|
||||
@@ -499,6 +394,8 @@ function (angular, $, kbn, moment, _) {
|
||||
elem.html('<img src="' + url + '"></img>');
|
||||
}
|
||||
|
||||
graphTooltip.register(elem, dashboard, scope);
|
||||
|
||||
elem.bind("plotselected", function (event, ranges) {
|
||||
scope.$apply(function() {
|
||||
timeSrv.setTime({
|
||||
|
||||
122
src/app/directives/grafanaGraph.tooltip.js
Normal file
122
src/app/directives/grafanaGraph.tooltip.js
Normal file
@@ -0,0 +1,122 @@
|
||||
define([
|
||||
'jquery',
|
||||
'kbn',
|
||||
],
|
||||
function ($, kbn) {
|
||||
'use strict';
|
||||
|
||||
function registerTooltipFeatures(elem, dashboard, scope) {
|
||||
|
||||
var $tooltip = $('<div id="tooltip">');
|
||||
|
||||
elem.mouseleave(function () {
|
||||
if(scope.panel.tooltip.shared) {
|
||||
var plot = elem.data().plot;
|
||||
$tooltip.detach();
|
||||
plot.clearCrosshair();
|
||||
plot.unhighlight();
|
||||
}
|
||||
});
|
||||
|
||||
function findHoverIndex(posX, series) {
|
||||
for (var j = 0; j < series.data.length; j++) {
|
||||
if (series.data[j][0] > posX) {
|
||||
return Math.max(j - 1, 0);
|
||||
}
|
||||
}
|
||||
return j - 1;
|
||||
}
|
||||
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
var plot = elem.data().plot;
|
||||
var data = plot.getData();
|
||||
var group, value, timestamp, seriesInfo, format, i, series, hoverIndex, seriesHtml;
|
||||
|
||||
if (scope.panel.tooltip.shared) {
|
||||
plot.unhighlight();
|
||||
|
||||
//check if all series has same length if so, only one x index will
|
||||
//be checked and only for exact timestamp values
|
||||
var pointCount = data[0].data.length;
|
||||
for (i = 1; i < data.length; i++) {
|
||||
if (data[i].data.length !== pointCount) {
|
||||
console.log('WARNING: tootltip shared can not be shown becouse of series points do not align, different point counts');
|
||||
$tooltip.detach();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
seriesHtml = '';
|
||||
series = data[0];
|
||||
hoverIndex = findHoverIndex(pos.x, series);
|
||||
|
||||
//now we know the current X (j) position for X and Y values
|
||||
timestamp = dashboard.formatDate(series.data[hoverIndex][0]);
|
||||
var last_value = 0; //needed for stacked values
|
||||
|
||||
for (i = data.length-1; i >= 0; --i) {
|
||||
//stacked values should be added in reverse order
|
||||
series = data[i];
|
||||
seriesInfo = series.info;
|
||||
format = scope.panel.y_formats[seriesInfo.yaxis - 1];
|
||||
|
||||
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
|
||||
value = series.data[hoverIndex][1];
|
||||
} else {
|
||||
last_value += series.data[hoverIndex][1];
|
||||
value = last_value;
|
||||
}
|
||||
|
||||
value = kbn.valueFormats[format](value, series.yaxis.tickDecimals);
|
||||
|
||||
if (seriesInfo.alias) {
|
||||
group = '<i class="icon-minus" style="color:' + series.color +';"></i> ' + seriesInfo.alias;
|
||||
} else {
|
||||
group = kbn.query_color_dot(series.color, 15) + ' ';
|
||||
}
|
||||
|
||||
//pre-pending new values
|
||||
seriesHtml = group + ': <span class="graph-tooltip-value">' + value + '</span><br>' + seriesHtml;
|
||||
|
||||
plot.highlight(i, hoverIndex);
|
||||
}
|
||||
|
||||
$tooltip.html('<div class="graph-tooltip small"><div class="graph-tooltip-time">'+ timestamp + '</div> ' + seriesHtml + '</div>')
|
||||
.place_tt(pos.pageX + 20, pos.pageY);
|
||||
return;
|
||||
}
|
||||
if (item) {
|
||||
seriesInfo = item.series.info;
|
||||
format = scope.panel.y_formats[seriesInfo.yaxis - 1];
|
||||
|
||||
if (seriesInfo.alias) {
|
||||
group = '<small style="font-size:0.9em;">' +
|
||||
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
|
||||
seriesInfo.alias +
|
||||
'</small><br>';
|
||||
} else {
|
||||
group = kbn.query_color_dot(item.series.color, 15) + ' ';
|
||||
}
|
||||
|
||||
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
|
||||
value = item.datapoint[1] - item.datapoint[2];
|
||||
}
|
||||
else {
|
||||
value = item.datapoint[1];
|
||||
}
|
||||
|
||||
value = kbn.valueFormats[format](value, item.series.yaxis.tickDecimals);
|
||||
timestamp = dashboard.formatDate(item.datapoint[0]);
|
||||
|
||||
$tooltip.html(group + value + " @ " + timestamp).place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
register: registerTooltipFeatures
|
||||
};
|
||||
});
|
||||
@@ -161,7 +161,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
|
||||
tooltip : {
|
||||
value_type: 'cumulative',
|
||||
query_as_alias: true
|
||||
shared: false,
|
||||
},
|
||||
|
||||
targets: [{}],
|
||||
|
||||
@@ -166,3 +166,19 @@
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
.graph-tooltip {
|
||||
.graph-tooltip-time {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
|
||||
.graph-tooltip-value {
|
||||
font-weight: bold;
|
||||
float: right;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
56
src/test/specs/graph-tooltip-specs.js
Normal file
56
src/test/specs/graph-tooltip-specs.js
Normal file
@@ -0,0 +1,56 @@
|
||||
define([
|
||||
'jquery',
|
||||
'directives/grafanaGraph.tooltip'
|
||||
], function($, tooltip) {
|
||||
'use strict';
|
||||
|
||||
describe('graph tooltip', function() {
|
||||
var elem = $('<div></div>');
|
||||
var dashboard = {
|
||||
formatDate: sinon.stub().returns('date'),
|
||||
};
|
||||
var scope = {
|
||||
panel: {
|
||||
tooltip: {
|
||||
shared: true
|
||||
},
|
||||
y_formats: ['ms', 'none'],
|
||||
}
|
||||
};
|
||||
|
||||
var data = [
|
||||
{
|
||||
data: [[10,10], [12,20]],
|
||||
info: { yaxis: 1 },
|
||||
yaxis: { tickDecimals: 2 },
|
||||
},
|
||||
{
|
||||
data: [[10,10], [12,20]],
|
||||
info: { yaxis: 1 },
|
||||
yaxis: { tickDecimals: 2 },
|
||||
}
|
||||
];
|
||||
|
||||
var plot = {
|
||||
getData: sinon.stub().returns(data),
|
||||
highlight: sinon.stub(),
|
||||
unhighlight: sinon.stub()
|
||||
};
|
||||
|
||||
elem.data('plot', plot);
|
||||
|
||||
beforeEach(function() {
|
||||
tooltip.register(elem, dashboard, scope);
|
||||
elem.trigger('plothover', [{}, {x: 13}, {}]);
|
||||
});
|
||||
|
||||
it('should add tooltip', function() {
|
||||
var tooltipHtml = $(".graph-tooltip").text();
|
||||
expect(tooltipHtml).to.be('date : 40.00 ms : 20.00 ms');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -129,6 +129,7 @@ require([
|
||||
'specs/influxdb-datasource-specs',
|
||||
'specs/graph-ctrl-specs',
|
||||
'specs/grafanaGraph-specs',
|
||||
'specs/graph-tooltip-specs',
|
||||
'specs/seriesOverridesCtrl-specs',
|
||||
'specs/timeSrv-specs',
|
||||
'specs/templateSrv-specs',
|
||||
|
||||
Reference in New Issue
Block a user