Is it possible to use a custom icon instead of a circle
and a symbol
(with the cluster_count
string) for Mapbox GL Js' "Superclusters?"
My goal here is to approximate something like Pie Clusters or MiniCharts that used to be possible in Leaflet. I need some way to visually represent what's inside a cluster.
Best Answer
I wound up getting a satisfactory result, but it took some doing. I expanded on this very helpful Supercluster property aggregation example with custom icons.
The main thing is to use the external Supercluster library to do all the clustering, and not use mapbox-gl's built-in clustering at all. Trying to share the work between internal and external Supercluster just complicates things. Future versions of mapbox-gl will expose this functionality in their API, but we can just use the external Supercluster for now. I'm using
mapbox-gl@0.41.0
andsupercluster@2.3.0
here.First, create the cluster object with the
initial
/map
/reduce
methods for property aggregation:Then, to get the cluster data as a geojson feature collection, I used the
featureCollection
function in@turf/helpers
:Note that
worldBounds
is the bounding box inwhich to calculate your clusters. I used the whole world because I don't have a lot of clusters. If you use your viewport bounding box, you'll need to recalculate clusters every time the map moves.intZoom
is the current integer zoom level (Math.floor(this.mapZoom)
).On the actual map, add the
clusterData
geojson featureCollection as a source.I also added a regular mapbox-gl layer for the unclustered points as per the regular cluster example. (
filter: ['!has', 'point_count'],
). The clusters themselves will be all custom icons, though.Mapbox GL doesn't do "layers" when it comes to custom icons (not in the way Leaflet does), so just use an array to keep track of all of your icon objects. (
this.customClusters = []
)Each time you change your
clusterData
, you will want to update and re-render the clusters:As I mentioned above, using custom icons means we can't use mapbox-gl's filter system, so we'll have to manually filter the array and update the clusters.
The last step is to re-calculate and re-render the clusters when the map or the data changes.
Re-calculate clusters when the zoom level changes by a whole value (
this.intZoom
)Re-calculate clusters when the geojson data changes (i.e. from filtering)
Re-render clusters when the cluster layer data changes (
this.clusterData
)Math.floor(this.mapZoom)