layerchart 0.30.1 → 0.31.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.
@@ -1,15 +1,17 @@
1
1
  <script>import { getContext } from 'svelte';
2
- import { area as d3Area } from 'd3-shape';
2
+ import { area as d3Area, areaRadial } from 'd3-shape';
3
3
  import { max } from 'd3-array';
4
4
  import { interpolatePath } from 'd3-interpolate-path';
5
5
  import { cls } from 'svelte-ux';
6
6
  import { motionStore } from '../stores/motionStore';
7
7
  import Spline from './Spline.svelte';
8
- const { data: contextData, xGet, yGet, yRange } = getContext('LayerCake');
8
+ const { data: contextData, xScale, yScale, xGet, yGet, yRange } = getContext('LayerCake');
9
9
  /** Override data instead of using context */
10
10
  export let data = undefined;
11
11
  /** Pass `<path d={...} />` explicitly instead of calculating from data / context */
12
12
  export let pathData = undefined;
13
+ /** Use radial instead of cartesian area generator, mapping `x` to `angle` and `y0`/`y1 to `innerRadius`/`outerRadius. Radial lines are positioned relative to the origin, use transform (ex. `<Group center>`) to change the origin */
14
+ export let radial = false;
13
15
  /** Override x accessor */
14
16
  export let x = undefined; // TODO: Update Type
15
17
  /** Override y0 accessor. Defaults to max($yRange) */
@@ -26,10 +28,15 @@ export let line = false;
26
28
  $: tweenedOptions = tweened ? { interpolate: interpolatePath, ...tweened } : false;
27
29
  $: tweened_d = motionStore('', { tweened: tweenedOptions });
