layerchart 0.81.3 → 0.90.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 (64) hide show
  1. package/dist/components/Arc.svelte +49 -14
  2. package/dist/components/Area.svelte +35 -11
  3. package/dist/components/Bar.svelte +15 -10
  4. package/dist/components/Bars.svelte +3 -3
  5. package/dist/components/Brush.svelte +20 -21
  6. package/dist/components/Calendar.svelte +2 -2
  7. package/dist/components/Chart.svelte +17 -4
  8. package/dist/components/Chart.svelte.d.ts +21 -10
  9. package/dist/components/ChartClipPath.svelte +0 -1
  10. package/dist/components/ChartClipPath.svelte.d.ts +0 -2
  11. package/dist/components/ChartContext.svelte +4 -11
  12. package/dist/components/ChartContext.svelte.d.ts +1 -1
  13. package/dist/components/Circle.svelte +35 -11
  14. package/dist/components/ClipPath.svelte +1 -1
  15. package/dist/components/ClipPath.svelte.d.ts +0 -5
  16. package/dist/components/ForceSimulation.svelte +8 -12
  17. package/dist/components/ForceSimulation.svelte.d.ts +6 -8
  18. package/dist/components/Frame.svelte +4 -7
  19. package/dist/components/Frame.svelte.d.ts +1 -5
  20. package/dist/components/GeoCircle.svelte +7 -1
  21. package/dist/components/GeoCircle.svelte.d.ts +6 -7
  22. package/dist/components/GeoPath.svelte +68 -39
  23. package/dist/components/GeoPath.svelte.d.ts +6 -12
  24. package/dist/components/GeoPoint.svelte +3 -24
  25. package/dist/components/GeoPoint.svelte.d.ts +4 -5
  26. package/dist/components/Graticule.svelte.d.ts +12 -6
  27. package/dist/components/Group.svelte +26 -6
  28. package/dist/components/Highlight.svelte +22 -12
  29. package/dist/components/Hull.svelte +20 -15
  30. package/dist/components/Hull.svelte.d.ts +7 -9
  31. package/dist/components/Legend.svelte +7 -7
  32. package/dist/components/Legend.svelte.d.ts +3 -3
  33. package/dist/components/Line.svelte +35 -10
  34. package/dist/components/Link.svelte +16 -9
  35. package/dist/components/Points.svelte +24 -57
  36. package/dist/components/Points.svelte.d.ts +0 -1
  37. package/dist/components/Rect.svelte +46 -15
  38. package/dist/components/Sankey.svelte +3 -4
  39. package/dist/components/Sankey.svelte.d.ts +1 -2
  40. package/dist/components/Spline.svelte +47 -11
  41. package/dist/components/Text.svelte +12 -6
  42. package/dist/components/TransformContext.svelte +8 -10
  43. package/dist/components/TransformContext.svelte.d.ts +9 -9
  44. package/dist/components/Voronoi.svelte +62 -40
  45. package/dist/components/Voronoi.svelte.d.ts +14 -13
  46. package/dist/components/charts/AreaChart.svelte +23 -15
  47. package/dist/components/charts/BarChart.svelte +24 -11
  48. package/dist/components/charts/LineChart.svelte +23 -15
  49. package/dist/components/charts/PieChart.svelte +22 -15
  50. package/dist/components/charts/PieChart.svelte.d.ts +29 -7
  51. package/dist/components/charts/ScatterChart.svelte +13 -7
  52. package/dist/components/index.d.ts +0 -1
  53. package/dist/components/index.js +0 -1
  54. package/dist/components/layout/Canvas.svelte +148 -7
  55. package/dist/components/layout/Canvas.svelte.d.ts +15 -2
  56. package/dist/components/tooltip/TooltipContext.svelte +25 -20
  57. package/dist/components/tooltip/TooltipContext.svelte.d.ts +6 -2
  58. package/dist/utils/canvas.js +39 -33
  59. package/dist/utils/color.d.ts +15 -0
  60. package/dist/utils/color.js +15 -0
  61. package/package.json +1 -1
  62. package/dist/components/Brush.svelte.d.ts +0 -65
  63. package/dist/components/HitCanvas.svelte +0 -118
  64. package/dist/components/HitCanvas.svelte.d.ts +0 -32
