diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/EchartsGauge.tsx b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/EchartsGauge.tsx
new file mode 100644
index 000000000000..5f57bad421f9
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/EchartsGauge.tsx
@@ -0,0 +1,25 @@
+/**
+ * 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 { EchartsProps } from '../types';
+import Echart from '../components/Echart';
+
+export default function EchartsGauge({ height, width, echartOptions }: EchartsProps) {
+ return ;
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/buildQuery.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/buildQuery.ts
new file mode 100644
index 000000000000..077e2baf46d1
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/buildQuery.ts
@@ -0,0 +1,28 @@
+/**
+ * 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,
+ groupby: formData.groupby || [],
+ },
+ ]);
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/constants.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/constants.ts
new file mode 100644
index 000000000000..0257354f21de
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/constants.ts
@@ -0,0 +1,80 @@
+/**
+ * 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 { GaugeSeriesOption } from 'echarts';
+
+export const DEFAULT_GAUGE_SERIES_OPTION: GaugeSeriesOption = {
+ splitLine: {
+ lineStyle: {
+ color: '#63677A',
+ },
+ },
+ axisLine: {
+ lineStyle: {
+ color: [[1, '#E6EBF8']],
+ },
+ },
+ axisLabel: {
+ color: '#464646',
+ },
+ axisTick: {
+ lineStyle: {
+ width: 2,
+ color: '#63677A',
+ },
+ },
+ detail: {
+ color: 'auto',
+ },
+};
+
+export const INTERVAL_GAUGE_SERIES_OPTION: GaugeSeriesOption = {
+ splitLine: {
+ lineStyle: {
+ color: 'auto',
+ },
+ },
+ axisTick: {
+ lineStyle: {
+ color: 'auto',
+ },
+ },
+ axisLabel: {
+ color: 'auto',
+ },
+ pointer: {
+ itemStyle: {
+ color: 'auto',
+ },
+ },
+};
+
+export const OFFSETS = {
+ ticksFromLine: 10,
+ titleFromCenter: 20,
+};
+
+export const FONT_SIZE_MULTIPLIERS = {
+ axisTickLength: 0.25,
+ axisLabelDistance: 1.2,
+ splitLineLength: 1,
+ splitLineWidth: 0.25,
+ titleOffsetFromTitle: 2,
+ detailOffsetFromTitle: 0.9,
+ detailFontSize: 1.2,
+};
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx
new file mode 100644
index 000000000000..50f259d51739
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/controlPanel.tsx
@@ -0,0 +1,284 @@
+/**
+ * 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 { t, validateNonEmpty, validateInteger } from '@superset-ui/core';
+import { sharedControls, ControlPanelConfig, D3_FORMAT_OPTIONS } from '@superset-ui/chart-controls';
+import { DEFAULT_FORM_DATA } from './types';
+
+const config: ControlPanelConfig = {
+ controlPanelSections: [
+ {
+ label: t('Query'),
+ expanded: true,
+ controlSetRows: [
+ [
+ {
+ name: 'groupby',
+ config: {
+ ...sharedControls.groupby,
+ label: t('Group by'),
+ description: t('Columns to group by'),
+ },
+ },
+ ],
+ ['metric'],
+ ['adhoc_filters'],
+ [
+ {
+ name: 'row_limit',
+ config: {
+ ...sharedControls.row_limit,
+ choices: [...Array(10).keys()].map(n => n + 1),
+ default: DEFAULT_FORM_DATA.rowLimit,
+ },
+ },
+ ],
+ ],
+ },
+ {
+ label: t('Chart Options'),
+ expanded: true,
+ controlSetRows: [
+ [
{t('General')}
],
+ [
+ {
+ name: 'min_val',
+ config: {
+ type: 'TextControl',
+ isInt: true,
+ default: String(DEFAULT_FORM_DATA.minVal),
+ validators: [validateNonEmpty, validateInteger],
+ renderTrigger: true,
+ label: t('Min'),
+ description: t('Minimum value on the gauge axis'),
+ },
+ },
+ {
+ name: 'max_val',
+ config: {
+ type: 'TextControl',
+ isInt: true,
+ default: DEFAULT_FORM_DATA.maxVal,
+ validators: [validateNonEmpty, validateInteger],
+ renderTrigger: true,
+ label: t('Max'),
+ description: t('Maximum value on the gauge axis'),
+ },
+ },
+ ],
+ [
+ {
+ name: 'start_angle',
+ config: {
+ type: 'TextControl',
+ label: t('Start angle'),
+ description: t('Angle at which to start progress axis'),
+ renderTrigger: true,
+ default: DEFAULT_FORM_DATA.startAngle,
+ },
+ },
+ {
+ name: 'end_angle',
+ config: {
+ type: 'TextControl',
+ label: t('End angle'),
+ description: t('Angle at which to end progress axis'),
+ renderTrigger: true,
+ default: DEFAULT_FORM_DATA.endAngle,
+ },
+ },
+ ],
+ ['color_scheme'],
+ [
+ {
+ name: 'font_size',
+ config: {
+ type: 'SliderControl',
+ label: t('Font size'),
+ description: t('Font size for axis labels, detail value and other text elements'),
+ renderTrigger: true,
+ min: 10,
+ max: 20,
+ default: DEFAULT_FORM_DATA.fontSize,
+ },
+ },
+ ],
+ [
+ {
+ name: 'number_format',
+ config: {
+ type: 'SelectControl',
+ label: t('Number format'),
+ description: 'D3 format syntax: https://github.com/d3/d3-format',
+ freeForm: true,
+ renderTrigger: true,
+ default: DEFAULT_FORM_DATA.numberFormat,
+ choices: D3_FORMAT_OPTIONS,
+ },
+ },
+ ],
+ [
+ {
+ name: 'value_formatter',
+ config: {
+ type: 'TextControl',
+ label: t('Value format'),
+ description: t('Additional text to add before or after the value, e.g. unit'),
+ renderTrigger: true,
+ default: DEFAULT_FORM_DATA.valueFormatter,
+ },
+ },
+ ],
+ [
+ {
+ name: 'show_pointer',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Show pointer'),
+ description: t('Whether to show the pointer'),
+ renderTrigger: true,
+ default: DEFAULT_FORM_DATA.showPointer,
+ },
+ },
+ ],
+ [
+ {
+ name: 'animation',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Animation'),
+ description: t('Whether to animate the progress and the value or just display them'),
+ renderTrigger: true,
+ default: DEFAULT_FORM_DATA.animation,
+ },
+ },
+ ],
+ [{t('Axis')}
],
+ [
+ {
+ name: 'show_axis_tick',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Show axis line ticks'),
+ description: t('Whether to show minor ticks on the axis'),
+ renderTrigger: true,
+ default: DEFAULT_FORM_DATA.showAxisTick,
+ },
+ },
+ ],
+ [
+ {
+ name: 'show_split_line',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Show split lines'),
+ description: t('Whether to show the split lines on the axis'),
+ renderTrigger: true,
+ default: DEFAULT_FORM_DATA.showSplitLine,
+ },
+ },
+ ],
+ [
+ {
+ name: 'split_number',
+ config: {
+ type: 'SliderControl',
+ label: t('Split number'),
+ description: t('Number of split segments on the axis'),
+ renderTrigger: true,
+ min: 3,
+ max: 30,
+ default: DEFAULT_FORM_DATA.splitNumber,
+ },
+ },
+ ],
+ [{t('Progress')}
],
+ [
+ {
+ name: 'show_progress',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Show progress'),
+ description: t('Whether to show the progress of gauge chart'),
+ renderTrigger: true,
+ default: DEFAULT_FORM_DATA.showProgress,
+ },
+ },
+ ],
+ [
+ {
+ name: 'overlap',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Overlap'),
+ description: t(
+ 'Whether the progress bar overlaps when there are multiple groups of data',
+ ),
+ renderTrigger: true,
+ default: DEFAULT_FORM_DATA.overlap,
+ },
+ },
+ ],
+ [
+ {
+ name: 'round_cap',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Round cap'),
+ description: t('Style the ends of the progress bar with a round cap'),
+ renderTrigger: true,
+ default: DEFAULT_FORM_DATA.roundCap,
+ },
+ },
+ ],
+ [{t('Intervals')}
],
+ [
+ {
+ name: 'intervals',
+ config: {
+ type: 'TextControl',
+ label: t('Interval bounds'),
+ description: t(
+ 'Comma-separated interval bounds, e.g. 2,4,5 for intervals 0-2, 2-4 and 4-5. Last number should match the value provided for MAX.',
+ ),
+ renderTrigger: true,
+ default: DEFAULT_FORM_DATA.intervals,
+ },
+ },
+ ],
+ [
+ {
+ name: 'interval_color_indices',
+ config: {
+ type: 'TextControl',
+ label: t('Interval colors'),
+ description: t(
+ 'Comma-separated color picks for the intervals, e.g. 1,2,4. Integers denote colors from the chosen color scheme and are 1-indexed. Length must be matching that of interval bounds.',
+ ),
+ renderTrigger: true,
+ default: DEFAULT_FORM_DATA.intervalColorIndices,
+ },
+ },
+ ],
+ ],
+ },
+ ],
+};
+
+export default config;
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/images/thumbnail.png b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/images/thumbnail.png
new file mode 100644
index 000000000000..2ad56001daad
Binary files /dev/null and b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/images/thumbnail.png differ
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/index.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/index.ts
new file mode 100644
index 000000000000..54fd67185c06
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/index.ts
@@ -0,0 +1,39 @@
+/**
+ * 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 { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
+import controlPanel from './controlPanel';
+import transformProps from './transformProps';
+import thumbnail from './images/thumbnail.png';
+import buildQuery from './buildQuery';
+
+export default class EchartsGaugeChartPlugin extends ChartPlugin {
+ constructor() {
+ super({
+ buildQuery,
+ controlPanel,
+ loadChart: () => import('./EchartsGauge'),
+ metadata: new ChartMetadata({
+ credits: ['https://echarts.apache.org'],
+ name: t('Gauge Chart'),
+ thumbnail,
+ }),
+ transformProps,
+ });
+ }
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts
new file mode 100644
index 000000000000..da9de2976ef0
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/transformProps.ts
@@ -0,0 +1,222 @@
+/**
+ * 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 {
+ QueryFormMetric,
+ ChartProps,
+ CategoricalColorNamespace,
+ CategoricalColorScale,
+ DataRecord,
+ getNumberFormatter,
+ getMetricLabel,
+} from '@superset-ui/core';
+import { EChartsOption, GaugeSeriesOption } from 'echarts';
+import { GaugeDataItemOption } from 'echarts/types/src/chart/gauge/GaugeSeries';
+import { parseNumbersList } from '../utils/controls';
+import {
+ DEFAULT_FORM_DATA as DEFAULT_GAUGE_FORM_DATA,
+ EchartsGaugeFormData,
+ AxisTickLineStyle,
+} from './types';
+import {
+ DEFAULT_GAUGE_SERIES_OPTION,
+ INTERVAL_GAUGE_SERIES_OPTION,
+ OFFSETS,
+ FONT_SIZE_MULTIPLIERS,
+} from './constants';
+
+const setIntervalBoundsAndColors = (
+ intervals: string,
+ intervalColorIndices: string,
+ colorFn: CategoricalColorScale,
+ normalizer: number,
+): Array<[number, string]> => {
+ let intervalBoundsNonNormalized;
+ let intervalColorIndicesArray;
+ try {
+ intervalBoundsNonNormalized = parseNumbersList(intervals, ',');
+ intervalColorIndicesArray = parseNumbersList(intervalColorIndices, ',');
+ } catch (error) {
+ intervalBoundsNonNormalized = [] as number[];
+ intervalColorIndicesArray = [] as number[];
+ }
+
+ const intervalBounds = intervalBoundsNonNormalized.map(bound => bound / normalizer);
+ const intervalColors = intervalColorIndicesArray.map(
+ ind => colorFn.colors[(ind - 1) % colorFn.colors.length],
+ );
+
+ return intervalBounds.map((val, idx) => {
+ const color = intervalColors[idx];
+ return [val, color || colorFn.colors[idx]];
+ });
+};
+
+const calculateAxisLineWidth = (data: DataRecord[], fontSize: number, overlap: boolean): number =>
+ overlap ? fontSize : data.length * fontSize;
+
+export default function transformProps(chartProps: ChartProps) {
+ const { width, height, formData, queriesData } = chartProps;
+ const {
+ groupby,
+ metric,
+ minVal,
+ maxVal,
+ colorScheme,
+ fontSize,
+ numberFormat,
+ animation,
+ showProgress,
+ overlap,
+ roundCap,
+ showAxisTick,
+ showSplitLine,
+ splitNumber,
+ startAngle,
+ endAngle,
+ showPointer,
+ intervals,
+ intervalColorIndices,
+ valueFormatter,
+ }: EchartsGaugeFormData = { ...DEFAULT_GAUGE_FORM_DATA, ...formData };
+ const data = (queriesData[0]?.data || []) as DataRecord[];
+ const numberFormatter = getNumberFormatter(numberFormat);
+ const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
+ const normalizer = maxVal;
+ const axisLineWidth = calculateAxisLineWidth(data, fontSize, overlap);
+ const axisTickLength = FONT_SIZE_MULTIPLIERS.axisTickLength * fontSize;
+ const splitLineLength = FONT_SIZE_MULTIPLIERS.splitLineLength * fontSize;
+ const titleOffsetFromTitle = FONT_SIZE_MULTIPLIERS.titleOffsetFromTitle * fontSize;
+ const detailOffsetFromTitle = FONT_SIZE_MULTIPLIERS.detailOffsetFromTitle * fontSize;
+ const intervalBoundsAndColors = setIntervalBoundsAndColors(
+ intervals,
+ intervalColorIndices,
+ colorFn,
+ normalizer,
+ );
+ const transformedData: GaugeDataItemOption[] = data.map((data_point, index) => ({
+ value: data_point[getMetricLabel(metric as QueryFormMetric)] as number,
+ name: groupby.map(column => `${column}: ${data_point[column]}`).join(', '),
+ itemStyle: {
+ color: colorFn(index),
+ },
+ title: {
+ offsetCenter: ['0%', `${index * titleOffsetFromTitle + OFFSETS.titleFromCenter}%`],
+ fontSize,
+ },
+ detail: {
+ offsetCenter: [
+ '0%',
+ `${index * titleOffsetFromTitle + OFFSETS.titleFromCenter + detailOffsetFromTitle}%`,
+ ],
+ fontSize: FONT_SIZE_MULTIPLIERS.detailFontSize * fontSize,
+ },
+ }));
+
+ const formatValue = (value: number) => valueFormatter.replace('{value}', numberFormatter(value));
+
+ const progress = {
+ show: showProgress,
+ overlap,
+ roundCap,
+ width: fontSize,
+ };
+ const splitLine = {
+ show: showSplitLine,
+ distance: -axisLineWidth - splitLineLength - OFFSETS.ticksFromLine,
+ length: splitLineLength,
+ lineStyle: {
+ width: FONT_SIZE_MULTIPLIERS.splitLineWidth * fontSize,
+ color: DEFAULT_GAUGE_SERIES_OPTION.splitLine?.lineStyle?.color,
+ },
+ };
+ const axisLine = {
+ roundCap,
+ lineStyle: {
+ width: axisLineWidth,
+ color: DEFAULT_GAUGE_SERIES_OPTION.axisLine?.lineStyle?.color,
+ },
+ };
+ const axisLabel = {
+ distance:
+ axisLineWidth -
+ FONT_SIZE_MULTIPLIERS.axisLabelDistance * fontSize -
+ (showSplitLine ? splitLineLength : 0) -
+ OFFSETS.ticksFromLine,
+ fontSize,
+ formatter: numberFormatter,
+ color: DEFAULT_GAUGE_SERIES_OPTION.axisLabel?.color,
+ };
+ const axisTick = {
+ show: showAxisTick,
+ distance: -axisLineWidth - axisTickLength - OFFSETS.ticksFromLine,
+ length: axisTickLength,
+ lineStyle: DEFAULT_GAUGE_SERIES_OPTION.axisTick?.lineStyle as AxisTickLineStyle,
+ };
+ const detail = {
+ valueAnimation: animation,
+ formatter: (value: number) => formatValue(value),
+ color: DEFAULT_GAUGE_SERIES_OPTION.detail?.color,
+ };
+ let pointer;
+
+ if (intervalBoundsAndColors.length) {
+ splitLine.lineStyle.color = INTERVAL_GAUGE_SERIES_OPTION.splitLine?.lineStyle?.color;
+ axisTick.lineStyle.color = INTERVAL_GAUGE_SERIES_OPTION?.axisTick?.lineStyle?.color as string;
+ axisLabel.color = INTERVAL_GAUGE_SERIES_OPTION.axisLabel?.color;
+ axisLine.lineStyle.color = intervalBoundsAndColors;
+ pointer = {
+ show: showPointer,
+ itemStyle: INTERVAL_GAUGE_SERIES_OPTION.pointer?.itemStyle,
+ };
+ } else {
+ pointer = {
+ show: showPointer,
+ };
+ }
+
+ const series: GaugeSeriesOption[] = [
+ {
+ type: 'gauge',
+ startAngle,
+ endAngle,
+ min: minVal,
+ max: maxVal,
+ progress,
+ animation,
+ axisLine: axisLine as GaugeSeriesOption['axisLine'],
+ splitLine,
+ splitNumber,
+ axisLabel,
+ axisTick,
+ pointer,
+ detail,
+ data: transformedData,
+ },
+ ];
+
+ const echartOptions: EChartsOption = {
+ series,
+ };
+
+ return {
+ width,
+ height,
+ echartOptions,
+ };
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/types.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/types.ts
new file mode 100644
index 000000000000..42b579b4fc8e
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/Gauge/types.ts
@@ -0,0 +1,71 @@
+/**
+ * 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 { DEFAULT_LEGEND_FORM_DATA } from '../types';
+
+export type AxisTickLineStyle = {
+ width: number;
+ color: string;
+};
+
+export type EchartsGaugeFormData = {
+ colorScheme?: string;
+ groupby: string[];
+ metric?: object;
+ rowLimit: number;
+ minVal: number;
+ maxVal: number;
+ fontSize: number;
+ numberFormat: string;
+ animation: boolean;
+ showProgress: boolean;
+ overlap: boolean;
+ roundCap: boolean;
+ showAxisTick: boolean;
+ showSplitLine: boolean;
+ splitNumber: number;
+ startAngle: number;
+ endAngle: number;
+ showPointer: boolean;
+ intervals: string;
+ intervalColorIndices: string;
+ valueFormatter: string;
+};
+
+export const DEFAULT_FORM_DATA: EchartsGaugeFormData = {
+ ...DEFAULT_LEGEND_FORM_DATA,
+ groupby: [],
+ rowLimit: 10,
+ minVal: 0,
+ maxVal: 100,
+ fontSize: 15,
+ numberFormat: 'SMART_NUMBER',
+ animation: true,
+ showProgress: true,
+ overlap: true,
+ roundCap: false,
+ showAxisTick: false,
+ showSplitLine: false,
+ splitNumber: 10,
+ startAngle: 225,
+ endAngle: -45,
+ showPointer: true,
+ intervals: '',
+ intervalColorIndices: '',
+ valueFormatter: '{value}',
+};
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 ccdc6f95052e..2c62d49cd062 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
@@ -20,6 +20,7 @@ export { default as EchartsBoxPlotChartPlugin } from './BoxPlot';
export { default as EchartsTimeseriesChartPlugin } from './Timeseries';
export { default as EchartsPieChartPlugin } from './Pie';
export { default as EchartsGraphChartPlugin } from './Graph';
+export { default as EchartsGaugeChartPlugin } from './Gauge';
/**
* Note: this file exports the default export from EchartsTimeseries.tsx.
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/utils/controls.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/utils/controls.ts
index 1c4f310be280..c0385bf5c093 100644
--- a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/utils/controls.ts
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/src/utils/controls.ts
@@ -16,6 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
+
+import { validateNumber } from '@superset-ui/core';
+
// eslint-disable-next-line import/prefer-default-export
export function parseYAxisBound(bound?: string | number | null): number | undefined {
if (bound === undefined || bound === null || Number.isNaN(Number(bound))) {
@@ -23,3 +26,11 @@ export function parseYAxisBound(bound?: string | number | null): number | undefi
}
return Number(bound);
}
+
+export function parseNumbersList(value: string, delim = ';') {
+ if (!value || !value.trim()) return [];
+ return value.split(delim).map(num => {
+ if (validateNumber(num)) throw new Error('All values must be numeric');
+ return Number(num);
+ });
+}
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/Gauge/buildQuery.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/Gauge/buildQuery.test.ts
new file mode 100644
index 000000000000..e300f2cf7233
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/Gauge/buildQuery.test.ts
@@ -0,0 +1,48 @@
+/**
+ * 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/Gauge/buildQuery';
+
+describe('Gauge buildQuery', () => {
+ const baseFormData = {
+ datasource: '5__table',
+ metric: 'foo',
+ viz_type: 'my_chart',
+ };
+
+ it('should build query fields with no group by column', () => {
+ const formData = { ...baseFormData, groupby: null };
+ const queryContext = buildQuery(formData);
+ const [query] = queryContext.queries;
+ expect(query.groupby).toEqual([]);
+ });
+
+ it('should build query fields with single group by column', () => {
+ const formData = { ...baseFormData, groupby: ['foo'] };
+ const queryContext = buildQuery(formData);
+ const [query] = queryContext.queries;
+ expect(query.groupby).toEqual(['foo']);
+ });
+
+ it('should build query fields with multiple group by columns', () => {
+ const formData = { ...baseFormData, groupby: ['foo', 'bar'] };
+ const queryContext = buildQuery(formData);
+ const [query] = queryContext.queries;
+ expect(query.groupby).toEqual(['foo', 'bar']);
+ });
+});
diff --git a/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/Gauge/transformProps.test.ts b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/Gauge/transformProps.test.ts
new file mode 100644
index 000000000000..210ba3be80e5
--- /dev/null
+++ b/superset-frontend/temporary_superset_ui/superset-ui/plugins/plugin-chart-echarts/test/Gauge/transformProps.test.ts
@@ -0,0 +1,334 @@
+/**
+ * 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 } from '@superset-ui/core';
+import transformProps from '../../src/Gauge/transformProps';
+import { DEFAULT_GAUGE_SERIES_OPTION } from '../../src/Gauge/constants';
+
+describe('Echarts Gauge transformProps', () => {
+ const baseFormData = {
+ datasource: '26__table',
+ vizType: 'gauge_chart',
+ metric: 'count',
+ adhocFilters: [],
+ rowLimit: 10,
+ minVal: '0',
+ maxVal: 100,
+ startAngle: 225,
+ endAngle: -45,
+ colorScheme: 'SUPERSET_DEFAULT',
+ fontSize: 14,
+ numberFormat: 'SMART_NUMBER',
+ valueFormatter: '{value}',
+ showPointer: true,
+ animation: true,
+ showAxisTick: false,
+ showSplitLine: false,
+ splitNumber: 10,
+ showProgress: true,
+ overlap: true,
+ roundCap: false,
+ };
+
+ it('should transform chart props for no group by column', () => {
+ const formData = { ...baseFormData, groupby: [] };
+ const queriesData = [
+ {
+ colnames: ['count'],
+ data: [
+ {
+ count: 16595,
+ },
+ ],
+ },
+ ];
+
+ const chartPropsConfig = {
+ formData,
+ width: 800,
+ height: 600,
+ queriesData,
+ };
+
+ const chartProps = new ChartProps(chartPropsConfig);
+ expect(transformProps(chartProps)).toEqual(
+ expect.objectContaining({
+ width: 800,
+ height: 600,
+ echartOptions: expect.objectContaining({
+ series: expect.arrayContaining([
+ expect.objectContaining({
+ data: [
+ {
+ value: 16595,
+ name: '',
+ itemStyle: {
+ color: '#1f77b4',
+ },
+ title: {
+ offsetCenter: ['0%', '20%'],
+ fontSize: 14,
+ },
+ detail: {
+ offsetCenter: ['0%', '32.6%'],
+ fontSize: 16.8,
+ },
+ },
+ ],
+ }),
+ ]),
+ }),
+ }),
+ );
+ });
+
+ it('should transform chart props for single group by column', () => {
+ const formData = { ...baseFormData, groupby: ['year'] };
+ const queriesData = [
+ {
+ colnames: ['year', 'count'],
+ data: [
+ {
+ year: 1988,
+ count: 15,
+ },
+ {
+ year: 1995,
+ count: 219,
+ },
+ ],
+ },
+ ];
+
+ const chartPropsConfig = {
+ formData,
+ width: 800,
+ height: 600,
+ queriesData,
+ };
+
+ const chartProps = new ChartProps(chartPropsConfig);
+ expect(transformProps(chartProps)).toEqual(
+ expect.objectContaining({
+ width: 800,
+ height: 600,
+ echartOptions: expect.objectContaining({
+ series: expect.arrayContaining([
+ expect.objectContaining({
+ data: [
+ {
+ value: 15,
+ name: 'year: 1988',
+ itemStyle: {
+ color: '#1f77b4',
+ },
+ title: {
+ offsetCenter: ['0%', '20%'],
+ fontSize: 14,
+ },
+ detail: {
+ offsetCenter: ['0%', '32.6%'],
+ fontSize: 16.8,
+ },
+ },
+ {
+ value: 219,
+ name: 'year: 1995',
+ itemStyle: {
+ color: '#ff7f0e',
+ },
+ title: {
+ offsetCenter: ['0%', '48%'],
+ fontSize: 14,
+ },
+ detail: {
+ offsetCenter: ['0%', '60.6%'],
+ fontSize: 16.8,
+ },
+ },
+ ],
+ }),
+ ]),
+ }),
+ }),
+ );
+ });
+
+ it('should transform chart props for multiple group by columns', () => {
+ const formData = { ...baseFormData, groupby: ['year', 'platform'] };
+ const queriesData = [
+ {
+ colnames: ['year', 'platform', 'count'],
+ data: [
+ {
+ year: 2011,
+ platform: 'PC',
+ count: 140,
+ },
+ {
+ year: 2008,
+ platform: 'PC',
+ count: 76,
+ },
+ ],
+ },
+ ];
+
+ const chartPropsConfig = {
+ formData,
+ width: 800,
+ height: 600,
+ queriesData,
+ };
+
+ const chartProps = new ChartProps(chartPropsConfig);
+ expect(transformProps(chartProps)).toEqual(
+ expect.objectContaining({
+ width: 800,
+ height: 600,
+ echartOptions: expect.objectContaining({
+ series: expect.arrayContaining([
+ expect.objectContaining({
+ data: [
+ {
+ value: 140,
+ name: 'year: 2011, platform: PC',
+ itemStyle: {
+ color: '#1f77b4',
+ },
+ title: {
+ offsetCenter: ['0%', '20%'],
+ fontSize: 14,
+ },
+ detail: {
+ offsetCenter: ['0%', '32.6%'],
+ fontSize: 16.8,
+ },
+ },
+ {
+ value: 76,
+ name: 'year: 2008, platform: PC',
+ itemStyle: {
+ color: '#ff7f0e',
+ },
+ title: {
+ offsetCenter: ['0%', '48%'],
+ fontSize: 14,
+ },
+ detail: {
+ offsetCenter: ['0%', '60.6%'],
+ fontSize: 16.8,
+ },
+ },
+ ],
+ }),
+ ]),
+ }),
+ }),
+ );
+ });
+
+ it('should transform chart props for intervals', () => {
+ const formData = {
+ ...baseFormData,
+ groupby: ['year', 'platform'],
+ intervals: '50,100',
+ intervalColorIndices: '1,2',
+ };
+ const queriesData = [
+ {
+ colnames: ['year', 'platform', 'count'],
+ data: [
+ {
+ year: 2011,
+ platform: 'PC',
+ count: 140,
+ },
+ {
+ year: 2008,
+ platform: 'PC',
+ count: 76,
+ },
+ ],
+ },
+ ];
+
+ const chartPropsConfig = {
+ formData,
+ width: 800,
+ height: 600,
+ queriesData,
+ };
+
+ const chartProps = new ChartProps(chartPropsConfig);
+ expect(transformProps(chartProps)).toEqual(
+ expect.objectContaining({
+ width: 800,
+ height: 600,
+ echartOptions: expect.objectContaining({
+ series: expect.arrayContaining([
+ expect.objectContaining({
+ axisLine: {
+ lineStyle: {
+ width: 14,
+ color: [
+ [0.5, '#1f77b4'],
+ [1, '#ff7f0e'],
+ ],
+ },
+ roundCap: false,
+ },
+ data: [
+ {
+ value: 140,
+ name: 'year: 2011, platform: PC',
+ itemStyle: {
+ color: '#1f77b4',
+ },
+ title: {
+ offsetCenter: ['0%', '20%'],
+ fontSize: 14,
+ },
+ detail: {
+ offsetCenter: ['0%', '32.6%'],
+ fontSize: 16.8,
+ },
+ },
+ {
+ value: 76,
+ name: 'year: 2008, platform: PC',
+ itemStyle: {
+ color: '#ff7f0e',
+ },
+ title: {
+ offsetCenter: ['0%', '48%'],
+ fontSize: 14,
+ },
+ detail: {
+ offsetCenter: ['0%', '60.6%'],
+ fontSize: 16.8,
+ },
+ },
+ ],
+ }),
+ ]),
+ }),
+ }),
+ );
+ });
+});