layerchart 0.60.2 → 0.70.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.
Files changed (47) hide show
  1. package/dist/components/Arc.svelte +83 -27
  2. package/dist/components/Area.svelte +51 -12
  3. package/dist/components/Bar.svelte +5 -1
  4. package/dist/components/Circle.svelte +54 -12
  5. package/dist/components/ComputedStyles.svelte +16 -0
  6. package/dist/components/ComputedStyles.svelte.d.ts +20 -0
  7. package/dist/components/GeoPath.svelte +36 -44
  8. package/dist/components/GeoPath.svelte.d.ts +2 -2
  9. package/dist/components/GeoPoint.svelte +27 -13
  10. package/dist/components/GeoTile.svelte +22 -7
  11. package/dist/components/Graticule.svelte.d.ts +4 -4
  12. package/dist/components/Group.svelte +46 -18
  13. package/dist/components/HitCanvas.svelte +7 -21
  14. package/dist/components/Legend.svelte.d.ts +1 -1
  15. package/dist/components/Line.svelte +70 -31
  16. package/dist/components/LinearGradient.svelte +89 -26
  17. package/dist/components/LinearGradient.svelte.d.ts +2 -2
  18. package/dist/components/Points.svelte +23 -41
  19. package/dist/components/Points.svelte.d.ts +2 -1
  20. package/dist/components/RadialGradient.svelte +86 -28
  21. package/dist/components/RadialGradient.svelte.d.ts +2 -2
  22. package/dist/components/Rect.svelte +62 -18
  23. package/dist/components/Rule.svelte +1 -1
  24. package/dist/components/Spline.svelte +95 -57
  25. package/dist/components/Text.svelte +80 -22
  26. package/dist/components/TransformControls.svelte.d.ts +1 -1
  27. package/dist/components/charts/AreaChart.svelte +14 -10
  28. package/dist/components/charts/BarChart.svelte +13 -9
  29. package/dist/components/charts/LineChart.svelte +13 -9
  30. package/dist/components/charts/PieChart.svelte +6 -2
  31. package/dist/components/charts/PieChart.svelte.d.ts +2 -1
  32. package/dist/components/charts/ScatterChart.svelte +14 -10
  33. package/dist/components/layout/Canvas.svelte +93 -11
  34. package/dist/components/layout/Canvas.svelte.d.ts +13 -0
  35. package/dist/components/layout/Svg.svelte +1 -1
  36. package/dist/components/tooltip/Tooltip.svelte.d.ts +1 -1
  37. package/dist/utils/canvas.d.ts +46 -0
  38. package/dist/utils/canvas.js +146 -0
  39. package/dist/utils/common.d.ts +10 -1
  40. package/dist/utils/common.js +13 -0
  41. package/dist/utils/index.d.ts +2 -0
  42. package/dist/utils/index.js +2 -0
  43. package/dist/utils/math.d.ts +2 -0
  44. package/dist/utils/math.js +10 -0
  45. package/dist/utils/path.d.ts +7 -0
  46. package/dist/utils/path.js +17 -3
  47. package/package.json +3 -4
@@ -17,7 +17,7 @@
17
17
  // https://svelte.dev/repl/09711e43a1264ba18945d7db7cab9335?version=3.38.2
18
18
  // https://codepen.io/simeydotme/pen/rrOEmO/
19
19
 
20
- import { tick } from 'svelte';
20
+ import { onDestroy, tick } from 'svelte';
21
21
  import type { SVGAttributes } from 'svelte/elements';
22
22
  import type { spring as springStore, tweened as tweenedStore } from 'svelte/motion';
23
23
  import { arc as d3arc } from 'd3-shape';
@@ -28,6 +28,8 @@
28
28
  import { motionStore } from '../stores/motionStore.js';
29
29
  import { degreesToRadians } from '../utils/math.js';
30
30
  import type { TooltipContextValue } from './tooltip/TooltipContext.svelte';
