feat: custom d3 number locale (#20075)
This commit is contained in:
parent
f2fc4a03dc
commit
a170ae4368
|
|
@ -50,6 +50,7 @@
|
|||
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
|
||||
"@superset-ui/preset-chart-xy": "file:./plugins/preset-chart-xy",
|
||||
"@superset-ui/switchboard": "file:./packages/superset-ui-switchboard",
|
||||
"@types/d3-format": "^3.0.1",
|
||||
"@visx/axis": "^3.0.1",
|
||||
"@visx/grid": "^3.0.1",
|
||||
"@visx/responsive": "^3.0.0",
|
||||
|
|
@ -5613,6 +5614,11 @@
|
|||
"d3-time-format": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@encodable/format/node_modules/@types/d3-format": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.2.tgz",
|
||||
"integrity": "sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ=="
|
||||
},
|
||||
"node_modules/@encodable/format/node_modules/d3-array": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
|
||||
|
|
@ -20465,9 +20471,9 @@
|
|||
"integrity": "sha512-fYtiVLBYy7VQX+Kx7wU/uOIkGQn8aAEY8oWMoyja3N4dLd8Yf6XgSIR/4yWvMuveNOH5VShnqCgRqqh/UNanBA=="
|
||||
},
|
||||
"node_modules/@types/d3-format": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.2.tgz",
|
||||
"integrity": "sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ=="
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz",
|
||||
"integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg=="
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "1.4.2",
|
||||
|
|
@ -60389,6 +60395,11 @@
|
|||
"react": ">=16.13.1"
|
||||
}
|
||||
},
|
||||
"packages/superset-ui-core/node_modules/@types/d3-format": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.2.tgz",
|
||||
"integrity": "sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ=="
|
||||
},
|
||||
"packages/superset-ui-core/node_modules/@types/d3-time": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
|
||||
|
|
@ -66037,6 +66048,11 @@
|
|||
"d3-time-format": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/d3-format": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.2.tgz",
|
||||
"integrity": "sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ=="
|
||||
},
|
||||
"d3-array": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
|
||||
|
|
@ -76715,6 +76731,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@types/d3-format": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.2.tgz",
|
||||
"integrity": "sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ=="
|
||||
},
|
||||
"@types/d3-time": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
|
||||
|
|
@ -78303,9 +78324,9 @@
|
|||
"integrity": "sha512-fYtiVLBYy7VQX+Kx7wU/uOIkGQn8aAEY8oWMoyja3N4dLd8Yf6XgSIR/4yWvMuveNOH5VShnqCgRqqh/UNanBA=="
|
||||
},
|
||||
"@types/d3-format": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.2.tgz",
|
||||
"integrity": "sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ=="
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz",
|
||||
"integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg=="
|
||||
},
|
||||
"@types/d3-interpolate": {
|
||||
"version": "1.4.2",
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@
|
|||
"@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud",
|
||||
"@superset-ui/preset-chart-xy": "file:./plugins/preset-chart-xy",
|
||||
"@superset-ui/switchboard": "file:./packages/superset-ui-switchboard",
|
||||
"@types/d3-format": "^3.0.1",
|
||||
"@visx/axis": "^3.0.1",
|
||||
"@visx/grid": "^3.0.1",
|
||||
"@visx/responsive": "^3.0.0",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,12 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { t, smartDateFormatter, NumberFormats } from '@superset-ui/core';
|
||||
import {
|
||||
t,
|
||||
smartDateFormatter,
|
||||
NumberFormats,
|
||||
getNumberFormatter,
|
||||
} from '@superset-ui/core';
|
||||
|
||||
// D3 specific formatting config
|
||||
export const D3_FORMAT_DOCS = t(
|
||||
|
|
@ -30,22 +35,26 @@ export const D3_NUMBER_FORMAT_DESCRIPTION_PERCENTAGE_TEXT = t(
|
|||
'Only applies when "Label Type" is not set to a percentage.',
|
||||
);
|
||||
|
||||
const d3Formatted: [string, string][] = [
|
||||
',d',
|
||||
'.1s',
|
||||
'.3s',
|
||||
',.1%',
|
||||
'.2%',
|
||||
'.3%',
|
||||
'.4r',
|
||||
',.1f',
|
||||
',.2f',
|
||||
',.3f',
|
||||
'+,',
|
||||
'$,.2f',
|
||||
].map(fmt => [fmt, `${fmt} (${getNumberFormatter(fmt).preview()})`]);
|
||||
|
||||
// input choices & options
|
||||
export const D3_FORMAT_OPTIONS: [string, string][] = [
|
||||
[NumberFormats.SMART_NUMBER, t('Adaptive formatting')],
|
||||
['~g', t('Original value')],
|
||||
[',d', ',d (12345.432 => 12,345)'],
|
||||
['.1s', '.1s (12345.432 => 10k)'],
|
||||
['.3s', '.3s (12345.432 => 12.3k)'],
|
||||
[',.1%', ',.1% (12345.432 => 1,234,543.2%)'],
|
||||
['.2%', '.2% (12345.432 => 1234543.20%)'],
|
||||
['.3%', '.3% (12345.432 => 1234543.200%)'],
|
||||
['.4r', '.4r (12345.432 => 12350)'],
|
||||
[',.1f', ',.1f (12345.432 => 12,345.4)'],
|
||||
[',.2f', ',.2f (12345.432 => 12,345.43)'],
|
||||
[',.3f', ',.3f (12345.432 => 12,345.432)'],
|
||||
['+,', '+, (12345.432 => +12,345.432)'],
|
||||
['$,.2f', '$,.2f (12345.432 => $12,345.43)'],
|
||||
...d3Formatted,
|
||||
['DURATION', t('Duration in ms (66000 => 1m 6s)')],
|
||||
['DURATION_SUB', t('Duration in ms (1.40008 => 1ms 400µs 80ns)')],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { FormatLocaleDefinition } from 'd3-format';
|
||||
|
||||
export const DEFAULT_D3_FORMAT: FormatLocaleDefinition = {
|
||||
decimal: '.',
|
||||
thousands: ',',
|
||||
grouping: [3],
|
||||
currency: ['$', ''],
|
||||
};
|
||||
|
|
@ -16,7 +16,9 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { FormatLocaleDefinition } from 'd3-format';
|
||||
import { RegistryWithDefaultKey, OverwritePolicy } from '../models';
|
||||
import { DEFAULT_D3_FORMAT } from './D3FormatConfig';
|
||||
import createD3NumberFormatter from './factories/createD3NumberFormatter';
|
||||
import createSmartNumberFormatter from './factories/createSmartNumberFormatter';
|
||||
import NumberFormats from './NumberFormats';
|
||||
|
|
@ -26,6 +28,8 @@ export default class NumberFormatterRegistry extends RegistryWithDefaultKey<
|
|||
NumberFormatter,
|
||||
NumberFormatter
|
||||
> {
|
||||
d3Format: FormatLocaleDefinition;
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
name: 'NumberFormatter',
|
||||
|
|
@ -41,6 +45,12 @@ export default class NumberFormatterRegistry extends RegistryWithDefaultKey<
|
|||
createSmartNumberFormatter({ signed: true }),
|
||||
);
|
||||
this.setDefaultKey(NumberFormats.SMART_NUMBER);
|
||||
this.d3Format = DEFAULT_D3_FORMAT;
|
||||
}
|
||||
|
||||
setD3Format(d3Format: Partial<FormatLocaleDefinition>) {
|
||||
this.d3Format = { ...DEFAULT_D3_FORMAT, ...d3Format };
|
||||
return this;
|
||||
}
|
||||
|
||||
get(formatterId?: string) {
|
||||
|
|
@ -59,6 +69,7 @@ export default class NumberFormatterRegistry extends RegistryWithDefaultKey<
|
|||
// Create new formatter if does not exist
|
||||
const formatter = createD3NumberFormatter({
|
||||
formatString: targetFormat,
|
||||
locale: this.d3Format,
|
||||
});
|
||||
this.registerValue(targetFormat, formatter);
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { FormatLocaleDefinition } from 'd3-format';
|
||||
import { makeSingleton } from '../utils';
|
||||
import NumberFormatterRegistry from './NumberFormatterRegistry';
|
||||
|
||||
|
|
@ -27,6 +28,10 @@ export function getNumberFormatter(format?: string) {
|
|||
return getInstance().get(format);
|
||||
}
|
||||
|
||||
export function setD3Format(d3Format: Partial<FormatLocaleDefinition>) {
|
||||
getInstance().setD3Format(d3Format);
|
||||
}
|
||||
|
||||
export function formatNumber(
|
||||
format: string | undefined,
|
||||
value: number | null | undefined,
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@
|
|||
|
||||
export { default as NumberFormats } from './NumberFormats';
|
||||
export { default as NumberFormatter, PREVIEW_VALUE } from './NumberFormatter';
|
||||
export { DEFAULT_D3_FORMAT } from './D3FormatConfig';
|
||||
|
||||
export {
|
||||
default as getNumberFormatterRegistry,
|
||||
formatNumber,
|
||||
setD3Format,
|
||||
getNumberFormatter,
|
||||
} from './NumberFormatterRegistrySingleton';
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
import {
|
||||
NumberFormatterRegistry,
|
||||
getNumberFormatterRegistry,
|
||||
setD3Format,
|
||||
getNumberFormatter,
|
||||
formatNumber,
|
||||
} from '@superset-ui/core';
|
||||
|
|
@ -55,4 +56,23 @@ describe('NumberFormatterRegistrySingleton', () => {
|
|||
expect(formatNumber(undefined, 1000)).toEqual('1k');
|
||||
});
|
||||
});
|
||||
describe('setD3Format()', () => {
|
||||
it('sets a specific FormatLocaleDefinition', () => {
|
||||
setD3Format({
|
||||
decimal: ';',
|
||||
thousands: '-',
|
||||
currency: ['€', ''],
|
||||
grouping: [2],
|
||||
});
|
||||
const formatter = getNumberFormatter('$,.2f');
|
||||
expect(formatter.format(12345.67)).toEqual('€1-23-45;67');
|
||||
});
|
||||
it('falls back to default value for unspecified locale format parameters', () => {
|
||||
setD3Format({
|
||||
currency: ['€', ''],
|
||||
});
|
||||
const formatter = getNumberFormatter('$,.1f');
|
||||
expect(formatter.format(12345.67)).toEqual('€12,345.7');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { t } from '@superset-ui/core';
|
||||
import { t, DEFAULT_D3_FORMAT } from '@superset-ui/core';
|
||||
|
||||
import { BootstrapData, CommonBootstrapData } from './types/bootstrapTypes';
|
||||
|
||||
|
|
@ -184,6 +184,7 @@ export const DEFAULT_COMMON_BOOTSTRAP_DATA: CommonBootstrapData = {
|
|||
color: '',
|
||||
},
|
||||
},
|
||||
d3_format: DEFAULT_D3_FORMAT,
|
||||
};
|
||||
|
||||
export const DEFAULT_BOOTSTRAP_DATA: BootstrapData = {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ setupColors(
|
|||
);
|
||||
|
||||
// Setup number formatters
|
||||
setupFormatters();
|
||||
setupFormatters(bootstrapData.common.d3_format);
|
||||
|
||||
setupDashboardComponents();
|
||||
|
||||
|
|
|
|||
|
|
@ -25,9 +25,13 @@ import {
|
|||
smartDateFormatter,
|
||||
smartDateVerboseFormatter,
|
||||
} from '@superset-ui/core';
|
||||
import { FormatLocaleDefinition } from 'd3-format';
|
||||
|
||||
export default function setupFormatters() {
|
||||
export default function setupFormatters(
|
||||
d3Format: Partial<FormatLocaleDefinition>,
|
||||
) {
|
||||
getNumberFormatterRegistry()
|
||||
.setD3Format(d3Format)
|
||||
// Add shims for format strings that are deprecated or common typos.
|
||||
// Temporary solution until performing a db migration to fix this.
|
||||
.registerValue(',0', getNumberFormatter(',.4~f'))
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
Locale,
|
||||
SequentialSchemeConfig,
|
||||
} from '@superset-ui/core';
|
||||
import { FormatLocaleDefinition } from 'd3-format';
|
||||
import { isPlainObject } from 'lodash';
|
||||
import { Languages } from 'src/features/home/LanguagePicker';
|
||||
import { FlashMessage } from '../components/FlashProvider';
|
||||
|
|
@ -150,6 +151,7 @@ export interface CommonBootstrapData {
|
|||
extra_sequential_color_schemes: SequentialSchemeConfig[];
|
||||
theme_overrides: JsonObject;
|
||||
menu_data: MenuData;
|
||||
d3_format: Partial<FormatLocaleDefinition>;
|
||||
}
|
||||
|
||||
export interface BootstrapData {
|
||||
|
|
|
|||
|
|
@ -378,6 +378,25 @@ LANGUAGES = {
|
|||
# incomplete and not well maintained.
|
||||
LANGUAGES = {}
|
||||
|
||||
|
||||
# Override the default d3 locale format
|
||||
# Default values are equivalent to
|
||||
# D3_FORMAT = {
|
||||
# "decimal": ".", # - decimal place string (e.g., ".").
|
||||
# "thousands": ",", # - group separator string (e.g., ",").
|
||||
# "grouping": [3], # - array of group sizes (e.g., [3]), cycled as needed.
|
||||
# "currency": ["$", ""] # - currency prefix/suffix strings (e.g., ["$", ""])
|
||||
# }
|
||||
# https://github.com/d3/d3-format/blob/main/README.md#formatLocale
|
||||
class D3Format(TypedDict, total=False):
|
||||
decimal: str
|
||||
thousands: str
|
||||
grouping: List[int]
|
||||
currency: List[str]
|
||||
|
||||
|
||||
D3_FORMAT: D3Format = {}
|
||||
|
||||
# ---------------------------------------------------
|
||||
# Feature flags
|
||||
# ---------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -428,6 +428,7 @@ def cached_common_bootstrap_data(user: User) -> Dict[str, Any]:
|
|||
"conf": frontend_config,
|
||||
"locale": locale,
|
||||
"language_pack": get_language_pack(locale),
|
||||
"d3_format": conf.get("D3_FORMAT"),
|
||||
"feature_flags": get_feature_flags(),
|
||||
"extra_sequential_color_schemes": conf["EXTRA_SEQUENTIAL_COLOR_SCHEMES"],
|
||||
"extra_categorical_color_schemes": conf["EXTRA_CATEGORICAL_COLOR_SCHEMES"],
|
||||
|
|
|
|||
Loading…
Reference in New Issue