layerchart 2.0.0-next.54 → 2.0.0-next.56

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 (81) hide show
  1. package/dist/bench/ComposableLineChart.svelte +1 -1
  2. package/dist/bench/GeoBench.svelte +1 -8
  3. package/dist/components/AnnotationRange.svelte +3 -1
  4. package/dist/components/Arc.svelte +1 -3
  5. package/dist/components/ArcLabel.svelte.test.js +7 -7
  6. package/dist/components/Axis.svelte +10 -2
  7. package/dist/components/Axis.svelte.d.ts +8 -2
  8. package/dist/components/Bar.svelte +14 -40
  9. package/dist/components/BoxPlot.svelte +4 -12
  10. package/dist/components/Cell.svelte +13 -8
  11. package/dist/components/Chart.svelte +69 -26
  12. package/dist/components/ChartChildren.svelte +22 -4
  13. package/dist/components/Circle.svelte +51 -9
  14. package/dist/components/Circle.svelte.d.ts +6 -0
  15. package/dist/components/CircleClipPath.svelte +13 -31
  16. package/dist/components/CircleClipPath.svelte.d.ts +7 -1
  17. package/dist/components/ClipPath.svelte +58 -21
  18. package/dist/components/ClipPath.svelte.d.ts +21 -12
  19. package/dist/components/Connector.svelte +18 -0
  20. package/dist/components/Connector.svelte.d.ts +5 -0
  21. package/dist/components/Ellipse.svelte +27 -6
  22. package/dist/components/GeoClipPath.svelte +14 -17
  23. package/dist/components/GeoClipPath.svelte.d.ts +6 -0
  24. package/dist/components/GeoLegend.svelte +1 -3
  25. package/dist/components/GeoPoint.svelte +25 -3
  26. package/dist/components/GeoSpline.svelte +1 -4
  27. package/dist/components/GeoTile.svelte +8 -4
  28. package/dist/components/Grid.svelte +15 -4
  29. package/dist/components/Grid.svelte.d.ts +14 -4
  30. package/dist/components/Group.svelte +11 -5
  31. package/dist/components/Highlight.svelte +4 -3
  32. package/dist/components/Image.svelte +42 -30
  33. package/dist/components/Labels.svelte +2 -4
  34. package/dist/components/Line.svelte +31 -3
  35. package/dist/components/Line.svelte.d.ts +7 -0
  36. package/dist/components/LinearGradient.svelte +8 -4
  37. package/dist/components/Link.svelte +8 -0
  38. package/dist/components/Marker.svelte +9 -1
  39. package/dist/components/Path.svelte +43 -23
  40. package/dist/components/Pattern.svelte +101 -5
  41. package/dist/components/Pattern.svelte.d.ts +3 -1
  42. package/dist/components/Pie.svelte +2 -6
  43. package/dist/components/RadialGradient.svelte +8 -4
  44. package/dist/components/Rect.svelte +117 -9
  45. package/dist/components/Rect.svelte.d.ts +13 -1
  46. package/dist/components/RectClipPath.svelte +11 -15
  47. package/dist/components/RectClipPath.svelte.d.ts +6 -0
  48. package/dist/components/Spline.svelte +22 -4
  49. package/dist/components/Text.svelte +16 -5
  50. package/dist/components/Trail.svelte +19 -7
  51. package/dist/components/Tree.svelte +7 -3
  52. package/dist/components/Vector.svelte +37 -14
  53. package/dist/components/Violin.svelte +1 -2
  54. package/dist/components/charts/ArcChart.svelte +8 -5
  55. package/dist/components/charts/AreaChart.svelte +6 -1
  56. package/dist/components/charts/BarChart.svelte +3 -1
  57. package/dist/components/charts/LineChart.svelte +6 -1
  58. package/dist/components/charts/PieChart.svelte +10 -3
  59. package/dist/components/tooltip/Tooltip.svelte +2 -8
  60. package/dist/contexts/chart.d.ts +1 -1
  61. package/dist/contexts/chart.js +3 -1
  62. package/dist/server/TestBarChart.svelte +28 -28
  63. package/dist/server/TestLineChart.svelte +28 -28
  64. package/dist/server/index.js +1 -1
  65. package/dist/states/brush.svelte.js +16 -13
  66. package/dist/states/chart.svelte.test.js +24 -19
  67. package/dist/states/geo.svelte.js +1 -4
  68. package/dist/states/series.svelte.js +1 -1
  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 +20 -11
  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/dist/utils/trail.js +3 -4
  81. package/package.json +1 -1
