mirror of
https://github.com/visgl/react-map-gl.git
synced 2026-01-25 16:02:50 +00:00
Migrate examples to functional components (#1273)
This commit is contained in:
parent
da2158c952
commit
e7f1cc61fc
@ -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 (
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100vw"
|
||||
height="100vh"
|
||||
mapStyle="mapbox://styles/mapbox/dark-v9"
|
||||
onViewportChange={setViewport}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
>
|
||||
<ChoroplethOverlay
|
||||
key="choropleth"
|
||||
globalOpacity={0.8}
|
||||
colorDomain={[0, 500, 1000]}
|
||||
colorRange={['#31a354', '#addd8e', '#f7fcb9']}
|
||||
renderWhileDragging={false}
|
||||
features={ZIPCODES}
|
||||
/>
|
||||
|
||||
return (
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100vw"
|
||||
height="100vh"
|
||||
mapStyle="mapbox://styles/mapbox/dark-v9"
|
||||
onViewportChange={v => this.setState({viewport: v})}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
>
|
||||
<ChoroplethOverlay
|
||||
key="choropleth"
|
||||
globalOpacity={0.8}
|
||||
colorDomain={[0, 500, 1000]}
|
||||
colorRange={['#31a354', '#addd8e', '#f7fcb9']}
|
||||
renderWhileDragging={false}
|
||||
features={ZIPCODES}
|
||||
/>
|
||||
|
||||
<ScatterplotOverlay
|
||||
key="scatterplot"
|
||||
locations={CITY_LOCATIONS}
|
||||
dotRadius={10}
|
||||
globalOpacity={0.8}
|
||||
compositeOperation="lighter"
|
||||
dotFill="#00a8fe"
|
||||
renderWhileDragging={true}
|
||||
/>
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
<ScatterplotOverlay
|
||||
key="scatterplot"
|
||||
locations={CITY_LOCATIONS}
|
||||
dotRadius={10}
|
||||
globalOpacity={0.8}
|
||||
compositeOperation="lighter"
|
||||
dotFill="#00a8fe"
|
||||
renderWhileDragging={true}
|
||||
/>
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.render(<App />, document.body.appendChild(document.createElement('div')));
|
||||
render(<App />, document.body.appendChild(document.createElement('div')));
|
||||
|
||||
@ -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 <CanvasOverlay redraw={this._redraw} />;
|
||||
}
|
||||
return <CanvasOverlay redraw={redraw} />;
|
||||
}
|
||||
|
||||
ChoroplethOverlay.propTypes = propTypes;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 <CanvasOverlay redraw={this._redraw} />;
|
||||
}
|
||||
return <CanvasOverlay redraw={redraw} />;
|
||||
}
|
||||
|
||||
ScatterplotOverlay.displayName = 'ScatterplotOverlay';
|
||||
ScatterplotOverlay.propTypes = propTypes;
|
||||
ScatterplotOverlay.defaultProps = defaultProps;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/dark-v9"
|
||||
onViewportChange={this._onViewportChange}
|
||||
onViewportChange={setViewport}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
interactiveLayerIds={[clusterLayer.id]}
|
||||
onClick={this._onClick}
|
||||
onClick={onClick}
|
||||
ref={mapRef}
|
||||
>
|
||||
<Source
|
||||
id="earthquakes"
|
||||
type="geojson"
|
||||
data="https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson"
|
||||
cluster={true}
|
||||
clusterMaxZoom={14}
|
||||
clusterRadius={50}
|
||||
ref={this._sourceRef}
|
||||
>
|
||||
<Layer {...clusterLayer} />
|
||||
<Layer {...clusterCountLayer} />
|
||||
<Layer {...unclusteredPointLayer} />
|
||||
</Source>
|
||||
<ControlPanel containerComponent={this.props.containerComponent} />
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
<ControlPanel />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -1,21 +1,20 @@
|
||||
import * as React from 'react';
|
||||
import {PureComponent} from 'react';
|
||||
|
||||
export default class ControlPanel extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Create and Style Clusters</h3>
|
||||
<p>Use Mapbox GL JS' built-in functions to visualize points as clusters.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/clusters"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
function ControlPanel() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Create and Style Clusters</h3>
|
||||
<p>Use Mapbox GL JS' built-in functions to visualize points as clusters.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/clusters"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
@ -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 && (
|
||||
<Popup
|
||||
tipSize={5}
|
||||
anchor="top"
|
||||
longitude={popupInfo.longitude}
|
||||
latitude={popupInfo.latitude}
|
||||
closeOnClick={false}
|
||||
onClose={() => this.setState({popupInfo: null})}
|
||||
>
|
||||
<CityInfo info={popupInfo} />
|
||||
</Popup>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {viewport} = this.state;
|
||||
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/dark-v9"
|
||||
onViewportChange={this._updateViewport}
|
||||
onViewportChange={setViewport}
|
||||
mapboxApiAccessToken={TOKEN}
|
||||
>
|
||||
<Pins data={CITIES} onClick={this._onClickMarker} />
|
||||
<Pins data={CITIES} onClick={setPopupInfo} />
|
||||
|
||||
{this._renderPopup()}
|
||||
{popupInfo && (
|
||||
<Popup
|
||||
tipSize={5}
|
||||
anchor="top"
|
||||
longitude={popupInfo.longitude}
|
||||
latitude={popupInfo.latitude}
|
||||
closeOnClick={false}
|
||||
onClose={setPopupInfo}
|
||||
>
|
||||
<CityInfo info={popupInfo} />
|
||||
</Popup>
|
||||
)}
|
||||
|
||||
<div style={geolocateStyle}>
|
||||
<GeolocateControl />
|
||||
@ -115,11 +92,11 @@ export default class App extends Component {
|
||||
<div style={scaleControlStyle}>
|
||||
<ScaleControl />
|
||||
</div>
|
||||
|
||||
<ControlPanel containerComponent={this.props.containerComponent} />
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
|
||||
<ControlPanel />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -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 (
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
{displayName} |{' '}
|
||||
<a
|
||||
target="_new"
|
||||
href={`http://en.wikipedia.org/w/index.php?title=Special:Search&search=${displayName}`}
|
||||
>
|
||||
Wikipedia
|
||||
</a>
|
||||
</div>
|
||||
<img width={240} src={info.image} />
|
||||
{displayName} |{' '}
|
||||
<a
|
||||
target="_new"
|
||||
href={`http://en.wikipedia.org/w/index.php?title=Special:Search&search=${displayName}`}
|
||||
>
|
||||
Wikipedia
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<img width={240} src={info.image} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(CityInfo);
|
||||
|
||||
@ -1,30 +1,29 @@
|
||||
import * as React from 'react';
|
||||
import {PureComponent} from 'react';
|
||||
|
||||
export default class ControlPanel extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Marker, Popup, NavigationControl and FullscreenControl </h3>
|
||||
<p>
|
||||
Map showing top 20 most populated cities of the United States. Click on a marker to learn
|
||||
more.
|
||||
</p>
|
||||
<p>
|
||||
Data source:{' '}
|
||||
<a href="https://en.wikipedia.org/wiki/List_of_United_States_cities_by_population">
|
||||
Wikipedia
|
||||
</a>
|
||||
</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/controls"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
function ControlPanel() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Marker, Popup, NavigationControl and FullscreenControl </h3>
|
||||
<p>
|
||||
Map showing top 20 most populated cities of the United States. Click on a marker to learn
|
||||
more.
|
||||
</p>
|
||||
<p>
|
||||
Data source:{' '}
|
||||
<a href="https://en.wikipedia.org/wiki/List_of_United_States_cities_by_population">
|
||||
Wikipedia
|
||||
</a>
|
||||
</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/controls"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
||||
|
||||
@ -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) => (
|
||||
<Marker key={`marker-${index}`} longitude={city.longitude} latitude={city.latitude}>
|
||||
<svg
|
||||
height={SIZE}
|
||||
viewBox="0 0 24 24"
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
fill: '#d00',
|
||||
stroke: 'none',
|
||||
transform: `translate(${-SIZE / 2}px,${-SIZE}px)`
|
||||
}}
|
||||
onClick={() => onClick(city)}
|
||||
>
|
||||
<path d={ICON} />
|
||||
</svg>
|
||||
</Marker>
|
||||
));
|
||||
}
|
||||
return data.map((city, index) => (
|
||||
<Marker key={`marker-${index}`} longitude={city.longitude} latitude={city.latitude}>
|
||||
<svg
|
||||
height={SIZE}
|
||||
viewBox="0 0 24 24"
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
fill: '#d00',
|
||||
stroke: 'none',
|
||||
transform: `translate(${-SIZE / 2}px,${-SIZE}px)`
|
||||
}}
|
||||
onClick={() => onClick(city)}
|
||||
>
|
||||
<path d={ICON} />
|
||||
</svg>
|
||||
</Marker>
|
||||
));
|
||||
}
|
||||
|
||||
export default React.memo(Pins);
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
@ -47,13 +39,12 @@ export default class App extends Component {
|
||||
invertZoom={settings.invertZoom}
|
||||
invertPan={settings.invertPan}
|
||||
onPress={settings.longPress ? () => window.alert('pressed') : null} // eslint-disable-line no-alert
|
||||
onViewportChange={this._onViewportChange}
|
||||
onViewportChange={setViewport}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
>
|
||||
<ControlPanel settings={settings} onChange={this._onSettingsChange} />
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
/>
|
||||
<ControlPanel settings={settings} onChange={onSettingsChange} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -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 (
|
||||
<div key={name} className="input">
|
||||
<label>{this._formatSettingName(name)}</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
onChange={evt => this.props.onChange(name, evt.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {settings} = this.props;
|
||||
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Custom Controller</h3>
|
||||
<p>Override default event handling logic.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/custom-controller"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
{this._renderCheckbox('invertZoom', settings.invertZoom)}
|
||||
{this._renderCheckbox('invertPan', settings.invertPan)}
|
||||
{this._renderCheckbox('longPress', settings.longPress)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function formatSettingName(name) {
|
||||
return name.match(camelPattern).join(' ');
|
||||
}
|
||||
|
||||
function Checkbox({name, value, onChange}) {
|
||||
return (
|
||||
<div key={name} className="input">
|
||||
<label>{formatSettingName(name)}</label>
|
||||
<input type="checkbox" checked={value} onChange={evt => onChange(name, evt.target.checked)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ControlPanel(props) {
|
||||
const {settings, onChange} = props;
|
||||
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Custom Controller</h3>
|
||||
<p>Override default event handling logic.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/custom-controller"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<Checkbox name="invertZoom" value={settings.invertZoom} onChange={onChange} />
|
||||
<Checkbox name="invertPan" value={settings.invertPan} onChange={onChange} />
|
||||
<Checkbox name="longPress" value={settings.longPress} onChange={onChange} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
# 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.
|
||||
## 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
|
||||
```
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle={MAP_STYLE}
|
||||
clickRadius={2}
|
||||
onClick={this._onClick}
|
||||
getCursor={this._getCursor}
|
||||
onClick={onClick}
|
||||
getCursor={getCursor}
|
||||
interactiveLayerIds={interactiveLayerIds}
|
||||
onViewportChange={this._onViewportChange}
|
||||
onViewportChange={setViewport}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
>
|
||||
<ControlPanel
|
||||
containerComponent={this.props.containerComponent}
|
||||
onChange={this._onInteractiveLayersChange}
|
||||
/>
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
/>
|
||||
<ControlPanel onChange={onInteractiveLayersChange} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -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 (
|
||||
<div key={name} className="input">
|
||||
<label>{name}</label>
|
||||
<input type="checkbox" checked={value} onChange={evt => onChange(name, evt.target.checked)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div key={name} className="input">
|
||||
<label>{name}</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={categories[name]}
|
||||
onChange={this._onToggleLayer.bind(this, name)}
|
||||
/>
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Custom Cursor</h3>
|
||||
<p>Customize the cursor based on interactivity.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/custom-cursor"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Custom Cursor</h3>
|
||||
<p>Customize the cursor based on interactivity.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/custom-cursor"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
<hr />
|
||||
<p>Clickable layers</p>
|
||||
{Object.keys(layerSelector).map(name => this._renderLayerControl(name))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<hr />
|
||||
<p>Clickable layers</p>
|
||||
{Object.keys(layerSelector).map(name => (
|
||||
<Checkbox key={name} name={name} value={categories[name]} onChange={toggleLayer} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(StyleControls);
|
||||
|
||||
12
examples/deckgl-overlay/README.md
Normal file
12
examples/deckgl-overlay/README.md
Normal file
@ -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
|
||||
```
|
||||
@ -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"
|
||||
|
||||
@ -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 (
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
maxPitch={85}
|
||||
onViewportChange={this._onViewportChange}
|
||||
mapboxApiAccessToken={TOKEN}
|
||||
>
|
||||
<DeckGL
|
||||
viewState={viewport}
|
||||
layers={[
|
||||
new ArcLayer({
|
||||
data: [
|
||||
{
|
||||
sourcePosition: [-122.41669, 37.7853],
|
||||
targetPosition: [-122.45669, 37.781]
|
||||
}
|
||||
],
|
||||
strokeWidth: 4,
|
||||
getSourceColor: x => [0, 0, 255],
|
||||
getTargetColor: x => [0, 255, 0]
|
||||
})
|
||||
]}
|
||||
/>
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
maxPitch={85}
|
||||
onViewportChange={setViewport}
|
||||
mapboxApiAccessToken={TOKEN}
|
||||
>
|
||||
<DeckGL viewState={viewport} layers={[arcLayer]} />
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/dark-v9"
|
||||
onViewportChange={this._updateViewport}
|
||||
onViewportChange={setViewport}
|
||||
mapboxApiAccessToken={TOKEN}
|
||||
>
|
||||
<Marker
|
||||
@ -83,24 +61,20 @@ export default class App extends Component {
|
||||
offsetTop={-20}
|
||||
offsetLeft={-10}
|
||||
draggable
|
||||
onDragStart={this._onMarkerDragStart}
|
||||
onDrag={this._onMarkerDrag}
|
||||
onDragEnd={this._onMarkerDragEnd}
|
||||
onDragStart={onMarkerDragStart}
|
||||
onDrag={onMarkerDrag}
|
||||
onDragEnd={onMarkerDragEnd}
|
||||
>
|
||||
<Pin size={20} />
|
||||
</Marker>
|
||||
|
||||
<div className="nav" style={navStyle}>
|
||||
<NavigationControl onViewportChange={this._updateViewport} />
|
||||
<NavigationControl />
|
||||
</div>
|
||||
|
||||
<ControlPanel
|
||||
containerComponent={this.props.containerComponent}
|
||||
events={this.state.events}
|
||||
/>
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
<ControlPanel events={events} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -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 (
|
||||
<div key={eventName}>
|
||||
<strong>{eventName}:</strong> {lngLat ? lngLat.map(round5).join(', ') : <em>null</em>}
|
||||
function ControlPanel(props) {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Draggable Marker</h3>
|
||||
<p>Try dragging the marker to another location.</p>
|
||||
<div>
|
||||
{eventNames.map(eventName => {
|
||||
const {events = {}} = props;
|
||||
const lngLat = events[eventName];
|
||||
return (
|
||||
<div key={eventName}>
|
||||
<strong>{eventName}:</strong> {lngLat ? lngLat.map(round5).join(', ') : <em>null</em>}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Draggable Marker</h3>
|
||||
<p>Try dragging the marker to another location.</p>
|
||||
<div>{eventNames.map(this.renderEvent)}</div>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/draggable-markers"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/draggable-markers"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
||||
|
||||
@ -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 (
|
||||
<svg height={size} viewBox="0 0 24 24" style={pinStyle}>
|
||||
<path d={ICON} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<svg height={size} viewBox="0 0 24 24" style={pinStyle}>
|
||||
<path d={ICON} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Pin);
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
# 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
|
||||
```
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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 (
|
||||
<div className="mapboxgl-ctrl-top-left">
|
||||
<div className="mapboxgl-ctrl-group mapboxgl-ctrl">
|
||||
<button
|
||||
className="mapbox-gl-draw_ctrl-draw-btn mapbox-gl-draw_polygon"
|
||||
title="Polygon tool (p)"
|
||||
onClick={() => this.setState({mode: new DrawPolygonMode()})}
|
||||
/>
|
||||
<button
|
||||
className="mapbox-gl-draw_ctrl-draw-btn mapbox-gl-draw_trash"
|
||||
title="Delete"
|
||||
onClick={this._onDelete}
|
||||
/>
|
||||
</div>
|
||||
const drawTools = (
|
||||
<div className="mapboxgl-ctrl-top-left">
|
||||
<div className="mapboxgl-ctrl-group mapboxgl-ctrl">
|
||||
<button
|
||||
className="mapbox-gl-draw_ctrl-draw-btn mapbox-gl-draw_polygon"
|
||||
title="Polygon tool (p)"
|
||||
onClick={() => setMode(new DrawPolygonMode())}
|
||||
/>
|
||||
<button
|
||||
className="mapbox-gl-draw_ctrl-draw-btn mapbox-gl-draw_trash"
|
||||
title="Delete"
|
||||
onClick={onDelete}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</div>
|
||||
);
|
||||
|
||||
_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 <ControlPanel containerComponent={this.props.containerComponent} polygon={polygon} />;
|
||||
};
|
||||
const features = editorRef.current && editorRef.current.getFeatures();
|
||||
const selectedFeature =
|
||||
features && (features[selectedFeatureIndex] || features[features.length - 1]);
|
||||
|
||||
render() {
|
||||
const {viewport, mode} = this.state;
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/satellite-v9"
|
||||
mapboxApiAccessToken={TOKEN}
|
||||
onViewportChange={this._updateViewport}
|
||||
onViewportChange={setViewport}
|
||||
>
|
||||
<Editor
|
||||
ref={_ => (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}
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
<ControlPanel polygon={selectedFeature} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -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 (
|
||||
<div className="control-panel">
|
||||
<h3>Draw Polygon</h3>
|
||||
{polygon && (
|
||||
<p>
|
||||
{polygonArea} <br />
|
||||
square meters
|
||||
</p>
|
||||
)}
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/draw-polygon"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
function ControlPanel(props) {
|
||||
const polygon = props.polygon;
|
||||
const polygonArea = polygon && area(polygon);
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Draw Polygon</h3>
|
||||
{polygon && (
|
||||
<p>
|
||||
{polygonArea} <br />
|
||||
square meters
|
||||
</p>
|
||||
)}
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/draw-polygon"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
@ -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 (
|
||||
<Popup longitude={hoverInfo.lngLat[0]} latitude={hoverInfo.lngLat[1]} closeButton={false}>
|
||||
<div className="county-info">{hoverInfo.county.COUNTY}</div>
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const selectedCounty = (hoverInfo && hoverInfo.countyName) || '';
|
||||
const filter = useMemo(() => ['in', 'COUNTY', selectedCounty], [selectedCounty]);
|
||||
|
||||
render() {
|
||||
const {viewport, filter} = this.state;
|
||||
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/light-v9"
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
onViewportChange={this._onViewportChange}
|
||||
onHover={this._onHover}
|
||||
onViewportChange={setViewport}
|
||||
onHover={onHover}
|
||||
interactiveLayerIds={['counties']}
|
||||
>
|
||||
<Source type="vector" url="mapbox://mapbox.82pkq93d">
|
||||
<Layer beforeId="waterway-label" {...countiesLayer} />
|
||||
<Layer beforeId="waterway-label" {...highlightLayer} filter={filter} />
|
||||
</Source>
|
||||
{this._renderPopup()}
|
||||
<ControlPanel containerComponent={this.props.containerComponent} />
|
||||
{selectedCounty && (
|
||||
<Popup
|
||||
longitude={hoverInfo.longitude}
|
||||
latitude={hoverInfo.latitude}
|
||||
closeButton={false}
|
||||
className="county-info"
|
||||
>
|
||||
{selectedCounty}
|
||||
</Popup>
|
||||
)}
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
<ControlPanel />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -1,21 +1,20 @@
|
||||
import * as React from 'react';
|
||||
import {PureComponent} from 'react';
|
||||
|
||||
export default class ControlPanel extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Highlight Features Containing Similar Data</h3>
|
||||
<p>Hover over counties to highlight counties that share the same name.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/filter"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
function ControlPanel() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Highlight Features Containing Similar Data</h3>
|
||||
<p>Hover over counties to highlight counties that share the same name.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/filter"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/light-v9"
|
||||
onViewportChange={this._onViewportChange}
|
||||
onViewportChange={setViewport}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
>
|
||||
{pointData && (
|
||||
@ -65,10 +55,10 @@ export default class App extends Component {
|
||||
<Layer {...pointLayer} />
|
||||
</Source>
|
||||
)}
|
||||
<ControlPanel containerComponent={this.props.containerComponent} />
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
<ControlPanel />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -1,21 +1,20 @@
|
||||
import * as React from 'react';
|
||||
import {PureComponent} from 'react';
|
||||
|
||||
export default class ControlPanel extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Animated GeoJSON</h3>
|
||||
<p>Render animation by updating GeoJSON data source.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/geojson-animation"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
function ControlPanel() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Animated GeoJSON</h3>
|
||||
<p>Render animation by updating GeoJSON data source.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/geojson-animation"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
||||
|
||||
@ -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]
|
||||
};
|
||||
}
|
||||
@ -1,7 +1,12 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 && (
|
||||
<div className="tooltip" style={{left: x, top: y}}>
|
||||
<div>State: {hoveredFeature.properties.name}</div>
|
||||
<div>Median Household Income: {hoveredFeature.properties.value}</div>
|
||||
<div>Percentile: {(hoveredFeature.properties.percentile / 8) * 100}</div>
|
||||
</div>
|
||||
)
|
||||
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 (
|
||||
<div style={{height: '100%', position: 'relative'}}>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/light-v9"
|
||||
onViewportChange={this._onViewportChange}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
onHover={this._onHover}
|
||||
>
|
||||
<Source type="geojson" data={data}>
|
||||
<Layer {...dataLayer} />
|
||||
</Source>
|
||||
{this._renderTooltip()}
|
||||
</MapGL>
|
||||
return (
|
||||
<>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/light-v9"
|
||||
onViewportChange={setViewport}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
interactiveLayerIds={['data']}
|
||||
onHover={onHover}
|
||||
>
|
||||
<Source type="geojson" data={data}>
|
||||
<Layer {...dataLayer} />
|
||||
</Source>
|
||||
{hoverInfo && (
|
||||
<div className="tooltip" style={{left: hoverInfo.x, top: hoverInfo.y}}>
|
||||
<div>State: {hoverInfo.feature.properties.name}</div>
|
||||
<div>Median Household Income: {hoverInfo.feature.properties.value}</div>
|
||||
<div>Percentile: {(hoverInfo.feature.properties.percentile / 8) * 100}</div>
|
||||
</div>
|
||||
)}
|
||||
</MapGL>
|
||||
|
||||
<ControlPanel
|
||||
containerComponent={this.props.containerComponent}
|
||||
settings={this.state}
|
||||
onChange={this._updateSettings}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<ControlPanel year={year} onChange={value => setYear(value)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -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 (
|
||||
<div className="control-panel">
|
||||
<h3>Interactive GeoJSON</h3>
|
||||
<p>
|
||||
Map showing median household income by state in year <b>{settings.year}</b>. Hover over a
|
||||
state to see details.
|
||||
</p>
|
||||
<p>
|
||||
Data source: <a href="www.census.gov">US Census Bureau</a>
|
||||
</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/geojson"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
<div key={'year'} className="input">
|
||||
<label>Year</label>
|
||||
<input
|
||||
type="range"
|
||||
value={settings.year}
|
||||
min={1995}
|
||||
max={2015}
|
||||
step={1}
|
||||
onChange={evt => this.props.onChange('year', evt.target.value)}
|
||||
/>
|
||||
</div>
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Interactive GeoJSON</h3>
|
||||
<p>
|
||||
Map showing median household income by state in year <b>{year}</b>. Hover over a state to
|
||||
see details.
|
||||
</p>
|
||||
<p>
|
||||
Data source: <a href="www.census.gov">US Census Bureau</a>
|
||||
</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/geojson"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<hr />
|
||||
|
||||
<div key={'year'} className="input">
|
||||
<label>Year</label>
|
||||
<input
|
||||
type="range"
|
||||
value={year}
|
||||
min={1995}
|
||||
max={2015}
|
||||
step={1}
|
||||
onChange={evt => props.onChange(evt.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
# 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
|
||||
```
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
# 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
|
||||
```
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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/
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 (
|
||||
<div style={{height: '100%', position: 'relative'}}>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/dark-v9"
|
||||
onViewportChange={this._onViewportChange}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
>
|
||||
{data && (
|
||||
<Source type="geojson" data={data}>
|
||||
<Layer {...heatmapLayer} />
|
||||
</Source>
|
||||
)}
|
||||
</MapGL>
|
||||
<ControlPanel
|
||||
containerComponent={this.props.containerComponent}
|
||||
startTime={startTime}
|
||||
endTime={endTime}
|
||||
selectedTime={selectedTime}
|
||||
allDay={allDay}
|
||||
onChangeDay={this._handleChangeDay}
|
||||
onChangeAllDay={this._handleChangeAllDay}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/dark-v9"
|
||||
onViewportChange={setViewport}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
>
|
||||
{data && (
|
||||
<Source type="geojson" data={data}>
|
||||
<Layer {...heatmapLayer} />
|
||||
</Source>
|
||||
)}
|
||||
</MapGL>
|
||||
<ControlPanel
|
||||
startTime={timeRange[0]}
|
||||
endTime={timeRange[1]}
|
||||
selectedTime={selectedTime}
|
||||
allDays={allDays}
|
||||
onChangeTime={selectTime}
|
||||
onChangeAllDays={useAllDays}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -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 (
|
||||
<div className="control-panel">
|
||||
<h3>Heatmap</h3>
|
||||
<p>
|
||||
Map showing earthquakes
|
||||
<br />
|
||||
from <b>{formatTime(startTime)}</b> to <b>{formatTime(endTime)}</b>.
|
||||
</p>
|
||||
<hr />
|
||||
<div className="input">
|
||||
<label>All Days</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="allday"
|
||||
checked={allDay}
|
||||
onChange={evt => onChangeAllDay(evt.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
<div className={`input ${allDay ? 'disabled' : ''}`}>
|
||||
<label>Each Day: {formatTime(selectedTime)}</label>
|
||||
<input
|
||||
type="range"
|
||||
disabled={allDay}
|
||||
min={1}
|
||||
max={days}
|
||||
step={1}
|
||||
onChange={_onChangeDay}
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<p>
|
||||
Data source:{' '}
|
||||
<a href="https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson">
|
||||
earthquakes.geojson
|
||||
</a>
|
||||
</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/heatmap"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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 (
|
||||
<div className="control-panel">
|
||||
<h3>Heatmap</h3>
|
||||
<p>
|
||||
Map showing earthquakes
|
||||
<br />
|
||||
from <b>{formatTime(startTime)}</b> to <b>{formatTime(endTime)}</b>.
|
||||
</p>
|
||||
<hr />
|
||||
<div className="input">
|
||||
<label>All Days</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="allday"
|
||||
checked={allDays}
|
||||
onChange={evt => onChangeAllDays(evt.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
<div className={`input ${allDays ? 'disabled' : ''}`}>
|
||||
<label>Each Day: {formatTime(selectedTime)}</label>
|
||||
<input
|
||||
type="range"
|
||||
disabled={allDays}
|
||||
min={1}
|
||||
max={days}
|
||||
value={selectedDay}
|
||||
step={1}
|
||||
onChange={onSelectDay}
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<p>
|
||||
Data source:{' '}
|
||||
<a href="https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson">
|
||||
earthquakes.geojson
|
||||
</a>
|
||||
</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/heatmap"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
@ -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 (
|
||||
<Marker
|
||||
key={i}
|
||||
longitude={coordinates[0]}
|
||||
latitude={coordinates[1]}
|
||||
captureDrag={false}
|
||||
captureDoubleClick={false}
|
||||
>
|
||||
<div className="station">
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
</Marker>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {viewport, settings, interactionState} = this.state;
|
||||
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
{...settings}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/dark-v9"
|
||||
onViewportChange={this._onViewportChange}
|
||||
onInteractionStateChange={this._onInteractionStateChange}
|
||||
onViewportChange={setViewport}
|
||||
onInteractionStateChange={s => setInteractionState({...s})}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
>
|
||||
<style>{MARKER_STYLE}</style>
|
||||
{bartStations.map(this._renderMarker)}
|
||||
<ControlPanel
|
||||
containerComponent={this.props.containerComponent}
|
||||
settings={settings}
|
||||
interactionState={{...interactionState}}
|
||||
onChange={this._onSettingChange}
|
||||
/>
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
/>
|
||||
<ControlPanel
|
||||
settings={settings}
|
||||
interactionState={interactionState}
|
||||
onChange={updateSettings}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -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]}
|
||||
]
|
||||
@ -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 (
|
||||
<div className="input">
|
||||
<label>{formatSettingName(name)}</label>
|
||||
<input type="checkbox" checked={value} onChange={evt => onChange(name, evt.target.checked)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderCheckbox(name, value) {
|
||||
return (
|
||||
<div key={name} className="input">
|
||||
<label>{this._formatSettingName(name)}</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value}
|
||||
onChange={evt => this.props.onChange(name, evt.target.checked)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function NumericInput({name, value, onChange}) {
|
||||
return (
|
||||
<div className="input">
|
||||
<label>{formatSettingName(name)}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={value}
|
||||
onChange={evt => onChange(name, Number(evt.target.value))}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderNumericInput(name, value) {
|
||||
return (
|
||||
<div key={name} className="input">
|
||||
<label>{this._formatSettingName(name)}</label>
|
||||
<input
|
||||
type="number"
|
||||
value={value}
|
||||
onChange={evt => this.props.onChange(name, Number(evt.target.value))}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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 <Checkbox key={name} name={name} value={value} onChange={onChange} />;
|
||||
case 'number':
|
||||
return this._renderNumericInput(name, value);
|
||||
return <NumericInput key={name} name={name} value={value} onChange={onChange} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Limit Map Interaction</h3>
|
||||
<p>Turn interactive features off/on.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/interaction"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
{Object.keys(settings).map(name => renderSetting(name, settings[name]))}
|
||||
|
||||
<hr />
|
||||
|
||||
_renderInteractionStates({isDragging, isPanning, isRotating, isZooming, inTransition}) {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<label>Dragging</label>
|
||||
<span>{isDragging && 'Yes'}</span>
|
||||
<span>{interactionState.isDragging && 'Yes'}</span>
|
||||
</div>
|
||||
<div>
|
||||
<label>Transition</label>
|
||||
<span>{inTransition && 'Yes'}</span>
|
||||
<span>{interactionState.inTransition && 'Yes'}</span>
|
||||
</div>
|
||||
<div>
|
||||
<label>Panning</label>
|
||||
<span>{isPanning && 'Yes'}</span>
|
||||
<span>{interactionState.isPanning && 'Yes'}</span>
|
||||
</div>
|
||||
<div>
|
||||
<label>Rotating</label>
|
||||
<span>{isRotating && 'Yes'}</span>
|
||||
<span>{interactionState.isRotating && 'Yes'}</span>
|
||||
</div>
|
||||
<div>
|
||||
<label>Zooming</label>
|
||||
<span>{isZooming && 'Yes'}</span>
|
||||
<span>{interactionState.isZooming && 'Yes'}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {settings, interactionState} = this.props;
|
||||
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Limit Map Interaction</h3>
|
||||
<p>Turn interactive features off/on.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/interaction"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
{Object.keys(settings).map(name => this._renderSetting(name, settings[name]))}
|
||||
|
||||
<hr />
|
||||
|
||||
{this._renderInteractionStates(interactionState)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
`;
|
||||
@ -1,7 +1,12 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
# 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.
|
||||
## 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
|
||||
```
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle={mapStyle}
|
||||
onViewportChange={this._onViewportChange}
|
||||
onViewportChange={setViewport}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
>
|
||||
<ControlPanel
|
||||
containerComponent={this.props.containerComponent}
|
||||
onChange={this._onStyleChange}
|
||||
/>
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
/>
|
||||
|
||||
<ControlPanel onChange={setMapStyle} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -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 (
|
||||
<div key={name} className="input">
|
||||
<label>{name}</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={visibility[name]}
|
||||
onChange={this._onVisibilityChange.bind(this, name)}
|
||||
/>
|
||||
<input
|
||||
type="color"
|
||||
value={color[name]}
|
||||
disabled={!visibility[name]}
|
||||
onChange={this._onColorChange.bind(this, name)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Dynamic Styling</h3>
|
||||
<p>Dynamically show/hide map layers and change color with Immutable map style.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/layers"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
<hr />
|
||||
{categories.map(name => this._renderLayerControl(name))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
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 (
|
||||
<div className="control-panel">
|
||||
<h3>Dynamic Styling</h3>
|
||||
<p>Dynamically show/hide map layers and change color with Immutable map style.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/layers"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
<hr />
|
||||
{categories.map(name => (
|
||||
<div key={name} className="input">
|
||||
<label>{name}</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={visibility[name]}
|
||||
onChange={evt => onVisibilityChange(name, evt.target.checked)}
|
||||
/>
|
||||
<input
|
||||
type="color"
|
||||
value={color[name]}
|
||||
disabled={!visibility[name]}
|
||||
onChange={evt => onColorChange(name, evt.target.value)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(StyleControls);
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
# 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
|
||||
```
|
||||
|
||||
@ -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 (
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/dark-v9"
|
||||
onViewportChange={this._onViewportChange}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
>
|
||||
<GeolocateControl
|
||||
style={geolocateStyle}
|
||||
positionOptions={{enableHighAccuracy: true}}
|
||||
trackUserLocation={true}
|
||||
/>
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/dark-v9"
|
||||
onViewportChange={setViewport}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
>
|
||||
<GeolocateControl
|
||||
style={geolocateStyle}
|
||||
positionOptions={positionOptions}
|
||||
trackUserLocation
|
||||
auto
|
||||
/>
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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 (
|
||||
<div style={{height: '100%'}}>
|
||||
<button style={{position: 'fixed', zIndex: 1}} onClick={this._toggleMap.bind(this)}>
|
||||
Toggle Map
|
||||
</button>
|
||||
{showMap && <BartMap mapStyle={mapStyle} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<button className="toggle-btn" onClick={toggleMap}>
|
||||
Toggle Map
|
||||
</button>
|
||||
{showMap && <BartMap mapStyle={mapStyle} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -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 (
|
||||
<Marker key={i} longitude={coordinates[0]} latitude={coordinates[1]}>
|
||||
<div className="station">
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
</Marker>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {mapStyle} = this.props;
|
||||
const {viewState} = this.state;
|
||||
return (
|
||||
<MapGL
|
||||
{...viewState}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
mapStyle={mapStyle}
|
||||
width="100%"
|
||||
height="100%"
|
||||
onViewportChange={this._onViewportChange}
|
||||
onLoad={this._onMapLoad}
|
||||
reuseMaps={true}
|
||||
>
|
||||
{bartStations.map(this._renderMarker)}
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<MapGL
|
||||
{...viewport}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
mapStyle={props.mapStyle}
|
||||
width="100%"
|
||||
height="100%"
|
||||
onViewportChange={setViewport}
|
||||
onLoad={onMapLoad}
|
||||
reuseMaps
|
||||
>
|
||||
{bartStations.map(({name, coordinates}, i) => (
|
||||
<Marker key={i} longitude={coordinates[0]} latitude={coordinates[1]}>
|
||||
<div className="station">
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
</Marker>
|
||||
))}
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
@ -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 (
|
||||
<div style={{height: '100%'}}>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
{...settings}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/light-v9"
|
||||
onViewportChange={this._onViewportChange}
|
||||
dragRotate={false}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
/>
|
||||
<ControlPanel
|
||||
containerComponent={this.props.containerComponent}
|
||||
onViewportChange={this._goToViewport}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<MapGL
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
mapStyle="mapbox://styles/mapbox/light-v9"
|
||||
onViewportChange={setViewport}
|
||||
dragRotate={false}
|
||||
mapboxApiAccessToken={MAPBOX_TOKEN}
|
||||
/>
|
||||
<ControlPanel onSelectCity={onSelectCity} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -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 (
|
||||
<div key={`btn-${index}`} className="input">
|
||||
<input
|
||||
type="radio"
|
||||
name="city"
|
||||
id={`city-${index}`}
|
||||
defaultChecked={city.city === 'San Francisco'}
|
||||
onClick={() => this.props.onViewportChange(city)}
|
||||
/>
|
||||
<label htmlFor={`city-${index}`}>{city.city}</label>
|
||||
function ControlPanel(props) {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Camera Transition</h3>
|
||||
<p>Smooth animate of the viewport.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/viewport-animation"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
<hr />
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Camera Transition</h3>
|
||||
<p>Smooth animate of the viewport.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/viewport-animation"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
{CITIES.filter(city => city.state === 'California').map((city, index) => (
|
||||
<div key={`btn-${index}`} className="input">
|
||||
<input
|
||||
type="radio"
|
||||
name="city"
|
||||
id={`city-${index}`}
|
||||
defaultChecked={city.city === 'San Francisco'}
|
||||
onClick={() => props.onSelectCity(city)}
|
||||
/>
|
||||
<label htmlFor={`city-${index}`}>{city.city}</label>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
{CITIES.filter(city => city.state === 'California').map(this._renderButton)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
<div align="center">
|
||||
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
|
||||
</div>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<MapGL
|
||||
ref={this._map}
|
||||
mapStyle={MAP_STYLE}
|
||||
interactiveLayerIds={['sf-neighborhoods-fill']}
|
||||
{...viewport}
|
||||
width="100%"
|
||||
height="100%"
|
||||
onClick={this._onClick}
|
||||
onViewportChange={this._updateViewport}
|
||||
mapStyle={MAP_STYLE}
|
||||
interactiveLayerIds={['sf-neighborhoods-fill']}
|
||||
onClick={onClick}
|
||||
onViewportChange={v => setViewport(v)}
|
||||
mapboxApiAccessToken={TOKEN}
|
||||
>
|
||||
<ControlPanel containerComponent={this.props.containerComponent} />
|
||||
</MapGL>
|
||||
);
|
||||
}
|
||||
/>
|
||||
<ControlPanel />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderToDom(container) {
|
||||
|
||||
@ -1,21 +1,20 @@
|
||||
import * as React from 'react';
|
||||
import {PureComponent} from 'react';
|
||||
|
||||
export default class ControlPanel extends PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Zoom to Bounding Box</h3>
|
||||
<p>Click on a San Fransisco Neighborhood to zoom in.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/zoom-to-bounds"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
function ControlPanel() {
|
||||
return (
|
||||
<div className="control-panel">
|
||||
<h3>Zoom to Bounding Box</h3>
|
||||
<p>Click on a San Fransisco Neighborhood to zoom in.</p>
|
||||
<div className="source-link">
|
||||
<a
|
||||
href="https://github.com/visgl/react-map-gl/tree/6.0-release/examples/zoom-to-bounds"
|
||||
target="_new"
|
||||
>
|
||||
View Code ↗
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ControlPanel);
|
||||
|
||||
@ -62,7 +62,7 @@
|
||||
margin: 24px;
|
||||
padding: 12px 24px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
top: 60px;
|
||||
right: 0;
|
||||
outline: none;
|
||||
cursor: auto;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user