Skip to content
This repository has been archived by the owner on Dec 10, 2021. It is now read-only.

Add SMART_NUMBER formatter and make it default #109

Merged
merged 6 commits into from
Feb 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 27 additions & 22 deletions packages/superset-ui-number-format/src/NumberFormats.ts
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
});
}
Original file line number Diff line number Diff line change
@@ -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');
Expand Down
Original file line number Diff line number Diff line change
@@ -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');
});
});
});
});