Skip to content

Add lazy loading for layers in GeoSet Multi Map#346

Merged
lhawkman27 merged 17 commits into
raft-tech:mainfrom
lhawkman27:add-lazy-loading-for-charts-in-geoset
Mar 19, 2026
Merged

Add lazy loading for layers in GeoSet Multi Map#346
lhawkman27 merged 17 commits into
raft-tech:mainfrom
lhawkman27:add-lazy-loading-for-charts-in-geoset

Conversation

@lhawkman27
Copy link
Copy Markdown
Collaborator

@lhawkman27 lhawkman27 commented Mar 18, 2026

Summary

  • Add per-layer Lazy Loading option to the GeoSet Multi Map chart, enabling three-phase layer loading:
    • Phase 1 (Autozoom): Layers with autozoom load in parallel first — the map canvas gates on this phase to prevent viewport jumps
    • Phase 2 (Eager): Remaining non-lazy, non-autozoom layers load in parallel and appear incrementally
    • Phase 3 (Lazy): Layers marked as lazy load in small batches in the background
  • Extract shared layer orchestration utilities (multiUtils.ts) from Multi.tsx for testability and reuse
  • Unify the DeckSliceConfig type and normalizeDeckSlices function between the map plugin and DeckSlicesControl, eliminating duplication

Focus Score

8/10 — All changes are focused on the lazy loading feature and directly related refactoring. The shared type unification and utility extraction are necessary to support the feature cleanly. Minor scope expansion for wiki documentation updates.

Detailed Summary of Each File Changed

