@rokkit/chart 1.0.0-next.16 → 1.0.0-next.161

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 (173) hide show
  1. package/README.md +150 -46
  2. package/package.json +42 -45
  3. package/src/AnimatedPlot.svelte +383 -0
  4. package/src/Chart.svelte +95 -0
  5. package/src/ChartProvider.svelte +10 -0
  6. package/src/FacetPlot/Panel.svelte +37 -0
  7. package/src/FacetPlot.svelte +114 -0
  8. package/src/Plot/Arc.svelte +29 -0
  9. package/src/Plot/Area.svelte +32 -0
  10. package/src/Plot/Axis.svelte +95 -0
  11. package/src/Plot/Bar.svelte +54 -0
  12. package/src/Plot/Grid.svelte +34 -0
  13. package/src/Plot/Legend.svelte +233 -0
  14. package/src/Plot/Line.svelte +37 -0
  15. package/src/Plot/Point.svelte +40 -0
  16. package/src/Plot/Root.svelte +62 -0
  17. package/src/Plot/Timeline.svelte +95 -0
  18. package/src/Plot/Tooltip.svelte +87 -0
  19. package/src/Plot/index.js +9 -0
  20. package/src/Plot.svelte +297 -0
  21. package/src/PlotState.svelte.js +350 -0
  22. package/src/Sparkline.svelte +108 -0
  23. package/src/Symbol.svelte +21 -0
  24. package/src/Texture.svelte +18 -0
  25. package/src/charts/AreaChart.svelte +27 -0
  26. package/src/charts/BarChart.svelte +28 -0
  27. package/src/charts/BoxPlot.svelte +21 -0
  28. package/src/charts/BubbleChart.svelte +23 -0
  29. package/src/charts/LineChart.svelte +26 -0
  30. package/src/charts/PieChart.svelte +35 -0
  31. package/src/charts/ScatterPlot.svelte +26 -0
  32. package/src/charts/ViolinPlot.svelte +21 -0
  33. package/src/crossfilter/CrossFilter.svelte +42 -0
  34. package/src/crossfilter/FilterBar.svelte +24 -0
  35. package/src/crossfilter/FilterHistogram.svelte +290 -0
  36. package/src/crossfilter/FilterSlider.svelte +83 -0
  37. package/src/crossfilter/createCrossFilter.svelte.js +124 -0
  38. package/src/elements/Bar.svelte +22 -24
  39. package/src/elements/ColorRamp.svelte +20 -22
  40. package/src/elements/ContinuousLegend.svelte +20 -17
  41. package/src/elements/DefinePatterns.svelte +24 -0
  42. package/src/elements/DiscreteLegend.svelte +15 -15
  43. package/src/elements/Label.svelte +4 -8
  44. package/src/elements/SymbolGrid.svelte +22 -0
  45. package/src/elements/index.js +6 -0
  46. package/src/examples/BarChartExample.svelte +81 -0
  47. package/src/geoms/Arc.svelte +126 -0
  48. package/src/geoms/Area.svelte +78 -0
  49. package/src/geoms/Bar.svelte +200 -0
  50. package/src/geoms/Box.svelte +113 -0
  51. package/src/geoms/LabelPill.svelte +17 -0
  52. package/src/geoms/Line.svelte +123 -0
  53. package/src/geoms/Point.svelte +145 -0
  54. package/src/geoms/Violin.svelte +56 -0
  55. package/src/geoms/lib/areas.js +154 -0
  56. package/src/geoms/lib/bars.js +223 -0
  57. package/src/index.js +74 -16
  58. package/src/lib/brewer.js +25 -0
  59. package/src/lib/brewing/BoxBrewer.svelte.js +14 -0
  60. package/src/lib/brewing/CartesianBrewer.svelte.js +21 -0
  61. package/src/lib/brewing/PieBrewer.svelte.js +14 -0
  62. package/src/lib/brewing/QuartileBrewer.svelte.js +51 -0
  63. package/src/lib/brewing/ViolinBrewer.svelte.js +14 -0
  64. package/src/lib/brewing/axes.svelte.js +270 -0
  65. package/src/lib/brewing/bars.svelte.js +201 -0
  66. package/src/lib/brewing/brewer.svelte.js +277 -0
  67. package/src/lib/brewing/colors.js +51 -0
  68. package/src/lib/brewing/dimensions.svelte.js +56 -0
  69. package/src/lib/brewing/index.svelte.js +205 -0
  70. package/src/lib/brewing/legends.svelte.js +137 -0
  71. package/src/lib/brewing/marks/arcs.js +43 -0
  72. package/src/lib/brewing/marks/areas.js +72 -0
  73. package/src/lib/brewing/marks/bars.js +49 -0
  74. package/src/lib/brewing/marks/boxes.js +75 -0
  75. package/src/lib/brewing/marks/lines.js +55 -0
  76. package/src/lib/brewing/marks/points.js +105 -0
  77. package/src/lib/brewing/marks/violins.js +90 -0
  78. package/src/lib/brewing/patterns.js +45 -0
  79. package/src/lib/brewing/scales.js +51 -0
  80. package/src/lib/brewing/scales.svelte.js +82 -0
  81. package/src/lib/brewing/stats.js +74 -0
  82. package/src/lib/brewing/symbols.js +10 -0
  83. package/src/lib/brewing/types.js +73 -0
  84. package/src/lib/chart.js +221 -0
  85. package/src/lib/context.js +131 -0
  86. package/src/lib/grid.js +85 -0
  87. package/src/lib/keyboard-nav.js +37 -0
  88. package/src/lib/plot/chartProps.js +76 -0
  89. package/src/lib/plot/crossfilter.js +16 -0
  90. package/src/lib/plot/facet.js +58 -0
  91. package/src/lib/plot/frames.js +81 -0
  92. package/src/lib/plot/helpers.js +14 -0
  93. package/src/lib/plot/preset.js +67 -0
  94. package/src/lib/plot/scales.js +81 -0
  95. package/src/lib/plot/stat.js +92 -0
  96. package/src/lib/plot/types.js +65 -0
  97. package/src/lib/preset.js +41 -0
  98. package/src/lib/scales.svelte.js +151 -0
  99. package/src/lib/swatch.js +13 -0
  100. package/src/lib/ticks.js +46 -0
  101. package/src/lib/utils.js +111 -118
  102. package/src/lib/xscale.js +31 -0
  103. package/src/patterns/DefinePatterns.svelte +32 -0
  104. package/src/patterns/PatternDef.svelte +27 -0
  105. package/src/patterns/index.js +4 -0
  106. package/src/patterns/patterns.js +360 -0
  107. package/src/patterns/scale.js +116 -0
  108. package/src/spec/chart-spec.js +72 -0
  109. package/src/symbols/RoundedSquare.svelte +33 -0
  110. package/src/symbols/Shape.svelte +37 -0
  111. package/src/symbols/constants/index.js +4 -0
  112. package/src/symbols/index.js +9 -0
  113. package/src/symbols/outline.svelte +60 -0
  114. package/src/symbols/solid.svelte +60 -0
  115. package/LICENSE +0 -21
  116. package/src/chart/FacetGrid.svelte +0 -51
  117. package/src/chart/Grid.svelte +0 -34
  118. package/src/chart/Legend.svelte +0 -16
  119. package/src/chart/PatternDefs.svelte +0 -13
  120. package/src/chart/Swatch.svelte +0 -93
  121. package/src/chart/SwatchButton.svelte +0 -29
  122. package/src/chart/SwatchGrid.svelte +0 -55
  123. package/src/chart/Symbol.svelte +0 -37
  124. package/src/chart/Texture.svelte +0 -16
  125. package/src/chart/TexturedShape.svelte +0 -27
  126. package/src/chart/TimelapseChart.svelte +0 -97
  127. package/src/chart/Timer.svelte +0 -27
  128. package/src/chart.js +0 -9
  129. package/src/components/charts/Axis.svelte +0 -66
  130. package/src/components/charts/Chart.svelte +0 -35
  131. package/src/components/index.js +0 -23
  132. package/src/components/lib/axis.js +0 -0
  133. package/src/components/lib/chart.js +0 -187
  134. package/src/components/lib/color.js +0 -327
  135. package/src/components/lib/funnel.js +0 -204
  136. package/src/components/lib/index.js +0 -19
  137. package/src/components/lib/pattern.js +0 -190
  138. package/src/components/lib/rollup.js +0 -55
  139. package/src/components/lib/shape.js +0 -199
  140. package/src/components/lib/summary.js +0 -145
  141. package/src/components/lib/theme.js +0 -23
  142. package/src/components/lib/timer.js +0 -41
  143. package/src/components/lib/utils.js +0 -165
  144. package/src/components/plots/BarPlot.svelte +0 -36
  145. package/src/components/plots/BoxPlot.svelte +0 -54
  146. package/src/components/plots/ScatterPlot.svelte +0 -30
  147. package/src/components/store.js +0 -70
  148. package/src/constants.js +0 -66
  149. package/src/elements/PatternDefs.svelte +0 -13
  150. package/src/elements/PatternMask.svelte +0 -20
  151. package/src/elements/Symbol.svelte +0 -38
  152. package/src/elements/Tooltip.svelte +0 -23
  153. package/src/funnel.svelte +0 -35
  154. package/src/geom.js +0 -105
  155. package/src/lib/axis.js +0 -75
  156. package/src/lib/colors.js +0 -32
  157. package/src/lib/geom.js +0 -4
  158. package/src/lib/shapes.js +0 -144
  159. package/src/lib/timer.js +0 -44
  160. package/src/lookup.js +0 -29
  161. package/src/plots/BarPlot.svelte +0 -55
  162. package/src/plots/BoxPlot.svelte +0 -0
  163. package/src/plots/FunnelPlot.svelte +0 -33
  164. package/src/plots/HeatMap.svelte +0 -5
  165. package/src/plots/HeatMapCalendar.svelte +0 -129
  166. package/src/plots/LinePlot.svelte +0 -55
  167. package/src/plots/Plot.svelte +0 -25
  168. package/src/plots/RankBarPlot.svelte +0 -38
  169. package/src/plots/ScatterPlot.svelte +0 -20
  170. package/src/plots/ViolinPlot.svelte +0 -11
  171. package/src/plots/heatmap.js +0 -70
  172. package/src/plots/index.js +0 -10
  173. package/src/swatch.js +0 -11