31
+ import { getCanvasContext } from './layout/Canvas.svelte';
32
+ import { renderPathData } from '../utils/canvas.js';
31
33
 
32
34
  export let spring: boolean | Parameters<typeof springStore>[1] = undefined;
33
35
  export let tweened: boolean | Parameters<typeof tweenedStore>[1] = undefined;
@@ -80,6 +82,11 @@
80
82
  export let padAngle = 0;
81
83
  // export let padRadius = 0;
82
84
 
85
+ export let fill: string | undefined = undefined;
86
+ export let fillOpacity: number | undefined = undefined;
87
+ export let stroke: string | undefined = undefined;
88
+ export let strokeWidth: number | undefined = undefined;
89
+
83
90
  export let track: boolean | SVGAttributes<SVGPathElement> = false;
84
91
 
85
92
  const { yRange } = chartContext();
@@ -183,36 +190,85 @@
183
190
  * Data to set when showing tooltip
184
191
  */
185
192
  export let data: any = undefined;
193
+
194
+ const canvasContext = getCanvasContext();
195
+ const renderContext = canvasContext ? 'canvas' : 'svg';
196
+
197
+ function render(ctx: CanvasRenderingContext2D) {
198
+ ctx.translate(xOffset, yOffset);
199
+
200
+ // Track
201
+ const trackProps = { ...(typeof track === 'object' ? track : null) };
202
+ renderPathData(ctx, trackArc(), {
203
+ styles: {
204
+ fill: trackProps['fill'] ?? undefined,
205
+ fillOpacity: trackProps['fill-opacity'] ?? undefined,
206
+ stroke: trackProps['stroke'] ?? undefined,
207
+ strokeWidth: trackProps['stroke-width'] ?? undefined,
208
+ opacity: trackProps['opacity'] ?? undefined,
209
+ },
210
+ classes: trackProps.class ?? undefined,
211
+ });
212
+
213
+ // Arc
214
+ renderPathData(ctx, arc(), {
215
+ styles: { fill, fillOpacity, stroke, strokeWidth },
216
+ classes: $$props.class,
217
+ });
218
+ }
219
+
220
+ let canvasUnregister: ReturnType<typeof canvasContext.register>;
221
+ $: if (renderContext === 'canvas') {
222
+ canvasUnregister = canvasContext.register({ name: 'Arc', render });
223
+ }
224
+
225
+ $: if (renderContext === 'canvas') {
226
+ // Redraw when props changes (TODO: styles, class, etc)
227
+ arc && trackArc;
228
+ canvasContext.invalidate();
229
+ }
230
+
231
+ onDestroy(() => {
232
+ if (renderContext === 'canvas') {
233
+ canvasUnregister();
234
+ }
235
+ });
186
236
  </script>
187
237
 
