layerchart 0.79.4 → 0.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { createEventDispatcher, type ComponentProps } from 'svelte';
2
+ import { type ComponentProps } from 'svelte';
3
3
  import type { SVGAttributes } from 'svelte/elements';
4
4
  import { extent, min, max } from 'd3-array';
5
5
  import { clamp } from '@layerstack/utils';
@@ -12,15 +12,11 @@
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
- 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] };
22
- }>();
23
-
24
20
  /** Axis to apply brushing */
25
21
  export let axis: 'x' | 'y' | 'both' = 'x';
26
22
 
@@ -30,14 +26,8 @@
30
26
  /** Only show range while actively brushing. Useful with `brushEnd` event */
31
27
  export let resetOnEnd = false;
32
28
 
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
- ];
29
+ export let xDomain: DomainType = $xScale.domain() as [number, number];
30
+ export let yDomain: DomainType = $yScale.domain() as [number, number];
41
31
 
42
32
  export let labels: ComponentProps<Text> | boolean = false;
43
33
 
@@ -65,6 +55,10 @@
65
55
  labels?: string;
66
56
  } = {};
67
57
 
58
+ export let onChange: (e: { xDomain?: DomainType; yDomain?: DomainType }) => void = () => {};
59
+ export let onBrushStart: (e: { xDomain?: DomainType; yDomain?: DomainType }) => void = () => {};
60
+ export let onBrushEnd: (e: { xDomain?: DomainType; yDomain?: DomainType }) => void = () => {};
61
+
68
62
  let frameEl: SVGRectElement;
69
63
 
