Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/superset-docs-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ jobs:
run_id: ${{ github.event.workflow_run.id }}
name: database-diagnostics
path: docs/src/data/
if_no_artifact_found: 'warning'
- name: Use fresh diagnostics
run: |
if [ -f "src/data/databases-diagnostics.json" ]; then
Expand Down
8 changes: 8 additions & 0 deletions UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ assists people when migrating to a new version.

## Next

### Deck.gl MapBox viewport and opacity controls are functional

The Deck.gl MapBox chart's **Opacity**, **Default longitude**, **Default latitude**, and **Zoom** controls were previously non-functional — changing them had no effect on the rendered map. These controls are now wired up correctly.

**Behavior change for existing charts:** Previously, the viewport controls had hard-coded default values (`-122.405293`, `37.772123`, zoom `11` — San Francisco) that were stored in each chart's `form_data` but never applied. The map always used `fitBounds` to center on the data. With this fix, those stored values are now respected, which means existing MapBox charts may open centered on the old default coordinates instead of fitting to data bounds.

**To restore fit-to-data behavior:** Open the chart in Explore, clear the **Default longitude**, **Default latitude**, and **Zoom** fields in the Viewport section, and re-save the chart.

### ClickHouse minimum driver version bump

The minimum required version of `clickhouse-connect` has been raised to `>=0.13.0`. If you are using the ClickHouse connector, please upgrade your `clickhouse-connect` package. The `_mutate_label` workaround that appended hash suffixes to column aliases has also been removed, as it is no longer needed with modern versions of the driver.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const BINARY_OPERATORS = [
'<=',
'ILIKE',
'LIKE',
'NOT ILIKE',
'NOT LIKE',
'REGEX',
'TEMPORAL_RANGE',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ interface MapBoxProps {
renderWhileDragging?: boolean;
rgb?: (string | number)[];
bounds?: [[number, number], [number, number]]; // May be undefined for empty datasets
viewportLongitude?: number;
viewportLatitude?: number;
viewportZoom?: number;
}

interface MapBoxState {
Expand All @@ -82,30 +85,10 @@ class MapBox extends Component<MapBoxProps, MapBoxState> {
constructor(props: MapBoxProps) {
super(props);

const { width = 400, height = 400, bounds } = this.props;
// Get a viewport that fits the given bounds, which all marks to be clustered.
// Derive lat, lon and zoom from this viewport. This is only done on initial
// render as the bounds don't update as we pan/zoom in the current design.

let latitude = 0;
let longitude = 0;
let zoom = 1;

// Guard against empty datasets where bounds may be undefined
if (bounds && bounds[0] && bounds[1]) {
const mercator = new WebMercatorViewport({
width,
height,
}).fitBounds(bounds);
({ latitude, longitude, zoom } = mercator);
}
const fitBounds = this.computeFitBoundsViewport();

this.state = {
viewport: {
longitude,
latitude,
zoom,
},
viewport: this.mergeViewportWithProps(fitBounds),
};
this.handleViewportChange = this.handleViewportChange.bind(this);
}
Expand All @@ -116,6 +99,75 @@ class MapBox extends Component<MapBoxProps, MapBoxState> {
onViewportChange!(viewport);
}

mergeViewportWithProps(
fitBounds: Viewport,
viewport: Viewport = fitBounds,
props: MapBoxProps = this.props,
useFitBoundsForUnset = true,
): Viewport {
const { viewportLongitude, viewportLatitude, viewportZoom } = props;

return {
...viewport,
longitude:
viewportLongitude ??
(useFitBoundsForUnset ? fitBounds.longitude : viewport.longitude),
latitude:
viewportLatitude ??
(useFitBoundsForUnset ? fitBounds.latitude : viewport.latitude),
zoom:
viewportZoom ?? (useFitBoundsForUnset ? fitBounds.zoom : viewport.zoom),
};
}

computeFitBoundsViewport(): Viewport {
const { width = 400, height = 400, bounds } = this.props;
if (bounds && bounds[0] && bounds[1]) {
const mercator = new WebMercatorViewport({ width, height }).fitBounds(
bounds,
);
return {
latitude: mercator.latitude,
longitude: mercator.longitude,
zoom: mercator.zoom,
};
}
return { latitude: 0, longitude: 0, zoom: 1 };
}

componentDidUpdate(prevProps: MapBoxProps) {
const { viewport } = this.state;
const fitBoundsInputsChanged =
prevProps.width !== this.props.width ||
prevProps.height !== this.props.height ||
prevProps.bounds !== this.props.bounds;
const viewportPropsChanged =
prevProps.viewportLongitude !== this.props.viewportLongitude ||
prevProps.viewportLatitude !== this.props.viewportLatitude ||
prevProps.viewportZoom !== this.props.viewportZoom;

if (!fitBoundsInputsChanged && !viewportPropsChanged) {
return;
}

const fitBounds = this.computeFitBoundsViewport();
const nextViewport = this.mergeViewportWithProps(
fitBounds,
viewport,
this.props,
fitBoundsInputsChanged || viewportPropsChanged,
);

const viewportChanged =
nextViewport.longitude !== viewport.longitude ||
nextViewport.latitude !== viewport.latitude ||
nextViewport.zoom !== viewport.zoom;

if (viewportChanged) {
this.setState({ viewport: nextViewport });
}
}

render() {
const {
width,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ const config: ControlPanelConfig = {
label: t('Opacity'),
default: 1,
isFloat: true,
renderTrigger: true,
description: t(
'Opacity of all clusters, points, and labels. Between 0 and 1.',
),
Expand Down Expand Up @@ -273,7 +274,7 @@ const config: ControlPanelConfig = {
type: 'TextControl',
label: t('Default longitude'),
renderTrigger: true,
default: -122.405293,
default: '',
isFloat: true,
description: t('Longitude of default viewport'),
places: 8,
Expand All @@ -287,7 +288,7 @@ const config: ControlPanelConfig = {
type: 'TextControl',
label: t('Default latitude'),
renderTrigger: true,
default: 37.772123,
default: '',
isFloat: true,
description: t('Latitude of default viewport'),
places: 8,
Expand All @@ -304,7 +305,7 @@ const config: ControlPanelConfig = {
label: t('Zoom'),
renderTrigger: true,
isFloat: true,
default: 11,
default: '',
description: t('Zoom level of the map'),
places: 8,
// Viewport zoom shouldn't prompt user to re-run query
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,30 @@ import { ChartProps } from '@superset-ui/core';
import { DEFAULT_POINT_RADIUS, DEFAULT_MAX_ZOOM } from './MapBox';

const NOOP = () => {};
const MIN_LONGITUDE = -180;
const MAX_LONGITUDE = 180;
const MIN_LATITUDE = -90;
const MAX_LATITUDE = 90;
const MIN_ZOOM = 0;

function toFiniteNumber(
value: string | number | null | undefined,
): number | undefined {
if (value === null || value === undefined) return undefined;
const normalizedValue = typeof value === 'string' ? value.trim() : value;
if (normalizedValue === '') return undefined;
const num = Number(normalizedValue);
return Number.isFinite(num) ? num : undefined;
}

function clampNumber(
value: number | undefined,
min: number,
max: number,
): number | undefined {
if (value === undefined) return undefined;
return Math.min(max, Math.max(min, value));
}

interface ClusterProperties {
metric: number;
Expand All @@ -45,6 +69,9 @@ export default function transformProps(chartProps: ChartProps) {
pandasAggfunc,
pointRadiusUnit,
renderWhileDragging,
viewportLongitude,
viewportLatitude,
viewportZoom,
} = formData;

// Validate mapbox color
Expand Down Expand Up @@ -93,7 +120,6 @@ export default function transformProps(chartProps: ChartProps) {
aggregatorName: pandasAggfunc,
bounds,
clusterer,
globalOpacity,
hasCustomMetric,
mapboxApiKey,
mapStyle: mapboxStyle,
Expand All @@ -116,5 +142,21 @@ export default function transformProps(chartProps: ChartProps) {
pointRadiusUnit,
renderWhileDragging,
rgb,
viewportLongitude: clampNumber(
toFiniteNumber(viewportLongitude),
MIN_LONGITUDE,
MAX_LONGITUDE,
),
viewportLatitude: clampNumber(
toFiniteNumber(viewportLatitude),
MIN_LATITUDE,
MAX_LATITUDE,
),
viewportZoom: clampNumber(
toFiniteNumber(viewportZoom),
MIN_ZOOM,
DEFAULT_MAX_ZOOM,
),
globalOpacity: Math.min(1, Math.max(0, toFiniteNumber(globalOpacity) ?? 1)),
};
}
Loading
Loading