layerchart 2.0.0-next.57 → 2.0.0-next.59

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.
Files changed (28) hide show
  1. package/dist/components/AnnotationLine.svelte +112 -66
  2. package/dist/components/AnnotationLine.svelte.d.ts +10 -2
  3. package/dist/components/AnnotationPoint.svelte +97 -23
  4. package/dist/components/AnnotationPoint.svelte.d.ts +8 -1
  5. package/dist/components/GeoPath.svelte +4 -4
  6. package/dist/components/Legend.svelte +1 -0
  7. package/dist/components/Link.svelte +261 -75
  8. package/dist/components/Link.svelte.d.ts +69 -26
  9. package/dist/components/Text.svelte +1 -1
  10. package/dist/components/Voronoi.svelte +35 -6
  11. package/dist/components/Voronoi.svelte.d.ts +9 -0
  12. package/dist/components/charts/__screenshots__/BarChart.svelte.test.ts/BarChart-separate-data-per-series-should-render-stacked-series-with-separate-data-arrays-1.png +0 -0
  13. package/dist/components/charts/__screenshots__/BarChart.svelte.test.ts/BarChart-separate-data-per-series-should-render-stacked-series-with-separate-data-arrays-2.png +0 -0
  14. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-series-header-for-multi-series-1.png +0 -0
  15. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-series-header-for-multi-series-2.png +0 -0
  16. package/dist/components/index.d.ts +0 -2
  17. package/dist/components/index.js +0 -2
  18. package/dist/components/tooltip/TooltipContext.svelte +39 -10
  19. package/dist/components/tooltip/TooltipContext.svelte.d.ts +14 -0
  20. package/dist/states/brush.svelte.d.ts +1 -1
  21. package/dist/states/chart.svelte.js +24 -8
  22. package/dist/states/chart.svelte.test.js +181 -0
  23. package/dist/utils/linkUtils.d.ts +42 -0
  24. package/dist/utils/{connectorUtils.js → linkUtils.js} +56 -6
  25. package/package.json +1 -1
  26. package/dist/components/Connector.svelte +0 -167
  27. package/dist/components/Connector.svelte.d.ts +0 -56
  28. package/dist/utils/connectorUtils.d.ts +0 -34
@@ -3,116 +3,169 @@
3
3
  import type { Without } from '../utils/types.js';
4
4
  import type { MotionNoneOption, MotionTweenOption } from '../utils/motion.svelte.js';
5
5
  import { curveBumpX, curveBumpY, type CurveFactory } from 'd3-shape';
6
+ import type { LinkSweep, LinkType } from '../utils/linkUtils.js';
7
+ import type { PathProps, PathPropsWithoutHTML } from './Path.svelte';
8
+ import type { Accessor } from '../utils/common.js';
6
9
 
