layerchart 2.0.0-next.3 → 2.0.0-next.31
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/components/AnnotationPoint.svelte +16 -9
- package/dist/components/AnnotationRange.svelte +3 -3
- package/dist/components/Arc.svelte +2 -2
- package/dist/components/Axis.svelte +83 -29
- package/dist/components/Axis.svelte.d.ts +13 -3
- package/dist/components/Bar.svelte +12 -8
- package/dist/components/Blur.svelte +5 -3
- package/dist/components/Blur.svelte.d.ts +2 -5
- package/dist/components/BrushContext.svelte +1 -1
- package/dist/components/Calendar.svelte +10 -6
- package/dist/components/Calendar.svelte.d.ts +2 -1
- package/dist/components/Chart.svelte +39 -3
- package/dist/components/Chart.svelte.d.ts +11 -0
- package/dist/components/Connector.svelte +2 -2
- package/dist/components/Connector.svelte.d.ts +1 -1
- package/dist/components/Ellipse.svelte +187 -0
- package/dist/components/Ellipse.svelte.d.ts +64 -0
- package/dist/components/ForceSimulation.svelte +184 -50
- package/dist/components/ForceSimulation.svelte.d.ts +88 -21
- package/dist/components/GeoPath.svelte +12 -5
- package/dist/components/GeoPoint.svelte +1 -2
- package/dist/components/GeoSpline.svelte +4 -4
- package/dist/components/GeoSpline.svelte.d.ts +1 -1
- package/dist/components/Group.svelte +2 -2
- package/dist/components/Highlight.svelte +9 -6
- package/dist/components/Hull.svelte +1 -1
- package/dist/components/Labels.svelte +3 -2
- package/dist/components/Labels.svelte.d.ts +2 -2
- package/dist/components/Legend.svelte +19 -12
- package/dist/components/Legend.svelte.d.ts +5 -5
- package/dist/components/MonthPath.svelte +14 -11
- package/dist/components/MonthPath.svelte.d.ts +4 -3
- package/dist/components/Polygon.svelte +285 -0
- package/dist/components/Polygon.svelte.d.ts +115 -0
- package/dist/components/RadialGradient.svelte +1 -3
- package/dist/components/Spline.svelte +30 -18
- package/dist/components/Spline.svelte.d.ts +12 -4
- package/dist/components/Text.svelte +62 -60
- package/dist/components/Text.svelte.d.ts +6 -0
- package/dist/components/TransformControls.svelte +16 -20
- package/dist/components/Treemap.svelte +63 -26
- package/dist/components/Treemap.svelte.d.ts +11 -11
- package/dist/components/Voronoi.svelte +51 -33
- package/dist/components/Voronoi.svelte.d.ts +3 -1
- package/dist/components/charts/ArcChart.svelte +5 -3
- package/dist/components/charts/AreaChart.svelte +11 -11
- package/dist/components/charts/BarChart.svelte +72 -53
- package/dist/components/charts/DefaultTooltip.svelte +1 -1
- package/dist/components/charts/LineChart.svelte +10 -6
- package/dist/components/charts/PieChart.svelte +5 -3
- package/dist/components/charts/ScatterChart.svelte +2 -3
- package/dist/components/charts/utils.svelte.d.ts +2 -2
- package/dist/components/charts/utils.svelte.js +5 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +5 -1
- package/dist/components/layout/Canvas.svelte +67 -49
- package/dist/components/layout/Canvas.svelte.d.ts +6 -0
- package/dist/components/layout/Layer.svelte +6 -4
- package/dist/components/layout/Layer.svelte.d.ts +6 -4
- package/dist/components/tooltip/Tooltip.svelte +14 -7
- package/dist/components/tooltip/TooltipContext.svelte +136 -43
- package/dist/components/tooltip/TooltipContext.svelte.d.ts +3 -3
- package/dist/components/tooltip/TooltipHeader.svelte +5 -4
- package/dist/components/tooltip/TooltipHeader.svelte.d.ts +3 -3
- package/dist/components/tooltip/TooltipItem.svelte +5 -4
- package/dist/components/tooltip/TooltipItem.svelte.d.ts +3 -3
- package/dist/components/tooltip/TooltipList.svelte +1 -1
- package/dist/components/tooltip/tooltipMetaContext.d.ts +2 -2
- package/dist/docs/Blockquote.svelte +6 -4
- package/dist/docs/Blockquote.svelte.d.ts +4 -19
- package/dist/docs/Code.svelte +20 -12
- package/dist/docs/Code.svelte.d.ts +9 -23
- package/dist/docs/Header1.svelte +4 -2
- package/dist/docs/Header1.svelte.d.ts +4 -28
- package/dist/docs/Json.svelte +11 -3
- package/dist/docs/Json.svelte.d.ts +9 -21
- package/dist/docs/Layout.svelte +10 -7
- package/dist/docs/Layout.svelte.d.ts +4 -19
- package/dist/docs/Link.svelte +7 -3
- package/dist/docs/Link.svelte.d.ts +4 -38
- package/dist/docs/Preview.svelte +6 -3
- package/dist/docs/TilesetField.svelte +20 -19
- package/dist/docs/TilesetField.svelte.d.ts +5 -22
- package/dist/docs/ViewSourceButton.svelte +9 -6
- package/dist/docs/ViewSourceButton.svelte.d.ts +7 -21
- package/dist/utils/arcText.svelte.js +4 -4
- package/dist/utils/array.d.ts +11 -0
- package/dist/utils/array.js +23 -0
- package/dist/utils/array.test.d.ts +1 -0
- package/dist/utils/array.test.js +200 -0
- package/dist/utils/canvas.d.ts +77 -0
- package/dist/utils/canvas.js +105 -41
- package/dist/utils/genData.d.ts +14 -0
- package/dist/utils/genData.js +24 -6
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/path.d.ts +10 -0
- package/dist/utils/path.js +30 -0
- package/dist/utils/rect.svelte.d.ts +2 -2
- package/dist/utils/rect.svelte.js +69 -1
- package/dist/utils/scales.svelte.d.ts +3 -2
- package/dist/utils/scales.svelte.js +7 -3
- package/dist/utils/shape.d.ts +43 -0
- package/dist/utils/shape.js +59 -0
- package/dist/utils/string.d.ts +49 -0
- package/dist/utils/string.js +4 -2
- package/dist/utils/ticks.d.ts +15 -4
- package/dist/utils/ticks.js +144 -158
- package/dist/utils/ticks.test.js +11 -16
- package/dist/utils/treemap.d.ts +1 -1
- package/package.json +27 -25
- package/dist/utils/object.js +0 -2
|
@@ -64,6 +64,13 @@
|
|
|
64
64
|
*/
|
|
65
65
|
ignoreTransform?: boolean;
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Disable the hit canvas (useful when animations are playing)
|
|
69
|
+
*
|
|
70
|
+
* @default false
|
|
71
|
+
*/
|
|
72
|
+
disableHitCanvas?: boolean;
|
|
73
|
+
|
|
67
74
|
/**
|
|
68
75
|
* Show the hit canvas for debugging purposes.
|
|
69
76
|
*
|
|
@@ -144,13 +151,13 @@
|
|
|
144
151
|
import { onMount, untrack, type Snippet } from 'svelte';
|
|
145
152
|
import { cls } from '@layerstack/tailwind';
|
|
146
153
|
import { Logger, localPoint } from '@layerstack/utils';
|
|
147
|
-
import {
|
|
154
|
+
import { MediaQueryPresets } from '@layerstack/svelte-state';
|
|
148
155
|
|
|
149
156
|
import { setRenderContext } from '../Chart.svelte';
|
|
150
157
|
import { getTransformContext } from '../TransformContext.svelte';
|
|
151
158
|
import { getPixelColor, scaleCanvas, type ComputedStylesOptions } from '../../utils/canvas.js';
|
|
152
159
|
import { getColorStr, rgbColorGenerator } from '../../utils/color.js';
|
|
153
|
-
import { Context } from 'runed';
|
|
160
|
+
import { Context, useMutationObserver, watch } from 'runed';
|
|
154
161
|
import type {
|
|
155
162
|
HTMLCanvasAttributes,
|
|
156
163
|
MouseEventHandler,
|
|
@@ -171,6 +178,7 @@
|
|
|
171
178
|
fallback,
|
|
172
179
|
center = false,
|
|
173
180
|
ignoreTransform = false,
|
|
181
|
+
disableHitCanvas = false,
|
|
174
182
|
class: className,
|
|
175
183
|
children,
|
|
176
184
|
onclick,
|
|
@@ -256,6 +264,23 @@
|
|
|
256
264
|
* end HitCanvas
|
|
257
265
|
*/
|
|
258
266
|
|
|
267
|
+
// Invalidate/redraw if color scheme changes, either via browser `prefers-color-scheme` (including emulation) or by changing `<html class="dark">` or `<html data-theme="...">`
|
|
268
|
+
const { dark } = new MediaQueryPresets();
|
|
269
|
+
watch(
|
|
270
|
+
() => dark.current,
|
|
271
|
+
() => {
|
|
272
|
+
canvasContext.invalidate();
|
|
273
|
+
}
|
|
274
|
+
);
|
|
275
|
+
useMutationObserver(
|
|
276
|
+
() => document.documentElement,
|
|
277
|
+
() => canvasContext.invalidate(),
|
|
278
|
+
{
|
|
279
|
+
attributes: true,
|
|
280
|
+
attributeFilter: ['class', 'data-theme'],
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
|
|
259
284
|
onMount(() => {
|
|
260
285
|
context = ref?.getContext('2d', { willReadFrequently }) as CanvasRenderingContext2D;
|
|
261
286
|
|
|
@@ -263,22 +288,7 @@
|
|
|
263
288
|
willReadFrequently: false, // Explicitly set to `false` to resolve pixel artifacts between fill and stroke with the same color (issue #372)
|
|
264
289
|
}) as CanvasRenderingContext2D;
|
|
265
290
|
|
|
266
|
-
// Invalidate/redraw if color scheme changes, either via browser `prefers-color-scheme` (including emulation) or by changing `<html class="dark">` or `<html data-theme="...">`
|
|
267
|
-
darkColorScheme.subscribe(() => {
|
|
268
|
-
canvasContext.invalidate();
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
const observer = new MutationObserver(() => {
|
|
272
|
-
canvasContext.invalidate();
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
observer.observe(document.documentElement, {
|
|
276
|
-
attributes: true,
|
|
277
|
-
attributeFilter: ['class', 'data-theme'],
|
|
278
|
-
});
|
|
279
|
-
|
|
280
291
|
return () => {
|
|
281
|
-
observer.disconnect();
|
|
282
292
|
if (frameId) {
|
|
283
293
|
cancelAnimationFrame(frameId);
|
|
284
294
|
}
|
|
@@ -336,45 +346,53 @@
|
|
|
336
346
|
context.restore();
|
|
337
347
|
}
|
|
338
348
|
|
|
339
|
-
|
|
349
|
+
/*
|
|
350
|
+
* Sync hit canvas with main canvas
|
|
351
|
+
*/
|
|
340
352
|
if (hitCanvasContext) {
|
|
341
|
-
// scale hit canvas to match main canvas
|
|
342
|
-
scaleCanvas(hitCanvasContext, ctx.containerWidth, ctx.containerHeight);
|
|
343
|
-
hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);
|
|
344
|
-
|
|
345
|
-
// reset and sync transform to the state after retainState components
|
|
346
|
-
hitCanvasContext.resetTransform();
|
|
347
|
-
hitCanvasContext.setTransform(mainTransformAfterRetain);
|
|
348
|
-
|
|
349
|
-
// reset color generator
|
|
350
|
-
colorGenerator = rgbColorGenerator();
|
|
351
|
-
|
|
352
353
|
const inactiveMoving = !activeCanvas && transformCtx.moving;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
354
|
+
if (disableHitCanvas || transformCtx.dragging || inactiveMoving) {
|
|
355
|
+
// Skip rendering hit canvas
|
|
356
|
+
hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);
|
|
357
|
+
} else {
|
|
358
|
+
// scale hit canvas to match main canvas
|
|
359
|
+
scaleCanvas(hitCanvasContext, ctx.containerWidth, ctx.containerHeight);
|
|
360
|
+
hitCanvasContext.clearRect(0, 0, ctx.containerWidth, ctx.containerHeight);
|
|
361
|
+
|
|
362
|
+
// reset and sync transform to the state after retainState components
|
|
363
|
+
hitCanvasContext.resetTransform();
|
|
364
|
+
hitCanvasContext.setTransform(mainTransformAfterRetain);
|
|
365
|
+
|
|
366
|
+
// reset color generator
|
|
367
|
+
colorGenerator = rgbColorGenerator();
|
|
368
|
+
|
|
369
|
+
// render retainState components on hit canvas (e.g., Group)
|
|
370
|
+
for (const c of retainStateComponents) {
|
|
371
|
+
const componentHasEvents =
|
|
372
|
+
c.events && Object.values(c.events).filter((d) => d).length > 0;
|
|
373
|
+
|
|
374
|
+
if (componentHasEvents) {
|
|
375
|
+
// since the transform was already applied via setTransform, skip rendering
|
|
376
|
+
// the retainState component's transform again; proceed to its children
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
362
379
|
}
|
|
363
|
-
}
|
|
364
380
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
381
|
+
// render non-retainState components on hit canvas
|
|
382
|
+
for (const c of nonRetainStateComponents) {
|
|
383
|
+
const componentHasEvents =
|
|
384
|
+
c.events && Object.values(c.events).filter((d) => d).length > 0;
|
|
368
385
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
386
|
+
if (componentHasEvents) {
|
|
387
|
+
const color = getColorStr(colorGenerator.next().value);
|
|
388
|
+
const styleOverrides = { styles: { fill: color, stroke: color, _fillOpacity: 0.1 } };
|
|
372
389
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
390
|
+
hitCanvasContext.save();
|
|
391
|
+
c.render(hitCanvasContext, styleOverrides);
|
|
392
|
+
hitCanvasContext.restore();
|
|
376
393
|
|
|
377
|
-
|
|
394
|
+
componentByColor.set(color, c);
|
|
395
|
+
}
|
|
378
396
|
}
|
|
379
397
|
}
|
|
380
398
|
}
|
|
@@ -55,6 +55,12 @@ export type CanvasPropsWithoutHTML = {
|
|
|
55
55
|
* @default false
|
|
56
56
|
*/
|
|
57
57
|
ignoreTransform?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Disable the hit canvas (useful when animations are playing)
|
|
60
|
+
*
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
disableHitCanvas?: boolean;
|
|
58
64
|
/**
|
|
59
65
|
* Show the hit canvas for debugging purposes.
|
|
60
66
|
*
|
|
@@ -3,17 +3,19 @@
|
|
|
3
3
|
|
|
4
4
|
export type CanvasLayerProps = {
|
|
5
5
|
type: 'canvas';
|
|
6
|
-
} & Omit<ComponentProps<typeof Canvas>, 'type'>;
|
|
6
|
+
} & Omit<ComponentProps<typeof Canvas>, 'type' | 'onpointermove'>;
|
|
7
7
|
|
|
8
8
|
export type HtmlLayerProps = {
|
|
9
9
|
type: 'html';
|
|
10
|
-
} & Omit<ComponentProps<typeof Html>, 'type'>;
|
|
10
|
+
} & Omit<ComponentProps<typeof Html>, 'type' | 'onpointermove'>;
|
|
11
11
|
|
|
12
12
|
export type SvgLayerProps = {
|
|
13
13
|
type: 'svg';
|
|
14
|
-
} & Omit<ComponentProps<typeof Svg>, 'type'>;
|
|
14
|
+
} & Omit<ComponentProps<typeof Svg>, 'type' | 'onpointermove'>;
|
|
15
15
|
|
|
16
|
-
export type LayerProps = CanvasLayerProps | HtmlLayerProps | SvgLayerProps
|
|
16
|
+
export type LayerProps = (CanvasLayerProps | HtmlLayerProps | SvgLayerProps) & {
|
|
17
|
+
onpointermove?: (e: PointerEvent) => void;
|
|
18
|
+
};
|
|
17
19
|
</script>
|
|
18
20
|
|
|
19
21
|
<script lang="ts">
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import type { ComponentProps } from 'svelte';
|
|
2
2
|
export type CanvasLayerProps = {
|
|
3
3
|
type: 'canvas';
|
|
4
|
-
} & Omit<ComponentProps<typeof Canvas>, 'type'>;
|
|
4
|
+
} & Omit<ComponentProps<typeof Canvas>, 'type' | 'onpointermove'>;
|
|
5
5
|
export type HtmlLayerProps = {
|
|
6
6
|
type: 'html';
|
|
7
|
-
} & Omit<ComponentProps<typeof Html>, 'type'>;
|
|
7
|
+
} & Omit<ComponentProps<typeof Html>, 'type' | 'onpointermove'>;
|
|
8
8
|
export type SvgLayerProps = {
|
|
9
9
|
type: 'svg';
|
|
10
|
-
} & Omit<ComponentProps<typeof Svg>, 'type'>;
|
|
11
|
-
export type LayerProps = CanvasLayerProps | HtmlLayerProps | SvgLayerProps
|
|
10
|
+
} & Omit<ComponentProps<typeof Svg>, 'type' | 'onpointermove'>;
|
|
11
|
+
export type LayerProps = (CanvasLayerProps | HtmlLayerProps | SvgLayerProps) & {
|
|
12
|
+
onpointermove?: (e: PointerEvent) => void;
|
|
13
|
+
};
|
|
12
14
|
import Canvas from './Canvas.svelte';
|
|
13
15
|
import Html from './Html.svelte';
|
|
14
16
|
import Svg from './Svg.svelte';
|
|
@@ -359,13 +359,8 @@
|
|
|
359
359
|
{#if tooltipCtx.data}
|
|
360
360
|
<div
|
|
361
361
|
{...props.root}
|
|
362
|
-
class={cls(
|
|
363
|
-
|
|
364
|
-
'absolute z-50 select-none',
|
|
365
|
-
!pointerEvents && 'pointer-events-none',
|
|
366
|
-
classes.root,
|
|
367
|
-
props.root?.class
|
|
368
|
-
)}
|
|
362
|
+
class={cls('root', layerClass('tooltip-root'), classes.root, props.root?.class)}
|
|
363
|
+
class:pointer-events-none={!pointerEvents}
|
|
369
364
|
style:top="{motionY.current}px"
|
|
370
365
|
style:left="{motionX.current}px"
|
|
371
366
|
transition:fade={{ duration: 100 }}
|
|
@@ -408,3 +403,15 @@
|
|
|
408
403
|
</div>
|
|
409
404
|
</div>
|
|
410
405
|
{/if}
|
|
406
|
+
|
|
407
|
+
<style>
|
|
408
|
+
.root {
|
|
409
|
+
position: absolute;
|
|
410
|
+
z-index: 50;
|
|
411
|
+
user-select: none;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.pointer-events-none {
|
|
415
|
+
pointer-events: none;
|
|
416
|
+
}
|
|
417
|
+
</style>
|
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
const _TooltipContext = new Context<TooltipContextValue>('TooltipContext');
|
|
7
7
|
|
|
8
8
|
type TooltipMode =
|
|
9
|
-
| 'bisect-x'
|
|
10
|
-
| 'bisect-y'
|
|
9
|
+
| 'bisect-x' // requires values to be sorted
|
|
10
|
+
| 'bisect-y' // requires values to be sorted
|
|
11
11
|
| 'band'
|
|
12
|
-
| 'bisect-band'
|
|
12
|
+
| 'bisect-band' // requires values to be sorted
|
|
13
13
|
| 'bounds'
|
|
14
14
|
| 'voronoi'
|
|
15
15
|
| 'quadtree'
|
|
16
|
+
| 'quadtree-x' // ignores y values (constant 0)
|
|
17
|
+
| 'quadtree-y' // ignores x values (constant 0)
|
|
16
18
|
| 'manual';
|
|
17
19
|
|
|
18
20
|
export type TooltipContextValue<T = any> = {
|
|
@@ -20,7 +22,11 @@
|
|
|
20
22
|
y: number;
|
|
21
23
|
data: T | null;
|
|
22
24
|
payload: TooltipPayload[];
|
|
23
|
-
show(
|
|
25
|
+
show(
|
|
26
|
+
e: PointerEvent | MouseEvent | TouchEvent,
|
|
27
|
+
tooltipData?: any,
|
|
28
|
+
payload?: TooltipPayload
|
|
29
|
+
): void;
|
|
24
30
|
hide(e?: PointerEvent): void;
|
|
25
31
|
mode: TooltipMode;
|
|
26
32
|
isHoveringTooltipArea: boolean;
|
|
@@ -72,7 +78,7 @@
|
|
|
72
78
|
locked?: boolean;
|
|
73
79
|
|
|
74
80
|
/**
|
|
75
|
-
* quadtree search radius
|
|
81
|
+
* quadtree search or voronoi clip radius
|
|
76
82
|
* @default Infinity
|
|
77
83
|
*/
|
|
78
84
|
radius?: number;
|
|
@@ -116,22 +122,23 @@
|
|
|
116
122
|
</script>
|
|
117
123
|
|
|
118
124
|
<script lang="ts" generics="TData = any">
|
|
125
|
+
import type { Snippet } from 'svelte';
|
|
119
126
|
import { bisector, max, min } from 'd3-array';
|
|
120
127
|
import { quadtree as d3Quadtree, type Quadtree } from 'd3-quadtree';
|
|
121
128
|
import { sortFunc, localPoint } from '@layerstack/utils';
|
|
122
129
|
import { cls } from '@layerstack/tailwind';
|
|
123
130
|
|
|
131
|
+
import { getChartContext } from '../Chart.svelte';
|
|
132
|
+
import { getGeoContext } from '../GeoContext.svelte';
|
|
124
133
|
import Svg from './../layout/Svg.svelte';
|
|
125
134
|
import Arc from '../Arc.svelte';
|
|
126
135
|
import ChartClipPath from './../ChartClipPath.svelte';
|
|
127
136
|
import Voronoi from './../Voronoi.svelte';
|
|
128
137
|
|
|
129
|
-
import { isScaleBand, scaleInvert } from '../../utils/scales.svelte.js';
|
|
138
|
+
import { isScaleBand, isScaleTime, scaleInvert } from '../../utils/scales.svelte.js';
|
|
130
139
|
import { cartesianToPolar } from '../../utils/math.js';
|
|
131
140
|
import { quadtreeRects } from '../../utils/quadtree.js';
|
|
132
141
|
import { raise } from '../../utils/chart.js';
|
|
133
|
-
import { getChartContext } from '../Chart.svelte';
|
|
134
|
-
import type { Snippet } from 'svelte';
|
|
135
142
|
import {
|
|
136
143
|
getTooltipMetaContext,
|
|
137
144
|
getTooltipPayload,
|
|
@@ -140,6 +147,7 @@
|
|
|
140
147
|
import { layerClass } from '../../utils/attributes.js';
|
|
141
148
|
|
|
142
149
|
const ctx = getChartContext<any>();
|
|
150
|
+
const geoCtx = getGeoContext();
|
|
143
151
|
|
|
144
152
|
let {
|
|
145
153
|
ref: refProp = $bindable(),
|
|
@@ -268,7 +276,7 @@
|
|
|
268
276
|
}
|
|
269
277
|
}
|
|
270
278
|
|
|
271
|
-
function showTooltip(e: PointerEvent, tooltipData?: any) {
|
|
279
|
+
function showTooltip(e: PointerEvent | MouseEvent | TouchEvent, tooltipData?: any) {
|
|
272
280
|
// Cancel hiding tooltip if from previous event loop
|
|
273
281
|
if (hideTimeoutId) {
|
|
274
282
|
clearTimeout(hideTimeoutId);
|
|
@@ -296,7 +304,6 @@
|
|
|
296
304
|
}
|
|
297
305
|
|
|
298
306
|
// If tooltipData not provided already (voronoi, etc), attempt to find it
|
|
299
|
-
// TODO: When using bisect-x/y/band, values should be sorted. Typically they are for `x`, but not `y` (and band depends on if x or y scale)
|
|
300
307
|
if (tooltipData == null) {
|
|
301
308
|
switch (mode) {
|
|
302
309
|
case 'bisect-x': {
|
|
@@ -309,6 +316,7 @@
|
|
|
309
316
|
xValueAtPoint = scaleInvert(ctx.xScale, point.x - ctx.padding.left);
|
|
310
317
|
}
|
|
311
318
|
|
|
319
|
+
// Requires values to be sorted
|
|
312
320
|
const index = bisectX(ctx.flatData, xValueAtPoint, 1);
|
|
313
321
|
const previousValue = ctx.flatData[index - 1];
|
|
314
322
|
const currentValue = ctx.flatData[index];
|
|
@@ -320,6 +328,7 @@
|
|
|
320
328
|
// `y` value at pointer coordinate
|
|
321
329
|
const yValueAtPoint = scaleInvert(ctx.yScale, point.y - ctx.padding.top);
|
|
322
330
|
|
|
331
|
+
// Requires values to be sorted
|
|
323
332
|
const index = bisectY(ctx.flatData, yValueAtPoint, 1);
|
|
324
333
|
const previousValue = ctx.flatData[index - 1];
|
|
325
334
|
const currentValue = ctx.flatData[index];
|
|
@@ -337,6 +346,7 @@
|
|
|
337
346
|
const bandData = ctx.flatData
|
|
338
347
|
.filter((d) => ctx.x(d) === xValueAtPoint)
|
|
339
348
|
.sort(sortFunc(ctx.y as () => any)); // sort for bisect
|
|
349
|
+
// Requires values to be sorted
|
|
340
350
|
const index = bisectY(bandData, yValueAtPoint, 1);
|
|
341
351
|
const previousValue = bandData[index - 1];
|
|
342
352
|
const currentValue = bandData[index];
|
|
@@ -346,6 +356,7 @@
|
|
|
346
356
|
const bandData = ctx.flatData
|
|
347
357
|
.filter((d) => ctx.y(d) === yValueAtPoint)
|
|
348
358
|
.sort(sortFunc(ctx.x as () => any)); // sort for bisect
|
|
359
|
+
// Requires values to be sorted
|
|
349
360
|
const index = bisectX(bandData, xValueAtPoint, 1);
|
|
350
361
|
const previousValue = bandData[index - 1];
|
|
351
362
|
const currentValue = bandData[index];
|
|
@@ -356,8 +367,14 @@
|
|
|
356
367
|
break;
|
|
357
368
|
}
|
|
358
369
|
|
|
370
|
+
case 'quadtree-x':
|
|
371
|
+
case 'quadtree-y':
|
|
359
372
|
case 'quadtree': {
|
|
360
|
-
tooltipData = quadtree?.find(
|
|
373
|
+
tooltipData = quadtree?.find(
|
|
374
|
+
point.x - ctx.padding.left,
|
|
375
|
+
point.y - ctx.padding.top,
|
|
376
|
+
radius
|
|
377
|
+
);
|
|
361
378
|
break;
|
|
362
379
|
}
|
|
363
380
|
}
|
|
@@ -400,13 +417,20 @@
|
|
|
400
417
|
}
|
|
401
418
|
|
|
402
419
|
const quadtree: Quadtree<[number, number]> | undefined = $derived.by(() => {
|
|
403
|
-
if (
|
|
420
|
+
if (['quadtree', 'quadtree-x', 'quadtree-y'].includes(mode)) {
|
|
404
421
|
return d3Quadtree()
|
|
405
|
-
.extent([
|
|
406
|
-
[0, 0],
|
|
407
|
-
[ctx.width, ctx.height],
|
|
408
|
-
])
|
|
409
422
|
.x((d) => {
|
|
423
|
+
if (mode === 'quadtree-y') {
|
|
424
|
+
return 0;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (geoCtx.projection) {
|
|
428
|
+
const lat = ctx.x(d);
|
|
429
|
+
const long = ctx.y(d);
|
|
430
|
+
const geoValue = geoCtx.projection([lat, long]) ?? [0, 0];
|
|
431
|
+
return geoValue[0];
|
|
432
|
+
}
|
|
433
|
+
|
|
410
434
|
const value = ctx.xGet(d);
|
|
411
435
|
|
|
412
436
|
if (Array.isArray(value)) {
|
|
@@ -420,6 +444,17 @@
|
|
|
420
444
|
}
|
|
421
445
|
})
|
|
422
446
|
.y((d) => {
|
|
447
|
+
if (mode === 'quadtree-x') {
|
|
448
|
+
return 0;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (geoCtx.projection) {
|
|
452
|
+
const lat = ctx.x(d);
|
|
453
|
+
const long = ctx.y(d);
|
|
454
|
+
const geoValue = geoCtx.projection([lat, long]) ?? [0, 0];
|
|
455
|
+
return geoValue[1];
|
|
456
|
+
}
|
|
457
|
+
|
|
423
458
|
const value = ctx.yGet(d);
|
|
424
459
|
|
|
425
460
|
if (Array.isArray(value)) {
|
|
@@ -458,14 +493,63 @@
|
|
|
458
493
|
const fullHeight = max(ctx.yRange) - min(ctx.yRange);
|
|
459
494
|
|
|
460
495
|
if (mode === 'band') {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
496
|
+
if (isScaleBand(ctx.xScale)) {
|
|
497
|
+
// full band width/height regardless of value
|
|
498
|
+
return {
|
|
499
|
+
x: x - xOffset,
|
|
500
|
+
y: min(ctx.yRange),
|
|
501
|
+
width: ctx.xScale.step(),
|
|
502
|
+
height: fullHeight,
|
|
503
|
+
data: d,
|
|
504
|
+
};
|
|
505
|
+
} else if (isScaleBand(ctx.yScale)) {
|
|
506
|
+
return {
|
|
507
|
+
x: min(ctx.xRange),
|
|
508
|
+
y: y - yOffset,
|
|
509
|
+
width: fullWidth,
|
|
510
|
+
height: ctx.yScale.step(),
|
|
511
|
+
data: d,
|
|
512
|
+
};
|
|
513
|
+
} else if (isScaleTime(ctx.xScale)) {
|
|
514
|
+
// Find width to next data point
|
|
515
|
+
const index = ctx.flatData.findIndex(
|
|
516
|
+
(d2) => Number(ctx.x(d2)) === Number(ctx.x(d))
|
|
517
|
+
);
|
|
518
|
+
const isLastPoint = index + 1 === ctx.flatData.length;
|
|
519
|
+
const nextDataPoint = isLastPoint
|
|
520
|
+
? max(ctx.xDomain)
|
|
521
|
+
: ctx.x(ctx.flatData[index + 1]);
|
|
522
|
+
|
|
523
|
+
return {
|
|
524
|
+
x: x - xOffset,
|
|
525
|
+
y: min(ctx.yRange),
|
|
526
|
+
width: (ctx.xScale(nextDataPoint) ?? 0) - (xValue ?? 0),
|
|
527
|
+
height: fullHeight,
|
|
528
|
+
data: d,
|
|
529
|
+
};
|
|
530
|
+
} else if (isScaleTime(ctx.yScale)) {
|
|
531
|
+
// Find height to next data point
|
|
532
|
+
const index = ctx.flatData.findIndex(
|
|
533
|
+
(d2) => Number(ctx.y(d2)) === Number(ctx.y(d))
|
|
534
|
+
);
|
|
535
|
+
const isLastPoint = index + 1 === ctx.flatData.length;
|
|
536
|
+
const nextDataPoint = isLastPoint
|
|
537
|
+
? max(ctx.yDomain)
|
|
538
|
+
: ctx.y(ctx.flatData[index + 1]);
|
|
539
|
+
|
|
540
|
+
return {
|
|
541
|
+
x: min(ctx.xRange),
|
|
542
|
+
y: y - yOffset,
|
|
543
|
+
width: fullWidth,
|
|
544
|
+
height: (ctx.yScale(nextDataPoint) ?? 0) - (yValue ?? 0),
|
|
545
|
+
data: d,
|
|
546
|
+
};
|
|
547
|
+
} else {
|
|
548
|
+
console.warn(
|
|
549
|
+
'[layerchart] TooltipContext band mode requires at least one scale to be band or time.'
|
|
550
|
+
);
|
|
551
|
+
return undefined;
|
|
552
|
+
}
|
|
469
553
|
} else if (mode === 'bounds') {
|
|
470
554
|
return {
|
|
471
555
|
x: isScaleBand(ctx.xScale) || Array.isArray(xValue) ? x - xOffset : min(ctx.xRange),
|
|
@@ -493,8 +577,26 @@
|
|
|
493
577
|
});
|
|
494
578
|
|
|
495
579
|
const triggerPointerEvents = $derived(
|
|
496
|
-
['bisect-x', 'bisect-y', 'bisect-band', 'quadtree'].includes(mode)
|
|
580
|
+
['bisect-x', 'bisect-y', 'bisect-band', 'quadtree', 'quadtree-x', 'quadtree-y'].includes(mode)
|
|
497
581
|
);
|
|
582
|
+
|
|
583
|
+
function onPointerEnter(e: PointerEvent | MouseEvent | TouchEvent) {
|
|
584
|
+
isHoveringTooltipArea = true;
|
|
585
|
+
if (triggerPointerEvents) {
|
|
586
|
+
showTooltip(e);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function onPointerMove(e: PointerEvent | MouseEvent | TouchEvent) {
|
|
591
|
+
if (triggerPointerEvents) {
|
|
592
|
+
showTooltip(e);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function onPointerLeave(e: PointerEvent | MouseEvent | TouchEvent) {
|
|
597
|
+
isHoveringTooltipArea = false;
|
|
598
|
+
hideTooltip();
|
|
599
|
+
}
|
|
498
600
|
</script>
|
|
499
601
|
|
|
500
602
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
@@ -505,25 +607,15 @@
|
|
|
505
607
|
style:height="{ctx.height}px"
|
|
506
608
|
class={cls(
|
|
507
609
|
layerClass('tooltip-context'),
|
|
508
|
-
'absolute
|
|
610
|
+
'absolute',
|
|
509
611
|
debug && triggerPointerEvents && 'bg-danger/10 outline outline-danger'
|
|
510
612
|
)}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
onpointermove={(e) => {
|
|
518
|
-
if (triggerPointerEvents) {
|
|
519
|
-
showTooltip(e);
|
|
520
|
-
}
|
|
521
|
-
}}
|
|
522
|
-
onpointerleave={(e) => {
|
|
523
|
-
isHoveringTooltipArea = false;
|
|
524
|
-
|
|
525
|
-
hideTooltip();
|
|
526
|
-
}}
|
|
613
|
+
onmouseenter={onPointerEnter}
|
|
614
|
+
ontouchstart={onPointerEnter}
|
|
615
|
+
onmousemove={onPointerMove}
|
|
616
|
+
ontouchmove={onPointerMove}
|
|
617
|
+
onmouseleave={onPointerLeave}
|
|
618
|
+
ontouchend={onPointerLeave}
|
|
527
619
|
onclick={(e) => {
|
|
528
620
|
// Ignore clicks without data (triggered from Legend clicks, for example)
|
|
529
621
|
if (triggerPointerEvents && tooltipContext.data != null) {
|
|
@@ -546,6 +638,7 @@
|
|
|
546
638
|
{#if mode === 'voronoi'}
|
|
547
639
|
<Svg>
|
|
548
640
|
<Voronoi
|
|
641
|
+
r={radius}
|
|
549
642
|
onpointerenter={(e, { data }) => {
|
|
550
643
|
showTooltip(e, data);
|
|
551
644
|
}}
|
|
@@ -621,7 +714,7 @@
|
|
|
621
714
|
{/each}
|
|
622
715
|
</g>
|
|
623
716
|
</Svg>
|
|
624
|
-
{:else if
|
|
717
|
+
{:else if ['quadtree', 'quadtree-x', 'quadtree-y'].includes(mode) && debug}
|
|
625
718
|
<Svg pointerEvents={false}>
|
|
626
719
|
<ChartClipPath>
|
|
627
720
|
<g class={layerClass('tooltip-quadtree-g')}>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
2
|
import type { Without } from '../../utils/types.js';
|
|
3
|
-
type TooltipMode = 'bisect-x' | 'bisect-y' | 'band' | 'bisect-band' | 'bounds' | 'voronoi' | 'quadtree' | 'manual';
|
|
3
|
+
type TooltipMode = 'bisect-x' | 'bisect-y' | 'band' | 'bisect-band' | 'bounds' | 'voronoi' | 'quadtree' | 'quadtree-x' | 'quadtree-y' | 'manual';
|
|
4
4
|
export type TooltipContextValue<T = any> = {
|
|
5
5
|
x: number;
|
|
6
6
|
y: number;
|
|
7
7
|
data: T | null;
|
|
8
8
|
payload: TooltipPayload[];
|
|
9
|
-
show(e: PointerEvent, tooltipData?: any, payload?: TooltipPayload): void;
|
|
9
|
+
show(e: PointerEvent | MouseEvent | TouchEvent, tooltipData?: any, payload?: TooltipPayload): void;
|
|
10
10
|
hide(e?: PointerEvent): void;
|
|
11
11
|
mode: TooltipMode;
|
|
12
12
|
isHoveringTooltipArea: boolean;
|
|
@@ -36,7 +36,7 @@ type TooltipContextPropsWithoutHTML<T = any> = {
|
|
|
36
36
|
*/
|
|
37
37
|
locked?: boolean;
|
|
38
38
|
/**
|
|
39
|
-
* quadtree search radius
|
|
39
|
+
* quadtree search or voronoi clip radius
|
|
40
40
|
* @default Infinity
|
|
41
41
|
*/
|
|
42
42
|
radius?: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
3
|
import type { Snippet } from 'svelte';
|
|
4
|
-
import type
|
|
4
|
+
import { asAny, type Without } from '../../utils/types.js';
|
|
5
5
|
|
|
6
6
|
export type TooltipHeaderPropsWithoutHTML = {
|
|
7
7
|
/**
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
/**
|
|
14
14
|
* The format to use when displaying the value.
|
|
15
15
|
*/
|
|
16
|
-
format?: FormatType;
|
|
16
|
+
format?: FormatType | FormatConfig;
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* The color to use for the color dot.
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
</script>
|
|
55
55
|
|
|
56
56
|
<script lang="ts">
|
|
57
|
-
import { format as formatUtil, type FormatType } from '@layerstack/utils';
|
|
57
|
+
import { format as formatUtil, type FormatType, type FormatConfig } from '@layerstack/utils';
|
|
58
58
|
import { cls } from '@layerstack/tailwind';
|
|
59
59
|
import { layerClass } from '../../utils/attributes.js';
|
|
60
60
|
|
|
@@ -114,6 +114,7 @@
|
|
|
114
114
|
{#if children}
|
|
115
115
|
{@render children?.()}
|
|
116
116
|
{:else}
|
|
117
|
-
|
|
117
|
+
<!-- @ts-expect-error - improve types -->
|
|
118
|
+
{format ? formatUtil(value, asAny(format)) : value}
|
|
118
119
|
{/if}
|
|
119
120
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
-
import type
|
|
3
|
+
import { type Without } from '../../utils/types.js';
|
|
4
4
|
export type TooltipHeaderPropsWithoutHTML = {
|
|
5
5
|
/**
|
|
6
6
|
* The value to display in the tooltip header when the `children`
|
|
@@ -10,7 +10,7 @@ export type TooltipHeaderPropsWithoutHTML = {
|
|
|
10
10
|
/**
|
|
11
11
|
* The format to use when displaying the value.
|
|
12
12
|
*/
|
|
13
|
-
format?: FormatType;
|
|
13
|
+
format?: FormatType | FormatConfig;
|
|
14
14
|
/**
|
|
15
15
|
* The color to use for the color dot.
|
|
16
16
|
*/
|
|
@@ -40,7 +40,7 @@ export type TooltipHeaderPropsWithoutHTML = {
|
|
|
40
40
|
children?: Snippet;
|
|
41
41
|
};
|
|
42
42
|
export type TooltipHeaderProps = TooltipHeaderPropsWithoutHTML & Without<HTMLAttributes<HTMLElement>, TooltipHeaderPropsWithoutHTML>;
|
|
43
|
-
import { type FormatType } from '@layerstack/utils';
|
|
43
|
+
import { type FormatType, type FormatConfig } from '@layerstack/utils';
|
|
44
44
|
declare const TooltipHeader: import("svelte").Component<TooltipHeaderProps, {}, "ref" | "colorRef">;
|
|
45
45
|
type TooltipHeader = ReturnType<typeof TooltipHeader>;
|
|
46
46
|
export default TooltipHeader;
|