diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Funnel/Stories.tsx b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Funnel/Stories.tsx
new file mode 100644
index 0000000000000..7f721f4d05733
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Funnel/Stories.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import { SuperChart, getChartTransformPropsRegistry } from '@superset-ui/core';
+import { boolean, number, select, withKnobs } from '@storybook/addon-knobs';
+import { EchartsFunnelChartPlugin } from '@superset-ui/plugin-chart-echarts';
+import transformProps from '@superset-ui/plugin-chart-echarts/lib/Funnel/transformProps';
+import { dataSource } from './constants';
+import { withResizableChartDemo } from '../../../../shared/components/ResizableChartDemo';
+
+new EchartsFunnelChartPlugin().configure({ key: 'echarts-funnel' }).register();
+
+getChartTransformPropsRegistry().registerValue('echarts-funnel', transformProps);
+
+export default {
+ title: 'Chart Plugins|plugin-chart-echarts/Funnel',
+ decorators: [withKnobs, withResizableChartDemo],
+};
+
+export const Funnel = ({ width, height }) => {
+ return (
+
+ );
+};
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Funnel/constants.ts b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Funnel/constants.ts
new file mode 100644
index 0000000000000..27885a6df44cc
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Funnel/constants.ts
@@ -0,0 +1,6 @@
+export const dataSource = [
+ { value: 89439, name: 'pv' },
+ { value: 5526, name: 'cart' },
+ { value: 2824, name: 'fav' },
+ { value: 2211, name: 'buy' },
+];
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/EchartsFunnel.tsx b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/EchartsFunnel.tsx
new file mode 100644
index 0000000000000..6e617c71f5e30
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/EchartsFunnel.tsx
@@ -0,0 +1,93 @@
+/**
+ * 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, { useCallback } from 'react';
+import { FunnelChartTransformedProps } from './types';
+import Echart from '../components/Echart';
+import { EventHandlers } from '../types';
+
+export default function EchartsFunnel({
+ height,
+ width,
+ echartOptions,
+ setDataMask,
+ labelMap,
+ groupby,
+ selectedValues,
+ formData,
+}: FunnelChartTransformedProps) {
+ const handleChange = useCallback(
+ (values: string[]) => {
+ if (!formData.emitFilter) {
+ return;
+ }
+
+ const groupbyValues = values.map(value => labelMap[value]);
+
+ 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,
+ },
+ ownState: {
+ selectedValues: values.length ? values : null,
+ },
+ });
+ },
+ [groupby, labelMap, setDataMask, selectedValues],
+ );
+
+ const eventHandlers: EventHandlers = {
+ click: props => {
+ const { name } = props;
+ const values = Object.values(selectedValues);
+ if (values.includes(name)) {
+ handleChange(values.filter(v => v !== name));
+ } else {
+ handleChange([...values, name]);
+ }
+ },
+ };
+
+ return (
+
+ );
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/buildQuery.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/buildQuery.ts
new file mode 100644
index 0000000000000..257fc15faeac6
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/buildQuery.ts
@@ -0,0 +1,27 @@
+/**
+ * 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 { buildQueryContext, QueryFormData } from '@superset-ui/core';
+
+export default function buildQuery(formData: QueryFormData) {
+ return buildQueryContext(formData, baseQueryObject => [
+ {
+ ...baseQueryObject,
+ },
+ ]);
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/controlPanel.tsx b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/controlPanel.tsx
new file mode 100644
index 0000000000000..81674f43febff
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/controlPanel.tsx
@@ -0,0 +1,192 @@
+/**
+ * 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 { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core';
+import {
+ ControlPanelConfig,
+ D3_FORMAT_OPTIONS,
+ sections,
+ sharedControls,
+} from '@superset-ui/chart-controls';
+import { DEFAULT_FORM_DATA, EchartsFunnelLabelTypeType } from './types';
+import {
+ legendMarginControl,
+ legendOrientationControl,
+ legendTypeControl,
+ showLegendControl,
+} from '../controls';
+
+const {
+ sort,
+ orient,
+ labelLine,
+ labelType,
+ numberFormat,
+ showLabels,
+ emitFilter,
+} = DEFAULT_FORM_DATA;
+
+const config: ControlPanelConfig = {
+ controlPanelSections: [
+ sections.legacyRegularTime,
+ {
+ label: t('Query'),
+ expanded: true,
+ controlSetRows: [
+ ['groupby'],
+ ['metric'],
+ ['adhoc_filters'],
+ [
+ {
+ name: 'row_limit',
+ config: {
+ ...sharedControls.row_limit,
+ default: 10,
+ },
+ },
+ ],
+ ],
+ },
+ {
+ label: t('Chart Options'),
+ expanded: true,
+ controlSetRows: [
+ ['color_scheme'],
+ isFeatureEnabled(FeatureFlag.DASHBOARD_CROSS_FILTERS)
+ ? [
+ {
+ name: 'emit_filter',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Enable emitting filters'),
+ default: emitFilter,
+ renderTrigger: true,
+ description: t('Enable emmiting filters.'),
+ },
+ },
+ ]
+ : [],
+ // eslint-disable-next-line react/jsx-key
+ [
{t('Legend')}
],
+ [showLegendControl],
+ [legendTypeControl],
+ [legendOrientationControl],
+ [legendMarginControl],
+ // eslint-disable-next-line react/jsx-key
+ [{t('Labels')}
],
+ [
+ {
+ name: 'label_type',
+ config: {
+ type: 'SelectControl',
+ label: t('Label Type'),
+ default: labelType,
+ renderTrigger: true,
+ choices: [
+ [EchartsFunnelLabelTypeType.Key, 'Category Name'],
+ [EchartsFunnelLabelTypeType.Value, 'Value'],
+ [EchartsFunnelLabelTypeType.Percent, 'Percentage'],
+ [EchartsFunnelLabelTypeType.KeyValue, 'Category and Value'],
+ [EchartsFunnelLabelTypeType.KeyPercent, 'Category and Percentage'],
+ [EchartsFunnelLabelTypeType.KeyValuePercent, 'Category, Value and Percentage'],
+ ],
+ description: t('What should be shown on the label?'),
+ },
+ },
+ ],
+ [
+ {
+ name: 'number_format',
+ config: {
+ type: 'SelectControl',
+ freeForm: true,
+ label: t('Number format'),
+ renderTrigger: true,
+ default: numberFormat,
+ choices: D3_FORMAT_OPTIONS,
+ description: `${t('D3 format syntax: https://github.com/d3/d3-format')} ${t(
+ 'Only applies when "Label Type" is set to show values.',
+ )}`,
+ },
+ },
+ ],
+ [
+ {
+ name: 'show_labels',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Show Labels'),
+ renderTrigger: true,
+ default: showLabels,
+ description: t('Whether to display the labels.'),
+ },
+ },
+ ],
+ [
+ {
+ name: 'label_line',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Label Line'),
+ default: labelLine,
+ renderTrigger: true,
+ description: t('Draw line from Funnel to label when labels outside?'),
+ },
+ },
+ ],
+ // eslint-disable-next-line react/jsx-key
+ [{t('Funnel shape')}
],
+ [
+ {
+ name: 'sort',
+ config: {
+ type: 'SelectControl',
+ label: t('sort'),
+ default: sort,
+ renderTrigger: true,
+ choices: [
+ [null, 'Default'],
+ ['ascending', 'Ascending'],
+ ['descending', 'Descending'],
+ ],
+ description: t('Sort data'),
+ },
+ },
+ {
+ name: 'orient',
+ config: {
+ type: 'SelectControl',
+ label: t('orient'),
+ default: orient,
+ renderTrigger: true,
+ choices: [
+ [null, 'Default'],
+ ['vertical', 'Vertical'],
+ ['horizontal', 'Horizontal'],
+ ],
+ description: t('Funnel chart orientation. The options are vertical, horizontal'),
+ },
+ },
+ ],
+ ],
+ },
+ ],
+};
+
+export default config;
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/images/thumbnail.png b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/images/thumbnail.png
new file mode 100644
index 0000000000000..6f594d447fb57
Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/images/thumbnail.png differ
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/index.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/index.ts
new file mode 100644
index 0000000000000..cf126e5a0d920
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/index.ts
@@ -0,0 +1,55 @@
+/**
+ * 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 { Behavior, ChartMetadata, ChartPlugin, t } from '@superset-ui/core';
+import buildQuery from './buildQuery';
+import controlPanel from './controlPanel';
+import transformProps from './transformProps';
+import thumbnail from './images/thumbnail.png';
+import { EchartsFunnelChartProps, EchartsFunnelFormData } from './types';
+
+export default class EchartsFunnelChartPlugin extends ChartPlugin<
+ EchartsFunnelFormData,
+ EchartsFunnelChartProps
+> {
+ /**
+ * The constructor is used to pass relevant metadata and callbacks that get
+ * registered in respective registries that are used throughout the library
+ * and application. A more thorough description of each property is given in
+ * the respective imported file.
+ *
+ * It is worth noting that `buildQuery` and is optional, and only needed for
+ * advanced visualizations that require either post processing operations
+ * (pivoting, rolling aggregations, sorting etc) or submitting multiple queries.
+ */
+ constructor() {
+ super({
+ buildQuery,
+ controlPanel,
+ loadChart: () => import('./EchartsFunnel'),
+ metadata: new ChartMetadata({
+ behaviors: [Behavior.INTERACTIVE_CHART],
+ credits: ['https://echarts.apache.org'],
+ description: 'Funnel Chart (Apache ECharts)',
+ name: t('Funnel Chart'),
+ thumbnail,
+ }),
+ transformProps,
+ });
+ }
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/transformProps.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/transformProps.ts
new file mode 100644
index 0000000000000..aa07ac0bdbfc3
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/transformProps.ts
@@ -0,0 +1,208 @@
+/**
+ * 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 {
+ CategoricalColorNamespace,
+ DataRecordValue,
+ DataRecord,
+ getMetricLabel,
+ getNumberFormatter,
+ NumberFormats,
+ NumberFormatter,
+} from '@superset-ui/core';
+import { CallbackDataParams } from 'echarts/types/src/util/types';
+import { EChartsOption, FunnelSeriesOption } from 'echarts';
+import {
+ DEFAULT_FORM_DATA as DEFAULT_FUNNEL_FORM_DATA,
+ EchartsFunnelChartProps,
+ EchartsFunnelFormData,
+ EchartsFunnelLabelTypeType,
+ FunnelChartTransformedProps,
+} from './types';
+import { DEFAULT_LEGEND_FORM_DATA } from '../types';
+import { extractGroupbyLabel, getChartPadding, getLegendProps } from '../utils/series';
+import { defaultGrid, defaultTooltip } from '../defaults';
+
+const percentFormatter = getNumberFormatter(NumberFormats.PERCENT_2_POINT);
+
+export function formatFunnelLabel({
+ params,
+ labelType,
+ numberFormatter,
+}: {
+ params: CallbackDataParams;
+ labelType: EchartsFunnelLabelTypeType;
+ numberFormatter: NumberFormatter;
+}): string {
+ const { name = '', value, percent } = params;
+ const formattedValue = numberFormatter(value as number);
+ const formattedPercent = percentFormatter((percent as number) / 100);
+ switch (labelType) {
+ case EchartsFunnelLabelTypeType.Key:
+ return name;
+ case EchartsFunnelLabelTypeType.Value:
+ return formattedValue;
+ case EchartsFunnelLabelTypeType.Percent:
+ return formattedPercent;
+ case EchartsFunnelLabelTypeType.KeyValue:
+ return `${name}: ${formattedValue}`;
+ case EchartsFunnelLabelTypeType.KeyValuePercent:
+ return `${name}: ${formattedValue} (${formattedPercent})`;
+ case EchartsFunnelLabelTypeType.KeyPercent:
+ return `${name}: ${formattedPercent}`;
+ default:
+ return name;
+ }
+}
+
+export default function transformProps(
+ chartProps: EchartsFunnelChartProps,
+): FunnelChartTransformedProps {
+ const { formData, height, hooks, ownState, queriesData, width } = chartProps;
+ const data: DataRecord[] = queriesData[0].data || [];
+
+ const {
+ colorScheme,
+ groupby,
+ orient,
+ sort,
+ gap,
+ labelLine,
+ labelType,
+ legendMargin,
+ legendOrientation,
+ legendType,
+ metric = '',
+ numberFormat,
+ showLabels,
+ showLegend,
+ emitFilter,
+ }: EchartsFunnelFormData = {
+ ...DEFAULT_LEGEND_FORM_DATA,
+ ...DEFAULT_FUNNEL_FORM_DATA,
+ ...formData,
+ };
+ const metricLabel = getMetricLabel(metric);
+ const keys = data.map(datum => extractGroupbyLabel({ datum, groupby, coltypeMapping: {} }));
+ const labelMap = data.reduce((acc: Record, datum) => {
+ const label = extractGroupbyLabel({
+ datum,
+ groupby,
+ coltypeMapping: {},
+ });
+ return {
+ ...acc,
+ [label]: groupby.map(col => datum[col]),
+ };
+ }, {});
+
+ const { setDataMask = () => {} } = hooks;
+
+ const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
+ const numberFormatter = getNumberFormatter(numberFormat);
+
+ const transformedData: FunnelSeriesOption[] = data.map(datum => {
+ const name = extractGroupbyLabel({ datum, groupby, coltypeMapping: {} });
+ return {
+ value: datum[metricLabel],
+ name,
+ itemStyle: {
+ color: colorFn(name),
+ },
+ };
+ });
+
+ const selectedValues = (ownState.selectedValues || []).reduce(
+ (acc: Record, selectedValue: string) => {
+ const index = transformedData.findIndex(({ name }) => name === selectedValue);
+ return {
+ ...acc,
+ [index]: selectedValue,
+ };
+ },
+ {},
+ );
+
+ const formatter = (params: CallbackDataParams) =>
+ formatFunnelLabel({ params, numberFormatter, labelType });
+
+ const defaultLabel = {
+ formatter,
+ show: showLabels,
+ color: '#000000',
+ };
+
+ const series: FunnelSeriesOption[] = [
+ {
+ type: 'funnel',
+ ...getChartPadding(showLegend, legendOrientation, legendMargin),
+ animation: true,
+ minSize: '0%',
+ maxSize: '100%',
+ sort,
+ orient,
+ gap,
+ funnelAlign: 'center',
+ labelLine: { show: !!labelLine },
+ label: {
+ ...defaultLabel,
+ position: labelLine ? 'outer' : 'inner',
+ },
+ emphasis: {
+ label: {
+ show: true,
+ fontWeight: 'bold',
+ },
+ },
+ data: transformedData,
+ },
+ ];
+
+ const echartOptions: EChartsOption = {
+ grid: {
+ ...defaultGrid,
+ },
+ tooltip: {
+ ...defaultTooltip,
+ trigger: 'item',
+ formatter: (params: any) =>
+ formatFunnelLabel({
+ params,
+ numberFormatter,
+ labelType: EchartsFunnelLabelTypeType.KeyValuePercent,
+ }),
+ },
+ legend: {
+ ...getLegendProps(legendType, legendOrientation, showLegend),
+ data: keys,
+ },
+ series,
+ };
+
+ return {
+ formData,
+ width,
+ height,
+ echartOptions,
+ setDataMask,
+ emitFilter,
+ labelMap,
+ groupby,
+ selectedValues,
+ };
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/types.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/types.ts
new file mode 100644
index 0000000000000..a265efca0310c
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Funnel/types.ts
@@ -0,0 +1,89 @@
+/**
+ * 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 { EChartsOption } from 'echarts';
+import {
+ ChartDataResponseResult,
+ ChartProps,
+ DataRecordValue,
+ QueryFormData,
+ SetDataMaskHook,
+} from '@superset-ui/core';
+import {
+ DEFAULT_LEGEND_FORM_DATA,
+ EchartsLegendFormData,
+ LegendOrientation,
+ LegendType,
+} from '../types';
+
+export type EchartsFunnelFormData = QueryFormData &
+ EchartsLegendFormData & {
+ colorScheme?: string;
+ groupby: string[];
+ labelLine: boolean;
+ labelType: EchartsFunnelLabelTypeType;
+ metric?: string;
+ showLabels: boolean;
+ numberFormat: string;
+ gap: number;
+ sort: 'descending' | 'ascending' | 'none' | undefined;
+ orient: 'vertical' | 'horizontal' | undefined;
+ emitFilter: boolean;
+ };
+
+export enum EchartsFunnelLabelTypeType {
+ Key,
+ Value,
+ Percent,
+ KeyValue,
+ KeyPercent,
+ KeyValuePercent,
+}
+
+export interface EchartsFunnelChartProps extends ChartProps {
+ formData: EchartsFunnelFormData;
+ queriesData: ChartDataResponseResult[];
+}
+
+// @ts-ignore
+export const DEFAULT_FORM_DATA: EchartsFunnelFormData = {
+ ...DEFAULT_LEGEND_FORM_DATA,
+ groupby: [],
+ labelLine: false,
+ labelType: EchartsFunnelLabelTypeType.Key,
+ legendOrientation: LegendOrientation.Top,
+ legendType: LegendType.Scroll,
+ numberFormat: 'SMART_NUMBER',
+ showLabels: true,
+ sort: 'descending',
+ orient: 'vertical',
+ gap: 0,
+ emitFilter: false,
+};
+
+export interface FunnelChartTransformedProps {
+ formData: EchartsFunnelFormData;
+ height: number;
+ width: number;
+ echartOptions: EChartsOption;
+ emitFilter: boolean;
+ setDataMask: SetDataMaskHook;
+ labelMap: Record;
+ groupby: string[];
+ selectedValues: Record;
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/index.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/index.ts
index 721f11ffa6f81..264170457ab6f 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/index.ts
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/index.ts
@@ -23,6 +23,7 @@ export { default as EchartsPieChartPlugin } from './Pie';
export { default as EchartsGraphChartPlugin } from './Graph';
export { default as EchartsGaugeChartPlugin } from './Gauge';
export { default as EchartsRadarChartPlugin } from './Radar';
+export { default as EchartsFunnelChartPlugin } from './Funnel';
/**
* Note: this file exports the default export from EchartsTimeseries.tsx.
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/Funnel/buildQuery.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/Funnel/buildQuery.test.ts
new file mode 100644
index 0000000000000..09a5974537a6e
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/Funnel/buildQuery.test.ts
@@ -0,0 +1,36 @@
+/**
+ * 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 buildQuery from '../../src/Funnel/buildQuery';
+
+describe('Funnel buildQuery', () => {
+ const formData = {
+ datasource: '5__table',
+ granularity_sqla: 'ds',
+ metric: 'foo',
+ groupby: ['bar'],
+ viz_type: 'my_chart',
+ };
+
+ it('should build query fields from form data', () => {
+ const queryContext = buildQuery(formData);
+ const [query] = queryContext.queries;
+ expect(query.metrics).toEqual(['foo']);
+ expect(query.columns).toEqual(['bar']);
+ });
+});
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/Funnel/transformProps.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/Funnel/transformProps.test.ts
new file mode 100644
index 0000000000000..9a4b481041674
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/Funnel/transformProps.test.ts
@@ -0,0 +1,106 @@
+/**
+ * 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 { ChartProps, getNumberFormatter } from '@superset-ui/core';
+import transformProps, { formatFunnelLabel } from '../../src/Funnel/transformProps';
+import { EchartsFunnelLabelTypeType } from '../../src/Funnel/types';
+
+describe('Funnel tranformProps', () => {
+ const formData = {
+ colorScheme: 'bnbColors',
+ datasource: '3__table',
+ granularity_sqla: 'ds',
+ metric: 'sum__num',
+ groupby: ['foo', 'bar'],
+ };
+ const chartProps = new ChartProps({
+ formData,
+ width: 800,
+ height: 600,
+ queriesData: [
+ {
+ data: [
+ { foo: 'Sylvester', bar: 1, sum__num: 10 },
+ { foo: 'Arnold', bar: 2, sum__num: 2.5 },
+ ],
+ },
+ ],
+ });
+
+ it('should tranform chart props for viz', () => {
+ expect(transformProps(chartProps)).toEqual(
+ expect.objectContaining({
+ width: 800,
+ height: 600,
+ echartOptions: expect.objectContaining({
+ series: [
+ expect.objectContaining({
+ data: expect.arrayContaining([
+ expect.objectContaining({
+ name: 'Arnold, 2',
+ value: 2.5,
+ }),
+ expect.objectContaining({
+ name: 'Sylvester, 1',
+ value: 10,
+ }),
+ ]),
+ }),
+ ],
+ }),
+ }),
+ );
+ });
+});
+
+describe('formatFunnelLabel', () => {
+ it('should generate a valid funnel chart label', () => {
+ const numberFormatter = getNumberFormatter();
+ const params = { name: 'My Label', value: 1234, percent: 12.34 };
+ expect(
+ formatFunnelLabel({ params, numberFormatter, labelType: EchartsFunnelLabelTypeType.Key }),
+ ).toEqual('My Label');
+ expect(
+ formatFunnelLabel({ params, numberFormatter, labelType: EchartsFunnelLabelTypeType.Value }),
+ ).toEqual('1.23k');
+ expect(
+ formatFunnelLabel({ params, numberFormatter, labelType: EchartsFunnelLabelTypeType.Percent }),
+ ).toEqual('12.34%');
+ expect(
+ formatFunnelLabel({
+ params,
+ numberFormatter,
+ labelType: EchartsFunnelLabelTypeType.KeyValue,
+ }),
+ ).toEqual('My Label: 1.23k');
+ expect(
+ formatFunnelLabel({
+ params,
+ numberFormatter,
+ labelType: EchartsFunnelLabelTypeType.KeyPercent,
+ }),
+ ).toEqual('My Label: 12.34%');
+ expect(
+ formatFunnelLabel({
+ params,
+ numberFormatter,
+ labelType: EchartsFunnelLabelTypeType.KeyValuePercent,
+ }),
+ ).toEqual('My Label: 1.23k (12.34%)');
+ });
+});
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/index.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/index.test.ts
index 9de7f1c5bd83e..c6272ce4116d0 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/index.test.ts
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/index.test.ts
@@ -21,6 +21,7 @@ import {
EchartsPieChartPlugin,
EchartsTimeseriesChartPlugin,
EchartsGraphChartPlugin,
+ EchartsFunnelChartPlugin,
} from '../src';
describe('@superset-ui/plugin-chart-echarts', () => {
@@ -29,5 +30,6 @@ describe('@superset-ui/plugin-chart-echarts', () => {
expect(EchartsPieChartPlugin).toBeDefined();
expect(EchartsTimeseriesChartPlugin).toBeDefined();
expect(EchartsGraphChartPlugin).toBeDefined();
+ expect(EchartsFunnelChartPlugin).toBeDefined();
});
});