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
package/dist/helpers/group.d.ts
CHANGED
|
@@ -3,4 +3,4 @@ import type { Channels } from '../types/index.js';
|
|
|
3
3
|
* Groups the data by the fx, fy and z channels and calls the reduce function
|
|
4
4
|
* for each group. Returns the new channels to be added in the transform.
|
|
5
5
|
*/
|
|
6
|
-
export declare function groupFacetsAndZ<T>(items: T[], channels: Channels<T>, reduce: (items: T[], groupProps?: Record<string, unknown>) => any): any;
|
|
6
|
+
export declare function groupFacetsAndZ<T>(items: T[], channels: Channels<T>, reduce: (items: T[], groupProps?: Record<string, unknown>) => any, implicitColorZ?: boolean): any;
|
package/dist/helpers/group.js
CHANGED
|
@@ -4,15 +4,15 @@ import { groups as d3Groups } from 'd3-array';
|
|
|
4
4
|
* Groups the data by the fx, fy and z channels and calls the reduce function
|
|
5
5
|
* for each group. Returns the new channels to be added in the transform.
|
|
6
6
|
*/
|
|
7
|
-
export function groupFacetsAndZ(items, channels, reduce) {
|
|
7
|
+
export function groupFacetsAndZ(items, channels, reduce, implicitColorZ = true) {
|
|
8
8
|
const groupBy = ['fx', 'fy', 'z'].map((groupChannel) => {
|
|
9
9
|
let groupByChannel = null;
|
|
10
10
|
if (groupChannel === 'z') {
|
|
11
11
|
if (channels.z)
|
|
12
12
|
groupByChannel = 'z';
|
|
13
|
-
else if (channels.fill)
|
|
13
|
+
else if (channels.fill && implicitColorZ)
|
|
14
14
|
groupByChannel = 'fill';
|
|
15
|
-
else if (channels.stroke)
|
|
15
|
+
else if (channels.stroke && implicitColorZ)
|
|
16
16
|
groupByChannel = 'stroke';
|
|
17
17
|
}
|
|
18
18
|
else if (channels[groupChannel]) {
|
|
@@ -19,8 +19,12 @@
|
|
|
19
19
|
|
|
20
20
|
const DEFAULTS = getPlotDefaults();
|
|
21
21
|
|
|
22
|
-
const legendTitle = $derived(plot.options.color.label ?? plot.scales.color?.autoTitle);
|
|
23
22
|
const scaleType = $derived(plot.scales.color.type);
|
|
23
|
+
const legendTitle = $derived(
|
|
24
|
+
scaleType === 'categorical'
|
|
25
|
+
? (plot.options.color.label ?? '')
|
|
26
|
+
: (plot.options.color.label ?? plot.scales.color?.autoTitle)
|
|
27
|
+
);
|
|
24
28
|
const tickFormat = $derived(
|
|
25
29
|
typeof plot.options.color?.tickFormat === 'function'
|
|
26
30
|
? plot.options.color.tickFormat
|
|
@@ -117,6 +117,8 @@
|
|
|
117
117
|
strokeMiterlimit?: number;
|
|
118
118
|
clipPath?: string;
|
|
119
119
|
class?: string;
|
|
120
|
+
/** Render using a canvas element instead of SVG paths. */
|
|
121
|
+
canvas?: boolean;
|
|
120
122
|
/** the horizontal facet channel */
|
|
121
123
|
fx?: ChannelAccessor<Datum>;
|
|
122
124
|
/** the vertical facet channel */
|
|
@@ -136,6 +138,7 @@
|
|
|
136
138
|
import { contours } from 'd3-contour';
|
|
137
139
|
import { geoPath } from 'd3-geo';
|
|
138
140
|
import Mark from '../Mark.svelte';
|
|
141
|
+
import GeoPathGroup from './helpers/GeoPathGroup.svelte';
|
|
139
142
|
import { usePlot } from '../hooks/usePlot.svelte.js';
|
|
140
143
|
import {
|
|
141
144
|
interpolateNone,
|
|
@@ -195,6 +198,7 @@
|
|
|
195
198
|
strokeMiterlimit = 1,
|
|
196
199
|
clipPath,
|
|
197
200
|
class: className = '',
|
|
201
|
+
canvas = false,
|
|
198
202
|
...options
|
|
199
203
|
}: ContourMarkProps = $derived({ ...DEFAULTS, ...markProps });
|
|
200
204
|
|
|
@@ -491,27 +495,6 @@
|
|
|
491
495
|
return tz;
|
|
492
496
|
}
|
|
493
497
|
|
|
494
|
-
/** Resolve a fill or stroke prop for a given contour level. */
|
|
495
|
-
function resolveColor(prop: string | undefined, threshold: number): string {
|
|
496
|
-
if (prop === 'value') {
|
|
497
|
-
return (plot.scales.color?.fn(threshold) as string) ?? 'currentColor';
|
|
498
|
-
}
|
|
499
|
-
return prop ?? 'none';
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/** Build the inline style string for a single contour path. */
|
|
503
|
-
function contourStyle(threshold: number): string {
|
|
504
|
-
const parts: string[] = [];
|
|
505
|
-
parts.push(`fill:${resolveColor(fill, threshold)}`);
|
|
506
|
-
parts.push(`stroke:${resolveColor(effectiveStroke, threshold)}`);
|
|
507
|
-
if (strokeWidth != null) parts.push(`stroke-width:${strokeWidth}`);
|
|
508
|
-
if (strokeOpacity != null) parts.push(`stroke-opacity:${strokeOpacity}`);
|
|
509
|
-
if (fillOpacity != null) parts.push(`fill-opacity:${fillOpacity}`);
|
|
510
|
-
if (opacity != null) parts.push(`opacity:${opacity}`);
|
|
511
|
-
if (strokeMiterlimit != null) parts.push(`stroke-miterlimit:${strokeMiterlimit}`);
|
|
512
|
-
return parts.join(';');
|
|
513
|
-
}
|
|
514
|
-
|
|
515
498
|
const path = geoPath();
|
|
516
499
|
|
|
517
500
|
/**
|
|
@@ -680,14 +663,22 @@
|
|
|
680
663
|
{...options}
|
|
681
664
|
{...markChannelProps}>
|
|
682
665
|
{#snippet children({ scaledData }: { scaledData: ScaledDataRecord[] })}
|
|
683
|
-
<
|
|
684
|
-
{
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
{
|
|
691
|
-
|
|
666
|
+
<GeoPathGroup
|
|
667
|
+
{scaledData}
|
|
668
|
+
{path}
|
|
669
|
+
geomKey={GEOM}
|
|
670
|
+
colorKeyword="value"
|
|
671
|
+
{fill}
|
|
672
|
+
stroke={effectiveStroke}
|
|
673
|
+
{strokeWidth}
|
|
674
|
+
{strokeOpacity}
|
|
675
|
+
{fillOpacity}
|
|
676
|
+
{opacity}
|
|
677
|
+
{strokeMiterlimit}
|
|
678
|
+
{clipPath}
|
|
679
|
+
className={className || undefined}
|
|
680
|
+
ariaLabel="contour"
|
|
681
|
+
{canvas}
|
|
682
|
+
{plot} />
|
|
692
683
|
{/snippet}
|
|
693
684
|
</Mark>
|
|
@@ -100,6 +100,8 @@ declare function $$render<Datum extends DataRow>(): {
|
|
|
100
100
|
strokeMiterlimit?: number;
|
|
101
101
|
clipPath?: string;
|
|
102
102
|
class?: string;
|
|
103
|
+
/** Render using a canvas element instead of SVG paths. */
|
|
104
|
+
canvas?: boolean;
|
|
103
105
|
/** the horizontal facet channel */
|
|
104
106
|
fx?: ChannelAccessor<Datum>;
|
|
105
107
|
/** the vertical facet channel */
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
Renders individual Delaunay triangulation edges as separate paths,
|
|
3
|
+
allowing per-edge styling based on the source data point.
|
|
4
|
+
-->
|
|
5
|
+
<script lang="ts" generics="Datum = DataRecord">
|
|
6
|
+
interface DelaunayLinkMarkProps extends BaseMarkProps<Datum>, LinkableMarkProps<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 triangulations 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
|
+
LinkableMarkProps,
|
|
25
|
+
MarkType,
|
|
26
|
+
ScaledDataRecord
|
|
27
|
+
} from '../types/index.js';
|
|
28
|
+
import { groupFacetsAndZ } from '../helpers/group.js';
|
|
29
|
+
import { recordizeXY } from '../transforms/recordize.js';
|
|
30
|
+
import { sort } from '../index.js';
|
|
31
|
+
import Mark from '../Mark.svelte';
|
|
32
|
+
import PathItems from './helpers/PathItems.svelte';
|
|
33
|
+
import { usePlot } from '../hooks/usePlot.svelte.js';
|
|
34
|
+
import { getPlotDefaults } from '../hooks/plotDefaults.js';
|
|
35
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
36
|
+
|
|
37
|
+
const DEFAULTS = {
|
|
38
|
+
...getPlotDefaults().delaunayLink
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
let markProps: DelaunayLinkMarkProps = $props();
|
|
42
|
+
|
|
43
|
+
const {
|
|
44
|
+
data = [] as Datum[],
|
|
45
|
+
class: className = 'delaunay-link',
|
|
46
|
+
canvas = false,
|
|
47
|
+
...options
|
|
48
|
+
}: DelaunayLinkMarkProps = $derived({ ...DEFAULTS, ...markProps });
|
|
49
|
+
|
|
50
|
+
const args = $derived(
|
|
51
|
+
sort(
|
|
52
|
+
recordizeXY({
|
|
53
|
+
data: data as any[],
|
|
54
|
+
...options
|
|
55
|
+
})
|
|
56
|
+
)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const plot = usePlot();
|
|
60
|
+
|
|
61
|
+
function computeEdges(scaledData: ScaledDataRecord[]) {
|
|
62
|
+
const scaledByDatum = new Map(scaledData.map((d) => [d.datum, d]));
|
|
63
|
+
const results: { 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
|
+
|
|
80
|
+
const delaunay = Delaunay.from(
|
|
81
|
+
groupScaled,
|
|
82
|
+
(d) => d.x as number,
|
|
83
|
+
(d) => d.y as number
|
|
84
|
+
);
|
|
85
|
+
const { halfedges, triangles } = delaunay;
|
|
86
|
+
const seen = new SvelteSet<string>();
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < halfedges.length; i++) {
|
|
89
|
+
const j = halfedges[i];
|
|
90
|
+
if (j < i && j !== -1) continue;
|
|
91
|
+
const a = triangles[i];
|
|
92
|
+
const b = triangles[i % 3 === 2 ? i - 2 : i + 1];
|
|
93
|
+
const key = a < b ? `${a},${b}` : `${b},${a}`;
|
|
94
|
+
if (seen.has(key)) continue;
|
|
95
|
+
seen.add(key);
|
|
96
|
+
|
|
97
|
+
const p = groupScaled[a];
|
|
98
|
+
const q = groupScaled[b];
|
|
99
|
+
results.push({
|
|
100
|
+
datum: p,
|
|
101
|
+
path: `M${p.x},${p.y}L${q.x},${q.y}`
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
false
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
return results;
|
|
109
|
+
}
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<Mark
|
|
113
|
+
type={'delaunayLink' as MarkType}
|
|
114
|
+
channels={['x', 'y', 'fill', 'opacity', 'stroke', 'fillOpacity', 'strokeOpacity']}
|
|
115
|
+
defaults={{ fill: 'none', stroke: 'currentColor' }}
|
|
116
|
+
{...args}>
|
|
117
|
+
{#snippet children({ mark, usedScales, scaledData })}
|
|
118
|
+
<PathItems
|
|
119
|
+
paths={computeEdges(scaledData)}
|
|
120
|
+
{args}
|
|
121
|
+
{options}
|
|
122
|
+
{className}
|
|
123
|
+
{usedScales}
|
|
124
|
+
{plot}
|
|
125
|
+
{canvas} />
|
|
126
|
+
{/snippet}
|
|
127
|
+
</Mark>
|
|
@@ -0,0 +1,175 @@
|
|
|
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 triangulations 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 individual Delaunay triangulation edges as separate paths,
|
|
171
|
+
* allowing per-edge styling based on the source data point.
|
|
172
|
+
*/
|
|
173
|
+
declare const DelaunayLink: $$IsomorphicComponent;
|
|
174
|
+
type DelaunayLink<Datum = DataRecord> = InstanceType<typeof DelaunayLink<Datum>>;
|
|
175
|
+
export default DelaunayLink;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<!-- @component
|
|
2
|
+
Renders the full Delaunay triangulation as a single SVG path.
|
|
3
|
+
-->
|
|
4
|
+
<script lang="ts" generics="Datum = DataRecord">
|
|
5
|
+
interface DelaunayMeshMarkProps 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 triangulations 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().delaunayMesh
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
let markProps: DelaunayMeshMarkProps = $props();
|
|
38
|
+
|
|
39
|
+
const {
|
|
40
|
+
data = [] as Datum[],
|
|
41
|
+
class: className = 'delaunay-mesh',
|
|
42
|
+
canvas = false,
|
|
43
|
+
...options
|
|
44
|
+
}: DelaunayMeshMarkProps = $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 scaledByDatum = new Map(scaledData.map((d) => [d.datum, d]));
|
|
57
|
+
const meshes: { path: string; datum: ScaledDataRecord }[] = [];
|
|
58
|
+
|
|
59
|
+
groupFacetsAndZ(
|
|
60
|
+
scaledData.map((d) => d.datum),
|
|
61
|
+
args,
|
|
62
|
+
(groupItems) => {
|
|
63
|
+
const groupScaled = groupItems
|
|
64
|
+
.map((d) => scaledByDatum.get(d))
|
|
65
|
+
.filter(
|
|
66
|
+
(d): d is ScaledDataRecord =>
|
|
67
|
+
d !== undefined &&
|
|
68
|
+
d.valid &&
|
|
69
|
+
Number.isFinite(d.x as number) &&
|
|
70
|
+
Number.isFinite(d.y as number)
|
|
71
|
+
);
|
|
72
|
+
if (groupScaled.length < 2) return;
|
|
73
|
+
const delaunay = Delaunay.from(
|
|
74
|
+
groupScaled,
|
|
75
|
+
(d) => d.x as number,
|
|
76
|
+
(d) => d.y as number
|
|
77
|
+
);
|
|
78
|
+
const path = delaunay.render();
|
|
79
|
+
if (path) meshes.push({ path, datum: groupScaled[0] });
|
|
80
|
+
},
|
|
81
|
+
false
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return meshes;
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<Mark
|
|
89
|
+
type={'delaunayMesh' as MarkType}
|
|
90
|
+
channels={['x', 'y', 'fill', 'stroke', 'strokeOpacity', 'fillOpacity', 'opacity']}
|
|
91
|
+
defaults={{ fill: 'none', stroke: 'currentColor' }}
|
|
92
|
+
{...args}>
|
|
93
|
+
{#snippet children({ scaledData, usedScales })}
|
|
94
|
+
<PathGroup
|
|
95
|
+
paths={computeMeshPaths(scaledData)}
|
|
96
|
+
{args}
|
|
97
|
+
{className}
|
|
98
|
+
{usedScales}
|
|
99
|
+
{plot}
|
|
100
|
+
{canvas} />
|
|
101
|
+
{/snippet}
|
|
102
|
+
</Mark>
|