Skip to content

Commit 8008606

Browse files
authored
Add @superset-ui/number-format package (#31)
* feat: Add number-format package
1 parent 367c841 commit 8008606

16 files changed

+498
-1
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ applications that leverage a Superset backend :chart_with_upwards_trend:
1515
| [@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) |
1616
| [@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) |
1717
| [@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) |
18+
| [@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) |
19+
| [@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) |
1820
| [@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) |
1921

2022
#### Coming :soon:
2123

2224
- Data providers
2325
- Embeddable charts
2426
- Chart collections
25-
- Demo storybook package
2627

2728
### Development
2829

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
## @superset-ui/number-format
2+
3+
[![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)
4+
[![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)
5+
6+
Description
7+
8+
#### Example usage
9+
10+
Functions `getNumberFormatter` and `formatNumber` should be used instead of calling `d3.format` directly.
11+
12+
```js
13+
import { getNumberFormatter } from '@superset-ui/number-format';
14+
const formatter = getNumberFormatter('.2f');
15+
console.log(formatter(1000));
16+
```
17+
18+
or
19+
20+
```js
21+
import { formatNumber } from '@superset-ui/number-format';
22+
console.log(formatNumber('.2f', 1000));
23+
```
24+
25+
It is powered by a registry to support registration of custom formatting, with fallback to `d3.format` and handle error for invalid format string.
26+
27+
```js
28+
import { getNumberFormatterRegistry, formatNumber, NumberFormatter } from '@superset-ui/number-format';
29+
30+
getNumberFormatterRegistry().registerValue('my_format', new NumberFormatter({
31+
id: 'my_format',
32+
formatFunc: v => `my special format of ${v}`
33+
});
34+
35+
console.log(formatNumber('my_format', 1000));
36+
// prints 'my special format of 1000'
37+
```
38+
39+
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).
40+
41+
```js
42+
import { NumberFormats } from '@superset-ui-number-format';
43+
44+
NumberFormats.PERCENT // ,.2%
45+
NumberFormats.PERCENT_3_POINT // ,.3%
46+
```
47+
48+
#### API
49+
50+
`fn(args)`
51+
52+
- Do something
53+
54+
### Development
55+
56+
`@data-ui/build-config` is used to manage the build configuration for this package including babel
57+
builds, jest testing, eslint, and prettier.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "@superset-ui/number-format",
3+
"version": "0.0.0",
4+
"description": "Superset UI number format",
5+
"sideEffects": false,
6+
"main": "lib/index.js",
7+
"module": "esm/index.js",
8+
"files": [
9+
"esm",
10+
"lib"
11+
],
12+
"repository": {
13+
"type": "git",
14+
"url": "git+https://github.com/apache-superset/superset-ui.git"
15+
},
16+
"keywords": [
17+
"superset"
18+
],
19+
"author": "Superset",
20+
"license": "Apache-2.0",
21+
"bugs": {
22+
"url": "https://github.com/apache-superset/superset-ui/issues"
23+
},
24+
"homepage": "https://github.com/apache-superset/superset-ui#readme",
25+
"publishConfig": {
26+
"access": "public"
27+
},
28+
"dependencies": {
29+
"@superset-ui/core": "^0.6.0",
30+
"d3-format": "^1.3.2",
31+
"lodash": "^4.17.11"
32+
}
33+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export const DOLLAR = '$,.2f';
2+
export const DOLLAR_CHANGE = '+$,.2f';
3+
export const DOLLAR_ROUND = '$,d';
4+
export const DOLLAR_ROUND_CHANGE = '+$,d';
5+
6+
export const FLOAT_1_POINT = ',.1f';
7+
export const FLOAT_2_POINT = ',.2f';
8+
export const FLOAT_3_POINT = ',.3f';
9+
export const FLOAT = FLOAT_2_POINT;
10+
11+
export const FLOAT_CHANGE_1_POINT = '+,.1f';
12+
export const FLOAT_CHANGE_2_POINT = '+,.2f';
13+
export const FLOAT_CHANGE_3_POINT = '+,.3f';
14+
export const FLOAT_CHANGE = FLOAT_CHANGE_2_POINT;
15+
16+
export const INTEGER = ',d';
17+
export const INTEGER_CHANGE = '+,d';
18+
19+
export const PERCENT_1_POINT = ',.1%';
20+
export const PERCENT_2_POINT = ',.2%';
21+
export const PERCENT_3_POINT = ',.3%';
22+
export const PERCENT = PERCENT_2_POINT;
23+
24+
export const PERCENT_CHANGE_1_POINT = '+,.1%';
25+
export const PERCENT_CHANGE_2_POINT = '+,.2%';
26+
export const PERCENT_CHANGE_3_POINT = '+,.3%';
27+
export const PERCENT_CHANGE = PERCENT_CHANGE_2_POINT;
28+
29+
export const SI_1_DIGIT = '.1s';
30+
export const SI_2_DIGIT = '.2s';
31+
export const SI_3_DIGIT = '.3s';
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { ExtensibleFunction, isRequired } from '@superset-ui/core';
2+
3+
export const PREVIEW_VALUE = 12345.432;
4+
5+
export default class NumberFormatter extends ExtensibleFunction {
6+
constructor({
7+
id = isRequired('config.id'),
8+
label,
9+
description = '',
10+
formatFunc = isRequired('config.formatFunc'),
11+
} = {}) {
12+
super((...args) => this.format(...args));
13+
14+
this.id = id;
15+
this.label = label || id;
16+
this.description = description;
17+
this.formatFunc = formatFunc;
18+
}
19+
20+
format(value) {
21+
if (value === null || value === undefined || Number.isNaN(value)) {
22+
return value;
23+
} else if (value === Number.POSITIVE_INFINITY) {
24+
return '∞';
25+
} else if (value === Number.NEGATIVE_INFINITY) {
26+
return '-∞';
27+
}
28+
29+
return this.formatFunc(value);
30+
}
31+
32+
preview(value = PREVIEW_VALUE) {
33+
return `${value} => ${this.format(value)}`;
34+
}
35+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { RegistryWithDefaultKey } from '@superset-ui/core';
2+
import D3Formatter from './formatters/D3Formatter';
3+
import { SI_3_DIGIT } from './NumberFormats';
4+
5+
const DEFAULT_FORMAT = SI_3_DIGIT;
6+
7+
export default class NumberFormatterRegistry extends RegistryWithDefaultKey {
8+
constructor() {
9+
super({
10+
initialDefaultKey: DEFAULT_FORMAT,
11+
name: 'NumberFormatter',
12+
});
13+
}
14+
15+
get(formatterId) {
16+
const targetFormat = formatterId || this.defaultKey;
17+
18+
if (this.has(targetFormat)) {
19+
return super.get(targetFormat);
20+
}
21+
22+
// Create new formatter if does not exist
23+
const formatter = new D3Formatter(targetFormat);
24+
this.registerValue(targetFormat, formatter);
25+
26+
return formatter;
27+
}
28+
29+
format(formatterId, value) {
30+
return this.get(formatterId)(value);
31+
}
32+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { makeSingleton } from '@superset-ui/core';
2+
import NumberFormatterRegistry from './NumberFormatterRegistry';
3+
4+
const getInstance = makeSingleton(NumberFormatterRegistry);
5+
6+
export default getInstance;
7+
8+
export function getNumberFormatter(format) {
9+
return getInstance().get(format);
10+
}
11+
12+
export function formatNumber(format, value) {
13+
return getInstance().format(format, value);
14+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import isString from 'lodash/isString';
2+
import { format as d3Format } from 'd3-format';
3+
import { isRequired } from '@superset-ui/core';
4+
import NumberFormatter from '../NumberFormatter';
5+
6+
export default class D3Formatter extends NumberFormatter {
7+
/**
8+
* Pass only the D3 format string to constructor
9+
*
10+
* new D3Formatter('.2f');
11+
*
12+
* or accompany it with human-readable label and description
13+
*
14+
* new D3Formatter({
15+
* id: '.2f',
16+
* label: 'Float with 2 decimal points',
17+
* description: 'lorem ipsum dolor sit amet',
18+
* });
19+
*
20+
* @param {String|Object} configOrFormatString
21+
*/
22+
constructor(configOrFormatString = isRequired('configOrFormatString')) {
23+
const config = isString(configOrFormatString)
24+
? { id: configOrFormatString }
25+
: configOrFormatString;
26+
27+
let formatFunc;
28+
let isInvalid = false;
29+
30+
try {
31+
formatFunc = d3Format(config.id);
32+
} catch (e) {
33+
formatFunc = () => `Invalid format: ${config.id}`;
34+
isInvalid = true;
35+
}
36+
37+
super({ ...config, formatFunc });
38+
this.isInvalid = isInvalid;
39+
}
40+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { format as d3Format } from 'd3-format';
2+
import NumberFormatter from '../NumberFormatter';
3+
4+
export default class SiAtMostNDigitFormatter extends NumberFormatter {
5+
constructor(n = 3) {
6+
const siFormatter = d3Format(`.${n}s`);
7+
8+
super({
9+
formatFunc: value => {
10+
const si = siFormatter(value);
11+
12+
// Removing trailing `.00` if any
13+
return si.slice(-1) < 'A' ? parseFloat(si).toString() : si;
14+
},
15+
id: `si_at_most_${n}_digit`,
16+
label: `SI with at most ${n} significant digits`,
17+
});
18+
}
19+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as NumberFormats from './NumberFormats';
2+
3+
export {
4+
default as getNumberFormatterRegistry,
5+
formatNumber,
6+
getNumberFormatter,
7+
} from './NumberFormatterRegistrySingleton';
8+
9+
export { default as NumberFormatter, PREVIEW_VALUE } from './NumberFormatter';
10+
export { NumberFormats };

0 commit comments

Comments
 (0)