28
30
  $: {
29
- const path = d3Area()
30
- .x(x ?? $xGet)
31
- .y0(y0 ?? max($yRange))
32
- .y1(y1 ?? $yGet);
31
+ const path = radial
32
+ ? areaRadial()
33
+ .angle((d) => (x ? $xScale(x(d)) : $xGet(d)))
34
+ .innerRadius((d) => (y0 ? $yScale(y0(d)) : max($yRange)))
35
+ .outerRadius((d) => (y1 ? $yScale(y1(d)) : $yGet(d)))
36
+ : d3Area()
37
+ .x((d) => (x ? $xScale(x(d)) : $xGet(d)))
38
+ .y0((d) => (y0 ? $yScale(y0(d)) : max($yRange)))
39
+ .y1((d) => (y1 ? $yScale(y1(d)) : $yGet(d)));
33
40
  if (curve)
34
41
  path.curve(curve);
35
42
  if (defined)
@@ -40,7 +47,7 @@ $: {
40
47
  </script>
41
48
 
42
49
  {#if line}
43
- <Spline {data} {curve} {defined} {tweened} {...typeof line === 'object' ? line : null} />
50
+ <Spline {data} y={y1} {curve} {defined} {tweened} {...typeof line === 'object' ? line : null} />
44
51
  {/if}
45
52
 
46
53
  <path
@@ -7,6 +7,7 @@ declare const __propDef: {
7
7
  [x: string]: any;
8
8
  data?: any;
9
9
  pathData?: string | undefined | null;
10
+ radial?: boolean | undefined;
10
11
  x?: any;
11
12
  y0?: any;
12
13
  y1?: any;
@@ -18,6 +19,7 @@ declare const __propDef: {
18
19
  [x: string]: any;
19
20
  data?: any;
20
21
  pathData?: string | null | undefined;
22
+ radial?: boolean | undefined;
21
23
  x?: any;
22
24
  y?: any;
23
25
  tweened?: boolean | import("svelte/motion").TweenedOptions<unknown> | undefined;
@@ -1,7 +1,7 @@
1
1
  <script>import { getContext } from 'svelte';
2
2
  import Area from './Area.svelte';
3
3
  import Spline from './Spline.svelte';
4
- const { data, yScale, rGet } = getContext('LayerCake');
4
+ const { data, rGet } = getContext('LayerCake');
5
5
  export let curve = undefined;
6
6
  export let defined = undefined;
7
7
  export let opacity = 0.3;
@@ -16,7 +16,7 @@ $: lineData = [...$data].reverse();
16
16
  {#each lineData as seriesData}
17
17
  <Spline
18
18
  data={seriesData}
19
- y={(d) => $yScale(d[1])}
19
+ y={(d) => d[1]}
20
20
  stroke={$rGet(seriesData)}
21
21
  {curve}
22
22
  {defined}
@@ -27,17 +27,19 @@ $: lineData = [...$data].reverse();
27
27
  </g>
28
28
  {/if}
29
29
 
30
- <g class="area-group">
31
- {#each $data as seriesData}
32
- <Area
33
- data={seriesData}
34
- y0={(d) => $yScale(d[0])}
35
- y1={(d) => $yScale(d[1])}
36
- fill={$rGet(seriesData)}
37
- {curve}
38
- {defined}
39
- {opacity}
40
- {tweened}
41
- />
42
- {/each}
43
- </g>
30
+ <slot data={$data}>
31
+ <g class="area-group">
32
+ {#each $data as seriesData}
33
+ <Area
34
+ data={seriesData}
35
+ y0={(d) => d[0]}
36
+ y1={(d) => d[1]}
37
+ fill={$rGet(seriesData)}
38
+ {curve}
39
+ {defined}
40
+ {opacity}
41
+ {tweened}
42
+ />
43
+ {/each}
44
+ </g>
45
+ </slot>
@@ -14,7 +14,11 @@ declare const __propDef: {
14
14
  events: {
15
15
  [evt: string]: CustomEvent<any>;
16
16
  };
17
- slots: {};
17
+ slots: {
18
+ default: {
19
+ data: any;
20
+ };
21
+ };
18
22
  };
19
23
  export type AreaStackProps = typeof __propDef.props;
20
24
  export type AreaStackEvents = typeof __propDef.events;
@@ -1,6 +1,7 @@
1
1
  <script>import { getContext } from 'svelte';
2
2
  import { format as formatValue, cls } from 'svelte-ux';
3
3
  import { extent } from 'd3-array';
4
+ import { pointRadial } from 'd3-shape';
4
5
  import Text from './Text.svelte';
5
6
  import { isScaleBand } from '../utils/scales';
6
7
  const { xScale, yScale, xRange, yRange, width } = getContext('LayerCake');
@@ -16,8 +17,15 @@ export let ticks = placement === 'left' || placement === 'right' ? 4 : undefined
16
17
  export let tickSize = 4;
17
18
  export let format = undefined;
18
19
  export let labelProps = undefined;
19
- $: orientation = ['top', 'bottom'].includes(placement) ? 'horizontal' : 'vertical';
20
- $: scale = orientation === 'horizontal' ? $xScale : $yScale;
20
+ $: orientation =
21
+ placement === 'angle'
22
+ ? 'angle'
23
+ : placement === 'radius'
24
+ ? 'radius'
25
+ : ['top', 'bottom'].includes(placement)
26
+ ? 'horizontal'
27
+ : 'vertical';
28
+ $: scale = ['horizontal', 'angle'].includes(orientation) ? $xScale : $yScale;
21
29
  $: [xRangeMin, xRangeMax] = extent($xRange);
22
30
  $: [yRangeMin, yRangeMax] = extent($yRange);
23
31
  $: tickVals = Array.isArray(ticks)
@@ -47,9 +55,19 @@ function getCoords(tick) {
47
55
  x: xRangeMax,
48
56
  y: $yScale(tick) + (isScaleBand($yScale) ? $yScale.bandwidth() / 2 : 0),
49
57
  };
58
+ case 'angle':
59
+ return {
60
+ x: $xScale(tick),
61
+ y: yRangeMax,
62
+ };
63
+ case 'radius':
64
+ return {
65
+ x: xRangeMin,
66
+ y: $yScale(tick),
67
+ };
50
68
  }
51
69
  }
52
- function getDefaultLabelProps() {
70
+ function getDefaultLabelProps(tick) {
53
71
  switch (placement) {
54
72
  case 'top':
55
73
  return {
@@ -77,6 +95,21 @@ function getDefaultLabelProps() {
77
95
  dx: 4,
78
96
  dy: -2, // manually adjusted until Text supports custom styles
79
97
  };
98
+ case 'angle':
99
+ const xValue = $xScale(tick);
100
+ return {
101
+ textAnchor: xValue === 0 || xValue === Math.PI ? 'middle' : xValue > Math.PI ? 'end' : 'start',
102
+ verticalAnchor: 'middle',
103
+ dx: 0,
104
+ dy: -2, // manually adjusted until Text supports custom styles
105
+ };
106
+ case 'radius':
107
+ return {
108
+ textAnchor: 'middle',
109
+ verticalAnchor: 'middle',
110
+ dx: 2,
111
+ dy: -2, // manually adjusted until Text supports custom styles
112
+ };
80
113
  }
81
114
  }
82
115
  </script>
@@ -105,10 +138,22 @@ function getDefaultLabelProps() {
105
138
  class={cls('rule stroke-surface-content/50', lineProps?.class)}
106
139
  />
107
140
  {/if}
141
+
142
+ <!-- TODO: angle rule? -->
143
+
144
+ {#if orientation === 'radius'}
145
+ <circle
146
+ r={$yRange[0] || 0}
147
+ {...lineProps}
148
+ class={cls('rule stroke-surface-content/20 fill-none', lineProps?.class)}
149
+ />
150
+ {/if}
108
151
  {/if}
109
152
 
110
153
  {#each tickVals as tick, i}
111
154
  {@const tickCoords = getCoords(tick)}
155
+ {@const radialTickCoords = pointRadial(tickCoords.x, tickCoords.y)}
156
+
112
157
  <g>
113
158
  {#if grid !== false}
114
159
  {@const lineProps = typeof grid === 'object' ? grid : null}
@@ -130,6 +175,24 @@ function getDefaultLabelProps() {
130
175
  {...lineProps}
131
176
  class={cls('grid stroke-surface-content/10', lineProps?.class)}
132
177
  />
178
+ {:else if orientation === 'angle'}
179
+ {@const [x1, y1] = pointRadial(tickCoords.x, yRangeMin)}
180
+ {@const [x2, y2] = pointRadial(tickCoords.x, yRangeMax)}
181
+
182
+ <line
183
+ {x1}
184
+ {y1}
185
+ {x2}
186
+ {y2}
187
+ {...lineProps}
188
+ class={cls('test grid stroke-surface-content/10', lineProps?.class)}
189
+ />
190
+ {:else if orientation === 'radius'}
191
+ <circle
192
+ r={tickCoords.y}
193
+ {...lineProps}
194
+ class={cls('grid stroke-surface-content/10 fill-none', lineProps?.class)}
195
+ />
133
196
  {/if}
134
197
  {/if}
135
198
 
@@ -153,10 +216,10 @@ function getDefaultLabelProps() {
153
216
  {/if}
154
217
 
155
218
  <Text
156
- x={tickCoords.x}
157
- y={tickCoords.y}
158
- value={formatValue(tick, format ?? scale.tickFormat?.())}
159
- {...getDefaultLabelProps()}
219
+ x={orientation === 'angle' ? radialTickCoords[0] : tickCoords.x}
220
+ y={orientation === 'angle' ? radialTickCoords[1] : tickCoords.y}
221
+ value={formatValue(tick, format ?? scale.tickFormat?.() ?? ((v) => v))}
222
+ {...getDefaultLabelProps(tick)}
160
223
  {...labelProps}
161
224
  class={cls(
162
225
  'label text-[10px] stroke-surface-100 [stroke-width:2px] font-light',
@@ -4,7 +4,7 @@ import type { SVGAttributes } from 'svelte/elements';
4
4
  import Text from './Text.svelte';
5
5
  declare const __propDef: {
6
6
  props: {
7
- /** Location of axis */ placement: 'top' | 'bottom' | 'left' | 'right';
7
+ /** Location of axis */ placement: 'top' | 'bottom' | 'left' | 'right' | 'angle' | 'radius';
8
8
  /** Draw a rule line. Use Rule component for greater rendering order control */ rule?: boolean | SVGAttributes<SVGLineElement> | undefined;
9
9
  /** Draw a grid lines */ grid?: boolean | SVGAttributes<SVGLineElement> | undefined;
10
10
  /** Control the number of ticks*/ ticks?: number | any[] | Function | undefined;
@@ -79,9 +79,13 @@ export let geo = undefined;
79
79
  let:width
80
80
  let:element
81
81
  let:xScale
82
+ let:xGet
82
83
  let:yScale
84
+ let:yGet
83
85
  let:zScale
86
+ let:zGet
84
87
  let:rScale
88
+ let:rGet
85
89
  let:padding
86
90
  let:data
87
91
  let:flatData
@@ -100,9 +104,13 @@ export let geo = undefined;
100
104
  {projection}
101
105
  {tooltip}
102
106
  {xScale}
107
+ {xGet}
103
108
  {yScale}
109
+ {yGet}
104
110
  {zScale}
111
+ {zGet}
105
112
  {rScale}
113
+ {rGet}
106
114
  {padding}
107
115
  {data}
108
116
  {flatData}
@@ -118,9 +126,13 @@ export let geo = undefined;
118
126
  {element}
119
127
  {projection}
120
128
  {xScale}
129
+ {xGet}
121
130
  {yScale}
131
+ {yGet}
122
132
  {zScale}
133
+ {zGet}
123
134
  {rScale}
135
+ {rGet}
124
136
  {padding}
125
137
  {data}
126
138
  {flatData}
@@ -32,9 +32,13 @@ declare const __propDef: {
32
32
  element: Element;
33
33
  projection: import("d3-geo").GeoProjection | import("d3-geo").GeoIdentityTransform;
34
34
  xScale: any;
35
+ xGet: any;
35
36
  yScale: any;
37
+ yGet: any;
36
38
  zScale: any;
39
+ zGet: any;
37
40
  rScale: any;
41
+ rGet: any;
38
42
  padding: {
39
43
  top: number;
40
44
  right: number;
@@ -23,7 +23,7 @@ $: tick().then(() => {
23
23
  cx={$tweened_cx}
24
24
  cy={$tweened_cy}
25
25
  r={$tweened_r}
26
- class={cls($$props.fill === undefined && 'fill-surface-content')}
26
+ class={cls($$props.fill == null && 'fill-surface-content')}
27
27
  {...$$restProps}
28
28
  on:click
29
29
  on:mousemove
@@ -4,11 +4,14 @@ import { notNull } from 'svelte-ux';
4
4
  import Circle from './Circle.svelte';
5
5
  import Link from './Link.svelte';
6
6
  import { isScaleBand } from '../utils/scales';
7
+ import { pointRadial } from 'd3-shape';
7
8
  const context = getContext('LayerCake');
8
9
  const { data, xGet, y, yGet, xScale, yScale, rGet, config } = context;
9
10
  export let r = 5;
10
11
  export let offsetX = undefined;
11
12
  export let offsetY = undefined;
13
+ /** Use radial instead of cartesian line generator, mapping `x` to `angle` and `y` to `radius`. Radial points are positioned relative to the origin, use transform (ex. `<Group center>`) to change the origin */
14
+ export let radial = false;
12
15
  /** Enable showing links between related points (array x/y accessors) */
13
16
  export let links = false;
14
17
  function getOffset(value, offset, scale) {
@@ -18,7 +21,7 @@ function getOffset(value, offset, scale) {
18
21
  else if (offset != null) {
19
22
  return offset;
20
23
  }
21
- else if (isScaleBand(scale)) {
24
+ else if (isScaleBand(scale) && !radial) {
22
25
  return scale.bandwidth() / 2;
23
26
  }
24
27
  else {
@@ -123,9 +126,10 @@ $: _links = $data.flatMap((d) => {
123
126
 
124
127
  <g class="point-group">
125
128
  {#each points as point}
129
+ {@const radialPoint = pointRadial(point.x, point.y)}
126
130
  <Circle
127
- cx={point.x}
128
- cy={point.y}
131
+ cx={radial ? radialPoint[0] : point.x}
132
+ cy={radial ? radialPoint[1] : point.y}
129
133
  {r}
130
134
  fill={$config.r ? $rGet(point.data) : null}
131
135
  {...$$restProps}
@@ -5,6 +5,7 @@ declare const __propDef: {
5
5
  r?: number | undefined;
6
6
  offsetX?: number | ((value: number, context: any) => number) | undefined;
7
7
  offsetY?: number | ((value: number, context: any) => number) | undefined;
8
+ radial?: boolean | undefined;
8
9
  links?: boolean | Partial<{
9
10
  [x: string]: any;
10
11
  data?: any;
@@ -29,7 +29,7 @@ $: tick().then(() => {
29
29
  y={$tweened_y}
30
30
  width={$tweened_width}
31
31
  height={$tweened_height}
32
- class={cls($$props.fill === undefined && 'fill-surface-content')}
32
+ class={cls($$props.fill == null && 'fill-surface-content')}
33
33
  {...$$restProps}
34
34
  on:click
35
35
  on:mouseover
@@ -3,20 +3,22 @@ import { writable } from 'svelte/store';
3
3
  import { tweened as tweenedStore } from 'svelte/motion';
4
4
  import { draw as _drawTransition } from 'svelte/transition';
5
5
  import { cubicInOut } from 'svelte/easing';
6
- import { line as d3Line } from 'd3-shape';
6
+ import { line as d3Line, lineRadial } from 'd3-shape';
7
7
  // import { interpolateString } from 'd3-interpolate';
8
8
  import { interpolatePath } from 'd3-interpolate-path';
9
9
  import { cls } from 'svelte-ux';
10
10
  import { motionStore } from '../stores/motionStore';
11
11
  import Group from './Group.svelte';
12
- const { data: contextData, xGet, yGet } = getContext('LayerCake');
12
+ const { data: contextData, xScale, yScale, xGet, yGet } = getContext('LayerCake');
13
13
  /** Override data instead of using context */
14
14
  export let data = undefined;
15
15
  /** Pass `<path d={...} />` explicitly instead of calculating from data / context */
16
16
  export let pathData = undefined;
17
- /** Override x accessor */
17
+ /** Use radial instead of cartesian line generator, mapping `x` to `angle` and `y` to `radius`. Radial lines are positioned relative to the origin, use transform (ex. `<Group center>`) to change the origin */
18
+ export let radial = false;
19
+ /** Override `x` accessor from Chart context. Applies to `angle` when `radial=true` */
18
20
  export let x = undefined; // TODO: Update Type
19
- /** Override y accessor */
21
+ /** Override `y` accessor from Chart context. Applies to `radius` when `radial=true` */
20
22
  export let y = undefined; // TODO: Update Type
21
23
  /** Interpolate path data using d3-interpolate-path. Works best without `draw` enabled */
22
24
  export let tweened = undefined;
@@ -37,9 +39,13 @@ let d = '';
37
39
  $: tweenedOptions = tweened ? { interpolate: interpolatePath, ...tweened } : false;
38
40
  $: tweened_d = motionStore('', { tweened: tweenedOptions });
39
41
  $: {
40
- const path = d3Line()
41
- .x(x ?? $xGet)
42
- .y(y ?? $yGet);
42
+ const path = radial
43
+ ? lineRadial()
44
+ .angle((d) => (x ? $xScale(x(d)) : $xGet(d)))
45
+ .radius((d) => (y ? $yScale(y(d)) : $yGet(d)))
46
+ : d3Line()
47
+ .x((d) => (x ? $xScale(x(d)) : $xGet(d)))
48
+ .y((d) => (y ? $yScale(y(d)) : $yGet(d)));
43
49
  if (curve)
44
50
  path.curve(curve);
45
51
  if (defined)
@@ -7,6 +7,7 @@ declare const __propDef: {
7
7
  [x: string]: any;
8
8
  data?: any;
9
9
  pathData?: string | undefined | null;
10
+ radial?: boolean | undefined;
10
11
  x?: any;
11
12
  y?: any;
12
13
  tweened?: boolean | Parameters<typeof tweenedStore>[1];
@@ -15,7 +15,7 @@ export let yOffset = typeof x === 'number' || typeof y === 'number' ? 0 : 10;
15
15
  export let anchor = 'top-left';
16
16
  export let contained = 'container'; // TODO: Support 'window' using getBoundingClientRect()
17
17
  export let animate = true;
18
- export let variant = 'dark';
18
+ export let variant = 'default';
19
19
  export let header = undefined;
20
20
  export let classes = {};
21
21
  const { padding, xGet, yGet, containerWidth, containerHeight } = getContext('LayerCake');
@@ -115,13 +115,13 @@ $: if ($tooltip?.data) {
115
115
  ['[&_.value]:text-sm [&_.value]:tabular-nums'],
116
116
  ],
117
117
  {
118
- dark: [
119
- 'bg-gray-900/90 backdrop-filter backdrop-blur-[2px] text-white',
120
- '[&_.label]:text-white/75',
118
+ default: [
119
+ 'bg-surface-100/90 dark:bg-surface-300/90 backdrop-filter backdrop-blur-[2px] text-surface-content',
120
+ '[&_.label]:text-surface-content/75',
121
121
  ],
122
- light: [
123
- 'bg-white text-gray-800 border border-gray-500',
124
- '[&_.label]:text-surface-content/50',
122
+ invert: [
123
+ 'bg-surface-content/90 backdrop-filter backdrop-blur-[2px] text-surface-100 border border-surface-content',
124
+ '[&_.label]:text-surface-100/50',
125
125
  ],
126
126
  none: '',
127
127
  }[variant],
@@ -9,7 +9,7 @@ declare const __propDef: {
9
9
  anchor?: ("center" | "top" | "bottom" | "left" | "right" | "top-left" | "top-right" | "bottom-left" | "bottom-right") | undefined;
10
10
  contained?: false | "container" | undefined;
11
11
  animate?: boolean | undefined;
12
- variant?: "none" | "dark" | "light" | undefined;
12
+ variant?: "default" | "none" | "invert" | undefined;
13
13
  header?: ((data: any) => any) | undefined;
14
14
  classes?: {
15
15
  root?: string | undefined;
@@ -18,11 +18,11 @@ function setTooltipContext(tooltip) {
18
18
  <script>import { raise } from 'layercake';
19
19
  import { writable } from 'svelte/store';
20
20
  import { bisector, max, min } from 'd3-array';
21
- import { Delaunay } from 'd3-delaunay';
22
21
  import { quadtree as d3Quadtree } from 'd3-quadtree';
23
- import { sortFunc } from 'svelte-ux';
22
+ import { cls, sortFunc } from 'svelte-ux';
24
23
  import { Svg, Html } from './Chart.svelte';
25
24
  import ChartClipPath from './ChartClipPath.svelte';
25
+ import Voronoi from './Voronoi.svelte';
26
26
  import { localPoint } from '../utils/event';
27
27
  import { isScaleBand, scaleInvert } from '../utils/scales';
28
28
  import { quadtreeRects } from '../utils/quadtree';
@@ -187,20 +187,6 @@ function hideTooltip() {
187
187
  $tooltip = { ...$tooltip, data: null };
188
188
  });
189
189
  }
190
- let points;
191
- let voronoi;
192
- $: if (mode === 'voronoi') {
193
- points = $flatData.map((d) => {
194
- const xValue = $xGet(d);
195
- const yValue = $yGet(d);
196
- const x = Array.isArray(xValue) ? min(xValue) : xValue;
197
- const y = Array.isArray(yValue) ? min(yValue) : yValue;
198
- const point = [x, y];
199
- point.data = d;
200
- return point;
201
- });
202
- voronoi = Delaunay.from(points).voronoi([0, 0, Math.max($width, 0), Math.max($height, 0)]); // width and/or height can sometimes be negative (when loading data remotely and updately)
203
- }
204
190
  let quadtree;
205
191
  $: if (mode === 'quadtree') {
206
192
  quadtree = d3Quadtree()
@@ -287,10 +273,9 @@ $: if (mode === 'bounds' || mode === 'band') {
287
273
  {#if ['bisect-x', 'bisect-y', 'bisect-band', 'quadtree'].includes(mode)}
288
274
  <Html>
289
275
  <div
290
- class="tooltip-trigger absolute"
291
276
  style:width="{$width}px"
292
277
  style:height="{$height}px"
293
- style:background={debug ? 'rgba(255 0 0 / .1)' : undefined}
278
+ class={cls('tooltip-trigger absolute', debug && 'bg-danger/10 outline outline-danger')}
294
279
  on:touchstart={showTooltip}
295
280
  on:touchmove={showTooltip}
296
281
  on:mousemove={showTooltip}
@@ -302,21 +287,14 @@ $: if (mode === 'bounds' || mode === 'band') {
302
287
  </Html>
303
288
  {:else if mode === 'voronoi'}
304
289
  <Svg>
305
- {#each points as point, i}
306
- <g class="tooltip-voronoi">
307
- <path
308
- d={voronoi.renderCell(i)}
309
- style:fill={debug ? 'red' : 'transparent'}
310
- style:fill-opacity={debug ? 0.1 : 0}
311
- style:stroke={debug ? 'red' : 'transparent'}
312
- on:mousemove={(e) => showTooltip(e, point.data)}
313
- on:mouseleave={hideTooltip}
314
- on:click={(e) => {
315
- onClick({ data: point.data });
316
- }}
317
- />
318
- </g>
319
- {/each}
290
+ <Voronoi
291
+ on:mousemove={(e) => showTooltip(e.detail.event, e.detail.point.data)}
292
+ on:mouseleave={hideTooltip}
293
+ on:click={(e) => {
294
+ onClick({ data: e.detail.point.data });
295
+ }}
296
+ classes={{ path: cls(debug && 'fill-danger/10 stroke-danger') }}
297
+ />
320
298
  </Svg>
321
299
  {:else if mode === 'bounds' || mode === 'band'}
322
300
  <Svg>
@@ -327,9 +305,7 @@ $: if (mode === 'bounds' || mode === 'band') {
327
305
  y={rect.y}
328
306
  width={rect.width}
329
307
  height={rect.height}
330
- style:fill={debug ? 'red' : 'transparent'}
331
- style:fill-opacity={debug ? 0.1 : 0}
332
- style:stroke={debug ? 'red' : 'transparent'}
308
+ class={cls(debug ? 'fill-danger/10 stroke-danger' : 'fill-transparent')}
333
309
  on:mousemove={(e) => showTooltip(e, rect.data)}
334
310
  on:mouseleave={hideTooltip}
335
311
  on:click={(e) => {
@@ -351,9 +327,7 @@ $: if (mode === 'bounds' || mode === 'band') {
351
327
  y={rect.y}
352
328
  width={rect.width}
353
329
  height={rect.height}
354
- style:fill={debug ? 'red' : 'transparent'}
355
- style:fill-opacity={debug ? 0.1 : 0}
356
- stroke="red"
330
+ class={cls(debug ? 'fill-danger/10 stroke-danger' : 'fill-transparent')}
357
331
  />
358
332
  {/each}
359
333
  </g>
@@ -0,0 +1,33 @@
1
+ <script>import { createEventDispatcher, getContext } from 'svelte';
2
+ import { draw as _drawTransition } from 'svelte/transition';
3
+ import { Delaunay } from 'd3-delaunay';
4
+ import { min } from 'd3-array';
5
+ import { cls } from 'svelte-ux';
6
+ const { flatData, xGet, yGet, width, height } = getContext('LayerCake');
7
+ /** Override data instead of using context */
8
+ export let data = undefined;
9
+ export let classes = {};
10
+ const dispatch = createEventDispatcher();
11
+ $: points = (data ?? $flatData).map((d) => {
12
+ const xValue = $xGet(d);
13
+ const yValue = $yGet(d);
14
+ const x = Array.isArray(xValue) ? min(xValue) : xValue;
15
+ const y = Array.isArray(yValue) ? min(yValue) : yValue;
16
+ const point = [x, y];
17
+ point.data = d;
18
+ return point;
19
+ });
20
+ $: voronoi = Delaunay.from(points).voronoi([0, 0, Math.max($width, 0), Math.max($height, 0)]); // width and/or height can sometimes be negative (when loading data remotely and updately)
21
+ </script>
22
+
23
+ <g {...$$restProps} class={cls(classes.root, $$props.class)}>
24
+ {#each points as point, i}
25
+ <path
26
+ d={voronoi.renderCell(i)}
27
+ class={cls('fill-transparent', classes.path)}
28
+ on:mousemove={(e) => dispatch('mousemove', { point, event: e })}
29
+ on:mouseleave
30
+ on:click={(e) => dispatch('click', { point })}
31
+ />
32
+ {/each}
33
+ </g>
@@ -0,0 +1,34 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ [x: string]: any;
5
+ data?: any;
6
+ classes?: {
7
+ root?: string | undefined;
8
+ path?: string | undefined;
9
+ } | undefined;
10
+ };
11
+ events: {
12
+ mouseleave: MouseEvent;
13
+ click: CustomEvent<{
14
+ point: {
15
+ data: any;
16
+ };
17
+ }>;
18
+ mousemove: CustomEvent<{
19
+ point: {
20
+ data: any;
21
+ };
22
+ event: MouseEvent;
23
+ }>;
24
+ } & {
25
+ [evt: string]: CustomEvent<any>;
26
+ };
27
+ slots: {};
28
+ };
29
+ export type VoronoiProps = typeof __propDef.props;
30
+ export type VoronoiEvents = typeof __propDef.events;
31
+ export type VoronoiSlots = typeof __propDef.slots;
32
+ export default class Voronoi extends SvelteComponentTyped<VoronoiProps, VoronoiEvents, VoronoiSlots> {
33
+ }
34
+ export {};
@@ -7,7 +7,7 @@ declare const __propDef: {
7
7
  spring?: boolean | Parameters<typeof motionStore>[1]['spring'];
8
8
  tweened?: boolean | Parameters<typeof motionStore>[1]['tweened'];
9
9
  disablePointer?: boolean | undefined;
10
- scroll?: "none" | "scale" | "translate" | undefined;
10
+ scroll?: "scale" | "translate" | "none" | undefined;
11
11
  clickDistance?: number | undefined;
12
12
  initialTranslate?: {
13
13
  x: number;
@@ -47,4 +47,5 @@ export { default as TooltipItem } from './TooltipItem.svelte';
47
47
  export { default as TooltipSeparator } from './TooltipSeparator.svelte';
48
48
  export { default as Tree } from './Tree.svelte';
49
49
  export { default as Treemap } from './Treemap.svelte';
50
+ export { default as Voronoi } from './Voronoi.svelte';
50
51
  export { default as Zoom } from './Zoom.svelte';
@@ -47,4 +47,5 @@ export { default as TooltipItem } from './TooltipItem.svelte';
47
47
  export { default as TooltipSeparator } from './TooltipSeparator.svelte';
48
48
  export { default as Tree } from './Tree.svelte';
49
49
  export { default as Treemap } from './Treemap.svelte';
50
+ export { default as Voronoi } from './Voronoi.svelte';
50
51
  export { default as Zoom } from './Zoom.svelte';
@@ -6,3 +6,24 @@ export declare function degreesToRadians(degrees: number): number;
6
6
  * Convert radians to degrees
7
7
  */
8
8
  export declare function radiansToDegrees(radians: number): number;
9
+ /**
10
+ * Convert polar to cartesian coordinate system.
11
+ * see also: https://d3js.org/d3-shape/symbol#pointRadial
12
+ * @param angle - Angle in radians
13
+ * @param radius - Radius
14
+ */
15
+ export declare function polarToCartesian(angle: number, radius: number): {
16
+ x: number;
17
+ y: number;
18
+ };
19
+ /**
20
+ * Convert cartesian to polar coordinate system. Angle in radians
21
+ */
22
+ export declare function cartesianToPolar(x: number, y: number): {
23
+ radius: number;
24
+ angle: number;
25
+ };
26
+ /** Convert celsius temperature to fahrenheit */
27
+ export declare function celsiusToFahrenheit(temperature: number): number;
28
+ /** Convert fahrenheit temperature to celsius */
29
+ export declare function fahrenheitToCelsius(temperature: number): number;
@@ -10,3 +10,32 @@ export function degreesToRadians(degrees) {
10
10
  export function radiansToDegrees(radians) {
11
11
  return radians * (180 / Math.PI);
12
12
  }
13
+ /**
14
+ * Convert polar to cartesian coordinate system.
15
+ * see also: https://d3js.org/d3-shape/symbol#pointRadial
16
+ * @param angle - Angle in radians
17
+ * @param radius - Radius
18
+ */
19
+ export function polarToCartesian(angle, radius) {
20
+ return {
21
+ x: radius * Math.cos(angle),
22
+ y: radius * Math.sin(angle),
23
+ };
24
+ }
25
+ /**
26
+ * Convert cartesian to polar coordinate system. Angle in radians
27
+ */
28
+ export function cartesianToPolar(x, y) {
29
+ return {
30
+ radius: Math.sqrt(x ** 2 + y ** 2),
31
+ angle: Math.atan(y / x),
32
+ };
33
+ }
34
+ /** Convert celsius temperature to fahrenheit */
35
+ export function celsiusToFahrenheit(temperature) {
36
+ return temperature * (9 / 5) + 32;
37
+ }
38
+ /** Convert fahrenheit temperature to celsius */
39
+ export function fahrenheitToCelsius(temperature) {
40
+ return (temperature - 32) * (5 / 9);
41
+ }
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.30.1",
7
+ "version": "0.31.0",
8
8
  "devDependencies": {
9
9
  "@changesets/cli": "^2.27.1",
10
10
  "@mdi/js": "^7.4.47",
@@ -83,7 +83,7 @@
83
83
  "lodash-es": "^4.17.21",
84
84
  "posthog-js": "^1.105.8",
85
85
  "shapefile": "^0.6.6",
86
- "svelte-ux": "0.60.2",
86
+ "svelte-ux": ">=0.60",
87
87
  "topojson-client": "^3.1.0"
88
88
  },
89
89
  "peerDependencies": {