7
10
  export type LinkPropsWithoutHTML = {
8
- // Override what is used from context
11
+ /**
12
+ * Source `x` coordinate. Accepts a `number` (pixel value), a string property
13
+ * name, or `(d) => value` accessor. Strings/functions use `ctx.xScale`.
14
+ */
15
+ x1?: Accessor;
16
+ /**
17
+ * Source `y` coordinate. Accepts a `number`, property name, or
18
+ * `(d) => value` accessor. Strings/functions use `ctx.yScale`.
19
+ */
20
+ y1?: Accessor;
21
+ /** Target `x` coordinate. See `x1`. */
22
+ x2?: Accessor;
23
+ /** Target `y` coordinate. See `y1`. */
24
+ y2?: Accessor;
25
+
26
+ /**
27
+ * Data for the link. In array mode (when `x1`/`y1`/`x2`/`y2` are strings or
28
+ * functions) this should be an array of rows; one path is rendered per row.
29
+ * In hierarchy/sankey mode, a single link object (`{source, target, ...}`).
30
+ * Defaults to `ctx.data` in array mode.
31
+ */
9
32
  data?: any;
10
33
 
11
34
  /**
12
- * Update source and target accessors to be compatible with d3-sankey. see: https://github.com/d3/d3-sankey#sankeyLinkHorizontal
35
+ * Update source and target accessors to be compatible with d3-sankey. see:
36
+ * https://github.com/d3/d3-sankey#sankeyLinkHorizontal
13
37
  *
14
38
  * @default false
15
39
  */
16
40
  sankey?: boolean;
41
+ /** Accessor returning the source node from `data` (hierarchy/sankey mode). */
17
42
  source?: (d: any) => any;
43
+ /** Accessor returning the target node from `data` (hierarchy/sankey mode). */
18
44
  target?: (d: any) => any;
19
-
20
- /**
21
- * Convenient property to swap x/y accessor logic
22
- */
23
- orientation?: 'vertical' | 'horizontal';
24
-
45
+ /** Accessor returning `x` for a node (hierarchy/sankey mode). */
25
46
  x?: (d: any) => any;
47
+ /** Accessor returning `y` for a node (hierarchy/sankey mode). */
26
48
  y?: (d: any) => any;
27
- curve?: CurveFactory;
28
49
 
29
50
  /**
30
- * Marker to attach to both start and end points of the line
51
+ * The link path type.
52
+ *
53
+ * Set to `'d3'` to use a D3 curve function via the `curve` prop.
54
+ *
55
+ * @default 'd3'
31
56
  */
32
- marker?: MarkerOptions;
57
+ type?: LinkType;
33
58
 
34
59
  /**
35
- * Marker to attach to the middle point of the line
60
+ * Corner radius (used by `'beveled'` and `'rounded'`).
61
+ *
62
+ * @default 20
36
63
  */
37
- markerMid?: MarkerOptions;
64
+ radius?: number;
38
65
 
39
66
  /**
40
- * Marker to attach to the start point of the line
67
+ * Bend angle in degrees for the `'swoop'` link type.
68
+ *
69
+ * @default 22.5
41
70
  */
42
- markerStart?: MarkerOptions;
71
+ bend?: number;
72
+
73
+ /** D3 curve function (used when `type === 'd3'`). */
74
+ curve?: CurveFactory;
75
+
76
+ /** Sweep direction for preset types and d3 paths. */
77
+ sweep?: LinkSweep;
43
78
 
44
79
  /**
45
- * Marker to attach to the end point of the line
80
+ * Natural flow direction (affects default curve and axis-dependent step
81
+ * curves). Also toggles x/y accessor logic in hierarchy mode.
46
82
  */
47
- markerEnd?: MarkerOptions;
83
+ orientation?: 'vertical' | 'horizontal';
48
84
 
49
85
  /**
50
- * Apply explicit coordinates to the line. Useful when dealing with
51
- * force simulation links.
86
+ * Interpret coords as polar (`x` = angle, `y` = radius) and render in
87
+ * radial space. Defaults to `ctx.radial` when unset.
52
88
  */
53
- explicitCoords?: {
54
- x1: number;
55
- y1: number;
56
- x2: number;
57
- y2: number;
58
- };
89
+ radial?: boolean;
90
+
91
+ /** Marker at both start and end points. */
92
+ marker?: MarkerOptions;
93
+ /** Marker at the middle point. */
94
+ markerMid?: MarkerOptions;
95
+ /** Marker at the start point. */
96
+ markerStart?: MarkerOptions;
97
+ /** Marker at the end point. */
98
+ markerEnd?: MarkerOptions;
59
99
 
60
100
  motion?: MotionTweenOption | MotionNoneOption;
61
- };
62
101
 
63
- export type LinkProps = LinkPropsWithoutHTML & Without<ConnectorProps, LinkPropsWithoutHTML>;
102
+ /**
103
+ * CSS class. In array mode, accepts a `(d) => string` function evaluated
104
+ * per datum.
105
+ */
106
+ class?: string | ((d: any) => string);
107
+ } & Omit<PathPropsWithoutHTML, 'class'>;
108
+
109
+ export type LinkProps = LinkPropsWithoutHTML & Without<PathProps, LinkPropsWithoutHTML>;
64
110
 
65
111
  const FALLBACK_COORDS = { x: 0, y: 0 };
