layerchart 0.60.3 → 0.70.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 +83 -27
- package/dist/components/Area.svelte +51 -12
- package/dist/components/Bar.svelte +5 -1
- package/dist/components/Circle.svelte +54 -12
- package/dist/components/ComputedStyles.svelte +16 -0
- package/dist/components/ComputedStyles.svelte.d.ts +20 -0
- package/dist/components/GeoPath.svelte +36 -44
- package/dist/components/GeoPath.svelte.d.ts +2 -2
- package/dist/components/GeoPoint.svelte +27 -13
- package/dist/components/GeoTile.svelte +22 -7
- package/dist/components/Graticule.svelte.d.ts +4 -4
- package/dist/components/Group.svelte +46 -18
- package/dist/components/HitCanvas.svelte +7 -21
- package/dist/components/Legend.svelte.d.ts +1 -1
- package/dist/components/Line.svelte +70 -31
- package/dist/components/LinearGradient.svelte +89 -26
- package/dist/components/LinearGradient.svelte.d.ts +2 -2
- package/dist/components/Points.svelte +23 -41
- package/dist/components/Points.svelte.d.ts +2 -1
- package/dist/components/RadialGradient.svelte +86 -28
- package/dist/components/RadialGradient.svelte.d.ts +2 -2
- package/dist/components/Rect.svelte +62 -18
- package/dist/components/Rule.svelte +1 -1
- package/dist/components/Spline.svelte +95 -57
- package/dist/components/Text.svelte +80 -22
- package/dist/components/TransformControls.svelte.d.ts +1 -1
- package/dist/components/charts/AreaChart.svelte +14 -10
- package/dist/components/charts/BarChart.svelte +13 -9
- package/dist/components/charts/LineChart.svelte +13 -9
- package/dist/components/charts/PieChart.svelte +6 -2
- package/dist/components/charts/PieChart.svelte.d.ts +2 -1
- package/dist/components/charts/ScatterChart.svelte +14 -10
- package/dist/components/layout/Canvas.svelte +93 -11
- package/dist/components/layout/Canvas.svelte.d.ts +13 -0
- package/dist/components/layout/Svg.svelte +1 -1
- package/dist/components/tooltip/Tooltip.svelte.d.ts +1 -1
- package/dist/utils/canvas.d.ts +46 -0
- package/dist/utils/canvas.js +146 -0
- package/dist/utils/common.d.ts +10 -1
- package/dist/utils/common.js +13 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/math.d.ts +2 -0
- package/dist/utils/math.js +10 -0
- package/dist/utils/path.d.ts +7 -0
- package/dist/utils/path.js +17 -3
- package/package.json +2 -2
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import Axis from '../Axis.svelte';
|
|
9
9
|
import Bars from '../Bars.svelte';
|
|
10
|
+
import Canvas from '../layout/Canvas.svelte';
|
|
10
11
|
import Chart from '../Chart.svelte';
|
|
11
12
|
import Grid from '../Grid.svelte';
|
|
12
13
|
import Highlight from '../Highlight.svelte';
|
|
@@ -16,7 +17,12 @@
|
|
|
16
17
|
import Svg from '../layout/Svg.svelte';
|
|
17
18
|
import * as Tooltip from '../tooltip/index.js';
|
|
18
19
|
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
accessor,
|
|
22
|
+
chartDataArray,
|
|
23
|
+
defaultChartPadding,
|
|
24
|
+
type Accessor,
|
|
25
|
+
} from '../../utils/common.js';
|
|
20
26
|
|
|
21
27
|
type ChartProps = ComponentProps<Chart<TData>>;
|
|
22
28
|
|
|
@@ -32,6 +38,7 @@
|
|
|
32
38
|
rule?: typeof rule;
|
|
33
39
|
series?: typeof series;
|
|
34
40
|
seriesLayout?: typeof seriesLayout;
|
|
41
|
+
renderContext?: typeof renderContext;
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
export let data: $$Props['data'] = [];
|
|
@@ -111,6 +118,8 @@
|
|
|
111
118
|
labels?: Partial<ComponentProps<Labels>>;
|
|
112
119
|
} = {};
|
|
113
120
|
|
|
121
|
+
export let renderContext: 'svg' | 'canvas' = 'svg';
|
|
122
|
+
|
|
114
123
|
$: allSeriesData = series
|
|
115
124
|
.flatMap((s) =>
|
|
116
125
|
s.data?.map((d) => {
|
|
@@ -195,12 +204,7 @@
|
|
|
195
204
|
{y1Range}
|
|
196
205
|
c={isVertical ? y : x}
|
|
197
206
|
cRange={['hsl(var(--color-primary))']}
|
|
198
|
-
padding={axis
|
|
199
|
-
? undefined
|
|
200
|
-
: {
|
|
201
|
-
left: axis === true || axis === 'y' ? 16 : 0,
|
|
202
|
-
bottom: (axis === true || axis === 'x' ? 16 : 0) + (legend === true ? 32 : 0),
|
|
203
|
-
}}
|
|
207
|
+
padding={defaultChartPadding(axis, legend)}
|
|
204
208
|
tooltip={{ mode: 'band' }}
|
|
205
209
|
{...$$restProps}
|
|
206
210
|
let:x
|
|
@@ -229,7 +233,7 @@
|
|
|
229
233
|
getBarsProps,
|
|
230
234
|
}}
|
|
231
235
|
<slot {...slotProps}>
|
|
232
|
-
<Svg>
|
|
236
|
+
<svelte:component this={renderContext === 'canvas' ? Canvas : Svg}>
|
|
233
237
|
<slot name="grid" {...slotProps}>
|
|
234
238
|
{#if grid}
|
|
235
239
|
<Grid
|
|
@@ -301,7 +305,7 @@
|
|
|
301
305
|
{#if labels}
|
|
302
306
|
<Labels {...props.labels} {...typeof labels === 'object' ? labels : null} />
|
|
303
307
|
{/if}
|
|
304
|
-
</
|
|
308
|
+
</svelte:component>
|
|
305
309
|
|
|
306
310
|
<slot name="legend" {...slotProps}>
|
|
307
311
|
{#if legend}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { format } from '@layerstack/utils';
|
|
5
5
|
|
|
6
6
|
import Axis from '../Axis.svelte';
|
|
7
|
+
import Canvas from '../layout/Canvas.svelte';
|
|
7
8
|
import Chart from '../Chart.svelte';
|
|
8
9
|
import Grid from '../Grid.svelte';
|
|
9
10
|
import Highlight from '../Highlight.svelte';
|
|
@@ -15,7 +16,12 @@
|
|
|
15
16
|
import Svg from '../layout/Svg.svelte';
|
|
16
17
|
import * as Tooltip from '../tooltip/index.js';
|
|
17
18
|
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
accessor,
|
|
21
|
+
chartDataArray,
|
|
22
|
+
defaultChartPadding,
|
|
23
|
+
type Accessor,
|
|
24
|
+
} from '../../utils/common.js';
|
|
19
25
|
|
|
20
26
|
interface $$Props extends ComponentProps<Chart<TData>> {
|
|
21
27
|
axis?: typeof axis;
|
|
@@ -26,6 +32,7 @@
|
|
|
26
32
|
props?: typeof props;
|
|
27
33
|
rule?: typeof rule;
|
|
28
34
|
series?: typeof series;
|
|
35
|
+
renderContext?: typeof renderContext;
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
export let data: $$Props['data'] = [];
|
|
@@ -65,6 +72,8 @@
|
|
|
65
72
|
points?: Partial<ComponentProps<Points>>;
|
|
66
73
|
} = {};
|
|
67
74
|
|
|
75
|
+
export let renderContext: 'svg' | 'canvas' = 'svg';
|
|
76
|
+
|
|
68
77
|
$: allSeriesData = series
|
|
69
78
|
.flatMap((s) => s.data?.map((d) => ({ seriesKey: s.key, ...d })))
|
|
70
79
|
.filter((d) => d) as Array<TData & { stackData?: any }>;
|
|
@@ -99,12 +108,7 @@
|
|
|
99
108
|
yBaseline={0}
|
|
100
109
|
yNice
|
|
101
110
|
{radial}
|
|
102
|
-
padding={radial
|
|
103
|
-
? undefined
|
|
104
|
-
: {
|
|
105
|
-
left: axis === true || axis === 'y' ? 16 : 0,
|
|
106
|
-
bottom: (axis === true || axis === 'x' ? 16 : 0) + (legend === true ? 32 : 0),
|
|
107
|
-
}}
|
|
111
|
+
padding={radial ? undefined : defaultChartPadding(axis, legend)}
|
|
108
112
|
tooltip={{ mode: 'bisect-x' }}
|
|
109
113
|
{...$$restProps}
|
|
110
114
|
let:x
|
|
@@ -133,7 +137,7 @@
|
|
|
133
137
|
getSplineProps,
|
|
134
138
|
}}
|
|
135
139
|
<slot {...slotProps}>
|
|
136
|
-
<Svg center={radial}>
|
|
140
|
+
<svelte:component this={renderContext === 'canvas' ? Canvas : Svg} center={radial}>
|
|
137
141
|
<slot name="grid" {...slotProps}>
|
|
138
142
|
{#if grid}
|
|
139
143
|
<Grid x={radial} y {...typeof grid === 'object' ? grid : null} {...props.grid} />
|
|
@@ -203,7 +207,7 @@
|
|
|
203
207
|
/>
|
|
204
208
|
{/each}
|
|
205
209
|
</slot>
|
|
206
|
-
</
|
|
210
|
+
</svelte:component>
|
|
207
211
|
|
|
208
212
|
<slot name="legend" {...slotProps}>
|
|
209
213
|
{#if legend}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { format } from '@layerstack/utils';
|
|
5
5
|
|
|
6
6
|
import Arc from '../Arc.svelte';
|
|
7
|
+
import Canvas from '../layout/Canvas.svelte';
|
|
7
8
|
import Chart from '../Chart.svelte';
|
|
8
9
|
import Group from '../Group.svelte';
|
|
9
10
|
import Legend from '../Legend.svelte';
|
|
@@ -30,6 +31,7 @@
|
|
|
30
31
|
range?: typeof range;
|
|
31
32
|
series?: typeof series;
|
|
32
33
|
value?: typeof label;
|
|
34
|
+
renderContext?: typeof renderContext;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export let data: ChartProps['data'] = [];
|
|
@@ -98,6 +100,8 @@
|
|
|
98
100
|
legend?: Partial<ComponentProps<Legend>>;
|
|
99
101
|
} = {};
|
|
100
102
|
|
|
103
|
+
export let renderContext: 'svg' | 'canvas' = 'svg';
|
|
104
|
+
|
|
101
105
|
$: allSeriesData = series
|
|
102
106
|
.flatMap((s) => s.data?.map((d) => ({ seriesKey: s.key, ...d })))
|
|
103
107
|
.filter((d) => d) as Array<TData>;
|
|
@@ -151,7 +155,7 @@
|
|
|
151
155
|
tooltip,
|
|
152
156
|
}}
|
|
153
157
|
<slot {...slotProps}>
|
|
154
|
-
<Svg {center}>
|
|
158
|
+
<svelte:component this={renderContext === 'canvas' ? Canvas : Svg} {center}>
|
|
155
159
|
<slot name="belowMarks" {...slotProps} />
|
|
156
160
|
|
|
157
161
|
<slot name="marks" {...slotProps}>
|
|
@@ -216,7 +220,7 @@
|
|
|
216
220
|
</slot>
|
|
217
221
|
|
|
218
222
|
<slot name="aboveMarks" {...slotProps} />
|
|
219
|
-
</
|
|
223
|
+
</svelte:component>
|
|
220
224
|
|
|
221
225
|
<slot name="legend" {...slotProps}>
|
|
222
226
|
{#if legend}
|
|
@@ -207,7 +207,7 @@ declare class __sveltets_Render<TData> {
|
|
|
207
207
|
tickValues?: any[] | undefined | undefined;
|
|
208
208
|
tickFontSize?: number | undefined;
|
|
209
209
|
tickLength?: number | undefined;
|
|
210
|
-
placement?: ("center" | "
|
|
210
|
+
placement?: ("center" | "bottom" | "left" | "right" | "top" | "top-left" | "top-right" | "bottom-left" | "bottom-right") | undefined;
|
|
211
211
|
orientation?: "horizontal" | "vertical" | undefined;
|
|
212
212
|
onClick?: ((tick: any) => any) | undefined | undefined;
|
|
213
213
|
variant?: "ramp" | "swatches" | undefined;
|
|
@@ -244,6 +244,7 @@ declare class __sveltets_Render<TData> {
|
|
|
244
244
|
props?: Partial<ComponentProps<Arc>>;
|
|
245
245
|
}[] | undefined;
|
|
246
246
|
value?: Accessor<TData>;
|
|
247
|
+
renderContext?: "canvas" | "svg";
|
|
247
248
|
};
|
|
248
249
|
events(): {} & {
|
|
249
250
|
[evt: string]: CustomEvent<any>;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { format } from '@layerstack/utils';
|
|
5
5
|
|
|
6
6
|
import Axis from '../Axis.svelte';
|
|
7
|
+
import Canvas from '../layout/Canvas.svelte';
|
|
7
8
|
import Chart from '../Chart.svelte';
|
|
8
9
|
import Grid from '../Grid.svelte';
|
|
9
10
|
import Highlight from '../Highlight.svelte';
|
|
@@ -14,7 +15,12 @@
|
|
|
14
15
|
import Svg from '../layout/Svg.svelte';
|
|
15
16
|
import * as Tooltip from '../tooltip/index.js';
|
|
16
17
|
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
accessor,
|
|
20
|
+
chartDataArray,
|
|
21
|
+
defaultChartPadding,
|
|
22
|
+
type Accessor,
|
|
23
|
+
} from '../../utils/common.js';
|
|
18
24
|
|
|
19
25
|
interface $$Props extends ComponentProps<Chart<TData>> {
|
|
20
26
|
axis?: typeof axis;
|
|
@@ -23,6 +29,7 @@
|
|
|
23
29
|
legend?: typeof legend;
|
|
24
30
|
props?: typeof props;
|
|
25
31
|
series?: typeof series;
|
|
32
|
+
renderContext?: typeof renderContext;
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
export let data: $$Props['data'] = [];
|
|
@@ -55,6 +62,8 @@
|
|
|
55
62
|
rule?: Partial<ComponentProps<Rule>>;
|
|
56
63
|
} = {};
|
|
57
64
|
|
|
65
|
+
export let renderContext: 'svg' | 'canvas' = 'svg';
|
|
66
|
+
|
|
58
67
|
// Default xScale based on first data's `x` value
|
|
59
68
|
$: xScale =
|
|
60
69
|
$$props.xScale ??
|
|
@@ -74,7 +83,7 @@
|
|
|
74
83
|
data: s.data,
|
|
75
84
|
stroke: s.color,
|
|
76
85
|
fill: s.color,
|
|
77
|
-
|
|
86
|
+
fillOpacity: 0.3,
|
|
78
87
|
...props.points,
|
|
79
88
|
...s.props,
|
|
80
89
|
};
|
|
@@ -90,12 +99,7 @@
|
|
|
90
99
|
{y}
|
|
91
100
|
{yScale}
|
|
92
101
|
yNice
|
|
93
|
-
padding={axis
|
|
94
|
-
? undefined
|
|
95
|
-
: {
|
|
96
|
-
left: axis === true || axis === 'y' ? 16 : 0,
|
|
97
|
-
bottom: (axis === true || axis === 'x' ? 16 : 0) + (legend === true ? 32 : 0),
|
|
98
|
-
}}
|
|
102
|
+
padding={defaultChartPadding(axis, legend)}
|
|
99
103
|
tooltip={{ mode: 'voronoi' }}
|
|
100
104
|
{...$$restProps}
|
|
101
105
|
let:x
|
|
@@ -117,7 +121,7 @@
|
|
|
117
121
|
: null}
|
|
118
122
|
|
|
119
123
|
<slot {...slotProps}>
|
|
120
|
-
<Svg>
|
|
124
|
+
<svelte:component this={renderContext === 'canvas' ? Canvas : Svg}>
|
|
121
125
|
<slot name="grid" {...slotProps}>
|
|
122
126
|
{#if grid}
|
|
123
127
|
<Grid x y {...typeof grid === 'object' ? grid : null} {...props.grid} />
|
|
@@ -171,7 +175,7 @@
|
|
|
171
175
|
{...typeof labels === 'object' ? labels : null}
|
|
172
176
|
/>
|
|
173
177
|
{/if}
|
|
174
|
-
</
|
|
178
|
+
</svelte:component>
|
|
175
179
|
|
|
176
180
|
<slot name="legend" {...slotProps}>
|
|
177
181
|
{#if legend}
|
|
@@ -1,11 +1,36 @@
|
|
|
1
|
+
<script lang="ts" context="module">
|
|
2
|
+
import { getContext, onDestroy, setContext } from 'svelte';
|
|
3
|
+
|
|
4
|
+
type ComponentRender = {
|
|
5
|
+
name: string;
|
|
6
|
+
render: (ctx: CanvasRenderingContext2D) => any;
|
|
7
|
+
retainState?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type CanvasContext = {
|
|
11
|
+
/** Register component to render. Returns method to unregister on component destory */
|
|
12
|
+
register(component: ComponentRender): () => void;
|
|
13
|
+
invalidate(): void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const canvasContextKey = Symbol();
|
|
17
|
+
|
|
18
|
+
export function getCanvasContext() {
|
|
19
|
+
return getContext<CanvasContext>(canvasContextKey);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function setCanvasContext(context: CanvasContext) {
|
|
23
|
+
setContext(canvasContextKey, context);
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
1
27
|
<script lang="ts">
|
|
2
|
-
import { onMount
|
|
3
|
-
import { writable } from 'svelte/store';
|
|
4
|
-
import { scaleCanvas } from 'layercake';
|
|
28
|
+
import { onMount } from 'svelte';
|
|
5
29
|
import { cls } from '@layerstack/tailwind';
|
|
6
30
|
|
|
7
31
|
import { chartContext } from '../ChartContext.svelte';
|
|
8
32
|
import { transformContext } from '../TransformContext.svelte';
|
|
33
|
+
import { scaleCanvas } from '../../utils/canvas.js';
|
|
9
34
|
|
|
10
35
|
const { width, height, containerWidth, containerHeight, padding } = chartContext();
|
|
11
36
|
|
|
@@ -38,21 +63,44 @@
|
|
|
38
63
|
/** A string passed to `aria-describedby` property on the `<canvas>` tag. */
|
|
39
64
|
export let describedBy: string | undefined = undefined;
|
|
40
65
|
|
|
41
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Translate children to center (useful for radial layouts)
|
|
68
|
+
*/
|
|
69
|
+
export let center: boolean | 'x' | 'y' = false;
|
|
70
|
+
|
|
71
|
+
let components = new Map<Symbol, ComponentRender>();
|
|
72
|
+
let pendingInvalidation = false;
|
|
73
|
+
let frameId: number | undefined;
|
|
74
|
+
|
|
75
|
+
const { mode, scale, translate } = transformContext();
|
|
42
76
|
|
|
43
77
|
onMount(() => {
|
|
44
78
|
context = element?.getContext('2d', { willReadFrequently }) as CanvasRenderingContext2D;
|
|
45
79
|
});
|
|
46
80
|
|
|
47
|
-
|
|
81
|
+
onDestroy(() => {
|
|
82
|
+
if (frameId) {
|
|
83
|
+
cancelAnimationFrame(frameId);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
48
86
|
|
|
49
|
-
|
|
87
|
+
function update() {
|
|
88
|
+
if (!context) return;
|
|
89
|
+
// TODO: only `scaleCanvas()` when containerWidth/Height change (not all invalidations)
|
|
90
|
+
// scaleCanvas in `update()` to fix `requestAnimationFrame()` timing causing flash of blank canvas
|
|
50
91
|
scaleCanvas(context, $containerWidth, $containerHeight);
|
|
92
|
+
|
|
51
93
|
context.clearRect(0, 0, $containerWidth, $containerHeight);
|
|
52
94
|
|
|
53
95
|
context.translate($padding.left ?? 0, $padding.top ?? 0);
|
|
54
96
|
|
|
55
|
-
if (
|
|
97
|
+
if (center) {
|
|
98
|
+
const newTranslate = {
|
|
99
|
+
x: center === 'x' || center === true ? $width / 2 : 0,
|
|
100
|
+
y: center === 'y' || center === true ? $height / 2 : 0,
|
|
101
|
+
};
|
|
102
|
+
context.translate(newTranslate.x, newTranslate.y);
|
|
103
|
+
} else if (mode === 'canvas') {
|
|
56
104
|
const center = { x: $width / 2, y: $height / 2 };
|
|
57
105
|
const newTranslate = {
|
|
58
106
|
x: $translate.x * $scale + center.x - center.x * $scale,
|
|
@@ -62,12 +110,46 @@
|
|
|
62
110
|
context.scale($scale, $scale);
|
|
63
111
|
}
|
|
64
112
|
|
|
65
|
-
|
|
66
|
-
|
|
113
|
+
components.forEach((c) => {
|
|
114
|
+
if (c.retainState) {
|
|
115
|
+
// Do not call save/restore canvas draw state (https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save) (ex. Group ctx.translate() affecting children)
|
|
116
|
+
c.render(context);
|
|
117
|
+
} else {
|
|
118
|
+
context.save();
|
|
119
|
+
c.render(context);
|
|
120
|
+
context.restore();
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
pendingInvalidation = false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const canvasContext: CanvasContext = {
|
|
128
|
+
register(component) {
|
|
129
|
+
const key = Symbol();
|
|
130
|
+
components.set(key, component);
|
|
131
|
+
this.invalidate();
|
|
132
|
+
|
|
133
|
+
// Unregister
|
|
134
|
+
return () => {
|
|
135
|
+
components.delete(key);
|
|
136
|
+
this.invalidate();
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
invalidate() {
|
|
140
|
+
if (pendingInvalidation) return;
|
|
141
|
+
pendingInvalidation = true;
|
|
142
|
+
frameId = requestAnimationFrame(update);
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
$: {
|
|
147
|
+
// Redraw when resized
|
|
148
|
+
$containerWidth, $containerHeight;
|
|
149
|
+
canvasContext.invalidate();
|
|
67
150
|
}
|
|
68
151
|
|
|
69
|
-
|
|
70
|
-
setContext('canvas', { ctx });
|
|
152
|
+
setCanvasContext(canvasContext);
|
|
71
153
|
</script>
|
|
72
154
|
|
|
73
155
|
<canvas
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { SvelteComponentTyped } from "svelte";
|
|
2
|
+
type ComponentRender = {
|
|
3
|
+
name: string;
|
|
4
|
+
render: (ctx: CanvasRenderingContext2D) => any;
|
|
5
|
+
retainState?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type CanvasContext = {
|
|
8
|
+
/** Register component to render. Returns method to unregister on component destory */
|
|
9
|
+
register(component: ComponentRender): () => void;
|
|
10
|
+
invalidate(): void;
|
|
11
|
+
};
|
|
12
|
+
export declare const canvasContextKey: unique symbol;
|
|
13
|
+
export declare function getCanvasContext(): CanvasContext;
|
|
2
14
|
declare const __propDef: {
|
|
3
15
|
props: {
|
|
4
16
|
[x: string]: any;
|
|
@@ -11,6 +23,7 @@ declare const __propDef: {
|
|
|
11
23
|
label?: string | undefined | undefined;
|
|
12
24
|
labelledBy?: string | undefined | undefined;
|
|
13
25
|
describedBy?: string | undefined | undefined;
|
|
26
|
+
center?: boolean | "x" | "y" | undefined;
|
|
14
27
|
};
|
|
15
28
|
events: {
|
|
16
29
|
pointerenter: PointerEvent;
|
|
@@ -6,7 +6,7 @@ declare const __propDef: {
|
|
|
6
6
|
y?: "pointer" | "data" | number | undefined | undefined;
|
|
7
7
|
xOffset?: number | undefined;
|
|
8
8
|
yOffset?: number | undefined;
|
|
9
|
-
anchor?: ("center" | "
|
|
9
|
+
anchor?: ("center" | "bottom" | "left" | "right" | "top" | "top-left" | "top-right" | "bottom-left" | "bottom-right") | undefined;
|
|
10
10
|
contained?: "container" | false | undefined;
|
|
11
11
|
variant?: "default" | "invert" | "none" | undefined;
|
|
12
12
|
motion?: boolean | undefined;
|
|
@@ -0,0 +1,46 @@
|
|
|
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 {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
export const DEFAULT_FILL = 'rgb(0, 0, 0)';
|
|
2
|
+
const CANVAS_STYLES_ELEMENT_ID = '__layerchart_canvas_styles_id';
|
|
3
|
+
/**
|
|
4
|
+
* Appends or reuses `<svg>` element below `<canvas>` to resolve CSS variables and classes (ex. `stroke: hsl(var(--color-primary))` => `stroke: rgb(...)` )
|
|
5
|
+
*/
|
|
6
|
+
export function getComputedStyles(canvas, { styles, classes } = {}) {
|
|
7
|
+
try {
|
|
8
|
+
// Get or create `<svg>` below `<canvas>`
|
|
9
|
+
let svg = document.getElementById(CANVAS_STYLES_ELEMENT_ID);
|
|
10
|
+
if (!svg) {
|
|
11
|
+
svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
12
|
+
svg.setAttribute('id', CANVAS_STYLES_ELEMENT_ID);
|
|
13
|
+
svg.style.display = 'none';
|
|
14
|
+
// Add `<svg>` next to `<canvas>` to allow same scope resolution for CSS variables
|
|
15
|
+
canvas.after(svg);
|
|
16
|
+
}
|
|
17
|
+
svg = svg; // guarantee SVG is set
|
|
18
|
+
// Remove any previously set styles or classes. Not able to do as part of cleanup below as `window.getComputedStyles()` appearing to be lazily read and removing `style` results in incorrect values, and copying result is very slow
|
|
19
|
+
svg.removeAttribute('style');
|
|
20
|
+
svg.removeAttribute('class');
|
|
21
|
+
// Add styles and class to svg element
|
|
22
|
+
if (styles) {
|
|
23
|
+
Object.assign(svg.style, styles);
|
|
24
|
+
}
|
|
25
|
+
if (classes) {
|
|
26
|
+
svg.setAttribute('class', classes);
|
|
27
|
+
}
|
|
28
|
+
const computedStyles = window.getComputedStyle(svg);
|
|
29
|
+
return computedStyles;
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
console.error('Unable to get computed styles', e);
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Render onto canvas context. Supports CSS variables and classes by tranferring to hidden `<svg>` element before retrieval) */
|
|
37
|
+
function render(canvasCtx, render, styleOptions = {}) {
|
|
38
|
+
// TODO: Consider memoizing? How about reactiving to CSS variable changes (light/dark mode toggle)
|
|
39
|
+
const computedStyles = getComputedStyles(canvasCtx.canvas, styleOptions);
|
|
40
|
+
// Adhere to CSS paint order: https://developer.mozilla.org/en-US/docs/Web/CSS/paint-order
|
|
41
|
+
const paintOrder = computedStyles?.paintOrder === 'stroke' ? ['stroke', 'fill'] : ['fill', 'stroke'];
|
|
42
|
+
if (computedStyles?.opacity) {
|
|
43
|
+
canvasCtx.globalAlpha = Number(computedStyles?.opacity);
|
|
44
|
+
}
|
|
45
|
+
// Text properties
|
|
46
|
+
canvasCtx.font = `${computedStyles.fontSize} ${computedStyles.fontFamily}`; // build string instead of using `computedStyles.font` to fix/workaround `tabular-nums` returning `null`
|
|
47
|
+
// TODO: Hack to handle `textAnchor` with canvas. Try to find a better approach
|
|
48
|
+
if (computedStyles.textAnchor === 'middle') {
|
|
49
|
+
canvasCtx.textAlign = 'center';
|
|
50
|
+
}
|
|
51
|
+
else if (computedStyles.textAnchor === 'end') {
|
|
52
|
+
canvasCtx.textAlign = 'right';
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
canvasCtx.textAlign = computedStyles.textAlign; // TODO: Handle/map `justify` and `match-parent`?
|
|
56
|
+
}
|
|
57
|
+
// TODO: Handle `textBaseline` / `verticalAnchor` (Text)
|
|
58
|
+
// canvasCtx.textBaseline = 'top';
|
|
59
|
+
// canvasCtx.textBaseline = 'middle';
|
|
60
|
+
// canvasCtx.textBaseline = 'bottom';
|
|
61
|
+
// canvasCtx.textBaseline = 'alphabetic';
|
|
62
|
+
// canvasCtx.textBaseline = 'hanging';
|
|
63
|
+
// canvasCtx.textBaseline = 'ideographic';
|
|
64
|
+
// Dashed lines
|
|
65
|
+
if (computedStyles.strokeDasharray.includes(',')) {
|
|
66
|
+
const dashArray = computedStyles.strokeDasharray
|
|
67
|
+
.split(',')
|
|
68
|
+
.map((s) => Number(s.replace('px', '')));
|
|
69
|
+
canvasCtx.setLineDash(dashArray);
|
|
70
|
+
}
|
|
71
|
+
paintOrder.forEach((attr) => {
|
|
72
|
+
if (attr === 'fill') {
|
|
73
|
+
const fill = styleOptions.styles?.fill instanceof CanvasGradient
|
|
74
|
+
? styleOptions.styles?.fill
|
|
75
|
+
: ['none', DEFAULT_FILL].includes(computedStyles?.fill)
|
|
76
|
+
? null
|
|
77
|
+
: computedStyles?.fill;
|
|
78
|
+
if (fill) {
|
|
79
|
+
const currentGlobalAlpha = canvasCtx.globalAlpha;
|
|
80
|
+
if (computedStyles?.fillOpacity) {
|
|
81
|
+
canvasCtx.globalAlpha = Number(computedStyles?.fillOpacity);
|
|
82
|
+
}
|
|
83
|
+
canvasCtx.fillStyle = fill;
|
|
84
|
+
render.fill(canvasCtx);
|
|
85
|
+
// Restore in case it was modified by `fillOpacity`
|
|
86
|
+
canvasCtx.globalAlpha = currentGlobalAlpha;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (attr === 'stroke') {
|
|
90
|
+
const stroke = styleOptions.styles?.stroke instanceof CanvasGradient
|
|
91
|
+
? styleOptions.styles?.stroke
|
|
92
|
+
: computedStyles?.stroke === 'none'
|
|
93
|
+
? null
|
|
94
|
+
: computedStyles?.stroke;
|
|
95
|
+
if (stroke) {
|
|
96
|
+
canvasCtx.lineWidth =
|
|
97
|
+
typeof computedStyles?.strokeWidth === 'string'
|
|
98
|
+
? Number(computedStyles?.strokeWidth?.replace('px', ''))
|
|
99
|
+
: (computedStyles?.strokeWidth ?? 1);
|
|
100
|
+
canvasCtx.strokeStyle = stroke;
|
|
101
|
+
render.stroke(canvasCtx);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Render SVG path data onto canvas context. Supports CSS variables and classes by tranferring to hidden `<svg>` element before retrieval) */
|
|
107
|
+
export function renderPathData(canvasCtx, pathData, styleOptions = {}) {
|
|
108
|
+
const path = new Path2D(pathData ?? '');
|
|
109
|
+
render(canvasCtx, {
|
|
110
|
+
fill: (ctx) => ctx.fill(path),
|
|
111
|
+
stroke: (ctx) => ctx.stroke(path),
|
|
112
|
+
}, styleOptions);
|
|
113
|
+
}
|
|
114
|
+
export function renderText(canvasCtx, text, coords, styleOptions = {}) {
|
|
115
|
+
if (text) {
|
|
116
|
+
render(canvasCtx, {
|
|
117
|
+
fill: (ctx) => ctx.fillText(text.toString(), coords.x, coords.y),
|
|
118
|
+
stroke: (ctx) => ctx.strokeText(text.toString(), coords.x, coords.y),
|
|
119
|
+
}, styleOptions);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export function renderRect(canvasCtx, coords, styleOptions = {}) {
|
|
123
|
+
render(canvasCtx, {
|
|
124
|
+
fill: (ctx) => ctx.fillRect(coords.x, coords.y, coords.width, coords.height),
|
|
125
|
+
stroke: (ctx) => ctx.strokeRect(coords.x, coords.y, coords.width, coords.height),
|
|
126
|
+
}, styleOptions);
|
|
127
|
+
}
|
|
128
|
+
/** Clear canvas accounting for Canvas `context.translate(...)` */
|
|
129
|
+
export function clearCanvasContext(canvasCtx, options) {
|
|
130
|
+
// Clear with negative offset due to Canvas `context.translate(...)`
|
|
131
|
+
canvasCtx.clearRect(-options.padding.left, -options.padding.top, options.containerWidth, options.containerHeight);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
Scales a canvas for high DPI / retina displays.
|
|
135
|
+
@see: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#examples
|
|
136
|
+
@see: https://web.dev/articles/canvas-hidipi
|
|
137
|
+
*/
|
|
138
|
+
export function scaleCanvas(ctx, width, height) {
|
|
139
|
+
const devicePixelRatio = window.devicePixelRatio || 1;
|
|
140
|
+
ctx.canvas.width = width * devicePixelRatio;
|
|
141
|
+
ctx.canvas.height = height * devicePixelRatio;
|
|
142
|
+
ctx.canvas.style.width = `${width}px`;
|
|
143
|
+
ctx.canvas.style.height = `${height}px`;
|
|
144
|
+
ctx.scale(devicePixelRatio, devicePixelRatio);
|
|
145
|
+
return { width: ctx.canvas.width, height: ctx.canvas.height };
|
|
146
|
+
}
|
package/dist/utils/common.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import type Chart from '../components/Chart.svelte';
|
|
2
1
|
import type { ComponentProps } from 'svelte';
|
|
2
|
+
import type Chart from '../components/Chart.svelte';
|
|
3
|
+
import type LineChart from '../components/charts/LineChart.svelte';
|
|
3
4
|
export type Accessor<TData = any> = number | string | ((d: TData) => any) | undefined | null | Accessor<TData>[];
|
|
4
5
|
export declare function accessor<TData = any>(prop: Accessor<TData>): (d: TData) => any;
|
|
5
6
|
/** Guarantee chart data is an array */
|
|
6
7
|
export declare function chartDataArray<TData = any>(data: ComponentProps<Chart<TData>>['data']): any[];
|
|
8
|
+
type SimplifiedChartProps = ComponentProps<LineChart<any>>;
|
|
9
|
+
export declare function defaultChartPadding(axis: SimplifiedChartProps['axis'], legend: SimplifiedChartProps['legend']): {
|
|
10
|
+
top: number;
|
|
11
|
+
left: number;
|
|
12
|
+
bottom: number;
|
|
13
|
+
right: number;
|
|
14
|
+
} | undefined;
|
|
15
|
+
export {};
|