mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #16134 from grafana/auto-decimals-graph-panels
Auto decimals react singlestat panels
This commit is contained in:
commit
bfc54b6424
@ -3,10 +3,10 @@ import React, { PureComponent, CSSProperties, ReactNode } from 'react';
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
// Utils
|
||||
import { getColorFromHexRgbOrName, getThresholdForValue, DisplayValue } from '../../utils';
|
||||
import { getColorFromHexRgbOrName, getThresholdForValue } from '../../utils';
|
||||
|
||||
// Types
|
||||
import { Themeable, TimeSeriesValue, Threshold, VizOrientation } from '../../types';
|
||||
import { DisplayValue, Themeable, TimeSeriesValue, Threshold, VizOrientation } from '../../types';
|
||||
|
||||
const BAR_SIZE_RATIO = 0.8;
|
||||
|
||||
|
@ -1,10 +1,9 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import $ from 'jquery';
|
||||
|
||||
import { Threshold, GrafanaThemeType } from '../../types';
|
||||
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 {
|
||||
height: number;
|
||||
|
11
packages/grafana-ui/src/types/displayValue.ts
Normal file
11
packages/grafana-ui/src/types/displayValue.ts
Normal file
@ -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;
|
||||
}
|
@ -6,3 +6,4 @@ export * from './datasource';
|
||||
export * from './theme';
|
||||
export * from './threshold';
|
||||
export * from './input';
|
||||
export * from './displayValue';
|
||||
|
@ -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) {
|
||||
processors.forEach(processor => {
|
||||
@ -144,6 +144,20 @@ describe('Format value', () => {
|
||||
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', () => {
|
||||
const valueMappings: ValueMapping[] = [
|
||||
{ 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');
|
||||
});
|
||||
});
|
||||
|
||||
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 });
|
||||
});
|
||||
});
|
||||
|
@ -1,21 +1,21 @@
|
||||
import { ValueMapping, Threshold } from '../types';
|
||||
// Libraries
|
||||
import _ from 'lodash';
|
||||
import { getValueFormat, DecimalCount } from './valueFormats/valueFormats';
|
||||
import { getMappedValue } from './valueMappings';
|
||||
import { GrafanaTheme, GrafanaThemeType } from '../types';
|
||||
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
|
||||
}
|
||||
// Utils
|
||||
import { getValueFormat } from './valueFormats/valueFormats';
|
||||
import { getMappedValue } from './valueMappings';
|
||||
import { getColorFromHexRgbOrName } from './namedColorsPalette';
|
||||
|
||||
// Types
|
||||
import { Threshold, ValueMapping, DecimalInfo, DisplayValue, GrafanaTheme, GrafanaThemeType } from '../types';
|
||||
import { DecimalCount } from './valueFormats/valueFormats';
|
||||
|
||||
export type DisplayProcessor = (value: any) => DisplayValue;
|
||||
|
||||
export interface DisplayValueOptions {
|
||||
unit?: string;
|
||||
decimals?: DecimalCount;
|
||||
scaledDecimals?: DecimalCount;
|
||||
dateFormat?: string; // If set try to convert numbers to date
|
||||
|
||||
color?: string;
|
||||
@ -32,11 +32,10 @@ export interface DisplayValueOptions {
|
||||
theme?: GrafanaTheme; // Will pick 'dark' if not defined
|
||||
}
|
||||
|
||||
export type DisplayProcessor = (value: any) => DisplayValue;
|
||||
|
||||
export function getDisplayProcessor(options?: DisplayValueOptions): DisplayProcessor {
|
||||
if (options && !_.isEmpty(options)) {
|
||||
const formatFunc = getValueFormat(options.unit || 'none');
|
||||
|
||||
return (value: any) => {
|
||||
const { prefix, suffix, mappings, thresholds, theme } = options;
|
||||
let color = options.color;
|
||||
@ -47,12 +46,15 @@ export function getDisplayProcessor(options?: DisplayValueOptions): DisplayProce
|
||||
let shouldFormat = true;
|
||||
if (mappings && mappings.length > 0) {
|
||||
const mappedValue = getMappedValue(mappings, value);
|
||||
|
||||
if (mappedValue) {
|
||||
text = mappedValue.text;
|
||||
const v = toNumber(text);
|
||||
|
||||
if (!isNaN(v)) {
|
||||
numeric = v;
|
||||
}
|
||||
|
||||
shouldFormat = false;
|
||||
}
|
||||
}
|
||||
@ -67,7 +69,19 @@ export function getDisplayProcessor(options?: DisplayValueOptions): DisplayProce
|
||||
|
||||
if (!isNaN(numeric)) {
|
||||
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) {
|
||||
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
|
||||
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 };
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import $ from 'jquery';
|
||||
import 'vendor/flot/jquery.flot';
|
||||
import 'vendor/flot/jquery.flot.gauge';
|
||||
import 'app/features/panel/panellinks/link_srv';
|
||||
import { getDecimalsForValue } from '@grafana/ui';
|
||||
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import config from 'app/core/config';
|
||||
@ -190,7 +191,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
data.value = 0;
|
||||
data.valueRounded = 0;
|
||||
} else {
|
||||
const decimalInfo = this.getDecimalsForValue(data.value);
|
||||
const decimalInfo = getDecimalsForValue(data.value);
|
||||
const formatFunc = getValueFormat(this.panel.format);
|
||||
|
||||
data.valueFormatted = formatFunc(
|
||||
@ -243,47 +244,6 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
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) {
|
||||
data.flotpairs = [];
|
||||
|
||||
@ -319,15 +279,17 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
data.value = this.series[0].stats[this.panel.valueName];
|
||||
data.flotpairs = this.series[0].flotpairs;
|
||||
|
||||
const decimalInfo = this.getDecimalsForValue(data.value);
|
||||
let decimals = this.panel.decimals;
|
||||
let scaledDecimals = 0;
|
||||
|
||||
data.valueFormatted = formatFunc(
|
||||
data.value,
|
||||
decimalInfo.decimals,
|
||||
decimalInfo.scaledDecimals,
|
||||
this.dashboard.isTimezoneUtc()
|
||||
);
|
||||
data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
|
||||
if (!this.panel.decimals) {
|
||||
const decimalInfo = getDecimalsForValue(data.value);
|
||||
decimals = decimalInfo.decimals;
|
||||
scaledDecimals = decimalInfo.scaledDecimals;
|
||||
}
|
||||
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user