From 2755b1f88f9f8767d4f92ec42ca324caebbd0050 Mon Sep 17 00:00:00 2001 From: Raymond Yee Date: Wed, 27 May 2026 16:14:16 -0700 Subject: [PATCH] =?UTF-8?q?explorer:=20heatmap=20overlay=20spike=20?= =?UTF-8?q?=E2=80=94=20phase=201=20(#233)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a toggleable heatmap overlay as a third visualization alongside cluster (H3 dots) and point (individual samples) mode. Phase 1 of the #233 spike: answer the viability question of heatmap.js + Cesium SingleTileImageryProvider + DuckDB-WASM composing into a filter-aware density layer. In scope this commit: - Loads heatmap.js v2.0.5 via jsDelivr CDN (alongside Cesium). - New `#heatmapToggle` checkbox in the source legend. - `refreshHeatmap()` queries `samples_map_lite.parquet` for in- viewport sample coords (applying source + material/feature/ specimen filters), bins them per pixel into a 512x512 grid before passing to heatmap.js (smart perf optimization: keeps the data array under 262k regardless of how many samples match — though see cap warning below), renders to an offscreen canvas, and swaps a `SingleTileImageryProvider` into the Cesium imagery layers. - Cancellation via `heatmapReqId` (matches the existing `facetCountsReqId` / `requestId` patterns). - Refresh triggers: `camera.moveEnd` (debounced 250 ms), source-filter change, material-filter change. moveStart bumps the reqId and shows "waiting for camera" status. - Toggle off removes the imagery layer. - Skip-if-same-key optimization on identical (viewport, filter) combos — only marked "done" after a successful render. - Status text (italic) reports point count per refresh, with explicit cap warning when at LIMIT. - New `tests/playwright/heatmap-overlay.spec.js` (4 tests): toggle exists, toggle on renders, toggle off clears, filter change regenerates (asserts on lastImageHash, not just lastRefreshAt, so the error path doesn't satisfy the test). Codex round-1 fixes baked in: - Stale dedupe key bug: `heatmapLastKey` is now set ONLY after a successful layer swap, and cleared on (a) error path, and (b) moveStart cancellation. Previously a toggle+camera-gesture race could leave the key set without a render having happened, wedging the overlay (next moveEnd would early-return). - Silent LIMIT 100k cap: status text now explicitly says "(capped — zoom or filter for full density)" when at LIMIT. Lite parquet has ~6M rows; the cap shows an arbitrary first 100k, not honest density. Phase 2 progressive refinement removes the cap. - `_heatmapOverlay.capped` field exposed for tests. Verified: - 4/4 spec tests pass on localhost in 45.1s (post-fixes) - Patient probe at Cyprus alt=500km confirms numbers match raw bbox query: no filter = 96,694 samples; +organicmaterial filter = 13 samples; image hash changes per refresh - No regression in facet-viewport.spec.js (4/4 still pass) Out of scope for this PR (deferred to phase 2+): - Progressive refinement (TABLESAMPLE 1% → 10% → 100%) — removes the LIMIT cap properly - Cache by (viewport-hash, filter-hash) - Kernel / color-ramp tuning, alpha tuning - Third-mode promotion (currently overlay, not exclusive mode) - Interaction with `#facetNote` apology copy - Antimeridian rectangle convention — current path uses `east + 360` for wrapped bboxes; Cesium normally expects `west > east`. Codex flagged for a dateline test, deferred. Implementation provenance: Bulk of explorer.qmd diff (~252 LOC) authored by OpenAI Codex CLI from a Claude-authored phase-1 plan that was sent for "review" but Codex jumped straight to implementation. Claude reviewed the implementation against the plan, verified it works (spec + visual probe), and asked Codex for a round-1 PR review. Codex caught real bugs (stale dedupe key, silent cap) — those are addressed in this amended commit. Co-Authored-By: OpenAI Codex CLI Co-Authored-By: Claude Opus 4.7 (1M context) --- explorer.qmd | 323 +++++++++++++++++++++++ tests/playwright/heatmap-overlay.spec.js | 138 ++++++++++ 2 files changed, 461 insertions(+) create mode 100644 tests/playwright/heatmap-overlay.spec.js diff --git a/explorer.qmd b/explorer.qmd index 146cb6c..3efccf7 100644 --- a/explorer.qmd +++ b/explorer.qmd @@ -19,6 +19,7 @@ format: ```{=html} +