mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
ValueFormats: dynamically create units (#20763)
* update fixed * update fixed * update fixed * don't change any tests * add mising space * Custom unit formats * return a string for kbn * return a string for kbn * return a string for kbn * Simplify unit tests * More units * fix more tests * fix more tests * fix more tests * format values * format values * TimeSeries to string * more kbn tests * use the formatted value * BarGauge: Fixed font size calculations * support prefix * add si support * avoid npe * BarGauge/BigValue: value formatting * fix some tests * fix tests * remove displayDateFormat * another unicode char * Graph: Use react unit picker * Updated unit picker * Fixed build errors * more formatting * graph2 tooltip formatting * optional chaining
This commit is contained in:
parent
3289ee8b77
commit
d7c76dacad
@ -151,7 +151,9 @@ describe('Format value', () => {
|
||||
it('should use override decimals', () => {
|
||||
const value = 100030303;
|
||||
const instance = getDisplayProcessor({ config: { decimals: 2, unit: 'bytes' } });
|
||||
expect(instance(value).text).toEqual('95.40 MiB');
|
||||
const disp = instance(value);
|
||||
expect(disp.text).toEqual('95.40');
|
||||
expect(disp.suffix).toEqual(' MiB');
|
||||
});
|
||||
|
||||
it('should return mapped value if there are matching value mappings', () => {
|
||||
@ -172,25 +174,33 @@ describe('Format value', () => {
|
||||
it('with value 1000 and unit short', () => {
|
||||
const value = 1000;
|
||||
const instance = getDisplayProcessor({ config: { decimals: null, unit: 'short' } });
|
||||
expect(instance(value).text).toEqual('1.000 K');
|
||||
const disp = instance(value);
|
||||
expect(disp.text).toEqual('1.000');
|
||||
expect(disp.suffix).toEqual(' K');
|
||||
});
|
||||
|
||||
it('with value 1200 and unit short', () => {
|
||||
const value = 1200;
|
||||
const instance = getDisplayProcessor({ config: { decimals: null, unit: 'short' } });
|
||||
expect(instance(value).text).toEqual('1.200 K');
|
||||
const disp = instance(value);
|
||||
expect(disp.text).toEqual('1.200');
|
||||
expect(disp.suffix).toEqual(' K');
|
||||
});
|
||||
|
||||
it('with value 1250 and unit short', () => {
|
||||
const value = 1250;
|
||||
const instance = getDisplayProcessor({ config: { decimals: null, unit: 'short' } });
|
||||
expect(instance(value).text).toEqual('1.250 K');
|
||||
const disp = instance(value);
|
||||
expect(disp.text).toEqual('1.250');
|
||||
expect(disp.suffix).toEqual(' K');
|
||||
});
|
||||
|
||||
it('with value 10000000 and unit short', () => {
|
||||
const value = 1000000;
|
||||
const instance = getDisplayProcessor({ config: { decimals: null, unit: 'short' } });
|
||||
expect(instance(value).text).toEqual('1.000 Mil');
|
||||
const disp = instance(value);
|
||||
expect(disp.text).toEqual('1.000');
|
||||
expect(disp.suffix).toEqual(' Mil');
|
||||
});
|
||||
});
|
||||
|
||||
@ -222,7 +232,7 @@ describe('Date display options', () => {
|
||||
type: FieldType.time,
|
||||
isUtc: true,
|
||||
config: {
|
||||
dateDisplayFormat: 'YYYY',
|
||||
unit: 'time:YYYY',
|
||||
},
|
||||
});
|
||||
expect(processor(0).text).toEqual('1970');
|
||||
|
@ -11,7 +11,7 @@ import { DisplayProcessor, DisplayValue, DecimalCount, DecimalInfo } from '../ty
|
||||
import { getValueFormat } from '../valueFormats/valueFormats';
|
||||
import { getMappedValue } from '../utils/valueMappings';
|
||||
import { Threshold } from '../types/threshold';
|
||||
import { DateTime, DEFAULT_DATE_TIME_FORMAT, isDateTime, dateTime, toUtc } from '../datetime';
|
||||
import { DEFAULT_DATE_TIME_FORMAT } from '../datetime';
|
||||
import { KeyValue } from '../types';
|
||||
|
||||
interface DisplayProcessorOptions {
|
||||
@ -37,22 +37,10 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
||||
if (options.type === FieldType.time) {
|
||||
if (field.unit && timeFormats[field.unit]) {
|
||||
// Currently selected unit is valid for time fields
|
||||
} else if (field.unit && field.unit.startsWith('time:')) {
|
||||
// Also OK
|
||||
} else {
|
||||
const dateFormat = field.dateDisplayFormat || DEFAULT_DATE_TIME_FORMAT;
|
||||
|
||||
// UTC or browser based timezone
|
||||
let fmt = (date: DateTime) => date.format(dateFormat);
|
||||
if (options.isUtc) {
|
||||
fmt = (date: DateTime) => toUtc(date).format(dateFormat);
|
||||
}
|
||||
|
||||
return (value: any) => {
|
||||
const date: DateTime = isDateTime(value) ? value : dateTime(value);
|
||||
return {
|
||||
numeric: isNaN(value) ? date.valueOf() : value,
|
||||
text: fmt(date),
|
||||
};
|
||||
};
|
||||
field.unit = `time:${DEFAULT_DATE_TIME_FORMAT}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +53,8 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
||||
|
||||
let text = _.toString(value);
|
||||
let numeric = toNumber(value);
|
||||
let prefix: string | undefined = undefined;
|
||||
let suffix: string | undefined = undefined;
|
||||
|
||||
let shouldFormat = true;
|
||||
if (mappings && mappings.length > 0) {
|
||||
@ -85,7 +75,10 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
||||
if (!isNaN(numeric)) {
|
||||
if (shouldFormat && !_.isBoolean(value)) {
|
||||
const { decimals, scaledDecimals } = getDecimalsForValue(value, field.decimals);
|
||||
text = formatFunc(numeric, decimals, scaledDecimals, options.isUtc);
|
||||
const v = formatFunc(numeric, decimals, scaledDecimals, options.isUtc);
|
||||
text = v.text;
|
||||
suffix = v.suffix;
|
||||
prefix = v.prefix;
|
||||
|
||||
// Check if the formatted text mapped to a different value
|
||||
if (mappings && mappings.length > 0) {
|
||||
@ -107,7 +100,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
||||
text = ''; // No data?
|
||||
}
|
||||
}
|
||||
return { text, numeric, color };
|
||||
return { text, numeric, color, prefix, suffix };
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -286,8 +286,12 @@ export function getDisplayValueAlignmentFactors(values: FieldDisplay[]): Display
|
||||
text: '',
|
||||
};
|
||||
|
||||
let prefixLength = 0;
|
||||
let suffixLength = 0;
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const v = values[i].display;
|
||||
|
||||
if (v.text && v.text.length > info.text.length) {
|
||||
info.text = v.text;
|
||||
}
|
||||
@ -295,6 +299,16 @@ export function getDisplayValueAlignmentFactors(values: FieldDisplay[]): Display
|
||||
if (v.title && v.title.length > info.title.length) {
|
||||
info.title = v.title;
|
||||
}
|
||||
|
||||
if (v.prefix && v.prefix.length > prefixLength) {
|
||||
info.prefix = v.prefix;
|
||||
prefixLength = v.prefix.length;
|
||||
}
|
||||
|
||||
if (v.suffix && v.suffix.length > suffixLength) {
|
||||
info.suffix = v.suffix;
|
||||
suffixLength = v.suffix.length;
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
@ -46,9 +46,6 @@ export interface FieldConfig {
|
||||
|
||||
// Visual options
|
||||
color?: string;
|
||||
|
||||
// Used for time field formatting
|
||||
dateDisplayFormat?: string;
|
||||
}
|
||||
|
||||
export interface Field<T = any, V = Vector<T>> {
|
||||
|
@ -1,20 +1,19 @@
|
||||
import { FormattedValue } from '../valueFormats';
|
||||
|
||||
export type DisplayProcessor = (value: any) => DisplayValue;
|
||||
|
||||
export interface DisplayValue {
|
||||
text: string; // Show in the UI
|
||||
export interface DisplayValue extends FormattedValue {
|
||||
numeric: number; // Use isNaN to check if it is a real number
|
||||
color?: string; // color based on configs or Threshold
|
||||
title?: string;
|
||||
fontSize?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* These represents the displau value with the longest title and text.
|
||||
* These represents the display value with the longest title and text.
|
||||
* Used to align widths and heights when displaying multiple DisplayValues
|
||||
*/
|
||||
export interface DisplayValueAlignmentFactors {
|
||||
export interface DisplayValueAlignmentFactors extends FormattedValue {
|
||||
title: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export type DecimalCount = number | null | undefined;
|
||||
|
@ -1,40 +1,41 @@
|
||||
import { toHex, toHex0x } from './arithmeticFormatters';
|
||||
import { formattedValueToString } from './valueFormats';
|
||||
|
||||
describe('hex', () => {
|
||||
it('positive integer', () => {
|
||||
const str = toHex(100, 0);
|
||||
expect(str).toBe('64');
|
||||
expect(formattedValueToString(str)).toBe('64');
|
||||
});
|
||||
it('negative integer', () => {
|
||||
const str = toHex(-100, 0);
|
||||
expect(str).toBe('-64');
|
||||
expect(formattedValueToString(str)).toBe('-64');
|
||||
});
|
||||
it('positive float', () => {
|
||||
const str = toHex(50.52, 1);
|
||||
expect(str).toBe('32.8');
|
||||
expect(formattedValueToString(str)).toBe('32.8');
|
||||
});
|
||||
it('negative float', () => {
|
||||
const str = toHex(-50.333, 2);
|
||||
expect(str).toBe('-32.547AE147AE14');
|
||||
expect(formattedValueToString(str)).toBe('-32.547AE147AE14');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hex 0x', () => {
|
||||
it('positive integeter', () => {
|
||||
const str = toHex0x(7999, 0);
|
||||
expect(str).toBe('0x1F3F');
|
||||
expect(formattedValueToString(str)).toBe('0x1F3F');
|
||||
});
|
||||
it('negative integer', () => {
|
||||
const str = toHex0x(-584, 0);
|
||||
expect(str).toBe('-0x248');
|
||||
expect(formattedValueToString(str)).toBe('-0x248');
|
||||
});
|
||||
|
||||
it('positive float', () => {
|
||||
const str = toHex0x(74.443, 3);
|
||||
expect(str).toBe('0x4A.716872B020C4');
|
||||
expect(formattedValueToString(str)).toBe('0x4A.716872B020C4');
|
||||
});
|
||||
it('negative float', () => {
|
||||
const str = toHex0x(-65.458, 1);
|
||||
expect(str).toBe('-0x41.8');
|
||||
expect(formattedValueToString(str)).toBe('-0x41.8');
|
||||
});
|
||||
});
|
||||
|
@ -1,43 +1,47 @@
|
||||
import { toFixed } from './valueFormats';
|
||||
import { toFixed, FormattedValue } from './valueFormats';
|
||||
import { DecimalCount } from '../types/displayValue';
|
||||
|
||||
export function toPercent(size: number, decimals: DecimalCount) {
|
||||
export function toPercent(size: number, decimals: DecimalCount): FormattedValue {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
return toFixed(size, decimals) + '%';
|
||||
return { text: toFixed(size, decimals), suffix: '%' };
|
||||
}
|
||||
|
||||
export function toPercentUnit(size: number, decimals: DecimalCount) {
|
||||
export function toPercentUnit(size: number, decimals: DecimalCount): FormattedValue {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
return toFixed(100 * size, decimals) + '%';
|
||||
return { text: toFixed(100 * size, decimals), suffix: '%' };
|
||||
}
|
||||
|
||||
export function toHex0x(value: number, decimals: DecimalCount) {
|
||||
export function toHex0x(value: number, decimals: DecimalCount): FormattedValue {
|
||||
if (value == null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
const hexString = toHex(value, decimals);
|
||||
if (hexString.substring(0, 1) === '-') {
|
||||
return '-0x' + hexString.substring(1);
|
||||
const asHex = toHex(value, decimals);
|
||||
if (asHex.text.substring(0, 1) === '-') {
|
||||
asHex.text = '-0x' + asHex.text.substring(1);
|
||||
} else {
|
||||
asHex.text = '0x' + asHex.text;
|
||||
}
|
||||
return '0x' + hexString;
|
||||
return asHex;
|
||||
}
|
||||
|
||||
export function toHex(value: number, decimals: DecimalCount) {
|
||||
export function toHex(value: number, decimals: DecimalCount): FormattedValue {
|
||||
if (value == null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
return parseFloat(toFixed(value, decimals))
|
||||
.toString(16)
|
||||
.toUpperCase();
|
||||
return {
|
||||
text: parseFloat(toFixed(value, decimals))
|
||||
.toString(16)
|
||||
.toUpperCase(),
|
||||
};
|
||||
}
|
||||
|
||||
export function sci(value: number, decimals: DecimalCount) {
|
||||
export function sci(value: number, decimals: DecimalCount): FormattedValue {
|
||||
if (value == null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
return value.toExponential(decimals as number);
|
||||
return { text: value.toExponential(decimals as number) };
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { locale, scaledUnits, simpleCountUnit, toFixed, toFixedUnit, ValueFormatCategory } from './valueFormats';
|
||||
import { locale, scaledUnits, simpleCountUnit, toFixedUnit, ValueFormatCategory } from './valueFormats';
|
||||
import {
|
||||
dateTimeAsIso,
|
||||
dateTimeAsUS,
|
||||
@ -24,7 +24,7 @@ export const getCategories = (): ValueFormatCategory[] => [
|
||||
{
|
||||
name: 'Misc',
|
||||
formats: [
|
||||
{ name: 'none', id: 'none', fn: toFixed },
|
||||
{ name: 'none', id: 'none', fn: toFixedUnit('') },
|
||||
{
|
||||
name: 'short',
|
||||
id: 'short',
|
||||
@ -107,10 +107,10 @@ export const getCategories = (): ValueFormatCategory[] => [
|
||||
{ name: 'Rubles (₽)', id: 'currencyRUB', fn: currency('₽') },
|
||||
{ name: 'Hryvnias (₴)', id: 'currencyUAH', fn: currency('₴') },
|
||||
{ name: 'Real (R$)', id: 'currencyBRL', fn: currency('R$') },
|
||||
{ name: 'Danish Krone (kr)', id: 'currencyDKK', fn: currency('kr') },
|
||||
{ name: 'Icelandic Króna (kr)', id: 'currencyISK', fn: currency('kr') },
|
||||
{ name: 'Norwegian Krone (kr)', id: 'currencyNOK', fn: currency('kr') },
|
||||
{ name: 'Swedish Krona (kr)', id: 'currencySEK', fn: currency('kr') },
|
||||
{ name: 'Danish Krone (kr)', id: 'currencyDKK', fn: currency('kr', true) },
|
||||
{ name: 'Icelandic Króna (kr)', id: 'currencyISK', fn: currency('kr', true) },
|
||||
{ name: 'Norwegian Krone (kr)', id: 'currencyNOK', fn: currency('kr', true) },
|
||||
{ name: 'Swedish Krona (kr)', id: 'currencySEK', fn: currency('kr', true) },
|
||||
{ name: 'Czech koruna (czk)', id: 'currencyCZK', fn: currency('czk') },
|
||||
{ name: 'Swiss franc (CHF)', id: 'currencyCHF', fn: currency('CHF') },
|
||||
{ name: 'Polish Złoty (PLN)', id: 'currencyPLN', fn: currency('PLN') },
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
toDurationInSeconds,
|
||||
toDurationInHoursMinutesSeconds,
|
||||
} from './dateTimeFormatters';
|
||||
import { formattedValueToString } from './valueFormats';
|
||||
import { toUtc, dateTime } from '../datetime/moment_wrapper';
|
||||
|
||||
describe('date time formats', () => {
|
||||
@ -19,226 +20,230 @@ describe('date time formats', () => {
|
||||
it('should format as iso date', () => {
|
||||
const expected = browserTime.format('YYYY-MM-DD HH:mm:ss');
|
||||
const actual = dateTimeAsIso(epoch, 0, 0, false);
|
||||
expect(actual).toBe(expected);
|
||||
expect(actual.text).toBe(expected);
|
||||
});
|
||||
|
||||
it('should format as iso date (in UTC)', () => {
|
||||
const expected = utcTime.format('YYYY-MM-DD HH:mm:ss');
|
||||
const actual = dateTimeAsIso(epoch, 0, 0, true);
|
||||
expect(actual).toBe(expected);
|
||||
expect(actual.text).toBe(expected);
|
||||
});
|
||||
|
||||
it('should format as iso date and skip date when today', () => {
|
||||
const now = dateTime();
|
||||
const expected = now.format('HH:mm:ss');
|
||||
const actual = dateTimeAsIso(now.valueOf(), 0, 0, false);
|
||||
expect(actual).toBe(expected);
|
||||
expect(actual.text).toBe(expected);
|
||||
});
|
||||
|
||||
it('should format as iso date (in UTC) and skip date when today', () => {
|
||||
const now = toUtc();
|
||||
const expected = now.format('HH:mm:ss');
|
||||
const actual = dateTimeAsIso(now.valueOf(), 0, 0, true);
|
||||
expect(actual).toBe(expected);
|
||||
expect(actual.text).toBe(expected);
|
||||
});
|
||||
|
||||
it('should format as US date', () => {
|
||||
const expected = browserTime.format('MM/DD/YYYY h:mm:ss a');
|
||||
const actual = dateTimeAsUS(epoch, 0, 0, false);
|
||||
expect(actual).toBe(expected);
|
||||
expect(actual.text).toBe(expected);
|
||||
});
|
||||
|
||||
it('should format as US date (in UTC)', () => {
|
||||
const expected = utcTime.format('MM/DD/YYYY h:mm:ss a');
|
||||
const actual = dateTimeAsUS(epoch, 0, 0, true);
|
||||
expect(actual).toBe(expected);
|
||||
expect(actual.text).toBe(expected);
|
||||
});
|
||||
|
||||
it('should format as US date and skip date when today', () => {
|
||||
const now = dateTime();
|
||||
const expected = now.format('h:mm:ss a');
|
||||
const actual = dateTimeAsUS(now.valueOf(), 0, 0, false);
|
||||
expect(actual).toBe(expected);
|
||||
expect(actual.text).toBe(expected);
|
||||
});
|
||||
|
||||
it('should format as US date (in UTC) and skip date when today', () => {
|
||||
const now = toUtc();
|
||||
const expected = now.format('h:mm:ss a');
|
||||
const actual = dateTimeAsUS(now.valueOf(), 0, 0, true);
|
||||
expect(actual).toBe(expected);
|
||||
expect(actual.text).toBe(expected);
|
||||
});
|
||||
|
||||
it('should format as from now with days', () => {
|
||||
const daysAgo = dateTime().add(-7, 'd');
|
||||
const expected = '7 days ago';
|
||||
const actual = dateTimeFromNow(daysAgo.valueOf(), 0, 0, false);
|
||||
expect(actual).toBe(expected);
|
||||
expect(actual.text).toBe(expected);
|
||||
});
|
||||
|
||||
it('should format as from now with days (in UTC)', () => {
|
||||
const daysAgo = toUtc().add(-7, 'd');
|
||||
const expected = '7 days ago';
|
||||
const actual = dateTimeFromNow(daysAgo.valueOf(), 0, 0, true);
|
||||
expect(actual).toBe(expected);
|
||||
expect(actual.text).toBe(expected);
|
||||
});
|
||||
|
||||
it('should format as from now with minutes', () => {
|
||||
const daysAgo = dateTime().add(-2, 'm');
|
||||
const expected = '2 minutes ago';
|
||||
const actual = dateTimeFromNow(daysAgo.valueOf(), 0, 0, false);
|
||||
expect(actual).toBe(expected);
|
||||
expect(actual.text).toBe(expected);
|
||||
});
|
||||
|
||||
it('should format as from now with minutes (in UTC)', () => {
|
||||
const daysAgo = toUtc().add(-2, 'm');
|
||||
const expected = '2 minutes ago';
|
||||
const actual = dateTimeFromNow(daysAgo.valueOf(), 0, 0, true);
|
||||
expect(actual).toBe(expected);
|
||||
expect(actual.text).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('duration', () => {
|
||||
it('0 milliseconds', () => {
|
||||
const str = toDurationInMilliseconds(0, 0);
|
||||
expect(str).toBe('0 milliseconds');
|
||||
expect(formattedValueToString(str)).toBe('0 milliseconds');
|
||||
});
|
||||
it('1 millisecond', () => {
|
||||
const str = toDurationInMilliseconds(1, 0);
|
||||
expect(str).toBe('1 millisecond');
|
||||
expect(formattedValueToString(str)).toBe('1 millisecond');
|
||||
});
|
||||
it('-1 millisecond', () => {
|
||||
const str = toDurationInMilliseconds(-1, 0);
|
||||
expect(str).toBe('1 millisecond ago');
|
||||
expect(formattedValueToString(str)).toBe('1 millisecond ago');
|
||||
});
|
||||
it('seconds', () => {
|
||||
const str = toDurationInSeconds(1, 0);
|
||||
expect(str).toBe('1 second');
|
||||
expect(formattedValueToString(str)).toBe('1 second');
|
||||
});
|
||||
it('minutes', () => {
|
||||
const str = toDuration(1, 0, Interval.Minute);
|
||||
expect(str).toBe('1 minute');
|
||||
expect(formattedValueToString(str)).toBe('1 minute');
|
||||
});
|
||||
it('hours', () => {
|
||||
const str = toDuration(1, 0, Interval.Hour);
|
||||
expect(str).toBe('1 hour');
|
||||
expect(formattedValueToString(str)).toBe('1 hour');
|
||||
});
|
||||
it('days', () => {
|
||||
const str = toDuration(1, 0, Interval.Day);
|
||||
expect(str).toBe('1 day');
|
||||
expect(formattedValueToString(str)).toBe('1 day');
|
||||
});
|
||||
it('weeks', () => {
|
||||
const str = toDuration(1, 0, Interval.Week);
|
||||
expect(str).toBe('1 week');
|
||||
expect(formattedValueToString(str)).toBe('1 week');
|
||||
});
|
||||
it('months', () => {
|
||||
const str = toDuration(1, 0, Interval.Month);
|
||||
expect(str).toBe('1 month');
|
||||
expect(formattedValueToString(str)).toBe('1 month');
|
||||
});
|
||||
it('years', () => {
|
||||
const str = toDuration(1, 0, Interval.Year);
|
||||
expect(str).toBe('1 year');
|
||||
expect(formattedValueToString(str)).toBe('1 year');
|
||||
});
|
||||
it('decimal days', () => {
|
||||
const str = toDuration(1.5, 2, Interval.Day);
|
||||
expect(str).toBe('1 day, 12 hours, 0 minutes');
|
||||
expect(formattedValueToString(str)).toBe('1 day, 12 hours, 0 minutes');
|
||||
});
|
||||
it('decimal months', () => {
|
||||
const str = toDuration(1.5, 3, Interval.Month);
|
||||
expect(str).toBe('1 month, 2 weeks, 1 day, 0 hours');
|
||||
expect(formattedValueToString(str)).toBe('1 month, 2 weeks, 1 day, 0 hours');
|
||||
});
|
||||
it('no decimals', () => {
|
||||
const str = toDuration(38898367008, 0, Interval.Millisecond);
|
||||
expect(str).toBe('1 year');
|
||||
expect(formattedValueToString(str)).toBe('1 year');
|
||||
});
|
||||
it('1 decimal', () => {
|
||||
const str = toDuration(38898367008, 1, Interval.Millisecond);
|
||||
expect(str).toBe('1 year, 2 months');
|
||||
expect(formattedValueToString(str)).toBe('1 year, 2 months');
|
||||
});
|
||||
it('too many decimals', () => {
|
||||
const str = toDuration(38898367008, 20, Interval.Millisecond);
|
||||
expect(str).toBe('1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, 7 seconds, 8 milliseconds');
|
||||
expect(formattedValueToString(str)).toBe(
|
||||
'1 year, 2 months, 3 weeks, 4 days, 5 hours, 6 minutes, 7 seconds, 8 milliseconds'
|
||||
);
|
||||
});
|
||||
it('floating point error', () => {
|
||||
const str = toDuration(36993906007, 8, Interval.Millisecond);
|
||||
expect(str).toBe('1 year, 2 months, 0 weeks, 3 days, 4 hours, 5 minutes, 6 seconds, 7 milliseconds');
|
||||
expect(formattedValueToString(str)).toBe(
|
||||
'1 year, 2 months, 0 weeks, 3 days, 4 hours, 5 minutes, 6 seconds, 7 milliseconds'
|
||||
);
|
||||
});
|
||||
it('1 dthms', () => {
|
||||
const str = toDurationInHoursMinutesSeconds(1);
|
||||
expect(str).toBe('00:00:01');
|
||||
expect(formattedValueToString(str)).toBe('00:00:01');
|
||||
});
|
||||
it('-1 dthms', () => {
|
||||
const str = toDurationInHoursMinutesSeconds(-1);
|
||||
expect(str).toBe('00:00:01 ago');
|
||||
expect(formattedValueToString(str)).toBe('00:00:01 ago');
|
||||
});
|
||||
it('0 dthms', () => {
|
||||
const str = toDurationInHoursMinutesSeconds(0);
|
||||
expect(str).toBe('00:00:00');
|
||||
expect(formattedValueToString(str)).toBe('00:00:00');
|
||||
});
|
||||
});
|
||||
|
||||
describe('clock', () => {
|
||||
it('size less than 1 second', () => {
|
||||
const str = toClock(999, 0);
|
||||
expect(str).toBe('999ms');
|
||||
expect(formattedValueToString(str)).toBe('999ms');
|
||||
});
|
||||
describe('size less than 1 minute', () => {
|
||||
it('default', () => {
|
||||
const str = toClock(59999);
|
||||
expect(str).toBe('59s:999ms');
|
||||
expect(formattedValueToString(str)).toBe('59s:999ms');
|
||||
});
|
||||
it('decimals equals 0', () => {
|
||||
const str = toClock(59999, 0);
|
||||
expect(str).toBe('59s');
|
||||
expect(formattedValueToString(str)).toBe('59s');
|
||||
});
|
||||
});
|
||||
describe('size less than 1 hour', () => {
|
||||
it('default', () => {
|
||||
const str = toClock(3599999);
|
||||
expect(str).toBe('59m:59s:999ms');
|
||||
expect(formattedValueToString(str)).toBe('59m:59s:999ms');
|
||||
});
|
||||
it('decimals equals 0', () => {
|
||||
const str = toClock(3599999, 0);
|
||||
expect(str).toBe('59m');
|
||||
expect(formattedValueToString(str)).toBe('59m');
|
||||
});
|
||||
it('decimals equals 1', () => {
|
||||
const str = toClock(3599999, 1);
|
||||
expect(str).toBe('59m:59s');
|
||||
expect(formattedValueToString(str)).toBe('59m:59s');
|
||||
});
|
||||
});
|
||||
describe('size greater than or equal 1 hour', () => {
|
||||
it('default', () => {
|
||||
const str = toClock(7199999);
|
||||
expect(str).toBe('01h:59m:59s:999ms');
|
||||
expect(formattedValueToString(str)).toBe('01h:59m:59s:999ms');
|
||||
});
|
||||
it('decimals equals 0', () => {
|
||||
const str = toClock(7199999, 0);
|
||||
expect(str).toBe('01h');
|
||||
expect(formattedValueToString(str)).toBe('01h');
|
||||
});
|
||||
it('decimals equals 1', () => {
|
||||
const str = toClock(7199999, 1);
|
||||
expect(str).toBe('01h:59m');
|
||||
expect(formattedValueToString(str)).toBe('01h:59m');
|
||||
});
|
||||
it('decimals equals 2', () => {
|
||||
const str = toClock(7199999, 2);
|
||||
expect(str).toBe('01h:59m:59s');
|
||||
expect(formattedValueToString(str)).toBe('01h:59m:59s');
|
||||
});
|
||||
});
|
||||
describe('size greater than or equal 1 day', () => {
|
||||
it('default', () => {
|
||||
const str = toClock(89999999);
|
||||
expect(str).toBe('24h:59m:59s:999ms');
|
||||
expect(formattedValueToString(str)).toBe('24h:59m:59s:999ms');
|
||||
});
|
||||
it('decimals equals 0', () => {
|
||||
const str = toClock(89999999, 0);
|
||||
expect(str).toBe('24h');
|
||||
expect(formattedValueToString(str)).toBe('24h');
|
||||
});
|
||||
it('decimals equals 1', () => {
|
||||
const str = toClock(89999999, 1);
|
||||
expect(str).toBe('24h:59m');
|
||||
expect(formattedValueToString(str)).toBe('24h:59m');
|
||||
});
|
||||
it('decimals equals 2', () => {
|
||||
const str = toClock(89999999, 2);
|
||||
expect(str).toBe('24h:59m:59s');
|
||||
expect(formattedValueToString(str)).toBe('24h:59m:59s');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { toDuration as duration, toUtc, dateTime } from '../datetime/moment_wrapper';
|
||||
|
||||
import { toFixed, toFixedScaled } from './valueFormats';
|
||||
import { toFixed, toFixedScaled, FormattedValue, ValueFormatter } from './valueFormats';
|
||||
import { DecimalCount } from '../types/displayValue';
|
||||
|
||||
interface IntervalsInSeconds {
|
||||
@ -29,13 +29,13 @@ const INTERVALS_IN_SECONDS: IntervalsInSeconds = {
|
||||
[Interval.Millisecond]: 0.001,
|
||||
};
|
||||
|
||||
export function toNanoSeconds(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
|
||||
export function toNanoSeconds(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount): FormattedValue {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
|
||||
if (Math.abs(size) < 1000) {
|
||||
return toFixed(size, decimals) + ' ns';
|
||||
return { text: toFixed(size, decimals), suffix: ' ns' };
|
||||
} else if (Math.abs(size) < 1000000) {
|
||||
return toFixedScaled(size / 1000, decimals, scaledDecimals, 3, ' µs');
|
||||
} else if (Math.abs(size) < 1000000000) {
|
||||
@ -47,13 +47,13 @@ export function toNanoSeconds(size: number, decimals?: DecimalCount, scaledDecim
|
||||
}
|
||||
}
|
||||
|
||||
export function toMicroSeconds(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
|
||||
export function toMicroSeconds(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount): FormattedValue {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
|
||||
if (Math.abs(size) < 1000) {
|
||||
return toFixed(size, decimals) + ' µs';
|
||||
return { text: toFixed(size, decimals), suffix: ' µs' };
|
||||
} else if (Math.abs(size) < 1000000) {
|
||||
return toFixedScaled(size / 1000, decimals, scaledDecimals, 3, ' ms');
|
||||
} else {
|
||||
@ -61,13 +61,13 @@ export function toMicroSeconds(size: number, decimals?: DecimalCount, scaledDeci
|
||||
}
|
||||
}
|
||||
|
||||
export function toMilliSeconds(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
|
||||
export function toMilliSeconds(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount): FormattedValue {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
|
||||
if (Math.abs(size) < 1000) {
|
||||
return toFixed(size, decimals) + ' ms';
|
||||
return { text: toFixed(size, decimals), suffix: ' ms' };
|
||||
} else if (Math.abs(size) < 60000) {
|
||||
// Less than 1 min
|
||||
return toFixedScaled(size / 1000, decimals, scaledDecimals, 3, ' s');
|
||||
@ -92,9 +92,9 @@ export function trySubstract(value1: DecimalCount, value2: DecimalCount): Decima
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function toSeconds(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
|
||||
export function toSeconds(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount): FormattedValue {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
|
||||
// Less than 1 µs, divide in ns
|
||||
@ -111,7 +111,7 @@ export function toSeconds(size: number, decimals?: DecimalCount, scaledDecimals?
|
||||
}
|
||||
|
||||
if (Math.abs(size) < 60) {
|
||||
return toFixed(size, decimals) + ' s';
|
||||
return { text: toFixed(size, decimals), suffix: ' s' };
|
||||
} else if (Math.abs(size) < 3600) {
|
||||
// Less than 1 hour, divide in minutes
|
||||
return toFixedScaled(size / 60, decimals, scaledDecimals, 1, ' min');
|
||||
@ -129,13 +129,13 @@ export function toSeconds(size: number, decimals?: DecimalCount, scaledDecimals?
|
||||
return toFixedScaled(size / 3.15569e7, decimals, scaledDecimals, 7, ' year');
|
||||
}
|
||||
|
||||
export function toMinutes(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
|
||||
export function toMinutes(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount): FormattedValue {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
|
||||
if (Math.abs(size) < 60) {
|
||||
return toFixed(size, decimals) + ' min';
|
||||
return { text: toFixed(size, decimals), suffix: ' min' };
|
||||
} else if (Math.abs(size) < 1440) {
|
||||
return toFixedScaled(size / 60, decimals, scaledDecimals, 2, ' hour');
|
||||
} else if (Math.abs(size) < 10080) {
|
||||
@ -147,13 +147,13 @@ export function toMinutes(size: number, decimals?: DecimalCount, scaledDecimals?
|
||||
}
|
||||
}
|
||||
|
||||
export function toHours(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
|
||||
export function toHours(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount): FormattedValue {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
|
||||
if (Math.abs(size) < 24) {
|
||||
return toFixed(size, decimals) + ' hour';
|
||||
return { text: toFixed(size, decimals), suffix: ' hour' };
|
||||
} else if (Math.abs(size) < 168) {
|
||||
return toFixedScaled(size / 24, decimals, scaledDecimals, 2, ' day');
|
||||
} else if (Math.abs(size) < 8760) {
|
||||
@ -163,13 +163,13 @@ export function toHours(size: number, decimals?: DecimalCount, scaledDecimals?:
|
||||
}
|
||||
}
|
||||
|
||||
export function toDays(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) {
|
||||
export function toDays(size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount): FormattedValue {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
|
||||
if (Math.abs(size) < 7) {
|
||||
return toFixed(size, decimals) + ' day';
|
||||
return { text: toFixed(size, decimals), suffix: ' day' };
|
||||
} else if (Math.abs(size) < 365) {
|
||||
return toFixedScaled(size / 7, decimals, scaledDecimals, 2, ' week');
|
||||
} else {
|
||||
@ -177,17 +177,22 @@ export function toDays(size: number, decimals?: DecimalCount, scaledDecimals?: D
|
||||
}
|
||||
}
|
||||
|
||||
export function toDuration(size: number, decimals: DecimalCount, timeScale: Interval): string {
|
||||
export function toDuration(size: number, decimals: DecimalCount, timeScale: Interval): FormattedValue {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
|
||||
if (size === 0) {
|
||||
return '0 ' + timeScale + 's';
|
||||
return { text: '0', suffix: ' ' + timeScale + 's' };
|
||||
}
|
||||
|
||||
if (size < 0) {
|
||||
return toDuration(-size, decimals, timeScale) + ' ago';
|
||||
const v = toDuration(-size, decimals, timeScale);
|
||||
if (!v.suffix) {
|
||||
v.suffix = '';
|
||||
}
|
||||
v.suffix += ' ago';
|
||||
return v;
|
||||
}
|
||||
|
||||
const units = [
|
||||
@ -228,17 +233,19 @@ export function toDuration(size: number, decimals: DecimalCount, timeScale: Inte
|
||||
}
|
||||
}
|
||||
|
||||
return strings.join(', ');
|
||||
return { text: strings.join(', ') };
|
||||
}
|
||||
|
||||
export function toClock(size: number, decimals?: DecimalCount) {
|
||||
export function toClock(size: number, decimals?: DecimalCount): FormattedValue {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
|
||||
// < 1 second
|
||||
if (size < 1000) {
|
||||
return toUtc(size).format('SSS\\m\\s');
|
||||
return {
|
||||
text: toUtc(size).format('SSS\\m\\s'),
|
||||
};
|
||||
}
|
||||
|
||||
// < 1 minute
|
||||
@ -247,7 +254,7 @@ export function toClock(size: number, decimals?: DecimalCount) {
|
||||
if (decimals === 0) {
|
||||
format = 'ss\\s';
|
||||
}
|
||||
return toUtc(size).format(format);
|
||||
return { text: toUtc(size).format(format) };
|
||||
}
|
||||
|
||||
// < 1 hour
|
||||
@ -258,7 +265,7 @@ export function toClock(size: number, decimals?: DecimalCount) {
|
||||
} else if (decimals === 1) {
|
||||
format = 'mm\\m:ss\\s';
|
||||
}
|
||||
return toUtc(size).format(format);
|
||||
return { text: toUtc(size).format(format) };
|
||||
}
|
||||
|
||||
let format = 'mm\\m:ss\\s:SSS\\m\\s';
|
||||
@ -273,20 +280,26 @@ export function toClock(size: number, decimals?: DecimalCount) {
|
||||
format = 'mm\\m:ss\\s';
|
||||
}
|
||||
|
||||
return format ? `${hours}:${toUtc(size).format(format)}` : hours;
|
||||
const text = format ? `${hours}:${toUtc(size).format(format)}` : hours;
|
||||
return { text };
|
||||
}
|
||||
|
||||
export function toDurationInMilliseconds(size: number, decimals: DecimalCount) {
|
||||
export function toDurationInMilliseconds(size: number, decimals: DecimalCount): FormattedValue {
|
||||
return toDuration(size, decimals, Interval.Millisecond);
|
||||
}
|
||||
|
||||
export function toDurationInSeconds(size: number, decimals: DecimalCount) {
|
||||
export function toDurationInSeconds(size: number, decimals: DecimalCount): FormattedValue {
|
||||
return toDuration(size, decimals, Interval.Second);
|
||||
}
|
||||
|
||||
export function toDurationInHoursMinutesSeconds(size: number): string {
|
||||
export function toDurationInHoursMinutesSeconds(size: number): FormattedValue {
|
||||
if (size < 0) {
|
||||
return toDurationInHoursMinutesSeconds(-size) + ' ago';
|
||||
const v = toDurationInHoursMinutesSeconds(-size);
|
||||
if (!v.suffix) {
|
||||
v.suffix = '';
|
||||
}
|
||||
v.suffix += ' ago';
|
||||
return v;
|
||||
}
|
||||
const strings = [];
|
||||
const numHours = Math.floor(size / 3600);
|
||||
@ -295,40 +308,42 @@ export function toDurationInHoursMinutesSeconds(size: number): string {
|
||||
numHours > 9 ? strings.push('' + numHours) : strings.push('0' + numHours);
|
||||
numMinutes > 9 ? strings.push('' + numMinutes) : strings.push('0' + numMinutes);
|
||||
numSeconds > 9 ? strings.push('' + numSeconds) : strings.push('0' + numSeconds);
|
||||
return strings.join(':');
|
||||
return { text: strings.join(':') };
|
||||
}
|
||||
|
||||
export function toTimeTicks(size: number, decimals: DecimalCount, scaledDecimals: DecimalCount) {
|
||||
export function toTimeTicks(size: number, decimals: DecimalCount, scaledDecimals: DecimalCount): FormattedValue {
|
||||
return toSeconds(size / 100, decimals, scaledDecimals);
|
||||
}
|
||||
|
||||
export function toClockMilliseconds(size: number, decimals: DecimalCount) {
|
||||
export function toClockMilliseconds(size: number, decimals: DecimalCount): FormattedValue {
|
||||
return toClock(size, decimals);
|
||||
}
|
||||
|
||||
export function toClockSeconds(size: number, decimals: DecimalCount) {
|
||||
export function toClockSeconds(size: number, decimals: DecimalCount): FormattedValue {
|
||||
return toClock(size * 1000, decimals);
|
||||
}
|
||||
|
||||
export function dateTimeAsIso(value: number, decimals: DecimalCount, scaledDecimals: DecimalCount, isUtc?: boolean) {
|
||||
const time = isUtc ? toUtc(value) : dateTime(value);
|
||||
|
||||
if (dateTime().isSame(value, 'day')) {
|
||||
return time.format('HH:mm:ss');
|
||||
}
|
||||
return time.format('YYYY-MM-DD HH:mm:ss');
|
||||
export function toDateTimeValueFormatter(pattern: string, todayPattern?: string): ValueFormatter {
|
||||
return (value: number, decimals: DecimalCount, scaledDecimals: DecimalCount, isUtc?: boolean): FormattedValue => {
|
||||
const time = isUtc ? toUtc(value) : dateTime(value);
|
||||
if (todayPattern) {
|
||||
if (dateTime().isSame(value, 'day')) {
|
||||
return { text: time.format(todayPattern) };
|
||||
}
|
||||
}
|
||||
return { text: time.format(pattern) };
|
||||
};
|
||||
}
|
||||
|
||||
export function dateTimeAsUS(value: number, decimals: DecimalCount, scaledDecimals: DecimalCount, isUtc?: boolean) {
|
||||
const time = isUtc ? toUtc(value) : dateTime(value);
|
||||
export const dateTimeAsIso = toDateTimeValueFormatter('YYYY-MM-DD HH:mm:ss', 'HH:mm:ss');
|
||||
export const dateTimeAsUS = toDateTimeValueFormatter('MM/DD/YYYY h:mm:ss a', 'h:mm:ss a');
|
||||
|
||||
if (dateTime().isSame(value, 'day')) {
|
||||
return time.format('h:mm:ss a');
|
||||
}
|
||||
return time.format('MM/DD/YYYY h:mm:ss a');
|
||||
}
|
||||
|
||||
export function dateTimeFromNow(value: number, decimals: DecimalCount, scaledDecimals: DecimalCount, isUtc?: boolean) {
|
||||
export function dateTimeFromNow(
|
||||
value: number,
|
||||
decimals: DecimalCount,
|
||||
scaledDecimals: DecimalCount,
|
||||
isUtc?: boolean
|
||||
): FormattedValue {
|
||||
const time = isUtc ? toUtc(value) : dateTime(value);
|
||||
return time.fromNow();
|
||||
return { text: time.fromNow() };
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
import { currency } from './symbolFormatters';
|
||||
|
||||
describe('Currency', () => {
|
||||
it('should format as usd', () => {
|
||||
expect(currency('$')(1532.82, 1, -1)).toEqual('$1.53K');
|
||||
});
|
||||
it('should format as krw', () => {
|
||||
expect(currency('₩')(1532.82, 1, -1)).toEqual('₩1.53K');
|
||||
});
|
||||
});
|
@ -1,19 +1,59 @@
|
||||
import { scaledUnits } from './valueFormats';
|
||||
import { scaledUnits, ValueFormatter } from './valueFormats';
|
||||
import { DecimalCount } from '../types/displayValue';
|
||||
|
||||
export function currency(symbol: string) {
|
||||
export function currency(symbol: string, asSuffix?: boolean): ValueFormatter {
|
||||
const units = ['', 'K', 'M', 'B', 'T'];
|
||||
const scaler = scaledUnits(1000, units);
|
||||
return (size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) => {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
const scaled = scaler(size, decimals, scaledDecimals);
|
||||
return symbol + scaled;
|
||||
if (asSuffix) {
|
||||
scaled.suffix = symbol;
|
||||
} else {
|
||||
scaled.prefix = symbol;
|
||||
}
|
||||
return scaled;
|
||||
};
|
||||
}
|
||||
|
||||
export function binarySIPrefix(unit: string, offset = 0) {
|
||||
export function getOffsetFromSIPrefix(c: string): number {
|
||||
switch (c) {
|
||||
case 'f':
|
||||
return -5;
|
||||
case 'p':
|
||||
return -4;
|
||||
case 'n':
|
||||
return -3;
|
||||
case 'μ': // Two different unicode chars for µ
|
||||
case 'µ':
|
||||
return -2;
|
||||
case 'm':
|
||||
return -1;
|
||||
case '':
|
||||
return 0;
|
||||
case 'k':
|
||||
return 1;
|
||||
case 'M':
|
||||
return 2;
|
||||
case 'G':
|
||||
return 3;
|
||||
case 'T':
|
||||
return 4;
|
||||
case 'P':
|
||||
return 5;
|
||||
case 'E':
|
||||
return 6;
|
||||
case 'Z':
|
||||
return 7;
|
||||
case 'Y':
|
||||
return 8;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function binarySIPrefix(unit: string, offset = 0): ValueFormatter {
|
||||
const prefixes = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'].slice(offset);
|
||||
const units = prefixes.map(p => {
|
||||
return ' ' + p + unit;
|
||||
@ -21,7 +61,7 @@ export function binarySIPrefix(unit: string, offset = 0) {
|
||||
return scaledUnits(1024, units);
|
||||
}
|
||||
|
||||
export function decimalSIPrefix(unit: string, offset = 0) {
|
||||
export function decimalSIPrefix(unit: string, offset = 0): ValueFormatter {
|
||||
let prefixes = ['f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
|
||||
prefixes = prefixes.slice(5 + (offset || 0));
|
||||
const units = prefixes.map(p => {
|
||||
|
@ -1,6 +1,85 @@
|
||||
import { toFixed, getValueFormat, scaledUnits } from './valueFormats';
|
||||
import { toFixed, getValueFormat, scaledUnits, formattedValueToString } from './valueFormats';
|
||||
import { DecimalCount } from '../types/displayValue';
|
||||
import { TimeZone } from '../types';
|
||||
import { dateTime } from '../datetime';
|
||||
|
||||
interface ValueFormatTest {
|
||||
id: string;
|
||||
decimals?: DecimalCount;
|
||||
scaledDecimals?: DecimalCount;
|
||||
timeZone?: TimeZone;
|
||||
value: number;
|
||||
result: string;
|
||||
}
|
||||
|
||||
const formatTests: ValueFormatTest[] = [
|
||||
// Currancy
|
||||
{ id: 'currencyUSD', decimals: 2, value: 1532.82, result: '$1.53K' },
|
||||
{ id: 'currencyKRW', decimals: 2, value: 1532.82, result: '₩1.53K' },
|
||||
|
||||
// Standard
|
||||
{ id: 'ms', decimals: 4, value: 0.0024, result: '0.0024 ms' },
|
||||
{ id: 'ms', decimals: 0, value: 100, result: '100 ms' },
|
||||
{ id: 'ms', decimals: 2, value: 1250, result: '1.25 s' },
|
||||
{ id: 'ms', decimals: 1, value: 10000086.123, result: '2.8 hour' },
|
||||
{ id: 'ms', decimals: 0, value: 1200, result: '1 s' },
|
||||
{ id: 'short', decimals: 0, scaledDecimals: -1, value: 98765, result: '98.77 K' },
|
||||
{ id: 'short', decimals: 0, scaledDecimals: 0, value: 9876543, result: '9.876543 Mil' },
|
||||
{ id: 'kbytes', decimals: 3, value: 10000000, result: '9.537 GiB' },
|
||||
{ id: 'deckbytes', decimals: 3, value: 10000000, result: '10.000 GB' },
|
||||
{ id: 'megwatt', decimals: 3, value: 1000, result: '1.000 GW' },
|
||||
{ id: 'kohm', decimals: 3, value: 1000, result: '1.000 MΩ' },
|
||||
{ id: 'Mohm', decimals: 3, value: 1000, result: '1.000 GΩ' },
|
||||
|
||||
{ id: 'farad', decimals: 3, value: 1000, result: '1.000 kF' },
|
||||
{ id: 'µfarad', decimals: 3, value: 1000, result: '1.000 mF' },
|
||||
{ id: 'nfarad', decimals: 3, value: 1000, result: '1.000 µF' },
|
||||
{ id: 'pfarad', decimals: 3, value: 1000, result: '1.000 nF' },
|
||||
{ id: 'ffarad', decimals: 3, value: 1000, result: '1.000 pF' },
|
||||
|
||||
{ id: 'henry', decimals: 3, value: 1000, result: '1.000 kH' },
|
||||
{ id: 'mhenry', decimals: 3, value: 1000, result: '1.000 H' },
|
||||
{ id: 'µhenry', decimals: 3, value: 1000, result: '1.000 mH' },
|
||||
|
||||
// Suffix (unknown units append to the end)
|
||||
{ id: 'a', decimals: 0, value: 1532.82, result: '1533 a' },
|
||||
{ id: 'b', decimals: 0, value: 1532.82, result: '1533 b' },
|
||||
|
||||
// Prefix (unknown units append to the end)
|
||||
{ id: 'prefix:b', value: 1532.82, result: 'b1533' },
|
||||
|
||||
// SI Units
|
||||
{ id: 'si:µF', value: 1234, decimals: 2, result: '1.23 mF' },
|
||||
{ id: 'si:µF', value: 1234000000, decimals: 2, result: '1.23 kF' },
|
||||
{ id: 'si:µF', value: 1234000000000000, decimals: 2, result: '1.23 GF' },
|
||||
|
||||
// Time format
|
||||
{ id: 'time:YYYY', decimals: 0, value: dateTime(new Date(1999, 6, 2)).valueOf(), result: '1999' },
|
||||
{ id: 'time:YYYY.MM', decimals: 0, value: dateTime(new Date(2010, 6, 2)).valueOf(), result: '2010.07' },
|
||||
];
|
||||
|
||||
describe('valueFormats', () => {
|
||||
it('Manually check a format', () => {
|
||||
// helpful for adding tests one at a time with the debugger
|
||||
const tests: ValueFormatTest[] = [
|
||||
{ id: 'time:YYYY.MM', decimals: 0, value: dateTime(new Date(2010, 6, 2)).valueOf(), result: '2010.07' },
|
||||
];
|
||||
const test = tests[0];
|
||||
const result = getValueFormat(test.id)(test.value, test.decimals, test.scaledDecimals);
|
||||
const full = formattedValueToString(result);
|
||||
expect(full).toBe(test.result);
|
||||
});
|
||||
|
||||
for (const test of formatTests) {
|
||||
describe(`value format: ${test.id}`, () => {
|
||||
it(`should translate ${test.value} as ${test.result}`, () => {
|
||||
const result = getValueFormat(test.id)(test.value, test.decimals, test.scaledDecimals);
|
||||
const full = formattedValueToString(result);
|
||||
expect(full).toBe(test.result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('normal cases', () => {
|
||||
it('toFixed should handle number correctly if decimal is null', () => {
|
||||
expect(toFixed(100)).toBe('100');
|
||||
@ -18,28 +97,6 @@ describe('valueFormats', () => {
|
||||
expect(toFixed(100.4, 2)).toBe('100.40');
|
||||
expect(toFixed(100.5, 2)).toBe('100.50');
|
||||
});
|
||||
|
||||
it('scaledUnit should handle number correctly if scaledDecimals is not null', () => {
|
||||
const units = ['', 'K', 'M', 'B', 'T'];
|
||||
const scaler = scaledUnits(1000, units);
|
||||
|
||||
expect(scaler(98765, 0, 0)).toBe('98.765K');
|
||||
expect(scaler(98765, 0, -1)).toBe('98.77K');
|
||||
|
||||
expect(scaler(9876543, 0, 0)).toBe('9.876543M');
|
||||
expect(scaler(9876543, 0, -1)).toBe('9.87654M');
|
||||
});
|
||||
|
||||
it('scaledUnit should handle number correctly if scaledDecimals is null', () => {
|
||||
const units = ['', 'K', 'M', 'B', 'T'];
|
||||
const scaler = scaledUnits(1000, units);
|
||||
|
||||
expect(scaler(98765, 1, null)).toBe('98.8K');
|
||||
expect(scaler(98765, 2, null)).toBe('98.77K');
|
||||
|
||||
expect(scaler(9876543, 2, null)).toBe('9.88M');
|
||||
expect(scaler(9876543, 3, null)).toBe('9.877M');
|
||||
});
|
||||
});
|
||||
|
||||
describe('format edge cases', () => {
|
||||
@ -54,9 +111,9 @@ describe('valueFormats', () => {
|
||||
|
||||
it('scaledUnits should handle non number input gracefully', () => {
|
||||
const disp = scaledUnits(5, ['a', 'b', 'c']);
|
||||
expect(disp(NaN)).toBe('NaN');
|
||||
expect(disp(Number.NEGATIVE_INFINITY)).toBe(negInf);
|
||||
expect(disp(Number.POSITIVE_INFINITY)).toBe(posInf);
|
||||
expect(disp(NaN).text).toBe('NaN');
|
||||
expect(disp(Number.NEGATIVE_INFINITY).text).toBe(negInf);
|
||||
expect(disp(Number.POSITIVE_INFINITY).text).toBe(posInf);
|
||||
});
|
||||
});
|
||||
|
||||
@ -66,109 +123,4 @@ describe('valueFormats', () => {
|
||||
expect(str).toBe('186');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ms format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('ms')(10000086.123, 1, null);
|
||||
expect(str).toBe('2.8 hour');
|
||||
});
|
||||
});
|
||||
|
||||
describe('kbytes format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('kbytes')(10000000, 3, null);
|
||||
expect(str).toBe('9.537 GiB');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deckbytes format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('deckbytes')(10000000, 3, null);
|
||||
expect(str).toBe('10.000 GB');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ms format when scaled decimals is 0', () => {
|
||||
it('should use scaledDecimals and add 3', () => {
|
||||
const str = getValueFormat('ms')(1200, 0, 0);
|
||||
expect(str).toBe('1.200 s');
|
||||
});
|
||||
});
|
||||
|
||||
describe('megawatt format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('megwatt')(1000, 3, null);
|
||||
expect(str).toBe('1.000 GW');
|
||||
});
|
||||
});
|
||||
|
||||
describe('kiloohm format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('kohm')(1000, 3, null);
|
||||
expect(str).toBe('1.000 MΩ');
|
||||
});
|
||||
});
|
||||
|
||||
describe('megaohm format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('Mohm')(1000, 3, null);
|
||||
expect(str).toBe('1.000 GΩ');
|
||||
});
|
||||
});
|
||||
|
||||
describe('farad format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('farad')(1000, 3, null);
|
||||
expect(str).toBe('1.000 kF');
|
||||
});
|
||||
});
|
||||
|
||||
describe('microfarad format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('µfarad')(1000, 3, null);
|
||||
expect(str).toBe('1.000 mF');
|
||||
});
|
||||
});
|
||||
|
||||
describe('nanofarad format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('nfarad')(1000, 3, null);
|
||||
expect(str).toBe('1.000 µF');
|
||||
});
|
||||
});
|
||||
|
||||
describe('picofarad format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('pfarad')(1000, 3, null);
|
||||
expect(str).toBe('1.000 nF');
|
||||
});
|
||||
});
|
||||
|
||||
describe('femtofarad format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('ffarad')(1000, 3, null);
|
||||
expect(str).toBe('1.000 pF');
|
||||
});
|
||||
});
|
||||
|
||||
describe('henry format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('henry')(1000, 3, null);
|
||||
expect(str).toBe('1.000 kH');
|
||||
});
|
||||
});
|
||||
|
||||
describe('millihenry format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('mhenry')(1000, 3, null);
|
||||
expect(str).toBe('1.000 H');
|
||||
});
|
||||
});
|
||||
|
||||
describe('microhenry format when scaled decimals is null do not use it', () => {
|
||||
it('should use specified decimals', () => {
|
||||
const str = getValueFormat('µhenry')(1000, 3, null);
|
||||
expect(str).toBe('1.000 mH');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,12 +1,24 @@
|
||||
import { getCategories } from './categories';
|
||||
import { DecimalCount } from '../types/displayValue';
|
||||
import { toDateTimeValueFormatter } from './dateTimeFormatters';
|
||||
import { getOffsetFromSIPrefix, decimalSIPrefix } from './symbolFormatters';
|
||||
|
||||
export interface FormattedValue {
|
||||
text: string;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
}
|
||||
|
||||
export function formattedValueToString(val: FormattedValue): string {
|
||||
return `${val.prefix ?? ''}${val.text}${val.suffix ?? ''}`;
|
||||
}
|
||||
|
||||
export type ValueFormatter = (
|
||||
value: number,
|
||||
decimals?: DecimalCount,
|
||||
scaledDecimals?: DecimalCount,
|
||||
isUtc?: boolean
|
||||
) => string;
|
||||
isUtc?: boolean // TODO: timezone?: string,
|
||||
) => FormattedValue;
|
||||
|
||||
export interface ValueFormat {
|
||||
name: string;
|
||||
@ -63,32 +75,42 @@ export function toFixedScaled(
|
||||
scaledDecimals: DecimalCount,
|
||||
additionalDecimals: number,
|
||||
ext?: string
|
||||
) {
|
||||
): FormattedValue {
|
||||
if (scaledDecimals === null || scaledDecimals === undefined) {
|
||||
return toFixed(value, decimals) + ext;
|
||||
return { text: toFixed(value, decimals), suffix: ext };
|
||||
}
|
||||
return toFixed(value, scaledDecimals + additionalDecimals) + ext;
|
||||
return {
|
||||
text: toFixed(value, scaledDecimals + additionalDecimals),
|
||||
suffix: ext,
|
||||
};
|
||||
}
|
||||
|
||||
export function toFixedUnit(unit: string): ValueFormatter {
|
||||
export function toFixedUnit(unit: string, asPrefix?: boolean): ValueFormatter {
|
||||
return (size: number, decimals?: DecimalCount) => {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
return toFixed(size, decimals) + ' ' + unit;
|
||||
const text = toFixed(size, decimals);
|
||||
if (unit) {
|
||||
if (asPrefix) {
|
||||
return { text, prefix: unit };
|
||||
}
|
||||
return { text, suffix: ' ' + unit };
|
||||
}
|
||||
return { text };
|
||||
};
|
||||
}
|
||||
|
||||
// Formatter which scales the unit string geometrically according to the given
|
||||
// numeric factor. Repeatedly scales the value down by the factor until it is
|
||||
// less than the factor in magnitude, or the end of the array is reached.
|
||||
export function scaledUnits(factor: number, extArray: string[]) {
|
||||
export function scaledUnits(factor: number, extArray: string[]): ValueFormatter {
|
||||
return (size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) => {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
if (size === Number.NEGATIVE_INFINITY || size === Number.POSITIVE_INFINITY || isNaN(size)) {
|
||||
return size.toLocaleString();
|
||||
return { text: size.toLocaleString() };
|
||||
}
|
||||
|
||||
let steps = 0;
|
||||
@ -99,7 +121,7 @@ export function scaledUnits(factor: number, extArray: string[]) {
|
||||
size /= factor;
|
||||
|
||||
if (steps >= limit) {
|
||||
return 'NA';
|
||||
return { text: 'NA' };
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,26 +129,29 @@ export function scaledUnits(factor: number, extArray: string[]) {
|
||||
decimals = scaledDecimals + 3 * steps;
|
||||
}
|
||||
|
||||
return toFixed(size, decimals) + extArray[steps];
|
||||
return { text: toFixed(size, decimals), suffix: extArray[steps] };
|
||||
};
|
||||
}
|
||||
|
||||
export function locale(value: number, decimals: DecimalCount) {
|
||||
export function locale(value: number, decimals: DecimalCount): FormattedValue {
|
||||
if (value == null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
return value.toLocaleString(undefined, { maximumFractionDigits: decimals as number });
|
||||
return {
|
||||
text: value.toLocaleString(undefined, { maximumFractionDigits: decimals as number }),
|
||||
};
|
||||
}
|
||||
|
||||
export function simpleCountUnit(symbol: string) {
|
||||
export function simpleCountUnit(symbol: string): ValueFormatter {
|
||||
const units = ['', 'K', 'M', 'B', 'T'];
|
||||
const scaler = scaledUnits(1000, units);
|
||||
return (size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) => {
|
||||
if (size === null) {
|
||||
return '';
|
||||
return { text: '' };
|
||||
}
|
||||
const scaled = scaler(size, decimals, scaledDecimals);
|
||||
return scaled + ' ' + symbol;
|
||||
const v = scaler(size, decimals, scaledDecimals);
|
||||
v.suffix += ' ' + symbol;
|
||||
return v;
|
||||
};
|
||||
}
|
||||
|
||||
@ -147,7 +172,27 @@ export function getValueFormat(id: string): ValueFormatter {
|
||||
buildFormats();
|
||||
}
|
||||
|
||||
return index[id];
|
||||
const fmt = index[id];
|
||||
if (!fmt && id) {
|
||||
const idx = id.indexOf(':');
|
||||
if (idx > 0) {
|
||||
const key = id.substring(0, idx);
|
||||
const sub = id.substring(idx + 1);
|
||||
if (key === 'prefix') {
|
||||
return toFixedUnit(sub, true);
|
||||
}
|
||||
if (key === 'time') {
|
||||
return toDateTimeValueFormatter(sub);
|
||||
}
|
||||
if (key === 'si') {
|
||||
const offset = getOffsetFromSIPrefix(sub.charAt(0));
|
||||
const unit = offset === 0 ? sub : sub.substring(1);
|
||||
return decimalSIPrefix(unit, offset);
|
||||
}
|
||||
}
|
||||
return toFixedUnit(id);
|
||||
}
|
||||
return fmt;
|
||||
}
|
||||
|
||||
export function getValueFormatterIndex(): ValueFormatterIndex {
|
||||
|
@ -15,7 +15,6 @@ import { getTheme } from '../../themes';
|
||||
|
||||
const green = '#73BF69';
|
||||
const orange = '#FF9830';
|
||||
// const red = '#BB';
|
||||
|
||||
function getProps(propOverrides?: Partial<Props>): Props {
|
||||
const props: Props = {
|
||||
|
@ -6,9 +6,14 @@ import {
|
||||
TimeSeriesValue,
|
||||
getActiveThreshold,
|
||||
DisplayValue,
|
||||
formattedValueToString,
|
||||
FormattedValue,
|
||||
DisplayValueAlignmentFactors,
|
||||
} from '@grafana/data';
|
||||
|
||||
// Compontents
|
||||
import { FormattedValueDisplay } from '../FormattedValueDisplay/FormattedValueDisplay';
|
||||
|
||||
// Utils
|
||||
import { getColorFromHexRgbOrName } from '@grafana/data';
|
||||
import { measureText, calculateFontSize } from '../../utils/measureText';
|
||||
@ -93,9 +98,7 @@ export class BarGauge extends PureComponent<Props> {
|
||||
|
||||
return (
|
||||
<div style={styles.wrapper}>
|
||||
<div className="bar-gauge__value" style={styles.value}>
|
||||
{value.text}
|
||||
</div>
|
||||
<FormattedValueDisplay className="bar-gauge__value" value={value} style={styles.value} />
|
||||
<div style={styles.bar} />
|
||||
</div>
|
||||
);
|
||||
@ -165,8 +168,8 @@ export class BarGauge extends PureComponent<Props> {
|
||||
const cellSize = Math.floor((maxSize - cellSpacing * cellCount) / cellCount);
|
||||
const valueColor = getValueColor(this.props);
|
||||
|
||||
const valueTextToBaseSizeOn = alignmentFactors ? alignmentFactors.text : value.text;
|
||||
const valueStyles = getValueStyles(valueTextToBaseSizeOn, valueColor, valueWidth, valueHeight, orientation);
|
||||
const valueToBaseSizeOn = alignmentFactors ? alignmentFactors : value;
|
||||
const valueStyles = getValueStyles(valueToBaseSizeOn, valueColor, valueWidth, valueHeight, orientation);
|
||||
|
||||
const containerStyles: CSSProperties = {
|
||||
width: `${wrapperWidth}px`,
|
||||
@ -180,6 +183,7 @@ export class BarGauge extends PureComponent<Props> {
|
||||
} else {
|
||||
containerStyles.flexDirection = 'row';
|
||||
containerStyles.alignItems = 'center';
|
||||
valueStyles.justifyContent = 'flex-end';
|
||||
}
|
||||
|
||||
const cells: JSX.Element[] = [];
|
||||
@ -213,9 +217,7 @@ export class BarGauge extends PureComponent<Props> {
|
||||
return (
|
||||
<div style={containerStyles}>
|
||||
{cells}
|
||||
<div className="bar-gauge__value" style={valueStyles}>
|
||||
{value.text}
|
||||
</div>
|
||||
<FormattedValueDisplay className="bar-gauge__value" value={value} style={valueStyles} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -394,8 +396,8 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles
|
||||
const valuePercent = getValuePercent(value.numeric, minValue, maxValue);
|
||||
const valueColor = getValueColor(props);
|
||||
|
||||
const valueTextToBaseSizeOn = alignmentFactors ? alignmentFactors.text : value.text;
|
||||
const valueStyles = getValueStyles(valueTextToBaseSizeOn, valueColor, valueWidth, valueHeight, orientation);
|
||||
const valueToBaseSizeOn = alignmentFactors ? alignmentFactors : value;
|
||||
const valueStyles = getValueStyles(valueToBaseSizeOn, valueColor, valueWidth, valueHeight, orientation);
|
||||
|
||||
const isBasic = displayMode === 'basic';
|
||||
const wrapperStyles: CSSProperties = {
|
||||
@ -504,13 +506,13 @@ export function getValueColor(props: Props): string {
|
||||
}
|
||||
|
||||
function getValueStyles(
|
||||
value: string,
|
||||
value: FormattedValue,
|
||||
color: string,
|
||||
width: number,
|
||||
height: number,
|
||||
orientation: VizOrientation
|
||||
): CSSProperties {
|
||||
const valueStyles: CSSProperties = {
|
||||
const styles: CSSProperties = {
|
||||
color: color,
|
||||
height: `${height}px`,
|
||||
width: `${width}px`,
|
||||
@ -523,14 +525,16 @@ function getValueStyles(
|
||||
let textWidth = width;
|
||||
|
||||
if (isVertical(orientation)) {
|
||||
valueStyles.justifyContent = `center`;
|
||||
styles.justifyContent = `center`;
|
||||
} else {
|
||||
valueStyles.justifyContent = `flex-start`;
|
||||
valueStyles.paddingLeft = `${VALUE_LEFT_PADDING}px`;
|
||||
styles.justifyContent = `flex-start`;
|
||||
styles.paddingLeft = `${VALUE_LEFT_PADDING}px`;
|
||||
// Need to remove the left padding from the text width constraints
|
||||
textWidth -= VALUE_LEFT_PADDING;
|
||||
}
|
||||
|
||||
valueStyles.fontSize = calculateFontSize(value, textWidth, height, VALUE_LINE_HEIGHT) + 'px';
|
||||
return valueStyles;
|
||||
const formattedValueString = formattedValueToString(value);
|
||||
styles.fontSize = calculateFontSize(formattedValueString, textWidth, height, VALUE_LINE_HEIGHT);
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
@ -20,14 +20,14 @@ exports[`BarGauge Render with basic options should render 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
<FormattedDisplayValue
|
||||
className="bar-gauge__value"
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"color": "#73BF69",
|
||||
"display": "flex",
|
||||
"fontSize": "175px",
|
||||
"fontSize": 175,
|
||||
"height": "300px",
|
||||
"justifyContent": "flex-start",
|
||||
"lineHeight": 1,
|
||||
@ -35,9 +35,13 @@ exports[`BarGauge Render with basic options should render 1`] = `
|
||||
"width": "60px",
|
||||
}
|
||||
}
|
||||
>
|
||||
25
|
||||
</div>
|
||||
value={
|
||||
Object {
|
||||
"numeric": 25,
|
||||
"text": "25",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
|
@ -11,7 +11,9 @@ import {
|
||||
getValueStyles,
|
||||
getTitleStyles,
|
||||
} from './styles';
|
||||
|
||||
import { renderGraph } from './renderGraph';
|
||||
import { FormattedValueDisplay } from '../FormattedValueDisplay/FormattedValueDisplay';
|
||||
|
||||
export interface BigValueSparkline {
|
||||
data: GraphSeriesValue[][];
|
||||
@ -67,26 +69,10 @@ export class BigValue extends PureComponent<Props> {
|
||||
<div className={className} style={panelStyles} onClick={onClick}>
|
||||
<div style={valueAndTitleContainerStyles}>
|
||||
{value.title && <div style={titleStyles}>{value.title}</div>}
|
||||
<div style={valueStyles}>{renderValueWithSmallerUnit(value.text, layout.valueFontSize)}</div>
|
||||
<FormattedValueDisplay value={value} style={valueStyles} />
|
||||
</div>
|
||||
{renderGraph(layout, sparkline)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function renderValueWithSmallerUnit(value: string, fontSize: number) {
|
||||
const valueParts = value.split(' ');
|
||||
const unitSize = `${fontSize * 0.7}px`;
|
||||
|
||||
if (valueParts.length === 2) {
|
||||
return (
|
||||
<>
|
||||
{valueParts[0]}
|
||||
<span style={{ fontSize: unitSize, paddingLeft: '2px' }}>{valueParts[1]}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
@ -27,19 +27,23 @@ exports[`BigValue Render with basic options should render 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
<FormattedDisplayValue
|
||||
style={
|
||||
Object {
|
||||
"color": "#EEE",
|
||||
"fontSize": "230px",
|
||||
"fontSize": 230,
|
||||
"fontWeight": 500,
|
||||
"lineHeight": 1.2,
|
||||
"textShadow": "#333 0px 0px 1px",
|
||||
}
|
||||
}
|
||||
>
|
||||
25
|
||||
</div>
|
||||
value={
|
||||
Object {
|
||||
"numeric": 25,
|
||||
"text": "25",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -3,7 +3,7 @@ import { CSSProperties } from 'react';
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
// Utils
|
||||
import { getColorFromHexRgbOrName, GrafanaTheme } from '@grafana/data';
|
||||
import { getColorFromHexRgbOrName, GrafanaTheme, formattedValueToString } from '@grafana/data';
|
||||
import { calculateFontSize } from '../../utils/measureText';
|
||||
|
||||
// Types
|
||||
@ -49,7 +49,7 @@ export function calculateLayout(props: Props): LayoutResult {
|
||||
const justifyCenter = shouldJustifyCenter(props);
|
||||
const panelPadding = height > 100 ? 12 : 8;
|
||||
const titleToAlignTo = alignmentFactors ? alignmentFactors.title : value.title;
|
||||
const valueToAlignTo = alignmentFactors ? alignmentFactors.text : value.text;
|
||||
const valueToAlignTo = formattedValueToString(alignmentFactors ? alignmentFactors : value);
|
||||
|
||||
const maxTitleFontSize = 30;
|
||||
const maxTextWidth = width - panelPadding * 2;
|
||||
@ -186,7 +186,7 @@ export function getTitleStyles(layout: LayoutResult) {
|
||||
|
||||
export function getValueStyles(layout: LayoutResult) {
|
||||
const styles: CSSProperties = {
|
||||
fontSize: `${layout.valueFontSize}px`,
|
||||
fontSize: layout.valueFontSize,
|
||||
color: '#EEE',
|
||||
textShadow: '#333 0px 0px 1px',
|
||||
fontWeight: 500,
|
||||
|
@ -0,0 +1,37 @@
|
||||
import React, { FC, CSSProperties } from 'react';
|
||||
import { FormattedValue } from '@grafana/data';
|
||||
|
||||
export interface Props {
|
||||
className?: string;
|
||||
value: FormattedValue;
|
||||
style: CSSProperties;
|
||||
}
|
||||
|
||||
function fontSizeReductionFactor(fontSize: number) {
|
||||
if (fontSize < 20) {
|
||||
return 0.9;
|
||||
}
|
||||
if (fontSize < 26) {
|
||||
return 0.8;
|
||||
}
|
||||
return 0.6;
|
||||
}
|
||||
|
||||
export const FormattedValueDisplay: FC<Props> = ({ value, className, style }) => {
|
||||
const fontSize = style.fontSize as number;
|
||||
const reductionFactor = fontSizeReductionFactor(fontSize);
|
||||
const hasPrefix = (value.prefix ?? '').length > 0;
|
||||
const hasSuffix = (value.suffix ?? '').length > 0;
|
||||
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<div>
|
||||
{hasPrefix && <span>{value.prefix}</span>}
|
||||
<span>{value.text}</span>
|
||||
{hasSuffix && <span style={{ fontSize: fontSize * reductionFactor }}>{value.suffix}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
FormattedValueDisplay.displayName = 'FormattedDisplayValue';
|
@ -1,8 +1,6 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import $ from 'jquery';
|
||||
import { Threshold, DisplayValue } from '@grafana/data';
|
||||
|
||||
import { getColorFromHexRgbOrName } from '@grafana/data';
|
||||
import { Threshold, DisplayValue, getColorFromHexRgbOrName, formattedValueToString } from '@grafana/data';
|
||||
import { Themeable } from '../../types';
|
||||
import { selectThemeVariant } from '../../themes';
|
||||
|
||||
@ -82,7 +80,8 @@ export class Gauge extends PureComponent<Props> {
|
||||
const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1;
|
||||
const gaugeWidth = Math.min(dimension / 5.5, 40) / gaugeWidthReduceRatio;
|
||||
const thresholdMarkersWidth = gaugeWidth / 5;
|
||||
const fontSize = Math.min(dimension / 4, 100) * (value.text !== null ? this.getFontScale(value.text.length) : 1);
|
||||
const text = formattedValueToString(value);
|
||||
const fontSize = Math.min(dimension / 4, 100) * (text !== null ? this.getFontScale(text.length) : 1);
|
||||
|
||||
const thresholdLabelFontSize = fontSize / 2.5;
|
||||
|
||||
@ -114,7 +113,7 @@ export class Gauge extends PureComponent<Props> {
|
||||
value: {
|
||||
color: value.color,
|
||||
formatter: () => {
|
||||
return value.text;
|
||||
return text;
|
||||
},
|
||||
font: { size: fontSize, family: theme.typography.fontFamily.sansSerif },
|
||||
},
|
||||
|
@ -6,7 +6,7 @@ import { SeriesColorChangeHandler } from './GraphWithLegend';
|
||||
import { LegendStatsList } from '../Legend/LegendStatsList';
|
||||
import { ThemeContext } from '../../themes/ThemeContext';
|
||||
import { stylesFactory } from '../../themes';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { GrafanaTheme, formattedValueToString } from '@grafana/data';
|
||||
|
||||
export interface GraphLegendItemProps {
|
||||
key?: React.Key;
|
||||
@ -124,7 +124,7 @@ export const GraphLegendTableRow: React.FunctionComponent<GraphLegendItemProps>
|
||||
item.displayValues.map((stat, index) => {
|
||||
return (
|
||||
<td className={styles.value} key={`${stat.title}-${index}`}>
|
||||
{stat.text}
|
||||
{formattedValueToString(stat)}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { getValueFromDimension, getColumnFromDimension } from '@grafana/data';
|
||||
import { getValueFromDimension, getColumnFromDimension, formattedValueToString } from '@grafana/data';
|
||||
import { SeriesTable } from './SeriesTable';
|
||||
import { GraphTooltipContentProps } from './types';
|
||||
|
||||
@ -15,11 +15,11 @@ export const SingleModeGraphTooltip: React.FC<GraphTooltipContentProps> = ({ dim
|
||||
}
|
||||
const time = getValueFromDimension(dimensions.xAxis, activeDimensions.xAxis[0], activeDimensions.xAxis[1]);
|
||||
const timeField = getColumnFromDimension(dimensions.xAxis, activeDimensions.xAxis[0]);
|
||||
const processedTime = timeField.display ? timeField.display(time).text : time;
|
||||
const processedTime = timeField.display ? formattedValueToString(timeField.display(time)) : time;
|
||||
|
||||
const valueField = getColumnFromDimension(dimensions.yAxis, activeDimensions.yAxis[0]);
|
||||
const value = getValueFromDimension(dimensions.yAxis, activeDimensions.yAxis[0], activeDimensions.yAxis[1]);
|
||||
const processedValue = valueField.display ? valueField.display(value).text : value;
|
||||
const processedValue = valueField.display ? formattedValueToString(valueField.display(value)) : value;
|
||||
|
||||
return (
|
||||
<SeriesTable
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { GraphSeriesValue, Field } from '@grafana/data';
|
||||
import { GraphSeriesValue, Field, formattedValueToString } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* Returns index of the closest datapoint BEFORE hover position
|
||||
@ -72,18 +72,18 @@ export const getMultiSeriesGraphHoverInfo = (
|
||||
(hoverDistance < 0 && hoverDistance > minDistance)
|
||||
) {
|
||||
minDistance = hoverDistance;
|
||||
minTime = time.display ? time.display(pointTime).text : pointTime;
|
||||
minTime = time.display ? formattedValueToString(time.display(pointTime)) : pointTime;
|
||||
}
|
||||
|
||||
value = series.values.get(hoverIndex);
|
||||
|
||||
results.push({
|
||||
value: series.display ? series.display(value).text : value,
|
||||
value: series.display ? formattedValueToString(series.display(value)) : value,
|
||||
datapointIndex: hoverIndex,
|
||||
seriesIndex: i,
|
||||
color: series.config.color,
|
||||
label: series.name,
|
||||
time: time.display ? time.display(pointTime).text : pointTime,
|
||||
time: time.display ? formattedValueToString(time.display(pointTime)) : pointTime,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { InlineList } from '../List/InlineList';
|
||||
import { css } from 'emotion';
|
||||
import { DisplayValue } from '@grafana/data';
|
||||
import { DisplayValue, formattedValueToString } from '@grafana/data';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
|
||||
const LegendItemStat: React.FunctionComponent<{ stat: DisplayValue }> = ({ stat }) => {
|
||||
@ -11,7 +11,7 @@ const LegendItemStat: React.FunctionComponent<{ stat: DisplayValue }> = ({ stat
|
||||
margin-left: 6px;
|
||||
`}
|
||||
>
|
||||
{stat.title && `${capitalize(stat.title)}:`} {stat.text}
|
||||
{stat.title && `${capitalize(stat.title)}:`} {formattedValueToString(stat)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { select, pie, arc, event } from 'd3';
|
||||
import sum from 'lodash/sum';
|
||||
import { DisplayValue, GrafanaThemeType } from '@grafana/data';
|
||||
import { DisplayValue, GrafanaThemeType, formattedValueToString } from '@grafana/data';
|
||||
import { Themeable } from '../../index';
|
||||
import { colors as grafana_colors } from '../../utils/index';
|
||||
|
||||
@ -49,7 +49,7 @@ export class PieChart extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
const data = values.map(datapoint => datapoint.numeric);
|
||||
const names = values.map(datapoint => datapoint.text);
|
||||
const names = values.map(datapoint => formattedValueToString(datapoint));
|
||||
const colors = values.map((p, idx) => {
|
||||
if (p.color) {
|
||||
return p.color;
|
||||
|
@ -50,6 +50,7 @@ export interface CommonProps<T> {
|
||||
onOpenMenu?: () => void;
|
||||
onCloseMenu?: () => void;
|
||||
tabSelectsValue?: boolean;
|
||||
formatCreateLabel?: (input: string) => string;
|
||||
allowCustomValue: boolean;
|
||||
}
|
||||
|
||||
@ -125,6 +126,7 @@ export class Select<T> extends PureComponent<SelectProps<T>> {
|
||||
onCloseMenu,
|
||||
onOpenMenu,
|
||||
allowCustomValue,
|
||||
formatCreateLabel,
|
||||
} = this.props;
|
||||
|
||||
let widthClass = '';
|
||||
@ -137,7 +139,7 @@ export class Select<T> extends PureComponent<SelectProps<T>> {
|
||||
|
||||
if (allowCustomValue) {
|
||||
SelectComponent = Creatable;
|
||||
creatableOptions.formatCreateLabel = (input: string) => input;
|
||||
creatableOptions.formatCreateLabel = formatCreateLabel ?? ((input: string) => input);
|
||||
}
|
||||
|
||||
const selectClassNames = classNames('gf-form-input', 'gf-form-input--form-dropdown', widthClass, className);
|
||||
|
@ -13,7 +13,6 @@ import {
|
||||
VAR_CALC,
|
||||
VAR_CELL_PREFIX,
|
||||
toIntegerOrUndefined,
|
||||
SelectableValue,
|
||||
FieldConfig,
|
||||
toFloatOrUndefined,
|
||||
toNumberString,
|
||||
@ -62,8 +61,8 @@ export const FieldPropertiesEditor: React.FC<Props> = ({ value, onChange, showMi
|
||||
[value.max, onChange]
|
||||
);
|
||||
|
||||
const onUnitChange = (unit: SelectableValue<string>) => {
|
||||
onChange({ ...value, unit: unit.value });
|
||||
const onUnitChange = (unit?: string) => {
|
||||
onChange({ ...value, unit });
|
||||
};
|
||||
|
||||
const commitChanges = useCallback(() => {
|
||||
@ -102,7 +101,7 @@ export const FieldPropertiesEditor: React.FC<Props> = ({ value, onChange, showMi
|
||||
|
||||
<div className="gf-form">
|
||||
<FormLabel width={labelWidth}>Unit</FormLabel>
|
||||
<UnitPicker defaultValue={unit} onChange={onUnitChange} />
|
||||
<UnitPicker value={unit} onChange={onUnitChange} />
|
||||
</div>
|
||||
{showMinMax && (
|
||||
<>
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
ValueFormatter,
|
||||
getColorFromHexRgbOrName,
|
||||
InterpolateFunction,
|
||||
formattedValueToString,
|
||||
} from '@grafana/data';
|
||||
|
||||
export interface TableCellBuilderOptions {
|
||||
@ -316,7 +317,7 @@ export function getFieldCellBuilder(field: Field, style: ColumnStyle | null, p:
|
||||
|
||||
return (
|
||||
<div style={style} className={clazz} title={disp.title}>
|
||||
{disp.text}
|
||||
{formattedValueToString(disp)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -2,21 +2,29 @@ import React, { PureComponent } from 'react';
|
||||
|
||||
import { Select } from '../Select/Select';
|
||||
|
||||
import { getValueFormats } from '@grafana/data';
|
||||
import { getValueFormats, SelectableValue } from '@grafana/data';
|
||||
|
||||
interface Props {
|
||||
onChange: (item: any) => void;
|
||||
defaultValue?: string;
|
||||
onChange: (item?: string) => void;
|
||||
value?: string;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
function formatCreateLabel(input: string) {
|
||||
return `Unit suffix: ${input}`;
|
||||
}
|
||||
|
||||
export class UnitPicker extends PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
width: 12,
|
||||
};
|
||||
|
||||
onChange = (value: SelectableValue<string>) => {
|
||||
this.props.onChange(value.value);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { defaultValue, onChange, width } = this.props;
|
||||
const { value, width } = this.props;
|
||||
|
||||
const unitGroups = getValueFormats();
|
||||
|
||||
@ -35,18 +43,20 @@ export class UnitPicker extends PureComponent<Props> {
|
||||
};
|
||||
});
|
||||
|
||||
const value = groupOptions.map(group => {
|
||||
return group.options.find(option => option.value === defaultValue);
|
||||
const valueOption = groupOptions.map(group => {
|
||||
return group.options.find(option => option.value === value);
|
||||
});
|
||||
|
||||
return (
|
||||
<Select
|
||||
width={width}
|
||||
defaultValue={value}
|
||||
defaultValue={valueOption}
|
||||
isSearchable={true}
|
||||
allowCustomValue={true}
|
||||
formatCreateLabel={formatCreateLabel}
|
||||
options={groupOptions}
|
||||
placeholder="Choose"
|
||||
onChange={onChange}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
ColorPicker,
|
||||
SeriesColorPickerPopoverWithTheme,
|
||||
SecretFormField,
|
||||
UnitPicker,
|
||||
DataLinksEditor,
|
||||
DataSourceHttpSettings,
|
||||
} from '@grafana/ui';
|
||||
@ -61,6 +62,11 @@ export function registerAngularDirectives() {
|
||||
'onColorChange',
|
||||
'onToggleAxis',
|
||||
]);
|
||||
react2AngularDirective('unitPicker', UnitPicker, [
|
||||
'value',
|
||||
'width',
|
||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
react2AngularDirective('metricSelect', MetricSelect, [
|
||||
'options',
|
||||
'onChange',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { getFlotTickDecimals } from 'app/core/utils/ticks';
|
||||
import _ from 'lodash';
|
||||
import { getValueFormat, ValueFormatter, stringToJsRegex, DecimalCount } from '@grafana/data';
|
||||
import { getValueFormat, ValueFormatter, stringToJsRegex, DecimalCount, formattedValueToString } from '@grafana/data';
|
||||
|
||||
function matchSeriesOverride(aliasOrRegex: string, seriesAlias: string) {
|
||||
if (!aliasOrRegex) {
|
||||
@ -339,7 +339,7 @@ export default class TimeSeries {
|
||||
if (!_.isFinite(value)) {
|
||||
value = null; // Prevent NaN formatting
|
||||
}
|
||||
return this.valueFormater(value, this.decimals, this.scaledDecimals);
|
||||
return formattedValueToString(this.valueFormater(value, this.decimals, this.scaledDecimals));
|
||||
}
|
||||
|
||||
isMsResolutionNeeded() {
|
||||
|
52
public/app/core/utils/kbn.test.ts
Normal file
52
public/app/core/utils/kbn.test.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import kbn from './kbn';
|
||||
import { DecimalCount, TimeZone } from '@grafana/data';
|
||||
|
||||
interface ValueFormatTest {
|
||||
id: string;
|
||||
decimals?: DecimalCount;
|
||||
scaledDecimals?: DecimalCount;
|
||||
timeZone?: TimeZone;
|
||||
value: number;
|
||||
result: string;
|
||||
}
|
||||
|
||||
const formatTests: ValueFormatTest[] = [
|
||||
// Currancy
|
||||
{ id: 'currencyUSD', decimals: 2, value: 1532.82, result: '$1.53K' },
|
||||
{ id: 'currencyKRW', decimals: 2, value: 1532.82, result: '₩1.53K' },
|
||||
|
||||
// Typical
|
||||
{ id: 'ms', decimals: 4, value: 0.0024, result: '0.0024 ms' },
|
||||
{ id: 'ms', decimals: 0, value: 100, result: '100 ms' },
|
||||
{ id: 'ms', decimals: 2, value: 1250, result: '1.25 s' },
|
||||
{ id: 'ms', decimals: 1, value: 10000086.123, result: '2.8 hour' },
|
||||
{ id: 'ms', decimals: 0, value: 1200, result: '1 s' },
|
||||
{ id: 'short', decimals: 0, scaledDecimals: -1, value: 98765, result: '98.77 K' },
|
||||
{ id: 'short', decimals: 0, scaledDecimals: 0, value: 9876543, result: '9.876543 Mil' },
|
||||
{ id: 'kbytes', decimals: 3, value: 10000000, result: '9.537 GiB' },
|
||||
{ id: 'deckbytes', decimals: 3, value: 10000000, result: '10.000 GB' },
|
||||
{ id: 'megwatt', decimals: 3, value: 1000, result: '1.000 GW' },
|
||||
{ id: 'kohm', decimals: 3, value: 1000, result: '1.000 MΩ' },
|
||||
{ id: 'Mohm', decimals: 3, value: 1000, result: '1.000 GΩ' },
|
||||
|
||||
{ id: 'farad', decimals: 3, value: 1000, result: '1.000 kF' },
|
||||
{ id: 'µfarad', decimals: 3, value: 1000, result: '1.000 mF' },
|
||||
{ id: 'nfarad', decimals: 3, value: 1000, result: '1.000 µF' },
|
||||
{ id: 'pfarad', decimals: 3, value: 1000, result: '1.000 nF' },
|
||||
{ id: 'ffarad', decimals: 3, value: 1000, result: '1.000 pF' },
|
||||
|
||||
{ id: 'henry', decimals: 3, value: 1000, result: '1.000 kH' },
|
||||
{ id: 'mhenry', decimals: 3, value: 1000, result: '1.000 H' },
|
||||
{ id: 'µhenry', decimals: 3, value: 1000, result: '1.000 mH' },
|
||||
];
|
||||
|
||||
describe('Chcek KBN value formats', () => {
|
||||
for (const test of formatTests) {
|
||||
describe(`value format: ${test.id}`, () => {
|
||||
it(`should translate ${test.value} as ${test.result}`, () => {
|
||||
const result = kbn.valueFormats[test.id](test.value, test.decimals, test.scaledDecimals);
|
||||
expect(result).toBe(test.result);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
@ -6,6 +6,8 @@ import {
|
||||
stringToJsRegex,
|
||||
TimeRange,
|
||||
deprecationWarning,
|
||||
DecimalCount,
|
||||
formattedValueToString,
|
||||
} from '@grafana/data';
|
||||
|
||||
const kbn: any = {};
|
||||
@ -308,7 +310,10 @@ if (typeof Proxy !== 'undefined') {
|
||||
|
||||
const formatter = getValueFormat(name);
|
||||
if (formatter) {
|
||||
return formatter;
|
||||
// Return the results as a simple string
|
||||
return (value: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount, isUtc?: boolean) => {
|
||||
return formattedValueToString(formatter(value, decimals, scaledDecimals, isUtc));
|
||||
};
|
||||
}
|
||||
|
||||
// default to look here
|
||||
|
@ -9,7 +9,7 @@
|
||||
<div ng-if="yaxis.show">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Unit</label>
|
||||
<div class="gf-form-dropdown-typeahead max-width-20" ng-model="yaxis.format" dropdown-typeahead2="ctrl.unitFormats" dropdown-typeahead-on-select="ctrl.setUnitFormat(yaxis, $subItem)"></div>
|
||||
<unit-picker onChange="ctrl.setUnitFormat(yaxis)" value="yaxis.format" width="20" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -49,9 +49,11 @@ export class AxesEditorCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
setUnitFormat(axis: { format: any }, subItem: { value: any }) {
|
||||
axis.format = subItem.value;
|
||||
this.panelCtrl.render();
|
||||
setUnitFormat(axis: { format: any }) {
|
||||
return (unit: string) => {
|
||||
axis.format = unit;
|
||||
this.panelCtrl.render();
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -35,6 +35,7 @@ import {
|
||||
getDisplayProcessor,
|
||||
getFlotPairsConstant,
|
||||
PanelEvents,
|
||||
formattedValueToString,
|
||||
} from '@grafana/data';
|
||||
import { GraphContextMenuCtrl } from './GraphContextMenuCtrl';
|
||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||
@ -862,7 +863,7 @@ class GraphElement {
|
||||
if (!formatter) {
|
||||
throw new Error(`Unit '${format}' is not supported`);
|
||||
}
|
||||
return formatter(val, axis.tickDecimals, axis.scaledDecimals);
|
||||
return formattedValueToString(formatter(val, axis.tickDecimals, axis.scaledDecimals));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ export const getGraphSeriesModel = (
|
||||
|
||||
const displayProcessor = getDisplayProcessor({
|
||||
config: {
|
||||
unit: fieldOptions?.defaults?.unit,
|
||||
decimals: legendOptions.decimals,
|
||||
},
|
||||
});
|
||||
@ -68,7 +69,6 @@ export const getGraphSeriesModel = (
|
||||
|
||||
return {
|
||||
...statDisplayValue,
|
||||
text: statDisplayValue.text,
|
||||
title: stat,
|
||||
};
|
||||
});
|
||||
@ -104,7 +104,7 @@ export const getGraphSeriesModel = (
|
||||
type: timeField.type,
|
||||
isUtc: timeZone === 'utc',
|
||||
config: {
|
||||
dateDisplayFormat: useMsDateFormat ? MS_DATE_TIME_FORMAT : DEFAULT_DATE_TIME_FORMAT,
|
||||
unit: `time:${useMsDateFormat ? MS_DATE_TIME_FORMAT : DEFAULT_DATE_TIME_FORMAT}`,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
GrafanaThemeType,
|
||||
stringToJsRegex,
|
||||
ScopedVars,
|
||||
formattedValueToString,
|
||||
} from '@grafana/data';
|
||||
import { ColumnStyle } from '@grafana/ui/src/components/Table/TableCellBuilder';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
@ -188,7 +189,7 @@ export class TableRenderer {
|
||||
}
|
||||
|
||||
this.setColorState(v, column.style);
|
||||
return valueFormatter(v, column.style.decimals, null);
|
||||
return formattedValueToString(valueFormatter(v, column.style.decimals, null));
|
||||
};
|
||||
}
|
||||
|
||||
@ -226,7 +227,11 @@ export class TableRenderer {
|
||||
}
|
||||
|
||||
formatColumnValue(colIndex: number, value: any) {
|
||||
return this.formatters[colIndex] ? this.formatters[colIndex](value) : value;
|
||||
const fmt = this.formatters[colIndex];
|
||||
if (fmt) {
|
||||
return fmt(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
renderCell(columnIndex: number, rowIndex: number, value: any, addWidthHack = false) {
|
||||
|
Loading…
Reference in New Issue
Block a user