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:
Evan Rusackas
2026-07-02 21:50:57 -07:00
committed by Claude Code
parent 3c53228c69
commit 24dbc8649c
5 changed files with 8 additions and 366 deletions

1
.gitignore vendored
View File

@@ -137,6 +137,7 @@ PROJECT.md
.aider*
.claude_rc*
.claude/settings.local.json
.claude/worktrees/
.env.local
oxc-custom-build/
*.code-workspace

View File

@@ -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).

View File

@@ -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'],
}}
/>
);

View File

@@ -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],
},
];

View File

@@ -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"""