svelteplot 0.14.2-pr-552.1 → 0.14.2-pr-555.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/constants.js CHANGED
@@ -120,7 +120,7 @@ export const CHANNEL_SCALE = {
120
120
  strokeOpacity: 'opacity'
121
121
  };
122
122
  export const CSS_VAR = /^var\(--([a-z-0-9,\s]+)\)$/;
123
- export const CSS_COLOR = /^color\(/;
123
+ export const CSS_COLOR = /^(?:color|light-dark)\(/;
124
124
  export const CSS_COLOR_MIX = /^color-mix\(/; // just check for prefix
125
125
  export const CSS_COLOR_CONTRAST = /^color-contrast\(/; // just check for prefix
126
126
  export const CSS_RGBA = /^rgba\(/; // just check for prefix
@@ -31,8 +31,6 @@ export { default as Geo } from './Geo.svelte';
31
31
  export { default as Graticule } from './Graticule.svelte';
32
32
  export { default as GridX } from './GridX.svelte';
33
33
  export { default as GridY } from './GridY.svelte';
34
- export { default as Hexbin } from './Hexbin.svelte';
35
- export { default as Hexgrid } from './Hexgrid.svelte';
36
34
  export { default as Hull } from './Hull.svelte';
37
35
  export { default as Image } from './Image.svelte';
38
36
  export { default as Line } from './Line.svelte';
@@ -31,8 +31,6 @@ export { default as Geo } from './Geo.svelte';
31
31
  export { default as Graticule } from './Graticule.svelte';
32
32
  export { default as GridX } from './GridX.svelte';
33
33
  export { default as GridY } from './GridY.svelte';
34
- export { default as Hexbin } from './Hexbin.svelte';
35
- export { default as Hexgrid } from './Hexgrid.svelte';
36
34
  export { default as Hull } from './Hull.svelte';
37
35
  export { default as Image } from './Image.svelte';
38
36
  export { default as Line } from './Line.svelte';
@@ -6,7 +6,6 @@ export { filter } from './filter.js';
6
6
  export { map, mapX, mapY } from './map.js';
7
7
  export { normalizeX, normalizeY, normalizeParallelX, normalizeParallelY } from './normalize.js';
8
8
  export { group, groupX, groupY, groupZ } from './group.js';
9
- export { hexbin } from './hexbin.js';
10
9
  export { intervalX, intervalY } from './interval.js';
11
10
  export { jitter, jitterX, jitterY } from './jitter.js';
12
11
  export { recordizeX, recordizeY } from './recordize.js';
@@ -6,7 +6,6 @@ export { filter } from './filter.js';
6
6
  export { map, mapX, mapY } from './map.js';
7
7
  export { normalizeX, normalizeY, normalizeParallelX, normalizeParallelY } from './normalize.js';
8
8
  export { group, groupX, groupY, groupZ } from './group.js';
9
- export { hexbin } from './hexbin.js';
10
9
  export { intervalX, intervalY } from './interval.js';
11
10
  export { jitter, jitterX, jitterY } from './jitter.js';
12
11
  export { recordizeX, recordizeY } from './recordize.js';
@@ -6,7 +6,7 @@ export type Mark<T> = {
6
6
  data: DataRecord<T>[];
7
7
  options: T;
8
8
  };
9
- export type MarkType = 'area' | 'arrow' | 'axisX' | 'axisY' | 'barX' | 'barY' | 'cell' | 'contour' | 'custom' | 'delaunayLink' | 'delaunayMesh' | 'density' | 'dot' | 'vector' | 'frame' | 'geo' | 'gridX' | 'gridY' | 'hexbin' | 'hexgrid' | 'hull' | 'image' | 'link' | 'line' | 'raster' | 'rect' | 'regression' | 'ruleX' | 'ruleY' | 'swoopyArrow' | 'text' | 'tickX' | 'tickY' | 'trail' | 'voronoi' | 'voronoiMesh' | 'waffleX' | 'waffleY';
9
+ export type MarkType = 'area' | 'arrow' | 'axisX' | 'axisY' | 'barX' | 'barY' | 'cell' | 'contour' | 'custom' | 'delaunayLink' | 'delaunayMesh' | 'density' | 'dot' | 'vector' | 'frame' | 'geo' | 'gridX' | 'gridY' | 'hull' | 'image' | 'link' | 'line' | 'raster' | 'rect' | 'regression' | 'ruleX' | 'ruleY' | 'swoopyArrow' | 'text' | 'tickX' | 'tickY' | 'trail' | 'voronoi' | 'voronoiMesh' | 'waffleX' | 'waffleY';
10
10
  export type MarkStyleProps = 'strokeDasharray' | 'strokeLinejoin' | 'strokeLinecap' | 'opacity' | 'cursor' | 'pointerEvents' | 'blend' | 'fill' | 'fillOpacity' | 'fontFamily' | 'fontWeight' | 'fontVariant' | 'fontSize' | 'fontStyle' | 'letterSpacing' | 'wordSpacing' | 'stroke' | 'strokeWidth' | 'strokeOpacity' | 'x' | 'y' | 'clipPath' | 'mask' | 'filter' | 'angle' | 'radius' | 'symbol' | 'textAnchor' | 'textTransform' | 'textDecoration' | 'width';
11
11
  import type { ChannelAccessor, ConstantAccessor, DataRecord, RawValue } from './index.js';
12
12
  import type * as CSS from 'csstype';
@@ -5,7 +5,7 @@ import type { ChannelAccessor, ChannelName, ColorScaleOptions, DataRecord, Legen
5
5
  import type { Snippet } from 'svelte';
6
6
  import type { GenericMarkOptions, Mark } from './index.js';
7
7
  import type { Clip } from '../helpers/projection.js';
8
- import type { Area, AreaX, AreaY, Arrow, AxisX, AxisY, BarX, BarY, BoxX, BoxY, Brush, BrushX, BrushY, Cell, Contour, DelaunayLink, DelaunayMesh, Density, DifferenceY, Dot, Frame, Geo, Graticule, GridX, GridY, Hexbin, Hull, Image, Line, Link, Pointer, Raster, Rect, RectX, RectY, RuleX, RuleY, Sphere, Spike, Text, TickX, TickY, Trail, Vector, Voronoi, VoronoiMesh } from '../marks/index.js';
8
+ import type { Area, AreaX, AreaY, Arrow, AxisX, AxisY, BarX, BarY, BoxX, BoxY, Brush, BrushX, BrushY, Cell, Contour, DelaunayLink, DelaunayMesh, Density, DifferenceY, Dot, Frame, Geo, Graticule, GridX, GridY, Hull, Image, Line, Link, Pointer, Raster, Rect, RectX, RectY, RuleX, RuleY, Sphere, Spike, Text, TickX, TickY, Trail, Vector, Voronoi, VoronoiMesh } from '../marks/index.js';
9
9
  import type WaffleX from '../marks/WaffleX.svelte';
10
10
  import type WaffleY from '../marks/WaffleY.svelte';
11
11
  import type BaseAxisX from '../marks/helpers/BaseAxisX.svelte';
@@ -260,10 +260,6 @@ export type PlotDefaults = {
260
260
  gridY: Partial<Omit<ComponentProps<typeof GridY>, IgnoreDefaults> & {
261
261
  implicit: boolean;
262
262
  }> | true;
263
- /**
264
- * default props for hexbin marks
265
- */
266
- hexbin: Partial<Omit<ComponentProps<typeof Hexbin>, IgnoreDefaults>>;
267
263
  /**
268
264
  * default props for hull marks
269
265
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelteplot",
3
- "version": "0.14.2-pr-552.1",
3
+ "version": "0.14.2-pr-555.1",
4
4
  "description": "A Svelte-native data visualization framework based on the layered grammar of graphics principles.",
5
5
  "keywords": [
6
6
  "svelte",
@@ -1,20 +0,0 @@
1
- export declare const HEX_DEFAULT_BIN_WIDTH = 20;
2
- export type HexLattice = {
3
- rx: number;
4
- ry: number;
5
- dx: number;
6
- dy: number;
7
- originX: number;
8
- originY: number;
9
- };
10
- export declare function hexLattice(binWidth: number, originX?: number, originY?: number): HexLattice;
11
- export declare function hexLatticeXY(dx: number, dy: number, originX?: number, originY?: number): HexLattice;
12
- export declare function hexCenter(i: number, j: number, lattice: HexLattice): [number, number];
13
- export declare function pointToHex(px: number, py: number, lattice: HexLattice): {
14
- i: number;
15
- j: number;
16
- cx: number;
17
- cy: number;
18
- };
19
- export declare function hexagonSubpath(cx: number, cy: number, rx: number, ry: number): string;
20
- export declare function hexCellsInRect(lattice: HexLattice, x0: number, y0: number, x1: number, y1: number): Generator<[number, number]>;
@@ -1,73 +0,0 @@
1
- // Shared hex lattice math used by the `hexbin` transform and the `Hexgrid`
2
- // mark so binning and the grid overlay always tile the same lattice. Pointy-
3
- // topped hexagons; odd rows are offset horizontally by rx. The lattice is
4
- // unit-agnostic — callers pass pitches in pixels (Hexgrid pixel mode) or in
5
- // data units (data-space mode).
6
- const SQRT3 = Math.sqrt(3);
7
- export const HEX_DEFAULT_BIN_WIDTH = 20;
8
- // Build a regular pointy-topped hex lattice from a single pitch. dy is the
9
- // canonical dx*√3/2 row pitch for tiling regular hexagons — used by Hexgrid
10
- // pixel mode and any caller working in a single coordinate system.
11
- export function hexLattice(binWidth, originX = 0, originY = 0) {
12
- const rx = binWidth / 2;
13
- const ry = (rx * 2) / SQRT3;
14
- return { rx, ry, dx: binWidth, dy: ry * 1.5, originX, originY };
15
- }
16
- // Build a lattice with independent column and row pitches. Use this when the
17
- // x and y axes have different units (data-space hexbin), so each axis's pitch
18
- // can be scaled by its own data extent and still tile cleanly.
19
- export function hexLatticeXY(dx, dy, originX = 0, originY = 0) {
20
- // ry is the vertical half-bounding-box; row pitch dy = 1.5 * ry, so ry = 2/3 dy.
21
- return { rx: dx / 2, ry: (dy * 2) / 3, dx, dy, originX, originY };
22
- }
23
- export function hexCenter(i, j, lattice) {
24
- const cx = lattice.originX + (i + (j & 1) / 2) * lattice.dx;
25
- const cy = lattice.originY + j * lattice.dy;
26
- return [cx, cy];
27
- }
28
- // Snaps a pixel point to the nearest hex cell. Must check the rounded row and
29
- // both neighbors because the row-offset lattice lets an adjacent row be closer
30
- // than the rounded one.
31
- export function pointToHex(px, py, lattice) {
32
- const { dx, dy, originX, originY } = lattice;
33
- const rx = dx / 2;
34
- const pj = Math.round((py - originY) / dy) || 0;
35
- let best = { i: 0, j: 0, cx: 0, cy: 0, d2: Infinity };
36
- for (const dj of [0, 1, -1]) {
37
- const cj = pj + dj;
38
- const ci = Math.round((px - originX - (cj & 1) * rx) / dx) || 0;
39
- const cx = originX + (ci + (cj & 1) / 2) * dx;
40
- const cy = originY + cj * dy;
41
- const d2 = (px - cx) ** 2 + (py - cy) ** 2;
42
- if (d2 < best.d2)
43
- best = { i: ci, j: cj, cx, cy, d2 };
44
- }
45
- return { i: best.i, j: best.j, cx: best.cx, cy: best.cy };
46
- }
47
- // SVG subpath for a single pointy-topped hex, rounded to compact the output.
48
- export function hexagonSubpath(cx, cy, rx, ry) {
49
- const r3 = (n) => Math.round(n * 1000) / 1000;
50
- return (`M${r3(cx)},${r3(cy - ry)}` +
51
- `l${r3(rx)},${r3(ry / 2)}` +
52
- `v${r3(ry)}` +
53
- `l${r3(-rx)},${r3(ry / 2)}` +
54
- `l${r3(-rx)},${r3(-ry / 2)}` +
55
- `v${r3(-ry)}Z`);
56
- }
57
- // Iterate hex cell centers covering the given pixel rect. Pads by one cell so
58
- // a clipPath can trim the edges cleanly without gaps.
59
- export function* hexCellsInRect(lattice, x0, y0, x1, y1) {
60
- const { dx, dy, originX, originY, rx, ry } = lattice;
61
- const jMin = Math.floor((y0 - originY - ry) / dy);
62
- const jMax = Math.ceil((y1 - originY + ry) / dy);
63
- for (let j = jMin; j <= jMax; j++) {
64
- const xOffset = (j & 1) * (dx / 2);
65
- const iMin = Math.floor((x0 - originX - xOffset - rx) / dx);
66
- const iMax = Math.ceil((x1 - originX - xOffset + rx) / dx);
67
- for (let i = iMin; i <= iMax; i++) {
68
- const cx = originX + (i + (j & 1) / 2) * dx;
69
- const cy = originY + j * dy;
70
- yield [cx, cy];
71
- }
72
- }
73
- }
@@ -1,293 +0,0 @@
1
- <!-- @component
2
- Renders a hexagonal binning of 2D scatter data: groups raw points into a
3
- pixel-space hex lattice, runs a reducer per bin, and draws each non-empty
4
- bin as a regular hexagonal cell. Cells are regular by construction because
5
- the lattice is computed in pixel space (after scales exist) rather than in
6
- data space, so they tile correctly under any axis aspect ratio.
7
-
8
- Pairs naturally with the data-less `<Hexgrid />` mark for an empty-cell
9
- backdrop — both default to `binWidth=20` (px), so a default `<Hexbin>` and
10
- a default `<Hexgrid>` align by convention without user coordination.
11
-
12
- The `fill` prop accepts a reducer name (`'count'`, `'mean'`, etc.) to map
13
- aggregated values through the plot's color scale, or a CSS color for a
14
- constant fill. Same shape for `stroke`.
15
- -->
16
- <script lang="ts" generics="Datum extends DataRecord">
17
- interface HexbinMarkProps {
18
- /** Input data — array of records with x/y positions. */
19
- data?: Datum[] | null;
20
- /** x position channel (data space). */
21
- x?: ChannelAccessor<Datum>;
22
- /** y position channel (data space). */
23
- y?: ChannelAccessor<Datum>;
24
- /**
25
- * Hex cell pitch in pixels (distance between adjacent cell centers
26
- * along x). Default 20, matching `<Hexgrid />`.
27
- */
28
- binWidth?: number;
29
- /**
30
- * Fill: a reducer name (`'count'`, `'mean'`, …) to map aggregated
31
- * values through the color scale, OR a CSS color for a constant fill.
32
- * Default `'count'` (maps bin counts through the color scale).
33
- */
34
- fill?: ReducerName | string;
35
- /** Stroke: same shape as `fill`. Default `'none'`. */
36
- stroke?: ReducerName | string;
37
- strokeWidth?: number;
38
- fillOpacity?: number;
39
- strokeOpacity?: number;
40
- opacity?: number;
41
- clipPath?: string;
42
- class?: string;
43
- }
44
-
45
- import Mark from '../Mark.svelte';
46
- import { usePlot } from '../hooks/usePlot.svelte.js';
47
- import { getPlotDefaults } from '../hooks/plotDefaults.js';
48
- import { reduceOutputs, type ReducerName } from '../helpers/reduce.js';
49
- import { resolveProp } from '../helpers/resolve.js';
50
- import { isColorOrNull } from '../helpers/typeChecks.js';
51
- import {
52
- hexLattice,
53
- pointToHex,
54
- hexagonSubpath,
55
- HEX_DEFAULT_BIN_WIDTH
56
- } from '../helpers/hexLattice.js';
57
- import type {
58
- ChannelAccessor,
59
- DataRecord,
60
- MarkType,
61
- ScaledDataRecord
62
- } from '../types/index.js';
63
-
64
- // Per-record symbols for the synthetic data passed to <Mark>. Density
65
- // uses the same pattern (Density.svelte:108-115) — the scale system reads
66
- // channel values via Symbol-keyed accessors, so user data fields can't
67
- // collide with our internal channels. Hexbin uses x/y point channels
68
- // (not Density's x1/x2/y1/y2 range channels) because a bin IS a point;
69
- // CHANNEL_SCALE maps both to the same x/y scale (see constants.ts:110).
70
- const GEOM = Symbol('hexbin_geom');
71
- const X_VAL = Symbol('hexbin_x');
72
- const Y_VAL = Symbol('hexbin_y');
73
- const FILL_VAL = Symbol('hexbin_fill');
74
- const STROKE_VAL = Symbol('hexbin_stroke');
75
-
76
- const DEFAULTS = {
77
- ...getPlotDefaults().hexbin
78
- };
79
-
80
- let markProps: HexbinMarkProps = $props();
81
-
82
- const {
83
- data,
84
- x: xAcc,
85
- y: yAcc,
86
- binWidth = HEX_DEFAULT_BIN_WIDTH,
87
- fill: rawFill = 'count',
88
- stroke: rawStroke = 'none',
89
- strokeWidth,
90
- fillOpacity,
91
- strokeOpacity,
92
- opacity,
93
- clipPath,
94
- class: className = 'hexbin',
95
- ...options
96
- }: HexbinMarkProps = $derived({ ...DEFAULTS, ...markProps });
97
-
98
- const plot = usePlot();
99
-
100
- // A fill/stroke value is a reducer when it's a function or a string that
101
- // looks like a reducer name rather than a CSS color. Mirrors Density's
102
- // isDensityAccessor logic (Density.svelte:159-175).
103
- function isReducer(v: unknown): boolean {
104
- if (typeof v === 'function') return true;
105
- if (typeof v !== 'string') return false;
106
- const lower = v.toLowerCase();
107
- if (lower === 'none' || lower === 'inherit' || lower === 'currentcolor') return false;
108
- return !isColorOrNull(v);
109
- }
110
-
111
- const fillIsReducer = $derived(isReducer(rawFill));
112
- const strokeIsReducer = $derived(isReducer(rawStroke));
113
-
114
- // Raw data extent in data units, used to bootstrap x/y scale domains
115
- // before bins are computed (the chicken-and-egg: bins need pixel-space
116
- // scales, scales need a domain). Same approach as Density.svelte:385-426.
117
- const extent = $derived.by(() => {
118
- if (!data?.length || xAcc == null || yAcc == null) return null;
119
- let xMin = Infinity,
120
- xMax = -Infinity,
121
- yMin = Infinity,
122
- yMax = -Infinity;
123
- for (const d of data as any[]) {
124
- const xv = resolveProp(xAcc as any, d);
125
- const yv = resolveProp(yAcc as any, d);
126
- if (typeof xv === 'number' && Number.isFinite(xv)) {
127
- if (xv < xMin) xMin = xv;
128
- if (xv > xMax) xMax = xv;
129
- }
130
- if (typeof yv === 'number' && Number.isFinite(yv)) {
131
- if (yv < yMin) yMin = yv;
132
- if (yv > yMax) yMax = yv;
133
- }
134
- }
135
- if (!isFinite(xMin) || !isFinite(yMin)) return null;
136
- return { x1: xMin, x2: xMax, y1: yMin, y2: yMax };
137
- });
138
-
139
- // Pixel-space binning. Builds a regular hex lattice from binWidth (px),
140
- // projects each raw point through the live x/y scales to pixel space,
141
- // and snaps to the nearest hex center via pointToHex. Returns null on
142
- // the bootstrap pass (before scales are computable).
143
- const binResult = $derived.by(() => {
144
- if (!data?.length || xAcc == null || yAcc == null) return null;
145
- const sx = plot.scales.x?.fn as ((v: any) => number) | undefined;
146
- const sy = plot.scales.y?.fn as ((v: any) => number) | undefined;
147
- if (!sx || !sy) return null;
148
-
149
- const ml = plot.options.marginLeft ?? 0;
150
- const mt = plot.options.marginTop ?? 0;
151
- // Origin matches Hexgrid (Hexgrid.svelte:53) and the existing hexbin
152
- // transform (transforms/hexbin.ts:85) so a default Hexbin and a default
153
- // Hexgrid tile the same lattice without coordination. Cell (0,0)
154
- // straddles the top-left corner — symmetric with the X half-pitch
155
- // offset, which puts the cell half-inside the left edge.
156
- const lattice = hexLattice(binWidth, ml + binWidth / 2, mt);
157
-
158
- const map = new Map<
159
- string,
160
- { i: number; j: number; cx: number; cy: number; items: any[] }
161
- >();
162
- for (const d of data as any[]) {
163
- const xv = resolveProp(xAcc as any, d);
164
- const yv = resolveProp(yAcc as any, d);
165
- const px = sx(xv);
166
- const py = sy(yv);
167
- if (!Number.isFinite(px) || !Number.isFinite(py)) continue;
168
- const { i, j, cx, cy } = pointToHex(px, py, lattice);
169
- const key = `${i},${j}`;
170
- let bin = map.get(key);
171
- if (!bin) {
172
- bin = { i, j, cx, cy, items: [] };
173
- map.set(key, bin);
174
- }
175
- bin.items.push(d);
176
- }
177
- return { lattice, bins: [...map.values()] };
178
- });
179
-
180
- // Synthetic records for <Mark>. Bootstrap pass emits two corner records so
181
- // x/y scales compute a domain before bins exist; result pass emits the same
182
- // two corner records plus one per bin carrying:
183
- // X_VAL / Y_VAL raw data extent → x/y scale domain (one value per record;
184
- // two records together span the extent)
185
- // FILL_VAL reducer output → color scale domain (when fill is a reducer)
186
- // STROKE_VAL reducer output → color scale domain (when stroke is a reducer)
187
- // GEOM pixel-space hex geometry → rendered by children snippet
188
- const markData = $derived.by((): DataRecord[] => {
189
- const ext = extent;
190
- const br = binResult;
191
- if (!ext) return [];
192
-
193
- if (!br) {
194
- return [
195
- { [X_VAL]: ext.x1, [Y_VAL]: ext.y1 } as DataRecord,
196
- { [X_VAL]: ext.x2, [Y_VAL]: ext.y2 } as DataRecord
197
- ];
198
- }
199
-
200
- const reducerOpts: any = {};
201
- const outputs: string[] = [];
202
- if (fillIsReducer) {
203
- reducerOpts.fill = rawFill;
204
- outputs.push('fill');
205
- }
206
- if (strokeIsReducer) {
207
- reducerOpts.stroke = rawStroke;
208
- outputs.push('stroke');
209
- }
210
-
211
- const records: any[] = [
212
- // Persistent corner records so x/y scale domain stays at [x1,x2]
213
- // / [y1,y2] across re-derivations. Without them, after the bootstrap
214
- // pass replaces markData with bin records (all near a single x/y),
215
- // the scale domain collapses and the next pass produces NaN.
216
- { [X_VAL]: ext.x1, [Y_VAL]: ext.y1 } as any,
217
- { [X_VAL]: ext.x2, [Y_VAL]: ext.y2 } as any
218
- ];
219
- for (const bin of br.bins) {
220
- const item: any = {
221
- // Channel values irrelevant for rendering (GEOM drives that),
222
- // but must be inside the extent so they don't expand the domain.
223
- [X_VAL]: ext.x1,
224
- [Y_VAL]: ext.y1,
225
- [GEOM]: {
226
- cx: bin.cx,
227
- cy: bin.cy,
228
- rx: br.lattice.rx,
229
- ry: br.lattice.ry
230
- }
231
- };
232
-
233
- // reduceOutputs writes item.__fill = countValue etc. (note the
234
- // `__` prefix — see reduce.ts:113); copy onto Symbol keys so the
235
- // channel accessors below resolve them and user-data field names
236
- // can't collide.
237
- if (outputs.length > 0) {
238
- reduceOutputs(
239
- item,
240
- bin.items,
241
- reducerOpts,
242
- outputs as any,
243
- { x: xAcc, y: yAcc } as any,
244
- {} as any
245
- );
246
- if (fillIsReducer) item[FILL_VAL] = item['__fill'];
247
- if (strokeIsReducer) item[STROKE_VAL] = item['__stroke'];
248
- }
249
-
250
- records.push(item);
251
- }
252
- return records;
253
- });
254
-
255
- const markChannels = $derived([
256
- 'x',
257
- 'y',
258
- ...(fillIsReducer ? ['fill'] : []),
259
- ...(strokeIsReducer ? ['stroke'] : [])
260
- ] as const);
261
-
262
- const markChannelProps = $derived({
263
- x: X_VAL as any,
264
- y: Y_VAL as any,
265
- ...(fillIsReducer ? { fill: FILL_VAL as any } : {}),
266
- ...(strokeIsReducer ? { stroke: STROKE_VAL as any } : {})
267
- });
268
- </script>
269
-
270
- <Mark
271
- type={'hexbin' as MarkType}
272
- data={markData}
273
- channels={markChannels as any}
274
- {...options}
275
- {...markChannelProps}>
276
- {#snippet children({ scaledData }: { scaledData: ScaledDataRecord[] })}
277
- <g class={className} clip-path={clipPath}>
278
- {#each scaledData as d, i (i)}
279
- {@const geom = (d.datum as any)?.[GEOM]}
280
- {#if geom}
281
- <path
282
- d={hexagonSubpath(geom.cx, geom.cy, geom.rx, geom.ry)}
283
- fill={fillIsReducer ? ((d as any).fill ?? 'currentColor') : rawFill}
284
- stroke={strokeIsReducer ? ((d as any).stroke ?? 'none') : rawStroke}
285
- stroke-width={strokeWidth}
286
- fill-opacity={fillOpacity}
287
- stroke-opacity={strokeOpacity}
288
- {opacity} />
289
- {/if}
290
- {/each}
291
- </g>
292
- {/snippet}
293
- </Mark>
@@ -1,67 +0,0 @@
1
- import { type ReducerName } from '../helpers/reduce.js';
2
- import type { ChannelAccessor, DataRecord } from '../types/index.js';
3
- declare function $$render<Datum extends DataRecord>(): {
4
- props: {
5
- /** Input data — array of records with x/y positions. */
6
- data?: Datum[] | null;
7
- /** x position channel (data space). */
8
- x?: ChannelAccessor<Datum>;
9
- /** y position channel (data space). */
10
- y?: ChannelAccessor<Datum>;
11
- /**
12
- * Hex cell pitch in pixels (distance between adjacent cell centers
13
- * along x). Default 20, matching `<Hexgrid />`.
14
- */
15
- binWidth?: number;
16
- /**
17
- * Fill: a reducer name (`'count'`, `'mean'`, …) to map aggregated
18
- * values through the color scale, OR a CSS color for a constant fill.
19
- * Default `'count'` (maps bin counts through the color scale).
20
- */
21
- fill?: ReducerName | string;
22
- /** Stroke: same shape as `fill`. Default `'none'`. */
23
- stroke?: ReducerName | string;
24
- strokeWidth?: number;
25
- fillOpacity?: number;
26
- strokeOpacity?: number;
27
- opacity?: number;
28
- clipPath?: string;
29
- class?: string;
30
- };
31
- exports: {};
32
- bindings: "";
33
- slots: {};
34
- events: {};
35
- };
36
- declare class __sveltets_Render<Datum extends DataRecord> {
37
- props(): ReturnType<typeof $$render<Datum>>['props'];
38
- events(): ReturnType<typeof $$render<Datum>>['events'];
39
- slots(): ReturnType<typeof $$render<Datum>>['slots'];
40
- bindings(): "";
41
- exports(): {};
42
- }
43
- interface $$IsomorphicComponent {
44
- new <Datum extends DataRecord>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Datum>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Datum>['props']>, ReturnType<__sveltets_Render<Datum>['events']>, ReturnType<__sveltets_Render<Datum>['slots']>> & {
45
- $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
46
- } & ReturnType<__sveltets_Render<Datum>['exports']>;
47
- <Datum extends DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
48
- z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
49
- }
50
- /**
51
- * Renders a hexagonal binning of 2D scatter data: groups raw points into a
52
- * pixel-space hex lattice, runs a reducer per bin, and draws each non-empty
53
- * bin as a regular hexagonal cell. Cells are regular by construction because
54
- * the lattice is computed in pixel space (after scales exist) rather than in
55
- * data space, so they tile correctly under any axis aspect ratio.
56
- *
57
- * Pairs naturally with the data-less `<Hexgrid />` mark for an empty-cell
58
- * backdrop — both default to `binWidth=20` (px), so a default `<Hexbin>` and
59
- * a default `<Hexgrid>` align by convention without user coordination.
60
- *
61
- * The `fill` prop accepts a reducer name (`'count'`, `'mean'`, etc.) to map
62
- * aggregated values through the plot's color scale, or a CSS color for a
63
- * constant fill. Same shape for `stroke`.
64
- */
65
- declare const Hexbin: $$IsomorphicComponent;
66
- type Hexbin<Datum extends DataRecord> = InstanceType<typeof Hexbin<Datum>>;
67
- export default Hexbin;
@@ -1,72 +0,0 @@
1
- <!-- @component
2
- Renders a hexagonal grid decoration, typically used alongside hexbin-transformed data.
3
- A data-less mark similar to Frame.
4
- -->
5
- <script lang="ts">
6
- interface HexgridMarkProps {
7
- /** the hexagon bin width in pixels */
8
- binWidth?: number;
9
- /** the stroke color of the grid lines */
10
- stroke?: string;
11
- /** the stroke opacity of the grid lines */
12
- strokeOpacity?: number;
13
- /** the stroke width of the grid lines */
14
- strokeWidth?: number;
15
- /** the fill color of the hexagons */
16
- fill?: string;
17
- /** the fill opacity of the hexagons */
18
- fillOpacity?: number;
19
- /** CSS class name */
20
- class?: string;
21
- }
22
-
23
- import Mark from '../Mark.svelte';
24
- import type { MarkType } from '../types/index.js';
25
- import { usePlot } from '../hooks/usePlot.svelte.js';
26
- import {
27
- hexLattice,
28
- hexCellsInRect,
29
- hexagonSubpath,
30
- HEX_DEFAULT_BIN_WIDTH
31
- } from '../helpers/hexLattice.js';
32
-
33
- let markProps: HexgridMarkProps = $props();
34
-
35
- const {
36
- binWidth = HEX_DEFAULT_BIN_WIDTH,
37
- stroke = 'currentColor',
38
- strokeOpacity = 0.1,
39
- strokeWidth = 1,
40
- fill = 'none',
41
- fillOpacity,
42
- class: className = 'hexgrid'
43
- }: HexgridMarkProps = $derived({ ...markProps });
44
-
45
- const plot = usePlot();
46
-
47
- const pathData = $derived.by(() => {
48
- const ml = plot.options.marginLeft;
49
- const mt = plot.options.marginTop;
50
- const w = plot.facetWidth;
51
- const h = plot.facetHeight;
52
-
53
- const lattice = hexLattice(binWidth, ml + binWidth / 2, mt);
54
- let path = '';
55
- for (const [cx, cy] of hexCellsInRect(lattice, ml, mt, ml + w, mt + h)) {
56
- path += hexagonSubpath(cx, cy, lattice.rx, lattice.ry);
57
- }
58
- return path;
59
- });
60
- </script>
61
-
62
- <Mark type={'hexgrid' as MarkType}>
63
- <g class={className}>
64
- <path
65
- d={pathData}
66
- {fill}
67
- fill-opacity={fillOpacity}
68
- {stroke}
69
- stroke-opacity={strokeOpacity}
70
- stroke-width={strokeWidth} />
71
- </g>
72
- </Mark>
@@ -1,23 +0,0 @@
1
- interface HexgridMarkProps {
2
- /** the hexagon bin width in pixels */
3
- binWidth?: number;
4
- /** the stroke color of the grid lines */
5
- stroke?: string;
6
- /** the stroke opacity of the grid lines */
7
- strokeOpacity?: number;
8
- /** the stroke width of the grid lines */
9
- strokeWidth?: number;
10
- /** the fill color of the hexagons */
11
- fill?: string;
12
- /** the fill opacity of the hexagons */
13
- fillOpacity?: number;
14
- /** CSS class name */
15
- class?: string;
16
- }
17
- /**
18
- * Renders a hexagonal grid decoration, typically used alongside hexbin-transformed data.
19
- * A data-less mark similar to Frame.
20
- */
21
- declare const Hexgrid: import("svelte").Component<HexgridMarkProps, {}, "">;
22
- type Hexgrid = ReturnType<typeof Hexgrid>;
23
- export default Hexgrid;
@@ -1,37 +0,0 @@
1
- import { type ReducerName } from '../helpers/reduce.js';
2
- import type { DataRecord, RawValue, TransformArg } from '../types/index.js';
3
- type ReducerOption = ReducerName | ((group: DataRecord[]) => RawValue);
4
- type HexbinOutputChannels = Partial<{
5
- fill: ReducerOption;
6
- stroke: ReducerOption;
7
- r: ReducerOption;
8
- opacity: ReducerOption;
9
- fillOpacity: ReducerOption;
10
- strokeOpacity: ReducerOption;
11
- }>;
12
- export type HexbinOptions = HexbinOutputChannels & {
13
- /**
14
- * Approximate number of hex bins along the x-axis.
15
- * The actual bin width is computed from the data extent.
16
- * Default: 20.
17
- */
18
- bins?: number;
19
- /**
20
- * Explicit bin width in data units. Overrides `bins` if set.
21
- */
22
- binWidth?: number;
23
- };
24
- /**
25
- * Bins data points into hexagonal cells and applies reducers to produce
26
- * aggregated output channels (e.g. fill="count", r="count").
27
- *
28
- * Usage:
29
- * ```svelte
30
- * <Dot {...hexbin(
31
- * { data: penguins, x: "culmen_length_mm", y: "culmen_depth_mm" },
32
- * { fill: "count", r: "count", bins: 15 }
33
- * )} symbol="hexagon" />
34
- * ```
35
- */
36
- export declare function hexbin({ data, ...channels }: TransformArg<DataRecord>, options?: HexbinOptions): TransformArg<DataRecord>;
37
- export {};
@@ -1,81 +0,0 @@
1
- import { resolveChannel } from '../helpers/resolve.js';
2
- import { extent } from 'd3-array';
3
- import { reduceOutputs } from '../helpers/reduce.js';
4
- import { groupFacetsAndZ } from '../helpers/group.js';
5
- import { hexLattice, pointToHex } from '../helpers/hexLattice.js';
6
- const CHANNELS = {
7
- x: Symbol('hexbin_x'),
8
- y: Symbol('hexbin_y')
9
- };
10
- /**
11
- * Bins data points into hexagonal cells and applies reducers to produce
12
- * aggregated output channels (e.g. fill="count", r="count").
13
- *
14
- * Usage:
15
- * ```svelte
16
- * <Dot {...hexbin(
17
- * { data: penguins, x: "culmen_length_mm", y: "culmen_depth_mm" },
18
- * { fill: "count", r: "count", bins: 15 }
19
- * )} symbol="hexagon" />
20
- * ```
21
- */
22
- export function hexbin({ data, ...channels }, options = {}) {
23
- const { bins = 20, binWidth: explicitBinWidth, ...reducerOptions } = options;
24
- if (channels.x == null || channels.y == null) {
25
- throw new Error('hexbin requires both x and y channels');
26
- }
27
- // Resolve x, y values from data
28
- const xValues = data.map((d) => resolveChannel('x', d, channels));
29
- const yValues = data.map((d) => resolveChannel('y', d, channels));
30
- const [xMin, xMax] = extent(xValues);
31
- const [yMin, yMax] = extent(yValues);
32
- if (xMin == null || yMin == null) {
33
- return { data: [], ...channels, x: CHANNELS.x, y: CHANNELS.y };
34
- }
35
- // Cell pitch in data units. Pointy-topped hex with regular geometry — dy
36
- // derived from dx, so dy is in the same data units as dx. Cells are visually
37
- // regular only when the data units happen to match across axes; for
38
- // pixel-correct cells under arbitrary data extents, use the <Hexbin> mark
39
- // instead, which builds its lattice in pixel space after scales exist.
40
- const dx = explicitBinWidth ?? (xMax - xMin) / Math.max(1, bins);
41
- const lattice = hexLattice(dx, xMin + dx / 2, yMin);
42
- const binMap = new Map();
43
- for (let i = 0; i < data.length; i++) {
44
- const px = xValues[i];
45
- const py = yValues[i];
46
- if (px == null || py == null || isNaN(px) || isNaN(py))
47
- continue;
48
- const { i: pi, j: pj, cx, cy } = pointToHex(px, py, lattice);
49
- const key = `${pi},${pj}`;
50
- let bin = binMap.get(key);
51
- if (!bin) {
52
- bin = { index: [], cx, cy };
53
- binMap.set(key, bin);
54
- }
55
- bin.index.push(i);
56
- }
57
- // Build output data from bins
58
- const xChannel = typeof channels.x === 'string' ? channels.x : '__hexbin_x';
59
- const yChannel = typeof channels.y === 'string' ? channels.y : '__hexbin_y';
60
- let newChannels = {
61
- ...channels,
62
- x: xChannel,
63
- y: yChannel
64
- };
65
- const outputs = ['fill', 'stroke', 'r', 'opacity', 'fillOpacity', 'strokeOpacity'];
66
- const newData = [];
67
- for (const [, bin] of binMap) {
68
- const items = bin.index.map((i) => data[i]);
69
- const newGroupChannels = groupFacetsAndZ(items, channels, (groupItems, groupProps) => {
70
- const item = {
71
- [xChannel]: bin.cx,
72
- [yChannel]: bin.cy,
73
- ...groupProps
74
- };
75
- reduceOutputs(item, groupItems, reducerOptions, outputs, channels, newChannels);
76
- newData.push(item);
77
- });
78
- newChannels = { ...newChannels, ...newGroupChannels };
79
- }
80
- return { data: newData, ...newChannels };
81
- }