@@ -95,7 +95,9 @@
95
95
  background?: string;
96
96
 
97
97
  /**
98
- * Render as a child of the pattern
98
+ * Render as a child of the pattern.
99
+ *
100
+ * Note: only supported on the `<Svg>` layer.
99
101
  */
100
102
  patternContent?: Snippet;
101
103
 
@@ -250,10 +252,102 @@
250
252
  }
251
253
 
252
254
  if (layerCtx === 'canvas') {
253
- chartCtx.registerComponent({ name: 'Pattern', kind: 'mark', canvasRender: {
254
- render,
255
- deps: () => [width, height, shapes, background],
256
- } });
255
+ chartCtx.registerComponent({
256
+ name: 'Pattern',
257
+ kind: 'group',
258
+ canvasRender: {
259
+ render,
260
+ deps: () => [width, height, shapes, background],
261
+ },
262
+ });
263
+ }
264
+
265
+ function withOpacity(color: string, opacity: number) {
266
+ return opacity === 1 ? color : `color-mix(in srgb, ${color} ${opacity * 100}%, transparent)`;
267
+ }
268
+
269
+ // Build a CSS `background` value from lines/circles/background.
270
+ // Uses repeating-linear-gradient for lines and radial-gradient for circles
271
+ // so CSS variables/classes resolve natively in the HTML layer.
272
+ function createCSSPattern(): string {
273
+ const layers: string[] = [];
274
+
275
+ if (linesProp) {
276
+ const lineDefs = Array.isArray(linesProp)
277
+ ? linesProp
278
+ : linesProp === true
279
+ ? [{}]
280
+ : [linesProp];
281
+ for (const line of lineDefs) {
282
+ const color = withOpacity(
283
+ line.color ?? 'var(--color-surface-content, currentColor)',
284
+ line.opacity ?? 1
285
+ );
286
+ const sw = line.width ?? 1;
287
+
288
+ let rotate = Math.round(line.rotate ?? 0) % 360;
289
+ if (rotate > 180) rotate = rotate - 360;
290
+ else if (rotate > 90) rotate = rotate - 180;
291
+ else if (rotate < -180) rotate = rotate + 360;
292
+ else if (rotate < -90) rotate = rotate + 180;
293
+
294
+ let angle: number;
295
+ let period: number;
296
+ if (rotate === 0) {
297
+ angle = 0;
298
+ period = height;
299
+ } else if (rotate === 90) {
300
+ angle = 90;
301
+ period = width;
302
+ } else if (rotate > 0) {
303
+ angle = 45;
304
+ period = (width * height) / Math.sqrt(width * width + height * height);
305
+ } else {
306
+ angle = 135;
307
+ period = (width * height) / Math.sqrt(width * width + height * height);
308
+ }
309
+
310
+ layers.push(
311
+ `repeating-linear-gradient(${angle}deg, ${color} 0 ${sw}px, transparent ${sw}px ${period}px)`
312
+ );
313
+ }
314
+ }
315
+
316
+ if (circlesProp) {
317
+ const circleDefs = Array.isArray(circlesProp)
318
+ ? circlesProp
319
+ : circlesProp === true
320
+ ? [{}]
321
+ : [circlesProp];
322
+ for (const circle of circleDefs) {
323
+ const color = withOpacity(
324
+ circle.color ?? 'var(--color-surface-content, currentColor)',
325
+ circle.opacity ?? 1
326
+ );
327
+ const r = circle.radius ?? 1;
328
+
329
+ if (circle.stagger) {
330
+ layers.push(
331
+ `radial-gradient(circle at 25% 25%, ${color} ${r}px, transparent ${r}px) 0 0 / ${size}px ${size}px`,
332
+ `radial-gradient(circle at 75% 75%, ${color} ${r}px, transparent ${r}px) 0 0 / ${size}px ${size}px`
333
+ );
334
+ } else {
335
+ layers.push(
336
+ `radial-gradient(circle at center, ${color} ${r}px, transparent ${r}px) 0 0 / ${size}px ${size}px`
337
+ );
338
+ }
339
+ }
340
+ }
341
+
342
+ // `background` may be either a plain color or an image value (e.g. `linear-gradient(...)`
343
+ // when used as `<Pattern background={gradient}>`). Images go in the image-layer list
344
+ // tiled at the pattern size (after shape layers so they paint underneath);
345
+ // colors go in the background-color slot.
346
+ const isImage = background != null && /gradient\(|url\(/i.test(background);
347
+ if (isImage) layers.push(`${background} 0 0 / ${width}px ${height}px`);
348
+
349
+ if (layers.length === 0) return background ?? 'transparent';
350
+ return !isImage && background ? `${layers.join(', ')}, ${background}` : layers.join(', ');
257
351
  }
258
352
  </script>
259
353
 
@@ -299,4 +393,6 @@
299
393
  </defs>
300
394
 
301
395
  {@render children?.({ id, pattern: `url(#${id})` })}
396
+ {:else if layerCtx === 'html'}
397
+ {@render children?.({ id, pattern: createCSSPattern() })}
302
398
  {/if}
@@ -75,7 +75,9 @@ export type PatternPropsWithoutHTML = {
75
75
  */
76
76
  background?: string;
77
77
  /**
78
- * Render as a child of the pattern
78
+ * Render as a child of the pattern.
79
+ *
80
+ * Note: only supported on the `<Svg>` layer.
79
81
  */
80
82
  patternContent?: Snippet;
81
83
  children?: Snippet<[{
@@ -109,9 +109,7 @@
109
109
 
110
110
  const endAngle = $derived(
111
111
  endAngleProp ??
112
- degreesToRadians(
113
- (ctx.config.xRange ? max(ctx.config.xRange as number[]) : max(range))!
114
- )
112
+ degreesToRadians((ctx.config.xRange ? max(ctx.config.xRange as number[]) : max(range))!)
115
113
  );
116
114
 
117
115
  const motionEndAngle = createMotion(0, () => endAngle, motion);
@@ -120,9 +118,7 @@
120
118
  let _pie = d3pie<any>()
121
119
  .startAngle(
122
120
  startAngleProp ??
123
- degreesToRadians(
124
- (ctx.config.xRange ? min(ctx.config.xRange as number[]) : min(range))!
125
- )
121
+ degreesToRadians((ctx.config.xRange ? min(ctx.config.xRange as number[]) : min(range))!)
126
122
  )
127
123
  .endAngle(motionEndAngle.current)
128
124
  .padAngle(padAngle)
@@ -144,10 +144,14 @@
144
144
  }
145
145
 
146
146
  if (layerCtx === 'canvas') {
147
- ctx.registerComponent({ name: 'Gradient', kind: 'group', canvasRender: {
148
- render,
149
- deps: () => [stops, cx, cy, fx, fy, ctx.width, ctx.height],
150
- } });
147
+ ctx.registerComponent({
148
+ name: 'Gradient',
149
+ kind: 'group',
150
+ canvasRender: {
151
+ render,
152
+ deps: () => [stops, cx, cy, fx, fy, ctx.width, ctx.height],
153
+ },
154
+ });
151
155
  }
152
156
  </script>
153
157
 
@@ -5,7 +5,13 @@
5
5
  import { createMotion, parseMotionProp, type MotionProp } from '../utils/motion.svelte.js';
6
6
  import { renderRect, type ComputedStylesOptions } from '../utils/canvas.js';
7
7
  import type { DataProp, DataDrivenStyleProps } from '../utils/dataProp.js';
8
- import type { Insets } from '../utils/rect.svelte.js';
8
+ import {
9
+ resolveCorners,
10
+ cornersUniform,
11
+ type Corners,
12
+ type Insets,
13
+ } from '../utils/rect.svelte.js';
14
+ import { roundedRectPath, parseDashArray } from '../utils/path.js';
9
15
 
10
16
  export type RectPropsWithoutHTML = {
11
17
  /**
@@ -119,6 +125,20 @@
119
125
  /** Motion configuration (pixel mode only). */
120
126
  motion?: MotionProp<'x' | 'y' | 'width' | 'height'>;
121
127
 
128
+ /**
129
+ * Dashed-border pattern. Accepts a number (single dash length), a
130
+ * `[dash, gap, ...]` array, or a string (same syntax as SVG
131
+ * `stroke-dasharray`). HTML layer approximates via `border-style: dashed`.
132
+ */
133
+ dashArray?: number | number[] | string;
134
+
135
+ /**
136
+ * Per-corner radii. Accepts a number (all corners equal — same as `rx`),
137
+ * a `[tl, tr, br, bl]` tuple, or `{ topLeft, topRight, bottomRight, bottomLeft }`.
138
+ * Takes precedence over `rx`/`ry` when corners differ.
139
+ */
140
+ corners?: Corners;
141
+
122
142
  /** Children content to render. Note: Only works for Html layers */
123
143
  children?: Snippet;
124
144
  } & DataDrivenStyleProps;
@@ -175,6 +195,8 @@
175
195
  key: keyFn = (_: any, i: number) => i,
176
196
  ref: refProp = $bindable(),
177
197
  motion,
198
+ corners,
199
+ dashArray,
178
200
  class: className,
179
201
  onclick,
180
202
  ondblclick,
@@ -279,10 +301,24 @@
279
301
  });
280
302
  });
