diff --git a/README.md b/README.md index db28b17..f43db6e 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,25 @@ class App extends React.Component { ReactDOM.render(, document.getElementById('gmaps')); ``` +MarkerClusterer +---- + +You can cluster markers together (see [Google's docs](https://developers.google.com/maps/documentation/javascript/marker-clustering)) by setting the `clusterMarkers` prop on the `Gmaps` component. By default this will expect icons for the clusters named `m1.png, m2.png, m3.png, m4.png, m5.png` to reside at `your_web_root/images/`. + +You can pass an options object to this prop, allowing you to specify a new location for these cluster icons. The root format will append `1.png`, `2.png`, etc to the supplied path. + +For example if your images reside at `http://localhost:3000/cluster-icons/` and are still called `m1.png`, you would supply a path of `http://localhost:3000/cluster-icons/m` similar to: + +``` + + ... + +``` + +At current this is basic implementation and could be improved by adding Events to the clusters, another improvement would be to allow 'de-clustering' based on zoom level so when you have multiple markers with the exact same location you can still separate them and click on them ([stackoverlow example](https://stackoverflow.com/questions/15276908/google-markerclusterer-decluster-markers-below-a-certain-zoom-level?rq=1) or [OverlappingMarkerSpiderfier](https://github.com/jawj/OverlappingMarkerSpiderfier)). + Test ---- diff --git a/dist/components/entity.js b/dist/components/entity.js index b05aa1a..6471b64 100644 --- a/dist/components/entity.js +++ b/dist/components/entity.js @@ -37,6 +37,7 @@ exports['default'] = function (name, latLngProp, events) { var options = this.getOptions(this.props); this.entity = new google.maps[name](options); this.addListeners(this.entity, events); + this.props.onCreate(name, this.entity); }, componentWillReceiveProps: function componentWillReceiveProps(nextProps) { diff --git a/dist/components/gmaps.js b/dist/components/gmaps.js index 1e0c798..7d81c00 100644 --- a/dist/components/gmaps.js +++ b/dist/components/gmaps.js @@ -24,6 +24,10 @@ var _objectAssign = require('object-assign'); var _objectAssign2 = _interopRequireDefault(_objectAssign); +var _googleMarkerclusterer = require('@google/markerclusterer'); + +var _googleMarkerclusterer2 = _interopRequireDefault(_googleMarkerclusterer); + var _eventsMap = require('../events/map'); var _eventsMap2 = _interopRequireDefault(_eventsMap); @@ -41,11 +45,12 @@ var _utilsCompareProps = require('../utils/compare-props'); var _utilsCompareProps2 = _interopRequireDefault(_utilsCompareProps); var Gmaps = (0, _createReactClass2['default'])({ - mixins: [_mixinsListener2['default']], map: null, + markers: [], + getInitialState: function getInitialState() { return { isMapCreated: false @@ -91,6 +96,9 @@ var Gmaps = (0, _createReactClass2['default'])({ if (this.props.onMapCreated) { this.props.onMapCreated(this.map); } + if (this.props.clusterMarkers) { + new _googleMarkerclusterer2['default'](this.map, this.markers, typeof this.props.clusterMarkers === 'object' ? this.props.clusterMarkers : null); + } }, getChildren: function getChildren() { @@ -102,11 +110,16 @@ var Gmaps = (0, _createReactClass2['default'])({ } return _react2['default'].cloneElement(child, { ref: child.ref, - map: _this.map + map: _this.map, + onCreate: _this.handleChildCreation }); }); }, + handleChildCreation: function handleChildCreation(entityType, entity) { + if (entityType === 'Marker') this.markers.push(entity); + }, + render: function render() { var style = (0, _objectAssign2['default'])({ width: this.props.width, @@ -119,7 +132,6 @@ var Gmaps = (0, _createReactClass2['default'])({ this.state.isMapCreated ? this.getChildren() : null ); } - }); exports['default'] = Gmaps; diff --git a/package.json b/package.json index 270b594..feeda84 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-gmaps", - "version": "1.9.0", + "version": "1.9.1", "description": "A Google Maps component for React.js", "main": "dist/index.js", "scripts": { @@ -57,6 +57,7 @@ ] }, "dependencies": { + "@google/markerclusterer": "^1.0.3", "create-react-class": "^15.5.2", "object-assign": "^4.0.1" }, diff --git a/src/components/entity.js b/src/components/entity.js index ccf1be4..e75cb64 100644 --- a/src/components/entity.js +++ b/src/components/entity.js @@ -14,6 +14,7 @@ export default (name, latLngProp, events) => { const options = this.getOptions(this.props); this.entity = new google.maps[name](options); this.addListeners(this.entity, events); + this.props.onCreate(name, this.entity); }, componentWillReceiveProps(nextProps) { @@ -53,7 +54,7 @@ export default (name, latLngProp, events) => { break; } }, - + render() { return null; } diff --git a/src/components/gmaps.js b/src/components/gmaps.js index c0e3801..db14677 100644 --- a/src/components/gmaps.js +++ b/src/components/gmaps.js @@ -2,17 +2,19 @@ import React from 'react'; import ReactDOM from 'react-dom'; import createReactClass from 'create-react-class'; import objectAssign from 'object-assign'; +import MarkerClusterer from '@google/markerclusterer'; import MapEvents from '../events/map'; import Listener from '../mixins/listener'; import GoogleMaps from '../utils/google-maps'; import compareProps from '../utils/compare-props'; const Gmaps = createReactClass({ - mixins: [Listener], map: null, + markers: [], + getInitialState() { return { isMapCreated: false @@ -60,33 +62,49 @@ const Gmaps = createReactClass({ if (this.props.onMapCreated) { this.props.onMapCreated(this.map); } + if (this.props.clusterMarkers) { + new MarkerClusterer( + this.map, + this.markers, + typeof this.props.clusterMarkers === 'object' + ? this.props.clusterMarkers + : null + ); + } }, getChildren() { - return React.Children.map(this.props.children, (child) => { + return React.Children.map(this.props.children, child => { if (!React.isValidElement(child)) { return child; } return React.cloneElement(child, { ref: child.ref, - map: this.map + map: this.map, + onCreate: this.handleChildCreation }); }); }, + handleChildCreation(entityType, entity) { + if (entityType === 'Marker') this.markers.push(entity); + }, + render() { - const style = objectAssign({ - width: this.props.width, - height: this.props.height - }, this.props.style); + const style = objectAssign( + { + width: this.props.width, + height: this.props.height + }, + this.props.style + ); return (
{this.props.loadingMessage || 'Loading...'} {this.state.isMapCreated ? this.getChildren() : null}
); - }, - + } }); export default Gmaps; diff --git a/yarn.lock b/yarn.lock index 9824ca5..38e89b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@google/markerclusterer@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@google/markerclusterer/-/markerclusterer-1.0.3.tgz#830b9dbba85fae9537a16d17947b484f9e860c65" + integrity sha512-/fRbSPyQKnm43zRnoyMerbiGS3vG3WkZiLgpBF3ovoLO84sKhEAzKMVcyozy/khEHlZoMJ9Axkr0NRVJRAQhcg== + JSONStream@^1.0.3: version "1.3.1" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a"