layerchart 2.0.0-next.53 → 2.0.0-next.55

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 (80) hide show
  1. package/dist/components/Arc.svelte +8 -7
  2. package/dist/components/Arc.svelte.test.js +1 -1
  3. package/dist/components/ArcLabel.svelte +1 -1
  4. package/dist/components/Axis.svelte +10 -2
  5. package/dist/components/Axis.svelte.d.ts +8 -2
  6. package/dist/components/Bar.svelte +10 -38
  7. package/dist/components/Circle.svelte +23 -3
  8. package/dist/components/Circle.svelte.d.ts +6 -0
  9. package/dist/components/CircleClipPath.svelte +13 -31
  10. package/dist/components/CircleClipPath.svelte.d.ts +7 -1
  11. package/dist/components/ClipPath.svelte +64 -21
  12. package/dist/components/ClipPath.svelte.d.ts +21 -12
  13. package/dist/components/Connector.svelte +18 -0
  14. package/dist/components/Connector.svelte.d.ts +5 -0
  15. package/dist/components/GeoClipPath.svelte +72 -0
  16. package/dist/components/GeoClipPath.svelte.d.ts +35 -0
  17. package/dist/components/Grid.svelte +15 -4
  18. package/dist/components/Grid.svelte.d.ts +14 -4
  19. package/dist/components/Highlight.svelte +1 -0
  20. package/dist/components/Hull.svelte +20 -2
  21. package/dist/components/Hull.svelte.d.ts +2 -2
  22. package/dist/components/Line.svelte +30 -3
  23. package/dist/components/Line.svelte.d.ts +7 -0
  24. package/dist/components/Link.svelte +9 -0
  25. package/dist/components/Pie.svelte +8 -2
  26. package/dist/components/Rect.svelte +98 -7
  27. package/dist/components/Rect.svelte.d.ts +13 -1
  28. package/dist/components/RectClipPath.svelte +11 -15
  29. package/dist/components/RectClipPath.svelte.d.ts +6 -0
  30. package/dist/components/Text.svelte +70 -16
  31. package/dist/components/Text.svelte.d.ts +10 -0
  32. package/dist/components/Tree.svelte +7 -3
  33. package/dist/components/charts/BarChart.svelte.test.js +1 -1
  34. package/dist/components/charts/DefaultTooltip.svelte.test.js +18 -18
  35. package/dist/components/charts/LineChart.svelte.test.js +1 -1
  36. package/dist/components/charts/PieChart.svelte.test.js +2 -2
  37. package/dist/components/charts/__screenshots__/BarChart.svelte.test.ts/BarChart-series-tooltip-should-use-explicit-series-colors--not-color-scale-1.png +0 -0
  38. package/dist/components/charts/__screenshots__/BarChart.svelte.test.ts/BarChart-series-tooltip-should-use-explicit-series-colors--not-color-scale-2.png +0 -0
  39. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-fade-non-highlighted-tooltip-series-items-on-hover-1.png +0 -0
  40. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-fade-non-highlighted-tooltip-series-items-on-hover-2.png +0 -0
  41. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-header-and-all-series-items-1.png +0 -0
  42. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-header-and-all-series-items-2.png +0 -0
  43. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-series-colors-in-tooltip-items-1.png +0 -0
  44. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-series-colors-in-tooltip-items-2.png +0 -0
  45. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-single-series-without-total-1.png +0 -0
  46. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-AreaChart--multi-series--quadtree-x-mode--should-show-single-series-without-total-2.png +0 -0
  47. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-LineChart--multi-series--quadtree-x-mode--should-show-header-and-all-series-items-1.png +0 -0
  48. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-LineChart--multi-series--quadtree-x-mode--should-show-header-and-all-series-items-2.png +0 -0
  49. 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
  50. 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
  51. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-x--y--and-r-items-when-r-is-configured-1.png +0 -0
  52. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-x--y--and-r-items-when-r-is-configured-2.png +0 -0
  53. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-x-and-y-items-in-tooltip-1.png +0 -0
  54. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-x-and-y-items-in-tooltip-2.png +0 -0
  55. package/dist/components/charts/__screenshots__/LineChart.svelte.test.ts/LineChart-tooltip-should-prefer-cScale-color-over-default-series-color-when-cScale-is-explicitly-provided-1.png +0 -0
  56. package/dist/components/charts/__screenshots__/LineChart.svelte.test.ts/LineChart-tooltip-should-prefer-cScale-color-over-default-series-color-when-cScale-is-explicitly-provided-2.png +0 -0
  57. package/dist/components/charts/__screenshots__/PieChart.svelte.test.ts/PieChart-uses-hovered-slice-identity-for-implicit-tooltip-series-1.png +0 -0
  58. package/dist/components/charts/__screenshots__/PieChart.svelte.test.ts/PieChart-uses-hovered-slice-identity-for-implicit-tooltip-series-2.png +0 -0
  59. package/dist/components/index.d.ts +2 -0
  60. package/dist/components/index.js +2 -0
  61. package/dist/components/tooltip/Tooltip.svelte +145 -29
  62. package/dist/components/tooltip/Tooltip.svelte.d.ts +16 -0
  63. package/dist/components/tooltip/Tooltip.svelte.test.d.ts +1 -0
  64. package/dist/components/tooltip/Tooltip.svelte.test.js +294 -0
  65. package/dist/components/tooltip/__screenshots__/Tooltip.svelte.test.ts/Tooltip-portal-should-portal-tooltip-to-a-custom-selector-target-1.png +0 -0
  66. package/dist/components/tooltip/__screenshots__/Tooltip.svelte.test.ts/Tooltip-portal-should-portal-tooltip-to-a-custom-selector-target-2.png +0 -0
  67. package/dist/components/tooltip/__screenshots__/Tooltip.svelte.test.ts/Tooltip-portal-should-render-tooltip-inline-when-portal-is-false-1.png +0 -0
  68. package/dist/components/tooltip/__screenshots__/Tooltip.svelte.test.ts/Tooltip-portal-should-render-tooltip-inline-when-portal-is-false-2.png +0 -0
  69. package/dist/utils/__screenshots__/canvas.svelte.test.ts/renderPathData-composes-element-opacity-with-inherited-globalAlpha--Group-opacity--1.png +0 -0
  70. package/dist/utils/__screenshots__/canvas.svelte.test.ts/renderPathData-composes-element-opacity-with-inherited-globalAlpha--Group-opacity--2.png +0 -0
  71. package/dist/utils/canvas.d.ts +2 -0
  72. package/dist/utils/canvas.js +13 -7
  73. package/dist/utils/canvas.svelte.test.js +55 -0
  74. package/dist/utils/connectorUtils.d.ts +13 -0
  75. package/dist/utils/connectorUtils.js +120 -1
  76. package/dist/utils/path.d.ts +19 -0
  77. package/dist/utils/path.js +72 -0
  78. package/dist/utils/rect.svelte.d.ts +18 -0
  79. package/dist/utils/rect.svelte.js +33 -0
  80. package/package.json +13 -13
