layerchart 2.0.0-next.61 → 2.0.0-next.62

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 (140) hide show
  1. package/dist/canvas.d.ts +2 -2
  2. package/dist/canvas.js +2 -2
  3. package/dist/components/Arc/Arc.base.svelte +49 -11
  4. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-full-circle--360-degree-range--1.png +0 -0
  5. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-full-circle--360-degree-range--2.png +0 -0
  6. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-innerRadius-of-0--pie-slice--1.png +0 -0
  7. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-innerRadius-of-0--pie-slice--2.png +0 -0
  8. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-negative-domain-values-1.png +0 -0
  9. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-negative-domain-values-2.png +0 -0
  10. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-partial-arc--e-g---180-degrees--1.png +0 -0
  11. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-partial-arc--e-g---180-degrees--2.png +0 -0
  12. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-at-max-domain-1.png +0 -0
  13. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-at-max-domain-2.png +0 -0
  14. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-below-domain-min-1.png +0 -0
  15. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-below-domain-min-2.png +0 -0
  16. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-exceeding-domain-max-1.png +0 -0
  17. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-exceeding-domain-max-2.png +0 -0
  18. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-of-0-1.png +0 -0
  19. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-of-0-2.png +0 -0
  20. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-pointer-enter-events-1.png +0 -0
  21. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-pointer-enter-events-2.png +0 -0
  22. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-pointer-move-events-1.png +0 -0
  23. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-pointer-move-events-2.png +0 -0
  24. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-touch-move-events-1.png +0 -0
  25. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-touch-move-events-2.png +0 -0
  26. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-custom-class-1.png +0 -0
  27. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-custom-class-2.png +0 -0
  28. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-fill-color-1.png +0 -0
  29. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-fill-color-2.png +0 -0
  30. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-fillOpacity-1.png +0 -0
  31. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-fillOpacity-2.png +0 -0
  32. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-offset-to-arc-position-1.png +0 -0
  33. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-offset-to-arc-position-2.png +0 -0
  34. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-opacity-1.png +0 -0
  35. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-opacity-2.png +0 -0
  36. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-stroke-color-1.png +0 -0
  37. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-stroke-color-2.png +0 -0
  38. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-strokeWidth-1.png +0 -0
  39. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-strokeWidth-2.png +0 -0
  40. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-zero-offset-by-default-1.png +0 -0
  41. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-zero-offset-by-default-2.png +0 -0
  42. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-call-tooltip-hide-on-pointer-leave-1.png +0 -0
  43. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-call-tooltip-hide-on-pointer-leave-2.png +0 -0
  44. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-call-tooltip-show-on-pointer-enter-with-data-1.png +0 -0
  45. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-call-tooltip-show-on-pointer-enter-with-data-2.png +0 -0
  46. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-handle-custom-start-angle-in-range-1.png +0 -0
  47. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-handle-custom-start-angle-in-range-2.png +0 -0
  48. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-have-stroke--none--by-default-1.png +0 -0
  49. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-have-stroke--none--by-default-2.png +0 -0
  50. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-an-arc-path-with-value-1.png +0 -0
  51. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-an-arc-path-with-value-2.png +0 -0
  52. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-when-track-prop-is-provided-1.png +0 -0
  53. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-when-track-prop-is-provided-2.png +0 -0
  54. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-custom-class-1.png +0 -0
  55. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-custom-class-2.png +0 -0
  56. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackCornerRadius-1.png +0 -0
  57. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackCornerRadius-2.png +0 -0
  58. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackEndAngle-1.png +0 -0
  59. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackEndAngle-2.png +0 -0
  60. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackInnerRadius-1.png +0 -0
  61. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackInnerRadius-2.png +0 -0
  62. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackInnerRadius-and-trackOuterRadius-1.png +0 -0
  63. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackInnerRadius-and-trackOuterRadius-2.png +0 -0
  64. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackOuterRadius-1.png +0 -0
  65. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackOuterRadius-2.png +0 -0
  66. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackPadAngle-1.png +0 -0
  67. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackPadAngle-2.png +0 -0
  68. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackStartAngle-1.png +0 -0
  69. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackStartAngle-2.png +0 -0
  70. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackStartAngle-and-trackEndAngle-1.png +0 -0
  71. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackStartAngle-and-trackEndAngle-2.png +0 -0
  72. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-both-startAngle-and-endAngle-1.png +0 -0
  73. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-both-startAngle-and-endAngle-2.png +0 -0
  74. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-cornerRadius-1.png +0 -0
  75. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-cornerRadius-2.png +0 -0
  76. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-domain-1.png +0 -0
  77. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-domain-2.png +0 -0
  78. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-domain-and-range-1.png +0 -0
  79. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-domain-and-range-2.png +0 -0
  80. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-range-1.png +0 -0
  81. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-range-2.png +0 -0
  82. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-endAngle-in-radians-1.png +0 -0
  83. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-endAngle-in-radians-2.png +0 -0
  84. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-innerRadius-1.png +0 -0
  85. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-innerRadius-2.png +0 -0
  86. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-innerRadius-and-outerRadius-1.png +0 -0
  87. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-innerRadius-and-outerRadius-2.png +0 -0
  88. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-outerRadius-1.png +0 -0
  89. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-outerRadius-2.png +0 -0
  90. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-padAngle-1.png +0 -0
  91. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-padAngle-2.png +0 -0
  92. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-startAngle-in-radians-1.png +0 -0
  93. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-startAngle-in-radians-2.png +0 -0
  94. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-should-render-Arc-element-1.png +0 -0
  95. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-should-render-Arc-element-2.png +0 -0
  96. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-should-render-track-1.png +0 -0
  97. package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-should-render-track-2.png +0 -0
  98. package/dist/components/{ArcLabel.svelte.test.js → ArcLabel/ArcLabel.svelte.test.js} +3 -3
  99. package/dist/components/ArcLabel/__screenshots__/ArcLabel.svelte.test.ts/ArcLabel-renders-a-text-element-with-the-supplied-value-at-the-centroid-1.png +0 -0
  100. package/dist/components/ArcLabel/__screenshots__/ArcLabel.svelte.test.ts/ArcLabel-renders-a-text-element-with-the-supplied-value-at-the-centroid-2.png +0 -0
  101. package/dist/components/Blur/Blur.canvas.svelte +25 -0
  102. package/dist/components/Blur/Blur.canvas.svelte.d.ts +4 -0
  103. package/dist/components/Blur/Blur.html.svelte +11 -0
  104. package/dist/components/Blur/Blur.html.svelte.d.ts +4 -0
  105. package/dist/components/{Blur.svelte.d.ts → Blur/Blur.shared.svelte.d.ts} +3 -5
  106. package/dist/components/Blur/Blur.svelte +23 -0
  107. package/dist/components/Blur/Blur.svelte.d.ts +4 -0
  108. package/dist/components/Blur/Blur.svg.svelte +24 -0
  109. package/dist/components/Blur/Blur.svg.svelte.d.ts +4 -0
  110. package/dist/components/Chart/Chart.base.svelte +13 -7
  111. package/dist/components/Chart/ChartCore.svelte.test.d.ts +1 -0
  112. package/dist/components/{ChartCore.svelte.test.js → Chart/ChartCore.svelte.test.js} +1 -1
  113. package/dist/components/Link/Link.base.svelte +15 -9
  114. package/dist/components/Path/Path.canvas.svelte +5 -2
  115. package/dist/components/Path/Path.shared.svelte.d.ts +17 -4
  116. package/dist/components/Path/Path.shared.svelte.js +26 -8
  117. package/dist/components/Path/Path.svg.svelte +75 -60
  118. package/dist/components/RectClipPath/RectClipPath.base.svelte +25 -1
  119. package/dist/components/RectClipPath/RectClipPath.shared.svelte.d.ts +8 -0
  120. package/dist/components/Trail/Trail.base.svelte +10 -7
  121. package/dist/components/charts/__screenshots__/ArcChart.svelte.test.ts/ArcChart-uses-the-chart-value-accessor-for-explicit-per-series-tooltip-values-1.png +0 -0
  122. package/dist/components/charts/__screenshots__/ArcChart.svelte.test.ts/ArcChart-uses-the-chart-value-accessor-for-explicit-per-series-tooltip-values-2.png +0 -0
  123. package/dist/components/charts/__screenshots__/BarChart.svelte.test.ts/BarChart-legend-series-toggle-adjusts-group-scale-should-adjust-grouped-bar-widths-when-series-are-toggled-via-legend-1.png +0 -0
  124. package/dist/components/charts/__screenshots__/PieChart.svelte.test.ts/PieChart-uses-hovered-slice-identity-for-implicit-tooltip-series-1.png +0 -0
  125. package/dist/components/charts/__screenshots__/PieChart.svelte.test.ts/PieChart-uses-hovered-slice-identity-for-implicit-tooltip-series-2.png +0 -0
  126. package/dist/components/index.d.ts +2 -2
  127. package/dist/components/index.js +2 -2
  128. package/dist/html.d.ts +2 -2
  129. package/dist/html.js +2 -2
  130. package/dist/states/chart.svelte.d.ts +4 -2
  131. package/dist/states/chart.svelte.js +45 -18
  132. package/dist/states/chart.svelte.test.js +1 -1
  133. package/dist/states/series.svelte.js +9 -13
  134. package/dist/states/series.svelte.test.js +5 -1
  135. package/dist/svg.d.ts +2 -2
  136. package/dist/svg.js +2 -2
  137. package/package.json +1 -1
  138. package/dist/components/Blur.svelte +0 -49
  139. /package/dist/components/{ArcLabel.svelte.test.d.ts → ArcLabel/ArcLabel.svelte.test.d.ts} +0 -0
  140. /package/dist/components/{ChartCore.svelte.test.d.ts → Blur/Blur.shared.svelte.js} +0 -0
