layerchart 2.0.0-next.54 → 2.0.0-next.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bench/ComposableLineChart.svelte +1 -1
- package/dist/bench/GeoBench.svelte +1 -8
- package/dist/components/AnnotationRange.svelte +3 -1
- package/dist/components/Arc.svelte +1 -3
- package/dist/components/ArcLabel.svelte.test.js +7 -7
- package/dist/components/Axis.svelte +10 -2
- package/dist/components/Axis.svelte.d.ts +8 -2
- package/dist/components/Bar.svelte +14 -40
- package/dist/components/BoxPlot.svelte +4 -12
- package/dist/components/Cell.svelte +13 -8
- package/dist/components/Chart.svelte +69 -26
- package/dist/components/ChartChildren.svelte +22 -4
- package/dist/components/Circle.svelte +51 -9
- 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 +58 -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/Ellipse.svelte +27 -6
- package/dist/components/GeoClipPath.svelte +14 -17
- package/dist/components/GeoClipPath.svelte.d.ts +6 -0
- package/dist/components/GeoLegend.svelte +1 -3
- package/dist/components/GeoPoint.svelte +25 -3
- package/dist/components/GeoSpline.svelte +1 -4
- package/dist/components/GeoTile.svelte +8 -4
- package/dist/components/Grid.svelte +15 -4
- package/dist/components/Grid.svelte.d.ts +14 -4
- package/dist/components/Group.svelte +11 -5
- package/dist/components/Highlight.svelte +4 -3
- package/dist/components/Image.svelte +42 -30
- package/dist/components/Labels.svelte +2 -4
- package/dist/components/Line.svelte +31 -3
- package/dist/components/Line.svelte.d.ts +7 -0
- package/dist/components/LinearGradient.svelte +8 -4
- package/dist/components/Link.svelte +8 -0
- package/dist/components/Marker.svelte +9 -1
- package/dist/components/Path.svelte +43 -23
- package/dist/components/Pattern.svelte +101 -5
- package/dist/components/Pattern.svelte.d.ts +3 -1
- package/dist/components/Pie.svelte +2 -6
- package/dist/components/RadialGradient.svelte +8 -4
- package/dist/components/Rect.svelte +117 -9
- 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/Spline.svelte +22 -4
- package/dist/components/Text.svelte +16 -5
- package/dist/components/Trail.svelte +19 -7
- package/dist/components/Tree.svelte +7 -3
- package/dist/components/Vector.svelte +37 -14
- package/dist/components/Violin.svelte +1 -2
- package/dist/components/charts/ArcChart.svelte +8 -5
- package/dist/components/charts/AreaChart.svelte +6 -1
- package/dist/components/charts/BarChart.svelte +3 -1
- package/dist/components/charts/LineChart.svelte +6 -1
- package/dist/components/charts/PieChart.svelte +10 -3
- package/dist/components/tooltip/Tooltip.svelte +2 -8
- package/dist/contexts/chart.d.ts +1 -1
- package/dist/contexts/chart.js +3 -1
- package/dist/server/TestBarChart.svelte +28 -28
- package/dist/server/TestLineChart.svelte +28 -28
- package/dist/server/index.js +1 -1
- package/dist/states/brush.svelte.js +16 -13
- package/dist/states/chart.svelte.test.js +24 -19
- package/dist/states/geo.svelte.js +1 -4
- package/dist/states/series.svelte.js +1 -1
- 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 +20 -11
- 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/dist/utils/trail.js +3 -4
- package/package.json +1 -1
|
@@ -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
|
|
|
@@ -215,6 +227,15 @@
|
|
|
215
227
|
const staticStrokeWidth = $derived(typeof strokeWidth === 'number' ? strokeWidth : undefined);
|
|
216
228
|
const staticOpacity = $derived(typeof opacity === 'number' ? opacity : undefined);
|
|
217
229
|
const staticClassName = $derived(typeof className === 'string' ? className : undefined);
|
|
230
|
+
// Match SVG's implicit `stroke-width: 1` default: if `stroke` is set but
|
|
231
|
+
// `strokeWidth` is not, render a 1px border so HTML matches SVG/Canvas layers.
|
|
232
|
+
const staticBorderWidth = $derived(
|
|
233
|
+
typeof strokeWidth === 'number'
|
|
234
|
+
? `${strokeWidth}px`
|
|
235
|
+
: typeof stroke === 'string'
|
|
236
|
+
? '1px'
|
|
237
|
+
: undefined
|
|
238
|
+
);
|
|
218
239
|
|
|
219
240
|
// Style options (shared between pixel and data mode)
|
|
220
241
|
function getStyleOptions(
|
|
@@ -250,7 +271,13 @@
|
|
|
250
271
|
'lc-circle',
|
|
251
272
|
itemClass ?? (typeof className === 'string' ? className : undefined)
|
|
252
273
|
),
|
|
253
|
-
style:
|
|
274
|
+
style:
|
|
275
|
+
[
|
|
276
|
+
restProps.style as string | undefined,
|
|
277
|
+
dashArrayAttr ? `stroke-dasharray: ${dashArrayAttr}` : undefined,
|
|
278
|
+
]
|
|
279
|
+
.filter(Boolean)
|
|
280
|
+
.join('; ') || undefined,
|
|
254
281
|
};
|
|
255
282
|
}
|
|
256
283
|
|
|
@@ -328,6 +355,7 @@
|
|
|
328
355
|
opacity,
|
|
329
356
|
className,
|
|
330
357
|
restProps.style,
|
|
358
|
+
dashArrayAttr,
|
|
331
359
|
],
|
|
332
360
|
}
|
|
333
361
|
: undefined,
|
|
@@ -352,6 +380,7 @@
|
|
|
352
380
|
stroke={resolvedStroke}
|
|
353
381
|
stroke-width={resolvedStrokeWidth}
|
|
354
382
|
opacity={resolvedOpacity}
|
|
383
|
+
stroke-dasharray={dashArrayAttr}
|
|
355
384
|
class={cls('lc-circle', resolvedClass)}
|
|
356
385
|
{...restProps}
|
|
357
386
|
/>
|
|
@@ -367,6 +396,7 @@
|
|
|
367
396
|
stroke={staticStroke}
|
|
368
397
|
stroke-width={staticStrokeWidth}
|
|
369
398
|
opacity={staticOpacity}
|
|
399
|
+
stroke-dasharray={dashArrayAttr}
|
|
370
400
|
class={cls('lc-circle', staticClassName)}
|
|
371
401
|
{...restProps}
|
|
372
402
|
/>
|
|
@@ -380,6 +410,12 @@
|
|
|
380
410
|
{@const resolvedStrokeWidth = resolveStyleProp(strokeWidth, item.d)}
|
|
381
411
|
{@const resolvedOpacity = resolveStyleProp(opacity, item.d)}
|
|
382
412
|
{@const resolvedClass = resolveStyleProp(className, item.d)}
|
|
413
|
+
{@const resolvedBorderWidth =
|
|
414
|
+
resolvedStrokeWidth != null
|
|
415
|
+
? `${resolvedStrokeWidth}px`
|
|
416
|
+
: resolvedStroke != null
|
|
417
|
+
? '1px'
|
|
418
|
+
: undefined}
|
|
383
419
|
<div
|
|
384
420
|
style:position="absolute"
|
|
385
421
|
style:left="{item.cx}px"
|
|
@@ -387,11 +423,12 @@
|
|
|
387
423
|
style:width="{item.r * 2}px"
|
|
388
424
|
style:height="{item.r * 2}px"
|
|
389
425
|
style:border-radius="50%"
|
|
390
|
-
style:background
|
|
426
|
+
style:background={resolvedFill}
|
|
427
|
+
style:background-origin="border-box"
|
|
391
428
|
style:opacity={resolvedOpacity}
|
|
392
|
-
style:border-width={
|
|
429
|
+
style:border-width={resolvedBorderWidth}
|
|
393
430
|
style:border-color={resolvedStroke}
|
|
394
|
-
style:border-style=
|
|
431
|
+
style:border-style={dashArrayResolved ? 'dashed' : 'solid'}
|
|
395
432
|
style:transform="translate(-50%, -50%)"
|
|
396
433
|
class={cls('lc-circle', resolvedClass)}
|
|
397
434
|
{...restProps}
|
|
@@ -405,11 +442,12 @@
|
|
|
405
442
|
style:width="{motionR.current * 2}px"
|
|
406
443
|
style:height="{motionR.current * 2}px"
|
|
407
444
|
style:border-radius="50%"
|
|
408
|
-
style:background
|
|
445
|
+
style:background={staticFill}
|
|
446
|
+
style:background-origin="border-box"
|
|
409
447
|
style:opacity={staticOpacity}
|
|
410
|
-
style:border-width={
|
|
448
|
+
style:border-width={staticBorderWidth}
|
|
411
449
|
style:border-color={staticStroke}
|
|
412
|
-
style:border-style=
|
|
450
|
+
style:border-style={dashArrayResolved ? 'dashed' : 'solid'}
|
|
413
451
|
style:transform="translate(-50%, -50%)"
|
|
414
452
|
class={cls('lc-circle', staticClassName)}
|
|
415
453
|
{...restProps}
|
|
@@ -435,8 +473,12 @@
|
|
|
435
473
|
}
|
|
436
474
|
|
|
437
475
|
/* Html layers */
|
|
438
|
-
:global(:where(.lc-layout-html .lc-circle)
|
|
439
|
-
|
|
476
|
+
:global(:where(.lc-layout-html .lc-circle)) {
|
|
477
|
+
/* Match SVG sizing (visual extent equals `r * 2`, border on outer edge) */
|
|
478
|
+
box-sizing: border-box;
|
|
479
|
+
}
|
|
480
|
+
:global(:where(.lc-layout-html .lc-circle):not([background])) {
|
|
481
|
+
background: var(--fill-color);
|
|
440
482
|
}
|
|
441
483
|
:global(:where(.lc-layout-html .lc-circle):not([border-color])) {
|
|
442
484
|
border-color: var(--stroke-color);
|
|
@@ -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,29 @@
|
|
|
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(`M0,0 H${chartCtx.width} V${chartCtx.height} H0 Z`);
|
|
90
|
+
|
|
91
|
+
// Effective path used for canvas + html layers when inverting.
|
|
92
|
+
const effectivePath = $derived(invert && path ? `${outerRect} ${path}` : path);
|
|
93
|
+
|
|
94
|
+
// Cache the Path2D so `ctx.clip()` gets a stable reference per `path` change.
|
|
95
|
+
const canvasPath = $derived(
|
|
96
|
+
layerCtx === 'canvas' && effectivePath ? new Path2D(effectivePath) : undefined
|
|
97
|
+
);
|
|
98
|
+
|
|
76
99
|
if (layerCtx === 'canvas') {
|
|
77
100
|
chartCtx.registerComponent({
|
|
78
101
|
name: 'ClipPath',
|
|
79
102
|
kind: 'group',
|
|
80
103
|
canvasRender: {
|
|
81
104
|
render: (ctx) => {
|
|
82
|
-
if (!disabled &&
|
|
83
|
-
|
|
84
|
-
ctx.clip();
|
|
105
|
+
if (!disabled && canvasPath) {
|
|
106
|
+
ctx.clip(canvasPath, invert ? 'evenodd' : 'nonzero');
|
|
85
107
|
}
|
|
86
108
|
},
|
|
87
|
-
deps: () => [disabled,
|
|
109
|
+
deps: () => [disabled, canvasPath, invert],
|
|
88
110
|
},
|
|
89
111
|
});
|
|
90
112
|
}
|
|
@@ -93,7 +115,11 @@
|
|
|
93
115
|
{#if layerCtx === 'svg'}
|
|
94
116
|
<defs>
|
|
95
117
|
<clipPath {id} {...restProps}>
|
|
96
|
-
{
|
|
118
|
+
{#if clip}
|
|
119
|
+
{@render clip({ id })}
|
|
120
|
+
{:else if effectivePath}
|
|
121
|
+
<path d={effectivePath} clip-rule={invert ? 'evenodd' : undefined} />
|
|
122
|
+
{/if}
|
|
97
123
|
|
|
98
124
|
{#if useId}
|
|
99
125
|
<use href="#{useId}" />
|
|
@@ -103,11 +129,22 @@
|
|
|
103
129
|
{/if}
|
|
104
130
|
|
|
105
131
|
{#if children}
|
|
106
|
-
{#if disabled
|
|
132
|
+
{#if disabled}
|
|
107
133
|
{@render children({ id, url, useId })}
|
|
108
|
-
{:else}
|
|
134
|
+
{:else if layerCtx === 'svg'}
|
|
109
135
|
<g style:clip-path={url} class="lc-clip-path-g">
|
|
110
136
|
{@render children({ id, url, useId })}
|
|
111
137
|
</g>
|
|
138
|
+
{:else if layerCtx === 'html' && effectivePath}
|
|
139
|
+
<div
|
|
140
|
+
class="lc-clip-path-div"
|
|
141
|
+
style:position="absolute"
|
|
142
|
+
style:inset="0"
|
|
143
|
+
style:clip-path={invert ? `path(evenodd, "${effectivePath}")` : `path("${effectivePath}")`}
|
|
144
|
+
>
|
|
145
|
+
{@render children({ id, url, useId })}
|
|
146
|
+
</div>
|
|
147
|
+
{:else}
|
|
148
|
+
{@render children({ id, url, useId })}
|
|
112
149
|
{/if}
|
|
113
150
|
{/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';
|
|
@@ -310,6 +310,15 @@
|
|
|
310
310
|
const staticStrokeWidth = $derived(typeof strokeWidth === 'number' ? strokeWidth : undefined);
|
|
311
311
|
const staticOpacity = $derived(typeof opacity === 'number' ? opacity : undefined);
|
|
312
312
|
const staticClassName = $derived(typeof className === 'string' ? className : undefined);
|
|
313
|
+
// Match SVG's implicit `stroke-width: 1` default: if `stroke` is set but
|
|
314
|
+
// `strokeWidth` is not, render a 1px border so HTML matches SVG/Canvas layers.
|
|
315
|
+
const staticBorderWidth = $derived(
|
|
316
|
+
typeof strokeWidth === 'number'
|
|
317
|
+
? `${strokeWidth}px`
|
|
318
|
+
: typeof stroke === 'string'
|
|
319
|
+
? '1px'
|
|
320
|
+
: undefined
|
|
321
|
+
);
|
|
313
322
|
|
|
314
323
|
chartCtx.registerComponent({
|
|
315
324
|
name: 'Ellipse',
|
|
@@ -401,6 +410,12 @@
|
|
|
401
410
|
{@const resolvedStrokeWidth = resolveStyleProp(strokeWidth, item.d)}
|
|
402
411
|
{@const resolvedOpacity = resolveStyleProp(opacity, item.d)}
|
|
403
412
|
{@const resolvedClass = resolveStyleProp(className, item.d)}
|
|
413
|
+
{@const resolvedBorderWidth =
|
|
414
|
+
resolvedStrokeWidth != null
|
|
415
|
+
? `${resolvedStrokeWidth}px`
|
|
416
|
+
: resolvedStroke != null
|
|
417
|
+
? '1px'
|
|
418
|
+
: undefined}
|
|
404
419
|
<div
|
|
405
420
|
style:position="absolute"
|
|
406
421
|
style:left="{item.cx}px"
|
|
@@ -408,9 +423,10 @@
|
|
|
408
423
|
style:width="{item.rx * 2}px"
|
|
409
424
|
style:height="{item.ry * 2}px"
|
|
410
425
|
style:border-radius="50%"
|
|
411
|
-
style:background
|
|
426
|
+
style:background={resolvedFill}
|
|
427
|
+
style:background-origin="border-box"
|
|
412
428
|
style:opacity={resolvedOpacity}
|
|
413
|
-
style:border-width={
|
|
429
|
+
style:border-width={resolvedBorderWidth}
|
|
414
430
|
style:border-color={resolvedStroke}
|
|
415
431
|
style:border-style="solid"
|
|
416
432
|
style:transform="translate(-50%, -50%)"
|
|
@@ -426,9 +442,10 @@
|
|
|
426
442
|
style:width="{motionRx.current * 2}px"
|
|
427
443
|
style:height="{motionRy.current * 2}px"
|
|
428
444
|
style:border-radius="50%"
|
|
429
|
-
style:background
|
|
445
|
+
style:background={staticFill}
|
|
446
|
+
style:background-origin="border-box"
|
|
430
447
|
style:opacity={staticOpacity}
|
|
431
|
-
style:border-width={
|
|
448
|
+
style:border-width={staticBorderWidth}
|
|
432
449
|
style:border-color={staticStroke}
|
|
433
450
|
style:border-style="solid"
|
|
434
451
|
style:transform="translate(-50%, -50%)"
|
|
@@ -454,8 +471,12 @@
|
|
|
454
471
|
}
|
|
455
472
|
|
|
456
473
|
/* Html layers */
|
|
457
|
-
:global(:where(.lc-layout-html .lc-ellipse)
|
|
458
|
-
|
|
474
|
+
:global(:where(.lc-layout-html .lc-ellipse)) {
|
|
475
|
+
/* Match SVG sizing (visual extent equals `rx * 2`×`ry * 2`, border on outer edge) */
|
|
476
|
+
box-sizing: border-box;
|
|
477
|
+
}
|
|
478
|
+
:global(:where(.lc-layout-html .lc-ellipse):not([background])) {
|
|
479
|
+
background: var(--fill-color);
|
|
459
480
|
}
|
|
460
481
|
:global(:where(.lc-layout-html .lc-ellipse):not([border-color])) {
|
|
461
482
|
border-color: var(--stroke-color);
|
|
@@ -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
|
*/
|
|
@@ -258,9 +258,7 @@
|
|
|
258
258
|
const width = $derived(Math.ceil(barWidth) + padding * 2);
|
|
259
259
|
const svgHeight = $derived(titleHeight + height + tickLabelHeight + padding * 2 + 3);
|
|
260
260
|
const barY = $derived(
|
|
261
|
-
labelPlacement === 'top'
|
|
262
|
-
? titleHeight + padding + tickLabelHeight
|
|
263
|
-
: titleHeight + padding
|
|
261
|
+
labelPlacement === 'top' ? titleHeight + padding + tickLabelHeight : titleHeight + padding
|
|
264
262
|
);
|
|
265
263
|
const tickLabelY = $derived(
|
|
266
264
|
labelPlacement === 'top'
|