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

michaelsmolina 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 d619078d25 feat: Improves the Waterfall chart (#25557)
d619078d25 is described below

commit d619078d25dde63c55e9afd87e98f05d4fb82b86
Author: Michael S. Molina <[email protected]>
AuthorDate: Fri Nov 3 13:24:15 2023 -0300

    feat: Improves the Waterfall chart (#25557)
---
 .../packages/superset-ui-core/src/color/index.ts   |   1 +
 .../packages/superset-ui-core/src/color/types.ts   |   7 +
 .../packages/superset-ui-core/src/color/utils.ts   |  33 ++
 .../superset-ui-core/test/color/utils.test.ts      |  26 +-
 .../src/MixedTimeseries/transformProps.ts          |   8 +-
 .../src/Timeseries/transformProps.ts               |   8 +-
 .../src/Timeseries/transformers.ts                 |  28 --
 .../src/Waterfall/EchartsWaterfall.tsx             |  58 +---
 .../src/Waterfall/buildQuery.ts                    |  16 +-
 .../src/Waterfall/controlPanel.tsx                 |  80 +++--
 .../src/Waterfall/images/example1.png              | Bin 0 -> 69717 bytes
 .../src/Waterfall/images/example2.png              | Bin 0 -> 51985 bytes
 .../src/Waterfall/images/example3.png              | Bin 0 -> 57935 bytes
 .../src/Waterfall/images/thumbnail.png             | Bin 77020 -> 54196 bytes
 .../plugin-chart-echarts/src/Waterfall/index.ts    |  19 +-
 .../src/Waterfall/transformProps.ts                | 349 +++++++++++++--------
 .../plugin-chart-echarts/src/Waterfall/types.ts    |  36 ++-
 .../utils/{getYAxisFormatter.ts => formatters.ts}  |  28 ++
 .../test/Waterfall/buildQuery.test.ts              |  11 +-
 .../test/Waterfall/transformProps.test.ts          | 112 +++----
 .../src/components/Collapse/Collapse.test.tsx      |   3 +-
 .../components/MetadataBar/MetadataBar.test.tsx    |   3 +-
 .../controls/VizTypeControl/VizTypeGallery.tsx     |   1 +
 superset-frontend/src/utils/colorUtils.ts          |  50 ---
 24 files changed, 486 insertions(+), 391 deletions(-)

diff --git a/superset-frontend/packages/superset-ui-core/src/color/index.ts 
b/superset-frontend/packages/superset-ui-core/src/color/index.ts
index 3bbdb5d0dc..cb7e569b47 100644
--- a/superset-frontend/packages/superset-ui-core/src/color/index.ts
+++ b/superset-frontend/packages/superset-ui-core/src/color/index.ts
@@ -32,6 +32,7 @@ export * from './SequentialScheme';
 export { default as ColorSchemeRegistry } from './ColorSchemeRegistry';
 export * from './colorSchemes';
 export * from './utils';
+export * from './types';
 export {
   default as getSharedLabelColor,
   SharedLabelColor,
diff --git a/superset-frontend/packages/superset-ui-core/src/color/types.ts 
b/superset-frontend/packages/superset-ui-core/src/color/types.ts
index 2e4b528f9b..bc8b50d7a9 100644
--- a/superset-frontend/packages/superset-ui-core/src/color/types.ts
+++ b/superset-frontend/packages/superset-ui-core/src/color/types.ts
@@ -24,3 +24,10 @@ export interface ColorsInitLookup {
 export interface ColorsLookup {
   [key: string]: string;
 }
+
+export interface RgbaColor {
+  r: number;
+  g: number;
+  b: number;
+  a: number;
+}
diff --git a/superset-frontend/packages/superset-ui-core/src/color/utils.ts 
b/superset-frontend/packages/superset-ui-core/src/color/utils.ts
index 1b362efe3e..55284f16b4 100644
--- a/superset-frontend/packages/superset-ui-core/src/color/utils.ts
+++ b/superset-frontend/packages/superset-ui-core/src/color/utils.ts
@@ -86,3 +86,36 @@ export function addAlpha(color: string, opacity: number): 
string {
 
   return `${color}${alpha}`;
 }
+
+export function hexToRgb(h: string) {
+  let r = '0';
+  let g = '0';
+  let b = '0';
+
+  // 3 digits
+  if (h.length === 4) {
+    r = `0x${h[1]}${h[1]}`;
+    g = `0x${h[2]}${h[2]}`;
+    b = `0x${h[3]}${h[3]}`;
+
+    // 6 digits
+  } else if (h.length === 7) {
+    r = `0x${h[1]}${h[2]}`;
+    g = `0x${h[3]}${h[4]}`;
+    b = `0x${h[5]}${h[6]}`;
+  }
+
+  return `rgb(${+r}, ${+g}, ${+b})`;
+}
+
+export function rgbToHex(red: number, green: number, blue: number) {
+  let r = red.toString(16);
+  let g = green.toString(16);
+  let b = blue.toString(16);
+
+  if (r.length === 1) r = `0${r}`;
+  if (g.length === 1) g = `0${g}`;
+  if (b.length === 1) b = `0${b}`;
+
+  return `#${r}${g}${b}`;
+}
diff --git 
a/superset-frontend/packages/superset-ui-core/test/color/utils.test.ts 
b/superset-frontend/packages/superset-ui-core/test/color/utils.test.ts
index 308eec726b..131ea04a78 100644
--- a/superset-frontend/packages/superset-ui-core/test/color/utils.test.ts
+++ b/superset-frontend/packages/superset-ui-core/test/color/utils.test.ts
@@ -17,7 +17,12 @@
  * under the License.
  */
 
-import { getContrastingColor, addAlpha } from '@superset-ui/core';
+import {
+  getContrastingColor,
+  addAlpha,
+  hexToRgb,
+  rgbToHex,
+} from '@superset-ui/core';
 
 describe('color utils', () => {
   describe('getContrastingColor', () => {
@@ -82,4 +87,23 @@ describe('color utils', () => {
       }).toThrow();
     });
   });
+  describe('hexToRgb', () => {
+    it('convert 3 digits hex', () => {
+      expect(hexToRgb('#fff')).toBe('rgb(255, 255, 255)');
+    });
+    it('convert 6 digits hex', () => {
+      expect(hexToRgb('#ffffff')).toBe('rgb(255, 255, 255)');
+    });
+    it('convert invalid hex', () => {
+      expect(hexToRgb('#ffffffffffffff')).toBe('rgb(0, 0, 0)');
+    });
+  });
+  describe('rgbToHex', () => {
+    it('convert rgb to hex - white', () => {
+      expect(rgbToHex(255, 255, 255)).toBe('#ffffff');
+    });
+    it('convert rgb to hex - black', () => {
+      expect(rgbToHex(0, 0, 0)).toBe('#000000');
+    });
+  });
 });
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 25b7e5364a..47411e2477 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
@@ -78,8 +78,6 @@ import { convertInteger } from '../utils/convertInteger';
 import { defaultGrid, defaultYAxis } from '../defaults';
 import {
   getPadding,
-  getTooltipTimeFormatter,
-  getXAxisFormatter,
   transformEventAnnotation,
   transformFormulaAnnotation,
   transformIntervalAnnotation,
@@ -88,7 +86,11 @@ import {
 } from '../Timeseries/transformers';
 import { TIMESERIES_CONSTANTS, TIMEGRAIN_TO_TIMESTAMP } from '../constants';
 import { getDefaultTooltip } from '../utils/tooltip';
-import { getYAxisFormatter } from '../utils/getYAxisFormatter';
+import {
+  getTooltipTimeFormatter,
+  getXAxisFormatter,
+  getYAxisFormatter,
+} from '../utils/formatters';
 
 const getFormatter = (
   customFormatters: Record<string, ValueFormatter>,
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 f76c457e1c..d44ae93580 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
@@ -80,8 +80,6 @@ import { defaultGrid, defaultYAxis } from '../defaults';
 import {
   getBaselineSeriesForStream,
   getPadding,
-  getTooltipTimeFormatter,
-  getXAxisFormatter,
   transformEventAnnotation,
   transformFormulaAnnotation,
   transformIntervalAnnotation,
@@ -94,7 +92,11 @@ import {
   TIMEGRAIN_TO_TIMESTAMP,
 } from '../constants';
 import { getDefaultTooltip } from '../utils/tooltip';
-import { getYAxisFormatter } from '../utils/getYAxisFormatter';
+import {
+  getTooltipTimeFormatter,
+  getXAxisFormatter,
+  getYAxisFormatter,
+} from '../utils/formatters';
 
 export default function transformProps(
   chartProps: EchartsTimeseriesChartProps,
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts 
b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts
index 0bcc5baf8d..3b62417f16 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformers.ts
@@ -24,14 +24,10 @@ import {
   EventAnnotationLayer,
   FilterState,
   FormulaAnnotationLayer,
-  getTimeFormatter,
   IntervalAnnotationLayer,
   isTimeseriesAnnotationResult,
   LegendState,
-  smartDateDetailedFormatter,
-  smartDateFormatter,
   SupersetTheme,
-  TimeFormatter,
   TimeseriesAnnotationLayer,
   TimeseriesDataRecord,
   ValueFormatter,
@@ -582,27 +578,3 @@ export function getPadding(
         : TIMESERIES_CONSTANTS.gridOffsetRight,
   });
 }
-
-export function getTooltipTimeFormatter(
-  format?: string,
-): TimeFormatter | StringConstructor {
-  if (format === smartDateFormatter.id) {
-    return smartDateDetailedFormatter;
-  }
-  if (format) {
-    return getTimeFormatter(format);
-  }
-  return String;
-}
-
-export function getXAxisFormatter(
-  format?: string,
-): TimeFormatter | StringConstructor | undefined {
-  if (format === smartDateFormatter.id || !format) {
-    return undefined;
-  }
-  if (format) {
-    return getTimeFormatter(format);
-  }
-  return String;
-}
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/EchartsWaterfall.tsx
 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/EchartsWaterfall.tsx
index a448c9f93e..b69d2d72b7 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/EchartsWaterfall.tsx
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/EchartsWaterfall.tsx
@@ -16,59 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React, { useCallback } from 'react';
+import React from 'react';
 import Echart from '../components/Echart';
-import { allEventHandlers } from '../utils/eventHandlers';
 import { WaterfallChartTransformedProps } from './types';
+import { EventHandlers } from '../types';
 
 export default function EchartsWaterfall(
   props: WaterfallChartTransformedProps,
 ) {
-  const {
-    height,
-    width,
-    echartOptions,
-    setDataMask,
-    labelMap,
-    groupby,
-    refs,
-    selectedValues,
-  } = props;
-  const handleChange = useCallback(
-    (values: string[]) => {
-      const groupbyValues = values.map(value => labelMap[value]);
+  const { height, width, echartOptions, refs, onLegendStateChanged } = props;
 
-      setDataMask({
-        extraFormData: {
-          filters:
-            values.length === 0
-              ? []
-              : groupby.map((col, idx) => {
-                  const val = groupbyValues.map(v => v[idx]);
-                  if (val === null || val === undefined)
-                    return {
-                      col,
-                      op: 'IS NULL',
-                    };
-                  return {
-                    col,
-                    op: 'IN',
-                    val: val as (string | number | boolean)[],
-                  };
-                }),
-        },
-        filterState: {
-          value: groupbyValues.length ? groupbyValues : null,
-          selectedValues: values.length ? values : null,
-        },
-      });
+  const eventHandlers: EventHandlers = {
+    legendselectchanged: payload => {
+      onLegendStateChanged?.(payload.selected);
+    },
+    legendselectall: payload => {
+      onLegendStateChanged?.(payload.selected);
+    },
+    legendinverseselect: payload => {
+      onLegendStateChanged?.(payload.selected);
     },
-    [setDataMask, groupby, labelMap],
-  );
-
-  const eventHandlers = {
-    ...allEventHandlers(props),
-    handleChange,
   };
 
   return (
@@ -78,7 +45,6 @@ export default function EchartsWaterfall(
       width={width}
       echartOptions={echartOptions}
       eventHandlers={eventHandlers}
-      selectedValues={selectedValues}
     />
   );
 }
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts
index 353ee8fa20..e47effb3c2 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/buildQuery.ts
@@ -16,14 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import { buildQueryContext, QueryFormData } from '@superset-ui/core';
+import {
+  buildQueryContext,
+  ensureIsArray,
+  getXAxisColumn,
+  isXAxisSet,
+  QueryFormData,
+} from '@superset-ui/core';
 
 export default function buildQuery(formData: QueryFormData) {
-  const { series, columns } = formData;
+  const columns = [
+    ...(isXAxisSet(formData) ? ensureIsArray(getXAxisColumn(formData)) : []),
+    ...ensureIsArray(formData.groupby),
+  ];
   return buildQueryContext(formData, baseQueryObject => [
     {
       ...baseQueryObject,
-      columns: columns?.length ? [series, columns] : [series],
+      columns,
+      orderby: columns?.map(column => [column, true]),
     },
   ]);
 }
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx
index 852f2680b3..7a71dd4fcb 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/controlPanel.tsx
@@ -17,24 +17,26 @@
  * under the License.
  */
 import React from 'react';
-import { ensureIsArray, t } from '@superset-ui/core';
+import { t } from '@superset-ui/core';
 import {
   ControlPanelConfig,
+  ControlSubSectionHeader,
+  D3_TIME_FORMAT_DOCS,
+  DEFAULT_TIME_FORMAT,
   formatSelectOptions,
-  getStandardizedControls,
-  sections,
+  sharedControls,
 } from '@superset-ui/chart-controls';
 import { showValueControl } from '../controls';
 
 const config: ControlPanelConfig = {
   controlPanelSections: [
-    sections.legacyTimeseriesTime,
     {
       label: t('Query'),
       expanded: true,
       controlSetRows: [
-        ['series'],
-        ['columns'],
+        ['x_axis'],
+        ['time_grain_sqla'],
+        ['groupby'],
         ['metric'],
         ['adhoc_filters'],
         ['row_limit'],
@@ -44,7 +46,6 @@ const config: ControlPanelConfig = {
       label: t('Chart Options'),
       expanded: true,
       controlSetRows: [
-        ['color_scheme'],
         [showValueControl],
         [
           {
@@ -58,21 +59,41 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        [
+          <ControlSubSectionHeader>
+            {t('Series colors')}
+          </ControlSubSectionHeader>,
+        ],
         [
           {
-            name: 'rich_tooltip',
+            name: 'increase_color',
             config: {
-              type: 'CheckboxControl',
-              label: t('Rich tooltip'),
+              label: t('Increase'),
+              type: 'ColorPickerControl',
+              default: { r: 90, g: 193, b: 137, a: 1 },
+              renderTrigger: true,
+            },
+          },
+          {
+            name: 'decrease_color',
+            config: {
+              label: t('Decrease'),
+              type: 'ColorPickerControl',
+              default: { r: 224, g: 67, b: 85, a: 1 },
+              renderTrigger: true,
+            },
+          },
+          {
+            name: 'total_color',
+            config: {
+              label: t('Total'),
+              type: 'ColorPickerControl',
+              default: { r: 102, g: 102, b: 102, a: 1 },
               renderTrigger: true,
-              default: true,
-              description: t(
-                'Shows a list of all series available at that point in time',
-              ),
             },
           },
         ],
-        [<div className="section-header">{t('X Axis')}</div>],
+        [<ControlSubSectionHeader>{t('X Axis')}</ControlSubSectionHeader>],
         [
           {
             name: 'x_axis_label',
@@ -84,6 +105,16 @@ const config: ControlPanelConfig = {
             },
           },
         ],
+        [
+          {
+            name: 'x_axis_time_format',
+            config: {
+              ...sharedControls.x_axis_time_format,
+              default: DEFAULT_TIME_FORMAT,
+              description: `${D3_TIME_FORMAT_DOCS}.`,
+            },
+          },
+        ],
         [
           {
             name: 'x_ticks_layout',
@@ -104,7 +135,7 @@ const config: ControlPanelConfig = {
             },
           },
         ],
-        [<div className="section-header">{t('Y Axis')}</div>],
+        [<ControlSubSectionHeader>{t('Y Axis')}</ControlSubSectionHeader>],
         [
           {
             name: 'y_axis_label',
@@ -117,26 +148,19 @@ const config: ControlPanelConfig = {
           },
         ],
         ['y_axis_format'],
+        ['currency_format'],
       ],
     },
   ],
   controlOverrides: {
-    columns: {
+    groupby: {
       label: t('Breakdowns'),
-      description: t('Defines how each series is broken down'),
+      description:
+        t(`Breaks down the series by the category specified in this control.
+      This can help viewers understand how each category affects the overall 
value.`),
       multi: false,
     },
   },
-  formDataOverrides: formData => {
-    const series = getStandardizedControls()
-      .popAllColumns()
-      .filter(col => !ensureIsArray(formData.columns).includes(col));
-    return {
-      ...formData,
-      series,
-      metric: getStandardizedControls().shiftMetric(),
-    };
-  },
 };
 
 export default config;
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/images/example1.png
 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/images/example1.png
new file mode 100644
index 0000000000..4785cace4a
Binary files /dev/null and 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/images/example1.png
 differ
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/images/example2.png
 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/images/example2.png
new file mode 100644
index 0000000000..aee32be991
Binary files /dev/null and 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/images/example2.png
 differ
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/images/example3.png
 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/images/example3.png
new file mode 100644
index 0000000000..6e3248b03e
Binary files /dev/null and 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/images/example3.png
 differ
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/images/thumbnail.png
 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/images/thumbnail.png
index 91ef20f515..95a79df590 100644
Binary files 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/images/thumbnail.png
 and 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/images/thumbnail.png
 differ
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/index.ts 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/index.ts
index 5242434f94..c0d7a11067 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/index.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/index.ts
@@ -22,6 +22,9 @@ import buildQuery from './buildQuery';
 import controlPanel from './controlPanel';
 import transformProps from './transformProps';
 import thumbnail from './images/thumbnail.png';
+import example1 from './images/example1.png';
+import example2 from './images/example2.png';
+import example3 from './images/example3.png';
 import { EchartsWaterfallChartProps, EchartsWaterfallFormData } from './types';
 
 export default class EchartsWaterfallChartPlugin extends ChartPlugin<
@@ -44,14 +47,22 @@ export default class EchartsWaterfallChartPlugin extends 
ChartPlugin<
       controlPanel,
       loadChart: () => import('./EchartsWaterfall'),
       metadata: new ChartMetadata({
-        behaviors: [Behavior.INTERACTIVE_CHART, Behavior.DRILL_TO_DETAIL],
+        behaviors: [Behavior.INTERACTIVE_CHART],
         credits: ['https://echarts.apache.org'],
         category: t('Evolution'),
-        description: '',
-        exampleGallery: [],
+        description: t(
+          `A waterfall chart is a form of data visualization that helps in 
understanding
+          the cumulative effect of sequentially introduced positive or 
negative values.
+          These intermediate values can either be time based or category 
based.`,
+        ),
+        exampleGallery: [
+          { url: example1 },
+          { url: example2 },
+          { url: example3 },
+        ],
         name: t('Waterfall Chart'),
+        tags: [t('Categorical'), t('Comparison'), t('ECharts')],
         thumbnail,
-        tags: [],
       }),
       transformProps,
     });
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts
 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts
index 8ea8f68826..7b5faed1b2 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/transformProps.ts
@@ -17,51 +17,68 @@
  * under the License.
  */
 import {
-  CategoricalColorNamespace,
+  CurrencyFormatter,
   DataRecord,
-  getColumnLabel,
+  ensureIsArray,
+  GenericDataType,
   getMetricLabel,
   getNumberFormatter,
+  getTimeFormatter,
+  isAdhocColumn,
   NumberFormatter,
+  rgbToHex,
   SupersetTheme,
 } from '@superset-ui/core';
 import { EChartsOption, BarSeriesOption } from 'echarts';
-import { CallbackDataParams } from 'echarts/types/src/util/types';
 import {
-  EchartsWaterfallFormData,
   EchartsWaterfallChartProps,
   ISeriesData,
   WaterfallChartTransformedProps,
+  ICallbackDataParams,
 } from './types';
 import { getDefaultTooltip } from '../utils/tooltip';
 import { defaultGrid, defaultYAxis } from '../defaults';
 import { ASSIST_MARK, LEGEND, TOKEN, TOTAL_MARK } from './constants';
-import { extractGroupbyLabel, getColtypesMapping } from '../utils/series';
+import { getColtypesMapping } from '../utils/series';
 import { Refs } from '../types';
+import { NULL_STRING } from '../constants';
 
 function formatTooltip({
   theme,
   params,
-  numberFormatter,
-  richTooltip,
+  breakdownName,
+  defaultFormatter,
+  xAxisFormatter,
 }: {
   theme: SupersetTheme;
-  params: any;
-  numberFormatter: NumberFormatter;
-  richTooltip: boolean;
+  params: ICallbackDataParams[];
+  breakdownName?: string;
+  defaultFormatter: NumberFormatter | CurrencyFormatter;
+  xAxisFormatter: (value: number | string, index: number) => string;
 }) {
-  const htmlMaker = (params: any) =>
-    `
-    <div>${params.name}</div>
+  const series = params.find(
+    param => param.seriesName !== ASSIST_MARK && param.data.value !== TOKEN,
+  );
+
+  // We may have no matching series depending on the legend state
+  if (!series) {
+    return '';
+  }
+
+  const isTotal = series?.seriesName === LEGEND.TOTAL;
+  if (!series) {
+    return NULL_STRING;
+  }
+
+  const createRow = (name: string, value: string) => `
     <div>
-      ${params.marker}
       <span style="
         font-size:${theme.typography.sizes.m}px;
         color:${theme.colors.grayscale.base};
         font-weight:${theme.typography.weights.normal};
         margin-left:${theme.gridUnit * 0.5}px;"
       >
-        ${params.seriesName}:
+        ${name}:
       </span>
       <span style="
         float:right;
@@ -70,42 +87,39 @@ function formatTooltip({
         color:${theme.colors.grayscale.base};
         font-weight:${theme.typography.weights.bold}"
       >
-        ${numberFormatter(params.data)}
+        ${value}
       </span>
     </div>
   `;
 
-  if (richTooltip) {
-    const [, increaseParams, decreaseParams, totalParams] = params;
-    if (increaseParams.data !== TOKEN || increaseParams.data === null) {
-      return htmlMaker(increaseParams);
-    }
-    if (decreaseParams.data !== TOKEN) {
-      return htmlMaker(decreaseParams);
-    }
-    if (totalParams.data !== TOKEN) {
-      return htmlMaker(totalParams);
-    }
-  } else if (params.seriesName !== ASSIST_MARK) {
-    return htmlMaker(params);
+  let result = '';
+  if (!isTotal || breakdownName) {
+    result = xAxisFormatter(series.name, series.dataIndex);
   }
-  return '';
+  if (!isTotal) {
+    result += createRow(
+      series.seriesName!,
+      defaultFormatter(series.data.originalValue),
+    );
+  }
+  result += createRow(TOTAL_MARK, defaultFormatter(series.data.totalSum));
+  return result;
 }
 
 function transformer({
   data,
-  breakdown,
-  series,
+  xAxis,
   metric,
+  breakdown,
 }: {
   data: DataRecord[];
-  breakdown: string;
-  series: string;
+  xAxis: string;
   metric: string;
+  breakdown?: string;
 }) {
   // Group by series (temporary map)
   const groupedData = data.reduce((acc, cur) => {
-    const categoryLabel = cur[series] as string;
+    const categoryLabel = cur[xAxis] as string;
     const categoryData = acc.get(categoryLabel) || [];
     categoryData.push(cur);
     acc.set(categoryLabel, categoryData);
@@ -114,7 +128,7 @@ function transformer({
 
   const transformedData: DataRecord[] = [];
 
-  if (breakdown?.length) {
+  if (breakdown) {
     groupedData.forEach((value, key) => {
       const tempValue = value;
       // Calc total per period
@@ -124,7 +138,7 @@ function transformer({
       );
       // Push total per period to the end of period values array
       tempValue.push({
-        [series]: key,
+        [xAxis]: key,
         [breakdown]: TOTAL_MARK,
         [metric]: sum,
       });
@@ -138,13 +152,13 @@ function transformer({
         0,
       );
       transformedData.push({
-        [series]: key,
+        [xAxis]: key,
         [metric]: sum,
       });
       total += sum;
     });
     transformedData.push({
-      [series]: TOTAL_MARK,
+      [xAxis]: TOTAL_MARK,
       [metric]: total,
     });
   }
@@ -159,50 +173,53 @@ export default function transformProps(
     width,
     height,
     formData,
+    legendState,
     queriesData,
     hooks,
-    filterState,
     theme,
     inContextMenu,
   } = chartProps;
   const refs: Refs = {};
   const { data = [] } = queriesData[0];
   const coltypeMapping = getColtypesMapping(queriesData[0]);
-  const { setDataMask = () => {}, onContextMenu } = hooks;
+  const { setDataMask = () => {}, onContextMenu, onLegendStateChanged } = 
hooks;
   const {
-    colorScheme,
+    currencyFormat,
+    groupby,
+    increaseColor,
+    decreaseColor,
+    totalColor,
     metric = '',
-    columns,
-    series,
+    xAxis,
     xTicksLayout,
+    xAxisTimeFormat,
     showLegend,
     yAxisLabel,
     xAxisLabel,
     yAxisFormat,
-    richTooltip,
     showValue,
-    sliceId,
-  } = formData as EchartsWaterfallFormData;
-  const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
-  const numberFormatter = getNumberFormatter(yAxisFormat);
-  const formatter = (params: CallbackDataParams) => {
-    const { value, seriesName } = params;
-    let formattedValue = numberFormatter(value as number);
-    if (seriesName === LEGEND.DECREASE) {
-      formattedValue = `-${formattedValue}`;
-    }
-    return formattedValue;
+  } = formData;
+  const defaultFormatter = currencyFormat?.symbol
+    ? new CurrencyFormatter({ d3Format: yAxisFormat, currency: currencyFormat 
})
+    : getNumberFormatter(yAxisFormat);
+
+  const seriesformatter = (params: ICallbackDataParams) => {
+    const { data } = params;
+    const { originalValue } = data;
+    return defaultFormatter(originalValue as number);
   };
-  const breakdown = columns?.length ? columns : '';
-  const groupby = breakdown ? [series, breakdown] : [series];
+  const groupbyArray = ensureIsArray(groupby);
+  const breakdownColumn = groupbyArray.length ? groupbyArray[0] : undefined;
+  const breakdownName = isAdhocColumn(breakdownColumn)
+    ? breakdownColumn.label!
+    : breakdownColumn;
+  const xAxisName = isAdhocColumn(xAxis) ? xAxis.label! : xAxis;
   const metricLabel = getMetricLabel(metric);
-  const columnLabels = groupby.map(getColumnLabel);
-  const columnsLabelMap = new Map<string, string[]>();
 
   const transformedData = transformer({
     data,
-    breakdown,
-    series,
+    breakdown: breakdownName,
+    xAxis: xAxisName,
     metric: metricLabel,
   });
 
@@ -211,48 +228,128 @@ export default function transformProps(
   const decreaseData: ISeriesData[] = [];
   const totalData: ISeriesData[] = [];
 
+  let previousTotal = 0;
+
   transformedData.forEach((datum, index, self) => {
     const totalSum = self.slice(0, index + 1).reduce((prev, cur, i) => {
-      if (breakdown?.length) {
-        if (cur[breakdown] !== TOTAL_MARK || i === 0) {
+      if (breakdownName) {
+        if (cur[breakdownName] !== TOTAL_MARK || i === 0) {
           return prev + ((cur[metricLabel] as number) ?? 0);
         }
-      } else if (cur[series] !== TOTAL_MARK) {
+      } else if (cur[xAxisName] !== TOTAL_MARK) {
         return prev + ((cur[metricLabel] as number) ?? 0);
       }
       return prev;
     }, 0);
 
-    const joinedName = extractGroupbyLabel({
-      datum,
-      groupby: columnLabels,
-      coltypeMapping,
-    });
-    columnsLabelMap.set(
-      joinedName,
-      columnLabels.map(col => datum[col] as string),
-    );
-    const value = datum[metricLabel] as number;
-    const isNegative = value < 0;
-    if (datum[breakdown] === TOTAL_MARK || datum[series] === TOTAL_MARK) {
-      increaseData.push(TOKEN);
-      decreaseData.push(TOKEN);
-      assistData.push(TOKEN);
-      totalData.push(totalSum);
-    } else if (isNegative) {
-      increaseData.push(TOKEN);
-      decreaseData.push(Math.abs(value));
-      assistData.push(totalSum);
-      totalData.push(TOKEN);
+    const isTotal =
+      (breakdownName && datum[breakdownName] === TOTAL_MARK) ||
+      datum[xAxisName] === TOTAL_MARK;
+
+    const originalValue = datum[metricLabel] as number;
+    let value = originalValue;
+    const oppositeSigns = Math.sign(previousTotal) !== Math.sign(totalSum);
+    if (oppositeSigns) {
+      value = Math.sign(value) * (Math.abs(value) - Math.abs(previousTotal));
+    }
+
+    if (isTotal) {
+      increaseData.push({ value: TOKEN });
+      decreaseData.push({ value: TOKEN });
+      totalData.push({
+        value: totalSum,
+        originalValue: totalSum,
+        totalSum,
+      });
+    } else if (value < 0) {
+      increaseData.push({ value: TOKEN });
+      decreaseData.push({
+        value: totalSum < 0 ? value : -value,
+        originalValue,
+        totalSum,
+      });
+      totalData.push({ value: TOKEN });
     } else {
-      increaseData.push(value);
-      decreaseData.push(TOKEN);
-      assistData.push(totalSum - value);
-      totalData.push(TOKEN);
+      increaseData.push({
+        value: totalSum > 0 ? value : -value,
+        originalValue,
+        totalSum,
+      });
+      decreaseData.push({ value: TOKEN });
+      totalData.push({ value: TOKEN });
     }
+
+    const color = oppositeSigns
+      ? value > 0
+        ? rgbToHex(increaseColor.r, increaseColor.g, increaseColor.b)
+        : rgbToHex(decreaseColor.r, decreaseColor.g, decreaseColor.b)
+      : 'transparent';
+
+    let opacity = 1;
+    if (legendState?.[LEGEND.INCREASE] === false && value > 0) {
+      opacity = 0;
+    } else if (legendState?.[LEGEND.DECREASE] === false && value < 0) {
+      opacity = 0;
+    }
+
+    if (isTotal) {
+      assistData.push({ value: TOKEN });
+    } else if (index === 0) {
+      assistData.push({
+        value: 0,
+      });
+    } else if (oppositeSigns || Math.abs(totalSum) > Math.abs(previousTotal)) {
+      assistData.push({
+        value: previousTotal,
+        itemStyle: { color, opacity },
+      });
+    } else {
+      assistData.push({
+        value: totalSum,
+        itemStyle: { color, opacity },
+      });
+    }
+
+    previousTotal = totalSum;
+  });
+
+  const xAxisColumns: string[] = [];
+  const xAxisData = transformedData.map(row => {
+    let column = xAxisName;
+    let value = row[xAxisName];
+    if (breakdownName && row[breakdownName] !== TOTAL_MARK) {
+      column = breakdownName;
+      value = row[breakdownName];
+    }
+    if (!value) {
+      value = NULL_STRING;
+    }
+    if (typeof value !== 'string' && typeof value !== 'number') {
+      value = String(value);
+    }
+    xAxisColumns.push(column);
+    return value;
   });
 
-  let axisLabel;
+  const xAxisFormatter = (value: number | string, index: number) => {
+    if (value === TOTAL_MARK) {
+      return TOTAL_MARK;
+    }
+    if (coltypeMapping[xAxisColumns[index]] === GenericDataType.TEMPORAL) {
+      if (typeof value === 'string') {
+        return getTimeFormatter(xAxisTimeFormat)(Number.parseInt(value, 10));
+      }
+      return getTimeFormatter(xAxisTimeFormat)(value);
+    }
+    return String(value);
+  };
+
+  let axisLabel: {
+    rotate?: number;
+    hideOverlap?: boolean;
+    show?: boolean;
+    formatter?: typeof xAxisFormatter;
+  };
   if (xTicksLayout === '45°') {
     axisLabel = { rotate: -45 };
   } else if (xTicksLayout === '90°') {
@@ -264,75 +361,59 @@ export default function transformProps(
   } else {
     axisLabel = { show: true };
   }
+  axisLabel.formatter = xAxisFormatter;
+  axisLabel.hideOverlap = false;
 
-  let xAxisData: string[] = [];
-  if (breakdown?.length) {
-    xAxisData = transformedData.map(row => {
-      if (row[breakdown] === TOTAL_MARK) {
-        return row[series] as string;
-      }
-      return row[breakdown] as string;
-    });
-  } else {
-    xAxisData = transformedData.map(row => row[series] as string);
-  }
+  const seriesProps: Pick<BarSeriesOption, 'type' | 'stack' | 'emphasis'> = {
+    type: 'bar',
+    stack: 'stack',
+    emphasis: {
+      disabled: true,
+    },
+  };
 
   const barSeries: BarSeriesOption[] = [
     {
+      ...seriesProps,
       name: ASSIST_MARK,
-      type: 'bar',
-      stack: 'stack',
-      itemStyle: {
-        borderColor: 'transparent',
-        color: 'transparent',
-      },
-      emphasis: {
-        itemStyle: {
-          borderColor: 'transparent',
-          color: 'transparent',
-        },
-      },
       data: assistData,
     },
     {
+      ...seriesProps,
       name: LEGEND.INCREASE,
-      type: 'bar',
-      stack: 'stack',
       label: {
         show: showValue,
         position: 'top',
-        formatter,
+        formatter: seriesformatter,
       },
       itemStyle: {
-        color: colorFn(LEGEND.INCREASE, sliceId),
+        color: rgbToHex(increaseColor.r, increaseColor.g, increaseColor.b),
       },
       data: increaseData,
     },
     {
+      ...seriesProps,
       name: LEGEND.DECREASE,
-      type: 'bar',
-      stack: 'stack',
       label: {
         show: showValue,
         position: 'bottom',
-        formatter,
+        formatter: seriesformatter,
       },
       itemStyle: {
-        color: colorFn(LEGEND.DECREASE, sliceId),
+        color: rgbToHex(decreaseColor.r, decreaseColor.g, decreaseColor.b),
       },
       data: decreaseData,
     },
     {
+      ...seriesProps,
       name: LEGEND.TOTAL,
-      type: 'bar',
-      stack: 'stack',
       label: {
         show: showValue,
         position: 'top',
-        formatter,
+        formatter: seriesformatter,
       },
       itemStyle: {
-        color: colorFn(LEGEND.TOTAL, sliceId),
+        color: rgbToHex(totalColor.r, totalColor.g, totalColor.b),
       },
       data: totalData,
     },
@@ -348,11 +429,12 @@ export default function transformProps(
     },
     legend: {
       show: showLegend,
+      selected: legendState,
       data: [LEGEND.INCREASE, LEGEND.DECREASE, LEGEND.TOTAL],
     },
     xAxis: {
-      type: 'category',
       data: xAxisData,
+      type: 'category',
       name: xAxisLabel,
       nameTextStyle: {
         padding: [theme.gridUnit * 4, 0, 0, 0],
@@ -368,19 +450,20 @@ export default function transformProps(
       },
       nameLocation: 'middle',
       name: yAxisLabel,
-      axisLabel: { formatter: numberFormatter },
+      axisLabel: { formatter: defaultFormatter },
     },
     tooltip: {
       ...getDefaultTooltip(refs),
       appendToBody: true,
-      trigger: richTooltip ? 'axis' : 'item',
+      trigger: 'axis',
       show: !inContextMenu,
       formatter: (params: any) =>
         formatTooltip({
           theme,
           params,
-          numberFormatter,
-          richTooltip,
+          breakdownName,
+          defaultFormatter,
+          xAxisFormatter,
         }),
     },
     series: barSeries,
@@ -393,9 +476,7 @@ export default function transformProps(
     height,
     echartOptions,
     setDataMask,
-    labelMap: Object.fromEntries(columnsLabelMap),
-    groupby,
-    selectedValues: filterState.selectedValues || [],
     onContextMenu,
+    onLegendStateChanged,
   };
 }
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/types.ts 
b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/types.ts
index 9821cf3146..4386501199 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/types.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Waterfall/types.ts
@@ -19,16 +19,14 @@
 import {
   ChartDataResponseResult,
   ChartProps,
+  QueryFormColumn,
   QueryFormData,
   QueryFormMetric,
+  RgbaColor,
 } from '@superset-ui/core';
 import { BarDataItemOption } from 'echarts/types/src/chart/bar/BarSeries';
-import { OptionDataValue } from 'echarts/types/src/util/types';
-import {
-  BaseTransformedProps,
-  CrossFilterTransformedProps,
-  LegendFormData,
-} from '../types';
+import { CallbackDataParams } from 'echarts/types/src/util/types';
+import { BaseTransformedProps, LegendFormData } from '../types';
 
 export type WaterfallFormXTicksLayout =
   | '45°'
@@ -37,20 +35,28 @@ export type WaterfallFormXTicksLayout =
   | 'flat'
   | 'staggered';
 
-export type ISeriesData =
-  | BarDataItemOption
-  | OptionDataValue
-  | OptionDataValue[];
+export type ISeriesData = {
+  originalValue?: number;
+  totalSum?: number;
+} & BarDataItemOption;
+
+export type ICallbackDataParams = CallbackDataParams & {
+  axisValueLabel: string;
+  data: ISeriesData;
+};
 
 export type EchartsWaterfallFormData = QueryFormData &
   LegendFormData & {
+    increaseColor: RgbaColor;
+    decreaseColor: RgbaColor;
+    totalColor: RgbaColor;
     metric: QueryFormMetric;
-    yAxisLabel: string;
+    xAxis: QueryFormColumn;
     xAxisLabel: string;
-    yAxisFormat: string;
+    xAxisTimeFormat?: string;
     xTicksLayout?: WaterfallFormXTicksLayout;
-    series: string;
-    columns?: string;
+    yAxisLabel: string;
+    yAxisFormat: string;
   };
 
 export const DEFAULT_FORM_DATA: Partial<EchartsWaterfallFormData> = {
@@ -63,4 +69,4 @@ export interface EchartsWaterfallChartProps extends 
ChartProps {
 }
 
 export type WaterfallChartTransformedProps =
-  BaseTransformedProps<EchartsWaterfallFormData> & CrossFilterTransformedProps;
+  BaseTransformedProps<EchartsWaterfallFormData>;
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/utils/getYAxisFormatter.ts 
b/superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts
similarity index 74%
rename from 
superset-frontend/plugins/plugin-chart-echarts/src/utils/getYAxisFormatter.ts
rename to superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts
index 00843c1612..5416fa1577 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/src/utils/getYAxisFormatter.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/formatters.ts
@@ -21,8 +21,12 @@ import {
   CurrencyFormatter,
   ensureIsArray,
   getNumberFormatter,
+  getTimeFormatter,
   isSavedMetric,
   QueryFormMetric,
+  smartDateDetailedFormatter,
+  smartDateFormatter,
+  TimeFormatter,
   ValueFormatter,
 } from '@superset-ui/core';
 
@@ -51,3 +55,27 @@ export const getYAxisFormatter = (
   }
   return defaultFormatter ?? getNumberFormatter();
 };
+
+export function getTooltipTimeFormatter(
+  format?: string,
+): TimeFormatter | StringConstructor {
+  if (format === smartDateFormatter.id) {
+    return smartDateDetailedFormatter;
+  }
+  if (format) {
+    return getTimeFormatter(format);
+  }
+  return String;
+}
+
+export function getXAxisFormatter(
+  format?: string,
+): TimeFormatter | StringConstructor | undefined {
+  if (format === smartDateFormatter.id || !format) {
+    return undefined;
+  }
+  if (format) {
+    return getTimeFormatter(format);
+  }
+  return String;
+}
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/test/Waterfall/buildQuery.test.ts
 
b/superset-frontend/plugins/plugin-chart-echarts/test/Waterfall/buildQuery.test.ts
index 9c5d28376b..0eb72be3ef 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/test/Waterfall/buildQuery.test.ts
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/test/Waterfall/buildQuery.test.ts
@@ -24,15 +24,18 @@ describe('Waterfall buildQuery', () => {
     datasource: '5__table',
     granularity_sqla: 'ds',
     metric: 'foo',
-    series: 'bar',
-    columns: 'baz',
-    viz_type: 'my_chart',
+    x_axis: 'bar',
+    groupby: ['baz'],
+    viz_type: 'waterfall',
   };
 
   it('should build query fields from form data', () => {
     const queryContext = buildQuery(formData as unknown as SqlaFormData);
     const [query] = queryContext.queries;
     expect(query.metrics).toEqual(['foo']);
-    expect(query.columns).toEqual(['bar', 'baz']);
+    expect(query.columns?.[0]).toEqual(
+      expect.objectContaining({ sqlExpression: 'bar' }),
+    );
+    expect(query.columns?.[1]).toEqual('baz');
   });
 });
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/test/Waterfall/transformProps.test.ts
 
b/superset-frontend/plugins/plugin-chart-echarts/test/Waterfall/transformProps.test.ts
index c221b93033..a4abec6d49 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/test/Waterfall/transformProps.test.ts
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/test/Waterfall/transformProps.test.ts
@@ -17,27 +17,41 @@
  * under the License.
  */
 import { ChartProps, supersetTheme } from '@superset-ui/core';
-import { EchartsWaterfallChartProps } from '../../src/Waterfall/types';
+import {
+  EchartsWaterfallChartProps,
+  WaterfallChartTransformedProps,
+} from '../../src/Waterfall/types';
 import transformProps from '../../src/Waterfall/transformProps';
 
+const extractSeries = (props: WaterfallChartTransformedProps) => {
+  const { echartOptions } = props;
+  const { series } = echartOptions as unknown as {
+    series: [{ data: [{ value: number }] }];
+  };
+  return series.map(item => item.data).map(item => item.map(i => i.value));
+};
+
 describe('Waterfall tranformProps', () => {
   const data = [
-    { foo: 'Sylvester', bar: '2019', sum: 10 },
-    { foo: 'Arnold', bar: '2019', sum: 3 },
-    { foo: 'Sylvester', bar: '2020', sum: -10 },
-    { foo: 'Arnold', bar: '2020', sum: 5 },
+    { year: '2019', name: 'Sylvester', sum: 10 },
+    { year: '2019', name: 'Arnold', sum: 3 },
+    { year: '2020', name: 'Sylvester', sum: -10 },
+    { year: '2020', name: 'Arnold', sum: 5 },
   ];
 
+  const formData = {
+    colorScheme: 'bnbColors',
+    datasource: '3__table',
+    x_axis: 'year',
+    metric: 'sum',
+    increaseColor: { r: 0, b: 0, g: 0 },
+    decreaseColor: { r: 0, b: 0, g: 0 },
+    totalColor: { r: 0, b: 0, g: 0 },
+  };
+
   it('should tranform chart props for viz when breakdown not exist', () => {
-    const formData1 = {
-      colorScheme: 'bnbColors',
-      datasource: '3__table',
-      granularity_sqla: 'ds',
-      metric: 'sum',
-      series: 'bar',
-    };
     const chartProps = new ChartProps({
-      formData: formData1,
+      formData: { ...formData, series: 'bar' },
       width: 800,
       height: 600,
       queriesData: [
@@ -47,43 +61,20 @@ describe('Waterfall tranformProps', () => {
       ],
       theme: supersetTheme,
     });
-    expect(
-      transformProps(chartProps as unknown as EchartsWaterfallChartProps),
-    ).toEqual(
-      expect.objectContaining({
-        width: 800,
-        height: 600,
-        echartOptions: expect.objectContaining({
-          series: [
-            expect.objectContaining({
-              data: [0, 8, '-'],
-            }),
-            expect.objectContaining({
-              data: [13, '-', '-'],
-            }),
-            expect.objectContaining({
-              data: ['-', 5, '-'],
-            }),
-            expect.objectContaining({
-              data: ['-', '-', 8],
-            }),
-          ],
-        }),
-      }),
+    const transformedProps = transformProps(
+      chartProps as unknown as EchartsWaterfallChartProps,
     );
+    expect(extractSeries(transformedProps)).toEqual([
+      [0, 8, '-'],
+      [13, '-', '-'],
+      ['-', 5, '-'],
+      ['-', '-', 8],
+    ]);
   });
 
   it('should tranform chart props for viz when breakdown exist', () => {
-    const formData1 = {
-      colorScheme: 'bnbColors',
-      datasource: '3__table',
-      granularity_sqla: 'ds',
-      metric: 'sum',
-      series: 'bar',
-      columns: 'foo',
-    };
     const chartProps = new ChartProps({
-      formData: formData1,
+      formData: { ...formData, groupby: 'name' },
       width: 800,
       height: 600,
       queriesData: [
@@ -93,29 +84,14 @@ describe('Waterfall tranformProps', () => {
       ],
       theme: supersetTheme,
     });
-    expect(
-      transformProps(chartProps as unknown as EchartsWaterfallChartProps),
-    ).toEqual(
-      expect.objectContaining({
-        width: 800,
-        height: 600,
-        echartOptions: expect.objectContaining({
-          series: [
-            expect.objectContaining({
-              data: [0, 10, '-', 3, 3, '-'],
-            }),
-            expect.objectContaining({
-              data: [10, 3, '-', '-', 5, '-'],
-            }),
-            expect.objectContaining({
-              data: ['-', '-', '-', 10, '-', '-'],
-            }),
-            expect.objectContaining({
-              data: ['-', '-', 13, '-', '-', 8],
-            }),
-          ],
-        }),
-      }),
+    const transformedProps = transformProps(
+      chartProps as unknown as EchartsWaterfallChartProps,
     );
+    expect(extractSeries(transformedProps)).toEqual([
+      [0, 10, '-', 3, 3, '-'],
+      [10, 3, '-', '-', 5, '-'],
+      ['-', '-', '-', 10, '-', '-'],
+      ['-', '-', 13, '-', '-', 8],
+    ]);
   });
 });
diff --git a/superset-frontend/src/components/Collapse/Collapse.test.tsx 
b/superset-frontend/src/components/Collapse/Collapse.test.tsx
index 99cc623027..75e004604a 100644
--- a/superset-frontend/src/components/Collapse/Collapse.test.tsx
+++ b/superset-frontend/src/components/Collapse/Collapse.test.tsx
@@ -19,8 +19,7 @@
 import React from 'react';
 import { render, screen } from 'spec/helpers/testing-library';
 import userEvent from '@testing-library/user-event';
-import { supersetTheme } from '@superset-ui/core';
-import { hexToRgb } from 'src/utils/colorUtils';
+import { supersetTheme, hexToRgb } from '@superset-ui/core';
 import Collapse, { CollapseProps } from '.';
 
 function renderCollapse(props?: CollapseProps) {
diff --git a/superset-frontend/src/components/MetadataBar/MetadataBar.test.tsx 
b/superset-frontend/src/components/MetadataBar/MetadataBar.test.tsx
index 549b917ec1..7a7f7430f2 100644
--- a/superset-frontend/src/components/MetadataBar/MetadataBar.test.tsx
+++ b/superset-frontend/src/components/MetadataBar/MetadataBar.test.tsx
@@ -20,8 +20,7 @@ import React from 'react';
 import { render, screen, within } from 'spec/helpers/testing-library';
 import userEvent from '@testing-library/user-event';
 import * as resizeDetector from 'react-resize-detector';
-import { supersetTheme } from '@superset-ui/core';
-import { hexToRgb } from 'src/utils/colorUtils';
+import { supersetTheme, hexToRgb } from '@superset-ui/core';
 import MetadataBar, {
   MIN_NUMBER_ITEMS,
   MAX_NUMBER_ITEMS,
diff --git 
a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx
 
b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx
index c194d2fae1..2563dba01c 100644
--- 
a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx
+++ 
b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx
@@ -854,6 +854,7 @@ export default function VizTypeGallery(props: 
VizTypeGalleryProps) {
             <Examples>
               {(selectedVizMetadata?.exampleGallery || []).map(example => (
                 <img
+                  key={example.url}
                   src={example.url}
                   alt={example.caption}
                   title={example.caption}
diff --git a/superset-frontend/src/utils/colorUtils.ts 
b/superset-frontend/src/utils/colorUtils.ts
deleted file mode 100644
index f30828df97..0000000000
--- a/superset-frontend/src/utils/colorUtils.ts
+++ /dev/null
@@ -1,50 +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.
- */
-export function hexToRgb(h: string) {
-  let r = '0';
-  let g = '0';
-  let b = '0';
-
-  // 3 digits
-  if (h.length === 4) {
-    r = `0x${h[1]}${h[1]}`;
-    g = `0x${h[2]}${h[2]}`;
-    b = `0x${h[3]}${h[3]}`;
-
-    // 6 digits
-  } else if (h.length === 7) {
-    r = `0x${h[1]}${h[2]}`;
-    g = `0x${h[3]}${h[4]}`;
-    b = `0x${h[5]}${h[6]}`;
-  }
-
-  return `rgb(${+r}, ${+g}, ${+b})`;
-}
-
-export function rgbToHex(red: number, green: number, blue: number) {
-  let r = red.toString(16);
-  let g = green.toString(16);
-  let b = blue.toString(16);
-
-  if (r.length === 1) r = `0${r}`;
-  if (g.length === 1) g = `0${g}`;
-  if (b.length === 1) b = `0${b}`;
-
-  return `#${r}${g}${b}`;
-}

Reply via email to