@rokkit/chart 1.3.6 → 1.3.8

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 (93) hide show
  1. package/dist/AnimatedPlot.svelte +26 -8
  2. package/dist/AnimatedPlot.svelte.d.ts +5 -0
  3. package/dist/Chart.svelte +1 -1
  4. package/dist/FacetPlot.svelte +1 -1
  5. package/dist/Plot/Axis.svelte +21 -10
  6. package/dist/Plot/Grid.svelte +32 -13
  7. package/dist/Plot/Grid.svelte.d.ts +6 -1
  8. package/dist/Plot/Root.svelte +64 -46
  9. package/dist/Plot/Root.svelte.d.ts +27 -2
  10. package/dist/Plot/Timeline.svelte +39 -13
  11. package/dist/Plot/index.d.ts +4 -4
  12. package/dist/Plot/index.js +4 -4
  13. package/dist/Plot.svelte +128 -164
  14. package/dist/Plot.svelte.d.ts +18 -2
  15. package/dist/PlotState.svelte.d.ts +23 -0
  16. package/dist/PlotState.svelte.js +155 -16
  17. package/dist/PlotSurface.svelte +162 -0
  18. package/dist/PlotSurface.svelte.d.ts +25 -0
  19. package/dist/charts/AreaChart.svelte +37 -4
  20. package/dist/charts/AreaChart.svelte.d.ts +12 -2
  21. package/dist/charts/BarChart.svelte +32 -3
  22. package/dist/charts/BarChart.svelte.d.ts +10 -1
  23. package/dist/charts/BoxPlot.svelte +4 -2
  24. package/dist/charts/BoxPlot.svelte.d.ts +1 -0
  25. package/dist/charts/BubbleChart.svelte +34 -3
  26. package/dist/charts/BubbleChart.svelte.d.ts +11 -1
  27. package/dist/charts/LineChart.svelte +35 -4
  28. package/dist/charts/LineChart.svelte.d.ts +11 -2
  29. package/dist/charts/PieChart.svelte +1 -1
  30. package/dist/charts/ScatterPlot.svelte +32 -3
  31. package/dist/charts/ScatterPlot.svelte.d.ts +10 -1
  32. package/dist/charts/ViolinPlot.svelte +4 -2
  33. package/dist/charts/ViolinPlot.svelte.d.ts +1 -0
  34. package/dist/examples/BarChartExample.svelte +1 -1
  35. package/dist/geoms/Arc.svelte +3 -2
  36. package/dist/geoms/Area.svelte +20 -3
  37. package/dist/geoms/Bar.svelte +57 -13
  38. package/dist/geoms/Box.svelte +131 -76
  39. package/dist/geoms/Box.svelte.d.ts +7 -0
  40. package/dist/geoms/Candlestick.svelte +17 -11
  41. package/dist/geoms/Highlight.svelte +105 -0
  42. package/dist/geoms/Highlight.svelte.d.ts +10 -0
  43. package/dist/geoms/Jitter.svelte +82 -0
  44. package/dist/geoms/Jitter.svelte.d.ts +15 -0
  45. package/dist/geoms/Line.svelte +26 -9
  46. package/dist/geoms/Point.svelte +37 -11
  47. package/dist/geoms/Rule.svelte +113 -0
  48. package/dist/geoms/Rule.svelte.d.ts +16 -0
  49. package/dist/geoms/Trend.svelte +66 -0
  50. package/dist/geoms/Trend.svelte.d.ts +12 -0
  51. package/dist/geoms/Violin.svelte +30 -6
  52. package/dist/geoms/Violin.svelte.d.ts +5 -0
  53. package/dist/geoms/Waterfall.svelte +16 -9
  54. package/dist/geoms/lib/areas.d.ts +6 -2
  55. package/dist/geoms/lib/areas.js +17 -11
  56. package/dist/geoms/lib/bars.d.ts +18 -3
  57. package/dist/geoms/lib/bars.js +70 -96
  58. package/dist/index.d.ts +30 -4
  59. package/dist/index.js +31 -5
  60. package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +1 -1
  61. package/dist/lib/brewing/brewer.svelte.d.ts +1 -0
  62. package/dist/lib/brewing/marks/arcs.js +7 -1
  63. package/dist/lib/brewing/marks/boxes.d.ts +11 -2
  64. package/dist/lib/brewing/marks/boxes.js +32 -11
  65. package/dist/lib/brewing/marks/lines.d.ts +6 -2
  66. package/dist/lib/brewing/marks/lines.js +19 -12
  67. package/dist/lib/brewing/marks/swarm.d.ts +32 -0
  68. package/dist/lib/brewing/marks/swarm.js +125 -0
  69. package/dist/lib/brewing/marks/violins.d.ts +4 -20
  70. package/dist/lib/brewing/marks/violins.js +41 -25
  71. package/dist/lib/brewing/stats.d.ts +3 -1
  72. package/dist/lib/brewing/stats.js +28 -11
  73. package/dist/lib/highlight.d.ts +10 -0
  74. package/dist/lib/highlight.js +43 -0
  75. package/dist/lib/plot/scales.js +12 -7
  76. package/dist/lib/plot/types.d.ts +9 -1
  77. package/dist/lib/plot/types.js +3 -1
  78. package/dist/lib/scale.d.ts +10 -0
  79. package/dist/lib/scale.js +14 -0
  80. package/dist/lib/select.d.ts +22 -0
  81. package/dist/lib/select.js +22 -0
  82. package/dist/lib/trend.d.ts +27 -0
  83. package/dist/lib/trend.js +113 -0
  84. package/dist/patterns/README.md +1 -1
  85. package/package.json +1 -1
  86. package/dist/Plot/Area.svelte +0 -54
  87. package/dist/Plot/Area.svelte.d.ts +0 -14
  88. package/dist/Plot/Bar.svelte +0 -69
  89. package/dist/Plot/Bar.svelte.d.ts +0 -11
  90. package/dist/Plot/Line.svelte +0 -54
  91. package/dist/Plot/Line.svelte.d.ts +0 -13
  92. package/dist/Plot/Point.svelte +0 -58
  93. package/dist/Plot/Point.svelte.d.ts +0 -13
