Skip to content

fix: facet-aware Pointer and HTMLTooltip positioning#509

Merged
gka merged 6 commits intomainfrom
fix/tooltip-facet-positioning
Mar 2, 2026
Merged

fix: facet-aware Pointer and HTMLTooltip positioning#509
gka merged 6 commits intomainfrom
fix/tooltip-facet-positioning

Conversation

@ljodea
Copy link
Copy Markdown
Collaborator

@ljodea ljodea commented Feb 28, 2026

Summary

Stacked on #500.

  • Pointer searched ALL quadtrees instead of only the hovered facet's trees — replaced flat trees[] array with a keyed SvelteMap<facetKey, Quadtree[]> so only the hovered facet's trees are searched
  • HTMLTooltip positioned via bounding rect diffs — replaced fragile getBoundingClientRect() offset computation with scale-based offsets using plot.scales.fx.fn(fxValue) (available after fix: give usePlot().scales.fx/fy valid ranges for facet-aware positioning #500)
  • Tree array index ≠ facet indexgroupFacetsAndZ produces groups in data-encounter order while FacetGrid iterates domain order; keyed map decouples lookup from array ordering
  • HTMLTooltip event listener leak — cleanup called removeEventListener('mouseleave') but registration used addEventListener('pointerleave'); both now use pointerleave

New shared helpers (src/lib/helpers/facets.ts)

Helper Purpose
facetKey(fx, fy) Stable JSON.stringify key for (fxValue, fyValue) pairs
invertBand(scale, domain, pixel) Invert a d3.scaleBand (no native .invert())
findFacetFromDOM(target) Walk DOM up to g.facet, read data-facet-x/y attributes
detectFacet(evt, plot) DOM walk first, band-inversion fallback; returns { fxValue, fyValue, offsetX, offsetY }

Test plan

  • invertBand — 5 unit tests (first/second band, out of range, single domain, padding)
  • facetKey — 6 unit tests (same values, different values, null, boolean, null vs "null", numeric)
  • Faceted Pointer — 4 tests (facet A isolation, facet B isolation, non-faceted regression, empty data)
  • HTMLTooltip — 4 tests (facet A datum, facet B datum, non-faceted regression, pointerleave)
  • All 704 existing tests pass
  • pnpm check — 0 errors
  • pnpm lint — clean

🤖 Generated with Claude Code

ljodea and others added 2 commits February 27, 2026 18:43
…ning

computeScales() creates fx/fy band scales with empty ranges because
getScaleRange() has no case for these scale names. This made fn() calls
return NaN, preventing custom components from using fx/fy scales for
facet-aware positioning (labels, tooltips, annotations).

After computeScales() returns, create fresh band scales for fx/fy with
the correct range ([0, plotWidth] / [0, plotHeight]) and padding config
matching FacetGrid's layout. Fresh scales are needed because d3 band
scales created with range([]) have corrupted internal ordinal mappings.

Closes #498

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace fragile DOM-walking and flat tree arrays with keyed tree map
and scale-based facet detection. Pointer and HTMLTooltip now search
only the hovered facet's quadtrees instead of all trees, preventing
cross-facet false matches.

- Add invertBand, facetKey, findFacetFromDOM, detectFacet helpers
- Refactor Pointer.svelte to use keyed SvelteMap<facetKey, Quadtree[]>
- Refactor HTMLTooltip.svelte to use scale-based offsets instead of
  getBoundingClientRect diffs
- Fix HTMLTooltip event listener cleanup mismatch (mouseleave vs pointerleave)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@netlify
Copy link
Copy Markdown

netlify bot commented Feb 28, 2026

Deploy Preview for svelteplot ready!

Name Link
🔨 Latest commit 4cbbcb2
🔍 Latest deploy log https://app.netlify.com/projects/svelteplot/deploys/69a5f5e3252ba200087f2b1a
😎 Deploy Preview https://deploy-preview-509--svelteplot.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@gka
Copy link
Copy Markdown
Contributor

gka commented Mar 2, 2026

@copilot can you resolve the conflicts?

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 2, 2026

@gka I've opened a new pull request, #513, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 4 commits March 2, 2026 21:31
`fix/tooltip-facet-positioning` conflicted with `main` in two example
files where both sides modified the same `Map.get()` spread lines
differently — the branch dropped `?? {}`, while `main` replaced it with
`as object`.

## Changes

- **`shift-map.svelte`** — adopt `main`'s `as object` cast over the
branch's bare spread
- **`spike-map.svelte`** — same resolution

```svelte
<!-- before (branch) -->
...electionByFips.get(Number(feat.id))

<!-- after (resolved) -->
...(electionByFips.get(Number(feat.id)) as object)
```

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in
our [2 minute survey](https://gh.io/copilot-coding-agent-survey).

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: gka <617518+gka@users.noreply.github.com>
## Summary

Stacked on #509.

- Removed erroneous `+ marginLeft` / `+ marginTop` from mouse-to-SVG
coordinate conversion in `Pointer.svelte` and `HTMLTooltip.svelte`
- The quadtree already stores points in scale-range space (which
includes margins via `projectX`/`projectY`), so adding margins again
caused a double offset that prevented point selection when margins > 0
- Added a regression test with `margin: 20` that fails before the fix
and passes after

## Test plan

- [x] New test: `selects nearest point with non-zero margins`
(margin=20, verifies point is found)
- [x] All 705 existing tests pass
- [x] `pnpm check` clean
- [x] `pnpm lint` clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@gka gka merged commit 70179ae into main Mar 2, 2026
8 checks passed
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.

3 participants