You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by kg...@apache.org on 2023/07/07 17:28:20 UTC

[superset] branch master updated: feat: Implement support for currencies in more charts (#24594)

This is an automated email from the ASF dual-hosted git repository.

kgabryje pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new d74d7eca23 feat: Implement support for currencies in more charts (#24594)
d74d7eca23 is described below

commit d74d7eca23a3c94bc48af082c115d34c103e815d
Author: Kamil Gabryjelski <ka...@gmail.com>
AuthorDate: Fri Jul 7 19:28:13 2023 +0200

    feat: Implement support for currencies in more charts (#24594)
---
 .../superset-ui-core/src/currency-format/index.ts  |   1 +
 .../superset-ui-core/src/currency-format/utils.ts} |  22 ++-
 .../test/currency-format/utils.test.ts             | 151 +++++++++++++++++++++
 .../legacy-plugin-chart-heatmap/src/Heatmap.js     |   6 +-
 .../src/transformProps.js                          |  14 +-
 .../legacy-plugin-chart-world-map/src/WorldMap.js  |   5 +-
 .../src/transformProps.js                          |  12 ++
 .../src/BigNumber/BigNumberTotal/transformProps.ts |   2 +-
 .../BigNumberWithTrendline/transformProps.ts       |   2 +-
 .../src/Funnel/transformProps.ts                   |   2 +-
 .../src/Gauge/transformProps.ts                    |   2 +-
 .../src/MixedTimeseries/transformProps.ts          | 124 +++++++++++++++--
 .../plugin-chart-echarts/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(-)

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<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);
+});
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<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(
   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<string>();
   const secondarySeries = new Set<string>();
@@ -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(`<span style="opacity: 0.7">${content}</span>`);
         });
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<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);
-};
+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<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);
+};
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;