@@ -32,6 +32,7 @@
32
32
  interface $$Props extends ComponentProps<Chart<TData>> {
33
33
  axis?: typeof axis;
34
34
  brush?: typeof brush;
35
+ debug?: typeof debug;
35
36
  grid?: typeof grid;
36
37
  labels?: typeof labels;
37
38
  legend?: typeof legend;
@@ -41,8 +42,8 @@
41
42
  rule?: typeof rule;
42
43
  series?: typeof series;
43
44
  renderContext?: typeof renderContext;
44
- onPointClick?: typeof onPointClick;
45
- onTooltipClick?: typeof onTooltipClick;
45
+ onpointclick?: typeof onpointclick;
46
+ ontooltipclick?: typeof ontooltipclick;
46
47
  }
47
48
 
48
49
  export let data: $$Props['data'] = [];
@@ -75,13 +76,16 @@
75
76
  export let rule: ComponentProps<Rule> | boolean = true;
76
77
 
77
78
  /** Event dispatched with current tooltip data */
78
- export let onTooltipClick: (e: { data: any }) => void = () => {};
79
+ export let ontooltipclick: (e: MouseEvent, details: { data: any }) => void = () => {};
79
80
 
80
81
  /** Event dispatched when Highlight point is clicked (useful with multiple series) */
81
- export let onPointClick: (e: {
82
- data: HighlightPointData;
83
- series: (typeof series)[number];
84
- }) => void = () => {};
82
+ export let onpointclick: (
83
+ e: MouseEvent,
84
+ details: {
85
+ data: HighlightPointData;
86
+ series: (typeof series)[number];
87
+ }
88
+ ) => void = () => {};
85
89
 
86
90
  export let props: {
87
91
  brush?: Partial<ComponentProps<Brush>>;
@@ -109,6 +113,9 @@
109
113
  /** Log initial render performance using `console.time` */
110
114
  export let profile = false;
111
115
 
116
+ /** Enable debug mode */
117
+ export let debug = false;
118
+
112
119
  $: allSeriesData = series
113
120
  .flatMap((s) => s.data?.map((d) => ({ seriesKey: s.key, ...d })))
114
121
  .filter((d) => d) as Array<TData & { stackData?: any }>;
@@ -208,7 +215,8 @@
208
215
  ? false
209
216
  : {
210
217
  mode: 'bisect-x',
211
- onClick: onTooltipClick,
218
+ onclick: ontooltipclick,
219
+ debug,
212
220
  ...props.tooltip?.context,
213
221
  ...$$props.tooltip,
214
222
  }}
@@ -241,7 +249,7 @@
241
249
  getSplineProps,
242
250
  }}
243
251
  <slot {...slotProps}>
244
- <svelte:component this={renderContext === 'canvas' ? Canvas : Svg} center={radial}>
252
+ <svelte:component this={renderContext === 'canvas' ? Canvas : Svg} center={radial} {debug}>
245
253
  <slot name="grid" {...slotProps}>
246
254
  {#if grid}
247
255
  <Grid x={radial} y {...typeof grid === 'object' ? grid : null} {...props.grid} />
@@ -313,9 +321,9 @@
313
321
  ),
314
322
  }}
315
323
  lines={i === 0}
316
- onPointClick={(e) => onPointClick({ ...e, series: s })}
317
- onPointEnter={() => (highlightSeriesKey = s.key)}
318
- onPointLeave={() => (highlightSeriesKey = null)}
324
+ onpointclick={(e, detail) => onpointclick(e, { ...detail, series: s })}
325
+ onpointenter={() => (highlightSeriesKey = s.key)}
326
+ onpointleave={() => (highlightSeriesKey = null)}
319
327
  {...props.highlight}
320
328
  />
321
329
  {/each}
@@ -350,9 +358,9 @@
350
358
  tickFormat={(key) => series.find((s) => s.key === key)?.label ?? key}
351
359
  placement="bottom"
352
360
  variant="swatches"
353
- onClick={(item) => $selectedSeries.toggleSelected(item.value)}
354
- onPointerEnter={(item) => (highlightSeriesKey = item.value)}
355
- onPointerLeave={(item) => (highlightSeriesKey = null)}
361
+ onclick={(e, item) => $selectedSeries.toggleSelected(item.value)}
362
+ onpointerenter={(e, item) => (highlightSeriesKey = item.value)}
363
+ onpointerleave={(e) => (highlightSeriesKey = null)}
356
364
  {...props.legend}
