layerchart 2.0.0-next.56 → 2.0.0-next.58

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 (29) hide show
  1. package/dist/components/AnnotationLine.svelte +112 -66
  2. package/dist/components/AnnotationLine.svelte.d.ts +10 -2
  3. package/dist/components/AnnotationPoint.svelte +97 -23
  4. package/dist/components/AnnotationPoint.svelte.d.ts +8 -1
  5. package/dist/components/AnnotationRange.svelte +17 -6
  6. package/dist/components/GeoPath.svelte +4 -4
  7. package/dist/components/Link.svelte +261 -75
  8. package/dist/components/Link.svelte.d.ts +69 -26
  9. package/dist/components/Spline.svelte +2 -2
  10. package/dist/components/Text.svelte +1 -1
  11. package/dist/components/Voronoi.svelte +35 -6
  12. package/dist/components/Voronoi.svelte.d.ts +9 -0
  13. package/dist/components/charts/__screenshots__/BarChart.svelte.test.ts/BarChart-separate-data-per-series-should-render-stacked-series-with-separate-data-arrays-1.png +0 -0
  14. package/dist/components/charts/__screenshots__/BarChart.svelte.test.ts/BarChart-separate-data-per-series-should-render-stacked-series-with-separate-data-arrays-2.png +0 -0
  15. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-series-header-for-multi-series-1.png +0 -0
  16. package/dist/components/charts/__screenshots__/DefaultTooltip.svelte.test.ts/DefaultTooltip-ScatterChart--single-point--quadtree-mode--should-show-series-header-for-multi-series-2.png +0 -0
  17. package/dist/components/index.d.ts +0 -2
  18. package/dist/components/index.js +0 -2
  19. package/dist/components/tooltip/TooltipContext.svelte +39 -10
  20. package/dist/components/tooltip/TooltipContext.svelte.d.ts +14 -0
  21. package/dist/states/brush.svelte.d.ts +1 -1
  22. package/dist/states/chart.svelte.js +38 -12
  23. package/dist/states/chart.svelte.test.js +227 -0
  24. package/dist/utils/linkUtils.d.ts +42 -0
  25. package/dist/utils/{connectorUtils.js → linkUtils.js} +56 -6
  26. package/package.json +1 -1
  27. package/dist/components/Connector.svelte +0 -167
  28. package/dist/components/Connector.svelte.d.ts +0 -56
  29. package/dist/utils/connectorUtils.d.ts +0 -34
@@ -1,9 +1,18 @@
1
1
  import type { Without } from '../utils/types.js';
2
+ import type { Accessor } from '../utils/common.js';
2
3
  export type VoronoiPropsWithoutHTML = {
3
4
  /**
4
5
  * Override data instead of using context
5
6
  */
6
7
  data?: any;
8
+ /**
9
+ * Override the `x` accessor used to place each point. Useful when the
10
+ * chart's `x` accessor returns an array of values (e.g. `['start', 'end']`)
11
+ * and you want to use a specific one.
12
+ */
13
+ x?: Accessor;
14
+ /** Override the `y` accessor used to place each point. See `x` above. */
15
+ y?: Accessor;
7
16
  /** Radius to clip voronoi cells. `0` or `undefined` to disables clipping */
8
17
  r?: number;
9
18
  /**
@@ -46,8 +46,6 @@ export { default as ClipPath } from './ClipPath.svelte';
46
46
  export * from './ClipPath.svelte';
47
47
  export { default as ColorRamp } from './ColorRamp.svelte';
48
48
  export * from './ColorRamp.svelte';
49
- export { default as Connector } from './Connector.svelte';
50
- export * from './Connector.svelte';
51
49
  export { default as Contour } from './Contour.svelte';
52
50
  export * from './Contour.svelte';
53
51
  export { default as Dagre } from './Dagre.svelte';
@@ -46,8 +46,6 @@ export { default as ClipPath } from './ClipPath.svelte';
46
46
  export * from './ClipPath.svelte';
47
47
  export { default as ColorRamp } from './ColorRamp.svelte';
48
48
  export * from './ColorRamp.svelte';
49
- export { default as Connector } from './Connector.svelte';
50
- export * from './Connector.svelte';
51
49
  export { default as Contour } from './Contour.svelte';
52
50
  export * from './Contour.svelte';
53
51
  export { default as Dagre } from './Dagre.svelte';
@@ -2,6 +2,7 @@
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
3
  import { asAny, type Without } from '../../utils/types.js';
4
4
  import type { TooltipState as TooltipStateType } from '../../states/tooltip.svelte.js';
5
+ import type { Accessor } from '../../utils/common.js';
5
6
 
6
7
  export type TooltipMode =
7
8
  | 'bisect-x' // requires values to be sorted
@@ -55,6 +56,21 @@
55
56
  */
56
57
  radius?: number;
57
58
 
59
+ /**
60
+ * Override the `x` accessor used for quadtree/voronoi hit detection.
61
+ * Useful when the chart's `x` accessor returns an array of values and you
62
+ * want hit detection at a specific endpoint.
63
+ *
64
+ * Accepts a string property name (e.g. `'POP_2015'`) or a function.
65
+ */
66
+ x?: Accessor;
67
+
68
+ /**
69
+ * Override the `y` accessor used for quadtree/voronoi hit detection.
70
+ * See `x` above.
71
+ */
72
+ y?: Accessor;
73
+
58
74
  /**
59
75
  * Enable debug view (show hit targets, etc)
60
76
  * @default false
@@ -131,6 +147,8 @@
131
147
  radius = Infinity,
132
148
  raiseTarget = false,
133
149
  state: stateProp = $bindable() as TooltipStateType<TData>,
150
+ x: xProp,
151
+ y: yProp,
134
152
  children,
135
153
  }: TooltipContextProps<TData> = $props();
136
154
 
@@ -411,6 +429,9 @@
411
429
  }, hideDelay);
412
430
  }
413
431
 
432
+ const xAccessorOverride = $derived(xProp != null ? accessor(xProp) : undefined);
433
+ const yAccessorOverride = $derived(yProp != null ? accessor(yProp) : undefined);
434
+
414
435
  const quadtree: Quadtree<[number, number]> | undefined = $derived.by(() => {
415
436
  if (['quadtree', 'quadtree-x', 'quadtree-y'].includes(mode)) {
416
437
  return d3Quadtree()
@@ -419,6 +440,11 @@
419
440
  return 0;
420
441
  }
421
442
 
443
+ if (xAccessorOverride) {
444
+ const scaled = ctx.xScale(xAccessorOverride(d));
445
+ return typeof scaled === 'number' ? scaled : 0;
446
+ }
447
+
422
448
  if (geo.projection) {
423
449
  const lat = ctx.x(d);
424
450
  const long = ctx.y(d);
@@ -429,11 +455,10 @@
429
455
  const value = ctx.xGet(d);
430
456
 
431
457
  if (Array.isArray(value)) {
432
- // `x` accessor with multiple properties (ex. `x={['start', 'end']})`)
433
- // Using first value. Consider using average, max, etc
434
- // const midpoint = new Date((value[1].valueOf() + value[0].getTime()) / 2);
435
- // return midpoint;
436
- return min(value);
458
+ // `x` accessor with multiple properties (ex. `x={['start', 'end']})`).
459
+ // Default to the max (typically the "target"/"end" endpoint); override
460
+ // via the `x` prop for explicit control.
461
+ return max(value);
437
462
  } else {
438
463
  return value;
439
464
  }
@@ -443,6 +468,11 @@
443
468
  return 0;
444
469
  }
445
470
 
471
+ if (yAccessorOverride) {
472
+ const scaled = ctx.yScale(yAccessorOverride(d));
473
+ return typeof scaled === 'number' ? scaled : 0;
474
+ }
475
+
446
476
  if (geo.projection) {
447
477
  const lat = ctx.x(d);
448
478
  const long = ctx.y(d);
@@ -453,11 +483,8 @@
453
483
  const value = ctx.yGet(d);
454
484
 
455
485
  if (Array.isArray(value)) {
456
- // `x` accessor with multiple properties (ex. `x={['start', 'end']})`)
457
- // Using first value. Consider using average, max, etc
458
- // const midpoint = new Date((value[1].valueOf() + value[0].getTime()) / 2);
459
- // return midpoint;
460
- return min(value);
486
+ // `y` accessor with multiple properties default to max endpoint.
487
+ return max(value);
461
488
  } else {
462
489
  return value;
463
490
  }
@@ -676,6 +703,8 @@
676
703
  {#if mode === 'voronoi'}
677
704
  <Svg>
678
705
  <Voronoi
706
+ x={xProp}
707
+ y={yProp}
679
708
  r={radius}
680
709
  onpointerenter={(e, { data }) => {
681
710
  showTooltip(e, data);
@@ -1,6 +1,7 @@
1
1
  import type { HTMLAttributes } from 'svelte/elements';
2
2
  import { type Without } from '../../utils/types.js';
3
3
  import type { TooltipState as TooltipStateType } from '../../states/tooltip.svelte.js';
4
+ import type { Accessor } from '../../utils/common.js';
4
5
  export type TooltipMode = 'bisect-x' | 'bisect-y' | 'band' | 'bisect-band' | 'bounds' | 'voronoi' | 'quadtree' | 'quadtree-x' | 'quadtree-y' | 'manual';
5
6
  type TooltipContextPropsWithoutHTML<T = any> = {
6
7
  /**
@@ -36,6 +37,19 @@ type TooltipContextPropsWithoutHTML<T = any> = {
36
37
  * @default Infinity
37
38
  */
38
39
  radius?: number;
40
+ /**
41
+ * Override the `x` accessor used for quadtree/voronoi hit detection.
42
+ * Useful when the chart's `x` accessor returns an array of values and you
43
+ * want hit detection at a specific endpoint.
44
+ *
45
+ * Accepts a string property name (e.g. `'POP_2015'`) or a function.
46
+ */
47
+ x?: Accessor;
48
+ /**
49
+ * Override the `y` accessor used for quadtree/voronoi hit detection.
50
+ * See `x` above.
51
+ */
52
+ y?: Accessor;
39
53
  /**
40
54
  * Enable debug view (show hit targets, etc)
41
55
  * @default false
@@ -35,7 +35,7 @@ export declare class BrushState {
35
35
  x: BrushDomainType;
36
36
  y: BrushDomainType;
37
37
  active: boolean | undefined;
38
- axis: "both" | "x" | "y";
38
+ axis: "x" | "y" | "both";
39
39
  handleSize: number;
40
40
  constructor(ctx: typeof this.ctx, options?: {
41
41
  x?: BrushDomainType;
@@ -174,6 +174,13 @@ export class ChartState {
174
174
  // Use the value axis accessor (y for horizontal charts, x for vertical).
175
175
  const valueAxis = this.valueAxis;
176
176
  const chartValueProp = valueAxis === 'y' ? this.props.y : this.props.x;
177
+ const chartValueKeys = Array.isArray(chartValueProp)
178
+ ? chartValueProp.filter((k) => typeof k === 'string')
179
+ : typeof chartValueProp === 'string'
180
+ ? [chartValueProp]
181
+ : [];
182
+ const chartHasOwnData = this.props.data != null &&
183
+ (!Array.isArray(this.props.data) || this.props.data.length > 0);
177
184
  const implicitSeries = [];
178
185
  for (const { info } of this._markInfos) {
179
186
  const valueAccessor = valueAxis === 'y' ? info.y : info.x;
@@ -181,10 +188,12 @@ export class ChartState {
181
188
  (typeof valueAccessor === 'string' ? valueAccessor : undefined);
182
189
  if (!key)
183
190
  continue;
184
- // Skip if the mark just reuses the chart's own axis accessor and has no
185
- // separate data it's not defining a new series, just using the chart's axis.
186
- // Marks with their own data arrays are kept (multi-dataset scenario).
187
- if (key === chartValueProp && !info.data)
191
+ // Skip if the mark's key matches one of the chart's axis accessors.
192
+ // When the chart has its own data, any mark using that accessor is just
193
+ // decorating (e.g. a filtered label subset), not defining a new series.
194
+ // Without explicit chart data, still skip unless the mark has its own
195
+ // dataset (multi-dataset scenario).
196
+ if (chartValueKeys.includes(key) && (chartHasOwnData || !info.data))
188
197
  continue;
189
198
  if (implicitSeries.some((s) => s.key === key))
190
199
  continue;
@@ -316,14 +325,21 @@ export class ChartState {
316
325
  // Use $derived fields instead of getters for caching
317
326
  containerWidth = $derived(this.props.width ?? this._containerWidth);
318
327
  containerHeight = $derived(this.props.height ?? this._containerHeight);
319
- // If seriesState has series-specific data, use visible series data (for domain calculations).
320
- // This allows simplified charts to pass raw data and let Chart derive chartData from seriesState.
321
- // Using visibleSeriesData ensures domain recalculates when series are shown/hidden via legend.
328
+ // When `<Chart data>` is passed with a non-empty dataset, it's canonical
329
+ // marks with their own `data` (e.g. filtered label subsets) still contribute
330
+ // to `flatData` for domain calculation but don't replace iteration data.
331
+ // Otherwise fall back to `visibleSeriesData` so simplified charts that pass
332
+ // data via series definitions still work, with reactive recomputation when
333
+ // series are shown/hidden via legend.
322
334
  data = $derived.by(() => {
335
+ const propsData = this.props.data;
336
+ if (propsData != null && (!Array.isArray(propsData) || propsData.length > 0)) {
337
+ return propsData;
338
+ }
323
339
  if (this.seriesState?.visibleSeriesData?.length) {
324
340
  return this.seriesState.visibleSeriesData;
325
341
  }
326
- return this.props.data ?? [];
342
+ return [];
327
343
  });
328
344
  flatData = $derived.by(() => {
329
345
  const base = (this.props.flatData ?? this.data);
@@ -678,8 +694,13 @@ export class ChartState {
678
694
  rDomain = $derived(calcDomain('r', this.extents, this.props.rDomain));
679
695
  x1Domain = $derived.by(() => {
680
696
  if (this.props.x1Domain) {
681
- const visibleKeys = new Set(this.seriesState.visibleSeries.map((s) => s.key));
682
- return this.props.x1Domain.filter((key) => visibleKeys.has(key));
697
+ // Only filter by visible series when series are configured — otherwise the
698
+ // full x1Domain is used as-is (composable charts without series).
699
+ if (this.seriesState.series.length > 0) {
700
+ const visibleKeys = new Set(this.seriesState.visibleSeries.map((s) => s.key));
701
+ return this.props.x1Domain.filter((key) => visibleKeys.has(key));
702
+ }
703
+ return this.props.x1Domain;
683
704
  }
684
705
  // Auto-derive for grouped series when x is the category axis
685
706
  if (this.props.seriesLayout === 'group' && this.valueAxis === 'y') {
@@ -692,8 +713,13 @@ export class ChartState {
692
713
  });
693
714
  y1Domain = $derived.by(() => {
694
715
  if (this.props.y1Domain) {
695
- const visibleKeys = new Set(this.seriesState.visibleSeries.map((s) => s.key));
696
- return this.props.y1Domain.filter((key) => visibleKeys.has(key));
716
+ // Only filter by visible series when series are configured — otherwise the
717
+ // full y1Domain is used as-is (composable charts without series).
718
+ if (this.seriesState.series.length > 0) {
719
+ const visibleKeys = new Set(this.seriesState.visibleSeries.map((s) => s.key));
720
+ return this.props.y1Domain.filter((key) => visibleKeys.has(key));
721
+ }
722
+ return this.props.y1Domain;
697
723
  }
698
724
  // Auto-derive for grouped series when y is the category axis
699
725
  if (this.props.seriesLayout === 'group' && this.valueAxis === 'x') {
@@ -534,6 +534,187 @@ describe('ChartState mark registration', () => {
534
534
  }
535
535
  });
536
536
  });
537
+ describe('ChartState data vs visibleSeriesData', () => {
538
+ it('should return props.data when explicit, even if a mark registers a filtered subset', () => {
539
+ const fullData = [
540
+ { date: '2024-01', value: 10 },
541
+ { date: '2024-02', value: 20 },
542
+ { date: '2024-03', value: 30 },
543
+ ];
544
+ const highlighted = [fullData[0]]; // filtered subset
545
+ const { state, cleanup } = createChartState({
546
+ data: fullData,
547
+ x: 'date',
548
+ y: 'value',
549
+ });
550
+ try {
551
+ // Simulate a decorative mark (e.g. <Text data={highlighted}>) registering
552
+ // its own filtered dataset with the same value accessor as the chart.
553
+ state.registerMark({ y: 'value', data: highlighted });
554
+ flushSync();
555
+ // ctx.data (used by sibling marks for iteration) should remain the full
556
+ // chart data, not be replaced by the filtered subset.
557
+ expect(state.data).toBe(fullData);
558
+ expect(state.data).toHaveLength(3);
559
+ }
560
+ finally {
561
+ cleanup();
562
+ }
563
+ });
564
+ it('should fall back to visibleSeriesData when props.data is not provided', () => {
565
+ const applesData = [
566
+ { date: '2024-01', value: 10 },
567
+ { date: '2024-02', value: 20 },
568
+ ];
569
+ const bananasData = [
570
+ { date: '2024-01', value: 15 },
571
+ { date: '2024-02', value: 25 },
572
+ ];
573
+ const { state, cleanup } = createChartState({
574
+ x: 'date',
575
+ y: 'value',
576
+ series: [
577
+ { key: 'apples', data: applesData },
578
+ { key: 'bananas', data: bananasData },
579
+ ],
580
+ });
581
+ try {
582
+ // No props.data — ctx.data should flatten series data for iteration.
583
+ expect(state.data).toHaveLength(4);
584
+ }
585
+ finally {
586
+ cleanup();
587
+ }
588
+ });
589
+ it('should not create an implicit series for a decorative mark when chart has own data', () => {
590
+ // Scenario: <Chart data={full}> + <Text data={highlighted} y="value"> (labels)
591
+ // The Text mark shouldn't create an implicit series that narrows the domain.
592
+ const fullData = [
593
+ { date: '2024-01', value: 10 },
594
+ { date: '2024-02', value: 50 },
595
+ { date: '2024-03', value: 100 },
596
+ ];
597
+ const highlighted = [fullData[1]];
598
+ const { state, cleanup } = createChartState({
599
+ data: fullData,
600
+ x: 'date',
601
+ y: 'value',
602
+ });
603
+ try {
604
+ state.registerMark({ y: 'value', data: highlighted });
605
+ flushSync();
606
+ // Decorative mark shouldn't turn this into a multi-series chart.
607
+ expect(state.seriesState.isDefaultSeries).toBe(true);
608
+ }
609
+ finally {
610
+ cleanup();
611
+ }
612
+ });
613
+ it('should not create an implicit series when chart uses array y accessor matching the mark key', () => {
614
+ const fullData = [
615
+ { date: '2024-01', v1: 10, v2: 20 },
616
+ { date: '2024-02', v1: 30, v2: 40 },
617
+ ];
618
+ const { state, cleanup } = createChartState({
619
+ data: fullData,
620
+ x: 'date',
621
+ y: ['v1', 'v2'],
622
+ });
623
+ try {
624
+ state.registerMark({ y: 'v2', data: [fullData[0]] });
625
+ flushSync();
626
+ expect(state.seriesState.isDefaultSeries).toBe(true);
627
+ }
628
+ finally {
629
+ cleanup();
630
+ }
631
+ });
632
+ it('should compute yDomain from full chart data when a decorative mark has a filtered subset', () => {
633
+ // Regression: Text labeling highlighted rows shouldn't narrow the y domain.
634
+ const fullData = [
635
+ { date: '2024-01', value: 10 },
636
+ { date: '2024-02', value: 50 },
637
+ { date: '2024-03', value: 100 },
638
+ ];
639
+ const highlighted = [fullData[1]]; // only value=50
640
+ const { state, cleanup } = createChartState({
641
+ data: fullData,
642
+ x: 'date',
643
+ y: 'value',
644
+ });
645
+ try {
646
+ state.registerMark({ y: 'value', data: highlighted });
647
+ flushSync();
648
+ // yDomain should reflect the full data extent [10, 100], not just [50, 50].
649
+ expect(state.yDomain).toEqual([10, 100]);
650
+ }
651
+ finally {
652
+ cleanup();
653
+ }
654
+ });
655
+ it('should compute yDomain across all array y accessors on Chart', () => {
656
+ const data = [
657
+ { date: '2024-01', v1: 3, v2: 5 },
658
+ { date: '2024-02', v1: 2, v2: 8 },
659
+ { date: '2024-03', v1: 4, v2: 9 },
660
+ ];
661
+ const { state, cleanup } = createChartState({
662
+ data,
663
+ x: 'date',
664
+ y: ['v1', 'v2'],
665
+ });
666
+ try {
667
+ // Domain should span min(v1) = 2 to max(v2) = 9
668
+ expect(state.yDomain).toEqual([2, 9]);
669
+ }
670
+ finally {
671
+ cleanup();
672
+ }
673
+ });
674
+ it('should keep full yDomain when decorative mark + array y both present', () => {
675
+ const data = [
676
+ { date: '2024-01', v1: 3, v2: 5 },
677
+ { date: '2024-02', v1: 2, v2: 8 },
678
+ { date: '2024-03', v1: 4, v2: 9 },
679
+ ];
680
+ const { state, cleanup } = createChartState({
681
+ data,
682
+ x: 'date',
683
+ y: ['v1', 'v2'],
684
+ });
685
+ try {
686
+ // Decorative Text mark with subset data and y matching one of chart's keys
687
+ state.registerMark({ y: 'v2', data: [data[0]] });
688
+ flushSync();
689
+ // Still the full range [2, 9], not narrowed to [5, 5]
690
+ expect(state.yDomain).toEqual([2, 9]);
691
+ expect(state.seriesState.isDefaultSeries).toBe(true);
692
+ }
693
+ finally {
694
+ cleanup();
695
+ }
696
+ });
697
+ it('should fall back to visibleSeriesData when props.data is an empty array', () => {
698
+ // Composite charts (BarChart, etc.) default `data = []` when not passed.
699
+ const applesData = [{ date: '2024-01', value: 10 }];
700
+ const bananasData = [{ date: '2024-01', value: 15 }];
701
+ const { state, cleanup } = createChartState({
702
+ data: [],
703
+ x: 'date',
704
+ y: 'value',
705
+ series: [
706
+ { key: 'apples', data: applesData },
707
+ { key: 'bananas', data: bananasData },
708
+ ],
709
+ });
710
+ try {
711
+ expect(state.data).toHaveLength(2);
712
+ }
713
+ finally {
714
+ cleanup();
715
+ }
716
+ });
717
+ });
537
718
  describe('ChartState geo projection skips markInfo', () => {
538
719
  const geoData = [
539
720
  { name: 'New York', longitude: -74.006, latitude: 40.7128 },
@@ -1406,3 +1587,49 @@ describe('ChartState group layout auto-derives x1/y1', () => {
1406
1587
  }
1407
1588
  });
1408
1589
  });
1590
+ describe('ChartState x1Domain/y1Domain without series', () => {
1591
+ const longData = [
1592
+ { year: 2019, fruit: 'apples', value: 3840 },
1593
+ { year: 2019, fruit: 'bananas', value: 1920 },
1594
+ { year: 2018, fruit: 'apples', value: 1600 },
1595
+ { year: 2018, fruit: 'bananas', value: 1440 },
1596
+ ];
1597
+ it('should pass through explicit x1Domain when no series are configured', () => {
1598
+ const { state, cleanup } = createChartState({
1599
+ data: longData,
1600
+ x: 'year',
1601
+ xScale: scaleBand(),
1602
+ y: 'value',
1603
+ x1: 'fruit',
1604
+ x1Domain: ['apples', 'bananas'],
1605
+ x1Range: ({ xScale }) => [0, xScale.bandwidth()],
1606
+ });
1607
+ try {
1608
+ expect(state.seriesState.series).toHaveLength(0);
1609
+ expect(state.x1Domain).toEqual(['apples', 'bananas']);
1610
+ expect(state.x1Scale.domain()).toEqual(['apples', 'bananas']);
1611
+ }
1612
+ finally {
1613
+ cleanup();
1614
+ }
1615
+ });
1616
+ it('should pass through explicit y1Domain when no series are configured', () => {
1617
+ const { state, cleanup } = createChartState({
1618
+ data: longData,
1619
+ y: 'year',
1620
+ yScale: scaleBand(),
1621
+ x: 'value',
1622
+ y1: 'fruit',
1623
+ y1Domain: ['apples', 'bananas'],
1624
+ y1Range: ({ yScale }) => [0, yScale.bandwidth()],
1625
+ });
1626
+ try {
1627
+ expect(state.seriesState.series).toHaveLength(0);
1628
+ expect(state.y1Domain).toEqual(['apples', 'bananas']);
1629
+ expect(state.y1Scale.domain()).toEqual(['apples', 'bananas']);
1630
+ }
1631
+ finally {
1632
+ cleanup();
1633
+ }
1634
+ });
1635
+ });
@@ -0,0 +1,42 @@
1
+ import { type CurveFactory } from 'd3-shape';
2
+ export type LinkCoords = {
3
+ x: number;
4
+ y: number;
5
+ };
6
+ export type PresetLinkType = 'straight' | 'square' | 'beveled' | 'rounded' | 'swoop';
7
+ export type LinkType = PresetLinkType | 'd3';
8
+ export type LinkSweep = 'horizontal-vertical' | 'vertical-horizontal' | 'none';
9
+ type GetLinkPresetPathProps = {
10
+ source: LinkCoords;
11
+ target: LinkCoords;
12
+ radius: number;
13
+ type: PresetLinkType;
14
+ sweep: LinkSweep;
15
+ /** Bend angle in degrees, used by 'swoop' type. Default 22.5. */
16
+ bend?: number;
17
+ };
18
+ export declare function getLinkPresetPath(opts: GetLinkPresetPathProps): string;
19
+ type GetLinkD3PathProps = Omit<GetLinkPresetPathProps, 'radius' | 'type'> & {
20
+ curve: CurveFactory;
21
+ /**
22
+ * Cartesian orientation hint for axis-dependent curves (d3 step variants step
23
+ * along x by default; for 'vertical' we step along y to match the natural flow).
24
+ */
25
+ orientation?: 'horizontal' | 'vertical';
26
+ };
27
+ export declare function getLinkD3Path({ source, target, sweep, curve, orientation, }: GetLinkD3PathProps): string;
28
+ type GetLinkRadialPresetPathProps = {
29
+ source: LinkCoords;
30
+ target: LinkCoords;
31
+ type: PresetLinkType;
32
+ radius: number;
33
+ bend?: number;
34
+ };
35
+ export declare function getLinkRadialPresetPath({ source, target, type, radius, bend, }: GetLinkRadialPresetPathProps): string;
36
+ type GetLinkRadialD3PathProps = {
37
+ source: LinkCoords;
38
+ target: LinkCoords;
39
+ curve?: CurveFactory;
40
+ };
41
+ export declare function getLinkRadialD3Path({ source, target, curve, }: GetLinkRadialD3PathProps): string;
42
+ export {};