feat: Implement support for currencies in more charts (#24594)

This commit is contained in:
Kamil Gabryjelski 2023-07-07 19:28:13 +02:00 committed by GitHub
parent c573cfcd12
commit d74d7eca23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 404 additions and 78 deletions

View File

@ -19,3 +19,4 @@
export { default as CurrencyFormatter } from './CurrencyFormatter'; export { default as CurrencyFormatter } from './CurrencyFormatter';
export * from './CurrencyFormatter'; export * from './CurrencyFormatter';
export * from './utils';

View File

@ -1,3 +1,21 @@
/**
* 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 { import {
Currency, Currency,
CurrencyFormatter, CurrencyFormatter,
@ -16,10 +34,8 @@ export const buildCustomFormatters = (
) => { ) => {
const metricsArray = ensureIsArray(metrics); const metricsArray = ensureIsArray(metrics);
return metricsArray.reduce((acc, metric) => { return metricsArray.reduce((acc, metric) => {
const actualD3Format = isSavedMetric(metric)
? columnFormats[metric] ?? d3Format
: d3Format;
if (isSavedMetric(metric)) { if (isSavedMetric(metric)) {
const actualD3Format = d3Format ?? columnFormats[metric];
return currencyFormats[metric] return currencyFormats[metric]
? { ? {
...acc, ...acc,

View File

@ -0,0 +1,151 @@
/*
* 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 {
buildCustomFormatters,
CurrencyFormatter,
getCustomFormatter,
getNumberFormatter,
getValueFormatter,
NumberFormatter,
ValueFormatter,
} from '@superset-ui/core';
it('buildCustomFormatters without saved metrics returns empty object', () => {
expect(
buildCustomFormatters(
[
{
expressionType: 'SIMPLE',
aggregate: 'COUNT',
column: { column_name: 'test' },
},
],
{
sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
},
{},
',.1f',
),
).toEqual({});
expect(
buildCustomFormatters(
undefined,
{
sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
},
{},
',.1f',
),
).toEqual({});
});
it('buildCustomFormatters with saved metrics returns custom formatters object', () => {
const customFormatters: Record<string, ValueFormatter> =
buildCustomFormatters(
[
{
expressionType: 'SIMPLE',
aggregate: 'COUNT',
column: { column_name: 'test' },
},
'sum__num',
'count',
],
{
sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
},
{ sum__num: ',.2' },
',.1f',
);
expect(customFormatters).toEqual({
sum__num: expect.any(Function),
count: expect.any(Function),
});
expect(customFormatters.sum__num).toBeInstanceOf(CurrencyFormatter);
expect(customFormatters.count).toBeInstanceOf(NumberFormatter);
expect((customFormatters.sum__num as CurrencyFormatter).d3Format).toEqual(
',.1f',
);
});
it('buildCustomFormatters uses dataset d3 format if not provided in control panel', () => {
const customFormatters: Record<string, ValueFormatter> =
buildCustomFormatters(
[
{
expressionType: 'SIMPLE',
aggregate: 'COUNT',
column: { column_name: 'test' },
},
'sum__num',
'count',
],
{
sum__num: { symbol: 'USD', symbolPosition: 'prefix' },
},
{ sum__num: ',.2' },
undefined,
);
expect((customFormatters.sum__num as CurrencyFormatter).d3Format).toEqual(
',.2',
);
});
it('getCustomFormatter', () => {
const customFormatters = {
sum__num: new CurrencyFormatter({
currency: { symbol: 'USD', symbolPosition: 'prefix' },
}),
count: getNumberFormatter(),
};
expect(getCustomFormatter(customFormatters, 'count')).toEqual(
customFormatters.count,
);
expect(
getCustomFormatter(customFormatters, ['count', 'sum__num'], 'count'),
).toEqual(customFormatters.count);
expect(getCustomFormatter(customFormatters, ['count', 'sum__num'])).toEqual(
undefined,
);
});
it('getValueFormatter', () => {
expect(
getValueFormatter(['count', 'sum__num'], {}, {}, ',.1f'),
).toBeInstanceOf(NumberFormatter);
expect(
getValueFormatter(['count', 'sum__num'], {}, {}, ',.1f', 'count'),
).toBeInstanceOf(NumberFormatter);
expect(
getValueFormatter(
['count', 'sum__num'],
{ count: { symbol: 'USD', symbolPosition: 'prefix' } },
{},
',.1f',
'count',
),
).toBeInstanceOf(CurrencyFormatter);
});

View File

@ -51,7 +51,7 @@ const propTypes = {
leftMargin: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), leftMargin: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
metric: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), metric: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
normalized: PropTypes.bool, normalized: PropTypes.bool,
numberFormat: PropTypes.string, valueFormatter: PropTypes.object,
showLegend: PropTypes.bool, showLegend: PropTypes.bool,
showPercentage: PropTypes.bool, showPercentage: PropTypes.bool,
showValues: PropTypes.bool, showValues: PropTypes.bool,
@ -90,7 +90,7 @@ function Heatmap(element, props) {
leftMargin, leftMargin,
metric, metric,
normalized, normalized,
numberFormat, valueFormatter,
showLegend, showLegend,
showPercentage, showPercentage,
showValues, showValues,
@ -115,8 +115,6 @@ function Heatmap(element, props) {
const pixelsPerCharX = 4.5; // approx, depends on font size const pixelsPerCharX = 4.5; // approx, depends on font size
let pixelsPerCharY = 6; // approx, depends on font size let pixelsPerCharY = 6; // approx, depends on font size
const valueFormatter = getNumberFormatter(numberFormat);
// Dynamically adjusts based on max x / y category lengths // Dynamically adjusts based on max x / y category lengths
function adjustMargins() { function adjustMargins() {
let longestX = 1; let longestX = 1;

View File

@ -1,3 +1,5 @@
import { getValueFormatter } from '@superset-ui/core';
/** /**
* Licensed to the Apache Software Foundation (ASF) under one * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file * or more contributor license agreements. See the NOTICE file
@ -17,7 +19,7 @@
* under the License. * under the License.
*/ */
export default function transformProps(chartProps) { export default function transformProps(chartProps) {
const { width, height, formData, queriesData } = chartProps; const { width, height, formData, queriesData, datasource } = chartProps;
const { const {
bottomMargin, bottomMargin,
canvasImageRendering, canvasImageRendering,
@ -37,7 +39,13 @@ export default function transformProps(chartProps) {
yAxisBounds, yAxisBounds,
yAxisFormat, yAxisFormat,
} = formData; } = formData;
const { columnFormats = {}, currencyFormats = {} } = datasource;
const valueFormatter = getValueFormatter(
metric,
currencyFormats,
columnFormats,
yAxisFormat,
);
return { return {
width, width,
height, height,
@ -50,7 +58,6 @@ export default function transformProps(chartProps) {
leftMargin, leftMargin,
metric, metric,
normalized, normalized,
numberFormat: yAxisFormat,
showLegend, showLegend,
showPercentage: showPerc, showPercentage: showPerc,
showValues, showValues,
@ -59,5 +66,6 @@ export default function transformProps(chartProps) {
xScaleInterval: parseInt(xscaleInterval, 10), xScaleInterval: parseInt(xscaleInterval, 10),
yScaleInterval: parseInt(yscaleInterval, 10), yScaleInterval: parseInt(yscaleInterval, 10),
yAxisBounds, yAxisBounds,
valueFormatter,
}; };
} }

View File

@ -21,7 +21,6 @@ import d3 from 'd3';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { extent as d3Extent } from 'd3-array'; import { extent as d3Extent } from 'd3-array';
import { import {
getNumberFormatter,
getSequentialSchemeRegistry, getSequentialSchemeRegistry,
CategoricalColorNamespace, CategoricalColorNamespace,
} from '@superset-ui/core'; } from '@superset-ui/core';
@ -47,10 +46,9 @@ const propTypes = {
setDataMask: PropTypes.func, setDataMask: PropTypes.func,
onContextMenu: PropTypes.func, onContextMenu: PropTypes.func,
emitCrossFilters: PropTypes.bool, emitCrossFilters: PropTypes.bool,
formatter: PropTypes.object,
}; };
const formatter = getNumberFormatter();
function WorldMap(element, props) { function WorldMap(element, props) {
const { const {
countryFieldtype, countryFieldtype,
@ -71,6 +69,7 @@ function WorldMap(element, props) {
inContextMenu, inContextMenu,
filterState, filterState,
emitCrossFilters, emitCrossFilters,
formatter,
} = props; } = props;
const div = d3.select(element); const div = d3.select(element);
div.classed('superset-legacy-chart-world-map', true); div.classed('superset-legacy-chart-world-map', true);

View File

@ -17,6 +17,7 @@
* under the License. * under the License.
*/ */
import { rgb } from 'd3-color'; import { rgb } from 'd3-color';
import { getValueFormatter } from '@superset-ui/core';
export default function transformProps(chartProps) { export default function transformProps(chartProps) {
const { const {
@ -28,6 +29,7 @@ export default function transformProps(chartProps) {
inContextMenu, inContextMenu,
filterState, filterState,
emitCrossFilters, emitCrossFilters,
datasource,
} = chartProps; } = chartProps;
const { onContextMenu, setDataMask } = hooks; const { onContextMenu, setDataMask } = hooks;
const { const {
@ -40,8 +42,17 @@ export default function transformProps(chartProps) {
colorBy, colorBy,
colorScheme, colorScheme,
sliceId, sliceId,
metric,
} = formData; } = formData;
const { r, g, b } = colorPicker; const { r, g, b } = colorPicker;
const { currencyFormats = {}, columnFormats = {} } = datasource;
const formatter = getValueFormatter(
metric,
currencyFormats,
columnFormats,
undefined,
);
return { return {
countryFieldtype, countryFieldtype,
@ -61,5 +72,6 @@ export default function transformProps(chartProps) {
inContextMenu, inContextMenu,
filterState, filterState,
emitCrossFilters, emitCrossFilters,
formatter,
}; };
} }

View File

@ -26,11 +26,11 @@ import {
getMetricLabel, getMetricLabel,
extractTimegrain, extractTimegrain,
QueryFormData, QueryFormData,
getValueFormatter,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { BigNumberTotalChartProps, BigNumberVizProps } from '../types'; import { BigNumberTotalChartProps, BigNumberVizProps } from '../types';
import { getDateFormatter, parseMetricValue } from '../utils'; import { getDateFormatter, parseMetricValue } from '../utils';
import { Refs } from '../../types'; import { Refs } from '../../types';
import { getValueFormatter } from '../../utils/valueFormatter';
export default function transformProps( export default function transformProps(
chartProps: BigNumberTotalChartProps, chartProps: BigNumberTotalChartProps,

View File

@ -28,6 +28,7 @@ import {
getXAxisLabel, getXAxisLabel,
Metric, Metric,
ValueFormatter, ValueFormatter,
getValueFormatter,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { EChartsCoreOption, graphic } from 'echarts'; import { EChartsCoreOption, graphic } from 'echarts';
import { import {
@ -39,7 +40,6 @@ import {
import { getDateFormatter, parseMetricValue } from '../utils'; import { getDateFormatter, parseMetricValue } from '../utils';
import { getDefaultTooltip } from '../../utils/tooltip'; import { getDefaultTooltip } from '../../utils/tooltip';
import { Refs } from '../../types'; import { Refs } from '../../types';
import { getValueFormatter } from '../../utils/valueFormatter';
const defaultNumberFormatter = getNumberFormatter(); const defaultNumberFormatter = getNumberFormatter();
export function renderTooltipFactory( export function renderTooltipFactory(

View File

@ -24,6 +24,7 @@ import {
NumberFormats, NumberFormats,
ValueFormatter, ValueFormatter,
getColumnLabel, getColumnLabel,
getValueFormatter,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { CallbackDataParams } from 'echarts/types/src/util/types'; import { CallbackDataParams } from 'echarts/types/src/util/types';
import { EChartsCoreOption, FunnelSeriesOption } from 'echarts'; import { EChartsCoreOption, FunnelSeriesOption } from 'echarts';
@ -45,7 +46,6 @@ import { defaultGrid } from '../defaults';
import { OpacityEnum, DEFAULT_LEGEND_FORM_DATA } from '../constants'; import { OpacityEnum, DEFAULT_LEGEND_FORM_DATA } from '../constants';
import { getDefaultTooltip } from '../utils/tooltip'; import { getDefaultTooltip } from '../utils/tooltip';
import { Refs } from '../types'; import { Refs } from '../types';
import { getValueFormatter } from '../utils/valueFormatter';
const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT); const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT);

View File

@ -23,6 +23,7 @@ import {
DataRecord, DataRecord,
getMetricLabel, getMetricLabel,
getColumnLabel, getColumnLabel,
getValueFormatter,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { EChartsCoreOption, GaugeSeriesOption } from 'echarts'; import { EChartsCoreOption, GaugeSeriesOption } from 'echarts';
import { GaugeDataItemOption } from 'echarts/types/src/chart/gauge/GaugeSeries'; import { GaugeDataItemOption } from 'echarts/types/src/chart/gauge/GaugeSeries';
@ -46,7 +47,6 @@ import { OpacityEnum } from '../constants';
import { getDefaultTooltip } from '../utils/tooltip'; import { getDefaultTooltip } from '../utils/tooltip';
import { Refs } from '../types'; import { Refs } from '../types';
import { getColtypesMapping } from '../utils/series'; import { getColtypesMapping } from '../utils/series';
import { getValueFormatter } from '../utils/valueFormatter';
const setIntervalBoundsAndColors = ( const setIntervalBoundsAndColors = (
intervals: string, intervals: string,

View File

@ -34,6 +34,11 @@ import {
isPhysicalColumn, isPhysicalColumn,
isDefined, isDefined,
ensureIsArray, ensureIsArray,
buildCustomFormatters,
ValueFormatter,
NumberFormatter,
QueryFormMetric,
getCustomFormatter,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { getOriginalSeries } from '@superset-ui/chart-controls'; import { getOriginalSeries } from '@superset-ui/chart-controls';
import { EChartsCoreOption, SeriesOption } from 'echarts'; import { EChartsCoreOption, SeriesOption } from 'echarts';
@ -83,6 +88,23 @@ import {
} from '../Timeseries/transformers'; } from '../Timeseries/transformers';
import { TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP } from '../constants'; import { TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP } from '../constants';
import { getDefaultTooltip } from '../utils/tooltip'; import { getDefaultTooltip } from '../utils/tooltip';
import { getYAxisFormatter } from '../utils/getYAxisFormatter';
const getFormatter = (
customFormatters: Record<string, ValueFormatter>,
defaultFormatter: NumberFormatter,
metrics: QueryFormMetric[],
formatterKey: string,
forcePercentFormat: boolean,
) => {
if (forcePercentFormat) {
return getNumberFormatter(',.0%');
}
return (
getCustomFormatter(customFormatters, metrics, formatterKey) ??
defaultFormatter
);
};
export default function transformProps( export default function transformProps(
chartProps: EchartsMixedTimeseriesProps, chartProps: EchartsMixedTimeseriesProps,
@ -99,7 +121,11 @@ export default function transformProps(
inContextMenu, inContextMenu,
emitCrossFilters, emitCrossFilters,
} = chartProps; } = chartProps;
const { verboseMap = {} } = datasource; const {
verboseMap = {},
currencyFormats = {},
columnFormats = {},
} = datasource;
const { label_map: labelMap } = const { label_map: labelMap } =
queriesData[0] as TimeseriesChartDataResponseResult; queriesData[0] as TimeseriesChartDataResponseResult;
const { label_map: labelMapB } = const { label_map: labelMapB } =
@ -160,6 +186,8 @@ export default function transformProps(
sliceId, sliceId,
timeGrainSqla, timeGrainSqla,
percentageThreshold, percentageThreshold,
metrics = [],
metricsB = [],
}: EchartsMixedTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData }; }: EchartsMixedTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData };
const refs: Refs = {}; const refs: Refs = {};
@ -194,6 +222,18 @@ export default function transformProps(
const formatterSecondary = getNumberFormatter( const formatterSecondary = getNumberFormatter(
contributionMode ? ',.0%' : yAxisFormatSecondary, contributionMode ? ',.0%' : yAxisFormatSecondary,
); );
const customFormatters = buildCustomFormatters(
[...metrics, ...metricsB],
currencyFormats,
columnFormats,
yAxisFormat,
);
const customFormattersSecondary = buildCustomFormatters(
[...metrics, ...metricsB],
currencyFormats,
columnFormats,
yAxisFormatSecondary,
);
const primarySeries = new Set<string>(); const primarySeries = new Set<string>();
const secondarySeries = new Set<string>(); const secondarySeries = new Set<string>();
@ -292,12 +332,6 @@ export default function transformProps(
parseYAxisBound, parseYAxisBound,
); );
const maxLabelFormatter = getOverMaxHiddenFormatter({ max, formatter });
const maxLabelFormatterSecondary = getOverMaxHiddenFormatter({
max: maxSecondary,
formatter: formatterSecondary,
});
const array = ensureIsArray(chartProps.rawFormData?.time_compare); const array = ensureIsArray(chartProps.rawFormData?.time_compare);
const inverted = invert(verboseMap); const inverted = invert(verboseMap);
@ -306,6 +340,14 @@ export default function transformProps(
const seriesName = inverted[entryName] || entryName; const seriesName = inverted[entryName] || entryName;
const colorScaleKey = getOriginalSeries(seriesName, array); const colorScaleKey = getOriginalSeries(seriesName, array);
const seriesFormatter = getFormatter(
customFormatters,
formatter,
metrics,
labelMap[seriesName]?.[0],
!!contributionMode,
);
const transformedSeries = transformSeries( const transformedSeries = transformSeries(
entry, entry,
colorScale, colorScale,
@ -325,8 +367,11 @@ export default function transformProps(
queryIndex: 0, queryIndex: 0,
formatter: formatter:
seriesType === EchartsTimeseriesSeriesType.Bar seriesType === EchartsTimeseriesSeriesType.Bar
? maxLabelFormatter ? getOverMaxHiddenFormatter({
: formatter, max,
formatter: seriesFormatter,
})
: seriesFormatter,
showValueIndexes: showValueIndexesA, showValueIndexes: showValueIndexesA,
totalStackedValues, totalStackedValues,
thresholdValues, thresholdValues,
@ -340,6 +385,14 @@ export default function transformProps(
const seriesName = `${inverted[entryName] || entryName} (1)`; const seriesName = `${inverted[entryName] || entryName} (1)`;
const colorScaleKey = getOriginalSeries(seriesName, array); const colorScaleKey = getOriginalSeries(seriesName, array);
const seriesFormatter = getFormatter(
customFormattersSecondary,
formatterSecondary,
metricsB,
labelMapB[seriesName]?.[0],
!!contributionMode,
);
const transformedSeries = transformSeries( const transformedSeries = transformSeries(
entry, entry,
colorScale, colorScale,
@ -361,8 +414,11 @@ export default function transformProps(
queryIndex: 1, queryIndex: 1,
formatter: formatter:
seriesTypeB === EchartsTimeseriesSeriesType.Bar seriesTypeB === EchartsTimeseriesSeriesType.Bar
? maxLabelFormatterSecondary ? getOverMaxHiddenFormatter({
: formatterSecondary, max: maxSecondary,
formatter: seriesFormatter,
})
: seriesFormatter,
showValueIndexes: showValueIndexesB, showValueIndexes: showValueIndexesB,
totalStackedValues: totalStackedValuesB, totalStackedValues: totalStackedValuesB,
thresholdValues: thresholdValuesB, thresholdValues: thresholdValuesB,
@ -434,7 +490,14 @@ export default function transformProps(
max, max,
minorTick: { show: true }, minorTick: { show: true },
minorSplitLine: { show: minorSplitLine }, minorSplitLine: { show: minorSplitLine },
axisLabel: { formatter }, axisLabel: {
formatter: getYAxisFormatter(
metrics,
!!contributionMode,
customFormatters,
yAxisFormat,
),
},
scale: truncateYAxis, scale: truncateYAxis,
name: yAxisTitle, name: yAxisTitle,
nameGap: convertInteger(yAxisTitleMargin), nameGap: convertInteger(yAxisTitleMargin),
@ -449,7 +512,14 @@ export default function transformProps(
minorTick: { show: true }, minorTick: { show: true },
splitLine: { show: false }, splitLine: { show: false },
minorSplitLine: { show: minorSplitLine }, minorSplitLine: { show: minorSplitLine },
axisLabel: { formatter: formatterSecondary }, axisLabel: {
formatter: getYAxisFormatter(
metricsB,
!!contributionMode,
customFormattersSecondary,
yAxisFormatSecondary,
),
},
scale: truncateYAxis, scale: truncateYAxis,
name: yAxisTitleSecondary, name: yAxisTitleSecondary,
alignTicks, alignTicks,
@ -475,10 +545,36 @@ export default function transformProps(
Object.keys(forecastValues).forEach(key => { Object.keys(forecastValues).forEach(key => {
const value = forecastValues[key]; const value = forecastValues[key];
// if there are no dimensions, key is a verbose name of a metric,
// otherwise it is a comma separated string where the first part is metric name
let formatterKey;
if (primarySeries.has(key)) {
formatterKey =
groupby.length === 0 ? inverted[key] : labelMap[key]?.[0];
} else {
formatterKey =
groupbyB.length === 0 ? inverted[key] : labelMapB[key]?.[0];
}
const tooltipFormatter = getFormatter(
customFormatters,
formatter,
metrics,
formatterKey,
!!contributionMode,
);
const tooltipFormatterSecondary = getFormatter(
customFormattersSecondary,
formatterSecondary,
metricsB,
formatterKey,
!!contributionMode,
);
const content = formatForecastTooltipSeries({ const content = formatForecastTooltipSeries({
...value, ...value,
seriesName: key, seriesName: key,
formatter: primarySeries.has(key) ? formatter : formatterSecondary, formatter: primarySeries.has(key)
? tooltipFormatter
: tooltipFormatterSecondary,
}); });
rows.push(`<span style="opacity: 0.7">${content}</span>`); rows.push(`<span style="opacity: 0.7">${content}</span>`);
}); });

View File

@ -25,6 +25,7 @@ import {
NumberFormats, NumberFormats,
t, t,
ValueFormatter, ValueFormatter,
getValueFormatter,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { CallbackDataParams } from 'echarts/types/src/util/types'; import { CallbackDataParams } from 'echarts/types/src/util/types';
import { EChartsCoreOption, PieSeriesOption } from 'echarts'; import { EChartsCoreOption, PieSeriesOption } from 'echarts';
@ -47,7 +48,6 @@ import { defaultGrid } from '../defaults';
import { convertInteger } from '../utils/convertInteger'; import { convertInteger } from '../utils/convertInteger';
import { getDefaultTooltip } from '../utils/tooltip'; import { getDefaultTooltip } from '../utils/tooltip';
import { Refs } from '../types'; import { Refs } from '../types';
import { getValueFormatter } from '../utils/valueFormatter';
const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT); const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT);

View File

@ -24,10 +24,11 @@ import {
getNumberFormatter, getNumberFormatter,
getSequentialSchemeRegistry, getSequentialSchemeRegistry,
getTimeFormatter, getTimeFormatter,
getValueFormatter,
NumberFormats, NumberFormats,
NumberFormatter,
SupersetTheme, SupersetTheme,
t, t,
ValueFormatter,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { EChartsCoreOption } from 'echarts'; import { EChartsCoreOption } from 'echarts';
import { CallbackDataParams } from 'echarts/types/src/util/types'; import { CallbackDataParams } from 'echarts/types/src/util/types';
@ -74,7 +75,7 @@ export function formatLabel({
}: { }: {
params: CallbackDataParams; params: CallbackDataParams;
labelType: EchartsSunburstLabelType; labelType: EchartsSunburstLabelType;
numberFormatter: NumberFormatter; numberFormatter: ValueFormatter;
}): string { }): string {
const { name = '', value } = params; const { name = '', value } = params;
const formattedValue = numberFormatter(value as number); const formattedValue = numberFormatter(value as number);
@ -93,7 +94,8 @@ export function formatLabel({
export function formatTooltip({ export function formatTooltip({
params, params,
numberFormatter, primaryValueFormatter,
secondaryValueFormatter,
colorByCategory, colorByCategory,
totalValue, totalValue,
metricLabel, metricLabel,
@ -107,7 +109,8 @@ export function formatTooltip({
value: number; value: number;
}[]; }[];
}; };
numberFormatter: NumberFormatter; primaryValueFormatter: ValueFormatter;
secondaryValueFormatter: ValueFormatter | undefined;
colorByCategory: boolean; colorByCategory: boolean;
totalValue: number; totalValue: number;
metricLabel: string; metricLabel: string;
@ -116,8 +119,10 @@ export function formatTooltip({
}): string { }): string {
const { data, treePathInfo = [] } = params; const { data, treePathInfo = [] } = params;
const node = data as TreeNode; const node = data as TreeNode;
const formattedValue = numberFormatter(node.value); const formattedValue = primaryValueFormatter(node.value);
const formattedSecondaryValue = numberFormatter(node.secondaryValue); const formattedSecondaryValue = secondaryValueFormatter?.(
node.secondaryValue,
);
const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT); const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT);
const compareValuePercentage = percentFormatter( const compareValuePercentage = percentFormatter(
@ -177,6 +182,7 @@ export default function transformProps(
theme, theme,
inContextMenu, inContextMenu,
emitCrossFilters, emitCrossFilters,
datasource,
} = chartProps; } = chartProps;
const { data = [] } = queriesData[0]; const { data = [] } = queriesData[0];
const coltypeMapping = getColtypesMapping(queriesData[0]); const coltypeMapping = getColtypesMapping(queriesData[0]);
@ -195,12 +201,28 @@ export default function transformProps(
showTotal, showTotal,
sliceId, sliceId,
} = formData; } = formData;
const { currencyFormats = {}, columnFormats = {} } = datasource;
const refs: Refs = {}; const refs: Refs = {};
const primaryValueFormatter = getValueFormatter(
metric,
currencyFormats,
columnFormats,
numberFormat,
);
const secondaryValueFormatter = secondaryMetric
? getValueFormatter(
secondaryMetric,
currencyFormats,
columnFormats,
numberFormat,
)
: undefined;
const numberFormatter = getNumberFormatter(numberFormat); const numberFormatter = getNumberFormatter(numberFormat);
const formatter = (params: CallbackDataParams) => const formatter = (params: CallbackDataParams) =>
formatLabel({ formatLabel({
params, params,
numberFormatter, numberFormatter: primaryValueFormatter,
labelType, labelType,
}); });
const minShowLabelAngle = (showLabelsThreshold || 0) * 3.6; const minShowLabelAngle = (showLabelsThreshold || 0) * 3.6;
@ -319,7 +341,8 @@ export default function transformProps(
formatter: (params: any) => formatter: (params: any) =>
formatTooltip({ formatTooltip({
params, params,
numberFormatter, primaryValueFormatter,
secondaryValueFormatter,
colorByCategory, colorByCategory,
totalValue, totalValue,
metricLabel, metricLabel,
@ -356,7 +379,7 @@ export default function transformProps(
top: 'center', top: 'center',
left: 'center', left: 'center',
style: { style: {
text: t('Total: %s', numberFormatter(totalValue)), text: t('Total: %s', primaryValueFormatter(totalValue)),
fontSize: 16, fontSize: 16,
fontWeight: 'bold', fontWeight: 'bold',
}, },

View File

@ -22,7 +22,6 @@ import {
AnnotationLayer, AnnotationLayer,
AxisType, AxisType,
CategoricalColorNamespace, CategoricalColorNamespace,
CurrencyFormatter,
ensureIsArray, ensureIsArray,
GenericDataType, GenericDataType,
getMetricLabel, getMetricLabel,
@ -33,13 +32,11 @@ import {
isFormulaAnnotationLayer, isFormulaAnnotationLayer,
isIntervalAnnotationLayer, isIntervalAnnotationLayer,
isPhysicalColumn, isPhysicalColumn,
isSavedMetric,
isTimeseriesAnnotationLayer, isTimeseriesAnnotationLayer,
NumberFormats,
QueryFormMetric,
t, t,
TimeseriesChartDataResponseResult, TimeseriesChartDataResponseResult,
ValueFormatter, buildCustomFormatters,
getCustomFormatter,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { import {
extractExtraMetrics, extractExtraMetrics,
@ -97,36 +94,7 @@ import {
TIMEGRAIN_TO_TIMESTAMP, TIMEGRAIN_TO_TIMESTAMP,
} from '../constants'; } from '../constants';
import { getDefaultTooltip } from '../utils/tooltip'; import { getDefaultTooltip } from '../utils/tooltip';
import { import { getYAxisFormatter } from '../utils/getYAxisFormatter';
buildCustomFormatters,
getCustomFormatter,
} from '../utils/valueFormatter';
const getYAxisFormatter = (
metrics: QueryFormMetric[],
forcePercentFormatter: boolean,
customFormatters: Record<string, ValueFormatter>,
yAxisFormat: string = NumberFormats.SMART_NUMBER,
) => {
if (forcePercentFormatter) {
return getNumberFormatter(',.0%');
}
const metricsArray = ensureIsArray(metrics);
if (
metricsArray.every(isSavedMetric) &&
metricsArray
.map(metric => customFormatters[metric])
.every(
(formatter, _, formatters) =>
formatter instanceof CurrencyFormatter &&
(formatter as CurrencyFormatter)?.currency?.symbol ===
(formatters[0] as CurrencyFormatter)?.currency?.symbol,
)
) {
return customFormatters[metricsArray[0]];
}
return getNumberFormatter(yAxisFormat);
};
export default function transformProps( export default function transformProps(
chartProps: EchartsTimeseriesChartProps, chartProps: EchartsTimeseriesChartProps,

View File

@ -24,6 +24,7 @@ import {
getTimeFormatter, getTimeFormatter,
NumberFormats, NumberFormats,
ValueFormatter, ValueFormatter,
getValueFormatter,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { TreemapSeriesNodeItemOption } from 'echarts/types/src/chart/treemap/TreemapSeries'; import { TreemapSeriesNodeItemOption } from 'echarts/types/src/chart/treemap/TreemapSeries';
import { EChartsCoreOption, TreemapSeriesOption } from 'echarts'; import { EChartsCoreOption, TreemapSeriesOption } from 'echarts';
@ -48,7 +49,6 @@ import { OpacityEnum } from '../constants';
import { getDefaultTooltip } from '../utils/tooltip'; import { getDefaultTooltip } from '../utils/tooltip';
import { Refs } from '../types'; import { Refs } from '../types';
import { treeBuilder, TreeNode } from '../utils/treeBuilder'; import { treeBuilder, TreeNode } from '../utils/treeBuilder';
import { getValueFormatter } from '../utils/valueFormatter';
export function formatLabel({ export function formatLabel({
params, params,

View File

@ -0,0 +1,54 @@
/**
* 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 {
CurrencyFormatter,
ensureIsArray,
getNumberFormatter,
isSavedMetric,
NumberFormats,
QueryFormMetric,
ValueFormatter,
} from '@superset-ui/core';
export const getYAxisFormatter = (
metrics: QueryFormMetric[],
forcePercentFormatter: boolean,
customFormatters: Record<string, ValueFormatter>,
yAxisFormat: string = NumberFormats.SMART_NUMBER,
) => {
if (forcePercentFormatter) {
return getNumberFormatter(',.0%');
}
const metricsArray = ensureIsArray(metrics);
if (
metricsArray.every(isSavedMetric) &&
metricsArray
.map(metric => customFormatters[metric])
.every(
(formatter, _, formatters) =>
formatter instanceof CurrencyFormatter &&
(formatter as CurrencyFormatter)?.currency?.symbol ===
(formatters[0] as CurrencyFormatter)?.currency?.symbol,
)
) {
return customFormatters[metricsArray[0]];
}
return getNumberFormatter(yAxisFormat);
};

View File

@ -518,7 +518,7 @@ export function getAxisType(dataType?: GenericDataType): AxisType {
export function getOverMaxHiddenFormatter( export function getOverMaxHiddenFormatter(
config: { config: {
max?: number; max?: number;
formatter?: NumberFormatter; formatter?: ValueFormatter;
} = {}, } = {},
) { ) {
const { max, formatter } = config; const { max, formatter } = config;