@rokkit/chart 1.3.8 → 1.3.9

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 (130) hide show
  1. package/dist/Chart.svelte +50 -64
  2. package/dist/Plot/Root.svelte +4 -1
  3. package/dist/Plot/Root.svelte.d.ts +2 -0
  4. package/dist/Plot/index.d.ts +1 -1
  5. package/dist/Plot/index.js +1 -1
  6. package/dist/PlotState.svelte.d.ts +14 -2
  7. package/dist/PlotState.svelte.js +46 -4
  8. package/dist/charts/AreaChart.svelte +8 -1
  9. package/dist/charts/AreaChart.svelte.d.ts +5 -0
  10. package/dist/charts/BarChart.svelte +8 -1
  11. package/dist/charts/BarChart.svelte.d.ts +5 -0
  12. package/dist/charts/BoxPlot.svelte +3 -1
  13. package/dist/charts/BoxPlot.svelte.d.ts +1 -0
  14. package/dist/charts/BubbleChart.svelte +3 -1
  15. package/dist/charts/BubbleChart.svelte.d.ts +1 -0
  16. package/dist/charts/LineChart.svelte +3 -1
  17. package/dist/charts/LineChart.svelte.d.ts +1 -0
  18. package/dist/charts/PieChart.svelte +4 -2
  19. package/dist/charts/PieChart.svelte.d.ts +1 -0
  20. package/dist/charts/ScatterPlot.svelte +3 -1
  21. package/dist/charts/ScatterPlot.svelte.d.ts +1 -0
  22. package/dist/charts/ViolinPlot.svelte +3 -1
  23. package/dist/charts/ViolinPlot.svelte.d.ts +1 -0
  24. package/dist/geoms/Arc.svelte +21 -55
  25. package/dist/geoms/Arc.svelte.d.ts +4 -4
  26. package/dist/geoms/Area.svelte +45 -44
  27. package/dist/geoms/Area.svelte.d.ts +8 -1
  28. package/dist/geoms/Bar.svelte +33 -56
  29. package/dist/geoms/Bar.svelte.d.ts +9 -0
  30. package/dist/geoms/Box.svelte +118 -131
  31. package/dist/geoms/Box.svelte.d.ts +5 -3
  32. package/dist/geoms/Candlestick.svelte +22 -77
  33. package/dist/geoms/Candlestick.svelte.d.ts +2 -0
  34. package/dist/geoms/Heatmap.svelte +21 -66
  35. package/dist/geoms/Heatmap.svelte.d.ts +2 -0
  36. package/dist/geoms/Hexbin.svelte +20 -100
  37. package/dist/geoms/Hexbin.svelte.d.ts +2 -0
  38. package/dist/geoms/Jitter.svelte +21 -36
  39. package/dist/geoms/Jitter.svelte.d.ts +5 -3
  40. package/dist/geoms/Line.svelte +27 -31
  41. package/dist/geoms/Line.svelte.d.ts +5 -0
  42. package/dist/geoms/Point.svelte +31 -66
  43. package/dist/geoms/Point.svelte.d.ts +5 -1
  44. package/dist/geoms/Ribbon.svelte +30 -124
  45. package/dist/geoms/Ribbon.svelte.d.ts +2 -0
  46. package/dist/geoms/Rule.svelte +5 -1
  47. package/dist/geoms/Rule.svelte.d.ts +3 -0
  48. package/dist/geoms/Violin.svelte +21 -40
  49. package/dist/geoms/Violin.svelte.d.ts +5 -3
  50. package/dist/geoms/Waterfall.svelte +19 -80
  51. package/dist/geoms/Waterfall.svelte.d.ts +2 -0
  52. package/dist/geoms/lib/GeomState.svelte.d.ts +51 -0
  53. package/dist/geoms/lib/GeomState.svelte.js +79 -0
  54. package/dist/geoms/lib/aesthetics.d.ts +36 -0
  55. package/dist/geoms/lib/aesthetics.js +45 -0
  56. package/dist/geoms/lib/areas.d.ts +1 -1
  57. package/dist/geoms/lib/areas.js +4 -2
  58. package/dist/geoms/lib/bars.d.ts +2 -2
  59. package/dist/geoms/lib/bars.js +13 -8
  60. package/dist/geoms/lib/marks/arc.d.ts +24 -0
  61. package/dist/geoms/lib/marks/arc.js +28 -0
  62. package/dist/geoms/lib/marks/area.d.ts +23 -0
  63. package/dist/geoms/lib/marks/area.js +38 -0
  64. package/dist/geoms/lib/marks/bar.d.ts +16 -0
  65. package/dist/geoms/lib/marks/bar.js +43 -0
  66. package/dist/geoms/lib/marks/box.d.ts +14 -0
  67. package/dist/geoms/lib/marks/box.js +30 -0
  68. package/dist/geoms/lib/marks/candlestick.d.ts +27 -0
  69. package/dist/geoms/lib/marks/candlestick.js +52 -0
  70. package/dist/geoms/lib/marks/heatmap.d.ts +24 -0
  71. package/dist/geoms/lib/marks/heatmap.js +41 -0
  72. package/dist/geoms/lib/marks/hexbin.d.ts +29 -0
  73. package/dist/geoms/lib/marks/hexbin.js +62 -0
  74. package/dist/geoms/lib/marks/jitter.d.ts +21 -0
  75. package/dist/geoms/lib/marks/jitter.js +36 -0
  76. package/dist/geoms/lib/marks/line.d.ts +26 -0
  77. package/dist/geoms/lib/marks/line.js +25 -0
  78. package/dist/geoms/lib/marks/point.d.ts +25 -0
  79. package/dist/geoms/lib/marks/point.js +58 -0
  80. package/dist/geoms/lib/marks/ribbon.d.ts +31 -0
  81. package/dist/geoms/lib/marks/ribbon.js +77 -0
  82. package/dist/geoms/lib/marks/violin.d.ts +13 -0
  83. package/dist/geoms/lib/marks/violin.js +31 -0
  84. package/dist/geoms/lib/marks/waterfall.d.ts +25 -0
  85. package/dist/geoms/lib/marks/waterfall.js +58 -0
  86. package/dist/index.d.ts +1 -8
  87. package/dist/index.js +1 -8
  88. package/dist/lib/preset.d.ts +11 -1
  89. package/dist/lib/preset.js +16 -4
  90. package/package.json +1 -1
  91. package/dist/Plot/Arc.svelte +0 -30
  92. package/dist/Plot/Arc.svelte.d.ts +0 -18
  93. package/dist/lib/brewer.d.ts +0 -9
  94. package/dist/lib/brewer.js +0 -25
  95. package/dist/lib/brewing/BoxBrewer.svelte.d.ts +0 -8
  96. package/dist/lib/brewing/BoxBrewer.svelte.js +0 -14
  97. package/dist/lib/brewing/CartesianBrewer.svelte.d.ts +0 -8
  98. package/dist/lib/brewing/CartesianBrewer.svelte.js +0 -21
  99. package/dist/lib/brewing/PieBrewer.svelte.d.ts +0 -8
  100. package/dist/lib/brewing/PieBrewer.svelte.js +0 -14
  101. package/dist/lib/brewing/QuartileBrewer.svelte.d.ts +0 -9
  102. package/dist/lib/brewing/QuartileBrewer.svelte.js +0 -51
  103. package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +0 -8
  104. package/dist/lib/brewing/ViolinBrewer.svelte.js +0 -14
  105. package/dist/lib/brewing/axes.svelte.d.ts +0 -66
  106. package/dist/lib/brewing/axes.svelte.js +0 -270
  107. package/dist/lib/brewing/bars.svelte.d.ts +0 -56
  108. package/dist/lib/brewing/bars.svelte.js +0 -201
  109. package/dist/lib/brewing/brewer.svelte.d.ts +0 -124
  110. package/dist/lib/brewing/brewer.svelte.js +0 -277
  111. package/dist/lib/brewing/dimensions.svelte.d.ts +0 -35
  112. package/dist/lib/brewing/dimensions.svelte.js +0 -56
  113. package/dist/lib/brewing/index.svelte.d.ts +0 -118
  114. package/dist/lib/brewing/index.svelte.js +0 -205
  115. package/dist/lib/brewing/legends.svelte.d.ts +0 -48
  116. package/dist/lib/brewing/legends.svelte.js +0 -137
  117. package/dist/lib/brewing/marks/areas.d.ts +0 -31
  118. package/dist/lib/brewing/marks/areas.js +0 -72
  119. package/dist/lib/brewing/marks/bars.d.ts +0 -1
  120. package/dist/lib/brewing/marks/bars.js +0 -49
  121. package/dist/lib/brewing/scales.svelte.d.ts +0 -24
  122. package/dist/lib/brewing/scales.svelte.js +0 -82
  123. package/dist/lib/context.d.ts +0 -13
  124. package/dist/lib/context.js +0 -131
  125. package/dist/lib/plot/chartProps.d.ts +0 -177
  126. package/dist/lib/plot/chartProps.js +0 -76
  127. package/dist/lib/scales.svelte.d.ts +0 -35
  128. package/dist/lib/scales.svelte.js +0 -151
  129. package/dist/lib/ticks.d.ts +0 -36
  130. package/dist/lib/ticks.js +0 -46
@@ -1,19 +1,20 @@
1
1
  <script lang="ts">
2
2
  import { getContext, onMount, onDestroy } from 'svelte'
3
3
  import type { PlotState } from '../PlotState.svelte.js'
4
- import { buildArcs } from '../lib/brewing/marks/arcs.js'
4
+ import { GeomState } from './lib/GeomState.svelte.js'
5
+ import { buildArcMarks } from './lib/marks/arc.js'
5
6
 
6
7
  type Row = Record<string, unknown>
7
8
 
8
- /**
9
- * `fill` is the primary prop name; `color` is accepted as an alias for
10
- * spec-driven usage (Plot.svelte passes `color` to all geoms generically).
11
- */
12
9
  type Props = {
13
10
  theta?: string
11
+ /** Slice interior aesthetic (field). */
14
12
  fill?: string
13
+ /** Accepted as an alias for `fill`. */
15
14
  color?: string
16
15
  pattern?: string
16
+ /** Fixed slice opacity 0–1; defaults to the per-geom preset (arc = 1). */
17
+ alpha?: number
17
18
  stat?: string
18
19
  labelFn?: (data: Row) => string
19
20
  options?: { innerRadius?: number }
@@ -25,58 +26,30 @@
25
26
  fill,
26
27
  color,
27
28
  pattern,
29
+ alpha,
28
30
  labelFn = undefined,
29
31
  stat = 'identity',
30
32
  options = {},
31
33
  onselect = undefined
32
34
  }: Props = $props()
33
35
 
34
- const fillField = $derived(fill ?? color)
35
-
36
36
  const plotState = getContext<PlotState>('plot-state')
37
- let id = $state<string | null>(null)
38
-
39
- onMount(() => {
40
- id = plotState.registerGeom({
41
- type: 'arc',
42
- channels: { color: fillField, y: theta, pattern },
43
- stat,
44
- options
45
- })
46
- })
47
- onDestroy(() => {
48
- if (id) plotState.unregisterGeom(id)
49
- })
50
37
 
51
- $effect(() => {
52
- if (id) plotState.updateGeom(id, { channels: { color: fillField, y: theta, pattern }, stat })
53
- })
38
+ const geom = new GeomState(plotState, () => ({
39
+ type: 'arc',
40
+ channels: { y: theta, color, fill, pattern },
41
+ stat,
42
+ options: { innerRadius: options.innerRadius ?? 0 },
43
+ alpha,
44
+ build: buildArcMarks
45
+ }))
46
+ onMount(geom.register)
47
+ onDestroy(geom.destroy)
48
+ $effect(geom.sync)
54
49
 
55
- const data = $derived(id ? plotState.geomData(id) : [])
56
- const colors = $derived(plotState.colors)
57
- const patterns = $derived(plotState.patterns)
50
+ const arcs = $derived(geom.marks)
58
51
  const w = $derived(plotState.innerWidth)
59
52
  const h = $derived(plotState.innerHeight)