File Changes
GeoSetMultiMap/Multi.tsx Refactored loadLayers into loadSingleLayer (per-layer) + loadLayersOrchestrated (three-phase orchestrator). Added generation counter (loadGenerationRef) to cancel stale lazy chains. Layer visibility for initiallyHidden layers is pre-set from config at the start of loadLayers — before any network requests — so the legend checkbox and map never flash visible. Category visibility is set via hideCategoriesIfNeeded when layers finish loading (categories aren't known until data is fetched). layerStatesWithVisibility includes a synchronous fallback to !entry.initiallyHidden as a render-time safety net. Loading gate checks for autozoom layers to prevent viewport jump — if autozoom layers exist, waits for phase 1; otherwise shows map immediately after metadata fetch.
GeoSetMultiMap/multiUtils.ts Exports DeckSliceConfig interface (with lazyLoading), resolveLayerAutozoom (disables autozoom when lazy), normalizeDeckSlices (handles legacy number[] format), and loadLayersOrchestrated (three-phase: autozoom parallel → eager parallel → lazy batched, with staleness abort).
geoset-map-chart/src/index.ts Exports normalizeDeckSlices and DeckSliceConfig type from the plugin's public API.
test/GeoSetMultiMap/multiUtils.test.ts 27 tests covering resolveLayerAutozoom, normalizeDeckSlices, and loadLayersOrchestrated (autozoom-only, eager-only, lazy-only, mixed three-phase ordering, staleness abort at phase boundaries, null filtering, batch size enforcement, sync throw handling, rejection propagation).
DeckSlicesControl/index.tsx Added Lazy Loading toggle to per-layer settings popover. Replaced duplicate normalizeValue with shared normalizeDeckSlices. Auto-disables autozoom when lazy loading is toggled on, restores prior autozoom value when toggled off. Context-dependent tooltip messaging.
tsconfig.json Added path mapping for @superset-ui/geoset-map-chart to enable direct TS imports from DeckSlicesControl to the plugin source.
wiki/Development-Guide.md Added multiUtils.ts entry to the Plugin Architecture file table. Added Layer Loading Orchestration section documenting the three-phase loading strategy and staleness handling.
wiki/GeoSet-Multi-Map.md Added documentation table for per-layer settings popover options including Lazy Loading behavior. Added Layer Loading Order section explaining the three-phase user-facing behavior.

Test Plan

  • Unit tests: Run npx jest multiUtils.test.ts — all 27 tests should pass covering three-phase orchestration logic, staleness, batching, null handling, and error propagation
  • Autozoom-only map: Open a Multi Map where all layers have autozoom enabled — map should render after all layers load with correct viewport (no jump)
  • Mixed autozoom + eager: Configure some layers with autozoom off — map renders after autozoom layers, then non-autozoom eager layers appear incrementally
  • All-lazy map: Set all layers to lazy — map renders immediately after metadata fetch, layers trickle in
  • No-autozoom map: Disable autozoom on all layers — map renders immediately after metadata fetch
  • Three-phase ordering: With autozoom + eager + lazy layers, verify autozoom loads first, then eager, then lazy
  • Staleness cancellation: While layers are loading, change a filter or form data that triggers a reload — stale chain should abort and new load starts fresh
  • Auto Zoom interaction: Toggle lazy loading on — verify autozoom checkbox is disabled and unchecked. Toggle lazy loading off — verify autozoom restores to its prior value
  • Static Viewport + Lazy Loading: With Static Viewport enabled, verify autozoom remains disabled regardless of lazy loading state and tooltip reflects the correct reason
  • Initially Hidden + Lazy: Configure a lazy layer as hidden by default — verify it loads in the background with a spinner in the legend, and when loading completes the layer remains hidden with no visible flash. Toggle it visible from the legend checkbox
  • Settings popover reopen: Open settings, change values, close without saving, reopen — verify draft state resets to saved values

🤖 Generated with Claude Code

lhawkins and others added 9 commits March 17, 2026 17:30
Layers marked as "Lazy Loading" are loaded sequentially in the background
after all eager layers have rendered, improving initial map load time for
dashboards with many layers. Autozoom is automatically disabled for
lazy-loaded layers.

- Extract DeckSliceConfig, normalizeDeckSlices, and new resolveLayerAutozoom
  into multiUtils.ts for reuse and testability
- Split layer loading into eager (parallel) and lazy (sequential) phases
- Add Lazy Loading checkbox to per-layer settings popover in DeckSlicesControl
- Add unit tests for resolveLayerAutozoom and normalizeDeckSlices

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract loadLayersOrchestrated to multiUtils.ts for testability
- Add load-bearing comment on .catch in loadSingleLayer
- Deduplicate normalizeValue in DeckSlicesControl (reuse normalizeDeckSlices)
- Add 8 orchestration tests: mixed eager/lazy, all-lazy, staleness
  cancellation, null filtering, and empty slices

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…orts

Fix critical bug where lazy-loaded layers with initiallyHidden were not
hidden on arrival (the effect only fired on the first 0→N transition).
Now detects newly-added layers by comparing previous vs current slice ID
sets so both eager and lazy layers are handled correctly.

Add .catch() to fire-and-forget loadLayersOrchestrated call to prevent
unhandled promise rejections. Wrap loadFn calls with Promise.resolve()
for defensive handling of synchronous throws. Remove no-op .then(() => {}).

Re-export DeckSliceConfig and normalizeDeckSlices from the plugin barrel
and add tsconfig path mapping so DeckSlicesControl imports from the
package entry point instead of reaching into src/.

Restore autozoom to its saved value when lazy loading is toggled off.
Rename test file to multiUtils.test.ts and add error rejection tests.
Update wiki docs for multiUtils.ts and per-layer settings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use type assertion instead of type predicate in Promise.all filter
to avoid Awaited<TLayer> vs TLayer mismatch in generic context.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch lazy layer loading from sequential to batched (batch size 2) for
better throughput, simplify safeLoadFn, and fix autozoom restore to use
the user's draft value via a ref instead of the saved prop. Update wiki
to reflect batched loading behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reset autozoomBeforeLazyRef when the layer settings popover reopens so
that toggling lazy loading off after a save/reopen cycle no longer
restores a stale autozoom value. Also expand the lazy-chain abort test
to span multiple batch boundaries for more robust coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@lhawkman27 lhawkman27 changed the title feat: Add lazy loading for layers in GeoSet Multi Map Add lazy loading for layers in GeoSet Multi Map Mar 18, 2026
@lhawkman27 lhawkman27 assigned lhawkman27 and unassigned lhawkman27 Mar 18, 2026
Ethan Bienstock and others added 6 commits March 18, 2026 15:34
…ers load

Build stub legend entries from slice metadata (already fetched) so the
legend is fully populated when the map opens. Each stub shows an antd
Spin indicator in place of checkboxes until the layer data arrives and
replaces the stub with the full legend content.

- Add buildStubLegendEntry() to parse legend name/icon from form_data
- Add pendingLegends memo to build stubs from slicesData
- Merge stubs into legendsBySlice so loaded entries replace stubs
- Add loading field to LegendEntry type
- Show Spin spinner for loading entries in LegendEntryContent
- Show Spin spinner for group checkbox when all entries are loading

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The loading gate blocked the entire map canvas (base map, legend, controls)
behind a loading GIF until all eager layers finished via Promise.all. Changed
the condition to only block on slice metadata fetch so the base map is visible
immediately and layers appear incrementally as they load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refactor two-phase loading into three phases so the map canvas renders
as soon as autozoom layers finish, without waiting for all eager layers:

  Phase 1: Autozoom layers load in parallel (map canvas gates on this)
  Phase 2: Remaining eager layers load in parallel, appended incrementally
  Phase 3: Lazy layers load in batches, appended incrementally

This prevents the viewport jump that occurred when the map rendered with
a default viewport then snapped to the autozoom extent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Layer visibility for initiallyHidden layers is now set from config at
the start of loadLayers, before any network requests. This eliminates
the timing dependency where the old useEffect-based approach could
render a hidden+lazy layer as visible for one frame before hiding it.

Category visibility is still set on load (categories aren't known until
data is fetched). The layerStatesWithVisibility fallback provides an
additional synchronous safety net during render.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ethanbienstock
Copy link
Copy Markdown
Contributor

Fix: Hidden + Lazy layer flash

When a layer was configured as both Hidden by Default and Lazy Loading, it would briefly flash visible on the map when loading completed. The legend checkbox would also flicker (checked → unchecked).

Root cause: The old code used a useEffect to detect newly-loaded layers and set their visibility to false. This created a two-render gap — the layer was added to state in render 1 (visible by default), then hidden in render 2 when the effect fired. That one-frame gap was the flash.

Fix (b0a65ba): Layer visibility is now pre-set from config at the start of loadLayers, before any network requests begin. Since initiallyHidden is known from the slice configuration upfront, there's no reason to wait for the layer data to arrive. Category visibility is still set on load (categories aren't known until data is fetched). As an additional safety net, layerStatesWithVisibility falls back to !entry.initiallyHidden during render if the visibility state hasn't been populated yet.

lhawkins and others added 2 commits March 19, 2026 11:59
Wrap the layerStatesWithVisibility computation in useMemo so
DeckGLContainer only receives new prop references when sortedLayers
or layerVisibility actually change, avoiding unnecessary renders.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verify that group checkboxes update immediately on click via
optimistic local state, without waiting for the layerVisibility
prop to change from the parent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@ethanbienstock ethanbienstock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works perfectly

@lhawkman27 lhawkman27 merged commit 3e60cff into raft-tech:main Mar 19, 2026
7 checks passed
@lhawkman27 lhawkman27 deleted the add-lazy-loading-for-charts-in-geoset branch April 1, 2026 13:51
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.

2 participants