You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2023/10/05 10:58:53 UTC
[superset] branch master updated: feat: Adds the ECharts Bubble chart (#22107)
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 c81c60c91f feat: Adds the ECharts Bubble chart (#22107)
c81c60c91f is described below
commit c81c60c91fbcb09dd63c05f050e18ee09ceebfd6
Author: Mayur <ma...@gmail.com>
AuthorDate: Thu Oct 5 16:28:46 2023 +0530
feat: Adds the ECharts Bubble chart (#22107)
Co-authored-by: Michael S. Molina <mi...@gmail.com>
---
.../plugin-chart-echarts/Bubble/Stories.tsx | 128 +++++++++
.../plugins/plugin-chart-echarts/Bubble/data.ts | 80 ++++++
.../legacy-preset-chart-nvd3/src/Bubble/index.js | 9 +-
.../src/Bubble/EchartsBubble.tsx | 33 +++
.../plugin-chart-echarts/src/Bubble/buildQuery.ts | 40 +++
.../plugin-chart-echarts/src/Bubble/constants.ts | 35 +++
.../src/Bubble/controlPanel.tsx | 287 +++++++++++++++++++++
.../src/Bubble/images/example1.png | Bin 0 -> 134620 bytes
.../src/Bubble/images/example2.png | Bin 0 -> 107431 bytes
.../src/Bubble/images/thumbnail.png | Bin 0 -> 114350 bytes
.../plugin-chart-echarts/src/Bubble/index.ts | 60 +++++
.../src/Bubble/transformProps.ts | 229 ++++++++++++++++
.../plugin-chart-echarts/src/Bubble/types.ts | 57 ++++
.../plugins/plugin-chart-echarts/src/index.ts | 2 +
.../test/Bubble/buildQuery.test.ts | 93 +++++++
.../test/Bubble/transformProps.test.ts | 160 ++++++++++++
.../controls/VizTypeControl/VizTypeGallery.tsx | 1 +
.../src/visualizations/presets/MainPreset.js | 2 +
18 files changed, 1214 insertions(+), 2 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Bubble/Stories.tsx b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Bubble/Stories.tsx
new file mode 100644
index 0000000000..b4731ee226
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Bubble/Stories.tsx
@@ -0,0 +1,128 @@
+/**
+ * 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 { SuperChart, getChartTransformPropsRegistry } from '@superset-ui/core';
+import {
+ boolean,
+ number,
+ select,
+ text,
+ withKnobs,
+} from '@storybook/addon-knobs';
+import {
+ EchartsBubbleChartPlugin,
+ BubbleTransformProps,
+} from '@superset-ui/plugin-chart-echarts';
+import { simpleBubbleData } from './data';
+import { withResizableChartDemo } from '../../../../shared/components/ResizableChartDemo';
+
+new EchartsBubbleChartPlugin().configure({ key: 'bubble_v2' }).register();
+
+getChartTransformPropsRegistry().registerValue(
+ 'bubble_v2',
+ BubbleTransformProps,
+);
+
+export default {
+ title: 'Chart Plugins/plugin-chart-echarts/Bubble',
+ decorators: [withKnobs, withResizableChartDemo],
+};
+
+export const SimpleBubble = ({ width, height }) => (
+ <SuperChart
+ chartType="bubble_v2"
+ width={width}
+ height={height}
+ queriesData={[{ data: simpleBubbleData }]}
+ formData={{
+ entity: 'customer_name',
+ x: 'count',
+ y: {
+ aggregate: 'SUM',
+ column: {
+ advanced_data_type: null,
+ certification_details: null,
+ certified_by: null,
+ column_name: 'price_each',
+ description: null,
+ expression: null,
+ filterable: true,
+ groupby: true,
+ id: 570,
+ is_certified: false,
+ is_dttm: false,
+ python_date_format: null,
+ type: 'DOUBLE PRECISION',
+ type_generic: 0,
+ verbose_name: null,
+ warning_markdown: null,
+ },
+ expressionType: 'SIMPLE',
+ hasCustomLabel: false,
+ isNew: false,
+ label: 'SUM(price_each)',
+ optionName: 'metric_d9rpclvys0a_fs4bs0m2l1f',
+ sqlExpression: null,
+ },
+ adhocFilters: [],
+ size: {
+ aggregate: 'SUM',
+ column: {
+ advanced_data_type: null,
+ certification_details: null,
+ certified_by: null,
+ column_name: 'sales',
+ description: null,
+ expression: null,
+ filterable: true,
+ groupby: true,
+ id: 571,
+ is_certified: false,
+ is_dttm: false,
+ python_date_format: null,
+ type: 'DOUBLE PRECISION',
+ type_generic: 0,
+ verbose_name: null,
+ warning_markdown: null,
+ },
+ expressionType: 'SIMPLE',
+ hasCustomLabel: false,
+ isNew: false,
+ label: 'SUM(sales)',
+ optionName: 'metric_itj9wncjxk_dp3yibib0q',
+ sqlExpression: null,
+ },
+ limit: 10,
+ colorScheme: 'supersetColors',
+ maxBubbleSize: select('Max bubble size', [5, 10, 25, 50, 100, 125], 10),
+ xAxisTitle: text('X axis title', ''),
+ xAxisTitleMargin: number('X axis title margin', 30),
+ yAxisTitle: text('Y axis title', ''),
+ yAxisTitleMargin: number('Y axis title margin', 30),
+ yAxisTitlePosition: 'Left',
+ xAxisFormat: null,
+ logYAxis: boolean('Log Y axis', false),
+ yAxisFormat: null,
+ logXAxis: boolean('Log X axis', false),
+ truncateYAxis: false,
+ yAxisBounds: [],
+ extraFormData: {},
+ }}
+ />
+);
diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Bubble/data.ts b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Bubble/data.ts
new file mode 100644
index 0000000000..c434e33e94
--- /dev/null
+++ b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-echarts/Bubble/data.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.
+ */
+export const simpleBubbleData = [
+ {
+ customer_name: 'AV Stores, Co.',
+ count: 51,
+ 'SUM(price_each)': 3975.33,
+ 'SUM(sales)': 157807.80999999997,
+ },
+ {
+ customer_name: 'Alpha Cognac',
+ count: 20,
+ 'SUM(price_each)': 1701.95,
+ 'SUM(sales)': 70488.44,
+ },
+ {
+ customer_name: 'Amica Models & Co.',
+ count: 26,
+ 'SUM(price_each)': 2218.41,
+ 'SUM(sales)': 94117.26000000002,
+ },
+ {
+ customer_name: "Anna's Decorations, Ltd",
+ count: 46,
+ 'SUM(price_each)': 3843.67,
+ 'SUM(sales)': 153996.13000000003,
+ },
+ {
+ customer_name: 'Atelier graphique',
+ count: 7,
+ 'SUM(price_each)': 558.4300000000001,
+ 'SUM(sales)': 24179.96,
+ },
+ {
+ customer_name: 'Australian Collectables, Ltd',
+ count: 23,
+ 'SUM(price_each)': 1809.7099999999998,
+ 'SUM(sales)': 64591.46000000001,
+ },
+ {
+ customer_name: 'Australian Collectors, Co.',
+ count: 55,
+ 'SUM(price_each)': 4714.479999999999,
+ 'SUM(sales)': 200995.40999999997,
+ },
+ {
+ customer_name: 'Australian Gift Network, Co',
+ count: 15,
+ 'SUM(price_each)': 1271.05,
+ 'SUM(sales)': 59469.11999999999,
+ },
+ {
+ customer_name: 'Auto Assoc. & Cie.',
+ count: 18,
+ 'SUM(price_each)': 1484.8600000000001,
+ 'SUM(sales)': 64834.32000000001,
+ },
+ {
+ customer_name: 'Auto Canal Petit',
+ count: 27,
+ 'SUM(price_each)': 2188.82,
+ 'SUM(sales)': 93170.65999999999,
+ },
+];
diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bubble/index.js b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bubble/index.js
index 4b5a032ee6..c2916c7a42 100644
--- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bubble/index.js
+++ b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bubble/index.js
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
+import { t, ChartMetadata, ChartPlugin, ChartLabel } from '@superset-ui/core';
import transformProps from '../transformProps';
import example from './images/example.jpg';
import thumbnail from './images/thumbnail.png';
@@ -29,7 +29,8 @@ const metadata = new ChartMetadata({
'Visualizes a metric across three dimensions of data in a single chart (X axis, Y axis, and bubble size). Bubbles from the same group can be showcased using bubble color.',
),
exampleGallery: [{ url: example }],
- name: t('Bubble Chart'),
+ label: ChartLabel.DEPRECATED,
+ name: t('Bubble Chart (legacy)'),
tags: [
t('Multi-Dimensions'),
t('Aesthetic'),
@@ -39,11 +40,15 @@ const metadata = new ChartMetadata({
t('Time'),
t('Trend'),
t('nvd3'),
+ t('Deprecated'),
],
thumbnail,
useLegacyApi: true,
});
+/**
+ * @deprecated in version 4.0.
+ */
export default class BubbleChartPlugin extends ChartPlugin {
constructor() {
super({
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/EchartsBubble.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/EchartsBubble.tsx
new file mode 100644
index 0000000000..1d64b25161
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/EchartsBubble.tsx
@@ -0,0 +1,33 @@
+/**
+ * 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 { BubbleChartTransformedProps } from './types';
+import Echart from '../components/Echart';
+
+export default function EchartsBubble(props: BubbleChartTransformedProps) {
+ const { height, width, echartOptions, refs } = props;
+ return (
+ <Echart
+ height={height}
+ width={width}
+ echartOptions={echartOptions}
+ refs={refs}
+ />
+ );
+}
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/buildQuery.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/buildQuery.ts
new file mode 100644
index 0000000000..31cdc0d9f6
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/buildQuery.ts
@@ -0,0 +1,40 @@
+/**
+ * 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,
+ ensureIsArray,
+ QueryFormData,
+} from '@superset-ui/core';
+
+export default function buildQuery(formData: QueryFormData) {
+ const columns = [
+ ...ensureIsArray(formData.entity),
+ ...ensureIsArray(formData.series),
+ ];
+
+ return buildQueryContext(formData, baseQueryObject => [
+ {
+ ...baseQueryObject,
+ columns,
+ orderby: baseQueryObject.orderby
+ ? [[baseQueryObject.orderby[0], !baseQueryObject.order_desc]]
+ : undefined,
+ },
+ ]);
+}
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/constants.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/constants.ts
new file mode 100644
index 0000000000..0f9bc0f305
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/constants.ts
@@ -0,0 +1,35 @@
+/**
+ * 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 '../constants';
+import { EchartsBubbleFormData } from './types';
+
+export const DEFAULT_FORM_DATA: Partial<EchartsBubbleFormData> = {
+ ...DEFAULT_LEGEND_FORM_DATA,
+ emitFilter: false,
+ logXAis: false,
+ logYAxis: false,
+ xAxisTitleMargin: 30,
+ yAxisTitleMargin: 30,
+ truncateYAxis: false,
+ yAxisBounds: [null, null],
+ xAxisLabelRotation: 0,
+ opacity: 0.6,
+};
+
+export const MINIMUM_BUBBLE_SIZE = 5;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/controlPanel.tsx
new file mode 100644
index 0000000000..53fba5de2b
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/controlPanel.tsx
@@ -0,0 +1,287 @@
+/**
+ * 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 } from '@superset-ui/core';
+import {
+ ControlPanelConfig,
+ formatSelectOptions,
+ sections,
+ ControlPanelsContainerProps,
+ sharedControls,
+} from '@superset-ui/chart-controls';
+
+import { DEFAULT_FORM_DATA } from './constants';
+import { legendSection } from '../controls';
+
+const { logAxis, truncateYAxis, yAxisBounds, xAxisLabelRotation, opacity } =
+ DEFAULT_FORM_DATA;
+
+const config: ControlPanelConfig = {
+ controlPanelSections: [
+ {
+ label: t('Query'),
+ expanded: true,
+ controlSetRows: [
+ ['series'],
+ ['entity'],
+ ['x'],
+ ['y'],
+ ['adhoc_filters'],
+ ['size'],
+ ['orderby'],
+ [
+ {
+ name: 'order_desc',
+ config: {
+ ...sharedControls.order_desc,
+ visibility: ({ controls }) => Boolean(controls.orderby.value),
+ },
+ },
+ ],
+ ['row_limit'],
+ ],
+ },
+ {
+ label: t('Chart Options'),
+ expanded: true,
+ tabOverride: 'customize',
+ controlSetRows: [
+ ['color_scheme'],
+ ...legendSection,
+ [
+ {
+ name: 'max_bubble_size',
+ config: {
+ type: 'SelectControl',
+ renderTrigger: true,
+ freeForm: true,
+ label: t('Max Bubble Size'),
+ default: '25',
+ choices: formatSelectOptions([
+ '5',
+ '10',
+ '15',
+ '25',
+ '50',
+ '75',
+ '100',
+ ]),
+ },
+ },
+ ],
+ [
+ {
+ name: 'tooltipSizeFormat',
+ config: {
+ ...sharedControls.y_axis_format,
+ label: t('Bubble size number format'),
+ },
+ },
+ ],
+ [
+ {
+ name: 'opacity',
+ config: {
+ type: 'SliderControl',
+ label: t('Bubble Opacity'),
+ renderTrigger: true,
+ min: 0,
+ max: 1,
+ step: 0.1,
+ default: opacity,
+ description: t(
+ 'Opacity of bubbles, 0 means completely transparent, 1 means opaque',
+ ),
+ },
+ },
+ ],
+ ],
+ },
+ {
+ label: t('X Axis'),
+ expanded: true,
+ controlSetRows: [
+ [
+ {
+ name: 'x_axis_label',
+ config: {
+ type: 'TextControl',
+ label: t('X Axis Title'),
+ renderTrigger: true,
+ default: '',
+ },
+ },
+ ],
+ [
+ {
+ name: 'xAxisLabelRotation',
+ config: {
+ type: 'SelectControl',
+ freeForm: true,
+ clearable: false,
+ label: t('Rotate x axis label'),
+ choices: [
+ [0, '0°'],
+ [45, '45°'],
+ ],
+ default: xAxisLabelRotation,
+ renderTrigger: true,
+ description: t(
+ 'Input field supports custom rotation. e.g. 30 for 30°',
+ ),
+ },
+ },
+ ],
+ [
+ {
+ name: 'x_axis_title_margin',
+ config: {
+ type: 'SelectControl',
+ freeForm: true,
+ clearable: true,
+ label: t('X AXIS TITLE MARGIN'),
+ renderTrigger: true,
+ default: sections.TITLE_MARGIN_OPTIONS[1],
+ choices: formatSelectOptions(sections.TITLE_MARGIN_OPTIONS),
+ },
+ },
+ ],
+ [
+ {
+ name: 'xAxisFormat',
+ config: {
+ ...sharedControls.y_axis_format,
+ label: t('X Axis Format'),
+ },
+ },
+ ],
+ [
+ {
+ name: 'logXAxis',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Logarithmic x-axis'),
+ renderTrigger: true,
+ default: logAxis,
+ description: t('Logarithmic x-axis'),
+ },
+ },
+ ],
+ ],
+ },
+ {
+ label: t('Y Axis'),
+ expanded: true,
+ controlSetRows: [
+ [
+ {
+ name: 'y_axis_label',
+ config: {
+ type: 'TextControl',
+ label: t('Y Axis Title'),
+ renderTrigger: true,
+ default: '',
+ },
+ },
+ ],
+ [
+ {
+ name: 'yAxisLabelRotation',
+ config: {
+ type: 'SelectControl',
+ freeForm: true,
+ clearable: false,
+ label: t('Rotate y axis label'),
+ choices: [
+ [0, '0°'],
+ [45, '45°'],
+ ],
+ default: xAxisLabelRotation,
+ renderTrigger: true,
+ description: t(
+ 'Input field supports custom rotation. e.g. 30 for 30°',
+ ),
+ },
+ },
+ ],
+ [
+ {
+ name: 'y_axis_title_margin',
+ config: {
+ type: 'SelectControl',
+ freeForm: true,
+ clearable: true,
+ label: t('Y AXIS TITLE MARGIN'),
+ renderTrigger: true,
+ default: sections.TITLE_MARGIN_OPTIONS[1],
+ choices: formatSelectOptions(sections.TITLE_MARGIN_OPTIONS),
+ },
+ },
+ ],
+ ['y_axis_format'],
+ [
+ {
+ name: 'logYAxis',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Logarithmic y-axis'),
+ renderTrigger: true,
+ default: logAxis,
+ description: t('Logarithmic y-axis'),
+ },
+ },
+ ],
+ [
+ {
+ name: 'truncateYAxis',
+ config: {
+ type: 'CheckboxControl',
+ label: t('Truncate Y Axis'),
+ default: truncateYAxis,
+ renderTrigger: true,
+ description: t(
+ 'Truncate Y Axis. Can be overridden by specifying a min or max bound.',
+ ),
+ },
+ },
+ ],
+ [
+ {
+ name: 'y_axis_bounds',
+ config: {
+ type: 'BoundsControl',
+ label: t('Y Axis Bounds'),
+ renderTrigger: true,
+ default: yAxisBounds,
+ description: t(
+ 'Bounds for the Y-axis. When left empty, the bounds are ' +
+ 'dynamically defined based on the min/max of the data. Note that ' +
+ "this feature will only expand the axis range. It won't " +
+ "narrow the data's extent.",
+ ),
+ visibility: ({ controls }: ControlPanelsContainerProps) =>
+ Boolean(controls?.truncateYAxis?.value),
+ },
+ },
+ ],
+ ],
+ },
+ ],
+};
+
+export default config;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/images/example1.png b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/images/example1.png
new file mode 100644
index 0000000000..5fdbdbd1c8
Binary files /dev/null and b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/images/example1.png differ
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/images/example2.png b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/images/example2.png
new file mode 100644
index 0000000000..3559d7d3ee
Binary files /dev/null and b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/images/example2.png differ
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/images/thumbnail.png b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/images/thumbnail.png
new file mode 100644
index 0000000000..11fd818011
Binary files /dev/null and b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/images/thumbnail.png differ
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/index.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/index.ts
new file mode 100644
index 0000000000..c07776c4ac
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/index.ts
@@ -0,0 +1,60 @@
+/**
+ * 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 thumbnail from './images/thumbnail.png';
+import transformProps from './transformProps';
+import buildQuery from './buildQuery';
+import controlPanel from './controlPanel';
+import example1 from './images/example1.png';
+import example2 from './images/example2.png';
+import { EchartsBubbleChartProps, EchartsBubbleFormData } from './types';
+
+export default class EchartsBubbleChartPlugin extends ChartPlugin<
+ EchartsBubbleFormData,
+ EchartsBubbleChartProps
+> {
+ constructor() {
+ super({
+ buildQuery,
+ controlPanel,
+ loadChart: () => import('./EchartsBubble'),
+ metadata: new ChartMetadata({
+ behaviors: [Behavior.INTERACTIVE_CHART],
+ category: t('Correlation'),
+ credits: ['https://echarts.apache.org'],
+ description: t(
+ 'Visualizes a metric across three dimensions of data in a single chart (X axis, Y axis, and bubble size). Bubbles from the same group can be showcased using bubble color.',
+ ),
+ exampleGallery: [{ url: example1 }, { url: example2 }],
+ name: t('Bubble Chart'),
+ tags: [
+ t('Multi-Dimensions'),
+ t('Aesthetic'),
+ t('Comparison'),
+ t('Scatter'),
+ t('Time'),
+ t('Trend'),
+ t('ECharts'),
+ ],
+ thumbnail,
+ }),
+ transformProps,
+ });
+ }
+}
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts
new file mode 100644
index 0000000000..7962bc2c36
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts
@@ -0,0 +1,229 @@
+/**
+ * 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 { EChartsCoreOption, ScatterSeriesOption } from 'echarts';
+import { extent } from 'd3-array';
+import {
+ CategoricalColorNamespace,
+ getNumberFormatter,
+ AxisType,
+ getMetricLabel,
+ NumberFormatter,
+} from '@superset-ui/core';
+import { EchartsBubbleChartProps, EchartsBubbleFormData } from './types';
+import { DEFAULT_FORM_DATA, MINIMUM_BUBBLE_SIZE } from './constants';
+import { defaultGrid } from '../defaults';
+import { getLegendProps } from '../utils/series';
+import { Refs } from '../types';
+import { parseYAxisBound } from '../utils/controls';
+import { getDefaultTooltip } from '../utils/tooltip';
+import { getPadding } from '../Timeseries/transformers';
+import { convertInteger } from '../utils/convertInteger';
+import { NULL_STRING } from '../constants';
+
+function normalizeSymbolSize(
+ nodes: ScatterSeriesOption[],
+ maxBubbleValue: number,
+) {
+ const [bubbleMinValue, bubbleMaxValue] = extent(nodes, x => x.data![0][2]);
+ const nodeSpread = bubbleMaxValue - bubbleMinValue;
+ nodes.forEach(node => {
+ // eslint-disable-next-line no-param-reassign
+ node.symbolSize =
+ (((node.data![0][2] - bubbleMinValue) / nodeSpread) *
+ (maxBubbleValue * 2) || 0) + MINIMUM_BUBBLE_SIZE;
+ });
+}
+
+export function formatTooltip(
+ params: any,
+ xAxisLabel: string,
+ yAxisLabel: string,
+ sizeLabel: string,
+ xAxisFormatter: NumberFormatter,
+ yAxisFormatter: NumberFormatter,
+ tooltipSizeFormatter: NumberFormatter,
+) {
+ const title = params.data[4]
+ ? `${params.data[3]} </br> ${params.data[4]}`
+ : params.data[3];
+
+ return `<p>${title}</p>
+ ${xAxisLabel}: ${xAxisFormatter(params.data[0])} <br/>
+ ${yAxisLabel}: ${yAxisFormatter(params.data[1])} <br/>
+ ${sizeLabel}: ${tooltipSizeFormatter(params.data[2])}`;
+}
+
+export default function transformProps(chartProps: EchartsBubbleChartProps) {
+ const { height, width, hooks, queriesData, formData, inContextMenu, theme } =
+ chartProps;
+
+ const { data = [] } = queriesData[0];
+ const {
+ x,
+ y,
+ size,
+ entity,
+ maxBubbleSize,
+ colorScheme,
+ series: bubbleSeries,
+ xAxisLabel: bubbleXAxisTitle,
+ yAxisLabel: bubbleYAxisTitle,
+ xAxisFormat,
+ yAxisFormat,
+ yAxisBounds,
+ logXAxis,
+ logYAxis,
+ xAxisTitleMargin,
+ yAxisTitleMargin,
+ truncateYAxis,
+ xAxisLabelRotation,
+ yAxisLabelRotation,
+ tooltipSizeFormat,
+ opacity,
+ showLegend,
+ legendOrientation,
+ legendMargin,
+ legendType,
+ }: EchartsBubbleFormData = { ...DEFAULT_FORM_DATA, ...formData };
+
+ const colorFn = CategoricalColorNamespace.getScale(colorScheme as string);
+
+ const legends: string[] = [];
+ const series: ScatterSeriesOption[] = [];
+
+ const xAxisLabel: string = getMetricLabel(x);
+ const yAxisLabel: string = getMetricLabel(y);
+ const sizeLabel: string = getMetricLabel(size);
+
+ const refs: Refs = {};
+
+ data.forEach(datum => {
+ const name =
+ ((bubbleSeries ? datum[bubbleSeries] : datum[entity]) as string) ||
+ NULL_STRING;
+ const bubbleSeriesValue = bubbleSeries ? datum[bubbleSeries] : null;
+
+ series.push({
+ name,
+ data: [
+ [
+ datum[xAxisLabel],
+ datum[yAxisLabel],
+ datum[sizeLabel],
+ datum[entity],
+ bubbleSeriesValue as any,
+ ],
+ ],
+ type: 'scatter',
+ itemStyle: { color: colorFn(name), opacity },
+ });
+ legends.push(name);
+ });
+
+ normalizeSymbolSize(series, maxBubbleSize);
+
+ const xAxisFormatter = getNumberFormatter(xAxisFormat);
+ const yAxisFormatter = getNumberFormatter(yAxisFormat);
+ const tooltipSizeFormatter = getNumberFormatter(tooltipSizeFormat);
+
+ const [min, max] = yAxisBounds.map(parseYAxisBound);
+
+ const padding = getPadding(
+ showLegend,
+ legendOrientation,
+ true,
+ false,
+ legendMargin,
+ true,
+ 'Left',
+ convertInteger(yAxisTitleMargin),
+ convertInteger(xAxisTitleMargin),
+ );
+
+ const echartOptions: EChartsCoreOption = {
+ series,
+ xAxis: {
+ axisLabel: { formatter: xAxisFormatter },
+ splitLine: {
+ lineStyle: {
+ type: 'dashed',
+ },
+ },
+ nameRotate: xAxisLabelRotation,
+ scale: true,
+ name: bubbleXAxisTitle,
+ nameLocation: 'middle',
+ nameTextStyle: {
+ fontWight: 'bolder',
+ },
+ nameGap: convertInteger(xAxisTitleMargin),
+ type: logXAxis ? AxisType.log : AxisType.value,
+ },
+ yAxis: {
+ axisLabel: { formatter: yAxisFormatter },
+ splitLine: {
+ lineStyle: {
+ type: 'dashed',
+ },
+ },
+ nameRotate: yAxisLabelRotation,
+ scale: truncateYAxis,
+ name: bubbleYAxisTitle,
+ nameLocation: 'middle',
+ nameTextStyle: {
+ fontWight: 'bolder',
+ },
+ nameGap: convertInteger(yAxisTitleMargin),
+ min,
+ max,
+ type: logYAxis ? AxisType.log : AxisType.value,
+ },
+ legend: {
+ ...getLegendProps(legendType, legendOrientation, showLegend, theme),
+ data: legends,
+ },
+ tooltip: {
+ show: !inContextMenu,
+ ...getDefaultTooltip(refs),
+ formatter: (params: any): string =>
+ formatTooltip(
+ params,
+ xAxisLabel,
+ yAxisLabel,
+ sizeLabel,
+ xAxisFormatter,
+ yAxisFormatter,
+ tooltipSizeFormatter,
+ ),
+ },
+ grid: { ...defaultGrid, ...padding },
+ };
+
+ const { onContextMenu, setDataMask = () => {} } = hooks;
+
+ return {
+ refs,
+ height,
+ width,
+ echartOptions,
+ onContextMenu,
+ setDataMask,
+ formData,
+ };
+}
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/types.ts
new file mode 100644
index 0000000000..ebb23174a7
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/types.ts
@@ -0,0 +1,57 @@
+/**
+ * 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,
+ ChartDataResponseResult,
+ QueryFormData,
+} from '@superset-ui/core';
+import {
+ LegendFormData,
+ BaseTransformedProps,
+ CrossFilterTransformedProps,
+} from '../types';
+
+export type EchartsBubbleFormData = QueryFormData &
+ LegendFormData & {
+ series?: string;
+ entity: string;
+ xAxisFormat: string;
+ yAXisFormat: string;
+ logXAxis: boolean;
+ logYAxis: boolean;
+ xAxisBounds: [number | undefined | null, number | undefined | null];
+ yAxisBounds: [number | undefined | null, number | undefined | null];
+ xAxisLabel?: string;
+ colorScheme?: string;
+ defaultValue?: string[] | null;
+ dateFormat: string;
+ emitFilter: boolean;
+ tooltipFormat: string;
+ x: string;
+ y: string;
+ };
+
+export interface EchartsBubbleChartProps
+ extends ChartProps<EchartsBubbleFormData> {
+ formData: EchartsBubbleFormData;
+ queriesData: ChartDataResponseResult[];
+}
+
+export type BubbleChartTransformedProps =
+ BaseTransformedProps<EchartsBubbleFormData> & CrossFilterTransformedProps;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/index.ts b/superset-frontend/plugins/plugin-chart-echarts/src/index.ts
index 0301f265b0..f8c7cf6103 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/index.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/index.ts
@@ -34,6 +34,7 @@ export { default as EchartsTreeChartPlugin } from './Tree';
export { default as EchartsTreemapChartPlugin } from './Treemap';
export { BigNumberChartPlugin, BigNumberTotalChartPlugin } from './BigNumber';
export { default as EchartsSunburstChartPlugin } from './Sunburst';
+export { default as EchartsBubbleChartPlugin } from './Bubble';
export { default as BoxPlotTransformProps } from './BoxPlot/transformProps';
export { default as FunnelTransformProps } from './Funnel/transformProps';
@@ -46,6 +47,7 @@ export { default as TimeseriesTransformProps } from './Timeseries/transformProps
export { default as TreeTransformProps } from './Tree/transformProps';
export { default as TreemapTransformProps } from './Treemap/transformProps';
export { default as SunburstTransformProps } from './Sunburst/transformProps';
+export { default as BubbleTransformProps } from './Bubble/transformProps';
export { DEFAULT_FORM_DATA as TimeseriesDefaultFormData } from './Timeseries/constants';
diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Bubble/buildQuery.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Bubble/buildQuery.test.ts
new file mode 100644
index 0000000000..cbe6003eb4
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-echarts/test/Bubble/buildQuery.test.ts
@@ -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 buildQuery from '../../src/Bubble/buildQuery';
+
+describe('Bubble buildQuery', () => {
+ const formData = {
+ datasource: '1__table',
+ viz_type: 'echarts_bubble',
+ entity: 'customer_name',
+ x: 'count',
+ y: {
+ aggregate: 'sum',
+ column: {
+ column_name: 'price_each',
+ },
+ expressionType: 'simple',
+ label: 'SUM(price_each)',
+ },
+ size: {
+ aggregate: 'sum',
+ column: {
+ column_name: 'sales',
+ },
+ expressionType: 'simple',
+ label: 'SUM(sales)',
+ },
+ };
+
+ it('Should build query without dimension', () => {
+ const queryContext = buildQuery(formData);
+ const [query] = queryContext.queries;
+ expect(query.columns).toEqual(['customer_name']);
+ expect(query.metrics).toEqual([
+ 'count',
+ {
+ aggregate: 'sum',
+ column: {
+ column_name: 'price_each',
+ },
+ expressionType: 'simple',
+ label: 'SUM(price_each)',
+ },
+ {
+ aggregate: 'sum',
+ column: {
+ column_name: 'sales',
+ },
+ expressionType: 'simple',
+ label: 'SUM(sales)',
+ },
+ ]);
+ });
+ it('Should build query with dimension', () => {
+ const queryContext = buildQuery({ ...formData, series: 'state' });
+ const [query] = queryContext.queries;
+ expect(query.columns).toEqual(['customer_name', 'state']);
+ expect(query.metrics).toEqual([
+ 'count',
+ {
+ aggregate: 'sum',
+ column: {
+ column_name: 'price_each',
+ },
+ expressionType: 'simple',
+ label: 'SUM(price_each)',
+ },
+ {
+ aggregate: 'sum',
+ column: {
+ column_name: 'sales',
+ },
+ expressionType: 'simple',
+ label: 'SUM(sales)',
+ },
+ ]);
+ });
+});
diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/Bubble/transformProps.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/Bubble/transformProps.test.ts
new file mode 100644
index 0000000000..2bb4ae0fc6
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-echarts/test/Bubble/transformProps.test.ts
@@ -0,0 +1,160 @@
+/**
+ * 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,
+ SqlaFormData,
+ supersetTheme,
+} from '@superset-ui/core';
+import { EchartsBubbleChartProps } from 'plugins/plugin-chart-echarts/src/Bubble/types';
+
+import transformProps, { formatTooltip } from '../../src/Bubble/transformProps';
+
+describe('Bubble transformProps', () => {
+ const formData: SqlaFormData = {
+ datasource: '1__table',
+ viz_type: 'echarts_bubble',
+ entity: 'customer_name',
+ x: 'count',
+ y: {
+ aggregate: 'sum',
+ column: {
+ column_name: 'price_each',
+ },
+ expressionType: 'simple',
+ label: 'SUM(price_each)',
+ },
+ size: {
+ aggregate: 'sum',
+ column: {
+ column_name: 'sales',
+ },
+ expressionType: 'simple',
+ label: 'SUM(sales)',
+ },
+ yAxisBounds: [null, null],
+ };
+ const chartProps = new ChartProps({
+ formData,
+ height: 800,
+ width: 800,
+ queriesData: [
+ {
+ data: [
+ {
+ customer_name: 'AV Stores, Co.',
+ count: 10,
+ 'SUM(price_each)': 20,
+ 'SUM(sales)': 30,
+ },
+ {
+ customer_name: 'Alpha Cognac',
+ count: 40,
+ 'SUM(price_each)': 50,
+ 'SUM(sales)': 60,
+ },
+ {
+ customer_name: 'Amica Models & Co.',
+ count: 70,
+ 'SUM(price_each)': 80,
+ 'SUM(sales)': 90,
+ },
+ ],
+ },
+ ],
+ theme: supersetTheme,
+ });
+
+ it('Should transform props for viz', () => {
+ expect(transformProps(chartProps as EchartsBubbleChartProps)).toEqual(
+ expect.objectContaining({
+ width: 800,
+ height: 800,
+ echartOptions: expect.objectContaining({
+ series: expect.arrayContaining([
+ expect.objectContaining({
+ data: expect.arrayContaining([
+ [10, 20, 30, 'AV Stores, Co.', null],
+ ]),
+ }),
+ expect.objectContaining({
+ data: expect.arrayContaining([
+ [40, 50, 60, 'Alpha Cognac', null],
+ ]),
+ }),
+ expect.objectContaining({
+ data: expect.arrayContaining([
+ [70, 80, 90, 'Amica Models & Co.', null],
+ ]),
+ }),
+ ]),
+ }),
+ }),
+ );
+ });
+});
+
+describe('Bubble formatTooltip', () => {
+ const dollerFormatter = getNumberFormatter('$,.2f');
+ const percentFormatter = getNumberFormatter(',.1%');
+
+ it('Should generate correct bubble label content with dimension', () => {
+ const params = {
+ data: [10000, 20000, 3, 'bubble title', 'bubble dimension'],
+ };
+
+ expect(
+ formatTooltip(
+ params,
+ 'x-axis-label',
+ 'y-axis-label',
+ 'size-label',
+ dollerFormatter,
+ dollerFormatter,
+ percentFormatter,
+ ),
+ ).toEqual(
+ `<p>bubble title </br> bubble dimension</p>
+ x-axis-label: $10,000.00 <br/>
+ y-axis-label: $20,000.00 <br/>
+ size-label: 300.0%`,
+ );
+ });
+ it('Should generate correct bubble label content without dimension', () => {
+ const params = {
+ data: [10000, 25000, 3, 'bubble title', null],
+ };
+ expect(
+ formatTooltip(
+ params,
+ 'x-axis-label',
+ 'y-axis-label',
+ 'size-label',
+ dollerFormatter,
+ dollerFormatter,
+ percentFormatter,
+ ),
+ ).toEqual(
+ `<p>bubble title</p>
+ x-axis-label: $10,000.00 <br/>
+ y-axis-label: $25,000.00 <br/>
+ size-label: 300.0%`,
+ );
+ });
+});
diff --git a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx
index 90b7856b05..c194d2fae1 100644
--- a/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx
+++ b/superset-frontend/src/explore/components/controls/VizTypeControl/VizTypeGallery.tsx
@@ -101,6 +101,7 @@ const DEFAULT_ORDER = [
'cal_heatmap',
'rose',
'bubble',
+ 'bubble_v2',
'deck_geojson',
'horizon',
'deck_multi',
diff --git a/superset-frontend/src/visualizations/presets/MainPreset.js b/superset-frontend/src/visualizations/presets/MainPreset.js
index 735027fdd0..451196c35d 100644
--- a/superset-frontend/src/visualizations/presets/MainPreset.js
+++ b/superset-frontend/src/visualizations/presets/MainPreset.js
@@ -65,6 +65,7 @@ import {
EchartsMixedTimeseriesChartPlugin,
EchartsTreeChartPlugin,
EchartsSunburstChartPlugin,
+ EchartsBubbleChartPlugin,
} from '@superset-ui/plugin-chart-echarts';
import {
SelectFilterPlugin,
@@ -160,6 +161,7 @@ export default class MainPreset extends Preset {
new EchartsTreeChartPlugin().configure({ key: 'tree_chart' }),
new EchartsSunburstChartPlugin().configure({ key: 'sunburst_v2' }),
new HandlebarsChartPlugin().configure({ key: 'handlebars' }),
+ new EchartsBubbleChartPlugin().configure({ key: 'bubble_v2' }),
...experimentalplugins,
],
});