60
-
61
- const arcs = $derived.by(() => {
62
- if (!data?.length) return []
63
- // Guard: skip until data catches up after a fill-field change.
64
- // When fillField changes, the $effect updates the geom asynchronously, but
65
- // this derived runs first with stale data whose rows don't have the new
66
- // field — causing all keys to be undefined (duplicate key error).
67
- if (fillField && !(fillField in data[0])) return []
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.
70
- return buildArcs(
71
- data,
72
- { color: fillField, y: theta, pattern },
73
- colors,
74
- w,
75
- h,
76
- { innerRadius: options.innerRadius ?? 0 },
77
- patterns
78
- )
79
- })
80
53
  </script>
81
54
 
82
55
  {#if arcs.length > 0}
@@ -86,6 +59,7 @@
86
59
  <path
87
60
  d={arc.d}
88
61
  fill={arc.fill}
62
+ fill-opacity={arc.alpha}
89
63
  stroke={arc.stroke}
90
64
  stroke-width="1"
91
65
  role={onselect ? 'button' : 'presentation'}
@@ -116,15 +90,7 @@
116
90
  pointer-events="none"
117
91
  data-plot-element="arc-label"
118
92
  >
119
- <rect
120
- x={-lw / 2}
121
- y="-9"
122
- width={lw}
123
- height="18"
124
- rx="4"
125
- fill="white"
126
- fill-opacity="0.82"
127
- />
93
+ <rect x={-lw / 2} y="-9" width={lw} height="18" rx="4" fill="white" fill-opacity="0.82" />
128
94
  <text
129
95
  text-anchor="middle"
130
96
  dominant-baseline="central"
@@ -1,13 +1,13 @@
1
1
  type Row = Record<string, unknown>;
2
- /**
3
- * `fill` is the primary prop name; `color` is accepted as an alias for
4
- * spec-driven usage (Plot.svelte passes `color` to all geoms generically).
5
- */
6
2
  type Props = {
7
3
  theta?: string;
4
+ /** Slice interior aesthetic (field). */
8
5
  fill?: string;
6
+ /** Accepted as an alias for `fill`. */
9
7
  color?: string;
10
8
  pattern?: string;
9
+ /** Fixed slice opacity 0–1; defaults to the per-geom preset (arc = 1). */
10
+ alpha?: number;
11
11
  stat?: string;
12
12
  labelFn?: (data: Row) => string;
13
13
  options?: {
@@ -1,73 +1,74 @@
1
1
  <script lang="ts">
2
2
  import { getContext, onMount, onDestroy } from 'svelte'
3
3
  import type { PlotState } from '../PlotState.svelte.js'
4
- import { buildAreas, buildStackedAreas } from './lib/areas.js'
4
+ import { GeomState } from './lib/GeomState.svelte.js'
5
+ import { buildAreaMarks } from './lib/marks/area.js'
5
6
  import { buildSelectDetail } from '../lib/select.js'
6
7
 
7
8
  type Row = Record<string, unknown>
8
-
9
9
  type Options = {
10
+ /** @deprecated use `position="stack"` — kept as an alias. */
10
11
  stack?: boolean
11
12
  curve?: 'linear' | 'smooth' | 'step'
12
- opacity?: number
13
13
  }
14
14
 
15
15
  type Props = {
16
16
  x?: string
17
17
  y?: string
18
+ /** Border aesthetic (field); a border is drawn only when both fill and color are set. */
18
19
  color?: string
20
+ /** Interior aesthetic (field); falls back to `color`. */
21
+ fill?: string
19
22
  pattern?: string
23
+ /** Multi-series arrangement: 'stack' | 'fill' (100%) | 'identity' (overlap, default). */
24
+ position?: 'stack' | 'fill' | 'identity'
25
+ /** Fixed area opacity 0–1; defaults to the per-geom preset (area = 0.6). */
26
+ alpha?: number
20
27
  stat?: string
21
28
  options?: Options
22
29
  }
23
30
 
24
- let { x, y, color, pattern, stat = 'identity', options = {} }: Props = $props()
31
+ let { x, y, color, fill, pattern, position, alpha, stat = 'identity', options = {} }: Props =
32
+ $props()
33
+
34
+ // `options.stack: true` is the back-compat alias for position='stack'.
35
+ const resolvedPosition = $derived(position ?? (options?.stack ? 'stack' : 'identity'))
25
36
 
26
37
  const plotState = getContext<PlotState>('plot-state')
27
- let id = $state<string | null>(null)
38
+
39
+ const geom = new GeomState(plotState, () => ({
40
+ type: 'area',
41
+ channels: { x, y, color, fill, pattern },
42
+ stat,
43
+ options: { stack: options?.stack ?? false, position: resolvedPosition, curve: options?.curve },
44
+ alpha,
45
+ build: buildAreaMarks
46
+ }))
47
+ onMount(geom.register)
48
+ onDestroy(geom.destroy)
49
+ $effect(geom.sync)
50
+
51
+ const areas = $derived(geom.marks)
52
+ // Per-row tooltip hit-circles are rendered from the geom's rows + shared scales.
53
+ const rows = $derived(geom.data)
54
+ const xScale = $derived(plotState.xScale)
55
+ const yScale = $derived(plotState.yScale)
56
+
57
+ const seriesField = $derived(fill ?? color)
28
58
 
29
59
  function selectPoint(d: Row, event: MouseEvent | KeyboardEvent) {
30
60
  if (plotState.interactive)
31
61
  plotState.handleSelect(
32
- buildSelectDetail(d, plotState.data.indexOf(d), { x, y }, 'area', color ? d[color] : undefined, event)
62
+ buildSelectDetail(
63
+ d,
64
+ plotState.data.indexOf(d),
65
+ { x, y },
66
+ 'area',
67
+ seriesField ? d[seriesField] : undefined,
68
+ event
69
+ )
33
70
  )
34
71
  }
35
-
36
- onMount(() => {
37
- id = plotState.registerGeom({
38
- type: 'area',
39
- channels: { x, y, color, pattern },
40
- stat,
41
- options: { stack: options?.stack ?? false }
42
- })
43
- })
44
- onDestroy(() => {
45
- if (id) plotState.unregisterGeom(id)
46
- })
47
-
48
- $effect(() => {
49
- if (id)
50
- plotState.updateGeom(id, {
51
- channels: { x, y, color, pattern },
52
- stat,
53
- options: { stack: options?.stack ?? false }
54
- })
55
- })
56
-
57
- const data = $derived(id ? plotState.geomData(id) : [])
58
- const xScale = $derived(plotState.xScale)
59
- const yScale = $derived(plotState.yScale)
60
- const colors = $derived(plotState.colors)
61
- const patterns = $derived(plotState.patterns)
62
-
63
- const areas = $derived.by(() => {
64
- if (!data?.length || !xScale || !yScale || !x || !y) return []
65
- const channels = { x, y, color, pattern }
66
- if (options.stack) {
67
- return buildStackedAreas(data, channels, xScale, yScale, colors, options.curve, patterns, plotState.place.bind(plotState))
68
- }
69
- return buildAreas(data, channels, xScale, yScale, colors, options.curve, patterns, plotState.place.bind(plotState))
70
- })
71
72
  </script>
72
73
 
73
74
  {#if areas.length > 0}
@@ -76,7 +77,7 @@
76
77
  <path
77
78
  d={seg.d}
78
79
  fill={seg.fill}
79
- fill-opacity={seg.patternId ? 1 : (options.opacity ?? plotState.chartPreset.opacity.area)}
80
+ fill-opacity={seg.patternId ? 1 : seg.alpha}
80
81
  stroke={seg.stroke ?? 'none'}
81
82
  data-plot-element="area"
82
83
  />
@@ -86,7 +87,7 @@
86
87
  {/each}
87
88
  <!-- Invisible hit circles for tooltip: one per data point -->
88
89
  {#if x && y}
89
- {#each data as d, i (`hover::${i}`)}
90
+ {#each rows as d, i (`hover::${i}`)}
90
91
  {@const px = typeof xScale?.bandwidth === 'function' ? (xScale(d[x]) ?? 0) + xScale.bandwidth() / 2 : (xScale?.(d[x]) ?? 0)}
91
92
  {@const py = yScale?.(d[y]) ?? 0}
92
93
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
@@ -1,13 +1,20 @@
1
1
  type Options = {
2
+ /** @deprecated use `position="stack"` — kept as an alias. */
2
3
  stack?: boolean;
3
4
  curve?: 'linear' | 'smooth' | 'step';
4
- opacity?: number;
5
5
  };
6
6
  type Props = {
7
7
  x?: string;
8
8
  y?: string;
9
+ /** Border aesthetic (field); a border is drawn only when both fill and color are set. */
9
10
  color?: string;
11
+ /** Interior aesthetic (field); falls back to `color`. */
12
+ fill?: string;
10
13
  pattern?: string;
14
+ /** Multi-series arrangement: 'stack' | 'fill' (100%) | 'identity' (overlap, default). */
15
+ position?: 'stack' | 'fill' | 'identity';
16
+ /** Fixed area opacity 0–1; defaults to the per-geom preset (area = 0.6). */
17
+ alpha?: number;
11
18
  stat?: string;
12
19
  options?: Options;
13
20
  };
@@ -2,7 +2,8 @@
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 { buildBars, buildStackedBars } from './lib/bars.js'
5
+ import { GeomState } from './lib/GeomState.svelte.js'
6
+ import { buildBarMarks } from './lib/marks/bar.js'
6
7
  import { keyboardNav } from '../lib/keyboard-nav.js'
7
8
  import { buildSelectDetail } from '../lib/select.js'
8
9
  import LabelPill from './LabelPill.svelte'
@@ -10,6 +11,7 @@
10
11
  type Row = Record<string, unknown>
11
12
  type CrossFilter = ReturnType<typeof createCrossFilter>
12
13
  type Options = {
14
+ /** @deprecated use `position="stack"` — kept as an alias. */
13
15
  stack?: boolean
14
16
  orientation?: 'horizontal' | 'vertical'
15
17
  labelInside?: boolean
@@ -19,9 +21,17 @@
19
21
  type Props = {
20
22
  x?: string
21
23
  y?: string
24
+ /** Border/outline aesthetic (field). */
22
25
  color?: string
26
+ /** Interior aesthetic (field); falls back to `color`. */
23
27
  fill?: string
24
28
  pattern?: string
29
+ /** Multi-series arrangement: 'stack' | 'dodge' (default) | 'fill' (100%) | 'identity' (overlap). */
30
+ position?: 'stack' | 'dodge' | 'fill' | 'identity'
31
+ /** Sub-series field for stack/dodge; defaults to `fill ?? color`. */
32
+ group?: string
33
+ /** Fixed bar opacity 0–1; defaults to the per-geom preset (bar = 1). */
34
+ alpha?: number
25
35
  label?: boolean | string | ((data: Row) => unknown)
26
36
  stat?: string
27
37
  options?: Options
@@ -34,8 +44,11 @@
34
44
  x,
35
45
  y,
36
46
  color,
37
- fill: fillProp,
47
+ fill,
38
48
  pattern,
49
+ position,
50
+ group,
51
+ alpha,
39
52
  label = false,
40
53
  stat = 'identity',
41
54
  options = {},
@@ -44,8 +57,8 @@
44
57
  keyboard = false
45
58
  }: Props = $props()
46
59
 
47
- // `fill` is accepted as an alias for `color` (consistent with Arc.svelte)
48
- const colorChannel = $derived(fillProp ?? color)
60
+ // `options.stack: true` is the back-compat alias for position='stack'.
61
+ const resolvedPosition = $derived(position ?? (options?.stack ? 'stack' : 'dodge'))
49
62
 
50
63
  function resolveLabel(data: Row, defaultField: string | undefined): string | null {
51
64
  if (!label) return null
@@ -68,59 +81,22 @@
68
81
 
69
82
  const plotState = getContext<PlotState>('plot-state')
70
83
  const cf = getContext<CrossFilter | undefined>('crossfilter')
71
- let id = $state<string | null>(null)
72
84
 
73
- onMount(() => {
74
- id = plotState.registerGeom({
75
- type: 'bar',
76
- channels: { x, y, color: colorChannel, pattern },
77
- stat,
78
- options: { stack: options?.stack ?? false }
79
- })
80
- })
81
- onDestroy(() => {
82
- if (id) plotState.unregisterGeom(id)
83
- })
84
-
85
- $effect(() => {
86
- if (id)
87
- plotState.updateGeom(id, {
88
- channels: { x, y, color: colorChannel, pattern },
89
- stat,
90
- options: { stack: options?.stack ?? false }
91
- })
92
- })
93
-
94
- const data = $derived(id ? plotState.geomData(id) : [])
95
- const xScale = $derived(plotState.xScale)
96
- const yScale = $derived(plotState.yScale)
97
- const colors = $derived(plotState.colors)
98
- const patterns = $derived(plotState.patterns)
85
+ const geom = new GeomState(plotState, () => ({
86
+ type: 'bar',
87
+ channels: { x, y, color, fill, pattern, group },
88
+ stat,
89
+ options: { stack: options?.stack ?? false, position: resolvedPosition },
90
+ alpha,
91
+ build: buildBarMarks
92
+ }))
93
+ onMount(geom.register)
94
+ onDestroy(geom.destroy)
95
+ $effect(geom.sync)
96
+
97
+ const bars = $derived(geom.marks)
99
98
  const effectiveOrientation = $derived(options.orientation ?? plotState.orientation)
100
- const innerHeight = $derived(plotState.innerHeight)
101
-
102
- const bars = $derived.by(() => {
103
- if (!data?.length || !xScale || !yScale) return []
104
- const channels = { x, y, color: colorChannel, pattern }
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)
109
- if (options.stack) {
110
- return buildStackedBars(data, channels, xScale, yScale, colors, innerHeight, patterns, place)
111
- }
112
- return buildBars(
113
- data,
114
- channels,
115
- xScale,
116
- yScale,
117
- colors,
118
- innerHeight,
119
- patterns,
120
- place,
121
- plotState.continuousCategory
122
- )
123
- })
99
+ const seriesField = $derived(fill ?? color)
124
100
 
125
101
  let dimmedByKey = $state<Record<string, boolean>>({})
126
102
 
@@ -155,7 +131,7 @@
155
131
  plotState.data.indexOf(bar.data),
156
132
  { x, y },
157
133
  'bar',
158
- colorChannel ? bar.data[colorChannel] : undefined,
134
+ seriesField ? bar.data[seriesField] : undefined,
159
135
  event
160
136
  )
161
137
  )
@@ -188,6 +164,7 @@
188
164
  width={Math.max(0, bar.width)}
189
165
  height={Math.max(0, bar.height)}
190
166
  fill={bar.fill}
167
+ fill-opacity={bar.alpha}
191
168
  stroke={bar.stroke ?? 'none'}
192
169
  stroke-width={bar.stroke ? 0.5 : 0}
193
170
  data-plot-element="bar"
@@ -1,5 +1,6 @@
1
1
  type Row = Record<string, unknown>;
2
2
  type Options = {
3
+ /** @deprecated use `position="stack"` — kept as an alias. */
3
4
  stack?: boolean;
4
5
  orientation?: 'horizontal' | 'vertical';
5
6
  labelInside?: boolean;
@@ -8,9 +9,17 @@ type Options = {
8
9
  type Props = {
9
10
  x?: string;
10
11
  y?: string;
12
+ /** Border/outline aesthetic (field). */
11
13
  color?: string;
14
+ /** Interior aesthetic (field); falls back to `color`. */
12
15
  fill?: string;
13
16
  pattern?: string;
17
+ /** Multi-series arrangement: 'stack' | 'dodge' (default) | 'fill' (100%) | 'identity' (overlap). */
18
+ position?: 'stack' | 'dodge' | 'fill' | 'identity';
19
+ /** Sub-series field for stack/dodge; defaults to `fill ?? color`. */
20
+ group?: string;
21
+ /** Fixed bar opacity 0–1; defaults to the per-geom preset (bar = 1). */
22
+ alpha?: number;
14
23
  label?: boolean | string | ((data: Row) => unknown);
15
24
  stat?: string;
16
25
  options?: Options;