Add <Hexbin> component on top of #552#554
Merged
gka merged 1 commit intoApr 26, 2026
Merged
Conversation
…ines A new <Hexbin> Svelte component that bins 2D scatter data into a pixel-space hex lattice, runs reducers per bin (default fill='count'), and renders each non-empty bin as a regular hexagonal cell. Cells are regular by construction because the lattice is computed in pixel space (after scales exist) rather than in data space - they tile correctly under any axis aspect ratio. Pairs with the existing <Hexgrid> at matching binWidth: a default <Hexbin/> + default <Hexgrid/> tile the same lattice with no user coordination. Architecture context: see HEXBIN_DESIGN_COMPARISON.md (local). This is Approach B in that doc - the component-flavored alternative to the existing transform-flavored hexbin(), which stays in place for now. Shared lattice helper - packages/svelteplot/src/helpers/hexLattice.ts (new, 102 lines): hexLattice / hexLatticeXY / hexCenter / pointToHex / hexagonSubpath / hexCellsInRect. Single source of truth for hex math used by both the transform and the components. - packages/svelteplot/src/helpers/hexLattice.test.ts (new, 7 tests). - transforms/hexbin.ts refactored 176 -> 142 lines to use the helper. - marks/Hexgrid.svelte refactored 92 -> 73 lines to use the helper. Bug fixes - Color scale: reduceOutputs writes to __fill/__stroke (with __ prefix, see helpers/reduce.ts:113), not fill/stroke. The Symbol-copy step in Hexbin.svelte was reading the wrong key, so FILL_VAL stayed undefined and cells fell through to plot.options.color.unknown (#cccccc99). - Y-origin alignment: Hexbin called hexLattice(bw, ml+bw/2, mt+bw/sqrt(3)) while Hexgrid (Hexgrid.svelte:53) and the existing transform (transforms/hexbin.ts:85) called it with originY = mt. Hexbin was the outlier - test svelteplot#6 caught the offset (= binWidth/sqrt(3) = 11.547 px). NEW_MARK_CHECKLIST (rebased to monorepo paths from main@04668ed4): Source & Types - [x] Component source packages/svelteplot/src/marks/Hexbin.svelte - [x] Props interface w/ generics Hexbin.svelte:16-43 - [x] JSDoc @component Hexbin.svelte:1-15 - [x] MarkType addition packages/svelteplot/src/types/mark.ts - [x] PlotDefaults type entry packages/svelteplot/src/types/plot.ts:341 - [x] PlotDefaults hook call Hexbin.svelte:47, 76-78 Exports - [x] Barrel export packages/svelteplot/src/marks/index.ts:34 - [x] Top-level re-export auto via packages/svelteplot/src/index.ts - [x] Package exports auto via packages/svelteplot/package.json:43 Documentation - [x] Docs page src/routes/marks/hexbin/+page.md - [x] Docs data loader src/routes/marks/hexbin/+page.ts - [x] Live code examples three in +page.md - [x] Sidebar entry config/sidebar.ts - [x] API reference regenerated src/routes/api/marks/+page.md:541 - [x] API anchor link src/routes/api/marks/+page.md:7 Testing - [x] Unit test fixture packages/svelteplot/tests/hexbin.test.svelte - [x] Unit test driver packages/svelteplot/tests/hexbin.test.svelte.ts - [x] Coverage: rendering, custom class, empty data, single-bin (count reducer plumbing), invalid coords, accessor functions, regular hexes under 2:1 aspect, binWidth scaling, Hexbin/Hexgrid alignment Examples & Visual Regression - [x] Examples gallery src/routes/examples/hexbin/ 0-basic.svelte 1-with-hexgrid.svelte 2-custom-binwidth.svelte - [x] Frontmatter (title/description/data/sortKey) in each - [x] VR baselines (light + dark) src/snapshots/hexbin/ 0-basic{,.dark}.png 1-with-hexgrid{,.dark}.png 2-custom-binwidth{,.dark}.png Tooling fix (incidental, pre-existing breakage) - scripts/generate-api.js: 13 stale src/lib/... paths updated to packages/svelteplot/src/... - leftover from the monorepo refactor in commit 3a1d520. Tests: 870/870 pass. pnpm check: 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
✅ Deploy Preview for svelteplot ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on top of #552. Adds the component-flavored
<Hexbin>(Approach B fromHEXBIN_DESIGN_COMPARISON.md, kept in my working tree) — a Svelte component that bins 2D scatter data into a pixel-space hex lattice and renders each bin as a regular hexagonal cell. Pairs with the existing<Hexgrid>at matchingbinWidth: a default<Hexbin />+ default<Hexgrid />tile the same lattice without user coordination.Why a component, not just the transform
The transform-flavored
hexbin()(already in #552) cannot bin in pixel space because it runs at template-evaluation time, before scales exist. Three structural symptoms followed:<Hexgrid>undernice,zero, or explicitdomain(transform readsextent(rawData), grid readsscale.domain()).binsbetween the transform call,<Hexgrid bins>, andron the Dot mark to get a coherent picture.<Hexbin>runs as a child of<Plot>, callsusePlot(), and builds its lattice in pixel space after scales exist. All three symptoms collapse: cells are regular by construction, the lattice is single-sourced, and there's nothing to coordinate. The transform stays for users who want the function-API ergonomics.What's in this PR
packages/svelteplot/src/helpers/hexLattice.ts(102 lines, 7 helper tests). Both the existing transform and the new component import from here.transforms/hexbin.tsrefactored 176→142 lines,marks/Hexgrid.svelte92→73 lines — behavior unchanged, just deduplicated math.<Hexbin>component atpackages/svelteplot/src/marks/Hexbin.svelte.packages/svelteplot/tests/hexbin.test.svelte{,.ts}— including a sub-pixel center-alignment test ((Suggestion) Make components generic to pasthrough datarow shape #6) between Hexbin and Hexgrid that catches a Y-origin bug fixed in this PR.src/routes/examples/hexbin/with light + dark VR baselines undersrc/snapshots/hexbin/.src/routes/marks/hexbin/+page.{md,ts}+ sidebar entry.setPlotDefaults.NEW_MARK_CHECKLIST.mdat the repo root — checklist for adding new marks with monorepo paths (rebased from a doc on my main branch). The commit message has each box ticked with file:line refs.scripts/generate-api.jshad 13 stalesrc/lib/...paths from before the monorepo refactor (3a1d5205) — updated topackages/svelteplot/src/...sopnpm docs:api:marksworks again.Bug fixes inline
Two real bugs found and fixed during this work:
Color scale was always "unknown".
reduceOutputswrites its result to__fill/__stroke(with the__prefix, seehelpers/reduce.ts:113), notfill/stroke. The Symbol-copy step in the component was reading the wrong key, so every bin rendered as#cccccc99. Caught by the count-reducer test.Hexbin/Hexgrid alignment was off by
binWidth/√3.Hexgrid.svelte:53andtransforms/hexbin.ts:85useoriginY = mt; the original Hexbin usedoriginY = mt + binWidth/√3to "push cell (0,0) inside the rect" — but that breaks symmetry with the X half-pitch offset and breaks alignment with Hexgrid by exactly that amount. Caught by the alignment test ((Suggestion) Make components generic to pasthrough datarow shape #6) — without it, screenshots looked plausibly close to aligned.Tests
pnpm check: 0 errors.The full commit message has the complete
NEW_MARK_CHECKLISTwith file:line citations for every box.🤖 Generated with Claude Code