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

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 (222) 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 +49 -0
  5. package/dist/crossfilter/createCrossFilter.svelte.d.ts +13 -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 +114 -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 +23 -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 +38 -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 +215 -0
  59. package/src/Chart.svelte +98 -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 +181 -0
  75. package/src/PlotState.svelte.js +277 -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 +120 -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 +103 -0
  104. package/src/geoms/LabelPill.svelte +17 -0
  105. package/src/geoms/Line.svelte +99 -0
  106. package/src/geoms/Point.svelte +105 -0
  107. package/src/geoms/Violin.svelte +46 -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 +17 -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 +230 -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 +66 -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 +220 -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/index.js +4 -0
  156. package/src/patterns/patterns.js +208 -0
  157. package/src/patterns/scale.js +87 -0
  158. package/src/spec/chart-spec.js +29 -0
  159. package/src/symbols/RoundedSquare.svelte +33 -0
  160. package/src/symbols/Shape.svelte +37 -0
  161. package/src/symbols/constants/index.js +4 -0
  162. package/src/symbols/index.js +9 -0
  163. package/src/symbols/outline.svelte +60 -0
  164. package/src/symbols/solid.svelte +60 -0
  165. package/src/chart/FacetGrid.svelte +0 -51
  166. package/src/chart/Grid.svelte +0 -34
  167. package/src/chart/Legend.svelte +0 -16
  168. package/src/chart/PatternDefs.svelte +0 -13
  169. package/src/chart/Swatch.svelte +0 -93
  170. package/src/chart/SwatchButton.svelte +0 -29
  171. package/src/chart/SwatchGrid.svelte +0 -55
  172. package/src/chart/Symbol.svelte +0 -37
  173. package/src/chart/Texture.svelte +0 -16
  174. package/src/chart/TexturedShape.svelte +0 -27
  175. package/src/chart/TimelapseChart.svelte +0 -97
  176. package/src/chart/Timer.svelte +0 -27
  177. package/src/chart.js +0 -9
  178. package/src/components/charts/Axis.svelte +0 -66
  179. package/src/components/charts/Chart.svelte +0 -35
  180. package/src/components/index.js +0 -23
  181. package/src/components/lib/axis.js +0 -0
  182. package/src/components/lib/chart.js +0 -187
  183. package/src/components/lib/color.js +0 -327
  184. package/src/components/lib/funnel.js +0 -204
  185. package/src/components/lib/index.js +0 -19
  186. package/src/components/lib/pattern.js +0 -190
  187. package/src/components/lib/rollup.js +0 -55
  188. package/src/components/lib/shape.js +0 -199
  189. package/src/components/lib/summary.js +0 -145
  190. package/src/components/lib/theme.js +0 -23
  191. package/src/components/lib/timer.js +0 -41
  192. package/src/components/lib/utils.js +0 -165
  193. package/src/components/plots/BarPlot.svelte +0 -36
  194. package/src/components/plots/BoxPlot.svelte +0 -54
  195. package/src/components/plots/ScatterPlot.svelte +0 -30
  196. package/src/components/store.js +0 -70
  197. package/src/constants.js +0 -66
  198. package/src/elements/PatternDefs.svelte +0 -13
  199. package/src/elements/PatternMask.svelte +0 -20
  200. package/src/elements/Symbol.svelte +0 -38
  201. package/src/elements/Tooltip.svelte +0 -23
  202. package/src/funnel.svelte +0 -35
  203. package/src/geom.js +0 -105
  204. package/src/lib/axis.js +0 -75
  205. package/src/lib/colors.js +0 -32
  206. package/src/lib/geom.js +0 -4
  207. package/src/lib/shapes.js +0 -144
  208. package/src/lib/timer.js +0 -44
  209. package/src/lookup.js +0 -29
  210. package/src/plots/BarPlot.svelte +0 -55
  211. package/src/plots/BoxPlot.svelte +0 -0
  212. package/src/plots/FunnelPlot.svelte +0 -33
  213. package/src/plots/HeatMap.svelte +0 -5
  214. package/src/plots/HeatMapCalendar.svelte +0 -129
  215. package/src/plots/LinePlot.svelte +0 -55
  216. package/src/plots/Plot.svelte +0 -25
  217. package/src/plots/RankBarPlot.svelte +0 -38
  218. package/src/plots/ScatterPlot.svelte +0 -20
  219. package/src/plots/ViolinPlot.svelte +0 -11
  220. package/src/plots/heatmap.js +0 -70
  221. package/src/plots/index.js +0 -10
  222. package/src/swatch.js +0 -11
