Browse Source

Merge pull request #16134 from grafana/auto-decimals-graph-panels

Auto decimals react singlestat panels
Torkel Ödegaard 6 years ago
parent
commit
bfc54b6424

+ 2 - 2
packages/grafana-ui/src/components/BarGauge/BarGauge.tsx

@@ -3,10 +3,10 @@ import React, { PureComponent, CSSProperties, ReactNode } from 'react';
 import tinycolor from 'tinycolor2';
 import tinycolor from 'tinycolor2';
 
 
 // Utils
 // Utils
-import { getColorFromHexRgbOrName, getThresholdForValue, DisplayValue } from '../../utils';
+import { getColorFromHexRgbOrName, getThresholdForValue } from '../../utils';
 
 
 // Types
 // Types
-import { Themeable, TimeSeriesValue, Threshold, VizOrientation } from '../../types';
+import { DisplayValue, Themeable, TimeSeriesValue, Threshold, VizOrientation } from '../../types';
 
 
 const BAR_SIZE_RATIO = 0.8;
 const BAR_SIZE_RATIO = 0.8;
 
 

+ 2 - 3
packages/grafana-ui/src/components/Gauge/Gauge.tsx

@@ -1,10 +1,9 @@
 import React, { PureComponent } from 'react';
 import React, { PureComponent } from 'react';
 import $ from 'jquery';
 import $ from 'jquery';
 
 
-import { Threshold, GrafanaThemeType } from '../../types';
 import { getColorFromHexRgbOrName } from '../../utils';
 import { getColorFromHexRgbOrName } from '../../utils';
