layerchart 2.0.0-next.62 → 2.0.0-next.64
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 +4 -0
- package/dist/canvas.js +4 -0
- package/dist/components/Arc/Arc.shared.svelte.d.ts +2 -0
- package/dist/components/ArcLabel/ArcLabel.shared.svelte.d.ts +1 -0
- 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/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/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/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/ArcChart/ArcChart.base.svelte +1 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +4 -0
- package/dist/html.d.ts +4 -0
- package/dist/html.js +4 -0
- package/dist/states/chart.svelte.js +8 -4
- package/dist/states/chart.svelte.test.js +53 -0
- package/dist/svg.d.ts +4 -0
- package/dist/svg.js +4 -0
- 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
|
@@ -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
|
+
});
|
|
@@ -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>
|
|
@@ -16,11 +16,7 @@
|
|
|
16
16
|
import { max, min } from 'd3-array';
|
|
17
17
|
import { rgb } from 'd3-color';
|
|
18
18
|
|
|
19
|
-
import {
|
|
20
|
-
accessor as resolveAccessor,
|
|
21
|
-
chartDataArray,
|
|
22
|
-
type Accessor,
|
|
23
|
-
} from '../../utils/common.js';
|
|
19
|
+
import { accessor as resolveAccessor, chartDataArray, type Accessor } from '../../utils/common.js';
|
|
24
20
|
import { getChartContext } from '../../contexts/chart.js';
|
|
25
21
|
import { getGeoContext } from '../../contexts/geo.js';
|
|
26
22
|
import { gridCellCenterToBounds, resolveRasterBounds } from '../../utils/index.js';
|
|
@@ -89,9 +85,7 @@
|
|
|
89
85
|
if (!ctx.width || !ctx.height) return new Float64Array(0);
|
|
90
86
|
|
|
91
87
|
if (isGridMode) {
|
|
92
|
-
return dataProp instanceof Float64Array
|
|
93
|
-
? dataProp
|
|
94
|
-
: Float64Array.from(dataProp as number[]);
|
|
88
|
+
return dataProp instanceof Float64Array ? dataProp : Float64Array.from(dataProp as number[]);
|
|
95
89
|
}
|
|
96
90
|
|
|
97
91
|
if (typeof valueProp === 'function' && valueProp.length >= 2) {
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
export type {
|
|
3
|
-
RectProps,
|
|
4
|
-
RectPropsWithoutHTML,
|
|
5
|
-
} from './Rect.shared.svelte.js';
|
|
2
|
+
export type { RectProps, RectPropsWithoutHTML } from './Rect.shared.svelte.js';
|
|
6
3
|
</script>
|
|
7
4
|
|
|
8
5
|
<script lang="ts">
|
|
@@ -90,6 +87,7 @@
|
|
|
90
87
|
height: item.height,
|
|
91
88
|
rx: c.rx,
|
|
92
89
|
ry: c.ry,
|
|
90
|
+
corners: c.resolveCorners(item.width, item.height),
|
|
93
91
|
},
|
|
94
92
|
styleOpts
|
|
95
93
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { RectProps, RectPropsWithoutHTML
|
|
1
|
+
export type { RectProps, RectPropsWithoutHTML } from './Rect.shared.svelte.js';
|
|
2
2
|
import { type RectProps } from './Rect.shared.svelte.js';
|
|
3
3
|
declare const Rect: import("svelte").Component<RectProps, {}, "">;
|
|
4
4
|
type Rect = ReturnType<typeof Rect>;
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
export type {
|
|
3
|
-
RectProps,
|
|
4
|
-
RectPropsWithoutHTML,
|
|
5
|
-
} from './Rect.shared.svelte.js';
|
|
2
|
+
export type { RectProps, RectPropsWithoutHTML } from './Rect.shared.svelte.js';
|
|
6
3
|
</script>
|
|
7
4
|
|
|
8
5
|
<script lang="ts">
|
|
@@ -11,10 +8,7 @@
|
|
|
11
8
|
import { resolveColorProp, resolveStyleProp } from '../../utils/dataProp.js';
|
|
12
9
|
import { RectState, rectMarkInfo, type RectProps } from './Rect.shared.svelte.js';
|
|
13
10
|
|
|
14
|
-
let {
|
|
15
|
-
children,
|
|
16
|
-
...rest
|
|
17
|
-
}: RectProps = $props();
|
|
11
|
+
let { children, ...rest }: RectProps = $props();
|
|
18
12
|
|
|
19
13
|
const c = new RectState(() => rest as RectProps);
|
|
20
14
|
|
|
@@ -55,7 +49,7 @@
|
|
|
55
49
|
style:border-width={resolvedBorderWidth}
|
|
56
50
|
style:border-style={c.dashArrayResolved ? 'dashed' : 'solid'}
|
|
57
51
|
style:border-color={resolvedStroke}
|
|
58
|
-
style:border-radius=
|
|
52
|
+
style:border-radius={c.borderRadius(item.width, item.height) ?? `${c.rx}px`}
|
|
59
53
|
class={cls('lc-rect', resolvedClass)}
|
|
60
54
|
></div>
|
|
61
55
|
{/each}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { RectProps, RectPropsWithoutHTML
|
|
1
|
+
export type { RectProps, RectPropsWithoutHTML } from './Rect.shared.svelte.js';
|
|
2
2
|
import { type RectProps } from './Rect.shared.svelte.js';
|
|
3
3
|
declare const Rect: import("svelte").Component<RectProps, {}, "">;
|
|
4
4
|
type Rect = ReturnType<typeof Rect>;
|
|
@@ -42,14 +42,14 @@ export type RectPropsWithoutHTML = {
|
|
|
42
42
|
*
|
|
43
43
|
* @default 0
|
|
44
44
|
*/
|
|
45
|
-
width?:
|
|
45
|
+
width?: DataProp;
|
|
46
46
|
initialWidth?: number;
|
|
47
47
|
/**
|
|
48
48
|
* The height of the rectangle (pixels).
|
|
49
49
|
*
|
|
50
50
|
* @default 0
|
|
51
51
|
*/
|
|
52
|
-
height?:
|
|
52
|
+
height?: DataProp;
|
|
53
53
|
initialHeight?: number;
|
|
54
54
|
/**
|
|
55
55
|
* Left/start x edge (data mode).
|
|
@@ -158,6 +158,9 @@ export declare class RectState {
|
|
|
158
158
|
get motionY(): any;
|
|
159
159
|
get motionWidth(): any;
|
|
160
160
|
get motionHeight(): any;
|
|
161
|
+
resolveCorners(width: number, height: number): import("../../utils/rect.svelte.js").CornerRadii | undefined;
|
|
162
|
+
roundedRectPath(x: number, y: number, width: number, height: number): string | undefined;
|
|
163
|
+
borderRadius(width: number, height: number): string | undefined;
|
|
161
164
|
resolvedCorners: import("../../utils/rect.svelte.js").CornerRadii | undefined;
|
|
162
165
|
borderRadiusStyle: string | undefined;
|
|
163
166
|
pixelPathData: string | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { untrack } from 'svelte';
|
|
2
|
-
import { hasAnyDataProp, resolveDataProp, resolveGeoDataPair
|
|
2
|
+
import { hasAnyDataProp, resolveDataProp, resolveGeoDataPair } from '../../utils/dataProp.js';
|
|
3
3
|
import { chartDataArray } from '../../utils/common.js';
|
|
4
4
|
import { resolveCorners, cornersUniform, resolveInsets, } from '../../utils/rect.svelte.js';
|
|
5
5
|
import { roundedRectPath, parseDashArray } from '../../utils/path.js';
|
|
@@ -32,7 +32,7 @@ export class RectState {
|
|
|
32
32
|
geo = getGeoContext();
|
|
33
33
|
// Data mode detection
|
|
34
34
|
hasEdgeProps = $derived(hasAnyDataProp(this.#getProps().x0, this.#getProps().y0, this.#getProps().x1, this.#getProps().y1));
|
|
35
|
-
dataMode = $derived(hasAnyDataProp(this.#getProps().x, this.#getProps().y) || this.hasEdgeProps);
|
|
35
|
+
dataMode = $derived(hasAnyDataProp(this.#getProps().x, this.#getProps().y, this.#getProps().width, this.#getProps().height) || this.hasEdgeProps);
|
|
36
36
|
// Data resolution
|
|
37
37
|
#resolvedData = $derived(this.dataMode ? (this.#getProps().data ?? chartDataArray(this.chartCtx.data)) : []);
|
|
38
38
|
resolvedItems = $derived.by(() => {
|
|
@@ -92,8 +92,10 @@ export class RectState {
|
|
|
92
92
|
return {
|
|
93
93
|
x: resolvedX + resolvedInsets.left,
|
|
94
94
|
y: resolvedY + resolvedInsets.top,
|
|
95
|
-
width: Math.max(0, (props.width
|
|
96
|
-
height: Math.max(0, (props.height
|
|
95
|
+
width: Math.max(0, resolveDataProp(props.width, d, undefined, 0) - resolvedInsets.left - resolvedInsets.right),
|
|
96
|
+
height: Math.max(0, resolveDataProp(props.height, d, undefined, 0) -
|
|
97
|
+
resolvedInsets.top -
|
|
98
|
+
resolvedInsets.bottom),
|
|
97
99
|
};
|
|
98
100
|
}
|
|
99
101
|
}
|
|
@@ -133,11 +135,24 @@ export class RectState {
|
|
|
133
135
|
return this.#motionHeight.current;
|
|
134
136
|
}
|
|
135
137
|
// Resolved per-corner radii (clamped to current bounds)
|
|
136
|
-
|
|
138
|
+
resolveCorners(width, height) {
|
|
137
139
|
const corners = this.#getProps().corners;
|
|
138
140
|
if (corners === undefined)
|
|
139
141
|
return undefined;
|
|
140
|
-
return resolveCorners(corners,
|
|
142
|
+
return resolveCorners(corners, width, height);
|
|
143
|
+
}
|
|
144
|
+
roundedRectPath(x, y, width, height) {
|
|
145
|
+
const corners = this.resolveCorners(width, height);
|
|
146
|
+
if (!corners || !this.cornersNonUniform)
|
|
147
|
+
return undefined;
|
|
148
|
+
return roundedRectPath(x, y, width, height, corners);
|
|
149
|
+
}
|
|
150
|
+
borderRadius(width, height) {
|
|
151
|
+
const corners = this.resolveCorners(width, height);
|
|
152
|
+
return corners ? corners.map((c) => `${c}px`).join(' ') : undefined;
|
|
153
|
+
}
|
|
154
|
+
resolvedCorners = $derived.by(() => {
|
|
155
|
+
return this.resolveCorners(this.motionWidth, this.motionHeight);
|
|
141
156
|
});
|
|
142
157
|
borderRadiusStyle = $derived(this.resolvedCorners ? this.resolvedCorners.map((c) => `${c}px`).join(' ') : undefined);
|
|
143
158
|
pixelPathData = $derived.by(() => {
|
|
@@ -158,9 +173,7 @@ export class RectState {
|
|
|
158
173
|
staticStrokeWidth = $derived(typeof this.#getProps().strokeWidth === 'number'
|
|
159
174
|
? this.#getProps().strokeWidth
|
|
160
175
|
: undefined);
|
|
161
|
-
staticOpacity = $derived(typeof this.#getProps().opacity === 'number'
|
|
162
|
-
? this.#getProps().opacity
|
|
163
|
-
: undefined);
|
|
176
|
+
staticOpacity = $derived(typeof this.#getProps().opacity === 'number' ? this.#getProps().opacity : undefined);
|
|
164
177
|
staticClassName = $derived(typeof this.#getProps().class === 'string' ? this.#getProps().class : undefined);
|
|
165
178
|
// Match SVG's implicit `stroke-width: 1` default
|
|
166
179
|
staticBorderWidth = $derived.by(() => {
|
|
@@ -176,13 +189,13 @@ export class RectState {
|
|
|
176
189
|
const initial = getProps();
|
|
177
190
|
const initialX = initial.initialX ?? (typeof initial.x === 'number' ? initial.x : 0);
|
|
178
191
|
const initialY = initial.initialY ?? (typeof initial.y === 'number' ? initial.y : 0);
|
|
179
|
-
const initialWidth = initial.initialWidth ?? initial.width
|
|
180
|
-
const initialHeight = initial.initialHeight ?? initial.height
|
|
192
|
+
const initialWidth = initial.initialWidth ?? (typeof initial.width === 'number' ? initial.width : 0);
|
|
193
|
+
const initialHeight = initial.initialHeight ?? (typeof initial.height === 'number' ? initial.height : 0);
|
|
181
194
|
const motion = initial.motion;
|
|
182
195
|
this.#motionX = createMotion(initialX, () => (typeof getProps().x === 'number' ? getProps().x : 0), motion === undefined ? undefined : parseMotionProp(motion, 'x'));
|
|
183
196
|
this.#motionY = createMotion(initialY, () => (typeof getProps().y === 'number' ? getProps().y : 0), motion === undefined ? undefined : parseMotionProp(motion, 'y'));
|
|
184
|
-
this.#motionWidth = createMotion(initialWidth, () => getProps().width
|
|
185
|
-
this.#motionHeight = createMotion(initialHeight, () => getProps().height
|
|
197
|
+
this.#motionWidth = createMotion(initialWidth, () => (typeof getProps().width === 'number' ? getProps().width : 0), motion === undefined ? undefined : parseMotionProp(motion, 'width'));
|
|
198
|
+
this.#motionHeight = createMotion(initialHeight, () => (typeof getProps().height === 'number' ? getProps().height : 0), motion === undefined ? undefined : parseMotionProp(motion, 'height'));
|
|
186
199
|
this.#dataMotionMap = createDataMotionMap(motion);
|
|
187
200
|
if (this.#dataMotionMap) {
|
|
188
201
|
const motionMap = this.#dataMotionMap;
|
|
@@ -47,6 +47,28 @@ describe('Rect', () => {
|
|
|
47
47
|
const rects = page.getByTestId(componentTestId).elements();
|
|
48
48
|
await expect.poll(() => rects.length).toBe(3);
|
|
49
49
|
});
|
|
50
|
+
it('should resolve width and height from data', async () => {
|
|
51
|
+
render(TestHarness, {
|
|
52
|
+
component: Rect,
|
|
53
|
+
chartProps: {
|
|
54
|
+
data: [{ x: 20, y: 30, width: 80, height: 40 }],
|
|
55
|
+
x: 'x',
|
|
56
|
+
y: 'y',
|
|
57
|
+
xDomain: [0, 100],
|
|
58
|
+
yDomain: [0, 100],
|
|
59
|
+
},
|
|
60
|
+
componentProps: {
|
|
61
|
+
x: 'x',
|
|
62
|
+
y: 'y',
|
|
63
|
+
width: 'width',
|
|
64
|
+
height: 'height',
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
const rect = page.getByTestId(componentTestId);
|
|
68
|
+
await expect.element(rect).toBeInTheDocument();
|
|
69
|
+
expect(rect.element()?.getAttribute('width')).toBe('80');
|
|
70
|
+
expect(rect.element()?.getAttribute('height')).toBe('40');
|
|
71
|
+
});
|
|
50
72
|
});
|
|
51
73
|
describe('data mode - colors', () => {
|
|
52
74
|
const data = [
|
|
@@ -153,6 +175,29 @@ describe('Rect', () => {
|
|
|
153
175
|
const width = Number(firstRect?.getAttribute('width'));
|
|
154
176
|
expect(width).toBeGreaterThan(0);
|
|
155
177
|
});
|
|
178
|
+
it('should render non-uniform corners from edge props', async () => {
|
|
179
|
+
render(TestHarness, {
|
|
180
|
+
component: Rect,
|
|
181
|
+
chartProps: {
|
|
182
|
+
data: [data[0]],
|
|
183
|
+
x: ['x0', 'x1'],
|
|
184
|
+
y: 'count',
|
|
185
|
+
xDomain: [0, 100],
|
|
186
|
+
yDomain: [0, 15],
|
|
187
|
+
},
|
|
188
|
+
componentProps: {
|
|
189
|
+
x0: 'x0',
|
|
190
|
+
x1: 'x1',
|
|
191
|
+
y0: (d) => 0,
|
|
192
|
+
y1: 'count',
|
|
193
|
+
corners: [12, 4, 10, 2],
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
const rect = page.getByTestId(componentTestId);
|
|
197
|
+
await expect.element(rect).toBeInTheDocument();
|
|
198
|
+
expect(rect.element()?.tagName.toLowerCase()).toBe('path');
|
|
199
|
+
expect(rect.element()?.getAttribute('d')).toContain('a');
|
|
200
|
+
});
|
|
156
201
|
it('should use explicit data prop over chart context data', async () => {
|
|
157
202
|
const explicitData = [{ x0: 0, x1: 50, count: 5 }];
|
|
158
203
|
render(TestHarness, {
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
export type {
|
|
3
|
-
RectProps,
|
|
4
|
-
RectPropsWithoutHTML,
|
|
5
|
-
} from './Rect.shared.svelte.js';
|
|
2
|
+
export type { RectProps, RectPropsWithoutHTML } from './Rect.shared.svelte.js';
|
|
6
3
|
</script>
|
|
7
4
|
|
|
8
5
|
<script lang="ts">
|
|
@@ -64,23 +61,41 @@
|
|
|
64
61
|
{@const resolvedStrokeWidth = resolveStyleProp(rest.strokeWidth, item.d)}
|
|
65
62
|
{@const resolvedOpacity = resolveStyleProp(rest.opacity, item.d)}
|
|
66
63
|
{@const resolvedClass = resolveStyleProp(rest.class, item.d)}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
64
|
+
{@const pathData = c.roundedRectPath(item.x, item.y, item.width, item.height)}
|
|
65
|
+
{#if pathData}
|
|
66
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
67
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
68
|
+
<path
|
|
69
|
+
{...rest as unknown as SVGAttributes<SVGPathElement>}
|
|
70
|
+
d={pathData}
|
|
71
|
+
fill={resolvedFill}
|
|
72
|
+
fill-opacity={resolvedFillOpacity}
|
|
73
|
+
stroke={resolvedStroke}
|
|
74
|
+
stroke-opacity={resolvedStrokeOpacity}
|
|
75
|
+
stroke-width={resolvedStrokeWidth}
|
|
76
|
+
opacity={resolvedOpacity}
|
|
77
|
+
stroke-dasharray={c.dashArrayAttr}
|
|
78
|
+
class={cls('lc-rect', resolvedClass)}
|
|
79
|
+
/>
|
|
80
|
+
{:else}
|
|
81
|
+
<rect
|
|
82
|
+
{...rest as any}
|
|
83
|
+
x={item.x}
|
|
84
|
+
y={item.y}
|
|
85
|
+
width={item.width}
|
|
86
|
+
height={item.height}
|
|
87
|
+
fill={resolvedFill}
|
|
88
|
+
fill-opacity={resolvedFillOpacity}
|
|
89
|
+
stroke={resolvedStroke}
|
|
90
|
+
stroke-opacity={resolvedStrokeOpacity}
|
|
91
|
+
stroke-width={resolvedStrokeWidth}
|
|
92
|
+
opacity={resolvedOpacity}
|
|
93
|
+
rx={c.rx}
|
|
94
|
+
ry={c.ry}
|
|
95
|
+
stroke-dasharray={c.dashArrayAttr}
|
|
96
|
+
class={cls('lc-rect', resolvedClass)}
|
|
97
|
+
/>
|
|
98
|
+
{/if}
|
|
84
99
|
{/each}
|
|
85
100
|
{:else if c.pixelPathData}
|
|
86
101
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { RectProps, RectPropsWithoutHTML
|
|
1
|
+
export type { RectProps, RectPropsWithoutHTML } from './Rect.shared.svelte.js';
|
|
2
2
|
import { type RectProps } from './Rect.shared.svelte.js';
|
|
3
3
|
declare const Rect: import("svelte").Component<RectProps, {}, "ref">;
|
|
4
4
|
type Rect = ReturnType<typeof Rect>;
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
pathData={seg.d}
|
|
40
40
|
stroke={seg.stroke}
|
|
41
41
|
fill={seg.fill}
|
|
42
|
-
opacity={seg.opacity ?? c.seriesOpacity}
|
|
42
|
+
opacity={seg.opacity ?? (c.seriesOpacity === 1 ? undefined : c.seriesOpacity)}
|
|
43
43
|
{...c.series?.props}
|
|
44
44
|
{...restProps}
|
|
45
45
|
/>
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
pathData={c.isTweened ? c.tweenedPath : c.d}
|
|
50
50
|
stroke={(typeof stroke === 'string' ? stroke : undefined) ?? c.series?.color}
|
|
51
51
|
fill={typeof fill === 'string' ? fill : undefined}
|
|
52
|
-
opacity={(typeof opacity === 'number' ? opacity : undefined) ??
|
|
52
|
+
opacity={(typeof opacity === 'number' ? opacity : undefined) ??
|
|
53
|
+
(c.seriesOpacity === 1 ? undefined : c.seriesOpacity)}
|
|
53
54
|
{...c.series?.props}
|
|
54
55
|
{...restProps}
|
|
55
56
|
/>
|