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.
- package/dist/core/Plot.svelte +3 -6
- package/dist/helpers/group.d.ts +1 -1
- package/dist/helpers/group.js +3 -3
- 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 +7 -3
- package/dist/marks/Contour.svelte +684 -0
- package/dist/marks/Contour.svelte.d.ts +152 -0
- package/dist/marks/DelaunayLink.svelte +127 -0
- package/dist/marks/DelaunayLink.svelte.d.ts +175 -0
- package/dist/marks/DelaunayMesh.svelte +102 -0
- package/dist/marks/DelaunayMesh.svelte.d.ts +172 -0
- package/dist/marks/Density.svelte +461 -0
- package/dist/marks/Density.svelte.d.ts +87 -0
- package/dist/marks/Hull.svelte +103 -0
- package/dist/marks/Hull.svelte.d.ts +175 -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/Voronoi.svelte +118 -0
- package/dist/marks/Voronoi.svelte.d.ts +172 -0
- package/dist/marks/VoronoiMesh.svelte +109 -0
- package/dist/marks/VoronoiMesh.svelte.d.ts +172 -0
- 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/DensityCanvas.svelte +118 -0
- package/dist/marks/helpers/DensityCanvas.svelte.d.ts +18 -0
- package/dist/marks/helpers/GeoPathCanvas.svelte +125 -0
- package/dist/marks/helpers/GeoPathCanvas.svelte.d.ts +24 -0
- package/dist/marks/helpers/GeoPathGroup.svelte +103 -0
- package/dist/marks/helpers/GeoPathGroup.svelte.d.ts +37 -0
- 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/PathGroup.svelte +100 -0
- package/dist/marks/helpers/PathGroup.svelte.d.ts +16 -0
- package/dist/marks/helpers/PathItems.svelte +112 -0
- package/dist/marks/helpers/PathItems.svelte.d.ts +16 -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 +7 -0
- package/dist/marks/index.js +7 -0
- package/dist/types/mark.d.ts +1 -1
- package/dist/types/plot.d.ts +33 -1
- 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;
|