svelteplot 0.12.0 → 0.14.0

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.
Files changed (54) hide show
  1. package/dist/core/Plot.svelte +3 -6
  2. package/dist/helpers/group.d.ts +1 -1
  3. package/dist/helpers/group.js +3 -3
  4. package/dist/helpers/scales.js +8 -0
  5. package/dist/helpers/vectorShapes.d.ts +13 -0
  6. package/dist/helpers/vectorShapes.js +57 -0
  7. package/dist/marks/Arrow.svelte +70 -59
  8. package/dist/marks/Arrow.svelte.d.ts +2 -0
  9. package/dist/marks/ColorLegend.svelte +7 -3
  10. package/dist/marks/Contour.svelte +684 -0
  11. package/dist/marks/Contour.svelte.d.ts +152 -0
  12. package/dist/marks/DelaunayLink.svelte +127 -0
  13. package/dist/marks/DelaunayLink.svelte.d.ts +175 -0
  14. package/dist/marks/DelaunayMesh.svelte +102 -0
  15. package/dist/marks/DelaunayMesh.svelte.d.ts +172 -0
  16. package/dist/marks/Density.svelte +461 -0
  17. package/dist/marks/Density.svelte.d.ts +87 -0
  18. package/dist/marks/Hull.svelte +103 -0
  19. package/dist/marks/Hull.svelte.d.ts +175 -0
  20. package/dist/marks/Image.svelte +37 -27
  21. package/dist/marks/Image.svelte.d.ts +2 -0
  22. package/dist/marks/Link.svelte +68 -50
  23. package/dist/marks/Link.svelte.d.ts +2 -0
  24. package/dist/marks/Raster.svelte +6 -1
  25. package/dist/marks/Vector.svelte +12 -81
  26. package/dist/marks/Vector.svelte.d.ts +2 -4
  27. package/dist/marks/Voronoi.svelte +118 -0
  28. package/dist/marks/Voronoi.svelte.d.ts +172 -0
  29. package/dist/marks/VoronoiMesh.svelte +109 -0
  30. package/dist/marks/VoronoiMesh.svelte.d.ts +172 -0
  31. package/dist/marks/helpers/ArrowCanvas.svelte +132 -0
  32. package/dist/marks/helpers/ArrowCanvas.svelte.d.ts +39 -0
  33. package/dist/marks/helpers/BaseAxisX.svelte +5 -7
  34. package/dist/marks/helpers/DensityCanvas.svelte +118 -0
  35. package/dist/marks/helpers/DensityCanvas.svelte.d.ts +18 -0
  36. package/dist/marks/helpers/GeoPathCanvas.svelte +125 -0
  37. package/dist/marks/helpers/GeoPathCanvas.svelte.d.ts +24 -0
  38. package/dist/marks/helpers/GeoPathGroup.svelte +103 -0
  39. package/dist/marks/helpers/GeoPathGroup.svelte.d.ts +37 -0
  40. package/dist/marks/helpers/ImageCanvas.svelte +126 -0
  41. package/dist/marks/helpers/ImageCanvas.svelte.d.ts +34 -0
  42. package/dist/marks/helpers/LinkCanvas.svelte +103 -0
  43. package/dist/marks/helpers/LinkCanvas.svelte.d.ts +32 -0
  44. package/dist/marks/helpers/PathGroup.svelte +100 -0
  45. package/dist/marks/helpers/PathGroup.svelte.d.ts +16 -0
  46. package/dist/marks/helpers/PathItems.svelte +112 -0
  47. package/dist/marks/helpers/PathItems.svelte.d.ts +16 -0
  48. package/dist/marks/helpers/VectorCanvas.svelte +127 -0
  49. package/dist/marks/helpers/VectorCanvas.svelte.d.ts +36 -0
  50. package/dist/marks/index.d.ts +7 -0
  51. package/dist/marks/index.js +7 -0
  52. package/dist/types/mark.d.ts +1 -1
  53. package/dist/types/plot.d.ts +33 -1
  54. package/package.json +185 -181
@@ -3,10 +3,7 @@
3
3
  The vector mark lets you place shapes (like arrows) on your plot.
4
4
  -->
5
5
  <script lang="ts" module>
6
- type D3Path = ReturnType<typeof import('d3-path').path>;
7
- export type ShapeRenderer = {
8
- draw(context: D3Path, l: number, r: number): void;
9
- };
6
+ export type { ShapeRenderer } from '../helpers/vectorShapes.js';
10
7
  </script>
11
8
 
12
9
  <script lang="ts" generics="Datum = DataRecord | GeoJSON.GeoJsonObject">
@@ -45,25 +42,23 @@
45
42
  } from '../types/index.js';
46
43
 
47
44
  import { getContext, type Snippet } from 'svelte';
48
- import { pathRound as path } from 'd3-path';
49
45
 
50
46
  import { resolveChannel, resolveProp, resolveStyles } from '../helpers/resolve.js';
51
47
  import { sort } from '../index.js';
52
48
  import Mark from '../Mark.svelte';
53
- //import DotCanvas from './helpers/DotCanvas.svelte';
49
+ import VectorCanvas from './helpers/VectorCanvas.svelte';
54
50
  import { isValid } from '../helpers/index.js';
55
51
  import { addEventHandlers } from './helpers/events.js';
56
52
  import { indexData } from '../transforms/recordize.js';
57
53
  import { getPlotDefaults } from '../hooks/plotDefaults.js';
58
54
  import { usePlot } from '../hooks/usePlot.svelte.js';
59
55
 
60
- const defaultRadius = 3;
61
-
62
- // The size of the arrowhead is proportional to its length, but we still allow
63
- // the relative size of the head to be controlled via the mark's width option;
64
- // doubling the default radius will produce an arrowhead that is twice as big.
65
- // That said, we'll probably want a arrow with a fixed head size, too.
66
- const wingRatio = defaultRadius * 5;
56
+ import {
57
+ defaultRadius,
58
+ maybeShape,
59
+ shapePath,
60
+ type ShapeRenderer
61
+ } from '../helpers/vectorShapes.js';
67
62
 
68
63
  let markProps: VectorMarkProps = $props();
69
64
  const DEFAULTS = {
@@ -83,72 +78,6 @@
83
78
 
84
79
  const plot = usePlot();
85
80
 
86
- const shapeArrow: ShapeRenderer = {
87
- draw(context: D3Path, l: number, r: number) {
88
- const wing = (l * r) / wingRatio;
89
- context.moveTo(0, 0);
90
- context.lineTo(0, -l);
91
- context.moveTo(-wing, wing - l);
92
- context.lineTo(0, -l);
93
- context.lineTo(wing, wing - l);
94
- }
95
- };
96
-
97
- const shapeSpike: ShapeRenderer = {
98
- draw(context: D3Path, l: number, r: number) {
99
- context.moveTo(-r, 0);
100
- context.lineTo(0, -l);
101
- context.lineTo(r, 0);
102
- }
103
- };
104
-
105
- const shapeArrowFilled: ShapeRenderer = {
106
- draw(context: D3Path, l: number, r: number) {
107
- // const wing = (l * r) / wingRatio;
108
- const headLength = Math.max(3, l * 0.3);
109
- const headSpike = headLength * 0.2;
110
- const headWidth = Math.max(2, l * 0.3);
111
- const tailWidth = Math.max(2, l * 0.3) * 0.3;
112
-
113
- context.moveTo(0, 0);
114
-
115
- context.lineTo(tailWidth * 0.5, -l + headLength - headSpike);
116
- context.lineTo(headWidth * 0.5, -l + headLength);
117
- context.lineTo(0, -l);
118
- context.lineTo(-headWidth * 0.5, -l + headLength);
119
- context.lineTo(-tailWidth * 0.5, -l + headLength - headSpike);
120
-
121
- context.closePath();
122
- }
123
- };
124
-
125
- const shapes = new Map([
126
- ['arrow', shapeArrow],
127
- ['arrow-filled', shapeArrowFilled],
128
- ['spike', shapeSpike]
129
- ]);
130
-
131
- function isShapeObject(value: any): value is ShapeRenderer {
132
- return value && typeof value.draw === 'function';
133
- }
134
-
135
- function maybeShape(shape: 'arrow' | 'spike' | 'arrow-filled' | ShapeRenderer) {
136
- if (isShapeObject(shape)) return shape;
137
- const value = shapes.get(`${shape}`.toLowerCase());
138
- if (value) return value;
139
- throw new Error(`invalid shape: ${shape}`);
140
- }
141
-
142
- function shapePath(
143
- shape: 'arrow' | 'spike' | 'arrow-filled' | ShapeRenderer,
144
- l: number,
145
- r: number
146
- ) {
147
- const context = path();
148
- maybeShape(shape).draw(context, l, r);
149
- return context.toString();
150
- }
151
-
152
81
  const args = $derived(
153
82
  sort({
154
83
  data: indexData(data as object[]) as unknown as Datum[],
@@ -177,8 +106,10 @@
177
106
  {#snippet children({ scaledData, usedScales })}
178
107
  <g class="vector" data-l={usedScales.length}>
179
108
  {#if canvas}
180
- <text x="30" y="30" style="color:red"
181
- >implement canvas rendering for vector mark</text>
109
+ <VectorCanvas
110
+ data={scaledData}
111
+ options={{ ...args, shape, anchor } as any}
112
+ {usedScales} />
182
113
  {:else}
183
114
  {#each scaledData as d, i (i)}
184
115
  {#if d.valid && isValid(d.r)}
@@ -1,9 +1,7 @@
1
- type D3Path = ReturnType<typeof import('d3-path').path>;
2
- export type ShapeRenderer = {
3
- draw(context: D3Path, l: number, r: number): void;
4
- };
1
+ export type { ShapeRenderer } from '../helpers/vectorShapes.js';
5
2
  import type { DataRecord, ChannelAccessor } from '../types/index.js';
6
3
  import { type Snippet } from 'svelte';
4
+ import { type ShapeRenderer } from '../helpers/vectorShapes.js';
7
5
  declare function $$render<Datum = DataRecord | GeoJSON.GeoJsonObject>(): {
8
6
  props: Partial<{
9
7
  filter: import("../types/index.js").ConstantAccessor<boolean, Datum>;
@@ -0,0 +1,118 @@
1
+ <!-- @component
2
+ Renders individual Voronoi cells, one per data point, allowing per-cell styling.
3
+ -->
4
+ <script lang="ts" generics="Datum = DataRecord">
5
+ interface VoronoiMarkProps extends BaseMarkProps<Datum>, LinkableMarkProps<Datum> {
6
+ /** the input data array */
7
+ data?: Datum[];
8
+ /** the horizontal position channel */
9
+ x?: ChannelAccessor<Datum>;
10
+ /** the vertical position channel */
11
+ y?: ChannelAccessor<Datum>;
12
+ /** the grouping channel; separate Voronoi diagrams per group */
13
+ z?: ChannelAccessor<Datum>;
14
+ /** Render using a canvas element instead of SVG paths. */
15
+ canvas?: boolean;
16
+ }
17
+
18
+ import { Delaunay } from 'd3-delaunay';
19
+ import type {
20
+ DataRecord,
21
+ BaseMarkProps,
22
+ ChannelAccessor,
23
+ LinkableMarkProps,
24
+ MarkType,
25
+ ScaledDataRecord
26
+ } from '../types/index.js';
27
+ import { groupFacetsAndZ } from '../helpers/group.js';
28
+ import { recordizeXY } from '../transforms/recordize.js';
29
+ import { sort } from '../index.js';
30
+ import Mark from '../Mark.svelte';
31
+ import PathItems from './helpers/PathItems.svelte';
32
+ import { usePlot } from '../hooks/usePlot.svelte.js';
33
+ import { getPlotDefaults } from '../hooks/plotDefaults.js';
34
+
35
+ const DEFAULTS = {
36
+ ...getPlotDefaults().voronoi
37
+ };
38
+
39
+ let markProps: VoronoiMarkProps = $props();
40
+
41
+ const {
42
+ data = [] as Datum[],
43
+ class: className = 'voronoi',
44
+ canvas = false,
45
+ ...options
46
+ }: VoronoiMarkProps = $derived({ ...DEFAULTS, ...markProps });
47
+
48
+ const args = $derived(
49
+ sort(
50
+ recordizeXY({
51
+ data: data as any[],
52
+ ...options
53
+ })
54
+ )
55
+ );
56
+
57
+ const plot = usePlot();
58
+
59
+ function computeVoronoi(scaledData: ScaledDataRecord[]) {
60
+ const x0 = plot.options.marginLeft;
61
+ const y0 = plot.options.marginTop;
62
+ const x1 = x0 + plot.facetWidth;
63
+ const y1 = y0 + plot.facetHeight;
64
+ if (!(x1 > x0) || !(y1 > y0)) return [];
65
+
66
+ const scaledByDatum = new Map(scaledData.map((d) => [d.datum, d]));
67
+ const results: { path: string; datum: ScaledDataRecord }[] = [];
68
+
69
+ groupFacetsAndZ(
70
+ scaledData.map((d) => d.datum),
71
+ args,
72
+ (groupItems) => {
73
+ const groupScaled = groupItems
74
+ .map((d) => scaledByDatum.get(d))
75
+ .filter(
76
+ (d): d is ScaledDataRecord =>
77
+ d !== undefined &&
78
+ d.valid &&
79
+ Number.isFinite(d.x as number) &&
80
+ Number.isFinite(d.y as number)
81
+ );
82
+ if (groupScaled.length < 2) return;
83
+
84
+ const delaunay = Delaunay.from(
85
+ groupScaled,
86
+ (d) => d.x as number,
87
+ (d) => d.y as number
88
+ );
89
+ const voronoi = delaunay.voronoi([x0, y0, x1, y1]);
90
+
91
+ groupScaled.forEach((d, cellIndex) => {
92
+ const path = voronoi.renderCell(cellIndex);
93
+ if (path) results.push({ path, datum: d });
94
+ });
95
+ },
96
+ false
97
+ );
98
+
99
+ return results;
100
+ }
101
+ </script>
102
+
103
+ <Mark
104
+ type={'voronoi' as MarkType}
105
+ channels={['x', 'y', 'fill', 'opacity', 'stroke', 'fillOpacity', 'strokeOpacity']}
106
+ defaults={{ fill: 'none', stroke: 'currentColor' }}
107
+ {...args}>
108
+ {#snippet children({ mark, usedScales, scaledData })}
109
+ <PathItems
110
+ paths={computeVoronoi(scaledData)}
111
+ {args}
112
+ {options}
113
+ {className}
114
+ {usedScales}
115
+ {plot}
116
+ {canvas} />
117
+ {/snippet}
118
+ </Mark>
@@ -0,0 +1,172 @@
1
+ import type { DataRecord, ChannelAccessor, LinkableMarkProps } from '../types/index.js';
2
+ declare function $$render<Datum = DataRecord>(): {
3
+ props: Partial<{
4
+ filter: import("../types/index.js").ConstantAccessor<boolean, Datum>;
5
+ facet: "auto" | "include" | "exclude";
6
+ fx: ChannelAccessor<Datum>;
7
+ fy: ChannelAccessor<Datum>;
8
+ dx: import("../types/index.js").ConstantAccessor<number, Datum>;
9
+ dy: import("../types/index.js").ConstantAccessor<number, Datum>;
10
+ dodgeX: import("../transforms/dodge.js").DodgeXOptions;
11
+ dodgeY: import("../transforms/dodge.js").DodgeYOptions;
12
+ fill: ChannelAccessor<Datum>;
13
+ fillOpacity: import("../types/index.js").ConstantAccessor<number, Datum>;
14
+ fontFamily: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontFamily, Datum>;
15
+ fontSize: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontSize<number>, Datum>;
16
+ fontStyle: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontStyle, Datum>;
17
+ fontVariant: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontVariant, Datum>;
18
+ fontWeight: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontWeight, Datum>;
19
+ letterSpacing: import("../types/index.js").ConstantAccessor<import("csstype").Property.LetterSpacing<0 | (string & {})>, Datum>;
20
+ wordSpacing: import("../types/index.js").ConstantAccessor<import("csstype").Property.WordSpacing<0 | (string & {})>, Datum>;
21
+ textAnchor: import("../types/index.js").ConstantAccessor<import("csstype").Property.TextAnchor, Datum>;
22
+ textTransform: import("../types/index.js").ConstantAccessor<import("csstype").Property.TextTransform, Datum>;
23
+ textDecoration: import("../types/index.js").ConstantAccessor<import("csstype").Property.TextDecoration<0 | (string & {})>, Datum>;
24
+ sort: ((a: import("../types/data.js").RawValue, b: import("../types/data.js").RawValue) => number) | {
25
+ channel: string;
26
+ order?: "ascending" | "descending";
27
+ } | import("../types/index.js").ConstantAccessor<import("../types/data.js").RawValue, Datum>;
28
+ stroke: ChannelAccessor<Datum>;
29
+ strokeWidth: import("../types/index.js").ConstantAccessor<number, Datum>;
30
+ strokeOpacity: import("../types/index.js").ConstantAccessor<number, Datum>;
31
+ strokeLinejoin: import("../types/index.js").ConstantAccessor<import("csstype").Property.StrokeLinejoin, Datum>;
32
+ strokeLinecap: import("../types/index.js").ConstantAccessor<import("csstype").Property.StrokeLinecap, Datum>;
33
+ strokeMiterlimit: import("../types/index.js").ConstantAccessor<number, Datum>;
34
+ opacity: ChannelAccessor<Datum>;
35
+ strokeDasharray: import("../types/index.js").ConstantAccessor<string, Datum>;
36
+ strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Datum>;
37
+ blend: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
38
+ mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
39
+ clipPath: string;
40
+ mask: string;
41
+ imageFilter: import("../types/index.js").ConstantAccessor<string, Datum>;
42
+ shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
43
+ paintOrder: import("../types/index.js").ConstantAccessor<string, Datum>;
44
+ onclick: (event: Event & {
45
+ currentTarget: SVGPathElement;
46
+ }, datum: Datum, index: number) => void;
47
+ ondblclick: (event: Event & {
48
+ currentTarget: SVGPathElement;
49
+ }, datum: Datum, index: number) => void;
50
+ onmouseup: (event: Event & {
51
+ currentTarget: SVGPathElement;
52
+ }, datum: Datum, index: number) => void;
53
+ onmousedown: (event: Event & {
54
+ currentTarget: SVGPathElement;
55
+ }, datum: Datum, index: number) => void;
56
+ onmouseenter: (event: Event & {
57
+ currentTarget: SVGPathElement;
58
+ }, datum: Datum, index: number) => void;
59
+ onmousemove: (event: Event & {
60
+ currentTarget: SVGPathElement;
61
+ }, datum: Datum, index: number) => void;
62
+ onmouseleave: (event: Event & {
63
+ currentTarget: SVGPathElement;
64
+ }, datum: Datum, index: number) => void;
65
+ onmouseout: (event: Event & {
66
+ currentTarget: SVGPathElement;
67
+ }, datum: Datum, index: number) => void;
68
+ onmouseover: (event: Event & {
69
+ currentTarget: SVGPathElement;
70
+ }, datum: Datum, index: number) => void;
71
+ onpointercancel: (event: Event & {
72
+ currentTarget: SVGPathElement;
73
+ }, datum: Datum, index: number) => void;
74
+ onpointerdown: (event: Event & {
75
+ currentTarget: SVGPathElement;
76
+ }, datum: Datum, index: number) => void;
77
+ onpointerup: (event: Event & {
78
+ currentTarget: SVGPathElement;
79
+ }, datum: Datum, index: number) => void;
80
+ onpointerenter: (event: Event & {
81
+ currentTarget: SVGPathElement;
82
+ }, datum: Datum, index: number) => void;
83
+ onpointerleave: (event: Event & {
84
+ currentTarget: SVGPathElement;
85
+ }, datum: Datum, index: number) => void;
86
+ onpointermove: (event: Event & {
87
+ currentTarget: SVGPathElement;
88
+ }, datum: Datum, index: number) => void;
89
+ onpointerover: (event: Event & {
90
+ currentTarget: SVGPathElement;
91
+ }, datum: Datum, index: number) => void;
92
+ onpointerout: (event: Event & {
93
+ currentTarget: SVGPathElement;
94
+ }, datum: Datum, index: number) => void;
95
+ ondrag: (event: Event & {
96
+ currentTarget: SVGPathElement;
97
+ }, datum: Datum, index: number) => void;
98
+ ondrop: (event: Event & {
99
+ currentTarget: SVGPathElement;
100
+ }, datum: Datum, index: number) => void;
101
+ ondragstart: (event: Event & {
102
+ currentTarget: SVGPathElement;
103
+ }, datum: Datum, index: number) => void;
104
+ ondragenter: (event: Event & {
105
+ currentTarget: SVGPathElement;
106
+ }, datum: Datum, index: number) => void;
107
+ ondragleave: (event: Event & {
108
+ currentTarget: SVGPathElement;
109
+ }, datum: Datum, index: number) => void;
110
+ ondragover: (event: Event & {
111
+ currentTarget: SVGPathElement;
112
+ }, datum: Datum, index: number) => void;
113
+ ondragend: (event: Event & {
114
+ currentTarget: SVGPathElement;
115
+ }, datum: Datum, index: number) => void;
116
+ ontouchstart: (event: Event & {
117
+ currentTarget: SVGPathElement;
118
+ }, datum: Datum, index: number) => void;
119
+ ontouchmove: (event: Event & {
120
+ currentTarget: SVGPathElement;
121
+ }, datum: Datum, index: number) => void;
122
+ ontouchend: (event: Event & {
123
+ currentTarget: SVGPathElement;
124
+ }, datum: Datum, index: number) => void;
125
+ ontouchcancel: (event: Event & {
126
+ currentTarget: SVGPathElement;
127
+ }, datum: Datum, index: number) => void;
128
+ oncontextmenu: (event: Event & {
129
+ currentTarget: SVGPathElement;
130
+ }, datum: Datum, index: number) => void;
131
+ onwheel: (event: Event & {
132
+ currentTarget: SVGPathElement;
133
+ }, datum: Datum, index: number) => void;
134
+ class: string;
135
+ style: string;
136
+ cursor: import("../types/index.js").ConstantAccessor<import("csstype").Property.Cursor, Datum>;
137
+ title: import("../types/index.js").ConstantAccessor<string, Datum>;
138
+ }> & LinkableMarkProps<Datum> & {
139
+ /** the input data array */
140
+ data?: Datum[];
141
+ /** the horizontal position channel */
142
+ x?: ChannelAccessor<Datum>;
143
+ /** the vertical position channel */
144
+ y?: ChannelAccessor<Datum>;
145
+ /** the grouping channel; separate Voronoi diagrams per group */
146
+ z?: ChannelAccessor<Datum>;
147
+ /** Render using a canvas element instead of SVG paths. */
148
+ canvas?: boolean;
149
+ };
150
+ exports: {};
151
+ bindings: "";
152
+ slots: {};
153
+ events: {};
154
+ };
155
+ declare class __sveltets_Render<Datum = DataRecord> {
156
+ props(): ReturnType<typeof $$render<Datum>>['props'];
157
+ events(): ReturnType<typeof $$render<Datum>>['events'];
158
+ slots(): ReturnType<typeof $$render<Datum>>['slots'];
159
+ bindings(): "";
160
+ exports(): {};
161
+ }
162
+ interface $$IsomorphicComponent {
163
+ new <Datum = 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']>> & {
164
+ $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
165
+ } & ReturnType<__sveltets_Render<Datum>['exports']>;
166
+ <Datum = DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
167
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
168
+ }
169
+ /** Renders individual Voronoi cells, one per data point, allowing per-cell styling. */
170
+ declare const Voronoi: $$IsomorphicComponent;
171
+ type Voronoi<Datum = DataRecord> = InstanceType<typeof Voronoi<Datum>>;
172
+ export default Voronoi;
@@ -0,0 +1,109 @@
1
+ <!-- @component
2
+ Renders the full Voronoi diagram as a single SVG path.
3
+ -->
4
+ <script lang="ts" generics="Datum = DataRecord">
5
+ interface VoronoiMeshMarkProps extends BaseMarkProps<Datum> {
6
+ /** the input data array */
7
+ data?: Datum[];
8
+ /** the horizontal position channel */
9
+ x?: ChannelAccessor<Datum>;
10
+ /** the vertical position channel */
11
+ y?: ChannelAccessor<Datum>;
12
+ /** the grouping channel; separate diagrams per group */
13
+ z?: ChannelAccessor<Datum>;
14
+ /** Render using a canvas element instead of SVG paths. */
15
+ canvas?: boolean;
16
+ }
17
+
18
+ import { Delaunay } from 'd3-delaunay';
19
+ import type {
20
+ DataRecord,
21
+ BaseMarkProps,
22
+ ChannelAccessor,
23
+ MarkType,
24
+ ScaledDataRecord
25
+ } from '../types/index.js';
26
+ import { groupFacetsAndZ } from '../helpers/group.js';
27
+ import { recordizeXY } from '../transforms/recordize.js';
28
+ import Mark from '../Mark.svelte';
29
+ import PathGroup from './helpers/PathGroup.svelte';
30
+ import { usePlot } from '../hooks/usePlot.svelte.js';
31
+ import { getPlotDefaults } from '../hooks/plotDefaults.js';
32
+
33
+ const DEFAULTS = {
34
+ ...getPlotDefaults().voronoiMesh
35
+ };
36
+
37
+ let markProps: VoronoiMeshMarkProps = $props();
38
+
39
+ const {
40
+ data = [] as Datum[],
41
+ class: className = 'voronoi-mesh',
42
+ canvas = false,
43
+ ...options
44
+ }: VoronoiMeshMarkProps = $derived({ ...DEFAULTS, ...markProps });
45
+
46
+ const args = $derived(
47
+ recordizeXY({
48
+ data: data as any[],
49
+ ...options
50
+ })
51
+ );
52
+
53
+ const plot = usePlot();
54
+
55
+ function computeMeshPaths(scaledData: ScaledDataRecord[]) {
56
+ const x0 = plot.options.marginLeft;
57
+ const y0 = plot.options.marginTop;
58
+ const x1 = x0 + plot.facetWidth;
59
+ const y1 = y0 + plot.facetHeight;
60
+ if (!(x1 > x0) || !(y1 > y0)) return [];
61
+
62
+ const scaledByDatum = new Map(scaledData.map((d) => [d.datum, d]));
63
+ const meshes: { path: string; datum: ScaledDataRecord }[] = [];
64
+
65
+ groupFacetsAndZ(
66
+ scaledData.map((d) => d.datum),
67
+ args,
68
+ (groupItems) => {
69
+ const groupScaled = groupItems
70
+ .map((d) => scaledByDatum.get(d))
71
+ .filter(
72
+ (d): d is ScaledDataRecord =>
73
+ d !== undefined &&
74
+ d.valid &&
75
+ Number.isFinite(d.x as number) &&
76
+ Number.isFinite(d.y as number)
77
+ );
78
+ if (groupScaled.length < 2) return;
79
+ const delaunay = Delaunay.from(
80
+ groupScaled,
81
+ (d) => d.x as number,
82
+ (d) => d.y as number
83
+ );
84
+ const voronoi = delaunay.voronoi([x0, y0, x1, y1]);
85
+ const path = voronoi.render();
86
+ if (path) meshes.push({ path, datum: groupScaled[0] });
87
+ },
88
+ false
89
+ );
90
+
91
+ return meshes;
92
+ }
93
+ </script>
94
+
95
+ <Mark
96
+ type={'voronoiMesh' as MarkType}
97
+ channels={['x', 'y', 'fill', 'stroke', 'strokeOpacity', 'fillOpacity', 'opacity']}
98
+ defaults={{ fill: 'none', stroke: 'currentColor' }}
99
+ {...args}>
100
+ {#snippet children({ scaledData, usedScales })}
101
+ <PathGroup
102
+ paths={computeMeshPaths(scaledData)}
103
+ {args}
104
+ {className}
105
+ {usedScales}
106
+ {plot}
107
+ {canvas} />
108
+ {/snippet}
109
+ </Mark>