@@ -254,7 +254,10 @@
254
254
  const ctx = getChartContext();
255
255
 
256
256
  const endAngle = $derived(
257
- endAngleProp ?? degreesToRadians(ctx.xRange ? max(ctx.xRange) : max(range))
257
+ endAngleProp ??
258
+ degreesToRadians(
259
+ (ctx.config.xRange ? max(ctx.config.xRange as number[]) : max(range))!
260
+ )
258
261
  );
259
262
 
260
263
  const motionEndAngle = createMotion(initialValue, () => value, motion);
@@ -279,13 +282,11 @@
279
282
  }
280
283
  }
281
284
 
282
- const outerRadius = $derived(
283
- getOuterRadius(outerRadiusProp, (Math.min(ctx.xRange[1], ctx.yRange[0]) ?? 0) / 2)
284
- );
285
+ const chartRadius = $derived((Math.min(ctx.width, ctx.height) ?? 0) / 2);
286
+
287
+ const outerRadius = $derived(getOuterRadius(outerRadiusProp, chartRadius));
285
288
  const trackOuterRadius = $derived(
286
- trackOuterRadiusProp
287
- ? getOuterRadius(trackOuterRadiusProp, (Math.min(ctx.xRange[1], ctx.yRange[0]) ?? 0) / 2)
288
- : outerRadius
289
+ trackOuterRadiusProp ? getOuterRadius(trackOuterRadiusProp, chartRadius) : outerRadius
289
290
  );
290
291
 
