svelteplot 0.1.3-next.9 → 0.2.1
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/README.md +4 -2
- package/dist/Mark.svelte +18 -2
- package/dist/Plot.svelte +45 -29
- package/dist/helpers/index.d.ts +2 -1
- package/dist/helpers/index.js +1 -0
- package/dist/helpers/resolve.js +7 -6
- package/dist/helpers/scales.d.ts +2 -2
- package/dist/helpers/scales.js +8 -5
- package/dist/helpers/typeChecks.js +14 -10
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/marks/BarX.svelte +15 -5
- package/dist/marks/BarY.svelte +20 -12
- package/dist/marks/BarY.svelte.d.ts +22 -1
- package/dist/marks/Brush.svelte +364 -0
- package/dist/marks/Brush.svelte.d.ts +32 -0
- package/dist/marks/BrushX.svelte +7 -0
- package/dist/marks/BrushX.svelte.d.ts +4 -0
- package/dist/marks/BrushY.svelte +7 -0
- package/dist/marks/BrushY.svelte.d.ts +4 -0
- package/dist/marks/Cell.svelte +0 -7
- package/dist/marks/ColorLegend.svelte +6 -10
- package/dist/marks/Dot.svelte +11 -20
- package/dist/marks/Dot.svelte.d.ts +8 -8
- package/dist/marks/Frame.svelte +10 -5
- package/dist/marks/Frame.svelte.d.ts +6 -1
- package/dist/marks/Geo.svelte +50 -41
- package/dist/marks/Geo.svelte.d.ts +3 -1
- package/dist/marks/GridX.svelte +17 -8
- package/dist/marks/GridY.svelte +17 -8
- package/dist/marks/Pointer.svelte +4 -3
- package/dist/marks/Pointer.svelte.d.ts +2 -2
- package/dist/marks/Rect.svelte +12 -19
- package/dist/marks/Sphere.svelte.d.ts +14 -4
- package/dist/marks/Text.svelte +2 -2
- package/dist/marks/Text.svelte.d.ts +2 -2
- package/dist/marks/helpers/CanvasLayer.svelte +10 -16
- package/dist/marks/helpers/CanvasLayer.svelte.d.ts +2 -6
- package/dist/marks/helpers/DotCanvas.svelte +82 -159
- package/dist/marks/helpers/DotCanvas.svelte.d.ts +2 -4
- package/dist/marks/helpers/GeoCanvas.svelte +95 -145
- package/dist/marks/helpers/GeoCanvas.svelte.d.ts +3 -5
- package/dist/marks/helpers/events.d.ts +13 -0
- package/dist/marks/helpers/events.js +32 -3
- package/dist/transforms/bin.d.ts +7 -7
- package/dist/transforms/recordize.d.ts +2 -0
- package/dist/transforms/recordize.js +20 -10
- package/dist/transforms/stack.js +10 -7
- package/dist/transforms/window.d.ts +2 -0
- package/dist/types.d.ts +34 -13
- package/package.json +23 -20
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export type Brush = {
|
|
3
|
+
x1?: Date | number;
|
|
4
|
+
x2?: Date | number;
|
|
5
|
+
y1?: Date | number;
|
|
6
|
+
y2?: Date | number;
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type BrushEvent = MouseEvent & { brush: Brush };
|
|
11
|
+
|
|
12
|
+
export type BrushMarkProps = {
|
|
13
|
+
brush: Brush;
|
|
14
|
+
/**
|
|
15
|
+
* limit brushing to x or y dimension
|
|
16
|
+
*/
|
|
17
|
+
limitDimension?: false | 'x' | 'y';
|
|
18
|
+
/**
|
|
19
|
+
* whether brush can move/resize outside domain
|
|
20
|
+
*/
|
|
21
|
+
constrainToDomain?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* size of the (invisible) drag resize area around the edges of the brush selection
|
|
24
|
+
*/
|
|
25
|
+
resizeHandleSize?: number;
|
|
26
|
+
onbrushstart?: (evt: BrushEvent) => void;
|
|
27
|
+
onbrushend?: (evt: BrushEvent) => void;
|
|
28
|
+
onbrush?: (evt: BrushEvent) => void;
|
|
29
|
+
} & Pick<
|
|
30
|
+
BaseMarkProps,
|
|
31
|
+
| 'cursor'
|
|
32
|
+
| 'stroke'
|
|
33
|
+
| 'strokeDasharray'
|
|
34
|
+
| 'strokeOpacity'
|
|
35
|
+
| 'strokeWidth'
|
|
36
|
+
| 'strokeLinecap'
|
|
37
|
+
| 'strokeDashoffset'
|
|
38
|
+
| 'strokeLinejoin'
|
|
39
|
+
| 'strokeMiterlimit'
|
|
40
|
+
>;
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<script lang="ts">
|
|
44
|
+
import { getContext } from 'svelte';
|
|
45
|
+
import Frame from './Frame.svelte';
|
|
46
|
+
import Rect from './Rect.svelte';
|
|
47
|
+
import type { BaseMarkProps, PlotContext } from '../types.js';
|
|
48
|
+
import { clientToLayerCoordinates } from './helpers/events.js';
|
|
49
|
+
|
|
50
|
+
let {
|
|
51
|
+
brush = $bindable({ enabled: false }),
|
|
52
|
+
stroke = 'currentColor',
|
|
53
|
+
strokeWidth,
|
|
54
|
+
strokeDasharray = '2,3',
|
|
55
|
+
strokeOpacity = 0.6,
|
|
56
|
+
strokeLinecap,
|
|
57
|
+
strokeDashoffset,
|
|
58
|
+
strokeLinejoin,
|
|
59
|
+
strokeMiterlimit,
|
|
60
|
+
cursor: forceCursor,
|
|
61
|
+
limitDimension = false,
|
|
62
|
+
constrainToDomain = false,
|
|
63
|
+
resizeHandleSize = 10,
|
|
64
|
+
onbrushstart,
|
|
65
|
+
onbrushend,
|
|
66
|
+
onbrush
|
|
67
|
+
}: BrushMarkProps = $props();
|
|
68
|
+
|
|
69
|
+
const { getPlotState } = getContext<PlotContext>('svelteplot');
|
|
70
|
+
const plot = $derived(getPlotState());
|
|
71
|
+
|
|
72
|
+
const xScaleFn = $derived(plot.scales.x.fn);
|
|
73
|
+
const yScaleFn = $derived(plot.scales.y.fn);
|
|
74
|
+
|
|
75
|
+
const xDomain = $derived(plot.scales.x.domain) as [number, number] | [Date, Date];
|
|
76
|
+
const xRange = $derived(plot.scales.x.range) as [number, number];
|
|
77
|
+
const yDomain = $derived(plot.scales.y.domain) as [number, number] | [Date, Date];
|
|
78
|
+
const yRange = $derived(plot.scales.y.range) as [number, number];
|
|
79
|
+
|
|
80
|
+
$effect(() => {
|
|
81
|
+
if (limitDimension !== 'y' && !xScaleFn.invert) {
|
|
82
|
+
throw new Error('brushing does not work with band/point scales');
|
|
83
|
+
}
|
|
84
|
+
if (limitDimension !== 'x' && !yScaleFn.invert) {
|
|
85
|
+
throw new Error('brushing does not work with band/point scales');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
let x1 = $state(brush.x1 as Date | number);
|
|
90
|
+
let x2 = $state(brush.x2 as Date | number);
|
|
91
|
+
let y1 = $state(brush.y1 as Date | number);
|
|
92
|
+
let y2 = $state(brush.y2 as Date | number);
|
|
93
|
+
|
|
94
|
+
type ActionType =
|
|
95
|
+
| 'move'
|
|
96
|
+
| 'draw'
|
|
97
|
+
| 'n-resize'
|
|
98
|
+
| 's-resize'
|
|
99
|
+
| 'w-resize'
|
|
100
|
+
| 'e-resize'
|
|
101
|
+
| 'ne-resize'
|
|
102
|
+
| 'nw-resize'
|
|
103
|
+
| 'se-resize'
|
|
104
|
+
| 'sw-resize'
|
|
105
|
+
| false;
|
|
106
|
+
|
|
107
|
+
let dragging = false;
|
|
108
|
+
let action: ActionType = $state(false);
|
|
109
|
+
|
|
110
|
+
let dragStart: [number, number];
|
|
111
|
+
|
|
112
|
+
let pxPointer = $state([0, 0]);
|
|
113
|
+
|
|
114
|
+
const pxBrush = $derived({
|
|
115
|
+
x1: xScaleFn(brush.x1 as Date | number),
|
|
116
|
+
x2: xScaleFn(brush.x2 as Date | number),
|
|
117
|
+
y1: yScaleFn(brush.y1 as Date | number),
|
|
118
|
+
y2: yScaleFn(brush.y2 as Date | number)
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const HALF_EDGE = $derived(resizeHandleSize * 0.5);
|
|
122
|
+
|
|
123
|
+
const isInsideBrush = $derived(
|
|
124
|
+
(limitDimension === 'y' || pxPointer[0] > pxBrush.x1 + HALF_EDGE) &&
|
|
125
|
+
(limitDimension === 'y' || pxPointer[0] < pxBrush.x2 - HALF_EDGE) &&
|
|
126
|
+
(limitDimension === 'x' || pxPointer[1] > pxBrush.y2 + HALF_EDGE) &&
|
|
127
|
+
(limitDimension === 'x' || pxPointer[1] < pxBrush.y1 - HALF_EDGE)
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const isXEdge: false | 'left' | 'right' = $derived(
|
|
131
|
+
pxPointer[0] > pxBrush.x1 - HALF_EDGE && pxPointer[0] < pxBrush.x1 + HALF_EDGE
|
|
132
|
+
? 'left'
|
|
133
|
+
: pxPointer[0] > pxBrush.x2 - HALF_EDGE && pxPointer[0] < pxBrush.x2 + HALF_EDGE
|
|
134
|
+
? 'right'
|
|
135
|
+
: false
|
|
136
|
+
);
|
|
137
|
+
const isYEdge: false | 'top' | 'bottom' = $derived(
|
|
138
|
+
pxPointer[1] > pxBrush.y1 - HALF_EDGE && pxPointer[1] < pxBrush.y1 + HALF_EDGE
|
|
139
|
+
? 'top'
|
|
140
|
+
: pxPointer[1] > pxBrush.y2 - HALF_EDGE && pxPointer[1] < pxBrush.y2 + HALF_EDGE
|
|
141
|
+
? 'bottom'
|
|
142
|
+
: false
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const CURSOR_MAP = { left: 'w', right: 'e', top: 's', bottom: 'n' };
|
|
146
|
+
|
|
147
|
+
const cursor = $derived(
|
|
148
|
+
forceCursor
|
|
149
|
+
? forceCursor
|
|
150
|
+
: action
|
|
151
|
+
? action === 'draw'
|
|
152
|
+
? 'crosshair'
|
|
153
|
+
: action
|
|
154
|
+
: brush.enabled && isInsideBrush
|
|
155
|
+
? 'move'
|
|
156
|
+
: brush.enabled && (isXEdge || isYEdge)
|
|
157
|
+
? `${[isYEdge, isXEdge]
|
|
158
|
+
.filter((d) => !!d)
|
|
159
|
+
.map((c) => CURSOR_MAP[c])
|
|
160
|
+
.join('')}-resize`
|
|
161
|
+
: 'crosshair'
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
$effect(() => {
|
|
165
|
+
brush.x1 =
|
|
166
|
+
!brush.enabled || limitDimension === 'y'
|
|
167
|
+
? undefined
|
|
168
|
+
: constrain((x1 < x2 ? x1 : x2) as Date | number, xDomain);
|
|
169
|
+
brush.x2 =
|
|
170
|
+
!brush.enabled || limitDimension === 'y'
|
|
171
|
+
? undefined
|
|
172
|
+
: constrain(x1 > x2 ? x1 : x2, xDomain);
|
|
173
|
+
brush.y1 =
|
|
174
|
+
!brush.enabled || limitDimension === 'x'
|
|
175
|
+
? undefined
|
|
176
|
+
: constrain(y1 < y2 ? y1 : y2, yDomain);
|
|
177
|
+
brush.y2 =
|
|
178
|
+
!brush.enabled || limitDimension === 'x'
|
|
179
|
+
? undefined
|
|
180
|
+
: constrain(y1 > y2 ? y1 : y2, yDomain);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
function constrain<T extends number | Date>(x: T, extent: [typeof x, typeof x]) {
|
|
184
|
+
const minE = extent[0] < extent[1] ? extent[0] : extent[1];
|
|
185
|
+
const maxE = extent[0] > extent[1] ? extent[0] : extent[1];
|
|
186
|
+
if (x < minE) return minE;
|
|
187
|
+
if (x > maxE) return maxE;
|
|
188
|
+
return x;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const DRAG_MIN_DISTANCE = 5;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* fallback to clientX/clientY to support basic user event testing
|
|
195
|
+
*/
|
|
196
|
+
function getLayerPos(e: MouseEvent): [number, number] {
|
|
197
|
+
// Use the clientToLayerCoordinates helper function
|
|
198
|
+
return clientToLayerCoordinates(e, plot.body);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
$effect(() => {
|
|
202
|
+
plot.body?.ownerDocument.body.addEventListener('pointerup', onpointerup);
|
|
203
|
+
plot.body?.ownerDocument.body.addEventListener('pointermove', onpointermove);
|
|
204
|
+
|
|
205
|
+
return () => {
|
|
206
|
+
plot.body?.ownerDocument.body.removeEventListener('pointerup', onpointerup);
|
|
207
|
+
plot.body?.ownerDocument.body.removeEventListener('pointermove', onpointermove);
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
function onpointerdown(e: MouseEvent) {
|
|
212
|
+
dragging = true;
|
|
213
|
+
dragStart = getLayerPos(e);
|
|
214
|
+
pxPointer = getLayerPos(e);
|
|
215
|
+
|
|
216
|
+
if (brush.enabled && isInsideBrush) {
|
|
217
|
+
// drag starts inside existing brush, if so, move the brush
|
|
218
|
+
action = 'move';
|
|
219
|
+
} else if (brush.enabled && (isXEdge || isYEdge)) {
|
|
220
|
+
// drag starts on a brush edge, so resize the brush
|
|
221
|
+
action = `${[isYEdge, isXEdge]
|
|
222
|
+
.filter((d) => !!d)
|
|
223
|
+
.map((c) => CURSOR_MAP[c])
|
|
224
|
+
.join('')}-resize` as ActionType;
|
|
225
|
+
} else {
|
|
226
|
+
// draw new brush selection
|
|
227
|
+
action = 'draw';
|
|
228
|
+
x1 = x2 = xScaleFn.invert(dragStart[0]);
|
|
229
|
+
y1 = y2 = yScaleFn.invert(dragStart[1]);
|
|
230
|
+
}
|
|
231
|
+
onbrushstart?.({ ...e, brush });
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const EAST: Set<ActionType> = new Set(['e-resize', 'ne-resize', 'se-resize']);
|
|
235
|
+
const WEST: Set<ActionType> = new Set(['w-resize', 'nw-resize', 'sw-resize']);
|
|
236
|
+
const NORTH: Set<ActionType> = new Set(['n-resize', 'ne-resize', 'nw-resize']);
|
|
237
|
+
const SOUTH: Set<ActionType> = new Set(['s-resize', 'se-resize', 'sw-resize']);
|
|
238
|
+
|
|
239
|
+
function onpointermove(e: MouseEvent) {
|
|
240
|
+
const newPos = getLayerPos(e);
|
|
241
|
+
|
|
242
|
+
if (dragging) {
|
|
243
|
+
let px = newPos[0] - pxPointer[0];
|
|
244
|
+
let py = newPos[1] - pxPointer[1];
|
|
245
|
+
|
|
246
|
+
if (constrainToDomain) {
|
|
247
|
+
if (action === 'move') {
|
|
248
|
+
// limit selection movement
|
|
249
|
+
px = constrain(px, [xRange[0] - pxBrush.x1, xRange[1] - pxBrush.x2]);
|
|
250
|
+
py = constrain(py, [yRange[0] - pxBrush.y1, yRange[1] - pxBrush.y2]);
|
|
251
|
+
} else if (action !== 'draw') {
|
|
252
|
+
// limit horizontal resizing
|
|
253
|
+
if (EAST.has(action)) {
|
|
254
|
+
px = constrain(px, [xRange[0] - pxBrush.x2, xRange[1] - pxBrush.x2]);
|
|
255
|
+
} else if (WEST.has(action)) {
|
|
256
|
+
px = constrain(px, [xRange[0] - pxBrush.x1, xRange[1] - pxBrush.x1]);
|
|
257
|
+
}
|
|
258
|
+
// limit vertical resizing
|
|
259
|
+
if (NORTH.has(action)) {
|
|
260
|
+
py = constrain(py, [yRange[0] - pxBrush.y2, yRange[1] - pxBrush.y2]);
|
|
261
|
+
} else if (SOUTH.has(action)) {
|
|
262
|
+
py = constrain(py, [yRange[0] - pxBrush.y1, yRange[1] - pxBrush.y1]);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const hasX = limitDimension !== 'y';
|
|
268
|
+
const hasY = limitDimension !== 'x';
|
|
269
|
+
|
|
270
|
+
const dx1 = !hasX ? 0 : xScaleFn.invert(xScaleFn(x1) + px);
|
|
271
|
+
const dx2 = !hasX ? 0 : xScaleFn.invert(xScaleFn(x2) + px);
|
|
272
|
+
const dy1 = !hasY ? 0 : yScaleFn.invert(yScaleFn(y1) + py);
|
|
273
|
+
const dy2 = !hasY ? 0 : yScaleFn.invert(yScaleFn(y2) + py);
|
|
274
|
+
|
|
275
|
+
if (action === 'move') {
|
|
276
|
+
// move edges
|
|
277
|
+
x1 = dx1;
|
|
278
|
+
x2 = dx2;
|
|
279
|
+
y1 = dy1;
|
|
280
|
+
y2 = dy2;
|
|
281
|
+
} else if (action === 'draw') {
|
|
282
|
+
x2 = !hasX ? 0 : xScaleFn.invert(newPos[0]);
|
|
283
|
+
y2 = !hasY ? 0 : yScaleFn.invert(newPos[1]);
|
|
284
|
+
|
|
285
|
+
if (constrainToDomain) {
|
|
286
|
+
x2 = constrain(x2, xDomain as [typeof x2, typeof x2]);
|
|
287
|
+
y2 = constrain(y2, yDomain as [typeof y2, typeof y2]);
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
if (EAST.has(action)) {
|
|
291
|
+
x2 = dx2;
|
|
292
|
+
} else if (WEST.has(action)) {
|
|
293
|
+
x1 = dx1;
|
|
294
|
+
}
|
|
295
|
+
if (NORTH.has(action)) {
|
|
296
|
+
y2 = dy2;
|
|
297
|
+
} else if (SOUTH.has(action)) {
|
|
298
|
+
y1 = dy1;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// if the user drag-resizes a brush edge over the opposite edge we need to
|
|
302
|
+
// flip the coordinates and invert the resize action name
|
|
303
|
+
|
|
304
|
+
[x1, x2, action] = swapIfNeeded(x1, x2, action, 'e', 'w');
|
|
305
|
+
[y1, y2, action] = swapIfNeeded(y1, y2, action, 'n', 's');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const dist = Math.sqrt(
|
|
309
|
+
(dragStart[0] - pxPointer[0]) ** 2 + (dragStart[1] - pxPointer[1]) ** 2
|
|
310
|
+
);
|
|
311
|
+
if (dist > DRAG_MIN_DISTANCE) brush.enabled = true;
|
|
312
|
+
onbrush?.({ ...e, brush });
|
|
313
|
+
|
|
314
|
+
pxPointer = [pxPointer[0] + px, pxPointer[1] + py];
|
|
315
|
+
} else {
|
|
316
|
+
pxPointer = getLayerPos(e);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function swapIfNeeded(
|
|
321
|
+
v1: number | Date,
|
|
322
|
+
v2: number | Date,
|
|
323
|
+
action: ActionType,
|
|
324
|
+
swapDir1: string,
|
|
325
|
+
swapDir2: string
|
|
326
|
+
) {
|
|
327
|
+
if (action && v2 < v1) {
|
|
328
|
+
return [
|
|
329
|
+
v2,
|
|
330
|
+
v1,
|
|
331
|
+
`${action.split('-')[0].replace(swapDir1, 'X').replace(swapDir2, swapDir1).replace('X', swapDir2)}-resize` as ActionType
|
|
332
|
+
];
|
|
333
|
+
}
|
|
334
|
+
return [v1, v2, action];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function onpointerup(e: MouseEvent) {
|
|
338
|
+
if (dragging) {
|
|
339
|
+
dragging = false;
|
|
340
|
+
action = false;
|
|
341
|
+
|
|
342
|
+
brush.enabled =
|
|
343
|
+
Math.sqrt((dragStart[0] - pxPointer[0]) ** 2 + (dragStart[1] - pxPointer[1]) ** 2) >
|
|
344
|
+
DRAG_MIN_DISTANCE;
|
|
345
|
+
onbrushend?.({ ...e, brush });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
</script>
|
|
349
|
+
|
|
350
|
+
{#if stroke && brush.enabled}
|
|
351
|
+
<Rect
|
|
352
|
+
class="brush-rect"
|
|
353
|
+
{...limitDimension === 'x' ? {} : { y1: brush.y1, y2: brush.y2 }}
|
|
354
|
+
{...limitDimension === 'y' ? {} : { x1: brush.x1, x2: brush.x2 }}
|
|
355
|
+
{stroke}
|
|
356
|
+
{strokeDasharray}
|
|
357
|
+
{strokeOpacity}
|
|
358
|
+
{strokeDashoffset}
|
|
359
|
+
{strokeLinecap}
|
|
360
|
+
{strokeLinejoin}
|
|
361
|
+
{strokeMiterlimit}
|
|
362
|
+
{strokeWidth} />
|
|
363
|
+
{/if}
|
|
364
|
+
<Frame fill="transparent" inset={-20} {cursor} {onpointerdown} {onpointermove} />
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type Brush = {
|
|
2
|
+
x1?: Date | number;
|
|
3
|
+
x2?: Date | number;
|
|
4
|
+
y1?: Date | number;
|
|
5
|
+
y2?: Date | number;
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
};
|
|
8
|
+
type BrushEvent = MouseEvent & {
|
|
9
|
+
brush: Brush;
|
|
10
|
+
};
|
|
11
|
+
export type BrushMarkProps = {
|
|
12
|
+
brush: Brush;
|
|
13
|
+
/**
|
|
14
|
+
* limit brushing to x or y dimension
|
|
15
|
+
*/
|
|
16
|
+
limitDimension?: false | 'x' | 'y';
|
|
17
|
+
/**
|
|
18
|
+
* whether brush can move/resize outside domain
|
|
19
|
+
*/
|
|
20
|
+
constrainToDomain?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* size of the (invisible) drag resize area around the edges of the brush selection
|
|
23
|
+
*/
|
|
24
|
+
resizeHandleSize?: number;
|
|
25
|
+
onbrushstart?: (evt: BrushEvent) => void;
|
|
26
|
+
onbrushend?: (evt: BrushEvent) => void;
|
|
27
|
+
onbrush?: (evt: BrushEvent) => void;
|
|
28
|
+
} & Pick<BaseMarkProps, 'cursor' | 'stroke' | 'strokeDasharray' | 'strokeOpacity' | 'strokeWidth' | 'strokeLinecap' | 'strokeDashoffset' | 'strokeLinejoin' | 'strokeMiterlimit'>;
|
|
29
|
+
import type { BaseMarkProps } from '../types.js';
|
|
30
|
+
declare const Brush: import("svelte").Component<BrushMarkProps, {}, "brush">;
|
|
31
|
+
type Brush = ReturnType<typeof Brush>;
|
|
32
|
+
export default Brush;
|
package/dist/marks/Cell.svelte
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { getContext } from 'svelte';
|
|
3
3
|
import { Plot, AxisX, Frame } from '../index.js';
|
|
4
4
|
import { symbol as d3Symbol, symbol } from 'd3-shape';
|
|
5
|
-
import { range as d3Range } from 'd3-array';
|
|
5
|
+
import { range as d3Range, extent } from 'd3-array';
|
|
6
6
|
import { maybeSymbol } from '../helpers/symbols.js';
|
|
7
7
|
|
|
8
8
|
import type { DefaultOptions, PlotContext } from '../types.js';
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
</div>
|
|
73
73
|
{/each}
|
|
74
74
|
{:else if scaleType === 'quantile' || scaleType === 'quantize' || scaleType === 'threshold'}
|
|
75
|
-
{@const domain = plot.scales.color.domain}
|
|
75
|
+
{@const domain = extent(plot.scales.color.fn.domain())}
|
|
76
76
|
{@const range = plot.scales.color.range}
|
|
77
77
|
{@const tickLabels =
|
|
78
78
|
scaleType === 'quantile'
|
|
@@ -85,7 +85,6 @@
|
|
|
85
85
|
domain[1],
|
|
86
86
|
(domain[1] - domain[0]) / range.length
|
|
87
87
|
).slice(1)}
|
|
88
|
-
|
|
89
88
|
<Plot
|
|
90
89
|
maxWidth="240px"
|
|
91
90
|
margins={1}
|
|
@@ -112,16 +111,13 @@
|
|
|
112
111
|
</linearGradient>
|
|
113
112
|
</defs>
|
|
114
113
|
<Frame dy={-5} stroke={null} fill="url(#gradient-{randId})" />
|
|
115
|
-
<AxisX tickSize={18} dy={-17} />
|
|
114
|
+
<AxisX tickSize={18} dy={-17} tickFormat={(d, i) => tickFormat(tickLabels[i])} />
|
|
116
115
|
</Plot>
|
|
117
116
|
{:else}
|
|
118
117
|
<!--- continuous -->
|
|
119
|
-
{@const domain = plot.scales.color.domain}
|
|
120
|
-
{@const ticks =
|
|
121
|
-
|
|
122
|
-
...(plot.scales.color?.fn?.ticks?.(Math.ceil(width / 5)) ?? []),
|
|
123
|
-
domain[1]
|
|
124
|
-
])}
|
|
118
|
+
{@const domain = extent(plot.scales.color.domain)}
|
|
119
|
+
{@const ticks = d3Range(domain[0], domain[1], (domain[1] - domain[0]) / 7).slice(1)}
|
|
120
|
+
|
|
125
121
|
<Plot
|
|
126
122
|
maxWidth="240px"
|
|
127
123
|
margins={1}
|
package/dist/marks/Dot.svelte
CHANGED
|
@@ -9,15 +9,9 @@
|
|
|
9
9
|
FacetContext,
|
|
10
10
|
PlotDefaults
|
|
11
11
|
} from '../types.js';
|
|
12
|
-
import {
|
|
13
|
-
resolveChannel,
|
|
14
|
-
resolveProp,
|
|
15
|
-
resolveScaledStyles,
|
|
16
|
-
resolveStyles
|
|
17
|
-
} from '../helpers/resolve.js';
|
|
12
|
+
import { resolveProp, resolveStyles } from '../helpers/resolve.js';
|
|
18
13
|
import { maybeSymbol } from '../helpers/symbols.js';
|
|
19
14
|
import { symbol as d3Symbol } from 'd3-shape';
|
|
20
|
-
import { projectXY } from '../helpers/scales.js';
|
|
21
15
|
import { sort } from '../index.js';
|
|
22
16
|
import Mark from '../Mark.svelte';
|
|
23
17
|
import DotCanvas from './helpers/DotCanvas.svelte';
|
|
@@ -36,14 +30,14 @@
|
|
|
36
30
|
children?: Snippet;
|
|
37
31
|
dx?: ConstantAccessor<number>;
|
|
38
32
|
dy?: ConstantAccessor<number>;
|
|
39
|
-
canvas
|
|
40
|
-
dotClass
|
|
41
|
-
in
|
|
42
|
-
inParams
|
|
43
|
-
out
|
|
44
|
-
outParams
|
|
45
|
-
transition
|
|
46
|
-
wrap
|
|
33
|
+
canvas?: boolean;
|
|
34
|
+
dotClass?: ConstantAccessor<string>;
|
|
35
|
+
in?: any;
|
|
36
|
+
inParams?: any;
|
|
37
|
+
out?: any;
|
|
38
|
+
outParams?: any;
|
|
39
|
+
transition?: any;
|
|
40
|
+
wrap?: Snippet;
|
|
47
41
|
};
|
|
48
42
|
|
|
49
43
|
let {
|
|
@@ -59,7 +53,7 @@
|
|
|
59
53
|
}: DotProps = $props();
|
|
60
54
|
|
|
61
55
|
const { getPlotState } = getContext<PlotContext>('svelteplot');
|
|
62
|
-
|
|
56
|
+
const plot = $derived(getPlotState());
|
|
63
57
|
|
|
64
58
|
function getSymbolPath(symbolType, size) {
|
|
65
59
|
return d3Symbol(maybeSymbol(symbolType), size)();
|
|
@@ -102,7 +96,7 @@
|
|
|
102
96
|
{#snippet children({ mark, usedScales, scaledData })}
|
|
103
97
|
<g class="dots {className || ''}">
|
|
104
98
|
{#if canvas}
|
|
105
|
-
<DotCanvas data={
|
|
99
|
+
<DotCanvas data={scaledData} {mark} />
|
|
106
100
|
{:else}
|
|
107
101
|
{#each scaledData as d}
|
|
108
102
|
{#if d.valid && isValid(d.r)}
|
|
@@ -132,6 +126,3 @@
|
|
|
132
126
|
</g>
|
|
133
127
|
{/snippet}
|
|
134
128
|
</Mark>
|
|
135
|
-
|
|
136
|
-
<style>
|
|
137
|
-
</style>
|
|
@@ -11,14 +11,14 @@ type DotProps = BaseMarkProps & {
|
|
|
11
11
|
children?: Snippet;
|
|
12
12
|
dx?: ConstantAccessor<number>;
|
|
13
13
|
dy?: ConstantAccessor<number>;
|
|
14
|
-
canvas
|
|
15
|
-
dotClass
|
|
16
|
-
in
|
|
17
|
-
inParams
|
|
18
|
-
out
|
|
19
|
-
outParams
|
|
20
|
-
transition
|
|
21
|
-
wrap
|
|
14
|
+
canvas?: boolean;
|
|
15
|
+
dotClass?: ConstantAccessor<string>;
|
|
16
|
+
in?: any;
|
|
17
|
+
inParams?: any;
|
|
18
|
+
out?: any;
|
|
19
|
+
outParams?: any;
|
|
20
|
+
transition?: any;
|
|
21
|
+
wrap?: Snippet;
|
|
22
22
|
};
|
|
23
23
|
declare const Dot: import("svelte").Component<DotProps, {}, "">;
|
|
24
24
|
type Dot = ReturnType<typeof Dot>;
|
package/dist/marks/Frame.svelte
CHANGED
|
@@ -7,11 +7,19 @@
|
|
|
7
7
|
import { addEventHandlers } from './helpers/events.js';
|
|
8
8
|
|
|
9
9
|
type FrameMarkProps = BaseMarkProps &
|
|
10
|
-
|
|
10
|
+
Omit<
|
|
11
|
+
BaseRectMarkProps,
|
|
12
|
+
'inset' | 'insetLeft' | 'insetRight' | 'insetTop' | 'insetBottom'
|
|
13
|
+
> & {
|
|
11
14
|
automatic?: boolean;
|
|
15
|
+
inset?: number;
|
|
16
|
+
insetLeft?: number;
|
|
17
|
+
insetRight?: number;
|
|
18
|
+
insetTop?: number;
|
|
19
|
+
insetBottom?: number;
|
|
12
20
|
};
|
|
13
21
|
|
|
14
|
-
let { automatic, class: className =
|
|
22
|
+
let { automatic, class: className = '', ...options }: FrameMarkProps = $props();
|
|
15
23
|
|
|
16
24
|
const { getPlotState } = getContext<PlotContext>('svelteplot');
|
|
17
25
|
const plot = $derived(getPlotState());
|
|
@@ -40,6 +48,3 @@
|
|
|
40
48
|
height={plot.facetHeight - (insetBottom || 0) - (insetTop || 0)}
|
|
41
49
|
use:addEventHandlers={{ getPlotState, options: options, datum: {} }} />
|
|
42
50
|
</Mark>
|
|
43
|
-
|
|
44
|
-
<style>
|
|
45
|
-
</style>
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { BaseRectMarkProps } from '../types.js';
|
|
2
2
|
import type { BaseMarkProps } from '../types.js';
|
|
3
|
-
type FrameMarkProps = BaseMarkProps & BaseRectMarkProps & {
|
|
3
|
+
type FrameMarkProps = BaseMarkProps & Omit<BaseRectMarkProps, 'inset' | 'insetLeft' | 'insetRight' | 'insetTop' | 'insetBottom'> & {
|
|
4
4
|
automatic?: boolean;
|
|
5
|
+
inset?: number;
|
|
6
|
+
insetLeft?: number;
|
|
7
|
+
insetRight?: number;
|
|
8
|
+
insetTop?: number;
|
|
9
|
+
insetBottom?: number;
|
|
5
10
|
};
|
|
6
11
|
declare const Frame: import("svelte").Component<FrameMarkProps, {}, "">;
|
|
7
12
|
type Frame = ReturnType<typeof Frame>;
|