diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 602cb49cb68..4bb1e43cc25 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -52,6 +52,8 @@ import {gfPageDirective} from './components/gf_page'; import {orgSwitcher} from './components/org_switcher'; import {profiler} from './profiler'; import {registerAngularDirectives} from './angular_wrappers'; +import {updateLegendValues} from './time_series2'; +import TimeSeries from './time_series2'; export { profiler, @@ -83,5 +85,7 @@ export { userGroupPicker, geminiScrollbar, gfPageDirective, - orgSwitcher + orgSwitcher, + TimeSeries, + updateLegendValues }; diff --git a/public/app/core/time_series2.ts b/public/app/core/time_series2.ts index 0f3dcbc1171..bf8a38761d3 100644 --- a/public/app/core/time_series2.ts +++ b/public/app/core/time_series2.ts @@ -1,4 +1,5 @@ import kbn from 'app/core/utils/kbn'; +import {getFlotTickDecimals} from 'app/core/utils/ticks'; import _ from 'lodash'; function matchSeriesOverride(aliasOrRegex, seriesAlias) { @@ -16,6 +17,43 @@ function translateFillOption(fill) { return fill === 0 ? 0.001 : fill/10; } +/** + * Calculate decimals for legend and update values for each series. + * @param data series data + * @param panel + * @param height Graph height + */ +export function updateLegendValues(data: TimeSeries[], panel, height) { + for (let i = 0; i < data.length; i++) { + let series = data[i]; + let yaxes = panel.yaxes; + let axis = yaxes[series.yaxis - 1]; + let {tickDecimals, scaledDecimals} = getFlotTickDecimals(data, axis, height); + let formater = kbn.valueFormats[panel.yaxes[series.yaxis - 1].format]; + + // decimal override + if (_.isNumber(panel.decimals)) { + series.updateLegendValues(formater, panel.decimals, null); + } else { + // auto decimals + // legend and tooltip gets one more decimal precision + // than graph legend ticks + tickDecimals = (tickDecimals || -1) + 1; + series.updateLegendValues(formater, tickDecimals, scaledDecimals + 2); + } + } +} + +export function getDataMinMax(data: TimeSeries[]) { + const datamin = _.minBy(data, (series) => { + return series.stats.min; + }).stats.min; + const datamax = _.maxBy(data, (series: TimeSeries) => { + return series.stats.max; + }).stats.max; + return {datamin, datamax}; +} + export default class TimeSeries { datapoints: any; id: string; diff --git a/public/app/core/utils/ticks.ts b/public/app/core/utils/ticks.ts index b033e9247a1..334090c056f 100644 --- a/public/app/core/utils/ticks.ts +++ b/public/app/core/utils/ticks.ts @@ -1,3 +1,5 @@ +import {getDataMinMax} from 'app/core/time_series2'; + /** * Calculate tick step. * Implementation from d3-array (ticks.js) @@ -32,6 +34,7 @@ export function getScaledDecimals(decimals, tick_size) { /** * Calculate tick size based on min and max values, number of ticks and precision. + * Implementation from Flot. * @param min Axis minimum * @param max Axis maximum * @param noTicks Number of ticks @@ -65,3 +68,107 @@ export function getFlotTickSize(min: number, max: number, noTicks: number, tickD return size; } + +/** + * Calculate axis range (min and max). + * Implementation from Flot. + */ +export function getFlotRange(panelMin, panelMax, datamin, datamax) { + const autoscaleMargin = 0.02; + + let min = +(panelMin != null ? panelMin : datamin); + let max = +(panelMax != null ? panelMax : datamax); + let delta = max - min; + + if (delta === 0.0) { + // Grafana fix: wide Y min and max using increased wideFactor + // when all series values are the same + var wideFactor = 0.25; + var widen = Math.abs(max === 0 ? 1 : max * wideFactor); + + if (panelMin === null) { + min -= widen; + } + // always widen max if we couldn't widen min to ensure we + // don't fall into min == max which doesn't work + if (panelMax == null || panelMin != null) { + max += widen; + } + } else { + // consider autoscaling + var margin = autoscaleMargin; + if (margin != null) { + if (panelMin == null) { + min -= delta * margin; + // make sure we don't go below zero if all values + // are positive + if (min < 0 && datamin != null && datamin >= 0) { + min = 0; + } + } + if (panelMax == null) { + max += delta * margin; + if (max > 0 && datamax != null && datamax <= 0) { + max = 0; + } + } + } + } + return {min, max}; +} + +/** + * Estimate number of ticks for Y axis. + * Implementation from Flot. + */ +export function getFlotNumberOfTicks(height, ticks?) { + let noTicks; + if (typeof ticks === "number" && ticks > 0) { + noTicks = ticks; + } else { + // heuristic based on the model a*sqrt(x) fitted to + // some data points that seemed reasonable + noTicks = 0.3 * Math.sqrt(height); + } + return noTicks; +} + +/** + * Calculate tick decimals. + * Implementation from Flot. + */ +export function getFlotTickDecimals(data, axis, height) { + let {datamin, datamax} = getDataMinMax(data); + let {min, max} = getFlotRange(axis.min, axis.max, datamin, datamax); + let noTicks = getFlotNumberOfTicks(height); + let tickDecimals, maxDec; + let delta = (max - min) / noTicks; + let dec = -Math.floor(Math.log(delta) / Math.LN10); + + let magn = Math.pow(10, -dec); + // norm is between 1.0 and 10.0 + let norm = delta / magn; + let size; + + if (norm < 1.5) { + size = 1; + } else if (norm < 3) { + size = 2; + // special case for 2.5, requires an extra decimal + if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { + size = 2.5; + ++dec; + } + } else if (norm < 7.5) { + size = 5; + } else { + size = 10; + } + + size *= magn; + + tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); + // grafana addition + const scaledDecimals = tickDecimals - Math.floor(Math.log(size) / Math.LN10); + return {tickDecimals, scaledDecimals}; +} diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index d24aedc66b9..30761238d82 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -22,7 +22,7 @@ import {EventManager} from 'app/features/annotations/all'; import {convertValuesToHistogram, getSeriesValues} from './histogram'; /** @ngInject **/ -function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) { +function graphDirective(timeSrv, popoverSrv, contextSrv) { return { restrict: 'A', template: '', @@ -34,7 +34,6 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) { var data; var plot; var sortedSeries; - var rootScope = scope.$root; var panelWidth = 0; var eventManager = new EventManager(ctrl); var thresholdManager = new ThresholdManager(ctrl); @@ -143,27 +142,6 @@ function graphDirective($rootScope, timeSrv, popoverSrv, contextSrv) { } function drawHook(plot) { - // Update legend values - 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[panel.yaxes[series.yaxis - 1].format]; - - // decimal override - if (_.isNumber(panel.decimals)) { - series.updateLegendValues(formater, panel.decimals, null); - } else { - // auto decimals - // legend and tooltip gets one more decimal precision - // than graph legend ticks - var tickDecimals = (axis.tickDecimals || -1) + 1; - series.updateLegendValues(formater, tickDecimals, axis.scaledDecimals + 2); - } - - if (!rootScope.$$phase) { scope.$digest(); } - } - // add left axis labels if (panel.yaxes[0].label && panel.yaxes[0].show) { $("
").text(panel.yaxes[0].label).appendTo(elem); diff --git a/public/app/plugins/panel/graph/legend.ts b/public/app/plugins/panel/graph/legend.ts index 1da5aa17f2b..ffb3e390201 100644 --- a/public/app/plugins/panel/graph/legend.ts +++ b/public/app/plugins/panel/graph/legend.ts @@ -2,6 +2,7 @@ import angular from 'angular'; import _ from 'lodash'; import $ from 'jquery'; import PerfectScrollbar from 'perfect-scrollbar'; +import {updateLegendValues} from 'app/core/core'; var module = angular.module('grafana.directives'); @@ -26,6 +27,11 @@ module.directive('graphLegend', function(popoverSrv, $timeout) { ctrl.events.emit('legend-rendering-complete'); }); + function updateLegendDecimals() { + let graphHeight = ctrl.height - $container.height(); + updateLegendValues(data, panel, graphHeight); + } + function getSeriesIndexForElement(el) { return el.parents('[data-series-index]').data('series-index'); } @@ -170,6 +176,7 @@ module.directive('graphLegend', function(popoverSrv, $timeout) { html += '' + series.aliasEscaped + ''; if (panel.legend.values) { + updateLegendDecimals(); var avg = series.formatValue(series.stats.avg); var current = series.formatValue(series.stats.current); var min = series.formatValue(series.stats.min);