291
292
  function getInnerRadius(innerRadius: number | undefined, outerRadius: number) {
@@ -383,7 +383,7 @@ describe(`Arc`, () => {
383
383
  const el = page.getByTestId(componentTestId);
384
384
  await expect.element(el).toBeInTheDocument();
385
385
  const transform = el.element()?.getAttribute('transform');
386
- expect(transform).toContain('translate(-4.539904997395462, 8.91006524188368)');
386
+ expect(transform).toContain('translate(1.2246467991473533e-15, 10)');
387
387
  });
388
388
  it('should apply zero offset by default', async () => {
389
389
  render(TestHarness, {
@@ -222,7 +222,7 @@
222
222
  y: offsetCentroid[1],
223
223
  textAnchor: 'middle' as const,
224
224
  verticalAnchor: 'middle' as const,
225
- transform: `rotate(${centroidRotation}, ${offsetCentroid[0]}, ${offsetCentroid[1]})`,
225
+ rotate: centroidRotation,
226
226
  };
227
227
  }
228
228
  return getArcTextProps?.('centroid', { startOffset, outerPadding }) ?? {};
@@ -33,10 +33,18 @@
33
33
  rule?: boolean | Partial<ComponentProps<typeof Rule>>;
34
34
 
35
35
  /**
36
- * Draw grid lines
36
+ * Draw grid lines. Pass props (class, style, stroke, strokeWidth, opacity,
37
+ * dashArray) to forward onto the underlying grid line.
37
38
  * @default false
38
39
  */
39
- grid?: boolean | Pick<SVGAttributes<SVGElement>, 'class' | 'style'>;
40
+ grid?:
41
+ | boolean
42
+ | (Pick<SVGAttributes<SVGElement>, 'class' | 'style'> & {
43
+ stroke?: string;
44
+ strokeWidth?: number;
45
+ opacity?: number;
46
+ dashArray?: number | number[] | string;
47
+ });
40
48
 
41
49
  /**
42
50
  * Control the number of ticks
@@ -28,10 +28,16 @@ export type AxisPropsWithoutHTML<In extends Transition = Transition> = {
28
28
  */
29
29
  rule?: boolean | Partial<ComponentProps<typeof Rule>>;
30
30
  /**
31
- * Draw grid lines
31
+ * Draw grid lines. Pass props (class, style, stroke, strokeWidth, opacity,
32
+ * dashArray) to forward onto the underlying grid line.
32
33
  * @default false
33
34
  */
34
- grid?: boolean | Pick<SVGAttributes<SVGElement>, 'class' | 'style'>;
35
+ grid?: boolean | (Pick<SVGAttributes<SVGElement>, 'class' | 'style'> & {
36
+ stroke?: string;
37
+ strokeWidth?: number;
38
+ opacity?: number;
39
+ dashArray?: number | number[] | string;
40
+ });
35
41
  /**
36
42
  * Control the number of ticks
37
43
  */
@@ -105,14 +105,13 @@
105
105
  import { greatestAbs } from '@layerstack/utils';
106
106
 
107
107
  import Rect from './Rect.svelte';
108
- import Path from './Path.svelte';
109
108
 
110
109
  import { isScaleBand, isScaleTime } from '../utils/scales.svelte.js';
111
110
  import { accessor, type Accessor } from '../utils/common.js';
112
111
  import { getChartContext } from '../contexts/chart.js';
113
112
  import type { CommonEvents, CommonStyleProps, Without } from '../utils/types.js';
114
113
  import { extractLayerProps } from '../utils/attributes.js';
115
- import { extractTweenConfig, type MotionProp } from '../utils/motion.svelte.js';
114
+ import { type MotionProp } from '../utils/motion.svelte.js';
116
115
  import Arc from './Arc.svelte';
117
116
 
118
117
  const ctx = getChartContext();
@@ -254,12 +253,14 @@
254
253
  const topRight = $derived(['all', 'top', 'right', 'top-right'].includes(rounded));
255
254
  const bottomLeft = $derived(['all', 'bottom', 'left', 'bottom-left'].includes(rounded));
256
255
  const bottomRight = $derived(['all', 'bottom', 'right', 'bottom-right'].includes(rounded));
257
- const width = $derived(dimensions.width);
258
- const height = $derived(dimensions.height);
259
256
 
260
- // Clamp radius to prevent extending beyond bounding box
261
- const r = $derived(Math.min(radius, width / 2, height / 2));
262
- const diameter = $derived(2 * r);
257
+ // Per-corner radii: [tl, tr, br, bl], matching CSS `border-radius` shorthand.
258
+ const corners = $derived<[number, number, number, number]>([
259
+ topLeft ? radius : 0,
260
+ topRight ? radius : 0,
261
+ bottomRight ? radius : 0,
262
+ bottomLeft ? radius : 0,
263
+ ]);
263
264
 
264
265
  // Auto-compute initial values for mount animation when motion is configured
265
266
  const resolvedInitialY = $derived(
@@ -275,20 +276,6 @@
275
276
  initialWidth ?? (motion && ctx.valueAxis === 'x' ? 0 : undefined)
276
277
  );
277
278
 
278
- const pathData = $derived(
279
- `M${dimensions.x + r},${dimensions.y} h${width - diameter}
280
- ${topRight ? `a${r},${r} 0 0 1 ${r},${r}` : `h${r}v${r}`}
281
- v${height - diameter}
282
- ${bottomRight ? `a${r},${r} 0 0 1 ${-r},${r}` : `v${r}h${-r}`}
283
- h${diameter - width}
284
- ${bottomLeft ? `a${r},${r} 0 0 1 ${-r},${-r}` : `h${-r}v${-r}`}
285
- v${diameter - height}
286
- ${topLeft ? `a${r},${r} 0 0 1 ${r},${-r}` : `v${-r}h${r}`}
287
- z`
288
- .split('\n')
289
- .join('')
290
- );
291
-
292
279
  const onPointerEnter: PointerEventHandler<Element> = (e) => {
293
280
  onpointerenter?.(e);
294
281
  if (tooltip) ctx.tooltip.show(e, data);
@@ -322,14 +309,14 @@
322
309
  onpointerleave={onPointerLeave}
323
310
  {...extractLayerProps(restProps, 'lc-bar')}
324
311
  />
325
- {:else if rounded === 'all' || rounded === 'none' || radius === 0}
312
+ {:else}
326
313
  <Rect
327
314
  {fill}
328
315
  {fillOpacity}
329
316
  {stroke}
330
317
  {strokeWidth}
331
318
  {opacity}
332
- rx={rounded === 'none' ? 0 : radius}
319
+ {corners}
333
320
  {motion}
334
321
  initialX={resolvedInitialX}
335
322
  initialY={resolvedInitialY}
@@ -341,19 +328,4 @@
341
328
  onpointerleave={onPointerLeave}
342
329
  {...extractLayerProps(restProps, 'lc-bar')}
343
330
  />
344
- {:else}
345
- {@const tweenMotion = extractTweenConfig(motion)}
346
- <Path
347
- {pathData}
348
- {fill}
349
- {fillOpacity}
350
- {stroke}
351
- {strokeWidth}
352
- {opacity}
353
- motion={tweenMotion}
354
- onpointerenter={onPointerEnter}
355
- onpointermove={onPointerMove}
356
- onpointerleave={onPointerLeave}
357
- {...extractLayerProps(restProps, 'lc-bar')}
358
- />
359
331
  {/if}
@@ -78,6 +78,13 @@
78
78
  /** Motion configuration (pixel mode only). */
79
79
  motion?: MotionProp;
80
80
 
81
+ /**
82
+ * Dashed-border pattern. Accepts a number (single dash length), a
83
+ * `[dash, gap, ...]` array, or a string (same syntax as SVG
84
+ * `stroke-dasharray`). HTML layer approximates via `border-style: dashed`.
85
+ */
86
+ dashArray?: number | number[] | string;
87
+
81
88
  /** Children content to render. Note: Only works for Html layers */
82
89
  children?: Snippet;
83
90
  } & DataDrivenStyleProps;
@@ -106,6 +113,7 @@
106
113
  import { chartDataArray } from '../utils/common.js';
107
114
  import type { SVGAttributes } from 'svelte/elements';
108
115
  import { createKey } from '../utils/key.svelte.js';
116
+ import { parseDashArray } from '../utils/path.js';
109
117
 
110
118
  let {
111
119
  cx = 0,
@@ -124,10 +132,14 @@
124
132
  opacity,
125
133
  class: className,
126
134
  ref: refProp = $bindable(),
135
+ dashArray,
127
136
  children,
128
137
  ...restProps
129
138
  }: CircleProps = $props();
130
139
 
140
+ const dashArrayResolved = $derived(parseDashArray(dashArray));
141
+ const dashArrayAttr = $derived(dashArrayResolved ? dashArrayResolved.join(' ') : undefined);
142
+
131
143
  // Data mode detection: if any positional prop is a string or function
132
144
  const dataMode = $derived(hasAnyDataProp(cx, cy, r));
133
145
 
@@ -250,7 +262,12 @@
250
262
  'lc-circle',
251
263
  itemClass ?? (typeof className === 'string' ? className : undefined)
252
264
  ),
253
- style: restProps.style as string | undefined,
265
+ style: [
266
+ restProps.style as string | undefined,
267
+ dashArrayAttr ? `stroke-dasharray: ${dashArrayAttr}` : undefined,
268
+ ]
269
+ .filter(Boolean)
270
+ .join('; ') || undefined,
254
271
  };
255
272
  }
