diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts index 7962bc2c3677a..ce53fdf26605b 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Bubble/transformProps.ts @@ -104,7 +104,7 @@ export default function transformProps(chartProps: EchartsBubbleChartProps) { const colorFn = CategoricalColorNamespace.getScale(colorScheme as string); - const legends: string[] = []; + const legends = new Set(); const series: ScatterSeriesOption[] = []; const xAxisLabel: string = getMetricLabel(x); @@ -114,9 +114,8 @@ export default function transformProps(chartProps: EchartsBubbleChartProps) { const refs: Refs = {}; data.forEach(datum => { - const name = - ((bubbleSeries ? datum[bubbleSeries] : datum[entity]) as string) || - NULL_STRING; + const dataName = bubbleSeries ? datum[bubbleSeries] : datum[entity]; + const name = dataName ? String(dataName) : NULL_STRING; const bubbleSeriesValue = bubbleSeries ? datum[bubbleSeries] : null; series.push({ @@ -133,7 +132,7 @@ export default function transformProps(chartProps: EchartsBubbleChartProps) { type: 'scatter', itemStyle: { color: colorFn(name), opacity }, }); - legends.push(name); + legends.add(name); }); normalizeSymbolSize(series, maxBubbleSize); @@ -196,7 +195,7 @@ export default function transformProps(chartProps: EchartsBubbleChartProps) { }, legend: { ...getLegendProps(legendType, legendOrientation, showLegend, theme), - data: legends, + data: Array.from(legends), }, tooltip: { show: !inContextMenu, diff --git a/superset/cli/viz_migrations.py b/superset/cli/viz_migrations.py index 4451580d0b0aa..f24dd8f444cbf 100644 --- a/superset/cli/viz_migrations.py +++ b/superset/cli/viz_migrations.py @@ -25,6 +25,7 @@ class VizType(str, Enum): AREA = "area" + BUBBLE = "bubble" DUAL_LINE = "dual_line" LINE = "line" PIVOT_TABLE = "pivot_table" @@ -76,6 +77,7 @@ def migrate(viz_type: VizType, is_downgrade: bool = False) -> None: # pylint: disable=import-outside-toplevel from superset.migrations.shared.migrate_viz.processors import ( MigrateAreaChart, + MigrateBubbleChart, MigrateDualLine, MigrateLineChart, MigratePivotTable, @@ -85,6 +87,7 @@ def migrate(viz_type: VizType, is_downgrade: bool = False) -> None: migrations = { VizType.AREA: MigrateAreaChart, + VizType.BUBBLE: MigrateBubbleChart, VizType.DUAL_LINE: MigrateDualLine, VizType.LINE: MigrateLineChart, VizType.PIVOT_TABLE: MigratePivotTable, diff --git a/superset/migrations/shared/migrate_viz/processors.py b/superset/migrations/shared/migrate_viz/processors.py index 2d2bebc6180c7..5fbd624aa8bf9 100644 --- a/superset/migrations/shared/migrate_viz/processors.py +++ b/superset/migrations/shared/migrate_viz/processors.py @@ -184,3 +184,32 @@ def _pre_action(self) -> None: ) self.data["opacity"] = 0.7 + + +class MigrateBubbleChart(MigrateViz): + source_viz_type = "bubble" + target_viz_type = "bubble_v2" + rename_keys = { + "bottom_margin": "x_axis_title_margin", + "left_margin": "y_axis_title_margin", + "limit": "row_limit", + "x_axis_format": "xAxisFormat", + "x_log_scale": "logXAxis", + "x_ticks_layout": "xAxisLabelRotation", + "y_axis_showminmax": "truncateYAxis", + "y_log_scale": "logYAxis", + } + remove_keys = {"x_axis_showminmax"} + + def _pre_action(self) -> None: + bottom_margin = self.data.get("bottom_margin") + if self.data.get("x_axis_label") and ( + not bottom_margin or bottom_margin == "auto" + ): + self.data["bottom_margin"] = 30 + + if x_ticks_layout := self.data.get("x_ticks_layout"): + self.data["x_ticks_layout"] = 45 if x_ticks_layout == "45°" else 0 + + # Truncate y-axis by default to preserve layout + self.data["y_axis_showminmax"] = True diff --git a/tests/unit_tests/migrations/viz/nvd3_bubble_chart_to_echarts_test.py b/tests/unit_tests/migrations/viz/nvd3_bubble_chart_to_echarts_test.py new file mode 100644 index 0000000000000..070083b7ae129 --- /dev/null +++ b/tests/unit_tests/migrations/viz/nvd3_bubble_chart_to_echarts_test.py @@ -0,0 +1,76 @@ +# 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. +from typing import Any + +from superset.migrations.shared.migrate_viz import MigrateBubbleChart +from tests.unit_tests.migrations.viz.utils import migrate_and_assert + +SOURCE_FORM_DATA: dict[str, Any] = { + "adhoc_filters": [], + "bottom_margin": 20, + "color_scheme": "default", + "entity": "count", + "left_margin": 20, + "limit": 100, + "max_bubble_size": 50, + "series": ["region"], + "show_legend": True, + "size": 75, + "viz_type": "bubble", + "x": "year", + "x_axis_format": "SMART_DATE", + "x_axis_label": "Year", + "x_axis_showminmax": True, + "x_log_scale": True, + "x_ticks_layout": "45°", + "y": "country", + "y_axis_bounds": [0, 100], + "y_axis_format": "SMART_DATE", + "y_axis_label": "Year", + "y_axis_showminmax": False, + "y_log_scale": True, +} + +TARGET_FORM_DATA: dict[str, Any] = { + "adhoc_filters": [], + "color_scheme": "default", + "entity": "count", + "form_data_bak": SOURCE_FORM_DATA, + "logXAxis": True, + "logYAxis": True, + "max_bubble_size": 50, + "row_limit": 100, + "series": ["region"], + "show_legend": True, + "size": 75, + "truncateYAxis": True, + "viz_type": "bubble_v2", + "x": "year", + "xAxisFormat": "SMART_DATE", + "xAxisLabelRotation": 45, + "x_axis_label": "Year", + "x_axis_title_margin": 20, + "y": "country", + "y_axis_bounds": [0, 100], + "y_axis_format": "SMART_DATE", + "y_axis_label": "Year", + "y_axis_title_margin": 20, +} + + +def test_migration() -> None: + migrate_and_assert(MigrateBubbleChart, SOURCE_FORM_DATA, TARGET_FORM_DATA)