From d74d7eca23a3c94bc48af082c115d34c103e815d Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Fri, 7 Jul 2023 19:28:13 +0200 Subject: [PATCH] feat: Implement support for currencies in more charts (#24594) --- .../src/currency-format/index.ts | 1 + .../src/currency-format/utils.ts} | 22 ++- .../test/currency-format/utils.test.ts | 151 ++++++++++++++++++ .../src/Heatmap.js | 6 +- .../src/transformProps.js | 14 +- .../src/WorldMap.js | 5 +- .../src/transformProps.js | 12 ++ .../BigNumberTotal/transformProps.ts | 2 +- .../BigNumberWithTrendline/transformProps.ts | 2 +- .../src/Funnel/transformProps.ts | 2 +- .../src/Gauge/transformProps.ts | 2 +- .../src/MixedTimeseries/transformProps.ts | 124 ++++++++++++-- .../src/Pie/transformProps.ts | 2 +- .../src/Sunburst/transformProps.ts | 41 +++-- .../src/Timeseries/transformProps.ts | 38 +---- .../src/Treemap/transformProps.ts | 2 +- .../src/utils/getYAxisFormatter.ts | 54 +++++++ .../plugin-chart-echarts/src/utils/series.ts | 2 +- 18 files changed, 404 insertions(+), 78 deletions(-) rename superset-frontend/{plugins/plugin-chart-echarts/src/utils/valueFormatter.ts => packages/superset-ui-core/src/currency-format/utils.ts} (65%) create mode 100644 superset-frontend/packages/superset-ui-core/test/currency-format/utils.test.ts create mode 100644 superset-frontend/plugins/plugin-chart-echarts/src/utils/getYAxisFormatter.ts diff --git a/superset-frontend/packages/superset-ui-core/src/currency-format/index.ts b/superset-frontend/packages/superset-ui-core/src/currency-format/index.ts index c7fa5a0388..45fa851e88 100644 --- a/superset-frontend/packages/superset-ui-core/src/currency-format/index.ts +++ b/superset-frontend/packages/superset-ui-core/src/currency-format/index.ts @@ -19,3 +19,4 @@ export { default as CurrencyFormatter } from './CurrencyFormatter'; export * from './CurrencyFormatter'; +export * from './utils'; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/valueFormatter.ts b/superset-frontend/packages/superset-ui-core/src/currency-format/utils.ts similarity index 65% rename from superset-frontend/plugins/plugin-chart-echarts/src/utils/valueFormatter.ts rename to superset-frontend/packages/superset-ui-core/src/currency-format/utils.ts index 5d995c9f00..014388b86d 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/valueFormatter.ts +++ b/superset-frontend/packages/superset-ui-core/src/currency-format/utils.ts @@ -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 { Currency, CurrencyFormatter, @@ -16,10 +34,8 @@ export const buildCustomFormatters = ( ) => { const metricsArray = ensureIsArray(metrics); return metricsArray.reduce((acc, metric) => { - const actualD3Format = isSavedMetric(metric) - ? columnFormats[metric] ?? d3Format - : d3Format; if (isSavedMetric(metric)) { + const actualD3Format = d3Format ?? columnFormats[metric]; return currencyFormats[metric] ? { ...acc, diff --git a/superset-frontend/packages/superset-ui-core/test/currency-format/utils.test.ts b/superset-frontend/packages/superset-ui-core/test/currency-format/utils.test.ts new file mode 100644 index 0000000000..9e35ce8d99 --- /dev/null +++ b/superset-frontend/packages/superset-ui-core/test/currency-format/utils.test.ts @@ -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 = + 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 = + 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); +}); diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js index b377c71c57..7d62622313 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js +++ b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/Heatmap.js @@ -51,7 +51,7 @@ const propTypes = { leftMargin: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), metric: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), normalized: PropTypes.bool, - numberFormat: PropTypes.string, + valueFormatter: PropTypes.object, showLegend: PropTypes.bool, showPercentage: PropTypes.bool, showValues: PropTypes.bool, @@ -90,7 +90,7 @@ function Heatmap(element, props) { leftMargin, metric, normalized, - numberFormat, + valueFormatter, showLegend, showPercentage, showValues, @@ -115,8 +115,6 @@ function Heatmap(element, props) { const pixelsPerCharX = 4.5; // 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 function adjustMargins() { let longestX = 1; diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/transformProps.js b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/transformProps.js index 9381250ac1..a6adf5f8b8 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/transformProps.js +++ b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/transformProps.js @@ -1,3 +1,5 @@ +import { getValueFormatter } from '@superset-ui/core'; + /** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -17,7 +19,7 @@ * under the License. */ export default function transformProps(chartProps) { - const { width, height, formData, queriesData } = chartProps; + const { width, height, formData, queriesData, datasource } = chartProps; const { bottomMargin, canvasImageRendering, @@ -37,7 +39,13 @@ export default function transformProps(chartProps) { yAxisBounds, yAxisFormat, } = formData; - + const { columnFormats = {}, currencyFormats = {} } = datasource; + const valueFormatter = getValueFormatter( + metric, + currencyFormats, + columnFormats, + yAxisFormat, + ); return { width, height, @@ -50,7 +58,6 @@ export default function transformProps(chartProps) { leftMargin, metric, normalized, - numberFormat: yAxisFormat, showLegend, showPercentage: showPerc, showValues, @@ -59,5 +66,6 @@ export default function transformProps(chartProps) { xScaleInterval: parseInt(xscaleInterval, 10), yScaleInterval: parseInt(yscaleInterval, 10), yAxisBounds, + valueFormatter, }; } diff --git a/superset-frontend/plugins/legacy-plugin-chart-world-map/src/WorldMap.js b/superset-frontend/plugins/legacy-plugin-chart-world-map/src/WorldMap.js index c8aa2cdc2a..540097be24 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-world-map/src/WorldMap.js +++ b/superset-frontend/plugins/legacy-plugin-chart-world-map/src/WorldMap.js @@ -21,7 +21,6 @@ import d3 from 'd3'; import PropTypes from 'prop-types'; import { extent as d3Extent } from 'd3-array'; import { - getNumberFormatter, getSequentialSchemeRegistry, CategoricalColorNamespace, } from '@superset-ui/core'; @@ -47,10 +46,9 @@ const propTypes = { setDataMask: PropTypes.func, onContextMenu: PropTypes.func, emitCrossFilters: PropTypes.bool, + formatter: PropTypes.object, }; -const formatter = getNumberFormatter(); - function WorldMap(element, props) { const { countryFieldtype, @@ -71,6 +69,7 @@ function WorldMap(element, props) { inContextMenu, filterState, emitCrossFilters, + formatter, } = props; const div = d3.select(element); div.classed('superset-legacy-chart-world-map', true); diff --git a/superset-frontend/plugins/legacy-plugin-chart-world-map/src/transformProps.js b/superset-frontend/plugins/legacy-plugin-chart-world-map/src/transformProps.js index 5f8c718449..182d1b0b11 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-world-map/src/transformProps.js +++ b/superset-frontend/plugins/legacy-plugin-chart-world-map/src/transformProps.js @@ -17,6 +17,7 @@ * under the License. */ import { rgb } from 'd3-color'; +import { getValueFormatter } from '@superset-ui/core'; export default function transformProps(chartProps) { const { @@ -28,6 +29,7 @@ export default function transformProps(chartProps) { inContextMenu, filterState, emitCrossFilters, + datasource, } = chartProps; const { onContextMenu, setDataMask } = hooks; const { @@ -40,8 +42,17 @@ export default function transformProps(chartProps) { colorBy, colorScheme, sliceId, + metric, } = formData; const { r, g, b } = colorPicker; + const { currencyFormats = {}, columnFormats = {} } = datasource; + + const formatter = getValueFormatter( + metric, + currencyFormats, + columnFormats, + undefined, + ); return { countryFieldtype, @@ -61,5 +72,6 @@ export default function transformProps(chartProps) { inContextMenu, filterState, emitCrossFilters, + formatter, }; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts index 5486030f46..bdef7852d1 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/transformProps.ts @@ -26,11 +26,11 @@ import { getMetricLabel, extractTimegrain, QueryFormData, + getValueFormatter, } from '@superset-ui/core'; import { BigNumberTotalChartProps, BigNumberVizProps } from '../types'; import { getDateFormatter, parseMetricValue } from '../utils'; import { Refs } from '../../types'; -import { getValueFormatter } from '../../utils/valueFormatter'; export default function transformProps( chartProps: BigNumberTotalChartProps, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts index c05a427f31..4daa8f4401 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/transformProps.ts @@ -28,6 +28,7 @@ import { getXAxisLabel, Metric, ValueFormatter, + getValueFormatter, } from '@superset-ui/core'; import { EChartsCoreOption, graphic } from 'echarts'; import { @@ -39,7 +40,6 @@ import { import { getDateFormatter, parseMetricValue } from '../utils'; import { getDefaultTooltip } from '../../utils/tooltip'; import { Refs } from '../../types'; -import { getValueFormatter } from '../../utils/valueFormatter'; const defaultNumberFormatter = getNumberFormatter(); export function renderTooltipFactory( diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/transformProps.ts index 796aa36e40..41319079dc 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/transformProps.ts @@ -24,6 +24,7 @@ import { NumberFormats, ValueFormatter, getColumnLabel, + getValueFormatter, } from '@superset-ui/core'; import { CallbackDataParams } from 'echarts/types/src/util/types'; import { EChartsCoreOption, FunnelSeriesOption } from 'echarts'; @@ -45,7 +46,6 @@ import { defaultGrid } from '../defaults'; import { OpacityEnum, DEFAULT_LEGEND_FORM_DATA } from '../constants'; import { getDefaultTooltip } from '../utils/tooltip'; import { Refs } from '../types'; -import { getValueFormatter } from '../utils/valueFormatter'; const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts index ffbb746bf0..0fd41e88a0 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts @@ -23,6 +23,7 @@ import { DataRecord, getMetricLabel, getColumnLabel, + getValueFormatter, } from '@superset-ui/core'; import { EChartsCoreOption, GaugeSeriesOption } from 'echarts'; import { GaugeDataItemOption } from 'echarts/types/src/chart/gauge/GaugeSeries'; @@ -46,7 +47,6 @@ import { OpacityEnum } from '../constants'; import { getDefaultTooltip } from '../utils/tooltip'; import { Refs } from '../types'; import { getColtypesMapping } from '../utils/series'; -import { getValueFormatter } from '../utils/valueFormatter'; const setIntervalBoundsAndColors = ( intervals: string, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts index 77e8550d09..a3028bfef8 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts @@ -34,6 +34,11 @@ import { isPhysicalColumn, isDefined, ensureIsArray, + buildCustomFormatters, + ValueFormatter, + NumberFormatter, + QueryFormMetric, + getCustomFormatter, } from '@superset-ui/core'; import { getOriginalSeries } from '@superset-ui/chart-controls'; import { EChartsCoreOption, SeriesOption } from 'echarts'; @@ -83,6 +88,23 @@ import { } from '../Timeseries/transformers'; import { TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP } from '../constants'; import { getDefaultTooltip } from '../utils/tooltip'; +import { getYAxisFormatter } from '../utils/getYAxisFormatter'; + +const getFormatter = ( + customFormatters: Record, + defaultFormatter: NumberFormatter, + metrics: QueryFormMetric[], + formatterKey: string, + forcePercentFormat: boolean, +) => { + if (forcePercentFormat) { + return getNumberFormatter(',.0%'); + } + return ( + getCustomFormatter(customFormatters, metrics, formatterKey) ?? + defaultFormatter + ); +}; export default function transformProps( chartProps: EchartsMixedTimeseriesProps, @@ -99,7 +121,11 @@ export default function transformProps( inContextMenu, emitCrossFilters, } = chartProps; - const { verboseMap = {} } = datasource; + const { + verboseMap = {}, + currencyFormats = {}, + columnFormats = {}, + } = datasource; const { label_map: labelMap } = queriesData[0] as TimeseriesChartDataResponseResult; const { label_map: labelMapB } = @@ -160,6 +186,8 @@ export default function transformProps( sliceId, timeGrainSqla, percentageThreshold, + metrics = [], + metricsB = [], }: EchartsMixedTimeseriesFormData = { ...DEFAULT_FORM_DATA, ...formData }; const refs: Refs = {}; @@ -194,6 +222,18 @@ export default function transformProps( const formatterSecondary = getNumberFormatter( contributionMode ? ',.0%' : yAxisFormatSecondary, ); + const customFormatters = buildCustomFormatters( + [...metrics, ...metricsB], + currencyFormats, + columnFormats, + yAxisFormat, + ); + const customFormattersSecondary = buildCustomFormatters( + [...metrics, ...metricsB], + currencyFormats, + columnFormats, + yAxisFormatSecondary, + ); const primarySeries = new Set(); const secondarySeries = new Set(); @@ -292,12 +332,6 @@ export default function transformProps( parseYAxisBound, ); - const maxLabelFormatter = getOverMaxHiddenFormatter({ max, formatter }); - const maxLabelFormatterSecondary = getOverMaxHiddenFormatter({ - max: maxSecondary, - formatter: formatterSecondary, - }); - const array = ensureIsArray(chartProps.rawFormData?.time_compare); const inverted = invert(verboseMap); @@ -306,6 +340,14 @@ export default function transformProps( const seriesName = inverted[entryName] || entryName; const colorScaleKey = getOriginalSeries(seriesName, array); + const seriesFormatter = getFormatter( + customFormatters, + formatter, + metrics, + labelMap[seriesName]?.[0], + !!contributionMode, + ); + const transformedSeries = transformSeries( entry, colorScale, @@ -325,8 +367,11 @@ export default function transformProps( queryIndex: 0, formatter: seriesType === EchartsTimeseriesSeriesType.Bar - ? maxLabelFormatter - : formatter, + ? getOverMaxHiddenFormatter({ + max, + formatter: seriesFormatter, + }) + : seriesFormatter, showValueIndexes: showValueIndexesA, totalStackedValues, thresholdValues, @@ -340,6 +385,14 @@ export default function transformProps( const seriesName = `${inverted[entryName] || entryName} (1)`; const colorScaleKey = getOriginalSeries(seriesName, array); + const seriesFormatter = getFormatter( + customFormattersSecondary, + formatterSecondary, + metricsB, + labelMapB[seriesName]?.[0], + !!contributionMode, + ); + const transformedSeries = transformSeries( entry, colorScale, @@ -361,8 +414,11 @@ export default function transformProps( queryIndex: 1, formatter: seriesTypeB === EchartsTimeseriesSeriesType.Bar - ? maxLabelFormatterSecondary - : formatterSecondary, + ? getOverMaxHiddenFormatter({ + max: maxSecondary, + formatter: seriesFormatter, + }) + : seriesFormatter, showValueIndexes: showValueIndexesB, totalStackedValues: totalStackedValuesB, thresholdValues: thresholdValuesB, @@ -434,7 +490,14 @@ export default function transformProps( max, minorTick: { show: true }, minorSplitLine: { show: minorSplitLine }, - axisLabel: { formatter }, + axisLabel: { + formatter: getYAxisFormatter( + metrics, + !!contributionMode, + customFormatters, + yAxisFormat, + ), + }, scale: truncateYAxis, name: yAxisTitle, nameGap: convertInteger(yAxisTitleMargin), @@ -449,7 +512,14 @@ export default function transformProps( minorTick: { show: true }, splitLine: { show: false }, minorSplitLine: { show: minorSplitLine }, - axisLabel: { formatter: formatterSecondary }, + axisLabel: { + formatter: getYAxisFormatter( + metricsB, + !!contributionMode, + customFormattersSecondary, + yAxisFormatSecondary, + ), + }, scale: truncateYAxis, name: yAxisTitleSecondary, alignTicks, @@ -475,10 +545,36 @@ export default function transformProps( Object.keys(forecastValues).forEach(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({ ...value, seriesName: key, - formatter: primarySeries.has(key) ? formatter : formatterSecondary, + formatter: primarySeries.has(key) + ? tooltipFormatter + : tooltipFormatterSecondary, }); rows.push(`${content}`); }); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts index 7ea9cc1ffb..ea616cb201 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts @@ -25,6 +25,7 @@ import { NumberFormats, t, ValueFormatter, + getValueFormatter, } from '@superset-ui/core'; import { CallbackDataParams } from 'echarts/types/src/util/types'; import { EChartsCoreOption, PieSeriesOption } from 'echarts'; @@ -47,7 +48,6 @@ import { defaultGrid } from '../defaults'; import { convertInteger } from '../utils/convertInteger'; import { getDefaultTooltip } from '../utils/tooltip'; import { Refs } from '../types'; -import { getValueFormatter } from '../utils/valueFormatter'; const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/transformProps.ts index a12a757e44..6af4c7f653 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/transformProps.ts @@ -24,10 +24,11 @@ import { getNumberFormatter, getSequentialSchemeRegistry, getTimeFormatter, + getValueFormatter, NumberFormats, - NumberFormatter, SupersetTheme, t, + ValueFormatter, } from '@superset-ui/core'; import { EChartsCoreOption } from 'echarts'; import { CallbackDataParams } from 'echarts/types/src/util/types'; @@ -74,7 +75,7 @@ export function formatLabel({ }: { params: CallbackDataParams; labelType: EchartsSunburstLabelType; - numberFormatter: NumberFormatter; + numberFormatter: ValueFormatter; }): string { const { name = '', value } = params; const formattedValue = numberFormatter(value as number); @@ -93,7 +94,8 @@ export function formatLabel({ export function formatTooltip({ params, - numberFormatter, + primaryValueFormatter, + secondaryValueFormatter, colorByCategory, totalValue, metricLabel, @@ -107,7 +109,8 @@ export function formatTooltip({ value: number; }[]; }; - numberFormatter: NumberFormatter; + primaryValueFormatter: ValueFormatter; + secondaryValueFormatter: ValueFormatter | undefined; colorByCategory: boolean; totalValue: number; metricLabel: string; @@ -116,8 +119,10 @@ export function formatTooltip({ }): string { const { data, treePathInfo = [] } = params; const node = data as TreeNode; - const formattedValue = numberFormatter(node.value); - const formattedSecondaryValue = numberFormatter(node.secondaryValue); + const formattedValue = primaryValueFormatter(node.value); + const formattedSecondaryValue = secondaryValueFormatter?.( + node.secondaryValue, + ); const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT); const compareValuePercentage = percentFormatter( @@ -177,6 +182,7 @@ export default function transformProps( theme, inContextMenu, emitCrossFilters, + datasource, } = chartProps; const { data = [] } = queriesData[0]; const coltypeMapping = getColtypesMapping(queriesData[0]); @@ -195,12 +201,28 @@ export default function transformProps( showTotal, sliceId, } = formData; + const { currencyFormats = {}, columnFormats = {} } = datasource; const refs: Refs = {}; + const primaryValueFormatter = getValueFormatter( + metric, + currencyFormats, + columnFormats, + numberFormat, + ); + const secondaryValueFormatter = secondaryMetric + ? getValueFormatter( + secondaryMetric, + currencyFormats, + columnFormats, + numberFormat, + ) + : undefined; + const numberFormatter = getNumberFormatter(numberFormat); const formatter = (params: CallbackDataParams) => formatLabel({ params, - numberFormatter, + numberFormatter: primaryValueFormatter, labelType, }); const minShowLabelAngle = (showLabelsThreshold || 0) * 3.6; @@ -319,7 +341,8 @@ export default function transformProps( formatter: (params: any) => formatTooltip({ params, - numberFormatter, + primaryValueFormatter, + secondaryValueFormatter, colorByCategory, totalValue, metricLabel, @@ -356,7 +379,7 @@ export default function transformProps( top: 'center', left: 'center', style: { - text: t('Total: %s', numberFormatter(totalValue)), + text: t('Total: %s', primaryValueFormatter(totalValue)), fontSize: 16, fontWeight: 'bold', }, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts index 2066148c84..63b2b2f6f4 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts @@ -22,7 +22,6 @@ import { AnnotationLayer, AxisType, CategoricalColorNamespace, - CurrencyFormatter, ensureIsArray, GenericDataType, getMetricLabel, @@ -33,13 +32,11 @@ import { isFormulaAnnotationLayer, isIntervalAnnotationLayer, isPhysicalColumn, - isSavedMetric, isTimeseriesAnnotationLayer, - NumberFormats, - QueryFormMetric, t, TimeseriesChartDataResponseResult, - ValueFormatter, + buildCustomFormatters, + getCustomFormatter, } from '@superset-ui/core'; import { extractExtraMetrics, @@ -97,36 +94,7 @@ import { TIMEGRAIN_TO_TIMESTAMP, } from '../constants'; import { getDefaultTooltip } from '../utils/tooltip'; -import { - buildCustomFormatters, - getCustomFormatter, -} from '../utils/valueFormatter'; - -const getYAxisFormatter = ( - metrics: QueryFormMetric[], - forcePercentFormatter: boolean, - customFormatters: Record, - 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); -}; +import { getYAxisFormatter } from '../utils/getYAxisFormatter'; export default function transformProps( chartProps: EchartsTimeseriesChartProps, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/transformProps.ts index 9e0454b386..a9e909abf8 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/transformProps.ts @@ -24,6 +24,7 @@ import { getTimeFormatter, NumberFormats, ValueFormatter, + getValueFormatter, } from '@superset-ui/core'; import { TreemapSeriesNodeItemOption } from 'echarts/types/src/chart/treemap/TreemapSeries'; import { EChartsCoreOption, TreemapSeriesOption } from 'echarts'; @@ -48,7 +49,6 @@ import { OpacityEnum } from '../constants'; import { getDefaultTooltip } from '../utils/tooltip'; import { Refs } from '../types'; import { treeBuilder, TreeNode } from '../utils/treeBuilder'; -import { getValueFormatter } from '../utils/valueFormatter'; export function formatLabel({ params, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/getYAxisFormatter.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/getYAxisFormatter.ts new file mode 100644 index 0000000000..8d52c44464 --- /dev/null +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/getYAxisFormatter.ts @@ -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, + 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); +}; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts index f9477ea25a..663548f25d 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts @@ -518,7 +518,7 @@ export function getAxisType(dataType?: GenericDataType): AxisType { export function getOverMaxHiddenFormatter( config: { max?: number; - formatter?: NumberFormatter; + formatter?: ValueFormatter; } = {}, ) { const { max, formatter } = config;