Website and examples polish (#294)

- Add geojson example
- Add introduction, credit and link to source to all examples
- CSS bug fixes
- Remove unused website components
- Header image
This commit is contained in:
Xiaoji Chen 2017-06-27 22:31:33 -07:00 committed by GitHub
parent 5e65c18ee5
commit dd5baee74e
70 changed files with 829 additions and 1171 deletions

View File

@ -3,15 +3,18 @@ body {
background: #000;
}
.city-pin {
cursor: pointer;
fill: #d00;
stroke: none;
}
.nav {
.options-panel {
position: absolute;
top: 0;
right: 0;
padding: 10px;
max-width: 320px;
background: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
padding: 12px 24px;
margin: 20px;
font-size: 13px;
line-height: 2;
color: #6b6b76;
text-transform: uppercase;
outline: none;
}

View File

@ -3,9 +3,11 @@ import React, {Component} from 'react';
import {render} from 'react-dom';
import MapGL, {Marker, Popup, NavigationControl} from 'react-map-gl';
import ControlPanel from './control-panel';
import CityPin from './city-pin';
import CityInfo from './city-info';
import CITIES from './cities.json';
import CITIES from '../../data/cities.json';
const token = process.env.MapboxAccessToken; // eslint-disable-line
@ -16,7 +18,7 @@ if (!token) {
const navStyle = {
position: 'absolute',
top: 0,
right: 0,
left: 0,
padding: '10px'
};
@ -28,29 +30,16 @@ export default class App extends Component {
viewport: {
latitude: 37.785164,
longitude: -100,
zoom: 4,
zoom: 3.5,
bearing: 0,
pitch: 0,
width: window.innerWidth,
height: window.innerHeight,
width: 500,
height: 500,
},
popupInfo: null
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.width !== this.state.viewport.width ||
nextProps.height !== this.state.viewport.height) {
this.setState({
viewport: {
...this.state.viewport,
width: nextProps.width,
height: nextProps.height
}
});
}
}
componentDidMount() {
window.addEventListener('resize', this._resize);
this._resize();
@ -60,19 +49,18 @@ export default class App extends Component {
window.removeEventListener('resize', this._resize);
}
_updateViewport = (viewport) => {
this.setState({viewport});
}
_resize = () => {
const {widthOffset, heightOffset} = this.props;
this.setState({
viewport: {
...this.state.viewport,
width: window.innerWidth - widthOffset,
height: window.innerHeight - heightOffset,
width: this.props.width || window.innerWidth,
height: this.props.height || window.innerHeight
}
});
};
_updateViewport = (viewport) => {
this.setState({viewport});
}
_renderCityMarker = (city, index) => {
@ -94,7 +82,7 @@ export default class App extends Component {
longitude={popupInfo.longitude}
latitude={popupInfo.latitude}
onClose={() => this.setState({popupInfo: null})} >
{popupInfo.city}, {popupInfo.state}
<CityInfo info={popupInfo} />
</Popup>
);
}
@ -118,15 +106,10 @@ export default class App extends Component {
<NavigationControl onViewportChange={this._updateViewport} />
</div>
<ControlPanel />
</MapGL>
);
}
}
// Used to render properly in docs. Ignore these props or remove if you're
// copying this as a starting point.
App.defaultProps = {
widthOffset: 0,
heightOffset: 0
};

View File

@ -1,22 +0,0 @@
[
{"city":"New York","state":"New York","latitude":40.6643,"longitude":-73.9385},
{"city":"Los Angeles","state":"California","latitude":34.0194,"longitude":-118.4108},
{"city":"Chicago","state":"Illinois","latitude":41.8376,"longitude":-87.6818},
{"city":"Houston","state":"Texas","latitude":29.7805,"longitude":-95.3863},
{"city":"Phoenix","state":"Arizona","latitude":33.5722,"longitude":-112.0880},
{"city":"Philadelphia","state":"Pennsylvania","latitude":40.0094,"longitude":-75.1333},
{"city":"San Antonio","state":"Texas","latitude":29.4724,"longitude":-98.5251},
{"city":"San Diego","state":"California","latitude":32.8153,"longitude":-117.1350},
{"city":"Dallas","state":"Texas","latitude":32.7757,"longitude":-96.7967},
{"city":"San Jose","state":"California","latitude":37.2969,"longitude":-121.8193},
{"city":"Austin","state":"Texas","latitude":30.3072,"longitude":-97.7560},
{"city":"Jacksonville","state":"Florida","latitude":30.3370,"longitude":-81.6613},
{"city":"San Francisco","state":"California","latitude":37.7751,"longitude":-122.4193},
{"city":"Columbus","state":"Ohio","latitude":39.9848,"longitude":-82.9850},
{"city":"Indianapolis","state":"Indiana","latitude":39.7767,"longitude":-86.1459},
{"city":"Fort Worth","state":"Texas","latitude":32.7795,"longitude":-97.3463},
{"city":"Charlotte","state":"North Carolina","latitude":35.2087,"longitude":-80.8307},
{"city":"Seattle","state":"Washington","latitude":47.6205,"longitude":-122.3509},
{"city":"Denver","state":"Colorado","latitude":39.7618,"longitude":-104.8806},
{"city":"El Paso","state":"Texas","latitude":31.8484,"longitude":-106.4270}
]

View File

@ -0,0 +1,21 @@
import React, {PureComponent} from 'react';
export default class CityInfo extends PureComponent {
render() {
const {info} = this.props;
const displayName = `${info.city}, ${info.state}`;
return (
<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} />
</div>
);
}
}

View File

