Number formatting: only 0-trim decimals in y axis ticks. omit currency, locale units. (#57386)

This commit is contained in:
Leon Sorokin 2022-10-21 22:35:29 -05:00 committed by GitHub
parent 45707ccf99
commit 0640eeef0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 68 additions and 16 deletions

View File

@ -305,7 +305,7 @@ describe('Format value', () => {
const value = 1200;
const instance = getDisplayProcessorFromConfig({ decimals: null, unit: 'short' });
const disp = instance(value);
expect(disp.text).toEqual('1.2');
expect(disp.text).toEqual('1.20');
expect(disp.suffix).toEqual(' K');
});
@ -329,7 +329,7 @@ describe('Format value', () => {
const value = 1500000;
const instance = getDisplayProcessorFromConfig({ decimals: null, unit: 'short' });
const disp = instance(value);
expect(disp.text).toEqual('1.5');
expect(disp.text).toEqual('1.50');
expect(disp.suffix).toEqual(' Mil');
});
@ -376,6 +376,53 @@ describe('Format value', () => {
expect(disp.text).toEqual(value);
});
});
describe('number formatting for y axis ticks (dynamic decimals with trailing 0s trimming)', () => {
// all these tests have non-null adjacentDecimals != null, which we only do durink axis tick formatting
it('should trim trailing zeros after decimal from fractional seconds when formatted as millis with adjacentDecimals=2', () => {
const processor = getDisplayProcessorFromConfig({ unit: 's' }, FieldType.number);
expect(processor(0.06, 2).text).toEqual('60');
});
it('should trim trailing zeros after decimal from number', () => {
const processor = getDisplayProcessorFromConfig({}, FieldType.number);
expect(processor(1.2, 2).text).toEqual('1.2');
// dynamic!
expect(processor(13.50008, 3).text).toEqual('13.5');
});
it('should not attempt to trim zeros from currency*', () => {
const processor = getDisplayProcessorFromConfig({ unit: 'currencyUSD' }, FieldType.number);
expect(processor(1.2, 2).text).toEqual('1.20');
});
it('should not attempt to trim zeros from bool', () => {
const processor = getDisplayProcessorFromConfig({ unit: 'bool' }, FieldType.number);
expect(processor(1, 2).text).toEqual('True');
});
it('should not attempt to trim zeros from time', () => {
const processor = getDisplayProcessorFromConfig({}, FieldType.time);
expect(processor(1666402869517, 2).text).toEqual('2022-10-21 20:41:09');
});
it('should not attempt to trim zeros from dateTimeAsUS', () => {
const processor = getDisplayProcessorFromConfig({ unit: 'dateTimeAsUS' }, FieldType.number);
expect(processor(1666402869517, 2).text).toEqual('10/21/2022 8:41:09 pm');
});
it('should not attempt to trim zeros from locale', () => {
const processor = getDisplayProcessorFromConfig({ unit: 'locale' }, FieldType.number);
expect(processor(3500000, 2).text).toEqual('3,500,000');
});
it('should not attempt to trim zeros when explicit decimals: 5', () => {
const processor = getDisplayProcessorFromConfig({ decimals: 5 }, FieldType.number);
expect(processor(35, 2).text).toEqual('35.00000');
});
});
});
describe('Date display options', () => {

View File

@ -10,7 +10,7 @@ import { Field, FieldType } from '../types/dataFrame';
import { DecimalCount, DisplayProcessor, DisplayValue } from '../types/displayValue';
import { anyToNumber } from '../utils/anyToNumber';
import { getValueMappingResult } from '../utils/valueMappings';
import { getValueFormat, isBooleanUnit } from '../valueFormats/valueFormats';
import { FormattedValue, getValueFormat, isBooleanUnit } from '../valueFormats/valueFormats';
import { getScaleCalculator } from './scale';
@ -72,16 +72,17 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
unit = 'string';
}
const hasCurrencyUnit = unit?.startsWith('currency');
const hasBoolUnit = unit === 'bool';
const isNumType = field.type === FieldType.number;
const isLocaleFormat = unit === 'locale';
const shouldTrimTrailingDecimalZeros =
!hasDateUnit && !hasBoolUnit && !isLocaleFormat && isNumType && config.decimals == null;
const canTrimTrailingDecimalZeros =
!hasDateUnit && !hasCurrencyUnit && !hasBoolUnit && !isLocaleFormat && isNumType && config.decimals == null;
const formatFunc = getValueFormat(unit || 'none');
const scaleFunc = getScaleCalculator(field, options.theme);
return (value: any, decimals?: DecimalCount) => {
return (value: any, adjacentDecimals?: DecimalCount) => {
const { mappings } = config;
const isStringUnit = unit === 'string';
@ -117,14 +118,18 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
if (!Number.isNaN(numeric)) {
if (text == null && !isBoolean(value)) {
const v = formatFunc(numeric, decimals ?? config.decimals, null, options.timeZone, showMs);
let v: FormattedValue;
// if no explicit decimals config, we strip trailing zeros e.g. 60.00 -> 60
// this is needed because we may have determined the minimum required `decimals` for y tick increments based on
// e.g. 'seconds' field unit (0.15s, 0.20s, 0.25s), but then formatFunc decided to return milli or nanos (150, 200, 250)
// so we end up with excess precision: 150.00, 200.00, 250.00
if (shouldTrimTrailingDecimalZeros) {
if (canTrimTrailingDecimalZeros && adjacentDecimals != null) {
v = formatFunc(numeric, adjacentDecimals, null, options.timeZone, showMs);
// if no explicit decimals config, we strip trailing zeros e.g. 60.00 -> 60
// this is needed because we may have determined the minimum determined `adjacentDecimals` for y tick increments based on
// e.g. 'seconds' field unit (0.15s, 0.20s, 0.25s), but then formatFunc decided to return milli or nanos (150, 200, 250)
// so we end up with excess precision: 150.00, 200.00, 250.00
v.text = +v.text + '';
} else {
v = formatFunc(numeric, config.decimals, null, options.timeZone, showMs);
}
text = v.text;

View File

@ -279,7 +279,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
label: customConfig.axisLabel,
size: customConfig.axisWidth,
placement: customConfig.axisPlacement ?? AxisPlacement.Auto,
formatValue: (v, decimals) => formattedValueToString(fmt(v, config.decimals ?? decimals)),
formatValue: (v, decimals) => formattedValueToString(fmt(v, decimals)),
theme,
grid: { show: customConfig.axisGridShow },
decimals: field.config.decimals,

View File

@ -253,7 +253,7 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<BarChartOptionsEX> = ({
label: customConfig.axisLabel,
size: customConfig.axisWidth,
placement,
formatValue: (v, decimals) => formattedValueToString(field.display!(v, field.config.decimals ?? decimals)),
formatValue: (v, decimals) => formattedValueToString(field.display!(v, decimals)),
theme,
grid: { show: customConfig.axisGridShow },
});

View File

@ -408,7 +408,7 @@ export function prepConfig(opts: PrepConfigOpts) {
size: yAxisConfig.axisWidth || null,
label: yAxisConfig.axisLabel,
theme: theme,
formatValue: (v, decimals) => formattedValueToString(dispY(v, yField.config.decimals ?? decimals)),
formatValue: (v, decimals) => formattedValueToString(dispY(v, decimals)),
splits: isOrdianalY
? (self: uPlot) => {
const meta = readHeatmapRowsCustomMeta(dataRef.current?.heatmap);

View File

@ -153,7 +153,7 @@ const prepConfig = (frame: DataFrame, theme: GrafanaTheme2) => {
scaleKey: 'y',
isTime: false,
placement: AxisPlacement.Left,
formatValue: (v, decimals) => formattedValueToString(dispY!(v, countField.config.decimals ?? decimals)),
formatValue: (v, decimals) => formattedValueToString(dispY!(v, decimals)),
//splits: config.xSplits,
//values: config.xValues,
//grid: false,