357
365
  {...typeof legend === 'object' ? legend : null}
358
366
  classes={{
@@ -28,13 +28,14 @@
28
28
  center?: typeof center;
29
29
  placement?: typeof placement;
30
30
  profile?: typeof profile;
31
+ debug?: typeof debug;
31
32
  props?: typeof props;
32
33
  range?: typeof range;
33
34
  series?: typeof series;
34
35
  value?: typeof value;
35
36
  renderContext?: typeof renderContext;
36
- onArcClick?: typeof onArcClick;
37
- onTooltipClick?: typeof onTooltipClick;
37
+ onarcclick?: typeof onarcclick;
38
+ ontooltipclick?: typeof ontooltipclick;
38
39
  }
39
40
 
40
41
  export let data: $$Props['data'] = [];
@@ -96,12 +97,15 @@
96
97
  /** Center chart. Override and use `props.group` for more control */
97
98
  export let center = placement === 'center';
98
99
 
99
- // TODO: Not usable with manual tooltip / arc path. Use `onArcClick`?
100
+ // TODO: Not usable with manual tooltip / arc path. Use `onarcclick`?
100
101
  /** Event dispatched with current tooltip data */
101
- export let onTooltipClick: (e: { data: any }) => void = () => {};
102
+ export let ontooltipclick: (e: MouseEvent, detail: { data: any }) => void = () => {};
102
103
 
103
104
  /** Event dispatched when individual Arc is clicked (useful with multiple series) */
104
- export let onArcClick: (e: { data: any; series: (typeof series)[number] }) => void = () => {};
105
+ export let onarcclick: (
106
+ e: MouseEvent,
107
+ detail: { data: any; series: (typeof series)[number] }
108
+ ) => void = () => {};
105
109
 
106
110
  export let props: {
107
111
  pie?: Partial<ComponentProps<Pie>>;
@@ -123,6 +127,9 @@
123
127
  /** Log initial render performance using `console.time` */
124
128
  export let profile = false;
125
129
 
130
+ /** Enable debug mode */
131
+ export let debug = false;
132
+
126
133
  $: allSeriesData = series
127
134
  .flatMap((s) => s.data?.map((d) => ({ seriesKey: s.key, ...d })))
128
135
  .filter((d) => d) as Array<TData>;
@@ -199,7 +206,7 @@
199
206
  visibleData,
200
207
  }}
201
208
  <slot {...slotProps}>
202
- <svelte:component this={renderContext === 'canvas' ? Canvas : Svg} {center}>
209
+ <svelte:component this={renderContext === 'canvas' ? Canvas : Svg} {center} {debug}>
203
210
  <slot name="belowMarks" {...slotProps} />
204
211
 
205
212
  <slot name="marks" {...slotProps}>
@@ -228,10 +235,10 @@
228
235
  track={{ fill: s.color ?? cScale?.(c(d)), 'fill-opacity': 0.1 }}
229
236
  {tooltip}
230
237
  data={d}