-import { Themeable } from '../../index';
-import { DisplayValue } from '../../utils/displayValue';
+
+import { DisplayValue, Threshold, GrafanaThemeType, Themeable } from '../../types';
 
 
 export interface Props extends Themeable {
 export interface Props extends Themeable {
   height: number;
   height: number;

+ 11 - 0
packages/grafana-ui/src/types/displayValue.ts

@@ -0,0 +1,11 @@
+export interface DisplayValue {
+  text: string; // Show in the UI
+  numeric: number; // Use isNaN to check if it is a real number
+  color?: string; // color based on configs or Threshold
+  title?: string;
+}
+
+export interface DecimalInfo {
+  decimals: number;
+  scaledDecimals: number;
+}

+ 1 - 0
packages/grafana-ui/src/types/index.ts

@@ -6,3 +6,4 @@ export * from './datasource';
 export * from './theme';
 export * from './theme';
 export * from './threshold';
 export * from './threshold';
 export * from './input';
 export * from './input';
+export * from './displayValue';

+ 31 - 2
packages/grafana-ui/src/utils/displayValue.test.ts

@@ -1,5 +1,5 @@
-import { getDisplayProcessor, getColorFromThreshold, DisplayProcessor, DisplayValue } from './displayValue';
-import { MappingType, ValueMapping } from '../types/panel';
+import { getDisplayProcessor, getColorFromThreshold, DisplayProcessor, getDecimalsForValue } from './displayValue';
+import { DisplayValue, MappingType, ValueMapping } from '../types';
 
 
 function assertSame(input: any, processors: DisplayProcessor[], match: DisplayValue) {
 function assertSame(input: any, processors: DisplayProcessor[], match: DisplayValue) {
   processors.forEach(processor => {
   processors.forEach(processor => {
@@ -144,6 +144,20 @@ describe('Format value', () => {
     expect(result.text).toEqual('10.0');
     expect(result.text).toEqual('10.0');
   });
   });
 
 
+  it('should set auto decimals, 1 significant', () => {
+    const value = '1.23';
+    const instance = getDisplayProcessor({ decimals: null });
+
+    expect(instance(value).text).toEqual('1.2');
+  });
+
+  it('should set auto decimals, 2 significant', () => {
+    const value = '0.0245';
+    const instance = getDisplayProcessor({ decimals: null });
+
+    expect(instance(value).text).toEqual('0.02');
+  });
+
   it('should return mapped value if there are matching value mappings', () => {
   it('should return mapped value if there are matching value mappings', () => {
     const valueMappings: ValueMapping[] = [
     const valueMappings: ValueMapping[] = [
       { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
       { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
@@ -155,3 +169,18 @@ describe('Format value', () => {
     expect(instance(value).text).toEqual('1-20');
     expect(instance(value).text).toEqual('1-20');
   });
   });
 });
 });
+
+describe('getDecimalsForValue()', () => {
+  it('should calculate reasonable decimals precision for given value', () => {
+    expect(getDecimalsForValue(1.01)).toEqual({ decimals: 1, scaledDecimals: 4 });
+    expect(getDecimalsForValue(9.01)).toEqual({ decimals: 0, scaledDecimals: 2 });
+    expect(getDecimalsForValue(1.1)).toEqual({ decimals: 1, scaledDecimals: 4 });
+    expect(getDecimalsForValue(2)).toEqual({ decimals: 0, scaledDecimals: 2 });
+    expect(getDecimalsForValue(20)).toEqual({ decimals: 0, scaledDecimals: 1 });
+    expect(getDecimalsForValue(200)).toEqual({ decimals: 0, scaledDecimals: 0 });
+    expect(getDecimalsForValue(2000)).toEqual({ decimals: 0, scaledDecimals: 0 });
+    expect(getDecimalsForValue(20000)).toEqual({ decimals: 0, scaledDecimals: -2 });
+    expect(getDecimalsForValue(200000)).toEqual({ decimals: 0, scaledDecimals: -3 });
+    expect(getDecimalsForValue(200000000)).toEqual({ decimals: 0, scaledDecimals: -6 });
+  });
+});

+ 63 - 13
packages/grafana-ui/src/utils/displayValue.ts

@@ -1,21 +1,21 @@
-import { ValueMapping, Threshold } from '../types';
+// Libraries
 import _ from 'lodash';
 import _ from 'lodash';
-import { getValueFormat, DecimalCount } from './valueFormats/valueFormats';
+import moment from 'moment';
+
+// Utils
+import { getValueFormat } from './valueFormats/valueFormats';
 import { getMappedValue } from './valueMappings';
 import { getMappedValue } from './valueMappings';
-import { GrafanaTheme, GrafanaThemeType } from '../types';
 import { getColorFromHexRgbOrName } from './namedColorsPalette';
 import { getColorFromHexRgbOrName } from './namedColorsPalette';
-import moment from 'moment';
 
 
-export interface DisplayValue {
-  text: string; // Show in the UI
-  numeric: number; // Use isNaN to check if it is a real number
-  color?: string; // color based on configs or Threshold
-}
+// Types
+import { Threshold, ValueMapping, DecimalInfo, DisplayValue, GrafanaTheme, GrafanaThemeType } from '../types';
+import { DecimalCount } from './valueFormats/valueFormats';
+
+export type DisplayProcessor = (value: any) => DisplayValue;
 
 
 export interface DisplayValueOptions {
 export interface DisplayValueOptions {
   unit?: string;
   unit?: string;
   decimals?: DecimalCount;
   decimals?: DecimalCount;
-  scaledDecimals?: DecimalCount;
   dateFormat?: string; // If set try to convert numbers to date
   dateFormat?: string; // If set try to convert numbers to date
 
 
   color?: string;
   color?: string;
@@ -32,11 +32,10 @@ export interface DisplayValueOptions {
   theme?: GrafanaTheme; // Will pick 'dark' if not defined
   theme?: GrafanaTheme; // Will pick 'dark' if not defined
 }
 }
 
 
-export type DisplayProcessor = (value: any) => DisplayValue;
-
 export function getDisplayProcessor(options?: DisplayValueOptions): DisplayProcessor {
 export function getDisplayProcessor(options?: DisplayValueOptions): DisplayProcessor {
   if (options && !_.isEmpty(options)) {
   if (options && !_.isEmpty(options)) {
     const formatFunc = getValueFormat(options.unit || 'none');
     const formatFunc = getValueFormat(options.unit || 'none');
+
     return (value: any) => {
     return (value: any) => {
       const { prefix, suffix, mappings, thresholds, theme } = options;
       const { prefix, suffix, mappings, thresholds, theme } = options;
       let color = options.color;
       let color = options.color;
@@ -47,12 +46,15 @@ export function getDisplayProcessor(options?: DisplayValueOptions): DisplayProce
       let shouldFormat = true;
       let shouldFormat = true;
       if (mappings && mappings.length > 0) {
       if (mappings && mappings.length > 0) {
         const mappedValue = getMappedValue(mappings, value);
         const mappedValue = getMappedValue(mappings, value);
+
         if (mappedValue) {
         if (mappedValue) {
           text = mappedValue.text;
           text = mappedValue.text;
           const v = toNumber(text);
           const v = toNumber(text);
+
           if (!isNaN(v)) {
           if (!isNaN(v)) {
             numeric = v;
             numeric = v;
           }
           }
+
           shouldFormat = false;
           shouldFormat = false;
         }
         }
       }
       }
@@ -67,7 +69,19 @@ export function getDisplayProcessor(options?: DisplayValueOptions): DisplayProce
 
 
       if (!isNaN(numeric)) {
       if (!isNaN(numeric)) {
         if (shouldFormat && !_.isBoolean(value)) {
         if (shouldFormat && !_.isBoolean(value)) {
-          text = formatFunc(numeric, options.decimals, options.scaledDecimals, options.isUtc);
+          let decimals;
+          let scaledDecimals = 0;
+
+          if (!options.decimals) {
+            const decimalInfo = getDecimalsForValue(value);
+
+            decimals = decimalInfo.decimals;
+            scaledDecimals = decimalInfo.scaledDecimals;
+          } else {
+            decimals = options.decimals;
+          }
+
+          text = formatFunc(numeric, decimals, scaledDecimals, options.isUtc);
         }
         }
         if (thresholds && thresholds.length > 0) {
         if (thresholds && thresholds.length > 0) {
           color = getColorFromThreshold(numeric, thresholds, theme);
           color = getColorFromThreshold(numeric, thresholds, theme);
@@ -143,3 +157,39 @@ export function getColorFromThreshold(value: number, thresholds: Threshold[], th
   // Use the first threshold as the default color
   // Use the first threshold as the default color
   return getColorFromHexRgbOrName(thresholds[0].color, themeType);
   return getColorFromHexRgbOrName(thresholds[0].color, themeType);
 }
 }
+
+export function getDecimalsForValue(value: number): DecimalInfo {
+  const delta = value / 2;
+  let dec = -Math.floor(Math.log(delta) / Math.LN10);
+
+  const magn = Math.pow(10, -dec);
+  const norm = delta / magn; // norm is between 1.0 and 10.0
+  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) {
+      size = 2.5;
+      ++dec;
+    }
+  } else if (norm < 7.5) {
+    size = 5;
+  } else {
+    size = 10;
+  }
+
+  size *= magn;
+
+  // reduce starting decimals if not needed
+  if (Math.floor(value) === value) {
+    dec = 0;
+  }
+
+  const decimals = Math.max(0, dec);
+  const scaledDecimals = decimals - Math.floor(Math.log(size) / Math.LN10) + 2;
+
+  return { decimals, scaledDecimals };
+}

+ 12 - 50
public/app/plugins/panel/singlestat/module.ts

@@ -3,6 +3,7 @@ import $ from 'jquery';
 import 'vendor/flot/jquery.flot';
 import 'vendor/flot/jquery.flot';
 import 'vendor/flot/jquery.flot.gauge';
 import 'vendor/flot/jquery.flot.gauge';
 import 'app/features/panel/panellinks/link_srv';
 import 'app/features/panel/panellinks/link_srv';
+import { getDecimalsForValue } from '@grafana/ui';
 
 
 import kbn from 'app/core/utils/kbn';
 import kbn from 'app/core/utils/kbn';
 import config from 'app/core/config';
 import config from 'app/core/config';
@@ -190,7 +191,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
       data.value = 0;
       data.value = 0;
       data.valueRounded = 0;
       data.valueRounded = 0;
     } else {
     } else {
-      const decimalInfo = this.getDecimalsForValue(data.value);
+      const decimalInfo = getDecimalsForValue(data.value);
       const formatFunc = getValueFormat(this.panel.format);
       const formatFunc = getValueFormat(this.panel.format);
 
 
       data.valueFormatted = formatFunc(
       data.valueFormatted = formatFunc(
@@ -243,47 +244,6 @@ class SingleStatCtrl extends MetricsPanelCtrl {
     this.render();
     this.render();
   }
   }
 
 
-  getDecimalsForValue(value) {
-    if (_.isNumber(this.panel.decimals)) {
-      return { decimals: this.panel.decimals, scaledDecimals: null };
-    }
-
-    const delta = value / 2;
-    let dec = -Math.floor(Math.log(delta) / Math.LN10);
-
-    const magn = Math.pow(10, -dec);
-    const norm = delta / magn; // norm is between 1.0 and 10.0
-    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) {
-        size = 2.5;
-        ++dec;
-      }
-    } else if (norm < 7.5) {
-      size = 5;
-    } else {
-      size = 10;
-    }
-
-    size *= magn;
-
-    // reduce starting decimals if not needed
-    if (Math.floor(value) === value) {
-      dec = 0;
-    }
-
-    const result: any = {};
-    result.decimals = Math.max(0, dec);
-    result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;
-
-    return result;
-  }
-
   setValues(data) {
   setValues(data) {
     data.flotpairs = [];
     data.flotpairs = [];
 
 
@@ -319,15 +279,17 @@ class SingleStatCtrl extends MetricsPanelCtrl {
         data.value = this.series[0].stats[this.panel.valueName];
         data.value = this.series[0].stats[this.panel.valueName];
         data.flotpairs = this.series[0].flotpairs;
         data.flotpairs = this.series[0].flotpairs;
 
 
-        const decimalInfo = this.getDecimalsForValue(data.value);
+        let decimals = this.panel.decimals;
+        let scaledDecimals = 0;
+
+        if (!this.panel.decimals) {
+          const decimalInfo = getDecimalsForValue(data.value);
+          decimals = decimalInfo.decimals;
+          scaledDecimals = decimalInfo.scaledDecimals;
+        }
 
 
-        data.valueFormatted = formatFunc(
-          data.value,
-          decimalInfo.decimals,
-          decimalInfo.scaledDecimals,
-          this.dashboard.isTimezoneUtc()
-        );
-        data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
+        data.valueFormatted = formatFunc(data.value, decimals, scaledDecimals, this.dashboard.isTimezoneUtc());
+        data.valueRounded = kbn.roundValue(data.value, decimals);
       }
       }
 
 
       // Add $__name variable for using in prefix or postfix
       // Add $__name variable for using in prefix or postfix