@@ -14,6 +14,7 @@
14
14
  mode?: 'light' | 'dark'
15
15
  grid?: boolean
16
16
  legend?: boolean
17
+ tooltip?: boolean | ((data: Row) => string)
17
18
  yFormat?: (v: unknown) => string
18
19
  yTicks?: number
19
20
  minorTicks?: boolean
@@ -26,15 +27,16 @@
26
27
  fill = undefined,
27
28
  width = 600,
28
29
  height = 400,
29
- mode = 'light',
30
+ mode = undefined,
30
31
  grid = true,
31
32
  legend = false,
33
+ tooltip = false,
32
34
  yFormat = undefined,
33
35
  yTicks = undefined,
34
36
  minorTicks = false
35
37
  }: Props = $props()
36
38
  </script>
37
39
 
38
- <Plot {data} {width} {height} {mode} {grid} {legend} {yFormat} {yTicks} {minorTicks}>
40
+ <Plot {data} {width} {height} {mode} {grid} {legend} {tooltip} {yFormat} {yTicks} {minorTicks}>
39
41
  <Box {x} {y} {fill} />
40
42
  </Plot>
@@ -9,6 +9,7 @@ type Props = {
9
9
  mode?: 'light' | 'dark';
10
10
  grid?: boolean;
11
11
  legend?: boolean;
12
+ tooltip?: boolean | ((data: Row) => string);
12
13
  yFormat?: (v: unknown) => string;
13
14
  yTicks?: number;
14
15
  minorTicks?: boolean;
@@ -4,6 +4,7 @@
4
4
 
5
5
  type Row = Record<string, unknown>
6
6
  type Format = (v: unknown) => string
7
+ type Method = string | number | { type: string; [k: string]: unknown }
7
8
 
8
9
  type Props = {
9
10
  data?: Row[]
@@ -17,11 +18,17 @@
17
18
  mode?: 'light' | 'dark'
18
19
  grid?: boolean
19
20
  legend?: boolean
21
+ tooltip?: boolean | ((data: Row) => string)
22
+ highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean)
23
+ trend?: Method | Method[]
20
24
  xFormat?: Format
21
25
  yFormat?: Format
22
26
  xTicks?: number
23
27
  yTicks?: number
24
28
  minorTicks?: boolean
29
+ onselect?: (detail: unknown) => void
30
+ selectable?: boolean
31
+ selected?: Row[]
25
32
  }
26
33
 
