Skip to content

feat(legend): continuous/graduated legends for vector layers (#258) - #262

Merged
cboettig merged 2 commits into
mainfrom
feat/continuous-vector-legend-258
Jun 20, 2026
Merged

feat(legend): continuous/graduated legends for vector layers (#258)#262
cboettig merged 2 commits into
mainfrom
feat/continuous-vector-legend-258

Conversation

@cboettig

Copy link
Copy Markdown
Member

Fixes #258.

Problem

Vector legends (PR #248, issue #118, v3.13.0) are categorical-only. A vector layer styled with a continuous interpolate/step ramp — a graduated choropleth — gets no legend, while a raster with the same kind of ramp gets a colorbar.

  • _hasLegend admitted a vector only when legendType === 'categorical' && legendClasses.length > 0.
  • _showLegend's continuous branch sourced its gradient from state.colormap + state.rescale — TiTiler/raster-only concepts a vector layer doesn't have.

Real impact (ca-30x30): 12 ACE richness layers + CalEnviroScreen are ["interpolate",["linear"],["get",<col>],…] ramps that render correctly but show no legend.

Approach

The graduated paint expression already encodes the gradient and value range, so the primary path auto-derives the legend from default_style — set legend_type: "continuous" and nothing else is needed.

  • app/legend-helpers.js (new, pure/tested): deriveContinuousLegend(paint) parses fill/line/circle-color interpolate/step expressions into { gradient, range }. Returns null for flat colors, match/categorical, or unparseable paint.
  • _hasLegend now accepts continuous vectors when a colorbar is resolvable; _continuousVectorLegend(state) resolves it — explicit legend_gradient/legend_range config wins, else derive from paint.
  • _showLegend gains a continuous-vector branch that reuses the existing .legend-colorbar/.legend-labels DOM; colors are validated through _safeColorHint (same XSS guard as the categorical path).
  • dataset-catalog.js plumbs the optional legend_range / legend_gradient overrides (vector + versioned + getMapLayerConfigs).
  • Docs: continuous-legend config table rows + a "Continuous (graduated) vector legends" subsection covering auto-derivation.

Categorical vectors and raster colorbars are unchanged.

Tests

  • deriveContinuousLegend: interpolate/step parsing, fill/line/circle keys, out-of-order stops, null cases (flat, match, empty, single-stop).
  • _hasLegend: continuous eligible via derived paint and via explicit config; not eligible with nothing to source from.
  • _showLegend: renders a derived colorbar with legend_label axis labels; explicit legend_range/legend_gradient override the paint.

Full suite green (317 tests).

Note: app/map-manager.js, dataset-catalog.js, and chat-ui.js are browser-bound per AGENTS.md, but the legend/catalog logic here is exercised by the jsdom-based map-manager.tooltip-legend and dataset-catalog suites. Visual confirmation on a ca-30x30-style app is still worth a manual check.

Vector legends (#118/#248) were categorical-only: a vector layer styled
with a continuous interpolate/step ramp got no legend, while a raster with
the same ramp got a colorbar. _hasLegend rejected continuous vectors, and
_showLegend's colorbar branch sourced its gradient from colormap+rescale
(TiTiler/raster-only concepts a vector has neither of).

- deriveContinuousLegend() (pure, legend-helpers.js) reads gradient + value
  range back out of a layer's interpolate/step color expression, so the
  legend mirrors the map with no duplicate config
- _hasLegend accepts continuous vectors when a colorbar is resolvable;
  _continuousVectorLegend resolves it (explicit legend_gradient/legend_range
  config wins, else derive from paint)
- _showLegend gains a continuous-vector branch reusing the colorbar DOM,
  colors validated via _safeColorHint
- plumb legend_range/legend_gradient config through dataset-catalog
- docs: continuous legend config + auto-derivation

Tests: deriveContinuousLegend unit coverage; _hasLegend + _showLegend
continuous-vector render + explicit-override paths.
@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 50.64% 3682 / 7270
🔵 Statements 50.62% 3682 / 7273
🔵 Functions 58.18% 128 / 220
🔵 Branches 82.9% 737 / 889
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
app/dataset-catalog.js 90.04% 78.35% 100% 90.04% 87-89, 115-117, 142-151, 171-173, 183-184, 431-436, 464-505, 733-739, 752-757
app/legend-helpers.js 100% 100% 100% 100%
app/map-manager.js 48.59% 86.98% 38.09% 48.63% 57-146, 153-157, 163-384, 393-437, 447-459, 467-479, 543-544, 561-562, 681-690, 718-719, 725-728, 737-747, 753-760, 816-819, 827-831, 834-850, 856-857, 863-866, 935-936, 956-958, 980-985, 991-994, 1004-1022, 1029-1033, 1041-1134, 1141-1219, 1226-1231, 1243-1289, 1337-1356, 1359-1374, 1384-1386, 1456-1463, 1497-1498, 1531-1535, 420
Generated in workflow #107 for commit 3fd326e by the Vitest Coverage Report Action

@cboettig
cboettig merged commit a26b82b into main Jun 20, 2026
1 check passed
@cboettig
cboettig deleted the feat/continuous-vector-legend-258 branch June 20, 2026 19:30
cboettig added a commit that referenced this pull request Jun 20, 2026
This local Claude Code permission config (machine-specific absolute paths,
a /tmp worktree path) was swept into #262 by a stray git add -A during
conflict resolution. It's not meant for the shared repo.
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.

feat(legend): continuous/graduated legends for vector layers (colorbar parity with rasters)

1 participant