112
+
113
+ function isAccessorAccessor(value: Accessor | undefined): boolean {
114
+ return typeof value === 'string' || typeof value === 'function';
115
+ }
66
116
  </script>
67
117
 
68
118
  <script lang="ts">
69
- /*
70
- TODO:
71
- - [ ] Show path progressively show / animated in on load. Also fix sliding in from left side (at last in from bottom)
72
- - [ ] Support link types
73
- - [ ] https://airbnb.io/visx/linktypes
74
- - [ ] https://github.com/airbnb/visx/tree/master/packages/visx-shape/src/shapes/link
75
- - [ ] https://observablehq.com/@nitaku/corner-connectors
76
- - [ ] Straight
77
- - [ ] Square
78
- - [ ] Beveled
79
- - [ ] Rounded
80
- - [ ] Investigate: https://observablehq.com/@fil/sankey-link-paths
81
- - [ ] Use for annotations - https://github.com/techniq/layerchart/issues/11
82
- */
83
- import Connector, { type ConnectorProps } from './Connector.svelte';
84
- import { extractLayerProps } from '../utils/attributes.js';
119
+ import {
120
+ getLinkD3Path,
121
+ getLinkPresetPath,
122
+ getLinkRadialD3Path,
123
+ getLinkRadialPresetPath,
124
+ } from '../utils/linkUtils.js';
85
125
  import { getChartContext } from '../contexts/chart.js';
126
+ import Path from './Path.svelte';
127
+ import { extractLayerProps } from '../utils/attributes.js';
128
+ import { accessor } from '../utils/common.js';
129
+ import { cls } from '@layerstack/tailwind';
130
+ import {
131
+ createMotion,
132
+ extractTweenConfig,
133
+ type ResolvedMotion,
134
+ } from '../utils/motion.svelte.js';
135
+ import { interpolatePath } from 'd3-interpolate-path';
86
136
 
87
137
  const ctx = getChartContext();
88
138
 
89
139
  let {
140
+ x1,
141
+ y1,
142
+ x2,
143
+ y2,
90
144
  data,
91
145
  sankey = false,
92
146
  source: sourceProp,
93
147
  target: targetProp,
94
- orientation: orientationProp,
95
148
  x: xProp,
96
149
  y: yProp,
150
+ orientation: orientationProp,
97
151
  curve: curveProp,
98
- explicitCoords,
99
152
  type = 'd3',
100
- sweep = 'none',
153
+ sweep: sweepProp,
101
154
  radius = 20,
155
+ bend = 22.5,
156
+ radial: radialProp,
157
+ marker,
158
+ markerStart,
159
+ markerMid,
160
+ markerEnd,
161
+ motion,
162
+ pathRef = $bindable(),
163
+ pathData: pathDataProp,
164
+ class: classProp,
102
165
  ...restProps
103
166
  }: LinkProps = $props();
104
167
 
105
- const sourceAccessor = $derived.by(() => {
106
- if (sourceProp) return sourceProp;
107
- if (sankey) return (d: any) => ({ node: d.source, y: d.y0, isSource: true });
108
- return (d: any) => d.source;
109
- });
110
-
111
- const targetAccessor = $derived.by(() => {
112
- if (targetProp) return targetProp;
113
- if (sankey) return (d: any) => ({ node: d.target, y: d.y1, isSource: false });
114
- return (d: any) => d.target;
115
- });
168
+ const radial = $derived(radialProp ?? ctx.radial ?? false);
116
169
 
117
170
  const orientation = $derived.by(() => {
118
171
  if (orientationProp) return orientationProp;
@@ -126,24 +179,82 @@ TODO:
126
179
  return curveBumpY;
127
180
  });
128
181
 