281
303
 
304
+ // Fold a uniform `corners` value into rx/ry so SVG `<rect>` renders rounded
305
+ // corners without needing a `<path>`. When corners are per-corner different,
306
+ // `pixelPathData` kicks in below and SVG emits a `<path>` instead.
307
+ const dashArrayResolved = $derived(parseDashArray(dashArray));
308
+ const dashArrayAttr = $derived(dashArrayResolved ? dashArrayResolved.join(' ') : undefined);
309
+
310
+ const cornersUniformValue = $derived.by(() => {
311
+ if (corners === undefined) return undefined;
312
+ if (typeof corners === 'number') return corners;
313
+ const resolved = resolveCorners(corners, Infinity, Infinity);
314
+ return cornersUniform(resolved) ? resolved[0] : undefined;
315
+ });
316
+ const cornersNonUniform = $derived(corners !== undefined && cornersUniformValue === undefined);
317
+
282
318
  // Normalize rx/ry - if only one is provided, use it for both (SVG behavior)
283
319
  // Coerce to number for canvas rendering (SVG allows string like "50%")
284
- const rx = $derived(Number(rxProp ?? ryProp) || 0);
285
- const ry = $derived(Number(ryProp ?? rxProp) || 0);
320
+ const rx = $derived(Number(rxProp ?? ryProp ?? cornersUniformValue) || 0);
321
+ const ry = $derived(Number(ryProp ?? rxProp ?? cornersUniformValue) || 0);
286
322
 