27
34
  let {
@@ -33,17 +40,41 @@
33
40
  size, // required: field name for bubble radius
34
41
  width = 600,
35
42
  height = 400,
36
- mode = 'light',
43
+ mode = undefined,
37
44
  grid = true,
38
45
  legend = false,
46
+ tooltip = false,
47
+ highlight = undefined,
48
+ trend = undefined,
39
49
  xFormat = undefined,
40
50
  yFormat = undefined,
41
51
  xTicks = undefined,
42
52
  yTicks = undefined,
43
- minorTicks = false
53
+ minorTicks = false,
54
+ onselect = undefined,
55
+ selectable = false,
56
+ selected = $bindable([])
44
57
  }: Props = $props()
45
58
  </script>
46
59
 
47
- <Plot {data} {width} {height} {mode} {grid} {legend} {xFormat} {yFormat} {xTicks} {yTicks} {minorTicks}>
60
+ <Plot
61
+ {data}
62
+ {width}
63
+ {height}
64
+ {mode}
65
+ {grid}
66
+ {legend}
67
+ {tooltip}
68
+ {highlight}
69
+ {trend}
70
+ {xFormat}
71
+ {yFormat}
72
+ {xTicks}
73
+ {yTicks}
74
+ {minorTicks}
75
+ {onselect}
76
+ {selectable}
77
+ bind:selected
78
+ >
48
79
  <Point {x} {y} {color} {size} />
49
80
  </Plot>
@@ -1,5 +1,9 @@
1
1
  type Row = Record<string, unknown>;
2
2
  type Format = (v: unknown) => string;
3
+ type Method = string | number | {
4
+ type: string;
5
+ [k: string]: unknown;
6
+ };
3
7
  type Props = {
4
8
  data?: Row[];
5
9
  x?: string;
@@ -12,12 +16,18 @@ type Props = {
12
16
  mode?: 'light' | 'dark';
13
17
  grid?: boolean;
14
18
  legend?: boolean;
19
+ tooltip?: boolean | ((data: Row) => string);
20
+ highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean);
21
+ trend?: Method | Method[];
15
22
  xFormat?: Format;
16
23
  yFormat?: Format;
17
24
  xTicks?: number;
18
25
  yTicks?: number;
19
26
  minorTicks?: boolean;
27
+ onselect?: (detail: unknown) => void;
28
+ selectable?: boolean;
29
+ selected?: Row[];
20
30
  };
21
- declare const BubbleChart: import("svelte").Component<Props, {}, "">;
31
+ declare const BubbleChart: import("svelte").Component<Props, {}, "selected">;
22
32
  type BubbleChart = ReturnType<typeof BubbleChart>;
23
33
  export default BubbleChart;
@@ -3,6 +3,7 @@
3
3
  import Line from '../geoms/Line.svelte'
4
4
 
5
5
  type Row = Record<string, unknown>
6
+ type Method = string | number | { type: string; [k: string]: unknown }
6
7
  type Format = (v: unknown) => string
7
8
 
8
9
  type Props = {
@@ -18,13 +19,18 @@
18
19
  width?: number
19
20
  height?: number
20
21
  mode?: 'light' | 'dark'
21
- grid?: boolean
22
+ grid?: boolean | 'x' | 'y' | 'both'
22
23
  legend?: boolean
24
+ highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean)
25
+ trend?: Method | Method[]
23
26
  xFormat?: Format
24
27
  yFormat?: Format
25
28
  xTicks?: number
26
29
  yTicks?: number
27
30
  minorTicks?: boolean
31
+ onselect?: (detail: unknown) => void
32
+ selectable?: boolean
33
+ selected?: Row[]
28
34
  }
29
35
 
30
36
  let {
@@ -39,17 +45,42 @@
39
45
  tooltip = false,
40
46
  width = 600,
41
47
  height = 400,
42
- mode = 'light',
48
+ mode = undefined,
43
49
  grid = true,
44
50
  legend = false,
51
+ highlight = undefined,
52
+ trend = undefined,
45
53
  xFormat = undefined,
46
54
  yFormat = undefined,
47
55
  xTicks = undefined,
48
56
  yTicks = undefined,
49
- minorTicks = false
57
+ minorTicks = false,
58
+ onselect = undefined,
59
+ selectable = false,
60
+ selected = $bindable([])
50
61
  }: Props = $props()