188
- {#if track}
238
+ {#if renderContext === 'svg'}
239
+ {#if track}
240
+ <path
241
+ d={trackArc()}
242
+ class="track"
243
+ bind:this={trackArcEl}
244
+ {...typeof track === 'object' ? track : null}
245
+ />
246
+ {/if}
247
+
248
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
189
249
  <path
190
- d={trackArc()}
191
- class="track"
192
- bind:this={trackArcEl}
193
- {...typeof track === 'object' ? track : null}
250
+ d={arc()}
251
+ transform="translate({xOffset}, {yOffset})"
252
+ {fill}
253
+ fill-opacity={fillOpacity}
254
+ {stroke}
255
+ stroke-width={strokeWidth}
256
+ {...$$restProps}
257
+ on:pointerenter={(e) => tooltip?.show(e, data)}
258
+ on:pointermove={(e) => tooltip?.show(e, data)}
259
+ on:pointerleave={(e) => tooltip?.hide()}
260
+ on:touchmove={(e) => {
261
+ if (tooltip) {
262
+ // Prevent touch to not interfer with pointer when using tooltip
263
+ e.preventDefault();
264
+ }
265
+ }}
266
+ on:click
267
+ on:pointerenter
268
+ on:pointermove
269
+ on:pointerleave
270
+ on:touchmove
194
271
  />
195
272
  {/if}
196
273
 
197
- <!-- svelte-ignore a11y-no-static-element-interactions -->
198
- <path
199
- d={arc()}
200
- transform="translate({xOffset}, {yOffset})"
201
- {...$$restProps}
202
- on:pointerenter={(e) => tooltip?.show(e, data)}
203
- on:pointermove={(e) => tooltip?.show(e, data)}
204
- on:pointerleave={(e) => tooltip?.hide()}
205
- on:touchmove={(e) => {
206
- if (tooltip) {
207
- // Prevent touch to not interfer with pointer when using tooltip
208
- e.preventDefault();
209
- }
210
- }}
211
- on:click
212
- on:pointerenter
213
- on:pointermove
214
- on:pointerleave
215
- on:touchmove
216
- />
217
-
218
274
  <slot value={$tweened_value} centroid={trackArcCentroid} {boundingBox} />
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { type ComponentProps } from 'svelte';
2
+ import { onDestroy, type ComponentProps } from 'svelte';
3
3
  import type { tweened as tweenedStore } from 'svelte/motion';
4
4
  import { type Area, area as d3Area, areaRadial } from 'd3-shape';
5
5
  import type { CurveFactory } from 'd3-shape';
@@ -14,6 +14,8 @@
14
14
  import Spline from './Spline.svelte';
15
15
  import { accessor, type Accessor } from '../utils/common.js';
16
16
  import { isScaleBand } from '../utils/scales.js';
17
+ import { renderPathData } from '../utils/canvas.js';
18
+ import { getCanvasContext } from './layout/Canvas.svelte';
17
19
 
18
20
  const {
19
21
  data: contextData,
@@ -23,8 +25,8 @@
23
25
  y,
24
26
  yDomain,
25
27
  yRange,
26
- config,
27
28
  radial,
29
+ config,
28
30
  } = chartContext();
29
31
 
30
32
  /** Override data instead of using context */
@@ -52,6 +54,11 @@
52
54
  /** Enable showing line */
53
55
  export let line: boolean | Partial<ComponentProps<Spline>> = false;
54
56
 
57
+ export let fill: string | undefined = undefined;
58
+ export let fillOpacity: number | undefined = undefined;
59
+ export let stroke: string | undefined = undefined;
60
+ export let strokeWidth: number | undefined = undefined;
61
+
55
62
  const xAccessor = x ? accessor(x) : $contextX;
56
63
  const y0Accessor = y0 ? accessor(y0) : (d: any) => min($yDomain);
57
64
  const y1Accessor = y1 ? accessor(y1) : $y;
@@ -125,8 +132,34 @@
125
132
  const d = pathData ?? path(data ?? $contextData);
126
133
  tweened_d.set(d ?? '');
127
134
  }
135
+
136
+ const canvasContext = getCanvasContext();
137
+ const renderContext = canvasContext ? 'canvas' : 'svg';
138
+
139
+ function render(ctx: CanvasRenderingContext2D) {
140
+ renderPathData(ctx, $tweened_d, {
141
+ styles: { fill, fillOpacity, stroke, strokeWidth },
142
+ classes: $$props.class,
143
+ });
144
+ }
145
+
146
+ let canvasUnregister: ReturnType<typeof canvasContext.register>;
147
+ $: if (renderContext === 'canvas') {
148
+ canvasUnregister = canvasContext.register({ name: 'Area', render });
149
+
150
+ tweened_d.subscribe(() => {
151
+ canvasContext.invalidate();
152
+ });
153
+ }
154
+
155
+ onDestroy(() => {
156
+ if (renderContext === 'canvas') {
157
+ canvasUnregister();
158
+ }
159
+ });
128
160
  </script>
129
161
 
162
+ <!-- TODO: Find way to not clear <Canvas> when rendering Spline (remove Area rendering). Idea: https://github.com/techniq/layerchart/issues/158#issuecomment-2543416108 -->
130
163
  {#if line}
131
164
  <Spline
132
165
  {data}
@@ -139,13 +172,19 @@
139
172
  />
140
173
  {/if}
141
174
 
142
- <!-- svelte-ignore a11y-no-static-element-interactions -->
143
- <path
144
- d={$tweened_d}
145
- clip-path={clipPath}
146
- {...$$restProps}
147
- class={cls('path-area', $$props.class)}
148
- on:click
149
- on:pointermove
150
- on:pointerleave
151
- />
175
+ {#if renderContext === 'svg'}
176
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
177
+ <path
178
+ d={$tweened_d}
179
+ clip-path={clipPath}
180
+ {fill}
181
+ fill-opacity={fillOpacity}
182
+ {stroke}
183
+ stroke-width={strokeWidth}
184
+ {...$$restProps}
185
+ class={cls('path-area', $$props.class)}
186
+ on:click
187
+ on:pointermove
188
+ on:pointerleave
189
+ />
190
+ {/if}
@@ -9,6 +9,7 @@
9
9
  import { isScaleBand } from '../utils/scales.js';
10
10
  import { accessor, type Accessor } from '../utils/common.js';
11
11
  import { greatestAbs } from '@layerstack/utils';
12
+ import { getCanvasContext } from './layout/Canvas.svelte';
12
13
 
13
14
  const { x: xContext, y: yContext, xScale } = chartContext();
14
15
 
@@ -106,9 +107,12 @@
106
107
  z`
107
108
  .split('\n')
108
109
  .join('');
110
+
111
+ const canvasContext = getCanvasContext();
112
+ const renderContext = canvasContext ? 'canvas' : 'svg';
109
113
  </script>
110
114
 
111
- {#if _rounded === 'all' || radius === 0}
115
+ {#if (_rounded === 'all' || radius === 0) && renderContext === 'svg'}
112
116
  <Rect
113
117
  {fill}
114
118
  {spring}
@@ -1,9 +1,12 @@
1
1
  <script lang="ts">
2
- import { tick } from 'svelte';
2
+ import { onDestroy, tick } from 'svelte';
3
3
  import type { spring as springStore, tweened as tweenedStore } from 'svelte/motion';
4
4
  import { cls } from '@layerstack/tailwind';
5
5
 
6
6
  import { motionStore } from '../stores/motionStore.js';
7
+ import { getCanvasContext } from './layout/Canvas.svelte';
8
+ import { circlePath } from '../utils/path.js';
9
+ import { renderPathData } from '../utils/canvas.js';
7
10
 
8
11
  export let cx: number = 0;
9
12
  export let initialCx = cx;
@@ -17,6 +20,11 @@
17
20
  export let spring: boolean | Parameters<typeof springStore>[1] = undefined;
18
21
  export let tweened: boolean | Parameters<typeof tweenedStore>[1] = undefined;
19
22
 
23
+ export let fill: string | undefined = undefined;
24
+ export let fillOpacity: number | undefined = undefined;
25
+ export let stroke: string | undefined = undefined;
26
+ export let strokeWidth: number | undefined = undefined;
27
+
20
28
  let tweened_cx = motionStore(initialCx, { spring, tweened });
21
29
  let tweened_cy = motionStore(initialCy, { spring, tweened });
22
30
  let tweened_r = motionStore(initialR, { spring, tweened });
@@ -26,16 +34,50 @@
26
34
  tweened_cy.set(cy);
27
35
  tweened_r.set(r);
28
36
  });
37
+
38
+ const canvasContext = getCanvasContext();
39
+ const renderContext = canvasContext ? 'canvas' : 'svg';
40
+
41
+ function render(ctx: CanvasRenderingContext2D) {
42
+ const pathData = circlePath({ cx: $tweened_cx, cy: $tweened_cy, r: $tweened_r });
43
+ renderPathData(ctx, pathData, {
44
+ styles: { fill, fillOpacity, stroke, strokeWidth },
45
+ classes: $$props.class,
46
+ });
47
+ }
48
+
49
+ let canvasUnregister: ReturnType<typeof canvasContext.register>;
50
+ $: if (renderContext === 'canvas') {
51
+ canvasUnregister = canvasContext.register({ name: 'Circle', render });
52
+ }
53
+
54
+ $: if (renderContext === 'canvas') {
55
+ // Redraw when props changes (TODO: styles, class, etc)
56
+ $tweened_cx && $tweened_cy && $tweened_r;
57
+ canvasContext.invalidate();
58
+ }
59
+
60
+ onDestroy(() => {
61
+ if (renderContext === 'canvas') {
62
+ canvasUnregister();
63
+ }
64
+ });
29
65
  </script>
30
66
 
31
- <!-- svelte-ignore a11y-no-static-element-interactions -->
32
- <circle
33
- cx={$tweened_cx}
34
- cy={$tweened_cy}
35
- r={$tweened_r}
36
- class={cls($$props.fill == null && 'fill-surface-content')}
37
- {...$$restProps}
38
- on:click
39
- on:pointermove
40
- on:pointerleave
41
- />
67
+ {#if renderContext === 'svg'}
68
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
69
+ <circle
70
+ cx={$tweened_cx}
71
+ cy={$tweened_cy}
72
+ r={$tweened_r}
73
+ {fill}
74
+ fill-opacity={fillOpacity}
75
+ {stroke}
76
+ stroke-width={strokeWidth}
77
+ class={cls($$props.fill == null && 'fill-surface-content')}
78
+ {...$$restProps}
79
+ on:click
80
+ on:pointermove
81
+ on:pointerleave
82
+ />
83
+ {/if}
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import { computedStyles } from '@layerstack/svelte-actions';
3
+ import { cls } from '@layerstack/tailwind';
4
+
5
+ let className: string | undefined = undefined;
6
+ export { className as class };
7
+
8
+ let styles: CSSStyleDeclaration;
9
+ </script>
10
+
11
+ <div
12
+ class={cls('ComputedStyles hidden', className)}
13
+ use:computedStyles={(_styles) => (styles = _styles)}
14
+ ></div>
15
+
16
+ <slot {styles} />
@@ -0,0 +1,20 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ class?: string | undefined;
5
+ };
6
+ events: {
7
+ [evt: string]: CustomEvent<any>;
8
+ };
9
+ slots: {
10
+ default: {
11
+ styles: CSSStyleDeclaration;
12
+ };
13
+ };
14
+ };
15
+ export type ComputedStylesProps = typeof __propDef.props;
16
+ export type ComputedStylesEvents = typeof __propDef.events;
17
+ export type ComputedStylesSlots = typeof __propDef.slots;
18
+ export default class ComputedStyles extends SvelteComponentTyped<ComputedStylesProps, ComputedStylesEvents, ComputedStylesSlots> {
19
+ }
20
+ export {};
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
- import { createEventDispatcher, getContext } from 'svelte';
3
- import type { Readable } from 'svelte/store';
2
+ import { createEventDispatcher, onDestroy } from 'svelte';
4
3
  import {
5
4
  geoTransform as d3geoTransform,
6
5
  type GeoIdentityTransform,
@@ -10,11 +9,12 @@
10
9
  } from 'd3-geo';
11
10
  import { cls } from '@layerstack/tailwind';
12
11
 
13
- import { chartContext } from './ChartContext.svelte';
14
12
  import { geoContext } from './GeoContext.svelte';
15
13
  import type { TooltipContextValue } from './tooltip/TooltipContext.svelte';
16
14
  import { curveLinearClosed, type CurveFactory, type CurveFactoryLineOnly } from 'd3-shape';
17
15
  import { geoCurvePath } from '../utils/geo.js';
16
+ import { renderPathData } from '../utils/canvas.js';
17
+ import { getCanvasContext } from './layout/Canvas.svelte';
18
18
 
19
19
  export let geojson: GeoPermissibleObjects | null | undefined = undefined;
20
20
 
@@ -22,13 +22,13 @@
22
22
  export let render:
23
23
  | ((
24
24
  ctx: CanvasRenderingContext2D,
25
- options: { geoPath: ReturnType<typeof geoCurvePath> }
25
+ options: { newGeoPath: () => ReturnType<typeof geoCurvePath> }
26
26
  ) => any)
27
27
  | undefined = undefined;
28
28
 
29
29
  export let fill: string | undefined = undefined;
30
30
  export let stroke: string | undefined = undefined;
31
- export let strokeWidth: number | string | undefined = undefined;
31
+ export let strokeWidth: number | undefined = undefined;
32
32
 
33
33
  /**
34
34
  * Tooltip context to setup mouse events to show tooltip for related data
@@ -53,8 +53,6 @@
53
53
  click: { geoPath: ReturnType<typeof geoCurvePath>; event: MouseEvent };
54
54
  }>();
55
55
 
56
- const { containerWidth, containerHeight, padding } = chartContext();
57
- const canvas = getContext<{ ctx: Readable<CanvasRenderingContext2D> }>('canvas');
58
56
  const geo = geoContext();
59
57
 
60
58
  /**
@@ -68,51 +66,45 @@
68
66
  $: _projection = geoTransform ? d3geoTransform(geoTransform($geo)) : $geo;
69
67
 
70
68
  $: geoPath = geoCurvePath(_projection, curve);
69
+ $: {
70
+ // Recreate `geoPath()` if `geojson` data changes (fixes ghosting issue when rendering to canvas)
71
+ geojson;
72
+ geoPath = geoCurvePath(_projection, curve);
73
+ }
71
74
 
72
- const DEFAULT_FILL = 'rgb(0, 0, 0)';
73
-
74
- $: renderContext = canvas ? 'canvas' : 'svg';
75
-
76
- $: ctx = canvas?.ctx;
77
- $: if (renderContext === 'canvas' && $ctx) {
78
- let computedStyles: Partial<CSSStyleDeclaration> = {};
79
-
80
- // Transfer classes defined on <GeoPath> to <canvas> to enable window.getComputedStyle() retrieval (Tailwind classes, etc)
81
- if (className) {
82
- $ctx.canvas.classList.add(...className.split(' '));
83
- computedStyles = window.getComputedStyle($ctx.canvas);
84
- }
85
-
86
- // console.count('render');
87
-
88
- // Clear with negative offset due to Canvas `context.translate(...)`
89
- $ctx.clearRect(-$padding.left, -$padding.top, $containerWidth, $containerHeight);
75
+ const canvasContext = getCanvasContext();
76
+ const renderContext = canvasContext ? 'canvas' : 'svg';
90
77
 
78
+ function _render(ctx: CanvasRenderingContext2D) {
91
79
  if (render) {
92
- geoPath = geoCurvePath(_projection, curve, $ctx);
93
- render($ctx, { geoPath });
80
+ render(ctx, { newGeoPath: () => geoCurvePath(_projection, curve) });
94
81
  } else {
95
- $ctx.beginPath();
96
- // Set the context here since setting it in `$: geoPath` is a circular reference
97
- geoPath = geoCurvePath(_projection, curve, $ctx);
98
82
  if (geojson) {
99
- geoPath(geojson);
83
+ const pathData = geoPath(geojson);
84
+ renderPathData(ctx, pathData, {
85
+ styles: { fill, stroke, strokeWidth },
86
+ classes: className,
87
+ });
100
88
  }
101
-
102
- $ctx.fillStyle =
103
- fill ??
104
- (computedStyles.fill !== DEFAULT_FILL ? computedStyles.fill : undefined) ??
105
- 'transparent';
106
- $ctx.fill();
107
-
108
- $ctx.lineWidth = Number(strokeWidth ?? 0);
109
- $ctx.strokeStyle =
110
- (stroke ?? computedStyles.stroke === 'none')
111
- ? 'transparent'
112
- : (computedStyles.stroke ?? '');
113
- $ctx.stroke();
114
89
  }
115
90
  }
91
+
92
+ let canvasUnregister: ReturnType<typeof canvasContext.register>;
93
+ $: if (renderContext === 'canvas') {
94
+ canvasUnregister = canvasContext.register({ name: 'GeoPath', render: _render });
95
+ }
96
+
97
+ $: if (renderContext === 'canvas') {
98
+ // Redraw when geojson, projection, or class change
99
+ geojson && _projection && className && fill && stroke && strokeWidth;
100
+ canvasContext.invalidate();
101
+ }
102
+
103
+ onDestroy(() => {
104
+ if (renderContext === 'canvas') {
105
+ canvasUnregister();
106
+ }
107
+ });
116
108
  </script>
117
109
 
118
110
  <!-- svelte-ignore a11y-no-static-element-interactions -->
@@ -8,11 +8,11 @@ declare const __propDef: {
8
8
  [x: string]: any;
9
9
  geojson?: GeoPermissibleObjects | null | undefined;
10
10
  render?: ((ctx: CanvasRenderingContext2D, options: {
11
- geoPath: ReturnType<typeof geoCurvePath>;
11
+ newGeoPath: () => ReturnType<typeof geoCurvePath>;
12
12
  }) => any) | undefined | undefined;
13
13
  fill?: string | undefined | undefined;
14
14
  stroke?: string | undefined | undefined;
15
- strokeWidth?: number | string | undefined | undefined;
15
+ strokeWidth?: number | undefined | undefined;
16
16
  tooltip?: TooltipContextValue | undefined;
17
17
  curve?: (CurveFactory | CurveFactoryLineOnly) | undefined;
18
18
  class?: string | undefined | undefined;
@@ -1,10 +1,10 @@
1
1
  <script lang="ts">
2
- import { getContext } from 'svelte';
3
- import type { Readable } from 'svelte/store';
2
+ import { onDestroy } from 'svelte';
4
3
 
5
4
  import { geoContext } from './GeoContext.svelte';
6
5
  import Circle from './Circle.svelte';
7
6
  import Group from './Group.svelte';
7
+ import { getCanvasContext } from './layout/Canvas.svelte';
8
8
 
9
9
  /** Latitude */
10
10
  export let lat: number;
@@ -17,24 +17,27 @@
17
17
  coords: { x: number; y: number }
18
18
  ) => any = () => {};
19
19
 
20
- const canvas = getContext<{ ctx: Readable<CanvasRenderingContext2D> }>('canvas');
21
20
  const geo = geoContext();
22
21
 
23
22
  $: [x, y] = $geo([long, lat]) ?? [0, 0];
24
23
 
25
- $: renderContext = canvas ? 'canvas' : 'svg';
24
+ const canvasContext = getCanvasContext();
25
+ const renderContext = canvasContext ? 'canvas' : 'svg';
26
26
 
27
- $: ctx = canvas?.ctx;
28
- $: if (renderContext === 'canvas' && $ctx) {
29
- // $ctx.clearRect(0, 0, $width, $height);
30
-
31
- // Transfer classes defined on <GeoPoint> to <canvas> to enable window.getComputedStyle() retrieval (Tailwind classes, etc)
32
- if ($$props.class) {
33
- $ctx.canvas.classList.add(...$$props.class.split(' '));
34
- }
27
+ function _render(ctx: CanvasRenderingContext2D) {
28
+ render(ctx, { x, y });
29
+ }
35
30
 
36
- render($ctx, { x, y });
31
+ let canvasUnregister: ReturnType<typeof canvasContext.register>;
32
+ $: if (renderContext === 'canvas') {
33
+ canvasUnregister = canvasContext.register({ name: 'GeoPoint', render: _render });
37
34
  }
35
+
36
+ onDestroy(() => {
37
+ if (renderContext === 'canvas') {
38
+ canvasUnregister();
39
+ }
40
+ });
38
41
  </script>
39
42
 
40
43
  {#if renderContext === 'svg'}
@@ -46,3 +49,14 @@
46
49
  <Circle cx={x} cy={y} {...$$restProps} />
47
50
  {/if}
48
51
  {/if}
52
+
53
+ {#if renderContext === 'canvas'}
54
+ {#if $$slots.default}
55
+ <!-- TODO: Handle Canvas translation. Conslidate with svg use case above (if `render` is not defined) -->
56
+ <!-- <Group {x} {y} {...$$restProps}> -->
57
+ <slot />
58
+ <!-- </Group> -->
59
+ {:else}
60
+ <Circle cx={x} cy={y} {...$$restProps} />
61
+ {/if}
62
+ {/if}
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
- import { getContext } from 'svelte';
3
- import type { Readable } from 'svelte/store';
2
+ import { onDestroy } from 'svelte';
4
3
  // @ts-expect-error
5
4
  import { tile as d3Tile } from 'd3-tile';
6
5
 
@@ -8,6 +7,7 @@
8
7
  import { geoContext } from './GeoContext.svelte';
9
8
  import Group from './Group.svelte';
10
9
  import TileImage from './TileImage.svelte';
10
+ import { getCanvasContext } from './layout/Canvas.svelte';
11
11
 
12
12
  export let url: (x: number, y: number, z: number) => string;
13
13
  export let zoomDelta = 0;
@@ -16,7 +16,6 @@
16
16
  export let debug = false;
17
17
 
18
18
  const { containerWidth, containerHeight, padding } = chartContext();
19
- const canvas = getContext<{ ctx: Readable<CanvasRenderingContext2D> }>('canvas');
20
19
  const geo = geoContext();
21
20
 
22
21
  $: center = $geo([0, 0]) ?? [0, 0];
@@ -34,18 +33,34 @@
34
33
  scale,
35
34
  } = tiles);
36
35
 
37
- $: renderContext = canvas ? 'canvas' : 'svg';
36
+ const canvasContext = getCanvasContext();
37
+ const renderContext = canvasContext ? 'canvas' : 'svg';
38
38
 
39
- $: ctx = canvas?.ctx;
40
- $: if (renderContext === 'canvas' && $ctx && url) {
39
+ function render(ctx: CanvasRenderingContext2D) {
41
40
  tiles.forEach(([x, y, z]: number[]) => {
42
41
  const image = new Image();
43
42
  image.onload = () => {
44
- $ctx.drawImage(image, (x + tx) * scale, (y + ty) * scale, scale, scale);
43
+ ctx.drawImage(image, (x + tx) * scale, (y + ty) * scale, scale, scale);
45
44
  };
46
45
  image.src = url(x, y, z);
47
46
  });
48
47
  }
48
+
49
+ let canvasUnregister: ReturnType<typeof canvasContext.register>;
50
+ $: if (renderContext === 'canvas') {
51
+ canvasUnregister = canvasContext.register({ name: 'GeoTile', render });
52
+ }
53
+
54
+ $: if (renderContext === 'canvas') {
55
+ tile;
56
+ canvasContext.invalidate();
57
+ }
58
+
59
+ onDestroy(() => {
60
+ if (renderContext === 'canvas') {
61
+ canvasUnregister();
62
+ }
63
+ });
49
64
  </script>
50
65
 
51
66
  {#if renderContext === 'svg' && url}