layerchart 0.78.0 → 0.79.0
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/Arc.svelte +15 -6
- package/dist/components/Area.svelte +16 -2
- package/dist/components/Circle.svelte +20 -5
- package/dist/components/GeoPath.svelte +10 -5
- package/dist/components/GeoTile.svelte +4 -4
- package/dist/components/Group.svelte +4 -4
- package/dist/components/Line.svelte +23 -8
- package/dist/components/LinearGradient.svelte +19 -17
- package/dist/components/RadialGradient.svelte +1 -0
- package/dist/components/Rect.svelte +22 -7
- package/dist/components/Spline.svelte +16 -11
- package/dist/components/Text.svelte +15 -7
- package/dist/utils/canvas.js +21 -4
- package/package.json +1 -1
- package/dist/utils/canvas.d.ts +0 -46
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
import { scaleLinear } from 'd3-scale';
|
|
25
25
|
import { min, max } from 'd3-array';
|
|
26
26
|
|
|
27
|
+
import { objectId } from '@layerstack/utils/object';
|
|
28
|
+
|
|
27
29
|
import { chartContext } from './ChartContext.svelte';
|
|
28
30
|
import { motionStore } from '../stores/motionStore.js';
|
|
29
31
|
import { degreesToRadians } from '../utils/math.js';
|
|
@@ -87,6 +89,9 @@
|
|
|
87
89
|
export let stroke: string | undefined = undefined;
|
|
88
90
|
export let strokeWidth: number | undefined = undefined;
|
|
89
91
|
|
|
92
|
+
let className: string | undefined = undefined;
|
|
93
|
+
export { className as class };
|
|
94
|
+
|
|
90
95
|
export let track: boolean | SVGAttributes<SVGPathElement> = false;
|
|
91
96
|
|
|
92
97
|
const { yRange } = chartContext();
|
|
@@ -213,19 +218,23 @@
|
|
|
213
218
|
// Arc
|
|
214
219
|
renderPathData(ctx, arc(), {
|
|
215
220
|
styles: { fill, fillOpacity, stroke, strokeWidth },
|
|
216
|
-
classes:
|
|
221
|
+
classes: className,
|
|
217
222
|
});
|
|
218
223
|
}
|
|
219
224
|
|
|
220
|
-
|
|
225
|
+
// TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
|
|
226
|
+
$: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
|
|
227
|
+
$: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
|
|
228
|
+
|
|
221
229
|
$: if (renderContext === 'canvas') {
|
|
222
|
-
|
|
230
|
+
// Redraw when props change
|
|
231
|
+
arc && trackArc && fillKey && fillOpacity && strokeKey && strokeWidth && className;
|
|
232
|
+
canvasContext.invalidate();
|
|
223
233
|
}
|
|
224
234
|
|
|
235
|
+
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
225
236
|
$: if (renderContext === 'canvas') {
|
|
226
|
-
|
|
227
|
-
arc && trackArc;
|
|
228
|
-
canvasContext.invalidate();
|
|
237
|
+
canvasUnregister = canvasContext.register({ name: 'Arc', render });
|
|
229
238
|
}
|
|
230
239
|
|
|
231
240
|
onDestroy(() => {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { interpolatePath } from 'd3-interpolate-path';
|
|
8
8
|
|
|
9
9
|
import { cls } from '@layerstack/tailwind';
|
|
10
|
+
import { objectId } from '@layerstack/utils/object';
|
|
10
11
|
|
|
11
12
|
import { motionStore } from '../stores/motionStore.js';
|
|
12
13
|
|
|
@@ -59,6 +60,9 @@
|
|
|
59
60
|
export let stroke: string | undefined = undefined;
|
|
60
61
|
export let strokeWidth: number | undefined = undefined;
|
|
61
62
|
|
|
63
|
+
let className: string | undefined = undefined;
|
|
64
|
+
export { className as class };
|
|
65
|
+
|
|
62
66
|
$: xAccessor = x ? accessor(x) : $contextX;
|
|
63
67
|
$: y0Accessor = y0 ? accessor(y0) : (d: any) => min($yDomain);
|
|
64
68
|
$: y1Accessor = y1 ? accessor(y1) : $y;
|
|
@@ -139,10 +143,20 @@
|
|
|
139
143
|
function render(ctx: CanvasRenderingContext2D) {
|
|
140
144
|
renderPathData(ctx, $tweened_d, {
|
|
141
145
|
styles: { fill, fillOpacity, stroke, strokeWidth },
|
|
142
|
-
classes:
|
|
146
|
+
classes: className,
|
|
143
147
|
});
|
|
144
148
|
}
|
|
145
149
|
|
|
150
|
+
// TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
|
|
151
|
+
$: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
|
|
152
|
+
$: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
|
|
153
|
+
|
|
154
|
+
$: if (renderContext === 'canvas') {
|
|
155
|
+
// Redraw when props change
|
|
156
|
+
fillKey && fillOpacity && strokeKey && strokeWidth && className;
|
|
157
|
+
canvasContext.invalidate();
|
|
158
|
+
}
|
|
159
|
+
|
|
146
160
|
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
147
161
|
$: if (renderContext === 'canvas') {
|
|
148
162
|
canvasUnregister = canvasContext.register({ name: 'Area', render });
|
|
@@ -182,7 +196,7 @@
|
|
|
182
196
|
{stroke}
|
|
183
197
|
stroke-width={strokeWidth}
|
|
184
198
|
{...$$restProps}
|
|
185
|
-
class={cls('path-area',
|
|
199
|
+
class={cls('path-area', className)}
|
|
186
200
|
on:click
|
|
187
201
|
on:pointermove
|
|
188
202
|
on:pointerleave
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { onDestroy, tick } from 'svelte';
|
|
3
3
|
import type { spring as springStore, tweened as tweenedStore } from 'svelte/motion';
|
|
4
4
|
import { cls } from '@layerstack/tailwind';
|
|
5
|
+
import { objectId } from '@layerstack/utils/object';
|
|
5
6
|
|
|
6
7
|
import { motionStore } from '../stores/motionStore.js';
|
|
7
8
|
import { getCanvasContext } from './layout/Canvas.svelte';
|
|
@@ -25,6 +26,9 @@
|
|
|
25
26
|
export let stroke: string | undefined = undefined;
|
|
26
27
|
export let strokeWidth: number | undefined = undefined;
|
|
27
28
|
|
|
29
|
+
let className: string | undefined = undefined;
|
|
30
|
+
export { className as class };
|
|
31
|
+
|
|
28
32
|
let tweened_cx = motionStore(initialCx, { spring, tweened });
|
|
29
33
|
let tweened_cy = motionStore(initialCy, { spring, tweened });
|
|
30
34
|
let tweened_r = motionStore(initialR, { spring, tweened });
|
|
@@ -46,15 +50,26 @@
|
|
|
46
50
|
});
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
// TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
|
|
54
|
+
$: fillKey = fill && typeof fill === 'object' ? objectId(fill) : fill;
|
|
55
|
+
$: strokeKey = stroke && typeof stroke === 'object' ? objectId(stroke) : stroke;
|
|
56
|
+
|
|
50
57
|
$: if (renderContext === 'canvas') {
|
|
51
|
-
|
|
58
|
+
// Redraw when props changes
|
|
59
|
+
$tweened_cx &&
|
|
60
|
+
$tweened_cy &&
|
|
61
|
+
$tweened_r &&
|
|
62
|
+
fillKey &&
|
|
63
|
+
fillOpacity &&
|
|
64
|
+
strokeKey &&
|
|
65
|
+
strokeWidth &&
|
|
66
|
+
className;
|
|
67
|
+
canvasContext.invalidate();
|
|
52
68
|
}
|
|
53
69
|
|
|
70
|
+
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
54
71
|
$: if (renderContext === 'canvas') {
|
|
55
|
-
|
|
56
|
-
$tweened_cx && $tweened_cy && $tweened_r;
|
|
57
|
-
canvasContext.invalidate();
|
|
72
|
+
canvasUnregister = canvasContext.register({ name: 'Circle', render });
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
onDestroy(() => {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { geoCurvePath } from '../utils/geo.js';
|
|
16
16
|
import { renderPathData } from '../utils/canvas.js';
|
|
17
17
|
import { getCanvasContext } from './layout/Canvas.svelte';
|
|
18
|
+
import { objectId } from '@layerstack/utils/object';
|
|
18
19
|
|
|
19
20
|
export let geojson: GeoPermissibleObjects | null | undefined = undefined;
|
|
20
21
|
|
|
@@ -89,17 +90,21 @@
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
$:
|
|
94
|
-
|
|
95
|
-
}
|
|
93
|
+
// TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
|
|
94
|
+
$: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
|
|
95
|
+
$: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
|
|
96
96
|
|
|
97
97
|
$: if (renderContext === 'canvas') {
|
|
98
98
|
// Redraw when geojson, projection, or class change
|
|
99
|
-
geojson && _projection &&
|
|
99
|
+
geojson && _projection && fillKey && strokeKey && strokeWidth && className;
|
|
100
100
|
canvasContext.invalidate();
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
104
|
+
$: if (renderContext === 'canvas') {
|
|
105
|
+
canvasUnregister = canvasContext.register({ name: 'GeoPath', render: _render });
|
|
106
|
+
}
|
|
107
|
+
|
|
103
108
|
onDestroy(() => {
|
|
104
109
|
if (renderContext === 'canvas') {
|
|
105
110
|
canvasUnregister();
|
|
@@ -46,14 +46,14 @@
|
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
50
49
|
$: if (renderContext === 'canvas') {
|
|
51
|
-
|
|
50
|
+
tile;
|
|
51
|
+
canvasContext.invalidate();
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
54
55
|
$: if (renderContext === 'canvas') {
|
|
55
|
-
|
|
56
|
-
canvasContext.invalidate();
|
|
56
|
+
canvasUnregister = canvasContext.register({ name: 'GeoTile', render });
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
onDestroy(() => {
|
|
@@ -53,14 +53,14 @@
|
|
|
53
53
|
ctx.translate($tweened_x ?? 0, $tweened_y ?? 0);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
57
56
|
$: if (renderContext === 'canvas') {
|
|
58
|
-
|
|
57
|
+
$tweened_x && $tweened_y;
|
|
58
|
+
canvasContext.invalidate();
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
61
62
|
$: if (renderContext === 'canvas') {
|
|
62
|
-
|
|
63
|
-
canvasContext.invalidate();
|
|
63
|
+
canvasUnregister = canvasContext.register({ name: 'Group', render, retainState: true });
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
onDestroy(() => {
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import { onDestroy, tick, type ComponentProps } from 'svelte';
|
|
3
3
|
import type { spring as springStore, tweened as tweenedStore } from 'svelte/motion';
|
|
4
4
|
import { cls } from '@layerstack/tailwind';
|
|
5
|
+
import { uniqueId } from '@layerstack/utils';
|
|
6
|
+
import { objectId } from '@layerstack/utils/object';
|
|
5
7
|
|
|
6
8
|
import { motionStore } from '../stores/motionStore.js';
|
|
7
|
-
import { uniqueId } from '@layerstack/utils';
|
|
8
9
|
|
|
9
10
|
import Marker from './Marker.svelte';
|
|
10
11
|
import { renderPathData } from '../utils/canvas.js';
|
|
@@ -26,6 +27,9 @@
|
|
|
26
27
|
export let stroke: string | undefined = undefined;
|
|
27
28
|
export let strokeWidth: number | undefined = undefined;
|
|
28
29
|
|
|
30
|
+
let className: string | undefined = undefined;
|
|
31
|
+
export { className as class };
|
|
32
|
+
|
|
29
33
|
/** Marker to attach to start and end points of path */
|
|
30
34
|
export let marker: ComponentProps<Marker>['type'] | ComponentProps<Marker> | undefined =
|
|
31
35
|
undefined;
|
|
@@ -61,19 +65,30 @@
|
|
|
61
65
|
const pathData = `M ${$tweened_x1},${$tweened_y1} L ${$tweened_x2},${$tweened_y2}`;
|
|
62
66
|
renderPathData(ctx, pathData, {
|
|
63
67
|
styles: { fill, stroke, strokeWidth },
|
|
64
|
-
classes:
|
|
68
|
+
classes: className,
|
|
65
69
|
});
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
|
|
72
|
+
// TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
|
|
73
|
+
$: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
|
|
74
|
+
$: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
|
|
75
|
+
|
|
69
76
|
$: if (renderContext === 'canvas') {
|
|
70
|
-
|
|
77
|
+
// Redraw when props change
|
|
78
|
+
$tweened_x1 &&
|
|
79
|
+
$tweened_y1 &&
|
|
80
|
+
$tweened_x2 &&
|
|
81
|
+
$tweened_y2 &&
|
|
82
|
+
fillKey &&
|
|
83
|
+
strokeKey &&
|
|
84
|
+
strokeWidth &&
|
|
85
|
+
className;
|
|
86
|
+
canvasContext.invalidate();
|
|
71
87
|
}
|
|
72
88
|
|
|
89
|
+
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
73
90
|
$: if (renderContext === 'canvas') {
|
|
74
|
-
|
|
75
|
-
$tweened_x1 && $tweened_y1 && $tweened_x2 && $tweened_y2;
|
|
76
|
-
canvasContext.invalidate();
|
|
91
|
+
canvasUnregister = canvasContext.register({ name: 'Line', render });
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
onDestroy(() => {
|
|
@@ -95,8 +110,8 @@
|
|
|
95
110
|
stroke-width={strokeWidth}
|
|
96
111
|
marker-start={markerStartId ? `url(#${markerStartId})` : undefined}
|
|
97
112
|
marker-end={markerEndId ? `url(#${markerEndId})` : undefined}
|
|
98
|
-
class={cls($$props.stroke === undefined && 'stroke-surface-content')}
|
|
99
113
|
{...$$restProps}
|
|
114
|
+
class={cls($$props.stroke === undefined && 'stroke-surface-content', className)}
|
|
100
115
|
on:click
|
|
101
116
|
on:pointermove
|
|
102
117
|
on:pointerleave
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { chartContext } from './ChartContext.svelte';
|
|
6
6
|
import { getCanvasContext } from './layout/Canvas.svelte';
|
|
7
|
-
import { getComputedStyles } from '../utils/canvas.js';
|
|
7
|
+
import { createLinearGradient, getComputedStyles } from '../utils/canvas.js';
|
|
8
8
|
import { parsePercent } from '../utils/math.js';
|
|
9
9
|
|
|
10
10
|
/** Unique id for linearGradient */
|
|
@@ -36,43 +36,45 @@
|
|
|
36
36
|
let canvasGradient: CanvasGradient;
|
|
37
37
|
|
|
38
38
|
function render(ctx: CanvasRenderingContext2D) {
|
|
39
|
-
// TODO: Use x1/y1/x2/y2 values (convert from pecentage strings)
|
|
40
|
-
const gradient = ctx.createLinearGradient(
|
|
41
|
-
$padding.left,
|
|
42
|
-
$padding.top,
|
|
43
|
-
vertical ? $padding.left : $width - $padding.right,
|
|
44
|
-
vertical ? $height + $padding.bottom : $padding.top
|
|
45
|
-
);
|
|
46
|
-
|
|
47
39
|
// Use `getComputedStyles()` to convert each stop (if using CSS variables and/or classes) to color values
|
|
48
|
-
stops.
|
|
40
|
+
const _stops = stops.map((stop, i) => {
|
|
49
41
|
if (Array.isArray(stop)) {
|
|
50
42
|
const { fill } = getComputedStyles(ctx.canvas, {
|
|
51
43
|
styles: { fill: stop[1] },
|
|
52
44
|
classes: $$props.class,
|
|
53
45
|
});
|
|
54
|
-
|
|
46
|
+
return { offset: parsePercent(stop[0]), color: fill };
|
|
55
47
|
} else {
|
|
56
48
|
const { fill } = getComputedStyles(ctx.canvas, {
|
|
57
49
|
styles: { fill: stop },
|
|
58
50
|
classes: $$props.class,
|
|
59
51
|
});
|
|
60
|
-
|
|
52
|
+
return { offset: i / (stops.length - 1), color: fill };
|
|
61
53
|
}
|
|
62
54
|
});
|
|
63
55
|
|
|
56
|
+
// TODO: Use x1/y1/x2/y2 values (convert from pecentage strings)
|
|
57
|
+
const gradient = createLinearGradient(
|
|
58
|
+
ctx,
|
|
59
|
+
$padding.left,
|
|
60
|
+
$padding.top,
|
|
61
|
+
vertical ? $padding.left : $width - $padding.right,
|
|
62
|
+
vertical ? $height + $padding.bottom : $padding.top,
|
|
63
|
+
_stops
|
|
64
|
+
);
|
|
65
|
+
|
|
64
66
|
canvasGradient = gradient;
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
68
69
|
$: if (renderContext === 'canvas') {
|
|
69
|
-
|
|
70
|
+
// Redraw when props changes (TODO: styles, class, etc)
|
|
71
|
+
x1 && y1 && x2 && y2 && stops;
|
|
72
|
+
canvasContext.invalidate();
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
72
76
|
$: if (renderContext === 'canvas') {
|
|
73
|
-
|
|
74
|
-
stops && x1 && y1 && x2 && y2 && $width && $height;
|
|
75
|
-
canvasContext.invalidate();
|
|
77
|
+
canvasUnregister = canvasContext.register({ name: 'Gradient', render });
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
onDestroy(() => {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
function render(ctx: CanvasRenderingContext2D) {
|
|
43
43
|
// TODO: Set correct values: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createRadialGradient. See also: LinearGradient
|
|
44
|
+
// TODO: Memoize `createRadialGradient()` (see LinearGradient)
|
|
44
45
|
const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, 0);
|
|
45
46
|
|
|
46
47
|
// Use `getComputedStyles()` to convert each stop (if using CSS variables and/or classes) to color values
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onDestroy, tick } from 'svelte';
|
|
3
3
|
import { cls } from '@layerstack/tailwind';
|
|
4
|
+
import { objectId } from '@layerstack/utils/object';
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
7
|
motionStore,
|
|
@@ -28,6 +29,9 @@
|
|
|
28
29
|
export let stroke: string | undefined = undefined;
|
|
29
30
|
export let strokeWidth: number | undefined = undefined;
|
|
30
31
|
|
|
32
|
+
let className: string | undefined = undefined;
|
|
33
|
+
export { className as class };
|
|
34
|
+
|
|
31
35
|
export let spring: boolean | SpringOptions | { [prop: string]: SpringOptions } = undefined;
|
|
32
36
|
export let tweened: boolean | TweenedOptions | { [prop: string]: TweenedOptions } = undefined;
|
|
33
37
|
|
|
@@ -52,20 +56,31 @@
|
|
|
52
56
|
{ x: $tweened_x, y: $tweened_y, width: $tweened_width, height: $tweened_height },
|
|
53
57
|
{
|
|
54
58
|
styles: { fill, fillOpacity, stroke, strokeWidth },
|
|
55
|
-
classes:
|
|
59
|
+
classes: className,
|
|
56
60
|
}
|
|
57
61
|
);
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
// TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
|
|
65
|
+
$: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
|
|
66
|
+
$: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
|
|
67
|
+
|
|
61
68
|
$: if (renderContext === 'canvas') {
|
|
62
|
-
|
|
69
|
+
// Redraw when props change
|
|
70
|
+
$tweened_x &&
|
|
71
|
+
$tweened_y &&
|
|
72
|
+
$tweened_width &&
|
|
73
|
+
$tweened_height &&
|
|
74
|
+
fillKey &&
|
|
75
|
+
strokeKey &&
|
|
76
|
+
strokeWidth &&
|
|
77
|
+
className;
|
|
78
|
+
canvasContext.invalidate();
|
|
63
79
|
}
|
|
64
80
|
|
|
81
|
+
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
65
82
|
$: if (renderContext === 'canvas') {
|
|
66
|
-
|
|
67
|
-
$tweened_x && $tweened_y && $tweened_width && $tweened_height;
|
|
68
|
-
canvasContext.invalidate();
|
|
83
|
+
canvasUnregister = canvasContext.register({ name: 'Rect', render });
|
|
69
84
|
}
|
|
70
85
|
|
|
71
86
|
onDestroy(() => {
|
|
@@ -83,7 +98,7 @@
|
|
|
83
98
|
y={$tweened_y}
|
|
84
99
|
width={$tweened_width}
|
|
85
100
|
height={$tweened_height}
|
|
86
|
-
class={cls(
|
|
101
|
+
class={cls(fill == null && 'fill-surface-content')}
|
|
87
102
|
{fill}
|
|
88
103
|
fill-opacity={fillOpacity}
|
|
89
104
|
{stroke}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { max } from 'd3-array';
|
|
13
13
|
import { cls } from '@layerstack/tailwind';
|
|
14
14
|
import { uniqueId } from '@layerstack/utils';
|
|
15
|
+
import { objectId } from '@layerstack/utils/object';
|
|
15
16
|
|
|
16
17
|
import { chartContext } from './ChartContext.svelte';
|
|
17
18
|
import Group from './Group.svelte';
|
|
@@ -68,6 +69,9 @@
|
|
|
68
69
|
export let strokeWidth: number | undefined = undefined;
|
|
69
70
|
export let opacity: number | undefined = undefined;
|
|
70
71
|
|
|
72
|
+
let className: string | undefined = undefined;
|
|
73
|
+
export { className as class };
|
|
74
|
+
|
|
71
75
|
/** Marker to attach to start, mid, and end points of path */
|
|
72
76
|
export let marker: ComponentProps<Marker>['type'] | ComponentProps<Marker> | undefined =
|
|
73
77
|
undefined;
|
|
@@ -166,17 +170,23 @@
|
|
|
166
170
|
function render(ctx: CanvasRenderingContext2D) {
|
|
167
171
|
renderPathData(ctx, $tweened_d, {
|
|
168
172
|
styles: { stroke, fill, strokeWidth, opacity },
|
|
169
|
-
classes:
|
|
173
|
+
classes: className,
|
|
170
174
|
});
|
|
171
175
|
}
|
|
172
176
|
|
|
177
|
+
// TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
|
|
178
|
+
$: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
|
|
179
|
+
$: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
|
|
180
|
+
|
|
181
|
+
$: if (renderContext === 'canvas') {
|
|
182
|
+
// Redraw when props change
|
|
183
|
+
$tweened_d && fillKey && strokeKey && strokeWidth && className;
|
|
184
|
+
canvasContext.invalidate();
|
|
185
|
+
}
|
|
186
|
+
|
|
173
187
|
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
174
188
|
$: if (renderContext === 'canvas') {
|
|
175
189
|
canvasUnregister = canvasContext.register({ name: 'Spline', render });
|
|
176
|
-
|
|
177
|
-
tweened_d.subscribe(() => {
|
|
178
|
-
canvasContext.invalidate();
|
|
179
|
-
});
|
|
180
190
|
}
|
|
181
191
|
|
|
182
192
|
onDestroy(() => {
|
|
@@ -225,12 +235,7 @@
|
|
|
225
235
|
<path
|
|
226
236
|
d={$tweened_d}
|
|
227
237
|
{...$$restProps}
|
|
228
|
-
class={cls(
|
|
229
|
-
'path-line',
|
|
230
|
-
!$$props.fill && 'fill-none',
|
|
231
|
-
!$$props.stroke && 'stroke-surface-content',
|
|
232
|
-
$$props.class
|
|
233
|
-
)}
|
|
238
|
+
class={cls('path-line', !fill && 'fill-none', !stroke && 'stroke-surface-content', className)}
|
|
234
239
|
{fill}
|
|
235
240
|
{stroke}
|
|
236
241
|
stroke-width={strokeWidth}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { onDestroy, tick } from 'svelte';
|
|
3
3
|
import type { spring as springStore, tweened as tweenedStore } from 'svelte/motion';
|
|
4
4
|
import { cls } from '@layerstack/tailwind';
|
|
5
|
+
import { objectId } from '@layerstack/utils/object';
|
|
5
6
|
|
|
6
7
|
import { getStringWidth } from '../utils/string.js';
|
|
7
8
|
import { motionStore } from '../stores/motionStore.js';
|
|
@@ -65,6 +66,9 @@
|
|
|
65
66
|
export let stroke: string | undefined = undefined;
|
|
66
67
|
export let strokeWidth: number | undefined = undefined;
|
|
67
68
|
|
|
69
|
+
let className: string | undefined = undefined;
|
|
70
|
+
export { className as class };
|
|
71
|
+
|
|
68
72
|
let wordsByLines: { words: string[]; width?: number }[] = [];
|
|
69
73
|
let wordsWithWidth: { word: string; width: number }[] = [];
|
|
70
74
|
let spaceWidth: number = 0;
|
|
@@ -192,21 +196,25 @@
|
|
|
192
196
|
},
|
|
193
197
|
{
|
|
194
198
|
styles: { fill, fillOpacity, stroke, strokeWidth, paintOrder: 'stroke', textAnchor },
|
|
195
|
-
classes: cls(fill === undefined && 'fill-surface-content',
|
|
199
|
+
classes: cls(fill === undefined && 'fill-surface-content', className),
|
|
196
200
|
}
|
|
197
201
|
);
|
|
198
202
|
});
|
|
199
203
|
}
|
|
200
204
|
|
|
201
|
-
|
|
205
|
+
// TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
|
|
206
|
+
$: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
|
|
207
|
+
$: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
|
|
208
|
+
|
|
202
209
|
$: if (renderContext === 'canvas') {
|
|
203
|
-
|
|
210
|
+
// Redraw when props change
|
|
211
|
+
value && $tweened_x && $tweened_y && fillKey && strokeKey && strokeWidth && className;
|
|
212
|
+
canvasContext.invalidate();
|
|
204
213
|
}
|
|
205
214
|
|
|
215
|
+
let canvasUnregister: ReturnType<typeof canvasContext.register>;
|
|
206
216
|
$: if (renderContext === 'canvas') {
|
|
207
|
-
|
|
208
|
-
value && $tweened_x && $tweened_y;
|
|
209
|
-
canvasContext.invalidate();
|
|
217
|
+
canvasUnregister = canvasContext.register({ name: 'Text', render });
|
|
210
218
|
}
|
|
211
219
|
|
|
212
220
|
onDestroy(() => {
|
|
@@ -231,7 +239,7 @@
|
|
|
231
239
|
fill-opacity={fillOpacity}
|
|
232
240
|
{stroke}
|
|
233
241
|
stroke-width={strokeWidth}
|
|
234
|
-
class={cls(fill === undefined && 'fill-surface-content',
|
|
242
|
+
class={cls(fill === undefined && 'fill-surface-content', className)}
|
|
235
243
|
>
|
|
236
244
|
{#each wordsByLines as line, index}
|
|
237
245
|
<tspan x={$tweened_x} dy={index === 0 ? startDy : lineHeight}>
|
package/dist/utils/canvas.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { memoize } from 'lodash-es';
|
|
1
2
|
export const DEFAULT_FILL = 'rgb(0, 0, 0)';
|
|
2
3
|
const CANVAS_STYLES_ELEMENT_ID = '__layerchart_canvas_styles_id';
|
|
3
4
|
/**
|
|
@@ -23,7 +24,10 @@ export function getComputedStyles(canvas, { styles, classes } = {}) {
|
|
|
23
24
|
Object.assign(svg.style, styles);
|
|
24
25
|
}
|
|
25
26
|
if (classes) {
|
|
26
|
-
svg.setAttribute('class', classes
|
|
27
|
+
svg.setAttribute('class', classes
|
|
28
|
+
.split(' ')
|
|
29
|
+
.filter((s) => !s.startsWith('transition-'))
|
|
30
|
+
.join(' '));
|
|
27
31
|
}
|
|
28
32
|
const computedStyles = window.getComputedStyle(svg);
|
|
29
33
|
return computedStyles;
|
|
@@ -35,6 +39,7 @@ export function getComputedStyles(canvas, { styles, classes } = {}) {
|
|
|
35
39
|
}
|
|
36
40
|
/** Render onto canvas context. Supports CSS variables and classes by tranferring to hidden `<svg>` element before retrieval) */
|
|
37
41
|
function render(canvasCtx, render, styleOptions = {}) {
|
|
42
|
+
// console.count('render');
|
|
38
43
|
// TODO: Consider memoizing? How about reactiving to CSS variable changes (light/dark mode toggle)
|
|
39
44
|
const computedStyles = getComputedStyles(canvasCtx.canvas, styleOptions);
|
|
40
45
|
// Adhere to CSS paint order: https://developer.mozilla.org/en-US/docs/Web/CSS/paint-order
|
|
@@ -77,9 +82,9 @@ function render(canvasCtx, render, styleOptions = {}) {
|
|
|
77
82
|
: computedStyles?.fill;
|
|
78
83
|
if (fill) {
|
|
79
84
|
const currentGlobalAlpha = canvasCtx.globalAlpha;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
const fillOpacity = Number(computedStyles?.fillOpacity);
|
|
86
|
+
const opacity = Number(computedStyles?.opacity);
|
|
87
|
+
canvasCtx.globalAlpha = fillOpacity * opacity;
|
|
83
88
|
canvasCtx.fillStyle = fill;
|
|
84
89
|
render.fill(canvasCtx);
|
|
85
90
|
// Restore in case it was modified by `fillOpacity`
|
|
@@ -144,3 +149,15 @@ export function scaleCanvas(ctx, width, height) {
|
|
|
144
149
|
ctx.scale(devicePixelRatio, devicePixelRatio);
|
|
145
150
|
return { width: ctx.canvas.width, height: ctx.canvas.height };
|
|
146
151
|
}
|
|
152
|
+
export function _createLinearGradient(canvasCtx, x0, y0, x1, y1, stops) {
|
|
153
|
+
const gradient = canvasCtx.createLinearGradient(x0, y0, x1, y1);
|
|
154
|
+
stops.forEach(({ offset, color }) => {
|
|
155
|
+
gradient.addColorStop(offset, color);
|
|
156
|
+
});
|
|
157
|
+
return gradient;
|
|
158
|
+
}
|
|
159
|
+
/** Create linear gradient and memoize result to fix reactivity */
|
|
160
|
+
export const createLinearGradient = memoize(_createLinearGradient, (canvasCtx, x0, y0, x1, y1, stops) => {
|
|
161
|
+
const key = JSON.stringify({ x0, y0, x1, y1, stops });
|
|
162
|
+
return key;
|
|
163
|
+
});
|
package/package.json
CHANGED
package/dist/utils/canvas.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
export declare const DEFAULT_FILL = "rgb(0, 0, 0)";
|
|
2
|
-
type ComputedStylesOptions = {
|
|
3
|
-
styles?: Partial<Omit<CSSStyleDeclaration, 'fillOpacity' | 'strokeWidth' | 'opacity'> & {
|
|
4
|
-
fillOpacity?: number | string;
|
|
5
|
-
strokeWidth?: number | string;
|
|
6
|
-
opacity?: number | string;
|
|
7
|
-
}>;
|
|
8
|
-
classes?: string;
|
|
9
|
-
};
|
|
10
|
-
/**
|
|
11
|
-
* Appends or reuses `<svg>` element below `<canvas>` to resolve CSS variables and classes (ex. `stroke: hsl(var(--color-primary))` => `stroke: rgb(...)` )
|
|
12
|
-
*/
|
|
13
|
-
export declare function getComputedStyles(canvas: HTMLCanvasElement, { styles, classes }?: ComputedStylesOptions): CSSStyleDeclaration;
|
|
14
|
-
/** Render SVG path data onto canvas context. Supports CSS variables and classes by tranferring to hidden `<svg>` element before retrieval) */
|
|
15
|
-
export declare function renderPathData(canvasCtx: CanvasRenderingContext2D, pathData: string | null | undefined, styleOptions?: ComputedStylesOptions): void;
|
|
16
|
-
export declare function renderText(canvasCtx: CanvasRenderingContext2D, text: string | number | null | undefined, coords: {
|
|
17
|
-
x: number;
|
|
18
|
-
y: number;
|
|
19
|
-
}, styleOptions?: ComputedStylesOptions): void;
|
|
20
|
-
export declare function renderRect(canvasCtx: CanvasRenderingContext2D, coords: {
|
|
21
|
-
x: number;
|
|
22
|
-
y: number;
|
|
23
|
-
width: number;
|
|
24
|
-
height: number;
|
|
25
|
-
}, styleOptions?: ComputedStylesOptions): void;
|
|
26
|
-
/** Clear canvas accounting for Canvas `context.translate(...)` */
|
|
27
|
-
export declare function clearCanvasContext(canvasCtx: CanvasRenderingContext2D, options: {
|
|
28
|
-
containerWidth: number;
|
|
29
|
-
containerHeight: number;
|
|
30
|
-
padding: {
|
|
31
|
-
top: number;
|
|
32
|
-
bottom: number;
|
|
33
|
-
left: number;
|
|
34
|
-
right: number;
|
|
35
|
-
};
|
|
36
|
-
}): void;
|
|
37
|
-
/**
|
|
38
|
-
Scales a canvas for high DPI / retina displays.
|
|
39
|
-
@see: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#examples
|
|
40
|
-
@see: https://web.dev/articles/canvas-hidipi
|
|
41
|
-
*/
|
|
42
|
-
export declare function scaleCanvas(ctx: CanvasRenderingContext2D, width: number, height: number): {
|
|
43
|
-
width: number;
|
|
44
|
-
height: number;
|
|
45
|
-
};
|
|
46
|
-
export {};
|