mirror of
https://github.com/visgl/react-map-gl.git
synced 2025-12-08 20:16:02 +00:00
open source
This commit is contained in:
commit
d8047648ea
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
npm-debug.log
|
||||
102
LICENSE
Normal file
102
LICENSE
Normal file
@ -0,0 +1,102 @@
|
||||
Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
This contains code from MapboxGL-js
|
||||
|
||||
Copyright (c) 2014, Mapbox
|
||||
|
||||
All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of Mapbox GL JS nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Contains glmatrix.js
|
||||
|
||||
Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Contains Hershey Simplex Font: http://paulbourke.net/dataformats/hershey/
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Contains code from glfx.js
|
||||
|
||||
Copyright (C) 2011 by Evan Wallace
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
88
README.md
Normal file
88
README.md
Normal file
@ -0,0 +1,88 @@
|
||||
# react-map-gl
|
||||
|
||||
react-map-gl provides a [React](http://facebook.github.io/react/) friendly
|
||||
API wrapper around [Mapbox GL JS](https://www.mapbox.com/mapbox-gl-js/). A webGL
|
||||
based vector tile mapping library.
|
||||
|
||||
WARNING: This project is still very new and the API may change. There also may
|
||||
be Mapbox APIs that haven't yet been exposed.
|
||||
|
||||

|
||||
|
||||
## Overview
|
||||
|
||||
react-map-gl provides an `overlay` API so you can add visualization overlays.
|
||||
|
||||
Supported Overlays:
|
||||
|
||||
1. ChoroplethOverlay
|
||||
2. ScatterplotOverlay
|
||||
3. DraggablePointsOverlay
|
||||
4. SVGOverlay
|
||||
5. CanvasOverlay
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm install react-map-gl --save
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
````js
|
||||
<MapGL width={400} height={400} latitude={37.7577} longitude={-122.4376}
|
||||
zoom={8} onChangeViewport={function(opts) {
|
||||
// opts = {latitude, longitude, zoom, bbox}
|
||||
}}
|
||||
/>
|
||||
````
|
||||
|
||||
### Using overlays
|
||||
|
||||
````js
|
||||
<MapGL {...mapProps}>
|
||||
<ScatterplotOverlay locations={locations} dotRadius={4} globalOpacity={1}
|
||||
compositeOperation="screen" />
|
||||
// Add additional overlays here...
|
||||
])
|
||||
````
|
||||
|
||||
|
||||
### ImmutableJS all the things
|
||||
|
||||
The `mapStyle` property of the `MapGL` as well as several of the built in
|
||||
overlay properties must be provided as
|
||||
[ImmutableJS](https://facebook.github.io/immutable-js/) objects. This allows
|
||||
the library to be fast since computing changes to props only involves checking
|
||||
if the immutable objects are the same instance.
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
To develop on this component, install the dependencies and then build and watch
|
||||
the static files.
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
```
|
||||
|
||||
To serve example app:
|
||||
|
||||
```bash
|
||||
$ npm start &
|
||||
$ open "http://localhost:9966/?access_token="`echo $MapboxAccessToken`
|
||||
```
|
||||
|
||||
Where `echo $MapboxAccessToken` returns your Mapbox access token.
|
||||
|
||||
Once complete, you can view the component in your browser at
|
||||
[localhost:9966](http://localhost:9966). Any changes you make will automatically
|
||||
run the compiler to build the files again.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This project is not affiliated with either Facebook or Mapbox.
|
||||
|
||||
## Example Data
|
||||
|
||||
1. SF GeoJSON data from: [SF OpenData](http://data.sfgov.org).
|
||||
12
example/data/cities.json
Normal file
12
example/data/cities.json
Normal file
@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"cityName": "San Francisco",
|
||||
"latitude": 37.7749,
|
||||
"longitude": -122.4194
|
||||
},
|
||||
{
|
||||
"cityName": "Pittsburgh",
|
||||
"latitude": 40.4406,
|
||||
"longitude": -79.9959
|
||||
}
|
||||
]
|
||||
65384
example/data/feature-example-sf.json
Normal file
65384
example/data/feature-example-sf.json
Normal file
File diff suppressed because it is too large
Load Diff
274
example/data/routes-example.json
Normal file
274
example/data/routes-example.json
Normal file
@ -0,0 +1,274 @@
|
||||
[
|
||||
{
|
||||
"coordinates": [
|
||||
[
|
||||
37.794219992184324,
|
||||
-122.3933557873733
|
||||
],
|
||||
[
|
||||
37.79398165755795,
|
||||
-122.39367189820892
|
||||
],
|
||||
[
|
||||
37.79388241198406,
|
||||
-122.39401838092769
|
||||
],
|
||||
[
|
||||
37.794423228499795,
|
||||
-122.3947071294908
|
||||
],
|
||||
[
|
||||
37.79445423060049,
|
||||
-122.39494688373745
|
||||
],
|
||||
[
|
||||
37.793858299053696,
|
||||
-122.39571409732676
|
||||
],
|
||||
[
|
||||
37.79245284608079,
|
||||
-122.39741853206209
|
||||
],
|
||||
[
|
||||
37.79106459055073,
|
||||
-122.39918399515115
|
||||
],
|
||||
[
|
||||
37.79035713167143,
|
||||
-122.40003607642596
|
||||
],
|
||||
[
|
||||
37.790068858430104,
|
||||
-122.40213357823212
|
||||
],
|
||||
[
|
||||
37.78983463559349,
|
||||
-122.40388909604812
|
||||
],
|
||||
[
|
||||
37.78960041201444,
|
||||
-122.4054166245374
|
||||
],
|
||||
[
|
||||
37.78920403195862,
|
||||
-122.40869967084274
|
||||
],
|
||||
[
|
||||
37.788789632353996,
|
||||
-122.41193711928267
|
||||
],
|
||||
[
|
||||
37.788429282982534,
|
||||
-122.41526576345333
|
||||
],
|
||||
[
|
||||
37.78799686141706,
|
||||
-122.41848041296066
|
||||
],
|
||||
[
|
||||
37.78667030140305,
|
||||
-122.42820266442968
|
||||
],
|
||||
[
|
||||
37.785191419954316,
|
||||
-122.43987587617389
|
||||
],
|
||||
[
|
||||
37.78061374219536,
|
||||
-122.43880657433475
|
||||
],
|
||||
[
|
||||
37.777866999452826,
|
||||
-122.43827192341516
|
||||
],
|
||||
[
|
||||
37.776704885260315,
|
||||
-122.44669267539851
|
||||
],
|
||||
[
|
||||
37.7757540509692,
|
||||
-122.44660356691189
|
||||
],
|
||||
[
|
||||
37.77476798767644,
|
||||
-122.45453422221897
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"coordinates": [
|
||||
[
|
||||
37.75251037780083,
|
||||
-122.44080380201595
|
||||
],
|
||||
[
|
||||
37.7523902843234,
|
||||
-122.4425396821639
|
||||
],
|
||||
[
|
||||
37.75281918870459,
|
||||
-122.44225217701438
|
||||
],
|
||||
[
|
||||
37.75321806754765,
|
||||
-122.44200806886859
|
||||
],
|
||||
[
|
||||
37.753471118602434,
|
||||
-122.44180193310093
|
||||
],
|
||||
[
|
||||
37.75388286084137,
|
||||
-122.44130829218389
|
||||
],
|
||||
[
|
||||
37.75459482638962,
|
||||
-122.44084177439416
|
||||
],
|
||||
[
|
||||
37.7546745592597,
|
||||
-122.44071982406115
|
||||
],
|
||||
[
|
||||
37.75508766756418,
|
||||
-122.44044034714439
|
||||
],
|
||||
[
|
||||
37.75567049861364,
|
||||
-122.44012846710689
|
||||
],
|
||||
[
|
||||
37.755907472562626,
|
||||
-122.44005961047523
|
||||
],
|
||||
[
|
||||
37.75640703677898,
|
||||
-122.44015276944751
|
||||
],
|
||||
[
|
||||
37.756528133045606,
|
||||
-122.44018924596475
|
||||
],
|
||||
[
|
||||
37.75666225134219,
|
||||
-122.44033181327828
|
||||
],
|
||||
[
|
||||
37.75680778368681,
|
||||
-122.44070537573275
|
||||
],
|
||||
[
|
||||
37.75725150991822,
|
||||
-122.44023039641965
|
||||
],
|
||||
[
|
||||
37.757200599225015,
|
||||
-122.43988427866199
|
||||
],
|
||||
[
|
||||
37.75708923196163,
|
||||
-122.4395582840298
|
||||
],
|
||||
[
|
||||
37.757098777733574,
|
||||
-122.43904715664357
|
||||
],
|
||||
[
|
||||
37.75743287897778,
|
||||
-122.4390310581432
|
||||
],
|
||||
[
|
||||
37.757496186354615,
|
||||
-122.43797228289426
|
||||
],
|
||||
[
|
||||
37.75758520091664,
|
||||
-122.43690020835358
|
||||
],
|
||||
[
|
||||
37.75763591465913,
|
||||
-122.43582256860111
|
||||
],
|
||||
[
|
||||
37.75773734204003,
|
||||
-122.43356465673874
|
||||
],
|
||||
[
|
||||
37.7578184838447,
|
||||
-122.43248701698633
|
||||
],
|
||||
[
|
||||
37.75940073124414,
|
||||
-122.4326537945671
|
||||
],
|
||||
[
|
||||
37.76103365621418,
|
||||
-122.43275642692444
|
||||
],
|
||||
[
|
||||
37.76182474975916,
|
||||
-122.43285905928187
|
||||
],
|
||||
[
|
||||
37.762663828581225,
|
||||
-122.43293905683697
|
||||
],
|
||||
[
|
||||
37.764194972084184,
|
||||
-122.43313584578942
|
||||
],
|
||||
[
|
||||
37.76511200542191,
|
||||
-122.43195511207469
|
||||
],
|
||||
[
|
||||
37.76584889897153,
|
||||
-122.4310229538788
|
||||
],
|
||||
[
|
||||
37.766757724237436,
|
||||
-122.42984222016415
|
||||
],
|
||||
[
|
||||
37.76783028743526,
|
||||
-122.4285786279431
|
||||
],
|
||||
[
|
||||
37.76905839413027,
|
||||
-122.42690074319054
|
||||
],
|
||||
[
|
||||
37.77008998799232,
|
||||
-122.42561643634292
|
||||
],
|
||||
[
|
||||
37.77093326434769,
|
||||
-122.42458070501422
|
||||
],
|
||||
[
|
||||
37.77189114912355,
|
||||
-122.42341032861272
|
||||
],
|
||||
[
|
||||
37.77302094598606,
|
||||
-122.42198101937899
|
||||
],
|
||||
[
|
||||
37.773970616950706,
|
||||
-122.42074849909775
|
||||
],
|
||||
[
|
||||
37.774813849048584,
|
||||
-122.41969205314248
|
||||
],
|
||||
[
|
||||
37.77560795203675,
|
||||
-122.41863560718714
|
||||
],
|
||||
[
|
||||
37.77623831637716,
|
||||
-122.41794166719687
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
91
example/examples/choropleth.react.js
Normal file
91
example/examples/choropleth.react.js
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
'use strict';
|
||||
|
||||
var assign = require('object-assign');
|
||||
var React = require('react');
|
||||
var r = require('r-dom');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var MapGL = require('../../src/index.js');
|
||||
var ChoroplethOverlay = require('../../src/overlays/choropleth.react');
|
||||
|
||||
// San Francisco
|
||||
var location = require('./../data/cities.json')[0];
|
||||
var _zipcodesSF = require('./../data/feature-example-sf.json');
|
||||
|
||||
_zipcodesSF.features.forEach(function _forEach(feature) {
|
||||
feature.properties.value = Math.random() * 1000;
|
||||
});
|
||||
|
||||
var ZIPCODES_SF = Immutable.fromJS(_zipcodesSF);
|
||||
|
||||
var ChoroplethOverlayExample = React.createClass({
|
||||
displayName: 'ChoroplethOverlayExample',
|
||||
|
||||
PropTypes: {
|
||||
width: React.PropTypes.number.isRequired,
|
||||
height: React.PropTypes.number.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function getInitialState() {
|
||||
return {
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
zoom: 11,
|
||||
startDragLatLng: null,
|
||||
isDragging: false
|
||||
};
|
||||
},
|
||||
|
||||
_onChangeViewport: function _onChangeViewport(opt) {
|
||||
this.setState({
|
||||
latitude: opt.latitude,
|
||||
longitude: opt.longitude,
|
||||
zoom: opt.zoom,
|
||||
startDragLatLng: opt.startDragLatLng,
|
||||
isDragging: opt.isDragging
|
||||
});
|
||||
},
|
||||
|
||||
render: function render() {
|
||||
return r(MapGL, assign({
|
||||
latitude: this.state.latitude,
|
||||
longitude: this.state.longitude,
|
||||
zoom: this.state.zoom,
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
startDragLatLng: this.state.startDragLatLng,
|
||||
isDragging: this.state.isDragging,
|
||||
onChangeViewport: this.props.onChangeViewport || this._onChangeViewport
|
||||
}, this.props), [
|
||||
r(ChoroplethOverlay, {
|
||||
globalOpacity: 0.8,
|
||||
colorDomain: [0, 500, 1000],
|
||||
colorRange: ['#31a354', '#addd8e', '#f7fcb9'],
|
||||
renderWhileDragging: false,
|
||||
features: ZIPCODES_SF.get('features')
|
||||
})
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ChoroplethOverlayExample;
|
||||
153
example/examples/custom.react.js
Normal file
153
example/examples/custom.react.js
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var assign = require('object-assign');
|
||||
var React = require('react');
|
||||
var d3 = require('d3');
|
||||
var window = require('global/window');
|
||||
var alphaify = require('alphaify');
|
||||
var transform = require('svg-transform');
|
||||
var Immutable = require('immutable');
|
||||
var r = require('r-dom');
|
||||
|
||||
var MapGL = require('../../src/index.js');
|
||||
var CanvasOverlay = require('../../src/overlays/canvas.react');
|
||||
var SVGOverlay = require('../../src/overlays/svg.react');
|
||||
|
||||
// San Francisco
|
||||
var location = require('./../data/cities.json')[0];
|
||||
|
||||
var wiggle = (function _wiggle() {
|
||||
var normal = d3.random.normal();
|
||||
return function __wiggle(scale) {
|
||||
return normal() * scale;
|
||||
};
|
||||
}());
|
||||
|
||||
// Example data.
|
||||
var locations = Immutable.fromJS(d3.range(30).map(function _map() {
|
||||
return [location.latitude + wiggle(0.01), location.longitude + wiggle(0.01)];
|
||||
}));
|
||||
|
||||
var OverlayExample = React.createClass({
|
||||
|
||||
displayName: 'OverlayExample',
|
||||
|
||||
PropTypes: {
|
||||
width: React.PropTypes.number.isRequired,
|
||||
height: React.PropTypes.number.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function getInitialState() {
|
||||
return {
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
zoom: 12.4,
|
||||
startDragLatLng: null,
|
||||
isDragging: false
|
||||
};
|
||||
},
|
||||
|
||||
_onChangeViewport: function _onChangeViewport(opt) {
|
||||
this.setState({
|
||||
latitude: opt.latitude,
|
||||
longitude: opt.longitude,
|
||||
zoom: opt.zoom,
|
||||
startDragLatLng: opt.startDragLatLng,
|
||||
isDragging: opt.isDragging
|
||||
});
|
||||
},
|
||||
|
||||
render: function render() {
|
||||
return r(MapGL, assign({
|
||||
latitude: this.state.latitude,
|
||||
longitude: this.state.longitude,
|
||||
zoom: this.state.zoom,
|
||||
startDragLatLng: this.state.startDragLatLng,
|
||||
isDragging: this.state.isDragging,
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
onChangeViewport: this.props.onChangeViewport || this._onChangeViewport
|
||||
}, this.props), [
|
||||
r(CanvasOverlay, {redraw: function _redrawCanvas(opt) {
|
||||
var p1 = opt.project([location.latitude, location.longitude]);
|
||||
opt.ctx.clearRect(0, 0, opt.width, opt.height);
|
||||
opt.ctx.strokeStyle = alphaify('#1FBAD6', 0.4);
|
||||
opt.ctx.lineWidth = 2;
|
||||
locations.forEach(function forEach(loc, index) {
|
||||
opt.ctx.beginPath();
|
||||
var p2 = opt.project(loc.toArray());
|
||||
opt.ctx.moveTo(p1.x, p1.y);
|
||||
opt.ctx.lineTo(p2.x, p2.y);
|
||||
opt.ctx.stroke();
|
||||
opt.ctx.beginPath();
|
||||
opt.ctx.fillStyle = alphaify('#1FBAD6', 0.4);
|
||||
opt.ctx.arc(p2.x, p2.y, 6, 0, 2 * Math.PI);
|
||||
opt.ctx.fill();
|
||||
opt.ctx.beginPath();
|
||||
opt.ctx.fillStyle = '#FFFFFF';
|
||||
opt.ctx.textAlign = 'center';
|
||||
opt.ctx.fillText(index, p2.x, p2.y + 4);
|
||||
});
|
||||
}}),
|
||||
// We use invisible SVG elements to support interactivity.
|
||||
r(SVGOverlay, {
|
||||
redraw: function _redrwaSVGOverlay(opt) {
|
||||
var p1 = opt.project([location.latitude, location.longitude]);
|
||||
var style = {
|
||||
// transparent but still clickable.
|
||||
fill: 'rgba(0, 0, 0, 0)'
|
||||
};
|
||||
return r.g({
|
||||
style: {
|
||||
pointerEvents: 'all',
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}, [r.circle({
|
||||
style: assign({}, style, {stroke: alphaify('#1FBAD6', 0.8)}),
|
||||
r: 10,
|
||||
onClick: function onClick() {
|
||||
var windowAlert = window.alert;
|
||||
windowAlert('center');
|
||||
},
|
||||
transform: transform([{translate: [p1.x, p1.y]}]),
|
||||
key: 0
|
||||
})].concat(locations.map(function _map(loc, index) {
|
||||
var p2 = opt.project([loc.get(0), loc.get(1)]);
|
||||
return r.circle({
|
||||
style: style,
|
||||
r: 6,
|
||||
onClick: function onClick() {
|
||||
var windowAlert = window.alert;
|
||||
windowAlert('dot ' + index);
|
||||
},
|
||||
transform: transform([{translate: [p2.x, p2.y]}]),
|
||||
key: index + 1
|
||||
});
|
||||
}, this))
|
||||
);
|
||||
}.bind(this)
|
||||
})
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = OverlayExample;
|
||||
149
example/examples/geodata-creator.react.js
Normal file
149
example/examples/geodata-creator.react.js
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var assign = require('object-assign');
|
||||
var React = require('react');
|
||||
var r = require('r-dom');
|
||||
var Immutable = require('immutable');
|
||||
var alphaify = require('alphaify');
|
||||
|
||||
var MapGL = require('../../src/index.js');
|
||||
var DraggableOverlay = require('../../src/overlays/draggable-points.react');
|
||||
var SVGOverlay = require('../../src/overlays/svg.react');
|
||||
|
||||
// A mock example path.
|
||||
var initialPoints = [
|
||||
{location: [37.79450507471435, -122.39508481737994], id: 0},
|
||||
{location: [37.79227619464379, -122.39750244137034], id: 1},
|
||||
{location: [37.789251178427776, -122.4013303460217], id: 2},
|
||||
{location: [37.786862920252986, -122.40475531334141], id: 3},
|
||||
{location: [37.78861431712821, -122.40505751634022], id: 4},
|
||||
{location: [37.79060449046487, -122.40556118800487], id: 5},
|
||||
{location: [37.790047247333675, -122.4088854209916], id: 6},
|
||||
{location: [37.79275381746233, -122.4091876239904], id: 7},
|
||||
{location: [37.795619489534374, -122.40989276432093], id: 8},
|
||||
{location: [37.79792786675678, -122.41049717031848], id: 9},
|
||||
{location: [37.80031576728801, -122.4109001076502], id: 10},
|
||||
{location: [37.79920142331301, -122.41916032295062], id: 11}
|
||||
];
|
||||
|
||||
var ids = initialPoints[initialPoints.length - 1].id;
|
||||
|
||||
var DemoGeodataCreator = React.createClass({
|
||||
|
||||
displayName: 'DemoGeodataCreator',
|
||||
|
||||
PropTypes: {
|
||||
width: React.PropTypes.number.isRequired,
|
||||
height: React.PropTypes.number.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function getInitialState() {
|
||||
return {
|
||||
longitude: -122.40677,
|
||||
latitude: 37.78949,
|
||||
zoom: 12.76901,
|
||||
startDragLatLng: null,
|
||||
isDragging: false,
|
||||
points: Immutable.fromJS(initialPoints)
|
||||
};
|
||||
},
|
||||
|
||||
_onChangeViewport: function _onChangeViewport(opt) {
|
||||
this.setState({
|
||||
latitude: opt.latitude,
|
||||
longitude: opt.longitude,
|
||||
zoom: opt.zoom,
|
||||
startDragLatLng: opt.startDragLatLng,
|
||||
isDragging: opt.isDragging
|
||||
});
|
||||
},
|
||||
|
||||
_onAddPoint: function _onAddPoint(_location) {
|
||||
var points = this.state.points.push(new Immutable.Map({
|
||||
location: new Immutable.List(_location),
|
||||
id: ++ids
|
||||
}));
|
||||
this.setState({points: points});
|
||||
},
|
||||
|
||||
_onUpdatePoint: function _onUpdatePoint(opt) {
|
||||
var index = this.state.points.findIndex(function filter(p) {
|
||||
return p.get('id') === opt.key;
|
||||
});
|
||||
var point = this.state.points.get(index);
|
||||
point = point.set('location', new Immutable.List(opt.location));
|
||||
var points = this.state.points.set(index, point);
|
||||
this.setState({points: points});
|
||||
},
|
||||
|
||||
render: function render() {
|
||||
return r.div([
|
||||
r(MapGL, assign({
|
||||
latitude: this.state.latitude,
|
||||
longitude: this.state.longitude,
|
||||
zoom: this.state.zoom,
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
startDragLatLng: this.state.startDragLatLng,
|
||||
isDragging: this.state.isDragging,
|
||||
style: {float: 'left'},
|
||||
onChangeViewport: this.props.onChangeViewport || this._onChangeViewport
|
||||
}, this.props), [
|
||||
r(SVGOverlay, {redraw: function _redraw(opt) {
|
||||
if (!this.state.points.size) {
|
||||
return null;
|
||||
}
|
||||
var d = 'M' + this.state.points.map(function _map(point) {
|
||||
var p = opt.project(point.get('location').toArray());
|
||||
return [p.x, p.y];
|
||||
}).join('L');
|
||||
return r.path({
|
||||
style: {stroke: '#1FBAD6', strokeWidth: 2, fill: 'none'},
|
||||
d: d
|
||||
});
|
||||
}.bind(this)}),
|
||||
r(DraggableOverlay, {
|
||||
points: this.state.points,
|
||||
onAddPoint: this._onAddPoint,
|
||||
onUpdatePoint: this._onUpdatePoint,
|
||||
renderPoint: function renderPoint(point, pixel) {
|
||||
return r.g({}, [
|
||||
r.circle({
|
||||
r: 10,
|
||||
style: {
|
||||
fill: alphaify('#1FBAD6', 0.5),
|
||||
pointerEvents: 'all'
|
||||
}
|
||||
}),
|
||||
r.text({
|
||||
style: {fill: 'white', textAnchor: 'middle'},
|
||||
y: 5
|
||||
}, point.get('id'))
|
||||
]);
|
||||
}
|
||||
})
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = DemoGeodataCreator;
|
||||
28
example/examples/index.js
Normal file
28
example/examples/index.js
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
Custom: require('./custom.react'),
|
||||
Choropleth: require('./choropleth.react'),
|
||||
GeodataCreator: require('./geodata-creator.react'),
|
||||
Route: require('./route.react'),
|
||||
Scatterplot: require('./scatterplot.react')
|
||||
};
|
||||
129
example/examples/route.react.js
Normal file
129
example/examples/route.react.js
Normal file
@ -0,0 +1,129 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var assign = require('object-assign');
|
||||
var React = require('react');
|
||||
var d3 = require('d3');
|
||||
var r = require('r-dom');
|
||||
var alphaify = require('alphaify');
|
||||
var window = require('global/window');
|
||||
var windowAlert = window.alert;
|
||||
|
||||
var MapGL = require('../../src/index.js');
|
||||
var SVGOverlay = require('../../src/overlays/svg.react');
|
||||
var CanvasOverlay = require('../../src/overlays/canvas.react');
|
||||
|
||||
var ROUTES = require('./../data/routes-example.json');
|
||||
|
||||
var color = d3.scale.category10();
|
||||
|
||||
var RouteOverlayExample = React.createClass({
|
||||
displayName: 'RouteOverlayExample',
|
||||
|
||||
propTypes: {
|
||||
width: React.PropTypes.number.isRequired,
|
||||
height: React.PropTypes.number.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function getInitialState() {
|
||||
return {
|
||||
latitude: 37.7736092599127,
|
||||
longitude: -122.42312591099463,
|
||||
zoom: 12.011557070552028,
|
||||
startDragLatLng: null,
|
||||
isDragging: false
|
||||
};
|
||||
},
|
||||
|
||||
_onChangeViewport: function _onChangeViewport(opt) {
|
||||
this.setState({
|
||||
latitude: opt.latitude,
|
||||
longitude: opt.longitude,
|
||||
zoom: opt.zoom,
|
||||
startDragLatLng: opt.startDragLatLng,
|
||||
isDragging: opt.isDragging
|
||||
});
|
||||
},
|
||||
|
||||
_renderRoute: function _renderRoute(points, index) {
|
||||
return r.g({style: {pointerEvents: 'click', cursor: 'pointer'}}, [
|
||||
r.g({
|
||||
style: {pointerEvents: 'visibleStroke'},
|
||||
onClick: function onClick() {
|
||||
windowAlert('route ' + index);
|
||||
}
|
||||
}, [
|
||||
r.path({
|
||||
d: 'M' + points.join('L'),
|
||||
style: {
|
||||
fill: 'none',
|
||||
stroke: alphaify(color(index), 0.7),
|
||||
strokeWidth: 6
|
||||
}
|
||||
})
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
_redrawSVGOverlay: function _redrawSVGOverlay(opt) {
|
||||
var routes = ROUTES.map(function _map(route, index) {
|
||||
var points = route.coordinates.map(opt.project).map(function __map(p) {
|
||||
return [d3.round(p.x, 1), d3.round(p.y, 1)];
|
||||
});
|
||||
return r.g({key: index}, this._renderRoute(points, index));
|
||||
}, this);
|
||||
return r.g(routes);
|
||||
},
|
||||
|
||||
_redrawCanvasOverlay: function _redrawCanvasOverlay(opt) {
|
||||
var ctx = opt.ctx;
|
||||
var width = opt.width;
|
||||
var height = opt.height;
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ROUTES.map(function _map(route, index) {
|
||||
route.coordinates.map(opt.project).forEach(function _forEach(p, i) {
|
||||
var point = [d3.round(p.x, 1), d3.round(p.y, 1)];
|
||||
ctx.fillStyle = d3.rgb(color(index)).brighter(1).toString();
|
||||
ctx.beginPath();
|
||||
ctx.arc(point[0], point[1], 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render: function render() {
|
||||
return r(MapGL, assign({
|
||||
latitude: this.state.latitude,
|
||||
longitude: this.state.longitude,
|
||||
zoom: this.state.zoom,
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
startDragLatLng: this.state.startDragLatLng,
|
||||
isDragging: this.state.isDragging,
|
||||
onChangeViewport: this.props.onChangeViewport || this._onChangeViewport
|
||||
}, this.props), [
|
||||
r(SVGOverlay, {redraw: this._redrawSVGOverlay}),
|
||||
r(CanvasOverlay, {redraw: this._redrawCanvasOverlay})
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = RouteOverlayExample;
|
||||
94
example/examples/scatterplot.react.js
Normal file
94
example/examples/scatterplot.react.js
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var assign = require('object-assign');
|
||||
var React = require('react');
|
||||
var r = require('r-dom');
|
||||
var Immutable = require('immutable');
|
||||
var d3 = require('d3');
|
||||
|
||||
var MapGL = require('../../src/index.js');
|
||||
var ScatterplotOverlay = require('../../src/overlays/scatterplot.react');
|
||||
|
||||
// San Francisco
|
||||
var location = require('./../data/cities.json')[0];
|
||||
|
||||
var normal = d3.random.normal();
|
||||
function wiggle(scale) {
|
||||
return normal() * scale;
|
||||
}
|
||||
|
||||
// Example data.
|
||||
var locations = Immutable.fromJS(d3.range(4000).map(function _map() {
|
||||
return [location.latitude + wiggle(0.01), location.longitude + wiggle(0.01)];
|
||||
}));
|
||||
|
||||
var ScatterplotOverlayExample = React.createClass({
|
||||
|
||||
displayName: 'ScatterplotOverlayExample',
|
||||
|
||||
PropTypes: {
|
||||
width: React.PropTypes.number.isRequired,
|
||||
height: React.PropTypes.number.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function getInitialState() {
|
||||
return {
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
zoom: 11,
|
||||
startDragLatLng: null,
|
||||
isDragging: false
|
||||
};
|
||||
},
|
||||
|
||||
_onChangeViewport: function _onChangeViewport(opt) {
|
||||
this.setState({
|
||||
latitude: opt.latitude,
|
||||
longitude: opt.longitude,
|
||||
zoom: opt.zoom,
|
||||
startDragLatLng: opt.startDragLatLng,
|
||||
isDragging: opt.isDragging
|
||||
});
|
||||
},
|
||||
|
||||
render: function render() {
|
||||
return r(MapGL, assign({
|
||||
latitude: this.state.latitude,
|
||||
longitude: this.state.longitude,
|
||||
zoom: this.state.zoom,
|
||||
isDragging: this.state.isDragging,
|
||||
startDragLatLng: this.state.startDragLatLng,
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
onChangeViewport: this.props.onChangeViewport || this._onChangeViewport
|
||||
}, this.props), [
|
||||
r(ScatterplotOverlay, {
|
||||
locations: locations,
|
||||
dotRadius: 2,
|
||||
globalOpacity: 1,
|
||||
compositeOperation: 'screen'
|
||||
})
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ScatterplotOverlayExample;
|
||||
80
example/main.js
Normal file
80
example/main.js
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var document = require('global/document');
|
||||
var React = require('react');
|
||||
var r = require('r-dom');
|
||||
var window = require('global/window');
|
||||
|
||||
var ChoroplethExample = require('./examples/choropleth.react');
|
||||
var CustomExample = require('./examples/custom.react');
|
||||
var GeodataCreator = require('./examples/geodata-creator.react');
|
||||
var ScatterplotExample = require('./examples/scatterplot.react');
|
||||
var RouteExample = require('./examples/route.react');
|
||||
|
||||
function getAccessToken() {
|
||||
var match = window.location.search.match(/access_token=([^&\/]*)/);
|
||||
var accessToken = match && match[1];
|
||||
if (accessToken) {
|
||||
window.localStorage.accessToken = accessToken;
|
||||
} else {
|
||||
accessToken = window.localStorage.accessToken;
|
||||
}
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
var App = React.createClass({
|
||||
|
||||
displayName: 'App',
|
||||
|
||||
_onChangeViewport: function _onChangeViewport(opt) {
|
||||
this.setState({
|
||||
latitude: opt.latitude,
|
||||
longitude: opt.longitude,
|
||||
zoom: opt.zoom,
|
||||
bbox: opt.bbox
|
||||
});
|
||||
},
|
||||
|
||||
getInitialState: function getInitialState() {
|
||||
return {
|
||||
map: {latitude: 37.7577, longitude: -122.4376, zoom: 8}
|
||||
};
|
||||
},
|
||||
|
||||
render: function render() {
|
||||
var common = {
|
||||
width: 400,
|
||||
height: 400,
|
||||
style: {float: 'left'},
|
||||
mapboxApiAccessToken: getAccessToken()
|
||||
};
|
||||
return r.div([
|
||||
r(RouteExample, common),
|
||||
r(ScatterplotExample, common),
|
||||
r(ChoroplethExample, common),
|
||||
r(CustomExample, common),
|
||||
r(GeodataCreator, common)
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
React.render(r(App), document.body);
|
||||
47
package.json
Normal file
47
package.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "react-map-gl",
|
||||
"description": "A React wrapper for MapboxGL-js and overlay API.",
|
||||
"version": "0.1.0",
|
||||
"main": "src/index.js",
|
||||
"keywords": [],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/uber/react-mapbox-gl.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"alphaify": "^1.0.0",
|
||||
"bowser": "^0.7.3",
|
||||
"canvas-composite-types": "^1.0.0",
|
||||
"d3": "^3.5.5",
|
||||
"debounce": "^1.0.0",
|
||||
"gl-matrix": "^2.3.1",
|
||||
"global": "^4.3.0",
|
||||
"keymirror": "^0.1.1",
|
||||
"mapbox-gl": "~0.11.1",
|
||||
"nop": "^1.0.0",
|
||||
"object-assign": "^3.0.0",
|
||||
"svg-transform": "0.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bistre": "^1.0.1",
|
||||
"budo": "^4.2.1",
|
||||
"husky": "^0.8.1",
|
||||
"uber-standard": "^3.6.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "budo ./example/main.js --live -t | bistre",
|
||||
"lint": "uber-standard",
|
||||
"precommit": "npm run lint -s",
|
||||
"prepush": "npm run lint -s"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"immutable": "^3.7.3",
|
||||
"r-dom": "^1.3.0",
|
||||
"react": "^0.13.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "0.10.x",
|
||||
"npm": "2.x"
|
||||
}
|
||||
}
|
||||
BIN
react-map-gl-screenshots.png
Normal file
BIN
react-map-gl-screenshots.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 805 KiB |
37
src/config.js
Normal file
37
src/config.js
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var browser = require('bowser').browser;
|
||||
|
||||
var prefix =
|
||||
browser.webkit ? '-webkit-' :
|
||||
browser.gecko ? '-moz-' :
|
||||
'';
|
||||
|
||||
var config = {
|
||||
DEFAULTS: {},
|
||||
CURSOR: {
|
||||
GRABBING: prefix + 'grabbing',
|
||||
GRAB: prefix + 'grab'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
22
src/index.js
Normal file
22
src/index.js
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./map.react');
|
||||
222
src/map-interactions.react.js
Normal file
222
src/map-interactions.react.js
Normal file
@ -0,0 +1,222 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var MapboxGL = require('mapbox-gl');
|
||||
var Point = MapboxGL.Point;
|
||||
var document = require('global/document');
|
||||
var window = require('global/window');
|
||||
var r = require('r-dom');
|
||||
var noop = require('./noop');
|
||||
|
||||
var ua = typeof window.navigator !== 'undefined' ?
|
||||
window.navigator.userAgent.toLowerCase() : '';
|
||||
var firefox = ua.indexOf('firefox') !== -1;
|
||||
|
||||
function mousePos(el, event) {
|
||||
var rect = el.getBoundingClientRect();
|
||||
event = event.touches ? event.touches[0] : event;
|
||||
return new Point(
|
||||
event.clientX - rect.left - el.clientLeft,
|
||||
event.clientY - rect.top - el.clientTop
|
||||
);
|
||||
}
|
||||
|
||||
/* eslint-disable max-len */
|
||||
// Portions of the code below originally from:
|
||||
// https://github.com/mapbox/mapbox-gl-js/blob/master/js/ui/handler/scroll_zoom.js
|
||||
/* eslint-enable max-len */
|
||||
|
||||
var MapInteractions = React.createClass({
|
||||
|
||||
displayName: 'MapInteractions',
|
||||
|
||||
PropTypes: {
|
||||
width: React.PropTypes.number.isRequired,
|
||||
height: React.PropTypes.number.isRequired,
|
||||
onMouseDown: React.PropTypes.func,
|
||||
onMouseDrag: React.PropTypes.func,
|
||||
onMouseUp: React.PropTypes.func,
|
||||
onZoom: React.PropTypes.func,
|
||||
onZoomEnd: React.PropTypes.func
|
||||
},
|
||||
|
||||
getDefaultProps: function getDefaultProps() {
|
||||
return {
|
||||
onMouseDown: noop,
|
||||
onMouseDrag: noop,
|
||||
onMouseUp: noop,
|
||||
onZoom: noop,
|
||||
onZoomEnd: noop
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function getInitialState() {
|
||||
return {
|
||||
startPos: null,
|
||||
pos: null,
|
||||
mouseWheelPos: null
|
||||
};
|
||||
},
|
||||
|
||||
_getMousePos: function _getMousePos(event) {
|
||||
var el = this.refs.container.getDOMNode();
|
||||
return mousePos(el, event);
|
||||
},
|
||||
|
||||
_onMouseDown: function _onMouseDown(event) {
|
||||
var pos = this._getMousePos(event);
|
||||
this.setState({startPos: pos, pos: pos});
|
||||
this.props.onMouseDown({pos: pos});
|
||||
document.addEventListener('mousemove', this._onMouseDrag, false);
|
||||
document.addEventListener('mouseup', this._onMouseUp, false);
|
||||
},
|
||||
|
||||
_onMouseDrag: function _onMouseDrag(event) {
|
||||
var pos = this._getMousePos(event);
|
||||
this.setState({pos: pos});
|
||||
this.props.onMouseDrag({pos: pos});
|
||||
},
|
||||
|
||||
_onMouseUp: function _onMouseUp(event) {
|
||||
document.removeEventListener('mousemove', this._onMouseDrag, false);
|
||||
document.removeEventListener('mouseup', this._onMouseUp, false);
|
||||
var pos = this._getMousePos(event);
|
||||
this.setState({pos: pos});
|
||||
this.props.onMouseUp({pos: pos});
|
||||
},
|
||||
|
||||
_onMouseMove: function _onMouseMove(event) {
|
||||
var pos = this._getMousePos(event);
|
||||
this.props.onMouseMove({pos: pos});
|
||||
},
|
||||
|
||||
/* eslint-disable complexity, max-statements */
|
||||
_onWheel: function _onWheel(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
var value = event.deltaY;
|
||||
// Firefox doubles the values on retina screens...
|
||||
if (firefox && event.deltaMode === window.WheelEvent.DOM_DELTA_PIXEL) {
|
||||
value /= window.devicePixelRatio;
|
||||
}
|
||||
if (event.deltaMode === window.WheelEvent.DOM_DELTA_LINE) {
|
||||
value *= 40;
|
||||
}
|
||||
|
||||
var type = this.state.mouseWheelType;
|
||||
var timeout = this.state.mouseWheelTimeout;
|
||||
var lastValue = this.state.mouseWheelLastValue;
|
||||
var time = this.state.mouseWheelTime;
|
||||
|
||||
var now = (window.performance || Date).now();
|
||||
var timeDelta = now - (time || 0);
|
||||
|
||||
var pos = this._getMousePos(event);
|
||||
time = now;
|
||||
|
||||
if (value !== 0 && value % 4.000244140625 === 0) {
|
||||
// This one is definitely a mouse wheel event.
|
||||
type = 'wheel';
|
||||
// Normalize this value to match trackpad.
|
||||
value = Math.floor(value / 4);
|
||||
} else if (value !== 0 && Math.abs(value) < 4) {
|
||||
// This one is definitely a trackpad event because it is so small.
|
||||
type = 'trackpad';
|
||||
} else if (timeDelta > 400) {
|
||||
// This is likely a new scroll action.
|
||||
type = null;
|
||||
lastValue = value;
|
||||
|
||||
// Start a timeout in case this was a singular event, and delay it by up
|
||||
// to 40ms.
|
||||
timeout = window.setTimeout(function setTimeout() {
|
||||
var _type = 'wheel';
|
||||
this._zoom(-this.state.mouseWheelLastValue, this.state.mouseWheelPos);
|
||||
this.setState({mouseWheelType: _type});
|
||||
}.bind(this), 40);
|
||||
} else if (!this._type) {
|
||||
// This is a repeating event, but we don't know the type of event just
|
||||
// yet.
|
||||
// If the delta per time is small, we assume it's a fast trackpad;
|
||||
// otherwise we switch into wheel mode.
|
||||
type = Math.abs(timeDelta * value) < 200 ? 'trackpad' : 'wheel';
|
||||
|
||||
// Make sure our delayed event isn't fired again, because we accumulate
|
||||
// the previous event (which was less than 40ms ago) into this event.
|
||||
if (timeout) {
|
||||
window.clearTimeout(timeout);
|
||||
timeout = null;
|
||||
value += lastValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Slow down zoom if shift key is held for more precise zooming
|
||||
if (event.shiftKey && value) {
|
||||
value = value / 4;
|
||||
}
|
||||
|
||||
// Only fire the callback if we actually know what type of scrolling device
|
||||
// the user uses.
|
||||
if (type) {
|
||||
this._zoom(-value, pos);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
mouseWheelTime: time,
|
||||
mouseWheelPos: pos,
|
||||
mouseWheelType: type,
|
||||
mouseWheelTimeout: timeout,
|
||||
mouseWheelLastValue: lastValue
|
||||
});
|
||||
},
|
||||
/* eslint-enable complexity, max-statements */
|
||||
|
||||
_zoom: function _zoom(delta, pos) {
|
||||
|
||||
// Scale by sigmoid of scroll wheel delta.
|
||||
var scale = 2 / (1 + Math.exp(-Math.abs(delta / 100)));
|
||||
if (delta < 0 && scale !== 0) {
|
||||
scale = 1 / scale;
|
||||
}
|
||||
this.props.onZoom({pos: pos, scale: scale});
|
||||
window.clearTimeout(this._zoomEndTimeout);
|
||||
this._zoomEndTimeout = window.setTimeout(function _setTimeout() {
|
||||
this.props.onZoomEnd();
|
||||
}.bind(this), 200);
|
||||
},
|
||||
|
||||
render: function render() {
|
||||
return r.div({
|
||||
ref: 'container',
|
||||
onMouseMove: this._onMouseMove,
|
||||
onMouseDown: this._onMouseDown,
|
||||
onWheel: this._onWheel,
|
||||
style: {
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
position: 'relative'
|
||||
}
|
||||
}, this.props.children);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = MapInteractions;
|
||||
603
src/map.react.js
Normal file
603
src/map.react.js
Normal file
@ -0,0 +1,603 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var React = require('react');
|
||||
var debounce = require('debounce');
|
||||
var r = require('r-dom');
|
||||
var d3 = require('d3');
|
||||
var noop = require('./noop');
|
||||
var assign = require('object-assign');
|
||||
var Immutable = require('immutable');
|
||||
var MapboxGL = require('mapbox-gl');
|
||||
var LngLatBounds = MapboxGL.LngLatBounds;
|
||||
var Point = MapboxGL.Point;
|
||||
// NOTE: Transform is not a public API so we should be careful to always lock
|
||||
// down mapbox-gl to a specific major, minor, and patch version.
|
||||
var Transform = require('mapbox-gl/js/geo/transform');
|
||||
var vec4 = require('gl-matrix').vec4;
|
||||
|
||||
var config = require('./config');
|
||||
var MapInteractions = require('./map-interactions.react');
|
||||
|
||||
function mod(value, divisor) {
|
||||
var modulus = value % divisor;
|
||||
return modulus < 0 ? divisor + modulus : modulus;
|
||||
}
|
||||
|
||||
function unproject(transform, point) {
|
||||
return transform.pointLocation(MapboxGL.Point.convert(point));
|
||||
}
|
||||
|
||||
function getBBoxFromTransform(transform, width, height) {
|
||||
return [unproject(transform, [0, 0]), unproject(transform, [width, height])];
|
||||
}
|
||||
|
||||
function cloneTransform(original) {
|
||||
var transform = new Transform(original._minZoom, original._maxZoom);
|
||||
transform.latRange = original.latRange;
|
||||
transform.width = original.width;
|
||||
transform.height = original.height;
|
||||
transform.zoom = original.zoom;
|
||||
transform.center = original.center;
|
||||
transform.angle = original.angle;
|
||||
transform.altitude = original.altitude;
|
||||
transform.pitch = original.pitch;
|
||||
return transform;
|
||||
}
|
||||
|
||||
var MapboxGLMap = React.createClass({
|
||||
|
||||
displayName: 'MapGL',
|
||||
|
||||
shouldComponentUpdate: function shouldComponentUpdate(nextProps, nextState) {
|
||||
var allTheSame = Object.keys(nextProps).reduce(function reduce(all, prop) {
|
||||
var same = nextProps[prop] === this.props[prop];
|
||||
return all && same;
|
||||
}.bind(this), true);
|
||||
|
||||
if (!allTheSame) {
|
||||
return true;
|
||||
}
|
||||
|
||||
allTheSame = Object.keys(nextState).reduce(function reduce(all, prop) {
|
||||
var same = nextState[prop] === this.state[prop];
|
||||
return all && same;
|
||||
}.bind(this), true);
|
||||
|
||||
return !allTheSame;
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
/**
|
||||
* The latitude of the center of the map.
|
||||
*/
|
||||
latitude: React.PropTypes.number.isRequired,
|
||||
/**
|
||||
* The longitude of the center of the map.
|
||||
*/
|
||||
longitude: React.PropTypes.number.isRequired,
|
||||
/**
|
||||
* The tile zoom level of the map.
|
||||
*/
|
||||
zoom: React.PropTypes.number.isRequired,
|
||||
/**
|
||||
* The mapbox style the component should use. Can either be a string url
|
||||
* or a MapboxGL style Immutable.Map object.
|
||||
*/
|
||||
mapStyle: React.PropTypes.oneOfType([
|
||||
React.PropTypes.string,
|
||||
React.PropTypes.instanceOf(Immutable.Map)
|
||||
]),
|
||||
/**
|
||||
* `onChangeViewport` callback is fired when the user interacted with the
|
||||
* map. The object passed to the callback containers `latitude`,
|
||||
* `longitude`, `zoom` and `bbox`. information.
|
||||
*/
|
||||
onChangeViewport: React.PropTypes.func,
|
||||
/**
|
||||
* The width of the map.
|
||||
*/
|
||||
width: React.PropTypes.number.isRequired,
|
||||
/**
|
||||
* The height of the map.
|
||||
*/
|
||||
height: React.PropTypes.number.isRequired,
|
||||
/**
|
||||
* Is the component currently being dragged. This is used to show/hide the
|
||||
* drag cursor. Also used as an optimization in some overlays by preventing
|
||||
* rendering while dragging.
|
||||
*/
|
||||
isDragging: React.PropTypes.bool,
|
||||
/**
|
||||
* Required to calculate the mouse projection after the first click event
|
||||
* during dragging. Where the map is depends on where you first clicked on
|
||||
* the map.
|
||||
*/
|
||||
startDragLatLng: React.PropTypes.array,
|
||||
/**
|
||||
* Called when a feature is hovered over. Features must set the
|
||||
* `interactive` property to `true` for this to work properly. see the
|
||||
* Mapbox example: https://www.mapbox.com/mapbox-gl-js/example/featuresat/
|
||||
* The first argument of the callback will be the array of feature the
|
||||
* mouse is over. This is the same response returned from `featuresAt`.
|
||||
*/
|
||||
onHoverFeatures: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* Show attribution control or not.
|
||||
*/
|
||||
attributionControl: React.PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps: function getDefaultProps() {
|
||||
return {
|
||||
mapStyle: 'mapbox://styles/mapbox/light-v8',
|
||||
onChangeViewport: noop,
|
||||
mapboxApiAccessToken: config.DEFAULTS.MAPBOX_API_ACCESS_TOKEN,
|
||||
attributionControl: true
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function getInitialState() {
|
||||
var defaultState = {};
|
||||
var stateChanges = this._updateStateFromProps(defaultState, this.props);
|
||||
var state = assign({}, defaultState, stateChanges);
|
||||
return state;
|
||||
},
|
||||
|
||||
// New props are comin' round the corner!
|
||||
componentWillReceiveProps: function componentWillReceiveProps(newProps) {
|
||||
var stateChanges = this._updateStateFromProps(this.state, newProps);
|
||||
this.setState(stateChanges);
|
||||
},
|
||||
|
||||
// Use props to create an object of state changes.
|
||||
_updateStateFromProps: function _updateStateFromProps(state, props) {
|
||||
var stateChanges = {
|
||||
latitude: props.latitude,
|
||||
longitude: props.longitude,
|
||||
zoom: props.zoom,
|
||||
width: props.width,
|
||||
height: props.height,
|
||||
mapStyle: props.mapStyle,
|
||||
startLatLng: props.startDragLatLng &&
|
||||
new MapboxGL.LngLat(props.startDragLatLng[1], props.startDragLatLng[0])
|
||||
};
|
||||
|
||||
assign(stateChanges, {
|
||||
prevLatitude: state.latitude,
|
||||
prevLongitude: state.longitude,
|
||||
prevZoom: state.zoom,
|
||||
prevWidth: state.width,
|
||||
prevHeight: state.height,
|
||||
prevMapStyle: state.mapStyle
|
||||
});
|
||||
|
||||
MapboxGL.accessToken = props.mapboxApiAccessToken;
|
||||
|
||||
return stateChanges;
|
||||
},
|
||||
|
||||
_onChangeViewport: function _onChangeViewport(_changes) {
|
||||
var map = this._getMap();
|
||||
var width = this.props.width;
|
||||
var height = this.props.height;
|
||||
var bbox = getBBoxFromTransform(map.transform, width, height);
|
||||
var center = map.getCenter();
|
||||
var startLatLng = this.state.startLatLng;
|
||||
var changes = assign({
|
||||
latitude: center.lat,
|
||||
longitude: center.lng,
|
||||
zoom: map.getZoom(),
|
||||
bbox: bbox,
|
||||
isDragging: this.props.isDragging,
|
||||
startDragLatLng: startLatLng && [startLatLng.lat, startLatLng.lng]
|
||||
}, _changes);
|
||||
changes.longitude = mod(changes.longitude + 180, 360) - 180;
|
||||
this.props.onChangeViewport(changes);
|
||||
},
|
||||
|
||||
_getMap: function _getMap() {
|
||||
return this._map;
|
||||
},
|
||||
|
||||
componentDidMount: function componentDidMount() {
|
||||
var mapStyle;
|
||||
if (this.props.mapStyle instanceof Immutable.Map) {
|
||||
mapStyle = this.props.mapStyle.toJS();
|
||||
} else {
|
||||
mapStyle = this.props.mapStyle;
|
||||
}
|
||||
var map = new MapboxGL.Map({
|
||||
container: this.refs.mapboxMap.getDOMNode(),
|
||||
center: [this.state.longitude, this.state.latitude],
|
||||
zoom: this.state.zoom,
|
||||
style: mapStyle,
|
||||
interactive: false
|
||||
// ,
|
||||
// attributionControl: this.props.attributionControl
|
||||
});
|
||||
|
||||
d3.select(map.getCanvas()).style('outline', 'none');
|
||||
|
||||
this._map = map;
|
||||
this._updateMapViewport();
|
||||
this._onChangeViewport();
|
||||
},
|
||||
|
||||
_updateMapViewport: function _updateMapViewport() {
|
||||
var state = this.state;
|
||||
if (state.latitude !== state.prevLatitude ||
|
||||
state.longitude !== state.prevLongitude ||
|
||||
state.zoom !== state.prevZoom
|
||||
) {
|
||||
this._getMap().jumpTo({
|
||||
center: [state.longitude, state.latitude],
|
||||
zoom: state.zoom,
|
||||
bearing: 0,
|
||||
pitch: 0
|
||||
});
|
||||
}
|
||||
if (state.width !== state.prevWidth || state.height !== state.prevHeight) {
|
||||
this._resizeMap();
|
||||
}
|
||||
},
|
||||
|
||||
_resizeMap: debounce(function _resizeMap() {
|
||||
var map = this._getMap();
|
||||
map.resize();
|
||||
}, 100),
|
||||
|
||||
_diffSources: function _diffSources(prevStyle, nextStyle) {
|
||||
var prevSources = prevStyle.get('sources');
|
||||
var nextSources = nextStyle.get('sources');
|
||||
var enter = [];
|
||||
var update = [];
|
||||
var exit = [];
|
||||
var prevIds = prevSources.keySeq().toArray();
|
||||
var nextIds = nextSources.keySeq().toArray();
|
||||
prevIds.forEach(function each(id) {
|
||||
var nextSource = nextSources.get(id);
|
||||
if (nextSource) {
|
||||
if (!nextSource.equals(prevSources.get(id))) {
|
||||
update.push({id: id, source: nextSources.get(id)});
|
||||
}
|
||||
} else {
|
||||
exit.push({id: id, source: prevSources.get(id)});
|
||||
}
|
||||
});
|
||||
nextIds.forEach(function each(id) {
|
||||
var prevSource = prevSources.get(id);
|
||||
if (!prevSource) {
|
||||
enter.push({id: id, source: nextSources.get(id)});
|
||||
}
|
||||
});
|
||||
return {enter: enter, update: update, exit: exit};
|
||||
},
|
||||
|
||||
_diffLayers: function _diffLayers(prevStyle, nextStyle) {
|
||||
var prevLayers = prevStyle.get('layers');
|
||||
var nextLayers = nextStyle.get('layers');
|
||||
var updates = [];
|
||||
var exitingIds = [];
|
||||
var prevMap = {};
|
||||
var nextMap = {};
|
||||
nextLayers.forEach(function map(layer, index) {
|
||||
var id = layer.get('id');
|
||||
nextMap[id] = {
|
||||
layer: layer,
|
||||
id: id,
|
||||
// The `id` of the layer before this one.
|
||||
before: index > 0 ? nextLayers.get(index - 1).get('id') : null,
|
||||
enter: true
|
||||
};
|
||||
});
|
||||
prevLayers.forEach(function map(layer, index) {
|
||||
var id = layer.get('id');
|
||||
prevMap[id] = {
|
||||
layer: layer,
|
||||
id: id,
|
||||
before: index > 0 ? prevLayers.get(index - 1).get('id') : null
|
||||
};
|
||||
if (nextMap[id]) {
|
||||
// Not a new layer.
|
||||
nextMap[id].enter = false;
|
||||
} else {
|
||||
// This layer is being removed.
|
||||
exitingIds.push(id);
|
||||
}
|
||||
});
|
||||
nextLayers.reverse().forEach(function map(layer) {
|
||||
var id = layer.get('id');
|
||||
if (
|
||||
!prevMap[id] ||
|
||||
!prevMap[id].layer.equals(nextMap[id].layer) ||
|
||||
prevMap[id].before !== nextMap[id].before
|
||||
) {
|
||||
// This layer is being changed.
|
||||
updates.push(nextMap[id]);
|
||||
}
|
||||
});
|
||||
return {updates: updates, exitingIds: exitingIds};
|
||||
},
|
||||
|
||||
// Individually update the maps source and layers that have changed if all
|
||||
// other style props haven't changed. This prevents flicking of the map when
|
||||
// styles only change sources or layers.
|
||||
_setDiffStyle: function _setDiffStyle(prevStyle, nextStyle) {
|
||||
var map = this._getMap();
|
||||
var prevKeysMap = prevStyle && styleKeysMap(prevStyle) || {};
|
||||
var nextKeysMap = styleKeysMap(nextStyle);
|
||||
function styleKeysMap(style) {
|
||||
return style.map(function _map() {
|
||||
return true;
|
||||
}).delete('layers').delete('sources').toJS();
|
||||
}
|
||||
function propsOtherThanLayersOrSourcesDiffer() {
|
||||
var prevKeysList = Object.keys(prevKeysMap);
|
||||
var nextKeysList = Object.keys(nextKeysMap);
|
||||
if (prevKeysList.length !== nextKeysList.length) {
|
||||
return true;
|
||||
}
|
||||
// `nextStyle` and `prevStyle` should not have the same set of props.
|
||||
if (nextKeysList.some(function forEach(key) {
|
||||
// But the value of one of those props is different.
|
||||
return prevStyle.get(key) !== nextStyle.get(key);
|
||||
})) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!prevStyle || propsOtherThanLayersOrSourcesDiffer()) {
|
||||
map.setStyle(nextStyle.toJS());
|
||||
return;
|
||||
}
|
||||
|
||||
var sourcesDiff = this._diffSources(prevStyle, nextStyle);
|
||||
var layersDiff = this._diffLayers(prevStyle, nextStyle);
|
||||
|
||||
map.batch(function batchStyleUpdates() {
|
||||
sourcesDiff.enter.forEach(function each(enter) {
|
||||
map.addSource(enter.id, enter.source.toJS());
|
||||
});
|
||||
sourcesDiff.update.forEach(function each(update) {
|
||||
map.removeSource(update.id);
|
||||
map.addSource(update.id, update.source.toJS());
|
||||
});
|
||||
sourcesDiff.exit.forEach(function each(exit) {
|
||||
map.removeSource(exit.id);
|
||||
});
|
||||
layersDiff.exitingIds.forEach(function forEach(id) {
|
||||
map.removeLayer(id);
|
||||
});
|
||||
layersDiff.updates.forEach(function forEach(update) {
|
||||
if (!update.enter) {
|
||||
// This is an old layer that needs to be updated. Remove the old layer
|
||||
// with the same id and add it back again.
|
||||
map.removeLayer(update.id);
|
||||
}
|
||||
map.addLayer(update.layer.toJS(), update.before);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_updateMapStyle: function _updateMapStyle() {
|
||||
var mapStyle = this.state.mapStyle;
|
||||
if (mapStyle !== this.state.prevMapStyle) {
|
||||
if (mapStyle instanceof Immutable.Map) {
|
||||
this._setDiffStyle(this.state.prevMapStyle, mapStyle);
|
||||
} else {
|
||||
this._getMap().setStyle(mapStyle);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
componentDidUpdate: function componentDidUpdate() {
|
||||
this._updateMapViewport();
|
||||
this._updateMapStyle();
|
||||
},
|
||||
|
||||
_onMouseDown: function _onMouseDown(opt) {
|
||||
var map = this._getMap();
|
||||
var startLatLng = unproject(map.transform, opt.pos);
|
||||
this._onChangeViewport({
|
||||
isDragging: true,
|
||||
startDragLatLng: [startLatLng.lat, startLatLng.lng]
|
||||
});
|
||||
},
|
||||
|
||||
_onMouseDrag: function _onMouseDrag(opt) {
|
||||
var p2 = opt.pos;
|
||||
var map = this._getMap();
|
||||
var width = this.props.width;
|
||||
var height = this.props.height;
|
||||
// take the start latlng and put it where the mouse is down.
|
||||
var transform = cloneTransform(map.transform);
|
||||
assert(this.state.startLatLng, '`startDragLatLng` prop is required for ' +
|
||||
'mouse drag behavior.');
|
||||
transform.setLocationAtPoint(this.state.startLatLng, p2);
|
||||
var bbox = getBBoxFromTransform(transform, width, height);
|
||||
this._onChangeViewport({
|
||||
latitude: transform.center.lat,
|
||||
longitude: transform.center.lng,
|
||||
zoom: transform.zoom,
|
||||
bbox: bbox,
|
||||
isDragging: true
|
||||
});
|
||||
},
|
||||
|
||||
_onMouseMove: function _onMouseMove(opt) {
|
||||
var map = this._getMap();
|
||||
var pos = opt.pos;
|
||||
if (!this.props.onHoverFeatures) {
|
||||
return;
|
||||
}
|
||||
map.featuresAt([pos.x, pos.y], {}, function callback(error, features) {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
if (!features.length) {
|
||||
return;
|
||||
}
|
||||
this.props.onHoverFeatures(features);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_onMouseUp: function _onMouseUp(opt) {
|
||||
var map = this._getMap();
|
||||
var width = this.props.width;
|
||||
var height = this.props.height;
|
||||
var transform = cloneTransform(map.transform);
|
||||
this._onChangeViewport({
|
||||
latitude: transform.center.lat,
|
||||
longitude: transform.center.lng,
|
||||
zoom: transform.zoom,
|
||||
isDragging: false,
|
||||
bbox: getBBoxFromTransform(transform, width, height)
|
||||
});
|
||||
},
|
||||
|
||||
_onZoom: function _onZoom(opt) {
|
||||
var map = this._getMap();
|
||||
var props = this.props;
|
||||
var transform = cloneTransform(map.transform);
|
||||
var around = unproject(transform, opt.pos);
|
||||
transform.zoom = transform.scaleZoom(map.transform.scale * opt.scale);
|
||||
transform.setLocationAtPoint(around, opt.pos);
|
||||
this._onChangeViewport({
|
||||
latitude: transform.center.lat,
|
||||
longitude: transform.center.lng,
|
||||
zoom: transform.zoom,
|
||||
isDragging: true,
|
||||
bbox: getBBoxFromTransform(transform, props.width, props.height)
|
||||
});
|
||||
},
|
||||
|
||||
_onZoomEnd: function _onZoomEnd() {
|
||||
this._onChangeViewport({isDragging: false});
|
||||
},
|
||||
|
||||
_renderOverlays: function _renderOverlays(transform) {
|
||||
var children = [];
|
||||
|
||||
// Calculate the transformation matrix once for a given render cycle
|
||||
// instead of for each point.
|
||||
// from: mapbox-gl-js/js/geo/transform.js
|
||||
var tileZoom = transform.tileZoom;
|
||||
var coordinatePointMatrix = transform.coordinatePointMatrix(tileZoom);
|
||||
function coordinatePoint(coord) {
|
||||
var matrix = coordinatePointMatrix;
|
||||
var p = vec4.transformMat4([], [coord.column, coord.row, 0, 1], matrix);
|
||||
return new Point(p[0] / p[3], p[1] / p[3]);
|
||||
}
|
||||
|
||||
function locationPoint(latlng) {
|
||||
return coordinatePoint(transform.locationCoordinate(latlng));
|
||||
}
|
||||
|
||||
function fastProject(latlng) {
|
||||
return locationPoint(new MapboxGL.LngLat(latlng[1], latlng[0]));
|
||||
}
|
||||
|
||||
React.Children.forEach(this.props.children, function _map(child) {
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
children.push(React.cloneElement(child, {
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
isDragging: this.props.isDragging,
|
||||
project: fastProject,
|
||||
unproject: unproject.bind(null, transform)
|
||||
}));
|
||||
}, this);
|
||||
return r.div({
|
||||
className: 'overlays',
|
||||
style: {position: 'absolute', left: 0, top: 0}
|
||||
}, children);
|
||||
},
|
||||
|
||||
render: function render() {
|
||||
var props = this.props;
|
||||
var style = assign({}, props.style, {
|
||||
width: props.width,
|
||||
height: props.height,
|
||||
cursor: this.props.isDragging ?
|
||||
config.CURSOR.GRABBING : config.CURSOR.GRAB
|
||||
});
|
||||
|
||||
var transform = new Transform();
|
||||
transform.width = props.width;
|
||||
transform.height = props.height;
|
||||
transform.zoom = this.props.zoom;
|
||||
transform.center.lat = this.props.latitude;
|
||||
transform.center.lng = this.props.longitude;
|
||||
|
||||
return r.div({
|
||||
style: assign({}, this.props.style, {
|
||||
width: this.props.width,
|
||||
height: this.props.height
|
||||
})
|
||||
}, [
|
||||
r(MapInteractions, {
|
||||
onMouseDown: this._onMouseDown,
|
||||
onMouseDrag: this._onMouseDrag,
|
||||
onMouseUp: this._onMouseUp,
|
||||
onMouseMove: this._onMouseMove,
|
||||
onZoom: this._onZoom,
|
||||
onZoomEnd: this._onZoomEnd,
|
||||
width: this.props.width,
|
||||
height: this.props.height
|
||||
}, [
|
||||
r.div({ref: 'mapboxMap', style: style, className: props.className}),
|
||||
this._renderOverlays(transform)
|
||||
])
|
||||
]
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
MapboxGLMap.fitBounds = function fitBounds(width, height, _bounds, options) {
|
||||
var bounds = new LngLatBounds([_bounds[0].reverse(), _bounds[1].reverse()]);
|
||||
options = options || {};
|
||||
var padding = typeof options.padding === 'undefined' ? 0 : options.padding;
|
||||
var offset = Point.convert([0, 0]);
|
||||
var tr = new Transform();
|
||||
tr.width = width;
|
||||
tr.height = height;
|
||||
var nw = tr.project(bounds.getNorthWest());
|
||||
var se = tr.project(bounds.getSouthEast());
|
||||
var size = se.sub(nw);
|
||||
var scaleX = (tr.width - padding * 2 - Math.abs(offset.x) * 2) / size.x;
|
||||
var scaleY = (tr.height - padding * 2 - Math.abs(offset.y) * 2) / size.y;
|
||||
|
||||
var center = tr.unproject(nw.add(se).div(2));
|
||||
var zoom = tr.scaleZoom(tr.scale * Math.min(scaleX, scaleY));
|
||||
return {
|
||||
latitude: center.lat,
|
||||
longitude: center.lng,
|
||||
zoom: zoom
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = MapboxGLMap;
|
||||
22
src/noop.js
Normal file
22
src/noop.js
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
module.exports = function noop() {};
|
||||
83
src/overlays/canvas.react.js
Normal file
83
src/overlays/canvas.react.js
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var window = require('global/window');
|
||||
var r = require('r-dom');
|
||||
|
||||
function devicePixelRatio() {
|
||||
return window.devicePixelRatio || 1;
|
||||
}
|
||||
|
||||
var CanvasOverlay = React.createClass({
|
||||
displayName: 'CanvasOverlay',
|
||||
|
||||
propTypes: {
|
||||
width: React.PropTypes.number,
|
||||
height: React.PropTypes.number,
|
||||
redraw: React.PropTypes.func.isRequired,
|
||||
project: React.PropTypes.func,
|
||||
isDragging: React.PropTypes.bool
|
||||
},
|
||||
|
||||
componentDidMount: function componentDidMount() {
|
||||
this._redraw();
|
||||
},
|
||||
|
||||
componentDidUpdate: function componentDidUpdate() {
|
||||
this._redraw();
|
||||
},
|
||||
|
||||
_redraw: function _redraw() {
|
||||
var pixelRatio = devicePixelRatio();
|
||||
var canvas = this.getDOMNode();
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.save();
|
||||
ctx.scale(pixelRatio, pixelRatio);
|
||||
this.props.redraw({
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
ctx: ctx,
|
||||
project: this.props.project,
|
||||
isDragging: this.props.isDragging
|
||||
});
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
render: function render() {
|
||||
var pixelRatio = devicePixelRatio();
|
||||
return r.canvas({
|
||||
ref: 'overlay',
|
||||
width: this.props.width * pixelRatio,
|
||||
height: this.props.height * pixelRatio,
|
||||
style: {
|
||||
width: this.props.width + 'px',
|
||||
height: this.props.height + 'px',
|
||||
position: 'absolute',
|
||||
pointerEvents: 'none',
|
||||
left: 0,
|
||||
top: 0
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = CanvasOverlay;
|
||||
141
src/overlays/choropleth.react.js
Normal file
141
src/overlays/choropleth.react.js
Normal file
@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var window = require('global/window');
|
||||
var d3 = require('d3');
|
||||
var r = require('r-dom');
|
||||
var Immutable = require('immutable');
|
||||
|
||||
var ChoroplethOverlay = React.createClass({
|
||||
|
||||
displayName: 'ChoroplethOverlay',
|
||||
|
||||
propTypes: {
|
||||
width: React.PropTypes.number,
|
||||
height: React.PropTypes.number,
|
||||
project: React.PropTypes.func,
|
||||
isDragging: React.PropTypes.bool,
|
||||
renderWhileDragging: React.PropTypes.bool,
|
||||
globalOpacity: React.PropTypes.number,
|
||||
/**
|
||||
* An Immutable List of feature objects.
|
||||
*/
|
||||
features: React.PropTypes.instanceOf(Immutable.List),
|
||||
colorDomain: React.PropTypes.array,
|
||||
colorRange: React.PropTypes.array,
|
||||
valueAccessor: React.PropTypes.func
|
||||
},
|
||||
|
||||
getDefaultProps: function getDefaultProps() {
|
||||
return {
|
||||
latLngAccessor: function latLngAccessor(location) {
|
||||
return [location.get(0), location.get(1)];
|
||||
},
|
||||
renderWhileDragging: true,
|
||||
globalOpacity: 1,
|
||||
colorDomain: null,
|
||||
colorRange: ['#FFFFFF', '#1FBAD6'],
|
||||
valueAccessor: function valueAccessor(feature) {
|
||||
return feature.get('properties').get('value');
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function componentDidMount() {
|
||||
this._redraw();
|
||||
},
|
||||
|
||||
componentDidUpdate: function componentDidUpdate() {
|
||||
this._redraw();
|
||||
},
|
||||
|
||||
_redraw: function _redraw() {
|
||||
var pixelRatio = window.devicePixelRatio;
|
||||
var canvas = this.getDOMNode();
|
||||
var ctx = canvas.getContext('2d');
|
||||
var project = this.props.project;
|
||||
|
||||
ctx.save();
|
||||
ctx.scale(pixelRatio, pixelRatio);
|
||||
ctx.clearRect(0, 0, this.props.width, this.props.height);
|
||||
|
||||
function projectPoint(lon, lat) {
|
||||
var point = project([lat, lon]);
|
||||
this.stream.point(point.x, point.y);
|
||||
}
|
||||
|
||||
if (this.props.renderWhileDragging || !this.props.isDragging) {
|
||||
var transform = d3.geo.transform({point: projectPoint});
|
||||
var path = d3.geo.path().projection(transform).context(ctx);
|
||||
this._drawFeatures(ctx, path);
|
||||
}
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
_drawFeatures: function _drawFeatures(ctx, path) {
|
||||
var colorDomain = this.props.colorDomain;
|
||||
var features = this.props.features;
|
||||
if (!features) {
|
||||
return;
|
||||
}
|
||||
if (!colorDomain) {
|
||||
colorDomain = d3.extent(features.toArray(), this.props.valueAccessor);
|
||||
}
|
||||
var colorScale = d3.scale.linear()
|
||||
.domain(colorDomain)
|
||||
.range(this.props.colorRange)
|
||||
.clamp(true);
|
||||
features.forEach(function _forEach(feature) {
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
|
||||
ctx.lineWidth = '1';
|
||||
ctx.fillStyle = colorScale(this.props.valueAccessor(feature));
|
||||
var geometry = feature.get('geometry');
|
||||
path({
|
||||
type: geometry.get('type'),
|
||||
coordinates: geometry.get('coordinates').toJS()
|
||||
});
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}, this);
|
||||
},
|
||||
|
||||
render: function render() {
|
||||
var pixelRatio = window.devicePixelRatio || 1;
|
||||
return r.canvas({
|
||||
ref: 'overlay',
|
||||
width: this.props.width * pixelRatio,
|
||||
height: this.props.height * pixelRatio,
|
||||
style: {
|
||||
width: this.props.width + 'px',
|
||||
height: this.props.height + 'px',
|
||||
position: 'absolute',
|
||||
pointerEvents: 'none',
|
||||
opacity: this.props.globalOpacity,
|
||||
left: 0,
|
||||
top: 0
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ChoroplethOverlay;
|
||||
135
src/overlays/draggable-points.react.js
Normal file
135
src/overlays/draggable-points.react.js
Normal file
@ -0,0 +1,135 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var assign = require('object-assign');
|
||||
var React = require('react');
|
||||
var Immutable = require('immutable');
|
||||
var r = require('r-dom');
|
||||
var transform = require('svg-transform');
|
||||
var document = require('global/document');
|
||||
var nop = require('nop');
|
||||
var config = require('../config');
|
||||
var mouse = require('../utils').relativeMousePosition;
|
||||
|
||||
var DraggablePointsOverlay = React.createClass({
|
||||
|
||||
displayName: 'DraggablePointsOverlay',
|
||||
|
||||
PropTypes: {
|
||||
width: React.PropTypes.number.isRequired,
|
||||
height: React.PropTypes.number.isRequired,
|
||||
points: React.PropTypes.instanceOf(Immutable.List),
|
||||
project: React.PropTypes.func.isRequired,
|
||||
unproject: React.PropTypes.func.isRequired,
|
||||
isDragging: React.PropTypes.bool,
|
||||
keyAccessor: React.PropTypes.func,
|
||||
locationAccessor: React.PropTypes.func,
|
||||
onAddPoint: React.PropTypes.func,
|
||||
onUpdatePoint: React.PropTypes.func,
|
||||
renderPoint: React.PropTypes.func
|
||||
},
|
||||
|
||||
getDefaultProps: function getDefaultProps() {
|
||||
return {
|
||||
keyAccessor: function keyAccessor(point) {
|
||||
return point.get('id');
|
||||
},
|
||||
locationAccessor: function locationAccessor(point) {
|
||||
return point.get('location').toArray();
|
||||
},
|
||||
onAddPoint: nop,
|
||||
onUpdatePoint: nop,
|
||||
renderPoint: nop,
|
||||
isDragging: false
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState: function _getInitialState() {
|
||||
return {
|
||||
draggedPointKey: null
|
||||
};
|
||||
},
|
||||
|
||||
_onDragStart: function _onDragStart(point, event) {
|
||||
event.stopPropagation();
|
||||
document.addEventListener('mousemove', this._onDrag, false);
|
||||
document.addEventListener('mouseup', this._onDragEnd, false);
|
||||
this.setState({draggedPointKey: this.props.keyAccessor(point)});
|
||||
},
|
||||
|
||||
_onDrag: function _onDrag(event) {
|
||||
event.stopPropagation();
|
||||
var pixel = mouse(this.refs.container.getDOMNode(), event);
|
||||
var latlng = this.props.unproject(pixel);
|
||||
this.props.onUpdatePoint({
|
||||
key: this.state.draggedPointKey,
|
||||
location: [latlng.lat, latlng.lng]
|
||||
});
|
||||
},
|
||||
|
||||
_onDragEnd: function _onDragEnd(event) {
|
||||
event.stopPropagation();
|
||||
document.removeEventListener('mousemove', this._onDrag, false);
|
||||
document.removeEventListener('mouseup', this._onDragEnd, false);
|
||||
this.setState({draggedPoint: null});
|
||||
},
|
||||
|
||||
_addPoint: function _addPoint(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
var pixel = mouse(this.refs.container.getDOMNode(), event);
|
||||
var location = this.props.unproject(pixel);
|
||||
this.props.onAddPoint([location.lat, location.lng]);
|
||||
},
|
||||
|
||||
render: function render() {
|
||||
var points = this.props.points;
|
||||
return r.svg({
|
||||
ref: 'container',
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
style: assign({
|
||||
pointerEvents: 'all',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
cursor: this.props.isDragging ?
|
||||
config.CURSOR.GRABBING : config.CURSOR.GRAB
|
||||
}, this.props.style),
|
||||
onContextMenu: this._addPoint
|
||||
}, [
|
||||
r.g({style: {cursor: 'pointer'}}, points.map(function _map(point, index) {
|
||||
var _pixel = this.props.project(this.props.locationAccessor(point));
|
||||
var pixel = [_pixel.x, _pixel.y];
|
||||
return r.g({
|
||||
key: index,
|
||||
transform: transform([{translate: pixel}]),
|
||||
onMouseDown: this._onDragStart.bind(this, point),
|
||||
style: {
|
||||
pointerEvents: 'all'
|
||||
}
|
||||
}, this.props.renderPoint.call(this, point, pixel));
|
||||
}, this))
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = DraggablePointsOverlay;
|
||||
56
src/overlays/html.react.js
Normal file
56
src/overlays/html.react.js
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var r = require('r-dom');
|
||||
var assign = require('object-assign');
|
||||
|
||||
var HTMLOverlay = React.createClass({
|
||||
displayName: 'HTMLOverlay',
|
||||
propTypes: {
|
||||
width: React.PropTypes.number,
|
||||
height: React.PropTypes.number,
|
||||
redraw: React.PropTypes.func,
|
||||
project: React.PropTypes.func,
|
||||
isDragging: React.PropTypes.bool
|
||||
},
|
||||
render: function render() {
|
||||
var style = assign({}, {
|
||||
position: 'absolute',
|
||||
pointerEvents: 'none',
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
left: 0,
|
||||
top: 0
|
||||
}, this.props.style);
|
||||
return r.div({
|
||||
ref: 'overlay',
|
||||
style: style
|
||||
}, this.props.redraw({
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
project: this.props.project,
|
||||
isDragging: this.props.isDragging
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = HTMLOverlay;
|
||||
119
src/overlays/scatterplot.react.js
Normal file
119
src/overlays/scatterplot.react.js
Normal file
@ -0,0 +1,119 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var window = require('global/window');
|
||||
var d3 = require('d3');
|
||||
var r = require('r-dom');
|
||||
var Immutable = require('immutable');
|
||||
var COMPOSITE_TYPES = require('canvas-composite-types');
|
||||
|
||||
var ScatterplotOverlay = React.createClass({
|
||||
displayName: 'ScatterplotOverlay',
|
||||
|
||||
propTypes: {
|
||||
width: React.PropTypes.number,
|
||||
height: React.PropTypes.number,
|
||||
project: React.PropTypes.func,
|
||||
isDragging: React.PropTypes.bool,
|
||||
locations: React.PropTypes.instanceOf(Immutable.List),
|
||||
latLngAccessor: React.PropTypes.func,
|
||||
renderWhileDragging: React.PropTypes.bool,
|
||||
globalOpacity: React.PropTypes.number,
|
||||
dotRadius: React.PropTypes.number,
|
||||
dotFill: React.PropTypes.string,
|
||||
compositeOperation: React.PropTypes.oneOf(COMPOSITE_TYPES)
|
||||
},
|
||||
|
||||
getDefaultProps: function getDefaultProps() {
|
||||
return {
|
||||
latLngAccessor: function latLngAccessor(location) {
|
||||
return [location.get(0), location.get(1)];
|
||||
},
|
||||
renderWhileDragging: true,
|
||||
dotRadius: 4,
|
||||
dotFill: '#1FBAD6',
|
||||
globalOpacity: 1,
|
||||
// Same as browser default.
|
||||
compositeOperation: 'source-over'
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function componentDidMount() {
|
||||
this._redraw();
|
||||
},
|
||||
|
||||
componentDidUpdate: function componentDidUpdate() {
|
||||
this._redraw();
|
||||
},
|
||||
|
||||
_redraw: function _redraw() {
|
||||
var pixelRatio = window.devicePixelRatio;
|
||||
var canvas = this.getDOMNode();
|
||||
var ctx = canvas.getContext('2d');
|
||||
var props = this.props;
|
||||
var radius = this.props.dotRadius;
|
||||
var fill = this.props.dotFill;
|
||||
ctx.save();
|
||||
ctx.scale(pixelRatio, pixelRatio);
|
||||
ctx.clearRect(0, 0, props.width, props.height);
|
||||
ctx.globalCompositeOperation = this.props.compositeOperation;
|
||||
if ((this.props.renderWhileDragging || !this.props.isDragging) &&
|
||||
this.props.locations
|
||||
) {
|
||||
this.props.locations.forEach(function _forEach(location) {
|
||||
var latLng = this.props.latLngAccessor(location);
|
||||
var pixel = this.props.project(latLng);
|
||||
var pixelRounded = [d3.round(pixel.x, 1), d3.round(pixel.y, 1)];
|
||||
if (pixelRounded[0] + radius >= 0 &&
|
||||
pixelRounded[0] - radius < props.width &&
|
||||
pixelRounded[1] + radius >= 0 &&
|
||||
pixelRounded[1] - radius < props.height
|
||||
) {
|
||||
ctx.fillStyle = fill;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pixelRounded[0], pixelRounded[1], radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
ctx.restore();
|
||||
},
|
||||
render: function render() {
|
||||
var pixelRatio = window.devicePixelRatio;
|
||||
return r.canvas({
|
||||
ref: 'overlay',
|
||||
width: this.props.width * pixelRatio,
|
||||
height: this.props.height * pixelRatio,
|
||||
style: {
|
||||
width: this.props.width + 'px',
|
||||
height: this.props.height + 'px',
|
||||
position: 'absolute',
|
||||
pointerEvents: 'none',
|
||||
opacity: this.props.globalOpacity,
|
||||
left: 0,
|
||||
top: 0
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ScatterplotOverlay;
|
||||
57
src/overlays/svg.react.js
Normal file
57
src/overlays/svg.react.js
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var r = require('r-dom');
|
||||
var assign = require('object-assign');
|
||||
|
||||
var SVGOverlay = React.createClass({
|
||||
displayName: 'SVGOverlay',
|
||||
propTypes: {
|
||||
width: React.PropTypes.number,
|
||||
height: React.PropTypes.number,
|
||||
redraw: React.PropTypes.func,
|
||||
project: React.PropTypes.func,
|
||||
isDragging: React.PropTypes.bool
|
||||
},
|
||||
render: function render() {
|
||||
var style = assign({}, {
|
||||
pointerEvents: 'none',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0
|
||||
}, this.props.style);
|
||||
return r.svg({
|
||||
ref: 'overlay',
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
style: style
|
||||
}, this.props.redraw({
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
project: this.props.project,
|
||||
unproject: this.props.unproject,
|
||||
isDragging: this.props.isDragging
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = SVGOverlay;
|
||||
31
src/utils.js
Normal file
31
src/utils.js
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
'use strict';
|
||||
|
||||
function relativeMousePosition(container, event) {
|
||||
var rect = container.getBoundingClientRect();
|
||||
var x = event.clientX - rect.left - container.clientLeft;
|
||||
var y = event.clientY - rect.top - container.clientTop;
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
relativeMousePosition: relativeMousePosition
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user