231
- on:click={() => {
232
- onArcClick({ data: d, series: s });
238
+ onclick={(e) => {
239
+ onarcclick(e, { data: d, series: s });
233
240
  // Workaround for `tooltip={{ mode: 'manual' }}
234
- onTooltipClick({ data: d });
241
+ ontooltipclick(e, { data: d });
235
242
  }}
236
243
  {...props.arc}
237
244
  {...s.props}
@@ -264,10 +271,10 @@
264
271
  fill={cScale?.(c(arc.data))}
265
272
  data={arc.data}
266
273
  {tooltip}
267
- on:click={() => {
268
- onArcClick({ data: arc.data, series: s });
274
+ onclick={(e) => {
275
+ onarcclick(e, { data: arc.data, series: s });
269
276
  // Workaround for `tooltip={{ mode: 'manual' }}
270
- onTooltipClick({ data: arc.data });
277
+ ontooltipclick(e, { data: arc.data });
271
278
  }}
272
279
  class={cls(
273
280
  'transition-opacity',
@@ -295,9 +302,9 @@
295
302
  }}
296
303
  placement="bottom"
297
304
  variant="swatches"
298
- onClick={(item) => $selectedKeys.toggleSelected(item.value)}
299
- onPointerEnter={(item) => (highlightKey = item.value)}
300
- onPointerLeave={(item) => (highlightKey = null)}
305
+ onclick={(e, item) => $selectedKeys.toggleSelected(item.value)}
306
+ onpointerenter={(e, item) => (highlightKey = item.value)}
307
+ onpointerleave={(e) => (highlightKey = null)}
301
308
  {...props.legend}
302
309
  {...typeof legend === 'object' ? legend : null}
303
310
  classes={{
@@ -121,13 +121,13 @@ declare class __sveltets_Render<TData> {
121
121
  geo?: import("svelte/store").Writable<import("d3-geo").GeoProjection>;
122
122
  }> | undefined;
123
123
  tooltip?: boolean | Partial<{
124
- mode?: "bisect-x" | "bisect-y" | "band" | "bisect-band" | "bounds" | "voronoi" | "quadtree" | "manual";
124
+ mode?: "manual" | "bisect-x" | "bisect-y" | "band" | "bisect-band" | "bounds" | "voronoi" | "quadtree";
125
125
  findTooltipData?: "closest" | "left" | "right";
126
126
  raiseTarget?: boolean;
127
127
  locked?: boolean;
128
128
  radius?: number;
129
129
  debug?: boolean;
130
- onClick?: ({ data }: {
130
+ onclick?: ({ data }: {
131
131
  data: any;
132
132
  }) => any;
133
133
  tooltip?: import("svelte/store").Writable<{
@@ -136,6 +136,7 @@ declare class __sveltets_Render<TData> {
136
136
  data: any;
137
137
  show: (e: PointerEvent, tooltipData?: any) => void;
138
138
  hide: () => void;
139
+ mode: "manual" | "bisect-x" | "bisect-y" | "band" | "bisect-band" | "bounds" | "voronoi" | "quadtree";
139
140
  }>;
140
141
  }> | undefined;
141
142
  transform?: Partial<{
@@ -150,6 +151,15 @@ declare class __sveltets_Render<TData> {
150
151
  disablePointer?: boolean;
151
152
  initialScrollMode?: "none" | "scale" | "translate";
152
153
  clickDistance?: number;
154
+ ondragstart?: (() => void) | undefined;
155
+ ondragend?: (() => void) | undefined;
156
+ ontransform?: ((e: {
157
+ scale: number;
158
+ translate: {
159
+ x: number;
160
+ y: number;
161
+ };
162
+ }) => void) | undefined;
153
163
  initialTranslate?: {
154
164
  x: number;
155
165
  y: number;
@@ -192,7 +202,18 @@ declare class __sveltets_Render<TData> {
192
202
  data: any;
193
203
  show: (e: PointerEvent, tooltipData?: any) => void;
194
204
  hide: () => void;
205
+ mode: "manual" | "bisect-x" | "bisect-y" | "band" | "bisect-band" | "bounds" | "voronoi" | "quadtree";
195
206
  }> | undefined;
207
+ onresize?: ((e: import("../ChartContext.svelte").ChartResizeDetail) => void) | undefined;
208
+ ondragstart?: (() => void) | undefined;
209
+ ondragend?: (() => void) | undefined;
210
+ ontransform?: ((e: {
211
+ scale: number;
212
+ translate: {
213
+ x: number;
214
+ y: number;
215
+ };
216
+ }) => void) | undefined;
196
217
  } & {
197
218
  cornerRadius?: number;
198
219
  innerRadius?: number | undefined;
@@ -211,9 +232,9 @@ declare class __sveltets_Render<TData> {
211
232
  tickLength?: number | undefined;
212
233
  placement?: ("center" | "bottom" | "left" | "right" | "top" | "top-left" | "top-right" | "bottom-left" | "bottom-right") | undefined;
213
234
  orientation?: "horizontal" | "vertical" | undefined;
214
- onClick?: ((item: any) => any) | undefined | undefined;
215
- onPointerEnter?: ((item: any) => any) | undefined | undefined;
216
- onPointerLeave?: ((item: any) => any) | undefined | undefined;
235
+ onclick?: ((e: MouseEvent, detail: any) => any) | undefined | undefined;
236
+ onpointerenter?: ((e: MouseEvent, detail: any) => any) | undefined | undefined;
237
+ onpointerleave?: ((e: MouseEvent, detail: any) => any) | undefined | undefined;
217
238
  variant?: "ramp" | "swatches" | undefined;
218
239
  classes?: {
219
240
  root?: string;
@@ -231,6 +252,7 @@ declare class __sveltets_Render<TData> {
231
252
  center?: boolean;
232
253
  placement?: "center" | "left" | "right";
233
254
  profile?: boolean;
255
+ debug?: boolean;
234
256
  props?: {
235
257
  pie?: Partial<ComponentProps<Pie>>;
236
258
  group?: Partial<ComponentProps<Group>>;
@@ -259,7 +281,7 @@ declare class __sveltets_Render<TData> {
259
281
  }[] | undefined;
260
282
  value?: Accessor<TData>;
261
283
  renderContext?: "canvas" | "svg";
262
- onArcClick?: ((e: {
284
+ onarcclick?: ((e: MouseEvent, detail: {
263
285
  data: any;
264
286
  series: {
265
287
  key: string | number;
@@ -273,7 +295,7 @@ declare class __sveltets_Render<TData> {
273
295
  props?: Partial<ComponentProps<Arc>>;
274
296
  };
275
297
  }) => void) | undefined;
276
- onTooltipClick?: ((e: {
298
+ ontooltipclick?: ((e: MouseEvent, detail: {
277
299
  data: any;
278
300
  }) => void) | undefined;
279
301
  };
@@ -36,7 +36,7 @@
36
36
  props?: typeof props;
37
37
  series?: typeof series;
38
38
  renderContext?: typeof renderContext;
39
- onTooltipClick?: typeof onTooltipClick;
39
+ ontooltipclick?: typeof ontooltipclick;
40
40
  }
41
41
 
42
42
  export let data: $$Props['data'] = [];
@@ -65,15 +65,17 @@
65
65
  export let rule: ComponentProps<Rule> | boolean = true;
66
66
 
67
67
  /** Event dispatched with current tooltip data */
68
- export let onTooltipClick: (e: { data: any }) => void = () => {};
68
+ export let ontooltipclick: (e: MouseEvent, detail: { data: any }) => void = () => {};
69
69
 
70
70
  export let props: {
71
71
  brush?: Partial<ComponentProps<Brush>>;
72
+ debug?: typeof debug;
72
73
  grid?: Partial<ComponentProps<Grid>>;
73
74
  highlight?: Partial<ComponentProps<Highlight>>;
74
75
  labels?: Partial<ComponentProps<Labels>>;
75
76
  legend?: Partial<ComponentProps<Legend>>;
76
77
  points?: Partial<ComponentProps<Points>>;
78
+ profile?: typeof profile;
77
79
  rule?: Partial<ComponentProps<Rule>>;
78
80
  tooltip?: {
79
81
  context?: Partial<ComponentProps<Tooltip.Context>>;
@@ -92,6 +94,9 @@
92
94
  /** Log initial render performance using `console.time` */
93
95
  export let profile = false;
94
96
 
97
+ /** Enable debug mode */
98
+ export let debug = false;
99
+
95
100
  // Default xScale based on first data's `x` value
96
101
  $: xScale =
97
102
  $$props.xScale ??
@@ -175,7 +180,8 @@
175
180
  ? false
176
181
  : {
177
182
  mode: 'voronoi',
178
- onClick: onTooltipClick,
183
+ onclick: ontooltipclick,
184
+ debug,
179
185
  ...props.tooltip?.context,
180
186
  ...$$props.tooltip,
181
187
  }}
@@ -213,7 +219,7 @@
213
219
  : null}
214
220
 
215
221
  <slot {...slotProps}>
216
- <svelte:component this={renderContext === 'canvas' ? Canvas : Svg}>
222
+ <svelte:component this={renderContext === 'canvas' ? Canvas : Svg} {debug}>
217
223
  <slot name="grid" {...slotProps}>
218
224
  {#if grid}
219
225
  <Grid x y {...typeof grid === 'object' ? grid : null} {...props.grid} />
@@ -300,9 +306,9 @@
300
306
  tickFormat={(key) => series.find((s) => s.key === key)?.label ?? key}
301
307
  placement="bottom"
302
308
  variant="swatches"
303
- onClick={(item) => $selectedSeries.toggleSelected(item.value)}
304
- onPointerEnter={(item) => (highlightSeriesKey = item.value)}
305
- onPointerLeave={(item) => (highlightSeriesKey = null)}
309
+ onclick={(e, item) => $selectedSeries.toggleSelected(item.value)}
310
+ onpointerenter={(e, item) => (highlightSeriesKey = item.value)}
311
+ onpointerleave={(e) => (highlightSeriesKey = null)}
306
312
  {...props.legend}
307
313
  {...typeof legend === 'object' ? legend : null}
308
314
  classes={{
@@ -31,7 +31,6 @@ export { default as Graticule } from './Graticule.svelte';
31
31
  export { default as Grid } from './Grid.svelte';
32
32
  export { default as Group } from './Group.svelte';
33
33
  export { default as Highlight } from './Highlight.svelte';
34
- export { default as HitCanvas } from './HitCanvas.svelte';
35
34
  export { default as Hull } from './Hull.svelte';
36
35
  export { default as Labels } from './Labels.svelte';
37
36
  export { default as Legend } from './Legend.svelte';
@@ -32,7 +32,6 @@ export { default as Graticule } from './Graticule.svelte';
32
32
  export { default as Grid } from './Grid.svelte';
33
33
  export { default as Group } from './Group.svelte';
34
34
  export { default as Highlight } from './Highlight.svelte';
35
- export { default as HitCanvas } from './HitCanvas.svelte';
36
35
  export { default as Hull } from './Hull.svelte';
37
36
  export { default as Labels } from './Labels.svelte';
38
37
  export { default as Legend } from './Legend.svelte';
@@ -3,8 +3,19 @@
3
3
 
4
4
  type ComponentRender = {
5
5
  name: string;
6
- render: (ctx: CanvasRenderingContext2D) => any;
6
+ render: (ctx: CanvasRenderingContext2D, styleOverrides?: ComputedStylesOptions) => any;
7
7
  retainState?: boolean;
8
+ events?: {
9
+ click?: (e: MouseEvent) => void;
10
+ dblclick?: (e: MouseEvent) => void;
11
+ pointerenter?: (e: PointerEvent) => void;
12
+ pointerover?: (e: PointerEvent) => void;
13
+ pointermove?: (e: PointerEvent) => void;
14
+ pointerleave?: (e: PointerEvent) => void;
15
+ pointerout?: (e: PointerEvent) => void;
16
+ pointerdown?: (e: PointerEvent) => void;
17
+ touchmove?: (e: TouchEvent) => void;
18
+ };
8
19
  };
9
20
 
10
21
  export type CanvasContext = {
@@ -30,7 +41,10 @@
30
41
 
31
42
  import { chartContext } from '../ChartContext.svelte';
32
43
  import { transformContext } from '../TransformContext.svelte';
33
- import { scaleCanvas } from '../../utils/canvas.js';
44
+ import { getPixelColor, scaleCanvas, type ComputedStylesOptions } from '../../utils/canvas.js';
45
+ import { getColorStr, rgbColorGenerator } from '../../utils/color.js';
46
+ import { localPoint } from '../../utils/event.js';
47
+ import { tooltipContext } from '../tooltip/TooltipContext.svelte';
34
48
 
35
49
  const { width, height, containerWidth, containerHeight, padding } = chartContext();
36
50
 
@@ -68,14 +82,78 @@
68
82
  */
69
83
  export let center: boolean | 'x' | 'y' = false;
70
84
 
85
+ /** Show hit canvas for debugging */
86
+ export let debug = false;
87
+
71
88
  let components = new Map<Symbol, ComponentRender>();
72
89
  let pendingInvalidation = false;
73
90
  let frameId: number | undefined;
74
91
 
75
- const { mode, scale, translate } = transformContext();
92
+ const { mode, scale, translate, dragging, moving } = transformContext();
93
+ const tooltip = tooltipContext();
94
+
95
+ /**
96
+ * HitCanvas
97
+ */
98
+ let hitCanvasElement: HTMLCanvasElement | undefined = undefined;
99
+ let hitCanvasContext: CanvasRenderingContext2D | undefined = undefined;
100
+ let colorGenerator = rgbColorGenerator();
101
+ let activeCanvas = false;
102
+ let lastActiveComponent: ComponentRender | undefined | null;
103
+ const componentByColor = new Map<string, ComponentRender>();
104
+
105
+ function getPointerComponent(e: PointerEvent | MouseEvent | TouchEvent) {
106
+ const { x, y } = localPoint(e.target as HTMLCanvasElement, e) ?? { x: 0, y: 0 };
107
+ const color = getPixelColor(hitCanvasContext!, x, y);
108
+ const colorKey = getColorStr(color);
109
+ return componentByColor.get(colorKey);
110
+ }
111
+
112
+ function onPointerMove(e: PointerEvent) {
113
+ activeCanvas = true;
114
+ const component = getPointerComponent(e);
115
+
116
+ if (lastActiveComponent == null) {
117
+ // TODO: Should these be handled differently
118
+ component?.events?.pointerenter?.(e);
119
+ component?.events?.pointerover?.(e);
120
+ } else if (lastActiveComponent != component) {
121
+ // TODO: Should these be handled differently
122
+ lastActiveComponent?.events?.pointerleave?.(e);
123
+ lastActiveComponent?.events?.pointerout?.(e);
124
+
125
+ component?.events?.pointermove?.(e);
126
+ } else {
127
+ component?.events?.pointermove?.(e);
128
+ }
129
+
130
+ if (e.buttons === 1) {
131
+ component?.events?.pointerdown?.(e);
132
+ }
133
+
134
+ lastActiveComponent = component;
135
+ }
136
+
137
+ function onPointerLeave(e: PointerEvent) {
138
+ // Pointer outside of canvas
139
+
140
+ // Call last active component `pointerleave` event in case it was not triggered by hit canvas (quickly exiting canvas element before `pointermove` is triggered)
141
+ lastActiveComponent?.events?.pointerleave?.(e);
142
+ lastActiveComponent?.events?.pointerout?.(e);
143
+
144
+ lastActiveComponent = null;
145
+ activeCanvas = false;
146
+ }
147
+ /**
148
+ * end HitCanvas
149
+ */
76
150
 
77
151
  onMount(() => {
78
152
  context = element?.getContext('2d', { willReadFrequently }) as CanvasRenderingContext2D;
153
+
154
+ hitCanvasContext = hitCanvasElement?.getContext('2d', {
155
+ willReadFrequently: true,
156
+ }) as CanvasRenderingContext2D;
79
157
  });
80
158
 
81
159
  onDestroy(() => {
@@ -110,6 +188,16 @@
110
188
  context.scale($scale, $scale);
111
189
  }
112
190
 
191
+ // Sync hit canvas transform with main canvas
192
+ if (hitCanvasContext) {
193
+ scaleCanvas(hitCanvasContext, $containerWidth, $containerHeight);
194
+ hitCanvasContext.clearRect(0, 0, $containerWidth, $containerHeight);
195
+ hitCanvasContext.setTransform(context.getTransform());
196
+
197
+ // Reset color generator whenever updated so always reusing same colors (and not exhausting)
198
+ colorGenerator = rgbColorGenerator();
199
+ }
200
+
113
201
  components.forEach((c) => {
114
202
  if (c.retainState) {
115
203
  // Do not call save/restore canvas draw state (https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save) (ex. Group ctx.translate() affecting children)
@@ -119,6 +207,27 @@
119
207
  c.render(context);
120
208
  context.restore();
121
209
  }
210
+
211
+ // Delayed rendering using `activeCanvas` can cause a delay for tooltip interactivity for complex canvases (ex. country choropleth) so only ignore while moving/animating programmatically (ex. clicking on countries on Animated Globe)
212
+ const inactiveMoving = !activeCanvas && $moving;
213
+
214
+ const componentHasEvents = c.events && Object.values(c.events).filter((d) => d).length > 0;
215
+
216
+ if (hitCanvasContext && componentHasEvents && !inactiveMoving && !$dragging) {
217
+ const color = getColorStr(colorGenerator.next().value);
218
+ // Stroking shape seems to help with dark border, but there is still antialising and thus gaps
219
+ const styleOverrides = { styles: { fill: color, stroke: color, _fillOpacity: 0.1 } };
220
+
221
+ if (c.retainState) {
222
+ c.render(hitCanvasContext, styleOverrides);
223
+ } else {
224
+ hitCanvasContext.save();
225
+ c.render(hitCanvasContext, styleOverrides);
226
+ hitCanvasContext.restore();
227
+ }
228
+
229
+ componentByColor.set(color, c);
230
+ }
122
231
  });
123
232
 
124
233
  pendingInvalidation = false;
@@ -144,8 +253,8 @@
144
253
  };
145
254
 
146
255
  $: {
147
- // Redraw when resized
148
- $containerWidth, $containerHeight;
256
+ // Redraw when resized or transform dragging changes. Note: adding `activeCanvas` (pointer enters/exits canvas) causes initial interactivity issues while canvas is rendering and is not needed
257
+ $containerWidth, $containerHeight && $dragging;
149
258
  canvasContext.invalidate();
150
259
  }
151
260
 
@@ -164,16 +273,48 @@
164
273
  aria-label={label}
165
274
  aria-labelledby={labelledBy}
166
275
  aria-describedby={describedBy}
276
+ on:click={(e) => {
277
+ const component = getPointerComponent(e);
278
+ component?.events?.click?.(e);
279
+ }}
280
+ on:click
281
+ on:dblclick={(e) => {
282
+ const component = getPointerComponent(e);
283
+ component?.events?.dblclick?.(e);
284
+ }}
285
+ on:pointerenter={onPointerMove}
167
286
  on:pointerenter
287
+ on:pointermove={onPointerMove}
168
288
  on:pointermove
289
+ on:pointerleave={onPointerLeave}
169
290
  on:pointerleave
170
- on:pointerleave
291
+ on:touchmove={(e) => {
292
+ // Prevent touch from interfering with pointer if over data
293
+ if (lastActiveComponent) {
294
+ e.preventDefault();
295
+ }
296
+
297
+ const component = getPointerComponent(e);
298
+ component?.events?.touchmove?.(e);
299
+ }}
171
300
  on:touchmove
172
- on:click
173
301
  >
174
302
  <slot name="fallback">
175
303
  {fallback || ''}
176
304
  </slot>
177
305
  </canvas>
178
306
 
307
+ <!-- Hit canvas used for hidden context -->
308
+ <canvas
309
+ bind:this={hitCanvasElement}
310
+ class={cls(
311
+ 'layerchart-hitcanvas',
312
+ 'absolute top-0 left-0 w-full h-full',
313
+ 'pointer-events-none', // events all handled by main canvas
314
+ // '[image-rendering:pixelated]', // https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering
315
+ 'border border-danger',
316
+ !debug && 'opacity-0'
317
+ )}
318
+ ></canvas>
319
+
179
320
  <slot {element} {context}></slot>
@@ -1,8 +1,19 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
2
  type ComponentRender = {
3
3
  name: string;
4
- render: (ctx: CanvasRenderingContext2D) => any;
4
+ render: (ctx: CanvasRenderingContext2D, styleOverrides?: ComputedStylesOptions) => any;
5
5
  retainState?: boolean;
6
+ events?: {
7
+ click?: (e: MouseEvent) => void;
8
+ dblclick?: (e: MouseEvent) => void;
9
+ pointerenter?: (e: PointerEvent) => void;
10
+ pointerover?: (e: PointerEvent) => void;
11
+ pointermove?: (e: PointerEvent) => void;
12
+ pointerleave?: (e: PointerEvent) => void;
13
+ pointerout?: (e: PointerEvent) => void;
14
+ pointerdown?: (e: PointerEvent) => void;
15
+ touchmove?: (e: TouchEvent) => void;
16
+ };
6
17
  };
7
18
  export type CanvasContext = {
8
19
  /** Register component to render. Returns method to unregister on component destory */
@@ -11,6 +22,7 @@ export type CanvasContext = {
11
22
  };
12
23
  export declare const canvasContextKey: unique symbol;
13
24
  export declare function getCanvasContext(): CanvasContext;
25
+ import { type ComputedStylesOptions } from '../../utils/canvas.js';
14
26
  declare const __propDef: {
15
27
  props: {
16
28
  [x: string]: any;
@@ -24,13 +36,14 @@ declare const __propDef: {
24
36
  labelledBy?: string | undefined | undefined;
25
37
  describedBy?: string | undefined | undefined;
26
38
  center?: boolean | "x" | "y" | undefined;
39
+ debug?: boolean | undefined;
27
40
  };
28
41
  events: {
42
+ click: MouseEvent;
29
43
  pointerenter: PointerEvent;
30
44
  pointermove: PointerEvent;
31
45
  pointerleave: PointerEvent;
32
46
  touchmove: TouchEvent;
33
- click: MouseEvent;
34
47
  } & {
35
48
  [evt: string]: CustomEvent<any>;
36
49
  };