diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/src/NumberFormats.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/src/NumberFormats.ts index c7d29dac376b..c5a181fc6753 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/src/NumberFormats.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/src/NumberFormats.ts @@ -1,63 +1,68 @@ const DOLLAR = '$,.2f'; -const DOLLAR_CHANGE = '+$,.2f'; +const DOLLAR_SIGNED = '+$,.2f'; const DOLLAR_ROUND = '$,d'; -const DOLLAR_ROUND_CHANGE = '+$,d'; +const DOLLAR_ROUND_SIGNED = '+$,d'; const FLOAT_1_POINT = ',.1f'; const FLOAT_2_POINT = ',.2f'; const FLOAT_3_POINT = ',.3f'; const FLOAT = FLOAT_2_POINT; -const FLOAT_CHANGE_1_POINT = '+,.1f'; -const FLOAT_CHANGE_2_POINT = '+,.2f'; -const FLOAT_CHANGE_3_POINT = '+,.3f'; -const FLOAT_CHANGE = FLOAT_CHANGE_2_POINT; +const FLOAT_SIGNED_1_POINT = '+,.1f'; +const FLOAT_SIGNED_2_POINT = '+,.2f'; +const FLOAT_SIGNED_3_POINT = '+,.3f'; +const FLOAT_SIGNED = FLOAT_SIGNED_2_POINT; const INTEGER = ',d'; -const INTEGER_CHANGE = '+,d'; +const INTEGER_SIGNED = '+,d'; const PERCENT_1_POINT = ',.1%'; const PERCENT_2_POINT = ',.2%'; const PERCENT_3_POINT = ',.3%'; const PERCENT = PERCENT_2_POINT; -const PERCENT_CHANGE_1_POINT = '+,.1%'; -const PERCENT_CHANGE_2_POINT = '+,.2%'; -const PERCENT_CHANGE_3_POINT = '+,.3%'; -const PERCENT_CHANGE = PERCENT_CHANGE_2_POINT; +const PERCENT_SIGNED_1_POINT = '+,.1%'; +const PERCENT_SIGNED_2_POINT = '+,.2%'; +const PERCENT_SIGNED_3_POINT = '+,.3%'; +const PERCENT_SIGNED = PERCENT_SIGNED_2_POINT; const SI_1_DIGIT = '.1s'; const SI_2_DIGIT = '.2s'; const SI_3_DIGIT = '.3s'; const SI = SI_3_DIGIT; +const SMART_NUMBER = 'SMART_NUMBER'; +const SMART_NUMBER_SIGNED = 'SMART_NUMBER_SIGNED'; + const NumberFormats = { DOLLAR, - DOLLAR_CHANGE, DOLLAR_ROUND, - DOLLAR_ROUND_CHANGE, + DOLLAR_ROUND_SIGNED, + DOLLAR_SIGNED, FLOAT, FLOAT_1_POINT, FLOAT_2_POINT, FLOAT_3_POINT, - FLOAT_CHANGE, - FLOAT_CHANGE_1_POINT, - FLOAT_CHANGE_2_POINT, - FLOAT_CHANGE_3_POINT, + FLOAT_SIGNED, + FLOAT_SIGNED_1_POINT, + FLOAT_SIGNED_2_POINT, + FLOAT_SIGNED_3_POINT, INTEGER, - INTEGER_CHANGE, + INTEGER_SIGNED, PERCENT, PERCENT_1_POINT, PERCENT_2_POINT, PERCENT_3_POINT, - PERCENT_CHANGE, - PERCENT_CHANGE_1_POINT, - PERCENT_CHANGE_2_POINT, - PERCENT_CHANGE_3_POINT, + PERCENT_SIGNED, + PERCENT_SIGNED_1_POINT, + PERCENT_SIGNED_2_POINT, + PERCENT_SIGNED_3_POINT, SI, SI_1_DIGIT, SI_2_DIGIT, SI_3_DIGIT, + SMART_NUMBER, + SMART_NUMBER_SIGNED, }; export default NumberFormats; diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/src/NumberFormatterRegistry.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/src/NumberFormatterRegistry.ts index b60a359b73f2..96237fa05191 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/src/NumberFormatterRegistry.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/src/NumberFormatterRegistry.ts @@ -1,5 +1,6 @@ import { RegistryWithDefaultKey, OverwritePolicy } from '@superset-ui/core'; import createD3NumberFormatter from './factories/createD3NumberFormatter'; +import createSmartNumberFormatter from './factories/createSmartNumberFormatter'; import NumberFormats from './NumberFormats'; import NumberFormatter from './NumberFormatter'; @@ -9,10 +10,16 @@ export default class NumberFormatterRegistry extends RegistryWithDefaultKey< > { constructor() { super({ - initialDefaultKey: NumberFormats.SI, name: 'NumberFormatter', overwritePolicy: OverwritePolicy.WARN, }); + + this.registerValue(NumberFormats.SMART_NUMBER, createSmartNumberFormatter()); + this.registerValue( + NumberFormats.SMART_NUMBER_SIGNED, + createSmartNumberFormatter({ signed: true }), + ); + this.setDefaultKey(NumberFormats.SMART_NUMBER); } get(formatterId?: string) { diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/src/factories/createSmartNumberFormatter.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/src/factories/createSmartNumberFormatter.ts new file mode 100644 index 000000000000..064640ffd335 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/src/factories/createSmartNumberFormatter.ts @@ -0,0 +1,48 @@ +/* eslint-disable no-magic-numbers */ + +import { format as d3Format } from 'd3-format'; +import NumberFormatter from '../NumberFormatter'; +import NumberFormats from '../NumberFormats'; + +const siFormatter = d3Format(`.3~s`); +const float2PointFormatter = d3Format(`.2~f`); +const float4PointFormatter = d3Format(`.4~f`); + +export default function createSmartNumberFormatter( + config: { + description?: string; + signed?: boolean; + id?: string; + label?: string; + } = {}, +) { + const { description, signed = false, id, label } = config; + const getSign = signed ? (value: number) => (value > 0 ? '+' : '') : () => ''; + + function formatValue(value: number) { + if (value === 0) { + return '0'; + } + const absoluteValue = Math.abs(value); + if (absoluteValue >= 1000) { + // Normal human being are more familiar + // with billion (B) that giga (G) + return siFormatter(value).replace('G', 'B'); + } else if (absoluteValue >= 1) { + return float2PointFormatter(value); + } else if (absoluteValue >= 0.001) { + return float4PointFormatter(value); + } else if (absoluteValue > 0.000001) { + return `${siFormatter(value * 1000000)}µ`; + } + + return siFormatter(value); + } + + return new NumberFormatter({ + description, + formatFunc: value => `${getSign(value)}${formatValue(value)}`, + id: id || signed ? NumberFormats.SMART_NUMBER_SIGNED : NumberFormats.SMART_NUMBER, + label: label || 'Adaptive formatter', + }); +} diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/test/NumberFormatterRegistry.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/test/NumberFormatterRegistry.test.ts index f0956d53fe0d..140e1840925c 100644 --- a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/test/NumberFormatterRegistry.test.ts +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/test/NumberFormatterRegistry.test.ts @@ -1,11 +1,15 @@ import NumberFormatterRegistry from '../src/NumberFormatterRegistry'; import NumberFormatter from '../src/NumberFormatter'; +import { NumberFormats } from '../src'; describe('NumberFormatterRegistry', () => { let registry: NumberFormatterRegistry; beforeEach(() => { registry = new NumberFormatterRegistry(); }); + it('has SMART_NUMBER as default formatter out of the box', () => { + expect(registry.getDefaultKey()).toBe(NumberFormats.SMART_NUMBER); + }); describe('.get(format)', () => { it('creates and returns a new formatter if does not exist', () => { const formatter = registry.get('.2f'); diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/test/factories/createSmartNumberFormatter.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/test/factories/createSmartNumberFormatter.test.ts new file mode 100644 index 000000000000..681b92c1c802 --- /dev/null +++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-number-format/test/factories/createSmartNumberFormatter.test.ts @@ -0,0 +1,130 @@ +import NumberFormatter from '../../src/NumberFormatter'; +import createSmartNumberFormatter from '../../src/factories/createSmartNumberFormatter'; + +describe('createSmartNumberFormatter(options)', () => { + it('creates an instance of NumberFormatter', () => { + const formatter = createSmartNumberFormatter(); + expect(formatter).toBeInstanceOf(NumberFormatter); + }); + describe('using default options', () => { + const formatter = createSmartNumberFormatter(); + it('formats 0 correctly', () => { + expect(formatter(0)).toBe('0'); + }); + describe('for positive numbers', () => { + it('formats billion with B in stead of G', () => { + expect(formatter(1000000000)).toBe('1B'); + expect(formatter(4560000000)).toBe('4.56B'); + }); + it('formats numbers that are >= 1,000 & <= 1,000,000,000 as SI format with precision 3', () => { + expect(formatter(1000)).toBe('1k'); + expect(formatter(10001)).toBe('10k'); + expect(formatter(10100)).toBe('10.1k'); + expect(formatter(111000000)).toBe('111M'); + }); + it('formats number that are >= 1 & < 1,000 as integer or float with at most 2 decimal points', () => { + expect(formatter(1)).toBe('1'); + expect(formatter(1.0)).toBe('1'); + expect(formatter(10)).toBe('10'); + expect(formatter(10.0)).toBe('10'); + expect(formatter(10.23432)).toBe('10.23'); + expect(formatter(274.2856)).toBe('274.29'); + expect(formatter(999)).toBe('999'); + }); + it('formats numbers that are < 1 & >= 0.001 as float with at most 4 decimal points', () => { + expect(formatter(0.1)).toBe('0.1'); + expect(formatter(0.23)).toBe('0.23'); + expect(formatter(0.699)).toBe('0.699'); + expect(formatter(0.0023)).toBe('0.0023'); + expect(formatter(0.002300001)).toBe('0.0023'); + }); + it('formats numbers that are < 0.001 & >= 0.000001 as micron', () => { + expect(formatter(0.0002300001)).toBe('230µ'); + expect(formatter(0.000023)).toBe('23µ'); + expect(formatter(0.000001)).toBe('1µ'); + }); + it('formats numbers that are less than 0.000001 as SI format with precision 3', () => { + expect(formatter(0.0000001)).toBe('100n'); + }); + }); + describe('for negative numbers', () => { + it('formats billion with B in stead of G', () => { + expect(formatter(-1000000000)).toBe('-1B'); + expect(formatter(-4560000000)).toBe('-4.56B'); + }); + it('formats numbers that are >= 1,000 & <= 1,000,000,000 as SI format with precision 3', () => { + expect(formatter(-1000)).toBe('-1k'); + expect(formatter(-10001)).toBe('-10k'); + expect(formatter(-10100)).toBe('-10.1k'); + expect(formatter(-111000000)).toBe('-111M'); + }); + it('formats number that are >= 1 & < 1,000 as integer or float with at most 2 decimal points', () => { + expect(formatter(-1)).toBe('-1'); + expect(formatter(-1.0)).toBe('-1'); + expect(formatter(-10)).toBe('-10'); + expect(formatter(-10.0)).toBe('-10'); + expect(formatter(-10.23432)).toBe('-10.23'); + expect(formatter(-274.2856)).toBe('-274.29'); + expect(formatter(-999)).toBe('-999'); + }); + it('formats numbers that are < 1 & >= 0.001 as float with at most 4 decimal points', () => { + expect(formatter(-0.1)).toBe('-0.1'); + expect(formatter(-0.23)).toBe('-0.23'); + expect(formatter(-0.699)).toBe('-0.699'); + expect(formatter(-0.0023)).toBe('-0.0023'); + expect(formatter(-0.002300001)).toBe('-0.0023'); + }); + it('formats numbers that are < 0.001 & >= 0.000001 as micron', () => { + expect(formatter(-0.0002300001)).toBe('-230µ'); + expect(formatter(-0.000023)).toBe('-23µ'); + expect(formatter(-0.000001)).toBe('-1µ'); + }); + it('formats numbers that are less than 0.000001 as SI format with precision 3', () => { + expect(formatter(-0.0000001)).toBe('-100n'); + }); + }); + }); + + describe('when options.signed is true, it adds + for positive numbers', () => { + const formatter = createSmartNumberFormatter({ signed: true }); + it('formats 0 correctly', () => { + expect(formatter(0)).toBe('0'); + }); + describe('for positive numbers', () => { + it('formats billion with B in stead of G', () => { + expect(formatter(1000000000)).toBe('+1B'); + expect(formatter(4560000000)).toBe('+4.56B'); + }); + it('formats numbers that are >= 1,000 & <= 1,000,000,000 as SI format with precision 3', () => { + expect(formatter(1000)).toBe('+1k'); + expect(formatter(10001)).toBe('+10k'); + expect(formatter(10100)).toBe('+10.1k'); + expect(formatter(111000000)).toBe('+111M'); + }); + it('formats number that are >= 1 & < 1,000 as integer or float with at most 2 decimal points', () => { + expect(formatter(1)).toBe('+1'); + expect(formatter(1.0)).toBe('+1'); + expect(formatter(10)).toBe('+10'); + expect(formatter(10.0)).toBe('+10'); + expect(formatter(10.23432)).toBe('+10.23'); + expect(formatter(274.2856)).toBe('+274.29'); + expect(formatter(999)).toBe('+999'); + }); + it('formats numbers that are < 1 & >= 0.001 as float with at most 4 decimal points', () => { + expect(formatter(0.1)).toBe('+0.1'); + expect(formatter(0.23)).toBe('+0.23'); + expect(formatter(0.699)).toBe('+0.699'); + expect(formatter(0.0023)).toBe('+0.0023'); + expect(formatter(0.002300001)).toBe('+0.0023'); + }); + it('formats numbers that are < 0.001 & >= 0.000001 as micron', () => { + expect(formatter(0.0002300001)).toBe('+230µ'); + expect(formatter(0.000023)).toBe('+23µ'); + expect(formatter(0.000001)).toBe('+1µ'); + }); + it('formats numbers that are less than 0.000001 as SI format with precision 3', () => { + expect(formatter(0.0000001)).toBe('+100n'); + }); + }); + }); +});