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/08/02 17:22:53 UTC

[superset] branch master updated: feat: Add currencies controls in control panels (#24718)

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 f7e76d02b7 feat: Add currencies controls in control panels (#24718)
f7e76d02b7 is described below

commit f7e76d02b7cbe4940946673590bb979984ace9f5
Author: Kamil Gabryjelski <ka...@gmail.com>
AuthorDate: Wed Aug 2 19:22:45 2023 +0200

    feat: Add currencies controls in control panels (#24718)
---
 .../src/components/ControlForm/controls.tsx        |  92 ---------------
 .../ColumnConfigControl/ColumnConfigPopover.tsx    |  73 ------------
 .../src/shared-controls/components/index.tsx       |   3 -
 .../src/shared-controls/sharedControls.tsx         |   7 ++
 .../superset-ui-chart-controls/src/types.ts        |  64 +++++++++-
 .../src/currency-format/CurrencyFormatter.ts       |   2 +-
 .../superset-ui-core/src/currency-format/utils.ts  |  42 +++++--
 .../test/currency-format/utils.test.ts             |  70 +++++++++--
 .../src/controlPanel.tsx                           |   1 +
 .../src/transformProps.js                          |   2 +
 .../src/controlPanel.ts                            |   5 +
 .../src/transformProps.js                          |   5 +-
 .../src/BigNumber/BigNumberTotal/controlPanel.ts   |   1 +
 .../src/BigNumber/BigNumberTotal/transformProps.ts |   2 +
 .../BigNumberWithTrendline/controlPanel.tsx        |   1 +
 .../BigNumberWithTrendline/transformProps.ts       |   2 +
 .../src/Funnel/controlPanel.tsx                    |   1 +
 .../src/Funnel/transformProps.ts                   |   2 +
 .../src/Gauge/controlPanel.tsx                     |   1 +
 .../src/Gauge/transformProps.ts                    |   2 +
 .../src/MixedTimeseries/controlPanel.tsx           |  10 ++
 .../src/MixedTimeseries/transformProps.ts          |  29 +++--
 .../plugin-chart-echarts/src/Pie/controlPanel.tsx  |   1 +
 .../plugin-chart-echarts/src/Pie/transformProps.ts |   2 +
 .../src/Sunburst/controlPanel.tsx                  |   1 +
 .../src/Sunburst/transformProps.ts                 |   3 +
 .../src/Timeseries/Area/controlPanel.tsx           |   1 +
 .../src/Timeseries/Regular/Bar/controlPanel.tsx    |   1 +
 .../src/Timeseries/Regular/Line/controlPanel.tsx   |   1 +
 .../Timeseries/Regular/Scatter/controlPanel.tsx    |   1 +
 .../Timeseries/Regular/SmoothLine/controlPanel.tsx |   1 +
 .../src/Timeseries/Step/controlPanel.tsx           |   1 +
 .../src/Timeseries/transformProps.ts               |   9 +-
 .../src/Treemap/controlPanel.tsx                   |   1 +
 .../src/Treemap/transformProps.ts                  |   2 +
 .../src/utils/getYAxisFormatter.ts                 |   5 +-
 .../plugins/plugin-chart-handlebars/src/types.ts   |   2 -
 .../src/PivotTableChart.tsx                        |  15 ++-
 .../src/plugin/controlPanel.tsx                    |   1 +
 .../src/plugin/transformProps.ts                   |   2 +
 .../plugins/plugin-chart-pivot-table/src/types.ts  |   1 +
 .../test/plugin/buildQuery.test.ts                 |   1 +
 .../test/plugin/transformProps.test.ts             |   2 +
 .../plugin-chart-table/src/controlPanel.tsx        |   2 +
 .../plugin-chart-table/src/transformProps.ts       |  10 +-
 .../plugins/plugin-chart-table/src/types.ts        |  20 +++-
 superset-frontend/src/GlobalStyles.tsx             |  24 ++++
 .../src/components/Datasource/DatasourceEditor.jsx |  54 ++-------
 .../Datasource/DatasourceEditor.test.jsx           |   8 +-
 .../src/explore/components/ControlHeader.tsx       |   2 +-
 .../ColumnConfigControl/ColumnConfigControl.tsx    |  14 ++-
 .../ColumnConfigControl/ColumnConfigItem.tsx       |   8 +-
 .../ColumnConfigControl/ColumnConfigPopover.tsx    |  95 +++++++++++++++
 .../ControlForm/ControlFormItem.tsx                |  28 ++---
 .../ColumnConfigControl/ControlForm/controls.ts}   |  28 +++--
 .../ColumnConfigControl}/ControlForm/index.tsx     |   2 +-
 .../controls}/ColumnConfigControl/constants.tsx    |  68 +++++++----
 .../controls}/ColumnConfigControl/index.tsx        |   0
 .../controls}/ColumnConfigControl/types.ts         |  24 +++-
 .../controls/CurrencyControl/CurrencyControl.tsx   | 129 +++++++++++++++++++++
 .../components/controls/CurrencyControl/index.ts   |   3 +
 .../src/explore/components/controls/index.js       |   4 +
 superset-frontend/src/views/types.ts               |   1 +
 63 files changed, 677 insertions(+), 318 deletions(-)

diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/components/ControlForm/controls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/components/ControlForm/controls.tsx
deleted file mode 100644
index a1b689cbaa..0000000000
--- a/superset-frontend/packages/superset-ui-chart-controls/src/components/ControlForm/controls.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * 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 React, { ReactNode } from 'react';
-import { Slider, InputNumber, Input } from 'antd';
-import Checkbox, { CheckboxProps } from 'antd/lib/checkbox';
-import Select, { SelectOption } from '../Select';
-import RadioButtonControl, {
-  RadioButtonOption,
-} from '../../shared-controls/components/RadioButtonControl';
-
-export const ControlFormItemComponents = {
-  Slider,
-  InputNumber,
-  Input,
-  Select,
-  // Directly export Checkbox will result in "using name from external module" error
-  // ref: https://stackoverflow.com/questions/43900035/ts4023-exported-variable-x-has-or-is-using-name-y-from-external-module-but
-  Checkbox: Checkbox as React.ForwardRefExoticComponent<
-    CheckboxProps & React.RefAttributes<HTMLInputElement>
-  >,
-  RadioButtonControl,
-};
-
-export type ControlType = keyof typeof ControlFormItemComponents;
-
-export type ControlFormValueValidator<V> = (value: V) => string | false;
-
-export type ControlFormItemSpec<T extends ControlType = ControlType> = {
-  controlType: T;
-  label: ReactNode;
-  description: ReactNode;
-  placeholder?: string;
-  required?: boolean;
-  validators?: ControlFormValueValidator<any>[];
-  width?: number | string;
-  /**
-   * Time to delay change propagation.
-   */
-  debounceDelay?: number;
-} & (T extends 'Select'
-  ? {
-      options: SelectOption<any>[];
-      value?: string;
-      defaultValue?: string;
-      creatable?: boolean;
-      minWidth?: number | string;
-      validators?: ControlFormValueValidator<string>[];
-    }
-  : T extends 'RadioButtonControl'
-  ? {
-      options: RadioButtonOption[];
-      value?: string;
-      defaultValue?: string;
-    }
-  : T extends 'Checkbox'
-  ? {
-      value?: boolean;
-      defaultValue?: boolean;
-    }
-  : T extends 'InputNumber' | 'Slider'
-  ? {
-      min?: number;
-      max?: number;
-      step?: number;
-      value?: number;
-      defaultValue?: number;
-      validators?: ControlFormValueValidator<number>[];
-    }
-  : T extends 'Input'
-  ? {
-      controlType: 'Input';
-      value?: string;
-      defaultValue?: string;
-      validators?: ControlFormValueValidator<string>[];
-    }
-  : {});
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/ColumnConfigPopover.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/ColumnConfigPopover.tsx
deleted file mode 100644
index dbc40b216e..0000000000
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/ColumnConfigPopover.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * 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 React from 'react';
-import { GenericDataType } from '@superset-ui/core';
-import ControlForm, {
-  ControlFormRow,
-  ControlFormItem,
-  ControlFormItemSpec,
-} from '../../../components/ControlForm';
-import {
-  SHARED_COLUMN_CONFIG_PROPS,
-  SharedColumnConfigProp,
-} from './constants';
-import {
-  ColumnConfig,
-  ColumnConfigFormLayout,
-  ColumnConfigInfo,
-} from './types';
-
-export type ColumnConfigPopoverProps = {
-  column: ColumnConfigInfo;
-  configFormLayout: ColumnConfigFormLayout;
-  onChange: (value: ColumnConfig) => void;
-};
-
-export default function ColumnConfigPopover({
-  column,
-  configFormLayout,
-  onChange,
-}: ColumnConfigPopoverProps) {
-  return (
-    <ControlForm onChange={onChange} value={column.config}>
-      {configFormLayout[
-        column.type === undefined ? GenericDataType.STRING : column.type
-      ].map((row, i) => (
-        <ControlFormRow key={i}>
-          {row.map(meta => {
-            const key = typeof meta === 'string' ? meta : meta.name;
-            const override =
-              typeof meta === 'string'
-                ? {}
-                : 'override' in meta
-                ? meta.override
-                : meta.config;
-            const props = {
-              ...(key in SHARED_COLUMN_CONFIG_PROPS
-                ? SHARED_COLUMN_CONFIG_PROPS[key as SharedColumnConfigProp]
-                : undefined),
-              ...override,
-            } as ControlFormItemSpec;
-            return <ControlFormItem key={key} name={key} {...props} />;
-          })}
-        </ControlFormRow>
-      ))}
-    </ControlForm>
-  );
-}
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/index.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/index.tsx
index b6e635e25f..93bfbcbe68 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/index.tsx
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/index.tsx
@@ -17,10 +17,8 @@
  * under the License.
  */
 import RadioButtonControl from './RadioButtonControl';