@ -4,14 +4,20 @@ 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,
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
C20.1,15.8,20.2,15.8,20.2,15.7z`;
const pinStyle = {
cursor: 'pointer',
fill: '#d00',
stroke: 'none'
};
export default class CityPin extends PureComponent {
render() {
const {size = 20, onClick} = this.props;
return (
<svg height={size} viewBox='0 0 24 24' className="city-pin"
style={{transform: `translate(${-size/2}px,${-size}px)`}}
<svg height={size} viewBox='0 0 24 24'
style={{...pinStyle, transform: `translate(${-size/2}px,${-size}px)`}}
onClick={onClick} >
<path d={ICON}/>
</svg>

View File

@ -0,0 +1,16 @@
import React, {PureComponent} from 'react';
export default class ControlPanel extends PureComponent {
render() {
return (
<div className="options-panel" tabIndex="0">
<h3>Marker, Popup, and NavigationControl</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/uber/react-map-gl/tree/master/examples/controls" target="_new">View Code </a>
</div>
</div>
);
}
}

View File

@ -1,12 +1,22 @@
[
{
"cityName": "San Francisco",
"latitude": 37.7749,
"longitude": -122.4194
},
{
"cityName": "Pittsburgh",
"latitude": 40.4406,
"longitude": -79.9959
}
{"city":"New York","population":"8,175,133","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/Above_Gotham.jpg/240px-Above_Gotham.jpg","state":"New York","latitude":40.6643,"longitude":-73.9385},
{"city":"Los Angeles","population":"3,792,621","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/5/57/LA_Skyline_Mountains2.jpg/240px-LA_Skyline_Mountains2.jpg","state":"California","latitude":34.0194,"longitude":-118.4108},
{"city":"Chicago","population":"2,695,598","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/8/85/2008-06-10_3000x1000_chicago_skyline.jpg/240px-2008-06-10_3000x1000_chicago_skyline.jpg","state":"Illinois","latitude":41.8376,"longitude":-87.6818},
{"city":"Houston","population":"2,100,263","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/6/60/Aerial_views_of_the_Houston%2C_Texas%2C_28005u.jpg/240px-Aerial_views_of_the_Houston%2C_Texas%2C_28005u.jpg","state":"Texas","latitude":29.7805,"longitude":-95.3863},
{"city":"Phoenix","population":"1,445,632","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/Downtown_Phoenix_Aerial_Looking_Northeast.jpg/207px-Downtown_Phoenix_Aerial_Looking_Northeast.jpg","state":"Arizona","latitude":33.5722,"longitude":-112.0880},
{"city":"Philadelphia","population":"1,526,006","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Philly_skyline.jpg/240px-Philly_skyline.jpg","state":"Pennsylvania","latitude":40.0094,"longitude":-75.1333},
{"city":"San Antonio","population":"1,327,407","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Downtown_San_Antonio_View.JPG/240px-Downtown_San_Antonio_View.JPG","state":"Texas","latitude":29.4724,"longitude":-98.5251},
{"city":"San Diego","population":"1,307,402","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/5/53/US_Navy_110604-N-NS602-574_Navy_and_Marine_Corps_personnel%2C_along_with_community_leaders_from_the_greater_San_Diego_area_come_together_to_commemora.jpg/240px-US_Navy_110604-N-NS602-574_Navy_and_Marine_Corps_personnel%2C_along_with_community_leaders_from_the_greater_San_Diego_area_come_together_to_commemora.jpg","state":"California","latitude":32.8153,"longitude":-117.1350},
{"city":"Dallas","population":"1,197,816","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Dallas_skyline_daytime.jpg/240px-Dallas_skyline_daytime.jpg","state":"Texas","latitude":32.7757,"longitude":-96.7967},
{"city":"San Jose","population":"945,942","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/1/1e/Downtown_San_Jose_skyline.PNG/240px-Downtown_San_Jose_skyline.PNG","state":"California","latitude":37.2969,"longitude":-121.8193},
{"city":"Austin","population":"790,390","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Austin2012-12-01.JPG/240px-Austin2012-12-01.JPG","state":"Texas","latitude":30.3072,"longitude":-97.7560},
{"city":"Jacksonville","population":"821,784","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/Skyline_of_Jacksonville_FL%2C_South_view_20160706_1.jpg/240px-Skyline_of_Jacksonville_FL%2C_South_view_20160706_1.jpg","state":"Florida","latitude":30.3370,"longitude":-81.6613},
{"city":"San Francisco","population":"805,235","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/San_Francisco_skyline_from_Coit_Tower.jpg/240px-San_Francisco_skyline_from_Coit_Tower.jpg","state":"California","latitude":37.7751,"longitude":-122.4193},
{"city":"Columbus","population":"787,033","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Columbus-ohio-skyline-panorama.jpg/240px-Columbus-ohio-skyline-panorama.jpg","state":"Ohio","latitude":39.9848,"longitude":-82.9850},
{"city":"Indianapolis","population":"820,445","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/1/16/Downtown_indy_from_parking_garage_zoom.JPG/213px-Downtown_indy_from_parking_garage_zoom.JPG","state":"Indiana","latitude":39.7767,"longitude":-86.1459},
{"city":"Fort Worth","population":"741,206","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/d/db/FortWorthTexasSkylineW.jpg/240px-FortWorthTexasSkylineW.jpg","state":"Texas","latitude":32.7795,"longitude":-97.3463},
{"city":"Charlotte","population":"731,424","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/7/7d/Charlotte_skyline45647.jpg/222px-Charlotte_skyline45647.jpg","state":"North Carolina","latitude":35.2087,"longitude":-80.8307},
{"city":"Seattle","population":"608,660","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/3/36/SeattleI5Skyline.jpg/240px-SeattleI5Skyline.jpg","state":"Washington","latitude":47.6205,"longitude":-122.3509},
{"city":"Denver","population":"600,158","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/0/0b/DenverCP.JPG/240px-DenverCP.JPG","state":"Colorado","latitude":39.7618,"longitude":-104.8806},
{"city":"El Paso","population":"649,121","image":"http://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Downtown_El_Paso_at_sunset.jpeg/240px-Downtown_El_Paso_at_sunset.jpeg","state":"Texas","latitude":31.8484,"longitude":-106.4270}
]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
<div align="center">
<img src="https://avatars3.githubusercontent.com/u/2105791?v=3&s=200" />
</div>
## Example: GeoJSON
This example showcases how to dynamically add and update custom data sources.

View File

@ -0,0 +1,20 @@
body {
margin: 0;
background: #000;
}
.options-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;
margin: 20px;
font-size: 13px;
line-height: 2;
color: #6b6b76;
text-transform: uppercase;
outline: none;
}

View File

@ -0,0 +1,11 @@
<!doctype html>
<html>
<head>
<meta charset='UTF-8' />
<title>react-map-gl GeoJSON Example</title>
<link rel="stylesheet" type="text/css" href="app.css">
</head>
<body>
<script src='bundle.js'></script>
</body>
</html>

View File

@ -0,0 +1,28 @@
{
"scripts": {
"start": "webpack-dev-server --progress --hot --open",
"start-local": "webpack-dev-server --env.local --progress --hot --open"
},
"dependencies": {
"immutable": "^3.8.1",
"react": "^15.4.1",
"react-dom": "^15.4.1",
"react-map-gl": ">=3.0.0-alpha.14"
},
"devDependencies": {
"babel-core": "^6.21.0",
"babel-loader": "^6.2.10",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-2": "^6.18.0",
"webpack": "^2.4.0",
"webpack-dev-server": "^2.4.0"
},
"babel": {
"presets": [
"es2015",
"stage-2",
"react"
]
}
}

View File

@ -0,0 +1,92 @@
/* global window */
import React, {Component} from 'react';
import {render} from 'react-dom';
import MapGL from 'react-map-gl';
import ControlPanel from './control-panel';
import {defaultMapStyle, pointLayer} from './map-style.js';
import {pointOnCircle} from './utils';
import {fromJS} from 'immutable';
const token = process.env.MapboxAccessToken; // eslint-disable-line
if (!token) {
throw new Error('Please specify a valid mapbox token');
}
let animation = null;
export default class App extends Component {
state = {
mapStyle: defaultMapStyle,
viewport: {
latitude: 0,
longitude: -100,
zoom: 3,
bearing: 0,
pitch: 0,
width: 500,
height: 500
}
};
componentDidMount() {
window.addEventListener('resize', this._resize);
this._resize();
animation = window.requestAnimationFrame(this._animatePoint);
}
componentWillUnmount() {
window.removeEventListener('resize', this._resize);
window.cancelAnimationFrame(animation);
}
_resize = () => {
this.setState({
viewport: {
...this.state.viewport,
width: this.props.width || window.innerWidth,
height: this.props.height || window.innerHeight
}
});
};
_animatePoint = () => {
this._updatePointData(pointOnCircle({center: [-100, 0], angle: Date.now() / 1000, radius: 20}));
animation = window.requestAnimationFrame(this._animatePoint);
}
_updatePointData = pointData => {
let {mapStyle} = this.state;
if (!mapStyle.hasIn(['source', 'point'])) {
mapStyle = mapStyle
// Add geojson source to map
.setIn(['sources', 'point'], fromJS({type: 'geojson'}))
// Add point layer to map
.set('layers', mapStyle.get('layers').push(pointLayer));
}
// Update data source
mapStyle = mapStyle.setIn(['sources', 'point', 'data'], pointData);
this.setState({mapStyle});
}
_onViewportChange = viewport => this.setState({viewport});
render() {
const {viewport, mapStyle} = this.state;
return (
<MapGL
{...viewport}
mapStyle={mapStyle}
onViewportChange={this._onViewportChange}
mapboxApiAccessToken={token} >
<ControlPanel />
</MapGL>
);
}
}

View File

@ -0,0 +1,15 @@
import React, {PureComponent} from 'react';
export default class ControlPanel extends PureComponent {
render() {
return (
<div className="options-panel" tabIndex="0">
<h3>Animated GeoJSON</h3>
<p>Render animation by updating GeoJSON data source.</p>
<div className="source-link">
<a href="https://github.com/uber/react-map-gl/tree/master/examples/geojson-animation" target="_new">View Code </a>
</div>
</div>
);
}
}

View File

@ -0,0 +1,14 @@
import {fromJS} from 'immutable';
import MAP_STYLE from '../../map-style-basic-v8.json';
export const pointLayer = fromJS({
id: 'point',
source: 'point',
type: 'circle',
paint: {
'circle-radius': 10,
'circle-color': '#007cbf'
}
});
export const defaultMapStyle = fromJS(MAP_STYLE);

View File

@ -0,0 +1,6 @@
/* global document */
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
ReactDOM.render(<App/>, document.body.appendChild(document.createElement('div')));

View File

@ -0,0 +1,9 @@
export function pointOnCircle({center, angle, radius}) {
return {
type: 'Point',
coordinates: [
center[0] + Math.cos(angle) * radius,
center[1] + Math.sin(angle) * radius
]
};
}

View File

@ -0,0 +1,40 @@
// NOTE: To use this example standalone (e.g. outside of deck.gl repo)
// delete the local development overrides at the bottom of this file
// avoid destructuring for older Node version support
const resolve = require('path').resolve;
const webpack = require('webpack');
const config = {
entry: {
app: resolve('./src/root.js')
},
devtool: 'source-map',
module: {
rules: [{
// Compile ES2015 using bable
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('.')],
exclude: [/node_modules/]
}]
},
resolve: {
alias: {
// From mapbox-gl-js README. Required for non-browserify bundlers (e.g. webpack):
'mapbox-gl$': resolve('./node_modules/mapbox-gl/dist/mapbox-gl.js')
}
},
// Optional: Enables reading mapbox token from environment variable
plugins: [
new webpack.EnvironmentPlugin(['MapboxAccessToken'])
]
};
// Enables bundling against src in this repo rather than the installed version
module.exports = env => env && env.local ?
require('../webpack.config.local')(config)(env) : config;

42
examples/geojson/app.css Normal file
View File

@ -0,0 +1,42 @@
body {
margin: 0;
font-family: Helvetica, Arial, sans-serif;
}
.options-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;
margin: 20px;
font-size: 13px;
line-height: 2;
color: #6b6b76;
outline: none;
text-transform: uppercase;
}
label {
display: inline-block;
width: 100px;
}
input {
margin-left: 20px;
width: 160px;
}
.tooltip {
position: absolute;
margin: 8px;
padding: 4px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
max-width: 300px;
font-size: 10px;
z-index: 9;
pointer-events: none;
}

View File

@ -3,7 +3,7 @@
<head>
<meta charset='UTF-8' />
<title>react-map-gl GeoJSON Example</title>
<style>body {margin: 0}</style>
<link rel="stylesheet" type="text/css" href="app.css">
</head>
<body>
<script src='bundle.js'></script>

View File

@ -4,6 +4,8 @@
"start-local": "webpack-dev-server --env.local --progress --hot --open"
},
"dependencies": {
"d3-request": "^1.0.5",
"d3-scale": "^1.0.6",
"immutable": "^3.8.1",
"react": "^15.4.1",
"react-dom": "^15.4.1",

View File

@ -1,11 +1,13 @@
/* global window */
/* global window, fetch */
import React, {Component} from 'react';
import {render} from 'react-dom';
import MapGL from 'react-map-gl';
import ControlPanel from './control-panel';
import {defaultMapStyle, pointLayer} from './map-style.js';
import {pointOnCircle} from './utils';
import {defaultMapStyle, dataLayer} from './map-style.js';
import {updatePercentiles} from './utils';
import {fromJS} from 'immutable';
import {json as requestJson} from 'd3-request';
const token = process.env.MapboxAccessToken; // eslint-disable-line
@ -13,85 +15,116 @@ if (!token) {
throw new Error('Please specify a valid mapbox token');
}
let animation = null;
export default class App extends Component {
state = {
mapStyle: defaultMapStyle,
year: 2015,
data: null,
hoveredFeature: null,
viewport: {
latitude: 0,
latitude: 40,
longitude: -100,
zoom: 3,
bearing: 0,
pitch: 0,
width: window.innerWidth,
height: window.innerHeight
width: 500,
height: 500
}
};
componentDidMount() {
window.addEventListener('resize', this._resize);
this._resize();
animation = window.requestAnimationFrame(this._animatePoint);
requestJson('data/us-income.geojson', (error, response) => {
if (!error) {
this._loadData(response);
}
});
}
componentWillUnmount() {
window.cancelAnimationFrame(animation);
}
_animatePoint = () => {
this._updatePointData(pointOnCircle({center: [-100, 0], angle: Date.now() / 1000, radius: 20}));
animation = window.requestAnimationFrame(this._animatePoint);
}
_updatePointData = pointData => {
let {mapStyle} = this.state;
if (!mapStyle.hasIn(['source', 'point'])) {
mapStyle = mapStyle
// Add geojson source to map
.setIn(['sources', 'point'], fromJS({type: 'geojson'}))
// Add point layer to map
.set('layers', mapStyle.get('layers').push(pointLayer));
}
// Update data source
mapStyle = mapStyle.setIn(['sources', 'point', 'data'], pointData);
this.setState({mapStyle});
window.removeEventListener('resize', this._resize);
}
_resize = () => {
const {widthOffset, heightOffset} = this.props;
this.setState({
viewport: {
...this.state.viewport,
width: window.innerWidth - widthOffset,
height: window.innerHeight - heightOffset
width: this.props.width || window.innerWidth,
height: this.props.height || window.innerHeight
}
});
};
_loadData = data => {
updatePercentiles(data, f => f.properties.income[this.state.year]);
const mapStyle = defaultMapStyle
// Add geojson source to map
.setIn(['sources', 'incomeByState'], fromJS({type: 'geojson', data}))
// Add point layer to map
.set('layers', defaultMapStyle.get('layers').push(dataLayer));
this.setState({data, mapStyle});
};
_updateSettings = (name, value) => {
if (name === 'year') {
this.setState({year: value});
const {data, mapStyle} = this.state;
if (data) {
updatePercentiles(data, f => f.properties.income[value]);
const newMapStyle = mapStyle.setIn(['sources', 'incomeByState', 'data'], fromJS(data));
this.setState({mapStyle: newMapStyle});
}
}
};
_onViewportChange = viewport => this.setState({viewport});
_onHover = event => {
const {features, srcEvent: {offsetX, offsetY}} = event;
const hoveredFeature = features && features.find(f => f.layer.id === 'data');
this.setState({hoveredFeature, x: offsetX, y: offsetY});
};
_renderTooltip() {
const {hoveredFeature, year, 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>
);
}
render() {
const {viewport, mapStyle} = this.state;
return (
<MapGL
{...viewport}
mapStyle={mapStyle}
onViewportChange={this._onViewportChange}
mapboxApiAccessToken={token} >
</MapGL>
<div>
<MapGL
{...viewport}
mapStyle={mapStyle}
onViewportChange={this._onViewportChange}
mapboxApiAccessToken={token}
onHover={this._onHover} >
{this._renderTooltip()}
</MapGL>
<ControlPanel settings={this.state} onChange={this._updateSettings} />
</div>
);
}
}
// Used to render properly in docs. Ignore these props or remove if you're
// copying this as a starting point.
App.defaultProps = {
widthOffset: 0,
heightOffset: 0
};

View File

@ -0,0 +1,27 @@
import React, {PureComponent} from 'react';
export default class ControlPanel extends PureComponent {
render() {
const {settings} = this.props;
return (
<div className="options-panel" tabIndex="0">
<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/uber/react-map-gl/tree/master/examples/geojson" target="_new">View Code </a>
</div>
<hr />
<div key={name} 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>
</div>
);
}
}

View File

@ -1,13 +1,28 @@
import {fromJS} from 'immutable';
import MAP_STYLE from '../../map-style-basic-v8.json';
export const pointLayer = fromJS({
id: 'point',
source: 'point',
type: 'circle',
// For more information on data-driven styles, see https://www.mapbox.com/help/gl-dds-ref/
export const dataLayer = fromJS({
id: 'data',
source: 'incomeByState',
type: 'fill',
interactive: true,
paint: {
'circle-radius': 10,
'circle-color': '#007cbf'
'fill-color': {
property: 'percentile',
stops: [
[0, '#3288bd'],
[1, '#66c2a5'],
[2, '#abdda4'],
[3, '#e6f598'],
[4, '#ffffbf'],
[5, '#fee08b'],
[6, '#fdae61'],
[7, '#f46d43'],
[8, '#d53e4f']
]
},
'fill-opacity': 0.8
}
});

View File

@ -1,9 +1,12 @@
export function pointOnCircle({center, angle, radius}) {
return {
type: 'Point',
coordinates: [
center[0] + Math.cos(angle) * radius,
center[1] + Math.sin(angle) * radius
]
};
import {range} from 'd3-array';
import {scaleQuantile} from 'd3-scale';
export function updatePercentiles(featureCollection, accessor) {
const {features} = featureCollection;
const scale = scaleQuantile().domain(features.map(accessor)).range(range(9));
features.forEach(f => {
const value = accessor(f);
f.properties.value = value;
f.properties.percentile = scale(value);
});
}

View File

@ -10,6 +10,13 @@ const config = {
app: resolve('./src/root.js')
},
devServer: {
contentBase: [
__dirname,
resolve(__dirname, '../')
]
},
devtool: 'source-map',
module: {

View File

@ -3,8 +3,11 @@ body {
font-family: Helvetica, Arial, sans-serif;
}
.control-panel {
.options-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;
@ -13,6 +16,7 @@ body {
line-height: 2;
color: #6b6b76;
text-transform: uppercase;
outline: none;
}
label {
@ -24,32 +28,3 @@ input {
margin-left: 20px;
max-width: 60px;
}
.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;
}

View File

@ -12,6 +12,8 @@ if (!token) {
throw new Error('Please specify a valid mapbox token');
}
import MARKER_STYLE from './marker-style';
export default class App extends Component {
state = {
@ -21,8 +23,8 @@ export default class App extends Component {
zoom: 11,
bearing: 0,
pitch: 50,
width: window.innerWidth,
height: window.innerHeight
width: 500,
height: 500
},
settings: {
dragPan: true,
@ -42,13 +44,16 @@ export default class App extends Component {
this._resize();
}
componentWillUnmount() {
window.removeEventListener('resize', this._resize);
}
_resize = () => {
const {widthOffset, heightOffset} = this.props;
this.setState({
viewport: {
...this.state.viewport,
width: window.innerWidth - widthOffset,
height: window.innerHeight - heightOffset
width: this.props.width || window.innerWidth,
height: this.props.height || window.innerHeight
}
});
};
@ -79,6 +84,7 @@ export default class App extends Component {
mapStyle="mapbox://styles/mapbox/dark-v9"
onViewportChange={this._onViewportChange}
mapboxApiAccessToken={token} >
<style>{MARKER_STYLE}</style>
{ bartStations.map(this._renderMarker) }
<ControlPanel settings={settings} onChange={this._onSettingChange} />
</MapGL>
@ -86,10 +92,3 @@ export default class App extends Component {
}
}
// Used to render properly in docs. Ignore these props or remove if you're
// copying this as a starting point.
App.defaultProps = {
widthOffset: 0,
heightOffset: 0
};

View File

@ -10,7 +10,7 @@ export default class ControlPanel extends PureComponent {
_renderCheckbox(name, value) {
return (
<div key={name}>
<div key={name} className="input">
<label>{this._formatSettingName(name)}</label>
<input type="checkbox" checked={value}
onChange={evt => this.props.onChange(name, evt.target.checked)} />
@ -20,7 +20,7 @@ export default class ControlPanel extends PureComponent {
_renderNumericInput(name, value) {
return (
<div key={name}>
<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))} />
@ -43,7 +43,14 @@ export default class ControlPanel extends PureComponent {
const {settings} = this.props;
return (
<div className="control-panel">
<div className="options-panel" tabIndex="0">
<h3>Limit Map Interaction</h3>
<p>Turn interactive features off/on.</p>
<div className="source-link">
<a href="https://github.com/uber/react-map-gl/tree/master/examples/interaction" target="_new">View Code </a>
</div>
<hr />
{ Object.keys(settings).map(name => this._renderSetting(name, settings[name])) }
</div>
);

View File

@ -0,0 +1,30 @@
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;
}
`;

