layerchart 0.92.0 → 0.93.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.
@@ -115,8 +115,8 @@
115
115
 
116
116
  /** Exposed to allow binding in Chart */
117
117
  export let tooltip = writable({
118
- y: 0,
119
118
  x: 0,
119
+ y: 0,
120
120
  data: null as any,
121
121
  show: showTooltip,
122
122
  hide: hideTooltip,
@@ -393,12 +393,12 @@
393
393
  <!-- svelte-ignore a11y-click-events-have-key-events -->
394
394
  <!-- svelte-ignore a11y-no-static-element-interactions -->
395
395
  <div
396
- style:width="{$width}px"
397
- style:height="{$height}px"
398
396
  style:top="{$padding.top}px"
399
397
  style:left="{$padding.left}px"
398
+ style:width="{$width}px"
399
+ style:height="{$height}px"
400
400
  class={cls(
401
- 'tooltip-trigger absolute touch-none',
401
+ 'TooltipContext absolute touch-none',
402
402
  debug && triggerPointerEvents && 'bg-danger/10 outline outline-danger'
403
403
  )}
404
404
  on:pointerenter={triggerPointerEvents ? showTooltip : undefined}
@@ -410,13 +410,13 @@
410
410
  }
411
411
  }}
412
412
  >
413
- <!-- Rendering slot within tooltip-trigger allows pointer events to bubble up (ex. Brush) -->
413
+ <!-- Rendering slot within TooltipContext to allow pointer events to bubble up (ex. Brush) -->
414
414
  <div
415
415
  class="absolute"
416
- style:width="{$containerWidth}px"
417
- style:height="{$containerHeight}px"
418
416
  style:top="-{$padding.top ?? 0}px"
419
417
  style:left="-{$padding.left ?? 0}px"
418
+ style:width="{$containerWidth}px"
419
+ style:height="{$containerHeight}px"
420
420
  >
421
421
  <slot tooltip={$tooltip} />
422
422
 
@@ -30,8 +30,8 @@ declare const __propDef: {
30
30
  data: any;
31
31
  }) => any;
