layerchart 0.79.3 → 0.80.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.
@@ -223,8 +223,8 @@
223
223
  }
224
224
 
225
225
  // TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
226
- $: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
227
- $: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
226
+ $: fillKey = fill && typeof fill === 'object' ? objectId(fill) : fill;
227
+ $: strokeKey = stroke && typeof stroke === 'object' ? objectId(stroke) : stroke;
228
228
 
229
229
  $: if (renderContext === 'canvas') {
230
230
  // Redraw when props change
@@ -148,8 +148,8 @@
148
148
  }
149
149
 
150
150
  // TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
151
- $: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
152
- $: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
151
+ $: fillKey = fill && typeof fill === 'object' ? objectId(fill) : fill;
152
+ $: strokeKey = stroke && typeof stroke === 'object' ? objectId(stroke) : stroke;
153
153
 
154
154
  $: if (renderContext === 'canvas') {
155
155
  // Redraw when props change
@@ -12,13 +12,15 @@
12
12
  import Text from './Text.svelte';
13
13
 
14
14
  import { localPoint } from '../utils/event.js';
15
+ import type { DomainType } from '../utils/scales.js';
16
+ import { asAny } from '../utils/types.js';
15
17
 
16
18
  const { xScale, yScale, width, height, padding } = chartContext();
17
19
 
18
20
  const dispatch = createEventDispatcher<{
19
- change: { xDomain?: [any, any]; yDomain?: [any, any] };
20
- brushStart: { xDomain?: [any, any]; yDomain?: [any, any] };
21
- brushEnd: { xDomain?: [any, any]; yDomain?: [any, any] };
21
+ change: { xDomain?: DomainType; yDomain?: DomainType };
22
+ brushStart: { xDomain?: DomainType; yDomain?: DomainType };
23
+ brushEnd: { xDomain?: DomainType; yDomain?: DomainType };
22
24
  }>();
23
25
 
24
26
  /** Axis to apply brushing */
@@ -30,14 +32,8 @@
30
32
  /** Only show range while actively brushing. Useful with `brushEnd` event */
31
33
  export let resetOnEnd = false;
32
34
 
33
- export let xDomain: [number | Date | null, number | Date | null] = $xScale.domain() as [
34
- number,
35
- number,
36
- ];
37
- export let yDomain: [number | Date | null, number | Date | null] = $yScale.domain() as [
38
- number,
39
- number,
40
- ];
35
+ export let xDomain: DomainType = $xScale.domain() as [number, number];
36
+ export let yDomain: DomainType = $yScale.domain() as [number, number];
41
37
 
42
38
  export let labels: ComponentProps<Text> | boolean = false;
43
39
 
@@ -79,8 +75,8 @@
79
75
  ) {
80
76
  return (e: PointerEvent) => {
81
77
  const start = {
82
- xDomain: [xDomain[0] ?? xDomainMin, xDomain[1] ?? xDomainMax] as [number, number],
83
- yDomain: [yDomain[0] ?? yDomainMin, yDomain[1] ?? yDomainMax] as [number, number],
78
+ xDomain: [xDomain?.[0] ?? xDomainMin, xDomain?.[1] ?? xDomainMax] as [number, number],
79
+ yDomain: [yDomain?.[0] ?? yDomainMin, yDomain?.[1] ?? yDomainMax] as [number, number],
84
80
  value: {
85
81
  x: $xScale.invert?.((localPoint(frameEl, e)?.x ?? 0) - $padding.left),
86
82
  y: $yScale.invert?.((localPoint(frameEl, e)?.y ?? 0) - $padding.top),
@@ -208,10 +204,10 @@
208
204
  yDomain = [yDomainMin, yDomainMax];
209
205
  }
210
206
 
211
- $: top = $yScale(yDomain[1]);
212
- $: bottom = $yScale(yDomain[0]);
213
- $: left = $xScale(xDomain[0]);
214
- $: right = $xScale(xDomain[1]);
207
+ $: top = $yScale(yDomain?.[1]);
208
+ $: bottom = $yScale(yDomain?.[0]);
209
+ $: left = $xScale(xDomain?.[0]);
210
+ $: right = $xScale(xDomain?.[1]);
215
211
 
216
212
  $: rangeTop = axis === 'both' || axis === 'y' ? top : 0;
217
213
  $: rangeLeft = axis === 'both' || axis === 'x' ? left : 0;
@@ -220,15 +216,10 @@
220
216
 
221
217
  // Set reactively to handle cases where xDomain/yDomain are set externally (ex. `bind:xDomain`)
222
218
  $: isActive =
223
- xDomain[0]?.valueOf() !== originalXDomain[0]?.valueOf() ||
224
- xDomain[1]?.valueOf() !== originalXDomain[1]?.valueOf() ||
225
- yDomain[0]?.valueOf() !== originalYDomain[0]?.valueOf() ||
226
- yDomain[1]?.valueOf() !== originalYDomain[1]?.valueOf();
227
-
228
- /** TODO: Fix types and remove workaround (Svelte 5)*/
229
- function any(value: any): any {
230
- return value;
231
- }
219
+ xDomain?.[0]?.valueOf() !== originalXDomain[0]?.valueOf() ||
220
+ xDomain?.[1]?.valueOf() !== originalXDomain[1]?.valueOf() ||
221
+ yDomain?.[0]?.valueOf() !== originalYDomain[0]?.valueOf() ||
222
+ yDomain?.[1]?.valueOf() !== originalYDomain[1]?.valueOf();
232
223
  </script>
233
224
 
234
225
  <g class={cls('Brush select-none', classes.root, $$props.class)}>
@@ -265,8 +256,10 @@
265
256
  class="handle top"
266
257
  on:pointerdown={adjustTop}
267
258
  on:dblclick={() => {
268
- yDomain[0] = yDomainMin;
269
- dispatch('change', { xDomain, yDomain });
259
+ if (yDomain) {
260
+ yDomain[0] = yDomainMin;
261
+ dispatch('change', { xDomain, yDomain });
262
+ }
270
263
  }}
271
264
  >
272
265
  <slot name="handle" edge="top" {rangeWidth} {rangeHeight}>
@@ -285,7 +278,9 @@
285
278
  class="handle bottom"
286
279
  on:pointerdown={adjustBottom}
287
280
  on:dblclick={() => {
288
- yDomain[1] = yDomainMax;
281
+ if (yDomain) {
282
+ yDomain[1] = yDomainMax;
283
+ }
289
284
  }}
290
285
  >
291
286
  <slot name="handle" edge="bottom" {rangeWidth} {rangeHeight}>
@@ -306,8 +301,10 @@
306
301
  class="handle left"
307
302
  on:pointerdown={adjustLeft}
308
303
  on:dblclick={() => {
309
- xDomain[0] = xDomainMin;
310
- dispatch('change', { xDomain, yDomain });
304
+ if (xDomain) {
305
+ xDomain[0] = xDomainMin;
306
+ dispatch('change', { xDomain, yDomain });
307
+ }
311
308
  }}
312
309
  >
313
310
  <slot name="handle" edge="left" {rangeWidth} {rangeHeight}>
@@ -326,8 +323,10 @@
326
323
  class="handle right"
327
324
  on:pointerdown={adjustRight}
328
325
  on:dblclick={() => {
329
- xDomain[1] = xDomainMax;
330
- dispatch('change', { xDomain, yDomain });
326
+ if (xDomain) {
327
+ xDomain[1] = xDomainMax;
328
+ dispatch('change', { xDomain, yDomain });
329
+ }
331
330
  }}
332
331
  >
333
332
  <slot name="handle" edge="right" {rangeWidth} {rangeHeight}>
@@ -356,7 +355,7 @@
356
355
  dx={-4}
357
356
  textAnchor="end"
358
357
  verticalAnchor="middle"
359
- value={formatValue(any(xDomain[0]), format)}
358
+ value={formatValue(asAny(xDomain?.[0]), format)}
360
359
  {...typeof labels === 'object' ? labels : null}
361
360
  class={labelClass}
362
361
  />
@@ -367,7 +366,7 @@
367
366
  dx={4}
368
367
  textAnchor="start"
369
368
  verticalAnchor="middle"
370
- value={formatValue(any(xDomain[1]), format)}
369
+ value={formatValue(asAny(xDomain?.[1]), format)}
371
370
  {...typeof labels === 'object' ? labels : null}
372
371
  class={labelClass}
373
372
  />
@@ -380,7 +379,7 @@
380
379
  dy={-4}
381
380
  textAnchor="middle"
382
381
  verticalAnchor="end"
383
- value={formatValue(any(yDomain[1]), format)}
382
+ value={formatValue(asAny(yDomain?.[1]), format)}
384
383
  {...typeof labels === 'object' ? labels : null}
385
384
  class={labelClass}
386
385
  />
@@ -391,7 +390,7 @@
391
390
  dy={4}
392
391
  textAnchor="middle"
393
392
  verticalAnchor="start"
394
- value={formatValue(any(yDomain[0]), format)}
393
+ value={formatValue(asAny(yDomain?.[0]), format)}
395
394
  {...typeof labels === 'object' ? labels : null}
396
395
  class={labelClass}
397
396
  />
@@ -3,14 +3,15 @@ import { type ComponentProps } from 'svelte';
3
3
  import type { SVGAttributes } from 'svelte/elements';
4
4
  import { type FormatType } from '@layerstack/utils';
5
5
  import Text from './Text.svelte';
6
+ import type { DomainType } from '../utils/scales.js';
6
7
  declare const __propDef: {
7
8
  props: {
8
9
  [x: string]: any;
9
10
  axis?: "x" | "y" | "both" | undefined;
10
11
  handleSize?: number | undefined;
11
12
  resetOnEnd?: boolean | undefined;
12
- xDomain?: [number | Date | null, number | Date | null] | undefined;
13
- yDomain?: [number | Date | null, number | Date | null] | undefined;
13
+ xDomain?: DomainType | undefined;
14
+ yDomain?: DomainType | undefined;
14
15
  labels?: (ComponentProps<Text> | boolean) | undefined;
15
16
  range?: SVGAttributes<SVGRectElement> | undefined;
16
17
  handle?: SVGAttributes<SVGRectElement> | undefined;
@@ -25,16 +26,16 @@ declare const __propDef: {
25
26
  };
26
27
  events: {
27
28
  change: CustomEvent<{
28
- xDomain?: [any, any];
29
- yDomain?: [any, any];
29
+ xDomain?: DomainType;
30
+ yDomain?: DomainType;
30
31
  }>;
31
32
  brushStart: CustomEvent<{
32
- xDomain?: [any, any];
33
- yDomain?: [any, any];
33
+ xDomain?: DomainType;
34
+ yDomain?: DomainType;
34
35
  }>;
35
36
  brushEnd: CustomEvent<{
36
- xDomain?: [any, any];
37
- yDomain?: [any, any];
37
+ xDomain?: DomainType;
38
+ yDomain?: DomainType;
38
39
  }>;
39
40
  } & {
40
41
  [evt: string]: CustomEvent<any>;
@@ -6,6 +6,9 @@
6
6
 
7
7
  /** Include padding area (ex. axis) */
8
8
  export let full = false;
9
+
10
+ /** Disable clipping (show all) */
11
+ export let disabled: boolean = false;
9
12
  </script>
10
13
 
11
14
  <RectClipPath
@@ -13,6 +16,7 @@
13
16
  y={full && $padding.top ? -$padding.top : 0}
14
17
  width={$width + (full ? ($padding?.left ?? 0) + ($padding?.right ?? 0) : 0)}
15
18
  height={$height + (full ? ($padding?.top ?? 0) + ($padding?.bottom ?? 0) : 0)}
19
+ {disabled}
16
20
  on:click
17
21
  {...$$restProps}
18
22
  >
@@ -3,6 +3,7 @@ declare const __propDef: {
3
3
  props: {
4
4
  [x: string]: any;
5
5
  full?: boolean | undefined;
6
+ disabled?: boolean | undefined;
6
7
  };
7
8
  events: {
8
9
  click: CustomEvent<any>;
@@ -14,9 +14,12 @@
14
14
  export let r: number;
15
15
  export let spring: boolean | Parameters<typeof springStore>[1] = undefined;
16
16
  export let tweened: boolean | Parameters<typeof tweenedStore>[1] = undefined;
17
+
18
+ /** Disable clipping (show all) */
19
+ export let disabled: boolean = false;
17
20
  </script>
18
21
 
19
- <ClipPath {id} let:url>
22
+ <ClipPath {id} {disabled} let:url>
20
23
  <Circle slot="clip" {cx} {cy} {r} {spring} {tweened} {...$$restProps} />
21
24
  <slot {id} {url} />
22
25
  </ClipPath>
@@ -91,8 +91,8 @@
91
91
  }
92
92
 
93
93
  // TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
94
- $: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
95
- $: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
94
+ $: fillKey = fill && typeof fill === 'object' ? objectId(fill) : fill;
95
+ $: strokeKey = stroke && typeof stroke === 'object' ? objectId(stroke) : stroke;
96
96
 
97
97
  $: if (renderContext === 'canvas') {
98
98
  // Redraw when geojson, projection, or class change
@@ -70,8 +70,8 @@
70
70
  }
71
71
 
72
72
  // TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
73
- $: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
74
- $: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
73
+ $: fillKey = fill && typeof fill === 'object' ? objectId(fill) : fill;
74
+ $: strokeKey = stroke && typeof stroke === 'object' ? objectId(stroke) : stroke;
75
75
 
76
76
  $: if (renderContext === 'canvas') {
77
77
  // Redraw when props change
@@ -62,8 +62,8 @@
62
62
  }
63
63
 
64
64
  // TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
65
- $: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
66
- $: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
65
+ $: fillKey = fill && typeof fill === 'object' ? objectId(fill) : fill;
66
+ $: strokeKey = stroke && typeof stroke === 'object' ? objectId(stroke) : stroke;
67
67
 
68
68
  $: if (renderContext === 'canvas') {
69
69
  // Redraw when props change
@@ -14,9 +14,12 @@
14
14
  export let height: number;
15
15
  export let spring: ComponentProps<Rect>['spring'] = undefined;
16
16
  export let tweened: ComponentProps<Rect>['tweened'] = undefined;
17
+
18
+ /** Disable clipping (show all) */
19
+ export let disabled: boolean = false;
17
20
  </script>
18
21
 
19
- <ClipPath {id} let:url>
22
+ <ClipPath {id} {disabled} let:url>
20
23
  <Rect slot="clip" {x} {y} {width} {height} {spring} {tweened} {...$$restProps} />
21
24
  <slot {id} {url} />
22
25
  </ClipPath>
@@ -175,8 +175,8 @@
175
175
  }
176
176
 
177
177
  // TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
178
- $: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
179
- $: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
178
+ $: fillKey = fill && typeof fill === 'object' ? objectId(fill) : fill;
179
+ $: strokeKey = stroke && typeof stroke === 'object' ? objectId(stroke) : stroke;
180
180
 
181
181
  $: if (renderContext === 'canvas') {
182
182
  // Redraw when props change
@@ -203,8 +203,8 @@
203
203
  }
204
204
 
205
205
  // TODO: Use objectId to work around Svelte 4 reactivity issue (even when memoizing gradients)
206
- $: fillKey = typeof fill === 'object' ? objectId(fill) : fill;
207
- $: strokeKey = typeof stroke === 'object' ? objectId(stroke) : stroke;
206
+ $: fillKey = fill && typeof fill === 'object' ? objectId(fill) : fill;
207
+ $: strokeKey = stroke && typeof stroke === 'object' ? objectId(stroke) : stroke;
208
208
 
209
209
  $: if (renderContext === 'canvas') {
210
210
  // Redraw when props change
@@ -9,8 +9,10 @@
9
9
 
10
10
  import Area from '../Area.svelte';
11
11
  import Axis from '../Axis.svelte';
12
+ import Brush from '../Brush.svelte';
12
13
  import Canvas from '../layout/Canvas.svelte';
13
14
  import Chart from '../Chart.svelte';
15
+ import ChartClipPath from '../ChartClipPath.svelte';
14
16
  import Grid from '../Grid.svelte';
15
17
  import Highlight, { type HighlightPointData } from '../Highlight.svelte';
16
18
  import Labels from '../Labels.svelte';
@@ -32,6 +34,7 @@
32
34
 
33
35
  interface $$Props extends ComponentProps<Chart<TData>> {
34
36
  axis?: typeof axis;
37
+ brush?: typeof brush;
35
38
  grid?: typeof grid;
36
39
  labels?: typeof labels;
37
40
  legend?: typeof legend;
@@ -50,6 +53,9 @@
50
53
  export let x: Accessor<TData> = undefined;
51
54
  export let y: Accessor<TData> = undefined;
52
55
 
56
+ /** Set xDomain. Useful for external brush control */
57
+ export let xDomain: ComponentProps<typeof Brush>['xDomain'] = undefined;
58
+
53
59
  /** Use radial instead of cartesian coordinates, mapping `x` to `angle` and `y`` to radial. Radial lines are positioned relative to the origin, use transform (ex. `<Group center>`) to change the origin */
54
60
  export let radial = false;
55
61
 
@@ -69,11 +75,12 @@
69
75
  $: stackSeries = seriesLayout.startsWith('stack');
70
76
 
71
77
  export let axis: ComponentProps<Axis> | 'x' | 'y' | boolean = true;
78
+ export let brush: ({ mode: 'integrated' } & ComponentProps<Brush>) | false = false;
72
79
  export let grid: ComponentProps<Grid> | boolean = true;
73
- export let rule: ComponentProps<Rule> | boolean = true;
74
80
  export let labels: ComponentProps<Labels> | boolean = false;
75
81
  export let legend: ComponentProps<Legend> | boolean = false;
76
82
  export let points: ComponentProps<Points> | boolean = false;
83
+ export let rule: ComponentProps<Rule> | boolean = true;
77
84
 
78
85
  /** Event dispatched with current tooltip data */
79
86
  export let onTooltipClick: (e: { data: any }) => void = () => {};
@@ -85,23 +92,25 @@
85
92
  }) => void = () => {};
86
93
 
87
94
  export let props: {
88
- xAxis?: Partial<ComponentProps<Axis>>;
89
- yAxis?: Partial<ComponentProps<Axis>>;
90
- grid?: Partial<ComponentProps<Grid>>;
91
- rule?: Partial<ComponentProps<Rule>>;
92
95
  area?: Partial<ComponentProps<Area>>;
93
- line?: Partial<ComponentProps<Line>>;
94
- legend?: Partial<ComponentProps<Legend>>;
95
- points?: Partial<ComponentProps<Points>>;
96
+ brush?: Partial<ComponentProps<Brush>>;
97
+ grid?: Partial<ComponentProps<Grid>>;
96
98
  highlight?: Partial<ComponentProps<Highlight>>;
97
99
  labels?: Partial<ComponentProps<Labels>>;
100
+ legend?: Partial<ComponentProps<Legend>>;
101
+ line?: Partial<ComponentProps<Line>>;
102
+ points?: Partial<ComponentProps<Points>>;
103
+ rule?: Partial<ComponentProps<Rule>>;
98
104
  tooltip?: {
105
+ context?: Partial<ComponentProps<Tooltip.Context>>;
99
106
  root?: Partial<ComponentProps<Tooltip.Root>>;
100
107
  header?: Partial<ComponentProps<Tooltip.Header>>;
101
108
  list?: Partial<ComponentProps<Tooltip.List>>;
102
109
  item?: Partial<ComponentProps<Tooltip.Item>>;
103
110
  separator?: Partial<ComponentProps<Tooltip.Separator>>;
104
111
  };
112
+ xAxis?: Partial<ComponentProps<Axis>>;
113
+ yAxis?: Partial<ComponentProps<Axis>>;
105
114
  } = {};
106
115
 
107
116
  export let renderContext: 'svg' | 'canvas' = 'svg';
@@ -250,6 +259,7 @@
250
259
  <Chart
251
260
  data={chartData}
252
261
  {x}
262
+ {xDomain}
253
263
  {xScale}
254
264
  y={y ??
255
265
  (stackSeries
@@ -260,7 +270,14 @@
260
270
  {radial}
261
271
  padding={radial ? undefined : defaultChartPadding(axis, legend)}
262
272
  {...$$restProps}
263
- tooltip={{ mode: 'bisect-x', onClick: onTooltipClick, ...$$props.tooltip, ...props.tooltip }}
273
+ tooltip={$$props.tooltip === false
274
+ ? false
275
+ : {
276
+ mode: 'bisect-x',
277
+ onClick: onTooltipClick,
278
+ ...props.tooltip?.context,
279
+ ...$$props.tooltip,
280
+ }}
264
281
  let:x
265
282
  let:xScale
266
283
  let:y
@@ -301,9 +318,11 @@
301
318
  <slot name="belowMarks" {...slotProps} />
302
319
 
303
320
  <slot name="marks" {...slotProps}>
304
- {#each visibleSeries as s, i (s.key)}
305
- <Area {...getAreaProps(s, i)} />
306
- {/each}
321
+ <ChartClipPath disabled={!brush}>
322
+ {#each visibleSeries as s, i (s.key)}
323
+ <Area {...getAreaProps(s, i)} />
324
+ {/each}
325
+ </ChartClipPath>
307
326
  </slot>
308
327
 
309
328
  <slot name="aboveMarks" {...slotProps} />
@@ -354,7 +373,13 @@
354
373
  <Highlight
355
374
  data={seriesTooltipData}
356
375
  y={stackSeries ? (d) => d.stackData[i][1] : (s.value ?? (s.data ? undefined : s.key))}
357
- points={{ fill: s.color }}
376
+ points={{
377
+ fill: s.color,
378
+ class: cls(
379
+ 'transition-opacity',
380
+ highlightSeriesKey && highlightSeriesKey !== s.key && 'opacity-10'
381
+ ),
382
+ }}
358
383
  lines={i == 0}
359
384
  onPointClick={(e) => onPointClick({ ...e, series: s })}
360
385
  onPointEnter={() => (highlightSeriesKey = s.key)}
@@ -371,6 +396,21 @@
371
396
  {/if}
372
397
  </svelte:component>
373
398
 
399
+ {#if brush && brush.mode === 'integrated'}
400
+ <Svg>
401
+ <Brush
402
+ axis="x"
403
+ resetOnEnd
404
+ {xDomain}
405
+ on:brushEnd={(e) => {
406
+ xDomain = e.detail.xDomain;
407
+ }}
408
+ {...typeof brush === 'object' ? brush : null}
409
+ {...props.brush}
410
+ />
411
+ </Svg>
412
+ {/if}
413
+
374
414
  <slot name="legend" {...slotProps}>
375
415
  {#if legend}
376
416
  <Legend
@@ -135,6 +135,7 @@
135
135
  highlight?: Partial<ComponentProps<Highlight>>;
136
136
  labels?: Partial<ComponentProps<Labels>>;
137
137
  tooltip?: {
138
+ context?: Partial<ComponentProps<Tooltip.Context>>;
138
139
  root?: Partial<ComponentProps<Tooltip.Root>>;
139
140
  header?: Partial<ComponentProps<Tooltip.Header>>;
140
141
  list?: Partial<ComponentProps<Tooltip.List>>;
@@ -300,7 +301,9 @@
300
301
  cRange={['hsl(var(--color-primary))']}
301
302
  padding={defaultChartPadding(axis, legend)}
302
303
  {...$$restProps}
303
- tooltip={{ mode: 'band', onClick: onTooltipClick, ...$$props.tooltip, ...props.tooltip }}
304
+ tooltip={$$props.tooltip === false
305
+ ? false
306
+ : { mode: 'band', onClick: onTooltipClick, ...props.tooltip?.context, ...$$props.tooltip }}
304
307
  let:x
305
308
  let:xScale
306
309
  let:y
@@ -6,8 +6,10 @@
6
6
  import { selectionStore } from '@layerstack/svelte-stores';
7
7
 
8
8
  import Axis from '../Axis.svelte';
9
+ import Brush from '../Brush.svelte';
9
10
  import Canvas from '../layout/Canvas.svelte';
10
11
  import Chart from '../Chart.svelte';
12
+ import ChartClipPath from '../ChartClipPath.svelte';
11
13
  import Grid from '../Grid.svelte';
12
14
  import Highlight, { type HighlightPointData } from '../Highlight.svelte';
13
15
  import Labels from '../Labels.svelte';
@@ -29,6 +31,7 @@
29
31
 
30
32
  interface $$Props extends ComponentProps<Chart<TData>> {
31
33
  axis?: typeof axis;
34
+ brush?: typeof brush;
32
35
  grid?: typeof grid;
33
36
  labels?: typeof labels;
34
37
  legend?: typeof legend;
@@ -46,6 +49,9 @@
46
49
  export let x: Accessor<TData> = undefined;
47
50
  export let y: Accessor<TData> = undefined;
48
51
 
52
+ /** Set xDomain. Useful for external brush control */
53
+ export let xDomain: ComponentProps<typeof Brush>['xDomain'] = undefined;
54
+
49
55
  /** Use radial instead of cartesian coordinates, mapping `x` to `angle` and `y`` to radial. Radial lines are positioned relative to the origin, use transform (ex. `<Group center>`) to change the origin */
50
56
  export let radial = false;
51
57
 
@@ -61,11 +67,12 @@
61
67
  $: isDefaultSeries = series.length === 1 && series[0].key === 'default';
62
68
 
63
69
  export let axis: ComponentProps<Axis> | 'x' | 'y' | boolean = true;
64
- export let rule: ComponentProps<Rule> | boolean = true;
70
+ export let brush: ({ mode: 'integrated' } & ComponentProps<Brush>) | false = false;
65
71
  export let grid: ComponentProps<Grid> | boolean = true;
66
72
  export let labels: ComponentProps<Labels> | boolean = false;
67
73
  export let legend: ComponentProps<Legend> | boolean = false;
68
74
  export let points: ComponentProps<Points> | boolean = false;
75
+ export let rule: ComponentProps<Rule> | boolean = true;
69
76
 
70
77
  /** Event dispatched with current tooltip data */
71
78
  export let onTooltipClick: (e: { data: any }) => void = () => {};
@@ -77,22 +84,24 @@
77
84
  }) => void = () => {};
78
85
 
79
86
  export let props: {
80
- xAxis?: Partial<ComponentProps<Axis>>;
81
- yAxis?: Partial<ComponentProps<Axis>>;
87
+ brush?: Partial<ComponentProps<Brush>>;
82
88
  grid?: Partial<ComponentProps<Grid>>;
83
- rule?: Partial<ComponentProps<Rule>>;
84
- spline?: Partial<ComponentProps<Spline>>;
85
- legend?: Partial<ComponentProps<Legend>>;
86
89
  highlight?: Partial<ComponentProps<Highlight>>;
87
90
  labels?: Partial<ComponentProps<Labels>>;
91
+ legend?: Partial<ComponentProps<Legend>>;
88
92
  points?: Partial<ComponentProps<Points>>;
93
+ rule?: Partial<ComponentProps<Rule>>;
94
+ spline?: Partial<ComponentProps<Spline>>;
89
95
  tooltip?: {
96
+ context?: Partial<ComponentProps<Tooltip.Context>>;
90
97
  root?: Partial<ComponentProps<Tooltip.Root>>;
91
98
  header?: Partial<ComponentProps<Tooltip.Header>>;
92
99
  list?: Partial<ComponentProps<Tooltip.List>>;
93
100
  item?: Partial<ComponentProps<Tooltip.Item>>;
94
101
  separator?: Partial<ComponentProps<Tooltip.Separator>>;
95
102
  };
103
+ xAxis?: Partial<ComponentProps<Axis>>;
104
+ yAxis?: Partial<ComponentProps<Axis>>;
96
105
  } = {};
97
106
 
98
107
  export let renderContext: 'svg' | 'canvas' = 'svg';
@@ -187,6 +196,7 @@
187
196
  <Chart
188
197
  data={chartData}
189
198
  {x}
199
+ {xDomain}
190
200
  {xScale}
191
201
  y={y ?? series.map((s) => s.value ?? s.key)}
192
202
  yBaseline={0}
@@ -194,7 +204,14 @@
194
204
  {radial}
195
205
  padding={radial ? undefined : defaultChartPadding(axis, legend)}
196
206
  {...$$restProps}
197
- tooltip={{ mode: 'bisect-x', onClick: onTooltipClick, ...$$props.tooltip, ...props.tooltip }}
207
+ tooltip={$$props.tooltip === false
208
+ ? false
209
+ : {
210
+ mode: 'bisect-x',
211
+ onClick: onTooltipClick,
212
+ ...props.tooltip?.context,
213
+ ...$$props.tooltip,
214
+ }}
198
215
  let:x
199
216
  let:xScale
200
217
  let:y
@@ -234,9 +251,11 @@
234
251
  <slot name="belowMarks" {...slotProps} />
235
252
 
236
253
  <slot name="marks" {...slotProps}>
237
- {#each visibleSeries as s, i (s.key)}
238
- <Spline {...getSplineProps(s, i)} />
239
- {/each}
254
+ <ChartClipPath disabled={!brush}>
255
+ {#each visibleSeries as s, i (s.key)}
256
+ <Spline {...getSplineProps(s, i)} />
257
+ {/each}
258
+ </ChartClipPath>
240
259
  </slot>
241
260
 
242
261
  <slot name="aboveMarks" {...slotProps} />
@@ -286,7 +305,13 @@
286
305
  <Highlight
287
306
  data={seriesTooltipData}
288
307
  y={s.value ?? (s.data ? undefined : s.key)}
289
- points={{ fill: s.color }}
308
+ points={{
309
+ fill: s.color,
310
+ class: cls(
311
+ 'transition-opacity',
312
+ highlightSeriesKey && highlightSeriesKey !== s.key && 'opacity-10'
313
+ ),
314
+ }}
290
315
  lines={i === 0}
291
316
  onPointClick={(e) => onPointClick({ ...e, series: s })}
292
317
  onPointEnter={() => (highlightSeriesKey = s.key)}
@@ -297,6 +322,21 @@
297
322
  </slot>
298
323
  </svelte:component>
299
324
 
325
+ {#if brush && brush.mode === 'integrated'}
326
+ <Svg>
327
+ <Brush
328
+ axis="x"
329
+ resetOnEnd
330
+ {xDomain}
331
+ on:brushEnd={(e) => {
332
+ xDomain = e.detail.xDomain;
333
+ }}
334
+ {...typeof brush === 'object' ? brush : null}
335
+ {...props.brush}
336
+ />
337
+ </Svg>
338
+ {/if}
339
+
300
340
  <slot name="legend" {...slotProps}>
301
341
  {#if legend}
302
342
  <Legend
@@ -111,6 +111,7 @@
111
111
  arc?: Partial<ComponentProps<Arc>>;
112
112
  legend?: Partial<ComponentProps<Legend>>;
113
113
  tooltip?: {
114
+ context?: Partial<ComponentProps<Tooltip.Context>>;
114
115
  root?: Partial<ComponentProps<Tooltip.Root>>;
115
116
  header?: Partial<ComponentProps<Tooltip.Header>>;
116
117
  list?: Partial<ComponentProps<Tooltip.List>>;
@@ -170,6 +171,7 @@
170
171
  ]}
171
172
  padding={{ bottom: legend === true ? 32 : 0 }}
172
173
  {...$$restProps}
174
+ tooltip={props.tooltip?.context}
173
175
  let:x
174
176
  let:xScale
175
177
  let:y
@@ -237,6 +237,7 @@ declare class __sveltets_Render<TData> {
237
237
  arc?: Partial<ComponentProps<Arc>>;
238
238
  legend?: Partial<ComponentProps<Legend>>;
239
239
  tooltip?: {
240
+ context?: Partial<ComponentProps<Tooltip.Context>>;
240
241
  root?: Partial<ComponentProps<Tooltip.Root>>;
241
242
  header?: Partial<ComponentProps<Tooltip.Header>>;
242
243
  list?: Partial<ComponentProps<Tooltip.List>>;
@@ -6,8 +6,10 @@
6
6
  import { selectionStore } from '@layerstack/svelte-stores';
7
7
 
8
8
  import Axis from '../Axis.svelte';
9
+ import Brush from '../Brush.svelte';
9
10
  import Canvas from '../layout/Canvas.svelte';
10
11
  import Chart from '../Chart.svelte';
12
+ import ChartClipPath from '../ChartClipPath.svelte';
11
13
  import Grid from '../Grid.svelte';
12
14
  import Highlight from '../Highlight.svelte';
13
15
  import Labels from '../Labels.svelte';
@@ -26,6 +28,7 @@
26
28
 
27
29
  interface $$Props extends ComponentProps<Chart<TData>> {
28
30
  axis?: typeof axis;
31
+ brush?: typeof brush;
29
32
  grid?: typeof grid;
30
33
  labels?: typeof labels;
31
34
  legend?: typeof legend;
@@ -40,6 +43,11 @@
40
43
  export let x: Accessor<TData> = undefined;
41
44
  export let y: Accessor<TData> = undefined;
42
45
 
46
+ /** Set xDomain. Useful for external brush control */
47
+ export let xDomain: ComponentProps<typeof Brush>['xDomain'] = undefined;
48
+ /** Set yDomain. Useful for external brush control */
49
+ export let yDomain: ComponentProps<typeof Brush>['yDomain'] = undefined;
50
+
43
51
  export let series: {
44
52
  key: string;
45
53
  label?: string;
@@ -50,6 +58,7 @@
50
58
  $: isDefaultSeries = series.length === 1 && series[0].key === 'default';
51
59
 
52
60
  export let axis: ComponentProps<Axis> | 'x' | 'y' | boolean = true;
61
+ export let brush: ({ mode: 'integrated' } & ComponentProps<Brush>) | false = false;
53
62
  export let grid: ComponentProps<Grid> | boolean = true;
54
63
  export let labels: ComponentProps<Labels> | boolean = false;
55
64
  export let legend: ComponentProps<Legend> | boolean = false;
@@ -59,21 +68,23 @@
59
68
  export let onTooltipClick: (e: { data: any }) => void = () => {};
60
69
 
61
70
  export let props: {
62
- xAxis?: Partial<ComponentProps<Axis>>;
63
- yAxis?: Partial<ComponentProps<Axis>>;
71
+ brush?: Partial<ComponentProps<Brush>>;
64
72
  grid?: Partial<ComponentProps<Grid>>;
65
- points?: Partial<ComponentProps<Points>>;
66
73
  highlight?: Partial<ComponentProps<Highlight>>;
67
74
  labels?: Partial<ComponentProps<Labels>>;
68
75
  legend?: Partial<ComponentProps<Legend>>;
76
+ points?: Partial<ComponentProps<Points>>;
69
77
  rule?: Partial<ComponentProps<Rule>>;
70
78
  tooltip?: {
79
+ context?: Partial<ComponentProps<Tooltip.Context>>;
71
80
  root?: Partial<ComponentProps<Tooltip.Root>>;
72
81
  header?: Partial<ComponentProps<Tooltip.Header>>;
73
82
  list?: Partial<ComponentProps<Tooltip.List>>;
74
83
  item?: Partial<ComponentProps<Tooltip.Item>>;
75
84
  separator?: Partial<ComponentProps<Tooltip.Separator>>;
76
85
  };
86
+ xAxis?: Partial<ComponentProps<Axis>>;
87
+ yAxis?: Partial<ComponentProps<Axis>>;
77
88
  } = {};
78
89
 
79
90
  export let renderContext: 'svg' | 'canvas' = 'svg';
@@ -152,13 +163,22 @@
152
163
  <Chart
153
164
  data={chartData}
154
165
  {x}
166
+ {xDomain}
155
167
  {xScale}
156
168
  {y}
169
+ {yDomain}
157
170
  {yScale}
158
171
  yNice
159
172
  padding={defaultChartPadding(axis, legend)}
160
173
  {...$$restProps}
161
- tooltip={{ mode: 'voronoi', onClick: onTooltipClick, ...$$props.tooltip, ...props.tooltip }}
174
+ tooltip={$$props.tooltip === false
175
+ ? false
176
+ : {
177
+ mode: 'voronoi',
178
+ onClick: onTooltipClick,
179
+ ...props.tooltip?.context,
180
+ ...$$props.tooltip,
181
+ }}
162
182
  let:x
163
183
  let:xScale
164
184
  let:y
@@ -203,9 +223,11 @@
203
223
  <slot name="belowMarks" {...slotProps} />
204
224
 
205
225
  <slot name="marks" {...slotProps}>
206
- {#each visibleSeries as s, i (s.key)}
207
- <Points {...getPointsProps(s, i)} />
208
- {/each}
226
+ <ChartClipPath disabled={!brush}>
227
+ {#each visibleSeries as s, i (s.key)}
228
+ <Points {...getPointsProps(s, i)} />
229
+ {/each}
230
+ </ChartClipPath>
209
231
  </slot>
210
232
 
211
233
  <slot name="aboveMarks" {...slotProps} />
@@ -247,6 +269,24 @@
247
269
  {/if}
248
270
  </svelte:component>
249
271
 
272
+ <!-- TODO: Determine how to coordinate with `tooltip={{ mode: 'voronoi' }} -->
273
+ {#if brush && brush.mode === 'integrated'}
274
+ <Svg>
275
+ <Brush
276
+ axis="both"
277
+ resetOnEnd
278
+ {xDomain}
279
+ {yDomain}
280
+ on:brushEnd={(e) => {
281
+ xDomain = e.detail.xDomain;
282
+ yDomain = e.detail.yDomain;
283
+ }}
284
+ {...typeof brush === 'object' ? brush : null}
285
+ {...props.brush}
286
+ />
287
+ </Svg>
288
+ {/if}
289
+
250
290
  <slot name="legend" {...slotProps}>
251
291
  {#if legend}
252
292
  <Legend
@@ -1 +1,5 @@
1
+ /**
2
+ * Useful to workaround Svelte 3/4 markup type issues
3
+ * TODO: Remove usage after migrating to Svelte 5
4
+ */
1
5
  export declare function asAny(x: any): any;
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Useful to workaround Svelte 3/4 markup type issues
3
+ * TODO: Remove usage after migrating to Svelte 5
4
+ */
1
5
  export function asAny(x) {
2
6
  return x;
3
7
  }
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.79.3",
7
+ "version": "0.80.0",
8
8
  "devDependencies": {
9
9
  "@changesets/cli": "^2.27.12",
10
10
  "@mdi/js": "^7.4.47",