layerchart 0.71.2 → 0.72.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.
@@ -47,6 +47,9 @@
47
47
  export let spring: ComponentProps<Rect>['spring'] = undefined;
48
48
  export let tweened: ComponentProps<Rect>['tweened'] = undefined;
49
49
 
50
+ /** Event dispatched when individual Bar is clicked */
51
+ export let onBarClick: (e: { data: any }) => void = () => {};
52
+
50
53
  $: _data = chartDataArray(data ?? $contextData);
51
54
  </script>
52
55
 
@@ -66,6 +69,7 @@
66
69
  {inset}
67
70
  {spring}
68
71
  {tweened}
72
+ on:click={() => onBarClick({ data: d })}
69
73
  {...$$restProps}
70
74
  />
71
75
  {/each}
@@ -1,3 +1,7 @@
1
+ <script lang="ts" context="module">
2
+ export type HighlightPointData = { x: any; y: any };
3
+ </script>
4
+
1
5
  <script lang="ts">
2
6
  import { type ComponentProps } from 'svelte';
3
7
  import { max, min } from 'd3-array';
@@ -62,10 +66,14 @@
62
66
  /** Set to false to disable spring transitions */
63
67
  export let motion = true;
64
68
 
69
+ export let onAreaClick: (e: { data: any }) => void = () => {};
70
+ export let onBarClick: (e: { data: any }) => void = () => {};
71
+ export let onPointClick: (e: { point: (typeof _points)[number]; data: any }) => void = () => {};
72
+
65
73
  const _x = accessor(x);
66
74
  const _y = accessor(y);
67
75
 
68
- let _points: { x: number; y: number; fill: string }[] = [];
76
+ let _points: { x: number; y: number; fill: string; data: HighlightPointData }[] = [];
69
77
  let _lines: { x1: number; y1: number; x2: number; y2: number }[] = [];
70
78
  let _area = {
71
79
  x: 0,
@@ -217,6 +225,10 @@
217
225
  x: $xScale(seriesPoint.point[1]) + xOffset,
218
226
  y: yCoord + yOffset,
219
227
  fill: $config.c ? $cGet(seriesPoint.series) : null,
228
+ data: {
229
+ x: seriesPoint.point[1],
230
+ y: yValue,
231
+ },
220
232
  };
221
233
  });
222
234
  }
@@ -229,6 +241,10 @@
229
241
  y: yCoord + yOffset,
230
242
  // TODO: is there a better way to expose the series key/value?
231
243
  fill: $config.c ? $cGet({ ...highlightData, $key }) : null,
244
+ data: {
245
+ x: xValue, // TODO: use highlightData[$key]?
246
+ y: yValue,
247
+ },
232
248
  };
233
249
  });
234
250
  }
@@ -256,6 +272,10 @@
256
272
  x: xCoord + xOffset,
257
273
  y: $yScale(seriesPoint.point[1]) + yOffset,
258
274
  fill: $config.c ? $cGet(seriesPoint.series) : null,
275
+ data: {
276
+ x: xValue,
277
+ y: seriesPoint.point[1],
278
+ },
259
279
  }));
260
280
  }
261
281
  } else {
@@ -267,6 +287,10 @@
267
287
  y: yItem + yOffset,
268
288
  // TODO: is there a better way to expose the series key/value?
269
289
  fill: $config.c ? $cGet({ ...highlightData, $key }) : null,
290
+ data: {
291
+ x: xValue,
292
+ y: yValue, // TODO: use highlightData[$key] ?
293
+ },
270
294
  };
271
295
  });
272
296
  }
@@ -276,6 +300,10 @@
276
300
  x: xCoord + xOffset,
277
301
  y: yCoord + yOffset,
278
302
  fill: $config.c ? $cGet(highlightData) : null,
303
+ data: {
304
+ x: xValue,
305
+ y: yValue,
306
+ },
279
307
  },
280
308
  ];
281
309
  } else {
@@ -322,7 +350,7 @@
322
350
  !area.fill && 'fill-surface-content/5',
323
351
  typeof area === 'object' ? area.class : null
324
352
  )}