@@ -0,0 +1,277 @@
1
+ import { untrack } from 'svelte'
2
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity'
3
+ import { applyGeomStat } from './lib/plot/stat.js'
4
+ import { inferFieldType, inferOrientation, buildUnifiedXScale, buildUnifiedYScale, inferColorScaleType } from './lib/plot/scales.js'
5
+ import { resolvePreset } from './lib/plot/preset.js'
6
+ import { resolveFormat, resolveTooltip, resolveGeom } from './lib/plot/helpers.js'
7
+ import { distinct, assignColors } from './lib/brewing/colors.js'
8
+ import { assignPatterns } from './lib/brewing/patterns.js'
9
+ import { assignSymbols } from './lib/brewing/marks/points.js'
10
+
11
+ let nextId = 0
12
+
13
+ export class PlotState {
14
+ #data = $state([])
15
+ #rawData = []
16
+ #channels = $state({})
17
+ #labels = $state({})
18
+ #helpers = $state({})
19
+ #presetName = $state(undefined)
20
+ #colorMidpoint = $state(undefined)
21
+ #colorSpec = $state(undefined)
22
+ #colorDomain = $state(undefined)
23
+ #xDomain = $state(undefined)
24
+ #yDomain = $state(undefined)
25
+ #width = $state(600)
26
+ #height = $state(400)
27
+ #margin = $state({ top: 20, right: 20, bottom: 40, left: 50 })
28
+ #marginOverride = $state(undefined)
29
+
30
+ #geoms = $state([])
31
+ #mode = $state('light')
32
+ #hovered = $state(null)
33
+
34
+ axisOrigin = $state([undefined, undefined])
35
+
36
+ #effectiveMargin = $derived(this.#marginOverride ?? this.#margin)
37
+ #innerWidth = $derived(this.#width - this.#effectiveMargin.left - this.#effectiveMargin.right)
38
+ #innerHeight = $derived(this.#height - this.#effectiveMargin.top - this.#effectiveMargin.bottom)
39
+
40
+ // Effective channels: prefer top-level channels; fall back to first geom's channels
41
+ // for the declarative API where no spec is provided.
42
+ #mergeGeomChannels(tc, geom) {
43
+ return {
44
+ x: tc.x ?? geom.channels?.x,
45
+ y: tc.y ?? geom.channels?.y,
46
+ color: tc.color ?? geom.channels?.color,
47
+ pattern: tc.pattern ?? geom.channels?.pattern,
48
+ symbol: tc.symbol ?? geom.channels?.symbol,
49
+ }
50
+ }
51
+
52
+ #effectiveChannels = $derived.by(() => {
53
+ const tc = this.#channels
54
+ if (tc.x && tc.y) return tc
55
+ const firstGeom = this.#geoms[0]
56
+ if (!firstGeom) return tc
57
+ return this.#mergeGeomChannels(tc, firstGeom)
58
+ })
59
+
60
+ #resolveXType(rawXType, yType) {
61
+ const hasBarGeom = this.#geoms.some((g) => g.type === 'bar')
62
+ return (hasBarGeom && rawXType === 'continuous' && yType === 'continuous') ? 'band' : rawXType
63
+ }
64
+
65
+ orientation = $derived.by(() => {
66
+ const xField = this.#effectiveChannels.x
67
+ const yField = this.#effectiveChannels.y
68
+ if (!xField || !yField) return 'none'
69
+ const rawXType = inferFieldType(this.#data, xField)
70
+ const yType = inferFieldType(this.#data, yField)
71
+ // Bar geoms treat numeric X as categorical (e.g. year on X → vertical bars).
72
+ return inferOrientation(this.#resolveXType(rawXType, yType), yType)
73
+ })
74
+
75
+ colorScaleType = $derived.by(() => {
76
+ const field = this.#effectiveChannels.color
77
+ if (!field) return 'categorical'
78
+ return inferColorScaleType(this.#data, field, {
79
+ colorScale: this.#colorSpec,
80
+ colorMidpoint: this.#colorMidpoint
81
+ })
82
+ })
83
+
84
+ xScale = $derived.by(() => {
85
+ const field = this.#effectiveChannels.x
86
+ if (!field) return null
87
+ const datasets = this.#geoms.length > 0
88
+ ? this.#geoms.map((g) => this.geomData(g.id))
89
+ : [this.#rawData]
90
+ const includeZero = this.orientation === 'horizontal'
91
+ // For vertical bar charts, force scaleBand even when X values are numeric (e.g. year).
92
+ // Horizontal bar charts keep X as a continuous value axis.
93
+ const hasBarGeom = this.#geoms.some((g) => g.type === 'bar')
94
+ const bandX = hasBarGeom && this.orientation !== 'horizontal'
95
+ return buildUnifiedXScale(datasets, field, this.#innerWidth, {
96
+ domain: this.#xDomain,
97
+ includeZero,
98
+ band: bandX
99
+ })
100
+ })
101
+
102
+ // For box/violin geoms, compute y domain from iqr_min/iqr_max instead of raw y values.
103
+ #resolveBoxDomain() {
104
+ const boxGeom = this.#geoms.find((g) => g.type === 'box' || g.type === 'violin')
105
+ if (!boxGeom) return null
106
+ const boxData = this.geomData(boxGeom.id)
107
+ const isValid = (v) => v !== null && v !== undefined && !isNaN(v)
108
+ const mins = boxData.map((d) => d.iqr_min).filter(isValid)
109
+ const maxs = boxData.map((d) => d.iqr_max).filter(isValid)
110
+ return (mins.length > 0 && maxs.length > 0) ? [Math.min(...mins), Math.max(...maxs)] : null
111
+ }
112
+
113
+ // For stacked bars, compute y domain from per-x column totals.
114
+ #resolveStackDomain(field) {
115
+ const stackGeom = this.#geoms.find((g) => g.options?.stack)
116
+ if (!stackGeom) return null
117
+ const xField = this.#effectiveChannels.x
118
+ const stackData = this.geomData(stackGeom.id)
119
+ if (!xField || stackData.length === 0) return null
120
+ // Mirror buildStackedBars/subBandFields: stack dimension is the first
121
+ // non-x field among [color, pattern]. Summing all raw rows (stat=identity)
122
+ // would overcount when multiple rows share the same (x, stack) key.
123
+ const colorField = this.#effectiveChannels.color
124
+ const patternField = this.#effectiveChannels.pattern
125
+ const stackField = [colorField, patternField].find((f) => f && f !== xField) ?? colorField
126
+ const lookup = new SvelteMap()
127
+ for (const d of stackData) {
128
+ const xVal = d[xField]
129
+ const cKey = stackField ? String(d[stackField]) : '_'
130
+ if (!lookup.has(xVal)) lookup.set(xVal, new SvelteMap())
131
+ lookup.get(xVal).set(cKey, Number(d[field]) || 0)
132
+ }
133
+ const totals = new SvelteMap()
134
+ for (const [xVal, colorMap] of lookup) {
135
+ totals.set(xVal, [...colorMap.values()].reduce((s, v) => s + v, 0))
136
+ }
137
+ return [0, Math.max(0, ...totals.values())]
138
+ }
139
+
140
+ yScale = $derived.by(() => {
141
+ const field = this.#effectiveChannels.y
142
+ if (!field) return null
143
+ const datasets = this.#geoms.length > 0
144
+ ? this.#geoms.map((g) => this.geomData(g.id))
145
+ : [this.#rawData]
146
+ const includeZero = this.orientation === 'vertical'
147
+ const yDomain = this.#yDomain ?? this.#resolveBoxDomain() ?? this.#resolveStackDomain(field)
148
+ return buildUnifiedYScale(datasets, field, this.#innerHeight, { domain: yDomain, includeZero })
149
+ })
150
+
151
+ // Colors: Map<colorKey, { fill, stroke }> for all distinct color field values.
152
+ // If a colorDomain is provided (e.g. from FacetPlot for cross-panel consistency),
153
+ // use it instead of deriving distinct values from the local panel data.
154
+ colors = $derived.by(() => {
155
+ const field = this.#effectiveChannels.color
156
+ const values = this.#colorDomain ?? distinct(this.#data, field)
157
+ return assignColors(values, this.#mode)
158
+ })
159
+
160
+ // Patterns: Map<patternKey, patternName> — only populated when a pattern channel is set
161
+ // and the pattern field is categorical (continuous fields can't be discretely patterned).
162
+ patterns = $derived.by(() => {
163
+ const pf = this.#effectiveChannels.pattern
164
+ if (!pf) return new SvelteMap()
165
+ if (inferFieldType(this.#data, pf) === 'continuous') return new SvelteMap()
166
+ return assignPatterns(distinct(this.#data, pf))
167
+ })
168
+
169
+ // Symbols: Map<symbolKey, shapeName> — only populated when a symbol channel is set.
170
+ symbols = $derived.by(() => {
171
+ const sf = this.#effectiveChannels.symbol
172
+ if (!sf) return new SvelteMap()
173
+ return assignSymbols(distinct(this.#data, sf))
174
+ })
175
+
176
+ // Expose effective channel fields for consumers (e.g. Legend)
177
+ colorField = $derived(this.#effectiveChannels.color)
178
+ patternField = $derived(this.#effectiveChannels.pattern)
179
+ symbolField = $derived(this.#effectiveChannels.symbol)
180
+
181
+ // Set of geom types currently registered (used by Legend to pick swatch style)
182
+ geomTypes = $derived(new SvelteSet(this.#geoms.map((g) => g.type)))
183
+
184
+ xAxisY = $derived.by(() => {
185
+ if (!this.yScale || typeof this.yScale !== 'function') return this.#innerHeight
186
+ const crossVal = this.axisOrigin[1]
187
+ if (crossVal !== undefined) return this.yScale(crossVal)
188
+ const domain = this.yScale.domain?.()
189
+ return domain ? this.yScale(domain[0]) : this.#innerHeight
190
+ })
191
+
192
+ yAxisX = $derived.by(() => {
193
+ if (!this.xScale || typeof this.xScale !== 'function') return 0
194
+ const crossVal = this.axisOrigin[0]
195
+ if (crossVal !== undefined) return this.xScale(crossVal)
196
+ const domain = this.xScale.domain?.()
197
+ return domain ? this.xScale(domain[0]) : 0
198
+ })
199
+
200
+ constructor(config = {}) {
201
+ this.#rawData = config.data ?? []
202
+ this.#data = config.data ?? []
203
+ this.#channels = config.channels ?? {}
204
+ this.#labels = config.labels ?? {}
205
+ this.#helpers = config.helpers ?? {}
206
+ this.#presetName = config.preset
207
+ this.#colorMidpoint = config.colorMidpoint
208
+ this.#colorSpec = config.colorScale
209
+ this.#colorDomain = config.colorDomain
210
+ this.#xDomain = config.xDomain
211
+ this.#yDomain = config.yDomain
212
+ this.#width = config.width ?? 600
213
+ this.#height = config.height ?? 400
214
+ this.#mode = config.mode ?? 'light'
215
+ this.#marginOverride = config.margin ?? undefined
216
+ }
217
+
218
+ update(config) {
219
+ if (config.data !== undefined) { this.#rawData = config.data; this.#data = config.data }
220
+ if (config.channels !== undefined) this.#channels = config.channels
221
+ if (config.labels !== undefined) this.#labels = config.labels
222
+ if (config.helpers !== undefined) this.#helpers = config.helpers
223
+ if (config.preset !== undefined) this.#presetName = config.preset
224
+ if (config.colorMidpoint !== undefined) this.#colorMidpoint = config.colorMidpoint
225
+ if (config.colorScale !== undefined) this.#colorSpec = config.colorScale
226
+ this.#colorDomain = config.colorDomain
227
+ this.#xDomain = config.xDomain
228
+ this.#yDomain = config.yDomain
229
+ if (config.width !== undefined) this.#width = config.width
230
+ if (config.height !== undefined) this.#height = config.height
231
+ if (config.mode !== undefined) this.#mode = config.mode
232
+ this.#marginOverride = config.margin ?? undefined
233
+ }
234
+
235
+ registerGeom(config) {
236
+ const id = `geom-${nextId++}`
237
+ this.#geoms = [...this.#geoms, { id, ...config }]
238
+ return id
239
+ }
240
+
241
+ updateGeom(id, config) {
242
+ // untrack the read of #geoms to avoid effect_update_depth_exceeded when
243
+ // called from a geom's $effect (which would otherwise track #geoms as a dependency)
244
+ this.#geoms = untrack(() => this.#geoms).map((g) => g.id === id ? { ...g, ...config } : g)
245
+ }
246
+
247
+ unregisterGeom(id) {
248
+ this.#geoms = this.#geoms.filter((g) => g.id !== id)
249
+ }
250
+
251
+ geomData(id) {
252
+ const geom = this.#geoms.find((g) => g.id === id)
253
+ if (!geom) return []
254
+ const stat = geom.stat ?? 'identity'
255
+ if (stat === 'identity') return this.#rawData
256
+ const mergedChannels = { ...this.#channels, ...geom.channels }
257
+ return applyGeomStat(this.#rawData, { stat, channels: mergedChannels }, this.#helpers)
258
+ }
259
+
260
+ label(field) {
261
+ return this.#labels?.[field] ?? field
262
+ }
263
+
264
+ format(field) { return resolveFormat(field, this.#helpers) }
265
+ tooltip() { return resolveTooltip(this.#helpers) }
266
+ geomComponent(type) { return resolveGeom(type, this.#helpers) }
267
+ preset() { return resolvePreset(this.#presetName, this.#helpers) }
268
+
269
+ get margin() { return this.#effectiveMargin }
270
+ get innerWidth() { return this.#innerWidth }
271
+ get innerHeight() { return this.#innerHeight }
272
+ get mode() { return this.#mode }
273
+ get hovered() { return this.#hovered }
274
+
275
+ setHovered(data) { this.#hovered = data }
276
+ clearHovered() { this.#hovered = null }
277
+ }
@@ -0,0 +1,69 @@
1
+ <script>
2
+ import { scaleLinear } from 'd3-scale'
3
+ import { line as d3line, area as d3area, curveCatmullRom } from 'd3-shape'
4
+
5
+ /**
6
+ * @type {number[] | object[]}
7
+ */
8
+ let {
9
+ data = [],
10
+ field = undefined,
11
+ type = 'line',
12
+ curve = 'linear',
13
+ color = 'primary',
14
+ width = 80,
15
+ height = 24,
16
+ min = undefined,
17
+ max = undefined
18
+ } = $props()
19
+
20
+ const values = $derived(
21
+ field ? data.map((d) => Number(d[field])) : data.map(Number)
22
+ )
23
+
24
+ const yMin = $derived(min ?? Math.min(...values))
25
+ const yMax = $derived(max ?? Math.max(...values))
26
+
27
+ const xScale = $derived(
28
+ scaleLinear().domain([0, values.length - 1]).range([0, width])
29
+ )
30
+ const yScale = $derived(
31
+ scaleLinear().domain([yMin, yMax]).range([height, 0])
32
+ )
33
+
34
+ const linePath = $derived.by(() => {
35
+ const gen = d3line().x((_, i) => xScale(i)).y((v) => yScale(v))
36
+ if (curve === 'smooth') gen.curve(curveCatmullRom)
37
+ return gen(values)
38
+ })
39
+
40
+ const areaPath = $derived.by(() => {
41
+ const gen = d3area().x((_, i) => xScale(i)).y0(height).y1((v) => yScale(v))
42
+ if (curve === 'smooth') gen.curve(curveCatmullRom)
43
+ return gen(values)
44
+ })
45
+
46
+ const barWidth = $derived(Math.max(1, width / values.length - 1))
47
+
48
+ const strokeColor = $derived(`rgb(var(--color-${color}-500, 100,116,139))`)
49
+ const fillColor = $derived(`rgba(var(--color-${color}-300), 0.25)`)
50
+ </script>
51
+
52
+ <svg {width} {height} style="overflow: visible; display: block;">
53
+ {#if type === 'line'}
54
+ <path d={linePath} fill="none" stroke={strokeColor} stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" />
55
+ {:else if type === 'area'}
56
+ <path d={areaPath} fill={fillColor} stroke="none" />
57
+ <path d={linePath} fill="none" stroke={strokeColor} stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" />
58
+ {:else if type === 'bar'}
59
+ {#each values as v, i (i)}
60
+ <rect
61
+ x={xScale(i) - barWidth / 2}
62
+ y={yScale(v)}
63
+ width={barWidth}
64
+ height={height - yScale(v)}
65
+ fill={strokeColor}
66
+ />
67
+ {/each}
68
+ {/if}
69
+ </svg>
@@ -0,0 +1,21 @@
1
+ <script>
2
+ import { components } from './symbols'
3
+
4
+ let {
5
+ x = 0,
6
+ y = 0,
7
+ size = 10,
8
+ fill = 'currentColor',
9
+ stroke = 'currentColor',
10
+ name = 'circle',
11
+ ...restProps
12
+ } = $props()
13
+
14
+ let RenderShape = $derived(components[name] ?? components.default)
15
+ let props = $derived({ ...restProps, x, y, size, fill, stroke })
16
+ </script>
17
+
18
+ <!-- {#snippet defaultSymbol(props)}
19
+ <circle cx={x} cy={y} r={size / 2} {fill} {stroke} />
20
+ {/snippet} -->
21
+ <RenderShape {...props} />
@@ -0,0 +1,18 @@
1
+ <script>
2
+ let {
3
+ id,
4
+ path,
5
+ fill = 'currentColor',
6
+ stroke = 'currentColor',
7
+ thickness = 0.5,
8
+ patternUnits = 'userSpaceOnUse',
9
+ size = 10
10
+ } = $props()
11
+ </script>
12
+
13
+ <pattern {id} {patternUnits} width={size} height={size}>
14
+ <rect width={size} height={size} {fill} />
15
+ {#if path}
16
+ <path d={path} fill="none" {stroke} stroke-width={thickness} />
17
+ {/if}
18
+ </pattern>
@@ -0,0 +1,25 @@
1
+ <script>
2
+ import Plot from '../Plot.svelte'
3
+ import Area from '../geoms/Area.svelte'
4
+
5
+ /** @type {import('../lib/plot/chartProps.js').LineAreaChartProps} */
6
+ let {
7
+ data = [],
8
+ x = undefined,
9
+ y = undefined,
10
+ fill = undefined,
11
+ stat = 'identity',
12
+ curve = undefined,
13
+ pattern = undefined,
14
+ stack = false,
15
+ width = 600,
16
+ height = 400,
17
+ mode = 'light',
18
+ grid = true,
19
+ legend = false
20
+ } = $props()
21
+ </script>
22
+
23
+ <Plot {data} {width} {height} {mode} {grid} {legend}>
24
+ <Area {x} {y} color={fill} {pattern} {stat} options={{ curve, stack }} />
25
+ </Plot>
@@ -0,0 +1,26 @@
1
+ <script>
2
+ import Plot from '../Plot.svelte'
3
+ import Bar from '../geoms/Bar.svelte'
4
+
5
+ /** @type {import('../lib/plot/chartProps.js').BarChartProps} */
6
+ let {
7
+ data = [],
8
+ x = undefined,
9
+ y = undefined,
10
+ fill = undefined, // mapped to color channel
11
+ pattern = undefined,
12
+ width = 600,
13
+ height = 400,
14
+ mode = 'light',
15
+ grid = true,
16
+ legend = false,
17
+ stat = 'identity',
18
+ stack = false,
19
+ label = false,
20
+ tooltip = false
21
+ } = $props()
22
+ </script>
23
+
24
+ <Plot {data} {width} {height} {mode} {grid} {legend} {tooltip}>
25
+ <Bar {x} {y} color={fill} {pattern} {label} {stat} options={{ stack }} />
26
+ </Plot>
@@ -0,0 +1,21 @@
1
+ <script>
2
+ import Plot from '../Plot.svelte'
3
+ import Box from '../geoms/Box.svelte'
4
+
5
+ /** @type {import('../lib/plot/chartProps.js').BoxViolinChartProps} */
6
+ let {
7
+ data = [],
8
+ x = undefined,
9
+ y = undefined,
10
+ fill = undefined,
11
+ width = 600,
12
+ height = 400,
13
+ mode = 'light',
14
+ grid = true,
15
+ legend = false
16
+ } = $props()
17
+ </script>
18
+
19
+ <Plot {data} {width} {height} {mode} {grid} {legend}>
20
+ <Box {x} {y} {fill} />
21
+ </Plot>
@@ -0,0 +1,23 @@
1
+ <script>
2
+ import Plot from '../Plot.svelte'
3
+ import Point from '../geoms/Point.svelte'
4
+
5
+ /** @type {import('../lib/plot/chartProps.js').ScatterBubbleChartProps} */
6
+ let {
7
+ data = [],
8
+ x = undefined,
9
+ y = undefined,
10
+ color = undefined,
11
+ symbol: _symbol = undefined,
12
+ size, // required: field name for bubble radius
13
+ width = 600,
14
+ height = 400,
15
+ mode = 'light',
16
+ grid = true,
17
+ legend = false
18
+ } = $props()
19
+ </script>
20
+
21
+ <Plot {data} {width} {height} {mode} {grid} {legend}>
22
+ <Point {x} {y} {color} {size} />
23
+ </Plot>
@@ -0,0 +1,26 @@
1
+ <script>
2
+ import Plot from '../Plot.svelte'
3
+ import Line from '../geoms/Line.svelte'
4
+
5
+ /** @type {import('../lib/plot/chartProps.js').LineAreaChartProps} */
6
+ let {
7
+ data = [],
8
+ x = undefined,
9
+ y = undefined,
10
+ color = undefined,
11
+ stat = 'identity',
12
+ symbol = undefined,
13
+ curve = undefined, // forwarded to Line options
14
+ label = false,
15
+ tooltip = false,
16
+ width = 600,
17
+ height = 400,
18
+ mode = 'light',
19
+ grid = true,
20
+ legend = false
21
+ } = $props()
22
+ </script>
23
+
24
+ <Plot {data} {width} {height} {mode} {grid} {legend} {tooltip}>
25
+ <Line {x} {y} {color} {symbol} {label} {stat} options={{ curve }} />
26
+ </Plot>
@@ -0,0 +1,25 @@
1
+ <script>
2
+ import Plot from '../Plot.svelte'
3
+ import Arc from '../geoms/Arc.svelte'
4
+
5
+ /** @type {import('../lib/plot/chartProps.js').PieChartProps} */
6
+ let {
7
+ data = [],
8
+ label = undefined,
9
+ y = undefined,
10
+ fill = undefined,
11
+ pattern = undefined,
12
+ innerRadius = 0,
13
+ labelFn = undefined,
14
+ tooltip = false,
15
+ width = 400,
16
+ height = 400,
17
+ mode = 'light',
18
+ legend = false,
19
+ stat = 'sum'
20
+ } = $props()
21
+ </script>
22
+
23
+ <Plot {data} {width} {height} {mode} grid={false} axes={false} margin={{ top: 10, right: 10, bottom: 10, left: 10 }} {legend} {tooltip}>
24
+ <Arc theta={y} fill={label ?? fill} {pattern} {labelFn} {stat} options={{ innerRadius }} />
25
+ </Plot>
@@ -0,0 +1,25 @@
1
+ <script>
2
+ import Plot from '../Plot.svelte'
3
+ import Point from '../geoms/Point.svelte'
4
+
5
+ /** @type {import('../lib/plot/chartProps.js').ScatterBubbleChartProps} */
6
+ let {
7
+ data = [],
8
+ x = undefined,
9
+ y = undefined,
10
+ color = undefined,
11
+ symbol = undefined,
12
+ size = undefined,
13
+ label = false,
14
+ tooltip = false,
15
+ width = 600,
16
+ height = 400,
17
+ mode = 'light',
18
+ grid = true,
19
+ legend = false
20
+ } = $props()
21
+ </script>
22
+
23
+ <Plot {data} {width} {height} {mode} {grid} {legend} {tooltip}>
24
+ <Point {x} {y} {color} {size} {symbol} {label} />
25
+ </Plot>
@@ -0,0 +1,21 @@
1
+ <script>
2
+ import Plot from '../Plot.svelte'
3
+ import Violin from '../geoms/Violin.svelte'
4
+
5
+ /** @type {import('../lib/plot/chartProps.js').BoxViolinChartProps} */
6
+ let {
7
+ data = [],
8
+ x = undefined,
9
+ y = undefined,
10
+ fill = undefined,
11
+ width = 600,
12
+ height = 400,
13
+ mode = 'light',
14
+ grid = true,
15
+ legend = false
16
+ } = $props()
17
+ </script>
18
+
19
+ <Plot {data} {width} {height} {mode} {grid} {legend}>
20
+ <Violin {x} {y} {fill} />
21
+ </Plot>
@@ -0,0 +1,38 @@
1
+ <script>
2
+ import { setContext, untrack } from 'svelte'
3
+ import { createCrossFilter } from './createCrossFilter.svelte.js'
4
+
5
+ /**
6
+ * @type {{
7
+ * crossfilter?: ReturnType<typeof createCrossFilter>,
8
+ * mode?: 'dim' | 'hide',
9
+ * filters?: import('./createCrossFilter.svelte.js').FilterState,
10
+ * children?: import('svelte').Snippet
11
+ * }}
12
+ */
13
+ let {
14
+ crossfilter: externalCf = undefined,
15
+ mode = 'dim',
16
+ filters = $bindable(),
17
+ children
18
+ } = $props()
19
+
20
+ // Use an externally provided instance (spec/helpers API) or create one internally.
21
+ // untrack() suppresses "captures initial value" warning — intentional: the cf
22
+ // instance is locked in at construction time and must not recreate on prop changes.
23
+ const cf = untrack(() => externalCf ?? createCrossFilter())
24
+
25
+ // Expose the reactive filters Map to callers via bind:filters
26
+ $effect(() => {
27
+ filters = cf.filters
28
+ })
29
+
30
+ setContext('crossfilter', cf)
31
+ // Use a getter object so children can read .mode reactively
32
+ const modeRef = { get mode() { return mode } }
33
+ setContext('crossfilter-mode', modeRef)
34
+ </script>
35
+
36
+ <div data-crossfilter data-crossfilter-mode={mode}>
37
+ {@render children?.()}
38
+ </div>
@@ -0,0 +1,32 @@
1
+ <script>
2
+ import PlotChart from '../Plot.svelte'
3
+ import Bar from '../geoms/Bar.svelte'
4
+
5
+ let {
6
+ data = [],
7
+ field,
8
+ valueField,
9
+ stat = 'sum',
10
+ width = 300,
11
+ height = 120,
12
+ mode = 'light'
13
+ } = $props()
14
+
15
+ const spec = $derived({
16
+ x: field,
17
+ y: valueField
18
+ })
19
+ </script>
20
+
21
+ <!-- FilterBar must be used inside a <CrossFilter> parent. Does not create its own context. -->
22
+ <PlotChart
23
+ {data}
24
+ {spec}
25
+ {width}
26
+ {height}
27
+ {mode}
28
+ grid={false}
29
+ legend={false}
30
+ >
31
+ <Bar x={field} y={valueField} {stat} filterable={true} />
32
+ </PlotChart>