-
Notifications
You must be signed in to change notification settings - Fork 39
feat: Support two render modes: Standard/deck.gl-first and MapboxOverlay #921
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,11 +2,9 @@ import * as React from "react"; | |||||||||||
import { useEffect, useCallback, useState, useRef } from "react"; | ||||||||||||
import { createRender, useModelState, useModel } from "@anywidget/react"; | ||||||||||||
import type { Initialize, Render } from "@anywidget/types"; | ||||||||||||
import Map from "react-map-gl/maplibre"; | ||||||||||||
import DeckGL from "@deck.gl/react"; | ||||||||||||
import { MapViewState, PickingInfo, type Layer } from "@deck.gl/core"; | ||||||||||||
import { BaseLayerModel, initializeLayer } from "./model/index.js"; | ||||||||||||
import type { WidgetModel } from "@jupyter-widgets/base"; | ||||||||||||
import type { IWidgetManager, WidgetModel } from "@jupyter-widgets/base"; | ||||||||||||
import { initParquetWasm } from "./parquet.js"; | ||||||||||||
import { isDefined, loadChildModels } from "./util.js"; | ||||||||||||
import { v4 as uuidv4 } from "uuid"; | ||||||||||||
|
@@ -25,6 +23,9 @@ import throttle from "lodash.throttle"; | |||||||||||
import SidePanel from "./sidepanel/index"; | ||||||||||||
import { getTooltip } from "./tooltip/index.js"; | ||||||||||||
import { DeckGLRef } from "@deck.gl/react"; | ||||||||||||
import OverlayRenderer from "./renderers/overlay.js"; | ||||||||||||
import { MapRendererProps } from "./renderers/types.js"; | ||||||||||||
import DeckFirstRenderer from "./renderers/deck-first.js"; | ||||||||||||
|
||||||||||||
await initParquetWasm(); | ||||||||||||
|
||||||||||||
|
@@ -116,6 +117,7 @@ function App() { | |||||||||||
); | ||||||||||||
const [parameters] = useModelState<object>("parameters"); | ||||||||||||
const [customAttribution] = useModelState<string>("custom_attribution"); | ||||||||||||
const [renderMode] = useModelState<string>("render_mode"); | ||||||||||||
|
||||||||||||
// initialViewState is the value of view_state on the Python side. This is | ||||||||||||
// called `initial` here because it gets passed in to deck's | ||||||||||||
|
@@ -156,7 +158,7 @@ function App() { | |||||||||||
const loadAndUpdateLayers = async () => { | ||||||||||||
try { | ||||||||||||
const childModels = await loadChildModels( | ||||||||||||
model.widget_manager, | ||||||||||||
model.widget_manager as IWidgetManager, | ||||||||||||
childLayerIds, | ||||||||||||
); | ||||||||||||
|
||||||||||||
|
@@ -229,6 +231,45 @@ function App() { | |||||||||||
[isOnMapHoverEventEnabled, justClicked], | ||||||||||||
); | ||||||||||||
|
||||||||||||
const mapRenderProps: MapRendererProps = { | ||||||||||||
mapStyle: mapStyle || DEFAULT_MAP_STYLE, | ||||||||||||
customAttribution, | ||||||||||||
deckRef, | ||||||||||||
initialViewState: ["longitude", "latitude", "zoom"].every((key) => | ||||||||||||
Object.keys(initialViewState).includes(key), | ||||||||||||
) | ||||||||||||
? initialViewState | ||||||||||||
: DEFAULT_INITIAL_VIEW_STATE, | ||||||||||||
layers: bboxSelectPolygonLayer | ||||||||||||
? layers.concat(bboxSelectPolygonLayer) | ||||||||||||
: layers, | ||||||||||||
getTooltip: (showTooltip && getTooltip) || undefined, | ||||||||||||
getCursor: () => (isDrawingBBoxSelection ? "crosshair" : "grab"), | ||||||||||||
pickingRadius: pickingRadius, | ||||||||||||
onClick: onMapClickHandler, | ||||||||||||
onHover: onMapHoverHandler, | ||||||||||||
// @ts-expect-error useDevicePixels should allow number | ||||||||||||
// https://github.com/visgl/deck.gl/pull/9826 | ||||||||||||
useDevicePixels: isDefined(useDevicePixels) ? useDevicePixels : true, | ||||||||||||
Comment on lines
+251
to
+253
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nitpick] Consider using a more specific type assertion instead of @ts-expect-error to make the intent clearer and safer.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||||||||
onViewStateChange: (event) => { | ||||||||||||
const { viewState } = event; | ||||||||||||
|
||||||||||||
// This condition is necessary to confirm that the viewState is | ||||||||||||
// of type MapViewState. | ||||||||||||
if ("latitude" in viewState) { | ||||||||||||
const { longitude, latitude, zoom, pitch, bearing } = viewState; | ||||||||||||
setViewState({ | ||||||||||||
longitude, | ||||||||||||
latitude, | ||||||||||||
zoom, | ||||||||||||
pitch, | ||||||||||||
bearing, | ||||||||||||
}); | ||||||||||||
} | ||||||||||||
}, | ||||||||||||
parameters: parameters || {}, | ||||||||||||
}; | ||||||||||||
|
||||||||||||
return ( | ||||||||||||
<div | ||||||||||||
className="lonboard" | ||||||||||||
|
@@ -252,58 +293,11 @@ function App() { | |||||||||||
/> | ||||||||||||
)} | ||||||||||||
<div className="bg-red-800 h-full w-full relative"> | ||||||||||||
<DeckGL | ||||||||||||
ref={deckRef} | ||||||||||||
style={{ width: "100%", height: "100%" }} | ||||||||||||
initialViewState={ | ||||||||||||
["longitude", "latitude", "zoom"].every((key) => | ||||||||||||
Object.keys(initialViewState).includes(key), | ||||||||||||
) | ||||||||||||
? initialViewState | ||||||||||||
: DEFAULT_INITIAL_VIEW_STATE | ||||||||||||
} | ||||||||||||
controller={true} | ||||||||||||
layers={ | ||||||||||||
bboxSelectPolygonLayer | ||||||||||||
? layers.concat(bboxSelectPolygonLayer) | ||||||||||||
: layers | ||||||||||||
} | ||||||||||||
getTooltip={(showTooltip && getTooltip) || undefined} | ||||||||||||
getCursor={() => (isDrawingBBoxSelection ? "crosshair" : "grab")} | ||||||||||||
pickingRadius={pickingRadius} | ||||||||||||
onClick={onMapClickHandler} | ||||||||||||
onHover={onMapHoverHandler} | ||||||||||||
useDevicePixels={ | ||||||||||||
isDefined(useDevicePixels) ? useDevicePixels : true | ||||||||||||
} | ||||||||||||
// https://deck.gl/docs/api-reference/core/deck#_typedarraymanagerprops | ||||||||||||
_typedArrayManagerProps={{ | ||||||||||||
overAlloc: 1, | ||||||||||||
poolSize: 0, | ||||||||||||
}} | ||||||||||||
onViewStateChange={(event) => { | ||||||||||||
const { viewState } = event; | ||||||||||||
|
||||||||||||
// This condition is necessary to confirm that the viewState is | ||||||||||||
// of type MapViewState. | ||||||||||||
if ("latitude" in viewState) { | ||||||||||||
const { longitude, latitude, zoom, pitch, bearing } = viewState; | ||||||||||||
setViewState({ | ||||||||||||
longitude, | ||||||||||||
latitude, | ||||||||||||
zoom, | ||||||||||||
pitch, | ||||||||||||
bearing, | ||||||||||||
}); | ||||||||||||
} | ||||||||||||
}} | ||||||||||||
parameters={parameters || {}} | ||||||||||||
> | ||||||||||||
<Map | ||||||||||||
mapStyle={mapStyle || DEFAULT_MAP_STYLE} | ||||||||||||
customAttribution={customAttribution} | ||||||||||||
></Map> | ||||||||||||
</DeckGL> | ||||||||||||
{renderMode === "overlay" ? ( | ||||||||||||
<OverlayRenderer {...mapRenderProps} /> | ||||||||||||
) : ( | ||||||||||||
<DeckFirstRenderer {...mapRenderProps} /> | ||||||||||||
)} | ||||||||||||
</div> | ||||||||||||
</div> | ||||||||||||
</div> | ||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import DeckGL from "@deck.gl/react"; | ||
import React from "react"; | ||
import Map from "react-map-gl/maplibre"; | ||
import type { MapRendererProps } from "./types"; | ||
|
||
/** | ||
* DeckFirst renderer: DeckGL wraps Map component | ||
* | ||
* In this rendering mode, deck.gl is the parent component that manages | ||
* the canvas and view state, with the map rendered as a child component. | ||
* This is the traditional approach where deck.gl has full control over | ||
* the rendering pipeline. | ||
*/ | ||
const DeckFirstRenderer: React.FC<MapRendererProps> = (mapProps) => { | ||
// Remove maplibre-specific props before passing to DeckGL | ||
const { mapStyle, customAttribution, deckRef, ...deckProps } = mapProps; | ||
return ( | ||
<DeckGL | ||
ref={deckRef} | ||
style={{ width: "100%", height: "100%" }} | ||
controller={true} | ||
// https://deck.gl/docs/api-reference/core/deck#_typedarraymanagerprops | ||
_typedArrayManagerProps={{ | ||
overAlloc: 1, | ||
poolSize: 0, | ||
}} | ||
{...deckProps} | ||
> | ||
<Map mapStyle={mapStyle} customAttribution={customAttribution}></Map> | ||
</DeckGL> | ||
); | ||
}; | ||
|
||
export default DeckFirstRenderer; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export { default as DeckFirst } from "./deck-first"; | ||
export { default as Overlay } from "./overlay"; | ||
export type { MapRendererProps } from "./types"; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,56 @@ | ||||||
import React from "react"; | ||||||
import Map, { useControl } from "react-map-gl/maplibre"; | ||||||
import { MapboxOverlay, MapboxOverlayProps } from "@deck.gl/mapbox"; | ||||||
import type { MapRendererProps } from "./types"; | ||||||
|
||||||
/** | ||||||
* DeckGLOverlay component that integrates deck.gl with react-map-gl | ||||||
* | ||||||
* Uses the useControl hook to create a MapboxOverlay instance that | ||||||
* renders deck.gl layers on top of the base map. | ||||||
*/ | ||||||
function DeckGLOverlay(props: MapboxOverlayProps) { | ||||||
const overlay = useControl(() => new MapboxOverlay(props)); | ||||||
overlay.setProps(props); | ||||||
return null; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Overlay renderer: Map wraps DeckGLOverlay component | ||||||
* | ||||||
* In this rendering mode, the map is the parent component that controls | ||||||
* the view state, with deck.gl layers rendered as an overlay using the | ||||||
* MapboxOverlay. This approach gives the base map more control and can | ||||||
* enable features like interleaved rendering between map and deck layers. | ||||||
*/ | ||||||
const OverlayRenderer: React.FC<MapRendererProps> = (mapProps) => { | ||||||
// Remove maplibre-specific props before passing to DeckGL | ||||||
const { | ||||||
mapStyle, | ||||||
customAttribution, | ||||||
initialViewState, | ||||||
// deckRef, | ||||||
|
// deckRef, | |
Copilot uses AI. Check for mistakes.
Outdated
Copilot
AI
Oct 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Remove commented-out code or add a TODO comment explaining why it's temporarily disabled. Dead code reduces readability.
// ref={deckRef} |
Copilot uses AI. Check for mistakes.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import type { DeckProps, View } from "@deck.gl/core"; | ||
import type { DeckGLRef } from "@deck.gl/react"; | ||
import type { RefObject } from "react"; | ||
|
||
type ViewOrViews = View | View[] | null; | ||
export type MapRendererProps<ViewsT extends ViewOrViews = null> = Pick< | ||
DeckProps<ViewsT>, | ||
| "getCursor" | ||
| "getTooltip" | ||
| "initialViewState" | ||
| "layers" | ||
| "onClick" | ||
| "onHover" | ||
| "onViewStateChange" | ||
| "parameters" | ||
| "pickingRadius" | ||
| "useDevicePixels" | ||
> & { | ||
mapStyle: string; | ||
customAttribution: string; | ||
deckRef?: RefObject<DeckGLRef | null>; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be further improved on from the Python side to connect with #908