325
- on:click
353
+ on:click={() => onAreaClick({ data: highlightData })}
326
354
  />
327
355
  </slot>
328
356
  {/if}
@@ -343,7 +371,7 @@
343
371
  !bar.fill && 'fill-primary',
344
372
  typeof bar === 'object' ? bar.class : null
345
373
  )}
346
- on:click
374
+ on:click={() => onBarClick({ data: highlightData })}
347
375
  />
348
376
  </slot>
349
377
  {/if}
@@ -382,6 +410,7 @@
382
410
  !point.fill && (typeof points === 'boolean' || !points.fill) && 'fill-primary',
383
411
  typeof points === 'object' ? points.class : null
384
412
  )}
413
+ on:click={() => onPointClick({ point, data: highlightData })}
385
414
  />
386
415
  {/each}
387
416
  </slot>
@@ -10,7 +10,7 @@
10
10
  import Canvas from '../layout/Canvas.svelte';
11
11
  import Chart from '../Chart.svelte';
12
12
  import Grid from '../Grid.svelte';
13
- import Highlight from '../Highlight.svelte';
13
+ import Highlight, { type HighlightPointData } from '../Highlight.svelte';
14
14
  import Labels from '../Labels.svelte';
15
15
  import Legend from '../Legend.svelte';
16
16
  import Line from '../Line.svelte';
@@ -38,6 +38,8 @@
38
38
  series?: typeof series;
39
39
  seriesLayout?: typeof seriesLayout;
40
40
  renderContext?: typeof renderContext;
41
+ onPointClick?: typeof onPointClick;
42
+ onTooltipClick?: typeof onTooltipClick;
41
43
  }
42
44
 
43
45
  export let data: $$Props['data'] = [];
@@ -69,6 +71,15 @@
69
71
  export let legend: ComponentProps<Legend> | boolean = false;
70
72
  export let points: ComponentProps<Points> | boolean = false;
71
73
 
74
+ /** Event dispatched with current tooltip data */
75
+ export let onTooltipClick: (e: { data: any }) => void = () => {};
76
+
77
+ /** Event dispatched when Highlight point is clicked (useful with multiple series) */
78
+ export let onPointClick: (e: {
79
+ data: HighlightPointData;
80
+ series: (typeof series)[number];
81
+ }) => void = () => {};
82
+
72
83
  export let props: {
73
84
  xAxis?: Partial<ComponentProps<Axis>>;
74
85
  yAxis?: Partial<ComponentProps<Axis>>;
@@ -162,7 +173,7 @@
162
173
  yNice
163
174
  {radial}
164
175
  padding={radial ? undefined : defaultChartPadding(axis, legend)}
165
- tooltip={{ mode: 'bisect-x' }}
176
+ tooltip={{ mode: 'bisect-x', onClick: onTooltipClick }}
166
177
  {...$$restProps}
167
178
  let:x
168
179
  let:xScale
@@ -262,6 +273,7 @@
262
273
  y={stackSeries ? (d) => d.stackData[i][1] : (s.value ?? (s.data ? undefined : s.key))}
263
274
  points={{ fill: s.color }}
264
275
  lines={i == 0}
276
+ onPointClick={(e) => onPointClick({ ...e, series: s })}
265
277
  {...props.highlight}
266
278
  />
267
279
  {/each}
@@ -21,6 +21,7 @@
21
21
  accessor,
22
22
  chartDataArray,
23
23
  defaultChartPadding,
24
+ findRelatedData,
24
25
  type Accessor,
25
26
  } from '../../utils/common.js';
26
27
 
@@ -39,6 +40,8 @@
39
40
  series?: typeof series;
40
41
  seriesLayout?: typeof seriesLayout;
41
42
  renderContext?: typeof renderContext;
43
+ onBarClick?: typeof onBarClick;
44
+ onTooltipClick?: typeof onTooltipClick;
42
45
  }
43
46
 
44
47
  export let data: $$Props['data'] = [];
@@ -81,6 +84,13 @@
81
84
  /** Padding between group/series items when using 'seriesLayout="group"', applied to scaleBand().padding() */
