@rokkit/chart 1.0.0-next.15 → 1.0.0-next.150

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 (223) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +150 -46
  3. package/dist/Plot/index.d.ts +9 -0
  4. package/dist/PlotState.svelte.d.ts +47 -0
  5. package/dist/crossfilter/createCrossFilter.svelte.d.ts +15 -0
  6. package/dist/elements/index.d.ts +6 -0
  7. package/dist/geoms/lib/areas.d.ts +52 -0
  8. package/dist/geoms/lib/bars.d.ts +3 -0
  9. package/dist/index.d.ts +51 -0
  10. package/dist/lib/brewer.d.ts +9 -0
  11. package/dist/lib/brewing/BoxBrewer.svelte.d.ts +10 -0
  12. package/dist/lib/brewing/CartesianBrewer.svelte.d.ts +8 -0
  13. package/dist/lib/brewing/PieBrewer.svelte.d.ts +8 -0
  14. package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +9 -0
  15. package/dist/lib/brewing/axes.svelte.d.ts +66 -0
  16. package/dist/lib/brewing/bars.svelte.d.ts +56 -0
  17. package/dist/lib/brewing/brewer.svelte.d.ts +145 -0
  18. package/dist/lib/brewing/colors.d.ts +17 -0
  19. package/dist/lib/brewing/dimensions.svelte.d.ts +35 -0
  20. package/dist/lib/brewing/index.svelte.d.ts +118 -0
  21. package/dist/lib/brewing/legends.svelte.d.ts +48 -0
  22. package/dist/lib/brewing/marks/arcs.d.ts +17 -0
  23. package/dist/lib/brewing/marks/areas.d.ts +31 -0
  24. package/dist/lib/brewing/marks/bars.d.ts +1 -0
  25. package/dist/lib/brewing/marks/boxes.d.ts +24 -0
  26. package/dist/lib/brewing/marks/lines.d.ts +24 -0
  27. package/dist/lib/brewing/marks/points.d.ts +40 -0
  28. package/dist/lib/brewing/marks/violins.d.ts +20 -0
  29. package/dist/lib/brewing/patterns.d.ts +14 -0
  30. package/dist/lib/brewing/scales.d.ts +28 -0
  31. package/dist/lib/brewing/scales.svelte.d.ts +24 -0
  32. package/dist/lib/brewing/stats.d.ts +31 -0
  33. package/dist/lib/brewing/symbols.d.ts +7 -0
  34. package/dist/lib/brewing/types.d.ts +162 -0
  35. package/dist/lib/chart.d.ts +40 -0
  36. package/dist/lib/context.d.ts +13 -0
  37. package/dist/lib/grid.d.ts +72 -0
  38. package/dist/lib/plot/chartProps.d.ts +177 -0
  39. package/dist/lib/plot/crossfilter.d.ts +13 -0
  40. package/dist/lib/plot/facet.d.ts +24 -0
  41. package/dist/lib/plot/frames.d.ts +47 -0
  42. package/dist/lib/plot/helpers.d.ts +3 -0
  43. package/dist/lib/plot/preset.d.ts +29 -0
  44. package/dist/lib/plot/scales.d.ts +5 -0
  45. package/dist/lib/plot/stat.d.ts +32 -0
  46. package/dist/lib/plot/types.d.ts +89 -0
  47. package/dist/lib/scales.svelte.d.ts +35 -0
  48. package/dist/lib/swatch.d.ts +12 -0
  49. package/dist/lib/ticks.d.ts +36 -0
  50. package/dist/lib/utils.d.ts +61 -0
  51. package/dist/lib/xscale.d.ts +11 -0
  52. package/dist/patterns/index.d.ts +4 -0
  53. package/dist/patterns/patterns.d.ts +72 -0
  54. package/dist/patterns/scale.d.ts +30 -0
  55. package/dist/symbols/constants/index.d.ts +1 -0
  56. package/dist/symbols/index.d.ts +5 -0
  57. package/package.json +41 -45
  58. package/src/AnimatedPlot.svelte +214 -0
  59. package/src/Chart.svelte +101 -0
  60. package/src/FacetPlot/Panel.svelte +23 -0
  61. package/src/FacetPlot.svelte +90 -0
  62. package/src/Plot/Arc.svelte +29 -0
  63. package/src/Plot/Area.svelte +25 -0
  64. package/src/Plot/Axis.svelte +73 -0
  65. package/src/Plot/Bar.svelte +96 -0
  66. package/src/Plot/Grid.svelte +30 -0
  67. package/src/Plot/Legend.svelte +167 -0
  68. package/src/Plot/Line.svelte +27 -0
  69. package/src/Plot/Point.svelte +27 -0
  70. package/src/Plot/Root.svelte +107 -0
  71. package/src/Plot/Timeline.svelte +95 -0
  72. package/src/Plot/Tooltip.svelte +81 -0
  73. package/src/Plot/index.js +9 -0
  74. package/src/Plot.svelte +189 -0
  75. package/src/PlotState.svelte.js +278 -0
  76. package/src/Sparkline.svelte +69 -0
  77. package/src/Symbol.svelte +21 -0
  78. package/src/Texture.svelte +18 -0
  79. package/src/charts/AreaChart.svelte +25 -0
  80. package/src/charts/BarChart.svelte +26 -0
  81. package/src/charts/BoxPlot.svelte +21 -0
  82. package/src/charts/BubbleChart.svelte +23 -0
  83. package/src/charts/LineChart.svelte +26 -0
  84. package/src/charts/PieChart.svelte +25 -0
  85. package/src/charts/ScatterPlot.svelte +25 -0
  86. package/src/charts/ViolinPlot.svelte +21 -0
  87. package/src/crossfilter/CrossFilter.svelte +38 -0
  88. package/src/crossfilter/FilterBar.svelte +32 -0
  89. package/src/crossfilter/FilterSlider.svelte +79 -0
  90. package/src/crossfilter/createCrossFilter.svelte.js +113 -0
  91. package/src/elements/Bar.svelte +22 -24
  92. package/src/elements/ColorRamp.svelte +20 -22
  93. package/src/elements/ContinuousLegend.svelte +20 -17
  94. package/src/elements/DefinePatterns.svelte +24 -0
  95. package/src/elements/DiscreteLegend.svelte +15 -15
  96. package/src/elements/Label.svelte +4 -8
  97. package/src/elements/SymbolGrid.svelte +22 -0
  98. package/src/elements/index.js +6 -0
  99. package/src/examples/BarChartExample.svelte +81 -0
  100. package/src/geoms/Arc.svelte +81 -0
  101. package/src/geoms/Area.svelte +50 -0
  102. package/src/geoms/Bar.svelte +142 -0
  103. package/src/geoms/Box.svelte +101 -0
  104. package/src/geoms/LabelPill.svelte +17 -0
  105. package/src/geoms/Line.svelte +100 -0
  106. package/src/geoms/Point.svelte +100 -0
  107. package/src/geoms/Violin.svelte +44 -0
  108. package/src/geoms/lib/areas.js +131 -0
  109. package/src/geoms/lib/bars.js +172 -0
  110. package/src/index.js +67 -16
  111. package/src/lib/brewer.js +25 -0
  112. package/src/lib/brewing/BoxBrewer.svelte.js +56 -0
  113. package/src/lib/brewing/CartesianBrewer.svelte.js +16 -0
  114. package/src/lib/brewing/PieBrewer.svelte.js +14 -0
  115. package/src/lib/brewing/ViolinBrewer.svelte.js +55 -0
  116. package/src/lib/brewing/axes.svelte.js +270 -0
  117. package/src/lib/brewing/bars.svelte.js +201 -0
  118. package/src/lib/brewing/brewer.svelte.js +229 -0
  119. package/src/lib/brewing/colors.js +22 -0
  120. package/src/lib/brewing/dimensions.svelte.js +56 -0
  121. package/src/lib/brewing/index.svelte.js +205 -0
  122. package/src/lib/brewing/legends.svelte.js +137 -0
  123. package/src/lib/brewing/marks/arcs.js +43 -0
  124. package/src/lib/brewing/marks/areas.js +59 -0
  125. package/src/lib/brewing/marks/bars.js +49 -0
  126. package/src/lib/brewing/marks/boxes.js +75 -0
  127. package/src/lib/brewing/marks/lines.js +48 -0
  128. package/src/lib/brewing/marks/points.js +57 -0
  129. package/src/lib/brewing/marks/violins.js +90 -0
  130. package/src/lib/brewing/patterns.js +31 -0
  131. package/src/lib/brewing/scales.js +51 -0
  132. package/src/lib/brewing/scales.svelte.js +82 -0
  133. package/src/lib/brewing/stats.js +62 -0
  134. package/src/lib/brewing/symbols.js +10 -0
  135. package/src/lib/brewing/types.js +73 -0
  136. package/src/lib/chart.js +213 -0
  137. package/src/lib/context.js +131 -0
  138. package/src/lib/grid.js +85 -0
  139. package/src/lib/plot/chartProps.js +76 -0
  140. package/src/lib/plot/crossfilter.js +16 -0
  141. package/src/lib/plot/facet.js +58 -0
  142. package/src/lib/plot/frames.js +80 -0
  143. package/src/lib/plot/helpers.js +14 -0
  144. package/src/lib/plot/preset.js +53 -0
  145. package/src/lib/plot/scales.js +56 -0
  146. package/src/lib/plot/stat.js +92 -0
  147. package/src/lib/plot/types.js +65 -0
  148. package/src/lib/scales.svelte.js +151 -0
  149. package/src/lib/swatch.js +13 -0
  150. package/src/lib/ticks.js +46 -0
  151. package/src/lib/utils.js +111 -118
  152. package/src/lib/xscale.js +31 -0
  153. package/src/patterns/DefinePatterns.svelte +32 -0
  154. package/src/patterns/PatternDef.svelte +27 -0
  155. package/src/patterns/README.md +3 -0
  156. package/src/patterns/index.js +4 -0
  157. package/src/patterns/patterns.js +208 -0
  158. package/src/patterns/scale.js +87 -0
  159. package/src/spec/chart-spec.js +29 -0
  160. package/src/symbols/RoundedSquare.svelte +33 -0
  161. package/src/symbols/Shape.svelte +37 -0
  162. package/src/symbols/constants/index.js +4 -0
  163. package/src/symbols/index.js +9 -0
  164. package/src/symbols/outline.svelte +60 -0
  165. package/src/symbols/solid.svelte +60 -0
  166. package/src/chart/FacetGrid.svelte +0 -51
  167. package/src/chart/Grid.svelte +0 -34
  168. package/src/chart/Legend.svelte +0 -16
  169. package/src/chart/PatternDefs.svelte +0 -13
  170. package/src/chart/Swatch.svelte +0 -93
  171. package/src/chart/SwatchButton.svelte +0 -29
  172. package/src/chart/SwatchGrid.svelte +0 -55
  173. package/src/chart/Symbol.svelte +0 -37
  174. package/src/chart/Texture.svelte +0 -16
  175. package/src/chart/TexturedShape.svelte +0 -27
  176. package/src/chart/TimelapseChart.svelte +0 -97
  177. package/src/chart/Timer.svelte +0 -27
  178. package/src/chart.js +0 -9
  179. package/src/components/charts/Axis.svelte +0 -66
  180. package/src/components/charts/Chart.svelte +0 -35
  181. package/src/components/index.js +0 -23
  182. package/src/components/lib/axis.js +0 -0
  183. package/src/components/lib/chart.js +0 -187
  184. package/src/components/lib/color.js +0 -327
  185. package/src/components/lib/funnel.js +0 -204
  186. package/src/components/lib/index.js +0 -19
  187. package/src/components/lib/pattern.js +0 -190
  188. package/src/components/lib/rollup.js +0 -55
  189. package/src/components/lib/shape.js +0 -199
  190. package/src/components/lib/summary.js +0 -145
  191. package/src/components/lib/theme.js +0 -23
  192. package/src/components/lib/timer.js +0 -41
  193. package/src/components/lib/utils.js +0 -165
  194. package/src/components/plots/BarPlot.svelte +0 -36
  195. package/src/components/plots/BoxPlot.svelte +0 -54
  196. package/src/components/plots/ScatterPlot.svelte +0 -30
  197. package/src/components/store.js +0 -70
  198. package/src/constants.js +0 -66
  199. package/src/elements/PatternDefs.svelte +0 -13
  200. package/src/elements/PatternMask.svelte +0 -20
  201. package/src/elements/Symbol.svelte +0 -38
  202. package/src/elements/Tooltip.svelte +0 -23
  203. package/src/funnel.svelte +0 -35
  204. package/src/geom.js +0 -105
  205. package/src/lib/axis.js +0 -75
  206. package/src/lib/colors.js +0 -32
  207. package/src/lib/geom.js +0 -4
  208. package/src/lib/shapes.js +0 -144
  209. package/src/lib/timer.js +0 -44
  210. package/src/lookup.js +0 -29
  211. package/src/plots/BarPlot.svelte +0 -55
  212. package/src/plots/BoxPlot.svelte +0 -0
  213. package/src/plots/FunnelPlot.svelte +0 -33
  214. package/src/plots/HeatMap.svelte +0 -5
  215. package/src/plots/HeatMapCalendar.svelte +0 -129
  216. package/src/plots/LinePlot.svelte +0 -55
  217. package/src/plots/Plot.svelte +0 -25
  218. package/src/plots/RankBarPlot.svelte +0 -38
  219. package/src/plots/ScatterPlot.svelte +0 -20
  220. package/src/plots/ViolinPlot.svelte +0 -11
  221. package/src/plots/heatmap.js +0 -70
  222. package/src/plots/index.js +0 -10
  223. package/src/swatch.js +0 -11
