Skip to content

Add <Hexbin> component on top of #552#554

Merged
gka merged 1 commit into
svelteplot:feat/hexbin-transformfrom
RobertFrenken:feat/hexbin-component
Apr 26, 2026
Merged

Add <Hexbin> component on top of #552#554
gka merged 1 commit into
svelteplot:feat/hexbin-transformfrom
RobertFrenken:feat/hexbin-component

Conversation

@RobertFrenken

Copy link
Copy Markdown
Contributor

Builds on top of #552. Adds the component-flavored <Hexbin> (Approach B from HEXBIN_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 matching binWidth: 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:

  1. Cells distort under non-1:1 axis aspect ratios (transform bins in data units; projected cells aren't regular hexagons).
  2. Lattice drift between the transform and <Hexgrid> under nice, zero, or explicit domain (transform reads extent(rawData), grid reads scale.domain()).
  3. User has to coordinate bins between the transform call, <Hexgrid bins>, and r on the Dot mark to get a coherent picture.

<Hexbin> runs as a child of <Plot>, calls usePlot(), 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

  • Shared lattice helper at packages/svelteplot/src/helpers/hexLattice.ts (102 lines, 7 helper tests). Both the existing transform and the new component import from here. transforms/hexbin.ts refactored 176→142 lines, marks/Hexgrid.svelte 92→73 lines — behavior unchanged, just deduplicated math.
  • <Hexbin> component at packages/svelteplot/src/marks/Hexbin.svelte.
  • 9 unit tests in 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.
  • 3 examples under src/routes/examples/hexbin/ with light + dark VR baselines under src/snapshots/hexbin/.
  • Showcase docs at src/routes/marks/hexbin/+page.{md,ts} + sidebar entry.
  • PlotDefaults wiring so users can configure default Hexbin props via setPlotDefaults.
  • NEW_MARK_CHECKLIST.md at 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.
  • Pre-existing tooling fix: scripts/generate-api.js had 13 stale src/lib/... paths from before the monorepo refactor (3a1d5205) — updated to packages/svelteplot/src/... so pnpm docs:api:marks works again.

Bug fixes inline

Two real bugs found and fixed during this work:

  1. Color scale was always "unknown". reduceOutputs writes its result to __fill/__stroke (with the __ prefix, see helpers/reduce.ts:113), not fill/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.

  2. Hexbin/Hexgrid alignment was off by binWidth/√3. Hexgrid.svelte:53 and transforms/hexbin.ts:85 use originY = mt; the original Hexbin used originY = mt + binWidth/√3 to "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

  • 870/870 tests pass (861 baseline + 9 new for Hexbin).
  • pnpm check: 0 errors.
  • VR baselines were generated on a SLURM compute node; on a login node the Vite dev compile was too slow for puppeteer's default 60s navigation timeout.

The full commit message has the complete NEW_MARK_CHECKLIST with file:line citations for every box.

🤖 Generated with Claude Code

…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>
@netlify

netlify Bot commented Apr 25, 2026

Copy link
Copy Markdown

Deploy Preview for svelteplot ready!

Name Link
🔨 Latest commit 6b8f11c
🔍 Latest deploy log https://app.netlify.com/projects/svelteplot/deploys/69ed35542f6c940008d0bbbd
😎 Deploy Preview https://deploy-preview-554--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 gka merged commit 64131f1 into svelteplot:feat/hexbin-transform Apr 26, 2026
4 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.

2 participants