182
+ const sweep = $derived.by(() => {
183
+ if (type === 'd3') return sweepProp ?? 'none';
184
+ if (sweepProp && sweepProp !== 'none') return sweepProp;
185
+ return orientation === 'vertical' ? 'horizontal-vertical' : 'vertical-horizontal';
186
+ });
187
+
188
+ // Array/data mode: any of x1/y1/x2/y2 is a string or function
189
+ const isArrayMode = $derived(
190
+ isAccessorAccessor(x1) || isAccessorAccessor(y1) || isAccessorAccessor(x2) || isAccessorAccessor(y2)
191
+ );
192
+
193
+ // Pixel mode: any of x1/y1/x2/y2 is a number (and not array mode)
194
+ const isPixelMode = $derived(
195
+ !isArrayMode &&
196
+ (typeof x1 === 'number' ||
197
+ typeof y1 === 'number' ||
198
+ typeof x2 === 'number' ||
199
+ typeof y2 === 'number')
200
+ );
201
+
202
+ // --- Hierarchy/sankey accessors (used only when !isArrayMode && !isPixelMode) ---
203
+ const sourceAccessor = $derived.by(() => {
204
+ if (sourceProp) return sourceProp;
205
+ if (sankey) return (d: any) => ({ node: d.source, y: d.y0, isSource: true });
206
+ return (d: any) => d.source;
207
+ });
208
+
209
+ const targetAccessor = $derived.by(() => {
210
+ if (targetProp) return targetProp;
211
+ if (sankey) return (d: any) => ({ node: d.target, y: d.y1, isSource: false });
212
+ return (d: any) => d.target;
213
+ });
214
+
129
215
  const xAccessor = $derived.by(() => {
130
216
  if (xProp) return xProp;
131
217
  if (sankey) return (d: any) => (d.isSource ? d.node.x1 : d.node.x0);
132
- if (ctx.radial) return (d: any) => d.x;
218
+ if (radial) return (d: any) => d.x;
133
219
  return (d: any) => (orientation === 'horizontal' ? d.y : d.x);
134
220
  });
135
221
 
136
222
  const yAccessor = $derived.by(() => {
137
223
  if (yProp) return yProp;
138
224
  if (sankey) return (d: any) => d.y;
139
- if (ctx.radial) return (d: any) => d.y;
225
+ if (radial) return (d: any) => d.y;
140
226
  return (d: any) => (orientation === 'horizontal' ? d.x : d.y);
141
227
  });
142
228
 
