svelteplot 0.12.0 → 0.13.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.
@@ -0,0 +1,132 @@
1
+ <!--
2
+ @component
3
+ Helper component for rendering Arrow marks in canvas
4
+ -->
5
+ <script lang="ts" generics="Datum extends DataRecord">
6
+ interface ArrowCanvasProps {
7
+ data: ScaledDataRecord<Datum>[];
8
+ options: BaseMarkProps<Datum> & {
9
+ headAngle?: ConstantAccessor<number, Datum>;
10
+ headLength?: ConstantAccessor<number, Datum>;
11
+ bend?: ConstantAccessor<number, Datum> | true;
12
+ inset?: ConstantAccessor<number, Datum>;
13
+ insetStart?: ConstantAccessor<number, Datum>;
14
+ insetEnd?: ConstantAccessor<number, Datum>;
15
+ sweep?: SweepOption;
16
+ };
17
+ usedScales: UsedScales;
18
+ }
19
+
20
+ import type {
21
+ BaseMarkProps,
22
+ ConstantAccessor,
23
+ DataRecord,
24
+ ScaledDataRecord,
25
+ UsedScales
26
+ } from '../../types/index.js';
27
+ import { resolveProp, resolveScaledStyleProps } from '../../helpers/resolve.js';
28
+ import { coalesce, maybeNumber } from '../../helpers/index.js';
29
+ import {
30
+ arrowPath,
31
+ maybeSweep,
32
+ type SweepFunc,
33
+ type SweepOption
34
+ } from '../../helpers/arrowPath.js';
35
+ import type { Attachment } from 'svelte/attachments';
36
+ import { devicePixelRatio } from 'svelte/reactivity/window';
37
+ import CanvasLayer from './CanvasLayer.svelte';
38
+ import { resolveColor } from './canvas.js';
39
+ import { usePlot } from '../../hooks/usePlot.svelte.js';
40
+
41
+ const plot = usePlot();
42
+
43
+ let { data, options, usedScales }: ArrowCanvasProps = $props();
44
+
45
+ const render: Attachment = (canvasEl: Element) => {
46
+ const canvas = canvasEl as HTMLCanvasElement;
47
+ const context = canvas.getContext('2d');
48
+
49
+ $effect(() => {
50
+ if (context) {
51
+ context.resetTransform();
52
+ context.scale(devicePixelRatio.current ?? 1, devicePixelRatio.current ?? 1);
53
+
54
+ const sweep = maybeSweep(options.sweep) as SweepFunc;
55
+
56
+ for (const d of data) {
57
+ if (!d.valid) continue;
58
+
59
+ const datum = d.datum as unknown as Datum;
60
+
61
+ const styleProps = resolveScaledStyleProps(
62
+ d.datum,
63
+ options,
64
+ usedScales,
65
+ plot,
66
+ 'stroke'
67
+ ) as Record<string, unknown>;
68
+
69
+ const opacity = +(styleProps['opacity'] ?? 1);
70
+ const strokeOpacity = +(styleProps['stroke-opacity'] ?? 1);
71
+ const stroke = resolveColor(
72
+ String(styleProps.stroke || 'currentColor'),
73
+ canvas
74
+ );
75
+
76
+ if (!stroke || stroke === 'none') continue;
77
+
78
+ const strokeWidth = (resolveProp(options.strokeWidth, datum, 1) ?? 1) as number;
79
+ const inset = resolveProp(options.inset, datum, 0);
80
+ const insetStart = resolveProp(options.insetStart, datum);
81
+ const insetEnd = resolveProp(options.insetEnd, datum);
82
+ const headAngle = (resolveProp(options.headAngle, datum, 60) ?? 60) as number;
83
+ const headLength = (resolveProp(options.headLength, datum, 8) ?? 8) as number;
84
+ const bendVal =
85
+ options.bend === true
86
+ ? 22.5
87
+ : ((resolveProp(
88
+ options.bend as ConstantAccessor<number, Datum>,
89
+ datum,
90
+ 0
91
+ ) ?? 0) as number);
92
+
93
+ const pathStr = arrowPath(
94
+ d.x1 ?? 0,
95
+ d.y1 ?? 0,
96
+ d.x2 ?? 0,
97
+ d.y2 ?? 0,
98
+ maybeNumber(coalesce(insetStart, inset)) ?? 0,
99
+ maybeNumber(coalesce(insetEnd, inset)) ?? 0,
100
+ headAngle,
101
+ headLength,
102
+ bendVal,
103
+ strokeWidth,
104
+ sweep
105
+ );
106
+
107
+ if (!pathStr) continue;
108
+
109
+ const path = new Path2D(pathStr);
110
+
111
+ context.lineWidth = strokeWidth;
112
+ context.lineCap = 'round';
113
+ context.lineJoin = 'round';
114
+ context.strokeStyle = stroke;
115
+ context.globalAlpha = opacity * strokeOpacity;
116
+ context.stroke(path);
117
+ }
118
+ }
119
+
120
+ return () => {
121
+ context?.clearRect(
122
+ 0,
123
+ 0,
124
+ plot.width * (devicePixelRatio.current ?? 1),
125
+ plot.height * (devicePixelRatio.current ?? 1)
126
+ );
127
+ };
128
+ });
129
+ };
130
+ </script>
131
+
132
+ <CanvasLayer {@attach render} />
@@ -0,0 +1,39 @@
1
+ import type { BaseMarkProps, ConstantAccessor, DataRecord, ScaledDataRecord, UsedScales } from '../../types/index.js';
2
+ import { type SweepOption } from '../../helpers/arrowPath.js';
3
+ declare function $$render<Datum extends DataRecord>(): {
4
+ props: {
5
+ data: ScaledDataRecord<Datum>[];
6
+ options: BaseMarkProps<Datum> & {
7
+ headAngle?: ConstantAccessor<number, Datum>;
8
+ headLength?: ConstantAccessor<number, Datum>;
9
+ bend?: ConstantAccessor<number, Datum> | true;
10
+ inset?: ConstantAccessor<number, Datum>;
11
+ insetStart?: ConstantAccessor<number, Datum>;
12
+ insetEnd?: ConstantAccessor<number, Datum>;
13
+ sweep?: SweepOption;
14
+ };
15
+ usedScales: UsedScales;
16
+ };
17
+ exports: {};
18
+ bindings: "";
19
+ slots: {};
20
+ events: {};
21
+ };
22
+ declare class __sveltets_Render<Datum extends DataRecord> {
23
+ props(): ReturnType<typeof $$render<Datum>>['props'];
24
+ events(): ReturnType<typeof $$render<Datum>>['events'];
25
+ slots(): ReturnType<typeof $$render<Datum>>['slots'];
26
+ bindings(): "";
27
+ exports(): {};
28
+ }
29
+ interface $$IsomorphicComponent {
30
+ 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']>> & {
31
+ $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
32
+ } & ReturnType<__sveltets_Render<Datum>['exports']>;
33
+ <Datum extends DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
34
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
35
+ }
36
+ /** Helper component for rendering Arrow marks in canvas */
37
+ declare const ArrowCanvas: $$IsomorphicComponent;
38
+ type ArrowCanvas<Datum extends DataRecord> = InstanceType<typeof ArrowCanvas<Datum>>;
39
+ export default ArrowCanvas;
@@ -206,6 +206,8 @@
206
206
  {@const moveDown =
207
207
  (tickSize + tickPadding + (tickRotate !== 0 ? tickFontSize_ * 0.35 : 0)) *
208
208
  (anchor === 'bottom' ? 1 : -1)}