70
64
  function handler(
@@ -79,15 +73,15 @@
79
73
  ) {
80
74
  return (e: PointerEvent) => {
81
75
  const start = {
82
- xDomain: [xDomain[0] ?? xDomainMin, xDomain[1] ?? xDomainMax] as [number, number],
83
- yDomain: [yDomain[0] ?? yDomainMin, yDomain[1] ?? yDomainMax] as [number, number],
76
+ xDomain: [xDomain?.[0] ?? xDomainMin, xDomain?.[1] ?? xDomainMax] as [number, number],
77
+ yDomain: [yDomain?.[0] ?? yDomainMin, yDomain?.[1] ?? yDomainMax] as [number, number],
84
78
  value: {
85
79
  x: $xScale.invert?.((localPoint(frameEl, e)?.x ?? 0) - $padding.left),
86
80
  y: $yScale.invert?.((localPoint(frameEl, e)?.y ?? 0) - $padding.top),
87
81
  },
88
82
  };
89
83
 
90
- dispatch('brushStart', { xDomain, yDomain });
84
+ onBrushStart({ xDomain, yDomain });
91
85
 
92
86
  const onPointerMove = (e: PointerEvent) => {
93
87
  fn(start, {
@@ -99,17 +93,17 @@
99
93
  // // Ignore?
100
94
  // // TODO: What about when using `x` or `y` axis?
101
95
  // } else {
102
- dispatch('change', { xDomain, yDomain });
96
+ onChange({ xDomain, yDomain });
103
97
  // }
104
98
  };
105
99
 
106
100
  const onPointerUp = (e: PointerEvent) => {
107
101
  if (e.target === frameEl) {
108
102
  reset();
109
- dispatch('change', { xDomain, yDomain });
103
+ onChange({ xDomain, yDomain });
110
104
  }
111
105
 
112
- dispatch('brushEnd', { xDomain, yDomain });
106
+ onBrushEnd({ xDomain, yDomain });
113
107
 
114
108
  if (resetOnEnd) {
115
109
  reset();
@@ -208,10 +202,10 @@
208
202
  yDomain = [yDomainMin, yDomainMax];
209
203
  }
210
204
 
211
- $: top = $yScale(yDomain[1]);
212
- $: bottom = $yScale(yDomain[0]);
213
- $: left = $xScale(xDomain[0]);
214
- $: right = $xScale(xDomain[1]);
205
+ $: top = $yScale(yDomain?.[1]);
206
+ $: bottom = $yScale(yDomain?.[0]);
207
+ $: left = $xScale(xDomain?.[0]);
208
+ $: right = $xScale(xDomain?.[1]);
215
209
 
216
210
  $: rangeTop = axis === 'both' || axis === 'y' ? top : 0;
217
211
  $: rangeLeft = axis === 'both' || axis === 'x' ? left : 0;
@@ -220,15 +214,10 @@
220
214
 
221
215
  // Set reactively to handle cases where xDomain/yDomain are set externally (ex. `bind:xDomain`)
222
216
  $: 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
- }
217
+ xDomain?.[0]?.valueOf() !== originalXDomain[0]?.valueOf() ||
218
+ xDomain?.[1]?.valueOf() !== originalXDomain[1]?.valueOf() ||
219
+ yDomain?.[0]?.valueOf() !== originalYDomain[0]?.valueOf() ||
220
+ yDomain?.[1]?.valueOf() !== originalYDomain[1]?.valueOf();
232
221
  </script>
233
222
 
234
223
  <g class={cls('Brush select-none', classes.root, $$props.class)}>
@@ -265,8 +254,10 @@
265
254
  class="handle top"
266
255
  on:pointerdown={adjustTop}
267
256
  on:dblclick={() => {
268
- yDomain[0] = yDomainMin;
269
- dispatch('change', { xDomain, yDomain });
257
+ if (yDomain) {
258
+ yDomain[0] = yDomainMin;
259
+ onChange({ xDomain, yDomain });
260
+ }
270
261
  }}
271
262
  >
272
263
  <slot name="handle" edge="top" {rangeWidth} {rangeHeight}>
@@ -285,7 +276,9 @@
285
276
  class="handle bottom"
286
277
  on:pointerdown={adjustBottom}
287
278
  on:dblclick={() => {
288
- yDomain[1] = yDomainMax;
279
+ if (yDomain) {
280
+ yDomain[1] = yDomainMax;
281
+ }
289
282
  }}
290
283
  >
291
284
  <slot name="handle" edge="bottom" {rangeWidth} {rangeHeight}>
@@ -306,8 +299,10 @@
306
299
  class="handle left"
307
300
  on:pointerdown={adjustLeft}
308
301
  on:dblclick={() => {
309
- xDomain[0] = xDomainMin;
310
- dispatch('change', { xDomain, yDomain });
302
+ if (xDomain) {
303
+ xDomain[0] = xDomainMin;
304
+ onChange({ xDomain, yDomain });
305
+ }
311
306
  }}
312
307
  >
313
308
  <slot name="handle" edge="left" {rangeWidth} {rangeHeight}>
@@ -326,8 +321,10 @@
326
321
  class="handle right"
327
322
  on:pointerdown={adjustRight}
328
323
  on:dblclick={() => {
329
- xDomain[1] = xDomainMax;
330
- dispatch('change', { xDomain, yDomain });
324
+ if (xDomain) {
325
+ xDomain[1] = xDomainMax;
326
+ onChange({ xDomain, yDomain });
327
+ }
331
328
  }}
332
329
  >
333
330
  <slot name="handle" edge="right" {rangeWidth} {rangeHeight}>
@@ -356,7 +353,7 @@
356
353
  dx={-4}
357
354
  textAnchor="end"
358
355
  verticalAnchor="middle"
359
- value={formatValue(any(xDomain[0]), format)}
356
+ value={formatValue(asAny(xDomain?.[0]), format)}
360
357
  {...typeof labels === 'object' ? labels : null}
361
358
  class={labelClass}
362
359
  />
@@ -367,7 +364,7 @@
367
364
  dx={4}
368
365
  textAnchor="start"
369
366
  verticalAnchor="middle"
370
- value={formatValue(any(xDomain[1]), format)}
367
+ value={formatValue(asAny(xDomain?.[1]), format)}
371
368
  {...typeof labels === 'object' ? labels : null}
372
369
  class={labelClass}
373
370
  />
@@ -380,7 +377,7 @@
380
377
  dy={-4}
381
378
  textAnchor="middle"
382
379
  verticalAnchor="end"
383
- value={formatValue(any(yDomain[1]), format)}
380
+ value={formatValue(asAny(yDomain?.[1]), format)}
384
381
  {...typeof labels === 'object' ? labels : null}
385
382
  class={labelClass}
386
383
  />
@@ -391,7 +388,7 @@
391
388
  dy={4}
392
389
  textAnchor="middle"
393
390
  verticalAnchor="start"
394
- value={formatValue(any(yDomain[0]), format)}
391
+ value={formatValue(asAny(yDomain?.[0]), format)}
395
392
  {...typeof labels === 'object' ? labels : null}
396
393
  class={labelClass}
397
394
  />
@@ -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;
@@ -22,21 +23,20 @@ declare const __propDef: {
22
23
  handle?: string;
23
24
  labels?: string;
24
25
  } | undefined;
26
+ onChange?: ((e: {
27
+ xDomain?: DomainType;
28
+ yDomain?: DomainType;
29
+ }) => void) | undefined;
30
+ onBrushStart?: ((e: {
31
+ xDomain?: DomainType;
32
+ yDomain?: DomainType;
33
+ }) => void) | undefined;
34
+ onBrushEnd?: ((e: {
35
+ xDomain?: DomainType;
36
+ yDomain?: DomainType;
37
+ }) => void) | undefined;
25
38
  };
26
39
  events: {
27
- change: CustomEvent<{
28
- xDomain?: [any, any];
29
- yDomain?: [any, any];
30
- }>;
31
- brushStart: CustomEvent<{
32
- xDomain?: [any, any];
33
- yDomain?: [any, any];
34
- }>;
35
- brushEnd: CustomEvent<{
36
- xDomain?: [any, any];
37
- yDomain?: [any, any];
38
- }>;
39
- } & {
40
40
  [evt: string]: CustomEvent<any>;
41
41
  };
42
42
  slots: {
@@ -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>
@@ -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>
@@ -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,22 @@
371
396
  {/if}
372
397
  </svelte:component>
373
398
 
399
+ {#if brush && brush.mode === 'integrated'}
400
+ <Svg>
401
+ {@const brushProps = { ...(typeof brush === 'object' ? brush : null), ...props.brush }}
402
+ <Brush
403
+ axis="x"
404
+ resetOnEnd
405
+ {xDomain}
406
+ {...brushProps}
407
+ onBrushEnd={(e) => {
408
+ xDomain = e.xDomain;
409
+ brushProps.onBrushEnd?.(e);
410
+ }}
411
+ />
412
+ </Svg>
413
+ {/if}
414
+
374
415
  <slot name="legend" {...slotProps}>
375
416
  {#if legend}
376
417
  <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,22 @@
297
322
  </slot>
298
323
  </svelte:component>
299
324
 
325
+ {#if brush && brush.mode === 'integrated'}
326
+ <Svg>
327
+ {@const brushProps = { ...(typeof brush === 'object' ? brush : null), ...props.brush }}
328
+ <Brush
329
+ axis="x"
330
+ resetOnEnd
331
+ {xDomain}
332
+ {...brushProps}
333
+ onBrushEnd={(e) => {
334
+ xDomain = e.xDomain;
335
+ brushProps.onBrushEnd?.(e);
336
+ }}
337
+ />
338
+ </Svg>
339
+ {/if}
340
+
300
341
  <slot name="legend" {...slotProps}>
301
342
  {#if legend}
302
343
  <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,25 @@
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
+ {@const brushProps = { ...(typeof brush === 'object' ? brush : null), ...props.brush }}
276
+ <Brush
277
+ axis="both"
278
+ resetOnEnd
279
+ {xDomain}
280
+ {yDomain}
281
+ {...brushProps}
282
+ onBrushEnd={(e) => {
283
+ xDomain = e.xDomain;
284
+ yDomain = e.yDomain;
285
+ brushProps.onBrushEnd?.(e);
286
+ }}
287
+ />
288
+ </Svg>
289
+ {/if}
290
+
250
291
  <slot name="legend" {...slotProps}>
251
292
  {#if legend}
252
293
  <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.4",
7
+ "version": "0.81.0",
8
8
  "devDependencies": {
9
9
  "@changesets/cli": "^2.27.12",
10
10
  "@mdi/js": "^7.4.47",