287
323
  // --- Pixel mode ---
288
324
  let ref = $state<SVGRectElement>();
@@ -330,11 +366,38 @@
330
366
  const staticStrokeWidth = $derived(typeof strokeWidth === 'number' ? strokeWidth : undefined);
331
367
  const staticOpacity = $derived(typeof opacity === 'number' ? opacity : undefined);
332
368
  const staticClassName = $derived(typeof className === 'string' ? className : undefined);
369
+ // Match SVG's implicit `stroke-width: 1` default: if `stroke` is set but
370
+ // `strokeWidth` is not, render a 1px border so HTML matches SVG/Canvas layers.
333
371
  const staticBorderWidth = $derived(
334
- typeof strokeWidth === 'number' ? `${strokeWidth}px` : undefined
372
+ typeof strokeWidth === 'number'
373
+ ? `${strokeWidth}px`
374
+ : typeof stroke === 'string'
375
+ ? '1px'
376
+ : undefined
335
377
  );
336
378
  const htmlRestProps = $derived(restProps as unknown as HTMLAttributes<HTMLDivElement>);
337
379
 
380
+ // Resolved per-corner radii for the static (pixel-mode) rect, clamped to bounds.
381
+ const resolvedCorners = $derived(
382
+ corners !== undefined
383
+ ? resolveCorners(corners, motionWidth.current, motionHeight.current)
384
+ : undefined
385
+ );
386
+ const borderRadiusStyle = $derived(
387
+ resolvedCorners ? resolvedCorners.map((c) => `${c}px`).join(' ') : undefined
388
+ );
389
+ const pixelPathData = $derived(
390
+ resolvedCorners && cornersNonUniform
391
+ ? roundedRectPath(
392
+ motionX.current,
393
+ motionY.current,
394
+ motionWidth.current,
395
+ motionHeight.current,
396
+ resolvedCorners
397
+ )
398
+ : undefined
399
+ );
400
+
338
401
  function getStyleOptions(
339
402
  styleOverrides: ComputedStylesOptions | undefined,
340
403
  itemFill?: string | undefined,
@@ -371,7 +434,13 @@
371
434
  'lc-rect',
372
435
  itemClass ?? (typeof className === 'string' ? className : undefined)
373
436
  ),
