Browse Source

graph: move auto decimals calc to ticks.ts and use it for legend values format.

Alexander Zobnin 8 years ago
parent
commit
3cdf0dce56

+ 5 - 1
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
 };

+ 38 - 0
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;

+ 107 - 0
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};
+}

+ 1 - 23
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) {
           $("<div class='axisLabel left-yaxis-label flot-temp-elem'></div>").text(panel.yaxes[0].label).appendTo(elem);

+ 7 - 0
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 += '<a class="graph-legend-alias pointer" title="' + series.aliasEscaped + '">' + series.aliasEscaped + '</a>';
 
           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);