diff --git a/example/examples/choropleth.react.js b/example/examples/choropleth.react.js index fb5818e0..4bffb781 100644 --- a/example/examples/choropleth.react.js +++ b/example/examples/choropleth.react.js @@ -17,7 +17,6 @@ // 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'); @@ -57,6 +56,9 @@ var ChoroplethOverlayExample = React.createClass({ }, _onChangeViewport: function _onChangeViewport(opt) { + if (this.props.onChangeViewport) { + return this.props.onChangeViewport(opt); + } this.setState({ latitude: opt.latitude, longitude: opt.longitude, @@ -67,24 +69,18 @@ var ChoroplethOverlayExample = React.createClass({ }, 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, - startDragLngLat: this.state.startDragLngLat, - 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') - }) - ]); + return r(MapGL, assign({}, this.state, this.props, { + onChangeViewport: this._onChangeViewport, + overlays: function overlays(viewport) { + return r(ChoroplethOverlay, assign({}, viewport, { + globalOpacity: 0.8, + colorDomain: [0, 500, 1000], + colorRange: ['#31a354', '#addd8e', '#f7fcb9'], + renderWhileDragging: false, + features: ZIPCODES_SF.get('features') + })); + } + }, this.props)); } }); diff --git a/example/examples/custom.react.js b/example/examples/custom.react.js index bfb28b03..cb784fda 100644 --- a/example/examples/custom.react.js +++ b/example/examples/custom.react.js @@ -67,6 +67,9 @@ var OverlayExample = React.createClass({ }, _onChangeViewport: function _onChangeViewport(opt) { + if (this.props.onChangeViewport) { + return this.props.onChangeViewport(opt); + } this.setState({ latitude: opt.latitude, longitude: opt.longitude, @@ -76,40 +79,33 @@ var OverlayExample = React.createClass({ }); }, - render: function render() { - return r(MapGL, assign({ - latitude: this.state.latitude, - longitude: this.state.longitude, - zoom: this.state.zoom, - startDragLngLat: this.state.startDragLngLat, - 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.longitude, location.latitude]); - 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[0], p1[1]); - opt.ctx.lineTo(p2[0], p2[1]); - opt.ctx.stroke(); - opt.ctx.beginPath(); - opt.ctx.fillStyle = alphaify('#1FBAD6', 0.4); - opt.ctx.arc(p2[0], p2[1], 6, 0, 2 * Math.PI); - opt.ctx.fill(); - opt.ctx.beginPath(); - opt.ctx.fillStyle = '#FFFFFF'; - opt.ctx.textAlign = 'center'; - opt.ctx.fillText(index, p2[0], p2[1] + 4); - }); - }}), + _renderOverlays: function _renderOverlays(viewport) { + return [ + r(CanvasOverlay, assign({}, viewport, { + redraw: function _redrawCanvas(opt) { + var p1 = opt.project([location.longitude, location.latitude]); + 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[0], p1[1]); + opt.ctx.lineTo(p2[0], p2[1]); + opt.ctx.stroke(); + opt.ctx.beginPath(); + opt.ctx.fillStyle = alphaify('#1FBAD6', 0.4); + opt.ctx.arc(p2[0], p2[1], 6, 0, 2 * Math.PI); + opt.ctx.fill(); + opt.ctx.beginPath(); + opt.ctx.fillStyle = '#FFFFFF'; + opt.ctx.textAlign = 'center'; + opt.ctx.fillText(index, p2[0], p2[1] + 4); + }); + } + })), // We use invisible SVG elements to support interactivity. - r(SVGOverlay, { + r(SVGOverlay, assign({}, viewport, { redraw: function _redrwaSVGOverlay(opt) { var p1 = opt.project([location.longitude, location.latitude]); var style = { @@ -144,8 +140,15 @@ var OverlayExample = React.createClass({ }, this)) ); }.bind(this) - }) - ]); + })) + ]; + }, + + render: function render() { + return r(MapGL, assign({}, this.state, this.props, { + onChangeViewport: this._onChangeViewport, + overlays: this._renderOverlays + }, this.props)); } }); diff --git a/example/examples/geodata-creator.react.js b/example/examples/geodata-creator.react.js index e98684c4..f5d2480e 100644 --- a/example/examples/geodata-creator.react.js +++ b/example/examples/geodata-creator.react.js @@ -68,6 +68,9 @@ var GeodataCreator = React.createClass({ }, _onChangeViewport: function _onChangeViewport(opt) { + if (this.props.onChangeViewport) { + return this.props.onChangeViewport(opt); + } this.setState({ latitude: opt.latitude, longitude: opt.longitude, @@ -95,52 +98,50 @@ var GeodataCreator = React.createClass({ this.setState({points: points}); }, + _renderOverlays: function _renderOverlays(viewport) { + return [ + r(SVGOverlay, assign({}, viewport, {redraw: function _redraw(opt) { + if (!this.state.points.size) { + return null; + } + var d = 'M' + this.state.points.map(function _map(point) { + return opt.project(point.get('location').toArray()); + }).join('L'); + return r.path({ + style: {stroke: '#1FBAD6', strokeWidth: 2, fill: 'none'}, + d: d + }); + }.bind(this)})), + r(DraggableOverlay, assign({}, viewport, { + 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')) + ]); + } + })) + ]; + }, + 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, - startDragLngLat: this.state.startDragLngLat, - isDragging: this.state.isDragging, + r(MapGL, assign({}, this.state, this.props, { 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) { - return opt.project(point.get('location').toArray()); - }).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')) - ]); - } - }) - ]) + onChangeViewport: this._onChangeViewport, + overlays: this._renderOverlays + }, this.props)) ]); } }); diff --git a/example/examples/not-interactive.react.js b/example/examples/not-interactive.react.js index d5e4b484..11cca4a3 100644 --- a/example/examples/not-interactive.react.js +++ b/example/examples/not-interactive.react.js @@ -17,7 +17,6 @@ // 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'); diff --git a/example/examples/route.react.js b/example/examples/route.react.js index d4346bb4..7dcfca96 100644 --- a/example/examples/route.react.js +++ b/example/examples/route.react.js @@ -54,6 +54,9 @@ var RouteOverlayExample = React.createClass({ }, _onChangeViewport: function _onChangeViewport(opt) { + if (this.props.onChangeViewport) { + return this.props.onChangeViewport(opt); + } this.setState({ latitude: opt.latitude, longitude: opt.longitude, @@ -110,19 +113,16 @@ var RouteOverlayExample = React.createClass({ }, 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, - startDragLngLat: this.state.startDragLngLat, - isDragging: this.state.isDragging, - onChangeViewport: this.props.onChangeViewport || this._onChangeViewport - }, this.props), [ - r(SVGOverlay, {redraw: this._redrawSVGOverlay}), - r(CanvasOverlay, {redraw: this._redrawCanvasOverlay}) - ]); + return r(MapGL, assign({}, this.state, this.props, { + onChangeViewport: this._onChangeViewport, + overlays: function overlays(viewport) { + return [ + r(SVGOverlay, assign({redraw: this._redrawSVGOverlay}, viewport)), + r(CanvasOverlay, assign({redraw: this._redrawCanvasOverlay}, + viewport)) + ]; + }.bind(this) + }, this.props)); } }); diff --git a/example/examples/scatterplot.react.js b/example/examples/scatterplot.react.js index 45ccded5..e3417e2e 100644 --- a/example/examples/scatterplot.react.js +++ b/example/examples/scatterplot.react.js @@ -61,6 +61,9 @@ var ScatterplotOverlayExample = React.createClass({ }, _onChangeViewport: function _onChangeViewport(opt) { + if (this.props.onChangeViewport) { + return this.props.onChangeViewport(opt); + } this.setState({ latitude: opt.latitude, longitude: opt.longitude, @@ -71,23 +74,17 @@ var ScatterplotOverlayExample = React.createClass({ }, render: function render() { - return r(MapGL, assign({ - latitude: this.state.latitude, - longitude: this.state.longitude, - zoom: this.state.zoom, - isDragging: this.state.isDragging, - startDragLngLat: this.state.startDragLngLat, - 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' - }) - ]); + return r(MapGL, assign({}, this.state, this.props, { + onChangeViewport: this._onChangeViewport, + overlays: function overlay(viewport) { + return r(ScatterplotOverlay, assign({}, viewport, { + locations: locations, + dotRadius: 2, + globalOpacity: 1, + compositeOperation: 'screen' + })); + } + }, this.props)); } }); diff --git a/src/map.react.js b/src/map.react.js index d5fd8842..ff21c4fd 100644 --- a/src/map.react.js +++ b/src/map.react.js @@ -155,7 +155,14 @@ var MapGL = React.createClass({ * The first argument of the callback will be the array of feature the * mouse is over. This is the same response returned from `featuresAt`. */ - onClickFeatures: React.PropTypes.func + onClickFeatures: React.PropTypes.func, + + /** + * A Callback used to render data overlays. This function will be passed + * a viewport object that includes two methods, `project` and `unproject` + * as well as the props, latitude, longitude, zoom, width, and height. + */ + overlays: React.PropTypes.func }, getDefaultProps: function getDefaultProps() { @@ -542,8 +549,9 @@ var MapGL = React.createClass({ }, _renderOverlays: function _renderOverlays(transform) { - var children = []; - + if (!this.props.overlays) { + return null; + } var viewportConfig = { center: [this.props.longitude, this.props.latitude], zoom: this.props.zoom, @@ -551,24 +559,24 @@ var MapGL = React.createClass({ dimensions: [this.props.width, this.props.height] }; - var viewport = ViewportMercator(viewportConfig); + var mercator = ViewportMercator(viewportConfig); + + // This is the `viewport` object exposed to the overlays. + var viewport = { + width: this.props.width, + height: this.props.height, + longitude: this.props.longitude, + latitude: this.props.latitude, + zoom: this.props.zoom, + isDragging: this.props.isDragging, + project: mercator.project, + unproject: mercator.unproject + }; - 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: viewport.project, - unproject: viewport.unproject - })); - }, this); return r.div({ className: 'overlays', style: {position: 'absolute', left: 0, top: 0} - }, children); + }, this.props.overlays(viewport)); }, render: function render() { @@ -588,7 +596,8 @@ var MapGL = React.createClass({ var content = [ r.div({ref: 'mapboxMap', style: style, className: props.className}), - this._renderOverlays(transform) + this._renderOverlays(transform), + r.div({position: 'absolute', left: 0, top: 0}, this.props.children) ]; if (this.props.onChangeViewport) { diff --git a/src/overlays/canvas.react.js b/src/overlays/canvas.react.js index e879e206..ea26f185 100644 --- a/src/overlays/canvas.react.js +++ b/src/overlays/canvas.react.js @@ -32,11 +32,11 @@ var CanvasOverlay = React.createClass({ displayName: 'CanvasOverlay', propTypes: { - width: React.PropTypes.number, - height: React.PropTypes.number, + width: React.PropTypes.number.isRequired, + height: React.PropTypes.number.isRequired, redraw: React.PropTypes.func.isRequired, - project: React.PropTypes.func, - isDragging: React.PropTypes.bool + project: React.PropTypes.func.isRequired, + isDragging: React.PropTypes.bool.isRequired }, componentDidMount: function componentDidMount() { diff --git a/src/overlays/choropleth.react.js b/src/overlays/choropleth.react.js index 2506765e..7f775d69 100644 --- a/src/overlays/choropleth.react.js +++ b/src/overlays/choropleth.react.js @@ -30,19 +30,19 @@ 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, + width: React.PropTypes.number.isRequired, + height: React.PropTypes.number.isRequired, + project: React.PropTypes.func.isRequired, + isDragging: React.PropTypes.bool.isRequired, + renderWhileDragging: React.PropTypes.bool.isRequired, + globalOpacity: React.PropTypes.number.isRequired, /** * An Immutable List of feature objects. */ features: React.PropTypes.instanceOf(Immutable.List), colorDomain: React.PropTypes.array, - colorRange: React.PropTypes.array, - valueAccessor: React.PropTypes.func + colorRange: React.PropTypes.array.isRequired, + valueAccessor: React.PropTypes.func.isRequired }, getDefaultProps: function getDefaultProps() { diff --git a/src/overlays/draggable-points.react.js b/src/overlays/draggable-points.react.js index ae44dae8..6efe5252 100644 --- a/src/overlays/draggable-points.react.js +++ b/src/overlays/draggable-points.react.js @@ -34,17 +34,17 @@ var DraggablePointsOverlay = React.createClass({ displayName: 'DraggablePointsOverlay', propTypes: { - width: React.PropTypes.number, - height: React.PropTypes.number, - points: React.PropTypes.instanceOf(Immutable.List), - project: React.PropTypes.func, - unproject: React.PropTypes.func, - isDragging: React.PropTypes.bool, - keyAccessor: React.PropTypes.func, - lngLatAccessor: React.PropTypes.func, - onAddPoint: React.PropTypes.func, - onUpdatePoint: React.PropTypes.func, - renderPoint: React.PropTypes.func + width: React.PropTypes.number.isRequired, + height: React.PropTypes.number.isRequired, + points: React.PropTypes.instanceOf(Immutable.List).isRequired, + project: React.PropTypes.func.isRequired, + unproject: React.PropTypes.func.isRequired, + isDragging: React.PropTypes.bool.isRequired, + keyAccessor: React.PropTypes.func.isRequired, + lngLatAccessor: React.PropTypes.func.isRequired, + onAddPoint: React.PropTypes.func.isRequired, + onUpdatePoint: React.PropTypes.func.isRequired, + renderPoint: React.PropTypes.func.isRequired }, getDefaultProps: function getDefaultProps() { diff --git a/src/overlays/html.react.js b/src/overlays/html.react.js index 3efece5e..1b7de05c 100644 --- a/src/overlays/html.react.js +++ b/src/overlays/html.react.js @@ -28,11 +28,11 @@ 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 + width: React.PropTypes.number.isRequired, + height: React.PropTypes.number.isRequired, + redraw: React.PropTypes.func.isRequired, + project: React.PropTypes.func.isRequired, + isDragging: React.PropTypes.bool.isRequired }, render: function render() { diff --git a/src/overlays/scatterplot.react.js b/src/overlays/scatterplot.react.js index 4ab3ee92..72b21d2a 100644 --- a/src/overlays/scatterplot.react.js +++ b/src/overlays/scatterplot.react.js @@ -31,17 +31,17 @@ 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), - lngLatAccessor: React.PropTypes.func, + width: React.PropTypes.number.isRequired, + height: React.PropTypes.number.isRequired, + project: React.PropTypes.func.isRequired, + isDragging: React.PropTypes.bool.isRequired, + locations: React.PropTypes.instanceOf(Immutable.List).isRequired, + lngLatAccessor: React.PropTypes.func.isRequired, renderWhileDragging: React.PropTypes.bool, - globalOpacity: React.PropTypes.number, - dotRadius: React.PropTypes.number, - dotFill: React.PropTypes.string, - compositeOperation: React.PropTypes.oneOf(COMPOSITE_TYPES) + globalOpacity: React.PropTypes.number.isRequired, + dotRadius: React.PropTypes.number.isRequired, + dotFill: React.PropTypes.string.isRequired, + compositeOperation: React.PropTypes.oneOf(COMPOSITE_TYPES).isRequired }, getDefaultProps: function getDefaultProps() { diff --git a/src/overlays/svg.react.js b/src/overlays/svg.react.js index bc54f874..fef22d36 100644 --- a/src/overlays/svg.react.js +++ b/src/overlays/svg.react.js @@ -28,11 +28,11 @@ 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 + width: React.PropTypes.number.isRequired, + height: React.PropTypes.number.isRequired, + redraw: React.PropTypes.func.isRequired, + project: React.PropTypes.func.isRequired, + isDragging: React.PropTypes.bool.isRequired }, render: function render() {