51
62
  </script>
52
63
 
53
- <Plot {data} {width} {height} {mode} {grid} {legend} {tooltip} {xFormat} {yFormat} {xTicks} {yTicks} {minorTicks}>
64
+ <Plot
65
+ {data}
66
+ {x}
67
+ {y}
68
+ {width}
69
+ {height}
70
+ {mode}
71
+ {grid}
72
+ {legend}
73
+ {tooltip}
74
+ {highlight}
75
+ {trend}
76
+ {xFormat}
77
+ {yFormat}
78
+ {xTicks}
79
+ {yTicks}
80
+ {minorTicks}
81
+ {onselect}
82
+ {selectable}
83
+ bind:selected
84
+ >
54
85
  <Line {x} {y} {color} {symbol} {label} {stat} options={{ curve }} />
55
86
  </Plot>
@@ -1,4 +1,8 @@
1
1
  type Row = Record<string, unknown>;
2
+ type Method = string | number | {
3
+ type: string;
4
+ [k: string]: unknown;
5
+ };
2
6
  type Format = (v: unknown) => string;
3
7
  type Props = {
4
8
  data?: Row[];
@@ -13,14 +17,19 @@ type Props = {
13
17
  width?: number;
14
18
  height?: number;
15
19
  mode?: 'light' | 'dark';
16
- grid?: boolean;
20
+ grid?: boolean | 'x' | 'y' | 'both';
17
21
  legend?: boolean;
22
+ highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean);
23
+ trend?: Method | Method[];
18
24
  xFormat?: Format;
19
25
  yFormat?: Format;
20
26
  xTicks?: number;
21
27
  yTicks?: number;
22
28
  minorTicks?: boolean;
29
+ onselect?: (detail: unknown) => void;
30
+ selectable?: boolean;
31
+ selected?: Row[];
23
32
  };
24
- declare const LineChart: import("svelte").Component<Props, {}, "">;
33
+ declare const LineChart: import("svelte").Component<Props, {}, "selected">;
25
34
  type LineChart = ReturnType<typeof LineChart>;
26
35
  export default LineChart;
@@ -31,7 +31,7 @@
31
31
  tooltip = false,
32
32
  width = 400,
33
33
  height = 400,
34
- mode = 'light',
34
+ mode = undefined,
35
35
  legend = false,
36
36
  stat = 'sum'
37
37
  }: Props = $props()
@@ -4,6 +4,7 @@
4
4
 
5
5
  type Row = Record<string, unknown>
6
6
  type Format = (v: unknown) => string
7
+ type Method = string | number | { type: string; [k: string]: unknown }
7
8
 
8
9
  type Props = {
9
10
  data?: Row[]
@@ -20,11 +21,16 @@
20
21
  grid?: boolean
21
22
  legend?: boolean
22
23
  jitter?: { width?: number; height?: number } | null
24
+ highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean)
25
+ trend?: Method | Method[]
23
26
  xFormat?: Format
24
27
  yFormat?: Format
25
28
  xTicks?: number
26
29
  yTicks?: number
27
30
  minorTicks?: boolean
31
+ onselect?: (detail: unknown) => void
32
+ selectable?: boolean
33
+ selected?: Row[]
28
34
  }
29
35
 
30
36
  let {
@@ -38,18 +44,41 @@
38
44
  tooltip = false,
39
45
  width = 600,
40
46
  height = 400,
41
- mode = 'light',
47
+ mode = undefined,
42
48
  grid = true,
43
49
  legend = false,
44
50
  jitter = undefined,
51
+ highlight = undefined,
52
+ trend = undefined,
45
53
  xFormat = undefined,
46
54
  yFormat = undefined,
47
55
  xTicks = undefined,
48
56
  yTicks = undefined,
49
- minorTicks = false
57
+ minorTicks = false,
58
+ onselect = undefined,
59
+ selectable = false,
60
+ selected = $bindable([])
50
61
  }: Props = $props()
51
62
  </script>
52
63
 