143
- const sourceCoords = $derived.by(() => {
144
- if (explicitCoords) return { x: explicitCoords.x1, y: explicitCoords.y1 };
145
- if (!data) return FALLBACK_COORDS;
229
+ // --- Array mode: resolve endpoint coords for each row ---
230
+ const x1Accessor = $derived(accessor(x1 as Accessor));
231
+ const y1Accessor = $derived(accessor(y1 as Accessor));
232
+ const x2Accessor = $derived(accessor(x2 as Accessor));
233
+ const y2Accessor = $derived(accessor(y2 as Accessor));
146
234
 
235
+ const resolveArrayCoords = (d: any) => {
236
+ const sxRaw = x1Accessor(d);
237
+ const syRaw = y1Accessor(d);
238
+ const txRaw = x2Accessor(d);
239
+ const tyRaw = y2Accessor(d);
240
+ const scaleX = typeof x1 === 'string' || typeof x1 === 'function' ? ctx.xScale : null;
241
+ const scaleY = typeof y1 === 'string' || typeof y1 === 'function' ? ctx.yScale : null;
242
+ const sx = scaleX && sxRaw != null ? scaleX(sxRaw) : typeof sxRaw === 'number' ? sxRaw : 0;
243
+ const sy = scaleY && syRaw != null ? scaleY(syRaw) : typeof syRaw === 'number' ? syRaw : 0;
244
+ const tx = scaleX && txRaw != null ? scaleX(txRaw) : typeof txRaw === 'number' ? txRaw : 0;
245
+ const ty = scaleY && tyRaw != null ? scaleY(tyRaw) : typeof tyRaw === 'number' ? tyRaw : 0;
246
+ return {
247
+ source: { x: Number.isFinite(sx) ? sx : 0, y: Number.isFinite(sy) ? sy : 0 },
248
+ target: { x: Number.isFinite(tx) ? tx : 0, y: Number.isFinite(ty) ? ty : 0 },
249
+ };
250
+ };
251
+
252
+ // --- Single-path coords (pixel or hierarchy/sankey mode) ---
253
+ const singleSourceCoords = $derived.by(() => {
254
+ if (isPixelMode) {
255
+ return { x: typeof x1 === 'number' ? x1 : 0, y: typeof y1 === 'number' ? y1 : 0 };
256
+ }
257
+ if (!data) return FALLBACK_COORDS;
147
258
  try {
148
259
  const sourceData = sourceAccessor(data);
149
260
  if (sourceData == null) return FALLBACK_COORDS;
@@ -156,10 +267,11 @@ TODO:
156
267
  }
157
268
  });
158
269
 
159
- const targetCoords = $derived.by(() => {
160
- if (explicitCoords) return { x: explicitCoords.x2, y: explicitCoords.y2 };
270
+ const singleTargetCoords = $derived.by(() => {
271
+ if (isPixelMode) {
272
+ return { x: typeof x2 === 'number' ? x2 : 100, y: typeof y2 === 'number' ? y2 : 100 };
273
+ }
161
274
  if (!data) return FALLBACK_COORDS;
162
-
163
275
  try {
164
276
  const targetData = targetAccessor(data);
165
277
  if (targetData == null) return FALLBACK_COORDS;
@@ -171,15 +283,89 @@ TODO:
171
283
  return FALLBACK_COORDS;
172
284
  }
173
285
  });
286
+
287
+ function buildPath(source: { x: number; y: number }, target: { x: number; y: number }) {
288
+ if (pathDataProp) return pathDataProp;
289
+ if (radial) {
290
+ return type === 'd3'
291
+ ? getLinkRadialD3Path({ source, target, curve })
292
+ : getLinkRadialPresetPath({ source, target, type, radius, bend });
293
+ }
294
+ if (type === 'd3') {
295
+ return getLinkD3Path({ source, target, sweep, curve, orientation });
296
+ }
297
+ return getLinkPresetPath({ source, target, sweep, type, radius, bend });
298
+ }
299
+
300
+ // --- Single-path case ---
301
+ const singlePathData = $derived(
302
+ isArrayMode ? '' : buildPath(singleSourceCoords, singleTargetCoords)
303
+ );
304
+
305
+ const extractedTween = extractTweenConfig(motion);
306
+ const tweenOptions: ResolvedMotion | undefined = extractedTween
307
+ ? {
308
+ type: extractedTween.type,
309
+ options: {
310
+ interpolate: interpolatePath,
311
+ ...extractedTween.options,
312
+ },
313
+ }
314
+ : undefined;
315
+
316
+ const motionPath = createMotion(
317
+ '',
318
+ () => singlePathData,
319
+ tweenOptions ? tweenOptions : { type: 'none' }
320
+ );
321
+
322
+ // --- Array mode paths ---
323
+ const arrayRows = $derived(isArrayMode ? ((data ?? ctx.data) ?? []) : []);
324
+
325
+ function resolvePerDatum<T>(value: T | ((d: any) => T) | undefined, d: any): T | undefined {
326
+ return typeof value === 'function' ? (value as (d: any) => T)(d) : (value as T | undefined);
327
+ }
328
+
329
+ function resolveClass(d: any): string | undefined {
330
+ return resolvePerDatum<string>(classProp as any, d);
331
+ }
332
+
333
+ // Pull potentially-per-datum style props out of restProps so we can resolve
334
+ // each per row in array mode (e.g. stroke={(d) => colorScale(...)})
335
+ const strokeProp = $derived((restProps as any).stroke);
336
+ const fillProp = $derived((restProps as any).fill);
337
+ const strokeWidthProp = $derived((restProps as any)['stroke-width'] ?? (restProps as any).strokeWidth);
174
338
  </script>
175
339
 
176
- <Connector
177
- source={sourceCoords}
178
- target={targetCoords}
179
- {type}
180
- {curve}
181
- {sweep}
182
- {radius}
183
- radial={ctx.radial}
184
- {...extractLayerProps(restProps, 'lc-link')}
185
- />
340
+ {#if isArrayMode}
341
+ {#each arrayRows as d, i (i)}
342
+ {@const { source, target } = resolveArrayCoords(d)}
343
+ {@const resolvedStroke =
344
+ resolvePerDatum(strokeProp, d) ?? (ctx.config.c ? ctx.cGet(d) : undefined)}
345
+ <Path
346
+ pathData={buildPath(source, target)}
347
+ {marker}
348
+ {markerStart}
349
+ {markerMid}
350
+ {markerEnd}
351
+ {...extractLayerProps(restProps, 'lc-link')}
352
+ {...restProps}
353
+ stroke={resolvedStroke}
354
+ fill={resolvePerDatum(fillProp, d)}
355
+ stroke-width={resolvePerDatum(strokeWidthProp, d)}
356
+ class={cls('lc-link', resolveClass(d))}
357
+ />
358
+ {/each}
359
+ {:else}
360
+ <Path
361
+ pathData={motionPath.current}
362
+ bind:pathRef
363
+ {marker}
364
+ {markerStart}
365
+ {markerMid}
366
+ {markerEnd}
367
+ {...extractLayerProps(restProps, 'lc-link')}
368
+ {...restProps}
369
+ class={cls('lc-link', typeof classProp === 'string' ? classProp : undefined)}
370
+ />
371
+ {/if}
@@ -2,53 +2,96 @@ import type { MarkerOptions } from './MarkerWrapper.svelte';
2
2
  import type { Without } from '../utils/types.js';
3
3
  import type { MotionNoneOption, MotionTweenOption } from '../utils/motion.svelte.js';
4
4
  import { type CurveFactory } from 'd3-shape';
5
+ import type { LinkSweep, LinkType } from '../utils/linkUtils.js';
6
+ import type { PathProps, PathPropsWithoutHTML } from './Path.svelte';
7
+ import type { Accessor } from '../utils/common.js';
5
8
  export type LinkPropsWithoutHTML = {
9
+ /**
10
+ * Source `x` coordinate. Accepts a `number` (pixel value), a string property
11
+ * name, or `(d) => value` accessor. Strings/functions use `ctx.xScale`.
12
+ */
13
+ x1?: Accessor;
14
+ /**
15
+ * Source `y` coordinate. Accepts a `number`, property name, or
16
+ * `(d) => value` accessor. Strings/functions use `ctx.yScale`.
17
+ */
18
+ y1?: Accessor;
19
+ /** Target `x` coordinate. See `x1`. */
20
+ x2?: Accessor;
21
+ /** Target `y` coordinate. See `y1`. */
22
+ y2?: Accessor;
23
+ /**
24
+ * Data for the link. In array mode (when `x1`/`y1`/`x2`/`y2` are strings or
25
+ * functions) this should be an array of rows; one path is rendered per row.
26
+ * In hierarchy/sankey mode, a single link object (`{source, target, ...}`).
27
+ * Defaults to `ctx.data` in array mode.
28
+ */
6
29
  data?: any;
7
30
  /**
8
- * Update source and target accessors to be compatible with d3-sankey. see: https://github.com/d3/d3-sankey#sankeyLinkHorizontal
31
+ * Update source and target accessors to be compatible with d3-sankey. see:
32
+ * https://github.com/d3/d3-sankey#sankeyLinkHorizontal
9
33
  *
10
34
  * @default false
11
35
  */
12
36
  sankey?: boolean;
37
+ /** Accessor returning the source node from `data` (hierarchy/sankey mode). */
13
38
  source?: (d: any) => any;
39
+ /** Accessor returning the target node from `data` (hierarchy/sankey mode). */
14
40
  target?: (d: any) => any;
15
- /**
16
- * Convenient property to swap x/y accessor logic
17
- */
18
- orientation?: 'vertical' | 'horizontal';
41
+ /** Accessor returning `x` for a node (hierarchy/sankey mode). */
19
42
  x?: (d: any) => any;
43
+ /** Accessor returning `y` for a node (hierarchy/sankey mode). */
20
44
  y?: (d: any) => any;
21
- curve?: CurveFactory;
22
45
  /**
23
- * Marker to attach to both start and end points of the line
46
+ * The link path type.
47
+ *
48
+ * Set to `'d3'` to use a D3 curve function via the `curve` prop.
49
+ *
50
+ * @default 'd3'
24
51
  */
25
- marker?: MarkerOptions;
52
+ type?: LinkType;
26
53
  /**
27
- * Marker to attach to the middle point of the line
54
+ * Corner radius (used by `'beveled'` and `'rounded'`).
55
+ *
56
+ * @default 20
28
57
  */
29
- markerMid?: MarkerOptions;
58
+ radius?: number;
30
59
  /**
31
- * Marker to attach to the start point of the line
60
+ * Bend angle in degrees for the `'swoop'` link type.
61
+ *
62
+ * @default 22.5
32
63
  */
33
- markerStart?: MarkerOptions;
64
+ bend?: number;
65
+ /** D3 curve function (used when `type === 'd3'`). */
66
+ curve?: CurveFactory;
67
+ /** Sweep direction for preset types and d3 paths. */
68
+ sweep?: LinkSweep;
34
69
  /**
35
- * Marker to attach to the end point of the line
70
+ * Natural flow direction (affects default curve and axis-dependent step
71
+ * curves). Also toggles x/y accessor logic in hierarchy mode.
36
72
  */
37
- markerEnd?: MarkerOptions;
73
+ orientation?: 'vertical' | 'horizontal';
38
74
  /**
39
- * Apply explicit coordinates to the line. Useful when dealing with
40
- * force simulation links.
75
+ * Interpret coords as polar (`x` = angle, `y` = radius) and render in
76
+ * radial space. Defaults to `ctx.radial` when unset.
41
77
  */
42
- explicitCoords?: {
43
- x1: number;
44
- y1: number;
45
- x2: number;
46
- y2: number;
47
- };
78
+ radial?: boolean;
79
+ /** Marker at both start and end points. */
80
+ marker?: MarkerOptions;
81
+ /** Marker at the middle point. */
82
+ markerMid?: MarkerOptions;
83
+ /** Marker at the start point. */
84
+ markerStart?: MarkerOptions;
85
+ /** Marker at the end point. */
86
+ markerEnd?: MarkerOptions;
48
87
  motion?: MotionTweenOption | MotionNoneOption;
49
- };
50
- export type LinkProps = LinkPropsWithoutHTML & Without<ConnectorProps, LinkPropsWithoutHTML>;
51
- import { type ConnectorProps } from './Connector.svelte';
52
- declare const Link: import("svelte").Component<LinkProps, {}, "">;
88
+ /**
89
+ * CSS class. In array mode, accepts a `(d) => string` function evaluated
90
+ * per datum.
91
+ */
92
+ class?: string | ((d: any) => string);
93
+ } & Omit<PathPropsWithoutHTML, 'class'>;
94
+ export type LinkProps = LinkPropsWithoutHTML & Without<PathProps, LinkPropsWithoutHTML>;
95
+ declare const Link: import("svelte").Component<LinkProps, {}, "pathRef">;
53
96
  type Link = ReturnType<typeof Link>;
54
97
  export default Link;
@@ -10,7 +10,7 @@
10
10
  * rather than a data property accessor.
11
11
  */
12
12
  function isCSSValue(value: string): boolean {
13
- return /^[\d.]+(%|em|rem|px|pt|cm|mm|in)?$/.test(value);
13
+ return /^-?[\d.]+(%|em|rem|px|pt|cm|mm|in)?$/.test(value);
14
14
  }
15
15
 
16
16
  /**
@@ -1,5 +1,6 @@
1
1
  <script lang="ts" module>
2
2
  import type { Without } from '../utils/types.js';
3
+ import type { Accessor } from '../utils/common.js';
3
4
 
4
5
  export type VoronoiPropsWithoutHTML = {
5
6
  /**
@@ -7,6 +8,16 @@
7
8
  */
8
9
  data?: any;
9
10
 
11
+ /**
12
+ * Override the `x` accessor used to place each point. Useful when the
13
+ * chart's `x` accessor returns an array of values (e.g. `['start', 'end']`)
14
+ * and you want to use a specific one.
15
+ */
16
+ x?: Accessor;
17
+
18
+ /** Override the `y` accessor used to place each point. See `x` above. */
19
+ y?: Accessor;
20
+
10
21
  /** Radius to clip voronoi cells. `0` or `undefined` to disables clipping */
11
22
  r?: number;
12
23
 
@@ -56,7 +67,7 @@
56
67
  </script>
57
68
 
58
69
  <script lang="ts">
59
- import { min } from 'd3-array';
70
+ import { max } from 'd3-array';
60
71
  import { Delaunay } from 'd3-delaunay';
61
72
  import { type GeoPermissibleObjects } from 'd3-geo';
62
73
  // @ts-expect-error
@@ -69,10 +80,13 @@
69
80
  import Path from './Path.svelte';
70
81
  import { getChartContext } from '../contexts/chart.js';
71
82
  import { getGeoContext } from '../contexts/geo.js';
83
+ import { accessor } from '../utils/common.js';
72
84
  import CircleClipPath from './CircleClipPath.svelte';
73
85
 
74
86
  let {
75
87
  data,
88
+ x: xProp,
89
+ y: yProp,
76
90
  r,
77
91
  classes = {},
78
92
  onclick,
@@ -86,14 +100,29 @@
86
100
  const ctx = getChartContext();
87
101
  const geo = getGeoContext();
88
102
 
103
+ const xAccessorOverride = $derived(xProp != null ? accessor(xProp) : undefined);
104
+ const yAccessorOverride = $derived(yProp != null ? accessor(yProp) : undefined);
105
+
89
106
  const points = $derived(
90
107
  (data ?? ctx.flatData).map((d: any) => {
91
108
  // geo voronoi needs raw latitude/longitude, not mapped to range (chart dimensions)
92
- const xValue = geo.projection ? ctx.x(d) : ctx.xGet(d);
93
- const yValue = geo.projection ? ctx.y(d) : ctx.yGet(d);
94
-
95
- const x = Array.isArray(xValue) ? min(xValue) : xValue;
96
- const y = Array.isArray(yValue) ? min(yValue) : yValue;
109
+ const xValue = xAccessorOverride
110
+ ? geo.projection
111
+ ? xAccessorOverride(d)
112
+ : ctx.xScale(xAccessorOverride(d))
113
+ : geo.projection
114
+ ? ctx.x(d)
115
+ : ctx.xGet(d);
116
+ const yValue = yAccessorOverride
117
+ ? geo.projection
118
+ ? yAccessorOverride(d)
119
+ : ctx.yScale(yAccessorOverride(d))
120
+ : geo.projection
121
+ ? ctx.y(d)
122
+ : ctx.yGet(d);
123
+
124
+ const x = Array.isArray(xValue) ? max(xValue) : xValue;
125
+ const y = Array.isArray(yValue) ? max(yValue) : yValue;
97
126
 
98
127
  let point: [number, number];
99
128
  if (ctx.radial) {
@@ -1,9 +1,18 @@
1
1
  import type { Without } from '../utils/types.js';
2
+ import type { Accessor } from '../utils/common.js';
2
3
  export type VoronoiPropsWithoutHTML = {
3
4
  /**
4
5
  * Override data instead of using context
5
6
  */
6
7
  data?: any;
8
+ /**
9
+ * Override the `x` accessor used to place each point. Useful when the
10
+ * chart's `x` accessor returns an array of values (e.g. `['start', 'end']`)
11
+ * and you want to use a specific one.
12
+ */
13
+ x?: Accessor;
14
+ /** Override the `y` accessor used to place each point. See `x` above. */
15
+ y?: Accessor;
7
16
  /** Radius to clip voronoi cells. `0` or `undefined` to disables clipping */
8
17
  r?: number;
9
18
  /**