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,103 @@
1
+ <!-- @component
2
+ Renders GeoJSON geometries as SVG <path> elements (or via canvas when
3
+ canvas=true). Used by the Contour and Density marks. Each path's style is
4
+ resolved per-threshold using a color keyword (e.g. "value" or "density") that
5
+ maps the RAW_VALUE attached to each fake datum through the plot's color scale.
6
+ -->
7
+ <script lang="ts">
8
+ import type { ScaledDataRecord, PlotState } from '../../types/index.js';
9
+ import type { GeoPath } from 'd3-geo';
10
+ import { RAW_VALUE } from '../../transforms/recordize.js';
11
+ import GeoPathCanvas from './GeoPathCanvas.svelte';
12
+
13
+ let {
14
+ scaledData,
15
+ path,
16
+ geomKey,
17
+ colorKeyword,
18
+ fill,
19
+ stroke,
20
+ strokeWidth,
21
+ strokeOpacity,
22
+ fillOpacity,
23
+ opacity,
24
+ strokeMiterlimit,
25
+ clipPath,
26
+ className,
27
+ ariaLabel,
28
+ canvas = false,
29
+ plot
30
+ }: {
31
+ scaledData: ScaledDataRecord[];
32
+ /** d3 geoPath renderer (must NOT have a canvas context set). */
33
+ path: GeoPath;
34
+ /** Symbol key used to retrieve the GeoJSON geometry from each datum. */
35
+ geomKey: symbol;
36
+ /**
37
+ * The color keyword that, when used as fill/stroke, maps the threshold
38
+ * value through the plot's color scale. E.g. "value" for Contour,
39
+ * "density" for Density.
40
+ */
41
+ colorKeyword: string;
42
+ fill: string;
43
+ stroke: string;
44
+ strokeWidth?: number;
45
+ strokeOpacity?: number;
46
+ fillOpacity?: number;
47
+ opacity?: number;
48
+ strokeMiterlimit?: number;
49
+ clipPath?: string;
50
+ className?: string;
51
+ ariaLabel?: string;
52
+ /** Render using a canvas element instead of SVG paths. */
53
+ canvas?: boolean;
54
+ plot: PlotState;
55
+ } = $props();
56
+
57
+ /** Resolve a fill/stroke prop that may be the colorKeyword. */
58
+ function resolveColor(prop: string | undefined, value: number): string {
59
+ if (prop != null && prop.toLowerCase() === colorKeyword.toLowerCase()) {
60
+ return (plot.scales.color?.fn(value) as string) ?? 'currentColor';
61
+ }
62
+ return prop ?? 'none';
63
+ }
64
+
65
+ /** Build the inline style string for a single contour/density path. */
66
+ function buildStyle(value: number): string {
67
+ const parts: string[] = [];
68
+ parts.push(`fill:${resolveColor(fill, value)}`);
69
+ parts.push(`stroke:${resolveColor(stroke, value)}`);
70
+ if (strokeWidth != null) parts.push(`stroke-width:${strokeWidth}`);
71
+ if (strokeOpacity != null) parts.push(`stroke-opacity:${strokeOpacity}`);
72
+ if (fillOpacity != null) parts.push(`fill-opacity:${fillOpacity}`);
73
+ if (opacity != null) parts.push(`opacity:${opacity}`);
74
+ if (strokeMiterlimit != null) parts.push(`stroke-miterlimit:${strokeMiterlimit}`);
75
+ return parts.join(';');
76
+ }
77
+ </script>
78
+
79
+ {#if canvas}
80
+ <GeoPathCanvas
81
+ {scaledData}
82
+ {path}
83
+ {geomKey}
84
+ {colorKeyword}
85
+ {fill}
86
+ {stroke}
87
+ {strokeWidth}
88
+ {strokeOpacity}
89
+ {fillOpacity}
90
+ {opacity}
91
+ {strokeMiterlimit} />
92
+ {:else}
93
+ <g clip-path={clipPath} class={className || null} aria-label={ariaLabel}>
94
+ {#each scaledData as d, i (i)}
95
+ {@const geom = d.datum[geomKey as any] as any}
96
+ {#if geom?.coordinates?.length}
97
+ <path
98
+ d={path(geom)}
99
+ style={buildStyle((d.datum[RAW_VALUE as any] as number) ?? 0)} />
100
+ {/if}
101
+ {/each}
102
+ </g>
103
+ {/if}
@@ -0,0 +1,37 @@
1
+ import type { ScaledDataRecord, PlotState } from '../../types/index.js';
2
+ import type { GeoPath } from 'd3-geo';
3
+ type $$ComponentProps = {
4
+ scaledData: ScaledDataRecord[];
5
+ /** d3 geoPath renderer (must NOT have a canvas context set). */
6
+ path: GeoPath;
7
+ /** Symbol key used to retrieve the GeoJSON geometry from each datum. */
8
+ geomKey: symbol;
9
+ /**
10
+ * The color keyword that, when used as fill/stroke, maps the threshold
11
+ * value through the plot's color scale. E.g. "value" for Contour,
12
+ * "density" for Density.
13
+ */
14
+ colorKeyword: string;
15
+ fill: string;
16
+ stroke: string;
17
+ strokeWidth?: number;
18
+ strokeOpacity?: number;
19
+ fillOpacity?: number;
20
+ opacity?: number;
21
+ strokeMiterlimit?: number;
22
+ clipPath?: string;
23
+ className?: string;
24
+ ariaLabel?: string;
25
+ /** Render using a canvas element instead of SVG paths. */
26
+ canvas?: boolean;
27
+ plot: PlotState;
28
+ };
29
+ /**
30
+ * Renders GeoJSON geometries as SVG <path> elements (or via canvas when
31
+ * canvas=true). Used by the Contour and Density marks. Each path's style is
32
+ * resolved per-threshold using a color keyword (e.g. "value" or "density") that
33
+ * maps the RAW_VALUE attached to each fake datum through the plot's color scale.
34
+ */
35
+ declare const GeoPathGroup: import("svelte").Component<$$ComponentProps, {}, "">;
36
+ type GeoPathGroup = ReturnType<typeof GeoPathGroup>;
37
+ export default GeoPathGroup;
@@ -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,100 @@
1
+ <script lang="ts">
2
+ import type {
3
+ PlotState,
4
+ ScaledDataRecord,
5
+ ScaledChannelName,
6
+ ChannelAccessor,
7
+ MarkStyleProps
8
+ } from '../../types/index.js';
9
+ import { resolveStyles, resolveProp } from '../../helpers/resolve.js';
10
+
11
+ type StyleArgs = Partial<Record<ScaledChannelName | MarkStyleProps, ChannelAccessor>>;
12
+ import { resolveColor } from './canvas.js';
13
+ import type { Attachment } from 'svelte/attachments';
14
+ import CanvasLayer from './CanvasLayer.svelte';
15
+ import { devicePixelRatio } from 'svelte/reactivity/window';
16
+
17
+ let {
18
+ paths,
19
+ args,
20
+ className,
21
+ usedScales,
22
+ plot,
23
+ defaultStrokeWidth = 1,
24
+ canvas = false
25
+ }: {
26
+ paths: { path: string; datum: ScaledDataRecord }[];
27
+ args: Record<string | symbol, any>;
28
+ className: string;
29
+ usedScales: Record<ScaledChannelName, boolean>;
30
+ plot: PlotState;
31
+ defaultStrokeWidth?: number;
32
+ canvas?: boolean;
33
+ } = $props();
34
+
35
+ const render: Attachment = (el: Element) => {
36
+ const canvasEl = el as HTMLCanvasElement;
37
+ const context = canvasEl.getContext('2d');
38
+
39
+ $effect(() => {
40
+ if (!context) return;
41
+
42
+ context.resetTransform();
43
+ context.scale(devicePixelRatio.current ?? 1, devicePixelRatio.current ?? 1);
44
+
45
+ for (const { path: pathStr, datum } of paths) {
46
+ const fillColor = resolveColor(datum.fill ?? 'none', canvasEl) as string;
47
+ const strokeColor = resolveColor(datum.stroke ?? 'none', canvasEl) as string;
48
+ const opacity = datum.opacity ?? 1;
49
+ const fillOpacity = datum.fillOpacity ?? 1;
50
+ const strokeOpacity = datum.strokeOpacity ?? 1;
51
+ const strokeWidth = resolveProp(
52
+ (args as StyleArgs).strokeWidth as ChannelAccessor,
53
+ datum.datum,
54
+ defaultStrokeWidth
55
+ ) as number;
56
+
57
+ const p = new Path2D(pathStr);
58
+
59
+ if (fillColor && fillColor !== 'none') {
60
+ context.fillStyle = fillColor;
61
+ context.globalAlpha = opacity * fillOpacity;
62
+ context.fill(p);
63
+ }
64
+
65
+ if (strokeColor && strokeColor !== 'none') {
66
+ context.strokeStyle = strokeColor;
67
+ context.lineWidth = strokeWidth ?? defaultStrokeWidth;
68
+ context.globalAlpha = opacity * strokeOpacity;
69
+ context.stroke(p);
70
+ }
71
+ }
72
+
73
+ return () => {
74
+ context.clearRect(
75
+ 0,
76
+ 0,
77
+ plot.width * (devicePixelRatio.current ?? 1),
78
+ plot.height * (devicePixelRatio.current ?? 1)
79
+ );
80
+ };
81
+ });
82
+ };
83
+ </script>
84
+
85
+ {#if canvas}
86
+ <CanvasLayer {@attach render} />
87
+ {:else}
88
+ <g class={className}>
89
+ {#each paths as { path, datum }, i (i)}
90
+ {@const [style, styleClass] = resolveStyles(
91
+ plot,
92
+ datum,
93
+ { strokeWidth: defaultStrokeWidth, ...(args as StyleArgs) },
94
+ 'stroke',
95
+ usedScales
96
+ )}
97
+ <path d={path} class={styleClass} {style} />
98
+ {/each}
99
+ </g>
100
+ {/if}
@@ -0,0 +1,16 @@
1
+ import type { PlotState, ScaledDataRecord, ScaledChannelName } from '../../types/index.js';
2
+ type $$ComponentProps = {
3
+ paths: {
4
+ path: string;
5
+ datum: ScaledDataRecord;
6
+ }[];
7
+ args: Record<string | symbol, any>;
8
+ className: string;
9
+ usedScales: Record<ScaledChannelName, boolean>;
10
+ plot: PlotState;
11
+ defaultStrokeWidth?: number;
12
+ canvas?: boolean;
13
+ };
14
+ declare const PathGroup: import("svelte").Component<$$ComponentProps, {}, "">;
15
+ type PathGroup = ReturnType<typeof PathGroup>;
16
+ export default PathGroup;
@@ -0,0 +1,112 @@
1
+ <script lang="ts">
2
+ import type {
3
+ PlotState,
4
+ ScaledDataRecord,
5
+ ScaledChannelName,
6
+ ChannelAccessor,
7
+ MarkStyleProps
8
+ } from '../../types/index.js';
9
+ import { resolveStyles, resolveProp } from '../../helpers/resolve.js';
10
+
11
+ type StyleArgs = Partial<Record<ScaledChannelName | MarkStyleProps, ChannelAccessor>>;
12
+ import { resolveColor } from './canvas.js';
13
+ import type { Attachment } from 'svelte/attachments';
14
+ import CanvasLayer from './CanvasLayer.svelte';
15
+ import { devicePixelRatio } from 'svelte/reactivity/window';
16
+ import SvelteAnchor from './Anchor.svelte';
17
+ import { addEventHandlers } from './events.js';
18
+
19
+ let {
20
+ paths,
21
+ args,
22
+ options,
23
+ className,
24
+ usedScales,
25
+ plot,
26
+ canvas = false
27
+ }: {
28
+ paths: { path: string; datum: ScaledDataRecord }[];
29
+ args: Record<string | symbol, any>;
30
+ options: Record<string, any>;
31
+ className: string;
32
+ usedScales: Record<ScaledChannelName, boolean>;
33
+ plot: PlotState;
34
+ canvas?: boolean;
35
+ } = $props();
36
+
37
+ const render: Attachment = (el: Element) => {
38
+ const canvasEl = el as HTMLCanvasElement;
39
+ const context = canvasEl.getContext('2d');
40
+
41
+ $effect(() => {
42
+ if (!context) return;
43
+
44
+ context.resetTransform();
45
+ context.scale(devicePixelRatio.current ?? 1, devicePixelRatio.current ?? 1);
46
+
47
+ for (const { path: pathStr, datum } of paths) {
48
+ const fillColor = resolveColor(datum.fill ?? 'none', canvasEl) as string;
49
+ const strokeColor = resolveColor(datum.stroke ?? 'none', canvasEl) as string;
50
+ const opacity = datum.opacity ?? 1;
51
+ const fillOpacity = datum.fillOpacity ?? 1;
52
+ const strokeOpacity = datum.strokeOpacity ?? 1;
53
+ const strokeWidth = resolveProp(
54
+ (args as StyleArgs).strokeWidth as ChannelAccessor,
55
+ datum.datum,
56
+ 1
57
+ ) as number;
58
+
59
+ const p = new Path2D(pathStr);
60
+
61
+ if (fillColor && fillColor !== 'none') {
62
+ context.fillStyle = fillColor;
63
+ context.globalAlpha = opacity * fillOpacity;
64
+ context.fill(p);
65
+ }
66
+
67
+ if (strokeColor && strokeColor !== 'none') {
68
+ context.strokeStyle = strokeColor;
69
+ context.lineWidth = strokeWidth ?? 1;
70
+ context.globalAlpha = opacity * strokeOpacity;
71
+ context.stroke(p);
72
+ }
73
+ }
74
+
75
+ return () => {
76
+ context.clearRect(
77
+ 0,
78
+ 0,
79
+ plot.width * (devicePixelRatio.current ?? 1),
80
+ plot.height * (devicePixelRatio.current ?? 1)
81
+ );
82
+ };
83
+ });
84
+ };
85
+ </script>
86
+
87
+ {#if canvas}
88
+ <CanvasLayer {@attach render} />
89
+ {:else}
90
+ <g class={className}>
91
+ {#each paths as { path, datum }, i (i)}
92
+ {@const [style, styleClass] = resolveStyles(
93
+ plot,
94
+ datum,
95
+ { strokeWidth: 1, ...(args as StyleArgs) },
96
+ 'stroke',
97
+ usedScales
98
+ )}
99
+ <SvelteAnchor {options} datum={datum.datum}>
100
+ <path
101
+ d={path}
102
+ class={styleClass}
103
+ {style}
104
+ {@attach addEventHandlers({
105
+ plot,
106
+ options: args as any,
107
+ datum: datum.datum
108
+ })} />
109
+ </SvelteAnchor>
110
+ {/each}
111
+ </g>
112
+ {/if}
@@ -0,0 +1,16 @@
1
+ import type { PlotState, ScaledDataRecord, ScaledChannelName } from '../../types/index.js';
2
+ type $$ComponentProps = {
3
+ paths: {
4
+ path: string;
5
+ datum: ScaledDataRecord;
6
+ }[];
7
+ args: Record<string | symbol, any>;
8
+ options: Record<string, any>;
9
+ className: string;
10
+ usedScales: Record<ScaledChannelName, boolean>;
11
+ plot: PlotState;
12
+ canvas?: boolean;
13
+ };
14
+ declare const PathItems: import("svelte").Component<$$ComponentProps, {}, "">;
15
+ type PathItems = ReturnType<typeof PathItems>;
16
+ export default PathItems;