@@ -9,9 +9,12 @@
9
9
  import { createKey } from '../../utils/key.svelte.js';
10
10
  import { PathState, type PathProps } from './Path.shared.svelte.js';
11
11
 
12
- let { ...rest }: PathProps = $props();
12
+ let { pathData, ...rest }: PathProps = $props();
13
13
 
14
- const c = new PathState(() => rest as PathProps);
14
+ const c = new PathState(
15
+ () => pathData,
16
+ () => rest as PathProps
17
+ );
15
18
 
16
19
  function render(
17
20
  ctx: CanvasRenderingContext2D,
@@ -7,10 +7,15 @@ import type { ChartState } from '../../states/chart.svelte.js';
7
7
  import type { draw as _drawTransition } from 'svelte/transition';
8
8
  export type PathPropsWithoutHTML = {
9
9
  /**
10
- * Pass `<path d={...} />` explicitly instead of calculating
11
- * from data / context
10
+ * The `d` attribute of the rendered `<path>`.
11
+ *
12
+ * Accepts either a value (resolved at call site) or a function that
13
+ * returns the current value. Passing a function lets the parent avoid
14
+ * re-rendering its own template on every change to the path data —
15
+ * useful when a parent like `Link` / `Spline` / `Area` updates the
16
+ * path on every animation tick across hundreds of instances.
12
17
  */
13
- pathData?: string | undefined | null;
18
+ pathData?: string | undefined | null | (() => string | undefined | null);
14
19
  /**
15
20
  * Whether to animate the drawing of the path over time.
16
21
  * Pass either `true` or an object with transition options to
@@ -66,5 +71,13 @@ export declare class PathState {
66
71
  chartCtx: ChartState;
67
72
  get tweenedPathData(): any;
68
73
  drawKey: symbol;
69
- constructor(getProps: () => PathProps);
74
+ /**
75
+ * @param getPathData Hot-path getter — reads only `pathData`. Kept separate from
76
+ * `getProps` so the `<path d=...>` updater (and the canvas
77
+ * `tweenedPathData` consumer) does not subscribe to every
78
+ * Path prop on every tick.
79
+ * @param getProps Full-props getter — used for one-time / cold-path config
80
+ * (motion, draw).
81
+ */
82
+ constructor(getPathData: () => PathProps['pathData'], getProps?: () => PathProps);
70
83
  }
@@ -2,11 +2,20 @@ import { interpolatePath } from 'd3-interpolate-path';
2
2
  import { flattenPathData } from '../../utils/path.js';
3
3
  import { createMotion, extractTweenConfig, } from '../../utils/motion.svelte.js';
4
4
  import { getChartContext } from '../../contexts/chart.js';
5
+ /** Resolve `pathData` whether it was passed as a value or a getter function. */
6
+ function resolvePathData(v) {
7
+ return typeof v === 'function' ? v() : v;
8
+ }
5
9
  /**
6
10
  * Reactive state shared by every per-layer Path variant.
7
11
  */
8
12
  export class PathState {
9
- #getProps = () => ({});
13
+ // Hot-path getter: reads only `pathData` (or invokes the function-getter form).
14
+ // Kept separate from the full-props getter so that the `<path d=...>` template
15
+ // updater does not subscribe to every Path prop on every read — critical for
16
+ // mark-heavy scenes (force-simulation graphs with hundreds of links updating
17
+ // per tick) where pre-fix each tween read re-evaluated all 15+ props.
18
+ #getPathData;
10
19
  // Contexts
11
20
  chartCtx = getChartContext();
12
21
  // Path data tween source — the actual `d` attribute / canvas render input
@@ -16,8 +25,16 @@ export class PathState {
16
25
  }
17
26
  // Re-key trigger for draw transitions
18
27
  drawKey = $state(Symbol());
19
- constructor(getProps) {
20
- this.#getProps = getProps;
28
+ /**
29
+ * @param getPathData Hot-path getter — reads only `pathData`. Kept separate from
30
+ * `getProps` so the `<path d=...>` updater (and the canvas
31
+ * `tweenedPathData` consumer) does not subscribe to every
32
+ * Path prop on every tick.
33
+ * @param getProps Full-props getter — used for one-time / cold-path config
34
+ * (motion, draw).
35
+ */
36
+ constructor(getPathData, getProps = () => ({})) {
37
+ this.#getPathData = () => resolvePathData(getPathData());
21
38
  const initial = getProps();
22
39
  const extractedTween = extractTweenConfig(initial.motion);
23
40
  const tweenedOptions = extractedTween
@@ -32,18 +49,19 @@ export class PathState {
32
49
  // Fast initial render when not tweened
33
50
  return '';
34
51
  }
35
- else if (initial.pathData) {
36
- return flattenPathData(initial.pathData, Math.min(this.chartCtx.yScale(0) ?? this.chartCtx.yRange[0], this.chartCtx.yRange[0]));
52
+ const resolved = resolvePathData(getPathData());
53
+ if (resolved) {
54
+ return flattenPathData(resolved, Math.min(this.chartCtx.yScale(0) ?? this.chartCtx.yRange[0], this.chartCtx.yRange[0]));
37
55
  }
38
56
  return '';
39
57
  })();
40
- this.#tweenedState = createMotion(defaultPathData, () => getProps().pathData, tweenedOptions);
58
+ this.#tweenedState = createMotion(defaultPathData, this.#getPathData, tweenedOptions);
41
59
  // Re-trigger draw transition when path data changes
42
60
  $effect(() => {
43
61
  if (!getProps().draw)
44
62
  return;
45
- // Touch dependency
46
- void getProps().pathData;
63
+ // Touch dependency (resolves getter form too)
64
+ void this.#getPathData();
47
65
  this.drawKey = Symbol();
48
66
  });
49
67
  }
@@ -16,7 +16,7 @@
16
16
  const uid = $props.id();
17
17
 
18
18
  let {
19
- pathRef: pathRefProp = $bindable(),
19
+ pathRef = $bindable(),
20
20
  marker,
21
21
  markerStart: markerStartProp,
22
22
  markerMid: markerMidProp,
@@ -24,29 +24,35 @@
24
24
  startContent,
25
25
  endContent,
26
26
  draw,
27
+ motion,
28
+ // Extracted out of `rest` so the `<path>` element's `{...rest}`
29
+ // spread doesn't re-evaluate on every frame in mark-heavy scenes
30
+ // (force-simulation graphs with hundreds of links updating per tick).
31
+ // - `pathData`: changes every frame
32
+ // - `class`: parents typically pass `cls(...)` which produces a new
33
+ // string reference per parent render
34
+ // - styling props: explicit on the <path> element below, no need to
35
+ // leak them through the spread
36
+ pathData: _pathData,
37
+ class: classProp,
38
+ fill: fillProp,
39
+ fillOpacity: fillOpacityProp,
40
+ stroke: strokeProp,
41
+ strokeOpacity: strokeOpacityProp,
42
+ strokeWidth: strokeWidthProp,
43
+ opacity: opacityProp,
27
44
  ...rest
28
45
  }: PathProps = $props();
29
46
 
47
+ // Pass `pathData` as its own getter so the hot-path tween read only subscribes
48
+ // to `pathData` (which changes per tick on force sims) and not to every other
49
+ // Path prop. Pre-fix the per-tick `<path d=...>` updater re-read all 15+ props
50
+ // through `getProps()` on each force-sim tick × hundreds of paths.
30
51
  const c = new PathState(
31
- () =>
32
- ({
33
- marker,
34
- markerStart: markerStartProp,
35
- markerMid: markerMidProp,
36
- markerEnd: markerEndProp,
37
- startContent,
38
- endContent,
39
- draw,
40
- ...rest,
41
- }) as PathProps
52
+ () => _pathData,
53
+ () => ({ draw, motion }) as PathProps
42
54
  );
43
55
 
44
- let pathRef = $state<SVGPathElement>();
45
-
46
- $effect.pre(() => {
47
- pathRefProp = pathRef;
48
- });
49
-
50
56
  const markerStart = $derived(markerStartProp ?? marker);
51
57
  const markerMid = $derived(markerMidProp ?? marker);
52
58
  const markerEnd = $derived(markerEndProp ?? marker);
@@ -56,7 +62,6 @@
56
62
  const markerEndId = $derived(markerEnd ? createId('marker-end', uid) : '');
57
63
 
58
64
  const drawTransition = $derived(draw ? _drawTransition : () => ({}));
59
-
60
65
  let startPoint = $state<DOMPoint | undefined>();
61
66
 
62
67
  const endPointDuration = $derived.by(() => {
@@ -70,60 +75,70 @@
70
75
  return 800;
71
76
  });
72
77
 
73
- const endPoint = createControlledMotion<DOMPoint | undefined>(
74
- undefined,
75
- draw
76
- ? {
77
- type: 'tween',
78
- duration: () => endPointDuration,
79
- easing: typeof draw === 'object' && draw.easing ? draw.easing : cubicInOut,
80
- interpolate() {
81
- return (t: number) => {
82
- const totalLength = pathRef?.getTotalLength() ?? 0;
83
- const point = pathRef?.getPointAtLength(totalLength * t);
84
- return point;
85
- };
86
- },
87
- }
88
- : { type: 'none' }
89
- );
90
-
91
- $effect(() => {
92
- if (!startContent && !endContent) return;
93
- // Track path data changes
94
- void c.tweenedPathData;
95
- if (!pathRef) return;
96
-
97
- tick().then(() => {
78
+ // Only allocate the controlled motion container when `draw` is configured;
79
+ // otherwise the per-Path `MotionNone` × hundreds of paths was a measurable
80
+ // mount-time cost in mark-heavy scenes.
81
+ const endPoint = draw
82
+ ? createControlledMotion<DOMPoint | undefined>(undefined, {
83
+ type: 'tween',
84
+ duration: () => endPointDuration,
85
+ easing: typeof draw === 'object' && draw.easing ? draw.easing : cubicInOut,
86
+ interpolate() {
87
+ return (t: number) => {
88
+ const totalLength = pathRef?.getTotalLength() ?? 0;
89
+ const point = pathRef?.getPointAtLength(totalLength * t);
90
+ return point;
91
+ };
92
+ },
93
+ })
94
+ : null;
95
+
96
+ // Only set up path-end tracking when startContent/endContent require it.
97
+ if (startContent || endContent) {
98
+ $effect(() => {
99
+ // Track path data changes
100
+ void c.tweenedPathData;
98
101
  if (!pathRef) return;
99
- const totalLength = pathRef.getTotalLength();
100
- if (!totalLength) return;
101
- startPoint = pathRef.getPointAtLength(0);
102
- endPoint.target = pathRef.getPointAtLength(totalLength);
102
+
103
+ tick().then(() => {
104
+ if (!pathRef) return;
105
+ const totalLength = pathRef.getTotalLength();
106
+ if (!totalLength) return;
107
+ startPoint = pathRef.getPointAtLength(0);
108
+ if (endPoint) {
109
+ endPoint.target = pathRef.getPointAtLength(totalLength);
110
+ }
111
+ });
103
112
  });
104
- });
113
+ }
105
114
  </script>
106
115
 
107
116
  {#key c.drawKey}
108
117
  <path
109
118
  {...rest as any}
110
119
  d={c.tweenedPathData}
111
- fill={rest.fill}
112
- fill-opacity={rest.fillOpacity}
113
- stroke={rest.stroke}
114
- stroke-opacity={rest.strokeOpacity}
115
- stroke-width={rest.strokeWidth}
116
- opacity={rest.opacity}
117
- class={cls('lc-path', rest.class as string | undefined)}
120
+ fill={fillProp}
121
+ fill-opacity={fillOpacityProp}
122
+ stroke={strokeProp}
123
+ stroke-opacity={strokeOpacityProp}
124
+ stroke-width={strokeWidthProp}
125
+ opacity={opacityProp}
126
+ class={cls('lc-path', classProp as string | undefined)}
118
127
  marker-start={markerStartId ? `url(#${markerStartId})` : undefined}
119
128
  marker-mid={markerMidId ? `url(#${markerMidId})` : undefined}
120
129
  marker-end={markerEndId ? `url(#${markerEndId})` : undefined}
121
130
  in:drawTransition|global={typeof draw === 'object' ? draw : undefined}
122
131
  bind:this={pathRef}
123
132
  />
124
- <MarkerWrapper id={markerStartId} marker={markerStart} />
125
- <MarkerWrapper id={markerMidId} marker={markerMid} />
126
- <MarkerWrapper id={markerEndId} marker={markerEnd} />
133
+ {#if markerStart}
134
+ <MarkerWrapper id={markerStartId} marker={markerStart} />
135
+ {/if}
136
+ {#if markerMid}
137
+ <MarkerWrapper id={markerMidId} marker={markerMid} />
138
+ {/if}
139
+ {#if markerEnd}
140
+ <MarkerWrapper id={markerEndId} marker={markerEnd} />
141
+ {/if}
127
142
 
128
143
  {#if startContent && startPoint}
129
144
  <Group x={startPoint.x} y={startPoint.y} class="lc-path-g-start">
@@ -137,7 +152,7 @@
137
152
  </Group>
138
153
  {/if}
139
154
 
140
- {#if endContent && endPoint.current}
155
+ {#if endContent && endPoint?.current}
141
156
  <Group x={endPoint.current.x} y={endPoint.current.y} class="lc-path-g-end">
142
157
  {@render endContent({
143
158
  point: endPoint.current,
@@ -11,6 +11,7 @@
11
11
 
12
12
  <script lang="ts">
13
13
  import { createId } from '../../utils/createId.js';
14
+ import { createMotion, parseMotionProp } from '../../utils/motion.svelte.js';
14
15
 
15
16
  const uid = $props.id();
16
17
 
@@ -19,14 +20,37 @@
19
20
  id = createId('clipPath-', uid),
20
21
  x = 0,
21
22
  y = 0,
23
+ initialX,
24
+ initialY,
22
25
  width,
23
26
  height,
27
+ initialWidth,
28
+ initialHeight,
24
29
  disabled = false,
25
30
  invert = false,
31
+ motion,
26
32
  children: childrenProp,
27
33
  }: RectClipPathBaseProps = $props();
28
34
 
29
- const path = $derived(`M${x},${y} h${width} v${height} h${-width} Z`);
35
+ // When `motion` is undefined `createMotion` returns a passthrough that just
36
+ // reads the getter, so we can call it unconditionally and let the fast path
37
+ // handle the no-motion case.
38
+ const motionX = createMotion(initialX ?? x, () => x, motion && parseMotionProp(motion, 'x'));
39
+ const motionY = createMotion(initialY ?? y, () => y, motion && parseMotionProp(motion, 'y'));
40
+ const motionWidth = createMotion(
41
+ initialWidth ?? width,
42
+ () => width,
43
+ motion && parseMotionProp(motion, 'width')
44
+ );
45
+ const motionHeight = createMotion(
46
+ initialHeight ?? height,
47
+ () => height,
48
+ motion && parseMotionProp(motion, 'height')
49
+ );
50
+
51
+ const path = $derived(
52
+ `M${motionX.current},${motionY.current} h${motionWidth.current} v${motionHeight.current} h${-motionWidth.current} Z`
53
+ );
30
54
  </script>
31
55
 
32
56
  <ClipPath {id} {disabled} {invert} {path}>
@@ -8,12 +8,20 @@ export type BaseRectClipPathPropsWithoutHTML = {
8
8
  id?: string;
9
9
  /** The x position of the clipPath. @default 0 */
10
10
  x?: number;
11
+ /** The initial x position (used as the animation start when `motion` is set). @default x */
12
+ initialX?: number;
11
13
  /** The y position of the clipPath. @default 0 */
12
14
  y?: number;
15
+ /** The initial y position (used as the animation start when `motion` is set). @default y */
16
+ initialY?: number;
13
17
  /** The width of the clipPath. @required */
14
18
  width: number;
19
+ /** The initial width (used as the animation start when `motion` is set). @default width */
20
+ initialWidth?: number;
15
21
  /** The height of the clipPath. @required */
16
22
  height: number;
23
+ /** The initial height (used as the animation start when `motion` is set). @default height */
24
+ initialHeight?: number;
17
25
  /** Whether to disable clipping (show all). @default false */
18
26
  disabled?: boolean;
19
27
  /** Invert the clip — content renders *outside* the rect. @default false */
@@ -122,12 +122,15 @@
122
122
  return '';
123
123
  }
124
124
 
125
- const tweenState = createMotion(defaultPathData(), () => trailPath, {
126
- type: 'tween',
127
- interpolate: interpolatePath,
128
- });
129
-
130
- const isTweened = $derived(extractTweenConfig(motion) != null);
125
+ // Only allocate the tween container when the user opts into a tween via
126
+ // `motion`; otherwise the template reads `trailPath` directly.
127
+ const tweenState =
128
+ extractTweenConfig(motion) != null
129
+ ? createMotion(defaultPathData(), () => trailPath, {
130
+ type: 'tween',
131
+ interpolate: interpolatePath,
132
+ })
133
+ : null;
131
134
 
132
135
  ctx.registerComponent({
133
136
  name: 'Trail',
@@ -137,7 +140,7 @@
137
140
  </script>
138
141
 
139
142
  <Path
140
- pathData={isTweened ? tweenState.current : trailPath}
143
+ pathData={tweenState ? tweenState.current : trailPath}
141
144
  {fill}
142
145
  {fillOpacity}
143
146
  {opacity}
@@ -18,8 +18,8 @@ export { default as Bar } from './Bar/Bar.svelte';
18
18
  export * from './Bar/Bar.svelte';
19
19
  export { default as Bars } from './Bars/Bars.svelte';
20
20
  export * from './Bars/Bars.svelte';
21
- export { default as Blur } from './Blur.svelte';
22
- export * from './Blur.svelte';
21
+ export { default as Blur } from './Blur/Blur.svelte';
22
+ export * from './Blur/Blur.svelte';
23
23
  export { default as BoxPlot } from './BoxPlot/BoxPlot.svelte';
24
24
  export * from './BoxPlot/BoxPlot.svelte';
25
25
  export { default as Bounds } from './Bounds.svelte';
@@ -18,8 +18,8 @@ export { default as Bar } from './Bar/Bar.svelte';
18
18
  export * from './Bar/Bar.svelte';
19
19
  export { default as Bars } from './Bars/Bars.svelte';
20
20
  export * from './Bars/Bars.svelte';
21
- export { default as Blur } from './Blur.svelte';
22
- export * from './Blur.svelte';
21
+ export { default as Blur } from './Blur/Blur.svelte';
22
+ export * from './Blur/Blur.svelte';
23
23
  export { default as BoxPlot } from './BoxPlot/BoxPlot.svelte';
24
24
  export * from './BoxPlot/BoxPlot.svelte';
25
25
  export { default as Bounds } from './Bounds.svelte';
package/dist/html.d.ts CHANGED
@@ -72,8 +72,8 @@ export { default as Calendar } from './components/Calendar/Calendar.html.svelte'
72
72
  export type { CalendarProps, CalendarPropsWithoutHTML, CalendarCell, } from './components/Calendar/Calendar.shared.svelte.js';
73
73
  export { default as Month } from './components/Month/Month.html.svelte';
74
74
  export type { MonthProps, MonthPropsWithoutHTML, MonthCell, } from './components/Month/Month.shared.svelte.js';
75
- export { default as Blur } from './components/Blur.svelte';
76
- export * from './components/Blur.svelte';
75
+ export { default as Blur } from './components/Blur/Blur.svelte';
76
+ export * from './components/Blur/Blur.svelte';
77
77
  export { default as Bounds } from './components/Bounds.svelte';
78
78
  export * from './components/Bounds.svelte';
79
79
  export { default as BrushContext } from './components/BrushContext.svelte';
package/dist/html.js CHANGED
@@ -47,8 +47,8 @@ export { default as Month } from './components/Month/Month.html.svelte';
47
47
  // helpers, context providers, or composite chart wrappers). Re-exported here
48
48
  // so the per-layer sub-path has a complete API.
49
49
  // Helpers / context providers
50
- export { default as Blur } from './components/Blur.svelte';
51
- export * from './components/Blur.svelte';
50
+ export { default as Blur } from './components/Blur/Blur.svelte';
51
+ export * from './components/Blur/Blur.svelte';
52
52
  export { default as Bounds } from './components/Bounds.svelte';
53
53
  export * from './components/Bounds.svelte';
54
54
  export { default as BrushContext } from './components/BrushContext.svelte';
@@ -50,7 +50,6 @@ export interface RegisterComponentOptions<T extends Element = Element> {
50
50
  }
51
51
  export declare class ChartState<TData = any, XScale extends AnyScale = AnyScale, YScale extends AnyScale = AnyScale> {
52
52
  #private;
53
- private _propsGetter;
54
53
  props: ChartPropsWithoutHTML<TData, XScale, YScale>;
55
54
  geoState: GeoState;
56
55
  transformState: TransformState;
@@ -90,7 +89,10 @@ export declare class ChartState<TData = any, XScale extends AnyScale = AnyScale,
90
89
  private _xDomainIsDate;
91
90
  private _yDomainIsDate;
92
91
  meta: Record<string, any>;
93
- constructor(propsGetter: () => ChartPropsWithoutHTML<TData, XScale, YScale>);
92
+ constructor(props: ChartPropsWithoutHTML<TData, XScale, YScale>, overrides?: {
93
+ brushXDomain?: () => BrushDomainType | undefined;
94
+ brushYDomain?: () => BrushDomainType | undefined;
95
+ });
94
96
  containerWidth: number;
95
97
  containerHeight: number;
96
98
  data: import("d3-sankey").SankeyGraph<any, any> | readonly TData[] | import("d3-hierarchy").HierarchyNode<TData>;
@@ -17,11 +17,22 @@ const defaultPadding = { top: 0, right: 0, bottom: 0, left: 0 };
17
17
  const EMPTY_SERIES = [];
18
18
  /** Svelte context key for tracking the nearest parent ComponentNode. */
19
19
  const _ParentNodeContext = new Context('ComponentTreeParent');
20
+ /** Mark info is "empty" when none of the fields the chart uses for series /
21
+ * domain inference are populated. Pixel-mode primitives produce empty info
22
+ * since they have no string/function accessors and no own data. */
23
+ function isEmptyMarkInfo(info) {
24
+ return !info.x && !info.y && !info.data && !info.color && !info.seriesKey && !info.label;
25
+ }
20
26
  export class ChartState {
21
- // Props getter function - set in constructor
22
- _propsGetter;
23
- // Props - accessed via getter function for fine-grained reactivity
24
- props = $derived(this._propsGetter());
27
+ // The `$props()` proxy from the host component. Reads on `this.props.X` go
28
+ // straight through to the underlying reactive prop — no spread / no derived
29
+ // wrapper needed.
30
+ props;
31
+ // Brush-domain overrides. The host component owns the brush state as local
32
+ // `$state` and supplies these getters so brush selections take precedence
33
+ // over `props.xDomain` / `props.yDomain` when reading the effective domain.
34
+ #brushXDomain;
35
+ #brushYDomain;
25
36
  // State / contexts
26
37
  geoState;
27
38
  transformState = $state(null);
@@ -132,14 +143,26 @@ export class ChartState {
132
143
  };
133
144
  });
134
145
  if (markInfo && !insideCompositeMark) {
135
- $effect(() => {
136
- const info = markInfo();
137
- // Skip registration for empty mark info (e.g. pixel-mode marks)
138
- // to avoid unnecessary array push/splice and version bumps
139
- if (!info.x && !info.y && !info.data && !info.color && !info.seriesKey && !info.label)
140
- return;
141
- return untrack(() => this.registerMark(info));
142
- });
146
+ // Probe once at construction: if mark info is initially empty
147
+ // (pixel-mode primitives where cx/cy/r are numbers), skip the
148
+ // tracking $effect entirely. This is the common case for
149
+ // mark-heavy scenes (force simulations, scatter plots with
150
+ // pixel coordinates) and avoids one effect frame per primitive.
151
+ //
152
+ // Trade-off: a primitive that starts in pixel mode and later
153
+ // flips to data mode (e.g. cx changes from number to string at
154
+ // runtime) won't register a mark. This is uncommon — modes are
155
+ // typically static — but if needed, use explicit `series` on the
156
+ // chart instead of relying on implicit mark-derived series.
157
+ const initial = untrack(markInfo);
158
+ if (!isEmptyMarkInfo(initial)) {
159
+ $effect(() => {
160
+ const info = markInfo();
161
+ if (isEmptyMarkInfo(info))
162
+ return;
163
+ return untrack(() => this.registerMark(info));
164
+ });
165
+ }
143
166
  }
144
167
  return node;
145
168
  }
@@ -159,8 +182,10 @@ export class ChartState {
159
182
  _yDomainIsDate = false;
160
183
  // Meta data - reactive to props.meta changes
161
184
  meta = $derived(this.props.meta ?? {});
162
- constructor(propsGetter) {
163
- this._propsGetter = propsGetter;
185
+ constructor(props, overrides) {
186
+ this.props = props;
187
+ this.#brushXDomain = overrides?.brushXDomain ?? (() => undefined);
188
+ this.#brushYDomain = overrides?.brushYDomain ?? (() => undefined);
164
189
  // Create GeoState instance — pass a dimensions getter so projection
165
190
  // is available during SSR (where $effect doesn't run)
166
191
  this.geoState = new GeoState(() => this.props.geo ?? {}, () => ({ width: this.width, height: this.height }));
@@ -272,7 +297,7 @@ export class ChartState {
272
297
  }
273
298
  });
274
299
  // Set up domain motion if motion prop is configured
275
- const motionProp = propsGetter().motion;
300
+ const motionProp = props.motion;
276
301
  if (motionProp) {
277
302
  const resolved = parseMotionProp(motionProp);
278
303
  this._xDomainMotion = createControlledMotion([], resolved);
@@ -373,7 +398,7 @@ export class ChartState {
373
398
  if (this.props.bandPadding != null && this.valueAxis === 'y') {
374
399
  return scaleBand().padding(this.props.bandPadding);
375
400
  }
376
- return autoScale(this.props.xDomain, this.flatData, this.x);
401
+ return autoScale(this.#brushXDomain() ?? this.props.xDomain, this.flatData, this.x);
377
402
  });
378
403
  _yScaleProp = $derived.by(() => {
379
404
  if (this.props.yScale)
@@ -385,7 +410,7 @@ export class ChartState {
385
410
  if (this.props.bandPadding != null && this.valueAxis === 'x') {
386
411
  return scaleBand().padding(this.props.bandPadding);
387
412
  }
388
- return autoScale(this.props.yDomain, this.flatData, this.y);
413
+ return autoScale(this.#brushYDomain() ?? this.props.yDomain, this.flatData, this.y);
389
414
  });
390
415
  _zScaleProp = $derived.by(() => {
391
416
  return this.props.zScale ?? autoScale(this.props.zDomain, this.flatData, this.props.z);
@@ -579,7 +604,9 @@ export class ChartState {
579
604
  return undefined;
580
605
  }
581
606
  resolveDomain(axis) {
582
- const domain = axis === 'x' ? this.props.xDomain : this.props.yDomain;
607
+ const domain = axis === 'x'
608
+ ? (this.#brushXDomain() ?? this.props.xDomain)
609
+ : (this.#brushYDomain() ?? this.props.yDomain);
583
610
  const interval = axis === 'x' ? this.props.xInterval : this.props.yInterval;
584
611
  const explicitBaseline = axis === 'x' ? this.props.xBaseline : this.props.yBaseline;
585
612
  // Use explicit baseline if provided (null means "no baseline"), otherwise auto-derive
@@ -9,7 +9,7 @@ function createChartState(props) {
9
9
  let cleanup;
10
10
  let state;
11
11
  cleanup = $effect.root(() => {
12
- state = new ChartState(() => props);
12
+ state = new ChartState(props);
13
13
  });
14
14
  // Access derived values after reactive graph is set up
15
15
  flushSync();
@@ -9,19 +9,6 @@ export class SeriesState {
9
9
  #series = $derived(this._getSeries());
10
10
  #stackConfig = $derived(this._getStackConfig());
11
11
  selectedKeys;
12
- /**
13
- * Reactively syncs selectedKeys when series `selected` props change.
14
- * When any series explicitly sets `selected: false`, the remaining series
15
- * (with `selected` undefined or true) are pre-selected.
16
- */
17
- #_syncSelectedFromProps = $effect.root(() => {
18
- $effect(() => {
19
- const keys = SeriesState.#selectedKeysFromSeries(this.#series);
20
- if (keys) {
21
- this.selectedKeys.current = keys;
22
- }
23
- });
24
- });
25
12
  /**
26
13
  * The current highlight series key for the chart.
27
14
  */
@@ -32,6 +19,15 @@ export class SeriesState {
32
19
  // Compute initial selectedKeys synchronously from series `selected` props
33
20
  const initialKeys = SeriesState.#selectedKeysFromSeries(getSeries());
34
21
  this.selectedKeys = new SelectionState({ initial: initialKeys ?? undefined });
22
+ // Reactively sync selectedKeys when series `selected` props change.
23
+ // When any series explicitly sets `selected: false`, the remaining series
24
+ // (with `selected` undefined or true) are pre-selected.
25
+ $effect(() => {
26
+ const keys = SeriesState.#selectedKeysFromSeries(this.#series);
27
+ if (keys) {
28
+ this.selectedKeys.current = keys;
29
+ }
30
+ });
35
31
  }
36
32
  /**
37
33
  * Extract selected keys from series definitions.