Clusters

Docs / 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.

For large datasets, always prefer <x-map-cluster-layer> over individual <x-map-marker> tags to maintain high frame rates.

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:

Stepped colors are only used when :cluster-colors has at least two entries and :cluster-thresholds is set; otherwise the single cluster-color is used. Extra thresholds without a matching color are ignored.

Data Format

The :data prop accepts an array of GeoJSON Feature objects. Each feature must have:

php
[
    'type' => 'Feature',
    'properties' => [
        'name' => 'Location Name',   // Any properties you want
    ],
    'geometry' => [
        'type' => 'Point',
        'coordinates' => [$lng, $lat],  // [longitude, latitude]
    ],
]
Remember that GeoJSON uses [longitude, latitude] order, not [latitude, longitude] — this is a common source of bugs.

Props

PropTypeDefaultDescription
:dataarray[]GeoJSON Features or raw items
url?stringnullURL to GeoJSON file (client-side fetch)
id?stringnullUnique ID for events/updates
:cluster-max-zoomint14Max zoom level to cluster at
:cluster-radiusint50Pixel radius to group points
:cluster-min-pointsint2Min points to form a cluster
cluster-colorstring'#1A56DB'Color of the cluster circle (used when stepped colors are not set)
:cluster-colors?arraynullStepped cluster colors, e.g. ['#22c55e', '#eab308', '#ef4444']. Pass one more color than thresholds
:cluster-thresholds?arraynullPoint counts where the next color starts, e.g. [50, 200]
cluster-text-colorstring'#FFFFFF'Color of the count text
:cluster-size-stopsarray[[0, 18], [100, 26], [1000, 34]]Circle radius in pixels by point count, as [count, radius] steps
point-colorstring'#1A56DB'Color of unclustered point
point-stroke-colorstring'#FFFFFF'Outline color of unclustered points
:point-radiusint6Radius of unclustered point
:show-countbooltrueShow point count in clusters
:text-font?arraynullFont stack for the count text, e.g. ['Noto Sans Bold'] (null uses ['Open Sans Semibold']). Must exist in the style's glyphs
popup-property?stringnullProperty to show in popup
popup-template?stringnullInline HTML popup template
:disable-popupboolfalseDisable popups on unclustered points
:click-zoombooltrueZoom in on cluster click
:bufferint256Tile buffer
:tolerancefloat0.5Simplification tolerance
:max-features-to-inlineint2000Deprecated, ignored. Data always ships in a JSON script block
classstring''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:

blade
<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

  1. Slot Control: <x-slot:popup>
  2. Inline Template: popup-template="..." attribute
  3. Config view: the cluster_popup_view config option, used when no inline template is passed
  4. Property Card: popup-property="name" attribute
  5. 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:

php
$this->dispatch("map:update-cluster-data-{$clusterId}",
    \Kwasii\LivewireMapcn\Support\GeoJSON::fromArray($filteredData)
);