374
- style: restProps.style as string | undefined,
437
+ style:
438
+ [
439
+ restProps.style as string | undefined,
440
+ dashArrayAttr ? `stroke-dasharray: ${dashArrayAttr}` : undefined,
441
+ ]
442
+ .filter(Boolean)
443
+ .join('; ') || undefined,
375
444
  };
376
445
  }
377
446
 
@@ -422,6 +491,7 @@
422
491
  height: motionHeight.current,
423
492
  rx,
424
493
  ry,
494
+ corners: resolvedCorners,
425
495
  },
426
496
  styleOpts
427
497
  );
@@ -475,6 +545,8 @@
475
545
  restProps.style,
476
546
  rx,
477
547
  ry,
548
+ resolvedCorners,
549
+ dashArrayAttr,
478
550
  ],
479
551
  }
480
552
  : undefined,
@@ -504,6 +576,7 @@
504
576
  opacity={resolvedOpacity}
505
577
  {rx}
506
578
  {ry}
579
+ stroke-dasharray={dashArrayAttr}
507
580
  class={cls('lc-rect', resolvedClass)}
508
581
  {...restProps}
509
582
  {onclick}
@@ -515,6 +588,28 @@
515
588
  {onpointerout}
516
589
  />
517
590
  {/each}
591
+ {:else if pixelPathData}
592
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
593
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
594
+ <path
595
+ d={pixelPathData}
596
+ fill={staticFill}
597
+ fill-opacity={staticFillOpacity}
598
+ stroke={staticStroke}
599
+ stroke-opacity={staticStrokeOpacity}
600
+ stroke-width={staticStrokeWidth}
601
+ opacity={staticOpacity}
602
+ stroke-dasharray={dashArrayAttr}
603
+ class={cls('lc-rect', staticClassName)}
604
+ {...restProps as unknown as SVGAttributes<SVGPathElement>}
605
+ {onclick}
606
+ {ondblclick}
607
+ {onpointerenter}
608
+ {onpointermove}
609
+ {onpointerleave}
610
+ {onpointerover}
611
+ {onpointerout}
612
+ />
518
613
  {:else}
519
614
  <rect
520
615
  x={motionX.current}
@@ -529,6 +624,7 @@
529
624
  opacity={staticOpacity}
530
625
  {rx}
531
626
  {ry}
627
+ stroke-dasharray={dashArrayAttr}
532
628
  class={cls('lc-rect', staticClassName)}
533
629
  {...restProps}
534
630
  {onclick}
@@ -550,6 +646,12 @@
550
646
  {@const resolvedStrokeWidth = resolveStyleProp(strokeWidth, item.d)}
551
647
  {@const resolvedOpacity = resolveStyleProp(opacity, item.d)}
552
648
  {@const resolvedClass = resolveStyleProp(className, item.d)}
649
+ {@const resolvedBorderWidth =
650
+ resolvedStrokeWidth != null
651
+ ? `${resolvedStrokeWidth}px`
652
+ : resolvedStroke != null
653
+ ? '1px'
654
+ : undefined}
553
655
  <!-- svelte-ignore a11y_click_events_have_key_events -->
554
656
  <!-- svelte-ignore a11y_no_static_element_interactions -->
555
657
  <div
@@ -559,9 +661,10 @@
559
661
  style:width="{item.width}px"
560
662
  style:height="{item.height}px"
561
663
  style:background={resolvedFill}
664
+ style:background-origin="border-box"
562
665
  style:opacity={resolvedOpacity}
563
- style:border-width="{resolvedStrokeWidth}px"
564
- style:border-style="solid"
666
+ style:border-width={resolvedBorderWidth}
667
+ style:border-style={dashArrayResolved ? 'dashed' : 'solid'}
565
668
  style:border-color={resolvedStroke}
566
669
  style:border-radius="{rx}px"
