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