Commit 2473ae3b by Torkel Ödegaard

Graph: shared multi series tooltip, refactoring PR #850

parent 3fb457cc
...@@ -3,9 +3,10 @@ define([ ...@@ -3,9 +3,10 @@ define([
'jquery', 'jquery',
'kbn', 'kbn',
'moment', 'moment',
'lodash' 'lodash',
'./grafanaGraph.tooltip'
], ],
function (angular, $, kbn, moment, _) { function (angular, $, kbn, moment, _, graphTooltip) {
'use strict'; 'use strict';
var module = angular.module('grafana.directives'); var module = angular.module('grafana.directives');
...@@ -15,8 +16,8 @@ function (angular, $, kbn, moment, _) { ...@@ -15,8 +16,8 @@ function (angular, $, kbn, moment, _) {
restrict: 'A', restrict: 'A',
template: '<div> </div>', template: '<div> </div>',
link: function(scope, elem) { link: function(scope, elem) {
var data, annotations;
var dashboard = scope.dashboard; var dashboard = scope.dashboard;
var data, annotations;
var legendSideLastValue = null; var legendSideLastValue = null;
scope.$on('refresh',function() { scope.$on('refresh',function() {
...@@ -174,7 +175,7 @@ function (angular, $, kbn, moment, _) { ...@@ -174,7 +175,7 @@ function (angular, $, kbn, moment, _) {
function callPlot() { function callPlot() {
try { try {
elem.flot=$.plot(elem, sortedSeries, options); $.plot(elem, sortedSeries, options);
} catch (e) { } catch (e) {
console.log('flotcharts error', e); console.log('flotcharts error', e);
} }
...@@ -343,112 +344,6 @@ function (angular, $, kbn, moment, _) { ...@@ -343,112 +344,6 @@ function (angular, $, kbn, moment, _) {
return "%H:%M"; 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) { function render_panel_as_graphite_png(url) {
url += '&width=' + elem.width(); url += '&width=' + elem.width();
url += '&height=' + elem.css('height').replace('px', ''); url += '&height=' + elem.css('height').replace('px', '');
...@@ -499,6 +394,8 @@ function (angular, $, kbn, moment, _) { ...@@ -499,6 +394,8 @@ function (angular, $, kbn, moment, _) {
elem.html('<img src="' + url + '"></img>'); elem.html('<img src="' + url + '"></img>');
} }
graphTooltip.register(elem, dashboard, scope);
elem.bind("plotselected", function (event, ranges) { elem.bind("plotselected", function (event, ranges) {
scope.$apply(function() { scope.$apply(function() {
timeSrv.setTime({ timeSrv.setTime({
......
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) { ...@@ -161,7 +161,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
tooltip : { tooltip : {
value_type: 'cumulative', value_type: 'cumulative',
query_as_alias: true shared: false,
}, },
targets: [{}], targets: [{}],
......
...@@ -166,3 +166,19 @@ ...@@ -166,3 +166,19 @@
float: left; 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;
}
}
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([ ...@@ -129,6 +129,7 @@ require([
'specs/influxdb-datasource-specs', 'specs/influxdb-datasource-specs',
'specs/graph-ctrl-specs', 'specs/graph-ctrl-specs',
'specs/grafanaGraph-specs', 'specs/grafanaGraph-specs',
'specs/graph-tooltip-specs',
'specs/seriesOverridesCtrl-specs', 'specs/seriesOverridesCtrl-specs',
'specs/timeSrv-specs', 'specs/timeSrv-specs',
'specs/templateSrv-specs', 'specs/templateSrv-specs',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment