svelteplot 0.1.3-next.8 → 0.2.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.
Files changed (40) hide show
  1. package/README.md +4 -2
  2. package/dist/Mark.svelte +25 -21
  3. package/dist/Plot.svelte +45 -29
  4. package/dist/core/Plot.svelte +4 -2
  5. package/dist/helpers/index.d.ts +1 -1
  6. package/dist/helpers/resolve.js +1 -1
  7. package/dist/helpers/scales.js +3 -1
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.js +3 -0
  10. package/dist/marks/BarX.svelte +15 -5
  11. package/dist/marks/BarY.svelte +20 -12
  12. package/dist/marks/BarY.svelte.d.ts +22 -1
  13. package/dist/marks/Brush.svelte +364 -0
  14. package/dist/marks/Brush.svelte.d.ts +32 -0
  15. package/dist/marks/BrushX.svelte +7 -0
  16. package/dist/marks/BrushX.svelte.d.ts +4 -0
  17. package/dist/marks/BrushY.svelte +7 -0
  18. package/dist/marks/BrushY.svelte.d.ts +4 -0
  19. package/dist/marks/Cell.svelte +0 -7
  20. package/dist/marks/Dot.svelte +9 -18
  21. package/dist/marks/Dot.svelte.d.ts +8 -8
  22. package/dist/marks/Frame.svelte +24 -12
  23. package/dist/marks/Frame.svelte.d.ts +6 -1
  24. package/dist/marks/GridX.svelte +15 -6
  25. package/dist/marks/GridY.svelte +15 -6
  26. package/dist/marks/Link.svelte +1 -1
  27. package/dist/marks/Pointer.svelte +2 -2
  28. package/dist/marks/Pointer.svelte.d.ts +2 -2
  29. package/dist/marks/Rect.svelte +12 -19
  30. package/dist/marks/Sphere.svelte.d.ts +14 -4
  31. package/dist/marks/Text.svelte +14 -6
  32. package/dist/marks/Text.svelte.d.ts +2 -2
  33. package/dist/marks/helpers/events.d.ts +13 -0
  34. package/dist/marks/helpers/events.js +32 -3
  35. package/dist/transforms/bin.d.ts +7 -7
  36. package/dist/transforms/recordize.d.ts +1 -0
  37. package/dist/transforms/recordize.js +4 -5
  38. package/dist/transforms/window.d.ts +2 -0
  39. package/dist/types.d.ts +29 -9
  40. package/package.json +9 -7
@@ -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;
@@ -0,0 +1,7 @@
1
+ <script lang="ts">
2
+ import Brush, { type BrushMarkProps } from './Brush.svelte';
3
+
4
+ let { brush, ...restProps }: Omit<BrushMarkProps, 'limitDimension'> = $props();
5
+ </script>
6
+
7
+ <Brush bind:brush limitDimension="x" {...restProps} />
@@ -0,0 +1,4 @@
1
+ import { type BrushMarkProps } from './Brush.svelte';
2
+ declare const BrushX: import("svelte").Component<Omit<BrushMarkProps, "limitDimension">, {}, "">;
3
+ type BrushX = ReturnType<typeof BrushX>;
4
+ export default BrushX;
@@ -0,0 +1,7 @@
1
+ <script lang="ts">
2
+ import Brush, { type BrushMarkProps } from './Brush.svelte';
3
+
4
+ let { brush, ...restProps }: Omit<BrushMarkProps, 'limitDimension'> = $props();
5
+ </script>
6
+
7
+ <Brush bind:brush limitDimension="y" {...restProps} />
@@ -0,0 +1,4 @@
1
+ import { type BrushMarkProps } from './Brush.svelte';
2
+ declare const BrushY: import("svelte").Component<Omit<BrushMarkProps, "limitDimension">, {}, "">;
3
+ type BrushY = ReturnType<typeof BrushY>;
4
+ export default BrushY;
@@ -101,10 +101,3 @@
101
101
  </g>
