layerchart 2.0.0-next.3 → 2.0.0-next.4
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/Voronoi.svelte +51 -33
- package/dist/components/Voronoi.svelte.d.ts +3 -1
- package/dist/components/charts/LineChart.svelte +0 -1
- package/dist/components/tooltip/TooltipContext.svelte +25 -8
- package/dist/components/tooltip/TooltipContext.svelte.d.ts +1 -1
- package/package.json +3 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
2
|
import type { Without } from '../utils/types.js';
|
|
3
|
-
import type { SVGAttributes } from 'svelte/elements';
|
|
4
3
|
|
|
5
4
|
export type VoronoiPropsWithoutHTML = {
|
|
6
5
|
/**
|
|
@@ -8,6 +7,9 @@
|
|
|
8
7
|
*/
|
|
9
8
|
data?: any;
|
|
10
9
|
|
|
10
|
+
/** Radius to clip voronoi cells. `0` or `undefined` to disables clipping */
|
|
11
|
+
r?: number;
|
|
12
|
+
|
|
11
13
|
/**
|
|
12
14
|
* Classes to apply to the root and path elements
|
|
13
15
|
*
|
|
@@ -56,7 +58,7 @@
|
|
|
56
58
|
<script lang="ts">
|
|
57
59
|
import { min } from 'd3-array';
|
|
58
60
|
import { Delaunay } from 'd3-delaunay';
|
|
59
|
-
import type
|
|
61
|
+
import { type GeoPermissibleObjects } from 'd3-geo';
|
|
60
62
|
// @ts-expect-error
|
|
61
63
|
import { geoVoronoi } from 'd3-geo-voronoi';
|
|
62
64
|
import { pointRadial } from 'd3-shape';
|
|
@@ -67,10 +69,13 @@
|
|
|
67
69
|
import Spline from './Spline.svelte';
|
|
68
70
|
import { getChartContext } from './Chart.svelte';
|
|
69
71
|
import { getGeoContext } from './GeoContext.svelte';
|
|
72
|
+
import CircleClipPath from './CircleClipPath.svelte';
|
|
73
|
+
|
|
70
74
|
import { layerClass } from '../utils/attributes.js';
|
|
71
75
|
|
|
72
76
|
let {
|
|
73
77
|
data,
|
|
78
|
+
r,
|
|
74
79
|
classes = {},
|
|
75
80
|
onclick,
|
|
76
81
|
onpointerenter,
|
|
@@ -109,53 +114,66 @@
|
|
|
109
114
|
// Width and/or height can sometimes be negative (when loading data remotely and updately)
|
|
110
115
|
const boundWidth = $derived(Math.max(ctx.width, 0));
|
|
111
116
|
const boundHeight = $derived(Math.max(ctx.height, 0));
|
|
117
|
+
|
|
118
|
+
const disableClip = $derived(r === 0 || r == null || r === Infinity);
|
|
112
119
|
</script>
|
|
113
120
|
|
|
114
121
|
<Group {...restProps} class={cls(layerClass('voronoi-g'), classes.root, className)}>
|
|
115
122
|
{#if geo.projection}
|
|
116
123
|
{@const polygons = geoVoronoi().polygons(points)}
|
|
117
124
|
{#each polygons.features as feature}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
onpointermove={(e) => onpointermove?.(e, { data: feature.properties.site.data, feature })}
|
|
128
|
-
onpointerdown={(e) => onpointerdown?.(e, { data: feature.properties.site.data, feature })}
|
|
129
|
-
{onpointerleave}
|
|
130
|
-
ontouchmove={(e) => {
|
|
131
|
-
// Prevent touch to not interfere with pointer
|
|
132
|
-
e.preventDefault();
|
|
133
|
-
}}
|
|
134
|
-
/>
|
|
135
|
-
{/each}
|
|
136
|
-
{:else}
|
|
137
|
-
{@const voronoi = Delaunay.from(points).voronoi([0, 0, boundWidth, boundHeight])}
|
|
138
|
-
{#each points as point, i}
|
|
139
|
-
{@const pathData = voronoi.renderCell(i)}
|
|
140
|
-
<!-- Wait to render Spline until pathData is available to fix path artifacts from injected tweened points in Spline -->
|
|
141
|
-
{#if pathData}
|
|
142
|
-
<Spline
|
|
143
|
-
{pathData}
|
|
125
|
+
{@const point = r ? geo.projection?.(feature.properties.sitecoordinates) : null}
|
|
126
|
+
<CircleClipPath
|
|
127
|
+
cx={point?.[0]}
|
|
128
|
+
cy={point?.[1]}
|
|
129
|
+
r={r ?? 0}
|
|
130
|
+
disabled={point == null || disableClip}
|
|
131
|
+
>
|
|
132
|
+
<GeoPath
|
|
133
|
+
geojson={feature}
|
|
144
134
|
class={cls(
|
|
145
|
-
layerClass('voronoi-path'),
|
|
135
|
+
layerClass('voronoi-geo-path'),
|
|
146
136
|
'fill-transparent stroke-transparent',
|
|
147
137
|
classes.path
|
|
148
138
|
)}
|
|
149
|
-
onclick={(e) => onclick?.(e, { data:
|
|
150
|
-
onpointerenter={(e) =>
|
|
151
|
-
|
|
139
|
+
onclick={(e) => onclick?.(e, { data: feature.properties.site.data, feature })}
|
|
140
|
+
onpointerenter={(e) =>
|
|
141
|
+
onpointerenter?.(e, { data: feature.properties.site.data, feature })}
|
|
142
|
+
onpointermove={(e) => onpointermove?.(e, { data: feature.properties.site.data, feature })}
|
|
143
|
+
onpointerdown={(e) => onpointerdown?.(e, { data: feature.properties.site.data, feature })}
|
|
152
144
|
{onpointerleave}
|
|
153
|
-
onpointerdown={(e) => onpointerdown?.(e, { data: point.data, point })}
|
|
154
145
|
ontouchmove={(e) => {
|
|
155
146
|
// Prevent touch to not interfere with pointer
|
|
156
147
|
e.preventDefault();
|
|
157
148
|
}}
|
|
158
149
|
/>
|
|
150
|
+
</CircleClipPath>
|
|
151
|
+
{/each}
|
|
152
|
+
{:else}
|
|
153
|
+
{@const voronoi = Delaunay.from(points).voronoi([0, 0, boundWidth, boundHeight])}
|
|
154
|
+
{#each points as point, i}
|
|
155
|
+
{@const pathData = voronoi.renderCell(i)}
|
|
156
|
+
<!-- Wait to render Spline until pathData is available to fix path artifacts from injected tweened points in Spline -->
|
|
157
|
+
{#if pathData}
|
|
158
|
+
<CircleClipPath cx={point[0]} cy={point[1]} r={r ?? 0} disabled={disableClip}>
|
|
159
|
+
<Spline
|
|
160
|
+
{pathData}
|
|
161
|
+
class={cls(
|
|
162
|
+
layerClass('voronoi-path'),
|
|
163
|
+
'fill-transparent stroke-transparent',
|
|
164
|
+
classes.path
|
|
165
|
+
)}
|
|
166
|
+
onclick={(e) => onclick?.(e, { data: point.data, point })}
|
|
167
|
+
onpointerenter={(e) => onpointerenter?.(e, { data: point.data, point })}
|
|
168
|
+
onpointermove={(e) => onpointermove?.(e, { data: point.data, point })}
|
|
169
|
+
{onpointerleave}
|
|
170
|
+
onpointerdown={(e) => onpointerdown?.(e, { data: point.data, point })}
|
|
171
|
+
ontouchmove={(e) => {
|
|
172
|
+
// Prevent touch to not interfere with pointer
|
|
173
|
+
e.preventDefault();
|
|
174
|
+
}}
|
|
175
|
+
/>
|
|
176
|
+
</CircleClipPath>
|
|
159
177
|
{/if}
|
|
160
178
|
{/each}
|
|
161
179
|
{/if}
|
|
@@ -4,6 +4,8 @@ export type VoronoiPropsWithoutHTML = {
|
|
|
4
4
|
* Override data instead of using context
|
|
5
5
|
*/
|
|
6
6
|
data?: any;
|
|
7
|
+
/** Radius to clip voronoi cells. `0` or `undefined` to disables clipping */
|
|
8
|
+
r?: number;
|
|
7
9
|
/**
|
|
8
10
|
* Classes to apply to the root and path elements
|
|
9
11
|
*
|
|
@@ -35,7 +37,7 @@ export type VoronoiPropsWithoutHTML = {
|
|
|
35
37
|
}) => void;
|
|
36
38
|
};
|
|
37
39
|
export type VoronoiProps = VoronoiPropsWithoutHTML & Without<Omit<GroupProps, 'children'>, VoronoiPropsWithoutHTML>;
|
|
38
|
-
import type
|
|
40
|
+
import { type GeoPermissibleObjects } from 'd3-geo';
|
|
39
41
|
import { type GroupProps } from './Group.svelte';
|
|
40
42
|
declare const Voronoi: import("svelte").Component<VoronoiProps, {}, "">;
|
|
41
43
|
type Voronoi = ReturnType<typeof Voronoi>;
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
locked?: boolean;
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* quadtree search radius
|
|
75
|
+
* quadtree search or voronoi clip radius
|
|
76
76
|
* @default Infinity
|
|
77
77
|
*/
|
|
78
78
|
radius?: number;
|
|
@@ -116,11 +116,14 @@
|
|
|
116
116
|
</script>
|
|
117
117
|
|
|
118
118
|
<script lang="ts" generics="TData = any">
|
|
119
|
+
import type { Snippet } from 'svelte';
|
|
119
120
|
import { bisector, max, min } from 'd3-array';
|
|
120
121
|
import { quadtree as d3Quadtree, type Quadtree } from 'd3-quadtree';
|
|
121
122
|
import { sortFunc, localPoint } from '@layerstack/utils';
|
|
122
123
|
import { cls } from '@layerstack/tailwind';
|
|
123
124
|
|
|
125
|
+
import { getChartContext } from '../Chart.svelte';
|
|
126
|
+
import { getGeoContext } from '../GeoContext.svelte';
|
|
124
127
|
import Svg from './../layout/Svg.svelte';
|
|
125
128
|
import Arc from '../Arc.svelte';
|
|
126
129
|
import ChartClipPath from './../ChartClipPath.svelte';
|
|
@@ -130,8 +133,6 @@
|
|
|
130
133
|
import { cartesianToPolar } from '../../utils/math.js';
|
|
131
134
|
import { quadtreeRects } from '../../utils/quadtree.js';
|
|
132
135
|
import { raise } from '../../utils/chart.js';
|
|
133
|
-
import { getChartContext } from '../Chart.svelte';
|
|
134
|
-
import type { Snippet } from 'svelte';
|
|
135
136
|
import {
|
|
136
137
|
getTooltipMetaContext,
|
|
137
138
|
getTooltipPayload,
|
|
@@ -140,6 +141,7 @@
|
|
|
140
141
|
import { layerClass } from '../../utils/attributes.js';
|
|
141
142
|
|
|
142
143
|
const ctx = getChartContext<any>();
|
|
144
|
+
const geoCtx = getGeoContext();
|
|
143
145
|
|
|
144
146
|
let {
|
|
145
147
|
ref: refProp = $bindable(),
|
|
@@ -357,7 +359,11 @@
|
|
|
357
359
|
}
|
|
358
360
|
|
|
359
361
|
case 'quadtree': {
|
|
360
|
-
tooltipData = quadtree?.find(
|
|
362
|
+
tooltipData = quadtree?.find(
|
|
363
|
+
point.x - ctx.padding.left,
|
|
364
|
+
point.y - ctx.padding.top,
|
|
365
|
+
radius
|
|
366
|
+
);
|
|
361
367
|
break;
|
|
362
368
|
}
|
|
363
369
|
}
|
|
@@ -402,11 +408,14 @@
|
|
|
402
408
|
const quadtree: Quadtree<[number, number]> | undefined = $derived.by(() => {
|
|
403
409
|
if (mode === 'quadtree') {
|
|
404
410
|
return d3Quadtree()
|
|
405
|
-
.extent([
|
|
406
|
-
[0, 0],
|
|
407
|
-
[ctx.width, ctx.height],
|
|
408
|
-
])
|
|
409
411
|
.x((d) => {
|
|
412
|
+
if (geoCtx.projection) {
|
|
413
|
+
const lat = ctx.x(d);
|
|
414
|
+
const long = ctx.y(d);
|
|
415
|
+
const geoValue = geoCtx.projection([lat, long]) ?? [0, 0];
|
|
416
|
+
return geoValue[0];
|
|
417
|
+
}
|
|
418
|
+
|
|
410
419
|
const value = ctx.xGet(d);
|
|
411
420
|
|
|
412
421
|
if (Array.isArray(value)) {
|
|
@@ -420,6 +429,13 @@
|
|
|
420
429
|
}
|
|
421
430
|
})
|
|
422
431
|
.y((d) => {
|
|
432
|
+
if (geoCtx.projection) {
|
|
433
|
+
const lat = ctx.x(d);
|
|
434
|
+
const long = ctx.y(d);
|
|
435
|
+
const geoValue = geoCtx.projection([lat, long]) ?? [0, 0];
|
|
436
|
+
return geoValue[1];
|
|
437
|
+
}
|
|
438
|
+
|
|
423
439
|
const value = ctx.yGet(d);
|
|
424
440
|
|
|
425
441
|
if (Array.isArray(value)) {
|
|
@@ -546,6 +562,7 @@
|
|
|
546
562
|
{#if mode === 'voronoi'}
|
|
547
563
|
<Svg>
|
|
548
564
|
<Voronoi
|
|
565
|
+
r={radius}
|
|
549
566
|
onpointerenter={(e, { data }) => {
|
|
550
567
|
showTooltip(e, data);
|
|
551
568
|
}}
|
package/package.json
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
"author": "Sean Lynch <techniq35@gmail.com>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "techniq/layerchart",
|
|
7
|
-
"version": "2.0.0-next.
|
|
7
|
+
"version": "2.0.0-next.4",
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@changesets/cli": "^2.29.2",
|
|
10
|
+
"@iconify-json/lucide": "^1.2.42",
|
|
10
11
|
"@mdi/js": "^7.4.47",
|
|
11
12
|
"@rollup/plugin-dsv": "^3.0.5",
|
|
12
13
|
"@sveltejs/adapter-cloudflare": "^4.9.0",
|
|
@@ -62,6 +63,7 @@
|
|
|
62
63
|
"tslib": "^2.8.1",
|
|
63
64
|
"typescript": "^5.8.3",
|
|
64
65
|
"unist-util-visit": "^5.0.0",
|
|
66
|
+
"unplugin-icons": "^22.1.0",
|
|
65
67
|
"us-atlas": "^3.0.1",
|
|
66
68
|
"vite": "^6.3.4",
|
|
67
69
|
"vitest": "^3.1.2"
|