32
32
  /** Exposed to allow binding in Chart */ tooltip?: import("svelte/store").Writable<{
33
- y: number;
34
33
  x: number;
34
+ y: number;
35
35
  data: any;
36
36
  show: (e: PointerEvent, tooltipData?: any) => void;
37
37
  hide: () => void;
@@ -44,8 +44,8 @@ declare const __propDef: {
44
44
  slots: {
45
45
  default: {
46
46
  tooltip: {
47
- y: number;
48
47
  x: number;
48
+ y: number;
49
49
  data: any;
50
50
  show: (e: PointerEvent, tooltipData?: any) => void;
51
51
  hide: () => void;
@@ -29,3 +29,5 @@ export declare function celsiusToFahrenheit(temperature: number): number;
29
29
  export declare function fahrenheitToCelsius(temperature: number): number;
30
30
  /** Parse percent string (`50%`) to decimal (`0.5`) */
31
31
  export declare function parsePercent(percent: string | number): number;
32
+ /** Add second value while maintaining `Date` or `number` type */
33
+ export declare function add(value1: Date | number, value2: number): number | Date;
@@ -55,3 +55,12 @@ export function parsePercent(percent) {
55
55
  return Number(percent.replace('%', '')) / 100;
56
56
  }
57
57
  }
58
+ /** Add second value while maintaining `Date` or `number` type */
59
+ export function add(value1, value2) {
60
+ if (value1 instanceof Date) {
61
+ return new Date(value1.getTime() + value2);
62
+ }
63
+ else {
64
+ return value1 + value2;
65
+ }
66
+ }
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.92.0",
7
+ "version": "0.93.0",
8
8
  "devDependencies": {
9
9
  "@changesets/cli": "^2.27.12",
10
10
  "@mdi/js": "^7.4.47",
@@ -1,418 +0,0 @@
1
- <script lang="ts">
2
- import { type ComponentProps } from 'svelte';
3
- import { extent, min, max } from 'd3-array';
4
- import { clamp } from '@layerstack/utils';
5
- import { cls } from '@layerstack/tailwind';
6
- import { format as formatValue, type FormatType } from '@layerstack/utils';
7
-
8
- import { chartContext } from './ChartContext.svelte';
9
- import Frame from './Frame.svelte';
10
- import Group from './Group.svelte';
11
- import Text from './Text.svelte';
12
-
13
- import { localPoint } from '../utils/event.js';
14
- import type { DomainType } from '../utils/scales.js';
15
- import { asAny } from '../utils/types.js';
16
- import Rect from './Rect.svelte';
17
-
18
- const { xScale, yScale, width, height, padding, config } = chartContext();
19
-
20
- /** Axis to apply brushing */
21
- export let axis: 'x' | 'y' | 'both' = 'x';
22
-
23
- /** Size of draggable handles (width/height) */
24
- export let handleSize = 5;
25
-
26
- /** Only show range while actively brushing. Useful with `brushEnd` event */
27
- export let resetOnEnd = false;
28
-
29
- export let xDomain: DomainType = $xScale.domain() as [number, number];
30
- export let yDomain: DomainType = $yScale.domain() as [number, number];
31
-
32
- export let labels: ComponentProps<Text> | boolean = false;
33
-
34
- /** Mode of operation
35
- * `integrated`: use with single chart
36
- * `separated`: use with separate (typically smaller) chart and state can be managed externally (sync with other charts, etc). Show active selection when domain does not equal original
37
- */
38
- export let mode: 'integrated' | 'separated' = 'integrated';
39
-
40
- // Capture original domains for reset()
41
- const originalXDomain = $config.xDomain;
42
- const originalYDomain = $config.yDomain;
43
-
44
- $: [xDomainMin, xDomainMax] = extent<number>($xScale.domain()) as [number, number];
45
- $: [yDomainMin, yDomainMax] = extent<number>($yScale.domain()) as [number, number];
46
-
47
- /** Attributes passed to range <rect> element */
48
- export let range: Partial<ComponentProps<Rect>> | undefined = undefined;
49
-
50
- /** Attributes passed to handle <rect> elements */
51
- export let handle: Partial<ComponentProps<Rect>> | undefined = undefined;
52
-
53
- /** Apply format to labels, if shown */
54
- export let format: FormatType | undefined = undefined;
55
-
56
- export let classes: {
57
- root?: string;
58
- frame?: string;
59
- range?: string;
60
- handle?: string;
61
- labels?: string;
62
- } = {};
63
-
64
- export let onchange: (detail: { xDomain?: DomainType; yDomain?: DomainType }) => void = () => {};
65
- export let onbrushstart: (detail: {
66
- xDomain?: DomainType;
67
- yDomain?: DomainType;
68
- }) => void = () => {};
69
- export let onbrushend: (detail: {
70
- xDomain?: DomainType;
71
- yDomain?: DomainType;
72
- }) => void = () => {};
73
- export let onreset: (detail: { xDomain?: DomainType; yDomain?: DomainType }) => void = () => {};
74
-
75
- let frameEl: SVGRectElement;
76
-
77
- function handler(
78
- fn: (
79
- start: {
80
- xDomain: [number, number];
81
- yDomain: [number, number];
82
- value: { x: number; y: number };
83
- },
84
- value: { x: number; y: number }
85
- ) => void
86
- ) {
87
- return (e: PointerEvent) => {
88
- const start = {
89
- xDomain: [xDomain?.[0] ?? xDomainMin, xDomain?.[1] ?? xDomainMax] as [number, number],
90
- yDomain: [yDomain?.[0] ?? yDomainMin, yDomain?.[1] ?? yDomainMax] as [number, number],
91
- value: {
92
- x: $xScale.invert?.((localPoint(frameEl, e)?.x ?? 0) - $padding.left),
93
- y: $yScale.invert?.((localPoint(frameEl, e)?.y ?? 0) - $padding.top),
94
- },
95
- };
96
-
97
- onbrushstart({ xDomain, yDomain });
98
-
99
- const onPointerMove = (e: PointerEvent) => {
100
- fn(start, {
101
- x: $xScale.invert?.((localPoint(frameEl, e)?.x ?? 0) - $padding.left),
102
- y: $yScale.invert?.((localPoint(frameEl, e)?.y ?? 0) - $padding.top),
103
- });
104
-
105
- // if (xDomain[0] === xDomain[1] || yDomain[0] === yDomain[1]) {
106
- // // Ignore?
107
- // // TODO: What about when using `x` or `y` axis?
108
- // } else {
109
- onchange({ xDomain, yDomain });
110
- // }
111
- };
112
-
113
- const onPointerUp = (e: PointerEvent) => {
114
- if (e.target === frameEl) {
115
- reset();
116
- onchange({ xDomain, yDomain });
117
- }
118
-
119
- onbrushend({ xDomain, yDomain });
120
-
121
- if (resetOnEnd) {
122
- reset();
123
- }
124
-
125
- window.removeEventListener('pointermove', onPointerMove);
126
- window.removeEventListener('pointerup', onPointerUp);
127
- };
128
-
129
- window.addEventListener('pointermove', onPointerMove);
130
- window.addEventListener('pointerup', onPointerUp);
131
- };
132
- }
133
-
134
- /** Add second value while maintaining `Date` or `number` type */
135
- function add(value1: Date | number, value2: number) {
136
- if (value1 instanceof Date) {
137
- return new Date(value1.getTime() + value2);
138
- } else {
139
- return value1 + value2;
140
- }
141
- }
142
-
143
- const createRange = handler((start, value) => {
144
- isActive = true;
145
-
146
- xDomain = [
147
- // @ts-expect-error
148
- clamp(min([start.value.x, value.x]), xDomainMin, xDomainMax),
149
- // @ts-expect-error
150
- clamp(max([start.value.x, value.x]), xDomainMin, xDomainMax),
151
- ];
152
- // xDomain = [start.value.x, value.x];
153
-
154
- yDomain = [
155
- // @ts-expect-error
156
- clamp(min([start.value.y, value.y]), yDomainMin, yDomainMax),
157
- // @ts-expect-error
158
- clamp(max([start.value.y, value.y]), yDomainMin, yDomainMax),
159
- ];
160
- });
161
-
162
- const adjustRange = handler((start, value) => {
163
- const dx = clamp(
164
- value.x - start.value.x,
165
- xDomainMin - start.xDomain[0],
166
- xDomainMax - start.xDomain[1]
167
- );
168
- xDomain = [add(start.xDomain[0], dx), add(start.xDomain[1], dx)];
169
-
170
- const dy = clamp(
171
- value.y - start.value.y,
172
- yDomainMin - start.yDomain[0],
173
- yDomainMax - start.yDomain[1]
174
- );
175
- yDomain = [add(start.yDomain[0], dy), add(start.yDomain[1], dy)];
176
- });
177
-
178
- const adjustBottom = handler((start, value) => {
179
- yDomain = [
180
- clamp(value.y > start.yDomain[1] ? start.yDomain[1] : value.y, yDomainMin, yDomainMax),
181
- clamp(value.y > start.yDomain[1] ? value.y : start.yDomain[1], yDomainMin, yDomainMax),
182
- ];
183
- });
184
-
185
- const adjustTop = handler((start, value) => {
186
- yDomain = [
187
- clamp(value.y < start.yDomain[1] ? value.y : start.yDomain[0], yDomainMin, yDomainMax),
188
- clamp(value.y < start.yDomain[1] ? start.yDomain[0] : value.y, yDomainMin, yDomainMax),
189
- ];
190
- });
191
-
192
- const adjustLeft = handler((start, value) => {
193
- xDomain = [
194
- clamp(value.x > start.xDomain[1] ? start.xDomain[1] : value.x, xDomainMin, xDomainMax),
195
- clamp(value.x > start.xDomain[1] ? value.x : start.xDomain[1], xDomainMin, xDomainMax),
196
- ];
197
- });
198
-
199
- const adjustRight = handler((start, value) => {
200
- xDomain = [
201
- clamp(value.x < start.xDomain[0] ? value.x : start.xDomain[0], xDomainMin, xDomainMax),
202
- clamp(value.x < start.xDomain[0] ? start.xDomain[0] : value.x, xDomainMin, xDomainMax),
203
- ];
204
- });
205
-
206
- function reset() {
207
- isActive = false;
208
-
209
- xDomain = originalXDomain;
210
- yDomain = originalYDomain;
211
-
212
- onreset({ xDomain, yDomain });
213
- }
214
-
215
- function selectAll() {
216
- xDomain = [xDomainMin, xDomainMax];
217
- yDomain = [yDomainMin, yDomainMax];
218
- }
219
-
220
- $: top = $yScale(yDomain?.[1]);
221
- $: bottom = $yScale(yDomain?.[0]);
222
- $: left = $xScale(xDomain?.[0]);
223
- $: right = $xScale(xDomain?.[1]);
224
-
225
- $: rangeTop = axis === 'both' || axis === 'y' ? top : 0;
226
- $: rangeLeft = axis === 'both' || axis === 'x' ? left : 0;
227
- $: rangeWidth = axis === 'both' || axis === 'x' ? right - left : $width;
228
- $: rangeHeight = axis === 'both' || axis === 'y' ? bottom - top : $height;
229
-
230
- let isActive = false;
231
- $: if (mode === 'separated') {
232
- // Set reactively to handle cases where xDomain/yDomain are set externally (ex. `bind:xDomain`)
233
- isActive =
234
- xDomain?.[0]?.valueOf() !== originalXDomain?.[0]?.valueOf() ||
235
- xDomain?.[1]?.valueOf() !== originalXDomain?.[1]?.valueOf() ||
236
- yDomain?.[0]?.valueOf() !== originalYDomain?.[0]?.valueOf() ||
237
- yDomain?.[1]?.valueOf() !== originalYDomain?.[1]?.valueOf();
238
- }
239
- </script>
240
-
241
- <g class={cls('Brush select-none', classes.root, $$props.class)}>
242
- <Frame
243
- class={cls('frame', 'fill-transparent', classes.frame)}
244
- onpointerdown={createRange}
245
- ondblclick={() => selectAll()}
246
- bind:element={frameEl}
247
- />
248
-
249
- {#if isActive}
250
- <Group x={rangeLeft} y={rangeTop} class="range">
251
- <slot name="range" {rangeWidth} {rangeHeight}>
252
- <Rect
253
- width={rangeWidth}
254
- height={rangeHeight}
255
- class={cls(
256
- 'cursor-move select-none',
257
- range?.fill == null && 'fill-surface-content/10',
258
- classes.range
259
- )}
260
- onpointerdown={adjustRange}
261
- ondblclick={() => reset()}
262
- {...range}
263
- />
264
- </slot>
265
- </Group>
266
-
267
- {#if axis === 'both' || axis === 'y'}
268
- <Group
269
- x={rangeLeft}
270
- y={rangeTop}
271
- class="handle top"
272
- onpointerdown={adjustTop}
273
- ondblclick={() => {
274
- if (yDomain) {
275
- yDomain[0] = yDomainMin;
276
- onchange({ xDomain, yDomain });
277
- }
278
- }}
279
- >
280
- <slot name="handle" edge="top" {rangeWidth} {rangeHeight}>
281
- <Rect
282
- width={rangeWidth}
283
- height={handleSize}
284
- class={cls('fill-transparent cursor-ns-resize select-none', classes.handle)}
285
- {...handle}
286
- />
287
- </slot>
288
- </Group>
289
-
290
- <Group
291
- x={rangeLeft}
292
- y={bottom - handleSize + 1}
293
- class="handle bottom"
294
- onpointerdown={adjustBottom}
295
- ondblclick={() => {
296
- if (yDomain) {
297
- yDomain[1] = yDomainMax;
298
- }
299
- }}
300
- >
301
- <slot name="handle" edge="bottom" {rangeWidth} {rangeHeight}>
302
- <Rect
303
- width={rangeWidth}
304
- height={handleSize}
305
- class={cls('fill-transparent cursor-ns-resize select-none', classes.handle)}
306
- {...handle}
307
- />
308
- </slot>
309
- </Group>
310
- {/if}
311
-
312
- {#if axis === 'both' || axis === 'x'}
313
- <Group
314
- x={rangeLeft}
315
- y={rangeTop}
316
- class="handle left"
317
- onpointerdown={adjustLeft}
318
- ondblclick={() => {
319
- if (xDomain) {
320
- xDomain[0] = xDomainMin;
321
- onchange({ xDomain, yDomain });
322
- }
323
- }}
324
- >
325
- <slot name="handle" edge="left" {rangeWidth} {rangeHeight}>
326
- <rect
327
- width={handleSize}
328
- height={rangeHeight}
329
- class={cls('fill-transparent cursor-ew-resize select-none', classes.handle)}
330
- {...handle}
331
- />
332
- </slot>
333
- </Group>
334
-
335
- <Group
336
- x={right - handleSize + 1}
337
- y={rangeTop}
338
- class="handle right"
339
- onpointerdown={adjustRight}
340
- ondblclick={() => {
341
- if (xDomain) {
342
- xDomain[1] = xDomainMax;
343
- onchange({ xDomain, yDomain });
344
- }
345
- }}
346
- >
347
- <slot name="handle" edge="right" {rangeWidth} {rangeHeight}>
348
- <Rect
349
- width={handleSize}
350
- height={rangeHeight}
351
- class={cls('fill-transparent cursor-ew-resize select-none', classes.handle)}
352
- {...handle}
353
- />
354
- </slot>
355
- </Group>
356
- {/if}
357
-
358
- <slot name="labels">
359
- {#if labels}
360
- {@const labelClass = cls(
361
- 'text-xs',
362
- classes.labels,
363
- typeof labels === 'object' ? labels.class : null
364
- )}
365
-
366
- {#if axis === 'x' || axis === 'both'}
367
- <Text
368
- x={left}
369
- y={rangeTop + rangeHeight / 2}
370
- dx={-4}
371
- textAnchor="end"
372
- verticalAnchor="middle"
373
- value={formatValue(asAny(xDomain?.[0]), format)}
374
- {...typeof labels === 'object' ? labels : null}
375
- class={labelClass}
376
- />
377
-
378
- <Text
379
- x={right}
380
- y={rangeTop + rangeHeight / 2}
381
- dx={4}
382
- textAnchor="start"
383
- verticalAnchor="middle"
384
- value={formatValue(asAny(xDomain?.[1]), format)}
385
- {...typeof labels === 'object' ? labels : null}
386
- class={labelClass}
387
- />
388
- {/if}
389
-
390
- {#if axis === 'y' || axis === 'both'}
391
- <Text
392
- x={rangeLeft + rangeWidth / 2}
393
- y={top}
394
- dy={-4}
395
- textAnchor="middle"
396
- verticalAnchor="end"
397
- value={formatValue(asAny(yDomain?.[1]), format)}
398
- {...typeof labels === 'object' ? labels : null}
399
- class={labelClass}
400
- />
401
-
402
- <Text
403
- x={rangeLeft + rangeWidth / 2}
404
- y={bottom}
405
- dy={4}
406
- textAnchor="middle"
407
- verticalAnchor="start"
408
- value={formatValue(asAny(yDomain?.[0]), format)}
409
- {...typeof labels === 'object' ? labels : null}
410
- class={labelClass}
411
- />
412
- {/if}
413
- {/if}
414
- </slot>
415
-
416
- <!-- TODO: Add diagonal/corner handles -->
417
- {/if}
418
- </g>