@@ -0,0 +1,107 @@
1
+ <script>
2
+ import { setContext } from 'svelte'
3
+ import { ChartBrewer } from '../lib/brewing/index.svelte.js'
4
+
5
+ let {
6
+ data = [],
7
+ width = 600,
8
+ height = 400,
9
+ margin = { top: 20, right: 30, bottom: 40, left: 50 },
10
+ fill = null,
11
+ responsive = true,
12
+ animationDuration = 300,
13
+ children
14
+ } = $props()
15
+
16
+ // Create chart brewer instance
17
+ let brewer = $state(new ChartBrewer())
18
+
19
+ $effect(() => {
20
+ brewer.setDimensions({ width, height, margin })
21
+ })
22
+
23
+ // Chart dimensions derived from brewer
24
+ let dimensions = $derived(brewer.getDimensions())
25
+
26
+ // Process data
27
+ $effect(() => {
28
+ // If data has a select method (dataset object), call it to get actual data
29
+ const chartData = data.select && typeof data.select === 'function' ? data.select() : data
30
+
31
+ // Update brewer with data and fields
32
+ brewer.setData(chartData)
33
+ brewer.setFields({ color: fill })
34
+
35
+ // Create scales after setting data
36
+ brewer.createScales()
37
+ })
38
+
39
+ // Provide chart context to child components
40
+ setContext('chart-brewer', brewer)
41
+
42
+ // Handle responsive behavior
43
+ let container
44
+
45
+ $effect(() => {
46
+ if (!responsive || !container || !document) return
47
+
48
+ const resizeObserver = new ResizeObserver((entries) => {
49
+ const entry = entries[0]
50
+ if (!entry) return
51
+
52
+ const containerWidth = entry.contentRect.width
53
+ const aspectRatio = height / width
54
+
55
+ // Update chart dimensions while maintaining aspect ratio
56
+ brewer.setDimensions({
57
+ width: containerWidth,
58
+ height: containerWidth * aspectRatio
59
+ })
60
+
61
+ // Update scales after dimensions change
62
+ brewer.createScales()
63
+ })
64
+
65
+ // Start observing container size
66
+ resizeObserver.observe(container)
67
+
68
+ return () => {
69
+ resizeObserver.disconnect()
70
+ }
71
+ })
72
+ </script>
73
+
74
+ <div class="chart-container" bind:this={container} data-plot-root>
75
+ <svg
76
+ width={dimensions.width}
77
+ height={dimensions.height}
78
+ viewBox="0 0 {dimensions.width} {dimensions.height}"
79
+ role="img"
80
+ aria-label="Chart visualization"
81
+ >
82
+ <g
83
+ class="chart-area"
84
+ transform="translate({dimensions.margin.left}, {dimensions.margin.top})"
85
+ data-plot-canvas
86
+ >
87
+ {@render children?.()}
88
+ </g>
89
+ </svg>
90
+ </div>
91
+
92
+ <style>
93
+ .chart-container {
94
+ position: relative;
95
+ width: 100%;
96
+ height: auto;
97
+ }
98
+
99
+ svg {
100
+ display: block;
101
+ overflow: visible;
102
+ }
103
+
104
+ .chart-area {
105
+ pointer-events: all;
106
+ }
107
+ </style>
@@ -0,0 +1,95 @@
1
+ <script>
2
+ /**
3
+ * @type {{
4
+ * frameKeys: unknown[],
5
+ * currentIndex: number,
6
+ * playing: boolean,
7
+ * speed: number,
8
+ * onplay: () => void,
9
+ * onpause: () => void,
10
+ * onscrub: (index: number) => void,
11
+ * onspeed: (speed: number) => void
12
+ * }}
13
+ */
14
+ let {
15
+ frameKeys = [],
16
+ currentIndex = 0,
17
+ playing = false,
18
+ speed = 1,
19
+ onplay,
20
+ onpause,
21
+ onscrub,
22
+ onspeed
23
+ } = $props()
24
+
25
+ const safeIndex = $derived(
26
+ frameKeys.length === 0 ? 0 : Math.min(currentIndex, frameKeys.length - 1)
27
+ )
28
+
29
+ const SPEEDS = [0.5, 1, 1.5, 2, 4]
30
+ </script>
31
+
32
+ <div class="timeline" data-plot-timeline>
33
+ <!-- Play / Pause -->
34
+ <button
35
+ class="play-pause"
36
+ aria-label={playing ? 'Pause' : 'Play'}
37
+ onclick={() => playing ? onpause?.() : onplay?.()}
38
+ disabled={frameKeys.length === 0}
39
+ data-plot-timeline-playpause
40
+ >
41
+ {playing ? '⏸' : '▶'}
42
+ </button>
43
+
44
+ <!-- Frame label -->
45
+ <span class="frame-label" data-plot-timeline-label>{frameKeys[safeIndex] ?? ''}</span>
46
+
47
+ <!-- Scrub slider -->
48
+ <input
49
+ type="range"
50
+ min="0"
51
+ max={Math.max(0, frameKeys.length - 1)}
52
+ value={safeIndex}
53
+ disabled={frameKeys.length === 0}
54
+ class="scrub"
55
+ aria-label="Animation timeline"
56
+ oninput={(e) => onscrub?.(Number(e.currentTarget.value))}
57
+ data-plot-timeline-scrub
58
+ />
59
+
60
+ <!-- Speed selector -->
61
+ <select
62
+ aria-label="Playback speed"
63
+ value={speed}
64
+ onchange={(e) => onspeed?.(Number(e.currentTarget.value))}
65
+ data-plot-timeline-speed
66
+ >
67
+ {#each SPEEDS as s (s)}
68
+ <option value={s}>{s}×</option>
69
+ {/each}
70
+ </select>
71
+ </div>
72
+
73
+ <style>
74
+ .timeline {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 8px;
78
+ padding: 8px 0;
79
+ font-size: 12px;
80
+ }
81
+ .play-pause {
82
+ font-size: 16px;
83
+ cursor: pointer;
84
+ background: none;
85
+ border: none;
86
+ padding: 0;
87
+ }
88
+ .scrub {
89
+ flex: 1;
90
+ }
91
+ .frame-label {
92
+ min-width: 4ch;
93
+ text-align: right;
94
+ }
95
+ </style>
@@ -0,0 +1,81 @@
1
+ <script>
2
+ import { getContext } from 'svelte'
3
+
4
+ /** @type {{ tooltip?: boolean | ((data: Record<string, unknown>) => string) }} */
5
+ let { tooltip = true } = $props()
6
+
7
+ const plotState = getContext('plot-state')
8
+
9
+ let mouseX = $state(0)
10
+ let mouseY = $state(0)
11
+
12
+ $effect(() => {
13
+ function onMove(e) { mouseX = e.clientX; mouseY = e.clientY }
14
+ window.addEventListener('mousemove', onMove)
15
+ return () => window.removeEventListener('mousemove', onMove)
16
+ })
17
+
18
+ const hovered = $derived(plotState.hovered)
19
+ const mode = $derived(plotState.mode)
20
+
21
+ function formatDefault(data) {
22
+ return Object.entries(data)
23
+ .filter(([, v]) => v !== undefined && v !== null)
24
+ .map(([k, v]) => `<div class="tt-row"><span class="tt-key">${k}</span><span class="tt-val">${v}</span></div>`)
25
+ .join('')
26
+ }
27
+
28
+ const content = $derived.by(() => {
29
+ if (!hovered) return ''
30
+ if (typeof tooltip === 'function') return String(tooltip(hovered) ?? '')
31
+ return formatDefault(hovered)
32
+ })
33
+ </script>
34
+
35
+ {#if hovered && content}
36
+ <div
37
+ class="plot-tooltip"
38
+ data-mode={mode}
39
+ style:left="{mouseX + 14}px"
40
+ style:top="{mouseY - 8}px"
41
+ >
42
+ {@html content}
43
+ </div>
44
+ {/if}
45
+
46
+ <style>
47
+ .plot-tooltip {
48
+ position: fixed;
49
+ pointer-events: none;
50
+ z-index: 1000;
51
+ padding: 8px 10px;
52
+ border-radius: 6px;
53
+ font-size: 12px;
54
+ line-height: 1.5;
55
+ white-space: nowrap;
56
+ box-shadow: 0 4px 12px rgb(0 0 0 / 0.12);
57
+ background: #ffffff;
58
+ border: 1px solid #e2e8f0;
59
+ color: #1e293b;
60
+ }
61
+
62
+ .plot-tooltip[data-mode="dark"] {
63
+ background: #1e293b;
64
+ border-color: #334155;
65
+ color: #f1f5f9;
66
+ }
67
+
68
+ :global(.tt-row) {
69
+ display: flex;
70
+ gap: 10px;
71
+ justify-content: space-between;
72
+ }
73
+
74
+ :global(.tt-key) {
75
+ opacity: 0.65;
76
+ }
77
+
78
+ :global(.tt-val) {
79
+ font-weight: 600;
80
+ }
81
+ </style>
@@ -0,0 +1,9 @@
1
+ export { default as Root } from './Root.svelte'
2
+ export { default as Axis } from './Axis.svelte'
3
+ export { default as Bar } from './Bar.svelte'
4
+ export { default as Legend } from './Legend.svelte'
5
+ export { default as Grid } from './Grid.svelte'
6
+ export { default as Line } from './Line.svelte'
7
+ export { default as Area } from './Area.svelte'
8
+ export { default as Point } from './Point.svelte'
9
+ export { default as Arc } from './Arc.svelte'
@@ -0,0 +1,189 @@
1
+ <script>
2
+ import { setContext, untrack } from 'svelte'
3
+ import { PlotState } from './PlotState.svelte.js'
4
+ import Axis from './Plot/Axis.svelte'
5
+ import Grid from './Plot/Grid.svelte'
6
+ import Legend from './Plot/Legend.svelte'
7
+ import Tooltip from './Plot/Tooltip.svelte'
8
+ import DefinePatterns from './patterns/DefinePatterns.svelte'
9
+ import Bar from './geoms/Bar.svelte'
10
+ import Line from './geoms/Line.svelte'
11
+ import Area from './geoms/Area.svelte'
12
+ import Point from './geoms/Point.svelte'
13
+ import Arc from './geoms/Arc.svelte'
14
+ import Box from './geoms/Box.svelte'
15
+ import Violin from './geoms/Violin.svelte'
16
+
17
+ /**
18
+ * @type {{
19
+ * data?: Object[],
20
+ * spec?: import('./lib/plot/types.js').PlotSpec,
21
+ * helpers?: import('./lib/plot/types.js').PlotHelpers,
22
+ * width?: number,
23
+ * height?: number,
24
+ * mode?: 'light' | 'dark',
25
+ * grid?: boolean,
26
+ * axes?: boolean,
27
+ * margin?: { top: number, right: number, bottom: number, left: number },
28
+ * legend?: boolean,
29
+ * title?: string,
30
+ * tooltip?: boolean | ((data: Record<string, unknown>) => string),
31
+ * children?: import('svelte').Snippet,
32
+ * }}
33
+ */
34
+ let {
35
+ data = [],
36
+ spec = undefined,
37
+ helpers = {},
38
+ width = 600,
39
+ height = 400,
40
+ mode = 'light',
41
+ grid = true,
42
+ axes = true,
43
+ margin = undefined,
44
+ legend = false,
45
+ title = '',
46
+ tooltip = false,
47
+ children
48
+ } = $props()
49
+
50
+ // Create PlotState with initial values and provide as context.
51
+ // untrack() suppresses "captures initial value" warnings — intentional:
52
+ // the $effect below handles all subsequent reactive updates.
53
+ const plotState = untrack(() => new PlotState({
54
+ data: spec?.data ?? data,
55
+ width: spec?.width ?? width,
56
+ height: spec?.height ?? height,
57
+ mode,
58
+ margin,
59
+ channels: spec ? { x: spec.x, y: spec.y, color: spec.color ?? spec.fill } : {},
60
+ labels: spec?.labels ?? {},
61
+ helpers,
62
+ xDomain: spec?.xDomain,
63
+ yDomain: spec?.yDomain,
64
+ colorDomain: spec?.colorDomain
65
+ }))
66
+ setContext('plot-state', plotState)
67
+
68
+ // Keep state in sync when reactive config changes
69
+ $effect(() => {
70
+ plotState.update({
71
+ data: spec?.data ?? data,
72
+ width: spec?.width ?? width,
73
+ height: spec?.height ?? height,
74
+ mode,
75
+ margin,
76
+ channels: spec ? { x: spec.x, y: spec.y, color: spec.color ?? spec.fill } : {},
77
+ labels: spec?.labels ?? {},
78
+ helpers,
79
+ xDomain: spec?.xDomain,
80
+ yDomain: spec?.yDomain,
81
+ colorDomain: spec?.colorDomain
82
+ })
83
+ })
84
+
85
+ const svgWidth = $derived(spec?.width ?? width)
86
+ const svgHeight = $derived(spec?.height ?? height)
87
+ const showGrid = $derived(spec?.grid ?? grid)
88
+ const showLegend = $derived(spec?.legend ?? legend)
89
+ const chartTitle = $derived(spec?.title ?? title)
90
+
91
+ // Geoms from spec (spec-driven API)
92
+ const specGeoms = $derived(spec?.geoms ?? [])
93
+
94
+ // Geom component resolver for spec-driven mode
95
+ const GEOM_COMPONENTS = { bar: Bar, line: Line, area: Area, point: Point, arc: Arc, box: Box, violin: Violin }
96
+
97
+ /**
98
+ * @param {string} type
99
+ */
100
+ function resolveGeomComponent(type) {
101
+ return helpers?.geoms?.[type] ?? GEOM_COMPONENTS[type]
102
+ }
103
+ </script>
104
+
105
+ <div class="plot-root" data-plot-root data-mode={mode}>
106
+ {#if chartTitle}
107
+ <div class="plot-title" data-plot-title>{chartTitle}</div>
108
+ {/if}
109
+
110
+ <svg
111
+ width={svgWidth}
112
+ height={svgHeight}
113
+ viewBox="0 0 {svgWidth} {svgHeight}"
114
+ role="img"
115
+ aria-label={chartTitle || 'Chart visualization'}
116
+ >
117
+ <!-- SVG pattern defs -->
118
+ <DefinePatterns />
119
+
120
+ <g
121
+ class="plot-canvas"
122
+ transform="translate({plotState.margin.left}, {plotState.margin.top})"
123
+ data-plot-canvas
124
+ >
125
+ <!-- Grid (behind everything) -->
126
+ {#if showGrid}
127
+ <Grid />
128
+ {/if}
129
+
130
+ <!-- Declarative children (geom components) -->
131
+ {@render children?.()}
132
+
133
+ <!-- Spec-driven geoms -->
134
+ {#each specGeoms as geomSpec}
135
+ {@const GeomComponent = resolveGeomComponent(geomSpec.type)}
136
+ {#if GeomComponent}
137
+ <GeomComponent
138
+ x={geomSpec.x ?? spec?.x}
139
+ y={geomSpec.y ?? spec?.y}
140
+ color={geomSpec.color ?? spec?.color}
141
+ fill={geomSpec.fill ?? spec?.fill}
142
+ stat={geomSpec.stat}
143
+ options={geomSpec.options}
144
+ />
145
+ {/if}
146
+ {/each}
147
+
148
+ <!-- Axes -->
149
+ {#if axes}
150
+ <Axis type="x" label={spec?.labels?.[spec?.x ?? ''] ?? ''} />
151
+ <Axis type="y" label={spec?.labels?.[spec?.y ?? ''] ?? ''} />
152
+ {/if}
153
+ </g>
154
+ </svg>
155
+
156
+ <!-- Legend (HTML, outside SVG) -->
157
+ {#if showLegend}
158
+ <Legend labels={spec?.labels ?? {}} />
159
+ {/if}
160
+
161
+ <!-- Tooltip (HTML, fixed-position overlay) -->
162
+ {#if tooltip}
163
+ <Tooltip {tooltip} />
164
+ {/if}
165
+ </div>
166
+
167
+ <style>
168
+ .plot-root {
169
+ position: relative;
170
+ width: 100%;
171
+ height: auto;
172
+ }
173
+
174
+ svg {
175
+ display: block;
176
+ overflow: visible;
177
+ }
178
+
179
+ .plot-canvas {
180
+ pointer-events: all;
181
+ }
182
+
183
+ .plot-title {
184
+ font-size: 14px;
185
+ font-weight: 600;
186
+ text-align: center;
187
+ margin-bottom: 4px;
188
+ }
189
+ </style>