Skip to content

Commit 94f4d1b

Browse files
committed
New major release 🎉
1 parent 13eaecd commit 94f4d1b

File tree

8 files changed

+167
-109
lines changed

8 files changed

+167
-109
lines changed

README.md

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,22 @@ React Native module that handles map clustering for you.
44

55
Works with **Expo** and **react-native-cli** 🚀
66

7-
This repo is proudly sponsored by:
8-
9-
<a href="https://nativeforms.com" rel="nofollow" target="_blank">
10-
<img src="https://raw.githubusercontent.com/venits/native-forms/master/assets/sponsor.png" width="350"><br />
11-
Build forms, surveys and polls for React Native apps.
12-
</a>
13-
147
## Demo
158

169
![Demo](https://raw.githubusercontent.com/venits/react-native-map-clustering/assets/assets/demo.gif)
1710

18-
## Spiral
19-
20-
### Converting same locations in spiral view (done automatically)
21-
22-
![Spiral](https://raw.githubusercontent.com/venits/react-native-map-clustering/assets/assets/spider_lib.png)
23-
2411
## Installation
2512

2613
```js
27-
// clustering module
2814
npm install react-native-map-clustering --save
15+
// yarn add react-native-map-clustering
2916

3017
// and only if you haven't installed it before
3118
npm install react-native-maps --save
3219
```
3320

3421
### Full example
3522

36-
Example of how to use clustering.
37-
3823
```js
3924
import React from "react";
4025
import MapView from "react-native-map-clustering";
@@ -44,7 +29,7 @@ const INITIAL_REGION = {
4429
latitude: 52.5,
4530
longitude: 19.2,
4631
latitudeDelta: 8.5,
47-
longitudeDelta: 8.5
32+
longitudeDelta: 8.5,
4833
};
4934

5035
const App = () => (
@@ -58,10 +43,6 @@ const App = () => (
5843
<Marker coordinate={{ latitude: 52.2, longitude: 21 }} />
5944
<Marker coordinate={{ latitude: 52.4, longitude: 21 }} />
6045
<Marker coordinate={{ latitude: 51.8, longitude: 20 }} />
61-
<Marker coordinate={{ latitude: 51.8, longitude: 20 }} />
62-
<Marker coordinate={{ latitude: 51.8, longitude: 20 }} />
63-
<Marker coordinate={{ latitude: 51.8, longitude: 20 }} />
64-
<Marker coordinate={{ latitude: 51.8, longitude: 20 }} />
6546
</MapView>
6647
);
6748

@@ -74,7 +55,9 @@ export default App;
7455
| ------------------------------------------- | --------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7556
| **clusterColor** | String | #00B386 | Background color of cluster. |
7657
| **clusterTextColor** | String | #FFFFFF | Color of text in cluster. |
58+
| **clusterFontFamily** | String | undefined | Font family of text in cluster. |
7759
| **onClusterPress(cluster, markers)** | Function | () => {} | Allows you to control cluster on click event. Function returns information about cluster and its markers. |
60+
| **tracksClusterViewChanges** | Bool | false | Sets whether the cluster markers should track view changes. It's turned off by default to improve cluster markers performance. |
7861
| **width** | Number | window width | map's width. |
7962
| **height** | Number | window height | map's height. |
8063
| **radius** | Number | window.width \* 6% | [SuperCluster radius](https://github.com/mapbox/supercluster#options). |
@@ -86,14 +69,23 @@ export default App;
8669
| **animationEnabled** | Bool | true | Animate imploding/exploding of clusters' markers and clusters size change. **Works only on iOS**. |
8770
| **layoutAnimationConf** | LayoutAnimationConfig | LayoutAnimation.Presets.spring | `LayoutAnimation.Presets.spring` | Custom Layout animation configuration object for clusters animation during implode / explode **Works only on iOS**. |
8871
| **onRegionChangeComplete(region, markers)** | Function | () => {} | Called when map's region changes. In return you get current region and markers data. |
72+
| **onMarkersChange(markers)** | Function | () => {} | Called when markers change. In return you get markers data. |
8973
| **mapRef(ref)** | Function | () => {} | Return reference to `react-native-maps` MapView component. |
9074
| **clusteringEnabled** | Bool | true | Set true to enable and false to disable clustering. |
75+
| **spiralEnabled** | Bool | true | Set true to enable and false to disable spiral view. |
9176
| **renderCluster** | Function | undefined | Enables you to render custom cluster with custom styles and logic. |
92-
| **spiderLineColor** | String | #FF0000 | Enables you to set color of spider line which joins spiral location with center location. |
77+
| **spiderLineColor** | String | #FF0000 | Enables you to set color of spider line which joins spiral location with center location. |
78+
79+
#### This repo is proudly sponsored by:
80+
81+
<a href="https://nativeforms.com" rel="nofollow" target="_blank">
82+
<img src="https://raw.githubusercontent.com/venits/native-forms/master/assets/sponsor.png" width="350"><br />
83+
Build forms, surveys and polls for React Native apps.
84+
</a>
9385

9486
## Support
9587

9688
Feel free to create issues and pull requests. I will try to provide as much support as possible over Github. In case of questions or problems, contact me at:
97-
[t.przybyl@venits.com](t.przybyl@venits.com)
89+
[tony@venits.com](tony@venits.com)
9890

99-
### Happy Coding 💖
91+
### Happy Coding 💖🚀

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"react": "16.8.3",
1313
"react-dom": "16.8.3",
1414
"react-native": "https://github.com/expo/react-native/archive/sdk-35.0.0.tar.gz",
15-
"react-native-map-clustering": "^3.0.6",
15+
"react-native-map-clustering": "^3.2.0",
1616
"react-native-maps": "^0.26.1",
1717
"react-native-web": "^0.11.7"
1818
},

index.d.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
declare module "react-native-map-clustering" {
2+
import * as React from "react";
3+
import { LayoutAnimationConfig } from "react-native";
4+
import Map, { MapViewProps, Marker } from "react-native-maps";
5+
6+
export type Cluster = {};
7+
8+
interface MapClusteringProps {
9+
clusteringEnabled?: boolean;
10+
spiralEnabled?: boolean;
11+
animationEnabled?: boolean;
12+
preserveClusterPressBehavior?: boolean;
13+
tracksClusterViewChanges?: boolean;
14+
layoutAnimationConf?: LayoutAnimationConfig;
15+
radius?: number;
16+
maxZoom?: number;
17+
minZoom?: number;
18+
extent?: number;
19+
nodeSize?: number;
20+
edgePadding?: { top: number; left: number; right: number; bottom: number };
21+
clusterColor?: string;
22+
clusterTextColor?: string;
23+
clusterFontFamily?: string;
24+
spiderLineColor?: string;
25+
onClusterPress?: (cluster: Marker, markers?: Marker[]) => void;
26+
mapRef?: (ref: React.Ref<Map>) => void;
27+
onMarkersChange?: (markers?: Marker[]) => void;
28+
}
29+
30+
export default class MapView extends React.Component<
31+
MapViewProps & MapClusteringProps,
32+
any
33+
> {}
34+
}

index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
import * as MapView from "./lib/ClusteredMapView";
2-
32
module.exports = MapView;

lib/ClusteredMapView.js

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { memo, useState, useEffect, useMemo, createRef } from "react";
1+
import React, { memo, useState, useEffect, useMemo, useRef } from "react";
22
import { Dimensions, LayoutAnimation, Platform } from "react-native";
33
import MapView, { Marker, Polyline } from "react-native-maps";
44
import SuperCluster from "supercluster";
@@ -8,7 +8,7 @@ import {
88
markerToGeoJSONFeature,
99
calculateBBox,
1010
returnMapZoom,
11-
generateSpiral
11+
generateSpiral,
1212
} from "./helpers";
1313

1414
const ClusteredMapView = ({
@@ -20,14 +20,18 @@ const ClusteredMapView = ({
2020
children,
2121
onClusterPress,
2222
onRegionChangeComplete,
23+
onMarkersChange,
2324
preserveClusterPressBehavior,
2425
clusteringEnabled,
2526
clusterColor,
2627
clusterTextColor,
28+
clusterFontFamily,
2729
spiderLineColor,
2830
layoutAnimationConf,
2931
animationEnabled,
3032
renderCluster,
33+
tracksClusterViewChanges,
34+
spiralEnabled,
3135
...restProps
3236
}) => {
3337
const [markers, updateMarkers] = useState([]);
@@ -39,26 +43,27 @@ const ClusteredMapView = ({
3943
);
4044

4145
const [isSpiderfier, updateSpiderfier] = useState(false);
42-
const [spiderfierMarker, updateSpiderfierMarker] = useState(null);
4346
const [clusterChildren, updateClusterChildren] = useState(null);
44-
const mapRef = createRef();
47+
const mapRef = useRef();
4548

4649
const propsChildren = useMemo(() => React.Children.toArray(children), [
47-
children
50+
children,
4851
]);
4952

5053
useEffect(() => {
5154
const rawData = [];
5255
const otherChildren = [];
5356

5457
if (!clusteringEnabled) {
58+
updateSpiderMarker([]);
59+
updateMarkers([]);
5560
updateChildren(propsChildren);
5661
return;
5762
}
5863

59-
React.Children.forEach(children, (child, i) => {
64+
React.Children.forEach(children, (child, index) => {
6065
if (isMarker(child)) {
61-
rawData.push(markerToGeoJSONFeature(child, i));
66+
rawData.push(markerToGeoJSONFeature(child, index));
6267
} else {
6368
otherChildren.push(child);
6469
}
@@ -69,7 +74,7 @@ const ClusteredMapView = ({
6974
maxZoom,
7075
minZoom,
7176
extent,
72-
nodeSize
77+
nodeSize,
7378
});
7479

7580
superCluster.load(rawData);
@@ -81,26 +86,26 @@ const ClusteredMapView = ({
8186
updateMarkers(markers);
8287
updateChildren(otherChildren);
8388
setSuperCluster(superCluster);
84-
}, [children, restProps.region, restProps.initialRegion]);
89+
}, [children, restProps.region, restProps.initialRegion, clusteringEnabled]);
8590

8691
useEffect(() => {
92+
if (!spiralEnabled) return;
93+
8794
if (isSpiderfier && markers.length > 0) {
88-
let positions = generateSpiral(
89-
markers[0].properties.point_count,
90-
markers[0].geometry.coordinates,
91-
clusterChildren
92-
);
93-
updateSpiderMarker(positions);
94-
updateSpiderfierMarker({
95-
latitude: markers[0].geometry.coordinates[1],
96-
longitude: markers[0].geometry.coordinates[0]
95+
let allSpiderMarkers;
96+
markers.map((marker) => {
97+
let positions = generateSpiral(marker, clusterChildren);
98+
if (allSpiderMarkers instanceof Array)
99+
allSpiderMarkers = allSpiderMarkers.concat(positions);
100+
else allSpiderMarkers = positions;
97101
});
102+
updateSpiderMarker(allSpiderMarkers);
98103
} else {
99104
updateSpiderMarker([]);
100105
}
101106
}, [isSpiderfier]);
102107

103-
const _onRegionChangeComplete = region => {
108+
const _onRegionChangeComplete = (region) => {
104109
if (superCluster) {
105110
const bBox = calculateBBox(region);
106111
const zoom = returnMapZoom(region, bBox, minZoom);
@@ -109,20 +114,23 @@ const ClusteredMapView = ({
109114
if (animationEnabled && Platform.OS === "ios") {
110115
LayoutAnimation.configureNext(layoutAnimationConf);
111116
}
112-
if (zoom >= 17 && markers.length === 1 && clusterChildren) {
113-
updateSpiderfier(true);
117+
if (zoom >= 17 && markers.length > 0 && clusterChildren) {
118+
if (spiralEnabled) updateSpiderfier(true);
114119
} else {
115-
updateSpiderfier(false);
120+
if (spiralEnabled) updateSpiderfier(false);
116121
}
117122

118123
updateMarkers(markers);
124+
onMarkersChange(markers);
119125
onRegionChangeComplete(region, markers);
120126
updateRegion(region);
127+
} else {
128+
onRegionChangeComplete(region);
121129
}
122130
};
123131

124-
const _onClusterPress = cluster => () => {
125-
const children = superCluster.getLeaves(cluster.id, (limit = Infinity));
132+
const _onClusterPress = (cluster) => () => {
133+
const children = superCluster.getLeaves(cluster.id, Infinity);
126134
updateClusterChildren(children);
127135

128136
if (preserveClusterPressBehavior) {
@@ -132,11 +140,11 @@ const ClusteredMapView = ({
132140

133141
const coordinates = children.map(({ geometry }) => ({
134142
latitude: geometry.coordinates[1],
135-
longitude: geometry.coordinates[0]
143+
longitude: geometry.coordinates[0],
136144
}));
137145

138146
mapRef.current.fitToCoordinates(coordinates, {
139-
edgePadding: restProps.edgePadding
147+
edgePadding: restProps.edgePadding,
140148
});
141149

142150
onClusterPress(cluster, children);
@@ -145,12 +153,13 @@ const ClusteredMapView = ({
145153
return (
146154
<MapView
147155
{...restProps}
148-
ref={map => {
156+
ref={(map) => {
149157
restProps.mapRef(map);
150158
mapRef.current = map;
151159
}}
152-
onRegionChangeComplete={_onRegionChangeComplete}>
153-
{markers.map(marker =>
160+
onRegionChangeComplete={_onRegionChangeComplete}
161+
>
162+
{markers.map((marker) =>
154163
marker.properties.point_count === 0 ? (
155164
propsChildren[marker.properties.index]
156165
) : !isSpiderfier ? (
@@ -159,7 +168,8 @@ const ClusteredMapView = ({
159168
onPress: _onClusterPress(marker),
160169
clusterColor,
161170
clusterTextColor,
162-
...marker
171+
clusterFontFamily,
172+
...marker,
163173
})
164174
) : (
165175
<ClusterMarker
@@ -168,30 +178,29 @@ const ClusteredMapView = ({
168178
onPress={_onClusterPress(marker)}
169179
clusterColor={clusterColor}
170180
clusterTextColor={clusterTextColor}
181+
clusterFontFamily={clusterFontFamily}
182+
tracksClusterViewChanges={tracksClusterViewChanges}
171183
/>
172184
)
173185
) : null
174186
)}
175187
{otherChildren}
176-
{spiderMarkers.map(marker => (
177-
<Marker
178-
key={marker.latitude}
179-
coordinate={marker}
180-
image={marker.image}
181-
onPress={marker.onPress}
182-
></Marker>
183-
))}
188+
{spiderMarkers.map((marker) => {
189+
return propsChildren[marker.index]
190+
? React.cloneElement(propsChildren[marker.index], {
191+
coordinate: { ...marker },
192+
})
193+
: null;
194+
})}
184195
{spiderMarkers.map((marker, index) => {
185196
{
186197
return (
187-
spiderfierMarker && (
188-
<Polyline
189-
key={index}
190-
coordinates={[spiderfierMarker, marker, spiderfierMarker]}
191-
strokeColor={spiderLineColor}
192-
strokeWidth={1}
193-
/>
194-
)
198+
<Polyline
199+
key={index}
200+
coordinates={[marker.centerPoint, marker, marker.centerPoint]}
201+
strokeColor={spiderLineColor}
202+
strokeWidth={1}
203+
/>
195204
);
196205
}
197206
})}
@@ -201,9 +210,11 @@ const ClusteredMapView = ({
201210

202211
ClusteredMapView.defaultProps = {
203212
clusteringEnabled: true,
213+
spiralEnabled: true,
204214
animationEnabled: true,
205215
preserveClusterPressBehavior: false,
206216
layoutAnimationConf: LayoutAnimation.Presets.spring,
217+
tracksClusterViewChanges: false,
207218
// SuperCluster parameters
208219
radius: Dimensions.get("window").width * 0.06,
209220
maxZoom: 20,
@@ -219,7 +230,8 @@ ClusteredMapView.defaultProps = {
219230
// Callbacks
220231
onRegionChangeComplete: () => {},
221232
onClusterPress: () => {},
222-
mapRef: () => {}
233+
onMarkersChange: () => {},
234+
mapRef: () => {},
223235
};
224236

225237
export default memo(ClusteredMapView);

0 commit comments

Comments
 (0)