53
- <Plot {data} {width} {height} {mode} {grid} {legend} {tooltip} {xFormat} {yFormat} {xTicks} {yTicks} {minorTicks}>
64
+ <Plot
65
+ {data}
66
+ {width}
67
+ {height}
68
+ {mode}
69
+ {grid}
70
+ {legend}
71
+ {tooltip}
72
+ {highlight}
73
+ {trend}
74
+ {xFormat}
75
+ {yFormat}
76
+ {xTicks}
77
+ {yTicks}
78
+ {minorTicks}
79
+ {onselect}
80
+ {selectable}
81
+ bind:selected
82
+ >
54
83
  <Point {x} {y} {color} {size} {symbol} {label} options={{ jitter }} />
55
84
  </Plot>
@@ -1,5 +1,9 @@
1
1
  type Row = Record<string, unknown>;
2
2
  type Format = (v: unknown) => string;
3
+ type Method = string | number | {
4
+ type: string;
5
+ [k: string]: unknown;
6
+ };
3
7
  type Props = {
4
8
  data?: Row[];
5
9
  x?: string;
@@ -18,12 +22,17 @@ type Props = {
18
22
  width?: number;
19
23
  height?: number;
20
24
  } | null;
25
+ highlight?: 'first' | 'last' | 'min' | 'max' | number | ((row: Row, i: number) => boolean);
26
+ trend?: Method | Method[];
21
27
  xFormat?: Format;
22
28
  yFormat?: Format;
23
29
  xTicks?: number;
24
30
  yTicks?: number;
25
31
  minorTicks?: boolean;
32
+ onselect?: (detail: unknown) => void;
33
+ selectable?: boolean;
34
+ selected?: Row[];
26
35
  };
27
- declare const ScatterPlot: import("svelte").Component<Props, {}, "">;
36
+ declare const ScatterPlot: import("svelte").Component<Props, {}, "selected">;
28
37
  type ScatterPlot = ReturnType<typeof ScatterPlot>;
29
38
  export default ScatterPlot;
@@ -14,6 +14,7 @@
14
14
  mode?: 'light' | 'dark'
15
15
  grid?: boolean
16
16
  legend?: boolean
17
+ tooltip?: boolean | ((data: Row) => string)
17
18
  yFormat?: (v: unknown) => string
18
19
  yTicks?: number
19
20
  minorTicks?: boolean
@@ -26,15 +27,16 @@
26
27
  fill = undefined,
27
28
  width = 600,
28
29
  height = 400,
29
- mode = 'light',
30
+ mode = undefined,
30
31
  grid = true,
31
32
  legend = false,
33
+ tooltip = false,
32
34
  yFormat = undefined,
33
35
  yTicks = undefined,
34
36
  minorTicks = false
35
37
  }: Props = $props()
36
38
  </script>
37
39
 
38
- <Plot {data} {width} {height} {mode} {grid} {legend} {yFormat} {yTicks} {minorTicks}>
40
+ <Plot {data} {width} {height} {mode} {grid} {legend} {tooltip} {yFormat} {yTicks} {minorTicks}>
39
41
  <Violin {x} {y} {fill} />
40
42
  </Plot>
@@ -9,6 +9,7 @@ type Props = {
9
9
  mode?: 'light' | 'dark';
10
10
  grid?: boolean;
11
11
  legend?: boolean;
12
+ tooltip?: boolean | ((data: Row) => string);
12
13
  yFormat?: (v: unknown) => string;
13
14
  yTicks?: number;
14
15
  minorTicks?: boolean;
@@ -36,7 +36,7 @@
36
36
  <Plot.Grid />
37
37
  <Plot.Axis type="x" label="Product Category" />
38
38
  <Plot.Axis type="y" label="Number of Products" />
39
- <Plot.Bar {data} x="category" y="count" fill="category" />
39
+ <Plot.Bar x="category" y="count" fill="category" />
40
40
  <Plot.Legend labels={{ category: 'Categories' }} />
41
41
  </Plot.Root>
42
42
  </div>
@@ -65,14 +65,15 @@
65
65
  // this derived runs first with stale data whose rows don't have the new
66
66
  // field — causing all keys to be undefined (duplicate key error).
67
67
  if (fillField && !(fillField in data[0])) return []
68
- const innerRadius = ((options.innerRadius ?? 0) * Math.min(w, h)) / 2
68
+ // innerRadius passed through raw — buildArcs interprets it (a fraction of the
69
+ // radius when <= 1, absolute pixels when > 1) and clamps it below the outer radius.
69
70
  return buildArcs(
70
71
  data,
71
72
  { color: fillField, y: theta, pattern },
72
73
  colors,
73
74
  w,
74
75
  h,
75
- { innerRadius },
76
+ { innerRadius: options.innerRadius ?? 0 },
76
77
  patterns
77
78
  )
78
79
  })
