layerchart 2.0.0-next.47 → 2.0.0-next.49
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/PrimitiveBench.svelte +66 -0
- package/dist/bench/PrimitiveBench.svelte.d.ts +10 -0
- package/dist/bench/primitives.svelte.bench.d.ts +1 -0
- package/dist/bench/primitives.svelte.bench.js +42 -0
- package/dist/components/Axis.svelte +14 -3
- package/dist/components/Axis.svelte.d.ts +1 -1
- package/dist/components/Chart.svelte +110 -12
- package/dist/components/Circle.svelte +20 -17
- package/dist/components/Contour.svelte +90 -13
- package/dist/components/Contour.svelte.d.ts +8 -0
- package/dist/components/Ellipse.svelte +18 -16
- package/dist/components/GeoPath.svelte +1 -1
- package/dist/components/Group.svelte +14 -12
- package/dist/components/Image.svelte +18 -16
- package/dist/components/Labels.svelte +56 -11
- package/dist/components/Labels.svelte.d.ts +3 -2
- package/dist/components/Line.svelte +18 -16
- package/dist/components/LinearGradient.svelte +1 -1
- package/dist/components/Marker.svelte +8 -3
- package/dist/components/Marker.svelte.d.ts +1 -1
- package/dist/components/Month.svelte +273 -0
- package/dist/components/Month.svelte.d.ts +70 -0
- package/dist/components/Path.svelte +28 -12
- package/dist/components/Polygon.svelte +25 -23
- package/dist/components/RadialGradient.svelte +1 -1
- package/dist/components/Raster.svelte +117 -29
- package/dist/components/Raster.svelte.d.ts +8 -0
- package/dist/components/Rect.svelte +26 -20
- package/dist/components/Spline.svelte +123 -25
- package/dist/components/Spline.svelte.d.ts +18 -1
- package/dist/components/Text.svelte +45 -20
- package/dist/components/Text.svelte.d.ts +6 -0
- package/dist/components/TransformContext.svelte +8 -0
- package/dist/components/TransformContext.svelte.test.d.ts +1 -0
- package/dist/components/TransformContext.svelte.test.js +166 -0
- package/dist/components/Vector.svelte +14 -12
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +2 -0
- package/dist/components/tests/TransformTestHarness.svelte +27 -0
- package/dist/components/tests/TransformTestHarness.svelte.d.ts +8 -0
- package/dist/states/__fixtures__/ComponentNodeLifecycleChild.svelte +1 -1
- package/dist/states/__screenshots__/chart.component-node.svelte.test.ts/ChartState-registerComponent-cleans-up-child-nodes-and-mark-registrations-when-components-unmount-1.png +0 -0
- package/dist/states/__screenshots__/chart.component-node.svelte.test.ts/ChartState-registerComponent-cleans-up-child-nodes-and-mark-registrations-when-components-unmount-2.png +0 -0
- package/dist/states/brush.svelte.d.ts +26 -17
- package/dist/states/brush.svelte.js +118 -25
- package/dist/states/brush.svelte.test.js +126 -1
- package/dist/states/chart.svelte.d.ts +6 -0
- package/dist/states/chart.svelte.js +100 -21
- package/dist/states/chart.svelte.test.js +16 -1
- package/dist/states/transform.svelte.js +3 -1
- package/dist/utils/dataProp.d.ts +2 -10
- package/dist/utils/dataProp.js +16 -5
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/motion.svelte.d.ts +12 -2
- package/dist/utils/motion.svelte.js +22 -0
- package/dist/utils/motion.test.js +49 -1
- package/dist/utils/rasterBounds.d.ts +18 -0
- package/dist/utils/rasterBounds.js +98 -0
- package/dist/utils/rasterBounds.test.d.ts +1 -0
- package/dist/utils/rasterBounds.test.js +63 -0
- package/dist/utils/scales.svelte.js +4 -2
- package/dist/utils/scales.svelte.test.d.ts +1 -0
- package/dist/utils/scales.svelte.test.js +67 -0
- package/dist/utils/ticks.js +7 -3
- package/dist/utils/ticks.test.js +13 -3
- package/package.json +3 -2
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export type MonthCell = {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
color: any;
|
|
5
|
+
data: any;
|
|
6
|
+
date: Date;
|
|
7
|
+
};
|
|
8
|
+
export type MonthPropsWithoutHTML = {
|
|
9
|
+
/**
|
|
10
|
+
* The start date of the calendar.
|
|
11
|
+
*/
|
|
12
|
+
start: Date;
|
|
13
|
+
/**
|
|
14
|
+
* The end date of the calendar.
|
|
15
|
+
*/
|
|
16
|
+
end: Date;
|
|
17
|
+
/**
|
|
18
|
+
* Size of the cell in the calendar.
|
|
19
|
+
*
|
|
20
|
+
* @default 25
|
|
21
|
+
*/
|
|
22
|
+
cellSize?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Number of months to display per row. If undefined, automatically calculated based on available width.
|
|
25
|
+
*/
|
|
26
|
+
monthsPerRow?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Padding multiplier between months (relative to cellSize).
|
|
29
|
+
*
|
|
30
|
+
* @default 1.2
|
|
31
|
+
*/
|
|
32
|
+
monthPadding?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Vertical spacing multiplier between month rows (in number of cell heights).
|
|
35
|
+
*
|
|
36
|
+
* @default 8
|
|
37
|
+
*/
|
|
38
|
+
rowSpacing?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Whether to show the day number in each cell.
|
|
41
|
+
*
|
|
42
|
+
* @default true
|
|
43
|
+
*/
|
|
44
|
+
showDayNumber?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Props to pass to the `<text>` element for month labels.
|
|
47
|
+
*/
|
|
48
|
+
monthLabel?: boolean | Partial<ComponentProps<typeof Text>>;
|
|
49
|
+
/**
|
|
50
|
+
* Props to pass to the `<text>` element for day numbers.
|
|
51
|
+
*/
|
|
52
|
+
dayNumberProps?: Partial<ComponentProps<typeof Text>>;
|
|
53
|
+
/**
|
|
54
|
+
* Setup pointer events to show tooltip for related data
|
|
55
|
+
*/
|
|
56
|
+
tooltip?: boolean;
|
|
57
|
+
children?: Snippet<[{
|
|
58
|
+
cells: MonthCell[];
|
|
59
|
+
cellSize: number;
|
|
60
|
+
}]>;
|
|
61
|
+
} & Omit<RectPropsWithoutHTML, 'children' | 'x' | 'y' | 'width' | 'height' | 'fill' | 'onpointermove' | 'onpointerleave'>;
|
|
62
|
+
export type MonthProps = MonthPropsWithoutHTML & Without<SVGAttributes<SVGRectElement>, MonthPropsWithoutHTML>;
|
|
63
|
+
import { type ComponentProps, type Snippet } from 'svelte';
|
|
64
|
+
import { type RectPropsWithoutHTML } from './Rect.svelte';
|
|
65
|
+
import Text from './Text.svelte';
|
|
66
|
+
import type { SVGAttributes } from 'svelte/elements';
|
|
67
|
+
import type { Without } from '../utils/types.js';
|
|
68
|
+
declare const Month: import("svelte").Component<MonthPropsWithoutHTML, {}, "">;
|
|
69
|
+
type Month = ReturnType<typeof Month>;
|
|
70
|
+
export default Month;
|
|
@@ -118,6 +118,14 @@
|
|
|
118
118
|
endContent,
|
|
119
119
|
opacity,
|
|
120
120
|
pathRef: pathRefProp = $bindable(),
|
|
121
|
+
onclick,
|
|
122
|
+
onpointerenter,
|
|
123
|
+
onpointermove,
|
|
124
|
+
onpointerleave,
|
|
125
|
+
onpointerdown,
|
|
126
|
+
onpointerover,
|
|
127
|
+
onpointerout,
|
|
128
|
+
ontouchmove,
|
|
121
129
|
...restProps
|
|
122
130
|
}: PathProps = $props();
|
|
123
131
|
|
|
@@ -185,26 +193,26 @@
|
|
|
185
193
|
}
|
|
186
194
|
|
|
187
195
|
// TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
|
|
188
|
-
const fillKey = createKey(() => fill);
|
|
189
|
-
const strokeKey = createKey(() => stroke);
|
|
196
|
+
const fillKey = layerCtx === 'canvas' ? createKey(() => fill) : undefined;
|
|
197
|
+
const strokeKey = layerCtx === 'canvas' ? createKey(() => stroke) : undefined;
|
|
190
198
|
|
|
191
199
|
if (layerCtx === 'canvas') {
|
|
192
200
|
ctx.registerComponent({ name: 'Path', kind: 'mark', canvasRender: {
|
|
193
201
|
render,
|
|
194
202
|
events: {
|
|
195
|
-
click
|
|
196
|
-
pointerenter
|
|
197
|
-
pointermove
|
|
198
|
-
pointerleave
|
|
199
|
-
pointerdown
|
|
200
|
-
pointerover
|
|
201
|
-
pointerout
|
|
202
|
-
touchmove
|
|
203
|
+
get click() { return onclick; },
|
|
204
|
+
get pointerenter() { return onpointerenter; },
|
|
205
|
+
get pointermove() { return onpointermove; },
|
|
206
|
+
get pointerleave() { return onpointerleave; },
|
|
207
|
+
get pointerdown() { return onpointerdown; },
|
|
208
|
+
get pointerover() { return onpointerover; },
|
|
209
|
+
get pointerout() { return onpointerout; },
|
|
210
|
+
get touchmove() { return ontouchmove; },
|
|
203
211
|
},
|
|
204
212
|
deps: () => [
|
|
205
|
-
fillKey
|
|
213
|
+
fillKey!.current,
|
|
206
214
|
fillOpacity,
|
|
207
|
-
strokeKey
|
|
215
|
+
strokeKey!.current,
|
|
208
216
|
strokeOpacity,
|
|
209
217
|
strokeWidth,
|
|
210
218
|
opacity,
|
|
@@ -275,6 +283,14 @@
|
|
|
275
283
|
<path
|
|
276
284
|
d={tweenedState.current}
|
|
277
285
|
{...restProps}
|
|
286
|
+
{onclick}
|
|
287
|
+
{onpointerenter}
|
|
288
|
+
{onpointermove}
|
|
289
|
+
{onpointerleave}
|
|
290
|
+
{onpointerdown}
|
|
291
|
+
{onpointerover}
|
|
292
|
+
{onpointerout}
|
|
293
|
+
{ontouchmove}
|
|
278
294
|
class={cls('lc-path', className)}
|
|
279
295
|
{fill}
|
|
280
296
|
fill-opacity={fillOpacity}
|
|
@@ -257,26 +257,28 @@
|
|
|
257
257
|
// --- Data mode motion ---
|
|
258
258
|
const dataMotionMap = createDataMotionMap(motion);
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
260
|
+
if (dataMotionMap) {
|
|
261
|
+
$effect(() => {
|
|
262
|
+
if (!dataMode) return;
|
|
263
|
+
const activeKeys = new Set<any>();
|
|
264
|
+
for (let i = 0; i < resolvedData.length; i++) {
|
|
265
|
+
const d = resolvedData[i];
|
|
266
|
+
const key = keyFn(d, i);
|
|
267
|
+
activeKeys.add(key);
|
|
268
|
+
// Polygon resolve returns a path string, so resolve coords separately for motion
|
|
269
|
+
let resolvedCx: number, resolvedCy: number;
|
|
270
|
+
if (geo.projection) {
|
|
271
|
+
[resolvedCx, resolvedCy] = resolveGeoDataPair(cx, cy, d, geo.projection);
|
|
272
|
+
} else {
|
|
273
|
+
resolvedCx = resolveDataProp(cx, d, chartCtx.xScale, 0);
|
|
274
|
+
resolvedCy = resolveDataProp(cy, d, chartCtx.yScale, 0);
|
|
275
|
+
}
|
|
276
|
+
const resolvedR = resolveDataProp(r, d, chartCtx.rScale, typeof r === 'number' ? r : 1);
|
|
277
|
+
untrack(() => dataMotionMap.update(key, { cx: resolvedCx, cy: resolvedCy, r: resolvedR }));
|
|
274
278
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
untrack(() => dataMotionMap.cleanup(activeKeys));
|
|
279
|
-
});
|
|
279
|
+
untrack(() => dataMotionMap.cleanup(activeKeys));
|
|
280
|
+
});
|
|
281
|
+
}
|
|
280
282
|
|
|
281
283
|
// TODO: Apply animated values from dataMotionMap to SVG/HTML/Canvas templates.
|
|
282
284
|
// Polygon uses a path string from resolvePolygon(), so animated cx/cy/r values
|
|
@@ -395,8 +397,8 @@
|
|
|
395
397
|
}
|
|
396
398
|
|
|
397
399
|
// TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
|
|
398
|
-
const fillKey = createKey(() => fill);
|
|
399
|
-
const strokeKey = createKey(() => stroke);
|
|
400
|
+
const fillKey = layerCtx === 'canvas' ? createKey(() => fill) : undefined;
|
|
401
|
+
const strokeKey = layerCtx === 'canvas' ? createKey(() => stroke) : undefined;
|
|
400
402
|
|
|
401
403
|
chartCtx.registerComponent({
|
|
402
404
|
name: 'Polygon',
|
|
@@ -425,9 +427,9 @@
|
|
|
425
427
|
deps: () => [
|
|
426
428
|
dataMode,
|
|
427
429
|
dataMode ? resolvedItems : null,
|
|
428
|
-
fillKey
|
|
430
|
+
fillKey!.current,
|
|
429
431
|
fillOpacity,
|
|
430
|
-
strokeKey
|
|
432
|
+
strokeKey!.current,
|
|
431
433
|
strokeWidth,
|
|
432
434
|
opacity,
|
|
433
435
|
className,
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
if (layerCtx === 'canvas') {
|
|
147
|
-
ctx.registerComponent({ name: 'Gradient', kind: '
|
|
147
|
+
ctx.registerComponent({ name: 'Gradient', kind: 'group', canvasRender: {
|
|
148
148
|
render,
|
|
149
149
|
deps: () => [stops, cx, cy, fx, fy, ctx.width, ctx.height],
|
|
150
150
|
} });
|
|
@@ -17,6 +17,15 @@
|
|
|
17
17
|
/** Grid height (rows). When set, `data` is treated as a flat grid array. */
|
|
18
18
|
height?: number;
|
|
19
19
|
|
|
20
|
+
/** Left bound of the raster in data coordinates. Defaults to `0` in grid mode. */
|
|
21
|
+
x1?: number;
|
|
22
|
+
/** Top bound of the raster in data coordinates. Defaults to `0` in grid mode. */
|
|
23
|
+
y1?: number;
|
|
24
|
+
/** Right bound of the raster in data coordinates. Defaults to `width` in grid mode. */
|
|
25
|
+
x2?: number;
|
|
26
|
+
/** Bottom bound of the raster in data coordinates. Defaults to `height` in grid mode. */
|
|
27
|
+
y2?: number;
|
|
28
|
+
|
|
20
29
|
/**
|
|
21
30
|
* Value channel. Interpretation depends on the type:
|
|
22
31
|
* - `(x, y) => number`: continuous function evaluated at each pixel (function sampling mode)
|
|
@@ -50,20 +59,27 @@
|
|
|
50
59
|
import { cls } from '@layerstack/tailwind';
|
|
51
60
|
import { scaleSequential } from 'd3-scale';
|
|
52
61
|
import { interpolateYlGnBu } from 'd3-scale-chromatic';
|
|
53
|
-
import { max, min
|
|
62
|
+
import { max, min } from 'd3-array';
|
|
54
63
|
import { rgb } from 'd3-color';
|
|
55
64
|
|
|
56
65
|
import { accessor as resolveAccessor, chartDataArray } from '../utils/common.js';
|
|
57
66
|
import { getChartContext } from '../contexts/chart.js';
|
|
67
|
+
import { getGeoContext } from '../contexts/geo.js';
|
|
68
|
+
import { gridCellCenterToBounds, resolveRasterBounds } from '../utils/index.js';
|
|
58
69
|
import { interpolateGrid } from '../utils/rasterInterpolate.js';
|
|
59
70
|
import Image from './Image.svelte';
|
|
60
71
|
|
|
61
72
|
const ctx = getChartContext();
|
|
73
|
+
const geo = getGeoContext();
|
|
62
74
|
|
|
63
75
|
let {
|
|
64
76
|
data: dataProp,
|
|
65
77
|
width: widthProp,
|
|
66
78
|
height: heightProp,
|
|
79
|
+
x1: x1Prop,
|
|
80
|
+
y1: y1Prop,
|
|
81
|
+
x2: x2Prop,
|
|
82
|
+
y2: y2Prop,
|
|
67
83
|
value: valueProp,
|
|
68
84
|
x: xProp,
|
|
69
85
|
y: yProp,
|
|
@@ -78,6 +94,15 @@
|
|
|
78
94
|
|
|
79
95
|
// Detect grid mode: data + width/height
|
|
80
96
|
const isGridMode = $derived(!!(dataProp && widthProp && heightProp));
|
|
97
|
+
const hasExplicitBounds = $derived(
|
|
98
|
+
x1Prop !== undefined || y1Prop !== undefined || x2Prop !== undefined || y2Prop !== undefined
|
|
99
|
+
);
|
|
100
|
+
const gridBounds = $derived(
|
|
101
|
+
resolveRasterBounds(widthProp ?? 0, heightProp ?? 0, x1Prop, y1Prop, x2Prop, y2Prop)
|
|
102
|
+
);
|
|
103
|
+
const useProjectedGridSampling = $derived(
|
|
104
|
+
!!(geo.projection && geo.projection.invert && isGridMode && hasExplicitBounds)
|
|
105
|
+
);
|
|
81
106
|
|
|
82
107
|
// Register as composite-mark with markInfo for grid mode domain participation
|
|
83
108
|
ctx.registerComponent({
|
|
@@ -87,8 +112,8 @@
|
|
|
87
112
|
if (!isGridMode) return {};
|
|
88
113
|
return {
|
|
89
114
|
data: [
|
|
90
|
-
{ x:
|
|
91
|
-
{ x:
|
|
115
|
+
{ x: gridBounds.x1, y: gridBounds.y1 },
|
|
116
|
+
{ x: gridBounds.x2, y: gridBounds.y2 },
|
|
92
117
|
],
|
|
93
118
|
x: 'x',
|
|
94
119
|
y: 'y',
|
|
@@ -97,26 +122,23 @@
|
|
|
97
122
|
});
|
|
98
123
|
|
|
99
124
|
// Grid dimensions (accounting for pixelSize downsampling)
|
|
100
|
-
const gridW = $derived(
|
|
101
|
-
|
|
102
|
-
);
|
|
103
|
-
const gridH = $derived(
|
|
104
|
-
heightProp ?? Math.max(1, Math.ceil(ctx.height / pixelSize))
|
|
105
|
-
);
|
|
125
|
+
const gridW = $derived(widthProp ?? Math.max(1, Math.ceil(ctx.width / pixelSize)));
|
|
126
|
+
const gridH = $derived(heightProp ?? Math.max(1, Math.ceil(ctx.height / pixelSize)));
|
|
106
127
|
|
|
107
128
|
// Scale factors from grid to chart pixel coordinates
|
|
108
129
|
const scaleX = $derived(ctx.width / gridW);
|
|
109
130
|
const scaleY = $derived(ctx.height / gridH);
|
|
110
131
|
|
|
132
|
+
const rasterScaleX = $derived(gridW / ctx.width);
|
|
133
|
+
const rasterScaleY = $derived(gridH / ctx.height);
|
|
134
|
+
|
|
111
135
|
// Resolve grid values
|
|
112
|
-
const
|
|
136
|
+
const sourceGridValues = $derived.by(() => {
|
|
113
137
|
if (!ctx.width || !ctx.height) return new Float64Array(0);
|
|
114
138
|
|
|
115
139
|
// Mode 1: Grid data (flat array + width/height)
|
|
116
140
|
if (isGridMode) {
|
|
117
|
-
return dataProp instanceof Float64Array
|
|
118
|
-
? dataProp
|
|
119
|
-
: Float64Array.from(dataProp as number[]);
|
|
141
|
+
return dataProp instanceof Float64Array ? dataProp : Float64Array.from(dataProp as number[]);
|
|
120
142
|
}
|
|
121
143
|
|
|
122
144
|
// Mode 2: Continuous function
|
|
@@ -143,7 +165,7 @@
|
|
|
143
165
|
|
|
144
166
|
const xAcc = xProp ? resolveAccessor(xProp) : ctx.x;
|
|
145
167
|
const yAcc = yProp ? resolveAccessor(yProp) : ctx.y;
|
|
146
|
-
const valAcc = resolveAccessor(valueProp ?? 'value');
|
|
168
|
+
const valAcc = resolveAccessor((valueProp ?? 'value') as Accessor);
|
|
147
169
|
|
|
148
170
|
const points: [number, number, number][] = chartData.map((d: any) => [
|
|
149
171
|
ctx.xScale(xAcc(d)) / scaleX,
|
|
@@ -154,21 +176,43 @@
|
|
|
154
176
|
return interpolateGrid(points, gridW, gridH, interpolateMethod);
|
|
155
177
|
});
|
|
156
178
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
179
|
+
const projectedGridPoints = $derived.by(() => {
|
|
180
|
+
if (!useProjectedGridSampling || !widthProp || !heightProp || !geo.projection) return [];
|
|
181
|
+
|
|
182
|
+
const points: [number, number, number][] = [];
|
|
183
|
+
for (let row = 0; row < heightProp; row++) {
|
|
184
|
+
for (let column = 0; column < widthProp; column++) {
|
|
185
|
+
const value = sourceGridValues[row * widthProp + column];
|
|
186
|
+
if (!Number.isFinite(value)) continue;
|
|
187
|
+
|
|
188
|
+
const point = gridCellCenterToBounds(column, row, widthProp, heightProp, gridBounds);
|
|
189
|
+
const projected = geo.projection([point.x, point.y]);
|
|
190
|
+
if (!projected || !Number.isFinite(projected[0]) || !Number.isFinite(projected[1]))
|
|
191
|
+
continue;
|
|
192
|
+
|
|
193
|
+
points.push([projected[0] * rasterScaleX, projected[1] * rasterScaleY, value]);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return points;
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const rasterValues = $derived.by(() => {
|
|
201
|
+
if (useProjectedGridSampling) {
|
|
202
|
+
return interpolateGrid(projectedGridPoints, gridW, gridH, interpolateMethod);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return sourceGridValues;
|
|
163
206
|
});
|
|
164
207
|
|
|
165
208
|
// Color scale: use chart's cScale (with auto-computed domain) or fall back to default
|
|
166
209
|
const colorScale = $derived.by(() => {
|
|
167
|
-
const validValues =
|
|
210
|
+
const validValues = rasterValues.filter((v) => !isNaN(v));
|
|
168
211
|
const minValue = min(validValues) ?? 0;
|
|
169
212
|
const maxValue = max(validValues) ?? 1;
|
|
170
213
|
if (ctx.cScale) {
|
|
171
|
-
|
|
214
|
+
const scale = ctx.cScale.copy();
|
|
215
|
+
return ctx.props.cDomain ? scale : scale.domain([minValue, maxValue]);
|
|
172
216
|
}
|
|
173
217
|
return scaleSequential([minValue, maxValue], interpolateYlGnBu);
|
|
174
218
|
});
|
|
@@ -191,23 +235,56 @@
|
|
|
191
235
|
return lut;
|
|
192
236
|
});
|
|
193
237
|
|
|
238
|
+
const imagePlacement = $derived.by(() => {
|
|
239
|
+
if (useProjectedGridSampling) {
|
|
240
|
+
return {
|
|
241
|
+
x: ctx.width / 2,
|
|
242
|
+
y: ctx.height / 2,
|
|
243
|
+
width: ctx.width,
|
|
244
|
+
height: ctx.height,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (isGridMode && hasExplicitBounds) {
|
|
249
|
+
const x1 = ctx.xScale(gridBounds.x1);
|
|
250
|
+
const x2 = ctx.xScale(gridBounds.x2);
|
|
251
|
+
const y1 = ctx.yScale(gridBounds.y1);
|
|
252
|
+
const y2 = ctx.yScale(gridBounds.y2);
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
x: (x1 + x2) / 2,
|
|
256
|
+
y: (y1 + y2) / 2,
|
|
257
|
+
width: Math.abs(x2 - x1),
|
|
258
|
+
height: Math.abs(y2 - y1),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
x: ctx.width / 2,
|
|
264
|
+
y: ctx.height / 2,
|
|
265
|
+
width: ctx.width,
|
|
266
|
+
height: ctx.height,
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
|
|
194
270
|
// Generate image data URL via offscreen canvas
|
|
195
271
|
const imageDataUrl = $derived.by(() => {
|
|
196
272
|
if (typeof document === 'undefined') return '';
|
|
197
|
-
if (
|
|
273
|
+
if (rasterValues.length === 0 || gridW <= 0 || gridH <= 0) return '';
|
|
198
274
|
|
|
199
275
|
const [minValue, maxValue] = colorScale.domain();
|
|
200
276
|
const range = maxValue - minValue || 1;
|
|
201
277
|
|
|
202
278
|
const canvas = document.createElement('canvas');
|
|
279
|
+
|
|
203
280
|
canvas.width = gridW;
|
|
204
281
|
canvas.height = gridH;
|
|
205
282
|
const canvasCtx = canvas.getContext('2d')!;
|
|
206
283
|
const imageData = canvasCtx.createImageData(gridW, gridH);
|
|
207
284
|
const pixels = imageData.data;
|
|
208
285
|
|
|
209
|
-
for (let i = 0; i <
|
|
210
|
-
const v =
|
|
286
|
+
for (let i = 0; i < rasterValues.length; i++) {
|
|
287
|
+
const v = rasterValues[i];
|
|
211
288
|
const offset = i * 4;
|
|
212
289
|
|
|
213
290
|
if (isNaN(v)) {
|
|
@@ -228,6 +305,17 @@
|
|
|
228
305
|
}
|
|
229
306
|
|
|
230
307
|
canvasCtx.putImageData(imageData, 0, 0);
|
|
308
|
+
|
|
309
|
+
if (blurRadius > 0) {
|
|
310
|
+
const blurredCanvas = document.createElement('canvas');
|
|
311
|
+
blurredCanvas.width = gridW;
|
|
312
|
+
blurredCanvas.height = gridH;
|
|
313
|
+
const blurredCtx = blurredCanvas.getContext('2d')!;
|
|
314
|
+
blurredCtx.filter = `blur(${blurRadius}px)`;
|
|
315
|
+
blurredCtx.drawImage(canvas, 0, 0);
|
|
316
|
+
return blurredCanvas.toDataURL();
|
|
317
|
+
}
|
|
318
|
+
|
|
231
319
|
return canvas.toDataURL();
|
|
232
320
|
});
|
|
233
321
|
</script>
|
|
@@ -235,10 +323,10 @@
|
|
|
235
323
|
{#if imageDataUrl}
|
|
236
324
|
<Image
|
|
237
325
|
href={imageDataUrl}
|
|
238
|
-
x={
|
|
239
|
-
y={
|
|
240
|
-
width={
|
|
241
|
-
height={
|
|
326
|
+
x={imagePlacement.x}
|
|
327
|
+
y={imagePlacement.y}
|
|
328
|
+
width={imagePlacement.width}
|
|
329
|
+
height={imagePlacement.height}
|
|
242
330
|
{imageRendering}
|
|
243
331
|
{opacity}
|
|
244
332
|
preserveAspectRatio="none"
|
|
@@ -14,6 +14,14 @@ export type RasterPropsWithoutHTML = {
|
|
|
14
14
|
width?: number;
|
|
15
15
|
/** Grid height (rows). When set, `data` is treated as a flat grid array. */
|
|
16
16
|
height?: number;
|
|
17
|
+
/** Left bound of the raster in data coordinates. Defaults to `0` in grid mode. */
|
|
18
|
+
x1?: number;
|
|
19
|
+
/** Top bound of the raster in data coordinates. Defaults to `0` in grid mode. */
|
|
20
|
+
y1?: number;
|
|
21
|
+
/** Right bound of the raster in data coordinates. Defaults to `width` in grid mode. */
|
|
22
|
+
x2?: number;
|
|
23
|
+
/** Bottom bound of the raster in data coordinates. Defaults to `height` in grid mode. */
|
|
24
|
+
y2?: number;
|
|
17
25
|
/**
|
|
18
26
|
* Value channel. Interpretation depends on the type:
|
|
19
27
|
* - `(x, y) => number`: continuous function evaluated at each pixel (function sampling mode)
|
|
@@ -240,18 +240,21 @@
|
|
|
240
240
|
// --- Data mode motion ---
|
|
241
241
|
const dataMotionMap = createDataMotionMap(motion as MotionOptions | undefined);
|
|
242
242
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
243
|
+
// Only create the data motion tracking effect when motion is actually configured
|
|
244
|
+
if (dataMotionMap) {
|
|
245
|
+
$effect(() => {
|
|
246
|
+
if (!dataMode) return;
|
|
247
|
+
const activeKeys = new Set<any>();
|
|
248
|
+
for (let i = 0; i < resolvedData.length; i++) {
|
|
249
|
+
const d = resolvedData[i];
|
|
250
|
+
const key = keyFn(d, i);
|
|
251
|
+
activeKeys.add(key);
|
|
252
|
+
const resolved = resolveRect(d);
|
|
253
|
+
untrack(() => dataMotionMap.update(key, resolved));
|
|
254
|
+
}
|
|
255
|
+
untrack(() => dataMotionMap.cleanup(activeKeys));
|
|
256
|
+
});
|
|
257
|
+
}
|
|
255
258
|
|
|
256
259
|
// Single source of truth: resolved values with animated overlay
|
|
257
260
|
const resolvedItems = $derived.by(() => {
|
|
@@ -288,25 +291,27 @@
|
|
|
288
291
|
const _initialWidth = initialWidth ?? (width ?? 0);
|
|
289
292
|
const _initialHeight = initialHeight ?? (height ?? 0);
|
|
290
293
|
|
|
294
|
+
// Parse motion config once, then pass resolved config to each axis
|
|
295
|
+
// (avoids 4 separate parseMotionProp calls that re-parse the same prop)
|
|
291
296
|
const motionX = createMotion(
|
|
292
297
|
_initialX,
|
|
293
298
|
() => (typeof x === 'number' ? x : 0),
|
|
294
|
-
parseMotionProp(motion, 'x')
|
|
299
|
+
motion === undefined ? undefined : parseMotionProp(motion, 'x')
|
|
295
300
|
);
|
|
296
301
|
const motionY = createMotion(
|
|
297
302
|
_initialY,
|
|
298
303
|
() => (typeof y === 'number' ? y : 0),
|
|
299
|
-
parseMotionProp(motion, 'y')
|
|
304
|
+
motion === undefined ? undefined : parseMotionProp(motion, 'y')
|
|
300
305
|
);
|
|
301
306
|
const motionWidth = createMotion(
|
|
302
307
|
_initialWidth,
|
|
303
308
|
() => width ?? 0,
|
|
304
|
-
parseMotionProp(motion, 'width')
|
|
309
|
+
motion === undefined ? undefined : parseMotionProp(motion, 'width')
|
|
305
310
|
);
|
|
306
311
|
const motionHeight = createMotion(
|
|
307
312
|
_initialHeight,
|
|
308
313
|
() => height ?? 0,
|
|
309
|
-
parseMotionProp(motion, 'height')
|
|
314
|
+
motion === undefined ? undefined : parseMotionProp(motion, 'height')
|
|
310
315
|
);
|
|
311
316
|
|
|
312
317
|
const layerCtx = getLayerContext();
|
|
@@ -378,8 +383,9 @@
|
|
|
378
383
|
}
|
|
379
384
|
|
|
380
385
|
// TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
|
|
381
|
-
|
|
382
|
-
const
|
|
386
|
+
// Only create key trackers when in canvas mode (they're only used for canvas dep tracking)
|
|
387
|
+
const fillKey = layerCtx === 'canvas' ? createKey(() => fill) : undefined;
|
|
388
|
+
const strokeKey = layerCtx === 'canvas' ? createKey(() => stroke) : undefined;
|
|
383
389
|
|
|
384
390
|
chartCtx.registerComponent({
|
|
385
391
|
name: 'Rect',
|
|
@@ -411,8 +417,8 @@
|
|
|
411
417
|
motionY.current,
|
|
412
418
|
motionWidth.current,
|
|
413
419
|
motionHeight.current,
|
|
414
|
-
fillKey
|
|
415
|
-
strokeKey
|
|
420
|
+
fillKey!.current,
|
|
421
|
+
strokeKey!.current,
|
|
416
422
|
fillOpacity,
|
|
417
423
|
strokeOpacity,
|
|
418
424
|
strokeWidth,
|