567
670
  class={cls('lc-rect', resolvedClass)}
@@ -585,11 +688,12 @@
585
688
  style:width="{motionWidth.current}px"
586
689
  style:height="{motionHeight.current}px"
587
690
  style:background={staticFill}
691
+ style:background-origin="border-box"
588
692
  style:opacity={staticOpacity}
589
693
  style:border-width={staticBorderWidth}
590
- style:border-style="solid"
694
+ style:border-style={dashArrayResolved ? 'dashed' : 'solid'}
591
695
  style:border-color={staticStroke}
592
- style:border-radius="{rx}px"
696
+ style:border-radius={borderRadiusStyle ?? `${rx}px`}
593
697
  class={cls('lc-rect', staticClassName)}
594
698
  {...htmlRestProps}
595
699
  {onclick}
@@ -621,6 +725,10 @@
621
725
  }
622
726
 
623
727
  /* Html layers */
728
+ :global(:where(.lc-layout-html .lc-rect)) {
729
+ /* Match SVG sizing/positioning (visual extent equals `width`×`height`, border on outer edge) */
730
+ box-sizing: border-box;
731
+ }
624
732
  :global(:where(.lc-layout-html .lc-rect):not([background])) {
625
733
  background: var(--fill-color);
626
734
  }
@@ -3,7 +3,7 @@ import type { SVGAttributes } from 'svelte/elements';
3
3
  import type { CommonEvents, Without } from '../utils/types.js';
4
4
  import { type MotionProp } from '../utils/motion.svelte.js';
5
5
  import type { DataProp, DataDrivenStyleProps } from '../utils/dataProp.js';
6
- import type { Insets } from '../utils/rect.svelte.js';
6
+ import { type Corners, type Insets } from '../utils/rect.svelte.js';
7
7
  export type RectPropsWithoutHTML = {
8
8
  /**
9
9
  * The x position of the rectangle.
@@ -101,6 +101,18 @@ export type RectPropsWithoutHTML = {
101
101
  ref?: SVGRectElement;
102
102
  /** Motion configuration (pixel mode only). */
103
103
  motion?: MotionProp<'x' | 'y' | 'width' | 'height'>;
104
+ /**
105
+ * Dashed-border pattern. Accepts a number (single dash length), a
106
+ * `[dash, gap, ...]` array, or a string (same syntax as SVG
107
+ * `stroke-dasharray`). HTML layer approximates via `border-style: dashed`.
108
+ */
109
+ dashArray?: number | number[] | string;
110
+ /**
111
+ * Per-corner radii. Accepts a number (all corners equal — same as `rx`),
112
+ * a `[tl, tr, br, bl]` tuple, or `{ topLeft, topRight, bottomRight, bottomLeft }`.
113
+ * Takes precedence over `rx`/`ry` when corners differ.
114
+ */
115
+ corners?: Corners;
104
116
  /** Children content to render. Note: Only works for Html layers */
105
117
  children?: Snippet;
106
118
  } & DataDrivenStyleProps;
@@ -1,5 +1,5 @@
1
1
  <script lang="ts" module>
2
- import Rect, { type RectPropsWithoutHTML } from './Rect.svelte';
2
+ import { type RectPropsWithoutHTML } from './Rect.svelte';
3
3
  import type { CommonEvents, Without } from '../utils/types.js';
4
4
  import type { SVGAttributes } from 'svelte/elements';
5
5
  import type { Snippet } from 'svelte';
@@ -45,6 +45,13 @@
45
45
  */
46
46
  disabled?: boolean;
47
47
 