-import ColumnConfigControl from './ColumnConfigControl';
 
 export * from './RadioButtonControl';
-export * from './ColumnConfigControl';
 
 /**
  * Shared chart controls. Can be referred via string shortcuts in chart control
@@ -28,5 +26,4 @@ export * from './ColumnConfigControl';
  */
 export default {
   RadioButtonControl,
-  ColumnConfigControl,
 };
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/sharedControls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/sharedControls.tsx
index a4af8bcbf2..abf5153bb0 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/sharedControls.tsx
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/sharedControls.tsx
@@ -317,6 +317,12 @@ const y_axis_format: SharedControlConfig<'SelectControl', SelectDefaultOption> =
     },
   };
 
+const currency_format: SharedControlConfig<'CurrencyControl'> = {
+  type: 'CurrencyControl',
+  label: t('Currency format'),
+  renderTrigger: true,
+};
+
 const x_axis_time_format: SharedControlConfig<
   'SelectControl',
   SelectDefaultOption
@@ -406,4 +412,5 @@ export default {
   x_axis: dndXAxisControl,
   show_empty_columns,
   temporal_columns_lookup,
+  currency_format,
 };
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
index dadd78cdd0..889bc9a47c 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts
@@ -34,7 +34,6 @@ import type {
 import { sharedControls, sharedControlComponents } from './shared-controls';
 
 export type { Metric } from '@superset-ui/core';
-export type { ControlFormItemSpec } from './components/ControlForm';
 export type { ControlComponentProps } from './shared-controls/components/types';
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -167,6 +166,12 @@ export type InternalControlType =
   | 'DndColumnSelect'
   | 'DndFilterSelect'
   | 'DndMetricSelect'
+  | 'CurrencyControl'
+  | 'InputNumber'
+  | 'Checkbox'
+  | 'Select'
+  | 'Slider'
+  | 'Input'
   | keyof SharedControlComponents; // expanded in `expandControlConfig`
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -495,3 +500,60 @@ export type SortSeriesData = {
   sort_series_type: SortSeriesType;
   sort_series_ascending: boolean;
 };
+
+export type ControlFormValueValidator<V> = (value: V) => string | false;
+
+export type ControlFormItemSpec<T extends ControlType = ControlType> = {
+  controlType: T;
+  label: ReactNode;
+  description: ReactNode;
+  placeholder?: string;
+  validators?: ControlFormValueValidator<any>[];
+  width?: number | string;
+  /**
+   * Time to delay change propagation.
+   */
+  debounceDelay?: number;
+} & (T extends 'Select'
+  ? {
+      options: any;
+      value?: string;
+      defaultValue?: string;
+      creatable?: boolean;
+      minWidth?: number | string;
+      validators?: ControlFormValueValidator<string>[];
+    }
+  : T extends 'RadioButtonControl'
+  ? {
+      options: [string, ReactNode][];
+      value?: string;
+      defaultValue?: string;
+    }
+  : T extends 'Checkbox'
+  ? {
+      value?: boolean;
+      defaultValue?: boolean;
+    }
+  : T extends 'InputNumber' | 'Slider'
+  ? {
+      min?: number;
+      max?: number;
+      step?: number;
+      value?: number;
+      defaultValue?: number;
+      validators?: ControlFormValueValidator<number>[];
+    }
+  : T extends 'Input'
+  ? {
+      controlType: 'Input';
+      value?: string;
+      defaultValue?: string;
+      validators?: ControlFormValueValidator<string>[];
+    }
+  : T extends 'CurrencyControl'
+  ? {
+      controlType: 'CurrencyControl';
+      value?: Currency;
+      defaultValue?: Currency;
+    }
+  : {});
diff --git a/superset-frontend/packages/superset-ui-core/src/currency-format/CurrencyFormatter.ts b/superset-frontend/packages/superset-ui-core/src/currency-format/CurrencyFormatter.ts
index 7c082abd34..a5f215fe95 100644
--- a/superset-frontend/packages/superset-ui-core/src/currency-format/CurrencyFormatter.ts
+++ b/superset-frontend/packages/superset-ui-core/src/currency-format/CurrencyFormatter.ts
@@ -31,7 +31,7 @@ interface CurrencyFormatter {
   (value: number | null | undefined): string;
 }
 
-export const getCurrencySymbol = (currency: Currency) =>
+export const getCurrencySymbol = (currency: Partial<Currency>) =>
   new Intl.NumberFormat('en-US', {
     style: 'currency',
     currency: currency.symbol,
diff --git a/superset-frontend/packages/superset-ui-core/src/currency-format/utils.ts b/superset-frontend/packages/superset-ui-core/src/currency-format/utils.ts
index 014388b86d..243aa92a9f 100644
--- a/superset-frontend/packages/superset-ui-core/src/currency-format/utils.ts
+++ b/superset-frontend/packages/superset-ui-core/src/currency-format/utils.ts
@@ -28,20 +28,24 @@ import {
 
 export const buildCustomFormatters = (
   metrics: QueryFormMetric | QueryFormMetric[] | undefined,
-  currencyFormats: Record<string, Currency>,
-  columnFormats: Record<string, string>,
+  savedCurrencyFormats: Record<string, Currency>,
+  savedColumnFormats: Record<string, string>,
   d3Format: string | undefined,
+  currencyFormat: Currency | undefined,
 ) => {
   const metricsArray = ensureIsArray(metrics);
   return metricsArray.reduce((acc, metric) => {
     if (isSavedMetric(metric)) {
-      const actualD3Format = d3Format ?? columnFormats[metric];
-      return currencyFormats[metric]
+      const actualD3Format = d3Format ?? savedColumnFormats[metric];
+      const actualCurrencyFormat = currencyFormat?.symbol
+        ? currencyFormat
+        : savedCurrencyFormats[metric];
+      return actualCurrencyFormat
         ? {
             ...acc,
             [metric]: new CurrencyFormatter({
               d3Format: actualD3Format,
-              currency: currencyFormats[metric],
+              currency: actualCurrencyFormat,
             }),
           }
         : {
@@ -67,13 +71,29 @@ export const getCustomFormatter = (
 
 export const getValueFormatter = (
   metrics: QueryFormMetric | QueryFormMetric[] | undefined,
-  currencyFormats: Record<string, Currency>,
-  columnFormats: Record<string, string>,
+  savedCurrencyFormats: Record<string, Currency>,
+  savedColumnFormats: Record<string, string>,
   d3Format: string | undefined,
+  currencyFormat: Currency | undefined,
   key?: string,
-) =>
-  getCustomFormatter(
-    buildCustomFormatters(metrics, currencyFormats, columnFormats, d3Format),
+) => {
+  const customFormatter = getCustomFormatter(
+    buildCustomFormatters(
+      metrics,
+      savedCurrencyFormats,
+      savedColumnFormats,
+      d3Format,
+      currencyFormat,
+    ),
     metrics,
     key,
-  ) ?? getNumberFormatter(d3Format);
+  );
+
+  if (customFormatter) {
+    return customFormatter;
+  }
+  if (currencyFormat?.symbol) {
+    return new CurrencyFormatter({ currency: currencyFormat, d3Format });
+  }
+  return getNumberFormatter(d3Format);
+};
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
index 9e35ce8d99..cb65528a89 100644
--- 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
@@ -27,7 +27,7 @@ import {
   ValueFormatter,
 } from '@superset-ui/core';
 
-it('buildCustomFormatters without saved metrics returns empty object', () => {
+test('buildCustomFormatters without saved metrics returns empty object', () => {
   expect(
     buildCustomFormatters(
       [
@@ -42,6 +42,7 @@ it('buildCustomFormatters without saved metrics returns empty object', () => {
       },
       {},
       ',.1f',
+      undefined,
     ),
   ).toEqual({});
 
@@ -53,11 +54,12 @@ it('buildCustomFormatters without saved metrics returns empty object', () => {
       },
       {},
       ',.1f',
+      undefined,
     ),
   ).toEqual({});
 });
 
-it('buildCustomFormatters with saved metrics returns custom formatters object', () => {
+test('buildCustomFormatters with saved metrics returns custom formatters object', () => {
   const customFormatters: Record<string, ValueFormatter> =
     buildCustomFormatters(
       [
@@ -74,6 +76,7 @@ it('buildCustomFormatters with saved metrics returns custom formatters object',
       },
       { sum__num: ',.2' },
       ',.1f',
+      undefined,
     );
 
   expect(customFormatters).toEqual({
@@ -88,7 +91,7 @@ it('buildCustomFormatters with saved metrics returns custom formatters object',
   );
 });
 
-it('buildCustomFormatters uses dataset d3 format if not provided in control panel', () => {
+test('buildCustomFormatters uses dataset d3 format if not provided in control panel', () => {
   const customFormatters: Record<string, ValueFormatter> =
     buildCustomFormatters(
       [
@@ -105,6 +108,7 @@ it('buildCustomFormatters uses dataset d3 format if not provided in control pane
       },
       { sum__num: ',.2' },
       undefined,
+      undefined,
     );
 
   expect((customFormatters.sum__num as CurrencyFormatter).d3Format).toEqual(
@@ -112,7 +116,7 @@ it('buildCustomFormatters uses dataset d3 format if not provided in control pane
   );
 });
 
-it('getCustomFormatter', () => {
+test('getCustomFormatter', () => {
   const customFormatters = {
     sum__num: new CurrencyFormatter({
       currency: { symbol: 'USD', symbolPosition: 'prefix' },
@@ -130,13 +134,20 @@ it('getCustomFormatter', () => {
   );
 });
 
-it('getValueFormatter', () => {
+test('getValueFormatter', () => {
   expect(
-    getValueFormatter(['count', 'sum__num'], {}, {}, ',.1f'),
+    getValueFormatter(['count', 'sum__num'], {}, {}, ',.1f', undefined),
   ).toBeInstanceOf(NumberFormatter);
 
   expect(
-    getValueFormatter(['count', 'sum__num'], {}, {}, ',.1f', 'count'),
+    getValueFormatter(
+      ['count', 'sum__num'],
+      {},
+      {},
+      ',.1f',
+      undefined,
+      'count',
+    ),
   ).toBeInstanceOf(NumberFormatter);
 
   expect(
@@ -145,7 +156,52 @@ it('getValueFormatter', () => {
       { count: { symbol: 'USD', symbolPosition: 'prefix' } },
       {},
       ',.1f',
+      undefined,
       'count',
     ),
   ).toBeInstanceOf(CurrencyFormatter);
 });
+
+test('getValueFormatter with currency from control panel', () => {
+  const countFormatter = getValueFormatter(
+    ['count', 'sum__num'],
+    { count: { symbol: 'USD', symbolPosition: 'prefix' } },
+    {},
+    ',.1f',
+    { symbol: 'EUR', symbolPosition: 'suffix' },
+    'count',
+  );
+  expect(countFormatter).toBeInstanceOf(CurrencyFormatter);
+  expect((countFormatter as CurrencyFormatter).currency).toEqual({
+    symbol: 'EUR',
+    symbolPosition: 'suffix',
+  });
+});
+
+test('getValueFormatter with currency from control panel when no saved currencies', () => {
+  const formatter = getValueFormatter(
+    ['count', 'sum__num'],
+    {},
+    {},
+    ',.1f',
+    { symbol: 'EUR', symbolPosition: 'suffix' },
+    undefined,
+  );
+  expect(formatter).toBeInstanceOf(CurrencyFormatter);
+  expect((formatter as CurrencyFormatter).currency).toEqual({
+    symbol: 'EUR',
+    symbolPosition: 'suffix',
+  });
+});
+
+test('getValueFormatter return NumberFormatter when no currency formatters', () => {
+  const formatter = getValueFormatter(
+    ['count', 'sum__num'],
+    {},
+    {},
+    ',.1f',
+    undefined,
+    undefined,
+  );
+  expect(formatter).toBeInstanceOf(NumberFormatter);
+});
diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx
index 3032654ba2..70a71e5024 100644
--- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx
+++ b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx
@@ -257,6 +257,7 @@ const config: ControlPanelConfig = {
           },
         ],
         ['y_axis_format'],
+        ['currency_format'],
         [
           {
             name: 'sort_x_axis',
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 a6adf5f8b8..c4906ddbe9 100644
--- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/transformProps.js
+++ b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/transformProps.js
@@ -38,6 +38,7 @@ export default function transformProps(chartProps) {
     yscaleInterval,
     yAxisBounds,
     yAxisFormat,
+    currencyFormat,
   } = formData;
   const { columnFormats = {}, currencyFormats = {} } = datasource;
   const valueFormatter = getValueFormatter(
@@ -45,6 +46,7 @@ export default function transformProps(chartProps) {
     currencyFormats,
     columnFormats,
     yAxisFormat,
+    currencyFormat,
   );
   return {
     width,
diff --git a/superset-frontend/plugins/legacy-plugin-chart-world-map/src/controlPanel.ts b/superset-frontend/plugins/legacy-plugin-chart-world-map/src/controlPanel.ts
index b0f3be22c5..4d1a21561b 100644
--- a/superset-frontend/plugins/legacy-plugin-chart-world-map/src/controlPanel.ts
+++ b/superset-frontend/plugins/legacy-plugin-chart-world-map/src/controlPanel.ts
@@ -129,6 +129,11 @@ const config: ControlPanelConfig = {
         ['color_scheme'],
       ],
     },
+    {
+      label: t('Chart Options'),
+      expanded: true,
+      controlSetRows: [['y_axis_format'], ['currency_format']],
+    },
   ],
   controlOverrides: {
     entity: {
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 182d1b0b11..4069e29428 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
@@ -43,6 +43,8 @@ export default function transformProps(chartProps) {
     colorScheme,
     sliceId,
     metric,
+    yAxisFormat,
+    currencyFormat,
   } = formData;
   const { r, g, b } = colorPicker;
   const { currencyFormats = {}, columnFormats = {} } = datasource;
@@ -51,7 +53,8 @@ export default function transformProps(chartProps) {
     metric,
     currencyFormats,
     columnFormats,
-    undefined,
+    yAxisFormat,
+    currencyFormat,
   );
 
   return {
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts
index abe4ce215f..2bb1c1d1d6 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts
@@ -62,6 +62,7 @@ export default {
         [headerFontSize],
         [subheaderFontSize],
         ['y_axis_format'],
+        ['currency_format'],
         [
           {
             name: 'time_format',
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 bdef7852d1..4a68e1ff9f 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
@@ -53,6 +53,7 @@ export default function transformProps(
     timeFormat,
     yAxisFormat,
     conditionalFormatting,
+    currencyFormat,
   } = formData;
   const refs: Refs = {};
   const { data = [], coltypes = [] } = queriesData[0];
@@ -80,6 +81,7 @@ export default function transformProps(
     currencyFormats,
     columnFormats,
     yAxisFormat,
+    currencyFormat,
   );
 
   const headerFormatter =
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/controlPanel.tsx
index 3b98f05645..26bae683c3 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberWithTrendline/controlPanel.tsx
@@ -136,6 +136,7 @@ const config: ControlPanelConfig = {
         [headerFontSize],
         [subheaderFontSize],
         ['y_axis_format'],
+        ['currency_format'],
         [
           {
             name: 'time_format',
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 4daa8f4401..ce35b65761 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
@@ -91,6 +91,7 @@ export default function transformProps(
     subheaderFontSize,
     forceTimestampFormatting,
     yAxisFormat,
+    currencyFormat,
     timeRangeFixed,
   } = formData;
   const granularity = extractTimegrain(rawFormData);
@@ -180,6 +181,7 @@ export default function transformProps(
     currencyFormats,
     columnFormats,
     yAxisFormat,
+    currencyFormat,
   );
 
   const headerFormatter =
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/controlPanel.tsx
index 75d062ce92..44a88fee4b 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/controlPanel.tsx
@@ -119,6 +119,7 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        ['currency_format'],
         [
           {
             name: 'show_labels',
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 41319079dc..538ec27750 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Funnel/transformProps.ts
@@ -112,6 +112,7 @@ export default function transformProps(
     legendType,
     metric = '',
     numberFormat,
+    currencyFormat,
     showLabels,
     showLegend,
     sliceId,
@@ -147,6 +148,7 @@ export default function transformProps(
     currencyFormats,
     columnFormats,
     numberFormat,
+    currencyFormat,
   );
 
   const transformedData: FunnelSeriesOption[] = data.map(datum => {
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx
index 5193961556..a7e0d3fdce 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx
@@ -154,6 +154,7 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        ['currency_format'],
         [
           {
             name: 'value_formatter',
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 0fd41e88a0..f32993b1df 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts
@@ -118,6 +118,7 @@ export default function transformProps(
     colorScheme,
     fontSize,
     numberFormat,
+    currencyFormat,
     animation,
     showProgress,
     overlap,
@@ -141,6 +142,7 @@ export default function transformProps(
     currencyFormats,
     columnFormats,
     numberFormat,
+    currencyFormat,
   );
   const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
   const axisLineWidth = calculateAxisLineWidth(data, fontSize, overlap);
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/controlPanel.tsx
index 58be782859..c9f9027a3e 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/controlPanel.tsx
@@ -389,6 +389,7 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        ['currency_format'],
         [
           {
             name: 'logAxis',
@@ -427,6 +428,15 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        [
+          {
+            name: 'currency_format_secondary',
+            config: {
+              ...sharedControls.currency_format,
+              label: t('Secondary currency format'),
+            },
+          },
+        ],
         [
           {
             name: 'yAxisTitleSecondary',
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 1ce4089357..25b7e5364a 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
@@ -36,9 +36,9 @@ import {
   ensureIsArray,
   buildCustomFormatters,
   ValueFormatter,
-  NumberFormatter,
   QueryFormMetric,
   getCustomFormatter,
+  CurrencyFormatter,
 } from '@superset-ui/core';
 import { getOriginalSeries } from '@superset-ui/chart-controls';
 import { EChartsCoreOption, SeriesOption } from 'echarts';
@@ -92,7 +92,7 @@ import { getYAxisFormatter } from '../utils/getYAxisFormatter';
 
 const getFormatter = (
   customFormatters: Record<string, ValueFormatter>,
-  defaultFormatter: NumberFormatter,
+  defaultFormatter: ValueFormatter,
   metrics: QueryFormMetric[],
   formatterKey: string,
   forcePercentFormat: boolean,
@@ -167,7 +167,9 @@ export default function transformProps(
     truncateYAxis,
     tooltipTimeFormat,
     yAxisFormat,
+    currencyFormat,
     yAxisFormatSecondary,
+    currencyFormatSecondary,
     xAxisTimeFormat,
     yAxisBounds,
     yAxisBoundsSecondary,
@@ -221,21 +223,32 @@ export default function transformProps(
   const xAxisDataType = dataTypes?.[xAxisLabel] ?? dataTypes?.[xAxisOrig];
   const xAxisType = getAxisType(xAxisDataType);
   const series: SeriesOption[] = [];
-  const formatter = getNumberFormatter(contributionMode ? ',.0%' : yAxisFormat);
-  const formatterSecondary = getNumberFormatter(
-    contributionMode ? ',.0%' : yAxisFormatSecondary,
-  );
+  const formatter = contributionMode
+    ? getNumberFormatter(',.0%')
+    : currencyFormat?.symbol
+    ? new CurrencyFormatter({ d3Format: yAxisFormat, currency: currencyFormat })
+    : getNumberFormatter(yAxisFormat);
+  const formatterSecondary = contributionMode
+    ? getNumberFormatter(',.0%')
+    : currencyFormatSecondary?.symbol
+    ? new CurrencyFormatter({
+        d3Format: yAxisFormatSecondary,
+        currency: currencyFormatSecondary,
+      })
+    : getNumberFormatter(yAxisFormatSecondary);
   const customFormatters = buildCustomFormatters(
     [...ensureIsArray(metrics), ...ensureIsArray(metricsB)],
     currencyFormats,
     columnFormats,
     yAxisFormat,
+    currencyFormat,
   );
   const customFormattersSecondary = buildCustomFormatters(
     [...ensureIsArray(metrics), ...ensureIsArray(metricsB)],
     currencyFormats,
     columnFormats,
     yAxisFormatSecondary,
+    currencyFormatSecondary,
   );
 
   const primarySeries = new Set<string>();
@@ -498,7 +511,7 @@ export default function transformProps(
             metrics,
             !!contributionMode,
             customFormatters,
-            yAxisFormat,
+            formatter,
           ),
         },
         scale: truncateYAxis,
@@ -520,7 +533,7 @@ export default function transformProps(
             metricsB,
             !!contributionMode,
             customFormattersSecondary,
-            yAxisFormatSecondary,
+            formatterSecondary,
           ),
         },
         scale: truncateYAxis,
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/controlPanel.tsx
index a82768b085..53d406538d 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/controlPanel.tsx
@@ -127,6 +127,7 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        ['currency_format'],
         [
           {
             name: 'date_format',
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 ea616cb201..4ad92b0bb2 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/transformProps.ts
@@ -165,6 +165,7 @@ export default function transformProps(
     legendType,
     metric = '',
     numberFormat,
+    currencyFormat,
     dateFormat,
     outerRadius,
     showLabels,
@@ -211,6 +212,7 @@ export default function transformProps(
     currencyFormats,
     columnFormats,
     numberFormat,
+    currencyFormat,
   );
 
   let totalValue = 0;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/controlPanel.tsx
index bc0a515141..1957caa234 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/controlPanel.tsx
@@ -136,6 +136,7 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        ['currency_format'],
         [
           {
             name: 'date_format',
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 6af4c7f653..a3a9b8777f 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Sunburst/transformProps.ts
@@ -195,6 +195,7 @@ export default function transformProps(
     linearColorScheme,
     labelType,
     numberFormat,
+    currencyFormat,
     dateFormat,
     showLabels,
     showLabelsThreshold,
@@ -208,6 +209,7 @@ export default function transformProps(
     currencyFormats,
     columnFormats,
     numberFormat,
+    currencyFormat,
   );
   const secondaryValueFormatter = secondaryMetric
     ? getValueFormatter(
@@ -215,6 +217,7 @@ export default function transformProps(
         currencyFormats,
         columnFormats,
         numberFormat,
+        currencyFormat,
       )
     : undefined;
 
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx
index 941face406..8515139548 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx
@@ -215,6 +215,7 @@ const config: ControlPanelConfig = {
         // eslint-disable-next-line react/jsx-key
         [<ControlSubSectionHeader>{t('Y Axis')}</ControlSubSectionHeader>],
         ['y_axis_format'],
+        ['currency_format'],
         [
           {
             name: 'logAxis',
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx
index ea43c875a1..47fe550ad7 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx
@@ -194,6 +194,7 @@ function createAxisControl(axis: 'x' | 'y'): ControlSetRow[] {
         },
       },
     ],
+    ['currency_format'],
     [
       {
         name: 'logAxis',
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx
index 396b3c7289..637a5fbc57 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx
@@ -203,6 +203,7 @@ const config: ControlPanelConfig = {
         // eslint-disable-next-line react/jsx-key
         [<ControlSubSectionHeader>{t('Y Axis')}</ControlSubSectionHeader>],
         ['y_axis_format'],
+        ['currency_format'],
         [
           {
             name: 'logAxis',
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Scatter/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Scatter/controlPanel.tsx
index ae4ea31e1d..ffcee71792 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Scatter/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Scatter/controlPanel.tsx
@@ -147,6 +147,7 @@ const config: ControlPanelConfig = {
         // eslint-disable-next-line react/jsx-key
         [<ControlSubSectionHeader>{t('Y Axis')}</ControlSubSectionHeader>],
         ['y_axis_format'],
+        ['currency_format'],
         [
           {
             name: 'logAxis',
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/SmoothLine/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/SmoothLine/controlPanel.tsx
index 67b896d225..cb7164e0ab 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/SmoothLine/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/SmoothLine/controlPanel.tsx
@@ -147,6 +147,7 @@ const config: ControlPanelConfig = {
         [<ControlSubSectionHeader>{t('Y Axis')}</ControlSubSectionHeader>],
 
         ['y_axis_format'],
+        ['currency_format'],
         [
           {
             name: 'logAxis',
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Step/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Step/controlPanel.tsx
index b9a9da8573..1921e698c2 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Step/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Step/controlPanel.tsx
@@ -197,6 +197,7 @@ const config: ControlPanelConfig = {
         // eslint-disable-next-line react/jsx-key
         [<ControlSubSectionHeader>{t('Y Axis')}</ControlSubSectionHeader>],
         ['y_axis_format'],
+        ['currency_format'],
         [
           {
             name: 'logAxis',
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 64aafc0237..0f29282c5f 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
@@ -37,6 +37,7 @@ import {
   TimeseriesChartDataResponseResult,
   buildCustomFormatters,
   getCustomFormatter,
+  CurrencyFormatter,
 } from '@superset-ui/core';
 import {
   extractExtraMetrics,
@@ -168,6 +169,7 @@ export default function transformProps(
     xAxisTitleMargin,
     yAxisBounds,
     yAxisFormat,
+    currencyFormat,
     yAxisTitle,
     yAxisTitleMargin,
     yAxisTitlePosition,
@@ -245,12 +247,15 @@ export default function transformProps(
 
   const forcePercentFormatter = Boolean(contributionMode || isAreaExpand);
   const percentFormatter = getNumberFormatter(',.0%');
-  const defaultFormatter = getNumberFormatter(yAxisFormat);
+  const defaultFormatter = currencyFormat?.symbol
+    ? new CurrencyFormatter({ d3Format: yAxisFormat, currency: currencyFormat })
+    : getNumberFormatter(yAxisFormat);
   const customFormatters = buildCustomFormatters(
     metrics,
     currencyFormats,
     columnFormats,
     yAxisFormat,
+    currencyFormat,
   );
 
   const array = ensureIsArray(chartProps.rawFormData?.time_compare);
@@ -468,7 +473,7 @@ export default function transformProps(
         metrics,
         forcePercentFormatter,
         customFormatters,
-        yAxisFormat,
+        defaultFormatter,
       ),
     },
     scale: truncateYAxis,
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/controlPanel.tsx
index 3ba079e180..e171000018 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/controlPanel.tsx
@@ -119,6 +119,7 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        ['currency_format'],
         [
           {
             name: 'date_format',
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 a9e909abf8..340ccdc93d 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Treemap/transformProps.ts
@@ -133,6 +133,7 @@ export default function transformProps(
     labelType,
     labelPosition,
     numberFormat,
+    currencyFormat,
     dateFormat,
     showLabels,
     showUpperLabels,
@@ -149,6 +150,7 @@ export default function transformProps(
     currencyFormats,
     columnFormats,
     numberFormat,
+    currencyFormat,
   );
 
   const formatter = (params: TreemapSeriesCallbackDataParams) =>
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/getYAxisFormatter.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/getYAxisFormatter.ts
index 8d52c44464..00843c1612 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/getYAxisFormatter.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/getYAxisFormatter.ts
@@ -22,7 +22,6 @@ import {
   ensureIsArray,
   getNumberFormatter,
   isSavedMetric,
-  NumberFormats,
   QueryFormMetric,
   ValueFormatter,
 } from '@superset-ui/core';
@@ -31,7 +30,7 @@ export const getYAxisFormatter = (
   metrics: QueryFormMetric[],
   forcePercentFormatter: boolean,
   customFormatters: Record<string, ValueFormatter>,
-  yAxisFormat: string = NumberFormats.SMART_NUMBER,
+  defaultFormatter: ValueFormatter,
 ) => {
   if (forcePercentFormatter) {
     return getNumberFormatter(',.0%');
@@ -50,5 +49,5 @@ export const getYAxisFormatter = (
   ) {
     return customFormatters[metricsArray[0]];
   }
-  return getNumberFormatter(yAxisFormat);
+  return defaultFormatter ?? getNumberFormatter();
 };
diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/types.ts b/superset-frontend/plugins/plugin-chart-handlebars/src/types.ts
index 741d3b982c..ff66f1a133 100644
--- a/superset-frontend/plugins/plugin-chart-handlebars/src/types.ts
+++ b/superset-frontend/plugins/plugin-chart-handlebars/src/types.ts
@@ -16,7 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { ColumnConfig } from '@superset-ui/chart-controls';
 import {
   QueryFormData,
   QueryFormMetric,
@@ -53,7 +52,6 @@ export type HandlebarsQueryFormData = QueryFormData &
     table_timestamp_format?: string;
     granularitySqla?: string;
     time_grain_sqla?: TimeGranularity;
-    column_config?: Record<string, ColumnConfig>;
   };
 
 export type HandlebarsProps = HandlebarsStylesProps &
diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx b/superset-frontend/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx
index f463990b1d..19211998a4 100644
--- a/superset-frontend/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx
+++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/PivotTableChart.tsx
@@ -140,6 +140,7 @@ export default function PivotTableChart(props: PivotTableProps) {
     colTotals,
     rowTotals,
     valueFormat,
+    currencyFormat,
     emitCrossFilters,
     setDataMask,
     selectedFilters,
@@ -155,8 +156,14 @@ export default function PivotTableChart(props: PivotTableProps) {
 
   const theme = useTheme();
   const defaultFormatter = useMemo(
-    () => getNumberFormatter(valueFormat),
-    [valueFormat],
+    () =>
+      currencyFormat?.symbol
+        ? new CurrencyFormatter({
+            currency: currencyFormat,
+            d3Format: valueFormat,
+          })
+        : getNumberFormatter(valueFormat),
+    [valueFormat, currencyFormat],
   );
   const customFormatsArray = useMemo(
     () =>
@@ -168,9 +175,9 @@ export default function PivotTableChart(props: PivotTableProps) {
       ).map(metricName => [
         metricName,
         columnFormats[metricName] || valueFormat,
-        currencyFormats[metricName],
+        currencyFormats[metricName] || currencyFormat,
       ]),
-    [columnFormats, currencyFormats, valueFormat],
+    [columnFormats, currencyFormat, currencyFormats, valueFormat],
   );
   const hasCustomMetricFormatters = customFormatsArray.length > 0;
   const metricFormatters = useMemo(
diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx
index d099406c55..3fbddffc98 100644
--- a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/controlPanel.tsx
@@ -272,6 +272,7 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        ['currency_format'],
         [
           {
             name: 'date_format',
diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts
index f335c6978e..d4b972c249 100644
--- a/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/plugin/transformProps.ts
@@ -102,6 +102,7 @@ export default function transformProps(chartProps: ChartProps<QueryFormData>) {
     metricsLayout,
     conditionalFormatting,
     timeGrainSqla,
+    currencyFormat,
   } = formData;
   const { selectedFilters } = filterState;
   const granularity = extractTimegrain(rawFormData);
@@ -157,6 +158,7 @@ export default function transformProps(chartProps: ChartProps<QueryFormData>) {
     colTotals,
     rowTotals,
     valueFormat,
+    currencyFormat,
     emitCrossFilters,
     setDataMask,
     selectedFilters,
diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/src/types.ts b/superset-frontend/plugins/plugin-chart-pivot-table/src/types.ts
index dea5236666..ebe1eb090c 100644
--- a/superset-frontend/plugins/plugin-chart-pivot-table/src/types.ts
+++ b/superset-frontend/plugins/plugin-chart-pivot-table/src/types.ts
@@ -65,6 +65,7 @@ interface PivotTableCustomizeProps {
   colTotals: boolean;
   rowTotals: boolean;
   valueFormat: string;
+  currencyFormat: Currency;
   setDataMask: SetDataMaskHook;
   emitCrossFilters?: boolean;
   selectedFilters?: SelectedFiltersType;
diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/buildQuery.test.ts b/superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/buildQuery.test.ts
index 7bb47d785c..fa13f5fcce 100644
--- a/superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/buildQuery.test.ts
+++ b/superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/buildQuery.test.ts
@@ -52,6 +52,7 @@ const formData: PivotTableQueryFormData = {
   margin: 0,
   time_grain_sqla: TimeGranularity.MONTH,
   temporal_columns_lookup: { col1: true },
+  currencyFormat: { symbol: 'USD', symbolPosition: 'prefix' },
 };
 
 test('should build groupby with series in form data', () => {
diff --git a/superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/transformProps.test.ts b/superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/transformProps.test.ts
index 91fc5d260e..c639140abe 100644
--- a/superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/transformProps.test.ts
+++ b/superset-frontend/plugins/plugin-chart-pivot-table/test/plugin/transformProps.test.ts
@@ -45,6 +45,7 @@ describe('PivotTableChart transformProps', () => {
     dateFormat: '',
     legacy_order_by: 'count',
     order_desc: true,
+    currencyFormat: { symbol: 'USD', symbolPosition: 'prefix' },
   };
   const chartProps = new ChartProps<QueryFormData>({
     formData,
@@ -91,6 +92,7 @@ describe('PivotTableChart transformProps', () => {
       emitCrossFilters: false,
       columnFormats: {},
       currencyFormats: {},
+      currencyFormat: { symbol: 'USD', symbolPosition: 'prefix' },
     });
   });
 });
diff --git a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
index 8630dc78b6..788643e130 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx
@@ -481,6 +481,8 @@ const config: ControlPanelConfig = {
               type: 'ColumnConfigControl',
               label: t('Customize columns'),
               description: t('Further customize how to display each column'),
+              width: 400,
+              height: 320,
               renderTrigger: true,
               shouldMapStateToProps() {
                 return true;
diff --git a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
index 1de9daefdd..4a48cdc8df 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-table/src/transformProps.ts
@@ -124,8 +124,11 @@ const processColumns = memoizeOne(function processColumns(
       const isTime = dataType === GenericDataType.TEMPORAL;
       const isNumber = dataType === GenericDataType.NUMERIC;
       const savedFormat = columnFormats?.[key];
-      const currency = currencyFormats?.[key];
+      const savedCurrency = currencyFormats?.[key];
       const numberFormat = config.d3NumberFormat || savedFormat;
+      const currency = config.currencyFormat?.symbol
+        ? config.currencyFormat
+        : savedCurrency;
 
       let formatter;
 
@@ -158,7 +161,10 @@ const processColumns = memoizeOne(function processColumns(
         formatter = getNumberFormatter(numberFormat || PERCENT_3_POINT);
       } else if (isMetric || (isNumber && numberFormat)) {
         formatter = currency
-          ? new CurrencyFormatter({ d3Format: numberFormat, currency })
+          ? new CurrencyFormatter({
+              d3Format: numberFormat,
+              currency,
+            })
           : getNumberFormatter(numberFormat);
       }
       return {
diff --git a/superset-frontend/plugins/plugin-chart-table/src/types.ts b/superset-frontend/plugins/plugin-chart-table/src/types.ts
index 35a463fe2c..02bae809fe 100644
--- a/superset-frontend/plugins/plugin-chart-table/src/types.ts
+++ b/superset-frontend/plugins/plugin-chart-table/src/types.ts
@@ -32,11 +32,25 @@ import {
   SetDataMaskHook,
   ContextMenuFilters,
   CurrencyFormatter,
+  Currency,
 } from '@superset-ui/core';
-import { ColorFormatters, ColumnConfig } from '@superset-ui/chart-controls';
+import { ColorFormatters } from '@superset-ui/chart-controls';
 
 export type CustomFormatter = (value: DataRecordValue) => string;
 
+export type TableColumnConfig = {
+  d3NumberFormat?: string;
+  d3SmallNumberFormat?: string;
+  d3TimeFormat?: string;
+  columnWidth?: number;
+  horizontalAlign?: 'left' | 'right' | 'center';
+  showCellBars?: boolean;
+  alignPositiveNegative?: boolean;
+  colorPositiveNegative?: boolean;
+  truncateLongCells?: boolean;
+  currencyFormat?: Currency;
+};
+
 export interface DataColumnMeta {
   // `key` is what is called `label` in the input props
   key: string;
@@ -51,7 +65,7 @@ export interface DataColumnMeta {
   isMetric?: boolean;
   isPercentMetric?: boolean;
   isNumeric?: boolean;
-  config?: ColumnConfig;
+  config?: TableColumnConfig;
 }
 
 export interface TableChartData {
@@ -75,7 +89,7 @@ export type TableChartFormData = QueryFormData & {
   show_cell_bars?: boolean;
   table_timestamp_format?: string;
   time_grain_sqla?: TimeGranularity;
-  column_config?: Record<string, ColumnConfig>;
+  column_config?: Record<string, TableColumnConfig>;
   allow_rearrange_columns?: boolean;
 };
 
diff --git a/superset-frontend/src/GlobalStyles.tsx b/superset-frontend/src/GlobalStyles.tsx
index 50f54ba88f..f983c262d7 100644
--- a/superset-frontend/src/GlobalStyles.tsx
+++ b/superset-frontend/src/GlobalStyles.tsx
@@ -66,6 +66,30 @@ export const GlobalStyles = () => (
           )};
         }
       }
+      .column-config-popover {
+        & .ant-input-number {
+          width: 100%;
+        }
+        && .btn-group svg {
+          line-height: 0;
+          top: 0;
+        }
+        & .btn-group > .btn {
+          padding: 5px 10px 6px;
+        }
+        && .ant-tabs {
+          margin-top: ${theme.gridUnit * -3}px;
+        }
+        & .ant-tabs-nav {
+          margin-left: ${theme.gridUnit * -4}px;
+          margin-right: ${theme.gridUnit * -4}px;
+          margin-bottom: ${theme.gridUnit * 2}px;
+        }
+        && .ant-tabs-tab {
+          flex: 1;
+          margin-right: 0;
+        }
+      }
     `}
   />
 );
diff --git a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
index d95839d972..3013da71d8 100644
--- a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
+++ b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx
@@ -53,6 +53,7 @@ import SpatialControl from 'src/explore/components/controls/SpatialControl';
 import withToasts from 'src/components/MessageToasts/withToasts';
 import { isFeatureEnabled } from 'src/featureFlags';
 import Icons from 'src/components/Icons';
+import CurrencyControl from 'src/explore/components/controls/CurrencyControl';
 import CollectionTable from './CollectionTable';
 import Fieldset from './Fieldset';
 import Field from './Field';
@@ -149,11 +150,6 @@ const DATA_TYPES = [
   { value: 'BOOLEAN', label: t('BOOLEAN') },
 ];
 
-const CURRENCY_SYMBOL_POSITION = [
-  { value: 'prefix', label: t('Prefix') },
-  { value: 'suffix', label: t('Suffix') },
-];
-
 const DATASOURCE_TYPES_ARR = [
   { key: 'physical', label: t('Physical (table or view)') },
   { key: 'virtual', label: t('Virtual (SQL)') },
@@ -580,43 +576,6 @@ function OwnersSelector({ datasource, onChange }) {
   );
 }
 
-const CurrencyControlContainer = styled.div`
-  ${({ theme }) => css`
-    display: flex;
-    align-items: center;
-
-    & > :first-child {
-      width: 25%;
-      margin-right: ${theme.gridUnit * 4}px;
-    }
-  `}
-`;
-const CurrencyControl = ({ onChange, value: currency = {}, currencies }) => (
-  <CurrencyControlContainer>
-    <Select
-      ariaLabel={t('Currency prefix or suffix')}
-      options={CURRENCY_SYMBOL_POSITION}
-      placeholder={t('Prefix or suffix')}
-      onChange={symbolPosition => {
-        onChange({ ...currency, symbolPosition });
-      }}
-      value={currency?.symbolPosition}
-      allowClear
-    />
-    <Select
-      ariaLabel={t('Currency symbol')}
-      options={currencies}
-      placeholder={t('Select or type currency symbol')}
-      onChange={symbol => {
-        onChange({ ...currency, symbol });
-      }}
-      value={currency?.symbol}
-      allowClear
-      allowNewOptions
-    />
-  </CurrencyControlContainer>
-);
-
 class DatasourceEditor extends React.PureComponent {
   constructor(props) {
     super(props);
@@ -1296,7 +1255,16 @@ class DatasourceEditor extends React.PureComponent {
               <Field
                 fieldKey="currency"
                 label={t('Metric currency')}
-                control={<CurrencyControl currencies={this.currencies} />}
+                control={
+                  <CurrencyControl
+                    currencySelectOverrideProps={{
+                      placeholder: t('Select or type currency symbol'),
+                    }}
+                    symbolSelectAdditionalStyles={css`
+                      max-width: 30%;
+                    `}
+                  />
+                }
               />
               <Field
                 label={t('Certified by')}
diff --git a/superset-frontend/src/components/Datasource/DatasourceEditor.test.jsx b/superset-frontend/src/components/Datasource/DatasourceEditor.test.jsx
index abb77c7f19..0cd051ace3 100644
--- a/superset-frontend/src/components/Datasource/DatasourceEditor.test.jsx
+++ b/superset-frontend/src/components/Datasource/DatasourceEditor.test.jsx
@@ -39,7 +39,12 @@ const props = {
 const DATASOURCE_ENDPOINT = 'glob:*/datasource/external_metadata_by_name/*';
 
 const asyncRender = props =>
-  waitFor(() => render(<DatasourceEditor {...props} />, { useRedux: true }));
+  waitFor(() =>
+    render(<DatasourceEditor {...props} />, {
+      useRedux: true,
+      initialState: { common: { currencies: ['USD', 'GBP', 'EUR'] } },
+    }),
+  );
 
 describe('DatasourceEditor', () => {
   fetchMock.get(DATASOURCE_ENDPOINT, []);
@@ -220,7 +225,6 @@ describe('DatasourceEditor RTL', () => {
   it('renders currency controls', async () => {
     const propsWithCurrency = {
       ...props,
-      currencies: ['USD', 'GBP', 'EUR'],
       datasource: {
         ...props.datasource,
         metrics: [
diff --git a/superset-frontend/src/explore/components/ControlHeader.tsx b/superset-frontend/src/explore/components/ControlHeader.tsx
index 9633fba613..503bbbcd2c 100644
--- a/superset-frontend/src/explore/components/ControlHeader.tsx
+++ b/superset-frontend/src/explore/components/ControlHeader.tsx
@@ -171,7 +171,7 @@ const ControlHeader: FC<ControlHeaderProps> = ({
               >
                 <Icons.ExclamationCircleOutlined
                   css={css`
-                    ${iconStyles}
+                    ${iconStyles};
                     color: ${labelColor};
                   `}
                 />
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/ColumnConfigControl.tsx b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigControl.tsx
similarity index 94%
rename from superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/ColumnConfigControl.tsx
rename to superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigControl.tsx
index 548dd4ae4d..9b10aa32b2 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/ColumnConfigControl.tsx
+++ b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigControl.tsx
@@ -23,9 +23,11 @@ import {
   t,
   GenericDataType,
 } from '@superset-ui/core';
-import ControlHeader from '../../../components/ControlHeader';
-import { ControlComponentProps } from '../types';
 
+import {
+  COLUMN_NAME_ALIASES,
+  ControlComponentProps,
+} from '@superset-ui/chart-controls';
 import ColumnConfigItem from './ColumnConfigItem';
 import {
   ColumnConfigInfo,
@@ -33,13 +35,15 @@ import {
   ColumnConfigFormLayout,
 } from './types';
 import { DEFAULT_CONFIG_FORM_LAYOUT } from './constants';
-import { COLUMN_NAME_ALIASES } from '../../../constants';
+import ControlHeader from '../../ControlHeader';
 
 export type ColumnConfigControlProps<T extends ColumnConfig> =
   ControlComponentProps<Record<string, T>> & {
     queryResponse?: ChartDataResponseResult;
     configFormLayout?: ColumnConfigFormLayout;
     appliedColumnNames?: string[];
+    width?: number | string;
+    height?: number | string;
   };
 
 /**
@@ -56,6 +60,8 @@ export default function ColumnConfigControl<T extends ColumnConfig>({
   value,
   onChange,
   configFormLayout = DEFAULT_CONFIG_FORM_LAYOUT,
+  width,
+  height,
   ...props
 }: ColumnConfigControlProps<T>) {
   const { colnames: _colnames, coltypes: _coltypes } = queryResponse || {};
@@ -127,6 +133,8 @@ export default function ColumnConfigControl<T extends ColumnConfig>({
             column={getColumnInfo(col)}
             onChange={config => setColumnConfig(col, config as T)}
             configFormLayout={configFormLayout}
+            width={width}
+            height={height}
           />
         ))}
         {needShowMoreButton && (
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/ColumnConfigItem.tsx b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigItem.tsx
similarity index 91%
rename from superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/ColumnConfigItem.tsx
rename to superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigItem.tsx
index f28d5b8d23..7e7d554e0f 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/ColumnConfigItem.tsx
+++ b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigItem.tsx
@@ -18,8 +18,8 @@
  */
 import React from 'react';
 import { useTheme } from '@superset-ui/core';
-import { Popover } from 'antd';
-import ColumnTypeLabel from '../../../components/ColumnTypeLabel/ColumnTypeLabel';
+import Popover from 'src/components/Popover';
+import { ColumnTypeLabel } from '@superset-ui/chart-controls';
 import ColumnConfigPopover, {
   ColumnConfigPopoverProps,
 } from './ColumnConfigPopover';
@@ -30,6 +30,8 @@ export default React.memo(function ColumnConfigItem({
   column,
   onChange,
   configFormLayout,
+  width,
+  height,
 }: ColumnConfigItemProps) {
   const { colors, gridUnit } = useTheme();
   const caretWidth = gridUnit * 6;
@@ -45,6 +47,8 @@ export default React.memo(function ColumnConfigItem({
       )}
       trigger="click"
       placement="right"
+      overlayInnerStyle={{ width, height }}
+      overlayClassName="column-config-popover"
     >
       <div
         css={{
diff --git a/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigPopover.tsx b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigPopover.tsx
new file mode 100644
index 0000000000..d35f46a671
--- /dev/null
+++ b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ColumnConfigPopover.tsx
@@ -0,0 +1,95 @@
+/**
+ * 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 React from 'react';
+import { GenericDataType } from '@superset-ui/core';
+import Tabs from 'src/components/Tabs';
+import {
+  SHARED_COLUMN_CONFIG_PROPS,
+  SharedColumnConfigProp,
+} from './constants';
+import {
+  ColumnConfig,
+  ColumnConfigFormItem,
+  ColumnConfigFormLayout,
+  ColumnConfigInfo,
+  ControlFormItemDefaultSpec,
+  isTabLayoutItem,
+} from './types';
+import ControlForm, { ControlFormItem, ControlFormRow } from './ControlForm';
+
+export type ColumnConfigPopoverProps = {
+  column: ColumnConfigInfo;
+  configFormLayout: ColumnConfigFormLayout;
+  onChange: (value: ColumnConfig) => void;
+  width?: number | string;
+  height?: number | string;
+};
+
+export default function ColumnConfigPopover({
+  column,
+  configFormLayout,
+  onChange,
+}: ColumnConfigPopoverProps) {
+  const renderRow = (row: ColumnConfigFormItem[], i: number) => (
+    <ControlFormRow key={i}>
+      {row.map(meta => {
+        const key = typeof meta === 'string' ? meta : meta.name;
+        const override =
+          typeof meta === 'string'
+            ? {}
+            : 'override' in meta
+            ? meta.override
+            : meta.config;
+        const props = {
+          ...(key in SHARED_COLUMN_CONFIG_PROPS
+            ? SHARED_COLUMN_CONFIG_PROPS[key as SharedColumnConfigProp]
+            : undefined),
+          ...override,
+        } as ControlFormItemDefaultSpec;
+        return <ControlFormItem key={key} name={key} {...props} />;
+      })}
+    </ControlFormRow>
+  );
+
+  const layout =
+    configFormLayout[
+      column.type === undefined ? GenericDataType.STRING : column.type
+    ];
+
+  if (isTabLayoutItem(layout[0])) {
+    return (
+      <Tabs centered>
+        {layout.map((item, i) =>
+          isTabLayoutItem(item) ? (
+            <Tabs.TabPane tab={item.tab} key={i}>
+              <ControlForm onChange={onChange} value={column.config}>
+                {item.children.map((row, i) => renderRow(row, i))}
+              </ControlForm>
+            </Tabs.TabPane>
+          ) : null,
+        )}
+      </Tabs>
+    );
+  }
+  return (
+    <ControlForm onChange={onChange} value={column.config}>
+      {layout.map((row, i) => renderRow(row as ColumnConfigFormItem[], i))}
+    </ControlForm>
+  );
+}
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/components/ControlForm/ControlFormItem.tsx b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ControlForm/ControlFormItem.tsx
similarity index 83%
rename from superset-frontend/packages/superset-ui-chart-controls/src/components/ControlForm/ControlFormItem.tsx
rename to superset-frontend/src/explore/components/controls/ColumnConfigControl/ControlForm/ControlFormItem.tsx
index 470972a81b..cacc3984fd 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/components/ControlForm/ControlFormItem.tsx
+++ b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ControlForm/ControlFormItem.tsx
@@ -18,13 +18,13 @@
  */
 import React, { useState, FunctionComponentElement, ChangeEvent } from 'react';
 import { JsonValue, useTheme } from '@superset-ui/core';
-import ControlHeader, { ControlHeaderProps } from '../ControlHeader';
-import InfoTooltipWithTrigger from '../InfoTooltipWithTrigger';
-import { ControlFormItemComponents, ControlFormItemSpec } from './controls';
+import { ControlFormItemComponents } from './controls';
+import ControlHeader, { ControlHeaderProps } from '../../../ControlHeader';
+import { ControlFormItemDefaultSpec } from '../types';
 
 export * from './controls';
 
-export type ControlFormItemProps = ControlFormItemSpec & {
+export type ControlFormItemProps = ControlFormItemDefaultSpec & {
   name: string;
   onChange?: (fieldValue: JsonValue) => void;
 };
@@ -45,7 +45,6 @@ export function ControlFormItem({
   description,
   width,
   validators,
-  required,
   onChange,
   value: initialValue,
   defaultValue,
@@ -70,7 +69,7 @@ export function ControlFormItem({
     const errors =
       (validators
         ?.map(validator =>
-          !required && isEmptyValue(fieldValue) ? false : validator(fieldValue),
+          isEmptyValue(fieldValue) ? false : validator(fieldValue),
         )
         .filter(x => !!x) as string[]) || [];
     setValidationErrors(errors);
@@ -87,20 +86,22 @@ export function ControlFormItem({
       css={{
         margin: 2 * gridUnit,
         width,
+        maxWidth: '100%',
+        flex: 1,
       }}
       onMouseEnter={() => setHovered(true)}
       onMouseLeave={() => setHovered(false)}
     >
       {controlType === 'Checkbox' ? (
         <ControlFormItemComponents.Checkbox
-          checked={value as boolean}
+          value={value as boolean}
           onChange={handleChange}
-        >
-          {label}{' '}
-          {hovered && description && (
-            <InfoTooltipWithTrigger tooltip={description} />
-          )}
-        </ControlFormItemComponents.Checkbox>
+          name={name}
+          label={label}
+          description={description}
+          validationErrors={validationErrors}
+          {...props}
+        />
       ) : (
         <>
           {label && (
@@ -110,7 +111,6 @@ export function ControlFormItem({
               description={description}
               validationErrors={validationErrors}
               hovered={hovered}
-              required={required}
             />
           )}
           {/* @ts-ignore */}
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/index.tsx b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ControlForm/controls.ts
similarity index 52%
copy from superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/index.tsx
copy to superset-frontend/src/explore/components/controls/ColumnConfigControl/ControlForm/controls.ts
index b6e635e25f..beded787bd 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/index.tsx
+++ b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ControlForm/controls.ts
@@ -16,17 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import RadioButtonControl from './RadioButtonControl';
-import ColumnConfigControl from './ColumnConfigControl';
+import { sharedControlComponents } from '@superset-ui/chart-controls';
+import { Select } from 'src/components';
+import { Input, InputNumber } from 'src/components/Input';
+import Slider from 'src/components/Slider';
+import CurrencyControl from '../../CurrencyControl';
+import CheckboxControl from '../../CheckboxControl';
 
-export * from './RadioButtonControl';
-export * from './ColumnConfigControl';
-
-/**
- * Shared chart controls. Can be referred via string shortcuts in chart control
- * configs.
- */
-export default {
-  RadioButtonControl,
-  ColumnConfigControl,
+export const ControlFormItemComponents = {
+  Slider,
+  InputNumber,
+  Input,
+  Select,
+  // Directly export Checkbox will result in "using name from external module" error
+  // ref: https://stackoverflow.com/questions/43900035/ts4023-exported-variable-x-has-or-is-using-name-y-from-external-module-but
+  Checkbox: CheckboxControl,
+  RadioButtonControl: sharedControlComponents.RadioButtonControl,
+  CurrencyControl,
 };
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/components/ControlForm/index.tsx b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ControlForm/index.tsx
similarity index 99%
rename from superset-frontend/packages/superset-ui-chart-controls/src/components/ControlForm/index.tsx
rename to superset-frontend/src/explore/components/controls/ColumnConfigControl/ControlForm/index.tsx
index 9dafb3c39f..4f4f739b85 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/components/ControlForm/index.tsx
+++ b/superset-frontend/src/explore/components/controls/ColumnConfigControl/ControlForm/index.tsx
@@ -39,8 +39,8 @@ export function ControlFormRow({ children }: ControlFormRowProps) {
       css={{
         display: 'flex',
         flexWrap: 'nowrap',
-        margin: -2 * gridUnit,
         marginBottom: gridUnit,
+        maxWidth: '100%',
       }}
     >
       {children}
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/constants.tsx b/superset-frontend/src/explore/components/controls/ColumnConfigControl/constants.tsx
similarity index 79%
rename from superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/constants.tsx
rename to superset-frontend/src/explore/components/controls/ColumnConfigControl/constants.tsx
index 7bf6c95c74..684d8faf14 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/constants.tsx
+++ b/superset-frontend/src/explore/components/controls/ColumnConfigControl/constants.tsx
@@ -18,16 +18,14 @@
  */
 import React from 'react';
 import { GenericDataType, t, validateNumber } from '@superset-ui/core';
-import { FaAlignLeft } from '@react-icons/all-files/fa/FaAlignLeft';
-import { FaAlignRight } from '@react-icons/all-files/fa/FaAlignRight';
-import { FaAlignCenter } from '@react-icons/all-files/fa/FaAlignCenter';
 import {
+  ControlFormItemSpec,
   D3_FORMAT_DOCS,
   D3_FORMAT_OPTIONS,
   D3_TIME_FORMAT_DOCS,
   D3_TIME_FORMAT_OPTIONS,
-} from '../../../utils';
-import { ControlFormItemSpec } from '../../../components/ControlForm';
+} from '@superset-ui/chart-controls';
+import Icons from 'src/components/Icons';
 import { ColumnConfigFormLayout } from './types';
 
 export type SharedColumnConfigProp =
@@ -40,13 +38,17 @@ export type SharedColumnConfigProp =
   | 'd3TimeFormat'
   | 'horizontalAlign'
   | 'truncateLongCells'
-  | 'showCellBars';
+  | 'showCellBars'
+  | 'currencyFormat';
 
 const d3NumberFormat: ControlFormItemSpec<'Select'> = {
   controlType: 'Select',
   label: t('D3 format'),
   description: D3_FORMAT_DOCS,
-  options: D3_FORMAT_OPTIONS,
+  options: D3_FORMAT_OPTIONS.map(option => ({
+    value: option[0],
+    label: option[1],
+  })),
   defaultValue: D3_FORMAT_OPTIONS[0][0],
   creatable: true,
   minWidth: '14em',
@@ -57,7 +59,10 @@ const d3TimeFormat: ControlFormItemSpec<'Select'> = {
   controlType: 'Select',
   label: t('D3 format'),
   description: D3_TIME_FORMAT_DOCS,
-  options: D3_TIME_FORMAT_OPTIONS,
+  options: D3_TIME_FORMAT_OPTIONS.map(option => ({
+    value: option[0],
+    label: option[1],
+  })),
   defaultValue: D3_TIME_FORMAT_OPTIONS[0][0],
   creatable: true,
   minWidth: '10em',
@@ -97,9 +102,9 @@ const horizontalAlign: ControlFormItemSpec<'RadioButtonControl'> & {
   debounceDelay: 50,
   defaultValue: 'left',
   options: [
-    ['left', <FaAlignLeft title={t('Left')} />],
-    ['center', <FaAlignCenter title={t('Center')} />],
-    ['right', <FaAlignRight title={t('Right')} />],
+    ['left', <Icons.AlignLeftOutlined iconSize="m" />],
+    ['center', <Icons.AlignCenterOutlined iconSize="m" />],
+    ['right', <Icons.AlignRightOutlined iconSize="m" />],
   ],
 };
 
@@ -139,6 +144,14 @@ const truncateLongCells: ControlFormItemSpec<'Checkbox'> = {
   debounceDelay: 400,
 };
 
+const currencyFormat: ControlFormItemSpec<'CurrencyControl'> = {
+  controlType: 'CurrencyControl',
+  label: t('Currency format'),
+  description: t(
+    'Customize chart metrics or columns with currency symbols as prefixes or suffixes. Choose a symbol from dropdown or type your own.',
+  ),
+  debounceDelay: 200,
+};
 /**
  * All configurable column formatting properties.
  */
@@ -160,10 +173,7 @@ export const SHARED_COLUMN_CONFIG_PROPS = {
   showCellBars,
   alignPositiveNegative,
   colorPositiveNegative,
-};
-
-export type SharedColumnConfig = {
-  [key in SharedColumnConfigProp]?: typeof SHARED_COLUMN_CONFIG_PROPS[key]['value'];
+  currencyFormat,
 };
 
 export const DEFAULT_CONFIG_FORM_LAYOUT: ColumnConfigFormLayout = {
@@ -175,14 +185,26 @@ export const DEFAULT_CONFIG_FORM_LAYOUT: ColumnConfigFormLayout = {
     ['truncateLongCells'],
   ],
   [GenericDataType.NUMERIC]: [
-    [
-      'columnWidth',
-      { name: 'horizontalAlign', override: { defaultValue: 'right' } },
-    ],
-    ['d3NumberFormat'],
-    ['d3SmallNumberFormat'],
-    ['alignPositiveNegative', 'colorPositiveNegative'],
-    ['showCellBars'],
+    {
+      tab: t('Display'),
+      children: [
+        [
+          'columnWidth',
+          { name: 'horizontalAlign', override: { defaultValue: 'right' } },
+        ],
+        ['showCellBars'],
+        ['alignPositiveNegative'],
+        ['colorPositiveNegative'],
+      ],
+    },
+    {
+      tab: t('Number formatting'),
+      children: [
+        ['d3NumberFormat'],
+        ['d3SmallNumberFormat'],
+        ['currencyFormat'],
+      ],
+    },
   ],
   [GenericDataType.TEMPORAL]: [
     [
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/index.tsx b/superset-frontend/src/explore/components/controls/ColumnConfigControl/index.tsx
similarity index 100%
rename from superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/index.tsx
rename to superset-frontend/src/explore/components/controls/ColumnConfigControl/index.tsx
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/types.ts b/superset-frontend/src/explore/components/controls/ColumnConfigControl/types.ts
similarity index 68%
rename from superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/types.ts
rename to superset-frontend/src/explore/components/controls/ColumnConfigControl/types.ts
index ef449ef8ed..c101c30435 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/components/ColumnConfigControl/types.ts
+++ b/superset-frontend/src/explore/components/controls/ColumnConfigControl/types.ts
@@ -21,11 +21,12 @@ import {
   JsonObject,
   StrictJsonValue,
 } from '@superset-ui/core';
-import { ControlFormItemSpec } from '../../../components/ControlForm';
+import { ControlFormItemSpec } from '@superset-ui/chart-controls';
 import {
   SHARED_COLUMN_CONFIG_PROPS,
   SharedColumnConfigProp,
 } from './constants';
+import { ControlFormItemComponents } from './ControlForm';
 
 /**
  * Column formatting configs.
@@ -44,14 +45,29 @@ export interface ColumnConfigInfo {
   config: JsonObject;
 }
 
+export type ControlFormItemDefaultSpec = ControlFormItemSpec<
+  keyof typeof ControlFormItemComponents
+>;
+
 export type ColumnConfigFormItem =
   | SharedColumnConfigProp
-  | { name: SharedColumnConfigProp; override: Partial<ControlFormItemSpec> }
-  | { name: string; config: ControlFormItemSpec };
+  | {
+      name: SharedColumnConfigProp;
+      override: Partial<ControlFormItemDefaultSpec>;
+    }
+  | { name: string; config: ControlFormItemDefaultSpec };
+
+export type TabLayoutItem = { tab: string; children: ColumnConfigFormItem[][] };
 
 export type ColumnConfigFormLayout = Record<
   GenericDataType,
-  ColumnConfigFormItem[][]
+  ColumnConfigFormItem[][] | TabLayoutItem[]
 >;
 
+export function isTabLayoutItem(
+  layoutItem: ColumnConfigFormItem[] | TabLayoutItem,
+): layoutItem is TabLayoutItem {
+  return !!(layoutItem as TabLayoutItem)?.tab;
+}
+
 export default {};
diff --git a/superset-frontend/src/explore/components/controls/CurrencyControl/CurrencyControl.tsx b/superset-frontend/src/explore/components/controls/CurrencyControl/CurrencyControl.tsx
new file mode 100644
index 0000000000..5bbe271150
--- /dev/null
+++ b/superset-frontend/src/explore/components/controls/CurrencyControl/CurrencyControl.tsx
@@ -0,0 +1,129 @@
+/**
+ * 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 React, { useMemo } from 'react';
+import { useSelector } from 'react-redux';
+import {
+  css,
+  Currency,
+  ensureIsArray,
+  getCurrencySymbol,
+  styled,
+  t,
+} from '@superset-ui/core';
+import { CSSObject } from '@emotion/react';
+import { Select } from 'src/components';
+import { ViewState } from 'src/views/types';
+import { SelectProps } from 'src/components/Select/types';
+import ControlHeader from '../../ControlHeader';
+
+export interface CurrencyControlProps {
+  onChange: (currency: Partial<Currency>) => void;
+  value?: Partial<Currency>;
+  symbolSelectOverrideProps?: Partial<SelectProps>;
+  currencySelectOverrideProps?: Partial<SelectProps>;
+  symbolSelectAdditionalStyles?: CSSObject;
+  currencySelectAdditionalStyles?: CSSObject;
+}
+
+const CurrencyControlContainer = styled.div`
+  ${({ theme }) => css`
+    display: flex;
+    align-items: center;
+
+    & > :first-child {
+      margin-right: ${theme.gridUnit * 4}px;
+      min-width: 0;
+      flex: 1;
+    }
+
+    & > :nth-child(2) {
+      min-width: 0;
+      flex: 1;
+    }
+  `}
+`;
+
+export const CURRENCY_SYMBOL_POSITION_OPTIONS = [
+  { value: 'prefix', label: t('Prefix') },
+  { value: 'suffix', label: t('Suffix') },
+];
+
+export const CurrencyControl = ({
+  onChange,
+  value: currency = {},
+  symbolSelectOverrideProps = {},
+  currencySelectOverrideProps = {},
+  symbolSelectAdditionalStyles,
+  currencySelectAdditionalStyles,
+  ...props
+}: CurrencyControlProps) => {
+  const currencies = useSelector<ViewState, string[]>(
+    state => state.common?.currencies,
+  );
+  const currenciesOptions = useMemo(
+    () =>
+      ensureIsArray(currencies).map(currencyCode => ({
+        value: currencyCode,
+        label: `${getCurrencySymbol({
+          symbol: currencyCode,
+        })} (${currencyCode})`,
+      })),
+    [currencies],
+  );
+  return (
+    <>
+      <ControlHeader {...props} />
+      <CurrencyControlContainer
+        css={css`
+          & > :first-child {
+            ${symbolSelectAdditionalStyles};
+          }
+          & > :nth-child(2) {
+            ${currencySelectAdditionalStyles};
+          }
+        `}
+        className="currency-control-container"
+      >
+        <Select
+          ariaLabel={t('Currency prefix or suffix')}
+          options={CURRENCY_SYMBOL_POSITION_OPTIONS}
+          placeholder={t('Prefix or suffix')}
+          onChange={(symbolPosition: string) => {
+            onChange({ ...currency, symbolPosition });
+          }}
+          value={currency?.symbolPosition}
+          allowClear
+          {...symbolSelectOverrideProps}
+        />
+        <Select
+          ariaLabel={t('Currency symbol')}
+          options={currenciesOptions}
+          placeholder={t('Currency')}
+          onChange={(symbol: string) => {
+            onChange({ ...currency, symbol });
+          }}
+          value={currency?.symbol}
+          allowClear
+          allowNewOptions
+          {...currencySelectOverrideProps}
+        />
+      </CurrencyControlContainer>
+    </>
+  );
+};
diff --git a/superset-frontend/src/explore/components/controls/CurrencyControl/index.ts b/superset-frontend/src/explore/components/controls/CurrencyControl/index.ts
new file mode 100644
index 0000000000..d99ab57767
--- /dev/null
+++ b/superset-frontend/src/explore/components/controls/CurrencyControl/index.ts
@@ -0,0 +1,3 @@
+import { CurrencyControl } from './CurrencyControl';
+
+export { CurrencyControl as default };
diff --git a/superset-frontend/src/explore/components/controls/index.js b/superset-frontend/src/explore/components/controls/index.js
index 21e3bd4cf2..725077cc8f 100644
--- a/superset-frontend/src/explore/components/controls/index.js
+++ b/superset-frontend/src/explore/components/controls/index.js
@@ -46,6 +46,8 @@ import DndColumnSelectControl, {
   DndMetricSelect,
 } from './DndColumnSelectControl';
 import XAxisSortControl from './XAxisSortControl';
+import CurrencyControl from './CurrencyControl';
+import ColumnConfigControl from './ColumnConfigControl';
 
 const controlMap = {
   AnnotationLayerControl,
@@ -54,6 +56,8 @@ const controlMap = {
   CollectionControl,
   ColorPickerControl,
   ColorSchemeControl,
+  ColumnConfigControl,
+  CurrencyControl,
   DatasourceControl,
   DateFilterControl,
   DndColumnSelectControl,
diff --git a/superset-frontend/src/views/types.ts b/superset-frontend/src/views/types.ts
index 437e7d77ef..f6b542c76b 100644
--- a/superset-frontend/src/views/types.ts
+++ b/superset-frontend/src/views/types.ts
@@ -25,6 +25,7 @@ export interface ViewState {
       SQLALCHEMY_DISPLAY_TEXT: string;
       ALERT_REPORTS_NOTIFICATION_METHODS: NotificationMethodOption[];
     };
+    currencies: string[];
   };
   messageToast: Array<Object>;
 }