layerchart 2.0.0-next.3 → 2.0.0-next.5

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.
@@ -118,13 +118,13 @@
118
118
  </script>
119
119
 
120
120
  {#if fill || className}
121
- <Rect {...rect} {fill} class={cls(props?.rect?.class, className)} />
121
+ <Rect {...rect} {...props?.rect} {fill} class={cls(props?.rect?.class, className)} />
122
122
  {/if}
123
123
 
124
124
  {#if gradient}
125
125
  <LinearGradient {...gradient}>
126
126
  {#snippet children({ gradient })}
127
- <Rect {...rect} fill={gradient} class={props?.rect?.class} />
127
+ <Rect {...rect} {...props?.rect} fill={gradient} />
128
128
  {/snippet}
129
129
  </LinearGradient>
130
130
  {/if}
@@ -132,7 +132,7 @@
132
132
  {#if pattern}
133
133
  <Pattern {...pattern}>
134
134
  {#snippet children({ pattern })}
135
- <Rect {...rect} fill={pattern} class={props?.rect?.class} />
135
+ <Rect {...rect} {...props?.rect} fill={pattern} />
136
136
  {/snippet}
137
137
  </Pattern>
138
138
  {/if}
@@ -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 { GeoPermissibleObjects } from 'd3-geo';
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
- <GeoPath
119
- geojson={feature}
120
- class={cls(
121
- layerClass('voronoi-geo-path'),
122
- 'fill-transparent stroke-transparent',
123
- classes.path
124
- )}
125
- onclick={(e) => onclick?.(e, { data: feature.properties.site.data, feature })}
126
- onpointerenter={(e) => onpointerenter?.(e, { data: feature.properties.site.data, feature })}
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: point.data, point })}
150
- onpointerenter={(e) => onpointerenter?.(e, { data: point.data, point })}
151
- onpointermove={(e) => onpointermove?.(e, { data: point.data, point })}
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 { GeoPermissibleObjects } from 'd3-geo';
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>;
@@ -377,7 +377,6 @@
377
377
  {@render childrenProp(snippetProps)}
378
378
  {:else}
379
379
  {@render belowContext?.(snippetProps)}
380
- <!-- TODO: Always use `Svg` until `Pattern` supports `Canvas` (issue #307) -->
381
380
 
382
381
  <Layer
383
382
  type={renderContext}
@@ -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(point.x, point.y, radius);
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
  }}
@@ -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;
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.3",
7
+ "version": "2.0.0-next.5",
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"