Skip to content

Add Choropleth and ChoroplethMap trace types#2

Open
jqnatividad wants to merge 10 commits into
feat/treemap-sunburst-tracesfrom
feat/choropleth-maps
Open

Add Choropleth and ChoroplethMap trace types#2
jqnatividad wants to merge 10 commits into
feat/treemap-sunburst-tracesfrom
feat/choropleth-maps

Conversation

@jqnatividad

@jqnatividad jqnatividad commented Jun 27, 2026

Copy link
Copy Markdown

Adds Choropleth map support (addresses plotly#408).

Stacked on top of feat/treemap-sunburst-traces (PR plotly#406) — this PR's base is that branch, so the diff shows only the choropleth work. Retarget to main once plotly#406 merges.

What's added

Trace types

  • Choropleth<Loc, Z> — classic choropleth on the existing geo subplot (LayoutGeo). Includes a LocationMode enum (ISO-3 / USA-states / country names / geojson-id), a dedicated choropleth::Marker (region-boundary line + opacity), Selection/SelectionMarker, z-bounds, and the shared ColorScale/ColorBar machinery. geojson accepts a URL string or an inline GeoJSON object (serde_json::Value).
  • ChoroplethMap<Loc, Z> — the modern plotly.js 3.x choroplethmap trace on the MapLibre map subplot (GeoJSON-only).

Layout

  • LayoutMap + MapStyle + MapBounds — the MapLibre map subplot, which the crate did not have before (only mapbox). Mirrors the existing Mapbox subplot and is wired into Layout.
  • LayoutGeo enhancements for the classic geo subplot:
    • fitbounds (GeoFitBounds: false / locations / geojson) — auto-frames the subplot to the plotted data, overriding center/lonaxis/lataxis when set.
    • resolution (GeoResolution: OneOverOneHundredTenMillion110 default, OneOverFiftyMillion50) — selects base-layer detail. Per plotly.js, the value is the scale denominator (110 = 1:110,000,000), so 50 gives higher-detail coastlines/land/borders. Pairs naturally with the UN geodata that became the plotly.js v3.1.0 default (we bundle 3.6.0).

Plumbing, tests, docs

  • New PlotType::Choropleth / PlotType::ChoroplethMap variants and top-level + sub-module re-exports.
  • Serialization tests (exact-JSON), doctests, runnable examples in examples/maps (choropleth + choropleth_map), and a new Maps → Choropleth Maps book recipe page.

Verification

  • cargo build --features all + plotly_embed_js; native + wasm clippy clean; cargo +nightly fmt.
  • 313 lib tests + 24 doctests pass (the 8 failing tests are the pre-existing browser-export ones needing chromedriver).
  • examples/maps builds and lints clean.

Notes

  • Attribute names/types were taken from the plotly.js v3.6.0 plot-schema.json (traces.choropleth, traces.choroplethmap, layout.map).
  • The book recipe embeds the example code (mdBook anchors). maps is intentionally not added to the automated book-example build because its main() performs a network fetch (scatter_geo) and isn't part of CI's BOOK_EXAMPLES.
  • Breaking change (new PlotType variants + new layout/trace fields generate new Restyle*/Relayout* enum variants), so this targets the next breaking release (0.15.0), consistent with the other in-flight work.

🤖 Generated with Claude Code

jqnatividad and others added 5 commits June 27, 2026 10:43
Adds choropleth (filled-region) map support, mirroring the existing geo
trace pattern:

- Choropleth<Loc, Z> on the geo subplot (LayoutGeo), with a LocationMode
  enum (ISO-3 / USA-states / country names / geojson-id), a dedicated
  choropleth::Marker (boundary line + opacity), Selection, and the shared
  colorscale/colorbar machinery. geojson accepts a URL string or an inline
  GeoJSON object (serde_json::Value).
- ChoroplethMap<Loc, Z> on the MapLibre `map` subplot (GeoJSON-only).
- New LayoutMap / MapStyle / MapBounds for the `map` subplot, wired into
  Layout (mirrors the existing mapbox subplot).

Registers both PlotType variants and top-level/sub-module re-exports, adds
serialization + doc tests, runnable examples (examples/maps), and a
"Maps / Choropleth Maps" book recipe page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `map` subplot (MapLibre) does not support the legacy Stamen mapbox
styles. Replace the Stamen* variants with the bundled plotly.js map styles
from the v3.6.0 schema: add carto-voyager and the carto-*-nolabels
variants. Update the serialization test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Include the generated inline HTML for the choropleth and choropleth_map
examples in the Choropleth Maps recipe page, and add `maps` to the book's
BOOK_EXAMPLES so the inline output is generated during the book build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The maps example is now part of the book build. scatter_geo fetched a
remote CSV with unwrap(), which would panic (and fail the book CI) if the
URL were unreachable. Handle fetch/HTTP errors gracefully: warn and skip
the example so the remaining maps examples still generate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the fail-safe path beyond the fetch: handle CSV header and record
parse errors with warn-and-return, and resolve the lat/lon column indices
up front (warn-and-return if a column is missing), defaulting missing cells
to NaN. Malformed or schema-changed upstream data can no longer panic the
book build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jqnatividad and others added 5 commits June 27, 2026 16:09
Expose Plotly's `geo.fitbounds` attribute on `LayoutGeo` via a typed
`GeoFitBounds` enum (`Locations` / `GeoJson`), serialized as the strings
`"locations"` / `"geojson"`. Setting `fitbounds = "locations"` frames a
geo subplot to the union of a choropleth trace's filled region
geometries, so callers can fit the view to whole countries/states rather
than to the source point extent (which clips the regions at the edges).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a GeoFitBounds::False variant with a custom Serialize impl that emits
the boolean `false` (the schema allows [false, "locations", "geojson"]),
so callers can explicitly override/clear a non-default fitbounds value.

Also add the missing `#[serde_with::skip_serializing_none]` to LayoutGeo,
which was serializing `null` for every unset field; this fixes the
fitbounds test and the pre-existing serialize_layout_geo test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expose Plotly.js `geo.resolution` via a type-safe `GeoResolution` enum
(`OneOverOneHundredTenMillion` -> 110, the default; `OneOverFiftyMillion`
-> 50) serialized as the numeric scale denominator. 50 selects the
higher-detail 1:50,000,000 base layers (coastlines, land, borders).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Demonstrate `LayoutGeo::resolution(GeoResolution::OneOverFiftyMillion)`
for higher-detail base layers in the choropleth example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expose plotly.js's `layout.geo.bgcolor` attribute on the typed `LayoutGeo`
builder. It was already honored by plotly.js (settable via relayout) but had
no Rust setter, so the geo subplot background — the area outside land/ocean
polygons (e.g. the corners around a non-rectangular projection) — couldn't be
themed from Rust. Mirrors the existing land/ocean/lake color fields
(FieldSetter-generated `.bgcolor(impl Color)`, serde key `bgcolor`).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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