0
# Marker Clustering
1
2
Components for grouping nearby markers into clusters to improve performance and user experience with large marker datasets. Includes both community and official Google clustering implementations, plus direct access to the Google Maps MarkerClusterer library.
3
4
## Capabilities
5
6
### MarkerClusterer Component
7
8
Community-driven marker clustering component that groups nearby markers into clusters for better performance and visual clarity.
9
10
```typescript { .api }
11
/**
12
* Groups nearby markers into clusters for better performance
13
* Community implementation with extensive customization options
14
*/
15
interface MarkerClustererProps {
16
children: React.ReactNode;
17
options?: ClustererOptions;
18
19
// Event handlers
20
onClusteringBegin?: (clusterer: Clusterer) => void;
21
onClusteringEnd?: (clusterer: Clusterer) => void;
22
onClick?: (cluster: Cluster) => void;
23
onMouseOver?: (cluster: Cluster) => void;
24
onMouseOut?: (cluster: Cluster) => void;
25
26
// Lifecycle events
27
onLoad?: (clusterer: Clusterer) => void;
28
onUnmount?: (clusterer: Clusterer) => void;
29
}
30
31
interface ClustererOptions {
32
gridSize?: number;
33
maxZoom?: number;
34
zoomOnClick?: boolean;
35
averageCenter?: boolean;
36
minimumClusterSize?: number;
37
styles?: ClusterIconStyle[];
38
calculator?: (markers: google.maps.Marker[], numStyles: number) => ClusterIconInfo;
39
ignoreHidden?: boolean;
40
enableRetinaIcons?: boolean;
41
imageExtension?: string;
42
imagePath?: string;
43
imageSizes?: number[];
44
title?: string;
45
}
46
47
function MarkerClusterer(props: MarkerClustererProps): JSX.Element;
48
function MarkerClustererF(props: MarkerClustererProps): JSX.Element;
49
50
// Community clusterer type definitions
51
interface Clusterer {
52
setAverageCenter(averageCenter: boolean): void;
53
setBatchSizeIE(batchSizeIE: number): void;
54
setCalculator(calculator: TCalculator): void;
55
setClusterClass(clusterClass: string): void;
56
setEnableRetinaIcons(enableRetinaIcons: boolean): void;
57
setGridSize(gridSize: number): void;
58
setIgnoreHidden(ignoreHidden: boolean): void;
59
setImageExtension(imageExtension: string): void;
60
setImagePath(imagePath: string): void;
61
setImageSizes(imageSizes: number[]): void;
62
setMaxZoom(maxZoom: number): void;
63
setMinimumClusterSize(minimumClusterSize: number): void;
64
setStyles(styles: ClusterIconStyle[]): void;
65
setTitle(title: string): void;
66
setZoomOnClick(zoomOnClick: boolean): void;
67
addMarker(marker: google.maps.Marker, nodraw?: boolean): void;
68
addMarkers(markers: google.maps.Marker[], nodraw?: boolean): void;
69
clearMarkers(): void;
70
getCalculator(): TCalculator;
71
getGridSize(): number;
72
getMap(): google.maps.Map;
73
getMarkers(): google.maps.Marker[];
74
getMaxZoom(): number;
75
getStyles(): ClusterIconStyle[];
76
getTotalClusters(): number;
77
getTotalMarkers(): number;
78
}
79
80
interface Cluster {
81
getCenter(): google.maps.LatLng;
82
getSize(): number;
83
getMarkers(): google.maps.Marker[];
84
}
85
86
interface ClusterIconInfo {
87
text: string;
88
index: number;
89
title?: string;
90
}
91
92
type TCalculator = (markers: google.maps.Marker[], numStyles: number) => ClusterIconInfo;
93
```
94
95
**Usage Examples:**
96
97
```typescript
98
import React, { useState, useMemo } from 'react';
99
import { GoogleMap, LoadScript, Marker, MarkerClusterer } from '@react-google-maps/api';
100
101
// Basic marker clustering
102
function BasicMarkerClustering() {
103
const markers = useMemo(() => {
104
const locations = [];
105
const center = { lat: 40.7128, lng: -74.0060 };
106
107
// Generate random markers around NYC
108
for (let i = 0; i < 100; i++) {
109
locations.push({
110
id: i,
111
position: {
112
lat: center.lat + (Math.random() - 0.5) * 0.2,
113
lng: center.lng + (Math.random() - 0.5) * 0.2
114
}
115
});
116
}
117
return locations;
118
}, []);
119
120
return (
121
<LoadScript googleMapsApiKey="YOUR_API_KEY">
122
<GoogleMap
123
center={{ lat: 40.7128, lng: -74.0060 }}
124
zoom={10}
125
mapContainerStyle={{ width: '100%', height: '400px' }}
126
>
127
<MarkerClusterer>
128
{markers.map(marker => (
129
<Marker
130
key={marker.id}
131
position={marker.position}
132
/>
133
))}
134
</MarkerClusterer>
135
</GoogleMap>
136
</LoadScript>
137
);
138
}
139
140
// Custom clustering with styled clusters
141
function CustomStyledClustering() {
142
const [clusterSize, setClusterSize] = useState(40);
143
144
const markers = useMemo(() => {
145
const locations = [];
146
const center = { lat: 40.7128, lng: -74.0060 };
147
148
for (let i = 0; i < 200; i++) {
149
locations.push({
150
id: i,
151
position: {
152
lat: center.lat + (Math.random() - 0.5) * 0.3,
153
lng: center.lng + (Math.random() - 0.5) * 0.3
154
},
155
title: `Marker ${i + 1}`
156
});
157
}
158
return locations;
159
}, []);
160
161
const clusterOptions = {
162
gridSize: clusterSize,
163
styles: [
164
{
165
textColor: 'white',
166
url: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m1.png',
167
height: 53,
168
width: 53
169
},
170
{
171
textColor: 'white',
172
url: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m2.png',
173
height: 56,
174
width: 56
175
},
176
{
177
textColor: 'white',
178
url: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m3.png',
179
height: 66,
180
width: 66
181
}
182
],
183
maxZoom: 15,
184
zoomOnClick: true,
185
averageCenter: true,
186
minimumClusterSize: 2
187
};
188
189
return (
190
<LoadScript googleMapsApiKey="YOUR_API_KEY">
191
<div>
192
<div style={{ padding: '10px', background: '#f0f0f0' }}>
193
<label>
194
Cluster Grid Size: {clusterSize}
195
<input
196
type="range"
197
min="20"
198
max="100"
199
value={clusterSize}
200
onChange={(e) => setClusterSize(parseInt(e.target.value))}
201
style={{ marginLeft: '10px' }}
202
/>
203
</label>
204
</div>
205
206
<GoogleMap
207
center={{ lat: 40.7128, lng: -74.0060 }}
208
zoom={10}
209
mapContainerStyle={{ width: '100%', height: '400px' }}
210
>
211
<MarkerClusterer
212
options={clusterOptions}
213
onLoad={(clusterer) => console.log('Clusterer loaded')}
214
onClick={(cluster) => console.log('Cluster clicked:', cluster.getSize())}
215
>
216
{markers.map(marker => (
217
<Marker
218
key={marker.id}
219
position={marker.position}
220
title={marker.title}
221
/>
222
))}
223
</MarkerClusterer>
224
</GoogleMap>
225
</div>
226
</LoadScript>
227
);
228
}
229
230
// Advanced clustering with custom calculator
231
function AdvancedClustering() {
232
const [showClustering, setShowClustering] = useState(true);
233
234
const markers = useMemo(() => {
235
// Generate markers with different categories
236
const categories = ['restaurant', 'hotel', 'attraction', 'shop'];
237
const locations = [];
238
const center = { lat: 40.7128, lng: -74.0060 };
239
240
for (let i = 0; i < 150; i++) {
241
locations.push({
242
id: i,
243
position: {
244
lat: center.lat + (Math.random() - 0.5) * 0.25,
245
lng: center.lng + (Math.random() - 0.5) * 0.25
246
},
247
category: categories[Math.floor(Math.random() * categories.length)],
248
rating: Math.floor(Math.random() * 5) + 1
249
});
250
}
251
return locations;
252
}, []);
253
254
const customCalculator = (markers: google.maps.Marker[], numStyles: number) => {
255
let index = 0;
256
const count = markers.length;
257
let dv = count;
258
259
while (dv !== 0) {
260
dv = Math.floor(dv / 10);
261
index++;
262
}
263
264
index = Math.min(index, numStyles);
265
266
return {
267
text: count.toString(),
268
index: index,
269
title: `Cluster of ${count} markers`
270
};
271
};
272
273
const clusterStyles = [
274
{
275
textColor: 'white',
276
textSize: 12,
277
url: 'data:image/svg+xml;base64,' + btoa(`
278
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40">
279
<circle cx="20" cy="20" r="18" fill="#ff6b6b" stroke="white" stroke-width="2"/>
280
</svg>
281
`),
282
height: 40,
283
width: 40
284
},
285
{
286
textColor: 'white',
287
textSize: 14,
288
url: 'data:image/svg+xml;base64,' + btoa(`
289
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50">
290
<circle cx="25" cy="25" r="23" fill="#4285f4" stroke="white" stroke-width="2"/>
291
</svg>
292
`),
293
height: 50,
294
width: 50
295
},
296
{
297
textColor: 'white',
298
textSize: 16,
299
url: 'data:image/svg+xml;base64,' + btoa(`
300
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60">
301
<circle cx="30" cy="30" r="28" fill="#34a853" stroke="white" stroke-width="2"/>
302
</svg>
303
`),
304
height: 60,
305
width: 60
306
}
307
];
308
309
const clusterOptions = {
310
styles: clusterStyles,
311
calculator: customCalculator,
312
gridSize: 50,
313
maxZoom: 14,
314
zoomOnClick: true,
315
averageCenter: true
316
};
317
318
return (
319
<LoadScript googleMapsApiKey="YOUR_API_KEY">
320
<div>
321
<div style={{ padding: '10px', background: '#f0f0f0' }}>
322
<label>
323
<input
324
type="checkbox"
325
checked={showClustering}
326
onChange={(e) => setShowClustering(e.target.checked)}
327
/>
328
Enable Clustering ({markers.length} markers)
329
</label>
330
</div>
331
332
<GoogleMap
333
center={{ lat: 40.7128, lng: -74.0060 }}
334
zoom={11}
335
mapContainerStyle={{ width: '100%', height: '400px' }}
336
>
337
{showClustering ? (
338
<MarkerClusterer options={clusterOptions}>
339
{markers.map(marker => (
340
<Marker
341
key={marker.id}
342
position={marker.position}
343
title={`${marker.category} (Rating: ${marker.rating})`}
344
icon={`https://maps.google.com/mapfiles/ms/icons/${
345
marker.category === 'restaurant' ? 'red' :
346
marker.category === 'hotel' ? 'blue' :
347
marker.category === 'attraction' ? 'green' : 'yellow'
348
}-dot.png`}
349
/>
350
))}
351
</MarkerClusterer>
352
) : (
353
markers.map(marker => (
354
<Marker
355
key={marker.id}
356
position={marker.position}
357
title={`${marker.category} (Rating: ${marker.rating})`}
358
icon={`https://maps.google.com/mapfiles/ms/icons/${
359
marker.category === 'restaurant' ? 'red' :
360
marker.category === 'hotel' ? 'blue' :
361
marker.category === 'attraction' ? 'green' : 'yellow'
362
}-dot.png`}
363
/>
364
))
365
)}
366
</GoogleMap>
367
</div>
368
</LoadScript>
369
);
370
}
371
```
372
373
### MarkerClustererF Component
374
375
Functional component variant of MarkerClusterer that uses React hooks internally.
376
377
```typescript { .api }
378
/**
379
* Functional variant of MarkerClusterer component using hooks internally
380
*/
381
function MarkerClustererF(props: MarkerClustererProps): JSX.Element;
382
```
383
384
### GoogleMarkerClusterer Component
385
386
Official Google Maps marker clustering implementation with enhanced performance and features.
387
388
```typescript { .api }
389
/**
390
* Official Google marker clustering implementation
391
* Enhanced performance and features compared to community version
392
*/
393
interface GoogleMarkerClustererProps {
394
children: React.ReactNode;
395
options?: google.maps.MarkerClustererOptions;
396
397
// Event handlers
398
onClusterClick?: (event: google.maps.MapMouseEvent, cluster: google.maps.markerclusterer.Cluster, map: google.maps.Map) => void;
399
400
// Lifecycle events
401
onLoad?: (clusterer: google.maps.MarkerClusterer) => void;
402
onUnmount?: (clusterer: google.maps.MarkerClusterer) => void;
403
}
404
405
interface google.maps.MarkerClustererOptions {
406
algorithm?: google.maps.markerclusterer.Algorithm;
407
map?: google.maps.Map;
408
markers?: google.maps.Marker[];
409
renderer?: google.maps.markerclusterer.Renderer;
410
onClusterClick?: (event: google.maps.MapMouseEvent, cluster: google.maps.markerclusterer.Cluster, map: google.maps.Map) => void;
411
}
412
413
function GoogleMarkerClusterer(props: GoogleMarkerClustererProps): JSX.Element;
414
```
415
416
**Usage Examples:**
417
418
```typescript
419
import React, { useMemo } from 'react';
420
import {
421
GoogleMap,
422
LoadScript,
423
Marker,
424
GoogleMarkerClusterer
425
} from '@react-google-maps/api';
426
427
// Basic Google marker clustering
428
function BasicGoogleClustering() {
429
const markers = useMemo(() => {
430
const locations = [];
431
const center = { lat: 40.7128, lng: -74.0060 };
432
433
for (let i = 0; i < 1000; i++) {
434
locations.push({
435
id: i,
436
position: {
437
lat: center.lat + (Math.random() - 0.5) * 0.5,
438
lng: center.lng + (Math.random() - 0.5) * 0.5
439
}
440
});
441
}
442
return locations;
443
}, []);
444
445
return (
446
<LoadScript googleMapsApiKey="YOUR_API_KEY">
447
<GoogleMap
448
center={{ lat: 40.7128, lng: -74.0060 }}
449
zoom={9}
450
mapContainerStyle={{ width: '100%', height: '400px' }}
451
>
452
<GoogleMarkerClusterer
453
onLoad={(clusterer) => console.log('Google clusterer loaded')}
454
onClusterClick={(event, cluster, map) => {
455
console.log('Cluster clicked:', cluster.count);
456
map.fitBounds(cluster.bounds);
457
}}
458
>
459
{markers.map(marker => (
460
<Marker
461
key={marker.id}
462
position={marker.position}
463
/>
464
))}
465
</GoogleMarkerClusterer>
466
</GoogleMap>
467
</LoadScript>
468
);
469
}
470
471
// Google clustering with custom renderer
472
function CustomGoogleClustering() {
473
const markers = useMemo(() => {
474
const locations = [];
475
const center = { lat: 40.7128, lng: -74.0060 };
476
477
for (let i = 0; i < 500; i++) {
478
locations.push({
479
id: i,
480
position: {
481
lat: center.lat + (Math.random() - 0.5) * 0.4,
482
lng: center.lng + (Math.random() - 0.5) * 0.4
483
},
484
weight: Math.floor(Math.random() * 10) + 1
485
});
486
}
487
return locations;
488
}, []);
489
490
// Custom renderer for cluster styling
491
const createCustomRenderer = () => {
492
return {
493
render: ({ count, position }: { count: number; position: google.maps.LatLng }) => {
494
const color = count > 100 ? '#ea4335' : count > 50 ? '#fbbc05' : '#34a853';
495
const size = count > 100 ? 60 : count > 50 ? 50 : 40;
496
497
const svg = `
498
<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
499
<circle cx="${size/2}" cy="${size/2}" r="${size/2 - 2}" fill="${color}" stroke="white" stroke-width="2"/>
500
<text x="${size/2}" y="${size/2}" text-anchor="middle" dy="0.3em" font-family="Arial" font-size="12" font-weight="bold" fill="white">
501
${count}
502
</text>
503
</svg>
504
`;
505
506
return new google.maps.Marker({
507
position,
508
icon: {
509
url: `data:image/svg+xml;base64,${btoa(svg)}`,
510
scaledSize: new google.maps.Size(size, size),
511
anchor: new google.maps.Point(size / 2, size / 2)
512
},
513
label: {
514
text: count.toString(),
515
color: 'white',
516
fontSize: '12px',
517
fontWeight: 'bold'
518
},
519
zIndex: 1000 + count
520
});
521
}
522
};
523
};
524
525
const clusterOptions = {
526
renderer: createCustomRenderer(),
527
// algorithm: new google.maps.markerclusterer.SuperClusterAlgorithm({
528
// radius: 100,
529
// maxZoom: 16
530
// })
531
};
532
533
return (
534
<LoadScript googleMapsApiKey="YOUR_API_KEY">
535
<GoogleMap
536
center={{ lat: 40.7128, lng: -74.0060 }}
537
zoom={10}
538
mapContainerStyle={{ width: '100%', height: '400px' }}
539
>
540
<GoogleMarkerClusterer
541
options={clusterOptions}
542
onLoad={(clusterer) => console.log('Custom Google clusterer loaded')}
543
onClusterClick={(event, cluster, map) => {
544
console.log('Custom cluster clicked:', cluster);
545
}}
546
>
547
{markers.map(marker => (
548
<Marker
549
key={marker.id}
550
position={marker.position}
551
title={`Weight: ${marker.weight}`}
552
/>
553
))}
554
</GoogleMarkerClusterer>
555
</GoogleMap>
556
</LoadScript>
557
);
558
}
559
```
560
561
### Clustering Performance Comparison
562
563
Performance comparison and recommendations for choosing between clustering implementations.
564
565
```typescript { .api }
566
/**
567
* Performance characteristics of different clustering approaches
568
*/
569
interface ClusteringPerformanceMetrics {
570
markerCount: number;
571
renderTime: number;
572
memoryUsage: number;
573
interactionLatency: number;
574
}
575
576
// Performance testing utility
577
const measureClusteringPerformance = (
578
clusteringComponent: React.ReactElement,
579
markerCount: number
580
): Promise<ClusteringPerformanceMetrics> => {
581
// Implementation would measure actual performance metrics
582
return Promise.resolve({
583
markerCount,
584
renderTime: 0,
585
memoryUsage: 0,
586
interactionLatency: 0
587
});
588
};
589
```
590
591
**Performance Comparison Examples:**
592
593
```typescript
594
// Side-by-side clustering comparison
595
function ClusteringComparison() {
596
const [markerCount, setMarkerCount] = useState(100);
597
const [activeMethod, setActiveMethod] = useState<'community' | 'google'>('community');
598
599
const markers = useMemo(() => {
600
const locations = [];
601
const center = { lat: 40.7128, lng: -74.0060 };
602
603
for (let i = 0; i < markerCount; i++) {
604
locations.push({
605
id: i,
606
position: {
607
lat: center.lat + (Math.random() - 0.5) * 0.3,
608
lng: center.lng + (Math.random() - 0.5) * 0.3
609
}
610
});
611
}
612
return locations;
613
}, [markerCount]);
614
615
return (
616
<LoadScript googleMapsApiKey="YOUR_API_KEY">
617
<div>
618
<div style={{ padding: '10px', background: '#f0f0f0' }}>
619
<div style={{ marginBottom: '10px' }}>
620
<label>
621
Marker Count: {markerCount}
622
<input
623
type="range"
624
min="50"
625
max="2000"
626
step="50"
627
value={markerCount}
628
onChange={(e) => setMarkerCount(parseInt(e.target.value))}
629
style={{ marginLeft: '10px', width: '200px' }}
630
/>
631
</label>
632
</div>
633
634
<div>
635
<label style={{ marginRight: '20px' }}>
636
<input
637
type="radio"
638
value="community"
639
checked={activeMethod === 'community'}
640
onChange={(e) => setActiveMethod(e.target.value as 'community')}
641
/>
642
Community Clusterer
643
</label>
644
<label>
645
<input
646
type="radio"
647
value="google"
648
checked={activeMethod === 'google'}
649
onChange={(e) => setActiveMethod(e.target.value as 'google')}
650
/>
651
Google Clusterer
652
</label>
653
</div>
654
655
<div style={{ marginTop: '10px', fontSize: '12px', color: '#666' }}>
656
Recommendations:
657
<ul style={{ margin: '5px 0', paddingLeft: '20px' }}>
658
<li>Community: Better customization, lighter weight (<1000 markers)</li>
659
<li>Google: Better performance, official support (>1000 markers)</li>
660
</ul>
661
</div>
662
</div>
663
664
<GoogleMap
665
center={{ lat: 40.7128, lng: -74.0060 }}
666
zoom={10}
667
mapContainerStyle={{ width: '100%', height: '400px' }}
668
>
669
{activeMethod === 'community' ? (
670
<MarkerClusterer
671
options={{
672
gridSize: 60,
673
maxZoom: 15,
674
zoomOnClick: true
675
}}
676
>
677
{markers.map(marker => (
678
<Marker key={marker.id} position={marker.position} />
679
))}
680
</MarkerClusterer>
681
) : (
682
<GoogleMarkerClusterer>
683
{markers.map(marker => (
684
<Marker key={marker.id} position={marker.position} />
685
))}
686
</GoogleMarkerClusterer>
687
)}
688
</GoogleMap>
689
</div>
690
</LoadScript>
691
);
692
}
693
694
// Clustering with dynamic data loading
695
function DynamicClusteringExample() {
696
const [markers, setMarkers] = useState<Array<{id: number, position: google.maps.LatLngLiteral}>>([]);
697
const [isLoading, setIsLoading] = useState(false);
698
699
const loadMoreMarkers = async (count: number) => {
700
setIsLoading(true);
701
702
// Simulate API call
703
await new Promise(resolve => setTimeout(resolve, 1000));
704
705
const newMarkers = [];
706
const center = { lat: 40.7128, lng: -74.0060 };
707
const startId = markers.length;
708
709
for (let i = 0; i < count; i++) {
710
newMarkers.push({
711
id: startId + i,
712
position: {
713
lat: center.lat + (Math.random() - 0.5) * 0.5,
714
lng: center.lng + (Math.random() - 0.5) * 0.5
715
}
716
});
717
}
718
719
setMarkers(prev => [...prev, ...newMarkers]);
720
setIsLoading(false);
721
};
722
723
const clearMarkers = () => {
724
setMarkers([]);
725
};
726
727
return (
728
<LoadScript googleMapsApiKey="YOUR_API_KEY">
729
<div>
730
<div style={{ padding: '10px', background: '#f0f0f0' }}>
731
<div>Markers: {markers.length}</div>
732
<button
733
onClick={() => loadMoreMarkers(100)}
734
disabled={isLoading}
735
style={{ marginRight: '10px' }}
736
>
737
{isLoading ? 'Loading...' : 'Add 100 Markers'}
738
</button>
739
<button
740
onClick={() => loadMoreMarkers(500)}
741
disabled={isLoading}
742
style={{ marginRight: '10px' }}
743
>
744
{isLoading ? 'Loading...' : 'Add 500 Markers'}
745
</button>
746
<button onClick={clearMarkers}>
747
Clear All
748
</button>
749
</div>
750
751
<GoogleMap
752
center={{ lat: 40.7128, lng: -74.0060 }}
753
zoom={9}
754
mapContainerStyle={{ width: '100%', height: '400px' }}
755
>
756
<GoogleMarkerClusterer
757
onLoad={(clusterer) => {
758
console.log('Clusterer ready for', markers.length, 'markers');
759
}}
760
>
761
{markers.map(marker => (
762
<Marker
763
key={marker.id}
764
position={marker.position}
765
title={`Marker ${marker.id}`}
766
/>
767
))}
768
</GoogleMarkerClusterer>
769
</GoogleMap>
770
</div>
771
</LoadScript>
772
);
773
}
774
```
775
776
### Clustering Best Practices
777
778
Guidelines for optimal clustering implementation and performance.
779
780
```typescript { .api }
781
/**
782
* Best practices for marker clustering implementation
783
*/
784
interface ClusteringBestPractices {
785
// Performance optimization
786
maxMarkersBeforeClustering: number; // ~500-1000 markers
787
optimalGridSize: number; // 40-80 pixels
788
maxZoomForClustering: number; // 15-17
789
790
// Visual design
791
clusterIconSizes: number[]; // [40, 50, 60] progressive sizes
792
colorScheme: string[]; // Consistent color progression
793
textVisibility: boolean; // Always show count numbers
794
795
// Interaction design
796
zoomOnClusterClick: boolean; // Usually true
797
spiderfyOnClick: boolean; // For overlapping markers
798
showClusterBounds: boolean; // Debug/development only
799
}
800
801
// Example optimized clustering configuration
802
const optimizedClusterConfig = {
803
community: {
804
gridSize: 60,
805
maxZoom: 15,
806
zoomOnClick: true,
807
averageCenter: true,
808
minimumClusterSize: 2,
809
styles: [
810
{ textColor: 'white', url: '/cluster-small.png', height: 40, width: 40 },
811
{ textColor: 'white', url: '/cluster-medium.png', height: 50, width: 50 },
812
{ textColor: 'white', url: '/cluster-large.png', height: 60, width: 60 }
813
]
814
},
815
google: {
816
// Use default algorithm for best performance
817
// Customize renderer for visual consistency
818
}
819
};
820
```
821
822
## GoogleMapsMarkerClusterer Namespace
823
824
Direct access to the official Google Maps MarkerClusterer library with all its classes, algorithms, and renderers.
825
826
### GoogleMapsMarkerClusterer Namespace Export
827
828
Complete namespace re-export providing access to the official `@googlemaps/markerclusterer` library components.
829
830
```typescript { .api }
831
/**
832
* Complete namespace export of @googlemaps/markerclusterer
833
* Provides direct access to Google's official clustering library
834
*/
835
import { GoogleMapsMarkerClusterer } from "@react-google-maps/api";
836
837
// Access to core MarkerClusterer class
838
class MarkerClusterer {
839
constructor(options: {
840
algorithm?: Algorithm;
841
map?: google.maps.Map;
842
markers?: google.maps.Marker[];
843
renderer?: Renderer;
844
onClusterClick?: onClusterClickHandler;
845
});
846
847
addMarker(marker: google.maps.Marker, noDraw?: boolean): void;
848
addMarkers(markers: google.maps.Marker[], noDraw?: boolean): void;
849
clearMarkers(noDraw?: boolean): void;
850
render(): void;
851
setMap(map: google.maps.Map | null): void;
852
}
853
854
// Access to clustering algorithms
855
interface Algorithm {
856
calculate(request: AlgorithmInput): AlgorithmOutput;
857
}
858
859
// Access to cluster renderers
860
interface Renderer {
861
render(cluster: Cluster, stats: ClusterStats): google.maps.Marker;
862
}
863
864
// Cluster statistics for renderer
865
interface ClusterStats {
866
count: number;
867
markers: google.maps.Marker[];
868
}
869
870
// Event handler types
871
type onClusterClickHandler = (
872
event: google.maps.MapMouseEvent,
873
cluster: Cluster,
874
map: google.maps.Map
875
) => void;
876
```
877
878
**Usage Example:**
879
880
```typescript
881
import { GoogleMapsMarkerClusterer } from "@react-google-maps/api";
882
883
// Direct instantiation of Google's MarkerClusterer
884
function MyAdvancedClusteringComponent() {
885
const map = useGoogleMap();
886
887
React.useEffect(() => {
888
if (map && markers.length > 0) {
889
const markerClusterer = new GoogleMapsMarkerClusterer.MarkerClusterer({
890
map,
891
markers,
892
onClusterClick: (event, cluster, map) => {
893
map.fitBounds(cluster.bounds!);
894
}
895
});
896
897
return () => markerClusterer.setMap(null);
898
}
899
}, [map, markers]);
900
901
return null;
902
}
903
```