82
85
  export let groupPadding = 0;
83
86
 
87
+ /** Event dispatched with current tooltip data */
88
+ export let onTooltipClick: (e: { data: any }) => void = () => {};
89
+
90
+ // TODO: Need to find a way to have this play nice with `tooltip={{ mode: 'band' }}`
91
+ /** Event dispatched when individual Bar is clicked (useful with multiple series) */
92
+ export let onBarClick: (e: { data: any; series: (typeof series)[number] }) => void = () => {};
93
+
84
94
  $: xScale = $$props.xScale ?? (isVertical ? scaleBand().padding(bandPadding) : scaleLinear());
85
95
  $: xBaseline = isVertical ? undefined : 0;
86
96
 
@@ -172,6 +182,7 @@
172
182
  radius: 4,
173
183
  strokeWidth: 1,
174
184
  fill: s.color,
185
+ onBarClick: (e) => onBarClick({ data: e.data, series: s }),
175
186
  ...props.bars,
176
187
  ...s.props,
177
188
  };
@@ -205,7 +216,7 @@
205
216
  c={isVertical ? y : x}
206
217
  cRange={['hsl(var(--color-primary))']}
207
218
  padding={defaultChartPadding(axis, legend)}
208
- tooltip={{ mode: 'band' }}
219
+ tooltip={{ mode: 'band', onClick: onTooltipClick }}
209
220
  {...$$restProps}
210
221
  let:x
211
222
  let:xScale
@@ -331,10 +342,11 @@
331
342
  <!-- Reverse series order so tooltip items match stacks -->
332
343
  {@const seriesItems = stackSeries ? [...series].reverse() : series}
333
344
  {#each seriesItems as s}
334
- {@const valueAccessor = accessor(s.value ?? s.key)}
345
+ {@const seriesTooltipData = s.data ? findRelatedData(s.data, data, x) : data}
346
+ {@const valueAccessor = accessor(s.value ?? (s.data ? (y as any) : s.key))}
335
347
  <Tooltip.Item
336
348
  label={s.label ?? (s.key !== 'default' ? s.key : 'value')}
337
- value={valueAccessor(data)}
349
+ value={seriesTooltipData ? valueAccessor(seriesTooltipData) : null}
338
350
  color={s.color ?? cScale?.(c(data))}
339
351
  {format}
340
352
  valueAlign="right"
@@ -347,8 +359,9 @@
347
359
  <Tooltip.Item
348
360
  label="total"
349
361
  value={sum(series, (s) => {
350
- const valueAccessor = accessor(s.value ?? s.key);
351
- return valueAccessor(data);
362
+ const seriesTooltipData = s.data ? findRelatedData(s.data, data, x) : data;
363
+ const valueAccessor = accessor(s.value ?? (s.data ? (y as any) : s.key));
364
+ return valueAccessor(seriesTooltipData);
352
365
  })}
353
366
  format="integer"
354
367
  valueAlign="right"
@@ -7,7 +7,7 @@
7
7
  import Canvas from '../layout/Canvas.svelte';
8
8
  import Chart from '../Chart.svelte';
9
9
  import Grid from '../Grid.svelte';
10
- import Highlight from '../Highlight.svelte';
10
+ import Highlight, { type HighlightPointData } from '../Highlight.svelte';
11
11
  import Labels from '../Labels.svelte';
12
12
  import Legend from '../Legend.svelte';
13
13
  import Points from '../Points.svelte';
@@ -34,6 +34,8 @@
34
34
  rule?: typeof rule;
35
35
  series?: typeof series;
36
36
  renderContext?: typeof renderContext;
37
+ onPointClick?: typeof onPointClick;
38
+ onTooltipClick?: typeof onTooltipClick;
37
39
  }
38
40
 
39
41
  export let data: $$Props['data'] = [];
@@ -61,6 +63,15 @@
61
63
  export let legend: ComponentProps<Legend> | boolean = false;
62
64
  export let points: ComponentProps<Points> | boolean = false;
63
65
 