48
+ /**
49
+ * Invert the clip — content renders *outside* the rect.
50
+ *
51
+ * @default false
52
+ */
53
+ invert?: boolean;
54
+
48
55
  /**
49
56
  * The default children snippet which provides
50
57
  * the id and url for the clipPath.
@@ -65,7 +72,6 @@
65
72
  <script lang="ts">
66
73
  import ClipPath from './ClipPath.svelte';
67
74
  import { createId } from '../utils/createId.js';
68
- import { extractLayerProps } from '../utils/attributes.js';
69
75
  import type { MotionProp } from '../utils/motion.svelte.js';
70
76
 
71
77
  const uid = $props.id();
@@ -77,24 +83,14 @@
77
83
  width,
78
84
  height,
79
85
  disabled = false,
86
+ invert = false,
80
87
  children: childrenProp,
81
- ...restProps
82
88
  }: RectClipPathProps = $props();
83
89
 
84
- function canvasClip(ctx: CanvasRenderingContext2D) {
85
- ctx.beginPath();
86
- ctx.rect(x, y, width, height);
87
- }
88
-
89
- function canvasClipDeps() {
90
- return [x, y, width, height];
91
- }
90
+ const path = $derived(`M${x},${y} h${width} v${height} h${-width} Z`);
92
91
  </script>
93
92
 
94
- <ClipPath {id} {disabled} {canvasClip} {canvasClipDeps}>
95
- {#snippet clip()}
96
- <Rect {x} {y} {width} {height} {...extractLayerProps(restProps, 'lc-clip-path-rect')} />
97
- {/snippet}
93
+ <ClipPath {id} {disabled} {invert} {path}>
98
94
  {#snippet children({ url })}
99
95
  {@render childrenProp?.({ id, url })}
100
96
  {/snippet}
@@ -37,6 +37,12 @@ export type BaseRectClipPathPropsWithoutHTML = {
37
37
  * @default false
38
38
  */
39
39
  disabled?: boolean;