256
273
 
@@ -328,6 +345,7 @@
328
345
  opacity,
329
346
  className,
330
347
  restProps.style,
348
+ dashArrayAttr,
331
349
  ],
332
350
  }
333
351
  : undefined,
@@ -352,6 +370,7 @@
352
370
  stroke={resolvedStroke}
353
371
  stroke-width={resolvedStrokeWidth}
354
372
  opacity={resolvedOpacity}
373
+ stroke-dasharray={dashArrayAttr}
355
374
  class={cls('lc-circle', resolvedClass)}
356
375
  {...restProps}
357
376
  />
@@ -367,6 +386,7 @@
367
386
  stroke={staticStroke}
368
387
  stroke-width={staticStrokeWidth}
369
388
  opacity={staticOpacity}
389
+ stroke-dasharray={dashArrayAttr}
370
390
  class={cls('lc-circle', staticClassName)}
371
391
  {...restProps}
372
392
  />
@@ -391,7 +411,7 @@
391
411
  style:opacity={resolvedOpacity}
392
412
  style:border-width={resolvedStrokeWidth}
393
413
  style:border-color={resolvedStroke}
394
- style:border-style="solid"
414
+ style:border-style={dashArrayResolved ? 'dashed' : 'solid'}
395
415
  style:transform="translate(-50%, -50%)"
