-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
add(examples): react widgets get started #8889
base: master
Are you sure you want to change the base?
Changes from all commits
a29515c
ae86152
534e997
8dc72e5
efbeefc
59c48df
9d3aabf
5a32240
b9cef03
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 |
---|---|---|
@@ -0,0 +1,17 @@ | ||
## Example: Use deck.gl with widgets | ||
|
||
Uses [Vite](https://vitejs.dev/) to bundle and serve files. | ||
|
||
## Usage | ||
|
||
To install dependencies: | ||
|
||
```bash | ||
npm install | ||
# or | ||
yarn | ||
``` | ||
|
||
Commands: | ||
* `npm start` is the development target, to serve the app and hot reload. | ||
* `npm run build` is the production target, to create the final bundle and write to disk. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import React, {useState, forwardRef, useImperativeHandle, useMemo, useRef} from 'react'; | ||
import {createRoot} from 'react-dom/client'; | ||
import DeckGL, {GeoJsonLayer, ArcLayer} from 'deck.gl'; | ||
import { | ||
CompassWidget, | ||
ZoomWidget, | ||
FullscreenWidget, | ||
DarkGlassTheme, | ||
LightGlassTheme | ||
} from '@deck.gl/widgets'; | ||
import '@deck.gl/widgets/stylesheet.css'; | ||
import {createPortal} from 'react-dom'; | ||
|
||
/* global window */ | ||
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); | ||
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. Nit: I wasn't aware that there was a standardized way to query for dark mode. Seems like something that could be built into the widget theming system as a default? 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. I prefer the exposition here since apps implement their themes in so many different ways. Maybe they don't want the "Glass" version, maybe they let the user override theme in a setting, etc.. |
||
const widgetTheme = prefersDarkScheme.matches ? DarkGlassTheme : LightGlassTheme; | ||
|
||
// source: Natural Earth http://www.naturalearthdata.com/ via geojson.xyz | ||
const COUNTRIES = | ||
'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_admin_0_scale_rank.geojson'; //eslint-disable-line | ||
const AIR_PORTS = | ||
'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_airports.geojson'; | ||
|
||
const INITIAL_VIEW_STATE = { | ||
latitude: 51.47, | ||
longitude: 0.45, | ||
zoom: 4, | ||
bearing: 0, | ||
pitch: 30 | ||
}; | ||
|
||
function useWidget(props = {}) { | ||
const [container, setContainer] = useState(null); | ||
|
||
class ReactWidget { | ||
constructor(props) { | ||
this.id = props.id || 'react'; | ||
this.placement = props.placement || 'top-left'; | ||
this.viewId = props.viewId; | ||
this.props = props; | ||
} | ||
|
||
onAdd() { | ||
const el = document.createElement('div'); | ||
// Defer state update to avoid conflicts with rendering | ||
requestAnimationFrame(() => setContainer(el)); | ||
return el; | ||
} | ||
|
||
onRemove() { | ||
requestAnimationFrame(() => setContainer(null)); | ||
} | ||
|
||
setProps(props) { | ||
this.props = props; | ||
this.placement = props.placement || this.placement; | ||
this.viewId = props.viewId || this.viewId; | ||
} | ||
} | ||
|
||
const widget = useMemo(() => new ReactWidget(props), [props]); | ||
|
||
return { | ||
widget, | ||
container | ||
}; | ||
} | ||
|
||
function DeckWidgetWithRef(props, ref) { | ||
const { widget, container } = useWidget(props); | ||
|
||
useImperativeHandle(ref, () => widget, [widget]); | ||
|
||
return container ? createPortal(props.children, container) : null; | ||
} | ||
|
||
const DeckWidget = forwardRef(DeckWidgetWithRef); | ||
|
||
function Root() { | ||
const [placement, setPlacement] = useState('top-left'); | ||
const infoWidget = useWidget({id: 'hook'}); | ||
const buttonWidget = useRef(null); | ||
console.log(infoWidget, buttonWidget) | ||
Check warning on line 83 in examples/get-started/react/widgets/app.jsx GitHub Actions / test-node
|
||
|
||
const onClick = () => { | ||
// eslint-disable-next-line | ||
// alert('React widget!'); | ||
setPlacement('top-right'); | ||
}; | ||
|
||
const infoWidgetEl = ( | ||
<div className="deck-widget" style={widgetTheme}> | ||
<div className="deck-widget-button"> | ||
<button className="deck-widget-icon-button" onClick={onClick}> | ||
<div style={{color: 'var(--button-icon-idle)'}}>i</div> | ||
</button> | ||
</div> | ||
</div> | ||
); | ||
|
||
return ( | ||
<> | ||
{infoWidget.container && createPortal(infoWidgetEl, infoWidget.container)} | ||
<DeckWidget ref={buttonWidget} id="component" placement={placement}> | ||
{infoWidgetEl} | ||
</DeckWidget> | ||
<DeckGL | ||
controller={true} | ||
initialViewState={INITIAL_VIEW_STATE} | ||
widgets={[ | ||
// new ZoomWidget({style: widgetTheme}), | ||
// new CompassWidget({style: widgetTheme}), | ||
// new FullscreenWidget({style: widgetTheme}), | ||
infoWidget.widget, | ||
buttonWidget.current | ||
]} | ||
layers={[ | ||
new GeoJsonLayer({ | ||
id: 'base-map', | ||
data: COUNTRIES, | ||
// Styles | ||
stroked: true, | ||
filled: true, | ||
lineWidthMinPixels: 2, | ||
opacity: 0.4, | ||
getLineColor: [60, 60, 60], | ||
getFillColor: [200, 200, 200] | ||
}), | ||
new GeoJsonLayer({ | ||
id: 'airports', | ||
data: AIR_PORTS, | ||
// Styles | ||
filled: true, | ||
pointRadiusMinPixels: 2, | ||
pointRadiusScale: 2000, | ||
getPointRadius: f => 11 - f.properties.scalerank, | ||
getFillColor: [200, 0, 80, 180] | ||
}), | ||
new ArcLayer({ | ||
id: 'arcs', | ||
data: AIR_PORTS, | ||
dataTransform: d => d.features.filter(f => f.properties.scalerank < 4), | ||
// Styles | ||
getSourcePosition: f => [-0.4531566, 51.4709959], // London | ||
getTargetPosition: f => f.geometry.coordinates, | ||
getSourceColor: [0, 128, 200], | ||
getTargetColor: [200, 0, 80], | ||
getWidth: 1 | ||
}) | ||
]} | ||
/> | ||
</> | ||
); | ||
} | ||
|
||
/* global document */ | ||
const container = document.body.appendChild(document.createElement('div')); | ||
createRoot(container).render(<Root />); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<title>deck.gl Example</title> | ||
<style> | ||
body {margin: 0; width: 100vw; height: 100vh; overflow: hidden;} | ||
</style> | ||
</head> | ||
<body></body> | ||
<script type="module" src="app.jsx"></script> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
{ | ||
"name": "deckgl-example-react-widgets", | ||
"version": "0.0.0", | ||
"private": true, | ||
"license": "MIT", | ||
"scripts": { | ||
"start": "vite --open", | ||
"start-local": "vite --config ../../../vite.config.local.mjs", | ||
"build": "vite build" | ||
}, | ||
"dependencies": { | ||
"deck.gl": "^9.0.0", | ||
"@deck.gl/widgets": "^9.0.0", | ||
"react": "^18.0.0", | ||
"react-dom": "^18.0.0" | ||
}, | ||
"devDependencies": { | ||
"vite": "^4.0.0" | ||
} | ||
} |
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.
@Pessimistress if this too complex for get-started then maybe I can write a custom widget developer guide. I think it'd be common for a react user to want to implement their own widget, and we could document a basic pattern they could use.