@@ -2,6 +2,9 @@
2
2
  import { getContext, onMount, onDestroy } from 'svelte'
3
3
  import type { PlotState } from '../PlotState.svelte.js'
4
4
  import { buildAreas, buildStackedAreas } from './lib/areas.js'
5
+ import { buildSelectDetail } from '../lib/select.js'
6
+
7
+ type Row = Record<string, unknown>
5
8
 
6
9
  type Options = {
7
10
  stack?: boolean
@@ -23,6 +26,13 @@
23
26
  const plotState = getContext<PlotState>('plot-state')
24
27
  let id = $state<string | null>(null)
25
28
 
29
+ function selectPoint(d: Row, event: MouseEvent | KeyboardEvent) {
30
+ if (plotState.interactive)
31
+ plotState.handleSelect(
32
+ buildSelectDetail(d, plotState.data.indexOf(d), { x, y }, 'area', color ? d[color] : undefined, event)
33
+ )
34
+ }
35
+
26
36
  onMount(() => {
27
37
  id = plotState.registerGeom({
28
38
  type: 'area',
@@ -54,9 +64,9 @@
54
64
  if (!data?.length || !xScale || !yScale || !x || !y) return []
55
65
  const channels = { x, y, color, pattern }
56
66
  if (options.stack) {
57
- return buildStackedAreas(data, channels, xScale, yScale, colors, options.curve, patterns)
67
+ return buildStackedAreas(data, channels, xScale, yScale, colors, options.curve, patterns, plotState.place.bind(plotState))
58
68
  }
59
- return buildAreas(data, channels, xScale, yScale, colors, options.curve, patterns)
69
+ return buildAreas(data, channels, xScale, yScale, colors, options.curve, patterns, plotState.place.bind(plotState))
60
70
  })
61
71
  </script>
62
72
 
@@ -79,16 +89,23 @@
79
89
  {#each data as d, i (`hover::${i}`)}
80
90
  {@const px = typeof xScale?.bandwidth === 'function' ? (xScale(d[x]) ?? 0) + xScale.bandwidth() / 2 : (xScale?.(d[x]) ?? 0)}
81
91
  {@const py = yScale?.(d[y]) ?? 0}
92
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
82
93
  <circle
83
94
  cx={px}
84
95
  cy={py}
85
96
  r="8"
86
97
  fill="transparent"
87
98
  stroke="none"
88
- role="presentation"
99
+ role={plotState.interactive ? 'button' : 'presentation'}
100
+ tabindex={plotState.interactive ? 0 : undefined}
101
+ style:cursor={plotState.interactive ? 'pointer' : undefined}
89
102
  data-plot-element="area-hover"
90
103
  onmouseenter={() => plotState.setHovered(d)}
91
104
  onmouseleave={() => plotState.clearHovered()}
105
+ onclick={plotState.interactive ? (e) => selectPoint(d, e) : undefined}
106
+ onkeydown={plotState.interactive
107
+ ? (e) => (e.key === 'Enter' || e.key === ' ') && selectPoint(d, e)
108
+ : undefined}
92
109
  />
93
110
  {/each}
94
111
  {/if}
@@ -2,8 +2,9 @@
2
2
  import { getContext, onMount, onDestroy } from 'svelte'
3
3
  import type { PlotState } from '../PlotState.svelte.js'
4
4
  import type { createCrossFilter } from '../crossfilter/createCrossFilter.svelte.js'
5
- import { buildGroupedBars, buildStackedBars, buildHorizontalBars } from './lib/bars.js'
5
+ import { buildBars, buildStackedBars } from './lib/bars.js'
6
6
  import { keyboardNav } from '../lib/keyboard-nav.js'
7
+ import { buildSelectDetail } from '../lib/select.js'
7
8
  import LabelPill from './LabelPill.svelte'
8
9
 
9
10
  type Row = Record<string, unknown>
@@ -101,13 +102,24 @@
101
102
  const bars = $derived.by(() => {
102
103
  if (!data?.length || !xScale || !yScale) return []
103
104
  const channels = { x, y, color: colorChannel, pattern }
104
- if (effectiveOrientation === 'horizontal') {
105
- return buildHorizontalBars(data, channels, xScale, yScale, colors, innerHeight)
106
- }
105
+ // One builder, one path: express bars in (category, value) space and map to screen via
106
+ // place() (identity vertical, swap when flipped). `continuousCategory` keeps a linear
107
+ // category axis (the bar-chart race's tweened rank) so it positions/tweens smoothly.
108
+ const place = plotState.place.bind(plotState)
107
109
  if (options.stack) {
108
- return buildStackedBars(data, channels, xScale, yScale, colors, innerHeight, patterns)
110
+ return buildStackedBars(data, channels, xScale, yScale, colors, innerHeight, patterns, place)
109
111
  }
110
- return buildGroupedBars(data, channels, xScale, yScale, colors, innerHeight, patterns)
112
+ return buildBars(
113
+ data,
114
+ channels,
115
+ xScale,
116
+ yScale,
117
+ colors,
118
+ innerHeight,
119
+ patterns,
120
+ place,
121
+ plotState.continuousCategory
122
+ )
111
123
  })
112
124
 
113
125
  let dimmedByKey = $state<Record<string, boolean>>({})
@@ -134,6 +146,36 @@
134
146
  if (!filterable || !x || !cf) return
135
147
  cf.toggleCategorical(x, barX)
136
148
  }
149
+
150
+ function selectBar(bar: { data: Row }, event: MouseEvent | KeyboardEvent) {
151
+ if (plotState.interactive)
152
+ plotState.handleSelect(
153
+ buildSelectDetail(
154
+ bar.data,
155
+ plotState.data.indexOf(bar.data),
156
+ { x, y },
157
+ 'bar',
158
+ colorChannel ? bar.data[colorChannel] : undefined,
159
+ event
160
+ )
161
+ )
162
+ }
163
+
164
+ function handleBarActivate(bar: { data: Row }, event: MouseEvent) {
165
+ if (filterable && x) handleBarClick(bar.data[x])
166
+ onselect?.(bar.data)
167
+ selectBar(bar, event)
168
+ }
169
+
170
+ function handleBarKeyActivate(bar: { data: Row }, event: KeyboardEvent) {
171
+ if (event.key !== 'Enter' && event.key !== ' ') return
172
+ if (filterable && x) {
173
+ handleBarClick(bar.data[x])
174
+ } else {
175
+ onselect?.(bar.data)
176
+ }
177
+ selectBar(bar, event)
178
+ }
137
179
  </script>
138
180
 
139
181
  {#if bars.length > 0}
@@ -152,13 +194,15 @@
152
194
  data-plot-value={bar.data[y ?? '']}
153
195
  data-plot-category={bar.data[x ?? '']}
154
196
  data-dimmed={dimmedByKey[bar.key] ? true : undefined}
155
- style:cursor={filterable || onselect ? 'pointer' : undefined}
156
- onclick={filterable && x ? () => { handleBarClick(bar.data[x]); onselect?.(bar.data) } : onselect ? () => onselect(bar.data) : undefined}
157
- onkeydown={filterable && x
158
- ? (e) => (e.key === 'Enter' || e.key === ' ') && handleBarClick(bar.data[x])
159
- : onselect ? (e) => (e.key === 'Enter' || e.key === ' ') && onselect(bar.data) : undefined}
160
- role={filterable || onselect || keyboard ? 'button' : 'graphics-symbol'}
161
- tabindex={filterable || onselect || keyboard ? 0 : undefined}
197
+ style:cursor={filterable || onselect || plotState.interactive ? 'pointer' : undefined}
198
+ onclick={filterable || onselect || keyboard || plotState.interactive
199
+ ? (e) => handleBarActivate(bar, e)
200
+ : undefined}
201
+ onkeydown={filterable || onselect || keyboard || plotState.interactive
202
+ ? (e) => handleBarKeyActivate(bar, e)
203
+ : undefined}
204
+ role={filterable || onselect || keyboard || plotState.interactive ? 'button' : 'graphics-symbol'}
205
+ tabindex={filterable || onselect || keyboard || plotState.interactive ? 0 : undefined}
162
206
  use:keyboardNav={keyboard}
163
207
  aria-label="{bar.data[x ?? '']}: {bar.data[y ?? '']}"
164
208
  onmouseenter={() => plotState.setHovered(bar.data)}