Commit 022cbdda by Torkel Ödegaard

Merge branch 'master' into panel_edit_menu_poc

parents bc9989f9 ae2523aa
# 1.9.0 (unreleased) # 1.9.0 (unreleased)
- [Issue #877](https://github.com/grafana/grafana/issues/877). Graph: Smart auto decimal precision when using scaled unit formats
- [Issue #850](https://github.com/grafana/grafana/issues/850). Graph: Shared tooltip that shows multiple series & crosshair line, thx @toni-moreno
# 1.8.1 (unreleased) # 1.8.1 (unreleased)
......
{ {
"version": "1.8.0", "version": "1.8.1",
"url": "http://grafanarel.s3.amazonaws.com/grafana-1.8.0.tar.gz" "url": "http://grafanarel.s3.amazonaws.com/grafana-1.8.1.tar.gz"
} }
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"company": "Coding Instinct AB" "company": "Coding Instinct AB"
}, },
"name": "grafana", "name": "grafana",
"version": "1.8.0", "version": "1.8.1",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "http://github.com/torkelo/grafana.git" "url": "http://github.com/torkelo/grafana.git"
......
...@@ -7,6 +7,7 @@ function($, _, moment) { ...@@ -7,6 +7,7 @@ function($, _, moment) {
'use strict'; 'use strict';
var kbn = {}; var kbn = {};
kbn.valueFormats = {};
kbn.round_interval = function(interval) { kbn.round_interval = function(interval) {
switch (true) { switch (true) {
...@@ -309,240 +310,27 @@ function($, _, moment) { ...@@ -309,240 +310,27 @@ function($, _, moment) {
].join(';') + '"></div>'; ].join(';') + '"></div>';
}; };
kbn.byteFormat = function(size, decimals) { kbn.valueFormats.percent = function(size, decimals) {
var ext, steps = 0; return kbn.toFixed(size, decimals) + '%';
if(_.isUndefined(decimals)) {
decimals = 2;
} else if (decimals === 0) {
decimals = undefined;
}
while (Math.abs(size) >= 1024) {
steps++;
size /= 1024;
}
switch (steps) {
case 0:
ext = " B";
break;
case 1:
ext = " KiB";
break;
case 2:
ext = " MiB";
break;
case 3:
ext = " GiB";
break;
case 4:
ext = " TiB";
break;
case 5:
ext = " PiB";
break;
case 6:
ext = " EiB";
break;
case 7:
ext = " ZiB";
break;
case 8:
ext = " YiB";
break;
}
return (size.toFixed(decimals) + ext);
}; };
kbn.bitFormat = function(size, decimals) { kbn.formatFuncCreator = function(factor, extArray) {
var ext, steps = 0; return function(size, decimals, scaledDecimals) {
var steps = 0;
if(_.isUndefined(decimals)) {
decimals = 2;
} else if (decimals === 0) {
decimals = undefined;
}
while (Math.abs(size) >= 1024) {
steps++;
size /= 1024;
}
switch (steps) {
case 0:
ext = " b";
break;
case 1:
ext = " Kib";
break;
case 2:
ext = " Mib";
break;
case 3:
ext = " Gib";
break;
case 4:
ext = " Tib";
break;
case 5:
ext = " Pib";
break;
case 6:
ext = " Eib";
break;
case 7:
ext = " Zib";
break;
case 8:
ext = " Yib";
break;
}
return (size.toFixed(decimals) + ext);
};
kbn.bpsFormat = function(size, decimals) {
var ext, steps = 0;
if(_.isUndefined(decimals)) {
decimals = 2;
} else if (decimals === 0) {
decimals = undefined;
}
while (Math.abs(size) >= 1000) {
steps++;
size /= 1000;
}
switch (steps) {
case 0:
ext = " bps";
break;
case 1:
ext = " Kbps";
break;
case 2:
ext = " Mbps";
break;
case 3:
ext = " Gbps";
break;
case 4:
ext = " Tbps";
break;
case 5:
ext = " Pbps";
break;
case 6:
ext = " Ebps";
break;
case 7:
ext = " Zbps";
break;
case 8:
ext = " Ybps";
break;
}
return (size.toFixed(decimals) + ext);
};
kbn.shortFormat = function(size, decimals) { while (Math.abs(size) >= factor) {
var ext, steps = 0; steps++;
size /= factor;
if(_.isUndefined(decimals)) { }
decimals = 2; if (steps > 0) {
} else if (decimals === 0) { decimals = scaledDecimals + (3 * steps);
decimals = undefined; }
}
while (Math.abs(size) >= 1000) {
steps++;
size /= 1000;
}
switch (steps) {
case 0:
ext = "";
break;
case 1:
ext = " K";
break;
case 2:
ext = " Mil";
break;
case 3:
ext = " Bil";
break;
case 4:
ext = " Tri";
break;
case 5:
ext = " Quadr";
break;
case 6:
ext = " Quint";
break;
case 7:
ext = " Sext";
break;
case 8:
ext = " Sept";
break;
}
return (size.toFixed(decimals) + ext);
};
kbn.getFormatFunction = function(formatName, decimals) { return kbn.toFixed(size, decimals) + extArray[steps];
switch(formatName) { };
case 'short':
return function(val) {
return kbn.shortFormat(val, decimals);
};
case 'bytes':
return function(val) {
return kbn.byteFormat(val, decimals);
};
case 'bits':
return function(val) {
return kbn.bitFormat(val, decimals);
};
case 'bps':
return function(val) {
return kbn.bpsFormat(val, decimals);
};
case 's':
return function(val) {
return kbn.sFormat(val, decimals);
};
case 'ms':
return function(val) {
return kbn.msFormat(val, decimals);
};
case 'µs':
return function(val) {
return kbn.microsFormat(val, decimals);
};
case 'ns':
return function(val) {
return kbn.nanosFormat(val, decimals);
};
case 'percent':
return function(val, axis) {
return kbn.noneFormat(val, axis ? axis.tickDecimals : null) + ' %';
};
default:
return function(val, axis) {
return kbn.noneFormat(val, axis ? axis.tickDecimals : null);
};
}
}; };
kbn.noneFormat = function(value, decimals) { kbn.toFixed = function(value, decimals) {
var factor = decimals ? Math.pow(10, decimals) : 1; var factor = decimals ? Math.pow(10, decimals) : 1;
var formatted = String(Math.round(value * factor) / factor); var formatted = String(Math.round(value * factor) / factor);
...@@ -553,7 +341,6 @@ function($, _, moment) { ...@@ -553,7 +341,6 @@ function($, _, moment) {
// If tickDecimals was specified, ensure that we have exactly that // If tickDecimals was specified, ensure that we have exactly that
// much precision; otherwise default to the value's own precision. // much precision; otherwise default to the value's own precision.
if (decimals != null) { if (decimals != null) {
var decimalPos = formatted.indexOf("."); var decimalPos = formatted.indexOf(".");
var precision = decimalPos === -1 ? 0 : formatted.length - decimalPos - 1; var precision = decimalPos === -1 ? 0 : formatted.length - decimalPos - 1;
...@@ -565,97 +352,87 @@ function($, _, moment) { ...@@ -565,97 +352,87 @@ function($, _, moment) {
return formatted; return formatted;
}; };
kbn.msFormat = function(size, decimals) { kbn.valueFormats.bits = kbn.formatFuncCreator(1024, [' b', ' Kib', ' Mib', ' Gib', ' Tib', ' Pib', ' Eib', ' Zib', ' Yib']);
// Less than 1 milli, downscale to micro kbn.valueFormats.bytes = kbn.formatFuncCreator(1024, [' B', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']);
if (size !== 0 && Math.abs(size) < 1) { kbn.valueFormats.bps = kbn.formatFuncCreator(1000, [' bps', ' Kbps', ' Mbps', ' Gbps', ' Tbps', ' Pbps', ' Ebps', ' Zbps', ' Ybps']);
return kbn.microsFormat(size * 1000, decimals); kbn.valueFormats.short = kbn.formatFuncCreator(1000, ['', ' K', ' Mil', ' Bil', ' Tri', ' Qaudr', ' Quint', ' Sext', ' Sept']);
} kbn.valueFormats.none = kbn.toFixed;
else if (Math.abs(size) < 1000) {
return size.toFixed(decimals) + " ms"; kbn.valueFormats.ms = function(size, decimals, scaledDecimals) {
if (Math.abs(size) < 1000) {
return kbn.toFixed(size, decimals) + " ms";
} }
// Less than 1 min // Less than 1 min
else if (Math.abs(size) < 60000) { else if (Math.abs(size) < 60000) {
return (size / 1000).toFixed(decimals) + " s"; return kbn.toFixed(size / 1000, scaledDecimals + 3) + " s";
} }
// Less than 1 hour, devide in minutes // Less than 1 hour, devide in minutes
else if (Math.abs(size) < 3600000) { else if (Math.abs(size) < 3600000) {
return (size / 60000).toFixed(decimals) + " min"; return kbn.toFixed(size / 60000, scaledDecimals + 5) + " min";
} }
// Less than one day, devide in hours // Less than one day, devide in hours
else if (Math.abs(size) < 86400000) { else if (Math.abs(size) < 86400000) {
return (size / 3600000).toFixed(decimals) + " hour"; return kbn.toFixed(size / 3600000, scaledDecimals + 7) + " hour";
} }
// Less than one year, devide in days // Less than one year, devide in days
else if (Math.abs(size) < 31536000000) { else if (Math.abs(size) < 31536000000) {
return (size / 86400000).toFixed(decimals) + " day"; return kbn.toFixed(size / 86400000, scaledDecimals + 8) + " day";
} }
return (size / 31536000000).toFixed(decimals) + " year"; return kbn.toFixed(size / 31536000000, scaledDecimals + 10) + " year";
}; };
kbn.sFormat = function(size, decimals) { kbn.valueFormats.s = function(size, decimals, scaledDecimals) {
// Less than 1 sec, downscale to milli if (Math.abs(size) < 600) {
if (size !== 0 && Math.abs(size) < 1) { return kbn.toFixed(size, decimals) + " s";
return kbn.msFormat(size * 1000, decimals);
}
// Less than 10 min, use seconds
else if (Math.abs(size) < 600) {
return size.toFixed(decimals) + " s";
} }
// Less than 1 hour, devide in minutes // Less than 1 hour, devide in minutes
else if (Math.abs(size) < 3600) { else if (Math.abs(size) < 3600) {
return (size / 60).toFixed(decimals) + " min"; return kbn.toFixed(size / 60, scaledDecimals + 1) + " min";
} }
// Less than one day, devide in hours // Less than one day, devide in hours
else if (Math.abs(size) < 86400) { else if (Math.abs(size) < 86400) {
return (size / 3600).toFixed(decimals) + " hour"; return kbn.toFixed(size / 3600, scaledDecimals + 4) + " hour";
} }
// Less than one week, devide in days // Less than one week, devide in days
else if (Math.abs(size) < 604800) { else if (Math.abs(size) < 604800) {
return (size / 86400).toFixed(decimals) + " day"; return kbn.toFixed(size / 86400, scaledDecimals + 5) + " day";
} }
// Less than one year, devide in week // Less than one year, devide in week
else if (Math.abs(size) < 31536000) { else if (Math.abs(size) < 31536000) {
return (size / 604800).toFixed(decimals) + " week"; return kbn.toFixed(size / 604800, scaledDecimals + 6) + " week";
} }
return (size / 3.15569e7).toFixed(decimals) + " year"; return kbn.toFixed(size / 3.15569e7, scaledDecimals + 7) + " year";
}; };
kbn.microsFormat = function(size, decimals) { kbn.valueFormats['µs'] = function(size, decimals, scaledDecimals) {
// Less than 1 micro, downscale to nano if (Math.abs(size) < 1000) {
if (size !== 0 && Math.abs(size) < 1) { return kbn.toFixed(size, decimals) + " µs";
return kbn.nanosFormat(size * 1000, decimals);
}
else if (Math.abs(size) < 1000) {
return size.toFixed(decimals) + " µs";
} }
else if (Math.abs(size) < 1000000) { else if (Math.abs(size) < 1000000) {
return (size / 1000).toFixed(decimals) + " ms"; return kbn.toFixed(size / 1000, scaledDecimals + 3) + " ms";
} }
else { else {
return (size / 1000000).toFixed(decimals) + " s"; return kbn.toFixed(size / 1000000, scaledDecimals + 6) + " s";
} }
}; };
kbn.nanosFormat = function(size, decimals) { kbn.valueFormats.ns = function(size, decimals, scaledDecimals) {
if (Math.abs(size) < 1) { if (Math.abs(size) < 1000) {
return size.toFixed(decimals) + " ns"; return kbn.toFixed(size, decimals) + " ns";
}
else if (Math.abs(size) < 1000) {
return size.toFixed(0) + " ns";
} }
else if (Math.abs(size) < 1000000) { else if (Math.abs(size) < 1000000) {
return (size / 1000).toFixed(decimals) + " µs"; return kbn.toFixed(size / 1000, scaledDecimals + 3) + " µs";
} }
else if (Math.abs(size) < 1000000000) { else if (Math.abs(size) < 1000000000) {
return (size / 1000000).toFixed(decimals) + " ms"; return kbn.toFixed(size / 1000000, scaledDecimals + 6) + " ms";
} }
else if (Math.abs(size) < 60000000000){ else if (Math.abs(size) < 60000000000){
return (size / 1000000000).toFixed(decimals) + " s"; return kbn.toFixed(size / 1000000000, scaledDecimals + 9) + " s";
} }
else { else {
return (size / 60000000000).toFixed(decimals) + " m"; return kbn.toFixed(size / 60000000000, scaledDecimals + 12) + " m";
} }
}; };
......
...@@ -40,6 +40,7 @@ require.config({ ...@@ -40,6 +40,7 @@ require.config({
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack', 'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent', 'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
'jquery.flot.time': '../vendor/jquery/jquery.flot.time', 'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
'jquery.flot.crosshair': '../vendor/jquery/jquery.flot.crosshair',
modernizr: '../vendor/modernizr-2.6.1', modernizr: '../vendor/modernizr-2.6.1',
...@@ -83,6 +84,7 @@ require.config({ ...@@ -83,6 +84,7 @@ require.config({
'jquery.flot.stack': ['jquery', 'jquery.flot'], 'jquery.flot.stack': ['jquery', 'jquery.flot'],
'jquery.flot.stackpercent':['jquery', 'jquery.flot'], 'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
'jquery.flot.time': ['jquery', 'jquery.flot'], 'jquery.flot.time': ['jquery', 'jquery.flot'],
'jquery.flot.crosshair':['jquery', 'jquery.flot'],
'angular-cookies': ['angular'], 'angular-cookies': ['angular'],
'angular-dragdrop': ['jquery','jquery-ui','angular'], 'angular-dragdrop': ['jquery','jquery-ui','angular'],
'angular-loader': ['angular'], 'angular-loader': ['angular'],
......
...@@ -54,7 +54,7 @@ function (_, kbn) { ...@@ -54,7 +54,7 @@ function (_, kbn) {
} }
}; };
TimeSeries.prototype.getFlotPairs = function (fillStyle, yFormats) { TimeSeries.prototype.getFlotPairs = function (fillStyle) {
var result = []; var result = [];
this.color = this.info.color; this.color = this.info.color;
...@@ -100,21 +100,21 @@ function (_, kbn) { ...@@ -100,21 +100,21 @@ function (_, kbn) {
} }
if (result.length) { if (result.length) {
this.info.avg = (this.info.total / result.length); this.info.avg = (this.info.total / result.length);
this.info.current = result[result.length-1][1]; this.info.current = result[result.length-1][1];
var formater = kbn.getFormatFunction(yFormats[this.yaxis - 1], 2);
this.info.avg = this.info.avg != null ? formater(this.info.avg) : null;
this.info.current = this.info.current != null ? formater(this.info.current) : null;
this.info.min = this.info.min != null ? formater(this.info.min) : null;
this.info.max = this.info.max != null ? formater(this.info.max) : null;
this.info.total = this.info.total != null ? formater(this.info.total) : null;
} }
return result; return result;
}; };
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;
};
return TimeSeries; return TimeSeries;
}); });
...@@ -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() {
...@@ -80,6 +81,18 @@ function (angular, $, kbn, moment, _) { ...@@ -80,6 +81,18 @@ function (angular, $, kbn, moment, _) {
} }
} }
function updateLegendValues(plot) {
var yaxis = plot.getYAxes();
for (var i = 0; i < data.length; i++) {
var series = data[i];
var axis = yaxis[series.yaxis - 1];
var formater = kbn.valueFormats[scope.panel.y_formats[series.yaxis - 1]];
series.updateLegendValues(formater, axis.tickDecimals, axis.scaledDecimals);
}
}
// Function for rendering panel // Function for rendering panel
function render_panel() { function render_panel() {
if (shouldAbortRender()) { if (shouldAbortRender()) {
...@@ -91,6 +104,7 @@ function (angular, $, kbn, moment, _) { ...@@ -91,6 +104,7 @@ function (angular, $, kbn, moment, _) {
// Populate element // Populate element
var options = { var options = {
hooks: { draw: [updateLegendValues] },
legend: { show: false }, legend: { show: false },
series: { series: {
stackpercent: panel.stack ? panel.percentage : false, stackpercent: panel.stack ? panel.percentage : false,
...@@ -113,7 +127,8 @@ function (angular, $, kbn, moment, _) { ...@@ -113,7 +127,8 @@ function (angular, $, kbn, moment, _) {
show: panel.points, show: panel.points,
fill: 1, fill: 1,
fillColor: false, fillColor: false,
radius: panel.pointradius radius: panel.points ? panel.pointradius : 2
// little points when highlight points
}, },
shadowSize: 1 shadowSize: 1
}, },
...@@ -130,6 +145,9 @@ function (angular, $, kbn, moment, _) { ...@@ -130,6 +145,9 @@ function (angular, $, kbn, moment, _) {
selection: { selection: {
mode: "x", mode: "x",
color: '#666' color: '#666'
},
crosshair: {
mode: panel.tooltip.shared ? "x" : null
} }
}; };
...@@ -299,7 +317,9 @@ function (angular, $, kbn, moment, _) { ...@@ -299,7 +317,9 @@ function (angular, $, kbn, moment, _) {
} }
function configureAxisMode(axis, format) { function configureAxisMode(axis, format) {
axis.tickFormatter = kbn.getFormatFunction(format, 1); axis.tickFormatter = function(val, axis) {
return kbn.valueFormats[format](val, axis.tickDecimals, axis.scaledDecimals);
};
} }
function time_format(interval, ticks, min, max) { function time_format(interval, ticks, min, max) {
...@@ -324,40 +344,6 @@ function (angular, $, kbn, moment, _) { ...@@ -324,40 +344,6 @@ function (angular, $, kbn, moment, _) {
return "%H:%M"; return "%H:%M";
} }
var $tooltip = $('<div id="tooltip">');
elem.bind("plothover", function (event, pos, item) {
var group, value, timestamp, seriesInfo, format;
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.getFormatFunction(format, 2)(value, item.series.yaxis);
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', '');
...@@ -408,6 +394,8 @@ function (angular, $, kbn, moment, _) { ...@@ -408,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
};
});
...@@ -15,7 +15,8 @@ define([ ...@@ -15,7 +15,8 @@ define([
'jquery.flot.selection', 'jquery.flot.selection',
'jquery.flot.time', 'jquery.flot.time',
'jquery.flot.stack', 'jquery.flot.stack',
'jquery.flot.stackpercent' 'jquery.flot.stackpercent',
'jquery.flot.crosshair'
], ],
function (angular, app, $, _, kbn, moment, TimeSeries) { function (angular, app, $, _, kbn, moment, TimeSeries) {
'use strict'; 'use strict';
...@@ -160,7 +161,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { ...@@ -160,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: [{}],
......
...@@ -51,8 +51,16 @@ ...@@ -51,8 +51,16 @@
<input type="radio" class="input-small" ng-model="panel.renderer" value="png" ng-change="get_data()" /> <input type="radio" class="input-small" ng-model="panel.renderer" value="png" ng-change="get_data()" />
</div> </div>
</div> </div>
<div class="section">
<h5>Tooltip</h5>
<div class="editor-option">
<label class="small">shared <tip> Show all series values on the same time in the same tooltip and a x croshair to help follow all series</tip> </label><input type="checkbox" ng-model="panel.tooltip.shared" ng-checked="panel.tooltip.shared" ng-change="render()">
</div>
</div>
</div> </div>
<div class="editor-row"> <div class="editor-row">
<div class="section"> <div class="section">
<h5>Series specific overrides <tip>Regex match example: /server[0-3]/i </tip></h5> <h5>Series specific overrides <tip>Regex match example: /server[0-3]/i </tip></h5>
......
...@@ -215,9 +215,9 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) { ...@@ -215,9 +215,9 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
if (id === title) { return; } if (id === title) { return; }
var self = this; var self = this;
self._getDashboardInternal(id, isTemp).then(function(dashboard) { self._getDashboardInternal(title, isTemp).then(function(dashboard) {
if (dashboard !== null) { if (dashboard !== null) {
self.deleteDashboard(id); self.deleteDashboard(title);
} }
}); });
}; };
......
...@@ -87,7 +87,7 @@ function (Settings) { ...@@ -87,7 +87,7 @@ function (Settings) {
// Example: "1m", "1h" // Example: "1m", "1h"
playlist_timespan: "1m", playlist_timespan: "1m",
// If you want to specify password before saving, please specify it bellow // If you want to specify password before saving, please specify it below
// The purpose of this password is not security, but to stop some users from accidentally changing dashboards // The purpose of this password is not security, but to stop some users from accidentally changing dashboards
admin: { admin: {
password: '' password: ''
......
...@@ -444,6 +444,18 @@ select.grafana-target-segment-input { ...@@ -444,6 +444,18 @@ select.grafana-target-segment-input {
max-width: 800px; max-width: 800px;
max-height: 600px; max-height: 600px;
overflow: hidden; overflow: hidden;
line-height: 14px;
}
.grafana-tooltip hr {
padding: 2px;
color: #c8c8c8;
margin: 0px;
border-bottom:0px solid #c8c8c8;
/*height:0px;
background-color: rgb(58, 57, 57);*/
} }
.tooltip.in { .tooltip.in {
......
...@@ -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;
}
}
...@@ -27,7 +27,10 @@ define([ ...@@ -27,7 +27,10 @@ define([
legend: {}, legend: {},
grid: {}, grid: {},
y_formats: [], y_formats: [],
seriesOverrides: [] seriesOverrides: [],
tooltip: {
shared: true
}
}; };
scope.hiddenSeries = {}; scope.hiddenSeries = {};
scope.dashboard = { timezone: 'browser' }; scope.dashboard = { timezone: 'browser' };
......
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');
});
});
});
...@@ -3,76 +3,31 @@ define([ ...@@ -3,76 +3,31 @@ define([
], function(kbn) { ], function(kbn) {
'use strict'; 'use strict';
describe('millisecond formating', function() { function describeValueFormat(desc, value, tickSize, tickDecimals, result) {
it('should translate 4378634603 as 1.67 years', function() { describe('value format: ' + desc, function() {
var str = kbn.msFormat(4378634603, 2); it('should translate ' + value + ' as ' + result, function() {
expect(str).to.be('50.68 day'); var scaledDecimals = tickDecimals - Math.floor(Math.log(tickSize) / Math.LN10);
var str = kbn.valueFormats[desc](value, tickDecimals, scaledDecimals);
expect(str).to.be(result);
});
}); });
it('should translate 3654454 as 1.02 hour', function() { }
var str = kbn.msFormat(3654454, 2);
expect(str).to.be('1.02 hour');
});
it('should not downscale when value is zero', function() {
var str = kbn.msFormat(0, 2);
expect(str).to.be('0.00 ms');
});
it('should translate 365445 as 6.09 min', function() {
var str = kbn.msFormat(365445, 2);
expect(str).to.be('6.09 min');
});
});
describe('high negative exponent, issue #696', function() {
it('should ignore decimal correction if exponent', function() {
var str = kbn.getFormatFunction('')(2.75e-10, { tickDecimals: 12 });
expect(str).to.be('2.75e-10');
});
it('should format 0 correctly', function() {
var str = kbn.getFormatFunction('')(0.0, { tickDecimals: 12 });
expect(str).to.be('0');
});
});
describe('none format tests', function() { describeValueFormat('ms', 0.0024, 0.0005, 4, '0.0024 ms');
it('should translate 2 as 2.0000 if axis decimals is 4', function() { describeValueFormat('ms', 100, 1, 0, '100 ms');
var str = kbn.getFormatFunction('')(2, { tickDecimals: 4 }); describeValueFormat('ms', 1250, 10, 0, '1.25 s');
expect(str).to.be('2.0000'); describeValueFormat('ms', 1250, 300, 0, '1.3 s');
}); describeValueFormat('ms', 65150, 10000, 0, '1.1 min');
}); describeValueFormat('ms', 6515000, 1500000, 0, '1.8 hour');
describeValueFormat('ms', 651500000, 150000000, 0, '8 day');
describe('nanosecond formatting', function () {
it('should translate 25 to 25 ns', function () {
var str = kbn.nanosFormat(25, 2);
expect(str).to.be("25 ns");
});
it('should translate 2558 to 2.56 µs', function () {
var str = kbn.nanosFormat(2558, 2);
expect(str).to.be("2.56 µs");
});
it('should translate 2558000 to 2.56 ms', function () { describeValueFormat('none', 2.75e-10, 0, 10, '3e-10');
var str = kbn.nanosFormat(2558000, 2); describeValueFormat('none', 0, 0, 2, '0');
expect(str).to.be("2.56 ms");
});
it('should translate 2019962000 to 2.02 s', function () {
var str = kbn.nanosFormat(2049962000, 2);
expect(str).to.be("2.05 s");
});
it('should translate 95199620000 to 1.59 m', function () { describeValueFormat('ns', 25, 1, 0, '25 ns');
var str = kbn.nanosFormat(95199620000, 2); describeValueFormat('ns', 2558, 50, 0, '2.56 µs');
expect(str).to.be("1.59 m");
});
});
describe('calculateInterval', function() { describe('calculateInterval', function() {
it('1h 100 resultion', function() { it('1h 100 resultion', function() {
......
...@@ -43,6 +43,7 @@ require.config({ ...@@ -43,6 +43,7 @@ require.config({
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack', 'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent', 'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
'jquery.flot.time': '../vendor/jquery/jquery.flot.time', 'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
'jquery.flot.crosshair': '../vendor/jquery/jquery.flot.crosshair',
modernizr: '../vendor/modernizr-2.6.1', modernizr: '../vendor/modernizr-2.6.1',
}, },
...@@ -77,6 +78,7 @@ require.config({ ...@@ -77,6 +78,7 @@ require.config({
'jquery.flot.stack': ['jquery', 'jquery.flot'], 'jquery.flot.stack': ['jquery', 'jquery.flot'],
'jquery.flot.stackpercent':['jquery', 'jquery.flot'], 'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
'jquery.flot.time': ['jquery', 'jquery.flot'], 'jquery.flot.time': ['jquery', 'jquery.flot'],
'jquery.flot.crosshair':['jquery', 'jquery.flot'],
'angular-route': ['angular'], 'angular-route': ['angular'],
'angular-cookies': ['angular'], 'angular-cookies': ['angular'],
...@@ -127,6 +129,7 @@ require([ ...@@ -127,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',
......
/* Flot plugin for showing crosshairs when the mouse hovers over the plot.
Copyright (c) 2007-2014 IOLA and Ole Laursen.
Licensed under the MIT license.
The plugin supports these options:
crosshair: {
mode: null or "x" or "y" or "xy"
color: color
lineWidth: number
}
Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical
crosshair that lets you trace the values on the x axis, "y" enables a
horizontal crosshair and "xy" enables them both. "color" is the color of the
crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of
the drawn lines (default is 1).
The plugin also adds four public methods:
- setCrosshair( pos )
Set the position of the crosshair. Note that this is cleared if the user
moves the mouse. "pos" is in coordinates of the plot and should be on the
form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple
axes), which is coincidentally the same format as what you get from a
"plothover" event. If "pos" is null, the crosshair is cleared.
- clearCrosshair()
Clear the crosshair.
- lockCrosshair(pos)
Cause the crosshair to lock to the current location, no longer updating if
the user moves the mouse. Optionally supply a position (passed on to
setCrosshair()) to move it to.
Example usage:
var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
$("#graph").bind( "plothover", function ( evt, position, item ) {
if ( item ) {
// Lock the crosshair to the data point being hovered
myFlot.lockCrosshair({
x: item.datapoint[ 0 ],
y: item.datapoint[ 1 ]
});
} else {
// Return normal crosshair operation
myFlot.unlockCrosshair();
}
});
- unlockCrosshair()
Free the crosshair to move again after locking it.
*/
(function ($) {
var options = {
crosshair: {
mode: null, // one of null, "x", "y" or "xy",
color: "rgba(170, 0, 0, 0.80)",
lineWidth: 1
}
};
function init(plot) {
// position of crosshair in pixels
var crosshair = { x: -1, y: -1, locked: false };
plot.setCrosshair = function setCrosshair(pos) {
if (!pos)
crosshair.x = -1;
else {
var o = plot.p2c(pos);
crosshair.x = Math.max(0, Math.min(o.left, plot.width()));
crosshair.y = Math.max(0, Math.min(o.top, plot.height()));
}
plot.triggerRedrawOverlay();
};
plot.clearCrosshair = plot.setCrosshair; // passes null for pos
plot.lockCrosshair = function lockCrosshair(pos) {
if (pos)
plot.setCrosshair(pos);
crosshair.locked = true;
};
plot.unlockCrosshair = function unlockCrosshair() {
crosshair.locked = false;
};
function onMouseOut(e) {
if (crosshair.locked)
return;
if (crosshair.x != -1) {
crosshair.x = -1;
plot.triggerRedrawOverlay();
}
}
function onMouseMove(e) {
if (crosshair.locked)
return;
if (plot.getSelection && plot.getSelection()) {
crosshair.x = -1; // hide the crosshair while selecting
return;
}
var offset = plot.offset();
crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
plot.triggerRedrawOverlay();
}
plot.hooks.bindEvents.push(function (plot, eventHolder) {
if (!plot.getOptions().crosshair.mode)
return;
eventHolder.mouseout(onMouseOut);
eventHolder.mousemove(onMouseMove);
});
plot.hooks.drawOverlay.push(function (plot, ctx) {
var c = plot.getOptions().crosshair;
if (!c.mode)
return;
var plotOffset = plot.getPlotOffset();
ctx.save();
ctx.translate(plotOffset.left, plotOffset.top);
if (crosshair.x != -1) {
var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0;
ctx.strokeStyle = c.color;
ctx.lineWidth = c.lineWidth;
ctx.lineJoin = "round";
ctx.beginPath();
if (c.mode.indexOf("x") != -1) {
var drawX = Math.floor(crosshair.x) + adj;
ctx.moveTo(drawX, 0);
ctx.lineTo(drawX, plot.height());
}
if (c.mode.indexOf("y") != -1) {
var drawY = Math.floor(crosshair.y) + adj;
ctx.moveTo(0, drawY);
ctx.lineTo(plot.width(), drawY);
}
ctx.stroke();
}
ctx.restore();
});
plot.hooks.shutdown.push(function (plot, eventHolder) {
eventHolder.unbind("mouseout", onMouseOut);
eventHolder.unbind("mousemove", onMouseMove);
});
}
$.plot.plugins.push({
init: init,
options: options,
name: 'crosshair',
version: '1.0'
});
})(jQuery);
...@@ -1728,6 +1728,8 @@ Licensed under the MIT license. ...@@ -1728,6 +1728,8 @@ Licensed under the MIT license.
axis.delta = delta; axis.delta = delta;
axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
axis.tickSize = opts.tickSize || size; axis.tickSize = opts.tickSize || size;
// grafana addition
axis.scaledDecimals = axis.tickDecimals - Math.floor(Math.log(axis.tickSize) / Math.LN10);
// Time mode was moved to a plug-in in 0.8, and since so many people use it // Time mode was moved to a plug-in in 0.8, and since so many people use it
// we'll add an especially friendly reminder to make sure they included it. // we'll add an especially friendly reminder to make sure they included it.
......
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