40
+ /**
41
+ * Invert the clip — content renders *outside* the rect.
42
+ *
43
+ * @default false
44
+ */
45
+ invert?: boolean;
40
46
  /**
41
47
  * The default children snippet which provides
42
48
  * the id and url for the clipPath.
@@ -95,12 +95,30 @@
95
95
  const ctx = getChartContext();
96
96
  const geo = getGeoContext();
97
97
 
98
- let { data, x, y, seriesKey, defined, curve, stroke, fill, opacity, motion, ...restProps }: SplineProps = $props();
98
+ let {
99
+ data,
100
+ x,
101
+ y,
102
+ seriesKey,
103
+ defined,
104
+ curve,
105
+ stroke,
106
+ fill,
107
+ opacity,
108
+ motion,
109
+ ...restProps
110
+ }: SplineProps = $props();
99
111
 
100
112
  ctx.registerComponent({
101
113
  name: 'Spline',
102
114
  kind: 'mark',
103
- markInfo: () => ({ data, x, y, seriesKey, color: typeof stroke === 'string' ? stroke : undefined }),
115
+ markInfo: () => ({
116
+ data,
117
+ x,
118
+ y,
119
+ seriesKey,
120
+ color: typeof stroke === 'string' ? stroke : undefined,
121
+ }),
104
122
  });
105
123
 
106
124
  function getScaleValue(
@@ -267,8 +285,8 @@
267
285
 
268
286
  const seriesOpacity = $derived(
269
287
  series?.key == null ||
270
- ctx.series.visibleSeries.length <= 1 ||
271
- ctx.series.isHighlighted(series.key, true)
288
+ ctx.series.visibleSeries.length <= 1 ||
289
+ ctx.series.isHighlighted(series.key, true)
272
290
  ? 1
273
291
  : 0.1
274
292
  );
@@ -686,7 +686,14 @@
686
686
  if (segments) {
687
687
  let xOffset = baseX;
688
688
  for (const segment of segments) {
689
- const segStyles = getTextStyles(undefined, undefined, undefined, undefined, undefined, segment.class);
689
+ const segStyles = getTextStyles(
690
+ undefined,
691
+ undefined,
692
+ undefined,
693
+ undefined,
694
+ undefined,
695
+ segment.class
696
+ );
690
697
  const text = String(segment.value);
691
698
  // Set font before rendering and measuring so width is accurate
692
699
  const segComputedStyles = getComputedStyles(ctx.canvas, segStyles);
@@ -838,10 +845,7 @@
838
845
  >
839
846
  {#if segments}
840
847
  {#each segments as segment, index (index)}
841
- <tspan
842
- dy={index === 0 ? startDy : 0}
843
- class={['lc-text-tspan', segment.class]}
844
- >
848
+ <tspan dy={index === 0 ? startDy : 0} class={['lc-text-tspan', segment.class]}>
845
849
  {segment.value}
846
850
  </tspan>
847
851
  {/each}
@@ -864,6 +868,9 @@
864
868
  {#if dataMode}
865
869
  {#each resolvedItems as item (item.key)}
866
870
  {@const text = resolveTextValue(item.d)}
871
+ {@const resolvedFill = resolveColorProp(fill, item.d, chartCtx.cScale)}
872
+ {@const resolvedFillOpacity = resolveStyleProp(fillOpacity, item.d)}
873
+ {@const resolvedOpacity = resolveStyleProp(opacity, item.d)}
867
874
  {@const resolvedClass = resolveStyleProp(className, item.d)}
868
875
  {@const translateX = textAnchor === 'middle' ? '-50%' : textAnchor === 'end' ? '-100%' : '0%'}
869
876
  {@const translateY =
@@ -881,6 +888,8 @@
881
888
  {textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'right' : 'left'}"
882
889
  style:white-space="pre-wrap"
883
890
  style:line-height={lineHeight}
891
+ style:color={resolvedFill}
892
+ style:opacity={resolvedOpacity ?? resolvedFillOpacity}
884
893
  class={['lc-text', resolvedClass]}
885
894
  >
886
895
  {text}
@@ -903,6 +912,8 @@
903
912
  {textAnchor === 'middle' ? 'center' : textAnchor === 'end' ? 'right' : 'left'}"
904
913
  style:white-space="pre-wrap"
905
914
  style:line-height={lineHeight}
915
+ style:color={staticFill}
916
+ style:opacity={staticOpacity ?? staticFillOpacity}
906
917
  class={['lc-text', staticClassName]}
907
918
  >
908
919
  {#if segments}
@@ -180,9 +180,15 @@
180
180
  .map((d: any) => ({
181
181
  x: getScaleValue(d, ctx.xScale, xAccessor) + xOffset,
182
182
  y: getScaleValue(d, ctx.yScale, yAccessor) + yOffset,
183
- r: resolvedR != null
184
- ? resolveDataProp(resolvedR, d, ctx.rScale, typeof resolvedR === 'number' ? resolvedR : 4)
185
- : 4,
183
+ r:
184
+ resolvedR != null
185
+ ? resolveDataProp(
186
+ resolvedR,
187
+ d,
188
+ ctx.rScale,
189
+ typeof resolvedR === 'number' ? resolvedR : 4
190
+ )
191
+ : 4,
186
192
  }));
187
193
 
188
194
  return computeTrailPath(points, { curve, cap, tension, resolution });
@@ -206,9 +212,15 @@
206
212
  .map((d: any) => ({
207
213
  x: getScaleValue(d, ctx.xScale, xAccessor) + xOffset,
208
214
  y: baseline,
209
- r: resolvedR != null
210
- ? resolveDataProp(resolvedR, d, ctx.rScale, typeof resolvedR === 'number' ? resolvedR : 4)
211
- : 4,
215
+ r:
216
+ resolvedR != null
217
+ ? resolveDataProp(
218
+ resolvedR,
219
+ d,
220
+ ctx.rScale,
221
+ typeof resolvedR === 'number' ? resolvedR : 4
222
+ )
223
+ : 4,
212
224
  }));
213
225
 
214
226
  return computeTrailPath(points, { curve, cap, tension, resolution });
@@ -236,7 +248,7 @@
236
248
  <Path
237
249
  pathData={isTweened ? tweenState.current : trailPath}
238
250
  {fill}
239
- fillOpacity={fillOpacity}
251
+ {fillOpacity}
240
252
  {opacity}
241
253
  stroke="none"
242
254
  class={cls('lc-trail', className)}
@@ -49,9 +49,13 @@
49
49
  const ctx = getChartContext();
50
50
 
51
51
  const treeData = $derived.by(() => {
52
- const _tree = d3Tree<T>().size(
53
- orientation === 'horizontal' ? [ctx.height, ctx.width] : [ctx.width, ctx.height]
54
- );
52
+ const _tree = ctx.radial
53
+ ? d3Tree<T>()
54
+ .size([2 * Math.PI, Math.min(ctx.width, ctx.height) / 2])
55
+ .separation((a, b) => (a.parent === b.parent ? 1 : 2) / a.depth)
56
+ : d3Tree<T>().size(
57
+ orientation === 'horizontal' ? [ctx.height, ctx.width] : [ctx.width, ctx.height]
58
+ );
55
59
 
56
60
  if (nodeSize) {
57
61
  _tree.nodeSize(nodeSize);