diff --git a/src/app/components/kbn.js b/src/app/components/kbn.js
index cb084b1c166..567e7c78f23 100644
--- a/src/app/components/kbn.js
+++ b/src/app/components/kbn.js
@@ -316,6 +316,10 @@ function($, _, moment) {
kbn.formatFuncCreator = function(factor, extArray) {
return function(size, decimals, scaledDecimals) {
+ if (size === null) {
+ return "";
+ }
+
var steps = 0;
while (Math.abs(size) >= factor) {
@@ -331,6 +335,10 @@ function($, _, moment) {
};
kbn.toFixed = function(value, decimals) {
+ if (value === null) {
+ return "";
+ }
+
var factor = decimals ? Math.pow(10, decimals) : 1;
var formatted = String(Math.round(value * factor) / factor);
@@ -359,6 +367,8 @@ function($, _, moment) {
kbn.valueFormats.none = kbn.toFixed;
kbn.valueFormats.ms = function(size, decimals, scaledDecimals) {
+ if (size === null) { return ""; }
+
if (Math.abs(size) < 1000) {
return kbn.toFixed(size, decimals) + " ms";
}
@@ -383,6 +393,8 @@ function($, _, moment) {
};
kbn.valueFormats.s = function(size, decimals, scaledDecimals) {
+ if (size === null) { return ""; }
+
if (Math.abs(size) < 600) {
return kbn.toFixed(size, decimals) + " s";
}
@@ -407,6 +419,8 @@ function($, _, moment) {
};
kbn.valueFormats['µs'] = function(size, decimals, scaledDecimals) {
+ if (size === null) { return ""; }
+
if (Math.abs(size) < 1000) {
return kbn.toFixed(size, decimals) + " µs";
}
@@ -419,6 +433,8 @@ function($, _, moment) {
};
kbn.valueFormats.ns = function(size, decimals, scaledDecimals) {
+ if (size === null) { return ""; }
+
if (Math.abs(size) < 1000) {
return kbn.toFixed(size, decimals) + " ns";
}
diff --git a/src/app/components/timeSeries.js b/src/app/components/timeSeries.js
index a448a93649c..b05e9333364 100644
--- a/src/app/components/timeSeries.js
+++ b/src/app/components/timeSeries.js
@@ -5,10 +5,15 @@ define([
function (_, kbn) {
'use strict';
+ function defaultValueFormater(value) {
+ return kbn.valueFormats.none(value, 2, 2);
+ }
+
function TimeSeries(opts) {
this.datapoints = opts.datapoints;
this.info = opts.info;
this.label = opts.info.alias;
+ this.valueFormater = defaultValueFormater;
}
function matchSeriesOverride(aliasOrRegex, seriesAlias) {
@@ -108,11 +113,14 @@ function (_, kbn) {
};
TimeSeries.prototype.updateLegendValues = function(formater, decimals, scaledDecimals) {
- this.info.avg = this.info.avg != null ? formater(this.info.avg, decimals, scaledDecimals) : null;
- this.info.current = this.info.current != null ? formater(this.info.current, decimals, scaledDecimals) : null;
- this.info.min = this.info.min != null ? formater(this.info.min, decimals, scaledDecimals) : null;
- this.info.max = this.info.max != null ? formater(this.info.max, decimals, scaledDecimals) : null;
- this.info.total = this.info.total != null ? formater(this.info.total, decimals, scaledDecimals) : null;
+ this.valueFormater = function(value) {
+ return formater(value, decimals, scaledDecimals);
+ };
+ this.info.avg = this.valueFormater(this.info.avg);
+ this.info.current = this.valueFormater(this.info.current);
+ this.info.min = this.valueFormater(this.info.min);
+ this.info.max = this.valueFormater(this.info.max);
+ this.info.total = this.valueFormater(this.info.total);
};
return TimeSeries;
diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js
index 685ac7e0b83..a267255a540 100755
--- a/src/app/directives/grafanaGraph.js
+++ b/src/app/directives/grafanaGraph.js
@@ -6,7 +6,7 @@ define([
'lodash',
'./grafanaGraph.tooltip'
],
-function (angular, $, kbn, moment, _, graphTooltip) {
+function (angular, $, kbn, moment, _, GraphTooltip) {
'use strict';
var module = angular.module('grafana.directives');
@@ -105,6 +105,7 @@ function (angular, $, kbn, moment, _, graphTooltip) {
function updateLegendValues(plot) {
var yaxis = plot.getYAxes();
+ console.log("value");
for (var i = 0; i < data.length; i++) {
var series = data[i];
@@ -416,7 +417,7 @@ function (angular, $, kbn, moment, _, graphTooltip) {
elem.html('
');
}
- graphTooltip.register(elem, dashboard, scope, $rootScope);
+ new GraphTooltip(elem, dashboard, scope);
elem.bind("plotselected", function (event, ranges) {
scope.$apply(function() {
diff --git a/src/app/directives/grafanaGraph.tooltip.js b/src/app/directives/grafanaGraph.tooltip.js
index 49f02e86980..6d10fbb89c0 100644
--- a/src/app/directives/grafanaGraph.tooltip.js
+++ b/src/app/directives/grafanaGraph.tooltip.js
@@ -1,14 +1,105 @@
define([
'jquery',
- 'kbn',
],
-function ($, kbn) {
+function ($) {
'use strict';
- function registerTooltipFeatures(elem, dashboard, scope) {
+ function GraphTooltip(elem, dashboard, scope) {
+ var self = this;
var $tooltip = $('
');
+ this.findHoverIndexFromDataPoints = function(posX, series,last) {
+ var ps = series.datapoints.pointsize;
+ var initial = last*ps;
+ var len = series.datapoints.points.length;
+ for (var j = initial; j < len; j += ps) {
+ if (series.datapoints.points[j] > posX) {
+ return Math.max(j - ps, 0)/ps;
+ }
+ }
+ return j/ps - 1;
+ };
+
+ this.findHoverIndexFromData = function(posX, series) {
+ var len = series.data.length;
+ for (var j = 0; j < len; j++) {
+ if (series.data[j][0] > posX) {
+ return Math.max(j - 1, 0);
+ }
+ }
+ return j - 1;
+ };
+
+ this.showTooltip = function(title, innerHtml, pos) {
+ var body = '
';
+ $tooltip.html(body).place_tt(pos.pageX + 20, pos.pageY);
+ };
+
+ this.getMultiSeriesPlotHoverInfo = function(seriesList, pos) {
+ var value, seriesInfo, i, series, hoverIndex;
+ var results = [];
+
+ var pointCount = seriesList[0].data.length;
+ for (i = 1; i < seriesList.length; i++) {
+ if (seriesList[i].data.length !== pointCount) {
+ results.pointCountMismatch = true;
+ return results;
+ }
+ }
+
+ series = seriesList[0];
+ hoverIndex = this.findHoverIndexFromData(pos.x, series);
+ var lasthoverIndex = 0;
+ if(!scope.panel.steppedLine) {
+ lasthoverIndex = hoverIndex;
+ }
+
+ //now we know the current X (j) position for X and Y values
+ results.time = series.data[hoverIndex][0];
+ var last_value = 0; //needed for stacked values
+
+ for (i = 0; i < seriesList.length; i++) {
+ series = seriesList[i];
+ seriesInfo = series.info;
+
+ if (scope.panel.stack) {
+ if (scope.panel.tooltip.value_type === 'individual') {
+ value = series.data[hoverIndex][1];
+ } else {
+ last_value += series.data[hoverIndex][1];
+ value = last_value;
+ }
+ } else {
+ value = series.data[hoverIndex][1];
+ }
+
+ // Highlighting multiple Points depending on the plot type
+ if (scope.panel.steppedLine || (scope.panel.stack && scope.panel.nullPointMode == "null")) {
+ // stacked and steppedLine plots can have series with different length.
+ // Stacked series can increase its length on each new stacked serie if null points found,
+ // to speed the index search we begin always on the las found hoverIndex.
+ var newhoverIndex = this.findHoverIndexFromDataPoints(pos.x, series,lasthoverIndex);
+ // update lasthoverIndex depends also on the plot type.
+ if(!scope.panel.steppedLine) {
+ // on stacked graphs new will be always greater than last
+ lasthoverIndex = newhoverIndex;
+ } else {
+ // if steppeLine, not always series increases its length, so we should begin
+ // to search correct index from the original hoverIndex on each serie.
+ lasthoverIndex = hoverIndex;
+ }
+
+ results.push({ value: value, hoverIndex: newhoverIndex });
+ } else {
+ results.push({ value: value, hoverIndex: hoverIndex });
+ }
+ }
+
+ return results;
+ };
+
elem.mouseleave(function () {
if (scope.panel.tooltip.shared || dashboard.sharedCrosshair) {
var plot = elem.data().plot;
@@ -20,36 +111,10 @@ function ($, kbn) {
}
});
- function findHoverIndexFromDataPoints(posX, series,last) {
- var ps=series.datapoints.pointsize;
- var initial=last*ps;
- for (var j = initial; j < series.datapoints.points.length; j+=ps) {
- if (series.datapoints.points[j] > posX) {
- return Math.max(j - ps, 0)/ps;
- }
- }
- return j/ps - 1;
- }
-
- function findHoverIndexFromData(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;
- }
-
- function showTooltip(title, innerHtml, pos) {
- var body = '
';
- $tooltip.html(body).place_tt(pos.pageX + 20, pos.pageY);
- }
-
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;
+ var group, value, timestamp, hoverInfo, i, series, seriesHtml;
if(dashboard.sharedCrosshair){
scope.appEvent('setCrosshair', { pos: pos, scope: scope });
@@ -58,86 +123,34 @@ function ($, kbn) {
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) {
- showTooltip('Shared tooltip error', '
' +
- '- Series point counts are not the same
' +
- '- Set null point mode to null or null as zero
' +
- '- For influxdb users set fill(0) in your query
', pos);
- return;
- }
+ var seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(data, pos);
+ if (seriesHoverInfo.pointCountMismatch) {
+ self.showTooltip('Shared tooltip error', '
' +
+ '- Series point counts are not the same
' +
+ '- Set null point mode to null or null as zero
' +
+ '- For influxdb users set fill(0) in your query
', pos);
+ return;
}
seriesHtml = '';
- series = data[0];
- hoverIndex = findHoverIndexFromData(pos.x, series);
- var lasthoverIndex=0;
- if(!scope.panel.steppedLine) {
- lasthoverIndex=hoverIndex;
- }
+ timestamp = dashboard.formatDate(seriesHoverInfo.time);
- //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 = 0; i < data.length; i++) {
+ for (i = 0; i < seriesHoverInfo.length; i++) {
series = data[i];
- seriesInfo = series.info;
- format = scope.panel.y_formats[seriesInfo.yaxis - 1];
+ hoverInfo = seriesHoverInfo[i];
+ value = series.valueFormater(hoverInfo.value);
- if (scope.panel.stack) {
- if (scope.panel.tooltip.value_type === 'individual') {
- value = series.data[hoverIndex][1];
- } else {
- last_value += series.data[hoverIndex][1];
- value = last_value;
- }
- } else {
- value = series.data[hoverIndex][1];
- }
-
- 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
+ group = '
' + series.label;
seriesHtml = group + ':
' + value + '' + seriesHtml;
-
- //Highlighting multiple Points depending on the plot type
- if (scope.panel.steppedLine || (scope.panel.stack && scope.panel.nullPointMode == "null")) {
- //stacked and steppedLine plots can have series with different length.
- //Stacked series can increase its length on each new stacked serie if null points found,
- //to speed the index search we begin always on the las found hoverIndex.
- var newhoverIndex=findHoverIndexFromDataPoints(pos.x, series,lasthoverIndex);
- //update lasthoverIndex depends also on the plot type.
- if(!scope.panel.steppedLine) {
- //on stacked graphs new will be always greater than last
- lasthoverIndex=newhoverIndex;
- } else {
- //if steppeLine, not always series increases its length, so we should begin
- //to search correct index from the original hoverIndex on each serie.
- lasthoverIndex=hoverIndex;
- }
- plot.highlight(i, newhoverIndex);
- } else {
- plot.highlight(i, hoverIndex);
- }
+ plot.highlight(i, hoverInfo.hoverIndex);
}
- showTooltip(timestamp, seriesHtml, pos);
- return;
+ self.showTooltip(timestamp, seriesHtml, pos);
}
- if (item) {
- seriesInfo = item.series.info;
- format = scope.panel.y_formats[seriesInfo.yaxis - 1];
- group = '
' + seriesInfo.alias;
+ // single series tooltip
+ else if (item) {
+ series = item.series;
+ group = '
' + series.label;
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
value = item.datapoint[1] - item.datapoint[2];
@@ -146,19 +159,18 @@ function ($, kbn) {
value = item.datapoint[1];
}
- value = kbn.valueFormats[format](value, item.series.yaxis.tickDecimals);
+ value = series.valueFormater(value);
timestamp = dashboard.formatDate(item.datapoint[0]);
group += ':
' + value + '';
- showTooltip(timestamp, group, pos);
- } else {
+ self.showTooltip(timestamp, group, pos);
+ }
+ // no hit
+ else {
$tooltip.detach();
}
});
-
}
- return {
- register: registerTooltipFeatures
- };
+ return GraphTooltip;
});
diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js
index bc15b851f9d..6f5a174056f 100644
--- a/src/app/panels/graph/module.js
+++ b/src/app/panels/graph/module.js
@@ -244,7 +244,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
var seriesInfo = {
alias: alias,
- color: color,
+ color: color,
};
$scope.legend.push(seriesInfo);
diff --git a/src/css/less/graph.less b/src/css/less/graph.less
index 01a697ddb2e..c0c3f371325 100644
--- a/src/css/less/graph.less
+++ b/src/css/less/graph.less
@@ -169,6 +169,8 @@
}
.graph-tooltip {
+ white-space: nowrap;
+
.graph-tooltip-time {
text-align: center;
font-weight: bold;
diff --git a/src/test/specs/graph-tooltip-specs.js b/src/test/specs/graph-tooltip-specs.js
index 7f20ad8dbc9..2277426bc81 100644
--- a/src/test/specs/graph-tooltip-specs.js
+++ b/src/test/specs/graph-tooltip-specs.js
@@ -1,55 +1,93 @@
define([
'jquery',
'directives/grafanaGraph.tooltip'
-], function($, tooltip) {
+], function($, GraphTooltip) {
'use strict';
- describe('graph tooltip', function() {
- var elem = $('
');
- var dashboard = {
- formatDate: sinon.stub().returns('date'),
- };
- var scope = {
- appEvent: sinon.spy(),
- onAppEvent: sinon.spy(),
- panel: {
- tooltip: {
- shared: true
- },
- y_formats: ['ms', 'none'],
- stack: true
- }
- };
+ var scope = {
+ appEvent: sinon.spy(),
+ onAppEvent: sinon.spy(),
+ };
- var data = [
- {
- data: [[10,10], [12,20]],
- info: { yaxis: 1 },
- yaxis: { tickDecimals: 2 },
+ var elem = $('
');
+ var dashboard = { };
+
+ function describeSharedTooltip(desc, fn) {
+ var ctx = {};
+ ctx.scope = scope;
+ ctx.scope.panel = {
+ tooltip: {
+ shared: true
},
- {
- data: [[10,10], [12,20]],
- info: { yaxis: 1 },
- yaxis: { tickDecimals: 2 },
- }
- ];
-
- var plot = {
- getData: sinon.stub().returns(data),
- highlight: sinon.stub(),
- unhighlight: sinon.stub()
+ stack: false
};
- elem.data('plot', plot);
+ ctx.setup = function(setupFn) {
+ ctx.setupFn = setupFn;
+ };
- beforeEach(function() {
- tooltip.register(elem, dashboard, scope);
- elem.trigger('plothover', [{}, {x: 13}, {}]);
+ describe(desc, function() {
+ beforeEach(function() {
+ ctx.setupFn();
+ var tooltip = new GraphTooltip(elem, dashboard, scope);
+ ctx.results = tooltip.getMultiSeriesPlotHoverInfo(ctx.data, ctx.pos);
+ });
+
+ fn(ctx);
+ });
+ }
+
+ describeSharedTooltip("steppedLine false, stack false", function(ctx) {
+ ctx.setup(function() {
+ ctx.data = [
+ { data: [[10, 15], [12, 20]], },
+ { data: [[10, 2], [12, 3]], }
+ ];
+ ctx.pos = { x: 11 };
});
- it('should add tooltip', function() {
- var tooltipHtml = $(".graph-tooltip").text();
- expect(tooltipHtml).to.be('date : 40.00 ms : 20.00 ms');
+ it('should return 2 series', function() {
+ expect(ctx.results.length).to.be(2);
+ });
+ it('should add time to results array', function() {
+ expect(ctx.results.time).to.be(10);
+ });
+ it('should set value and hoverIndex', function() {
+ expect(ctx.results[0].value).to.be(15);
+ expect(ctx.results[1].value).to.be(2);
+ expect(ctx.results[0].hoverIndex).to.be(0);
+ });
+ });
+
+ describeSharedTooltip("steppedLine false, stack true, individual false", function(ctx) {
+ ctx.setup(function() {
+ ctx.data = [
+ { data: [[10, 15], [12, 20]], },
+ { data: [[10, 2], [12, 3]], }
+ ];
+ ctx.scope.panel.stack = true;
+ ctx.pos = { x: 11 };
+ });
+
+ it('should show stacked value', function() {
+ expect(ctx.results[1].value).to.be(17);
+ });
+
+ });
+
+ describeSharedTooltip("steppedLine false, stack true, individual true", function(ctx) {
+ ctx.setup(function() {
+ ctx.data = [
+ { data: [[10, 15], [12, 20]], },
+ { data: [[10, 2], [12, 3]], }
+ ];
+ ctx.scope.panel.stack = true;
+ ctx.scope.panel.tooltip.value_type = 'individual';
+ ctx.pos = { x: 11 };
+ });
+
+ it('should not show stacked value', function() {
+ expect(ctx.results[1].value).to.be(2);
});
});