layerchart 2.0.0-next.61 → 2.0.0-next.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/canvas.d.ts +6 -2
- package/dist/canvas.js +6 -2
- package/dist/components/Arc/Arc.base.svelte +49 -11
- package/dist/components/Arc/Arc.shared.svelte.d.ts +2 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-full-circle--360-degree-range--1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-full-circle--360-degree-range--2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-innerRadius-of-0--pie-slice--1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-innerRadius-of-0--pie-slice--2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-negative-domain-values-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-negative-domain-values-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-partial-arc--e-g---180-degrees--1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-partial-arc--e-g---180-degrees--2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-at-max-domain-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-at-max-domain-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-below-domain-min-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-below-domain-min-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-exceeding-domain-max-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-exceeding-domain-max-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-of-0-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-edge-cases-should-handle-value-of-0-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-pointer-enter-events-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-pointer-enter-events-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-pointer-move-events-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-pointer-move-events-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-touch-move-events-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-events-should-handle-touch-move-events-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-custom-class-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-custom-class-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-fill-color-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-fill-color-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-fillOpacity-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-fillOpacity-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-offset-to-arc-position-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-offset-to-arc-position-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-opacity-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-opacity-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-stroke-color-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-stroke-color-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-strokeWidth-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-strokeWidth-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-zero-offset-by-default-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-apply-zero-offset-by-default-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-call-tooltip-hide-on-pointer-leave-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-call-tooltip-hide-on-pointer-leave-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-call-tooltip-show-on-pointer-enter-with-data-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-call-tooltip-show-on-pointer-enter-with-data-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-handle-custom-start-angle-in-range-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-handle-custom-start-angle-in-range-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-have-stroke--none--by-default-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-have-stroke--none--by-default-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-an-arc-path-with-value-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-an-arc-path-with-value-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-when-track-prop-is-provided-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-when-track-prop-is-provided-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-custom-class-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-custom-class-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackCornerRadius-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackCornerRadius-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackEndAngle-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackEndAngle-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackInnerRadius-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackInnerRadius-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackInnerRadius-and-trackOuterRadius-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackInnerRadius-and-trackOuterRadius-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackOuterRadius-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackOuterRadius-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackPadAngle-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackPadAngle-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackStartAngle-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackStartAngle-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackStartAngle-and-trackEndAngle-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-track-with-trackStartAngle-and-trackEndAngle-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-both-startAngle-and-endAngle-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-both-startAngle-and-endAngle-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-cornerRadius-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-cornerRadius-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-domain-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-domain-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-domain-and-range-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-domain-and-range-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-range-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-custom-range-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-endAngle-in-radians-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-endAngle-in-radians-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-innerRadius-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-innerRadius-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-innerRadius-and-outerRadius-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-innerRadius-and-outerRadius-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-outerRadius-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-outerRadius-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-padAngle-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-padAngle-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-startAngle-in-radians-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-props-should-render-with-startAngle-in-radians-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-should-render-Arc-element-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-should-render-Arc-element-2.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-should-render-track-1.png +0 -0
- package/dist/components/Arc/__screenshots__/Arc.svelte.test.ts/Arc-should-render-track-2.png +0 -0
- package/dist/components/ArcLabel/ArcLabel.shared.svelte.d.ts +1 -0
- package/dist/components/{ArcLabel.svelte.test.js → ArcLabel/ArcLabel.svelte.test.js} +3 -3
- 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
- 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
- package/dist/components/Blur/Blur.canvas.svelte +25 -0
- package/dist/components/Blur/Blur.canvas.svelte.d.ts +4 -0
- package/dist/components/Blur/Blur.html.svelte +11 -0
- package/dist/components/Blur/Blur.html.svelte.d.ts +4 -0
- package/dist/components/{Blur.svelte.d.ts → Blur/Blur.shared.svelte.d.ts} +3 -5
- package/dist/components/Blur/Blur.svelte +23 -0
- package/dist/components/Blur/Blur.svelte.d.ts +4 -0
- package/dist/components/Blur/Blur.svg.svelte +24 -0
- package/dist/components/Blur/Blur.svg.svelte.d.ts +4 -0
- package/dist/components/Chart/Chart.base.svelte +13 -7
- package/dist/components/Chart/ChartCore.svelte.test.d.ts +1 -0
- package/dist/components/{ChartCore.svelte.test.js → Chart/ChartCore.svelte.test.js} +1 -1
- package/dist/components/Circle/Circle.shared.svelte.js +24 -5
- package/dist/components/Circle/Circle.svelte.test.js +70 -0
- package/dist/components/Dodge/Dodge.shared.svelte.d.ts +132 -0
- package/dist/components/Dodge/Dodge.shared.svelte.js +240 -0
- package/dist/components/Dodge/Dodge.svelte +88 -0
- package/dist/components/Dodge/Dodge.svelte.d.ts +27 -0
- package/dist/components/Dodge/Dodge.test.d.ts +1 -0
- package/dist/components/Dodge/Dodge.test.js +128 -0
- package/dist/components/Image/Image.html.svelte +0 -8
- package/dist/components/Image/Image.svg.svelte +1 -9
- package/dist/components/Link/Link.base.svelte +15 -9
- package/dist/components/Path/Path.canvas.svelte +5 -2
- package/dist/components/Path/Path.shared.svelte.d.ts +17 -4
- package/dist/components/Path/Path.shared.svelte.js +26 -8
- package/dist/components/Path/Path.svg.svelte +75 -60
- package/dist/components/Pattern/Pattern.canvas.svelte +4 -1
- package/dist/components/Pattern/Pattern.shared.svelte.d.ts +31 -2
- package/dist/components/Pattern/Pattern.shared.svelte.js +20 -1
- package/dist/components/Pattern/Pattern.svg.svelte +17 -1
- package/dist/components/Raster/Raster.base.svelte +2 -8
- package/dist/components/Rect/Rect.canvas.svelte +2 -4
- package/dist/components/Rect/Rect.canvas.svelte.d.ts +1 -1
- package/dist/components/Rect/Rect.html.svelte +3 -9
- package/dist/components/Rect/Rect.html.svelte.d.ts +1 -1
- package/dist/components/Rect/Rect.shared.svelte.d.ts +5 -2
- package/dist/components/Rect/Rect.shared.svelte.js +26 -13
- package/dist/components/Rect/Rect.svelte.test.js +45 -0
- package/dist/components/Rect/Rect.svg.svelte +36 -21
- package/dist/components/Rect/Rect.svg.svelte.d.ts +1 -1
- package/dist/components/RectClipPath/RectClipPath.base.svelte +25 -1
- package/dist/components/RectClipPath/RectClipPath.shared.svelte.d.ts +8 -0
- package/dist/components/Spline/Spline.base.svelte +3 -2
- package/dist/components/Text/Text.canvas.svelte +9 -0
- package/dist/components/Text/Text.html.svelte +6 -0
- package/dist/components/Text/Text.shared.svelte.d.ts +25 -2
- package/dist/components/Text/Text.shared.svelte.js +36 -5
- package/dist/components/Text/Text.svelte.test.js +40 -0
- package/dist/components/Text/Text.svg.svelte +7 -1
- package/dist/components/Trail/Trail.base.svelte +10 -7
- package/dist/components/Waffle/Waffle.shared.svelte.d.ts +182 -0
- package/dist/components/Waffle/Waffle.shared.svelte.js +300 -0
- package/dist/components/Waffle/Waffle.svelte +148 -0
- package/dist/components/Waffle/Waffle.svelte.d.ts +5 -0
- 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
- 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
- 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
- package/dist/components/charts/__screenshots__/PieChart.svelte.test.ts/PieChart-uses-hovered-slice-identity-for-implicit-tooltip-series-1.png +0 -0
- package/dist/components/charts/__screenshots__/PieChart.svelte.test.ts/PieChart-uses-hovered-slice-identity-for-implicit-tooltip-series-2.png +0 -0
- package/dist/components/index.d.ts +6 -2
- package/dist/components/index.js +6 -2
- package/dist/html.d.ts +6 -2
- package/dist/html.js +6 -2
- package/dist/states/chart.svelte.d.ts +4 -2
- package/dist/states/chart.svelte.js +53 -22
- package/dist/states/chart.svelte.test.js +54 -1
- package/dist/states/series.svelte.js +9 -13
- package/dist/states/series.svelte.test.js +5 -1
- package/dist/svg.d.ts +6 -2
- package/dist/svg.js +6 -2
- package/dist/utils/canvas.js +54 -13
- package/dist/utils/canvas.svelte.test.js +44 -0
- package/dist/utils/download.d.ts +5 -3
- package/dist/utils/download.js +36 -16
- package/dist/utils/stack.js +10 -2
- package/package.json +1 -1
- package/dist/components/Blur.svelte +0 -49
- /package/dist/components/{ArcLabel.svelte.test.d.ts → ArcLabel/ArcLabel.svelte.test.d.ts} +0 -0
- /package/dist/components/{ChartCore.svelte.test.d.ts → Blur/Blur.shared.svelte.js} +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { dodge } from './Dodge.shared.svelte.js';
|
|
3
|
+
/** Build inputs for the circular case (`rx === ry === r`). */
|
|
4
|
+
function input(arr) {
|
|
5
|
+
return arr.map((d, index) => ({
|
|
6
|
+
x: d.x,
|
|
7
|
+
rx: d.r,
|
|
8
|
+
ry: d.r,
|
|
9
|
+
data: { id: d.id },
|
|
10
|
+
index,
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
/** Build inputs for rectangular cases (per-axis half-extents). */
|
|
14
|
+
function rectInput(arr) {
|
|
15
|
+
return arr.map((d, index) => ({
|
|
16
|
+
x: d.x,
|
|
17
|
+
rx: d.rx,
|
|
18
|
+
ry: d.ry,
|
|
19
|
+
data: { id: d.id },
|
|
20
|
+
index,
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
describe('dodge() — circular packing', () => {
|
|
24
|
+
it('places a single item at the natural anchor position', () => {
|
|
25
|
+
const out = dodge(input([{ id: 'a', x: 50, r: 10 }]), {
|
|
26
|
+
axis: 'y',
|
|
27
|
+
anchor: 'bottom',
|
|
28
|
+
padding: 0,
|
|
29
|
+
baseline: 100,
|
|
30
|
+
});
|
|
31
|
+
expect(out).toHaveLength(1);
|
|
32
|
+
expect(out[0].x).toBe(50);
|
|
33
|
+
expect(out[0].y).toBe(90); // baseline - r
|
|
34
|
+
});
|
|
35
|
+
it('places non-overlapping items both at the anchor', () => {
|
|
36
|
+
// Two items 100px apart, r=10 each — no horizontal overlap, both can sit at the bottom
|
|
37
|
+
const out = dodge(input([
|
|
38
|
+
{ id: 'a', x: 0, r: 10 },
|
|
39
|
+
{ id: 'b', x: 100, r: 10 },
|
|
40
|
+
]), { axis: 'y', anchor: 'bottom', padding: 0, baseline: 100 });
|
|
41
|
+
expect(out[0].y).toBe(90);
|
|
42
|
+
expect(out[1].y).toBe(90);
|
|
43
|
+
});
|
|
44
|
+
it('stacks overlapping items vertically', () => {
|
|
45
|
+
// Two items at the same x — must stack with a gap ≥ r1 + r2 + padding = 21.
|
|
46
|
+
// padding is also applied to the chart edge, so the first item sits 1px
|
|
47
|
+
// above the bottom (y=89, not 90).
|
|
48
|
+
const out = dodge(input([
|
|
49
|
+
{ id: 'a', x: 50, r: 10 },
|
|
50
|
+
{ id: 'b', x: 50, r: 10 },
|
|
51
|
+
]), { axis: 'y', anchor: 'bottom', padding: 1, baseline: 100 });
|
|
52
|
+
expect(out[0].y).toBe(89);
|
|
53
|
+
expect(out[1].y).toBeCloseTo(89 - 21, 5); // gap of sumR+padding
|
|
54
|
+
});
|
|
55
|
+
it('returns items in original input order', () => {
|
|
56
|
+
const out = dodge(input([
|
|
57
|
+
{ id: 'small', x: 50, r: 5 },
|
|
58
|
+
{ id: 'large', x: 50, r: 20 },
|
|
59
|
+
]), { axis: 'y', anchor: 'bottom', padding: 0, baseline: 100 });
|
|
60
|
+
expect(out[0].data.id).toBe('small');
|
|
61
|
+
expect(out[1].data.id).toBe('large');
|
|
62
|
+
// Algorithm processes input order: small placed first at bottom (y=95),
|
|
63
|
+
// then large stacks above.
|
|
64
|
+
expect(out[0].y).toBe(95);
|
|
65
|
+
expect(out[1].y).toBeLessThan(95);
|
|
66
|
+
});
|
|
67
|
+
it('respects anchor=top (stacks downward)', () => {
|
|
68
|
+
const out = dodge(input([
|
|
69
|
+
{ id: 'a', x: 50, r: 10 },
|
|
70
|
+
{ id: 'b', x: 50, r: 10 },
|
|
71
|
+
]), { axis: 'y', anchor: 'top', padding: 0, baseline: 0 });
|
|
72
|
+
expect(out[0].y).toBe(10); // first at top
|
|
73
|
+
expect(out[1].y).toBeCloseTo(30, 5); // second below, gap = r1+r2 = 20
|
|
74
|
+
});
|
|
75
|
+
it('swaps axes for axis=x', () => {
|
|
76
|
+
const out = dodge(input([{ id: 'a', x: 30, r: 5 }]), {
|
|
77
|
+
axis: 'x',
|
|
78
|
+
anchor: 'left',
|
|
79
|
+
padding: 0,
|
|
80
|
+
baseline: 0,
|
|
81
|
+
});
|
|
82
|
+
expect(out[0].x).toBe(5); // dodged: at left edge
|
|
83
|
+
expect(out[0].y).toBe(30); // anchor y preserved
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('dodge() — rectangular packing (rx + ry)', () => {
|
|
87
|
+
// ry = 8 ⇒ row spacing = 2 * ry = 16 (matches the old `rowHeight: 16` cases).
|
|
88
|
+
it('places non-overlapping items in row 0', () => {
|
|
89
|
+
const out = dodge(rectInput([
|
|
90
|
+
{ id: 'a', x: 0, rx: 20, ry: 8 }, // spans -20 to 20
|
|
91
|
+
{ id: 'b', x: 100, rx: 20, ry: 8 }, // spans 80 to 120
|
|
92
|
+
]), { axis: 'y', anchor: 'bottom', padding: 0, baseline: 200, rectangular: true });
|
|
93
|
+
// Row 0 center from bottom = baseline - ry = 192
|
|
94
|
+
expect(out[0].y).toBe(192);
|
|
95
|
+
expect(out[1].y).toBe(192);
|
|
96
|
+
});
|
|
97
|
+
it('stacks horizontally-overlapping items into separate rows', () => {
|
|
98
|
+
const out = dodge(rectInput([
|
|
99
|
+
{ id: 'a', x: 50, rx: 30, ry: 8 },
|
|
100
|
+
{ id: 'b', x: 60, rx: 30, ry: 8 },
|
|
101
|
+
{ id: 'c', x: 70, rx: 30, ry: 8 },
|
|
102
|
+
]), { axis: 'y', anchor: 'bottom', padding: 0, baseline: 200, rectangular: true });
|
|
103
|
+
// Input order: a → row 0, b overlaps a → row 1, c overlaps both → row 2
|
|
104
|
+
// Row centers from bottom: 192, 176, 160.
|
|
105
|
+
expect(out[0].y).toBe(192);
|
|
106
|
+
expect(out[1].y).toBe(176);
|
|
107
|
+
expect(out[2].y).toBe(160);
|
|
108
|
+
});
|
|
109
|
+
it("reuses row 0 when later items don't overlap earlier ones in that row", () => {
|
|
110
|
+
const out = dodge(rectInput([
|
|
111
|
+
{ id: 'a', x: 0, rx: 10, ry: 8 },
|
|
112
|
+
{ id: 'b', x: 5, rx: 10, ry: 8 }, // overlaps a → row 1
|
|
113
|
+
{ id: 'c', x: 100, rx: 10, ry: 8 }, // far from both → row 0
|
|
114
|
+
]), { axis: 'y', anchor: 'bottom', padding: 0, baseline: 200, rectangular: true });
|
|
115
|
+
expect(out[0].y).toBe(192);
|
|
116
|
+
expect(out[1].y).toBe(176);
|
|
117
|
+
expect(out[2].y).toBe(192);
|
|
118
|
+
});
|
|
119
|
+
it('respects anchor=top for rectangular mode', () => {
|
|
120
|
+
const out = dodge(rectInput([
|
|
121
|
+
{ id: 'a', x: 50, rx: 30, ry: 8 },
|
|
122
|
+
{ id: 'b', x: 60, rx: 30, ry: 8 },
|
|
123
|
+
]), { axis: 'y', anchor: 'top', padding: 0, baseline: 0, rectangular: true });
|
|
124
|
+
// Row 0 from top: 0 + ry = 8. Row 1: 24.
|
|
125
|
+
expect(out[0].y).toBe(8);
|
|
126
|
+
expect(out[1].y).toBe(24);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
getLinkRadialPresetPath,
|
|
19
19
|
} from '../../utils/linkUtils.js';
|
|
20
20
|
import { getChartContext } from '../../contexts/chart.js';
|
|
21
|
-
import { extractLayerProps } from '../../utils/attributes.js';
|
|
22
21
|
import { accessor, type Accessor } from '../../utils/common.js';
|
|
23
22
|
import { cls } from '@layerstack/tailwind';
|
|
24
23
|
import {
|
|
@@ -203,11 +202,20 @@
|
|
|
203
202
|
}
|
|
204
203
|
: undefined;
|
|
205
204
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
205
|
+
// Pass `tweenOptions` (possibly undefined) so `createMotion` takes its
|
|
206
|
+
// fast-path passthrough when no tween is configured — avoids allocating
|
|
207
|
+
// a MotionNone container + per-instance `$effect` that fires on every
|
|
208
|
+
// x1/y1/x2/y2 change. Critical for force-simulation graphs which can
|
|
209
|
+
// have hundreds of links updating on every tick.
|
|
210
|
+
const motionPath = createMotion('', () => singlePathData, tweenOptions);
|
|
211
|
+
|
|
212
|
+
// Stable getter handed to `<Path>` instead of `motionPath.current`.
|
|
213
|
+
// Reading `motionPath.current` directly in the template would subscribe
|
|
214
|
+
// *this* component's template to per-tick updates, forcing the entire
|
|
215
|
+
// `<Path>` block to re-evaluate (and re-spread props) on every change.
|
|
216
|
+
// By passing a function reference, the per-tick `current` read happens
|
|
217
|
+
// inside `<Path>`'s own template — the parent stays stable.
|
|
218
|
+
const getPathData = () => motionPath.current;
|
|
211
219
|
|
|
212
220
|
const arrayRows = $derived(isArrayMode ? data ?? ctx.data ?? [] : []);
|
|
213
221
|
|
|
@@ -237,7 +245,6 @@
|
|
|
237
245
|
{markerStart}
|
|
238
246
|
{markerMid}
|
|
239
247
|
{markerEnd}
|
|
240
|
-
{...extractLayerProps(restProps, 'lc-link')}
|
|
241
248
|
{...restProps}
|
|
242
249
|
stroke={resolvedStroke}
|
|
243
250
|
fill={resolvePerDatum(fillProp, d)}
|
|
@@ -247,13 +254,12 @@
|
|
|
247
254
|
{/each}
|
|
248
255
|
{:else}
|
|
249
256
|
<Path
|
|
250
|
-
pathData={
|
|
257
|
+
pathData={getPathData}
|
|
251
258
|
bind:pathRef
|
|
252
259
|
{marker}
|
|
253
260
|
{markerStart}
|
|
254
261
|
{markerMid}
|
|
255
262
|
{markerEnd}
|
|
256
|
-
{...extractLayerProps(restProps, 'lc-link')}
|
|
257
263
|
{...restProps}
|
|
258
264
|
class={cls('lc-link', typeof classProp === 'string' ? classProp : undefined)}
|
|
259
265
|
/>
|
|
@@ -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(
|
|
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
|
-
*
|
|
11
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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={
|
|
112
|
-
fill-opacity={
|
|
113
|
-
stroke={
|
|
114
|
-
stroke-opacity={
|
|
115
|
-
stroke-width={
|
|
116
|
-
opacity={
|
|
117
|
-
class={cls('lc-path',
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
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,
|
|
@@ -22,11 +22,14 @@
|
|
|
22
22
|
height = size,
|
|
23
23
|
lines: linesProp,
|
|
24
24
|
circles: circlesProp,
|
|
25
|
+
rects: rectsProp,
|
|
25
26
|
background,
|
|
26
27
|
children,
|
|
27
28
|
}: PatternProps = $props();
|
|
28
29
|
|
|
29
|
-
const shapes = $derived(
|
|
30
|
+
const shapes = $derived(
|
|
31
|
+
buildPatternShapes(linesProp, circlesProp, size, width, height, rectsProp)
|
|
32
|
+
);
|
|
30
33
|
|
|
31
34
|
let canvasPattern = $state<CanvasPattern | null>(null);
|
|
32
35
|
|
|
@@ -21,6 +21,22 @@ export type PatternCircleDef = {
|
|
|
21
21
|
/** The opacity of the circle @default 1 */
|
|
22
22
|
opacity?: number;
|
|
23
23
|
};
|
|
24
|
+
export type PatternRectDef = {
|
|
25
|
+
/**
|
|
26
|
+
* Inset from each edge of the pattern tile, in pixels. Useful for cell
|
|
27
|
+
* grids — set to half the desired gap between cells.
|
|
28
|
+
* @default 0
|
|
29
|
+
*/
|
|
30
|
+
inset?: number;
|
|
31
|
+
/** Horizontal corner radius, or `"100%"` for a full ellipse / circle. */
|
|
32
|
+
rx?: number | string;
|
|
33
|
+
/** Vertical corner radius. Defaults to `rx` if not provided. */
|
|
34
|
+
ry?: number | string;
|
|
35
|
+
/** Fill color @default 'var(--color-surface-content)' */
|
|
36
|
+
color?: string;
|
|
37
|
+
/** Opacity @default 1 */
|
|
38
|
+
opacity?: number;
|
|
39
|
+
};
|
|
24
40
|
export type PatternPropsWithoutHTML = {
|
|
25
41
|
/** The id of the pattern */
|
|
26
42
|
id?: string;
|
|
@@ -34,6 +50,8 @@ export type PatternPropsWithoutHTML = {
|
|
|
34
50
|
lines?: boolean | PatternLineDef | PatternLineDef[];
|
|
35
51
|
/** The number of circles to render */
|
|
36
52
|
circles?: boolean | PatternCircleDef | PatternCircleDef[];
|
|
53
|
+
/** Rect(s) to render in each pattern tile */
|
|
54
|
+
rects?: boolean | PatternRectDef | PatternRectDef[];
|
|
37
55
|
/** The background color of the pattern */
|
|
38
56
|
background?: string;
|
|
39
57
|
/** Render as a child of the pattern. Note: only supported on the `<Svg>` layer. */
|
|
@@ -59,9 +77,20 @@ export type LineShape = {
|
|
|
59
77
|
strokeWidth: string | number;
|
|
60
78
|
opacity: number;
|
|
61
79
|
};
|
|
62
|
-
export type
|
|
80
|
+
export type RectShape = {
|
|
81
|
+
type: 'rect';
|
|
82
|
+
x: number;
|
|
83
|
+
y: number;
|
|
84
|
+
width: number;
|
|
85
|
+
height: number;
|
|
86
|
+
rx?: number | string;
|
|
87
|
+
ry?: number | string;
|
|
88
|
+
fill: string;
|
|
89
|
+
opacity: number;
|
|
90
|
+
};
|
|
91
|
+
export type PatternShape = CircleShape | LineShape | RectShape;
|
|
63
92
|
/**
|
|
64
93
|
* Build the SVG/canvas shape descriptors for a pattern's lines/circles.
|
|
65
94
|
* Pure function — no reactivity.
|
|
66
95
|
*/
|
|
67
|
-
export declare function buildPatternShapes(linesProp: PatternPropsWithoutHTML['lines'], circlesProp: PatternPropsWithoutHTML['circles'], size: number, width: number, height: number): PatternShape[];
|
|
96
|
+
export declare function buildPatternShapes(linesProp: PatternPropsWithoutHTML['lines'], circlesProp: PatternPropsWithoutHTML['circles'], size: number, width: number, height: number, rectsProp?: PatternPropsWithoutHTML['rects']): PatternShape[];
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Build the SVG/canvas shape descriptors for a pattern's lines/circles.
|
|
3
3
|
* Pure function — no reactivity.
|
|
4
4
|
*/
|
|
5
|
-
export function buildPatternShapes(linesProp, circlesProp, size, width, height) {
|
|
5
|
+
export function buildPatternShapes(linesProp, circlesProp, size, width, height, rectsProp) {
|
|
6
6
|
const shapes = [];
|
|
7
7
|
if (linesProp) {
|
|
8
8
|
const lineDefs = Array.isArray(linesProp) ? linesProp : linesProp === true ? [{}] : [linesProp];
|
|
@@ -67,5 +67,24 @@ export function buildPatternShapes(linesProp, circlesProp, size, width, height)
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
if (rectsProp) {
|
|
71
|
+
const rectDefs = Array.isArray(rectsProp) ? rectsProp : rectsProp === true ? [{}] : [rectsProp];
|
|
72
|
+
for (const rect of rectDefs) {
|
|
73
|
+
const inset = rect.inset ?? 0;
|
|
74
|
+
const fill = rect.color ?? 'var(--color-surface-content, currentColor)';
|
|
75
|
+
const opacity = rect.opacity ?? 1;
|
|
76
|
+
shapes.push({
|
|
77
|
+
type: 'rect',
|
|
78
|
+
x: inset,
|
|
79
|
+
y: inset,
|
|
80
|
+
width: Math.max(0, width - 2 * inset),
|
|
81
|
+
height: Math.max(0, height - 2 * inset),
|
|
82
|
+
rx: rect.rx,
|
|
83
|
+
ry: rect.ry,
|
|
84
|
+
fill,
|
|
85
|
+
opacity,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
70
89
|
return shapes;
|
|
71
90
|
}
|
|
@@ -19,13 +19,16 @@
|
|
|
19
19
|
height = size,
|
|
20
20
|
lines: linesProp,
|
|
21
21
|
circles: circlesProp,
|
|
22
|
+
rects: rectsProp,
|
|
22
23
|
background,
|
|
23
24
|
patternContent,
|
|
24
25
|
children,
|
|
25
26
|
...rest
|
|
26
27
|
}: PatternProps = $props();
|
|
27
28
|
|
|
28
|
-
const shapes = $derived(
|
|
29
|
+
const shapes = $derived(
|
|
30
|
+
buildPatternShapes(linesProp, circlesProp, size, width, height, rectsProp)
|
|
31
|
+
);
|
|
29
32
|
</script>
|
|
30
33
|
|
|
31
34
|
<defs>
|
|
@@ -62,6 +65,19 @@
|
|
|
62
65
|
opacity={circle.opacity}
|
|
63
66
|
/>
|
|
64
67
|
{/each}
|
|
68
|
+
|
|
69
|
+
{#each shapes.filter((s) => s.type === 'rect') as rect}
|
|
70
|
+
<rect
|
|
71
|
+
x={rect.x}
|
|
72
|
+
y={rect.y}
|
|
73
|
+
width={rect.width}
|
|
74
|
+
height={rect.height}
|
|
75
|
+
rx={rect.rx as number | string | undefined}
|
|
76
|
+
ry={rect.ry as number | string | undefined}
|
|
77
|
+
fill={rect.fill}
|
|
78
|
+
opacity={rect.opacity}
|
|
79
|
+
/>
|
|
80
|
+
{/each}
|
|
65
81
|
{/if}
|
|
66
82
|
</pattern>
|
|
67
83
|
</defs>
|