mirror of
https://github.com/apache/superset.git
synced 2026-05-12 11:25:56 +00:00
fix(map-box): make opacity, lon, lat, and zoom controls functional (#38374)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: codeant-ai-for-open-source[bot] <244253245+codeant-ai-for-open-source[bot]@users.noreply.github.com>
(cherry picked from commit 96705c156a)
This commit is contained in:
committed by
Michael S. Molina
parent
e855fbf71d
commit
cffca7367c
@@ -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 {
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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.',
|
||||
),
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -45,6 +69,9 @@ export default function transformProps(chartProps: ChartProps) {
|
||||
pandasAggfunc,
|
||||
pointRadiusUnit,
|
||||
renderWhileDragging,
|
||||
viewportLongitude,
|
||||
viewportLatitude,
|
||||
viewportZoom,
|
||||
} = formData;
|
||||
|
||||
// Validate mapbox color
|
||||
@@ -93,7 +120,6 @@ export default function transformProps(chartProps: ChartProps) {
|
||||
aggregatorName: pandasAggfunc,
|
||||
bounds,
|
||||
clusterer,
|
||||
globalOpacity,
|
||||
hasCustomMetric,
|
||||
mapboxApiKey,
|
||||
mapStyle: mapboxStyle,
|
||||
@@ -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)),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user