102
102
  {/snippet}
103
103
  </Mark>
104
-
105
- <style>
106
- rect {
107
- stroke: none;
108
- /* fill: none; */
109
- }
110
- </style>
@@ -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: boolean;
40
- dotClass: ConstantAccessor<string>;
41
- in: any;
42
- inParams: any;
43
- out: any;
44
- outParams: any;
45
- transition: any;
46
- wrap: Snippet;
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 {
@@ -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: boolean;
15
- dotClass: ConstantAccessor<string>;
16
- in: any;
17
- inParams: any;
18
- out: any;
19
- outParams: any;
20
- transition: any;
21
- wrap: Snippet;
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>;
@@ -7,17 +7,32 @@
7
7
  import { addEventHandlers } from './helpers/events.js';
8
8
 
9
9
  type FrameMarkProps = BaseMarkProps &
10
- BaseRectMarkProps & {
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 = null, ...options }: FrameMarkProps = $props();
22
+ let { automatic, class: className = '', ...options }: FrameMarkProps = $props();
15
23
 
16
24
  const { getPlotState } = getContext<PlotContext>('svelteplot');
17
- let plot = $derived(getPlotState());
25
+ const plot = $derived(getPlotState());
18
26
 
19
- let dx = $derived(resolveProp(options.dx, null, 0));
20
- let dy = $derived(resolveProp(options.dy, null, 0));
27
+ const dx = $derived(resolveProp(options.dx, null, 0));
28
+ const dy = $derived(resolveProp(options.dy, null, 0));
29
+
30
+ const {
31
+ insetLeft = options.inset || 0,
32
+ insetTop = options.inset || 0,
33
+ insetRight = options.inset || 0,
34
+ insetBottom = options.inset || 0
35
+ } = $derived(options);
21
36
  </script>
22
37
 
23
38
  <Mark type="frame" {automatic}>
@@ -25,14 +40,11 @@
25
40
  class={['frame', className]}
26
41
  transform={dx || dy ? `translate(${dx},${dy})` : null}
27
42
  style={resolveScaledStyles({}, options, {}, plot, 'stroke')}
28
- x={plot.options.marginLeft}
29
- y={plot.options.marginTop}
43
+ x={plot.options.marginLeft + +insetLeft}
44
+ y={plot.options.marginTop + +insetTop}
30
45
  rx={resolveProp(options.rx, null, null)}
31
46
  ry={resolveProp(options.ry, null, null)}
32
- width={plot.facetWidth}
33
- height={plot.facetHeight}
47
+ width={plot.facetWidth - (insetLeft || 0) - (insetRight || 0)}
48
+ height={plot.facetHeight - (insetBottom || 0) - (insetTop || 0)}
34
49
  use:addEventHandlers={{ getPlotState, options: options, datum: {} }} />
35
50
  </Mark>
36
-
37
- <style>
38
- </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>;
@@ -2,10 +2,10 @@
2
2
  import { getContext } from 'svelte';
3
3
  import Mark from '../Mark.svelte';
4
4
  import type { PlotContext, BaseMarkProps, RawValue } from '../types.js';
5
- import { resolveChannel, resolveScaledStyles } from '../helpers/resolve.js';
5
+ import { resolveChannel, resolveStyles } from '../helpers/resolve.js';
6
6
  import { autoTicks } from '../helpers/autoTicks.js';
7
- import { getUsedScales } from '../helpers/scales.js';
8
7
  import { testFilter } from '../helpers/index.js';
8
+ import { RAW_VALUE } from '../transforms/recordize.js';
9
9
 
10
10
  type GrixXMarkProps = BaseMarkProps & {
11
11
  data?: RawValue[];
@@ -15,13 +15,13 @@
15
15
  let { data = [], automatic = false, ...options }: GrixXMarkProps = $props();
16
16
 
17
17
  const { getPlotState } = getContext<PlotContext>('svelteplot');
18
- let plot = $derived(getPlotState());
18
+ const plot = $derived(getPlotState());
19
19
 
20
- let autoTickCount = $derived(
20
+ const autoTickCount = $derived(
21
21
  Math.max(3, Math.round(plot.facetWidth / plot.options.x.tickSpacing))
22
22
  );
23
23
 
24
- let ticks: RawValue[] = $derived(
24
+ const ticks: RawValue[] = $derived(
25
25
  data.length > 0
26
26
  ? // use custom tick values if user passed any as prop
27
27
  data
@@ -54,9 +54,18 @@
54
54
  {@const y2_ = resolveChannel('y2', tick, options)}
55
55
  {@const y1 = options.y1 != null ? plot.scales.y.fn(y1_) : 0}
56
56
  {@const y2 = options.y2 != null ? plot.scales.y.fn(y2_) : plot.facetHeight}
57
+ {@const [style, styleClass] = resolveStyles(
58
+ plot,
59
+ { datum: { [RAW_VALUE]: tick } },
60
+ options,
61
+ 'stroke',
62
+ usedScales,
63
+ true
64
+ )}
57
65
  <line
66
+ class={styleClass}
58
67
  transform="translate({x},{plot.options.marginTop})"
59
- style={resolveScaledStyles(tick, options, usedScales, plot, 'stroke')}
68
+ {style}
60
69
  {y1}
61
70
  {y2} />
62
71
  {/if}
@@ -2,23 +2,23 @@
2
2
  import { getContext } from 'svelte';
3
3
  import Mark from '../Mark.svelte';
4
4
  import type { PlotContext, BaseMarkProps, RawValue, DataRecord } from '../types.js';
5
- import { resolveChannel, resolveScaledStyles } from '../helpers/resolve.js';
5
+ import { resolveChannel, resolveStyles } from '../helpers/resolve.js';
6
6
  import { autoTicks } from '../helpers/autoTicks.js';
7
- import { getUsedScales } from '../helpers/scales.js';
8
7
  import { testFilter } from '../helpers/index.js';
8
+ import { RAW_VALUE } from '../transforms/recordize.js';
9
9
 
10
10
  type GridYMarkProps = BaseMarkProps & { data?: RawValue[]; automatic?: boolean };
11
11
 
12
12
  let { data = [], automatic = false, ...options }: GridYMarkProps = $props();
13
13
 
14
14
  const { getPlotState } = getContext<PlotContext>('svelteplot');
15
- let plot = $derived(getPlotState());
15
+ const plot = $derived(getPlotState());
16
16
 
17
- let autoTickCount = $derived(
17
+ const autoTickCount = $derived(
18
18
  Math.max(2, Math.round(plot.facetHeight / plot.options.y.tickSpacing))
19
19
  );
20
20
 
21
- let ticks: RawValue[] = $derived(
21
+ const ticks: RawValue[] = $derived(
22
22
  data.length > 0
23
23
  ? // use custom tick values if user passed any as prop
24
24
  data
@@ -51,9 +51,18 @@
51
51
  {@const x2_ = resolveChannel('x2', tick, options)}
52
52
  {@const x1 = options.x1 != null ? plot.scales.x.fn(x1_) : 0}
53
53
  {@const x2 = options.x2 != null ? plot.scales.x.fn(x2_) : plot.facetWidth}
54
+ {@const [style, styleClass] = resolveStyles(
55
+ plot,
56
+ { datum: { [RAW_VALUE]: tick } },
57
+ options,
58
+ 'stroke',
59
+ usedScales,
60
+ true
61
+ )}
54
62
  <line
63
+ {style}
64
+ class={styleClass}
55
65
  transform="translate({plot.options.marginLeft},{y})"
56
- style={resolveScaledStyles(tick, options, usedScales, plot, 'stroke')}
57
66
  {x1}
58
67
  {x2} />
59
68
  {/if}