From 2473ae3b47d637f534b04465244c3273dd0d45cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 29 Sep 2014 16:57:05 +0200 Subject: [PATCH] Graph: shared multi series tooltip, refactoring PR #850 --- src/app/directives/grafanaGraph.js | 117 ++------------------ src/app/directives/grafanaGraph.tooltip.js | 122 +++++++++++++++++++++ src/app/panels/graph/module.js | 2 +- src/css/less/graph.less | 16 +++ src/test/specs/graph-tooltip-specs.js | 56 ++++++++++ src/test/test-main.js | 1 + 6 files changed, 203 insertions(+), 111 deletions(-) create mode 100644 src/app/directives/grafanaGraph.tooltip.js create mode 100644 src/test/specs/graph-tooltip-specs.js diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index c7c937d8bd6..42ef1b91cb5 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -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: '
', 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 = $('
'); - - //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 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 = '' + - ' ' + seriesInfo.alias; - } else { - group = kbn.query_color_dot(series.color, 15) + ' '; - } - //pre-pending new values - s_final= group+ ": "+value +'
'+ s; - s=s_final; - //higligth point - elem.flot.highlight(i,j); - } - - $tooltip.html('Time@ '+ - timestamp + '

' + s + '
').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 = '' + - '' + ' ' + - seriesInfo.alias + - '
'; - } 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(''); } + graphTooltip.register(elem, dashboard, scope); + elem.bind("plotselected", function (event, ranges) { scope.$apply(function() { timeSrv.setTime({ diff --git a/src/app/directives/grafanaGraph.tooltip.js b/src/app/directives/grafanaGraph.tooltip.js new file mode 100644 index 00000000000..513d7126985 --- /dev/null +++ b/src/app/directives/grafanaGraph.tooltip.js @@ -0,0 +1,122 @@ +define([ + 'jquery', + 'kbn', +], +function ($, kbn) { + 'use strict'; + + function registerTooltipFeatures(elem, dashboard, scope) { + + var $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 = ' ' + seriesInfo.alias; + } else { + group = kbn.query_color_dot(series.color, 15) + ' '; + } + + //pre-pending new values + seriesHtml = group + ': ' + value + '
' + seriesHtml; + + plot.highlight(i, hoverIndex); + } + + $tooltip.html('
'+ timestamp + '
' + seriesHtml + '
') + .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 = '' + + '' + ' ' + + seriesInfo.alias + + '
'; + } 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 + }; +}); diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index bbe64d10c37..bc15b851f9d 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -161,7 +161,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { tooltip : { value_type: 'cumulative', - query_as_alias: true + shared: false, }, targets: [{}], diff --git a/src/css/less/graph.less b/src/css/less/graph.less index d53c55345c7..92f57dbfc71 100644 --- a/src/css/less/graph.less +++ b/src/css/less/graph.less @@ -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; + } + +} diff --git a/src/test/specs/graph-tooltip-specs.js b/src/test/specs/graph-tooltip-specs.js new file mode 100644 index 00000000000..2ca3ec85cac --- /dev/null +++ b/src/test/specs/graph-tooltip-specs.js @@ -0,0 +1,56 @@ +define([ + 'jquery', + 'directives/grafanaGraph.tooltip' +], function($, tooltip) { + 'use strict'; + + describe('graph tooltip', function() { + var elem = $('
'); + 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'); + }); + + }); + +}); + + diff --git a/src/test/test-main.js b/src/test/test-main.js index f60cdb87da4..6862c9fa8ae 100644 --- a/src/test/test-main.js +++ b/src/test/test-main.js @@ -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',