Skip to content

feat: add native point clustering to ScatterMap#418

Open
jqnatividad wants to merge 6 commits into
plotly:mainfrom
dathere:feat/scatter-map-cluster
Open

feat: add native point clustering to ScatterMap#418
jqnatividad wants to merge 6 commits into
plotly:mainfrom
dathere:feat/scatter-map-cluster

Conversation

@jqnatividad

Copy link
Copy Markdown
Contributor

Summary

Adds native point clustering to the MapLibre ScatterMap trace (#417) via a new Cluster option, exposing plotly.js's scattermap.cluster attributes.

When enabled, nearby markers are grouped client-side (MapLibre's supercluster) into cluster bubbles that progressively expand into individual points as the map is zoomed in — decluttering dense point maps without dropping any data (every point still ships in the trace and becomes individually visible/hoverable at higher zoom).

This is a scattermap-only feature: the deprecated Mapbox-based ScatterMapbox has no equivalent, which is why it's a follow-up rather than part of the field-for-field port in #417.

⚠️ Stacked on #417

Cluster attaches to ScatterMap, so this PR is stacked on #417#416#410 and should be reviewed/merged after them. Until they land in main, the diff here also shows their commits; once merged, this diff reduces to just the Cluster addition.

What changed

New Cluster struct on plotly::traces::scatter_map, plus a cluster: Option<Cluster> field on ScatterMap (with the usual FieldSetter builder):

  • enabled (bool)
  • color (color or per-step array, Dim<Box<dyn Color>>)
  • maxzoom (0-24; zoom past which clusters expand into points)
  • opacity
  • size (single or per-step array)
  • step (single or per-step array; -1 = auto)

Includes a serialize_cluster unit test and a Cluster doctest.

Testing

  • cargo test -p plotly --lib scatter_mapserialize_cluster and serialize_scatter_map pass.
  • cargo test -p plotly --doc scatter_mapCluster and ScatterMap doctests pass.
  • cargo clippy — clean.
  • cargo +nightly fmt --all -- --check — clean.
  • Verified end-to-end rendering in-browser (plotly.js 3.6.0): clusters form at the fitted zoom, expand into individual hoverable points on zoom-in, and maxzoom avoids the MapLibre clusterMaxZoom > source maxzoom warning.

🤖 Generated with Claude Code

jqnatividad and others added 6 commits July 1, 2026 06:48
Add choropleth map support (addresses plotly#408):

- `Choropleth<Loc, Z>` on the existing `geo` subplot, with a `LocationMode`
  enum (ISO-3 / USA-states / country names / geojson-id), a dedicated
  `choropleth::Marker`, selection support, z-bounds, and the shared
  `ColorScale`/`ColorBar` machinery. `geojson` accepts a URL string or an
  inline GeoJSON object.
- `ChoroplethMap<Loc, Z>`, the plotly.js 3.x `choroplethmap` trace on the
  MapLibre `map` subplot (GeoJSON-only).
- New MapLibre `map` subplot via `LayoutMap` + `MapStyle` + `MapBounds`.
- `LayoutGeo` additions: `fitbounds` (`GeoFitBounds`), `resolution`
  (`GeoResolution`, 1:110M/1:50M base-layer detail), and `bgcolor`.
- `PlotType::Choropleth`/`ChoroplethMap` variants, re-exports, serialization
  tests, doctests, runnable `examples/maps` examples, and a Maps book recipe.

Attribute names/types follow the plotly.js v3.6.0 plot-schema.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The embedded choropleth example calls GeoResolution::OneOverFiftyMillion,
but the shared import block omitted GeoResolution, so copying the documented
imports alongside the example would not compile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the modern MapLibre-based `densitymap` trace type, the density
counterpart to `ChoroplethMap`. It draws a weighted kernel-density
heatmap from lat/lon points (optional per-point weight z) onto the
`layout.map` subplot, with full color-scale (colorscale, colorbar,
showscale, reversescale, autocolorscale, coloraxis, zmin/zmax/zmid)
and hover (text, hovertext, hoverinfo, hovertemplate, hoverlabel,
customdata) support. Unlike ChoroplethMap it has no marker/selection.

Registers the trace: DensityMap variant on PlotType (serializes to
"densitymap"), module + type re-exports in traces/mod.rs and lib.rs.
Includes a serialize_density_map unit test and a doctest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `ScatterMap`, the MapLibre-based counterpart to `ScatterMapbox`, drawn
on the `layout.map` subplot (`LayoutMap`/`MapStyle`). It mirrors
`ScatterMapbox` field-for-field — markers/lines, `Fill`/`Selection`, and the
full hover set (text/hovertext/hoverinfo/hovertemplate/customdata/hoverlabel)
— but emits `"type": "scattermap"` and references `layout.map`.

Add a `PlotType::ScatterMap` variant, re-export `ScatterMap` and the
`scatter_map` module, and add a `serialize_scatter_map` unit test plus a doc
example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a `Cluster` option to the MapLibre `ScatterMap` trace, exposing
plotly.js's `scattermap.cluster` attributes (`enabled`, `color`,
`maxzoom`, `opacity`, `size`, `step`). Clustering groups nearby markers
client-side (MapLibre supercluster) into bubbles that expand into
individual points on zoom, decluttering dense point maps without
dropping any data.

This is a `scattermap`-only feature; the deprecated `ScatterMapbox` has
no equivalent. Stacked on the ScatterMap trace addition (plotly#417).
@jqnatividad jqnatividad mentioned this pull request Jul 6, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant