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
@@ -0,0 +1,87 @@
1
+ import type { DataRecord, ChannelAccessor } from '../types/index.js';
2
+ declare function $$render<Datum extends DataRecord>(): {
3
+ props: {
4
+ /** Input data — an array of records with x/y positions. */
5
+ data?: Datum[] | null;
6
+ /** x position channel (data space). */
7
+ x?: ChannelAccessor<Datum>;
8
+ /** y position channel (data space). */
9
+ y?: ChannelAccessor<Datum>;
10
+ /** Optional weight channel; defaults to 1 for each point. */
11
+ weight?: ChannelAccessor<Datum>;
12
+ /**
13
+ * Gaussian kernel bandwidth in screen pixels (default 20).
14
+ * Larger values produce smoother, more blurred density estimates.
15
+ */
16
+ bandwidth?: number;
17
+ /**
18
+ * Density threshold levels. Can be:
19
+ * - a **count** (number): that many evenly-spaced levels from 0 to the
20
+ * maximum density (default 20)
21
+ * - an explicit **array** of threshold values in k-scaled density units
22
+ * (where k = 100; values from 0 to roughly 100× the peak density)
23
+ */
24
+ thresholds?: number | number[];
25
+ /**
26
+ * Fill color for density bands. Use `"density"` to map each band's
27
+ * estimated density through the plot's color scale. Default `"none"`.
28
+ */
29
+ fill?: string;
30
+ /**
31
+ * Stroke color for density isolines. Use `"density"` to map each
32
+ * isoline's estimated density through the plot's color scale.
33
+ * Default `"currentColor"` when fill is `"none"`, otherwise `"none"`.
34
+ */
35
+ stroke?: string;
36
+ strokeWidth?: number;
37
+ strokeOpacity?: number;
38
+ fillOpacity?: number;
39
+ opacity?: number;
40
+ strokeMiterlimit?: number;
41
+ clipPath?: string;
42
+ class?: string;
43
+ /** Render using a canvas element instead of SVG paths. */
44
+ canvas?: boolean;
45
+ /** the horizontal facet channel */
46
+ fx?: ChannelAccessor<Datum>;
47
+ /** the vertical facet channel */
48
+ fy?: ChannelAccessor<Datum>;
49
+ };
50
+ exports: {};
51
+ bindings: "";
52
+ slots: {};
53
+ events: {};
54
+ };
55
+ declare class __sveltets_Render<Datum extends DataRecord> {
56
+ props(): ReturnType<typeof $$render<Datum>>['props'];
57
+ events(): ReturnType<typeof $$render<Datum>>['events'];
58
+ slots(): ReturnType<typeof $$render<Datum>>['slots'];
59
+ bindings(): "";
60
+ exports(): {};
61
+ }
62
+ interface $$IsomorphicComponent {
63
+ 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']>> & {
64
+ $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
65
+ } & ReturnType<__sveltets_Render<Datum>['exports']>;
66
+ <Datum extends DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
67
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
68
+ }
69
+ /**
70
+ * Renders two-dimensional kernel density estimation as filled or stroked
71
+ * contour paths.
72
+ *
73
+ * Data points with `x` and `y` channels are projected into pixel space and
74
+ * passed to d3's `contourDensity` estimator, which uses a Gaussian kernel to
75
+ * produce a density grid. Iso-density contour bands are then drawn using the
76
+ * marching-squares algorithm.
77
+ *
78
+ * Styling: `fill` and `stroke` accept ordinary CSS color strings **or** the
79
+ * special keyword `"density"`, which maps each contour level's estimated
80
+ * density through the plot's color scale. Defaults: `fill="none"`,
81
+ * `stroke="currentColor"`.
82
+ *
83
+ * Supports faceting via `fx`/`fy`.
84
+ */
85
+ declare const Density: $$IsomorphicComponent;
86
+ type Density<Datum extends DataRecord> = InstanceType<typeof Density<Datum>>;
87
+ export default Density;
@@ -0,0 +1,103 @@
1
+ <!-- @component
2
+ Renders the convex hull of data points. Supports grouping by z/fill/stroke
3
+ to draw separate hulls per group.
4
+ -->
5
+ <script lang="ts" generics="Datum = DataRecord">
6
+ interface HullMarkProps extends BaseMarkProps<Datum> {
7
+ /** the input data array */
8
+ data?: Datum[];
9
+ /** the horizontal position channel */
10
+ x?: ChannelAccessor<Datum>;
11
+ /** the vertical position channel */
12
+ y?: ChannelAccessor<Datum>;
13
+ /** the grouping channel; separate hulls per group */
14
+ z?: ChannelAccessor<Datum>;
15
+ /** Render using a canvas element instead of SVG paths. */
16
+ canvas?: boolean;
17
+ }
18
+
19
+ import { Delaunay } from 'd3-delaunay';
20
+ import type {
21
+ DataRecord,
22
+ BaseMarkProps,
23
+ ChannelAccessor,
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 Mark from '../Mark.svelte';
30
+ import PathGroup from './helpers/PathGroup.svelte';
31
+ import { usePlot } from '../hooks/usePlot.svelte.js';
32
+ import { getPlotDefaults } from '../hooks/plotDefaults.js';
33
+
34
+ const DEFAULTS = {
35
+ ...getPlotDefaults().hull
36
+ };
37
+
38
+ let markProps: HullMarkProps = $props();
39
+
40
+ const {
41
+ data = [] as Datum[],
42
+ class: className = 'hull',
43
+ canvas = false,
44
+ ...options
45
+ }: HullMarkProps = $derived({ ...DEFAULTS, ...markProps });
46
+
47
+ const args = $derived(
48
+ recordizeXY({
49
+ data: data as any[],
50
+ ...options
51
+ })
52
+ );
53
+
54
+ const plot = usePlot();
55
+
56
+ function computeHulls(scaledData: ScaledDataRecord[]) {
57
+ const scaledByDatum = new Map(scaledData.map((d) => [d.datum, d]));
58
+ const hulls: { path: string; datum: ScaledDataRecord }[] = [];
59
+
60
+ groupFacetsAndZ(
61
+ scaledData.map((d) => d.datum),
62
+ args,
63
+ (groupItems) => {
64
+ const groupScaled = groupItems
65
+ .map((d) => scaledByDatum.get(d))
66
+ .filter(
67
+ (d): d is ScaledDataRecord =>
68
+ d !== undefined &&
69
+ d.valid &&
70
+ Number.isFinite(d.x as number) &&
71
+ Number.isFinite(d.y as number)
72
+ );
73
+ if (groupScaled.length < 2) return;
74
+ const delaunay = Delaunay.from(
75
+ groupScaled,
76
+ (d) => d.x as number,
77
+ (d) => d.y as number
78
+ );
79
+ const path = delaunay.renderHull();
80
+ if (path) hulls.push({ path, datum: groupScaled[0] });
81
+ }
82
+ );
83
+
84
+ return hulls;
85
+ }
86
+ </script>
87
+
88
+ <Mark
89
+ type={'hull' as MarkType}
90
+ channels={['x', 'y', 'fill', 'opacity', 'stroke', 'fillOpacity', 'strokeOpacity']}
91
+ defaults={{ fill: 'none', stroke: 'currentColor' }}
92
+ {...args}>
93
+ {#snippet children({ scaledData, usedScales })}
94
+ <PathGroup
95
+ paths={computeHulls(scaledData)}
96
+ {args}
97
+ {className}
98
+ {usedScales}
99
+ {plot}
100
+ defaultStrokeWidth={1.5}
101
+ {canvas} />
102
+ {/snippet}
103
+ </Mark>
@@ -0,0 +1,175 @@
1
+ import type { DataRecord, ChannelAccessor } 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
+ }> & {
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 hulls 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
+ /**
170
+ * Renders the convex hull of data points. Supports grouping by z/fill/stroke
171
+ * to draw separate hulls per group.
172
+ */
173
+ declare const Hull: $$IsomorphicComponent;
174
+ type Hull<Datum = DataRecord> = InstanceType<typeof Hull<Datum>>;
175
+ export default Hull;
@@ -22,7 +22,8 @@
22
22
  title?: ConstantAccessor<string, Datum>;
23
23
  /** the SVG preserveAspectRatio attribute for the image (e.g. "xMidYMid meet") */
24
24
  preserveAspectRatio?: string;
25
- // canvas?: boolean;
25
+ /** if true, renders using Canvas instead of SVG */
26
+ canvas?: boolean;
26
27
  /** CSS class name(s) to apply to individual image elements */
27
28
  imageClass?: ConstantAccessor<string, Datum>;
28
29
  }
@@ -39,6 +40,7 @@
39
40
  import { getPlotDefaults } from '../hooks/plotDefaults.js';
40
41
  import { sort } from '../transforms/index.js';
41
42
  import Anchor from './helpers/Anchor.svelte';
43
+ import ImageCanvas from './helpers/ImageCanvas.svelte';
42
44
  import Mark from '../Mark.svelte';
43
45
 
44
46
  let markProps: ImageMarkProps = $props();
@@ -51,6 +53,7 @@
51
53
 
52
54
  const {
53
55
  data = [{} as Datum],
56
+ canvas = false,
54
57
  width,
55
58
  height,
56
59
  src,
@@ -71,31 +74,38 @@
71
74
  channels={['x', 'y', 'r', 'fill', 'opacity', 'stroke', 'fillOpacity', 'strokeOpacity']}
72
75
  {...args}
73
76
  type="image">
74
- {#snippet children({ scaledData })}
75
- {#each scaledData as record, i (i)}
76
- {#if record.valid}
77
- {@const w =
78
- record.r !== undefined
79
- ? record.r * 2
80
- : Number(resolveProp(width, record.datum, 20) ?? 20)}
81
- {@const h =
82
- record.r !== undefined
83
- ? record.r * 2
84
- : Number(resolveProp(height || width, record.datum, 20) ?? 20)}
85
- <Anchor {options} datum={record.datum}>
86
- <image
87
- class={resolveProp(imageClass, record.datum, null)}
88
- href={resolveProp(src, record.datum, '')}
89
- x={record.x! - w * 0.5}
90
- y={record.y! - h * 0.5}
91
- {preserveAspectRatio}
92
- clip-path={record.r !== undefined ? `circle(${record.r}px)` : null}
93
- width={w}
94
- height={h}
95
- >{#if title}<title>{resolveProp(title, record.datum, '')}</title
96
- >{/if}</image>
97
- </Anchor>
98
- {/if}
99
- {/each}
77
+ {#snippet children({ scaledData, usedScales })}
78
+ {#if canvas}
79
+ <ImageCanvas
80
+ data={scaledData}
81
+ options={{ ...options, src, width, height }}
82
+ {usedScales} />
83
+ {:else}
84
+ {#each scaledData as record, i (i)}
85
+ {#if record.valid}
86
+ {@const w =
87
+ record.r !== undefined
88
+ ? record.r * 2
89
+ : Number(resolveProp(width, record.datum, 20) ?? 20)}
90
+ {@const h =
91
+ record.r !== undefined
92
+ ? record.r * 2
93
+ : Number(resolveProp(height || width, record.datum, 20) ?? 20)}
94
+ <Anchor {options} datum={record.datum}>
95
+ <image
96
+ class={resolveProp(imageClass, record.datum, null)}
97
+ href={resolveProp(src, record.datum, '')}
98
+ x={record.x! - w * 0.5}
99
+ y={record.y! - h * 0.5}
100
+ {preserveAspectRatio}
101
+ clip-path={record.r !== undefined ? `circle(${record.r}px)` : null}
102
+ width={w}
103
+ height={h}
104
+ >{#if title}<title>{resolveProp(title, record.datum, '')}</title
105
+ >{/if}</image>
106
+ </Anchor>
107
+ {/if}
108
+ {/each}
109
+ {/if}
100
110
  {/snippet}
101
111
  </Mark>
@@ -154,6 +154,8 @@ declare function $$render<Datum extends DataRecord>(): {
154
154
  title?: ConstantAccessor<string, Datum>;
155
155
  /** the SVG preserveAspectRatio attribute for the image (e.g. "xMidYMid meet") */
156
156
  preserveAspectRatio?: string;
157
+ /** if true, renders using Canvas instead of SVG */
158
+ canvas?: boolean;
157
159
  /** CSS class name(s) to apply to individual image elements */
158
160
  imageClass?: ConstantAccessor<string, Datum>;
159
161
  };
@@ -45,6 +45,8 @@
45
45
  textStartOffset?: ConstantAccessor<string, Datum>;
46
46
  /** the stroke width for the text label rendered along the link */
47
47
  textStrokeWidth?: ConstantAccessor<number, Datum>;
48
+ /** if true, renders using Canvas instead of SVG */
49
+ canvas?: boolean;
48
50
  }
49
51
  import type {
50
52
  DataRecord,
@@ -65,6 +67,7 @@
65
67
  import { maybeCurve } from '../helpers/curves.js';
66
68
  import { geoPath } from 'd3-geo';
67
69
  import { pick } from 'es-toolkit';
70
+ import LinkCanvas from './helpers/LinkCanvas.svelte';
68
71
  import { sort } from '../transforms/sort.js';
69
72
  import { indexData } from '../transforms/recordize.js';
70
73
  import { getPlotDefaults } from '../hooks/plotDefaults.js';
@@ -81,6 +84,7 @@
81
84
  tension = 0,
82
85
  bend,
83
86
  text,
87
+ canvas,
84
88
  class: className = '',
85
89
  ...options
86
90
  }: LinkMarkProps = $derived({
@@ -103,6 +107,13 @@
103
107
 
104
108
  const sphericalLine = $derived(plot.scales.projection && curve === 'auto');
105
109
 
110
+ const curveFactory = $derived(
111
+ maybeCurve(
112
+ curve === 'auto' ? 'linear' : curve,
113
+ bend === true ? 0.6 : bend === false ? 0 : (bend ?? tension ?? 0)
114
+ )
115
+ );
116
+
106
117
  const linePath: (d: ScaledDataRecord, reversed?: boolean) => string | null = $derived.by(() => {
107
118
  const fn: D3Line<[number, number]> = line<[number, number]>()
108
119
  .curve(
@@ -168,56 +179,63 @@
168
179
  channels={['x1', 'y1', 'x2', 'y2', 'opacity', 'stroke', 'strokeOpacity']}
169
180
  {...args}>
170
181
  {#snippet children({ mark, scaledData, usedScales })}
171
- <g class={['link', className]} data-use-x={usedScales.x ? 1 : 0}>
172
- {#each scaledData as d, i (i)}
173
- {#if d.valid || true}
174
- {@const [style, styleClass] = resolveStyles(
175
- plot,
176
- d,
177
- { strokeWidth: 1.6, ...args },
178
- 'stroke',
179
- usedScales
180
- )}
181
- {@const [textStyle, textStyleClass] = resolveStyles(
182
- plot,
183
- d,
184
- {
185
- textAnchor: 'middle',
186
- ...pick(args, ['fontSize', 'fontWeight', 'fontStyle']),
187
- fill: options.textFill || args.stroke,
188
- stroke: options.textStroke,
189
- strokeWidth: options.textStrokeWidth
190
- },
191
- 'fill',
192
- usedScales
193
- )}
182
+ {#if canvas}
183
+ <LinkCanvas data={scaledData} options={args} {usedScales} {curveFactory} />
184
+ {:else}
185
+ <g class={['link', className]} data-use-x={usedScales.x ? 1 : 0}>
186
+ {#each scaledData as d, i (i)}
187
+ {#if d.valid || true}
188
+ {@const [style, styleClass] = resolveStyles(
189
+ plot,
190
+ d,
191
+ { strokeWidth: 1.6, ...args },
192
+ 'stroke',
193
+ usedScales
194
+ )}
195
+ {@const [textStyle, textStyleClass] = resolveStyles(
196
+ plot,
197
+ d,
198
+ {
199
+ textAnchor: 'middle',
200
+ ...pick(args, ['fontSize', 'fontWeight', 'fontStyle']),
201
+ fill: options.textFill || args.stroke,
202
+ stroke: options.textStroke,
203
+ strokeWidth: options.textStrokeWidth
204
+ },
205
+ 'fill',
206
+ usedScales
207
+ )}
194
208
 
195
- <MarkerPath
196
- mark={{ ...mark, options: args }}
197
- transform=""
198
- scales={plot.scales}
199
- markerStart={options.markerStart as boolean | MarkerShape | undefined}
200
- markerEnd={options.markerEnd as boolean | MarkerShape | undefined}
201
- marker={options.marker as boolean | MarkerShape | undefined}
202
- markerScale={options.markerScale}
203
- class={styleClass ?? undefined}
204
- strokeWidth={options.strokeWidth as ConstantAccessor<number>}
205
- datum={d.datum as DataRecord}
206
- color={d.stroke ?? 'currentColor'}
207
- d={(sphericalLine ? sphericalLinePath(d) : linePath(d)) ?? ''}
208
- dInv={(sphericalLine ? sphericalLinePath(d, true) : linePath(d, true)) ??
209
- undefined}
210
- style={style ?? ''}
211
- text={text ? ((resolveProp(text, d.datum as Datum) as string) ?? '') : ''}
212
- startOffset={(resolveProp(
213
- options.textStartOffset,
214
- d.datum as Datum,
215
- '50%'
216
- ) as string) ?? '50%'}
217
- textStyle={textStyle ?? ''}
218
- {textStyleClass} />
219
- {/if}
220
- {/each}
221
- </g>
209
+ <MarkerPath
210
+ mark={{ ...mark, options: args }}
211
+ transform=""
212
+ scales={plot.scales}
213
+ markerStart={options.markerStart as boolean | MarkerShape | undefined}
214
+ markerEnd={options.markerEnd as boolean | MarkerShape | undefined}
215
+ marker={options.marker as boolean | MarkerShape | undefined}
216
+ markerScale={options.markerScale}
217
+ class={styleClass ?? undefined}
218
+ strokeWidth={options.strokeWidth as ConstantAccessor<number>}
219
+ datum={d.datum as DataRecord}
220
+ color={d.stroke ?? 'currentColor'}
221
+ d={(sphericalLine ? sphericalLinePath(d) : linePath(d)) ?? ''}
222
+ dInv={(sphericalLine
223
+ ? sphericalLinePath(d, true)
224
+ : linePath(d, true)) ?? undefined}
225
+ style={style ?? ''}
226
+ text={text
227
+ ? ((resolveProp(text, d.datum as Datum) as string) ?? '')
228
+ : ''}
229
+ startOffset={(resolveProp(
230
+ options.textStartOffset,
231
+ d.datum as Datum,
232
+ '50%'
233
+ ) as string) ?? '50%'}
234
+ textStyle={textStyle ?? ''}
235
+ {textStyleClass} />
236
+ {/if}
237
+ {/each}
238
+ </g>
239
+ {/if}
222
240
  {/snippet}
223
241
  </Mark>
@@ -180,6 +180,8 @@ declare function $$render<Datum = DataRecord | GeoJSON.GeoJsonObject>(): {
180
180
  textStartOffset?: ConstantAccessor<string, Datum>;
181
181
  /** the stroke width for the text label rendered along the link */
182
182
  textStrokeWidth?: ConstantAccessor<number, Datum>;
183
+ /** if true, renders using Canvas instead of SVG */
184
+ canvas?: boolean;
183
185
  };
184
186
  exports: {};
185
187
  bindings: "";
@@ -90,6 +90,11 @@
90
90
  } from '../helpers/rasterInterpolate.js';
91
91
  import { X, Y, RAW_VALUE } from '../transforms/recordize.js';
92
92
  import { scaleLinear } from 'd3-scale';
93
+ import { getPlotDefaults } from '../hooks/plotDefaults.js';
94
+
95
+ const DEFAULTS = {
96
+ ...getPlotDefaults().raster
97
+ };
93
98
 
94
99
  let markProps: RasterMarkProps = $props();
95
100
 
@@ -108,7 +113,7 @@
108
113
  interpolate,
109
114
  imageRendering = 'auto',
110
115
  ...options
111
- }: RasterMarkProps = $derived({ ...markProps });
116
+ }: RasterMarkProps = $derived({ ...DEFAULTS, ...markProps });
112
117
 
113
118
  const plot = usePlot();
114
119