layerchart 0.39.0 → 0.40.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.
@@ -0,0 +1,244 @@
1
+ <script>import { createEventDispatcher, getContext } from 'svelte';
2
+ import { extent } from 'd3-array';
3
+ import { clamp, cls } from 'svelte-ux';
4
+ import Frame from './Frame.svelte';
5
+ import { localPoint } from '../utils/event.js';
6
+ import Group from './Group.svelte';
7
+ const { xScale, yScale, width, height, padding } = getContext('LayerCake');
8
+ const dispatch = createEventDispatcher();
9
+ /** Axis to apply brushing */
10
+ export let axis = 'x';
11
+ /** Size of draggable handles (width/height) */
12
+ export let handleSize = 5;
13
+ /** Only show range while actively brushing. Useful with `brushEnd` event */
14
+ export let resetOnEnd = false;
15
+ export let xDomain = $xScale.domain();
16
+ export let yDomain = $yScale.domain();
17
+ // Capture original domains for reset()
18
+ const originalXDomain = $xScale.domain();
19
+ const originalYDomain = $yScale.domain();
20
+ $: [xDomainMin, xDomainMax] = extent($xScale.domain());
21
+ $: [yDomainMin, yDomainMax] = extent($yScale.domain());
22
+ /** Attributes passed to range <rect> element */
23
+ export let range = undefined;
24
+ /** Attributes passed to handle <rect> elements */
25
+ export let handle = undefined;
26
+ export let classes = {};
27
+ let frameEl;
28
+ function handler(fn) {
29
+ return (e) => {
30
+ const start = {
31
+ xDomain: [xDomain[0] ?? xDomainMin, xDomain[1] ?? xDomainMax],
32
+ yDomain: [yDomain[0] ?? yDomainMin, yDomain[1] ?? yDomainMax],
33
+ value: {
34
+ x: $xScale.invert(localPoint(frameEl, e)?.x - $padding.left),
35
+ y: $yScale.invert(localPoint(frameEl, e)?.y - $padding.top),
36
+ },
37
+ };
38
+ dispatch('brushStart', { xDomain, yDomain });
39
+ const onPointerMove = (e) => {
40
+ fn(start, {
41
+ x: $xScale.invert(localPoint(frameEl, e)?.x - $padding.left),
42
+ y: $yScale.invert(localPoint(frameEl, e)?.y - $padding.top),
43
+ });
44
+ // if (xDomain[0] === xDomain[1] || yDomain[0] === yDomain[1]) {
45
+ // // Ignore?
46
+ // // TODO: What about when using `x` or `y` axis?
47
+ // } else {
48
+ dispatch('change', { xDomain, yDomain });
49
+ // }
50
+ };
51
+ const onPointerUp = (e) => {
52
+ if (e.target === frameEl) {
53
+ reset();
54
+ dispatch('change', { xDomain, yDomain });
55
+ }
56
+ dispatch('brushEnd', { xDomain, yDomain });
57
+ if (resetOnEnd) {
58
+ reset();
59
+ }
60
+ window.removeEventListener('pointermove', onPointerMove);
61
+ window.removeEventListener('pointerup', onPointerUp);
62
+ };
63
+ window.addEventListener('pointermove', onPointerMove);
64
+ window.addEventListener('pointerup', onPointerUp);
65
+ };
66
+ }
67
+ const createRange = handler((start, value) => {
68
+ isActive = true;
69
+ xDomain = [
70
+ clamp(Math.min(start.value.x, value.x), xDomainMin, xDomainMax),
71
+ clamp(Math.max(start.value.x, value.x), xDomainMin, xDomainMax),
72
+ ];
73
+ yDomain = [
74
+ clamp(Math.min(start.value.y, value.y), yDomainMin, yDomainMax),
75
+ clamp(Math.max(start.value.y, value.y), yDomainMin, yDomainMax),
76
+ ];
77
+ });
78
+ const adjustRange = handler((start, value) => {
79
+ const dx = clamp(value.x - start.value.x, xDomainMin - start.xDomain[0], xDomainMax - start.xDomain[1]);
80
+ xDomain = [Number(start.xDomain[0]) + dx, Number(start.xDomain[1]) + dx];
81
+ const dy = clamp(value.y - start.value.y, yDomainMin - start.yDomain[0], yDomainMax - start.yDomain[1]);
82
+ yDomain = [Number(start.yDomain[0]) + dy, Number(start.yDomain[1]) + dy];
83
+ });
84
+ const adjustBottom = handler((start, value) => {
85
+ yDomain = [
86
+ clamp(value.y > start.yDomain[1] ? start.yDomain[1] : value.y, yDomainMin, yDomainMax),
87
+ clamp(value.y > start.yDomain[1] ? value.y : start.yDomain[1], yDomainMin, yDomainMax),
88
+ ];
89
+ });
90
+ const adjustTop = handler((start, value) => {
91
+ yDomain = [
92
+ clamp(value.y < start.yDomain[1] ? value.y : start.yDomain[0], yDomainMin, yDomainMax),
93
+ clamp(value.y < start.yDomain[1] ? start.yDomain[0] : value.y, yDomainMin, yDomainMax),
94
+ ];
95
+ });
96
+ const adjustLeft = handler((start, value) => {
97
+ xDomain = [
98
+ clamp(value.x > start.xDomain[1] ? start.xDomain[1] : value.x, xDomainMin, xDomainMax),
99
+ clamp(value.x > start.xDomain[1] ? value.x : start.xDomain[1], xDomainMin, xDomainMax),
100
+ ];
101
+ });
102
+ const adjustRight = handler((start, value) => {
103
+ xDomain = [
104
+ clamp(value.x < start.xDomain[0] ? value.x : start.xDomain[0], xDomainMin, xDomainMax),
105
+ clamp(value.x < start.xDomain[0] ? start.xDomain[0] : value.x, xDomainMin, xDomainMax),
106
+ ];
107
+ });
108
+ function reset() {
109
+ isActive = false;
110
+ xDomain = originalXDomain;
111
+ yDomain = originalYDomain;
112
+ }
113
+ function selectAll() {
114
+ xDomain = [xDomainMin, xDomainMax];
115
+ yDomain = [yDomainMin, yDomainMax];
116
+ }
117
+ $: top = $yScale(yDomain[1]);
118
+ $: bottom = $yScale(yDomain[0]);
119
+ $: left = $xScale(xDomain[0]);
120
+ $: right = $xScale(xDomain[1]);
121
+ $: rangeTop = axis === 'both' || axis === 'y' ? top : 0;
122
+ $: rangeLeft = axis === 'both' || axis === 'x' ? left : 0;
123
+ $: rangeWidth = axis === 'both' || axis === 'x' ? right - left : $width;
124
+ $: rangeHeight = axis === 'both' || axis === 'y' ? bottom - top : $height;
125
+ // Set reactively to handle cases where xDomain/yDomain are set externally (ex. `bind:xDomain`)
126
+ $: isActive =
127
+ xDomain[0]?.valueOf() !== originalXDomain[0]?.valueOf() ||
128
+ xDomain[1]?.valueOf() !== originalXDomain[1]?.valueOf() ||
129
+ yDomain[0]?.valueOf() !== originalYDomain[0]?.valueOf() ||
130
+ yDomain[1]?.valueOf() !== originalYDomain[1]?.valueOf();
131
+ </script>
132
+
133
+ <g class={cls('Brush select-none', classes.root, $$props.class)}>
134
+ <Frame
135
+ class={cls('frame', 'fill-transparent', classes.frame)}
136
+ on:pointerdown={createRange}
137
+ on:dblclick={() => selectAll()}
138
+ bind:rectEl={frameEl}
139
+ />
140
+
141
+ {#if isActive}
142
+ <Group x={rangeLeft} y={rangeTop} class="range">
143
+ <slot name="range" {rangeWidth} {rangeHeight}>
144
+ <rect
145
+ width={rangeWidth}
146
+ height={rangeHeight}
147
+ class={cls(
148
+ 'cursor-move select-none',
149
+ range?.fill == null && 'fill-surface-content/10',
150
+ classes.range
151
+ )}
152
+ on:pointerdown={adjustRange}
153
+ on:dblclick={() => reset()}
154
+ {...range}
155
+ />
156
+ </slot>
157
+ </Group>
158
+
159
+ {#if axis === 'both' || axis === 'y'}
160
+ <Group
161
+ x={rangeLeft}
162
+ y={rangeTop}
163
+ class="handle top"
164
+ on:pointerdown={adjustTop}
165
+ on:dblclick={() => {
166
+ yDomain[0] = yDomainMin;
167
+ dispatch('change', { xDomain, yDomain });
168
+ }}
169
+ >
170
+ <slot name="handle" edge="top" {rangeWidth} {rangeHeight}>
171
+ <rect
172
+ width={rangeWidth}
173
+ height={handleSize}
174
+ class={cls('fill-transparent cursor-ns-resize select-none', classes.handle)}
175
+ {...handle}
176
+ />
177
+ </slot>
178
+ </Group>
179
+
180
+ <Group
181
+ x={rangeLeft}
182
+ y={bottom - handleSize + 1}
183
+ class="handle bottom"
184
+ on:pointerdown={adjustBottom}
185
+ on:dblclick={() => {
186
+ yDomain[1] = yDomainMax;
187
+ }}
188
+ >
189
+ <slot name="handle" edge="bottom" {rangeWidth} {rangeHeight}>
190
+ <rect
191
+ width={rangeWidth}
192
+ height={handleSize}
193
+ class={cls('fill-transparent cursor-ns-resize select-none', classes.handle)}
194
+ {...handle}
195
+ />
196
+ </slot>
197
+ </Group>
198
+ {/if}
199
+
200
+ {#if axis === 'both' || axis === 'x'}
201
+ <Group
202
+ x={rangeLeft}
203
+ y={rangeTop}
204
+ class="handle left"
205
+ on:pointerdown={adjustLeft}
206
+ on:dblclick={() => {
207
+ xDomain[0] = xDomainMin;
208
+ dispatch('change', { xDomain, yDomain });
209
+ }}
210
+ >
211
+ <slot name="handle" edge="left" {rangeWidth} {rangeHeight}>
212
+ <rect
213
+ width={handleSize}
214
+ height={rangeHeight}
215
+ class={cls('fill-transparent cursor-ew-resize select-none', classes.handle)}
216
+ {...handle}
217
+ />
218
+ </slot>
219
+ </Group>
220
+
221
+ <Group
222
+ x={right - handleSize + 1}
223
+ y={rangeTop}
224
+ class="handle right"
225
+ on:pointerdown={adjustRight}
226
+ on:dblclick={() => {
227
+ xDomain[1] = xDomainMax;
228
+ dispatch('change', { xDomain, yDomain });
229
+ }}
230
+ >
231
+ <slot name="handle" edge="right" {rangeWidth} {rangeHeight}>
232
+ <rect
233
+ width={handleSize}
234
+ height={rangeHeight}
235
+ class={cls('fill-transparent cursor-ew-resize select-none', classes.handle)}
236
+ {...handle}
237
+ />
238
+ </slot>
239
+ </Group>
240
+ {/if}
241
+
242
+ <!-- TODO: Add diagonal/corner handles -->
243
+ {/if}
244
+ </g>
@@ -0,0 +1,53 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ import type { SVGAttributes } from 'svelte/elements';
3
+ declare const __propDef: {
4
+ props: {
5
+ [x: string]: any;
6
+ axis?: "x" | "y" | "both" | undefined;
7
+ handleSize?: number | undefined;
8
+ resetOnEnd?: boolean | undefined;
9
+ xDomain?: [number | null, number | null] | undefined;
10
+ yDomain?: [number | null, number | null] | undefined;
11
+ range?: SVGAttributes<SVGRectElement> | undefined;
12
+ handle?: SVGAttributes<SVGRectElement> | undefined;
13
+ classes?: {
14
+ root?: string | undefined;
15
+ frame?: string | undefined;
16
+ range?: string | undefined;
17
+ handle?: string | undefined;
18
+ } | undefined;
19
+ };
20
+ events: {
21
+ change: CustomEvent<{
22
+ xDomain?: [any, any] | undefined;
23
+ yDomain?: [any, any] | undefined;
24
+ }>;
25
+ brushStart: CustomEvent<{
26
+ xDomain?: [any, any] | undefined;
27
+ yDomain?: [any, any] | undefined;
28
+ }>;
29
+ brushEnd: CustomEvent<{
30
+ xDomain?: [any, any] | undefined;
31
+ yDomain?: [any, any] | undefined;
32
+ }>;
33
+ } & {
34
+ [evt: string]: CustomEvent<any>;
35
+ };
36
+ slots: {
37
+ range: {
38
+ rangeWidth: any;
39
+ rangeHeight: any;
40
+ };
41
+ handle: {
42
+ edge: string;
43
+ rangeWidth: any;
44
+ rangeHeight: any;
45
+ };
46
+ };
47
+ };
48
+ export type BrushProps = typeof __propDef.props;
49
+ export type BrushEvents = typeof __propDef.events;
50
+ export type BrushSlots = typeof __propDef.slots;
51
+ export default class Brush extends SvelteComponentTyped<BrushProps, BrushEvents, BrushSlots> {
52
+ }
53
+ export {};
@@ -76,8 +76,6 @@ export let tooltip = undefined;
76
76
  /** Props passed to TransformContext */
77
77
  export let transform = undefined;
78
78
  export let transformContext = undefined;
79
- /** Set transformContext's initial translate and scale based on geo object. Requires `geo.projection` to also be set */
80
- export let fitGeoObject = undefined;
81
79
  // Binded for access within TransformContext
82
80
  let geoProjection = undefined;
83
81
  // Track when mounted since LayerCake initializes width/height with `100` until binded `clientWidth`/`clientWidth` can run
@@ -115,9 +113,10 @@ onMount(() => {
115
113
  let:data
116
114
  let:flatData
117
115
  >
116
+ <!-- Apply fitGeojson using TransformContext instead of GeoContext if `applyTransform` is used -->
118
117
  {@const initialTransform =
119
- fitGeoObject && geo?.projection
120
- ? geoFitObjectTransform(geo.projection(), [width, height], fitGeoObject)
118
+ geo?.applyTransform && geo?.fitGeojson && geo?.projection
119
+ ? geoFitObjectTransform(geo.projection(), [width, height], geo.fitGeojson)
121
120
  : undefined}
122
121
 
123
122
  {#key isMounted}
@@ -11,13 +11,12 @@ import { type ComponentProps } from 'svelte';
11
11
  import GeoContext from './GeoContext.svelte';
12
12
  import TooltipContext from './TooltipContext.svelte';
13
13
  import TransformContext from './TransformContext.svelte';
14
- import { geoFitObjectTransform } from '../utils/geo.js';
15
14
  declare const __propDef: {
16
15
  props: {
17
16
  [x: string]: any;
18
17
  data?: any[] | undefined;
19
- x?: (string | ((d: any) => number)) | (string | ((d: any) => number))[] | undefined;
20
- y?: (string | ((d: any) => number)) | (string | ((d: any) => number))[] | undefined;
18
+ x?: (string | number | ((d: any) => number)) | (string | number | ((d: any) => number))[] | undefined;
19
+ y?: (string | number | ((d: any) => number)) | (string | number | ((d: any) => number))[] | undefined;
21
20
  yScale?: Function | undefined;
22
21
  xBaseline?: number | null | undefined;
23
22
  yBaseline?: number | null | undefined;
@@ -25,7 +24,6 @@ declare const __propDef: {
25
24
  tooltip?: Partial<ComponentProps<TooltipContext>> | boolean | undefined;
26
25
  transform?: Partial<ComponentProps<TransformContext>> | undefined;
27
26
  transformContext?: TransformContext | undefined;
28
- fitGeoObject?: Parameters<typeof geoFitObjectTransform>[2] | undefined;
29
27
  };
30
28
  events: {
31
29
  transform: CustomEvent<{
@@ -1,15 +1,15 @@
1
1
  <script>import { getContext } from 'svelte';
2
2
  import RectClipPath from './RectClipPath.svelte';
3
3
  const { width, height, padding } = getContext('LayerCake');
4
- /** Whether clipping should include chart padding (ex. axis) */
5
- export let includePadding = false;
4
+ /** Include padding area (ex. axis) */
5
+ export let full = false;
6
6
  </script>
7
7
 
8
8
  <RectClipPath
9
- x={includePadding ? -$padding.left : 0}
10
- y={includePadding ? -$padding.top : 0}
11
- width={$width + (includePadding ? $padding.left + $padding.right : 0)}
12
- height={$height + (includePadding ? $padding.top + $padding.bottom : 0)}
9
+ x={full ? -$padding.left : 0}
10
+ y={full ? -$padding.top : 0}
11
+ width={$width + (full ? $padding.left + $padding.right : 0)}
12
+ height={$height + (full ? $padding.top + $padding.bottom : 0)}
13
13
  on:click
14
14
  {...$$restProps}
15
15
  >
@@ -2,7 +2,7 @@ import { SvelteComponentTyped } from "svelte";
2
2
  declare const __propDef: {
3
3
  props: {
4
4
  [x: string]: any;
5
- includePadding?: boolean | undefined;
5
+ full?: boolean | undefined;
6
6
  };
7
7
  events: {
8
8
  click: CustomEvent<any>;
@@ -2,6 +2,8 @@
2
2
  const { width, height, padding } = getContext('LayerCake');
3
3
  /** Include padding area */
4
4
  export let full = false;
5
+ /** Access underlying `<rect>` element */
6
+ export let rectEl;
5
7
  </script>
6
8
 
7
9
  <rect
@@ -10,5 +12,8 @@ export let full = false;
10
12
  width={$width + (full ? $padding.left + $padding.right : 0)}
11
13
  height={$height + (full ? $padding.top + $padding.bottom : 0)}
12
14
  on:click
15
+ on:pointerdown
16
+ on:dblclick
17
+ bind:this={rectEl}
13
18
  {...$$restProps}
14
19
  />
@@ -3,9 +3,12 @@ declare const __propDef: {
3
3
  props: {
4
4
  [x: string]: any;
5
5
  full?: boolean | undefined;
6
+ rectEl: SVGRectElement;
6
7
  };
7
8
  events: {
8
9
  click: MouseEvent;
10
+ pointerdown: PointerEvent;
11
+ dblclick: MouseEvent;
9
12
  } & {
10
13
  [evt: string]: CustomEvent<any>;
11
14
  };
@@ -37,6 +37,7 @@ $: if (center || x != null || y != null) {
37
37
  {transform}
38
38
  {...$$restProps}
39
39
  on:click
40
+ on:pointerdown
40
41
  on:pointerenter
41
42
  on:pointermove
42
43
  on:pointerleave
@@ -14,6 +14,7 @@ declare const __propDef: {
14
14
  };
15
15
  events: {
16
16
  click: MouseEvent;
17
+ pointerdown: PointerEvent;
17
18
  pointerenter: PointerEvent;
18
19
  pointermove: PointerEvent;
19
20
  pointerleave: PointerEvent;
@@ -36,4 +36,5 @@ $: tick().then(() => {
36
36
  on:pointermove
37
37
  on:pointerout
38
38
  on:pointerleave
39
+ on:dblclick
39
40
  />
@@ -24,6 +24,7 @@ declare const __propDef: {
24
24
  pointermove: PointerEvent;
25
25
  pointerout: PointerEvent;
26
26
  pointerleave: PointerEvent;
27
+ dblclick: MouseEvent;
27
28
  } & {
28
29
  [evt: string]: CustomEvent<any>;
29
30
  };
@@ -112,6 +112,14 @@ function showTooltip(e, tooltipData) {
112
112
  const point = localPoint(referenceNode, e);
113
113
  const localX = point?.x ?? 0;
114
114
  const localY = point?.y ?? 0;
115
+ if (e.offsetX < e.currentTarget.offsetLeft ||
116
+ e.offsetX > e.currentTarget.offsetLeft + e.currentTarget.offsetWidth ||
117
+ e.offsetY < e.currentTarget.offsetTop ||
118
+ e.offsetY > e.currentTarget.offsetTop + e.currentTarget.offsetHeight) {
119
+ // Ignore if within padding of chart
120
+ hideTooltip();
121
+ return;
122
+ }
115
123
  // If tooltipData not provided already (voronoi, etc), attempt to find it
116
124
  // TODO: When using bisect-x/y/band, values should be sorted. Tyipcally are for `x`, but not `y` (and band depends on if x or y scale)
117
125
  if (tooltipData == null) {
@@ -267,26 +275,30 @@ $: if (mode === 'bounds' || mode === 'band') {
267
275
  }
268
276
  </script>
269
277
 
270
- <slot tooltip={$tooltip} />
271
-
272
278
  {#if ['bisect-x', 'bisect-y', 'bisect-band', 'quadtree'].includes(mode)}
273
- <Html>
274
- <div
275
- style:width="{$width}px"
276
- style:height="{$height}px"
277
- class={cls(
278
- 'tooltip-trigger absolute touch-none',
279
- debug && 'bg-danger/10 outline outline-danger'
280
- )}
281
- on:pointerenter={showTooltip}
282
- on:pointermove={showTooltip}
283
- on:pointerleave={hideTooltip}
284
- on:click={(e) => {
285
- onClick({ data: $tooltip?.data });
286
- }}
287
- />
288
- </Html>
279
+ <div
280
+ style:width="{$width}px"
281
+ style:height="{$height}px"
282
+ style:top="{$padding.top}px"
283
+ style:left="{$padding.left}px"
284
+ class={cls(
285
+ 'tooltip-trigger absolute touch-none',
286
+ debug && 'bg-danger/10 outline outline-danger'
287
+ )}
288
+ on:pointerenter={showTooltip}
289
+ on:pointermove={showTooltip}
290
+ on:pointerleave={hideTooltip}
291
+ on:click={(e) => {
292
+ onClick({ data: $tooltip?.data });
293
+ }}
294
+ >
295
+ <!-- Rendering slot within tooltip-trigger allows pointer events to bubble up (ex. Brush) -->
296
+ <div class="absolute" style:top="-{$padding.top}px" style:left="-{$padding.left}px">
297
+ <slot tooltip={$tooltip} />
298
+ </div>
299
+ </div>
289
300
  {:else if mode === 'voronoi'}
301
+ <slot tooltip={$tooltip} />
290
302
  <Svg>
291
303
  <Voronoi
292
304
  on:pointerenter={(e) => showTooltip(e.detail.event, e.detail.data)}
@@ -299,6 +311,7 @@ $: if (mode === 'bounds' || mode === 'band') {
299
311
  />
300
312
  </Svg>
301
313
  {:else if mode === 'bounds' || mode === 'band'}
314
+ <slot tooltip={$tooltip} />
302
315
  <Svg>
303
316
  <g class="tooltip-rects">
304
317
  {#each rects as rect}
@@ -318,10 +331,12 @@ $: if (mode === 'bounds' || mode === 'band') {
318
331
  {/each}
319
332
  </g>
320
333
  </Svg>
334
+ {:else}
335
+ <slot tooltip={$tooltip} />
321
336
  {/if}
322
337
 
323
338
  {#if mode === 'quadtree' && debug}
324
- <Svg>
339
+ <Svg pointerEvents={false}>
325
340
  <ChartClipPath>
326
341
  <g class="tooltip-quadtree">
327
342
  {#each quadtreeRects(quadtree, false) as rect}
@@ -31,6 +31,7 @@ function setTransformContext(transform) {
31
31
  import { motionStore, motionFinishHandler } from '../stores/motionStore.js';
32
32
  const { width, height } = getContext('LayerCake');
33
33
  export let mode = 'none';
34
+ /** Translate towards point (ex. mouse cursor/center) while zooming in/out */
34
35
  export let translateOnScale = false;
35
36
  export let spring = undefined;
36
37
  export let tweened = undefined;
@@ -37,7 +37,7 @@ import { motionStore, type MotionOptions } from '../stores/motionStore.js';
37
37
  declare const __propDef: {
38
38
  props: {
39
39
  mode?: TransformMode | undefined;
40
- translateOnScale?: boolean | undefined;
40
+ /** Translate towards point (ex. mouse cursor/center) while zooming in/out */ translateOnScale?: boolean | undefined;
41
41
  spring?: boolean | Parameters<typeof motionStore>[1]['spring'];
42
42
  tweened?: boolean | Parameters<typeof motionStore>[1]['tweened'];
43
43
  processTranslate?: ((x: number, y: number, deltaX: number, deltaY: number, scale: number) => {
@@ -6,6 +6,7 @@ export { default as Axis } from './Axis.svelte';
6
6
  export { default as Bar } from './Bar.svelte';
7
7
  export { default as Bars } from './Bars.svelte';
8
8
  export { default as Blur } from './Blur.svelte';
9
+ export { default as Brush } from './Brush.svelte';
9
10
  export { default as Bounds } from './Bounds.svelte';
10
11
  export { default as Calendar } from './Calendar.svelte';
11
12
  export { default as Canvas } from './layout/Canvas.svelte';
@@ -7,6 +7,7 @@ export { default as Axis } from './Axis.svelte';
7
7
  export { default as Bar } from './Bar.svelte';
8
8
  export { default as Bars } from './Bars.svelte';
9
9
  export { default as Blur } from './Blur.svelte';
10
+ export { default as Brush } from './Brush.svelte';
10
11
  export { default as Bounds } from './Bounds.svelte';
11
12
  export { default as Calendar } from './Calendar.svelte';
12
13
  export { default as Canvas } from './layout/Canvas.svelte';
@@ -8,6 +8,12 @@ export declare function getRandomNumber(min: number, max: number): number;
8
8
  * see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#getting_a_random_integer_between_two_values_inclusive
9
9
  */
10
10
  export declare function getRandomInteger(min: number, max: number, includeMax?: boolean): number;
11
+ /**
12
+ * @see: https://observablehq.com/@d3/d3-cumsum
13
+ */
14
+ export declare function randomWalk(options?: {
15
+ count?: number;
16
+ }): number[];
11
17
  export declare function createSeries(options: {
12
18
  count?: number;
13
19
  min: number;
@@ -1,4 +1,6 @@
1
1
  import { addMinutes, startOfDay, startOfToday, subDays } from 'date-fns';
2
+ import { cumsum } from 'd3-array';
3
+ import { randomNormal } from 'd3-random';
2
4
  import { degreesToRadians, radiansToDegrees } from './math.js';
3
5
  /**
4
6
  * Get random number between min (inclusive) and max (exclusive)
@@ -16,6 +18,13 @@ export function getRandomInteger(min, max, includeMax = true) {
16
18
  max = Math.floor(max);
17
19
  return Math.floor(Math.random() * (max - min + (includeMax ? 1 : 0)) + min);
18
20
  }
21
+ /**
22
+ * @see: https://observablehq.com/@d3/d3-cumsum
23
+ */
24
+ export function randomWalk(options) {
25
+ const random = randomNormal();
26
+ return Array.from(cumsum({ length: options?.count ?? 100 }, random));
27
+ }
19
28
  export function createSeries(options) {
20
29
  const count = options.count ?? 10;
21
30
  const min = options.min;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "author": "Sean Lynch <techniq35@gmail.com>",
5
5
  "license": "MIT",
6
6
  "repository": "techniq/layerchart",
7
- "version": "0.39.0",
7
+ "version": "0.40.1",
8
8
  "devDependencies": {
9
9
  "@changesets/cli": "^2.27.5",
10
10
  "@mdi/js": "^7.4.47",
@@ -58,7 +58,7 @@
58
58
  "typescript": "^5.4.5",
59
59
  "unist-util-visit": "^5.0.0",
60
60
  "us-atlas": "^3.0.1",
61
- "vite": "^5.2.12"
61
+ "vite": "^5.2.13"
62
62
  },
63
63
  "type": "module",
64
64
  "dependencies": {
@@ -85,9 +85,9 @@
85
85
  "date-fns": "^3.6.0",
86
86
  "layercake": "^8.3.0",
87
87
  "lodash-es": "^4.17.21",
88
- "posthog-js": "^1.138.0",
88
+ "posthog-js": "^1.138.3",
89
89
  "shapefile": "^0.6.6",
90
- "svelte-ux": "^0.66.5",
90
+ "svelte-ux": "^0.66.6",
91
91
  "topojson-client": "^3.1.0"
92
92
  },
93
93
  "peerDependencies": {