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
package/dist/marks/Vector.svelte
CHANGED
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
The vector mark lets you place shapes (like arrows) on your plot.
|
|
4
4
|
-->
|
|
5
5
|
<script lang="ts" module>
|
|
6
|
-
type
|
|
7
|
-
export type ShapeRenderer = {
|
|
8
|
-
draw(context: D3Path, l: number, r: number): void;
|
|
9
|
-
};
|
|
6
|
+
export type { ShapeRenderer } from '../helpers/vectorShapes.js';
|
|
10
7
|
</script>
|
|
11
8
|
|
|
12
9
|
<script lang="ts" generics="Datum = DataRecord | GeoJSON.GeoJsonObject">
|
|
@@ -45,25 +42,23 @@
|
|
|
45
42
|
} from '../types/index.js';
|
|
46
43
|
|
|
47
44
|
import { getContext, type Snippet } from 'svelte';
|
|
48
|
-
import { pathRound as path } from 'd3-path';
|
|
49
45
|
|
|
50
46
|
import { resolveChannel, resolveProp, resolveStyles } from '../helpers/resolve.js';
|
|
51
47
|
import { sort } from '../index.js';
|
|
52
48
|
import Mark from '../Mark.svelte';
|
|
53
|
-
|
|
49
|
+
import VectorCanvas from './helpers/VectorCanvas.svelte';
|
|
54
50
|
import { isValid } from '../helpers/index.js';
|
|
55
51
|
import { addEventHandlers } from './helpers/events.js';
|
|
56
52
|
import { indexData } from '../transforms/recordize.js';
|
|
57
53
|
import { getPlotDefaults } from '../hooks/plotDefaults.js';
|
|
58
54
|
import { usePlot } from '../hooks/usePlot.svelte.js';
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const wingRatio = defaultRadius * 5;
|
|
56
|
+
import {
|
|
57
|
+
defaultRadius,
|
|
58
|
+
maybeShape,
|
|
59
|
+
shapePath,
|
|
60
|
+
type ShapeRenderer
|
|
61
|
+
} from '../helpers/vectorShapes.js';
|
|
67
62
|
|
|
68
63
|
let markProps: VectorMarkProps = $props();
|
|
69
64
|
const DEFAULTS = {
|
|
@@ -83,72 +78,6 @@
|
|
|
83
78
|
|
|
84
79
|
const plot = usePlot();
|
|
85
80
|
|
|
86
|
-
const shapeArrow: ShapeRenderer = {
|
|
87
|
-
draw(context: D3Path, l: number, r: number) {
|
|
88
|
-
const wing = (l * r) / wingRatio;
|
|
89
|
-
context.moveTo(0, 0);
|
|
90
|
-
context.lineTo(0, -l);
|
|
91
|
-
context.moveTo(-wing, wing - l);
|
|
92
|
-
context.lineTo(0, -l);
|
|
93
|
-
context.lineTo(wing, wing - l);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const shapeSpike: ShapeRenderer = {
|
|
98
|
-
draw(context: D3Path, l: number, r: number) {
|
|
99
|
-
context.moveTo(-r, 0);
|
|
100
|
-
context.lineTo(0, -l);
|
|
101
|
-
context.lineTo(r, 0);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const shapeArrowFilled: ShapeRenderer = {
|
|
106
|
-
draw(context: D3Path, l: number, r: number) {
|
|
107
|
-
// const wing = (l * r) / wingRatio;
|
|
108
|
-
const headLength = Math.max(3, l * 0.3);
|
|
109
|
-
const headSpike = headLength * 0.2;
|
|
110
|
-
const headWidth = Math.max(2, l * 0.3);
|
|
111
|
-
const tailWidth = Math.max(2, l * 0.3) * 0.3;
|
|
112
|
-
|
|
113
|
-
context.moveTo(0, 0);
|
|
114
|
-
|
|
115
|
-
context.lineTo(tailWidth * 0.5, -l + headLength - headSpike);
|
|
116
|
-
context.lineTo(headWidth * 0.5, -l + headLength);
|
|
117
|
-
context.lineTo(0, -l);
|
|
118
|
-
context.lineTo(-headWidth * 0.5, -l + headLength);
|
|
119
|
-
context.lineTo(-tailWidth * 0.5, -l + headLength - headSpike);
|
|
120
|
-
|
|
121
|
-
context.closePath();
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const shapes = new Map([
|
|
126
|
-
['arrow', shapeArrow],
|
|
127
|
-
['arrow-filled', shapeArrowFilled],
|
|
128
|
-
['spike', shapeSpike]
|
|
129
|
-
]);
|
|
130
|
-
|
|
131
|
-
function isShapeObject(value: any): value is ShapeRenderer {
|
|
132
|
-
return value && typeof value.draw === 'function';
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function maybeShape(shape: 'arrow' | 'spike' | 'arrow-filled' | ShapeRenderer) {
|
|
136
|
-
if (isShapeObject(shape)) return shape;
|
|
137
|
-
const value = shapes.get(`${shape}`.toLowerCase());
|
|
138
|
-
if (value) return value;
|
|
139
|
-
throw new Error(`invalid shape: ${shape}`);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function shapePath(
|
|
143
|
-
shape: 'arrow' | 'spike' | 'arrow-filled' | ShapeRenderer,
|
|
144
|
-
l: number,
|
|
145
|
-
r: number
|
|
146
|
-
) {
|
|
147
|
-
const context = path();
|
|
148
|
-
maybeShape(shape).draw(context, l, r);
|
|
149
|
-
return context.toString();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
81
|
const args = $derived(
|
|
153
82
|
sort({
|
|
154
83
|
data: indexData(data as object[]) as unknown as Datum[],
|
|
@@ -177,8 +106,10 @@
|
|
|
177
106
|
{#snippet children({ scaledData, usedScales })}
|
|
178
107
|
<g class="vector" data-l={usedScales.length}>
|
|
179
108
|
{#if canvas}
|
|
180
|
-
<
|
|
181
|
-
|
|
109
|
+
<VectorCanvas
|
|
110
|
+
data={scaledData}
|
|
111
|
+
options={{ ...args, shape, anchor } as any}
|
|
112
|
+
{usedScales} />
|
|
182
113
|
{:else}
|
|
183
114
|
{#each scaledData as d, i (i)}
|
|
184
115
|
{#if d.valid && isValid(d.r)}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
type
|
|
2
|
-
export type ShapeRenderer = {
|
|
3
|
-
draw(context: D3Path, l: number, r: number): void;
|
|
4
|
-
};
|
|
1
|
+
export type { ShapeRenderer } from '../helpers/vectorShapes.js';
|
|
5
2
|
import type { DataRecord, ChannelAccessor } from '../types/index.js';
|
|
6
3
|
import { type Snippet } from 'svelte';
|
|
4
|
+
import { type ShapeRenderer } from '../helpers/vectorShapes.js';
|
|
7
5
|
declare function $$render<Datum = DataRecord | GeoJSON.GeoJsonObject>(): {
|
|
8
6
|
props: Partial<{
|
|
9
7
|
filter: import("../types/index.js").ConstantAccessor<boolean, Datum>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
Renders individual Voronoi cells, one per data point, allowing per-cell styling.
|
|
3
|
+
-->
|
|
4
|
+
<script lang="ts" generics="Datum = DataRecord">
|
|
5
|
+
interface VoronoiMarkProps extends BaseMarkProps<Datum>, LinkableMarkProps<Datum> {
|
|
6
|
+
/** the input data array */
|
|
7
|
+
data?: Datum[];
|
|
8
|
+
/** the horizontal position channel */
|
|
9
|
+
x?: ChannelAccessor<Datum>;
|
|
10
|
+
/** the vertical position channel */
|
|
11
|
+
y?: ChannelAccessor<Datum>;
|
|
12
|
+
/** the grouping channel; separate Voronoi diagrams per group */
|
|
13
|
+
z?: ChannelAccessor<Datum>;
|
|
14
|
+
/** Render using a canvas element instead of SVG paths. */
|
|
15
|
+
canvas?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
import { Delaunay } from 'd3-delaunay';
|
|
19
|
+
import type {
|
|
20
|
+
DataRecord,
|
|
21
|
+
BaseMarkProps,
|
|
22
|
+
ChannelAccessor,
|
|
23
|
+
LinkableMarkProps,
|
|
24
|
+
MarkType,
|
|
25
|
+
ScaledDataRecord
|
|
26
|
+
} from '../types/index.js';
|
|
27
|
+
import { groupFacetsAndZ } from '../helpers/group.js';
|
|
28
|
+
import { recordizeXY } from '../transforms/recordize.js';
|
|
29
|
+
import { sort } from '../index.js';
|
|
30
|
+
import Mark from '../Mark.svelte';
|
|
31
|
+
import PathItems from './helpers/PathItems.svelte';
|
|
32
|
+
import { usePlot } from '../hooks/usePlot.svelte.js';
|
|
33
|
+
import { getPlotDefaults } from '../hooks/plotDefaults.js';
|
|
34
|
+
|
|
35
|
+
const DEFAULTS = {
|
|
36
|
+
...getPlotDefaults().voronoi
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
let markProps: VoronoiMarkProps = $props();
|
|
40
|
+
|
|
41
|
+
const {
|
|
42
|
+
data = [] as Datum[],
|
|
43
|
+
class: className = 'voronoi',
|
|
44
|
+
canvas = false,
|
|
45
|
+
...options
|
|
46
|
+
}: VoronoiMarkProps = $derived({ ...DEFAULTS, ...markProps });
|
|
47
|
+
|
|
48
|
+
const args = $derived(
|
|
49
|
+
sort(
|
|
50
|
+
recordizeXY({
|
|
51
|
+
data: data as any[],
|
|
52
|
+
...options
|
|
53
|
+
})
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const plot = usePlot();
|
|
58
|
+
|
|
59
|
+
function computeVoronoi(scaledData: ScaledDataRecord[]) {
|
|
60
|
+
const x0 = plot.options.marginLeft;
|
|
61
|
+
const y0 = plot.options.marginTop;
|
|
62
|
+
const x1 = x0 + plot.facetWidth;
|
|
63
|
+
const y1 = y0 + plot.facetHeight;
|
|
64
|
+
if (!(x1 > x0) || !(y1 > y0)) return [];
|
|
65
|
+
|
|
66
|
+
const scaledByDatum = new Map(scaledData.map((d) => [d.datum, d]));
|
|
67
|
+
const results: { path: string; datum: ScaledDataRecord }[] = [];
|
|
68
|
+
|
|
69
|
+
groupFacetsAndZ(
|
|
70
|
+
scaledData.map((d) => d.datum),
|
|
71
|
+
args,
|
|
72
|
+
(groupItems) => {
|
|
73
|
+
const groupScaled = groupItems
|
|
74
|
+
.map((d) => scaledByDatum.get(d))
|
|
75
|
+
.filter(
|
|
76
|
+
(d): d is ScaledDataRecord =>
|
|
77
|
+
d !== undefined &&
|
|
78
|
+
d.valid &&
|
|
79
|
+
Number.isFinite(d.x as number) &&
|
|
80
|
+
Number.isFinite(d.y as number)
|
|
81
|
+
);
|
|
82
|
+
if (groupScaled.length < 2) return;
|
|
83
|
+
|
|
84
|
+
const delaunay = Delaunay.from(
|
|
85
|
+
groupScaled,
|
|
86
|
+
(d) => d.x as number,
|
|
87
|
+
(d) => d.y as number
|
|
88
|
+
);
|
|
89
|
+
const voronoi = delaunay.voronoi([x0, y0, x1, y1]);
|
|
90
|
+
|
|
91
|
+
groupScaled.forEach((d, cellIndex) => {
|
|
92
|
+
const path = voronoi.renderCell(cellIndex);
|
|
93
|
+
if (path) results.push({ path, datum: d });
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
false
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return results;
|
|
100
|
+
}
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<Mark
|
|
104
|
+
type={'voronoi' as MarkType}
|
|
105
|
+
channels={['x', 'y', 'fill', 'opacity', 'stroke', 'fillOpacity', 'strokeOpacity']}
|
|
106
|
+
defaults={{ fill: 'none', stroke: 'currentColor' }}
|
|
107
|
+
{...args}>
|
|
108
|
+
{#snippet children({ mark, usedScales, scaledData })}
|
|
109
|
+
<PathItems
|
|
110
|
+
paths={computeVoronoi(scaledData)}
|
|
111
|
+
{args}
|
|
112
|
+
{options}
|
|
113
|
+
{className}
|
|
114
|
+
{usedScales}
|
|
115
|
+
{plot}
|
|
116
|
+
{canvas} />
|
|
117
|
+
{/snippet}
|
|
118
|
+
</Mark>
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { DataRecord, ChannelAccessor, LinkableMarkProps } from '../types/index.js';
|
|
2
|
+
declare function $$render<Datum = DataRecord>(): {
|
|
3
|
+
props: Partial<{
|
|
4
|
+
filter: import("../types/index.js").ConstantAccessor<boolean, Datum>;
|
|
5
|
+
facet: "auto" | "include" | "exclude";
|
|
6
|
+
fx: ChannelAccessor<Datum>;
|
|
7
|
+
fy: ChannelAccessor<Datum>;
|
|
8
|
+
dx: import("../types/index.js").ConstantAccessor<number, Datum>;
|
|
9
|
+
dy: import("../types/index.js").ConstantAccessor<number, Datum>;
|
|
10
|
+
dodgeX: import("../transforms/dodge.js").DodgeXOptions;
|
|
11
|
+
dodgeY: import("../transforms/dodge.js").DodgeYOptions;
|
|
12
|
+
fill: ChannelAccessor<Datum>;
|
|
13
|
+
fillOpacity: import("../types/index.js").ConstantAccessor<number, Datum>;
|
|
14
|
+
fontFamily: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontFamily, Datum>;
|
|
15
|
+
fontSize: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontSize<number>, Datum>;
|
|
16
|
+
fontStyle: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontStyle, Datum>;
|
|
17
|
+
fontVariant: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontVariant, Datum>;
|
|
18
|
+
fontWeight: import("../types/index.js").ConstantAccessor<import("csstype").Property.FontWeight, Datum>;
|
|
19
|
+
letterSpacing: import("../types/index.js").ConstantAccessor<import("csstype").Property.LetterSpacing<0 | (string & {})>, Datum>;
|
|
20
|
+
wordSpacing: import("../types/index.js").ConstantAccessor<import("csstype").Property.WordSpacing<0 | (string & {})>, Datum>;
|
|
21
|
+
textAnchor: import("../types/index.js").ConstantAccessor<import("csstype").Property.TextAnchor, Datum>;
|
|
22
|
+
textTransform: import("../types/index.js").ConstantAccessor<import("csstype").Property.TextTransform, Datum>;
|
|
23
|
+
textDecoration: import("../types/index.js").ConstantAccessor<import("csstype").Property.TextDecoration<0 | (string & {})>, Datum>;
|
|
24
|
+
sort: ((a: import("../types/data.js").RawValue, b: import("../types/data.js").RawValue) => number) | {
|
|
25
|
+
channel: string;
|
|
26
|
+
order?: "ascending" | "descending";
|
|
27
|
+
} | import("../types/index.js").ConstantAccessor<import("../types/data.js").RawValue, Datum>;
|
|
28
|
+
stroke: ChannelAccessor<Datum>;
|
|
29
|
+
strokeWidth: import("../types/index.js").ConstantAccessor<number, Datum>;
|
|
30
|
+
strokeOpacity: import("../types/index.js").ConstantAccessor<number, Datum>;
|
|
31
|
+
strokeLinejoin: import("../types/index.js").ConstantAccessor<import("csstype").Property.StrokeLinejoin, Datum>;
|
|
32
|
+
strokeLinecap: import("../types/index.js").ConstantAccessor<import("csstype").Property.StrokeLinecap, Datum>;
|
|
33
|
+
strokeMiterlimit: import("../types/index.js").ConstantAccessor<number, Datum>;
|
|
34
|
+
opacity: ChannelAccessor<Datum>;
|
|
35
|
+
strokeDasharray: import("../types/index.js").ConstantAccessor<string, Datum>;
|
|
36
|
+
strokeDashoffset: import("../types/index.js").ConstantAccessor<number, Datum>;
|
|
37
|
+
blend: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
|
|
38
|
+
mixBlendMode: import("../types/index.js").ConstantAccessor<import("csstype").Property.MixBlendMode, Datum>;
|
|
39
|
+
clipPath: string;
|
|
40
|
+
mask: string;
|
|
41
|
+
imageFilter: import("../types/index.js").ConstantAccessor<string, Datum>;
|
|
42
|
+
shapeRendering: import("../types/index.js").ConstantAccessor<import("csstype").Property.ShapeRendering, Datum>;
|
|
43
|
+
paintOrder: import("../types/index.js").ConstantAccessor<string, Datum>;
|
|
44
|
+
onclick: (event: Event & {
|
|
45
|
+
currentTarget: SVGPathElement;
|
|
46
|
+
}, datum: Datum, index: number) => void;
|
|
47
|
+
ondblclick: (event: Event & {
|
|
48
|
+
currentTarget: SVGPathElement;
|
|
49
|
+
}, datum: Datum, index: number) => void;
|
|
50
|
+
onmouseup: (event: Event & {
|
|
51
|
+
currentTarget: SVGPathElement;
|
|
52
|
+
}, datum: Datum, index: number) => void;
|
|
53
|
+
onmousedown: (event: Event & {
|
|
54
|
+
currentTarget: SVGPathElement;
|
|
55
|
+
}, datum: Datum, index: number) => void;
|
|
56
|
+
onmouseenter: (event: Event & {
|
|
57
|
+
currentTarget: SVGPathElement;
|
|
58
|
+
}, datum: Datum, index: number) => void;
|
|
59
|
+
onmousemove: (event: Event & {
|
|
60
|
+
currentTarget: SVGPathElement;
|
|
61
|
+
}, datum: Datum, index: number) => void;
|
|
62
|
+
onmouseleave: (event: Event & {
|
|
63
|
+
currentTarget: SVGPathElement;
|
|
64
|
+
}, datum: Datum, index: number) => void;
|
|
65
|
+
onmouseout: (event: Event & {
|
|
66
|
+
currentTarget: SVGPathElement;
|
|
67
|
+
}, datum: Datum, index: number) => void;
|
|
68
|
+
onmouseover: (event: Event & {
|
|
69
|
+
currentTarget: SVGPathElement;
|
|
70
|
+
}, datum: Datum, index: number) => void;
|
|
71
|
+
onpointercancel: (event: Event & {
|
|
72
|
+
currentTarget: SVGPathElement;
|
|
73
|
+
}, datum: Datum, index: number) => void;
|
|
74
|
+
onpointerdown: (event: Event & {
|
|
75
|
+
currentTarget: SVGPathElement;
|
|
76
|
+
}, datum: Datum, index: number) => void;
|
|
77
|
+
onpointerup: (event: Event & {
|
|
78
|
+
currentTarget: SVGPathElement;
|
|
79
|
+
}, datum: Datum, index: number) => void;
|
|
80
|
+
onpointerenter: (event: Event & {
|
|
81
|
+
currentTarget: SVGPathElement;
|
|
82
|
+
}, datum: Datum, index: number) => void;
|
|
83
|
+
onpointerleave: (event: Event & {
|
|
84
|
+
currentTarget: SVGPathElement;
|
|
85
|
+
}, datum: Datum, index: number) => void;
|
|
86
|
+
onpointermove: (event: Event & {
|
|
87
|
+
currentTarget: SVGPathElement;
|
|
88
|
+
}, datum: Datum, index: number) => void;
|
|
89
|
+
onpointerover: (event: Event & {
|
|
90
|
+
currentTarget: SVGPathElement;
|
|
91
|
+
}, datum: Datum, index: number) => void;
|
|
92
|
+
onpointerout: (event: Event & {
|
|
93
|
+
currentTarget: SVGPathElement;
|
|
94
|
+
}, datum: Datum, index: number) => void;
|
|
95
|
+
ondrag: (event: Event & {
|
|
96
|
+
currentTarget: SVGPathElement;
|
|
97
|
+
}, datum: Datum, index: number) => void;
|
|
98
|
+
ondrop: (event: Event & {
|
|
99
|
+
currentTarget: SVGPathElement;
|
|
100
|
+
}, datum: Datum, index: number) => void;
|
|
101
|
+
ondragstart: (event: Event & {
|
|
102
|
+
currentTarget: SVGPathElement;
|
|
103
|
+
}, datum: Datum, index: number) => void;
|
|
104
|
+
ondragenter: (event: Event & {
|
|
105
|
+
currentTarget: SVGPathElement;
|
|
106
|
+
}, datum: Datum, index: number) => void;
|
|
107
|
+
ondragleave: (event: Event & {
|
|
108
|
+
currentTarget: SVGPathElement;
|
|
109
|
+
}, datum: Datum, index: number) => void;
|
|
110
|
+
ondragover: (event: Event & {
|
|
111
|
+
currentTarget: SVGPathElement;
|
|
112
|
+
}, datum: Datum, index: number) => void;
|
|
113
|
+
ondragend: (event: Event & {
|
|
114
|
+
currentTarget: SVGPathElement;
|
|
115
|
+
}, datum: Datum, index: number) => void;
|
|
116
|
+
ontouchstart: (event: Event & {
|
|
117
|
+
currentTarget: SVGPathElement;
|
|
118
|
+
}, datum: Datum, index: number) => void;
|
|
119
|
+
ontouchmove: (event: Event & {
|
|
120
|
+
currentTarget: SVGPathElement;
|
|
121
|
+
}, datum: Datum, index: number) => void;
|
|
122
|
+
ontouchend: (event: Event & {
|
|
123
|
+
currentTarget: SVGPathElement;
|
|
124
|
+
}, datum: Datum, index: number) => void;
|
|
125
|
+
ontouchcancel: (event: Event & {
|
|
126
|
+
currentTarget: SVGPathElement;
|
|
127
|
+
}, datum: Datum, index: number) => void;
|
|
128
|
+
oncontextmenu: (event: Event & {
|
|
129
|
+
currentTarget: SVGPathElement;
|
|
130
|
+
}, datum: Datum, index: number) => void;
|
|
131
|
+
onwheel: (event: Event & {
|
|
132
|
+
currentTarget: SVGPathElement;
|
|
133
|
+
}, datum: Datum, index: number) => void;
|
|
134
|
+
class: string;
|
|
135
|
+
style: string;
|
|
136
|
+
cursor: import("../types/index.js").ConstantAccessor<import("csstype").Property.Cursor, Datum>;
|
|
137
|
+
title: import("../types/index.js").ConstantAccessor<string, Datum>;
|
|
138
|
+
}> & LinkableMarkProps<Datum> & {
|
|
139
|
+
/** the input data array */
|
|
140
|
+
data?: Datum[];
|
|
141
|
+
/** the horizontal position channel */
|
|
142
|
+
x?: ChannelAccessor<Datum>;
|
|
143
|
+
/** the vertical position channel */
|
|
144
|
+
y?: ChannelAccessor<Datum>;
|
|
145
|
+
/** the grouping channel; separate Voronoi diagrams per group */
|
|
146
|
+
z?: ChannelAccessor<Datum>;
|
|
147
|
+
/** Render using a canvas element instead of SVG paths. */
|
|
148
|
+
canvas?: boolean;
|
|
149
|
+
};
|
|
150
|
+
exports: {};
|
|
151
|
+
bindings: "";
|
|
152
|
+
slots: {};
|
|
153
|
+
events: {};
|
|
154
|
+
};
|
|
155
|
+
declare class __sveltets_Render<Datum = DataRecord> {
|
|
156
|
+
props(): ReturnType<typeof $$render<Datum>>['props'];
|
|
157
|
+
events(): ReturnType<typeof $$render<Datum>>['events'];
|
|
158
|
+
slots(): ReturnType<typeof $$render<Datum>>['slots'];
|
|
159
|
+
bindings(): "";
|
|
160
|
+
exports(): {};
|
|
161
|
+
}
|
|
162
|
+
interface $$IsomorphicComponent {
|
|
163
|
+
new <Datum = DataRecord>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Datum>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Datum>['props']>, ReturnType<__sveltets_Render<Datum>['events']>, ReturnType<__sveltets_Render<Datum>['slots']>> & {
|
|
164
|
+
$$bindings?: ReturnType<__sveltets_Render<Datum>['bindings']>;
|
|
165
|
+
} & ReturnType<__sveltets_Render<Datum>['exports']>;
|
|
166
|
+
<Datum = DataRecord>(internal: unknown, props: ReturnType<__sveltets_Render<Datum>['props']> & {}): ReturnType<__sveltets_Render<Datum>['exports']>;
|
|
167
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
168
|
+
}
|
|
169
|
+
/** Renders individual Voronoi cells, one per data point, allowing per-cell styling. */
|
|
170
|
+
declare const Voronoi: $$IsomorphicComponent;
|
|
171
|
+
type Voronoi<Datum = DataRecord> = InstanceType<typeof Voronoi<Datum>>;
|
|
172
|
+
export default Voronoi;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
Renders the full Voronoi diagram as a single SVG path.
|
|
3
|
+
-->
|
|
4
|
+
<script lang="ts" generics="Datum = DataRecord">
|
|
5
|
+
interface VoronoiMeshMarkProps extends BaseMarkProps<Datum> {
|
|
6
|
+
/** the input data array */
|
|
7
|
+
data?: Datum[];
|
|
8
|
+
/** the horizontal position channel */
|
|
9
|
+
x?: ChannelAccessor<Datum>;
|
|
10
|
+
/** the vertical position channel */
|
|
11
|
+
y?: ChannelAccessor<Datum>;
|
|
12
|
+
/** the grouping channel; separate diagrams per group */
|
|
13
|
+
z?: ChannelAccessor<Datum>;
|
|
14
|
+
/** Render using a canvas element instead of SVG paths. */
|
|
15
|
+
canvas?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
import { Delaunay } from 'd3-delaunay';
|
|
19
|
+
import type {
|
|
20
|
+
DataRecord,
|
|
21
|
+
BaseMarkProps,
|
|
22
|
+
ChannelAccessor,
|
|
23
|
+
MarkType,
|
|
24
|
+
ScaledDataRecord
|
|
25
|
+
} from '../types/index.js';
|
|
26
|
+
import { groupFacetsAndZ } from '../helpers/group.js';
|
|
27
|
+
import { recordizeXY } from '../transforms/recordize.js';
|
|
28
|
+
import Mark from '../Mark.svelte';
|
|
29
|
+
import PathGroup from './helpers/PathGroup.svelte';
|
|
30
|
+
import { usePlot } from '../hooks/usePlot.svelte.js';
|
|
31
|
+
import { getPlotDefaults } from '../hooks/plotDefaults.js';
|
|
32
|
+
|
|
33
|
+
const DEFAULTS = {
|
|
34
|
+
...getPlotDefaults().voronoiMesh
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
let markProps: VoronoiMeshMarkProps = $props();
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
data = [] as Datum[],
|
|
41
|
+
class: className = 'voronoi-mesh',
|
|
42
|
+
canvas = false,
|
|
43
|
+
...options
|
|
44
|
+
}: VoronoiMeshMarkProps = $derived({ ...DEFAULTS, ...markProps });
|
|
45
|
+
|
|
46
|
+
const args = $derived(
|
|
47
|
+
recordizeXY({
|
|
48
|
+
data: data as any[],
|
|
49
|
+
...options
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const plot = usePlot();
|
|
54
|
+
|
|
55
|
+
function computeMeshPaths(scaledData: ScaledDataRecord[]) {
|
|
56
|
+
const x0 = plot.options.marginLeft;
|
|
57
|
+
const y0 = plot.options.marginTop;
|
|
58
|
+
const x1 = x0 + plot.facetWidth;
|
|
59
|
+
const y1 = y0 + plot.facetHeight;
|
|
60
|
+
if (!(x1 > x0) || !(y1 > y0)) return [];
|
|
61
|
+
|
|
62
|
+
const scaledByDatum = new Map(scaledData.map((d) => [d.datum, d]));
|
|
63
|
+
const meshes: { path: string; datum: ScaledDataRecord }[] = [];
|
|
64
|
+
|
|
65
|
+
groupFacetsAndZ(
|
|
66
|
+
scaledData.map((d) => d.datum),
|
|
67
|
+
args,
|
|
68
|
+
(groupItems) => {
|
|
69
|
+
const groupScaled = groupItems
|
|
70
|
+
.map((d) => scaledByDatum.get(d))
|
|
71
|
+
.filter(
|
|
72
|
+
(d): d is ScaledDataRecord =>
|
|
73
|
+
d !== undefined &&
|
|
74
|
+
d.valid &&
|
|
75
|
+
Number.isFinite(d.x as number) &&
|
|
76
|
+
Number.isFinite(d.y as number)
|
|
77
|
+
);
|
|
78
|
+
if (groupScaled.length < 2) return;
|
|
79
|
+
const delaunay = Delaunay.from(
|
|
80
|
+
groupScaled,
|
|
81
|
+
(d) => d.x as number,
|
|
82
|
+
(d) => d.y as number
|
|
83
|
+
);
|
|
84
|
+
const voronoi = delaunay.voronoi([x0, y0, x1, y1]);
|
|
85
|
+
const path = voronoi.render();
|
|
86
|
+
if (path) meshes.push({ path, datum: groupScaled[0] });
|
|
87
|
+
},
|
|
88
|
+
false
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
return meshes;
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<Mark
|
|
96
|
+
type={'voronoiMesh' as MarkType}
|
|
97
|
+
channels={['x', 'y', 'fill', 'stroke', 'strokeOpacity', 'fillOpacity', 'opacity']}
|
|
98
|
+
defaults={{ fill: 'none', stroke: 'currentColor' }}
|
|
99
|
+
{...args}>
|
|
100
|
+
{#snippet children({ scaledData, usedScales })}
|
|
101
|
+
<PathGroup
|
|
102
|
+
paths={computeMeshPaths(scaledData)}
|
|
103
|
+
{args}
|
|
104
|
+
{className}
|
|
105
|
+
{usedScales}
|
|
106
|
+
{plot}
|
|
107
|
+
{canvas} />
|
|
108
|
+
{/snippet}
|
|
109
|
+
</Mark>
|