cluster_marker_style 0.4.0
cluster_marker_style: ^0.4.0 copied to clipboard
Beautiful, count-aware, cached cluster marker icons for Google Maps. A drop-in styling/rendering layer for any map clustering algorithm.
cluster_marker_style #
Beautiful, count-aware, cached cluster marker icons for Google Maps — a one-line drop-in for any clustering algorithm.
This is a styling/rendering layer, not a clustering algorithm. You keep
using your clustering package of choice (e.g.
google_maps_cluster_manager_2);
this package supplies the cluster icon — rendered sharply, cached, and
good-looking by default.


Rendered by the package itself — regenerate with
flutter test --update-goldens tool/readme_image_test.dart.
The problem #
Clustering packages decide which markers group together, then hand you an
empty markerBuilder slot and walk away. To draw the cluster you end up
re-writing ~40 lines of PictureRecorder / Canvas / TextPainter
boilerplate — every project — with no caching (so the map janks on every camera
move), wrong device-pixel-ratio handling (blurry on high-DPI screens), and one
hardcoded color (a cluster of 5 looks identical to a cluster of 5000).
This is the still-open Flutter issue flutter/flutter#153092 ("Add support for custom cluster icon"). This package fills that gap.
Before / after #
| Before — ~40 lines, no cache, hardcoded | After — one line |
|---|---|
|
|
Why it's worth a dependency #
Drawing a circle is trivial. The value is everything around it that developers re-implement badly or skip:
- Caching — keyed on the visual outcome, not the raw count, so a cluster of 47 and 48 share one bitmap. This is the single biggest factor in a smooth map.
- Device-correct rendering — rasterized at native resolution via
imagePixelRatio, sharp on every screen, on Android, iOS, and web. - Count-aware defaults — color and size change with the count automatically, with zero tuning.
- Beautiful defaults — clearly better out of the box than the red/blue circle everyone hand-writes.
Quick start (with google_maps_cluster_manager_2) #
Add the dependencies:
dependencies:
cluster_marker_style_gmcm: ^0.1.0 # brings in cluster_marker_style
google_maps_flutter: ^2.9.0
google_maps_cluster_manager_2: ^3.0.0
Wire it up:
import 'package:cluster_marker_style_gmcm/cluster_marker_style_gmcm.dart';
// 1. Pick a style and create a renderer (reuse it — it holds the cache).
final renderer = ClusterRenderer(
ClusterStyle.soft(
tiers: const [
CountTier(upTo: 10, color: Colors.blue),
CountTier(upTo: 100, color: Colors.orange),
CountTier(upTo: 1000, color: Colors.red),
],
),
);
// 2. Hand the one-line builder to your ClusterManager.
ClusterManager<Place>(
places,
_updateMarkers,
markerBuilder: clusterMarkerBuilder(
renderer: renderer,
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
onTap: (cluster) => debugPrint('Tapped ${cluster.count}'),
// singleItemBuilder: (cluster) async => ... // optional override
),
);
To avoid icons popping in on the first frame, warm the cache once the device
pixel ratio is known (e.g. in didChangeDependencies):
renderer.precache(
counts: List.generate(99, (i) => i + 2), // every cluster up to 100
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
);
Omit tiers entirely to get the polished default ramp. See the runnable
example/ app, which clusters a few thousand points and switches
between styles live. (Add your own Google Maps API key — placeholders are in
example/android/.../AndroidManifest.xml and example/web/index.html.)
Named styles #
Three polished, zero-config looks that read on both light and dark maps:
| Style | Look |
|---|---|
ClusterStyle.soft() |
Cool→warm ramp, white text, thin translucent ring, soft shadow. |
ClusterStyle.flat() |
Solid Material colors, crisp, no ring or shadow. |
ClusterStyle.outlined() |
White fill with a bold count-aware colored ring + matching text. |
Each is fully overridable, or build a ClusterStyle(...) from scratch with your
own tiers, ClusterTextStyle, ClusterBorder, ClusterShadow, padding,
and CountFormatter.
Shapes #
Icons are circles by default. With long labels — a custom CountFormatter,
or large counts at a small size — circles shrink the text to fit. Switch to
ClusterShape.pill to keep the text size and widen the icon instead; short
labels still render as circles:
final style = ClusterStyle.soft().copyWith(shape: ClusterShape.pill);
Migrating from 0.3.x #
The adapters moved out of this package in 0.4.0. Swap the dependency and the import; the API is unchanged:
| 0.3.x import | 0.4.0 dependency + import |
|---|---|
cluster_marker_style/cluster_marker_style_gmcm.dart |
cluster_marker_style_gmcm → cluster_marker_style_gmcm/cluster_marker_style_gmcm.dart |
cluster_marker_style/cluster_marker_style_fluster.dart |
cluster_marker_style_fluster → cluster_marker_style_fluster/cluster_marker_style_fluster.dart |
Scope (and what this is not) #
This package styles cluster icons. Bring your own clustering algorithm. It is deliberately small and does exactly one thing well.
It is not a clustering algorithm, not a map widget, not an animation
system, and not a viewport/diff manager. The core renderer depends only on
google_maps_flutter (for BitmapDescriptor) — cluster_marker_style does
not depend on any clustering package, so it never adds version constraints
you didn't ask for. Clustering bridges are separate packages: the
google_maps_cluster_manager_2 one is
cluster_marker_style_gmcm.
There's also a fluster adapter,
cluster_marker_style_fluster. fluster only
runs the clustering algorithm — it hands back a flat list where isCluster
marks an aggregate and gives you no rendering at all — so flusterMarkers(...)
styles + caches those aggregates and delegates individual points to your builder:
import 'package:cluster_marker_style_fluster/cluster_marker_style_fluster.dart';
final markers = await flusterClusterMarkers<MapMarker>(
fluster: fluster,
bounds: [westLng, southLat, eastLng, northLat],
zoom: zoom.round(),
renderer: clusterRenderer,
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
pointMarkerBuilder: (p) async => Marker(
markerId: MarkerId(p.markerId!),
position: LatLng(p.latitude!, p.longitude!),
),
);
More clustering-tool adapters are planned (e.g. for flutter_map clustering
tools). Because the core is independent, new adapters are thin bridges that don't
touch it.
API surface #
Core (cluster_marker_style.dart): ClusterStyle (+ .soft/.flat/.outlined),
CountTier, ClusterShape, ClusterTextStyle, ClusterBorder,
ClusterShadow, CountFormatter (+ DefaultCountFormatter), ClusterRenderer,
ClusterIconCache, ClusterCacheKey, and the tierFor / sizeFor helpers.
Adapter packages (each re-exports the core):
cluster_marker_style_gmcm→clusterMarkerBuilder(...)forgoogle_maps_cluster_manager_2.cluster_marker_style_fluster→flusterMarkers(...)/flusterClusterMarkers(...)forfluster.
License #
MIT — see LICENSE.