66
+ /** Event dispatched with current tooltip data */
67
+ export let onTooltipClick: (e: { data: any }) => void = () => {};
68
+
69
+ /** Event dispatched when Highlight point is clicked (useful with multiple series) */
70
+ export let onPointClick: (e: {
71
+ data: HighlightPointData;
72
+ series: (typeof series)[number];
73
+ }) => void = () => {};
74
+
64
75
  export let props: {
65
76
  xAxis?: Partial<ComponentProps<Axis>>;
66
77
  yAxis?: Partial<ComponentProps<Axis>>;
@@ -110,7 +121,7 @@
110
121
  yNice
111
122
  {radial}
112
123
  padding={radial ? undefined : defaultChartPadding(axis, legend)}
113
- tooltip={{ mode: 'bisect-x' }}
124
+ tooltip={{ mode: 'bisect-x', onClick: onTooltipClick }}
114
125
  {...$$restProps}
115
126
  let:x
116
127
  let:xScale
@@ -206,6 +217,7 @@
206
217
  y={s.value ?? (s.data ? undefined : s.key)}
207
218
  points={{ fill: s.color }}
208
219
  lines={i === 0}
220
+ onPointClick={(e) => onPointClick({ ...e, series: s })}
209
221
  {...props.highlight}
210
222
  />
211
223
  {/each}
@@ -30,8 +30,10 @@
30
30
  props?: typeof props;
31
31
  range?: typeof range;
32
32
  series?: typeof series;
33
- value?: typeof label;
33
+ value?: typeof value;
34
34
  renderContext?: typeof renderContext;
35
+ onArcClick?: typeof onArcClick;
36
+ onTooltipClick?: typeof onTooltipClick;
35
37
  }
36
38
 
37
39
  export let data: ChartProps['data'] = [];
@@ -93,6 +95,13 @@
93
95
  /** Center chart. Override and use `props.group` for more control */
94
96
  export let center = placement === 'center';
95
97
 
98
+ // TODO: Not usable with manual tooltip / arc path. Use `onArcClick`?
99
+ /** Event dispatched with current tooltip data */
100
+ export let onTooltipClick: (e: { data: any }) => void = () => {};
101
+
102
+ /** Event dispatched when individual Arc is clicked (useful with multiple series) */
103
+ export let onArcClick: (e: { data: any; series: (typeof series)[number] }) => void = () => {};
104
+
96
105
  export let props: {
97
106
  pie?: Partial<ComponentProps<Pie>>;
98
107
  group?: Partial<ComponentProps<Group>>;
@@ -184,6 +193,11 @@
184
193
  track={{ fill: s.color ?? cScale?.(c(d)), 'fill-opacity': 0.1 }}
185
194
  {tooltip}
186
195
  data={d}
196
+ on:click={() => {
197
+ onArcClick({ data: d, series: s });
198
+ // Workaround for `tooltip={{ mode: 'manual' }}
199
+ onTooltipClick({ data: d });
200
+ }}
187
201
  {...props.arc}
188
202
  {...s.props}
189
203
  />
@@ -209,6 +223,11 @@
209
223
  fill={cScale?.(c(arc.data))}
210
224
  data={arc.data}
211
225
  {tooltip}
226
+ on:click={() => {
227
+ onArcClick({ data: arc.data, series: s });
228
+ // Workaround for `tooltip={{ mode: 'manual' }}
229
+ onTooltipClick({ data: arc.data });
230
+ }}
212
231
  {...props.arc}
213
232
  {...s.props}
214
233
  />
@@ -245,6 +245,23 @@ declare class __sveltets_Render<TData> {
245
245
  }[] | undefined;
246
246
  value?: Accessor<TData>;
247
247
  renderContext?: "canvas" | "svg";
248
+ onArcClick?: ((e: {
249
+ data: any;
250
+ series: {
251
+ key: string | number;
252
+ label?: string;
253
+ value?: Accessor<TData>;
254
+ /** Provider series data, else uses chart data (with value/key accessor) */
255
+ data?: TData[] | undefined;
256
+ /** Maximum possible value, useful when `data` is single item */
257
+ maxValue?: number;
258
+ color?: string;
259
+ props?: Partial<ComponentProps<Arc>>;
260
+ };
261
+ }) => void) | undefined;
262
+ onTooltipClick?: ((e: {
263
+ data: any;
264
+ }) => void) | undefined;
248
265
  };
249
266
  events(): {} & {
250
267
  [evt: string]: CustomEvent<any>;
@@ -30,6 +30,7 @@
30
30
  props?: typeof props;
31
31
  series?: typeof series;
32
32
  renderContext?: typeof renderContext;
33
+ onTooltipClick?: typeof onTooltipClick;
33
34
  }
34
35
 
35
36
  export let data: $$Props['data'] = [];
@@ -51,6 +52,9 @@
51
52
  export let legend: ComponentProps<Legend> | boolean = false;
52
53
  export let rule: ComponentProps<Rule> | boolean = true;
53
54
 
55
+ /** Event dispatched with current tooltip data */
56
+ export let onTooltipClick: (e: { data: any }) => void = () => {};
57
+
54
58
  export let props: {
55
59
  xAxis?: Partial<ComponentProps<Axis>>;
56
60
  yAxis?: Partial<ComponentProps<Axis>>;
@@ -100,7 +104,7 @@
100
104
  {yScale}
101
105
  yNice
102
106
  padding={defaultChartPadding(axis, legend)}
103
- tooltip={{ mode: 'voronoi' }}
107
+ tooltip={{ mode: 'voronoi', onClick: onTooltipClick }}
104
108
  {...$$restProps}
105
109
  let:x
106
110
  let:xScale
@@ -8,9 +8,9 @@
8
8
  import { isScaleBand } from '../../utils/scales.js';
9
9
 
10
10
  /** `x` position of tooltip. By default uses the pointer/mouse, can also snap to data or an explicit fixed position. */
11
- export let x: 'pointer' | 'data' | number | undefined = 'pointer';
11
+ export let x: 'pointer' | 'data' | number = 'pointer';
12
12
  /** `y` position of tooltip. By default uses the pointer/mouse, can also snap to data or an explicit fixed position. */
13
- export let y: 'pointer' | 'data' | number | undefined = 'pointer';
13
+ export let y: 'pointer' | 'data' | number = 'pointer';
14
14
 
15
15
  /** Offset added to `x` position */
16
16
  export let xOffset = x === 'pointer' ? 10 : 0;
@@ -135,20 +135,25 @@
135
135
  rect.right = rect.left + tooltipWidth;
136
136
 
137
137
  if (contained === 'container') {
138
- // Check if outside of container and swap align side accordingly
139
- if ((xAlign === 'start' || xAlign === 'center') && rect.right > $containerWidth) {
140
- rect.left = alignValue(xValue, 'end', xOffset, tooltipWidth);
141
- }
142
- if ((xAlign === 'end' || xAlign === 'center') && rect.left < $padding.left) {
143
- rect.left = alignValue(xValue, 'start', xOffset, tooltipWidth);
138
+ // Only attempt repositiong if not fixed (ie. `pointer`/`data`)
139
+ if (typeof x !== 'number') {
140
+ // Check if outside of container and swap align side accordingly
141
+ if ((xAlign === 'start' || xAlign === 'center') && rect.right > $containerWidth) {
142
+ rect.left = alignValue(xValue, 'end', xOffset, tooltipWidth);
143
+ }
144
+ if ((xAlign === 'end' || xAlign === 'center') && rect.left < $padding.left) {
145
+ rect.left = alignValue(xValue, 'start', xOffset, tooltipWidth);
146
+ }
144
147
  }
145
148
  rect.right = rect.left + tooltipWidth;
146
149
 
147
- if ((yAlign === 'start' || yAlign === 'center') && rect.bottom > $containerHeight) {
148
- rect.top = alignValue(yValue, 'end', yOffset, tooltipHeight);
149
- }
150
- if ((yAlign === 'end' || yAlign === 'center') && rect.top < $padding.top) {
151
- rect.top = alignValue(yValue, 'start', yOffset, tooltipHeight);
150
+ if (typeof y !== 'number') {
151
+ if ((yAlign === 'start' || yAlign === 'center') && rect.bottom > $containerHeight) {
152
+ rect.top = alignValue(yValue, 'end', yOffset, tooltipHeight);
153
+ }
154
+ if ((yAlign === 'end' || yAlign === 'center') && rect.top < $padding.top) {
155
+ rect.top = alignValue(yValue, 'start', yOffset, tooltipHeight);
156
+ }
152
157
  }
153
158
  rect.bottom = rect.top + tooltipHeight;
154
159
  } else if (contained === 'window') {
@@ -156,25 +161,34 @@
156
161
  // Root <div> won't be available on initial mount
157
162
  if (rootEl?.parentElement) {
158
163
  const parentViewportRect = rootEl.parentElement.getBoundingClientRect();
159
- if (
160
- (xAlign === 'start' || xAlign === 'center') &&
161
- parentViewportRect.left + rect.right > window.innerWidth
162
- ) {
163
- rect.left = alignValue(xValue, 'end', xOffset, tooltipWidth);
164
- }
165
- if ((xAlign === 'end' || xAlign === 'center') && parentViewportRect.left + rect.left < 0) {
166
- rect.left = alignValue(xValue, 'start', xOffset, tooltipWidth);
164
+
165
+ // Only attempt repositiong if not fixed (ie. `pointer`/`data`)
166
+ if (typeof x !== 'number') {
167
+ if (
168
+ (xAlign === 'start' || xAlign === 'center') &&
169
+ parentViewportRect.left + rect.right > window.innerWidth
170
+ ) {
171
+ rect.left = alignValue(xValue, 'end', xOffset, tooltipWidth);
172
+ }
173
+ if (
174
+ (xAlign === 'end' || xAlign === 'center') &&
175
+ parentViewportRect.left + rect.left < 0
176
+ ) {
177
+ rect.left = alignValue(xValue, 'start', xOffset, tooltipWidth);
178
+ }
167
179
  }
168
180
  rect.right = rect.left + tooltipWidth;
169
181
 
170
- if (
171
- (yAlign === 'start' || yAlign === 'center') &&
172
- parentViewportRect.top + rect.bottom > window.innerHeight
173
- ) {
174
- rect.top = alignValue(yValue, 'end', yOffset, tooltipHeight);
175
- }
176
- if ((yAlign === 'end' || yAlign === 'center') && parentViewportRect.top + rect.top < 0) {
177
- rect.top = alignValue(yValue, 'start', yOffset, tooltipHeight);
182
+ if (typeof y !== 'number') {
183
+ if (
184
+ (yAlign === 'start' || yAlign === 'center') &&
185
+ parentViewportRect.top + rect.bottom > window.innerHeight
186
+ ) {
187
+ rect.top = alignValue(yValue, 'end', yOffset, tooltipHeight);
188
+ }
189
+ if ((yAlign === 'end' || yAlign === 'center') && parentViewportRect.top + rect.top < 0) {
190
+ rect.top = alignValue(yValue, 'start', yOffset, tooltipHeight);
191
+ }
178
192
  }
179
193
  rect.bottom = rect.top + tooltipHeight;
180
194
  }
@@ -2,8 +2,8 @@ import { SvelteComponentTyped } from "svelte";
2
2
  declare const __propDef: {
3
3
  props: {
4
4
  [x: string]: any;
5
- x?: "pointer" | "data" | number | undefined | undefined;
6
- y?: "pointer" | "data" | number | undefined | undefined;
5
+ x?: "pointer" | "data" | number | undefined;
6
+ y?: "pointer" | "data" | number | undefined;
7
7
  xOffset?: number | undefined;
8
8
  yOffset?: number | undefined;
9
9
  anchor?: ("center" | "bottom" | "left" | "right" | "top" | "top-left" | "top-right" | "bottom-left" | "bottom-right") | undefined;
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.71.2",
7
+ "version": "0.72.0",
8
8
  "devDependencies": {
9
9
  "@changesets/cli": "^2.27.10",
10
10
  "@mdi/js": "^7.4.47",