396
416
  class={cls('lc-circle', resolvedClass)}
397
417
  {...restProps}
@@ -409,7 +429,7 @@
409
429
  style:opacity={staticOpacity}
410
430
  style:border-width={staticStrokeWidth}
411
431
  style:border-color={staticStroke}
412
- style:border-style="solid"
432
+ style:border-style={dashArrayResolved ? 'dashed' : 'solid'}
413
433
  style:transform="translate(-50%, -50%)"
414
434
  class={cls('lc-circle', staticClassName)}
415
435
  {...restProps}
@@ -66,6 +66,12 @@ export type CirclePropsWithoutHTML = {
66
66
  ref?: SVGCircleElement;
67
67
  /** Motion configuration (pixel mode only). */
68
68
  motion?: MotionProp;
69
+ /**
70
+ * Dashed-border pattern. Accepts a number (single dash length), a
71
+ * `[dash, gap, ...]` array, or a string (same syntax as SVG
72
+ * `stroke-dasharray`). HTML layer approximates via `border-style: dashed`.
73
+ */
74
+ dashArray?: number | number[] | string;
69
75
  /** Children content to render. Note: Only works for Html layers */
70
76
  children?: Snippet;
71
77
  } & DataDrivenStyleProps;
@@ -36,6 +36,13 @@
36
36
  */
37
37
  disabled?: boolean;
38
38
 
39
+ /**
40
+ * Invert the clip — content renders *outside* the circle.
41
+ *
42
+ * @default false
43
+ */
44
+ invert?: boolean;
45
+
39
46
  /**
40
47
  * A bindable reference to the underlying `<circle>` element'
41
48
  *
@@ -53,9 +60,7 @@
53
60
  </script>
54
61
 
55
62
  <script lang="ts">
56
- import Circle from './Circle.svelte';
57
63
  import { createId } from '../utils/createId.js';
58
- import { extractLayerProps } from '../utils/attributes.js';
59
64
 
60
65
  const uid = $props.id();
61
66
 
@@ -64,38 +69,15 @@
64
69
  cx = 0,
65
70
  cy = 0,
66
71
  r,
67
- motion,
68
72
  disabled = false,
69
- ref: refProp = $bindable(),
73
+ invert = false,
70
74
  children,
71
- ...restProps
72
75
  }: CircleClipPathPropsWithoutHTML = $props();
73
76
 
74
- let ref = $state<SVGCircleElement>();
75
-
76
- $effect.pre(() => {
77
- refProp = ref;
78
- });
79
-
80
- function canvasClip(ctx: CanvasRenderingContext2D) {
81
- ctx.beginPath();
82
- ctx.arc(cx, cy, r, 0, Math.PI * 2);
83
- }
84
-
85
- function canvasClipDeps() {
86
- return [cx, cy, r];
87
- }
77
+ // Two 180° arcs produce a full circle that Path2D / `clip-path: path()` accept.
78
+ const path = $derived(
79
+ `M${cx - r},${cy} a${r},${r} 0 1,0 ${2 * r},0 a${r},${r} 0 1,0 ${-2 * r},0 Z`
80
+ );
88
81
  </script>
89
82
 
90
- <ClipPath {id} {disabled} {children} {canvasClip} {canvasClipDeps}>
91
- {#snippet clip()}
92
- <Circle
93
- {cx}
94
- {cy}
95
- {r}
96
- {motion}
97
- {...extractLayerProps(restProps, 'lc-clip-path-circle')}
98
- bind:ref
99
- />
100
- {/snippet}
101
- </ClipPath>
83
+ <ClipPath {id} {disabled} {invert} {children} {path} />
@@ -29,6 +29,12 @@ export type CircleClipPathPropsWithoutHTML = {
29
29
  * @default false
30
30
  */
