From e7f1cc61fc23aa094e218a2227d7d8bfc7cbf5c1 Mon Sep 17 00:00:00 2001 From: Xiaoji Chen Date: Mon, 28 Dec 2020 11:57:50 -0800 Subject: [PATCH] Migrate examples to functional components (#1273) --- examples/additional-overlays/src/app.js | 97 +++++---- .../src/choropleth-overlay.js | 64 +++--- .../src/choropleth-overlay.md | 6 +- .../src/scatterplot-overlay.js | 22 +-- .../src/scatterplot-overlay.md | 6 +- examples/clusters/README.md | 15 +- examples/clusters/src/app.js | 52 +++-- examples/clusters/src/control-panel.js | 33 ++-- examples/controls/README.md | 16 +- examples/controls/src/app.js | 83 +++----- examples/controls/src/city-info.js | 35 ++-- examples/controls/src/control-panel.js | 51 +++-- examples/controls/src/pins.js | 43 ++-- examples/custom-controller/README.md | 15 +- examples/custom-controller/src/app.js | 57 +++--- .../custom-controller/src/control-panel.js | 79 ++++---- examples/custom-cursor/README.md | 15 +- examples/custom-cursor/src/app.js | 67 +++---- examples/custom-cursor/src/control-panel.js | 103 +++++----- examples/deckgl-overlay/README.md | 12 ++ examples/deckgl-overlay/package.json | 2 +- examples/deckgl-overlay/src/app.js | 77 +++----- examples/draggable-markers/README.md | 16 +- examples/draggable-markers/src/app.js | 100 ++++------ .../draggable-markers/src/control-panel.js | 53 +++-- examples/draggable-markers/src/pin.js | 19 +- examples/draw-polygon/README.md | 16 +- examples/draw-polygon/package.json | 2 +- examples/draw-polygon/src/app.js | 118 +++++------ examples/draw-polygon/src/control-panel.js | 47 +++-- examples/filter/README.md | 15 +- examples/filter/src/app.js | 90 ++++----- examples/filter/src/control-panel.js | 33 ++-- examples/geojson-animation/README.md | 15 +- examples/geojson-animation/src/app.js | 64 +++--- .../geojson-animation/src/control-panel.js | 33 ++-- examples/geojson-animation/src/utils.js | 6 - examples/geojson/README.md | 15 +- examples/geojson/package.json | 1 - examples/geojson/src/app.js | 150 ++++++-------- examples/geojson/src/control-panel.js | 73 ++++--- examples/get-started/classic/README.md | 25 ++- examples/get-started/classic/package.json | 5 +- examples/get-started/hooks/README.md | 25 ++- examples/get-started/hooks/app.js | 2 +- examples/get-started/hooks/package.json | 2 +- examples/heatmap/README.md | 17 +- examples/heatmap/package.json | 1 - examples/heatmap/src/app.js | 151 ++++++-------- examples/heatmap/src/control-panel.js | 133 ++++++------- examples/interaction/README.md | 15 +- examples/interaction/src/app.js | 117 +++++------ examples/interaction/src/bart-station.json | 46 ----- examples/interaction/src/control-panel.js | 124 ++++++------ examples/interaction/src/marker-style.js | 30 --- examples/layers/README.md | 15 +- examples/layers/src/app.js | 47 ++--- examples/layers/src/control-panel.js | 185 ++++++++---------- examples/locate-user/README.md | 16 +- examples/locate-user/src/app.js | 60 +++--- examples/reuse-map/README.md | 15 +- examples/reuse-map/app.css | 26 +-- examples/reuse-map/src/app.js | 54 ++--- examples/reuse-map/src/bart-map.js | 75 +++---- examples/viewport-animation/README.md | 15 +- examples/viewport-animation/src/app.js | 67 +++---- .../viewport-animation/src/control-panel.js | 61 +++--- examples/zoom-to-bounds/README.md | 16 +- examples/zoom-to-bounds/src/app.js | 80 +++----- examples/zoom-to-bounds/src/control-panel.js | 33 ++-- website/static/style.css | 2 +- 71 files changed, 1417 insertions(+), 1859 deletions(-) create mode 100644 examples/deckgl-overlay/README.md delete mode 100644 examples/geojson-animation/src/utils.js delete mode 100644 examples/interaction/src/bart-station.json delete mode 100644 examples/interaction/src/marker-style.js diff --git a/examples/additional-overlays/src/app.js b/examples/additional-overlays/src/app.js index 641cb31a..7e9a6747 100644 --- a/examples/additional-overlays/src/app.js +++ b/examples/additional-overlays/src/app.js @@ -19,11 +19,10 @@ // THE SOFTWARE. /* global document */ -import * as ReactDOM from 'react-dom'; import * as React from 'react'; -import {Component} from 'react'; +import {useState} from 'react'; +import {render} from 'react-dom'; import MapGL from 'react-map-gl'; -import Immutable from 'immutable'; import ScatterplotOverlay from './scatterplot-overlay'; import ChoroplethOverlay from './choropleth-overlay'; @@ -33,59 +32,51 @@ import CITIES from '../../.data/cities.json'; const MAPBOX_TOKEN = ''; // Set your mapbox token here -const ZIPCODES = Immutable.fromJS(ZIPCODES_SF.features).map(f => - f.setIn(['properties', 'value'], Math.random() * 1000) -); +const ZIPCODES = ZIPCODES_SF.features.map(f => { + f.properties.value = Math.random() * 1000; + return f; +}); -const CITY_LOCATIONS = Immutable.fromJS(CITIES.map(c => [c.longitude, c.latitude])); +const CITY_LOCATIONS = CITIES.map(c => [c.longitude, c.latitude]); -export default class App extends Component { - constructor(props) { - super(props); - this.state = { - viewport: { - latitude: 37.785164, - longitude: -122.41669, - zoom: 8, - bearing: 0, - pitch: 0 - } - }; - } +export default function App() { + const [viewport, setViewport] = useState({ + latitude: 37.785164, + longitude: -122.41669, + zoom: 8, + bearing: 0, + pitch: 0 + }); - render() { - const {viewport} = this.state; + return ( + + - return ( - this.setState({viewport: v})} - mapboxApiAccessToken={MAPBOX_TOKEN} - > - - - - - ); - } + + + ); } -ReactDOM.render(, document.body.appendChild(document.createElement('div'))); +render(, document.body.appendChild(document.createElement('div'))); diff --git a/examples/additional-overlays/src/choropleth-overlay.js b/examples/additional-overlays/src/choropleth-overlay.js index dc086a3c..90c84c0d 100644 --- a/examples/additional-overlays/src/choropleth-overlay.js +++ b/examples/additional-overlays/src/choropleth-overlay.js @@ -18,12 +18,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import * as React from 'react'; -import {PureComponent} from 'react'; +import {useCallback} from 'react'; import PropTypes from 'prop-types'; import {extent} from 'd3-array'; import {scaleLinear} from 'd3-scale'; import {geoPath, geoTransform} from 'd3-geo'; -import Immutable from 'immutable'; import {CanvasOverlay} from 'react-map-gl'; @@ -33,7 +32,7 @@ const propTypes = { /** * An Immutable List of feature objects. */ - features: PropTypes.instanceOf(Immutable.List), + features: PropTypes.array.isRequired, /* eslint-disable react/forbid-prop-types */ colorDomain: PropTypes.array, colorRange: PropTypes.array.isRequired, @@ -45,11 +44,29 @@ const defaultProps = { globalOpacity: 1, colorDomain: null, colorRange: ['#FFFFFF', '#1FBAD6'], - valueAccessor: feature => feature.get('properties').get('value') + valueAccessor: feature => feature.properties.value }; -export default class ChoroplethOverlay extends PureComponent { - _redraw = ({width, height, ctx, isDragging, project, unproject}) => { +function drawFeatures(ctx, path, props) { + const {features} = props; + const colorDomain = props.colorDomain || extent(features, props.valueAccessor); + + const colorScale = scaleLinear().domain(colorDomain).range(props.colorRange).clamp(true); + + for (const feature of features) { + ctx.beginPath(); + ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; + ctx.lineWidth = '1'; + ctx.fillStyle = colorScale(props.valueAccessor(feature)); + const geometry = feature.geometry; + path(geometry); + ctx.fill(); + ctx.stroke(); + } +} + +export default function ChoroplethOverlay(props) { + const redraw = useCallback(({width, height, ctx, isDragging, project, unproject}) => { ctx.clearRect(0, 0, width, height); function projectPoint(lon, lat) { @@ -59,41 +76,14 @@ export default class ChoroplethOverlay extends PureComponent { /* eslint-enable no-invalid-this */ } - if (this.props.renderWhileDragging || !isDragging) { + if (props.renderWhileDragging || !isDragging) { const transform = geoTransform({point: projectPoint}); const path = geoPath().projection(transform).context(ctx); - this._drawFeatures(ctx, path); + drawFeatures(ctx, path, props); } - }; + }, []); - _drawFeatures(ctx, path) { - const {features} = this.props; - if (!features) { - return; - } - const colorDomain = - this.props.colorDomain || extent(features.toArray(), this.props.valueAccessor); - - const colorScale = scaleLinear().domain(colorDomain).range(this.props.colorRange).clamp(true); - - for (const feature of features) { - ctx.beginPath(); - ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; - ctx.lineWidth = '1'; - ctx.fillStyle = colorScale(this.props.valueAccessor(feature)); - const geometry = feature.get('geometry'); - path({ - type: geometry.get('type'), - coordinates: geometry.get('coordinates').toJS() - }); - ctx.fill(); - ctx.stroke(); - } - } - - render() { - return ; - } + return ; } ChoroplethOverlay.propTypes = propTypes; diff --git a/examples/additional-overlays/src/choropleth-overlay.md b/examples/additional-overlays/src/choropleth-overlay.md index cdf0bf3b..e2f4b647 100644 --- a/examples/additional-overlays/src/choropleth-overlay.md +++ b/examples/additional-overlays/src/choropleth-overlay.md @@ -21,10 +21,10 @@ const ChoroplethOverlay = require('./choropleth-overlay'); ## Props #### features -An [ImmutableJS](https://facebook.github.io/immutable-js/) [List](https://facebook.github.io/immutable-js/docs/#/List) of GeoJSON features. +An array of GeoJSON features. ```js -const features = Immutable.fromJS([ +const features = [ { type: 'Feature', geometry: { @@ -33,7 +33,7 @@ const features = Immutable.fromJS([ } }, ... -]); +]; ``` #### valueAccessor diff --git a/examples/additional-overlays/src/scatterplot-overlay.js b/examples/additional-overlays/src/scatterplot-overlay.js index a11b0fe1..540c7a30 100644 --- a/examples/additional-overlays/src/scatterplot-overlay.js +++ b/examples/additional-overlays/src/scatterplot-overlay.js @@ -18,10 +18,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import * as React from 'react'; -import {PureComponent} from 'react'; +import {useCallback} from 'react'; import PropTypes from 'prop-types'; -import Immutable from 'immutable'; import {CanvasOverlay} from 'react-map-gl'; function round(x, n) { @@ -30,7 +29,7 @@ function round(x, n) { } const propTypes = { - locations: PropTypes.instanceOf(Immutable.List).isRequired, + locations: PropTypes.array.isRequired, lngLatAccessor: PropTypes.func, renderWhileDragging: PropTypes.bool, globalOpacity: PropTypes.number, @@ -40,7 +39,7 @@ const propTypes = { }; const defaultProps = { - lngLatAccessor: location => [location.get(0), location.get(1)], + lngLatAccessor: location => location, renderWhileDragging: true, dotRadius: 4, dotFill: '#1FBAD6', @@ -49,9 +48,9 @@ const defaultProps = { compositeOperation: 'source-over' }; -export default class ScatterplotOverlay extends PureComponent { +export default function ScatterplotOverlay(props) { /* eslint-disable max-statements */ - _redraw = ({width, height, ctx, isDragging, project, unproject}) => { + const redraw = useCallback(({width, height, ctx, isDragging, project, unproject}) => { const { dotRadius, dotFill, @@ -59,12 +58,12 @@ export default class ScatterplotOverlay extends PureComponent { renderWhileDragging, locations, lngLatAccessor - } = this.props; + } = props; ctx.clearRect(0, 0, width, height); ctx.globalCompositeOperation = compositeOperation; - if ((renderWhileDragging || !isDragging) && locations) { + if (renderWhileDragging || !isDragging) { for (const location of locations) { const pixel = project(lngLatAccessor(location)); const pixelRounded = [round(pixel[0], 1), round(pixel[1], 1)]; @@ -81,14 +80,11 @@ export default class ScatterplotOverlay extends PureComponent { } } } - }; + }, []); /* eslint-enable max-statements */ - render() { - return ; - } + return ; } -ScatterplotOverlay.displayName = 'ScatterplotOverlay'; ScatterplotOverlay.propTypes = propTypes; ScatterplotOverlay.defaultProps = defaultProps; diff --git a/examples/additional-overlays/src/scatterplot-overlay.md b/examples/additional-overlays/src/scatterplot-overlay.md index 46743b6e..e4917b83 100644 --- a/examples/additional-overlays/src/scatterplot-overlay.md +++ b/examples/additional-overlays/src/scatterplot-overlay.md @@ -21,15 +21,15 @@ const ScatterplotOverlay = require('./scatterplot-overlay'); ## Props #### locations -An [ImmutableJS](https://facebook.github.io/immutable-js/) [List](https://facebook.github.io/immutable-js/docs/#/List) of points. +An array of points. ```js -const locations = Immutable.fromJS([ +const locations = [ [-122.39851786165565, 37.78736425435588], [-122.40015469418074, 37.78531678199267], [-122.4124101516789, 37.80051001607987], // ... -]); +]; ``` #### lngLatAccessor diff --git a/examples/clusters/README.md b/examples/clusters/README.md index 6f3eec6a..c6cd5773 100644 --- a/examples/clusters/README.md +++ b/examples/clusters/README.md @@ -1,9 +1,14 @@ -
- -
- -## Example: Supercluster +# Example: Supercluster This app reproduces Mapbox's [Create and style clusters](https://docs.mapbox.com/mapbox-gl-js/example/cluster/) example. This example showcases how to visualize points as clusters. + +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` diff --git a/examples/clusters/src/app.js b/examples/clusters/src/app.js index 39218c6e..7f75c23c 100644 --- a/examples/clusters/src/app.js +++ b/examples/clusters/src/app.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState, useRef} from 'react'; import {render} from 'react-dom'; import MapGL, {Source, Layer} from 'react-map-gl'; @@ -8,34 +8,29 @@ import {clusterLayer, clusterCountLayer, unclusteredPointLayer} from './layers'; const MAPBOX_TOKEN = ''; // Set your mapbox token here -export default class App extends Component { - state = { - viewport: { - latitude: 40.67, - longitude: -103.59, - zoom: 3, - bearing: 0, - pitch: 0 - } - }; +export default function App() { + const [viewport, setViewport] = useState({ + latitude: 40.67, + longitude: -103.59, + zoom: 3, + bearing: 0, + pitch: 0 + }); + const mapRef = useRef(null); - _sourceRef = React.createRef(); - - _onViewportChange = viewport => this.setState({viewport}); - - _onClick = event => { + const onClick = event => { const feature = event.features[0]; const clusterId = feature.properties.cluster_id; - const mapboxSource = this._sourceRef.current.getSource(); + const mapboxSource = mapRef.current.getMap().getSource('earthquakes'); mapboxSource.getClusterExpansionZoom(clusterId, (err, zoom) => { if (err) { return; } - this._onViewportChange({ - ...this.state.viewport, + setViewport({ + ...viewport, longitude: feature.geometry.coordinates[0], latitude: feature.geometry.coordinates[1], zoom, @@ -44,36 +39,35 @@ export default class App extends Component { }); }; - render() { - const {viewport} = this.state; - - return ( + return ( + <> - - ); - } + + + ); } export function renderToDom(container) { diff --git a/examples/clusters/src/control-panel.js b/examples/clusters/src/control-panel.js index 70b2185c..fd51218d 100644 --- a/examples/clusters/src/control-panel.js +++ b/examples/clusters/src/control-panel.js @@ -1,21 +1,20 @@ import * as React from 'react'; -import {PureComponent} from 'react'; -export default class ControlPanel extends PureComponent { - render() { - return ( -
-

Create and Style Clusters

-

Use Mapbox GL JS' built-in functions to visualize points as clusters.

- +function ControlPanel() { + return ( +
+

Create and Style Clusters

+

Use Mapbox GL JS' built-in functions to visualize points as clusters.

+ - ); - } +
+ ); } + +export default React.memo(ControlPanel); diff --git a/examples/controls/README.md b/examples/controls/README.md index 1ea300b7..d0e00288 100644 --- a/examples/controls/README.md +++ b/examples/controls/README.md @@ -1,12 +1,12 @@ -
- -
- -## Example: Controls +# Example: Controls Demonstrates how various control components can be used with react-map-gl. -``` - npm install - npm start +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start ``` diff --git a/examples/controls/src/app.js b/examples/controls/src/app.js index ef3c1834..f7f4efb3 100644 --- a/examples/controls/src/app.js +++ b/examples/controls/src/app.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState} from 'react'; import {render} from 'react-dom'; import MapGL, { Popup, @@ -45,63 +45,40 @@ const scaleControlStyle = { padding: '10px' }; -export default class App extends Component { - constructor(props) { - super(props); - this.state = { - viewport: { - latitude: 37.785164, - longitude: -100, - zoom: 3.5, - bearing: 0, - pitch: 0 - }, - popupInfo: null - }; - } +export default function App() { + const [viewport, setViewport] = useState({ + latitude: 40, + longitude: -100, + zoom: 3.5, + bearing: 0, + pitch: 0 + }); + const [popupInfo, setPopupInfo] = useState(null); - _updateViewport = viewport => { - this.setState({viewport}); - }; - - _onClickMarker = city => { - this.setState({popupInfo: city}); - }; - - _renderPopup() { - const {popupInfo} = this.state; - - return ( - popupInfo && ( - this.setState({popupInfo: null})} - > - - - ) - ); - } - - render() { - const {viewport} = this.state; - - return ( + return ( + <> - + - {this._renderPopup()} + {popupInfo && ( + + + + )}
@@ -115,11 +92,11 @@ export default class App extends Component {
- - - ); - } + + + + ); } export function renderToDom(container) { diff --git a/examples/controls/src/city-info.js b/examples/controls/src/city-info.js index a98b9c6f..96f5c68f 100644 --- a/examples/controls/src/city-info.js +++ b/examples/controls/src/city-info.js @@ -1,24 +1,23 @@ import * as React from 'react'; -import {PureComponent} from 'react'; -export default class CityInfo extends PureComponent { - render() { - const {info} = this.props; - const displayName = `${info.city}, ${info.state}`; +function CityInfo(props) { + const {info} = props; + const displayName = `${info.city}, ${info.state}`; - return ( + return ( +
-
- {displayName} |{' '} - - Wikipedia - -
- + {displayName} |{' '} + + Wikipedia +
- ); - } + +
+ ); } + +export default React.memo(CityInfo); diff --git a/examples/controls/src/control-panel.js b/examples/controls/src/control-panel.js index 72a62855..bfdd700e 100644 --- a/examples/controls/src/control-panel.js +++ b/examples/controls/src/control-panel.js @@ -1,30 +1,29 @@ import * as React from 'react'; -import {PureComponent} from 'react'; -export default class ControlPanel extends PureComponent { - render() { - return ( -
-

Marker, Popup, NavigationControl and FullscreenControl

-

- Map showing top 20 most populated cities of the United States. Click on a marker to learn - more. -

-

- Data source:{' '} - - Wikipedia - -

- +function ControlPanel() { + return ( +
+

Marker, Popup, NavigationControl and FullscreenControl

+

+ Map showing top 20 most populated cities of the United States. Click on a marker to learn + more. +

+

+ Data source:{' '} + + Wikipedia + +

+ - ); - } +
+ ); } + +export default React.memo(ControlPanel); diff --git a/examples/controls/src/pins.js b/examples/controls/src/pins.js index 1ceacf92..3ce0c981 100644 --- a/examples/controls/src/pins.js +++ b/examples/controls/src/pins.js @@ -1,5 +1,4 @@ import * as React from 'react'; -import {PureComponent} from 'react'; import {Marker} from 'react-map-gl'; const ICON = `M20.2,15.7L20.2,15.7c1.1-1.6,1.8-3.6,1.8-5.7c0-5.6-4.5-10-10-10S2,4.5,2,10c0,2,0.6,3.9,1.6,5.4c0,0.1,0.1,0.2,0.2,0.3 @@ -9,26 +8,26 @@ const ICON = `M20.2,15.7L20.2,15.7c1.1-1.6,1.8-3.6,1.8-5.7c0-5.6-4.5-10-10-10S2, const SIZE = 20; // Important for perf: the markers never change, avoid rerender when the map viewport changes -export default class Pins extends PureComponent { - render() { - const {data, onClick} = this.props; +function Pins(props) { + const {data, onClick} = props; - return data.map((city, index) => ( - - onClick(city)} - > - - - - )); - } + return data.map((city, index) => ( + + onClick(city)} + > + + + + )); } + +export default React.memo(Pins); diff --git a/examples/custom-controller/README.md b/examples/custom-controller/README.md index 5386d30f..16c6f4fb 100644 --- a/examples/custom-controller/README.md +++ b/examples/custom-controller/README.md @@ -1,7 +1,12 @@ -
- -
- -## Example: Custom Controller +# Example: Custom Controller This example showcases how to extend the default controller to support custom interactivity. + +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` diff --git a/examples/custom-controller/src/app.js b/examples/custom-controller/src/app.js index 3097a7a2..2763633c 100644 --- a/examples/custom-controller/src/app.js +++ b/examples/custom-controller/src/app.js @@ -1,6 +1,6 @@ /* global window */ import * as React from 'react'; -import {Component} from 'react'; +import {useState, useCallback} from 'react'; import {render} from 'react-dom'; import MapGL from 'react-map-gl'; @@ -10,34 +10,26 @@ const customController = new MapController(); const MAPBOX_TOKEN = ''; // Set your mapbox token here -export default class App extends Component { - state = { - viewport: { - latitude: 37.773, - longitude: -122.481, - zoom: 15.5, - bearing: 0, - pitch: 0 - }, - settings: { - invertZoom: false, - invertPan: false, - longPress: false - } - }; +export default function App() { + const [viewport, setViewport] = useState({ + longitude: -122.45, + latitude: 37.78, + zoom: 15.5, + bearing: 0, + pitch: 0 + }); + const [settings, setSettings] = useState({ + invertZoom: false, + invertPan: false, + longPress: false + }); - _onViewportChange = viewport => this.setState({viewport}); + const onSettingsChange = useCallback((name, value) => { + setSettings(s => ({...s, [name]: value})); + }, []); - _onSettingsChange = (name, value) => { - this.setState({ - settings: {...this.state.settings, [name]: value} - }); - }; - - render() { - const {viewport, settings} = this.state; - - return ( + return ( + <> window.alert('pressed') : null} // eslint-disable-line no-alert - onViewportChange={this._onViewportChange} + onViewportChange={setViewport} mapboxApiAccessToken={MAPBOX_TOKEN} - > - - - ); - } + /> + + + ); } export function renderToDom(container) { diff --git a/examples/custom-controller/src/control-panel.js b/examples/custom-controller/src/control-panel.js index 720cdd10..44d3f47c 100644 --- a/examples/custom-controller/src/control-panel.js +++ b/examples/custom-controller/src/control-panel.js @@ -1,47 +1,42 @@ import * as React from 'react'; -import {PureComponent} from 'react'; const camelPattern = /(^|[A-Z])[a-z]*/g; -export default class ControlPanel extends PureComponent { - _formatSettingName(name) { - return name.match(camelPattern).join(' '); - } - - _renderCheckbox(name, value) { - return ( -
- - this.props.onChange(name, evt.target.checked)} - /> -
- ); - } - - render() { - const {settings} = this.props; - - return ( -
-

Custom Controller

-

Override default event handling logic.

- -
- - {this._renderCheckbox('invertZoom', settings.invertZoom)} - {this._renderCheckbox('invertPan', settings.invertPan)} - {this._renderCheckbox('longPress', settings.longPress)} -
- ); - } +function formatSettingName(name) { + return name.match(camelPattern).join(' '); } + +function Checkbox({name, value, onChange}) { + return ( +
+ + onChange(name, evt.target.checked)} /> +
+ ); +} + +function ControlPanel(props) { + const {settings, onChange} = props; + + return ( +
+

Custom Controller

+

Override default event handling logic.

+ +
+ + + + +
+ ); +} + +export default React.memo(ControlPanel); diff --git a/examples/custom-cursor/README.md b/examples/custom-cursor/README.md index 26ca2b16..385c838a 100644 --- a/examples/custom-cursor/README.md +++ b/examples/custom-cursor/README.md @@ -1,7 +1,12 @@ -
- -
+# Example: Custom Cursor -## Example: Custom Cursor +This example showcases how to dynamically change the cursor over the map based on interactivity. -This example showcases how to dynamically change the cursor over the map based on interactivity. \ No newline at end of file +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` diff --git a/examples/custom-cursor/src/app.js b/examples/custom-cursor/src/app.js index f425e852..1f6383b5 100644 --- a/examples/custom-cursor/src/app.js +++ b/examples/custom-cursor/src/app.js @@ -1,6 +1,6 @@ /* global window */ import * as React from 'react'; -import {Component} from 'react'; +import {useState, useCallback} from 'react'; import {render} from 'react-dom'; import MapGL from 'react-map-gl'; import ControlPanel from './control-panel'; @@ -8,62 +8,49 @@ import MAP_STYLE from '../../map-style-basic-v8.json'; const MAPBOX_TOKEN = ''; // Set your mapbox token here -export default class App extends Component { - state = { - mapStyle: '', - viewport: { - latitude: 37.773, - longitude: -122.481, - zoom: 15.5, - bearing: 0, - pitch: 0 - }, - interactiveLayerIds: [] - }; +function getCursor({isHovering, isDragging}) { + return isDragging ? 'grabbing' : isHovering ? 'pointer' : 'default'; +} - _onViewportChange = viewport => this.setState({viewport}); +export default function App() { + const [viewport, setViewport] = useState({ + longitude: -122.48, + latitude: 37.78, + zoom: 15.5, + bearing: 0, + pitch: 0 + }); + const [interactiveLayerIds, setInteractiveLayerIds] = useState([]); - _onInteractiveLayersChange = layerFilter => { - this.setState({ - interactiveLayerIds: MAP_STYLE.layers.map(layer => layer.id).filter(layerFilter) - }); - }; + const onInteractiveLayersChange = useCallback(layerFilter => { + setInteractiveLayerIds(MAP_STYLE.layers.map(layer => layer.id).filter(layerFilter)); + }, []); - _onClick = event => { + const onClick = useCallback(event => { const feature = event.features && event.features[0]; if (feature) { window.alert(`Clicked layer ${feature.layer.id}`); // eslint-disable-line no-alert } - }; + }, []); - _getCursor = ({isHovering, isDragging}) => { - return isHovering ? 'pointer' : 'default'; - }; - - render() { - const {viewport, interactiveLayerIds} = this.state; - - return ( + return ( + <> - - - ); - } + /> + + + ); } export function renderToDom(container) { diff --git a/examples/custom-cursor/src/control-panel.js b/examples/custom-cursor/src/control-panel.js index e40c49f2..9b1a6012 100644 --- a/examples/custom-cursor/src/control-panel.js +++ b/examples/custom-cursor/src/control-panel.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import {PureComponent} from 'react'; +import {useState, useEffect} from 'react'; // Layer id patterns by category const layerSelector = { @@ -18,64 +18,51 @@ function getLayerFilter(categories, layerId) { return false; } -export default class StyleControls extends PureComponent { - state = { - categories: { - parks: true, - buildings: true, - roads: true, - labels: true - } +function Checkbox({name, value, onChange}) { + return ( +
+ + onChange(name, evt.target.checked)} /> +
+ ); +} + +function StyleControls(props) { + const [categories, setCategories] = useState({ + parks: true, + buildings: true, + roads: true, + labels: true + }); + + useEffect(() => { + const filter = layerId => getLayerFilter(categories, layerId); + props.onChange(filter); + }, [categories]); + + const toggleLayer = (name, on) => { + setCategories({...categories, [name]: on}); }; - componentDidMount() { - const filter = getLayerFilter.bind(null, this.state.categories); - this.props.onChange(filter); - } - - _onToggleLayer(name, event) { - const categories = { - ...this.state.categories, - [name]: event.target.checked - }; - this.setState({categories}); - - const filter = getLayerFilter.bind(null, categories); - this.props.onChange(filter); - } - - _renderLayerControl(name) { - const {categories} = this.state; - - return ( -
- - + return ( +
+

Custom Cursor

+

Customize the cursor based on interactivity.

+ - ); - } - - render() { - return ( -
-

Custom Cursor

-

Customize the cursor based on interactivity.

- -
-

Clickable layers

- {Object.keys(layerSelector).map(name => this._renderLayerControl(name))} -
- ); - } +
+

Clickable layers

+ {Object.keys(layerSelector).map(name => ( + + ))} +
+ ); } + +export default React.memo(StyleControls); diff --git a/examples/deckgl-overlay/README.md b/examples/deckgl-overlay/README.md new file mode 100644 index 00000000..d79f9957 --- /dev/null +++ b/examples/deckgl-overlay/README.md @@ -0,0 +1,12 @@ +# Example: DeckGL Overlay + +This example demonstrates using [deck.gl](https://deck.gl) to render a data overlay on top of react-map-gl. + +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` diff --git a/examples/deckgl-overlay/package.json b/examples/deckgl-overlay/package.json index 21edf390..88274fa5 100644 --- a/examples/deckgl-overlay/package.json +++ b/examples/deckgl-overlay/package.json @@ -5,7 +5,7 @@ "start-local": "webpack-dev-server --env.local --progress --hot --open" }, "dependencies": { - "deck.gl": "^7.0.0", + "deck.gl": "^8.0.0", "react": "^16.3.0", "react-dom": "^16.3.0", "react-map-gl": "^6.0.0" diff --git a/examples/deckgl-overlay/src/app.js b/examples/deckgl-overlay/src/app.js index 49154288..11eee78a 100644 --- a/examples/deckgl-overlay/src/app.js +++ b/examples/deckgl-overlay/src/app.js @@ -1,60 +1,41 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState} from 'react'; import {render} from 'react-dom'; import DeckGL, {ArcLayer} from 'deck.gl'; import MapGL from 'react-map-gl'; const TOKEN = ''; // Set your mapbox token here -export default class App extends Component { - constructor(props) { - super(props); - this.state = { - viewport: { - longitude: -122.45, - latitude: 37.78, - zoom: 11, - bearing: 0, - pitch: 30 - } - }; - } +export default function App() { + const [viewport, setViewport] = useState({ + longitude: -122.45, + latitude: 37.75, + zoom: 11, + bearing: 0, + pitch: 60 + }); - _onViewportChange = viewport => { - this.setState({viewport}); - }; + const arcLayer = new ArcLayer({ + data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/bart-segments.json', + getSourcePosition: d => d.from.coordinates, + getTargetPosition: d => d.to.coordinates, + getSourceColor: [255, 200, 0], + getTargetColor: [0, 140, 255], + getWidth: 12 + }); - render() { - const {viewport} = this.state; - - return ( - - [0, 0, 255], - getTargetColor: x => [0, 255, 0] - }) - ]} - /> - - ); - } + return ( + + + + ); } export function renderToDom(container) { diff --git a/examples/draggable-markers/README.md b/examples/draggable-markers/README.md index 858a4588..45c186af 100644 --- a/examples/draggable-markers/README.md +++ b/examples/draggable-markers/README.md @@ -1,12 +1,12 @@ -
- -
- -## Example: Draggable Marker +# Example: Draggable Marker Demonstrates how Marker component can be dragged with react-map-gl. -``` - npm install - npm start +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start ``` diff --git a/examples/draggable-markers/src/app.js b/examples/draggable-markers/src/app.js index 543bb661..1d3d08cc 100644 --- a/examples/draggable-markers/src/app.js +++ b/examples/draggable-markers/src/app.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState, useCallback} from 'react'; import {render} from 'react-dom'; import MapGL, {Marker, NavigationControl} from 'react-map-gl'; @@ -15,66 +15,44 @@ const navStyle = { padding: '10px' }; -export default class App extends Component { - constructor(props) { - super(props); - this.state = { - viewport: { - latitude: 37.785164, - longitude: -100, - zoom: 3.5, - bearing: 0, - pitch: 0 - }, - marker: { - latitude: 37.785164, - longitude: -100 - }, - events: {} - }; - } +export default function App() { + const [viewport, setViewport] = useState({ + latitude: 40, + longitude: -100, + zoom: 3.5, + bearing: 0, + pitch: 0 + }); + const [marker, setMarker] = useState({ + latitude: 40, + longitude: -100 + }); + const [events, logEvents] = useState({}); - _updateViewport = viewport => { - this.setState({viewport}); - }; + const onMarkerDragStart = useCallback(event => { + logEvents(_events => ({..._events, onDragStart: event.lngLat})); + }, []); - _logDragEvent(name, event) { - this.setState({ - events: { - ...this.state.events, - [name]: event.lngLat - } + const onMarkerDrag = useCallback(event => { + logEvents(_events => ({..._events, onDrag: event.lngLat})); + }, []); + + const onMarkerDragEnd = useCallback(event => { + logEvents(_events => ({..._events, onDragEnd: event.lngLat})); + setMarker({ + longitude: event.lngLat[0], + latitude: event.lngLat[1] }); - } + }, []); - _onMarkerDragStart = event => { - this._logDragEvent('onDragStart', event); - }; - - _onMarkerDrag = event => { - this._logDragEvent('onDrag', event); - }; - - _onMarkerDragEnd = event => { - this._logDragEvent('onDragEnd', event); - this.setState({ - marker: { - longitude: event.lngLat[0], - latitude: event.lngLat[1] - } - }); - }; - - render() { - const {viewport, marker} = this.state; - - return ( + return ( + <>
- +
- -
- ); - } + + + ); } export function renderToDom(container) { diff --git a/examples/draggable-markers/src/control-panel.js b/examples/draggable-markers/src/control-panel.js index 021b27d7..651f3e7f 100644 --- a/examples/draggable-markers/src/control-panel.js +++ b/examples/draggable-markers/src/control-panel.js @@ -1,5 +1,4 @@ import * as React from 'react'; -import {PureComponent} from 'react'; const eventNames = ['onDragStart', 'onDrag', 'onDragEnd']; @@ -7,32 +6,32 @@ function round5(value) { return (Math.round(value * 1e5) / 1e5).toFixed(5); } -export default class ControlPanel extends PureComponent { - renderEvent = eventName => { - const {events = {}} = this.props; - const lngLat = events[eventName]; - return ( -
- {eventName}: {lngLat ? lngLat.map(round5).join(', ') : null} +function ControlPanel(props) { + return ( +
+

Draggable Marker

+

Try dragging the marker to another location.

+
+ {eventNames.map(eventName => { + const {events = {}} = props; + const lngLat = events[eventName]; + return ( +
+ {eventName}: {lngLat ? lngLat.map(round5).join(', ') : null} +
+ ); + })}
- ); - }; - - render() { - return ( -
-

Draggable Marker

-

Try dragging the marker to another location.

-
{eventNames.map(this.renderEvent)}
- + - ); - } +
+ ); } + +export default React.memo(ControlPanel); diff --git a/examples/draggable-markers/src/pin.js b/examples/draggable-markers/src/pin.js index 0111b04c..2c155a99 100644 --- a/examples/draggable-markers/src/pin.js +++ b/examples/draggable-markers/src/pin.js @@ -1,5 +1,4 @@ import * as React from 'react'; -import {PureComponent} from 'react'; const ICON = `M20.2,15.7L20.2,15.7c1.1-1.6,1.8-3.6,1.8-5.7c0-5.6-4.5-10-10-10S2,4.5,2,10c0,2,0.6,3.9,1.6,5.4c0,0.1,0.1,0.2,0.2,0.3 c0,0,0.1,0.1,0.1,0.2c0.2,0.3,0.4,0.6,0.7,0.9c2.6,3.1,7.4,7.6,7.4,7.6s4.8-4.5,7.4-7.5c0.2-0.3,0.5-0.6,0.7-0.9 @@ -10,14 +9,14 @@ const pinStyle = { stroke: 'none' }; -export default class Pin extends PureComponent { - render() { - const {size = 20} = this.props; +function Pin(props) { + const {size = 20} = props; - return ( - - - - ); - } + return ( + + + + ); } + +export default React.memo(Pin); diff --git a/examples/draw-polygon/README.md b/examples/draw-polygon/README.md index 294fff64..057fe1ab 100644 --- a/examples/draw-polygon/README.md +++ b/examples/draw-polygon/README.md @@ -1,12 +1,12 @@ -
- -
+# Example: Draw Polygon -## Example: Draw Polygon +Demonstrates how to use [react-map-gl-draw](https://github.com/uber/nebula.gl/tree/master/modules/react-map-gl-draw) to draw polygons with react-map-gl. -Demonstrates how to use [`react-map-gl-draw`](https://github.com/uber/nebula.gl/tree/master/modules/react-map-gl-draw) to draw polygons with react-map-gl. +## Usage -``` - yarn - yarn start-local +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start ``` diff --git a/examples/draw-polygon/package.json b/examples/draw-polygon/package.json index 5872214a..062f866a 100644 --- a/examples/draw-polygon/package.json +++ b/examples/draw-polygon/package.json @@ -8,7 +8,7 @@ "react": "^16.3.0", "react-dom": "^16.3.0", "react-map-gl": "^6.0.0", - "react-map-gl-draw": "^6.0.0", + "react-map-gl-draw": "^0.21.0", "styled-components": "^4.3.2" }, "devDependencies": { diff --git a/examples/draw-polygon/src/app.js b/examples/draw-polygon/src/app.js index 65420c53..9d4868b0 100644 --- a/examples/draw-polygon/src/app.js +++ b/examples/draw-polygon/src/app.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState, useRef, useCallback} from 'react'; import {render} from 'react-dom'; import MapGL from 'react-map-gl'; import {Editor, DrawPolygonMode, EditingMode} from 'react-map-gl-draw'; @@ -9,101 +9,79 @@ import {getFeatureStyle, getEditHandleStyle} from './style'; const TOKEN = ''; // Set your mapbox token here -export default class App extends Component { - constructor(props) { - super(props); - this._editorRef = null; - this.state = { - viewport: { - longitude: -91.874, - latitude: 42.76, - zoom: 12 - }, - mode: null, - selectedFeatureIndex: null - }; - } +export default function App() { + const [viewport, setViewport] = useState({ + longitude: -91.874, + latitude: 42.76, + zoom: 12 + }); + const [mode, setMode] = useState(null); + const [selectedFeatureIndex, setSelectedFeatureIndex] = useState(null); + const editorRef = useRef(null); - _updateViewport = viewport => { - this.setState({viewport}); - }; + const onSelect = useCallback(options => { + setSelectedFeatureIndex(options && options.selectedFeatureIndex); + }, []); - _onSelect = options => { - this.setState({selectedFeatureIndex: options && options.selectedFeatureIndex}); - }; - - _onDelete = () => { - const selectedIndex = this.state.selectedFeatureIndex; - if (selectedIndex !== null && selectedIndex >= 0) { - this._editorRef.deleteFeatures(selectedIndex); + const onDelete = useCallback(() => { + if (selectedFeatureIndex !== null && selectedFeatureIndex >= 0) { + editorRef.current.deleteFeatures(selectedFeatureIndex); } - }; + }, [selectedFeatureIndex]); - _onUpdate = ({editType}) => { + const onUpdate = useCallback(({editType}) => { if (editType === 'addFeature') { - this.setState({ - mode: new EditingMode() - }); + setMode(new EditingMode()); } - }; + }, []); - _renderDrawTools = () => { - // copy from mapbox - return ( -
-
-
+ const drawTools = ( +
+
+
- ); - }; +
+ ); - _renderControlPanel = () => { - const features = this._editorRef && this._editorRef.getFeatures(); - let featureIndex = this.state.selectedFeatureIndex; - if (features && featureIndex === null) { - featureIndex = features.length - 1; - } - const polygon = features && features.length ? features[featureIndex] : null; - return ; - }; + const features = editorRef.current && editorRef.current.getFeatures(); + const selectedFeature = + features && (features[selectedFeatureIndex] || features[features.length - 1]); - render() { - const {viewport, mode} = this.state; - return ( + return ( + <> (this._editorRef = _)} + ref={editorRef} style={{width: '100%', height: '100%'}} clickRadius={12} mode={mode} - onSelect={this._onSelect} - onUpdate={this._onUpdate} + onSelect={onSelect} + onUpdate={onUpdate} editHandleShape={'circle'} featureStyle={getFeatureStyle} editHandleStyle={getEditHandleStyle} /> - {this._renderDrawTools()} - {this._renderControlPanel()} + {drawTools} - ); - } + + + ); } export function renderToDom(container) { diff --git a/examples/draw-polygon/src/control-panel.js b/examples/draw-polygon/src/control-panel.js index 79e6a33d..c87a10ad 100644 --- a/examples/draw-polygon/src/control-panel.js +++ b/examples/draw-polygon/src/control-panel.js @@ -1,29 +1,28 @@ import * as React from 'react'; -import {PureComponent} from 'react'; import area from '@turf/area'; -export default class ControlPanel extends PureComponent { - render() { - const polygon = this.props.polygon; - const polygonArea = polygon && area(polygon); - return ( -
-

Draw Polygon

- {polygon && ( -

- {polygonArea}
- square meters -

- )} - +function ControlPanel(props) { + const polygon = props.polygon; + const polygonArea = polygon && area(polygon); + return ( +
+

Draw Polygon

+ {polygon && ( +

+ {polygonArea}
+ square meters +

+ )} + - ); - } +
+ ); } + +export default React.memo(ControlPanel); diff --git a/examples/filter/README.md b/examples/filter/README.md index d537eaf3..74daf852 100644 --- a/examples/filter/README.md +++ b/examples/filter/README.md @@ -1,9 +1,14 @@ -
- -
- -## Example: Highlight By Filter +# Example: Highlight By Filter This app reproduces Mapbox's [Highlight features containing similar data](https://www.mapbox.com/mapbox-gl-js/example/query-similar-features/) example. This example showcases how to dynamically add/remove filters from layers. + +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` diff --git a/examples/filter/src/app.js b/examples/filter/src/app.js index ea9e1bde..f353e31f 100644 --- a/examples/filter/src/app.js +++ b/examples/filter/src/app.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState, useMemo, useCallback} from 'react'; import {render} from 'react-dom'; import MapGL, {Popup, Source, Layer} from 'react-map-gl'; import ControlPanel from './control-panel'; @@ -8,75 +8,59 @@ import {countiesLayer, highlightLayer} from './map-style.js'; const MAPBOX_TOKEN = ''; // Set your mapbox token here -export default class App extends Component { - state = { - filter: ['in', 'COUNTY', ''], - viewport: { - latitude: 38.88, - longitude: -98, - zoom: 3, - minZoom: 2, - bearing: 0, - pitch: 0 - }, - hoverInfo: null - }; +export default function App() { + const [viewport, setViewport] = useState({ + latitude: 38.88, + longitude: -98, + zoom: 3, + minZoom: 2, + bearing: 0, + pitch: 0 + }); + const [hoverInfo, setHoverInfo] = useState(null); - _onViewportChange = viewport => this.setState({viewport}); - - _onHover = event => { - let countyName = ''; - let hoverInfo = null; - - const county = event.features[0]; - if (county) { - hoverInfo = { - lngLat: event.lngLat, - county: county.properties - }; - countyName = county.properties.COUNTY; - } - this.setState({ - filter: ['in', 'COUNTY', countyName], - hoverInfo + const onHover = useCallback(event => { + const county = event.features && event.features[0]; + setHoverInfo({ + longitude: event.lngLat[0], + latitude: event.lngLat[1], + countyName: county && county.properties.COUNTY }); - }; + }, []); - _renderPopup() { - const {hoverInfo} = this.state; - if (hoverInfo) { - return ( - -
{hoverInfo.county.COUNTY}
-
- ); - } - return null; - } + const selectedCounty = (hoverInfo && hoverInfo.countyName) || ''; + const filter = useMemo(() => ['in', 'COUNTY', selectedCounty], [selectedCounty]); - render() { - const {viewport, filter} = this.state; - - return ( + return ( + <> - {this._renderPopup()} - + {selectedCounty && ( + + {selectedCounty} + + )} - ); - } + + + ); } export function renderToDom(container) { diff --git a/examples/filter/src/control-panel.js b/examples/filter/src/control-panel.js index 71d7f356..e39a8af3 100644 --- a/examples/filter/src/control-panel.js +++ b/examples/filter/src/control-panel.js @@ -1,21 +1,20 @@ import * as React from 'react'; -import {PureComponent} from 'react'; -export default class ControlPanel extends PureComponent { - render() { - return ( -
-

Highlight Features Containing Similar Data

-

Hover over counties to highlight counties that share the same name.

- +function ControlPanel() { + return ( +
+

Highlight Features Containing Similar Data

+

Hover over counties to highlight counties that share the same name.

+ - ); - } +
+ ); } + +export default React.memo(ControlPanel); diff --git a/examples/geojson-animation/README.md b/examples/geojson-animation/README.md index c82639f7..3b7bd0e9 100644 --- a/examples/geojson-animation/README.md +++ b/examples/geojson-animation/README.md @@ -1,9 +1,14 @@ -
- -
- -## Example: Animated GeoJSON +# Example: Animated GeoJSON This app reproduces Mapbox's [Animate point along line](https://www.mapbox.com/mapbox-gl-js/example/animate-point-along-line/) example. This example showcases how to dynamically add and update custom data sources. + +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` diff --git a/examples/geojson-animation/src/app.js b/examples/geojson-animation/src/app.js index 111dcfcf..d24e06e4 100644 --- a/examples/geojson-animation/src/app.js +++ b/examples/geojson-animation/src/app.js @@ -1,11 +1,10 @@ /* global window */ import * as React from 'react'; -import {Component} from 'react'; +import {useState, useEffect} from 'react'; import {render} from 'react-dom'; import MapGL, {Source, Layer} from 'react-map-gl'; import ControlPanel from './control-panel'; -import {pointOnCircle} from './utils'; const MAPBOX_TOKEN = ''; // Set your mapbox token here @@ -17,47 +16,38 @@ const pointLayer = { } }; -export default class App extends Component { - state = { - pointData: null, - viewport: { - latitude: 0, - longitude: -100, - zoom: 3, - bearing: 0, - pitch: 0 - } +function pointOnCircle({center, angle, radius}) { + return { + type: 'Point', + coordinates: [center[0] + Math.cos(angle) * radius, center[1] + Math.sin(angle) * radius] }; +} - componentDidMount() { - this._animatePoint(); - } +export default function App() { + const [viewport, setViewport] = useState({ + latitude: 0, + longitude: -100, + zoom: 3, + bearing: 0, + pitch: 0 + }); + const [pointData, setPointData] = useState(null); - componentWillUnmount() { - window.cancelAnimationFrame(this.animation); - } + useEffect(() => { + const animation = window.requestAnimationFrame(() => + setPointData(pointOnCircle({center: [-100, 0], angle: Date.now() / 1000, radius: 20})) + ); + return () => window.cancelAnimationFrame(animation); + }); - animation = null; - - _animatePoint = () => { - this.setState({ - pointData: pointOnCircle({center: [-100, 0], angle: Date.now() / 1000, radius: 20}) - }); - this.animation = window.requestAnimationFrame(this._animatePoint); - }; - - _onViewportChange = viewport => this.setState({viewport}); - - render() { - const {viewport, pointData} = this.state; - - return ( + return ( + <> {pointData && ( @@ -65,10 +55,10 @@ export default class App extends Component { )} - - ); - } + + + ); } export function renderToDom(container) { diff --git a/examples/geojson-animation/src/control-panel.js b/examples/geojson-animation/src/control-panel.js index 75ff217d..cc7f5a71 100644 --- a/examples/geojson-animation/src/control-panel.js +++ b/examples/geojson-animation/src/control-panel.js @@ -1,21 +1,20 @@ import * as React from 'react'; -import {PureComponent} from 'react'; -export default class ControlPanel extends PureComponent { - render() { - return ( -
-

Animated GeoJSON

-

Render animation by updating GeoJSON data source.

- +function ControlPanel() { + return ( +
+

Animated GeoJSON

+

Render animation by updating GeoJSON data source.

+ - ); - } +
+ ); } + +export default React.memo(ControlPanel); diff --git a/examples/geojson-animation/src/utils.js b/examples/geojson-animation/src/utils.js deleted file mode 100644 index 008ce72c..00000000 --- a/examples/geojson-animation/src/utils.js +++ /dev/null @@ -1,6 +0,0 @@ -export function pointOnCircle({center, angle, radius}) { - return { - type: 'Point', - coordinates: [center[0] + Math.cos(angle) * radius, center[1] + Math.sin(angle) * radius] - }; -} diff --git a/examples/geojson/README.md b/examples/geojson/README.md index 2b3b1782..d5622291 100644 --- a/examples/geojson/README.md +++ b/examples/geojson/README.md @@ -1,7 +1,12 @@ -
- -
- -## Example: GeoJSON +# Example: GeoJSON This example showcases how to dynamically add and update custom data sources. + +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` diff --git a/examples/geojson/package.json b/examples/geojson/package.json index 8ddf7222..e1cafa41 100644 --- a/examples/geojson/package.json +++ b/examples/geojson/package.json @@ -4,7 +4,6 @@ "start-local": "webpack-dev-server --env.local --progress --hot --open" }, "dependencies": { - "d3-request": "^1.0.5", "d3-scale": "^1.0.6", "react": "^16.3.0", "react-dom": "^16.3.0", diff --git a/examples/geojson/src/app.js b/examples/geojson/src/app.js index 98f6e275..4e97e242 100644 --- a/examples/geojson/src/app.js +++ b/examples/geojson/src/app.js @@ -1,114 +1,84 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState, useEffect, useMemo, useCallback} from 'react'; import {render} from 'react-dom'; import MapGL, {Source, Layer} from 'react-map-gl'; import ControlPanel from './control-panel'; import {dataLayer} from './map-style.js'; import {updatePercentiles} from './utils'; -import {json as requestJson} from 'd3-request'; const MAPBOX_TOKEN = ''; // Set your mapbox token here -export default class App extends Component { - state = { - year: 2015, - data: null, - hoveredFeature: null, - viewport: { - latitude: 40, - longitude: -100, - zoom: 3, - bearing: 0, - pitch: 0 - } - }; +export default function App() { + const [viewport, setViewport] = useState({ + latitude: 40, + longitude: -100, + zoom: 3, + bearing: 0, + pitch: 0 + }); + const [year, setYear] = useState(2015); + const [allData, setAllData] = useState(null); + const [hoverInfo, setHoverInfo] = useState(null); - componentDidMount() { - requestJson( - 'https://raw.githubusercontent.com/uber/react-map-gl/master/examples/.data/us-income.geojson', - (error, response) => { - if (!error) { - this._loadData(response); - } - } - ); - } + useEffect(() => { + /* global fetch */ + fetch( + 'https://raw.githubusercontent.com/uber/react-map-gl/master/examples/.data/us-income.geojson' + ) + .then(resp => resp.json()) + .then(json => setAllData(json)); + }, []); - _loadData = data => { - this.setState({ - data: updatePercentiles(data, f => f.properties.income[this.state.year]) - }); - }; - - _updateSettings = (name, value) => { - if (name === 'year') { - this.setState({year: value}); - - const {data} = this.state; - if (data) { - // trigger update - this.setState({ - data: updatePercentiles(data, f => f.properties.income[value]) - }); - } - } - }; - - _onViewportChange = viewport => this.setState({viewport}); - - _onHover = event => { + const onHover = useCallback(event => { const { features, srcEvent: {offsetX, offsetY} } = event; - const hoveredFeature = features && features.find(f => f.layer.id === 'data'); + const hoveredFeature = features && features[0]; - this.setState({hoveredFeature, x: offsetX, y: offsetY}); - }; - - _renderTooltip() { - const {hoveredFeature, x, y} = this.state; - - return ( - hoveredFeature && ( -
-
State: {hoveredFeature.properties.name}
-
Median Household Income: {hoveredFeature.properties.value}
-
Percentile: {(hoveredFeature.properties.percentile / 8) * 100}
-
- ) + setHoverInfo( + hoveredFeature + ? { + feature: hoveredFeature, + x: offsetX, + y: offsetY + } + : null ); - } + }, []); - render() { - const {viewport, data} = this.state; + const data = useMemo(() => { + return allData && updatePercentiles(allData, f => f.properties.income[year]); + }, [allData, year]); - return ( -
- - - - - {this._renderTooltip()} - + return ( + <> + + + + + {hoverInfo && ( +
+
State: {hoverInfo.feature.properties.name}
+
Median Household Income: {hoverInfo.feature.properties.value}
+
Percentile: {(hoverInfo.feature.properties.percentile / 8) * 100}
+
+ )} +
- -
- ); - } + setYear(value)} /> + + ); } export function renderToDom(container) { diff --git a/examples/geojson/src/control-panel.js b/examples/geojson/src/control-panel.js index e37cb6f3..7339ea4e 100644 --- a/examples/geojson/src/control-panel.js +++ b/examples/geojson/src/control-panel.js @@ -1,42 +1,41 @@ import * as React from 'react'; -import {PureComponent} from 'react'; -export default class ControlPanel extends PureComponent { - render() { - const {settings} = this.props; +function ControlPanel(props) { + const {year} = props; - return ( -
-

Interactive GeoJSON

-

- Map showing median household income by state in year {settings.year}. Hover over a - state to see details. -

-

- Data source: US Census Bureau -

- -
- -
- - this.props.onChange('year', evt.target.value)} - /> -
+ return ( +
+

Interactive GeoJSON

+

+ Map showing median household income by state in year {year}. Hover over a state to + see details. +

+

+ Data source: US Census Bureau +

+ - ); - } +
+ +
+ + props.onChange(evt.target.value)} + /> +
+
+ ); } + +export default React.memo(ControlPanel); diff --git a/examples/get-started/classic/README.md b/examples/get-started/classic/README.md index d0fcd324..121c4e67 100644 --- a/examples/get-started/classic/README.md +++ b/examples/get-started/classic/README.md @@ -1,13 +1,18 @@ -
- -
+# react-map-gl Example Using React Class Component -## react-map-gl example with React Component +This example shows a minimal app configuration to use react-map-gl with a React class component. -The configuration showcased here is a bit less straightforward than its browserify -equivalent due to some incompatibilities with mapbox-gl, but has been kept at a -strict minimum. +## Usage -You should keep in mind that it is a development configuration, and probably -should be tweaked a bit for production optimization, there is plenty ressources -on the subject and are not in the scope of this example. +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` + +To build a production version: + +```bash +npm run build +``` diff --git a/examples/get-started/classic/package.json b/examples/get-started/classic/package.json index e53e7514..ee6df9c0 100644 --- a/examples/get-started/classic/package.json +++ b/examples/get-started/classic/package.json @@ -1,11 +1,12 @@ { "scripts": { - "start": "webpack-dev-server --progress --hot --open" + "start": "webpack-dev-server --progress --hot --open", + "build": "webpack -p" }, "dependencies": { "react": "^16.3.0", "react-dom": "^16.3.0", - "react-map-gl": "^5.1.0" + "react-map-gl": "^6.0.0" }, "devDependencies": { "@babel/core": "^7.0.0", diff --git a/examples/get-started/hooks/README.md b/examples/get-started/hooks/README.md index f1a05e9f..9e24da61 100644 --- a/examples/get-started/hooks/README.md +++ b/examples/get-started/hooks/README.md @@ -1,13 +1,18 @@ -
- -
+# react-map-gl Example Using React Functional Component -## react-map-gl example with React Hooks +This example shows a minimal app configuration to use react-map-gl with a React functional component. -The configuration showcased here is a bit less straightforward than its browserify -equivalent due to some incompatibilities with mapbox-gl, but has been kept at a -strict minimum. +## Usage -You should keep in mind that it is a development configuration, and probably -should be tweaked a bit for production optimization, there is plenty ressources -on the subject and are not in the scope of this example. +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` + +To build a production version: + +```bash +npm run build +``` diff --git a/examples/get-started/hooks/app.js b/examples/get-started/hooks/app.js index 12850f71..80b4967e 100644 --- a/examples/get-started/hooks/app.js +++ b/examples/get-started/hooks/app.js @@ -21,7 +21,7 @@ function Root() { width="100vw" height="100vh" mapStyle="mapbox://styles/mapbox/dark-v9" - onViewportChange={nextViewport => setViewport(nextViewport)} + onViewportChange={setViewport} mapboxApiAccessToken={MAPBOX_TOKEN} /> ); diff --git a/examples/get-started/hooks/package.json b/examples/get-started/hooks/package.json index 8545fe57..b2a022ee 100644 --- a/examples/get-started/hooks/package.json +++ b/examples/get-started/hooks/package.json @@ -5,7 +5,7 @@ "dependencies": { "react": "^16.8.0", "react-dom": "^16.8.0", - "react-map-gl": "^5.1.0" + "react-map-gl": "^6.0.0" }, "devDependencies": { "@babel/core": "^7.0.0", diff --git a/examples/heatmap/README.md b/examples/heatmap/README.md index 45cc928e..a1831066 100644 --- a/examples/heatmap/README.md +++ b/examples/heatmap/README.md @@ -1,15 +1,12 @@ -## Example: Heatmap layer +# Example: Heatmap layer -This example showcases how to add a heatmap similar as described in https://docs.mapbox.com/mapbox-gl-js/example/heatmap-layer/. +This app reproduces Mapbox's [Create a heatmap layer](https://docs.mapbox.com/mapbox-gl-js/example/heatmap-layer/) example. -### How to run `heatmap layer example`? +## Usage -Install dependencies (only once) -``` +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash npm i +npm run start ``` -and run example -``` -MapboxAccessToken={YOUR_MAPBOX_TOKEN} npm run start-local -``` -which will open and point your browser to http://localhost:8081/ diff --git a/examples/heatmap/package.json b/examples/heatmap/package.json index 9cc136e9..0ae73b96 100644 --- a/examples/heatmap/package.json +++ b/examples/heatmap/package.json @@ -4,7 +4,6 @@ "start-local": "webpack-dev-server --env.local --progress --hot --open" }, "dependencies": { - "d3-request": "^1.0.5", "react": "^16.3.0", "react-dom": "^16.3.0", "react-map-gl": "^6.0.0" diff --git a/examples/heatmap/src/app.js b/examples/heatmap/src/app.js index 08710a1f..e6d25eec 100644 --- a/examples/heatmap/src/app.js +++ b/examples/heatmap/src/app.js @@ -1,9 +1,8 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState, useEffect, useMemo} from 'react'; import {render} from 'react-dom'; import MapGL, {Source, Layer} from 'react-map-gl'; import ControlPanel from './control-panel'; -import {json as requestJson} from 'd3-request'; import {heatmapLayer} from './map-style'; const MAPBOX_TOKEN = ''; // Set your mapbox token here @@ -24,104 +23,66 @@ function filterFeaturesByDay(featureCollection, time) { return {type: 'FeatureCollection', features}; } -export default class App extends Component { - constructor(props) { - super(props); - const current = new Date().getTime(); +export default function App() { + const [viewport, setViewport] = useState({ + latitude: 40, + longitude: -100, + zoom: 3, + bearing: 0, + pitch: 0 + }); + const [allDays, useAllDays] = useState(true); + const [timeRange, setTimeRange] = useState([0, 0]); + const [selectedTime, selectTime] = useState(0); + const [earthquakes, setEarthQuakes] = useState(null); - this.state = { - viewport: { - latitude: 40, - longitude: -100, - zoom: 3, - bearing: 0, - pitch: 0 - }, - allDay: true, - startTime: current, - endTime: current, - selectedTime: current, - earthquakes: null - }; + useEffect(() => { + /* global fetch */ + fetch('https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson') + .then(resp => resp.json()) + .then(json => { + // Note: In a real application you would do a validation of JSON data before doing anything with it, + // but for demonstration purposes we ingore this part here and just trying to select needed data... + const features = json.features; + const endTime = features[0].properties.time; + const startTime = features[features.length - 1].properties.time; - this._handleChangeDay = this._handleChangeDay.bind(this); - this._handleChangeAllDay = this._handleChangeAllDay.bind(this); - } - - componentDidMount() { - requestJson( - 'https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson', - (error, response) => { - if (!error) { - // Note: In a real application you would do a validation of JSON data before doing anything with it, - // but for demonstration purposes we ingore this part here and just trying to select needed data... - const features = response.features; - const endTime = features[0].properties.time; - const startTime = features[features.length - 1].properties.time; - - this.setState({ - data: response, - earthquakes: response, - endTime, - startTime, - selectedTime: endTime - }); - } - } - ); - } - - _onViewportChange = viewport => this.setState({viewport}); - - _handleChangeDay = time => { - this.setState({selectedTime: time}); - if (this.state.earthquakes) { - this.setState({data: filterFeaturesByDay(this.state.earthquakes, time)}); - } - }; - - _handleChangeAllDay = allDay => { - this.setState({allDay}); - if (this.state.earthquakes) { - this.setState({ - data: allDay - ? this.state.earthquakes - : filterFeaturesByDay(this.state.earthquakes, this.state.selectedTime) + setTimeRange([startTime, endTime]); + setEarthQuakes(json); + selectTime(endTime); }); - } - }; + }, []); - render() { - const {viewport, data, allDay, selectedTime, startTime, endTime} = this.state; + const data = useMemo(() => { + return allDays ? earthquakes : filterFeaturesByDay(earthquakes, selectedTime); + }, [earthquakes, allDays, selectedTime]); - return ( -
- - {data && ( - - - - )} - - -
- ); - } + return ( + <> + + {data && ( + + + + )} + + + + ); } export function renderToDom(container) { diff --git a/examples/heatmap/src/control-panel.js b/examples/heatmap/src/control-panel.js index b5aab712..648131dc 100644 --- a/examples/heatmap/src/control-panel.js +++ b/examples/heatmap/src/control-panel.js @@ -1,69 +1,70 @@ import * as React from 'react'; -import {PureComponent} from 'react'; -export default class ControlPanel extends PureComponent { - render() { - const {startTime, endTime, onChangeDay, allDay, onChangeAllDay, selectedTime} = this.props; - const day = 24 * 60 * 60 * 1000; - const days = Math.round((endTime - startTime) / day); - - const _onChangeDay = evt => { - const daysToAdd = evt.target.value; - // add selected days to start time to calculate new time - const newTime = startTime + daysToAdd * day; - onChangeDay(newTime); - }; - - const formatTime = time => { - const date = new Date(time); - return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`; - }; - - return ( -
-

Heatmap

-

- Map showing earthquakes -
- from {formatTime(startTime)} to {formatTime(endTime)}. -

-
-
- - onChangeAllDay(evt.target.checked)} - /> -
-
- - -
-
-

- Data source:{' '} - - earthquakes.geojson - -

- -
- ); - } +function formatTime(time) { + const date = new Date(time); + return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`; } + +function ControlPanel(props) { + const {startTime, endTime, onChangeTime, allDays, onChangeAllDays, selectedTime} = props; + const day = 24 * 60 * 60 * 1000; + const days = Math.round((endTime - startTime) / day); + const selectedDay = Math.round((selectedTime - startTime) / day); + + const onSelectDay = evt => { + const daysToAdd = evt.target.value; + // add selected days to start time to calculate new time + const newTime = startTime + daysToAdd * day; + onChangeTime(newTime); + }; + + return ( +
+

Heatmap

+

+ Map showing earthquakes +
+ from {formatTime(startTime)} to {formatTime(endTime)}. +

+
+
+ + onChangeAllDays(evt.target.checked)} + /> +
+
+ + +
+
+

+ Data source:{' '} + + earthquakes.geojson + +

+ +
+ ); +} + +export default React.memo(ControlPanel); diff --git a/examples/interaction/README.md b/examples/interaction/README.md index 977186cf..eb993d3b 100644 --- a/examples/interaction/README.md +++ b/examples/interaction/README.md @@ -1,7 +1,12 @@ -
- -
- -## Example: Interaction +# Example: Interaction This example showcases how to toggle/limit user interaction. + +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` diff --git a/examples/interaction/src/app.js b/examples/interaction/src/app.js index 18466870..3e10d583 100644 --- a/examples/interaction/src/app.js +++ b/examples/interaction/src/app.js @@ -1,91 +1,62 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState, useCallback} from 'react'; import {render} from 'react-dom'; -import MapGL, {Marker} from 'react-map-gl'; +import MapGL from 'react-map-gl'; import ControlPanel from './control-panel'; -import bartStations from './bart-station.json'; - const MAPBOX_TOKEN = ''; // Set your mapbox token here -import MARKER_STYLE from './marker-style'; +export default function App() { + const [viewport, setViewport] = useState({ + latitude: 37.729, + longitude: -122.36, + zoom: 11, + bearing: 0, + pitch: 50 + }); + const [interactionState, setInteractionState] = useState({}); + const [settings, setSettings] = useState({ + dragPan: true, + dragRotate: true, + scrollZoom: true, + touchZoom: true, + touchRotate: true, + keyboard: true, + doubleClickZoom: true, + minZoom: 0, + maxZoom: 20, + minPitch: 0, + maxPitch: 85 + }); -export default class App extends Component { - state = { - viewport: { - latitude: 37.729, - longitude: -122.36, - zoom: 11, - bearing: 0, - pitch: 50 - }, - interactionState: {}, - settings: { - dragPan: true, - dragRotate: true, - scrollZoom: true, - touchZoom: true, - touchRotate: true, - keyboard: true, - doubleClickZoom: true, - minZoom: 0, - maxZoom: 20, - minPitch: 0, - maxPitch: 85 - } - }; + const updateSettings = useCallback( + (name, value) => + setSettings(s => ({ + ...s, + [name]: value + })), + [] + ); - _onViewportChange = viewport => this.setState({viewport}); - - _onInteractionStateChange = interactionState => this.setState({interactionState}); - - _onSettingChange = (name, value) => - this.setState({ - settings: {...this.state.settings, [name]: value} - }); - - _renderMarker(station, i) { - const {name, coordinates} = station; - return ( - -
- {name} -
-
- ); - } - - render() { - const {viewport, settings, interactionState} = this.state; - - return ( + return ( + <> setInteractionState({...s})} mapboxApiAccessToken={MAPBOX_TOKEN} - > - - {bartStations.map(this._renderMarker)} - - - ); - } + /> + + + ); } export function renderToDom(container) { diff --git a/examples/interaction/src/bart-station.json b/examples/interaction/src/bart-station.json deleted file mode 100644 index 0da362a8..00000000 --- a/examples/interaction/src/bart-station.json +++ /dev/null @@ -1,46 +0,0 @@ -[ - {"name":"Lafayette (LAFY)","coordinates":[-122.123801,37.893394]}, - {"name":"12th St. Oakland City Center (12TH)","coordinates":[-122.271604,37.803664]}, - {"name":"16th St. Mission (16TH)","coordinates":[-122.419694,37.765062]}, - {"name":"19th St. Oakland (19TH)","coordinates":[-122.269029,37.80787]}, - {"name":"24th St. Mission (24TH)","coordinates":[-122.418466,37.752254]}, - {"name":"Ashby (ASHB)","coordinates":[-122.26978,37.853024]}, - {"name":"Balboa Park (BALB)","coordinates":[-122.447414,37.721981]}, - {"name":"Bay Fair (BAYF)","coordinates":[-122.126871,37.697185]}, - {"name":"Castro Valley (CAST)","coordinates":[-122.075567,37.690754]}, - {"name":"Civic Center/UN Plaza (CIVC)","coordinates":[-122.413756,37.779528]}, - {"name":"Colma (COLM)","coordinates":[-122.466233,37.684638]}, - {"name":"Coliseum/Oakland Airport (COLS)","coordinates":[-122.197273,37.754006]}, - {"name":"Concord (CONC)","coordinates":[-122.029095,37.973737]}, - {"name":"Daly City (DALY)","coordinates":[-122.469081,37.706121]}, - {"name":"Downtown Berkeley (DBRK)","coordinates":[-122.268045,37.869867]}, - {"name":"El Cerrito del Norte (DELN)","coordinates":[-122.317269,37.925655]}, - {"name":"Dublin/Pleasanton (DUBL)","coordinates":[-121.900367,37.701695]}, - {"name":"Embarcadero (EMBR)","coordinates":[-122.396742,37.792976]}, - {"name":"Fremont (FRMT)","coordinates":[-121.9764,37.557355]}, - {"name":"Fruitvale (FTVL)","coordinates":[-122.224274,37.774963]}, - {"name":"Glen Park (GLEN)","coordinates":[-122.434092,37.732921]}, - {"name":"Hayward (HAYW)","coordinates":[-122.087967,37.670399]}, - {"name":"Lake Merritt (LAKE)","coordinates":[-122.265609,37.797484]}, - {"name":"MacArthur (MCAR)","coordinates":[-122.267227,37.828415]}, - {"name":"Millbrae (MLBR)","coordinates":[-122.38666,37.599787]}, - {"name":"Montgomery St. (MONT)","coordinates":[-122.401407,37.789256]}, - {"name":"North Berkeley (NBRK)","coordinates":[-122.283451,37.87404]}, - {"name":"North Concord/Martinez (NCON)","coordinates":[-122.024597,38.003275]}, - {"name":"Orinda (ORIN)","coordinates":[-122.183791,37.878361]}, - {"name":"Pleasant Hill/Contra Costa Centre (PHIL)","coordinates":[-122.056013,37.928403]}, - {"name":"Pittsburg/Bay Point (PITT)","coordinates":[-121.945154,38.018914]}, - {"name":"El Cerrito Plaza (PLZA)","coordinates":[-122.299272,37.903059]}, - {"name":"Powell St. (POWL)","coordinates":[-122.406857,37.784991]}, - {"name":"Richmond (RICH)","coordinates":[-122.353165,37.936887]}, - {"name":"Rockridge (ROCK)","coordinates":[-122.251793,37.844601]}, - {"name":"San Leandro (SANL)","coordinates":[-122.161311,37.722619]}, - {"name":"San Bruno (SBRN)","coordinates":[-122.416038,37.637753]}, - {"name":"San Francisco Int'l Airport (SFIA)","coordinates":[-122.392612,37.616035]}, - {"name":"South Hayward (SHAY)","coordinates":[-122.057551,37.6348]}, - {"name":"South San Francisco (SSAN)","coordinates":[-122.444116,37.664174]}, - {"name":"Union City (UCTY)","coordinates":[-122.017867,37.591208]}, - {"name":"Walnut Creek (WCRK)","coordinates":[-122.067423,37.905628]}, - {"name":"West Dublin/Pleasanton (WDUB)","coordinates":[-121.928099,37.699759]}, - {"name":"West Oakland (WOAK)","coordinates":[-122.294582,37.804675]} -] \ No newline at end of file diff --git a/examples/interaction/src/control-panel.js b/examples/interaction/src/control-panel.js index 45c784ad..6c7889d1 100644 --- a/examples/interaction/src/control-panel.js +++ b/examples/interaction/src/control-panel.js @@ -1,100 +1,88 @@ import * as React from 'react'; -import {PureComponent} from 'react'; const camelPattern = /(^|[A-Z])[a-z]*/g; +function formatSettingName(name) { + return name.match(camelPattern).join(' '); +} -export default class ControlPanel extends PureComponent { - _formatSettingName(name) { - return name.match(camelPattern).join(' '); - } +function Checkbox({name, value, onChange}) { + return ( +
+ + onChange(name, evt.target.checked)} /> +
+ ); +} - _renderCheckbox(name, value) { - return ( -
- - this.props.onChange(name, evt.target.checked)} - /> -
- ); - } +function NumericInput({name, value, onChange}) { + return ( +
+ + onChange(name, Number(evt.target.value))} + /> +
+ ); +} - _renderNumericInput(name, value) { - return ( -
- - this.props.onChange(name, Number(evt.target.value))} - /> -
- ); - } +function ControlPanel(props) { + const {settings, interactionState, onChange} = props; - _renderSetting(name, value) { + const renderSetting = (name, value) => { switch (typeof value) { case 'boolean': - return this._renderCheckbox(name, value); + return ; case 'number': - return this._renderNumericInput(name, value); + return ; default: return null; } - } + }; + + return ( +
+

Limit Map Interaction

+

Turn interactive features off/on.

+ +
+ + {Object.keys(settings).map(name => renderSetting(name, settings[name]))} + +
- _renderInteractionStates({isDragging, isPanning, isRotating, isZooming, inTransition}) { - return (
- {isDragging && 'Yes'} + {interactionState.isDragging && 'Yes'}
- {inTransition && 'Yes'} + {interactionState.inTransition && 'Yes'}
- {isPanning && 'Yes'} + {interactionState.isPanning && 'Yes'}
- {isRotating && 'Yes'} + {interactionState.isRotating && 'Yes'}
- {isZooming && 'Yes'} + {interactionState.isZooming && 'Yes'}
- ); - } - - render() { - const {settings, interactionState} = this.props; - - return ( -
-

Limit Map Interaction

-

Turn interactive features off/on.

- -
- - {Object.keys(settings).map(name => this._renderSetting(name, settings[name]))} - -
- - {this._renderInteractionStates(interactionState)} -
- ); - } +
+ ); } + +export default React.memo(ControlPanel); diff --git a/examples/interaction/src/marker-style.js b/examples/interaction/src/marker-style.js deleted file mode 100644 index ab6546eb..00000000 --- a/examples/interaction/src/marker-style.js +++ /dev/null @@ -1,30 +0,0 @@ -export default ` -.station:before { - content: ' '; - display: inline-block; - width: 8px; - height: 8px; - background: red; - border-radius: 8px; - margin: 0 8px; -} -.station { - border-radius: 20px; - padding-right: 12px; - margin: -12px; - color: transparent; - line-height: 24px; - font-size: 13px; - white-space: nowrap; -} -.station span { - display: none; -} -.station:hover { - background: rgba(0,0,0,0.8); - color: #fff; -} -.station:hover span { - display: inline-block; -} -`; diff --git a/examples/layers/README.md b/examples/layers/README.md index 8ec6ef0a..405308e9 100644 --- a/examples/layers/README.md +++ b/examples/layers/README.md @@ -1,7 +1,12 @@ -
- -
+# Example: Layers -## Example: Layers +This example showcases how to dynamically change layer styles and show/hide layers. -This example showcases how to dynamically change layer styles and show/hide layers. \ No newline at end of file +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` diff --git a/examples/layers/src/app.js b/examples/layers/src/app.js index ca04be95..44d8dfcd 100644 --- a/examples/layers/src/app.js +++ b/examples/layers/src/app.js @@ -1,46 +1,35 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState} from 'react'; import {render} from 'react-dom'; import MapGL from 'react-map-gl'; import ControlPanel from './control-panel'; const MAPBOX_TOKEN = ''; // Set your mapbox token here -export default class App extends Component { - state = { - mapStyle: '', - viewport: { - latitude: 37.805, - longitude: -122.447, - zoom: 15.5, - bearing: 0, - pitch: 0 - } - }; +export default function App() { + const [viewport, setViewport] = useState({ + latitude: 37.805, + longitude: -122.447, + zoom: 15.5, + bearing: 0, + pitch: 0 + }); + const [mapStyle, setMapStyle] = useState(''); - _onViewportChange = viewport => this.setState({viewport}); - - _onStyleChange = mapStyle => this.setState({mapStyle}); - - render() { - const {viewport, mapStyle} = this.state; - - return ( + return ( + <> - - - ); - } + /> + + + + ); } export function renderToDom(container) { diff --git a/examples/layers/src/control-panel.js b/examples/layers/src/control-panel.js index fd185aad..85a772f9 100644 --- a/examples/layers/src/control-panel.js +++ b/examples/layers/src/control-panel.js @@ -1,9 +1,10 @@ import * as React from 'react'; -import {PureComponent} from 'react'; +import {useState, useEffect} from 'react'; import {fromJS} from 'immutable'; import MAP_STYLE from '../../map-style-basic-v8.json'; const defaultMapStyle = fromJS(MAP_STYLE); +const defaultLayers = defaultMapStyle.get('layers'); const categories = ['labels', 'roads', 'buildings', 'parks', 'water', 'background']; @@ -25,107 +26,87 @@ const colorClass = { symbol: 'text-color' }; -export default class StyleControls extends PureComponent { - constructor(props) { - super(props); - - this._defaultLayers = defaultMapStyle.get('layers'); - - this.state = { - visibility: { - water: true, - parks: true, - buildings: true, - roads: true, - labels: true, - background: true - }, - color: { - water: '#DBE2E6', - parks: '#E6EAE9', - buildings: '#c0c0c8', - roads: '#ffffff', - labels: '#78888a', - background: '#EBF0F0' +function getMapStyle({visibility, color}) { + const layers = defaultLayers + .filter(layer => { + const id = layer.get('id'); + return categories.every(name => visibility[name] || !layerSelector[name].test(id)); + }) + .map(layer => { + const id = layer.get('id'); + const type = layer.get('type'); + const category = categories.find(name => layerSelector[name].test(id)); + if (category && colorClass[type]) { + return layer.setIn(['paint', colorClass[type]], color[category]); } - }; - } + return layer; + }); - componentDidMount() { - this._updateMapStyle(this.state); - } - - _onColorChange(name, event) { - const color = {...this.state.color, [name]: event.target.value}; - this.setState({color}); - this._updateMapStyle({...this.state, color}); - } - - _onVisibilityChange(name, event) { - const visibility = { - ...this.state.visibility, - [name]: event.target.checked - }; - this.setState({visibility}); - this._updateMapStyle({...this.state, visibility}); - } - - _updateMapStyle({visibility, color}) { - const layers = this._defaultLayers - .filter(layer => { - const id = layer.get('id'); - return categories.every(name => visibility[name] || !layerSelector[name].test(id)); - }) - .map(layer => { - const id = layer.get('id'); - const type = layer.get('type'); - const category = categories.find(name => layerSelector[name].test(id)); - if (category && colorClass[type]) { - return layer.setIn(['paint', colorClass[type]], color[category]); - } - return layer; - }); - - this.props.onChange(defaultMapStyle.set('layers', layers)); - } - - _renderLayerControl(name) { - const {visibility, color} = this.state; - - return ( -
- - - -
- ); - } - - render() { - return ( -
-

Dynamic Styling

-

Dynamically show/hide map layers and change color with Immutable map style.

- -
- {categories.map(name => this._renderLayerControl(name))} -
- ); - } + return defaultMapStyle.set('layers', layers); } + +function StyleControls(props) { + const [visibility, setVisibility] = useState({ + water: true, + parks: true, + buildings: true, + roads: true, + labels: true, + background: true + }); + + const [color, setColor] = useState({ + water: '#DBE2E6', + parks: '#E6EAE9', + buildings: '#c0c0c8', + roads: '#ffffff', + labels: '#78888a', + background: '#EBF0F0' + }); + + useEffect(() => { + props.onChange(getMapStyle({visibility, color})); + }, [visibility, color]); + + const onColorChange = (name, value) => { + setColor({...color, [name]: value}); + }; + + const onVisibilityChange = (name, value) => { + setVisibility({...visibility, [name]: value}); + }; + + return ( +
+

Dynamic Styling

+

Dynamically show/hide map layers and change color with Immutable map style.

+ +
+ {categories.map(name => ( +
+ + onVisibilityChange(name, evt.target.checked)} + /> + onColorChange(name, evt.target.value)} + /> +
+ ))} +
+ ); +} + +export default React.memo(StyleControls); diff --git a/examples/locate-user/README.md b/examples/locate-user/README.md index 9903942c..8a2fda34 100644 --- a/examples/locate-user/README.md +++ b/examples/locate-user/README.md @@ -1,12 +1,12 @@ -
- -
+# Example: Locate User -## Example: Locate User +Demonstrates how to automatically locate the user and track their current location with react-map-gl. -Demonstrates how to locate the user and track its current location with react-map-gl. +## Usage -``` - npm install - npm start +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start ``` diff --git a/examples/locate-user/src/app.js b/examples/locate-user/src/app.js index ef27b17d..fb7b634c 100644 --- a/examples/locate-user/src/app.js +++ b/examples/locate-user/src/app.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState} from 'react'; import {render} from 'react-dom'; import MapGL, {GeolocateControl} from 'react-map-gl'; @@ -11,40 +11,34 @@ const geolocateStyle = { left: 0, margin: 10 }; +const positionOptions = {enableHighAccuracy: true}; -export default class App extends Component { - state = { - viewport: { - latitude: 37.8, - longitude: 96, - zoom: 3, - bearing: 0, - pitch: 0 - } - }; +export default function App() { + const [viewport, setViewport] = useState({ + latitude: 37.8, + longitude: 96, + zoom: 3, + bearing: 0, + pitch: 0 + }); - _onViewportChange = viewport => this.setState({viewport}); - - render() { - const {viewport} = this.state; - - return ( - - - - ); - } + return ( + + + + ); } export function renderToDom(container) { diff --git a/examples/reuse-map/README.md b/examples/reuse-map/README.md index 71a79268..32012566 100644 --- a/examples/reuse-map/README.md +++ b/examples/reuse-map/README.md @@ -1,7 +1,12 @@ -
- -
- -## Example: Reuse Map +# Example: Reuse Map This example showcases how to reuse the same map without destroying it. + +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` diff --git a/examples/reuse-map/app.css b/examples/reuse-map/app.css index fde1682d..e2605e32 100644 --- a/examples/reuse-map/app.css +++ b/examples/reuse-map/app.css @@ -7,30 +7,10 @@ body { height: 100vh; } -.control-panel { - position: absolute; - top: 0; - right: 0; - max-width: 320px; - background: #fff; - box-shadow: 0 2px 4px rgba(0,0,0,0.3); - padding: 12px 24px; +.toggle-btn { + position: fixed; + z-index: 1; margin: 20px; - font-size: 13px; - line-height: 2; - color: #6b6b76; - text-transform: uppercase; - outline: none; -} - -label { - display: inline-block; - width: 160px; -} - -input { - margin-left: 20px; - max-width: 60px; } /* marker */ diff --git a/examples/reuse-map/src/app.js b/examples/reuse-map/src/app.js index 1bacbc78..0a8db09a 100644 --- a/examples/reuse-map/src/app.js +++ b/examples/reuse-map/src/app.js @@ -1,47 +1,31 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState} from 'react'; import {render} from 'react-dom'; import BartMap from './bart-map'; const LIGHT_STYLE = 'mapbox://styles/mapbox/light-v9'; const DARK_STYLE = 'mapbox://styles/mapbox/dark-v9'; -export default class App extends Component { - state = { - showMap: true, - mapStyleLight: true +export default function App() { + const [showMap, setShowMap] = useState(true); + const [mapStyle, setMapStyle] = useState(LIGHT_STYLE); + + const toggleMap = () => { + setShowMap(!showMap); + + if (showMap) { + setMapStyle(mapStyle === LIGHT_STYLE ? DARK_STYLE : LIGHT_STYLE); + } }; - _toggleMap() { - let {showMap, mapStyleLight} = this.state; - - showMap = !this.state.showMap; - if (showMap) { - mapStyleLight = !mapStyleLight; - } - - this.setState({ - showMap, - mapStyleLight - }); - } - - render() { - const {showMap, mapStyleLight} = this.state; - const mapStyle = mapStyleLight ? LIGHT_STYLE : DARK_STYLE; - if (showMap) { - // eslint-disable-next-line no-console, no-undef - console.warn(mapStyle); - } - return ( -
- - {showMap && } -
- ); - } + return ( + <> + + {showMap && } + + ); } export function renderToDom(container) { diff --git a/examples/reuse-map/src/bart-map.js b/examples/reuse-map/src/bart-map.js index 802e0598..8762a391 100644 --- a/examples/reuse-map/src/bart-map.js +++ b/examples/reuse-map/src/bart-map.js @@ -1,54 +1,41 @@ import * as React from 'react'; -import {Component} from 'react'; -import MapGL, {Marker} from '../../../src'; +import {useState} from 'react'; +import MapGL, {Marker} from 'react-map-gl'; import bartStations from '../../.data/bart-station.json'; const MAPBOX_TOKEN = ''; // Set your mapbox token here -export default class BartMap extends Component { - state = { - viewState: { - latitude: 37.729, - longitude: -122.36, - zoom: 11, - bearing: 0, - pitch: 50 - } - }; - - _onViewportChange = viewState => this.setState({viewState}); +export default function BartMap(props) { + const [viewport, setViewport] = useState({ + latitude: 37.73, + longitude: -122.36, + zoom: 11, + bearing: 0, + pitch: 50 + }); // eslint-disable-next-line - _onMapLoad = event => console.log(event); + const onMapLoad = event => console.log(event); - _renderMarker(station, i) { - const {name, coordinates} = station; - return ( - -
- {name} -
-
- ); - } - - render() { - const {mapStyle} = this.props; - const {viewState} = this.state; - return ( - - {bartStations.map(this._renderMarker)} - - ); - } + return ( + + {bartStations.map(({name, coordinates}, i) => ( + +
+ {name} +
+
+ ))} +
+ ); } diff --git a/examples/viewport-animation/README.md b/examples/viewport-animation/README.md index c501ee2f..4b9d0f1c 100644 --- a/examples/viewport-animation/README.md +++ b/examples/viewport-animation/README.md @@ -1,7 +1,12 @@ -
- -
- -## Example: Viewport Animation +# Example: Viewport Animation This example showcases how to transition smoothly between one viewport to another. + +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` diff --git a/examples/viewport-animation/src/app.js b/examples/viewport-animation/src/app.js index 14ca0d7b..7224fe58 100644 --- a/examples/viewport-animation/src/app.js +++ b/examples/viewport-animation/src/app.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState, useCallback} from 'react'; import {render} from 'react-dom'; import MapGL, {FlyToInterpolator} from 'react-map-gl'; @@ -7,54 +7,39 @@ import ControlPanel from './control-panel'; const MAPBOX_TOKEN = ''; // Set your mapbox token here -export default class App extends Component { - state = { - viewport: { - latitude: 37.7751, - longitude: -122.4193, - zoom: 11, - bearing: 0, - pitch: 0 - } - }; +export default function App() { + const [viewport, setViewport] = useState({ + latitude: 37.7751, + longitude: -122.4193, + zoom: 11, + bearing: 0, + pitch: 0 + }); - _onViewportChange = viewport => - this.setState({ - viewport: {...this.state.viewport, ...viewport} - }); - - _goToViewport = ({longitude, latitude}) => { - this._onViewportChange({ + const onSelectCity = useCallback(({longitude, latitude}) => { + setViewport({ longitude, latitude, zoom: 11, transitionInterpolator: new FlyToInterpolator({speed: 1.2}), transitionDuration: 'auto' }); - }; + }, []); - render() { - const {viewport, settings} = this.state; - - return ( -
- - -
- ); - } + return ( + <> + + + + ); } export function renderToDom(container) { diff --git a/examples/viewport-animation/src/control-panel.js b/examples/viewport-animation/src/control-panel.js index efd41c8b..ff1d3ec7 100644 --- a/examples/viewport-animation/src/control-panel.js +++ b/examples/viewport-animation/src/control-panel.js @@ -1,41 +1,36 @@ import * as React from 'react'; -import {PureComponent} from 'react'; import CITIES from '../../.data/cities.json'; -export default class ControlPanel extends PureComponent { - _renderButton = (city, index) => { - return ( -
- this.props.onViewportChange(city)} - /> - +function ControlPanel(props) { + return ( +
+

Camera Transition

+

Smooth animate of the viewport.

+ - ); - }; +
- render() { - return ( -
-

Camera Transition

-

Smooth animate of the viewport.

-
- - View Code ↗ - + {CITIES.filter(city => city.state === 'California').map((city, index) => ( +
+ props.onSelectCity(city)} + /> +
-
- - {CITIES.filter(city => city.state === 'California').map(this._renderButton)} -
- ); - } + ))} +
+ ); } + +export default React.memo(ControlPanel); diff --git a/examples/zoom-to-bounds/README.md b/examples/zoom-to-bounds/README.md index 120040ec..167d0ebc 100644 --- a/examples/zoom-to-bounds/README.md +++ b/examples/zoom-to-bounds/README.md @@ -1,12 +1,12 @@ -
- -
- -## Example: Zoom To Bounds +# Example: Zoom To Bounds Demonstrates how to zoom to a bounding box with react-map-gl. -``` - npm install - npm start +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start ``` diff --git a/examples/zoom-to-bounds/src/app.js b/examples/zoom-to-bounds/src/app.js index 99675c08..9bc10820 100644 --- a/examples/zoom-to-bounds/src/app.js +++ b/examples/zoom-to-bounds/src/app.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import {Component} from 'react'; +import {useState} from 'react'; import {render} from 'react-dom'; import MapGL, {LinearInterpolator, WebMercatorViewport} from 'react-map-gl'; import bbox from '@turf/bbox'; @@ -9,35 +9,23 @@ import MAP_STYLE from './map-style'; const TOKEN = ''; // Set your mapbox token here -export default class App extends Component { - constructor(props) { - super(props); - this.state = { - viewport: { - latitude: 37.785164, - longitude: -122.4, - zoom: 11, - bearing: 0, - pitch: 0 - }, - popupInfo: null - }; +export default function App() { + const [viewport, setViewport] = useState({ + latitude: 37.78, + longitude: -122.4, + zoom: 11, + bearing: 0, + pitch: 0 + }); - this._map = React.createRef(); - } - - _updateViewport = viewport => { - this.setState({viewport}); - }; - - _onClick = event => { + const onClick = event => { const feature = event.features[0]; if (feature) { // calculate the bounding box of the feature const [minLng, minLat, maxLng, maxLat] = bbox(feature); // construct a viewport instance from the current state - const viewport = new WebMercatorViewport(this.state.viewport); - const {longitude, latitude, zoom} = viewport.fitBounds( + const vp = new WebMercatorViewport(viewport); + const {longitude, latitude, zoom} = vp.fitBounds( [ [minLng, minLat], [maxLng, maxLat] @@ -47,40 +35,34 @@ export default class App extends Component { } ); - this.setState({ - viewport: { - ...this.state.viewport, - longitude, - latitude, - zoom, - transitionInterpolator: new LinearInterpolator({ - around: [event.offsetCenter.x, event.offsetCenter.y] - }), - transitionDuration: 1000 - } + setViewport({ + ...viewport, + longitude, + latitude, + zoom, + transitionInterpolator: new LinearInterpolator({ + around: [event.offsetCenter.x, event.offsetCenter.y] + }), + transitionDuration: 1000 }); } }; - render() { - const {viewport} = this.state; - - return ( + return ( + <> setViewport(v)} mapboxApiAccessToken={TOKEN} - > - - - ); - } + /> + + + ); } export function renderToDom(container) { diff --git a/examples/zoom-to-bounds/src/control-panel.js b/examples/zoom-to-bounds/src/control-panel.js index 3391a10f..fa059d12 100644 --- a/examples/zoom-to-bounds/src/control-panel.js +++ b/examples/zoom-to-bounds/src/control-panel.js @@ -1,21 +1,20 @@ import * as React from 'react'; -import {PureComponent} from 'react'; -export default class ControlPanel extends PureComponent { - render() { - return ( -
-

Zoom to Bounding Box

-

Click on a San Fransisco Neighborhood to zoom in.

- +function ControlPanel() { + return ( +
+

Zoom to Bounding Box

+

Click on a San Fransisco Neighborhood to zoom in.

+ - ); - } +
+ ); } + +export default React.memo(ControlPanel); diff --git a/website/static/style.css b/website/static/style.css index d93c6a9e..1c19ae0d 100644 --- a/website/static/style.css +++ b/website/static/style.css @@ -62,7 +62,7 @@ margin: 24px; padding: 12px 24px; position: absolute; - top: 0; + top: 60px; right: 0; outline: none; cursor: auto;