View File

@ -1,20 +0,0 @@
import React, {PureComponent} from 'react';
import {Marker} from 'react-map-gl';
import bartStations from './bart-station.json';
export default class Markers extends PureComponent {
_renderMarker(station, i) {
const {name, coordinates} = station;
return (
<Marker key={i} longitude={coordinates[0]} latitude={coordinates[1]} >
{name}
</Marker>
);
}
render() {
return bartStations.map(this._renderMarker);
}
}

View File

@ -3,8 +3,11 @@ body {
font-family: Helvetica, Arial, sans-serif;
}
.control-panel {
.options-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;
@ -13,6 +16,7 @@ body {
line-height: 2;
color: #6b6b76;
text-transform: uppercase;
outline: none;
}
label {

View File

@ -2,7 +2,7 @@
import React, {Component} from 'react';
import {render} from 'react-dom';
import MapGL from 'react-map-gl';
import StyleControls from './style-controls';
import ControlPanel from './control-panel';
const token = process.env.MapboxAccessToken; // eslint-disable-line
@ -20,8 +20,8 @@ export default class App extends Component {
zoom: 15.5,
bearing: 0,
pitch: 0,
width: window.innerWidth,
height: window.innerHeight
width: 500,
height: 500
}
}
@ -30,13 +30,16 @@ export default class App extends Component {
this._resize();
}
componentWillUnmount() {
window.removeEventListener('resize', this._resize);
}
_resize = () => {
const {widthOffset, heightOffset} = this.props;
this.setState({
viewport: {
...this.state.viewport,
width: window.innerWidth - widthOffset,
height: window.innerHeight - heightOffset
width: this.props.width || window.innerWidth,
height: this.props.height || window.innerHeight
}
});
};
@ -55,16 +58,9 @@ export default class App extends Component {
mapStyle={mapStyle}
onViewportChange={this._onViewportChange}
mapboxApiAccessToken={token} >
<StyleControls onChange={this._onStyleChange} />
<ControlPanel onChange={this._onStyleChange} />
</MapGL>
);
}
}
// Used to render properly in docs. Ignore these props or remove if you're
// copying this as a starting point.
App.defaultProps = {
widthOffset: 0,
heightOffset: 0
};