31
31
  disabled?: boolean;
32
+ /**
33
+ * Invert the clip — content renders *outside* the circle.
34
+ *
35
+ * @default false
36
+ */
37
+ invert?: boolean;
32
38
  /**
33
39
  * A bindable reference to the underlying `<circle>` element'
34
40
  *
@@ -41,6 +47,6 @@ export type CircleClipPathPropsWithoutHTML = {
41
47
  children?: ClipPathPropsWithoutHTML['children'];
42
48
  motion?: MotionProp;
43
49
  };
44
- declare const CircleClipPath: import("svelte").Component<CircleClipPathPropsWithoutHTML, {}, "ref">;
50
+ declare const CircleClipPath: import("svelte").Component<CircleClipPathPropsWithoutHTML, {}, "">;
45
51
  type CircleClipPath = ReturnType<typeof CircleClipPath>;
46
52
  export default CircleClipPath;
@@ -26,26 +26,37 @@
26
26
  disabled?: boolean;
27
27
 
28
28
  /**
29
- * A snippet to insert content into the clipPath.
30
- * Provides the id for the clipPath as a snippet prop.
29
+ * Invert the clip content renders *outside* the shape instead of inside.
30
+ * Implemented by combining the shape with an outer rect covering the chart
31
+ * bounds and applying the even-odd fill rule.
32
+ *
33
+ * @default false
31
34
  */
32
- clip?: Snippet<[{ id: string }]>;
35
+ invert?: boolean;
33
36
 
34
37
  /**
35
- * Children to render in the `<g>` element that links to the clipPath (if not disabled).
36
- * Provides the id, url, and useId for the clipPath as snippet props.
38
+ * SVG path `d` string describing the clip shape. When provided, this single
39
+ * value drives all three layers:
40
+ * - SVG: rendered as `<path d={path}>` inside the `<clipPath>`
41
+ * - Canvas: wrapped in `Path2D` and applied via `ctx.clip(...)`
42
+ * - HTML: emitted as CSS `clip-path: path("...")` on a wrapper `<div>`
43
+ *
44
+ * For shapes that can't be expressed as an SVG path (or for advanced
45
+ * per-layer customization), use the `clip` snippet (SVG) alongside `path`.
37
46
  */
38
- children?: Snippet<[{ id: string; url: string; useId?: string }]>;
47
+ path?: string;
48
+
39
49
  /**
40
- * Canvas clip path function. When provided and in canvas mode, sets up a canvas
41
- * clip region by drawing a path and calling `ctx.clip()` before rendering children.
50
+ * A snippet to insert custom SVG content into the `<clipPath>`. When
51
+ * omitted and `path` is set, a `<path d={path}>` is rendered automatically.
42
52
  */
43
- canvasClip?: (ctx: CanvasRenderingContext2D) => void;
53
+ clip?: Snippet<[{ id: string }]>;
54
+
44
55
  /**
45
- * Reactive deps for canvas clip invalidation. Return array of values that,
46
- * when changed, should trigger a canvas redraw.
56
+ * Children to render in the `<g>` element that links to the clipPath (if not disabled).
57
+ * Provides the id, url, and useId for the clipPath as snippet props.
47
58
  */
48
- canvasClipDeps?: () => any[];
59
+ children?: Snippet<[{ id: string; url: string; useId?: string }]>;
49
60
  };
50
61
 
51
62
  export type ClipPathProps = ClipPathPropsWithoutHTML &
@@ -61,10 +72,10 @@
61
72
  id = createId('clipPath-', uid),
