mirror of
https://github.com/apache/superset.git
synced 2026-07-18 12:45:44 +00:00
chore(viz): phase 0 — remove orphaned viz.py classes and nvd3 BoxPlot leftovers (#41715)
Co-authored-by: Claude Code <noreply@anthropic.com>
This commit is contained in:
committed by
Claude Code
parent
3c53228c69
commit
24dbc8649c
1
.gitignore
vendored
1
.gitignore
vendored
@@ -137,6 +137,7 @@ PROJECT.md
|
||||
.aider*
|
||||
.claude_rc*
|
||||
.claude/settings.local.json
|
||||
.claude/worktrees/
|
||||
.env.local
|
||||
oxc-custom-build/
|
||||
*.code-workspace
|
||||
|
||||
10
SCOPE.md
10
SCOPE.md
@@ -62,9 +62,9 @@ unchanged. "Retarget" = migrate saved charts to an existing modern chart via a
|
||||
## Phases / sub-PRs (each targets this feature branch)
|
||||
|
||||
### Phase 0 — Dead code deletion
|
||||
- [ ] Delete `superset-frontend/plugins/legacy-preset-chart-deckgl/` (empty husk: only a stale `node_modules/`, unreferenced, absent from `package.json`)
|
||||
- [ ] Delete `legacy-preset-chart-nvd3/src/BoxPlot/` leftovers (unregistered; superseded by ECharts BoxPlot)
|
||||
- [ ] Delete orphaned `viz.py` classes with no registered frontend: `MapboxViz`, `MapLibreViz`, `EventFlowViz` (+ their tests)
|
||||
- [x] `superset-frontend/plugins/legacy-preset-chart-deckgl/` turned out to have **zero tracked files** — it was a stale local `node_modules/` husk, removed locally, nothing to commit
|
||||
- [x] Delete `legacy-preset-chart-nvd3/src/BoxPlot/` leftovers (unregistered; superseded by ECharts BoxPlot)
|
||||
- [x] Delete orphaned `viz.py` classes with no registered frontend: `MapboxViz`, `MapLibreViz`, `EventFlowViz` (no tests referenced them; `mapbox` saved charts were already migrated to `point_cluster_map` by revision `ce6bd21901ab`; the frontend `point_cluster_map` plugin has its own modern `buildQuery`, so removing `MapLibreViz` also fixes cache warm-up, which routed any `viz_type in viz_types` through the legacy path)
|
||||
|
||||
### Phase 1 — Chart migrations (tiers 1→4, easiest first; order above)
|
||||
Per-chart checklist:
|
||||
@@ -92,6 +92,10 @@ Per-chart checklist:
|
||||
|
||||
(append entries as sub-PRs merge: date, sub-PR #, summary)
|
||||
|
||||
- 2026-07-02 — Phase 0: removed orphaned `viz.py` classes (`MapboxViz`, `MapLibreViz`,
|
||||
`EventFlowViz`, −204 lines) and the unregistered nvd3 BoxPlot story leftovers.
|
||||
Verified: `ruff` clean, `tests/unit_tests/test_viz_*` pass.
|
||||
|
||||
## Decisions
|
||||
|
||||
- **compare** → migrating to `echarts_timeseries_line`; nvd3's interactive % rebasing is not preserved (flagged in UPDATES.md).
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { SuperChart, VizType } from '@superset-ui/core';
|
||||
import { EchartsBoxPlotChartPlugin } from '@superset-ui/plugin-chart-echarts';
|
||||
import { dummyDatasource, withResizableChartDemo } from '@storybook-shared';
|
||||
import data from './data';
|
||||
|
||||
new EchartsBoxPlotChartPlugin().configure({ key: 'box-plot' }).register();
|
||||
|
||||
export default {
|
||||
title: 'Legacy Chart Plugins/legacy-preset-chart-nvd3/BoxPlot',
|
||||
decorators: [withResizableChartDemo],
|
||||
args: {
|
||||
colorScheme: 'd3Category10',
|
||||
whiskerOptions: 'Min/max (no outliers)',
|
||||
},
|
||||
argTypes: {
|
||||
colorScheme: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'supersetColors',
|
||||
'd3Category10',
|
||||
'bnbColors',
|
||||
'googleCategory20c',
|
||||
],
|
||||
},
|
||||
whiskerOptions: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'Tukey',
|
||||
'Min/max (no outliers)',
|
||||
'2/98 percentiles',
|
||||
'9/91 percentiles',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Basic = ({
|
||||
colorScheme,
|
||||
whiskerOptions,
|
||||
width,
|
||||
height,
|
||||
}: {
|
||||
colorScheme: string;
|
||||
whiskerOptions: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}) => (
|
||||
<SuperChart
|
||||
chartType="box-plot"
|
||||
width={width}
|
||||
height={height}
|
||||
datasource={dummyDatasource}
|
||||
queriesData={[{ data }]}
|
||||
formData={{
|
||||
color_scheme: colorScheme,
|
||||
viz_type: VizType.BoxPlot,
|
||||
whisker_options: whiskerOptions,
|
||||
groupby: ['region'],
|
||||
metrics: ['sum__SP_POP_TOTL'],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable sort-keys, no-magic-numbers */
|
||||
// Data format for ECharts BoxPlot - requires metric__min, metric__q1, metric__median, etc.
|
||||
export default [
|
||||
{
|
||||
region: 'East Asia & Pacific',
|
||||
sum__SP_POP_TOTL__min: 1031863394.0,
|
||||
sum__SP_POP_TOTL__q1: 1384725172.5,
|
||||
sum__SP_POP_TOTL__median: 1717904169.0,
|
||||
sum__SP_POP_TOTL__q3: 2032724922.5,
|
||||
sum__SP_POP_TOTL__max: 2240687901.0,
|
||||
sum__SP_POP_TOTL__mean: 1681000000.0,
|
||||
sum__SP_POP_TOTL__count: 50,
|
||||
sum__SP_POP_TOTL__outliers: [],
|
||||
},
|
||||
{
|
||||
region: 'Europe & Central Asia',
|
||||
sum__SP_POP_TOTL__min: 660881033.0,
|
||||
sum__SP_POP_TOTL__q1: 751386460.5,
|
||||
sum__SP_POP_TOTL__median: 820716895.0,
|
||||
sum__SP_POP_TOTL__q3: 862814192.5,
|
||||
sum__SP_POP_TOTL__max: 903095786.0,
|
||||
sum__SP_POP_TOTL__mean: 799778873.0,
|
||||
sum__SP_POP_TOTL__count: 50,
|
||||
sum__SP_POP_TOTL__outliers: [],
|
||||
},
|
||||
{
|
||||
region: 'Latin America & Caribbean',
|
||||
sum__SP_POP_TOTL__min: 220564224.0,
|
||||
sum__SP_POP_TOTL__q1: 313690832.5,
|
||||
sum__SP_POP_TOTL__median: 421490233.0,
|
||||
sum__SP_POP_TOTL__q3: 529668114.5,
|
||||
sum__SP_POP_TOTL__max: 626270167.0,
|
||||
sum__SP_POP_TOTL__mean: 422336714.0,
|
||||
sum__SP_POP_TOTL__count: 50,
|
||||
sum__SP_POP_TOTL__outliers: [],
|
||||
},
|
||||
{
|
||||
region: 'South Asia',
|
||||
sum__SP_POP_TOTL__min: 572036107.0,
|
||||
sum__SP_POP_TOTL__q1: 772373036.5,
|
||||
sum__SP_POP_TOTL__median: 1059570231.0,
|
||||
sum__SP_POP_TOTL__q3: 1398841234.0,
|
||||
sum__SP_POP_TOTL__max: 1720976995.0,
|
||||
sum__SP_POP_TOTL__mean: 1104759521.0,
|
||||
sum__SP_POP_TOTL__count: 50,
|
||||
sum__SP_POP_TOTL__outliers: [],
|
||||
},
|
||||
{
|
||||
region: 'Sub-Saharan Africa',
|
||||
sum__SP_POP_TOTL__min: 228268752.0,
|
||||
sum__SP_POP_TOTL__q1: 320037758.0,
|
||||
sum__SP_POP_TOTL__median: 467337821.0,
|
||||
sum__SP_POP_TOTL__q3: 676768689.0,
|
||||
sum__SP_POP_TOTL__max: 974315323.0,
|
||||
sum__SP_POP_TOTL__mean: 533345669.0,
|
||||
sum__SP_POP_TOTL__count: 50,
|
||||
sum__SP_POP_TOTL__outliers: [1100000000, 1200000000],
|
||||
},
|
||||
];
|
||||
204
superset/viz.py
204
superset/viz.py
@@ -1446,177 +1446,6 @@ class HorizonViz(NVD3TimeSeriesViz):
|
||||
)
|
||||
|
||||
|
||||
class MapboxViz(BaseViz):
|
||||
"""Rich maps made with Mapbox"""
|
||||
|
||||
viz_type = "mapbox"
|
||||
verbose_name = _("Mapbox")
|
||||
is_timeseries = False
|
||||
credits = "<a href=https://www.mapbox.com/mapbox-gl-js/api/>Mapbox GL JS</a>"
|
||||
|
||||
@deprecated(deprecated_in="3.0")
|
||||
def query_obj(self) -> QueryObjectDict:
|
||||
query_obj = super().query_obj()
|
||||
label_col = self.form_data.get("mapbox_label")
|
||||
|
||||
if not self.form_data.get("groupby"):
|
||||
if (
|
||||
self.form_data.get("all_columns_x") is None
|
||||
or self.form_data.get("all_columns_y") is None
|
||||
):
|
||||
raise QueryObjectValidationError(
|
||||
_("[Longitude] and [Latitude] must be set")
|
||||
)
|
||||
query_obj["columns"] = [
|
||||
self.form_data.get("all_columns_x"),
|
||||
self.form_data.get("all_columns_y"),
|
||||
]
|
||||
|
||||
if label_col and len(label_col) >= 1:
|
||||
if label_col[0] == "count":
|
||||
raise QueryObjectValidationError(
|
||||
_(
|
||||
"Must have a [Group By] column to have 'count' as the "
|
||||
+ "[Label]"
|
||||
)
|
||||
)
|
||||
query_obj["columns"].append(label_col[0])
|
||||
|
||||
if self.form_data.get("point_radius") != "Auto":
|
||||
query_obj["columns"].append(self.form_data.get("point_radius"))
|
||||
|
||||
# Ensure this value is sorted so that it does not
|
||||
# cause the cache key generation (which hashes the
|
||||
# query object) to generate different keys for values
|
||||
# that should be considered the same.
|
||||
query_obj["columns"] = sorted(set(query_obj["columns"]))
|
||||
else:
|
||||
# Ensuring columns chosen are all in group by
|
||||
if (
|
||||
label_col
|
||||
and len(label_col) >= 1
|
||||
and label_col[0] != "count"
|
||||
and label_col[0] not in self.form_data["groupby"]
|
||||
):
|
||||
raise QueryObjectValidationError(
|
||||
_("Choice of [Label] must be present in [Group By]")
|
||||
)
|
||||
|
||||
if (
|
||||
self.form_data.get("point_radius") != "Auto"
|
||||
and self.form_data.get("point_radius") not in self.form_data["groupby"]
|
||||
):
|
||||
raise QueryObjectValidationError(
|
||||
_("Choice of [Point Radius] must be present in [Group By]")
|
||||
)
|
||||
|
||||
if (
|
||||
self.form_data.get("all_columns_x") not in self.form_data["groupby"]
|
||||
or self.form_data.get("all_columns_y") not in self.form_data["groupby"]
|
||||
):
|
||||
raise QueryObjectValidationError(
|
||||
_(
|
||||
"[Longitude] and [Latitude] columns must be present in "
|
||||
+ "[Group By]"
|
||||
)
|
||||
)
|
||||
return query_obj
|
||||
|
||||
@deprecated(deprecated_in="3.0")
|
||||
def get_data(self, df: pd.DataFrame) -> VizData:
|
||||
if df.empty:
|
||||
return None
|
||||
|
||||
label_col = self.form_data.get("mapbox_label")
|
||||
has_custom_metric = label_col is not None and len(label_col) > 0
|
||||
metric_col = [None] * len(df.index)
|
||||
if has_custom_metric:
|
||||
if label_col[0] == self.form_data.get("all_columns_x"): # type: ignore
|
||||
metric_col = df[self.form_data.get("all_columns_x")]
|
||||
elif label_col[0] == self.form_data.get("all_columns_y"): # type: ignore
|
||||
metric_col = df[self.form_data.get("all_columns_y")]
|
||||
else:
|
||||
metric_col = df[label_col[0]] # type: ignore
|
||||
point_radius_col = (
|
||||
[None] * len(df.index)
|
||||
if self.form_data.get("point_radius") == "Auto"
|
||||
else df[self.form_data.get("point_radius")]
|
||||
)
|
||||
|
||||
# limiting geo precision as long decimal values trigger issues
|
||||
# around json-bignumber in Mapbox
|
||||
geo_precision = 10
|
||||
# using geoJSON formatting
|
||||
geo_json = {
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {"metric": metric, "radius": point_radius},
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
round(lon, geo_precision),
|
||||
round(lat, geo_precision),
|
||||
],
|
||||
},
|
||||
}
|
||||
for lon, lat, metric, point_radius in zip(
|
||||
df[self.form_data.get("all_columns_x")],
|
||||
df[self.form_data.get("all_columns_y")],
|
||||
metric_col,
|
||||
point_radius_col,
|
||||
strict=False,
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
x_series, y_series = (
|
||||
df[self.form_data.get("all_columns_x")],
|
||||
df[self.form_data.get("all_columns_y")],
|
||||
)
|
||||
south_west = [x_series.min(), y_series.min()]
|
||||
north_east = [x_series.max(), y_series.max()]
|
||||
|
||||
return {
|
||||
"geoJSON": geo_json,
|
||||
"hasCustomMetric": has_custom_metric,
|
||||
"mapboxApiKey": current_app.config["MAPBOX_API_KEY"],
|
||||
"mapStyle": self.form_data.get("mapbox_style"),
|
||||
"aggregatorName": self.form_data.get("pandas_aggfunc"),
|
||||
"clusteringRadius": self.form_data.get("clustering_radius"),
|
||||
"pointRadiusUnit": self.form_data.get("point_radius_unit"),
|
||||
"globalOpacity": self.form_data.get("global_opacity"),
|
||||
"bounds": [south_west, north_east],
|
||||
"renderWhileDragging": self.form_data.get("render_while_dragging"),
|
||||
"tooltip": self.form_data.get("rich_tooltip"),
|
||||
"color": self.form_data.get("mapbox_color"),
|
||||
}
|
||||
|
||||
|
||||
class MapLibreViz(MapboxViz):
|
||||
"""Rich maps made with MapLibre"""
|
||||
|
||||
viz_type = "point_cluster_map"
|
||||
verbose_name = _("Point Cluster Map")
|
||||
credits = '<a href="https://maplibre.org/">MapLibre GL JS</a>'
|
||||
|
||||
@deprecated(deprecated_in="3.0")
|
||||
def query_obj(self) -> QueryObjectDict:
|
||||
self.form_data["mapbox_label"] = self.form_data.get("map_label")
|
||||
return super().query_obj()
|
||||
|
||||
@deprecated(deprecated_in="3.0")
|
||||
def get_data(self, df: pd.DataFrame) -> VizData:
|
||||
self.form_data["mapbox_label"] = self.form_data.get("map_label")
|
||||
self.form_data["mapbox_style"] = self.form_data.get("map_style")
|
||||
self.form_data["mapbox_color"] = self.form_data.get("map_color")
|
||||
data = super().get_data(df)
|
||||
if data:
|
||||
data.pop("mapboxApiKey", None)
|
||||
return data
|
||||
|
||||
|
||||
class DeckGLMultiLayer(BaseViz):
|
||||
"""Pile on multiple DeckGL layers"""
|
||||
|
||||
@@ -2657,39 +2486,6 @@ class DeckArc(BaseDeckGLViz):
|
||||
}
|
||||
|
||||
|
||||
class EventFlowViz(BaseViz):
|
||||
"""A visualization to explore patterns in event sequences"""
|
||||
|
||||
viz_type = "event_flow"
|
||||
verbose_name = _("Event flow")
|
||||
credits = 'from <a href="https://github.com/williaster/data-ui">@data-ui</a>'
|
||||
is_timeseries = True
|
||||
|
||||
@deprecated(deprecated_in="3.0")
|
||||
def query_obj(self) -> QueryObjectDict:
|
||||
query = super().query_obj()
|
||||
form_data = self.form_data
|
||||
|
||||
event_key = form_data["all_columns_x"]
|
||||
entity_key = form_data["entity"]
|
||||
meta_keys = [
|
||||
col
|
||||
for col in form_data["all_columns"] or []
|
||||
if col not in (event_key, entity_key)
|
||||
]
|
||||
|
||||
query["columns"] = [event_key, entity_key] + meta_keys
|
||||
|
||||
if form_data["order_by_entity"]:
|
||||
query["orderby"] = [(entity_key, True)]
|
||||
|
||||
return query
|
||||
|
||||
@deprecated(deprecated_in="3.0")
|
||||
def get_data(self, df: pd.DataFrame) -> VizData:
|
||||
return df.to_dict(orient="records")
|
||||
|
||||
|
||||
class PairedTTestViz(BaseViz):
|
||||
"""A table displaying paired t-test values"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user