View File

@ -91,7 +91,7 @@ export default class StyleControls extends PureComponent {
const {visibility, color} = this.state;
return (
<div key={name}>
<div key={name} className="input">
<label>{name}</label>
<input type="checkbox" checked={visibility[name]}
onChange={this._onVisibilityChange.bind(this, name)} />
@ -103,7 +103,13 @@ export default class StyleControls extends PureComponent {
render() {
return (
<div className="control-panel">
<div className="options-panel" tabIndex="0">
<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/uber/react-map-gl/tree/master/examples/layers" target="_new">View Code </a>
</div>
<hr />
{ categories.map(name => this._renderLayerControl(name)) }
</div>
);

View File

@ -8,8 +8,10 @@ import ClickExample from '../views/click';
// Standalone
import Controls from '../../controls/src/app';
import GeoJson from '../../geojson/src/app';
import GeoJsonAnimation from '../../geojson-animation/src/app';
import Interaction from '../../interaction/src/app';
import Layers from '../../layers/src/app';
import ViewportAnimation from '../../viewport-animation/src/app';
export const BASIC_EXAMPLES = 'basicExamples';
export const STANDALONE_EXAMPLES = 'standalonExamples';
@ -17,6 +19,39 @@ export const STANDALONE_EXAMPLES = 'standalonExamples';
export const DEFAULT_EXAMPLE = 'markerExample';
// TOC
export const standaloneExamples = [
{
path: 'layersExample',
name: 'Dynamic Styling',
component: Layers
},
{
path: 'controlsExample',
name: 'Markers & Popups',
component: Controls
},
{
path: 'geojsonExample',
name: 'GeoJSON',
component: GeoJson
},
{
path: 'geojsonAnimationExample',
name: 'GeoJSON Animation',
component: GeoJsonAnimation
},
{
path: 'interactionExample',
name: 'Limit Map Interaction',
component: Interaction
},
{
path: 'viewportAnimationExample',
name: 'Camera Transition',
component: ViewportAnimation
}
];
export default [
{
path: BASIC_EXAMPLES,
@ -56,28 +91,7 @@ export default [
},
{
path: STANDALONE_EXAMPLES,
name: 'Advanced Examples',
children: [
{
path: 'controlsExample',
name: 'Controls',
component: Controls
},
{
path: 'geoJsonExample',
name: 'GeoJSON',
component: GeoJson
},
{
path: 'interactionExample',
name: 'Interaction',
component: Interaction
},
{
path: 'layersExample',
name: 'Layers',
component: Layers
}
]
name: 'Standalone Examples',
children: standaloneExamples
}
];

View File

@ -33,7 +33,7 @@ export default class App extends Component {
};
}
componentDidMount() {
componentWillMount() {
window.onresize = () => this.setState({
viewport: {
width: window.innerWidth - 240,
@ -52,7 +52,7 @@ export default class App extends Component {
const ExampleComponent = component;
return (
<div className="flexbox-item flexbox-item--fill">
<ExampleComponent widthOffset={240} {...viewport} />
<ExampleComponent {...viewport} />
</div>
);
}

View File

@ -9,6 +9,7 @@
"d3-array": "^1.0.1",
"d3-color": "^1.0.1",
"d3-random": "^1.0.2",
"d3-request": "^1.0.5",
"d3-scale": "^1.0.3",
"immutable": "^3.8.1",
"react": "^15.4.1",

View File

@ -105,6 +105,9 @@
box-shadow: 0 0 4px rgba(0, 0, 0, 0.15);
margin: 24px;
padding: 12px 24px;
position: absolute;
top: 0;
right: 0;
hr {
margin: 12px -24px;
@ -128,8 +131,9 @@
white-space: nowrap;
}
label {
text-transform: uppercase;
display: inline-block;
width: 40%;
width: 50%;
margin-right: 10%;
color: $black-40;
margin-bottom: 4px;
@ -138,10 +142,13 @@
font-size: 0.9em;
display: inline-block;
padding: 0 4px;
width: 50%;
width: 40%;
height: 20px;
line-height: 1.833;
}
input[type="checkbox"], input[type="radio"], input[type="color"] {
width: 20%;
}
input {
border: solid 1px #ccc;
@ -268,39 +275,3 @@ code {
padding: 40px 12px 96px;
}
}
.icon-demo {
position: relative;
&.clickable {
cursor: pointer;
}
.tooltip {
padding: 4px 12px;
max-width: 240px;
max-height: 320px;
box-sizing: content-box;
overflow-y: hidden;
h5 {
font-size: 1em;
}
p {
display: none;
text-indent: 4px;
}
}
.tooltip.interactive {
border: solid 4px transparent;
margin: -4px;
background: $white;
color: $black;
pointer-events: all;
width: 240px;
overflow-y: auto;
p {
display: block;
}
}
}

View File

@ -12,40 +12,6 @@ $topbar-height: 64px;
@import 'gallery';
@import '../node_modules/mapbox-gl/dist/mapbox-gl.css';
.control-panel {
position: absolute;
background: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
padding: 12px 24px;
margin: 20px;
font-size: 13px;
line-height: 2;
color: #6b6b76;
text-transform: uppercase;
}
.control-panel label {
display: inline-block;
width: 100px;
}
.control-panel input {
margin-left: 20px;
}
.city-pin {
cursor: pointer;
fill: #d00;
stroke: none;
}
.nav {
position: absolute;
top: 0;
right: 0;
padding: 10px;
}
body {
font-family: ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important;
font-size: 12px;
@ -279,12 +245,6 @@ hr.short {
bottom: 24px;
color: $white-40;
}
.mapboxgl-ctrl-attrib {
position: absolute;
bottom: 2px;
right: 4px;
z-index: 9;
}
.overlays {
cursor: crosshair;
}

View File

@ -25,7 +25,12 @@ const config = {
devServer: {
stats: {
warnings: false
}
},
contentBase: [
__dirname,
resolve(__dirname, '../')
]
},
devtool: 'source-maps',

View File

@ -3,10 +3,11 @@ body {
font-family: Helvetica, Arial, sans-serif;
}
.control-panel {
.options-panel {
position: absolute;
left: 0;
top: 0;
right: 0;
max-width: 320px;
background: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
padding: 12px 24px;
@ -15,6 +16,7 @@ body {
line-height: 2;
color: #6b6b76;
text-transform: uppercase;
outline: none;
}
.btn {

View File

@ -2,9 +2,10 @@
import React, {Component} from 'react';
import {render} from 'react-dom';
import MapGL from 'react-map-gl';
import {PerspectiveMercatorViewport} from 'viewport-mercator-project';
import TWEEN from 'tween.js';
import CITIES from './cities.json';
import ControlPanel from './control-panel';
const token = process.env.MapboxAccessToken; // eslint-disable-line
@ -19,30 +20,17 @@ function animate() {
}
animate();
export default class Root extends Component {
export default class App extends Component {
state = {
viewport: {
latitude: 37.785164,
longitude: -100,
zoom: 4,
latitude: 37.7751,
longitude: -122.4193,
zoom: 11,
bearing: 0,
pitch: 0,
width: this.props.width,
height: this.props.height
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.width !== this.state.viewport.width ||
nextProps.height !== this.state.viewport.height) {
this.setState({
viewport: {
...this.state.viewport,
width: nextProps.width,
height: nextProps.height
}
});
width: 500,
height: 500
}
}
@ -51,12 +39,16 @@ export default class Root extends Component {
this._resize();
}
componentWillUnmount() {
window.removeEventListener('resize', this._resize);
}
_resize = () => {
this.setState({
viewport: {
...this.state.viewport,
width: window.innerWidth,
height: window.innerHeight
width: this.props.width || window.innerWidth,
height: this.props.height || window.innerHeight
}
});
};
@ -79,15 +71,6 @@ export default class Root extends Component {
_onViewportChange = viewport => this.setState({viewport});
_renderButton = (city, index) => {
return (
<div key={`btn-${index}`} className="btn"
onClick={() => this._easeTo(city)} >
{city.city}, {city.state}
</div>
);
};
render() {
const {viewport, settings} = this.state;
@ -101,17 +84,9 @@ export default class Root extends Component {
onViewportChange={this._onViewportChange}
dragToRotate={false}
mapboxApiAccessToken={token} />
<div className="control-panel">
{ CITIES.map(this._renderButton) }
</div>
<ControlPanel onViewportChange={this._easeTo} />
</div>
);
}
}
App.defaultProps = {
width: 500,
height: 500
};

View File

@ -1,22 +0,0 @@
[
{"city":"New York","state":"New York","latitude":40.6643,"longitude":-73.9385},
{"city":"Los Angeles","state":"California","latitude":34.0194,"longitude":-118.4108},
{"city":"Chicago","state":"Illinois","latitude":41.8376,"longitude":-87.6818},
{"city":"Houston","state":"Texas","latitude":29.7805,"longitude":-95.3863},
{"city":"Phoenix","state":"Arizona","latitude":33.5722,"longitude":-112.0880},
{"city":"Philadelphia","state":"Pennsylvania","latitude":40.0094,"longitude":-75.1333},
{"city":"San Antonio","state":"Texas","latitude":29.4724,"longitude":-98.5251},
{"city":"San Diego","state":"California","latitude":32.8153,"longitude":-117.1350},
{"city":"Dallas","state":"Texas","latitude":32.7757,"longitude":-96.7967},
{"city":"San Jose","state":"California","latitude":37.2969,"longitude":-121.8193},
{"city":"Austin","state":"Texas","latitude":30.3072,"longitude":-97.7560},
{"city":"Jacksonville","state":"Florida","latitude":30.3370,"longitude":-81.6613},
{"city":"San Francisco","state":"California","latitude":37.7751,"longitude":-122.4193},
{"city":"Columbus","state":"Ohio","latitude":39.9848,"longitude":-82.9850},
{"city":"Indianapolis","state":"Indiana","latitude":39.7767,"longitude":-86.1459},
{"city":"Fort Worth","state":"Texas","latitude":32.7795,"longitude":-97.3463},
{"city":"Charlotte","state":"North Carolina","latitude":35.2087,"longitude":-80.8307},
{"city":"Seattle","state":"Washington","latitude":47.6205,"longitude":-122.3509},
{"city":"Denver","state":"Colorado","latitude":39.7618,"longitude":-104.8806},
{"city":"El Paso","state":"Texas","latitude":31.8484,"longitude":-106.4270}
]

View File

@ -0,0 +1,33 @@
import React, {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'}
onChange={() => this.props.onViewportChange(city)} />
<label htmlFor={`city-${index}`}>{city.city}</label>
</div>
);
};
render() {
return (
<div className="options-panel" tabIndex="0">
<h3>Camera Transition</h3>
<p>Smooth animate of the viewport.</p>
<div className="source-link">
<a href="https://github.com/uber/react-map-gl/tree/master/examples/viewport-animation" target="_new">View Code </a>
</div>
<hr />
{ CITIES.filter(city => city.state === 'California').map(this._renderButton) }
</div>
);
}
}

View File

@ -18,7 +18,8 @@
"babel-polyfill": "^6.1.19",
"babel-register": "^6.22.0",
"d3-color": "^1.0.1",
"d3-request": "^1.0.2",
"d3-request": "^1.0.5",
"d3-scale": "^1.0.6",
"highlight.js": "^9.7.0",
"immutable": "^3.7.5",
"marked": "^0.3.6",

View File

@ -1,6 +1,4 @@
import {request, json, text} from 'd3-request';
import {StreamParser} from '../utils/worker-utils';
import {text} from 'd3-request';
const loadContentSuccess = (name, content) => {
const payload = {};
@ -26,92 +24,6 @@ export const loadContent = filename => {
};
};
const loadDataStart = owner => ({type: 'LOAD_DATA_START', owner});
const loadDataSuccess = (context, index, data, meta) => {
if (context.isArray) {
context.resultData = context.resultData.slice(0);
context.resultData[index] = data;
} else {
context.resultData = data;
}
context.resultMeta = {...context.resultMeta, ...meta};
return {
type: 'LOAD_DATA_SUCCESS',
payload: {
owner: context.owner,
data: context.resultData,
meta: context.resultMeta
}
};
};
/*
* loads data for a demo
* @param {String} owner - identifier of the demo
* @param {Object | Array} source - an object or array of objects specifying
* the data that needs to be loaded
* {String} source.url - (required) url of the data file
* {String} source.worker - (optional) url of a web worker
* if specified, then the loaded file content will be passed to the worker
* if not specified, then the loaded file will be parsed as JSON or text
* based on its extension
*/
export const loadData = (owner, source) => {
return (dispatch, getState) => {
if (getState().vis.owner === owner) {
// already loading / loaded
return;
}
const isArray = Array.isArray(source);
if (!isArray) {
source = [source];
}
const context = {
owner,
resultData: [],
resultMeta: [],
isArray
};
dispatch(loadDataStart(owner));
source.forEach(({url, worker}, index) => {
if (worker) {
const req = request(url);
// use a web worker to parse data
const dataParser = new StreamParser(worker, (data, meta) => {
dispatch(loadDataSuccess(context, index, data, meta));
});
req.on('progress', dataParser.onProgress)
.on('load', dataParser.onLoad)
.get();
} else if (/\.(json|geojson)$/.test(url)) {
// load as json
json(url, (error, response) => {
if (!error) {
dispatch(loadDataSuccess(context, index, response, {}));
}
});
} else {
// load as plain text
text(url, (error, response) => {
if (!error) {
dispatch(loadDataSuccess(context, index, response, {}));
}
});
}
});
};
};
export const updateMap = viewport => ({type: 'UPDATE_MAP', viewport});
export const updateMeta = meta => ({type: 'UPDATE_META', meta});
export const updateParam = (name, value) => ({type: 'UPDATE_PARAM', payload: {name, value}});

View File

@ -10,20 +10,14 @@ export default class Examples extends Component {
constructor(props) {
super(props);
this.state = {
viewport: {
width: window.innerWidth - WIDTH_OFFSET,
height: window.innerHeight - HEIGHT_OFFSET
}
viewport: this._getSize()
};
}
componentDidMount() {
window.onresize = () => {
this.setState({
viewport: {
width: window.innerWidth - WIDTH_OFFSET,
height: window.innerHeight - HEIGHT_OFFSET
}
viewport: this._getSize()
});
};
}
@ -32,6 +26,15 @@ export default class Examples extends Component {
window.onresize = null;
}
_getSize() {
const {innerWidth, innerHeight} = window;
return {
width: innerWidth >= 576 ? innerWidth - WIDTH_OFFSET : innerWidth,
height: innerHeight - HEIGHT_OFFSET
};
}
render() {
const {viewport} = this.state;
const {route: {childComponent}} = this.props;
@ -39,8 +42,6 @@ export default class Examples extends Component {
return (
<div className="flexbox-item flexbox-item--fill">
<ExampleComponent
widthOffset={WIDTH_OFFSET}
heightOffset={HEIGHT_OFFSET}
{...viewport} />
</div>
);

View File

@ -6,13 +6,12 @@ export default class Home extends Component {
return (
<div className="home-wrapper">
<section ref="banner" id="banner">
<div className="container soft-left">
<section id="banner" style={{backgroundImage: 'url(images/hero.jpg)'}}>
<div className="container soft">
<h1>react-map-gl</h1>
<p>A React port of Mapbox GL JS</p>
<a href="#/documentation/getting-started" className="btn">Get started</a>
</div>
<div ref="fps" className="fps" />
</section>
<section id="features">

View File

@ -1,36 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import GenericInput from './input';
// import * as Demos from './demos';
import {updateParam} from '../actions/app-actions';
class InfoPanel extends Component {
render() {
const {demo, hasFocus, onInteract, params, owner, meta} = this.props;
// const DemoComponent = Demos[demo];
// const metaLoaded = owner === demo ? meta : {};
return (
<div className={`options-panel top-right ${hasFocus ? 'focus' : ''}`} onClick={onInteract}>
{/* {DemoComponent.renderInfo(metaLoaded)} */}
{Object.keys(params).length > 0 && <hr />}
{Object.keys(params).map((name, i) => (
<GenericInput key={i}
name={name}
{...params[name]}
onChange={this.props.updateParam} />
))}
{this.props.children}
</div>
);
}
}
export default connect(state => state.vis, {updateParam})(InfoPanel);

View File

@ -192,8 +192,7 @@ export default class MarkdownPage extends PureComponent {
}
MarkdownPage.propTypes = {
content: PropTypes.string,
renderDemo: PropTypes.func.isRequired
content: PropTypes.string
};
MarkdownPage.defaultProps = {

View File

@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import autobind from 'autobind-decorator';
import InfoPanel from './info-panel';
import MarkdownPage from './markdown-page';
import {loadContent, updateMap} from '../actions/app-actions';
@ -38,23 +37,6 @@ class Page extends Component {
return content;
}
@autobind _renderDemo(name, sourceLink) {
const {mapHasFocus} = this.state;
return (
<div className="demo">
<InfoPanel
demo={name}
hasFocus={!mapHasFocus}
onInteract={this._onMapBlur} >
{sourceLink && (<div className="source-link">
<a href={sourceLink} target="_new">View Code </a>
</div>)}
</InfoPanel>
</div>
);
}
// replaces the current query string in react-router
@autobind _updateQueryString(queryString) {
const {location: {pathname, search}} = this.props;
@ -77,8 +59,7 @@ class Page extends Component {
} else if (typeof content === 'string') {
child = (<MarkdownPage content={contents[content]}
query={query}
updateQueryString={this._updateQueryString}
renderDemo={this._renderDemo} />);
updateQueryString={this._updateQueryString} />);
}
return <div className="page">{child}</div>;

View File

@ -28,7 +28,7 @@ export default class TableOfContents extends Component {
// is external link
return (
<li key={`page-${i}`}>
<a className="link" href={page.external} target="_blank" >{page.name}</a>
<a className="link" href={page.external} target="_new" >{page.name}</a>
</li>
);
}

View File

@ -1,4 +1,4 @@
import ExamplesToc from '../../../examples/main/constants/toc';
import {standaloneExamples} from '../../../examples/main/constants/toc';
import ExamplesComponent from '../components/examples';
import PagesComponent from '../components/page';
@ -14,7 +14,7 @@ function generatePath(tree, parentPath = '') {
tree.forEach(branch => generatePath(branch, parentPath));
}
if (tree.name) {
tree.path = tree.name.match(/(GeoJson|3D|API|([A-Z]|^)[a-z'0-9]+|\d+)/g)
tree.path = tree.name.match(/(GeoJSON|3D|API|([A-Z]|^)[a-z'0-9]+|\d+)/g)
.join('-')
.toLowerCase()
.replace(/[^\w-]/g, '');
@ -31,7 +31,7 @@ function generatePath(tree, parentPath = '') {
const examplePages = {
title: 'Examples',
pageComponent: ExamplesComponent,
paths: generatePath(ExamplesToc)
paths: generatePath(standaloneExamples)
};
const docPages = {

View File

@ -1,9 +1,6 @@
import {handleActions} from 'redux-actions';
import {DEFAULT_VIEWPORT_STATE} from '../constants/defaults';
import ViewportAnimation from '../utils/map-utils';
ViewportAnimation.init();
export default handleActions({

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@ -1,142 +0,0 @@
{
"marker-1": {
"x": 0,
"y": 0,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-2": {
"x": 128,
"y": 0,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-3": {
"x": 256,
"y": 0,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-4": {
"x": 384,
"y": 0,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-5": {
"x": 0,
"y": 128,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-6": {
"x": 128,
"y": 128,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-7": {
"x": 256,
"y": 128,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-8": {
"x": 384,
"y": 128,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-9": {
"x": 0,
"y": 256,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-10": {
"x": 128,
"y": 256,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-20": {
"x": 256,
"y": 256,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-30": {
"x": 384,
"y": 256,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-40": {
"x": 0,
"y": 384,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-50": {
"x": 128,
"y": 384,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-60": {
"x": 256,
"y": 384,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-70": {
"x": 384,
"y": 384,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-80": {
"x": 0,
"y": 512,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-90": {
"x": 128,
"y": 512,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker-100": {
"x": 256,
"y": 512,
"width": 128,
"height": 128,
"anchorY": 128
},
"marker": {
"x": 384,
"y": 512,
"width": 128,
"height": 128,
"anchorY": 128
}
}

View File

@ -8,7 +8,6 @@
<link rel="icon" type="img/ico" href="favicon.ico">
<link rel="stylesheet" id="font-link" href="https://d1a3f4spazzrp4.cloudfront.net/uber-fonts/3.1.0/refresh.css">
<link rel="stylesheet" type="text/css" href="node_modules/mapbox-gl/dist/mapbox-gl.css" />
<!-— facebook open graph tags -->
<meta property="og:url" content="https://uber.github.io/deck.gl/" />

View File

@ -1,84 +0,0 @@
### Core Layers
<div>
<div class="thumb">
<div class="bg-black" data-title="Flights at Heathrow" data-name="LineLayer">
<a href="#/examples/core-layers/line-layer">
<img src="images/demo-thumb-line.jpg" />
</a>
</div>
</div>
<div class="thumb">
<div class="bg-black" data-title="Road Safety in UK" data-name="HexagonLayer">
<a href="#/examples/core-layers/hexagon-layer">
<img src="images/demo-thumb-heatmap.jpg" />
</a>
</div>
</div>
<div class="thumb">
<div data-title="Vancouver Property Value" data-name="GeoJsonLayer">
<a href="#/examples/core-layers/geojson-layer">
<img src="images/demo-thumb-geojson.jpg" />
</a>
</div>
</div>
<div class="thumb">
<div class="bg-black" data-title="Access Public Transit in California" data-name="ScreenGridLayer">
<a href="#/examples/core-layers/screen-grid-layer">
<img src="images/demo-thumb-screengrid.jpg" />
</a>
</div>
</div>
<div class="thumb">
<div data-title="US County-to-County Migration" data-name="ArcLayer">
<a href="#/examples/core-layers/arc-layer">
<img src="images/demo-thumb-arc.jpg" />
</a>
</div>
</div>
<div class="thumb">
<div data-title="Every Person in NYC" data-name="ScatterplotLayer">
<a href="#/examples/core-layers/scatterplot-layer">
<img src="images/demo-thumb-scatterplot.jpg" />
</a>
</div>
</div>
</div>
### Custom Layers
<div>
<div class="thumb">
<div data-title="US County-to-County Migration" data-name="BrushingLayer">
<a href="#/examples/custom-layers/brushing-layer">
<img src="images/demo-thumb-brushing.jpg" />
</a>
</div>
</div>
<div class="thumb">
<div class="bg-black" data-title="Taxi Trips in NYC" data-name="TripsLayer">
<a href="#/examples/custom-layers/trip-routes">
<img src="images/demo-thumb-trip.jpg" />
</a>
</div>
</div>
</div>
### Beyond Maps
<div>
<div class="thumb">
<div data-title="3D Surface Explorer" data-name="PlotLayer">
<a href="#/examples/beyond-maps/3d-surface-explorer">
<img src="images/demo-thumb-plot.jpg" />
</a>
</div>
</div>
<div class="thumb">
<div data-title="3D Indoor Scan" data-name="PointCloudLayer">
<a href="https://gnavvy.github.io/point-cloud-example/" target="_blank">
<img src="images/demo-thumb-point-cloud.jpg" />
</a>
</div>
</div>
</div>

View File

@ -1,4 +1,3 @@
/* from deck.gl */
.gallery-wrapper {
position: fixed;;
@ -106,6 +105,10 @@
box-shadow: 0 0 4px rgba(0, 0, 0, 0.15);
margin: 24px;
padding: 12px 24px;
position: absolute;
top: 0;
right: 0;
outline: none;
hr {
margin: 12px -24px;
@ -129,8 +132,9 @@
white-space: nowrap;
}
label {
text-transform: uppercase;
display: inline-block;
width: 40%;
width: 50%;
margin-right: 10%;
color: $black-40;
margin-bottom: 4px;
@ -139,10 +143,13 @@
font-size: 0.9em;
display: inline-block;
padding: 0 4px;
width: 50%;
width: 40%;
height: 20px;
line-height: 1.833;
}
input[type="checkbox"], input[type="radio"], input[type="color"] {
width: 20%;
}
input {
border: solid 1px #ccc;
@ -244,7 +251,7 @@ code {
width: 100%;
margin: 0;
}
.options-panel:not(.focus) {
.options-panel:not(:focus) {
cursor: pointer;
>div > *, hr, .input {
display: none;
@ -269,39 +276,3 @@ code {
padding: 40px 12px 96px;
}
}
.icon-demo {
position: relative;
&.clickable {
cursor: pointer;
}
.tooltip {
padding: 4px 12px;
max-width: 240px;
max-height: 320px;
box-sizing: content-box;
overflow-y: hidden;
h5 {
font-size: 1em;
}
p {
display: none;
text-indent: 4px;
}
}
.tooltip.interactive {
border: solid 4px transparent;
margin: -4px;
background: $white;
color: $black;
pointer-events: all;
width: 240px;
overflow-y: auto;
p {
display: block;
}
}
}

View File

@ -48,24 +48,30 @@
}
#banner {
height: 80vh;
background: $black-20;
color: $white;
position: relative;
@include venderPrefix(user-select, none);
background-repeat: no-repeat;
background-size: cover;
background-position: center;
> .fps > div {
position: absolute !important;
bottom: 24px !important;
right: 10px !important;
top: inherit !important;
left: inherit !important;
}
>.hero {
&:before {
content: ' ';
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
@include linearGradientH(background, rgba(0,0,0,0.75), transparent, 0%, 60%);
display: block;
}
> .container {
position: relative;
top: 50%;
transform: translateY(-50%);
}
p {
@ -155,16 +161,21 @@
@media screen and (max-width: 576px) {
.home-wrapper {
h1 {
font-size: 3.6em;
font-size: 3.2em;
}
h2 {
font-size: 1.6em;
}
}
#banner {
height: 100vh;
p {
font-size: 12px;
}
&:before {
@include linearGradientH(background, rgba(0,0,0,0.75), transparent);
}
}
#features {
.container >div {

View File

@ -7,13 +7,13 @@
#{$property_name}: linear-gradient(to bottom, $top 0%,$bottom 100%); /* W3C */
}
@mixin linearGradientH($property_name, $left, $right){
@mixin linearGradientH($property_name, $left, $right, $let_position: 0%, $right_position: 100%){
#{$property_name}: $left; /* Old browsers */
#{$property_name}: -moz-linear-gradient(left, $left 0%, $right 100%); /* FF3.6+ */
#{$property_name}: -webkit-linear-gradient(left, $left 0%,$right 100%); /* Chrome10+,Safari5.1+ */
#{$property_name}: -o-linear-gradient(left, $left 0%,$right 100%); /* Opera 11.10+ */
#{$property_name}: -ms-linear-gradient(left, $left 0%,$right 100%); /* IE10+ */
#{$property_name}: linear-gradient(to right, $left 0%,$right 100%); /* W3C */
#{$property_name}: -moz-linear-gradient(left, $left $let_position, $right $right_position); /* FF3.6+ */
#{$property_name}: -webkit-linear-gradient(left, $left $let_position,$right $right_position); /* Chrome10+,Safari5.1+ */
#{$property_name}: -o-linear-gradient(left, $left $let_position,$right $right_position); /* Opera 11.10+ */
#{$property_name}: -ms-linear-gradient(left, $left $let_position,$right $right_position); /* IE10+ */
#{$property_name}: linear-gradient(to right, $left $let_position,$right $right_position); /* W3C */
}
@mixin venderPrefix($property_name, $value) {

View File

@ -16,44 +16,6 @@ $topbar-height: 64px;
@import 'gallery';
@import '../../node_modules/mapbox-gl/dist/mapbox-gl.css';
.mapboxgl-ctrl.mapboxgl-ctrl-attrib {
width: 115px;
};
.control-panel {
position: absolute;
background: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
padding: 12px 24px;
margin: 20px;
font-size: 13px;
line-height: 2;
color: #6b6b76;
text-transform: uppercase;
}
.control-panel label {
display: inline-block;
width: 100px;
}
.control-panel input {
margin-left: 20px;
}
.city-pin {
cursor: pointer;
fill: #d00;
stroke: none;
}
.nav {
position: absolute;
top: 0;
right: 0;
padding: 10px;
}
body {
font-family: ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important;
font-size: 12px;
@ -226,60 +188,6 @@ hr.short {
background: $black;
color: $white;
}
.thumb {
cursor: pointer;
position: relative;
width: 32%;
display: inline-block;
line-height: 0;
img {
transition: opacity 0.4s;
}
>div:before, >div:after {
display: block;
z-index: 1;
position: absolute;
transition: opacity 0.4s;
opacity: 0;
text-align: center;
pointer-events: none;
box-sizing: border-box;
line-height: 1.5;
}
>div:before {
content: attr(data-title);
font-size: 1.4em;
font-weight: 100;
width: 100%;
padding: 12%;
left: 0;
top: 50%;
transform: translateY(-50%);
}
>div:after {
font-size: 0.833em;
content: attr(data-name);
padding: 5%;
left: 0;
width: 80%;
height: 80%;
margin: 10%;
top: 0;
left: 0;
border: solid 2px;
@include linearGradientV(border-image, $primary, $secondary);
border-image-slice: 2;
box-sizing: border-box;
}
>div:hover img {
opacity: 0.2;
}
>div:hover:before, >div:hover:after {
opacity: 1;
}
}
.mapbox-tip {
position: absolute;
@ -287,26 +195,10 @@ hr.short {
bottom: 24px;
color: $white-40;
}
.mapboxgl-ctrl-attrib {
position: absolute;
bottom: 2px;
right: 4px;
z-index: 9;
}
.overlays {
cursor: crosshair;
}
@media screen and (max-width: 1200px) {
.thumb {
width: 49%;
}
}
@media screen and (max-width: 800px) {
.thumb {
width: 100%;
}
}
@media screen and (max-width: 576px) {
.container {
padding: 0 12px;

View File

@ -1,52 +0,0 @@
import {rgb} from 'd3-color';
export const normalizeParam = p => {
if (p.type === 'function') {
let displayValue = p.value.toString();
// pretty print function code:
// convert `function funcName(d) {...}` to `d => {...}`
displayValue = displayValue.replace(/^function (\w+)?\((\w*?)\)/, '$2 =>');
// convert `function funcName(d, i) {...}` to `(d, i) => {...}`
displayValue = displayValue.replace(/^function (\w+)?(\(.*?\))/, '$2 =>');
// convert `d => {return 1}` to `d => 1`
displayValue = displayValue.replace(/\{\s*return\s*(.*?);?\s*\}$/, '$1');
return {...p, displayValue};
}
if (p.type === 'json') {
return {...p, displayValue: JSON.stringify(p.value)};
}
if (p.type === 'color') {
return {...p, displayValue: colorToHex(p.value)};
}
return {...p, displayValue: String(p.value)};
};
export const readableInteger = x => {
if (!x) {
return 0;
}
if (x < 1000) {
return x.toString();
}
x /= 1000;
if (x < 1000) {
return `${x.toFixed(1)}K`;
}
x /= 1000;
return `${x.toFixed(1)}M`;
};
export function colorToHex(color) {
return colorToRGBArray(color).reduce(
(acc, v) => `${acc}${v < 16 ? '0' : ''}${v.toString(16)}`,
'#'
);
}
export function colorToRGBArray(color) {
if (Array.isArray(color)) {
return color.slice(0, 3);
}
const c = rgb(color);
return [c.r, c.g, c.b];
}

View File

@ -1,80 +0,0 @@
const blackList = [
'projectionMode',
'modelMatrix'
];
/* eslint-disable complexity */
/*
* infer parameter type from a prop
*/
export function propToParam(key, value) {
if (blackList.indexOf(key) >= 0) {
return null;
}
const param = {
name: key,
displayName: key,
value
};
switch (typeof value) {
case 'boolean':
return {...param, type: 'checkbox'};
case 'number':
if (/pixels|width|height|size|scale|radius|limit/i.test(key)) {
param.max = 100;
param.step = 1;
} else {
param.max = 1;
param.step = 0.01;
}
return {...param, type: 'range', min: 0};
case 'function':
if (key.indexOf('get') === 0) {
// is accessor
return {...param, type: 'function'};
}
break;
case 'string':
if (/\.(png|jpg|jpeg|gif)/i.test(value)) {
return {...param, type: 'link'};
}
break;
case 'object':
if (/color/i.test(key) && value && Number.isFinite(value[0])) {
return {...param, type: 'color'};
}
if (/mapping|domain|range/i.test(key)) {
return {...param, type: 'json'};
}
break;
default:
}
return null;
}
/* eslint-enable complexity */
/*
* get array of parameters from a layer's default props
* sorted by type
*/
export function getLayerParams(layer) {
const paramsMap = {};
const paramsArray = [];
Object.keys(layer.props).forEach(key => {
const param = propToParam(key, layer.props[key]);
if (param) {
paramsArray.push(param);
}
});
paramsArray.sort((p1, p2) => p1.type.localeCompare(p2.type));
paramsArray.forEach(param => {
paramsMap[param.name] = param;
});
return paramsMap;
}

View File

@ -1,50 +0,0 @@
/* global window */
import TWEEN from 'tween.js';
let viewportTween;
const animate = () => {
TWEEN.update();
window.requestAnimationFrame(animate);
};
// get a linear tween
const ease = (fromState, toState, duration) =>
new TWEEN.Tween(fromState).to(toState, duration);
// fly to new viewport
const fly = (fromViewport, toViewport, duration, onUpdate) => {
const fromState = {};
const nanState = {};
const toState = {};
Object.keys(toViewport).forEach(key => {
const v0 = fromViewport[key];
const v1 = toViewport[key];
if (Number.isFinite(v0) && Number.isFinite(v1)) {
fromState[key] = v0;
toState[key] = v1;
} else {
nanState[key] = v1;
}
});
if (viewportTween) {
TWEEN.remove(viewportTween);
}
viewportTween = new TWEEN.Tween(fromState)
.to(toState, duration)
.onUpdate(function update() {
onUpdate({...this, ...nanState}); // eslint-disable-line no-invalid-this
});
return viewportTween;
};
export default {
init: animate,
Easing: TWEEN.Easing,
ease,
fly
};

View File

@ -1,45 +0,0 @@
/* global Worker */
const workers = {};
export function StreamParser(workerUrl, callback) {
let parsedLength = 0;
if (workers[workerUrl]) {
workers[workerUrl].terminate();
}
const workerInstance = new Worker(workerUrl);
workers[workerUrl] = workerInstance;
let streamedData = [];
workerInstance.onmessage = e => {
const {action, data, meta} = e.data;
if (action === 'end') {
workerInstance.terminate();
} else if (action === 'add' && data && data.length) {
streamedData = streamedData.concat(data);
callback(streamedData, meta); // eslint-disable-line callback-return
}
};
this.onProgress = e => {
const {responseText} = e.target;
const lineBreak = responseText.lastIndexOf('\n') + 1;
workerInstance.postMessage({
event: 'progress',
text: responseText.slice(parsedLength, lineBreak)
});
parsedLength = lineBreak;
};
this.onLoad = target => {
const {responseText} = target;
workerInstance.postMessage({
event: 'load',
text: responseText.slice(parsedLength)
});
};
}

View File

@ -69,6 +69,10 @@ module.exports = {
from: '../docs',
to: 'docs'
},
{
from: '../examples/data',
to: 'data'
},
{
from: './src/static'
}