62
73
  useId,
63
74
  disabled = false,
75
+ invert = false,
64
76
  children,
65
77
  clip,
66
- canvasClip,
67
- canvasClipDeps,
78
+ path,
68
79
  ...restProps
69
80
  }: ClipPathPropsWithoutHTML = $props();
70
81
 
@@ -73,18 +84,33 @@
73
84
  const layerCtx = getLayerContext();
74
85
  const chartCtx = getChartContext();
75
86
 
87
+ // Outer rect covering the chart bounds — combined with the clip shape under
88
+ // the even-odd fill rule to invert the clip.
89
+ const outerRect = $derived(
90
+ `M0,0 H${chartCtx.width} V${chartCtx.height} H0 Z`
91
+ );
92
+
93
+ // Effective path used for canvas + html layers when inverting.
94
+ const effectivePath = $derived(
95
+ invert && path ? `${outerRect} ${path}` : path
96
+ );
97
+
98
+ // Cache the Path2D so `ctx.clip()` gets a stable reference per `path` change.
99
+ const canvasPath = $derived(
100
+ layerCtx === 'canvas' && effectivePath ? new Path2D(effectivePath) : undefined
101
+ );
102
+
76
103
  if (layerCtx === 'canvas') {
77
104
  chartCtx.registerComponent({
78
105
  name: 'ClipPath',
79
106
  kind: 'group',
80
107
  canvasRender: {
81
108
  render: (ctx) => {
82
- if (!disabled && canvasClip) {
83
- canvasClip(ctx);
84
- ctx.clip();
109
+ if (!disabled && canvasPath) {
110
+ ctx.clip(canvasPath, invert ? 'evenodd' : 'nonzero');
85
111
  }
86
112
  },
87
- deps: () => canvasClipDeps?.() ?? [],
113
+ deps: () => [disabled, canvasPath, invert],
88
114
  },
89
115
  });
90
116
  }
@@ -93,7 +119,11 @@
93
119
  {#if layerCtx === 'svg'}
94
120
  <defs>
95
121
  <clipPath {id} {...restProps}>
96
- {@render clip?.({ id })}
122
+ {#if clip}
123
+ {@render clip({ id })}
124
+ {:else if effectivePath}
125
+ <path d={effectivePath} clip-rule={invert ? 'evenodd' : undefined} />
126
+ {/if}
97
127
 
98
128
  {#if useId}
99
129
  <use href="#{useId}" />
@@ -103,11 +133,24 @@
103
133
  {/if}
104
134
 
105
135
  {#if children}
106
- {#if disabled || layerCtx !== 'svg'}
136
+ {#if disabled}
107
137
  {@render children({ id, url, useId })}
108
- {:else}
138
+ {:else if layerCtx === 'svg'}
109
139
  <g style:clip-path={url} class="lc-clip-path-g">
110
140
  {@render children({ id, url, useId })}
111
141
  </g>
142
+ {:else if layerCtx === 'html' && effectivePath}
143
+ <div
144
+ class="lc-clip-path-div"
145
+ style:position="absolute"
146
+ style:inset="0"
147
+ style:clip-path={invert
148
+ ? `path(evenodd, "${effectivePath}")`
149
+ : `path("${effectivePath}")`}
150
+ >
151
+ {@render children({ id, url, useId })}
152
+ </div>
153
+ {:else}
154
+ {@render children({ id, url, useId })}
112
155
  {/if}
113
156
  {/if}
@@ -19,8 +19,27 @@ export type ClipPathPropsWithoutHTML = {
19
19
  */
20
20
  disabled?: boolean;
21
21
  /**
22
- * A snippet to insert content into the clipPath.
23
- * Provides the id for the clipPath as a snippet prop.
22
+ * Invert the clip content renders *outside* the shape instead of inside.
23
+ * Implemented by combining the shape with an outer rect covering the chart
24
+ * bounds and applying the even-odd fill rule.
25
+ *
26
+ * @default false
27
+ */
28
+ invert?: boolean;
29
+ /**
30
+ * SVG path `d` string describing the clip shape. When provided, this single
31
+ * value drives all three layers:
32
+ * - SVG: rendered as `<path d={path}>` inside the `<clipPath>`
33
+ * - Canvas: wrapped in `Path2D` and applied via `ctx.clip(...)`
34
+ * - HTML: emitted as CSS `clip-path: path("...")` on a wrapper `<div>`
35
+ *
36
+ * For shapes that can't be expressed as an SVG path (or for advanced
37
+ * per-layer customization), use the `clip` snippet (SVG) alongside `path`.
38
+ */
39
+ path?: string;
40
+ /**
41
+ * A snippet to insert custom SVG content into the `<clipPath>`. When
42
+ * omitted and `path` is set, a `<path d={path}>` is rendered automatically.
24
43
  */
25
44
  clip?: Snippet<[{
26
45
  id: string;
@@ -34,16 +53,6 @@ export type ClipPathPropsWithoutHTML = {
34
53
  url: string;
35
54
  useId?: string;
36
55
  }]>;
37
- /**
38
- * Canvas clip path function. When provided and in canvas mode, sets up a canvas
39
- * clip region by drawing a path and calling `ctx.clip()` before rendering children.
40
- */
41
- canvasClip?: (ctx: CanvasRenderingContext2D) => void;
42
- /**
43
- * Reactive deps for canvas clip invalidation. Return array of values that,
44
- * when changed, should trigger a canvas redraw.
45
- */
46
- canvasClipDeps?: () => any[];
47
56
  };
48
57
  export type ClipPathProps = ClipPathPropsWithoutHTML & Without<SVGAttributes<SVGClipPathElement>, ClipPathPropsWithoutHTML>;
49
58
  declare const ClipPath: import("svelte").Component<ClipPathPropsWithoutHTML, {}, "">;
@@ -46,6 +46,12 @@
46
46
  * @default `d3.curveLinear`
47
47
  */
48
48
  curve?: CurveFactory;
49
+
50
+ /**
51
+ * Interpret `source`/`target` as polar coordinates (`x` = angle, `y` = radius)
52
+ * and render the path in radial space. Defaults to `ctx.radial` when unset.
53
+ */
54
+ radial?: boolean;
49
55
  } & PathPropsWithoutHTML;
50
56
 
51
57
  export type ConnectorProps = ConnectorPropsWithoutHTML &
@@ -57,10 +63,13 @@
57
63
  import {
58
64
  getConnectorD3Path,
59
65
  getConnectorPresetPath,
66
+ getConnectorRadialD3Path,
67
+ getConnectorRadialPresetPath,
60
68
  type ConnectorCoords,
61
69
  type ConnectorSweep,
62
70
  type ConnectorType,
63
71
  } from '../utils/connectorUtils.js';
72
+ import { getChartContext } from '../contexts/chart.js';
64
73
  import Path, { type PathProps, type PathPropsWithoutHTML } from './Path.svelte';
65
74
  import type { Without } from '../utils/types.js';
66
75
  import { createId } from '../utils/createId.js';
@@ -82,6 +91,7 @@
82
91
  type = 'rounded',
83
92
  radius = 20,
84
93
  curve = curveLinear,
94
+ radial: radialProp,
85
95
  pathRef = $bindable(),
86
96
  pathData: pathDataProp,
87
97
  marker,
@@ -92,6 +102,9 @@
92
102
  ...restProps
93
103
  }: ConnectorProps = $props();
94
104
 
105
+ const ctx = getChartContext();
106
+ const radial = $derived(radialProp ?? ctx.radial ?? false);
107
+
95
108
  const sweep = $derived.by(() => {
96
109
  if (sweepProp) return sweepProp;
97
110
  if (type === 'd3') return 'none';
@@ -116,6 +129,11 @@
116
129
 
117
130
  const pathData = $derived.by(() => {
118
131
  if (pathDataProp) return pathDataProp;
132
+ if (radial) {
133
+ return type === 'd3'
134
+ ? getConnectorRadialD3Path({ source, target, curve })
135
+ : getConnectorRadialPresetPath({ source, target, type, radius });
136
+ }
119
137
  if (type === 'd3') {
120
138
  return getConnectorD3Path({
121
139
  source,
@@ -40,6 +40,11 @@ export type ConnectorPropsWithoutHTML = {
40
40
  * @default `d3.curveLinear`
41
41
  */
42
42
  curve?: CurveFactory;
43
+ /**
44
+ * Interpret `source`/`target` as polar coordinates (`x` = angle, `y` = radius)
45
+ * and render the path in radial space. Defaults to `ctx.radial` when unset.
46
+ */
47
+ radial?: boolean;
43
48
  } & PathPropsWithoutHTML;
44
49
  export type ConnectorProps = ConnectorPropsWithoutHTML & Without<PathProps, ConnectorPropsWithoutHTML>;
45
50
  import { type CurveFactory } from 'd3-shape';