From 58b05cede8b1799b5ed135e828b7e9b32cc65e4a Mon Sep 17 00:00:00 2001 From: Jerome Cukier Date: Tue, 12 Sep 2017 12:01:08 -0700 Subject: [PATCH] Post dry run (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove binds, be more explicit w/ “polish” in deck.gl code * remove too clever deconstructs * fixed typos / issues on code, added a Hero unit * more typos and mistakes found * feat(demo): hot-reload --- .gitignore | 5 + src/components/Hero.js | 38 +++++ src/config.js | 19 +-- src/demos/add-charts/app.js | 114 +++++-------- src/demos/add-charts/charts.js | 38 +---- src/demos/add-charts/deckgl-overlay.js | 54 ++---- src/demos/add-charts/layer-controls.js | 53 +++++- src/demos/add-charts/style.js | 6 + src/demos/data/{taxi.csv => taxi.js} | 12 +- src/demos/hexagon-overlay/app.js | 109 +++++------- src/demos/hexagon-overlay/deckgl-overlay.js | 28 ++-- src/demos/hexagon-overlay/layer-controls.js | 53 +++++- src/demos/hexagon-overlay/style.js | 12 +- src/demos/introducing-interaction/app.js | 137 ++++++--------- src/demos/introducing-interaction/charts.js | 8 +- .../introducing-interaction/deckgl-overlay.js | 54 ++---- .../introducing-interaction/layer-controls.js | 53 +++++- src/demos/introducing-interaction/style.js | 6 + src/demos/linking-it-all/app.js | 148 +++++++---------- src/demos/linking-it-all/deckgl-overlay.js | 61 +++---- src/demos/linking-it-all/layer-controls.js | 53 +++++- src/demos/linking-it-all/style.js | 6 + src/demos/scatterplot-overlay/app.js | 70 ++++---- .../scatterplot-overlay/deckgl-overlay.js | 12 +- .../scatterplot-overlay/layer-controls.js | 53 +++++- src/demos/scatterplot-overlay/style.js | 12 +- src/demos/starting-code/index.html | 16 +- src/demos/starting-code/layer-controls.js | 140 ++++++++++++++++ src/demos/starting-code/root.js | 15 +- src/demos/starting-code/spinner.js | 29 ++++ src/demos/starting-code/style.js | 55 ++++++ src/demos/starting-with-map/app.js | 17 +- src/docs/deckgl/hexagon-overlay.md | 46 +++--- src/docs/deckgl/scatterplot-overlay.md | 156 ++++++++++++++++-- src/docs/mapping/hexagon.md | 2 +- src/docs/react-vis/basic.md | 50 +++--- src/docs/react-vis/interactions.md | 73 ++++---- src/docs/react-vis/linking-it-all.md | 101 ++++++------ src/docs/setup.md | 4 + src/docs/starting-with-map.md | 44 +++-- src/styles/_index.scss | 10 ++ 41 files changed, 1240 insertions(+), 732 deletions(-) create mode 100644 src/components/Hero.js rename src/demos/data/{taxi.csv => taxi.js} (99%) create mode 100644 src/demos/starting-code/layer-controls.js create mode 100644 src/demos/starting-code/spinner.js create mode 100644 src/demos/starting-code/style.js diff --git a/.gitignore b/.gitignore index af8ad632..3676ee2d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,8 @@ node_modules/ *.log* .DS_Store +docs/appcache/manifest.appcache +docs/appcache/manifest.html +docs/bundle-6bb1dfe73158c06fb2c4.js +docs/styles-6bb1dfe73158c06fb2c4.css +docs/sw.js \ No newline at end of file diff --git a/src/components/Hero.js b/src/components/Hero.js new file mode 100644 index 00000000..4fce91ad --- /dev/null +++ b/src/components/Hero.js @@ -0,0 +1,38 @@ +import React from 'react'; +import App from '../demos/linking-it-all/app.js'; + +function Hero() { + return (
+ + {'Get started'} +
); +} + +export default Hero; diff --git a/src/config.js b/src/config.js index 9214e10a..85438a01 100644 --- a/src/config.js +++ b/src/config.js @@ -14,24 +14,15 @@ export const PROJECTS = { 'react-vis': 'https://uber.github.io/react-vis', }; -export const HOME_HEADING = PROJECT_DESC; - export const HOME_RIGHT = (
- -

Welcome to the visualization tutorials!

- +

Uber Visualization tutorial

- In the following pages, we're going to learn how to use 3 of Uber's open source visualization libraries: React Map GL, Deck GL and React Vis. All of these libraries have a different function and work in the React ecosystem. We're going to build an app using each library separately. -

- -

- React Map GL is a React wrapper on Mapbox: it allows you to render and interact with Mapbox maps in React applications. - Deck GL is an interface to use WebGL in React. With DeckGL, users build layers of visualized elements that they can superimpose. DeckGL works especially well with React Map GL, but is not limited to maps. - React Vis is Uber's charting library. It provides an abstraction to create, interact and style a variety of charts. - For the purpose of this tutorial, we're going to use a dataset of taxi pickups and dropoffs in New York, where this information is public. + This tutorial will show you how to build an app that showcases three of Uber visualization libraries: ReactMapGL for maps, DeckGL to create WebGL-based data overlays and React-Vis> for simple charts. +

+

+ You will learn how to use these libraries separately, but also how they can be combined to work together.

-
); diff --git a/src/demos/add-charts/app.js b/src/demos/add-charts/app.js index 8797d0b7..a2608d5a 100644 --- a/src/demos/add-charts/app.js +++ b/src/demos/add-charts/app.js @@ -2,87 +2,53 @@ import React, {Component} from 'react'; import MapGL from 'react-map-gl'; import DeckGLOverlay from './deckgl-overlay'; -import LayerControls from './layer-controls'; +import { + LayerControls, + HEXAGON_CONTROLS +} from './layer-controls'; import Charts from './charts'; import Spinner from './spinner'; import {tooltipStyle} from './style'; -import taxiData from '../data/taxi.csv'; +import taxiData from '../data/taxi'; -const MAPBOX_STYLE = 'mapbox://styles/uberdata/cive485h000192imn6c6cc8fc'; +const MAPBOX_STYLE = 'mapbox://styles/mapbox/dark-v9'; // Set your mapbox token here const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line -const LAYER_CONTROLS = { - showHexagon: { - displayName: 'Show Hexagon', - type: 'boolean', - value: false - }, - radius: { - displayName: 'Hexagon Radius', - type: 'range', - value: 250, - step: 50, - min: 50, - max: 1000 - }, - coverage: { - displayName: 'Hexagon Coverage', - type: 'range', - value: 0.7, - step: 0.1, - min: 0, - max: 1 - }, - upperPercentile: { - displayName: 'Hexagon Upper Percentile', - type: 'range', - value: 100, - step: 0.1, - min: 80, - max: 100 - }, - radiusScale: { - displayName: 'Scatterplot Radius', - type: 'range', - value: 30, - step: 10, - min: 10, - max: 200 - } -}; - export default class App extends Component { constructor(props) { super(props); this.state = { viewport: { - ...DeckGLOverlay.defaultViewport, - width: 500, - height: 500 + width: window.innerWidth, + height: window.innerHeight, + longitude: -74, + latitude: 40.7, + zoom: 11, + maxZoom: 16 }, - points: [], - settings: Object.keys(LAYER_CONTROLS).reduce((accu, key) => ({ + settings: Object.keys(HEXAGON_CONTROLS).reduce((accu, key) => ({ ...accu, - [key]: LAYER_CONTROLS[key].value + [key]: HEXAGON_CONTROLS[key].value }), {}), - // hoverInfo - x: 0, - y: 0, - hoveredObject: null, status: 'LOADING' }; + this._resize = this._resize.bind(this); } componentDidMount() { this._processData(); - window.addEventListener('resize', this._resize.bind(this)); + window.addEventListener('resize', this._resize); this._resize(); } + componentWillUnmount() { + window.removeEventListener('resize', this._resize); + } + _processData() { if (taxiData) { this.setState({status: 'LOADED'}); @@ -139,10 +105,6 @@ export default class App extends Component { } } - updateLayerSettings(settings) { - this.setState({settings}); - } - _onHover({x, y, object}) { this.setState({x, y, hoveredObject: object}); } @@ -160,31 +122,39 @@ export default class App extends Component { }); } + _updateLayerSettings(settings) { + this.setState({settings}); + } + render() { - const {viewport, hoveredObject, points, settings, status, x, y} = this.state; return (
- {hoveredObject && -
-
{hoveredObject.id}
+ {this.state.hoveredObject && +
+
{this.state.hoveredObject.id}
} + settings={this.state.settings} + propTypes={HEXAGON_CONTROLS} + onChange={settings => this._updateLayerSettings(settings)}/> this._onViewportChange(viewport)} mapboxApiAccessToken={MAPBOX_TOKEN}> + viewport={this.state.viewport} + data={this.state.points} + onHover={hover => this._onHover(hover)} + {...this.state.settings} + /> - +
); } diff --git a/src/demos/add-charts/charts.js b/src/demos/add-charts/charts.js index 0c7654ae..ef7c1a5f 100644 --- a/src/demos/add-charts/charts.js +++ b/src/demos/add-charts/charts.js @@ -22,14 +22,14 @@ export default function Charts({pickups}) { yDomain={[0, 1000]} > (d / 100).toFixed(0) + '%'} + tickFormat={d => (d / 100).toFixed(0) + '%'} /> (h % 24) >= 12 ? + tickFormat={h => (h % 24) >= 12 ? (h % 12 || 12) + 'PM' : (h % 12 || 12) + 'AM' } @@ -39,37 +39,3 @@ export default function Charts({pickups}) {
); } - -/* inital setup - -import React from 'react'; -import {charts} from './style'; - -import { - VerticalBarSeries, - XAxis, - XYPlot, - YAxis -} from 'react-vis'; - -export default function Charts({pickups}) { - if (!pickups) { - return (
); - } - return (
-

Pickups by hour

-

As percentage of all trips

- - - - - -
); -} - -*/ diff --git a/src/demos/add-charts/deckgl-overlay.js b/src/demos/add-charts/deckgl-overlay.js index 258da575..857c738a 100644 --- a/src/demos/add-charts/deckgl-overlay.js +++ b/src/demos/add-charts/deckgl-overlay.js @@ -1,17 +1,16 @@ import React, {Component} from 'react'; - import DeckGL, {ScatterplotLayer, HexagonLayer} from 'deck.gl'; const PICKUP_COLOR = [0, 128, 255]; const DROPOFF_COLOR = [255, 0, 128]; const HEATMAP_COLORS = [ - [213,62,79], - [252,141,89], - [254,224,139], - [230,245,152], - [153,213,148], - [50,136,189] + [213, 62, 79], + [252, 141, 89], + [254, 224, 139], + [230, 245, 152], + [153, 213, 148], + [50, 136, 189] ].reverse(); const LIGHT_SETTINGS = { @@ -27,63 +26,46 @@ const elevationRange = [0, 1000]; export default class DeckGLOverlay extends Component { - static get defaultViewport() { - return { - longitude: -74, - latitude: 40.7, - zoom: 11, - maxZoom: 16, - pitch: 0, - bearing: 0 - }; - } - _initialize(gl) { gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); } render() { - const {viewport, data, onHover, settings} = this.props; - - if (!data) { + if (!this.props.data) { return null; } const layers = [ - !settings.showHexagon ? new ScatterplotLayer({ + !this.props.showHexagon ? new ScatterplotLayer({ id: 'scatterplot', - data, getPosition: d => d.position, getColor: d => d.pickup ? PICKUP_COLOR : DROPOFF_COLOR, getRadius: d => 1, opacity: 0.5, pickable: true, - onHover, - radiusScale: 30, radiusMinPixels: 0.25, - radiusMaxPixels: 30 + radiusMaxPixels: 30, + ...this.props }) : null, - settings.showHexagon ? new HexagonLayer({ + this.props.showHexagon ? new HexagonLayer({ id: 'heatmap', colorRange: HEATMAP_COLORS, - coverage: settings.coverage, - data, elevationRange, elevationScale: 10, extruded: true, getPosition: d => d.position, lightSettings: LIGHT_SETTINGS, - onHover, opacity: 1, pickable: true, - radius: settings.radius, - upperPercentile: settings.upperPercentile - }): null + ...this.props + }) : null ]; - return ( - - ); + return (); } } diff --git a/src/demos/add-charts/layer-controls.js b/src/demos/add-charts/layer-controls.js index ee1b898d..f107a6cd 100644 --- a/src/demos/add-charts/layer-controls.js +++ b/src/demos/add-charts/layer-controls.js @@ -1,7 +1,58 @@ import React, {Component} from 'react'; import {layerControl} from './style'; -export default class LayerControls extends Component { +export const HEXAGON_CONTROLS = { + showHexagon: { + displayName: 'Show Hexagon', + type: 'boolean', + value: true + }, + radius: { + displayName: 'Hexagon Radius', + type: 'range', + value: 250, + step: 50, + min: 50, + max: 1000 + }, + coverage: { + displayName: 'Hexagon Coverage', + type: 'range', + value: 0.7, + step: 0.1, + min: 0, + max: 1 + }, + upperPercentile: { + displayName: 'Hexagon Upper Percentile', + type: 'range', + value: 100, + step: 0.1, + min: 80, + max: 100 + }, + radiusScale: { + displayName: 'Scatterplot Radius', + type: 'range', + value: 30, + step: 10, + min: 10, + max: 200 + } +}; + +export const SCATTERPLOT_CONTROLS = { + radiusScale: { + displayName: 'Scatterplot Radius', + type: 'range', + value: 30, + step: 10, + min: 10, + max: 200 + } +}; + +export class LayerControls extends Component { _onValueChange(settingName, newValue) { const {settings} = this.props; diff --git a/src/demos/add-charts/style.js b/src/demos/add-charts/style.js index 4bf9b5ac..3a9abc65 100644 --- a/src/demos/add-charts/style.js +++ b/src/demos/add-charts/style.js @@ -12,6 +12,9 @@ export const tooltipStyle = { export const layerControl = { borderRadius: 3, boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', + fontFamily: 'ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important', + fontSize: '12px', + lineHeight: 1.833, width: 200, position: 'absolute', top: '20px', @@ -25,6 +28,9 @@ export const charts = { background: 'white', borderRadius: 3, boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', + fontFamily: 'ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important', + fontSize: '12px', + lineHeight: 1.833, height: 210, padding: '10px', position: 'absolute', diff --git a/src/demos/data/taxi.csv b/src/demos/data/taxi.js similarity index 99% rename from src/demos/data/taxi.csv rename to src/demos/data/taxi.js index 5683c8f0..6dfe51e3 100644 --- a/src/demos/data/taxi.csv +++ b/src/demos/data/taxi.js @@ -1,4 +1,4 @@ -VendorID,tpep_pickup_datetime,tpep_dropoff_datetime,passenger_count,trip_distance,pickup_longitude,pickup_latitude,dropoff_longitude,dropoff_latitude,fare_amount,tip_amount,total_amount +const data = `VendorID,tpep_pickup_datetime,tpep_dropoff_datetime,passenger_count,trip_distance,pickup_longitude,pickup_latitude,dropoff_longitude,dropoff_latitude,fare_amount,tip_amount,total_amount 2,2015-01-15 15:33:26 +00:00,2015-01-15 15:52:27 +00:00,1,2.89,-73.96710205,40.80395889,-73.96305084,40.77251816,15,0,15.8 2,2015-01-15 18:05:53 +00:00,2015-01-15 18:13:58 +00:00,1,1.1,-73.96154022,40.77421188,-73.94589233,40.77362823,7,0,8.8 2,2015-01-15 14:04:10 +00:00,2015-01-15 15:02:46 +00:00,5,16.1,-73.99071503,40.7285881,-73.80666351,40.65282059,52,14.33,72.46 @@ -9997,4 +9997,12 @@ VendorID,tpep_pickup_datetime,tpep_dropoff_datetime,passenger_count,trip_distanc 1,2015-01-15 22:12:29 +00:00,2015-01-15 22:19:57 +00:00,1,2,-74.00585938,40.74208069,-74.0136261,40.71749878,9,0,10.3 2,2015-01-15 21:55:37 +00:00,2015-01-15 22:08:56 +00:00,1,1.92,-73.98204803,40.7692337,-74.00080872,40.7480278,10.5,1,12.8 2,2015-01-15 20:28:10 +00:00,2015-01-15 20:41:30 +00:00,3,1.85,-73.98207855,40.76473999,-73.95909882,40.77198029,10.5,2.2,14 -1,2015-01-15 20:29:18 +00:00,2015-01-15 20:40:53 +00:00,2,2.4,-73.97306061,40.78549194,-73.99123383,40.75609589,10.5,0,11.8 +1,2015-01-15 20:29:18 +00:00,2015-01-15 20:40:53 +00:00,2,2.4,-73.97306061,40.78549194,-73.99123383,40.75609589,10.5,0,11.8`; + +const rows = data.split('\n'); +const fields = rows[0].split(','); +const dataRows = rows.slice(1); + +export default dataRows.map(r => r.split(',').reduce((prev, curr, i) => { + prev[fields[i]] = curr; return prev; +}, {})); diff --git a/src/demos/hexagon-overlay/app.js b/src/demos/hexagon-overlay/app.js index d8eec224..06ece5dd 100644 --- a/src/demos/hexagon-overlay/app.js +++ b/src/demos/hexagon-overlay/app.js @@ -2,55 +2,18 @@ import React, {Component} from 'react'; import MapGL from 'react-map-gl'; import DeckGLOverlay from './deckgl-overlay'; -import LayerControls from './layer-controls'; +import { + LayerControls, + HEXAGON_CONTROLS +} from './layer-controls'; import Spinner from './spinner'; import {tooltipStyle} from './style'; -import taxiData from '../data/taxi.csv'; +import taxiData from '../data/taxi'; -const MAPBOX_STYLE = 'mapbox://styles/uberdata/cive485h000192imn6c6cc8fc'; +const MAPBOX_STYLE = 'mapbox://styles/mapbox/dark-v9'; // Set your mapbox token here const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line -const LAYER_CONTROLS = { - showHexagon: { - displayName: 'Show Hexagon', - type: 'boolean', - value: true - }, - radius: { - displayName: 'Hexagon Radius', - type: 'range', - value: 250, - step: 50, - min: 50, - max: 1000 - }, - coverage: { - displayName: 'Hexagon Coverage', - type: 'range', - value: 0.7, - step: 0.1, - min: 0, - max: 1 - }, - upperPercentile: { - displayName: 'Hexagon Upper Percentile', - type: 'range', - value: 100, - step: 0.1, - min: 80, - max: 100 - }, - radiusScale: { - displayName: 'Scatterplot Radius', - type: 'range', - value: 30, - step: 10, - min: 10, - max: 200 - } -}; - export default class App extends Component { constructor(props) { @@ -64,25 +27,27 @@ export default class App extends Component { zoom: 11, maxZoom: 16 }, - points: [], - settings: Object.keys(LAYER_CONTROLS).reduce((accu, key) => ({ + + settings: Object.keys(HEXAGON_CONTROLS).reduce((accu, key) => ({ ...accu, - [key]: LAYER_CONTROLS[key].value + [key]: HEXAGON_CONTROLS[key].value }), {}), - // hoverInfo - x: 0, - y: 0, - hoveredObject: null, + status: 'LOADING' }; + this._resize = this._resize.bind(this); } componentDidMount() { this._processData(); - window.addEventListener('resize', this._resize.bind(this)); + window.addEventListener('resize', () => this._resize); this._resize(); } + componentWillUnmount() { + window.removeEventListener('resize', () => this._resize); + } + _processData() { if (taxiData) { this.setState({status: 'LOADED'}); @@ -105,11 +70,7 @@ export default class App extends Component { } } - updateLayerSettings(settings) { - this.setState({settings}); - } - - _onHover({x, y, object}) { + _onHover({x, y, object}) { this.setState({x, y, hoveredObject: object}); } @@ -126,30 +87,38 @@ export default class App extends Component { }); } + _updateLayerSettings(settings) { + this.setState({settings}); + } + render() { - const {viewport, points, settings, status, x, y, hoveredObject} = this.state; return (
- {hoveredObject && -
-
{hoveredObject.id}
+ {this.state.hoveredObject && +
+
{this.state.hoveredObject.id}
} + settings={this.state.settings} + propTypes={HEXAGON_CONTROLS} + onChange={settings => this._updateLayerSettings(settings)}/> this._onViewportChange(viewport)} mapboxApiAccessToken={MAPBOX_TOKEN}> + viewport={this.state.viewport} + data={this.state.points} + onHover={hover => this._onHover(hover)} + {...this.state.settings} + /> - +
); } diff --git a/src/demos/hexagon-overlay/deckgl-overlay.js b/src/demos/hexagon-overlay/deckgl-overlay.js index 64a3c3dd..857c738a 100644 --- a/src/demos/hexagon-overlay/deckgl-overlay.js +++ b/src/demos/hexagon-overlay/deckgl-overlay.js @@ -32,46 +32,40 @@ export default class DeckGLOverlay extends Component { } render() { - const {viewport, data, onHover, settings} = this.props; - - if (!data) { + if (!this.props.data) { return null; } const layers = [ - !settings.showHexagon ? new ScatterplotLayer({ + !this.props.showHexagon ? new ScatterplotLayer({ id: 'scatterplot', - data, getPosition: d => d.position, getColor: d => d.pickup ? PICKUP_COLOR : DROPOFF_COLOR, getRadius: d => 1, opacity: 0.5, pickable: true, - onHover, - radiusScale: settings.radiusScale, radiusMinPixels: 0.25, - radiusMaxPixels: 30 + radiusMaxPixels: 30, + ...this.props }) : null, - settings.showHexagon ? new HexagonLayer({ + this.props.showHexagon ? new HexagonLayer({ id: 'heatmap', colorRange: HEATMAP_COLORS, - coverage: settings.coverage, - data, elevationRange, elevationScale: 10, extruded: true, getPosition: d => d.position, lightSettings: LIGHT_SETTINGS, - onHover, opacity: 1, pickable: true, - radius: settings.radius, - upperPercentile: settings.upperPercentile + ...this.props }) : null ]; - return ( - - ); + return (); } } diff --git a/src/demos/hexagon-overlay/layer-controls.js b/src/demos/hexagon-overlay/layer-controls.js index ee1b898d..f107a6cd 100644 --- a/src/demos/hexagon-overlay/layer-controls.js +++ b/src/demos/hexagon-overlay/layer-controls.js @@ -1,7 +1,58 @@ import React, {Component} from 'react'; import {layerControl} from './style'; -export default class LayerControls extends Component { +export const HEXAGON_CONTROLS = { + showHexagon: { + displayName: 'Show Hexagon', + type: 'boolean', + value: true + }, + radius: { + displayName: 'Hexagon Radius', + type: 'range', + value: 250, + step: 50, + min: 50, + max: 1000 + }, + coverage: { + displayName: 'Hexagon Coverage', + type: 'range', + value: 0.7, + step: 0.1, + min: 0, + max: 1 + }, + upperPercentile: { + displayName: 'Hexagon Upper Percentile', + type: 'range', + value: 100, + step: 0.1, + min: 80, + max: 100 + }, + radiusScale: { + displayName: 'Scatterplot Radius', + type: 'range', + value: 30, + step: 10, + min: 10, + max: 200 + } +}; + +export const SCATTERPLOT_CONTROLS = { + radiusScale: { + displayName: 'Scatterplot Radius', + type: 'range', + value: 30, + step: 10, + min: 10, + max: 200 + } +}; + +export class LayerControls extends Component { _onValueChange(settingName, newValue) { const {settings} = this.props; diff --git a/src/demos/hexagon-overlay/style.js b/src/demos/hexagon-overlay/style.js index 8279f9a4..3a9abc65 100644 --- a/src/demos/hexagon-overlay/style.js +++ b/src/demos/hexagon-overlay/style.js @@ -12,6 +12,9 @@ export const tooltipStyle = { export const layerControl = { borderRadius: 3, boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', + fontFamily: 'ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important', + fontSize: '12px', + lineHeight: 1.833, width: 200, position: 'absolute', top: '20px', @@ -22,15 +25,18 @@ export const layerControl = { }; export const charts = { - background: '#fff', + background: 'white', borderRadius: 3, boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', + fontFamily: 'ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important', + fontSize: '12px', + lineHeight: 1.833, height: 210, padding: '10px', position: 'absolute', left: 20, top: 20, - width: 300, + width: 500, zIndex: 100 }; @@ -46,4 +52,4 @@ export const spinner = { left: 60, transformOrigin: '20px 20px', zIndex: 110 -} +} \ No newline at end of file diff --git a/src/demos/introducing-interaction/app.js b/src/demos/introducing-interaction/app.js index 593c5a8f..999385ae 100644 --- a/src/demos/introducing-interaction/app.js +++ b/src/demos/introducing-interaction/app.js @@ -2,91 +2,53 @@ import React, {Component} from 'react'; import MapGL from 'react-map-gl'; import DeckGLOverlay from './deckgl-overlay'; -import LayerControls from './layer-controls'; +import { + LayerControls, + HEXAGON_CONTROLS +} from './layer-controls'; import Charts from './charts'; import Spinner from './spinner'; import {tooltipStyle} from './style'; -import taxiData from '../data/taxi.csv'; +import taxiData from '../data/taxi'; -const MAPBOX_STYLE = 'mapbox://styles/uberdata/cive485h000192imn6c6cc8fc'; +const MAPBOX_STYLE = 'mapbox://styles/mapbox/dark-v9'; // Set your mapbox token here const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line -const LAYER_CONTROLS = { - showHexagon: { - displayName: 'Show Hexagon', - type: 'boolean', - value: false - }, - radius: { - displayName: 'Hexagon Radius', - type: 'range', - value: 250, - step: 50, - min: 50, - max: 1000 - }, - coverage: { - displayName: 'Hexagon Coverage', - type: 'range', - value: 0.7, - step: 0.1, - min: 0, - max: 1 - }, - upperPercentile: { - displayName: 'Hexagon Upper Percentile', - type: 'range', - value: 100, - step: 0.1, - min: 80, - max: 100 - }, - radiusScale: { - displayName: 'Scatterplot Radius', - type: 'range', - value: 30, - step: 10, - min: 10, - max: 200 - } -}; - export default class App extends Component { constructor(props) { super(props); this.state = { viewport: { - ...DeckGLOverlay.defaultViewport, - width: 500, - height: 500 + width: window.innerWidth, + height: window.innerHeight, + longitude: -74, + latitude: 40.7, + zoom: 11, + maxZoom: 16 }, - points: [], - settings: Object.keys(LAYER_CONTROLS).reduce((accu, key) => ({ + settings: Object.keys(HEXAGON_CONTROLS).reduce((accu, key) => ({ ...accu, - [key]: LAYER_CONTROLS[key].value + [key]: HEXAGON_CONTROLS[key].value }), {}), - // hoverInfo - x: 0, - y: 0, - hoveredObject: null, - status: 'LOADING', - - // for chart interaction - highlightedHour: null, - selectedHour: null + status: 'LOADING' }; + this._resize = this._resize.bind(this); } componentDidMount() { this._processData(); - window.addEventListener('resize', this._resize.bind(this)); + window.addEventListener('resize', this._resize); this._resize(); } + componentWillUnmount() { + window.removeEventListener('resize', this._resize); + } + _processData() { if (taxiData) { this.setState({status: 'LOADED'}); @@ -143,14 +105,22 @@ export default class App extends Component { } } - updateLayerSettings(settings) { - this.setState({settings}); + _onHighlight(highlightedHour) { + this.setState({highlightedHour}); } _onHover({x, y, object}) { this.setState({x, y, hoveredObject: object}); } + _onSelect(selectedHour) { + this.setState({selectedHour: + selectedHour === this.state.selectedHour ? + null : + selectedHour + }); + } + _onViewportChange(viewport) { this.setState({ viewport: {...this.state.viewport, ...viewport} @@ -164,39 +134,42 @@ export default class App extends Component { }); } - render() { - const {viewport, hoveredObject, points, settings, status, x, y} = this.state; + _updateLayerSettings(settings) { + this.setState({settings}); + } + render() { return (
- {hoveredObject && -
-
{hoveredObject.id}
+ {this.state.hoveredObject && +
+
{this.state.hoveredObject.id}
} + settings={this.state.settings} + propTypes={HEXAGON_CONTROLS} + onChange={settings => this._updateLayerSettings(settings)}/> this._onViewportChange(viewport)} mapboxApiAccessToken={MAPBOX_TOKEN}> + viewport={this.state.viewport} + data={this.state.points} + onHover={hover => this._onHover(hover)} + {...this.state.settings} + /> this.setState({highlightedHour})} - select={(hour) => - this.setState({ - selectedHour: hour === this.state.selectedHour ? null : hour - }) - } + highlight={hour => this._onHighlight(hour)} + select={hour => this._onSelect(hour)} /> - +
); } diff --git a/src/demos/introducing-interaction/charts.js b/src/demos/introducing-interaction/charts.js index 9ab342bb..3154ef53 100644 --- a/src/demos/introducing-interaction/charts.js +++ b/src/demos/introducing-interaction/charts.js @@ -40,17 +40,17 @@ export default function Charts({ onMouseLeave={() => highlight(null)} > (d / 100).toFixed(0) + '%'} + tickFormat={d => (d / 100).toFixed(0) + '%'} /> highlight(d.hour)} - onValueClick={(d) => select(d.hour)} + onValueMouseOver={d => highlight(d.hour)} + onValueClick={d => select(d.hour)} style={{cursor: 'pointer'}} /> (h % 24) >= 12 ? + tickFormat={h => (h % 24) >= 12 ? (h % 12 || 12) + 'PM' : (h % 12 || 12) + 'AM' } diff --git a/src/demos/introducing-interaction/deckgl-overlay.js b/src/demos/introducing-interaction/deckgl-overlay.js index 55533285..857c738a 100644 --- a/src/demos/introducing-interaction/deckgl-overlay.js +++ b/src/demos/introducing-interaction/deckgl-overlay.js @@ -1,17 +1,16 @@ import React, {Component} from 'react'; - import DeckGL, {ScatterplotLayer, HexagonLayer} from 'deck.gl'; const PICKUP_COLOR = [0, 128, 255]; const DROPOFF_COLOR = [255, 0, 128]; const HEATMAP_COLORS = [ - [213,62,79], - [252,141,89], - [254,224,139], - [230,245,152], - [153,213,148], - [50,136,189] + [213, 62, 79], + [252, 141, 89], + [254, 224, 139], + [230, 245, 152], + [153, 213, 148], + [50, 136, 189] ].reverse(); const LIGHT_SETTINGS = { @@ -27,63 +26,46 @@ const elevationRange = [0, 1000]; export default class DeckGLOverlay extends Component { - static get defaultViewport() { - return { - longitude: -74, - latitude: 40.7, - zoom: 11, - maxZoom: 16, - pitch: 0, - bearing: 0 - }; - } - _initialize(gl) { gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); } render() { - const {viewport, data, onHover, settings} = this.props; - - if (!data) { + if (!this.props.data) { return null; } const layers = [ - !settings.showHexagon ? new ScatterplotLayer({ + !this.props.showHexagon ? new ScatterplotLayer({ id: 'scatterplot', - data, getPosition: d => d.position, getColor: d => d.pickup ? PICKUP_COLOR : DROPOFF_COLOR, getRadius: d => 1, opacity: 0.5, pickable: true, - onHover, - radiusScale: settings.radiusScale, radiusMinPixels: 0.25, - radiusMaxPixels: 30 + radiusMaxPixels: 30, + ...this.props }) : null, - settings.showHexagon ? new HexagonLayer({ + this.props.showHexagon ? new HexagonLayer({ id: 'heatmap', colorRange: HEATMAP_COLORS, - coverage: settings.coverage, - data, elevationRange, elevationScale: 10, extruded: true, getPosition: d => d.position, lightSettings: LIGHT_SETTINGS, - onHover, opacity: 1, pickable: true, - radius: settings.radius, - upperPercentile: settings.upperPercentile - }): null + ...this.props + }) : null ]; - return ( - - ); + return (); } } diff --git a/src/demos/introducing-interaction/layer-controls.js b/src/demos/introducing-interaction/layer-controls.js index ee1b898d..f107a6cd 100644 --- a/src/demos/introducing-interaction/layer-controls.js +++ b/src/demos/introducing-interaction/layer-controls.js @@ -1,7 +1,58 @@ import React, {Component} from 'react'; import {layerControl} from './style'; -export default class LayerControls extends Component { +export const HEXAGON_CONTROLS = { + showHexagon: { + displayName: 'Show Hexagon', + type: 'boolean', + value: true + }, + radius: { + displayName: 'Hexagon Radius', + type: 'range', + value: 250, + step: 50, + min: 50, + max: 1000 + }, + coverage: { + displayName: 'Hexagon Coverage', + type: 'range', + value: 0.7, + step: 0.1, + min: 0, + max: 1 + }, + upperPercentile: { + displayName: 'Hexagon Upper Percentile', + type: 'range', + value: 100, + step: 0.1, + min: 80, + max: 100 + }, + radiusScale: { + displayName: 'Scatterplot Radius', + type: 'range', + value: 30, + step: 10, + min: 10, + max: 200 + } +}; + +export const SCATTERPLOT_CONTROLS = { + radiusScale: { + displayName: 'Scatterplot Radius', + type: 'range', + value: 30, + step: 10, + min: 10, + max: 200 + } +}; + +export class LayerControls extends Component { _onValueChange(settingName, newValue) { const {settings} = this.props; diff --git a/src/demos/introducing-interaction/style.js b/src/demos/introducing-interaction/style.js index 4bf9b5ac..3a9abc65 100644 --- a/src/demos/introducing-interaction/style.js +++ b/src/demos/introducing-interaction/style.js @@ -12,6 +12,9 @@ export const tooltipStyle = { export const layerControl = { borderRadius: 3, boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', + fontFamily: 'ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important', + fontSize: '12px', + lineHeight: 1.833, width: 200, position: 'absolute', top: '20px', @@ -25,6 +28,9 @@ export const charts = { background: 'white', borderRadius: 3, boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', + fontFamily: 'ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important', + fontSize: '12px', + lineHeight: 1.833, height: 210, padding: '10px', position: 'absolute', diff --git a/src/demos/linking-it-all/app.js b/src/demos/linking-it-all/app.js index 3f041a48..9e9449f0 100644 --- a/src/demos/linking-it-all/app.js +++ b/src/demos/linking-it-all/app.js @@ -2,88 +2,53 @@ import React, {Component} from 'react'; import MapGL from 'react-map-gl'; import DeckGLOverlay from './deckgl-overlay'; -import LayerControls from './layer-controls'; +import { + LayerControls, + HEXAGON_CONTROLS +} from './layer-controls'; import Charts from './charts'; import Spinner from './spinner'; import {tooltipStyle} from './style'; +import taxiData from '../data/taxi'; -import taxiData from '../data/taxi.csv'; - -const MAPBOX_STYLE = 'mapbox://styles/uberdata/cive485h000192imn6c6cc8fc'; +const MAPBOX_STYLE = 'mapbox://styles/mapbox/dark-v9'; // Set your mapbox token here const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line -const LAYER_CONTROLS = { - showHexagon: { - displayName: 'Show Hexagon', - type: 'boolean', - value: false - }, - radius: { - displayName: 'Hexagon Radius', - type: 'range', - value: 250, - step: 50, - min: 50, - max: 1000 - }, - coverage: { - displayName: 'Hexagon Coverage', - type: 'range', - value: 0.7, - step: 0.1, - min: 0, - max: 1 - }, - upperPercentile: { - displayName: 'Hexagon Upper Percentile', - type: 'range', - value: 100, - step: 0.1, - min: 80, - max: 100 - }, - radiusScale: { - displayName: 'Scatterplot Radius', - type: 'range', - value: 30, - step: 10, - min: 10, - max: 200 - } -}; - export default class App extends Component { constructor(props) { super(props); this.state = { + ...props, viewport: { - ...DeckGLOverlay.defaultViewport, - width: 500, - height: 500 + width: window.innerWidth, + height: window.innerHeight, + longitude: -74, + latitude: 40.7, + zoom: 11, + maxZoom: 16, + ...props.viewport }, - points: [], - settings: Object.keys(LAYER_CONTROLS).reduce((accu, key) => ({ + settings: Object.keys(HEXAGON_CONTROLS).reduce((accu, key) => ({ ...accu, - [key]: LAYER_CONTROLS[key].value + [key]: HEXAGON_CONTROLS[key].value }), {}), - - // hoverInfo - x: 0, - y: 0, - hoveredObject: null, status: 'LOADING', - hour: null + selectedHour: null }; + this._resize = this._resize.bind(this); } componentDidMount() { this._processData(); - window.addEventListener('resize', this._resize.bind(this)); + window.addEventListener('resize', this._resize); this._resize(); } - + + componentWillUnmount() { + window.removeEventListener('resize', this._resize); + } _processData() { if (taxiData) { this.setState({status: 'LOADED'}); @@ -140,14 +105,22 @@ export default class App extends Component { } } - updateLayerSettings(settings) { - this.setState({settings}); + _onHighlight(highlightedHour) { + this.setState({highlightedHour}); } _onHover({x, y, object}) { this.setState({x, y, hoveredObject: object}); } + _onSelect(selectedHour) { + this.setState({selectedHour: + selectedHour === this.state.selectedHour ? + null : + selectedHour + }); + } + _onViewportChange(viewport) { this.setState({ viewport: {...this.state.viewport, ...viewport} @@ -161,43 +134,46 @@ export default class App extends Component { }); } + _updateLayerSettings(settings) { + this.setState({settings}); + } + render() { - const {viewport, hoveredObject, - points, settings, status, x, y, - highlightedHour, selectedHour - } = this.state; return (
- {hoveredObject && -
-
{hoveredObject.id}
+ {this.state.hoveredObject && +
+
{this.state.hoveredObject.id}
} - + {this.props.noControls ? null : this._updateLayerSettings(settings)} + />} { + this._onViewportChange(viewport); + }} mapboxApiAccessToken={MAPBOX_TOKEN}> + viewport={this.state.viewport} + data={this.state.points} + hour={this.state.highlightedHour || this.state.selectedHour} + onHover={hover => this._onHover(hover)} + {...this.state.settings} + /> this.setState({highlightedHour})} - select={(hour) => - this.setState({ - selectedHour: hour === this.state.selectedHour ? null : hour - }) - } + highlight={hour => this._onHighlight(hour)} + select={hour => this._onSelect(hour)} /> - +
); } diff --git a/src/demos/linking-it-all/deckgl-overlay.js b/src/demos/linking-it-all/deckgl-overlay.js index 1309c9e1..4384db63 100644 --- a/src/demos/linking-it-all/deckgl-overlay.js +++ b/src/demos/linking-it-all/deckgl-overlay.js @@ -1,17 +1,16 @@ import React, {Component} from 'react'; - import DeckGL, {ScatterplotLayer, HexagonLayer} from 'deck.gl'; const PICKUP_COLOR = [0, 128, 255]; const DROPOFF_COLOR = [255, 0, 128]; const HEATMAP_COLORS = [ - [213,62,79], - [252,141,89], - [254,224,139], - [230,245,152], - [153,213,148], - [50,136,189] + [213, 62, 79], + [252, 141, 89], + [254, 224, 139], + [230, 245, 152], + [153, 213, 148], + [50, 136, 189] ].reverse(); const LIGHT_SETTINGS = { @@ -27,66 +26,52 @@ const elevationRange = [0, 1000]; export default class DeckGLOverlay extends Component { - static get defaultViewport() { - return { - longitude: -74, - latitude: 40.7, - zoom: 11, - maxZoom: 16, - pitch: 0, - bearing: 0 - }; - } - _initialize(gl) { gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); } render() { - const {viewport, data, hour = null, onHover, settings} = this.props; - - if (!data) { + if (!this.props.data) { return null; } - const filteredData = hour === null ? data : - data.filter(d => d.hour === hour); + const filteredData = this.props.hour === null ? this.props.data : + this.props.data.filter(d => d.hour === this.props.hour); const layers = [ - !settings.showHexagon ? new ScatterplotLayer({ + !this.props.showHexagon ? new ScatterplotLayer({ id: 'scatterplot', - data: filteredData, getPosition: d => d.position, getColor: d => d.pickup ? PICKUP_COLOR : DROPOFF_COLOR, getRadius: d => 1, opacity: 0.5, pickable: true, - onHover, - radiusScale: 30, radiusMinPixels: 0.25, - radiusMaxPixels: 30 + radiusMaxPixels: 30, + ...this.props, + data: filteredData }) : null, - settings.showHexagon ? new HexagonLayer({ + this.props.showHexagon ? new HexagonLayer({ id: 'heatmap', colorRange: HEATMAP_COLORS, - coverage: settings.coverage, - data: filteredData, elevationRange, elevationScale: 10, extruded: true, getPosition: d => d.position, lightSettings: LIGHT_SETTINGS, - onHover, opacity: 1, pickable: true, - radius: settings.radius, - upperPercentile: settings.upperPercentile - }): null + ...this.props, + data: filteredData + }) : null ]; - return ( - - ); + return (); } } + diff --git a/src/demos/linking-it-all/layer-controls.js b/src/demos/linking-it-all/layer-controls.js index ee1b898d..f107a6cd 100644 --- a/src/demos/linking-it-all/layer-controls.js +++ b/src/demos/linking-it-all/layer-controls.js @@ -1,7 +1,58 @@ import React, {Component} from 'react'; import {layerControl} from './style'; -export default class LayerControls extends Component { +export const HEXAGON_CONTROLS = { + showHexagon: { + displayName: 'Show Hexagon', + type: 'boolean', + value: true + }, + radius: { + displayName: 'Hexagon Radius', + type: 'range', + value: 250, + step: 50, + min: 50, + max: 1000 + }, + coverage: { + displayName: 'Hexagon Coverage', + type: 'range', + value: 0.7, + step: 0.1, + min: 0, + max: 1 + }, + upperPercentile: { + displayName: 'Hexagon Upper Percentile', + type: 'range', + value: 100, + step: 0.1, + min: 80, + max: 100 + }, + radiusScale: { + displayName: 'Scatterplot Radius', + type: 'range', + value: 30, + step: 10, + min: 10, + max: 200 + } +}; + +export const SCATTERPLOT_CONTROLS = { + radiusScale: { + displayName: 'Scatterplot Radius', + type: 'range', + value: 30, + step: 10, + min: 10, + max: 200 + } +}; + +export class LayerControls extends Component { _onValueChange(settingName, newValue) { const {settings} = this.props; diff --git a/src/demos/linking-it-all/style.js b/src/demos/linking-it-all/style.js index 4bf9b5ac..3a9abc65 100644 --- a/src/demos/linking-it-all/style.js +++ b/src/demos/linking-it-all/style.js @@ -12,6 +12,9 @@ export const tooltipStyle = { export const layerControl = { borderRadius: 3, boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', + fontFamily: 'ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important', + fontSize: '12px', + lineHeight: 1.833, width: 200, position: 'absolute', top: '20px', @@ -25,6 +28,9 @@ export const charts = { background: 'white', borderRadius: 3, boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', + fontFamily: 'ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important', + fontSize: '12px', + lineHeight: 1.833, height: 210, padding: '10px', position: 'absolute', diff --git a/src/demos/scatterplot-overlay/app.js b/src/demos/scatterplot-overlay/app.js index 8af20a6d..3800d837 100644 --- a/src/demos/scatterplot-overlay/app.js +++ b/src/demos/scatterplot-overlay/app.js @@ -2,26 +2,15 @@ import React, {Component} from 'react'; import MapGL from 'react-map-gl'; import DeckGLOverlay from './deckgl-overlay'; -import LayerControls from './layer-controls'; +import {LayerControls, SCATTERPLOT_CONTROLS} from './layer-controls'; import Spinner from './spinner'; import {tooltipStyle} from './style'; -import taxiData from '../data/taxi.csv'; +import taxiData from '../data/taxi'; -const MAPBOX_STYLE = 'mapbox://styles/uberdata/cive485h000192imn6c6cc8fc'; +const MAPBOX_STYLE = 'mapbox://styles/mapbox/dark-v9'; // Set your mapbox token here const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line -const LAYER_CONTROLS = { - radiusScale: { - displayName: 'Scatterplot Radius', - type: 'range', - value: 30, - step: 10, - min: 10, - max: 200 - } -}; - export default class App extends Component { constructor(props) { @@ -36,9 +25,9 @@ export default class App extends Component { maxZoom: 16 }, points: [], - settings: Object.keys(LAYER_CONTROLS).reduce((accu, key) => ({ + settings: Object.keys(SCATTERPLOT_CONTROLS).reduce((accu, key) => ({ ...accu, - [key]: LAYER_CONTROLS[key].value + [key]: SCATTERPLOT_CONTROLS[key].value }), {}), // hoverInfo x: 0, @@ -46,16 +35,17 @@ export default class App extends Component { hoveredObject: null, status: 'LOADING' }; + this._resize = this._resize.bind(this); } componentDidMount() { - this._processData(this.props); - window.addEventListener('resize', this._resize.bind(this)); + this._processData(); + window.addEventListener('resize', this._resize); this._resize(); } - + componentWillUnmount() { - window.removeEventListener('resize', this._resize.bind(this)); + window.removeEventListener('resize', this._resize); } _processData() { @@ -80,10 +70,6 @@ export default class App extends Component { } } - updateLayerSettings(settings) { - this.setState({settings}); - } - _onHover({x, y, object}) { this.setState({x, y, hoveredObject: object}); } @@ -101,30 +87,38 @@ export default class App extends Component { }); } + _updateLayerSettings(settings) { + this.setState({settings}); + } + render() { - const {viewport, points, settings, status, x, y, hoveredObject} = this.state; return (
- {hoveredObject && -
-
{hoveredObject.id}
+ {this.state.hoveredObject && +
+
{this.state.hoveredObject.id}
} + settings={this.state.settings} + propTypes={SCATTERPLOT_CONTROLS} + onChange={settings => this._updateLayerSettings(settings)}/> this._onViewportChange(viewport)} mapboxApiAccessToken={MAPBOX_TOKEN}> + viewport={this.state.viewport} + data={this.state.points} + onHover={hover => this._onHover(hover)} + {...this.state.settings} + /> - +
); } diff --git a/src/demos/scatterplot-overlay/deckgl-overlay.js b/src/demos/scatterplot-overlay/deckgl-overlay.js index ea412fa2..453e6b8e 100644 --- a/src/demos/scatterplot-overlay/deckgl-overlay.js +++ b/src/demos/scatterplot-overlay/deckgl-overlay.js @@ -7,30 +7,26 @@ const DROPOFF_COLOR = [255, 0, 128]; export default class DeckGLOverlay extends Component { render() { - const {viewport, data, onHover, settings} = this.props; - - if (!data) { + if (!this.props.data) { return null; } const layers = [ new ScatterplotLayer({ id: 'scatterplot', - data, getPosition: d => d.position, getColor: d => d.pickup ? PICKUP_COLOR : DROPOFF_COLOR, getRadius: d => 1, opacity: 0.5, pickable: true, - onHover, - radiusScale: settings.radiusScale, radiusMinPixels: 0.25, - radiusMaxPixels: 30 + radiusMaxPixels: 30, + ...this.props }) ]; return ( - + ); } } diff --git a/src/demos/scatterplot-overlay/layer-controls.js b/src/demos/scatterplot-overlay/layer-controls.js index ee1b898d..f107a6cd 100644 --- a/src/demos/scatterplot-overlay/layer-controls.js +++ b/src/demos/scatterplot-overlay/layer-controls.js @@ -1,7 +1,58 @@ import React, {Component} from 'react'; import {layerControl} from './style'; -export default class LayerControls extends Component { +export const HEXAGON_CONTROLS = { + showHexagon: { + displayName: 'Show Hexagon', + type: 'boolean', + value: true + }, + radius: { + displayName: 'Hexagon Radius', + type: 'range', + value: 250, + step: 50, + min: 50, + max: 1000 + }, + coverage: { + displayName: 'Hexagon Coverage', + type: 'range', + value: 0.7, + step: 0.1, + min: 0, + max: 1 + }, + upperPercentile: { + displayName: 'Hexagon Upper Percentile', + type: 'range', + value: 100, + step: 0.1, + min: 80, + max: 100 + }, + radiusScale: { + displayName: 'Scatterplot Radius', + type: 'range', + value: 30, + step: 10, + min: 10, + max: 200 + } +}; + +export const SCATTERPLOT_CONTROLS = { + radiusScale: { + displayName: 'Scatterplot Radius', + type: 'range', + value: 30, + step: 10, + min: 10, + max: 200 + } +}; + +export class LayerControls extends Component { _onValueChange(settingName, newValue) { const {settings} = this.props; diff --git a/src/demos/scatterplot-overlay/style.js b/src/demos/scatterplot-overlay/style.js index 8279f9a4..3a9abc65 100644 --- a/src/demos/scatterplot-overlay/style.js +++ b/src/demos/scatterplot-overlay/style.js @@ -12,6 +12,9 @@ export const tooltipStyle = { export const layerControl = { borderRadius: 3, boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', + fontFamily: 'ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important', + fontSize: '12px', + lineHeight: 1.833, width: 200, position: 'absolute', top: '20px', @@ -22,15 +25,18 @@ export const layerControl = { }; export const charts = { - background: '#fff', + background: 'white', borderRadius: 3, boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', + fontFamily: 'ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important', + fontSize: '12px', + lineHeight: 1.833, height: 210, padding: '10px', position: 'absolute', left: 20, top: 20, - width: 300, + width: 500, zIndex: 100 }; @@ -46,4 +52,4 @@ export const spinner = { left: 60, transformOrigin: '20px 20px', zIndex: 110 -} +} \ No newline at end of file diff --git a/src/demos/starting-code/index.html b/src/demos/starting-code/index.html index 5853b6cf..6d847ad4 100644 --- a/src/demos/starting-code/index.html +++ b/src/demos/starting-code/index.html @@ -5,12 +5,24 @@ Visualization Tutorial - Starting Code +
diff --git a/src/demos/starting-code/layer-controls.js b/src/demos/starting-code/layer-controls.js new file mode 100644 index 00000000..f107a6cd --- /dev/null +++ b/src/demos/starting-code/layer-controls.js @@ -0,0 +1,140 @@ +import React, {Component} from 'react'; +import {layerControl} from './style'; + +export const HEXAGON_CONTROLS = { + showHexagon: { + displayName: 'Show Hexagon', + type: 'boolean', + value: true + }, + radius: { + displayName: 'Hexagon Radius', + type: 'range', + value: 250, + step: 50, + min: 50, + max: 1000 + }, + coverage: { + displayName: 'Hexagon Coverage', + type: 'range', + value: 0.7, + step: 0.1, + min: 0, + max: 1 + }, + upperPercentile: { + displayName: 'Hexagon Upper Percentile', + type: 'range', + value: 100, + step: 0.1, + min: 80, + max: 100 + }, + radiusScale: { + displayName: 'Scatterplot Radius', + type: 'range', + value: 30, + step: 10, + min: 10, + max: 200 + } +}; + +export const SCATTERPLOT_CONTROLS = { + radiusScale: { + displayName: 'Scatterplot Radius', + type: 'range', + value: 30, + step: 10, + min: 10, + max: 200 + } +}; + +export class LayerControls extends Component { + + _onValueChange(settingName, newValue) { + const {settings} = this.props; + // Only update if we have a confirmed change + if (settings[settingName] !== newValue) { + // Create a new object so that shallow-equal detects a change + const newSettings = { + ...this.props.settings, + [settingName]: newValue + }; + + this.props.onChange(newSettings); + } + } + + render() { + const {title, settings, propTypes = {}} = this.props; + + return ( +
+ {title &&

{title}

} + {Object.keys(settings).map(key => +
+ +
+ {settings[key]}
+ +
)} +
+ ); + } +} + +const Setting = props => { + const {propType} = props; + if (propType && propType.type) { + switch (propType.type) { + case 'range': + return ; + + case 'boolean': + return ; + default: + return ; + } + } +}; + +const Checkbox = ({settingName, value, onChange}) => { + return ( +
+
+ onChange(settingName, e.target.checked) }/> +
+
+ ); +}; + +const Slider = ({settingName, value, propType, onChange}) => { + + const {max = 100} = propType; + + return ( +
+
+
+ onChange(settingName, Number(e.target.value)) }/> +
+
+
+ ); +}; diff --git a/src/demos/starting-code/root.js b/src/demos/starting-code/root.js index 8183bdd9..6b8ba4d8 100644 --- a/src/demos/starting-code/root.js +++ b/src/demos/starting-code/root.js @@ -1,10 +1,15 @@ -/* global document */ import React from 'react'; import {render} from 'react-dom'; + import App from './app'; -const Root = () => ( -
-); +const element = document.getElementById('root'); + +render(, element); -render(, document.body.appendChild(document.createElement('div'))); +if (module.hot) { + module.hot.accept('./app', () => { + const Next = require('./app').default; + render(, element); + }); +} diff --git a/src/demos/starting-code/spinner.js b/src/demos/starting-code/spinner.js new file mode 100644 index 00000000..331bb0c1 --- /dev/null +++ b/src/demos/starting-code/spinner.js @@ -0,0 +1,29 @@ +import React from 'react'; +import {spinner} from './style'; + +const messages = { + LOADING: 'Loading data...', + LOADED: 'Processing data...', + READY: 'HI!' +} + +const speeds = new Array(20).fill(0).map((d, i) => ({speed: 2 - .025 * i, size: 5 + i * 0.2})); + +export default function Spinner({status}) { + + return (
+ {speeds.map((s, i) => ( +
))} +
+ {messages[status]}
+
) +} diff --git a/src/demos/starting-code/style.js b/src/demos/starting-code/style.js new file mode 100644 index 00000000..3a9abc65 --- /dev/null +++ b/src/demos/starting-code/style.js @@ -0,0 +1,55 @@ +export const tooltipStyle = { + position: 'absolute', + padding: '4px', + background: 'rgba(0, 0, 0, 0.8)', + color: '#fff', + maxWidth: '300px', + fontSize: '10px', + zIndex: 9, + pointerEvents: 'none' +}; + +export const layerControl = { + borderRadius: 3, + boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', + fontFamily: 'ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important', + fontSize: '12px', + lineHeight: 1.833, + width: 200, + position: 'absolute', + top: '20px', + right: '20px', + padding: '20px', + zIndex: 100, + background: 'white' +}; + +export const charts = { + background: 'white', + borderRadius: 3, + boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)', + fontFamily: 'ff-clan-web-pro, "Helvetica Neue", Helvetica, sans-serif !important', + fontSize: '12px', + lineHeight: 1.833, + height: 210, + padding: '10px', + position: 'absolute', + left: 20, + top: 20, + width: 500, + zIndex: 100 +}; + +export const spinner = { + animation: 'linear 2s infinite', + background: '#1EACC7', + borderRadius: '50%', + height: 10, + width: 10, + opacity: 0.4, + position: 'absolute', + top: 70, + left: 60, + transformOrigin: '20px 20px', + zIndex: 110 +} \ No newline at end of file diff --git a/src/demos/starting-with-map/app.js b/src/demos/starting-with-map/app.js index 06ee3b69..e6ad5cfe 100644 --- a/src/demos/starting-with-map/app.js +++ b/src/demos/starting-with-map/app.js @@ -2,7 +2,7 @@ import React, {Component} from 'react'; import MapGL from 'react-map-gl'; -const MAPBOX_STYLE = 'mapbox://styles/uberdata/cive485h000192imn6c6cc8fc'; +const MAPBOX_STYLE = 'mapbox://styles/mapbox/dark-v9'; // Set your mapbox token here const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line @@ -20,15 +20,16 @@ export default class App extends Component { maxZoom: 16 } }; + this._resize = this._resize.bind(this); } componentDidMount() { - window.addEventListener('resize', this._resize.bind(this)); + window.addEventListener('resize', this._resize); this._resize(); } - + componentWillUnmount() { - window.removeEventListener('resize', this._resize.bind(this)); + window.removeEventListener('resize', this._resize); } _onViewportChange(viewport) { @@ -45,14 +46,14 @@ export default class App extends Component { } render() { - const {viewport} = this.state; return (
+ onViewportChange={viewport => this._onViewportChange.bind(viewport)} + mapboxApiAccessToken={MAPBOX_TOKEN}> +
); } diff --git a/src/docs/deckgl/hexagon-overlay.md b/src/docs/deckgl/hexagon-overlay.md index 82e9566e..6acfac51 100644 --- a/src/docs/deckgl/hexagon-overlay.md +++ b/src/docs/deckgl/hexagon-overlay.md @@ -11,9 +11,17 @@ points, we need a layer that can aggregate points into a geo grid. `HexagonLayer` and `GridLayer` are both aggregation layers that can visualize a distribution heatmap from raw points. -The functional change that's needed here is to add more code to `deckgl-overlay.js` +## 1. Update our control panel +We're going to upgrade our control panel so we can switch from the scatterplot layer to the hexagon layer. Let's do that now, so you can see the changes on the hexagon layer as we build it. -## 1. Add Constants for Hexagon Layer +Replace everywhere in app.js SCATTERPLOT_CONTROLS by HEXAGON_CONTROLS. It appears 4 times: +- in the import statement, +- in the component's contructor method, while preparing the initial state, +- in the render method, as an argument to the LayerControls component. + +Now, to implement our new overlay, let's focus on `deckgl-overlay.js`: + +## 2. Add Constants for Hexagon Layer Deck.gl performances shallow compares on layer props to decide how to update attribute buffer. To avoid unnecessary re-calculations, we define constant params outside of the render function. @@ -43,7 +51,7 @@ const LIGHT_SETTINGS = { const elevationRange = [0, 1000]; ``` -## 2. Add Hexagon Layer +## 3. Add Hexagon Layer We have already passed the necessary data into this component from the previous example. So now we only need to take care of rendering the `HexagonLayer` when needed. @@ -61,47 +69,41 @@ export default class DeckGLOverlay extends Component { } render() { - const {viewport, data, onHover, settings} = this.props; - - if (!data) { + if (!this.props.data) { return null; } const layers = [ - !settings.showHexagon ? new ScatterplotLayer({ + !this.props.showHexagon ? new ScatterplotLayer({ id: 'scatterplot', - data, getPosition: d => d.position, getColor: d => d.pickup ? PICKUP_COLOR : DROPOFF_COLOR, getRadius: d => 1, opacity: 0.5, pickable: true, - onHover, - radiusScale: settings.radiusScale, radiusMinPixels: 0.25, - radiusMaxPixels: 30 + radiusMaxPixels: 30, + ...this.props }) : null, - settings.showHexagon ? new HexagonLayer({ + this.props.showHexagon ? new HexagonLayer({ id: 'heatmap', colorRange: HEATMAP_COLORS, - coverage: settings.coverage, - data, elevationRange, elevationScale: 10, extruded: true, getPosition: d => d.position, lightSettings: LIGHT_SETTINGS, - onHover, opacity: 1, pickable: true, - radius: settings.radius, - upperPercentile: settings.upperPercentile + ...this.props }) : null ]; - return ( - - ); + return (); } } ``` @@ -133,7 +135,3 @@ Hexagon cells with value larger than upperPercentile will be hidden ##### `pickable` {Bool} Indicates whether this layer would be interactive. - -## 3. Adding Polish - -To get the completed example, you need to modify the settings panel that was added in the polish step in the previous example. Check out the source code for more info. diff --git a/src/docs/deckgl/scatterplot-overlay.md b/src/docs/deckgl/scatterplot-overlay.md index 5e73c3c7..6a94952f 100644 --- a/src/docs/deckgl/scatterplot-overlay.md +++ b/src/docs/deckgl/scatterplot-overlay.md @@ -23,7 +23,7 @@ and then import the file into your `app.js` component. If you cloned our tutorial repo as-is, your import statement should look like this: ```js -import taxiData from '../data/taxi.csv'; +import taxiData from '../data/taxi'; ``` Now we need to process this data into a usable format. Since we are only going @@ -63,6 +63,7 @@ export default class App extends Component { } } + // ... } ``` @@ -78,16 +79,14 @@ import DeckGL, {ScatterplotLayer} from 'deck.gl'; export default class DeckGLOverlay extends Component { render() { - const {viewport, data} = this.props; - - if (!data) { + if (!this.props.data) { return null; } const layers = []; return ( - + ); } } @@ -99,11 +98,9 @@ to render our `deck.gl` overlay. You'll notice that `layers` is being passed to separately. Let's edit the function and initialize a `ScatterplotLayer` in `render()` function. ```js - const layers = [ new ScatterplotLayer({ id: 'scatterplot', - data, getPosition: d => d.position, getColor: d => [0, 128, 255], getRadius: d => 1, @@ -111,10 +108,10 @@ separately. Let's edit the function and initialize a `ScatterplotLayer` in `rend pickable: false, radiusScale: 3, radiusMinPixels: 0.25, - radiusMaxPixels: 30 + radiusMaxPixels: 30, + ...this.props }) ]; - ```` Once we add the code to initialize a `ScatterplotLayer`, we will have @@ -123,13 +120,11 @@ the dots by `pickup` or `dropoff`. First lets define colors outside the componen under the imports. ```js - import React, {Component} from 'react'; import DeckGL, {ScatterplotLayer} from 'deck.gl'; const PICKUP_COLOR = [0, 128, 255]; const DROPOFF_COLOR = [255, 0, 128]; - ``` Then let's edit our `ScatterplotLayer` to have the color depends on pickup or dropoff by changing @@ -166,18 +161,16 @@ import DeckGLOverlay from './deckgl-overlay'; export default class App extends Component { render() { - const {viewport, points} = this.state; return (
+ viewport={this.state.viewport} + data={this.state.points} />
); } - } ``` @@ -189,3 +182,136 @@ data as a scatterplot overlay. If you check out the source code for this step, you'll see extra code that add functionalities such as a settings panel, hover handler, hover tooltip, and loading spinner. + +The control for the settings panel is already provided in your starting code. It's a typical React component, so there's no use going through the details in this tutorial. + + +Here's the complete app.js file including the control panel: + +```js +/* global window */ +import React, {Component} from 'react'; +import MapGL from 'react-map-gl'; +import DeckGLOverlay from './deckgl-overlay'; +import {LayerControls, SCATTERPLOT_CONTROLS} from './layer-controls'; +import Spinner from './spinner'; +import {tooltipStyle} from './style'; +import taxiData from '../data/taxi'; + +const MAPBOX_STYLE = 'mapbox://styles/mapbox/dark-v9'; +// Set your mapbox token here +const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line + +export default class App extends Component { + + constructor(props) { + super(props); + this.state = { + viewport: { + width: window.innerWidth, + height: window.innerHeight, + longitude: -74, + latitude: 40.7, + zoom: 11, + maxZoom: 16 + }, + points: [], + settings: Object.keys(SCATTERPLOT_CONTROLS).reduce((accu, key) => ({ + ...accu, + [key]: SCATTERPLOT_CONTROLS[key].value + }), {}), + // hoverInfo + x: 0, + y: 0, + hoveredObject: null, + status: 'LOADING' + }; + this._resize = this._resize.bind(this); + } + + componentDidMount() { + this._processData(); + window.addEventListener('resize', this._resize); + this._resize(); + } + + componentWillUnmount() { + window.removeEventListener('resize', this._resize); + } + + _processData() { + if (taxiData) { + this.setState({status: 'LOADED'}); + const points = taxiData.reduce((accu, curr) => { + accu.push({ + position: [Number(curr.pickup_longitude), Number(curr.pickup_latitude)], + pickup: true + }); + + accu.push({ + position: [Number(curr.dropoff_longitude), Number(curr.dropoff_latitude)], + pickup: false + }); + return accu; + }, []); + this.setState({ + points, + status: 'READY' + }); + } + } + + _onHover({x, y, object}) { + this.setState({x, y, hoveredObject: object}); + } + + _onViewportChange(viewport) { + this.setState({ + viewport: {...this.state.viewport, ...viewport} + }); + } + + _resize() { + this._onViewportChange({ + width: window.innerWidth, + height: window.innerHeight + }); + } + + _updateLayerSettings(settings) { + this.setState({settings}); + } + + render() { + return ( +
+ {this.state.hoveredObject && +
+
{this.state.hoveredObject.id}
+
} + this._updateLayerSettings(settings)}/> + this._onViewportChange(viewport)} + mapboxApiAccessToken={MAPBOX_TOKEN}> + this._onHover(hover)} + {...this.state.settings} + /> + + +
+ ); + } +} +``` diff --git a/src/docs/mapping/hexagon.md b/src/docs/mapping/hexagon.md index 4bf26cb7..e36ef5af 100644 --- a/src/docs/mapping/hexagon.md +++ b/src/docs/mapping/hexagon.md @@ -38,7 +38,7 @@ const layers = [ ]; ``` ### Color Value -By default, hexagon color is based on number of points it containst. `getColorValue` gives you the option to color the hexagons based +By default, hexagon color is based on number of points it contains. `getColorValue` gives you the option to color the hexagons based on your choice of value aggregation. For example, You can pass in getColorValue to color the bins by avg/mean/max of a specific attributes of each point.

diff --git a/src/docs/react-vis/basic.md b/src/docs/react-vis/basic.md index 7ad676a2..3e09fd99 100644 --- a/src/docs/react-vis/basic.md +++ b/src/docs/react-vis/basic.md @@ -41,7 +41,7 @@ in your app.js file, change your _processData method by this one: pickup: false }); } - + const prevPickups = accu.pickupObj[pickupHour] || 0; const prevDropoffs = accu.dropoffObj[dropoffHour] || 0; @@ -103,28 +103,33 @@ towards the top of the file with your other imports, and update the render metho ```js render() { - const {viewport, points, settings, status} = this.state; - return (

- {this._renderTooltip()} + {this.state.hoveredObject && +
+
{this.state.hoveredObject.id}
+
} + settings={this.state.settings} + propTypes={HEXAGON_CONTROLS} + onChange={settings => this._updateLayerSettings(settings)}/> this._onViewportChange(viewport)} mapboxApiAccessToken={MAPBOX_TOKEN}> + viewport={this.state.viewport} + data={this.state.points} + onHover={hover => this._onHover(hover)} + settings={this.state.settings}/> - +
); } @@ -162,7 +167,6 @@ export default function Charts({pickups}) {
); } - ``` This code produces this output: @@ -187,7 +191,7 @@ We can do that by changing the way the ticks are represented in the axes. ```js (d / 100).toFixed(0) + '%'} + tickFormat={d => (d / 100).toFixed(0) + '%'} /> ``` @@ -249,10 +253,10 @@ Finally, we can set *tickInnerSize* to 0 to only have ticks going from the axis ```js (h % 24) >= 12 ? + tickFormat={h => (h % 24) >= 12 ? (h % 12 || 12) + 'PM' : - (h % 12 || 12) + 'AM'; - }} + (h % 12 || 12) + 'AM' + } tickSizeInner={0} tickValues={[0, 6, 12, 18, 24]} /> @@ -315,17 +319,17 @@ export default function Charts({pickups}) { yDomain={[0, 1000]} > (d / 100).toFixed(0) + '%'} + tickFormat={d => (d / 100).toFixed(0) + '%'} /> (h % 24) >= 12 ? + tickFormat={h => (h % 24) >= 12 ? (h % 12 || 12) + 'PM' : - (h % 12 || 12) + 'AM'; - }} + (h % 12 || 12) + 'AM' + } tickSizeInner={0} tickValues={[0, 6, 12, 18, 24]} /> diff --git a/src/docs/react-vis/interactions.md b/src/docs/react-vis/interactions.md index 8a45fc31..bde47db6 100644 --- a/src/docs/react-vis/interactions.md +++ b/src/docs/react-vis/interactions.md @@ -7,16 +7,18 @@ React-Vis has many methods to [handle interaction](http://uber.github.io/react-vis/#/documentation/general-principles/interaction). We're already using the state of our app to store our data and interaction with the deck.gl components, so let's use it for react-vis interaction as well. -In app.js, let's add this property to our initial state, +In app.js, let's create a method to handle this interaction: ```js -constructor(props) { +export default class App extends Component { - super(props); - this.state = { - // all previous properties - highlightedHour: null - }; +// ... + +_onHighlight(highlightedHour) { + this.setState({highlightedHour}); +} + +// ... } ``` @@ -25,7 +27,7 @@ then in the render method: ```js this.setState({highlightedHour})} + highlight={hour => this._onHighlight(hour)} /> ``` @@ -47,8 +49,8 @@ Then, before the return statement: ```js const data = pickups.map(d => ({ - ...d, color: d.hour === selectedHour ? '#19CDD7' : '#125C77' -}); + ...d, color: d.hour === highlightedHour ? '#19CDD7' : '#125C77' +})); ``` And finally, in the VerticalBarSeries component: @@ -57,11 +59,11 @@ And finally, in the VerticalBarSeries component: highlight(d.hour)} + onValueMouseOver={d => highlight(d.hour)} /> ``` - + We are getting which hour is highlighted from the state of the parent component, and a callback method to change it. @@ -75,23 +77,6 @@ onValueMouseOver passes the object which corresponds to the mark which is highli I also changed the colorType to be "literal". There are many ways to pass color to a react-vis series, but if we pass explicit color values in the dataset, we must signal it to the series. -```js - - - - (d % 24) >= 12 ? (d % 12 || 12) + 'PM' : (d % 12 || 12) + 'AM'} - /> - (d / 100).toFixed(0) + '%'} - /> - -``` - ## 2. Fine-tuning: handling mousing out of the chart and clicks. For now the last highlighted bar remains highlighted even if the cursor leaves the chart. We can fix that by adding the following property to XYPlot: @@ -108,13 +93,19 @@ But eventually we'd like to leave a bar selected while we mouse over elsewhere o Let's go back to app.js and make the following changes: ```js -constructor(props) { +export default class App extends Component { + +// ... - super(props); - this.state = { - // all previous properties - selectedHour: null - }; + _onSelect(selectedHour) { + this.setState({selectedHour: + selectedHour === this.state.selectedHour ? + null : + selectedHour + }); + } + +// ... } ``` @@ -123,12 +114,8 @@ and in the render method: ```js this.setState({highlightedHour})} - select={(hour) => - this.setState({ - selectedHour: hour === this.state.selectedHour ? null : hour - }) - } + highlight={hour => this._onHighlight(hour)} + select={hour => this._onSelect(hour)} /> ``` @@ -163,8 +150,8 @@ And in the VerticalBarSeries component: highlight(d.hour)} - onValueClick={(d) => select(d.hour)} + onValueMouseOver={d => highlight(d.hour)} + onValueClick={d => select(d.hour)} style={{cursor: 'pointer'}} /> ``` diff --git a/src/docs/react-vis/linking-it-all.md b/src/docs/react-vis/linking-it-all.md index dadf090d..95c07af6 100644 --- a/src/docs/react-vis/linking-it-all.md +++ b/src/docs/react-vis/linking-it-all.md @@ -4,26 +4,34 @@ For our grand finale, we're going to link interaction on the charts with the deck.gl overlays! -In our app.js in the render method, let's make the following two changes: +In our app.js: + +let's add selectedHour to the default state: ```js -const { - viewport, - hoveredObject, - points, - settings, - status, - x, y, - highlightedHour, - selectedHour -} = this.state; + constructor(props) { + + // ... + + this.state = { + // ... + selectedHour: null + }; + + // ... + + } +``` +Then, in the render method, let's add an "hour" property to DeckGLOverlay: + +```js this._onHover(hover)} + settings={this.state.settings} /> ``` @@ -32,39 +40,36 @@ And in our deckgl-overlay.js file, let's make these changes: We're creting filteredData which is only the datapoints which correspond to the time slot highlighted or selected on the bar chart, and we're replacing data by filtered data in the layers. ```js -const filteredData = hour === null ? data : data.filter(d => d.hour === hour); + const filteredData = this.props.hour === null ? this.props.data : + this.props.data.filter(d => d.hour === this.props.hour); -const layers = [ - !settings.showHexagon ? new ScatterplotLayer({ - id: 'scatterplot', - data: filteredData, - getPosition: d => d.position, - getColor: d => d.pickup ? PICKUP_COLOR : DROPOFF_COLOR, - getRadius: d => 1, - opacity: 0.5, - pickable: true, - onHover, - radiusScale: 30, - radiusMinPixels: 0.25, - radiusMaxPixels: 30 - }) : null, - settings.showHexagon ? new HexagonLayer({ - id: 'heatmap', - colorRange: HEATMAP_COLORS, - coverage: settings.coverage, - data: filteredData, - elevationRange, - elevationScale: 10, - extruded: true, - getPosition: d => d.position, - lightSettings: LIGHT_SETTINGS, - onHover, - opacity: 1, - pickable: true, - radius: settings.radius, - upperPercentile: settings.upperPercentile - }): null -]; + const layers = [ + !this.props.showHexagon ? new ScatterplotLayer({ + id: 'scatterplot', + getPosition: d => d.position, + getColor: d => d.pickup ? PICKUP_COLOR : DROPOFF_COLOR, + getRadius: d => 1, + opacity: 0.5, + pickable: true, + radiusMinPixels: 0.25, + radiusMaxPixels: 30, + ...this.props, + data: filteredData + }) : null, + this.props.showHexagon ? new HexagonLayer({ + id: 'heatmap', + colorRange: HEATMAP_COLORS, + elevationRange, + elevationScale: 10, + extruded: true, + getPosition: d => d.position, + lightSettings: LIGHT_SETTINGS, + opacity: 1, + pickable: true, + ...this.props, + data: filteredData + }) : null + ]; ``` And as you can see, interactions on the bar chart are now visible on our deck.gl part! diff --git a/src/docs/setup.md b/src/docs/setup.md index 62bfa0e3..7f522c19 100644 --- a/src/docs/setup.md +++ b/src/docs/setup.md @@ -28,6 +28,10 @@ npm start A page should automatically be opened in your browser, with a pretty simple app (for now!). +Note that the repository you cloned also contains all the content and code of this tutorial, and you may run it locally if you want (repeat the last two steps from above from vis-tutorial/). + +In the pages of this tutorial, you can use the back quote key ` (typically located below ESC) to see the code examples in full page. + ## 3. Start Coding! You can now open your text editor with the following file: diff --git a/src/docs/starting-with-map.md b/src/docs/starting-with-map.md index cd75259e..af7c8843 100644 --- a/src/docs/starting-with-map.md +++ b/src/docs/starting-with-map.md @@ -17,9 +17,7 @@ highly recommended that you do so, or your application might not work. The app component in the starting code above currently looks like this: ```js -/* global window */ -import React, {Component} from 'react'; - +... export default class App extends Component { constructor(props) { @@ -42,6 +40,7 @@ above, and not the whole thing. `react-map-gl` requires a viewport that specifies the dimension, location, and basic settings of the map, so let's give ourselves a set of defaults: ```js +... export default class App extends Component { constructor(props) { @@ -74,11 +73,10 @@ import MapGL from 'react-map-gl'; export default class App extends Component { render() { - const {viewport} = this.state; return (
@@ -110,6 +108,7 @@ export default class App extends Component { viewport: {...this.state.viewport, ...viewport} }); } + } ``` @@ -120,7 +119,7 @@ return ( this._onViewportChange(viewport)} // ... />
@@ -137,14 +136,24 @@ Let's quickly add a resize handler that updates our viewport with the new dimens ```js export default class App extends Component { + constructor(props) { + //... + this._resize = this._resize.bind(this); + } + componentDidMount() { - window.addEventListener('resize', this._resize.bind(this)); + window.addEventListener('resize', this._resize); this._resize(); } + componentWillUnmount() { + window.removeEventListener('resize', this._resize); + } + _resize() { this._onViewportChange({ - width: window.innerWidth + width: window.innerWidth, + height: window.innerHeight }); } @@ -157,9 +166,14 @@ where viewport state is actually being updated. Our completed component should now look like this: ```js +/* global window */ import React, {Component} from 'react'; import MapGL from 'react-map-gl'; +const MAPBOX_STYLE = 'mapbox://styles/mapbox/dark-v9'; +// Set your mapbox token here +const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line + export default class App extends Component { constructor(props) { @@ -174,12 +188,17 @@ export default class App extends Component { maxZoom: 16 } }; + this._resize = this._resize.bind(this); } componentDidMount() { - window.addEventListener('resize', this._resize.bind(this)); + window.addEventListener('resize', this._resize); this._resize(); } + + componentWillUnmount() { + window.removeEventListener('resize', this._resize); + } _onViewportChange(viewport) { this.setState({ @@ -195,18 +214,17 @@ export default class App extends Component { } render() { - const {viewport} = this.state; return (
this._onViewportChange.bind(viewport)} mapboxApiAccessToken={MAPBOX_TOKEN}>
); } - } ``` diff --git a/src/styles/_index.scss b/src/styles/_index.scss index ddaad49a..9cb1827d 100644 --- a/src/styles/_index.scss +++ b/src/styles/_index.scss @@ -1 +1,11 @@ @import "~react-vis/dist/main.scss"; + +.Hero { + .btn { + background-color: rgba(0, 0, 0, 0.2); + } + h2 { + margin-bottom: 0; + font-size: 1rem; + } +}