@@ -0,0 +1,277 @@
1
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity'
2
+ import { distinct, assignColors } from './colors.js'
3
+ import { assignPatterns, toPatternId, PATTERN_ORDER } from './patterns.js'
4
+ import { assignSymbols } from './symbols.js'
5
+ import { buildXScale, buildYScale, buildSizeScale } from './scales.js'
6
+ import { buildBars } from './marks/bars.js'
7
+ import { buildLines } from './marks/lines.js'
8
+ import { buildAreas } from './marks/areas.js'
9
+ import { buildArcs } from './marks/arcs.js'
10
+ import { buildPoints } from './marks/points.js'
11
+
12
+ const DEFAULT_MARGIN = { top: 20, right: 20, bottom: 40, left: 50 }
13
+
14
+ /**
15
+ * Groups aesthetic channel mappings by field name, merging aesthetics that
16
+ * share the same field into one legend section.
17
+ *
18
+ * @param {{ fill?: string, color?: string, pattern?: string, symbol?: string }} channels
19
+ * `fill` takes precedence over `color` for polygon charts (bars, areas, pie slices).
20
+ * @param {Map<unknown, {fill:string, stroke:string}>} colorMap
21
+ * @param {Map<unknown, string>} patternMap
22
+ * @param {Map<unknown, string>} symbolMap
23
+ * @returns {{ field: string, items: { label: string, fill: string|null, stroke: string|null, patternId: string|null, shape: string|null }[] }[]}
24
+ */
25
+ function addAesthetic(byField, field, aesthetic, keys) {
26
+ if (byField.has(field)) {
27
+ byField.get(field).aesthetics.push(aesthetic)
28
+ } else {
29
+ byField.set(field, { aesthetics: [aesthetic], keys })
30
+ }
31
+ }
32
+
33
+ function buildLegendItem(key, aesthetics, colorMap, patternMap, symbolMap) {
34
+ const hasColor = aesthetics.includes('color')
35
+ return {
36
+ label: String(key),
37
+ fill: hasColor ? (colorMap.get(key)?.fill ?? null) : null,
38
+ stroke: hasColor ? (colorMap.get(key)?.stroke ?? null) : null,
39
+ patternId: aesthetics.includes('pattern') && patternMap.has(key) ? toPatternId(key) : null,
40
+ shape: aesthetics.includes('symbol') ? (symbolMap.get(key) ?? 'circle') : null
41
+ }
42
+ }
43
+
44
+ export function buildLegendGroups(channels, colorMap, patternMap, symbolMap) {
45
+ const cf = channels.fill ?? channels.color
46
+ const { pattern: pf, symbol: sf } = channels
47
+ const byField = new SvelteMap()
48
+
49
+ if (cf) byField.set(cf, { aesthetics: ['color'], keys: [...colorMap.keys()] })
50
+ if (pf) addAesthetic(byField, pf, 'pattern', [...patternMap.keys()])
51
+ if (sf) addAesthetic(byField, sf, 'symbol', [...symbolMap.keys()])
52
+
53
+ return [...byField.entries()]
54
+ .map(([field, { aesthetics, keys }]) => ({
55
+ field,
56
+ items: keys
57
+ .filter((k) => k !== null && k !== undefined)
58
+ .map((key) => buildLegendItem(key, aesthetics, colorMap, patternMap, symbolMap))
59
+ }))
60
+ .filter((group) => group.items.length > 0)
61
+ }
62
+
63
+ export class ChartBrewer {
64
+ #rawData = $state([])
65
+ #channels = $state({})
66
+ #width = $state(600)
67
+ #height = $state(400)
68
+ #mode = $state('light')
69
+ #margin = $state(DEFAULT_MARGIN)
70
+ #layers = $state([])
71
+ #curve = $state(/** @type {'linear'|'smooth'|'step'|undefined} */ (undefined))
72
+ #stat = $state('identity')
73
+
74
+ /**
75
+ * Override in subclasses to apply stat aggregation.
76
+ * @param {Object[]} data
77
+ * @param {Object} channels
78
+ * @param {string|Function} stat
79
+ * @returns {Object[]}
80
+ */
81
+ transform(data, _channels, _stat) {
82
+ return data
83
+ }
84
+
85
+ /** Aggregated data — all derived marks read this, not #rawData */
86
+ processedData = $derived(this.transform(this.#rawData, this.#channels, this.#stat))
87
+
88
+ /** Exposes channels to subclasses for use in their own $derived properties */
89
+ get channels() {
90
+ return this.#channels
91
+ }
92
+
93
+ // Maps are built from rawData so the legend always reflects the full set of
94
+ // original values — independent of whichever stat aggregation is applied.
95
+ // e.g. pattern=quarter with stat=sum still shows all 8 quarters in the legend.
96
+
97
+ /** @type {Map<unknown, {fill:string,stroke:string}>} */
98
+ colorMap = $derived(
99
+ (this.#channels.fill ?? this.#channels.color)
100
+ ? assignColors(
101
+ distinct(this.#rawData, this.#channels.fill ?? this.#channels.color),
102
+ this.#mode
103
+ )
104
+ : new SvelteMap()
105
+ )
106
+
107
+ /** @type {Map<unknown, string>} */
108
+ patternMap = $derived(
109
+ this.#channels.pattern
110
+ ? assignPatterns(distinct(this.#rawData, this.#channels.pattern))
111
+ : new SvelteMap()
112
+ )
113
+
114
+ /**
115
+ * Unified pattern defs for ChartPatternDefs.
116
+ * When fill and pattern map the same field, pattern key = color key (simple case).
117
+ * When they differ, each unique (fillKey, patternKey) pair gets its own pattern def
118
+ * so bars/areas can have distinct colors per region AND distinct textures per category.
119
+ * @type {Array<{ id: string, name: string, fill: string, stroke: string }>}
120
+ */
121
+ patternDefs = $derived(
122
+ (() => {
123
+ const pf = this.#channels.pattern
124
+ const ff = this.#channels.fill ?? this.#channels.color
125
+ if (!pf || this.patternMap.size === 0) return []
126
+ if (!ff || pf === ff) {
127
+ // Same field: pattern key = fill key — simple 1:1 lookup
128
+ return Array.from(this.patternMap.entries()).map(([key, name]) => {
129
+ const color = this.colorMap.get(key) ?? { fill: '#ddd', stroke: '#666' }
130
+ return { id: toPatternId(key), name, fill: color.fill, stroke: color.stroke }
131
+ })
132
+ }
133
+ // Different fields: need two sets of defs in the SVG:
134
+ // 1. Simple defs (neutral background) — referenced by legend swatches via toPatternId(patternKey)
135
+ // 2. Composite defs (fill-colored background) — referenced by bars via toPatternId(fillKey::patternKey)
136
+ const defs = []
137
+ for (const [pk, name] of this.patternMap.entries()) {
138
+ defs.push({ id: toPatternId(pk), name, fill: '#ddd', stroke: '#666' })
139
+ }
140
+ const seenComposite = new SvelteSet()
141
+ for (const d of this.processedData) {
142
+ const fk = d[ff]
143
+ const pk = d[pf]
144
+ if (pk === null || pk === undefined) continue
145
+ const compositeKey = `${fk}::${pk}`
146
+ if (seenComposite.has(compositeKey)) continue
147
+ seenComposite.add(compositeKey)
148
+ const name = this.patternMap.get(pk) ?? PATTERN_ORDER[0]
149
+ const color = this.colorMap.get(fk) ?? { fill: '#ddd', stroke: '#666' }
150
+ defs.push({ id: toPatternId(compositeKey), name, fill: color.fill, stroke: color.stroke })
151
+ }
152
+ return defs
153
+ })()
154
+ )
155
+
156
+ /** @type {Map<unknown, string>} */
157
+ symbolMap = $derived(
158
+ this.#channels.symbol
159
+ ? assignSymbols(distinct(this.#rawData, this.#channels.symbol))
160
+ : new SvelteMap()
161
+ )
162
+
163
+ get innerWidth() {
164
+ return this.#width - this.#margin.left - this.#margin.right
165
+ }
166
+ get innerHeight() {
167
+ return this.#height - this.#margin.top - this.#margin.bottom
168
+ }
169
+
170
+ xScale = $derived(
171
+ this.#channels.x ? buildXScale(this.processedData, this.#channels.x, this.innerWidth) : null
172
+ )
173
+
174
+ yScale = $derived(
175
+ this.#channels.y
176
+ ? buildYScale(this.processedData, this.#channels.y, this.innerHeight, this.#layers)
177
+ : null
178
+ )
179
+
180
+ sizeScale = $derived(
181
+ this.#channels.size ? buildSizeScale(this.processedData, this.#channels.size) : null
182
+ )
183
+
184
+ bars = $derived(
185
+ this.xScale && this.yScale
186
+ ? buildBars(
187
+ this.processedData,
188
+ this.#channels,
189
+ this.xScale,
190
+ this.yScale,
191
+ this.colorMap,
192
+ this.patternMap
193
+ )
194
+ : []
195
+ )
196
+
197
+ lines = $derived(
198
+ this.xScale && this.yScale
199
+ ? buildLines(
200
+ this.processedData,
201
+ this.#channels,
202
+ this.xScale,
203
+ this.yScale,
204
+ this.colorMap,
205
+ this.#curve
206
+ )
207
+ : []
208
+ )
209
+
210
+ areas = $derived(
211
+ this.xScale && this.yScale
212
+ ? buildAreas(
213
+ this.processedData,
214
+ this.#channels,
215
+ this.xScale,
216
+ this.yScale,
217
+ this.colorMap,
218
+ this.#curve,
219
+ this.patternMap
220
+ )
221
+ : []
222
+ )
223
+
224
+ arcs = $derived(
225
+ this.#channels.y
226
+ ? buildArcs(this.processedData, this.#channels, this.colorMap, this.#width, this.#height)
227
+ : []
228
+ )
229
+
230
+ points = $derived(
231
+ this.xScale && this.yScale
232
+ ? buildPoints(
233
+ this.processedData,
234
+ this.#channels,
235
+ this.xScale,
236
+ this.yScale,
237
+ this.colorMap,
238
+ this.sizeScale,
239
+ this.symbolMap
240
+ )
241
+ : []
242
+ )
243
+
244
+ legendGroups = $derived(
245
+ buildLegendGroups(this.#channels, this.colorMap, this.patternMap, this.symbolMap)
246
+ )
247
+
248
+ get margin() {
249
+ return this.#margin
250
+ }
251
+ get width() {
252
+ return this.#width
253
+ }
254
+ get height() {
255
+ return this.#height
256
+ }
257
+ get mode() {
258
+ return this.#mode
259
+ }
260
+
261
+ /**
262
+ * @param {{ data?: Object[], channels?: Object, width?: number, height?: number, mode?: string, margin?: Object, layers?: Object[], curve?: string, stat?: string|Function }} opts
263
+ * Supported channel keys: `x`, `y`, `fill`, `color`, `pattern`, `symbol`, `size`, `label`.
264
+ * `frame` is reserved for future animation use (no-op).
265
+ */
266
+ update(opts = {}) {
267
+ if (opts.data !== undefined) this.#rawData = opts.data
268
+ if (opts.channels !== undefined) this.#channels = opts.channels
269
+ if (opts.width !== undefined) this.#width = opts.width
270
+ if (opts.height !== undefined) this.#height = opts.height
271
+ if (opts.mode !== undefined) this.#mode = opts.mode
272
+ if (opts.margin !== undefined) this.#margin = { ...DEFAULT_MARGIN, ...opts.margin }
273
+ if (opts.layers !== undefined) this.#layers = opts.layers
274
+ if (opts.curve !== undefined) this.#curve = opts.curve
275
+ if (opts.stat !== undefined) this.#stat = opts.stat
276
+ }
277
+ }
@@ -0,0 +1,51 @@
1
+ import masterPalette from '../palette.json'
2
+ import { defaultPreset } from '../preset.js'
3
+
4
+ /**
5
+ * Returns true if the value looks like a CSS color literal (not a field name).
6
+ * Supports hex (#rgb, #rrggbb, #rrggbbaa), and functional notations (rgb, hsl, oklch, etc.).
7
+ * @param {unknown} value
8
+ * @returns {boolean}
9
+ */
10
+ export function isLiteralColor(value) {
11
+ if (!value || typeof value !== 'string') return false
12
+ if (/^#([0-9a-fA-F]{3,8})$/.test(value)) return true
13
+ if (/^(rgb|rgba|hsl|hsla|oklch|oklab|hwb|lab|lch|color)\s*\(/i.test(value)) return true
14
+ return false
15
+ }
16
+
17
+ /**
18
+ * Extracts distinct values for a given field from the data array.
19
+ * @param {Object[]} data
20
+ * @param {string|null} field
21
+ * @returns {unknown[]}
22
+ */
23
+ export function distinct(data, field) {
24
+ if (!field) return []
25
+ return [...new Set(data.map((d) => d[field]))].filter((v) => v !== null && v !== undefined)
26
+ }
27
+
28
+ /**
29
+ * Assigns palette colors to an array of distinct values.
30
+ * @param {unknown[]} values
31
+ * @param {'light'|'dark'} mode
32
+ * @param {typeof defaultPreset} preset
33
+ * @returns {Map<unknown, {fill: string, stroke: string}>}
34
+ */
35
+ export function assignColors(values, mode = 'light', preset = defaultPreset) {
36
+ const { colors, shades } = preset
37
+ const { fill, stroke } = shades[mode]
38
+ return new Map(
39
+ values.map((v, i) => {
40
+ const colorName = colors[i % colors.length]
41
+ const group = masterPalette[colorName]
42
+ return [
43
+ v,
44
+ {
45
+ fill: group?.[fill] ?? '#888',
46
+ stroke: group?.[stroke] ?? '#444'
47
+ }
48
+ ]
49
+ })
50
+ )
51
+ }
@@ -0,0 +1,56 @@
1
+ import {} from './types.js'
2
+
3
+ /**
4
+ * @typedef {import('./types').ChartMargin} ChartMargin
5
+ * @typedef {import('./types').ChartDimensions} ChartDimensions
6
+ */
7
+
8
+ /**
9
+ * Default chart margin
10
+ * @type {ChartMargin}
11
+ */
12
+ export const DEFAULT_MARGIN = { top: 20, right: 30, bottom: 40, left: 50 }
13
+
14
+ /**
15
+ * Creates chart dimensions based on width, height and margins
16
+ *
17
+ * @param {number} width - Total chart width
18
+ * @param {number} height - Total chart height
19
+ * @param {ChartMargin} margin - Chart margins
20
+ * @returns {ChartDimensions} Chart dimensions
21
+ */
22
+ export function createDimensions(width = 600, height = 400, margin = DEFAULT_MARGIN) {
23
+ return {
24
+ width,
25
+ height,
26
+ margin: { ...margin },
27
+ innerWidth: width - margin.left - margin.right,
28
+ innerHeight: height - margin.top - margin.bottom
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Updates existing dimensions with new values
34
+ *
35
+ * @param {ChartDimensions} dimensions - Current dimensions
36
+ * @param {Object} updates - Values to update
37
+ * @param {number} [updates.width] - New width
38
+ * @param {number} [updates.height] - New height
39
+ * @param {ChartMargin} [updates.margin] - New margin
40
+ * @returns {ChartDimensions} Updated dimensions
41
+ */
42
+ export function updateDimensions(dimensions, updates = {}) {
43
+ const newDimensions = { ...dimensions }
44
+
45
+ if (updates.width !== undefined) newDimensions.width = updates.width
46
+ if (updates.height !== undefined) newDimensions.height = updates.height
47
+ if (updates.margin !== undefined) newDimensions.margin = { ...updates.margin }
48
+
49
+ // Recalculate inner dimensions
50
+ newDimensions.innerWidth =
51
+ newDimensions.width - newDimensions.margin.left - newDimensions.margin.right
52
+ newDimensions.innerHeight =
53
+ newDimensions.height - newDimensions.margin.top - newDimensions.margin.bottom
54
+
55
+ return newDimensions
56
+ }
@@ -0,0 +1,205 @@
1
+ import { createDimensions, updateDimensions } from './dimensions.svelte.js'
2
+ import { createScales, getOrigin } from './scales.svelte.js'
3
+ import { createBars, filterBars, createGroupedBars } from './bars.svelte.js'
4
+ import { createXAxis, createYAxis, createGrid, createTickAttributes } from './axes.svelte.js'
5
+ import { createLegend, filterByLegend, createLegendItemAttributes } from './legends.svelte.js'
6
+
7
+ /**
8
+ * Chart Brewing - A collection of pure functions for chart data preparation
9
+ */
10
+ export {
11
+ // Dimensions
12
+ createDimensions,
13
+ updateDimensions,
14
+
15
+ // Scales
16
+ createScales,
17
+ getOrigin,
18
+
19
+ // Bars
20
+ createBars,
21
+ filterBars,
22
+ createGroupedBars,
23
+
24
+ // Axes
25
+ createXAxis,
26
+ createYAxis,
27
+ createGrid,
28
+ createTickAttributes,
29
+
30
+ // Legends
31
+ createLegend,
32
+ filterByLegend,
33
+ createLegendItemAttributes
34
+ }
35
+
36
+ /**
37
+ * Main class that manages chart state and provides access to all brewing functions
38
+ */
39
+ export class ChartBrewer {
40
+ #data = []
41
+ #dimensions = createDimensions()
42
+ #scales = { x: null, y: null, color: null }
43
+ #fields = { x: null, y: null, color: null }
44
+ #options = {
45
+ padding: 0.2,
46
+ animationDuration: 300
47
+ }
48
+
49
+ /**
50
+ * Creates a new ChartBrewer instance
51
+ *
52
+ * @param {Object} options Configuration options
53
+ */
54
+ constructor(options = {}) {
55
+ this.#dimensions = createDimensions(options.width, options.height, options.margin)
56
+
57
+ if (options.padding !== undefined) this.#options.padding = options.padding
58
+ if (options.animationDuration !== undefined)
59
+ this.#options.animationDuration = options.animationDuration
60
+ }
61
+
62
+ /**
63
+ * Sets the data for the chart
64
+ *
65
+ * @param {Array} data Data array
66
+ * @returns {ChartBrewer} this for method chaining
67
+ */
68
+ setData(data) {
69
+ this.#data = Array.isArray(data) ? data : []
70
+ return this
71
+ }
72
+
73
+ /**
74
+ * Sets the field mappings for axes and color
75
+ *
76
+ * @param {Object} fields Field mappings
77
+ * @returns {ChartBrewer} this for method chaining
78
+ */
79
+ setFields({ x, y, color }) {
80
+ if (x !== undefined) this.#fields.x = x
81
+ if (y !== undefined) this.#fields.y = y
82
+ if (color !== undefined) this.#fields.color = color
83
+ return this
84
+ }
85
+
86
+ /**
87
+ * Sets the dimensions of the chart
88
+ *
89
+ * @param {Object} dimensions Chart dimensions
90
+ * @returns {ChartBrewer} this for method chaining
91
+ */
92
+ setDimensions({ width, height, margin }) {
93
+ this.#dimensions = updateDimensions(this.#dimensions, { width, height, margin })
94
+ return this
95
+ }
96
+
97
+ /**
98
+ * Creates scales based on data and dimensions
99
+ *
100
+ * @returns {ChartBrewer} this for method chaining
101
+ */
102
+ createScales() {
103
+ this.#scales = createScales(this.#data, this.#fields, this.#dimensions, {
104
+ padding: this.#options.padding
105
+ })
106
+ return this
107
+ }
108
+
109
+ /**
110
+ * Creates bar data for rendering
111
+ *
112
+ * @returns {Array} Data for rendering bars
113
+ */
114
+ createBars() {
115
+ return createBars(this.#data, this.#fields, this.#scales, { dimensions: this.#dimensions })
116
+ }
117
+
118
+ /**
119
+ * Creates x-axis tick data for rendering
120
+ *
121
+ * @param {Object} options Axis options
122
+ * @returns {Object} Axis rendering data
123
+ */
124
+ createXAxis(options = {}) {
125
+ return createXAxis(this.#scales, this.#dimensions, options)
126
+ }
127
+
128
+ /**
129
+ * Creates y-axis tick data for rendering
130
+ *
131
+ * @param {Object} options Axis options
132
+ * @returns {Object} Axis rendering data
133
+ */
134
+ createYAxis(options = {}) {
135
+ return createYAxis(this.#scales, this.#dimensions, options)
136
+ }
137
+
138
+ /**
139
+ * Creates grid line data for rendering
140
+ *
141
+ * @param {Object} options Grid options
142
+ * @returns {Object} Grid rendering data
143
+ */
144
+ createGrid(options = {}) {
145
+ return createGrid(this.#scales, this.#dimensions, options)
146
+ }
147
+
148
+ /**
149
+ * Creates legend data for rendering
150
+ *
151
+ * @param {Object} options Legend options
152
+ * @returns {Object} Legend rendering data
153
+ */
154
+ createLegend(options = {}) {
155
+ return createLegend(this.#data, this.#fields, this.#scales, {
156
+ ...options,
157
+ dimensions: this.#dimensions
158
+ })
159
+ }
160
+
161
+ /**
162
+ * Gets all chart dimensions
163
+ *
164
+ * @returns {Object} Chart dimensions
165
+ */
166
+ getDimensions() {
167
+ return { ...this.#dimensions }
168
+ }
169
+
170
+ /**
171
+ * Gets all chart scales
172
+ *
173
+ * @returns {Object} Chart scales
174
+ */
175
+ getScales() {
176
+ return { ...this.#scales }
177
+ }
178
+
179
+ /**
180
+ * Gets the animation duration
181
+ *
182
+ * @returns {number} Animation duration in ms
183
+ */
184
+ getAnimationDuration() {
185
+ return this.#options.animationDuration
186
+ }
187
+
188
+ /**
189
+ * Gets the data being used
190
+ *
191
+ * @returns {Array} Chart data
192
+ */
193
+ getData() {
194
+ return [...this.#data]
195
+ }
196
+
197
+ /**
198
+ * Gets the fields configuration
199
+ *
200
+ * @returns {Object} Fields configuration
201
+ */
202
+ getFields() {
203
+ return { ...this.#fields }
204
+ }
205
+ }