209
+ {@const tickBaseline =
210
+ tickRotate !== 0 ? 'central' : anchor === 'bottom' ? 'hanging' : 'auto'}
209
211
  {@const [textStyle, textClass] = resolveStyles(
210
212
  plot,
211
213
  toScaledDatum(tick),
@@ -230,22 +232,18 @@
230
232
  )}
231
233
  <text
232
234
  bind:this={tickTextElements[t]}
233
- transform="translate(0, {moveDown}) rotate({tickRotate})"
235
+ transform="translate(0, {moveDown}) rotate({tickRotate})"
234
236
  style={textStyle}
235
237
  class={textClass}
236
238
  x={0}
237
239
  y={0}
238
- dominant-baseline={tickRotate !== 0
239
- ? 'central'
240
- : anchor === 'bottom'
241
- ? 'hanging'
242
- : 'auto'}>
240
+ dominant-baseline={tickBaseline}>
243
241
  {#if ticks.length > 0 || t === 0 || t === ticks.length - 1}
244
242
  {#if textLines.length === 1}
245
243
  {textLines[0]}
246
244
  {:else}
247
245
  {#each textLines as line, i (i)}
248
- <tspan x="0" dy={i ? 12 : 0}
246
+ <tspan x="0" dy={i ? 12 : 0} dominant-baseline={tickBaseline}
249
247
  >{!prevTextLines ||
250
248
  prevTextLines[i] !== line ||
251
249
  options.removeDuplicateTicks === false
@@ -0,0 +1,126 @@
1
+ <!--
2
+ @component
3
+ Helper component for rendering Image marks in canvas
4
+ -->
5
+ <script lang="ts" generics="Datum extends DataRecord">
6
+ interface ImageCanvasProps {
7
+ data: ScaledDataRecord<Datum>[];
8
+ options: BaseMarkProps<Datum> & {
9
+ src?: ConstantAccessor<string, Datum>;
10
+ width?: ConstantAccessor<number, Datum>;
11
+ height?: ConstantAccessor<number, Datum>;
12
+ };
13
+ usedScales: UsedScales;
14
+ }
15
+
16
+ import type {
17
+ BaseMarkProps,
18
+ ConstantAccessor,
19
+ DataRecord,
20
+ ScaledDataRecord,
21
+ UsedScales
22
+ } from '../../types/index.js';
23
+ import { resolveProp } from '../../helpers/resolve.js';
24
+ import type { Attachment } from 'svelte/attachments';
25
+ import { SvelteMap } from 'svelte/reactivity';
26
+ import { devicePixelRatio } from 'svelte/reactivity/window';
27
+ import CanvasLayer from './CanvasLayer.svelte';
28
+ import { usePlot } from '../../hooks/usePlot.svelte.js';
29
+
30
+ const plot = usePlot();
31
+
32
+ let { data, options, usedScales }: ImageCanvasProps = $props();
33
+
34
+ // Cache loaded images by URL to avoid redundant fetches
35
+ const imageCache = new SvelteMap<string, HTMLImageElement>();
36
+
37
+ // Reactive counter: incremented when an image finishes loading,
38
+ // which triggers the $effect to re-run and repaint the canvas.
39
+ let loadedCount = $state(0);
40
+
41
+ function getImage(url: string): HTMLImageElement | undefined {
42
+ if (imageCache.has(url)) {
43
+ const img = imageCache.get(url)!;
44
+ return img.complete && img.naturalWidth > 0 ? img : undefined;
45
+ }
46
+ const img = new Image();
47
+ img.crossOrigin = 'anonymous';
48
+ imageCache.set(url, img);
49
+ img.onload = () => {
50
+ loadedCount++;
51
+ };
52
+ img.onerror = () => {
53
+ // Remove failed entries so they can be retried
54
+ imageCache.delete(url);
55
+ };
56
+ img.src = url;
57
+ return img.complete && img.naturalWidth > 0 ? img : undefined;
58
+ }
59
+
60
+ const render: Attachment = (canvasEl: Element) => {
61
+ const canvas = canvasEl as HTMLCanvasElement;
62
+ const context = canvas.getContext('2d');
63
+
64
+ $effect(() => {
65
+ // Reference loadedCount so this effect re-runs when images load
66
+ void loadedCount;
67
+
68
+ if (context) {
69
+ context.resetTransform();
70
+ context.scale(devicePixelRatio.current ?? 1, devicePixelRatio.current ?? 1);
71
+
72
+ for (const d of data) {
73
+ if (!d.valid) continue;
74
+
75
+ const datum = d.datum as unknown as Datum;
76
+
77
+ const srcUrl = resolveProp(options.src, datum, '') as string;
78
+ if (!srcUrl) continue;
79
+
80
+ const img = getImage(srcUrl);
81
+ if (!img) continue;
82
+
83
+ const w =
84
+ d.r !== undefined
85
+ ? (d.r as number) * 2
86
+ : (Number(resolveProp(options.width, datum, 20) ?? 20) as number);
87
+ const h =
88
+ d.r !== undefined
89
+ ? (d.r as number) * 2
90
+ : (Number(
91
+ resolveProp(options.height || options.width, datum, 20) ?? 20
92
+ ) as number);
93
+
94
+ const x = (d.x ?? 0) - w * 0.5;
95
+ const y = (d.y ?? 0) - h * 0.5;
96
+
97
+ if (d.r !== undefined) {
98
+ // Circular clipping
99
+ const cx = d.x as number;
100
+ const cy = d.y as number;
101
+ const r = d.r as number;
102
+ context.save();
103
+ context.beginPath();
104
+ context.arc(cx, cy, r, 0, Math.PI * 2);
105
+ context.clip();
106
+ context.drawImage(img, x, y, w, h);
107
+ context.restore();
108
+ } else {
109
+ context.drawImage(img, x, y, w, h);
110
+ }
111
+ }
112
+ }
113
+
114
+ return () => {
115
+ context?.clearRect(
116
+ 0,
117
+ 0,
118
+ plot.width * (devicePixelRatio.current ?? 1),
119
+ plot.height * (devicePixelRatio.current ?? 1)
120
+ );
121
+ };
122
+ });
123
+ };
124
+ </script>
125
+
126
+ <CanvasLayer {@attach render} />
@@ -0,0 +1,34 @@
1
+ import type { BaseMarkProps, ConstantAccessor, DataRecord, ScaledDataRecord, UsedScales } from '../../types/index.js';
2
+ declare function $$render<Datum extends DataRecord>(): {
3
+ props: {
4
+ data: ScaledDataRecord<Datum>[];
5
+ options: BaseMarkProps<Datum> & {
6
+ src?: ConstantAccessor<string, Datum>;
7
+ width?: ConstantAccessor<number, Datum>;
8
+ height?: ConstantAccessor<number, Datum>;
9
+ };
10
+ usedScales: UsedScales;
11
+ };
12
+ exports: {};
13
+ bindings: "";
14
+ slots: {};
15
+ events: {};
16
+ };
17
+ declare class __sveltets_Render<Datum extends DataRecord> {
18
+ props(): ReturnType<typeof $$render<Datum>>['props'];
19
+ events(): ReturnType<typeof $$render<Datum>>['events'];
20
+ slots(): ReturnType<typeof $$render<Datum>>['slots'];
21
+ bindings(): "";
22
+ exports(): {};
23
+ }
24
+ interface $$IsomorphicComponent {
25
+ 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']>> & {
26
+ $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
27
+ } & ReturnType<__sveltets_Render<Datum>['exports']>;
28
+ <Datum extends DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
29
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
30
+ }
31
+ /** Helper component for rendering Image marks in canvas */
32
+ declare const ImageCanvas: $$IsomorphicComponent;
33
+ type ImageCanvas<Datum extends DataRecord> = InstanceType<typeof ImageCanvas<Datum>>;
34
+ export default ImageCanvas;
@@ -0,0 +1,103 @@
1
+ <!--
2
+ @component
3
+ Helper component for rendering Link marks in canvas
4
+ -->
5
+ <script lang="ts" generics="Datum extends DataRecord">
6
+ interface LinkCanvasProps {
7
+ data: ScaledDataRecord<Datum>[];
8
+ options: BaseMarkProps<Datum>;
9
+ usedScales: UsedScales;
10
+ curveFactory: CurveFactory | CurveBundleFactory;
11
+ }
12
+
13
+ import type {
14
+ BaseMarkProps,
15
+ DataRecord,
16
+ ScaledDataRecord,
17
+ UsedScales
18
+ } from '../../types/index.js';
19
+ import { resolveProp, resolveScaledStyleProps } from '../../helpers/resolve.js';
20
+ import {
21
+ line,
22
+ type CurveFactory,
23
+ type CurveBundleFactory,
24
+ type Line as D3Line
25
+ } from 'd3-shape';
26
+ import CanvasLayer from './CanvasLayer.svelte';
27
+ import type { Attachment } from 'svelte/attachments';
28
+ import { devicePixelRatio } from 'svelte/reactivity/window';
29
+ import { resolveColor } from './canvas.js';
30
+ import { usePlot } from '../../hooks/usePlot.svelte.js';
31
+
32
+ const plot = usePlot();
33
+
34
+ let { data, options, usedScales, curveFactory }: LinkCanvasProps = $props();
35
+
36
+ const render: Attachment = (canvasEl: Element) => {
37
+ const canvas = canvasEl as HTMLCanvasElement;
38
+ const context = canvas.getContext('2d');
39
+
40
+ $effect(() => {
41
+ if (context) {
42
+ const fn: D3Line<[number, number]> = line<[number, number]>()
43
+ .curve(curveFactory as CurveFactory)
44
+ .x((d) => d[0])
45
+ .y((d) => d[1])
46
+ .context(context);
47
+
48
+ context.resetTransform();
49
+ context.scale(devicePixelRatio.current ?? 1, devicePixelRatio.current ?? 1);
50
+ context.lineJoin = 'round';
51
+ context.lineCap = 'round';
52
+
53
+ for (const d of data) {
54
+ if (!d.valid) continue;
55
+
56
+ const styleProps = resolveScaledStyleProps(
57
+ d.datum,
58
+ options,
59
+ usedScales,
60
+ plot,
61
+ 'stroke'
62
+ ) as Record<string, unknown>;
63
+
64
+ const opacity = +(styleProps['opacity'] ?? 1);
65
+ const strokeOpacity = +(styleProps['stroke-opacity'] ?? 1);
66
+ const stroke = resolveColor(
67
+ String(styleProps.stroke || 'currentColor'),
68
+ canvas
69
+ );
70
+
71
+ if (!stroke || stroke === 'none') continue;
72
+
73
+ const strokeWidth = (resolveProp(options.strokeWidth, d.datum, 1.6) ??
74
+ 1.6) as number;
75
+
76
+ context.lineWidth = strokeWidth;
77
+ context.strokeStyle = stroke;
78
+ context.globalAlpha = opacity * strokeOpacity;
79
+
80
+ context.beginPath();
81
+ fn([
82
+ [d.x1 as number, d.y1 as number],
83
+ [d.x2 as number, d.y2 as number]
84
+ ]);
85
+ context.stroke();
86
+ }
87
+
88
+ fn.context(null);
89
+ }
90
+
91
+ return () => {
92
+ context?.clearRect(
93
+ 0,
94
+ 0,
95
+ plot.width * (devicePixelRatio.current ?? 1),
96
+ plot.height * (devicePixelRatio.current ?? 1)
97
+ );
98
+ };
99
+ });
100
+ };
101
+ </script>
102
+
103
+ <CanvasLayer {@attach render} />
@@ -0,0 +1,32 @@
1
+ import type { BaseMarkProps, DataRecord, ScaledDataRecord, UsedScales } from '../../types/index.js';
2
+ import { type CurveFactory, type CurveBundleFactory } from 'd3-shape';
3
+ declare function $$render<Datum extends DataRecord>(): {
4
+ props: {
5
+ data: ScaledDataRecord<Datum>[];
6
+ options: BaseMarkProps<Datum>;
7
+ usedScales: UsedScales;
8
+ curveFactory: CurveFactory | CurveBundleFactory;
9
+ };
10
+ exports: {};
11
+ bindings: "";
12
+ slots: {};
13
+ events: {};
14
+ };
15
+ declare class __sveltets_Render<Datum extends DataRecord> {
16
+ props(): ReturnType<typeof $$render<Datum>>['props'];
17
+ events(): ReturnType<typeof $$render<Datum>>['events'];
18
+ slots(): ReturnType<typeof $$render<Datum>>['slots'];
19
+ bindings(): "";
20
+ exports(): {};
21
+ }
22
+ interface $$IsomorphicComponent {
23
+ 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']>> & {
24
+ $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
25
+ } & ReturnType<__sveltets_Render<Datum>['exports']>;
26
+ <Datum extends DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
27
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
28
+ }
29
+ /** Helper component for rendering Link marks in canvas */
30
+ declare const LinkCanvas: $$IsomorphicComponent;
31
+ type LinkCanvas<Datum extends DataRecord> = InstanceType<typeof LinkCanvas<Datum>>;
32
+ export default LinkCanvas;
@@ -0,0 +1,127 @@
1
+ <!--
2
+ @component
3
+ Helper component for rendering Vector marks in canvas
4
+ -->
5
+ <script lang="ts" generics="Datum extends DataRecord">
6
+ interface VectorCanvasProps {
7
+ data: ScaledDataRecord<Datum>[];
8
+ options: BaseMarkProps<Datum> & {
9
+ rotate?: ChannelAccessor<Datum>;
10
+ r?: number;
11
+ anchor?: 'start' | 'middle' | 'end';
12
+ shape?: 'arrow' | 'spike' | 'arrow-filled' | ShapeRenderer;
13
+ };
14
+ usedScales: UsedScales;
15
+ }
16
+
17
+ import type {
18
+ BaseMarkProps,
19
+ ChannelAccessor,
20
+ DataRecord,
21
+ ScaledDataRecord,
22
+ UsedScales
23
+ } from '../../types/index.js';
24
+ import { resolveProp, resolveScaledStyleProps } from '../../helpers/resolve.js';
25
+ import { defaultRadius, shapePath, type ShapeRenderer } from '../../helpers/vectorShapes.js';
26
+ import type { Attachment } from 'svelte/attachments';
27
+ import { devicePixelRatio } from 'svelte/reactivity/window';
28
+ import CanvasLayer from './CanvasLayer.svelte';
29
+ import { resolveColor } from './canvas.js';
30
+ import { usePlot } from '../../hooks/usePlot.svelte.js';
31
+
32
+ const plot = usePlot();
33
+
34
+ let { data, options, usedScales }: VectorCanvasProps = $props();
35
+
36
+ const render: Attachment = (canvasEl: Element) => {
37
+ const canvas = canvasEl as HTMLCanvasElement;
38
+ const context = canvas.getContext('2d');
39
+
40
+ $effect(() => {
41
+ if (context) {
42
+ context.resetTransform();
43
+ context.scale(devicePixelRatio.current ?? 1, devicePixelRatio.current ?? 1);
44
+
45
+ const shape = options.shape ?? 'arrow';
46
+ const anchor = options.anchor ?? 'middle';
47
+ const defaultColorProp = shape === 'arrow-filled' ? 'fill' : 'stroke';
48
+
49
+ for (const d of data) {
50
+ if (!d.valid) continue;
51
+
52
+ const datum = d.datum as unknown as Datum;
53
+ const length = d.length as number;
54
+ const r = (d.r as number) ?? defaultRadius;
55
+
56
+ if (length == null || r == null) continue;
57
+
58
+ const styleProps = resolveScaledStyleProps(
59
+ d.datum,
60
+ options,
61
+ usedScales,
62
+ plot,
63
+ defaultColorProp
64
+ ) as Record<string, unknown>;
65
+
66
+ const opacity = +(styleProps['opacity'] ?? 1);
67
+ const fillOpacity = +(styleProps['fill-opacity'] ?? 1);
68
+ const strokeOpacity = +(styleProps['stroke-opacity'] ?? 1);
69
+ const fill = resolveColor(String(styleProps.fill || 'none'), canvas);
70
+ const stroke = resolveColor(String(styleProps.stroke || 'none'), canvas);
71
+
72
+ const hasFill = fill && fill !== 'none';
73
+ const hasStroke = stroke && stroke !== 'none';
74
+
75
+ if (!hasFill && !hasStroke) continue;
76
+
77
+ const rotateDeg = (resolveProp(options.rotate, datum, 0) ?? 0) as number;
78
+ const rotateRad = (rotateDeg * Math.PI) / 180;
79
+
80
+ const anchorOffset =
81
+ anchor === 'start' ? 0 : anchor === 'end' ? length : length / 2;
82
+
83
+ const pathStr = shapePath(shape, length, r);
84
+ if (!pathStr) continue;
85
+ const path2d = new Path2D(pathStr);
86
+
87
+ const strokeWidth = (resolveProp(options.strokeWidth, datum, 1.5) ??
88
+ 1.5) as number;
89
+
90
+ context.save();
91
+ context.translate(d.x as number, d.y as number);
92
+ context.rotate(rotateRad);
93
+ context.translate(0, anchorOffset);
94
+
95
+ context.lineWidth = strokeWidth;
96
+ context.lineCap = 'round';
97
+ context.lineJoin = 'round';
98
+
99
+ if (hasFill) {
100
+ context.fillStyle = fill as string;
101
+ context.globalAlpha = opacity * fillOpacity;
102
+ context.fill(path2d);
103
+ }
104
+
105
+ if (hasStroke) {
106
+ context.strokeStyle = stroke as string;
107
+ context.globalAlpha = opacity * strokeOpacity;
108
+ context.stroke(path2d);
109
+ }
110
+
111
+ context.restore();
112
+ }
113
+ }
114
+
115
+ return () => {
116
+ context?.clearRect(
117
+ 0,
118
+ 0,
119
+ plot.width * (devicePixelRatio.current ?? 1),
120
+ plot.height * (devicePixelRatio.current ?? 1)
121
+ );
122
+ };
123
+ });
124
+ };
125
+ </script>
126
+
127
+ <CanvasLayer {@attach render} />
@@ -0,0 +1,36 @@
1
+ import type { BaseMarkProps, ChannelAccessor, DataRecord, ScaledDataRecord, UsedScales } from '../../types/index.js';
2
+ import { type ShapeRenderer } from '../../helpers/vectorShapes.js';
3
+ declare function $$render<Datum extends DataRecord>(): {
4
+ props: {
5
+ data: ScaledDataRecord<Datum>[];
6
+ options: BaseMarkProps<Datum> & {
7
+ rotate?: ChannelAccessor<Datum>;
8
+ r?: number;
9
+ anchor?: "start" | "middle" | "end";
10
+ shape?: "arrow" | "spike" | "arrow-filled" | ShapeRenderer;
11
+ };
12
+ usedScales: UsedScales;
13
+ };
14
+ exports: {};
15
+ bindings: "";
16
+ slots: {};
17
+ events: {};
18
+ };
19
+ declare class __sveltets_Render<Datum extends DataRecord> {
20
+ props(): ReturnType<typeof $$render<Datum>>['props'];
21
+ events(): ReturnType<typeof $$render<Datum>>['events'];
22
+ slots(): ReturnType<typeof $$render<Datum>>['slots'];
23
+ bindings(): "";
24
+ exports(): {};
25
+ }
26
+ interface $$IsomorphicComponent {
27
+ 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']>> & {
28
+ $$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
29
+ } & ReturnType<__sveltets_Render<Datum>['exports']>;
30
+ <Datum extends DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
31
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
32
+ }
33
+ /** Helper component for rendering Vector marks in canvas */
34
+ declare const VectorCanvas: $$IsomorphicComponent;
35
+ type VectorCanvas<Datum extends DataRecord> = InstanceType<typeof VectorCanvas<Datum>>;
36
+ export default VectorCanvas;
@@ -14,6 +14,7 @@ export { default as Brush } from './Brush.svelte';
14
14
  export { default as BrushX } from './BrushX.svelte';
15
15
  export { default as BrushY } from './BrushY.svelte';
16
16
  export { default as Cell } from './Cell.svelte';
17
+ export { default as Contour } from './Contour.svelte';
17
18
  export { default as CellX } from './CellX.svelte';
18
19
  export { default as CellY } from './CellY.svelte';
19
20
  export { default as CustomMark } from './CustomMark.svelte';