Clusters
Overview
The <x-map-cluster-layer> component efficiently handles large numbers of markers by grouping them into clusters. As you zoom in, clusters break apart into individual points. This is the recommended approach for datasets with hundreds or thousands of points.
Basic Usage
Pass an array of GeoJSON Features to the :data prop. Click on clusters to zoom in:
From GeoJSON URL
Instead of passing an array, you can point to a remote GeoJSON file. MapLibre will fetch and cluster the data client-side:
Custom Cluster Color
Change the cluster circle color to match your brand:
Stepped Cluster Colors
Color clusters by size with :cluster-colors and :cluster-thresholds. The first color is used below the first threshold, and each following color applies from its threshold upwards, so pass one more color than thresholds. Below, clusters under 50 points are green, 50 to 199 are yellow, and 200 or more are red:
Data Format
The :data prop accepts an array of GeoJSON Feature objects. Each feature must have:
[
'type' => 'Feature',
'properties' => [
'name' => 'Location Name', // Any properties you want
],
'geometry' => [
'type' => 'Point',
'coordinates' => [$lng, $lat], // [longitude, latitude]
],
]
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| :data | array | [] | GeoJSON Features or raw items |
| url | ?string | null | URL to GeoJSON file (client-side fetch) |
| id | ?string | null | Unique ID for events/updates |
| :cluster-max-zoom | int | 14 | Max zoom level to cluster at |
| :cluster-radius | int | 50 | Pixel radius to group points |
| :cluster-min-points | int | 2 | Min points to form a cluster |
| cluster-color | string | '#1A56DB' | Color of the cluster circle (used when stepped colors are not set) |
| :cluster-colors | ?array | null | Stepped cluster colors, e.g. ['#22c55e', '#eab308', '#ef4444']. Pass one more color than thresholds |
| :cluster-thresholds | ?array | null | Point counts where the next color starts, e.g. [50, 200] |
| cluster-text-color | string | '#FFFFFF' | Color of the count text |
| :cluster-size-stops | array | [[0, 18], [100, 26], [1000, 34]] | Circle radius in pixels by point count, as [count, radius] steps |
| point-color | string | '#1A56DB' | Color of unclustered point |
| point-stroke-color | string | '#FFFFFF' | Outline color of unclustered points |
| :point-radius | int | 6 | Radius of unclustered point |
| :show-count | bool | true | Show point count in clusters |
| :text-font | ?array | null | Font stack for the count text, e.g. ['Noto Sans Bold'] (null uses ['Open Sans Semibold']). Must exist in the style's glyphs |
| popup-property | ?string | null | Property to show in popup |
| popup-template | ?string | null | Inline HTML popup template |
| :disable-popup | bool | false | Disable popups on unclustered points |
| :click-zoom | bool | true | Zoom in on cluster click |
| :buffer | int | 256 | Tile buffer |
| :tolerance | float | 0.5 | Simplification tolerance |
| :max-features-to-inline | int | 2000 | Deprecated, ignored. Data always ships in a JSON script block |
| class | string | '' | Additional CSS classes |
Point Popups
Customize the popup shown when clicking unclustered points. Only one point popup is open at a time: opening another closes the previous one.
Using the Popup Slot
The recommended approach is using the popup slot. Use {property} placeholders for dynamic data:
<x-map-cluster-layer :data="$locations"> <x-slot:popup> <div class="p-3"> <h3 class="font-bold">{name}</h3> <p class="text-xs">{address}</p> <p class="text-[10px] text-gray-400 mt-2">{lat}, {lng}</p> </div> </x-slot:popup> </x-map-cluster-layer>
Priority Order
- Slot Control: <x-slot:popup>
- Inline Template: popup-template="..." attribute
- Config view: the cluster_popup_view config option, used when no inline template is passed
- Property Card: popup-property="name" attribute
- Default: Auto-detected name/label property
Dynamic Data Updates
Cluster layers are reactive: when the array bound to :data changes on a Livewire re-render, the source is updated in place, and changing clustering props (radius, max zoom, min points) or colors redraws the layer without re-creating the map.
To update data without a re-render, give the layer an id and dispatch a map event from your PHP component. Each cluster layer listens for its unique ID:
$this->dispatch("map:update-cluster-data-{$clusterId}", \Kwasii\LivewireMapcn\Support\GeoJSON::fromArray($filteredData) );