Skip to content

Commit

Permalink
Add @superset-ui/number-format package (#31)
Browse files Browse the repository at this point in the history
* feat: Add number-format package
  • Loading branch information
kristw authored and zhaoyongjie committed Nov 24, 2021
1 parent 50cb761 commit 3d12b0f
Show file tree
Hide file tree
Showing 16 changed files with 498 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ applications that leverage a Superset backend :chart_with_upwards_trend:
| [@superset-ui/color](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-color) | [![Version](https://img.shields.io/npm/v/@superset-ui/color.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/color.svg?style=flat-square) |
| [@superset-ui/connection](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-connection) | [![Version](https://img.shields.io/npm/v/@superset-ui/connection.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/connection.svg?style=flat-square) |
| [@superset-ui/core](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-core) | [![Version](https://img.shields.io/npm/v/@superset-ui/core.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/core.svg?style=flat-square) |
| [@superset-ui/generator-superset](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-generator-superset) | [![Version](https://img.shields.io/npm/v/@superset-ui/generator-superset.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/generator-superset.svg?style=flat-square) |
| [@superset-ui/number-format](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-number-format) | [![Version](https://img.shields.io/npm/v/@superset-ui/number-format.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/number-format.svg?style=flat-square) |
| [@superset-ui/translation](https://github.com/apache-superset/superset-ui/tree/master/packages/superset-ui-translation) | [![Version](https://img.shields.io/npm/v/@superset-ui/translation.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/translation.svg?style=flat-square) |

#### Coming :soon:

- Data providers
- Embeddable charts
- Chart collections
- Demo storybook package

### Development

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## @superset-ui/number-format

[![Version](https://img.shields.io/npm/v/@superset-ui/number-format.svg?style=flat)](https://img.shields.io/npm/v/@superset-ui/number-format.svg?style=flat)
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui.svg?path=packages%2Fsuperset-ui-number-format&style=flat-square)](https://david-dm.org/apache-superset/superset-ui?path=packages/superset-ui-number-format)

Description

#### Example usage

Functions `getNumberFormatter` and `formatNumber` should be used instead of calling `d3.format` directly.

```js
import { getNumberFormatter } from '@superset-ui/number-format';
const formatter = getNumberFormatter('.2f');
console.log(formatter(1000));
```

or

```js
import { formatNumber } from '@superset-ui/number-format';
console.log(formatNumber('.2f', 1000));
```

It is powered by a registry to support registration of custom formatting, with fallback to `d3.format` and handle error for invalid format string.

```js
import { getNumberFormatterRegistry, formatNumber, NumberFormatter } from '@superset-ui/number-format';

getNumberFormatterRegistry().registerValue('my_format', new NumberFormatter({
id: 'my_format',
formatFunc: v => `my special format of ${v}`
});

console.log(formatNumber('my_format', 1000));
// prints 'my special format of 1000'
```
It also define constants for common d3 formats. See the full list of formats in [NumberFormats.js](https://github.com/apache-superset/superset-ui/blob/master/packages/superset-ui-number-format/src/NumberFormats.js).
```js
import { NumberFormats } from '@superset-ui-number-format';

NumberFormats.PERCENT // ,.2%
NumberFormats.PERCENT_3_POINT // ,.3%
```
#### API
`fn(args)`
- Do something
### Development
`@data-ui/build-config` is used to manage the build configuration for this package including babel
builds, jest testing, eslint, and prettier.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@superset-ui/number-format",
"version": "0.0.0",
"description": "Superset UI number format",
"sideEffects": false,
"main": "lib/index.js",
"module": "esm/index.js",
"files": [
"esm",
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/apache-superset/superset-ui.git"
},
"keywords": [
"superset"
],
"author": "Superset",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/apache-superset/superset-ui/issues"
},
"homepage": "https://github.com/apache-superset/superset-ui#readme",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@superset-ui/core": "^0.6.0",
"d3-format": "^1.3.2",
"lodash": "^4.17.11"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export const DOLLAR = '$,.2f';
export const DOLLAR_CHANGE = '+$,.2f';
export const DOLLAR_ROUND = '$,d';
export const DOLLAR_ROUND_CHANGE = '+$,d';

export const FLOAT_1_POINT = ',.1f';
export const FLOAT_2_POINT = ',.2f';
export const FLOAT_3_POINT = ',.3f';
export const FLOAT = FLOAT_2_POINT;

export const FLOAT_CHANGE_1_POINT = '+,.1f';
export const FLOAT_CHANGE_2_POINT = '+,.2f';
export const FLOAT_CHANGE_3_POINT = '+,.3f';
export const FLOAT_CHANGE = FLOAT_CHANGE_2_POINT;

export const INTEGER = ',d';
export const INTEGER_CHANGE = '+,d';

export const PERCENT_1_POINT = ',.1%';
export const PERCENT_2_POINT = ',.2%';
export const PERCENT_3_POINT = ',.3%';
export const PERCENT = PERCENT_2_POINT;

export const PERCENT_CHANGE_1_POINT = '+,.1%';
export const PERCENT_CHANGE_2_POINT = '+,.2%';
export const PERCENT_CHANGE_3_POINT = '+,.3%';
export const PERCENT_CHANGE = PERCENT_CHANGE_2_POINT;

export const SI_1_DIGIT = '.1s';
export const SI_2_DIGIT = '.2s';
export const SI_3_DIGIT = '.3s';
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ExtensibleFunction, isRequired } from '@superset-ui/core';

export const PREVIEW_VALUE = 12345.432;

export default class NumberFormatter extends ExtensibleFunction {
constructor({
id = isRequired('config.id'),
label,
description = '',
formatFunc = isRequired('config.formatFunc'),
} = {}) {
super((...args) => this.format(...args));

this.id = id;
this.label = label || id;
this.description = description;
this.formatFunc = formatFunc;
}

format(value) {
if (value === null || value === undefined || Number.isNaN(value)) {
return value;
} else if (value === Number.POSITIVE_INFINITY) {
return '∞';
} else if (value === Number.NEGATIVE_INFINITY) {
return '-∞';
}

return this.formatFunc(value);
}

preview(value = PREVIEW_VALUE) {
return `${value} => ${this.format(value)}`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { RegistryWithDefaultKey } from '@superset-ui/core';
import D3Formatter from './formatters/D3Formatter';
import { SI_3_DIGIT } from './NumberFormats';

const DEFAULT_FORMAT = SI_3_DIGIT;

export default class NumberFormatterRegistry extends RegistryWithDefaultKey {
constructor() {
super({
initialDefaultKey: DEFAULT_FORMAT,
name: 'NumberFormatter',
});
}

get(formatterId) {
const targetFormat = formatterId || this.defaultKey;

if (this.has(targetFormat)) {
return super.get(targetFormat);
}

// Create new formatter if does not exist
const formatter = new D3Formatter(targetFormat);
this.registerValue(targetFormat, formatter);

return formatter;
}

format(formatterId, value) {
return this.get(formatterId)(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { makeSingleton } from '@superset-ui/core';
import NumberFormatterRegistry from './NumberFormatterRegistry';

const getInstance = makeSingleton(NumberFormatterRegistry);

export default getInstance;

export function getNumberFormatter(format) {
return getInstance().get(format);
}

export function formatNumber(format, value) {
return getInstance().format(format, value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import isString from 'lodash/isString';
import { format as d3Format } from 'd3-format';
import { isRequired } from '@superset-ui/core';
import NumberFormatter from '../NumberFormatter';

export default class D3Formatter extends NumberFormatter {
/**
* Pass only the D3 format string to constructor
*
* new D3Formatter('.2f');
*
* or accompany it with human-readable label and description
*
* new D3Formatter({
* id: '.2f',
* label: 'Float with 2 decimal points',
* description: 'lorem ipsum dolor sit amet',
* });
*
* @param {String|Object} configOrFormatString
*/
constructor(configOrFormatString = isRequired('configOrFormatString')) {
const config = isString(configOrFormatString)
? { id: configOrFormatString }
: configOrFormatString;

let formatFunc;
let isInvalid = false;

try {
formatFunc = d3Format(config.id);
} catch (e) {
formatFunc = () => `Invalid format: ${config.id}`;
isInvalid = true;
}

super({ ...config, formatFunc });
this.isInvalid = isInvalid;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { format as d3Format } from 'd3-format';
import NumberFormatter from '../NumberFormatter';

export default class SiAtMostNDigitFormatter extends NumberFormatter {
constructor(n = 3) {
const siFormatter = d3Format(`.${n}s`);

super({
formatFunc: value => {
const si = siFormatter(value);

// Removing trailing `.00` if any
return si.slice(-1) < 'A' ? parseFloat(si).toString() : si;
},
id: `si_at_most_${n}_digit`,
label: `SI with at most ${n} significant digits`,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as NumberFormats from './NumberFormats';

export {
default as getNumberFormatterRegistry,
formatNumber,
getNumberFormatter,
} from './NumberFormatterRegistrySingleton';

export { default as NumberFormatter, PREVIEW_VALUE } from './NumberFormatter';
export { NumberFormats };
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import NumberFormatter from '../src/NumberFormatter';

describe('NumberFormatter', () => {
describe('new NumberFormatter(config)', () => {
it('requires config.id', () => {
expect(() => new NumberFormatter()).toThrow();
});
it('requires config.formatFunc', () => {
expect(
() =>
new NumberFormatter({
id: 'my_format',
}),
).toThrow();
});
});
describe('formatter is also a format function itself', () => {
const formatter = new NumberFormatter({
id: 'fixed_3',
formatFunc: value => value.toFixed(3),
});
it('returns formatted value', () => {
expect(formatter(12345.67)).toEqual('12345.670');
});
it('formatter(value) is the same with formatter.format(value)', () => {
const value = 12345.67;
expect(formatter(value)).toEqual(formatter.format(value));
});
});
describe('.format(value)', () => {
const formatter = new NumberFormatter({
id: 'fixed_3',
formatFunc: value => value.toFixed(3),
});
it('handles null', () => {
expect(formatter.format(null)).toBeNull();
});
it('handles undefined', () => {
expect(formatter.format(undefined)).toBeUndefined();
});
it('handles NaN', () => {
expect(formatter.format(NaN)).toBeNaN();
});
it('handles positive and negative infinity', () => {
expect(formatter.format(Number.POSITIVE_INFINITY)).toEqual('∞');
expect(formatter.format(Number.NEGATIVE_INFINITY)).toEqual('-∞');
});
it('otherwise returns formatted value', () => {
expect(formatter.format(12345.67)).toEqual('12345.670');
});
});
describe('.preview(value)', () => {
const formatter = new NumberFormatter({
id: 'fixed_2',
formatFunc: value => value.toFixed(2),
});
it('returns string comparing value before and after formatting', () => {
expect(formatter.preview(100)).toEqual('100 => 100.00');
});
it('uses the default preview value if not specified', () => {
expect(formatter.preview()).toEqual('12345.432 => 12345.43');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import NumberFormatterRegistry from '../src/NumberFormatterRegistry';
import NumberFormatter from '../src/NumberFormatter';

describe('NumberFormatterRegistry', () => {
let registry;
beforeEach(() => {
registry = new NumberFormatterRegistry();
});
describe('.get(format)', () => {
it('creates and returns a new formatter if does not exist', () => {
const formatter = registry.get('.2f');
expect(formatter).toBeInstanceOf(NumberFormatter);
expect(formatter.format(100)).toEqual('100.00');
});
it('returns an existing formatter if already exists', () => {
const formatter = registry.get('.2f');
const formatter2 = registry.get('.2f');
expect(formatter).toBe(formatter2);
});
it('falls back to default format if format is not specified', () => {
registry.setDefaultKey('.1f');
const formatter = registry.get();
expect(formatter.format(100)).toEqual('100.0');
});
});
describe('.format(format, value)', () => {
it('return the value with the specified format', () => {
expect(registry.format('.2f', 100)).toEqual('100.00');
expect(registry.format(',d', 100)).toEqual('100');
});
});
});

0 comments on commit 3d12b0f

Please sign in to comment.