svelteplot 0.13.0 → 0.14.0-pr-545.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/helpers/group.d.ts +1 -1
- package/dist/helpers/group.js +3 -3
- package/dist/marks/ColorLegend.svelte +5 -1
- package/dist/marks/Contour.svelte +21 -30
- package/dist/marks/Contour.svelte.d.ts +2 -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 +552 -0
- package/dist/marks/Density.svelte.d.ts +108 -0
- package/dist/marks/Hull.svelte +103 -0
- package/dist/marks/Hull.svelte.d.ts +175 -0
- 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/DensityCanvas.svelte +118 -0
- package/dist/marks/helpers/DensityCanvas.svelte.d.ts +18 -0
- package/dist/marks/helpers/GeoPathCanvas.svelte +130 -0
- package/dist/marks/helpers/GeoPathCanvas.svelte.d.ts +24 -0
- package/dist/marks/helpers/GeoPathGroup.svelte +104 -0
- package/dist/marks/helpers/GeoPathGroup.svelte.d.ts +37 -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/index.d.ts +7 -1
- package/dist/marks/index.js +7 -1
- package/dist/types/mark.d.ts +1 -1
- package/dist/types/plot.d.ts +25 -1
- package/package.json +1 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
Renders the convex hull of data points. Supports grouping by z/fill/stroke
|
|
3
|
+
to draw separate hulls per group.
|
|
4
|
+
-->
|
|
5
|
+
<script lang="ts" generics="Datum = DataRecord">
|
|
6
|
+
interface HullMarkProps extends BaseMarkProps<Datum> {
|
|
7
|
+
/** the input data array */
|
|
8
|
+
data?: Datum[];
|
|
9
|
+
/** the horizontal position channel */
|
|
10
|
+
x?: ChannelAccessor<Datum>;
|
|
11
|
+
/** the vertical position channel */
|
|
12
|
+
y?: ChannelAccessor<Datum>;
|
|
13
|
+
/** the grouping channel; separate hulls per group */
|
|
14
|
+
z?: ChannelAccessor<Datum>;
|
|
15
|
+
/** Render using a canvas element instead of SVG paths. */
|
|
16
|
+
canvas?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
import { Delaunay } from 'd3-delaunay';
|
|
20
|
+
import type {
|
|
21
|
+
DataRecord,
|
|
22
|
+
BaseMarkProps,
|
|
23
|
+
ChannelAccessor,
|
|
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 Mark from '../Mark.svelte';
|
|
30
|
+
import PathGroup from './helpers/PathGroup.svelte';
|
|
31
|
+
import { usePlot } from '../hooks/usePlot.svelte.js';
|
|
32
|
+
import { getPlotDefaults } from '../hooks/plotDefaults.js';
|
|
33
|
+
|
|
34
|
+
const DEFAULTS = {
|
|
35
|
+
...getPlotDefaults().hull
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
let markProps: HullMarkProps = $props();
|
|
39
|
+
|
|
40
|
+
const {
|
|
41
|
+
data = [] as Datum[],
|
|
42
|
+
class: className = 'hull',
|
|
43
|
+
canvas = false,
|
|
44
|
+
...options
|
|
45
|
+
}: HullMarkProps = $derived({ ...DEFAULTS, ...markProps });
|
|
46
|
+
|
|
47
|
+
const args = $derived(
|
|
48
|
+
recordizeXY({
|
|
49
|
+
data: data as any[],
|
|
50
|
+
...options
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const plot = usePlot();
|
|
55
|
+
|
|
56
|
+
function computeHulls(scaledData: ScaledDataRecord[]) {
|
|
57
|
+
const scaledByDatum = new Map(scaledData.map((d) => [d.datum, d]));
|
|
58
|
+
const hulls: { path: string; datum: ScaledDataRecord }[] = [];
|
|
59
|
+
|
|
60
|
+
groupFacetsAndZ(
|
|
61
|
+
scaledData.map((d) => d.datum),
|
|
62
|
+
args,
|
|
63
|
+
(groupItems) => {
|
|
64
|
+
const groupScaled = groupItems
|
|
65
|
+
.map((d) => scaledByDatum.get(d))
|
|
66
|
+
.filter(
|
|
67
|
+
(d): d is ScaledDataRecord =>
|
|
68
|
+
d !== undefined &&
|
|
69
|
+
d.valid &&
|
|
70
|
+
Number.isFinite(d.x as number) &&
|
|
71
|
+
Number.isFinite(d.y as number)
|
|
72
|
+
);
|
|
73
|
+
if (groupScaled.length < 2) return;
|
|
74
|
+
const delaunay = Delaunay.from(
|
|
75
|
+
groupScaled,
|
|
76
|
+
(d) => d.x as number,
|
|
77
|
+
(d) => d.y as number
|
|
78
|
+
);
|
|
79
|
+
const path = delaunay.renderHull();
|
|
80
|
+
if (path) hulls.push({ path, datum: groupScaled[0] });
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return hulls;
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<Mark
|
|
89
|
+
type={'hull' as MarkType}
|
|
90
|
+
channels={['x', 'y', 'fill', 'opacity', 'stroke', 'fillOpacity', 'strokeOpacity']}
|
|
91
|
+
defaults={{ fill: 'none', stroke: 'currentColor' }}
|
|
92
|
+
{...args}>
|
|
93
|
+
{#snippet children({ scaledData, usedScales })}
|
|
94
|
+
<PathGroup
|
|
95
|
+
paths={computeHulls(scaledData)}
|
|
96
|
+
{args}
|
|
97
|
+
{className}
|
|
98
|
+
{usedScales}
|
|
99
|
+
{plot}
|
|
100
|
+
defaultStrokeWidth={1.5}
|
|
101
|
+
{canvas} />
|
|
102
|
+
{/snippet}
|
|
103
|
+
</Mark>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type { DataRecord, ChannelAccessor } 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
|
+
}> & {
|
|
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 hulls 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
|
+
/**
|
|
170
|
+
* Renders the convex hull of data points. Supports grouping by z/fill/stroke
|
|
171
|
+
* to draw separate hulls per group.
|
|
172
|
+
*/
|
|
173
|
+
declare const Hull: $$IsomorphicComponent;
|
|
174
|
+
type Hull<Datum = DataRecord> = InstanceType<typeof Hull<Datum>>;
|
|
175
|
+
export default Hull;
|
|
@@ -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>
|