Skip to content

Commit

Permalink
feat(MarkerClusterer): Support for MarkerClusterPlus API
Browse files Browse the repository at this point in the history
  • Loading branch information
farrrr authored and tomchentw committed Nov 19, 2015
1 parent 472454a commit d56551c
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"google-maps-infobox": "^1.1.13",
"invariant": "^2.1.1",
"lodash.isequal": "^3.0.4",
"marker-clusterer-plus": "^2.1.2",
"scriptjs": "^2.5.8",
"warning": "^2.1.0"
},
Expand Down
14 changes: 14 additions & 0 deletions src/Marker.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ export default class Marker extends Component {
this.setState({ marker });
}

componentWillUnmount () {
if (!canUseDOM) {
return;
}

const {anchorHolderRef, marker} = this.props;

if (anchorHolderRef) {
if ("MarkerClusterer" === anchorHolderRef.getAnchorType()) {
anchorHolderRef.getAnchor().removeMarker(marker);
}
}
}

render () {
if (this.state.marker) {
return (
Expand Down
102 changes: 102 additions & 0 deletions src/addons/MarkerClusterer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
default as React,
PropTypes,
Component,
} from 'react';

import {
default as canUseDOM,
} from 'can-use-dom';

import {
default as MarkerClustererCreator,
markerClusterDefaultPropTypes,
markerClusterControlledPropTypes,
markerClusterEventPropTypes,
} from './addonsCreators/MarkerClustererCreator';

export default class MarkerClusterer extends Component {
static propTypes = {
...markerClusterDefaultPropTypes,
...markerClusterControlledPropTypes,
...markerClusterEventPropTypes,
}

// Public APIs
// http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/docs/reference.html#events
getAvaerageCenter() { return this.state.markerClusterer.getAvaerageCenter(); }

getBatchSizeIE() { return this.state.markerClusterer.getBatchSizeIE(); }

getCalculator() { return this.state.markerClusterer.getCalculator(); }

getClusterClass() { return this.state.markerClusterer.getClusterClass(); }

getClusters() { return this.state.markerClusterer.getClusters(); }

getEnableRetinaIcons() { return this.state.markerClusterer.getEnableRetinaIcons(); }

getGridSize() { return this.state.markerClusterer.getGridSize(); }

getIgnoreHidden() { return this.state.markerClusterer.getIgnoreHidden(); }

getImageExtension() { return this.state.markerClusterer.getImageExtension(); }

getImagePath() { return this.state.markerClusterer.getImagePath(); }

getImageSize() { return this.state.markerClusterer.getImageSize(); }

getMarkers() { return this.state.markerClusterer.getMarkers(); }

getMaxZoom() { return this.state.markerClusterer.getMaxZoom(); }

getMinimumClusterSize() { return this.state.markerClusterer.getMinimumClusterSize(); }

getStyles() { return this.state.markerClusterer.getStyles(); }

getTitle() { return this.state.markerClusterer.getTitle(); }

getTotalClusters() { return this.state.markerClusterer.getTotalClusters(); }

getZoomOnClick() { return this.state.markerClusterer.getZoomOnClick(); }

// Public APIs - Use this carefully
addMarker(marker, nodraw = false) { return this.state.markerClusterer.addMarker(marker, nodraw); }

addMarkers(markers, nodraw = false) { return this.state.markerClusterer.addMarkers(markers, nodraw); }

removeMarker(marker, nodraw = false) { return this.state.markerClusterer.removeMarker(marker, nodraw); }

removeMarkers(markers, nodraw = false) { return this.state.markerClusterer.removeMarkers(markers, nodraw); }

clearMarkers() { return this.state.markerClusterer.clearMarkers(); }

fitMapToMarkers() { return this.state.markerClusterer.fitMapToMarkers(); }

repaint() { return this.state.markerClusterer.repaint(); }

state = {}

componentWillMount() {
if (!canUseDOM) {
return;
}

const { mapHolderRef, ...markerClustererProps } = this.props;
const markerClusterer = MarkerClustererCreator._createMarkerClusterer(mapHolderRef, markerClustererProps);

this.setState({ markerClusterer });
}

render() {
if (this.state.markerClusterer) {
return (
<MarkerClustererCreator markerClusterer={ this.state.markerClusterer } { ...this.props }>
{ this.props.children }
</MarkerClustererCreator>
);
} else {
return <noscript />;
}
}
}
135 changes: 135 additions & 0 deletions src/addons/addonsCreators/MarkerClustererCreator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {
default as React,
PropTypes,
Component,
Children,
} from 'react';
import { default as invariant } from 'invariant';

import { default as MarkerClustererEventList } from '../addonsEventLists/MarkerClustererEventList';
import { default as eventHandlerCreator } from '../../utils/eventHandlerCreator';
import { default as defaultPropsCreator } from '../../utils/defaultPropsCreator';
import { default as composeOptions } from '../../utils/composeOptions';
import { default as componentLifecycleDecorator } from '../../utils/componentLifecycleDecorator';
import { default as GoogleMapHolder } from '../../creators/GoogleMapHolder';

export const markerClustererControlledPropTypes = {
// http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/docs/reference.html
averageCenter: PropTypes.bool,
batchSizeIE: PropTypes.number,
calculator: PropTypes.func,
clusterClass: PropTypes.string,
enableRetinaIcons: PropTypes.bool,
gridSize: PropTypes.number,
ignoreHidden: PropTypes.bool,
imageExtension: PropTypes.string,
imagePath: PropTypes.string,
imageSizes: PropTypes.array,
maxZoom: PropTypes.number,
minimumClusterSize: PropTypes.number,
styles: PropTypes.array,
title: PropTypes.string,
zoomOnClick: PropTypes.bool,
};

export const markerClustererDefaultPropTypes = defaultPropsCreator(markerClustererControlledPropTypes);

const markerClustererUpdaters = {
averageCenter(averageCenter, component) { component.getMarkerClusterer().setAverageCenter(averageCenter); },

batchSizeIE(batchSizeIE, component) { component.getMarkerClusterer().setBatchSizeIE(batchSizeIE); },

calculator(calculator, component) { component.getMarkerClusterer().setCalculator(calculator); },

enableRetinaIcons(enableRetinaIcons, component) { component.getMarkerClusterer().setEnableRetinaIcons(enableRetinaIcons); },

gridSize(gridSize, component) { component.getMarkerClusterer().setGridSize(gridSize); },

ignoreHidden(ignoreHidden, component) { component.getMarkerClusterer().setIgnoreHidden(ignoreHidden); },

imageExtension(imageExtension, component) { component.getMarkerClusterer().setImageExtension(imageExtension); },

imagePath(imagePath, component) { component.getMarkerClusterer().setImagePath(imagePath); },

imageSizes(imageSizes, component) { component.getMarkerClusterer().setImageSizes(imageSizes); },

maxZoom(maxZoom, component) { component.getMarkerClusterer().setMaxZoom(maxZoom); },

minimumClusterSize(minimumClusterSize, component) { component.getMarkerClusterer().setMinimumClusterSize(minimumClusterSize); },

styles(styles, component) { component.getMarkerClusterer().setStyles(styles); },

title(title, component) { component.getMarkerClusterer().setTitle(title); },

zoomOnClick(zoomOnClick, component) { component.getMarkerClusterer().setZoomOnClick(zoomOnClick); },
};

const { eventPropTypes, registerEvents } = eventHandlerCreator(MarkerClustererEventList);

export const markerClustererEventPropTypes = eventPropTypes;

@componentLifecycleDecorator({
registerEvents,
instanceMethodName: 'getMarkerClusterer',
updaters: markerClustererUpdaters,
})
export default class MarkerClustererCreator extends Component {
static PropTypes = {
mapHolderRef: PropTypes.instanceOf(GoogleMapHolder).isRequired,
markerClusterer: PropTypes.object.isRequired,
}

static _createMarkerClusterer(mapHolderRef, markerClustererProps) {
const GoogleMarkerClusterer = require('marker-clusterer-plus');

// http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/docs/reference.html#events
const markerClusterer = new GoogleMarkerClusterer(mapHolderRef.getMap(), [], composeOptions(markerClustererProps, markerClustererControlledPropTypes));

return markerClusterer;
}

getMarkerClusterer() {
return this.props.markerClusterer;
}

componentDidUpdate(prevProps) {
this.props.markerClusterer.repaint();
}

componentWillUnmount() {
this.props.markerClusterer.setMap(null);
}

getAnchor() {
return this.props.markerClusterer;
}

getAnchorType() {
return 'MarkerClusterer';
}

render() {
const { mapHolderRef, children } = this.props;

if (0 < Children.count(children)) {
return (
<div>
{
Children.map(children, childElement => {
if (React.isValidElement(childElement)) {
return React.cloneElement(childElement, {
mapHolderRef,
anchorHolderRef: this,
});
} else {
return childElement;
}
})
}
</div>
);
} else {
return <noscript />;
}
}
}
8 changes: 8 additions & 0 deletions src/addons/addonsEventLists/MarkerClustererEventList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/docs/reference.html
export default [
"click",
"clusteringbegin",
"clusteringend",
"mouseout",
"mouseover",
];
10 changes: 8 additions & 2 deletions src/creators/MarkerCreator.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,17 @@ export default class MarkerCreator extends Component {
}

static _createMarker (markerProps) {
const {mapHolderRef} = markerProps;
const {mapHolderRef, anchorHolderRef} = markerProps;
// https://developers.google.com/maps/documentation/javascript/3.exp/reference#Marker
const marker = new google.maps.Marker(composeOptions(markerProps, markerControlledPropTypes));

marker.setMap(mapHolderRef.getMap());
if (anchorHolderRef) {
if ("MarkerClusterer" === anchorHolderRef.getAnchorType()) {
anchorHolderRef.getAnchor().addMarker(marker);
}
} else {
marker.setMap(mapHolderRef.getMap());
}

return marker;
}
Expand Down

0 comments on commit d56551c

Please sign in to comment.