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.
- package/dist/core/Plot.svelte +3 -6
- package/dist/helpers/scales.js +8 -0
- package/dist/helpers/vectorShapes.d.ts +13 -0
- package/dist/helpers/vectorShapes.js +57 -0
- package/dist/marks/Arrow.svelte +70 -59
- package/dist/marks/Arrow.svelte.d.ts +2 -0
- package/dist/marks/ColorLegend.svelte +3 -3
- package/dist/marks/Contour.svelte +693 -0
- package/dist/marks/Contour.svelte.d.ts +150 -0
- package/dist/marks/Image.svelte +37 -27
- package/dist/marks/Image.svelte.d.ts +2 -0
- package/dist/marks/Link.svelte +68 -50
- package/dist/marks/Link.svelte.d.ts +2 -0
- package/dist/marks/Raster.svelte +6 -1
- package/dist/marks/Vector.svelte +12 -81
- package/dist/marks/Vector.svelte.d.ts +2 -4
- package/dist/marks/helpers/ArrowCanvas.svelte +132 -0
- package/dist/marks/helpers/ArrowCanvas.svelte.d.ts +39 -0
- package/dist/marks/helpers/BaseAxisX.svelte +5 -7
- package/dist/marks/helpers/ImageCanvas.svelte +126 -0
- package/dist/marks/helpers/ImageCanvas.svelte.d.ts +34 -0
- package/dist/marks/helpers/LinkCanvas.svelte +103 -0
- package/dist/marks/helpers/LinkCanvas.svelte.d.ts +32 -0
- package/dist/marks/helpers/VectorCanvas.svelte +127 -0
- package/dist/marks/helpers/VectorCanvas.svelte.d.ts +36 -0
- package/dist/marks/index.d.ts +1 -0
- package/dist/marks/index.js +1 -0
- package/dist/types/plot.d.ts +9 -1
- package/package.json +185 -181
|
@@ -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})
|
|
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={
|
|
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;
|
package/dist/marks/index.d.ts
CHANGED
|
@@ -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';
|