@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,57 @@
1
+ import { symbol, symbolCircle, symbolSquare, symbolTriangle, symbolDiamond, symbolCross, symbolStar } from 'd3-shape'
2
+
3
+ const SYMBOL_TYPES = [symbolCircle, symbolSquare, symbolTriangle, symbolDiamond, symbolCross, symbolStar]
4
+ const SYMBOL_NAMES = ['circle', 'square', 'triangle', 'diamond', 'cross', 'star']
5
+
6
+ /**
7
+ * Returns a Map assigning shape names to distinct values, cycling through available shapes.
8
+ * @param {unknown[]} values
9
+ * @returns {Map<unknown, string>}
10
+ */
11
+ export function assignSymbols(values) {
12
+ return new Map(values.map((v, i) => [v, SYMBOL_NAMES[i % SYMBOL_NAMES.length]]))
13
+ }
14
+
15
+ /**
16
+ * Builds an SVG path string for a given shape name and radius.
17
+ * @param {string} shapeName
18
+ * @param {number} r
19
+ * @returns {string}
20
+ */
21
+ export function buildSymbolPath(shapeName, r) {
22
+ const idx = SYMBOL_NAMES.indexOf(shapeName)
23
+ const type = idx >= 0 ? SYMBOL_TYPES[idx] : symbolCircle
24
+ return symbol().type(type).size(Math.PI * r * r)() ?? ''
25
+ }
26
+
27
+ /**
28
+ * Builds point geometry for scatter/bubble charts.
29
+ * @param {Object[]} data
30
+ * @param {{ x: string, y: string, color?: string, size?: string, symbol?: string }} channels
31
+ * @param {Function} xScale
32
+ * @param {Function} yScale
33
+ * @param {Map} colors
34
+ * @param {Function|null} sizeScale
35
+ * @param {Map<unknown, string>|null} symbolMap — maps symbol field value → shape name
36
+ * @param {number} defaultRadius
37
+ */
38
+ export function buildPoints(data, channels, xScale, yScale, colors, sizeScale, symbolMap, defaultRadius = 5) {
39
+ const { x: xf, y: yf, color: cf, size: sf, symbol: symf } = channels
40
+ return data.map((d) => {
41
+ const colorKey = cf ? d[cf] : null
42
+ const colorEntry = colors?.get(colorKey) ?? { fill: '#888', stroke: '#444' }
43
+ const r = sf && sizeScale ? sizeScale(d[sf]) : defaultRadius
44
+ const shapeName = symf && symbolMap ? (symbolMap.get(d[symf]) ?? 'circle') : null
45
+ const symbolPath = shapeName ? buildSymbolPath(shapeName, r) : null
46
+ return {
47
+ data: d,
48
+ cx: xScale(d[xf]),
49
+ cy: yScale(d[yf]),
50
+ r,
51
+ fill: colorEntry.fill,
52
+ stroke: colorEntry.stroke,
53
+ symbolPath,
54
+ key: colorKey
55
+ }
56
+ })
57
+ }
@@ -0,0 +1,90 @@
1
+ import { line, curveCatmullRom } from 'd3-shape'
2
+
3
+ // Relative widths at each stat anchor (fraction of max half-width)
4
+ const DENSITY_AT = { iqr_min: 0.08, q1: 0.55, median: 1.0, q3: 0.55, iqr_max: 0.08 }
5
+ const ANCHOR_ORDER = ['iqr_max', 'q3', 'median', 'q1', 'iqr_min']
6
+
7
+ /**
8
+ * Builds a closed violin shape path for each group.
9
+ * Input rows must have { q1, median, q3, iqr_min, iqr_max } from applyBoxStat.
10
+ *
11
+ * When `fill` differs from `x`, violins are sub-grouped within each x-band
12
+ * (one narrower violin per fill value per x category, like grouped bars).
13
+ * Violin body uses the lighter fill shade; outline uses the darker stroke shade.
14
+ *
15
+ * @param {Object[]} data
16
+ * @param {{ x: string, fill?: string }} channels
17
+ * `fill` drives violin interior and outline (defaults to x-field).
18
+ * @param {import('d3-scale').ScaleBand} xScale
19
+ * @param {import('d3-scale').ScaleLinear} yScale
20
+ * @param {Map} colors
21
+ * @returns {Array}
22
+ */
23
+ export function buildViolins(data, channels, xScale, yScale, colors) {
24
+ const { x: xf, fill: ff } = channels
25
+ const bw = typeof xScale.bandwidth === 'function' ? xScale.bandwidth() : 40
26
+ const grouped = ff && ff !== xf
27
+
28
+ const pathGen = line()
29
+ .x((pt) => pt.x)
30
+ .y((pt) => pt.y)
31
+ .curve(curveCatmullRom.alpha(0.5))
32
+
33
+ if (grouped) {
34
+ const fillValues = [...new Set(data.map((d) => d[ff]))]
35
+ const n = fillValues.length
36
+ const subBandWidth = bw / n
37
+ const halfMax = subBandWidth * 0.45
38
+
39
+ return data.map((d) => {
40
+ const fillVal = d[ff]
41
+ const subIndex = fillValues.indexOf(fillVal)
42
+ const bandStart = xScale(d[xf]) ?? 0
43
+ const cx = bandStart + subIndex * subBandWidth + subBandWidth / 2
44
+ const colorEntry = colors?.get(fillVal) ?? { fill: '#aaa', stroke: '#666' }
45
+
46
+ const rightPts = ANCHOR_ORDER.map((key) => ({
47
+ x: cx + halfMax * DENSITY_AT[key],
48
+ y: yScale(d[key])
49
+ }))
50
+ const leftPts = [...ANCHOR_ORDER].reverse().map((key) => ({
51
+ x: cx - halfMax * DENSITY_AT[key],
52
+ y: yScale(d[key])
53
+ }))
54
+
55
+ return {
56
+ data: d,
57
+ cx,
58
+ d: pathGen([...rightPts, ...leftPts, rightPts[0]]),
59
+ fill: colorEntry.fill,
60
+ stroke: colorEntry.stroke
61
+ }
62
+ })
63
+ }
64
+
65
+ // Non-grouped: one violin per x category
66
+ const halfMax = bw * 0.45
67
+
68
+ return data.map((d) => {
69
+ const fillKey = ff ? d[ff] : d[xf]
70
+ const colorEntry = colors?.get(fillKey) ?? { fill: '#aaa', stroke: '#666' }
71
+ const cx = (xScale(d[xf]) ?? 0) + (typeof xScale.bandwidth === 'function' ? bw / 2 : 0)
72
+
73
+ const rightPts = ANCHOR_ORDER.map((key) => ({
74
+ x: cx + halfMax * DENSITY_AT[key],
75
+ y: yScale(d[key])
76
+ }))
77
+ const leftPts = [...ANCHOR_ORDER].reverse().map((key) => ({
78
+ x: cx - halfMax * DENSITY_AT[key],
79
+ y: yScale(d[key])
80
+ }))
81
+
82
+ return {
83
+ data: d,
84
+ cx,
85
+ d: pathGen([...rightPts, ...leftPts, rightPts[0]]),
86
+ fill: colorEntry.fill,
87
+ stroke: colorEntry.stroke
88
+ }
89
+ })
90
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Converts a data key to a safe SVG element ID for pattern references.
3
+ * Spaces and non-word characters are replaced to avoid broken url(#...) refs.
4
+ * @param {unknown} key
5
+ * @returns {string}
6
+ */
7
+ export function toPatternId(key) {
8
+ return `chart-pat-${String(key).replace(/\s+/g, '-').replace(/[^\w-]/g, '_')}`
9
+ }
10
+
11
+ // Keys must match the keys in packages/chart/src/patterns/patterns.js
12
+ export const PATTERN_ORDER = [
13
+ 'diagonal',
14
+ 'dots',
15
+ 'triangles',
16
+ 'hatch',
17
+ 'lattice',
18
+ 'swell',
19
+ 'checkerboard',
20
+ 'waves',
21
+ 'petals'
22
+ ]
23
+
24
+ /**
25
+ * Assigns patterns from PATTERN_ORDER to an array of distinct values.
26
+ * @param {unknown[]} values
27
+ * @returns {Map<unknown, string>}
28
+ */
29
+ export function assignPatterns(values) {
30
+ return new Map(values.map((v, i) => [v, PATTERN_ORDER[i % PATTERN_ORDER.length]]))
31
+ }
@@ -0,0 +1,51 @@
1
+ import { scaleBand, scaleLinear, scaleSqrt } from 'd3-scale'
2
+ import { max, extent } from 'd3-array'
3
+
4
+ /**
5
+ * Builds an x scale (band for categorical, linear for numeric).
6
+ * @param {Object[]} data
7
+ * @param {string} field
8
+ * @param {number} width - inner width (pixels)
9
+ * @param {{ padding?: number }} opts
10
+ */
11
+ export function buildXScale(data, field, width, opts = {}) {
12
+ const values = [...new Set(data.map((d) => d[field]))]
13
+ const isNumeric = values.every((v) => typeof v === 'number' || (!isNaN(Number(v)) && v !== ''))
14
+ if (isNumeric) {
15
+ const [minVal, maxVal] = extent(data, (d) => Number(d[field]))
16
+ return scaleLinear().domain([minVal, maxVal]).range([0, width]).nice()
17
+ }
18
+ return scaleBand()
19
+ .domain(values)
20
+ .range([0, width])
21
+ .padding(opts.padding ?? 0.2)
22
+ }
23
+
24
+ function maxFromLayer(layer) {
25
+ if (layer.data && layer.y) return max(layer.data, (d) => Number(d[layer.y])) ?? 0
26
+ return 0
27
+ }
28
+
29
+ /**
30
+ * Builds a y linear scale from 0 to max, extended by any layer overrides.
31
+ * @param {Object[]} data
32
+ * @param {string} field
33
+ * @param {number} height - inner height (pixels)
34
+ * @param {Array<{data?: Object[], y?: string}>} layers
35
+ */
36
+ export function buildYScale(data, field, height, layers = []) {
37
+ const dataMax = max(data, (d) => Number(d[field])) ?? 0
38
+ const maxVal = layers.reduce((m, layer) => Math.max(m, maxFromLayer(layer)), dataMax)
39
+ return scaleLinear().domain([0, maxVal]).range([height, 0]).nice()
40
+ }
41
+
42
+ /**
43
+ * Builds a sqrt scale for bubble/point size.
44
+ * @param {Object[]} data
45
+ * @param {string} field
46
+ * @param {number} maxRadius
47
+ */
48
+ export function buildSizeScale(data, field, maxRadius = 20) {
49
+ const maxVal = max(data, (d) => Number(d[field])) ?? 1
50
+ return scaleSqrt().domain([0, maxVal]).range([0, maxRadius])
51
+ }
@@ -0,0 +1,82 @@
1
+ import { SvelteSet } from 'svelte/reactivity'
2
+ import { min, max } from 'd3-array'
3
+ import { scaleLinear, scaleOrdinal } from 'd3-scale'
4
+ import { schemeCategory10 } from 'd3-scale-chromatic'
5
+ import {} from './types.js'
6
+ import { buildXScale } from '../xscale.js'
7
+
8
+ /**
9
+ * @typedef {import('./types').ChartScales} ChartScales
10
+ * @typedef {import('./types').ScaleFields} ScaleFields
11
+ * @typedef {import('./types').ChartDimensions} ChartDimensions
12
+ */
13
+
14
+ /**
15
+ * @param {Array} data
16
+ * @param {string} colorField
17
+ * @returns {import('d3-scale').ScaleOrdinal}
18
+ */
19
+ function buildColorScale(data, colorField) {
20
+ const colorValues = [...new SvelteSet(data.map((d) => d[colorField]))]
21
+ return scaleOrdinal().domain(colorValues).range(schemeCategory10)
22
+ }
23
+
24
+ /**
25
+ * @param {Array} data
26
+ * @param {string} yField
27
+ * @param {Object} dimensions
28
+ * @returns {import('d3-scale').ScaleContinuousNumeric}
29
+ */
30
+ function buildYScale(data, yField, dimensions) {
31
+ const yValues = data.map((d) => d[yField])
32
+ return scaleLinear()
33
+ .domain([0, max(yValues) * 1.1])
34
+ .nice()
35
+ .range([dimensions.innerHeight, 0])
36
+ }
37
+
38
+ /**
39
+ * @param {Array} data
40
+ * @param {ScaleFields} fields
41
+ * @returns {boolean}
42
+ */
43
+ function hasRequiredFields(data, fields) {
44
+ return Boolean(data && data.length && fields.x && fields.y)
45
+ }
46
+
47
+ /**
48
+ * Creates scales based on data, fields, and dimensions
49
+ *
50
+ * @param {Array} data - Chart data
51
+ * @param {ScaleFields} fields - Field mappings
52
+ * @param {Object} dimensions - Chart dimensions
53
+ * @param {Object} options - Scale options
54
+ * @param {number} [options.padding=0.2] - Padding for band scales
55
+ * @returns {ChartScales} Chart scales
56
+ */
57
+ export function createScales(data, fields, dimensions, options = {}) {
58
+ if (!hasRequiredFields(data, fields)) return { x: null, y: null, color: null }
59
+
60
+ const padding = options.padding ?? 0.2
61
+ const xValues = data.map((d) => d[fields.x])
62
+
63
+ return {
64
+ x: buildXScale(xValues, dimensions, padding),
65
+ y: buildYScale(data, fields.y, dimensions),
66
+ color: fields.color ? buildColorScale(data, fields.color) : null
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Gets the origin coordinates for the axes
72
+ *
73
+ * @param {ChartScales} scales - Chart scales
74
+ * @param {Object} dimensions - Chart dimensions
75
+ * @returns {Object} Origin coordinates
76
+ */
77
+ export function getOrigin(scales, dimensions) {
78
+ return {
79
+ x: scales.y ? scales.y(0) : dimensions.innerHeight,
80
+ y: scales.x ? (scales.x.ticks ? scales.x(Math.max(0, min(scales.x.domain()))) : 0) : 0
81
+ }
82
+ }
@@ -0,0 +1,66 @@
1
+ import { sum, mean, min, max, quantile, ascending } from 'd3-array'
2
+ import { dataset } from '@rokkit/data'
3
+
4
+ function sortedQuantile(values, p) {
5
+ return quantile([...values].sort(ascending), p)
6
+ }
7
+
8
+ /**
9
+ * Built-in reduction functions. Each receives an array of numeric values.
10
+ * @type {Record<string, (values: number[]) => number>}
11
+ */
12
+ export const STAT_FNS = {
13
+ sum,
14
+ mean,
15
+ min,
16
+ max,
17
+ count: (values) => values.length
18
+ }
19
+
20
+ /**
21
+ * Computes box plot quartile statistics grouped by x (and optionally color).
22
+ * Output rows have { q1, median, q3, iqr_min, iqr_max } replacing the raw y values.
23
+ *
24
+ * @param {Object[]} data
25
+ * @param {{ x?: string, y?: string, color?: string }} channels
26
+ * @returns {Object[]}
27
+ */
28
+ export function applyBoxStat(data, channels) {
29
+ const { x: xf, y: yf, color: cf } = channels
30
+ if (!xf || !yf) return data
31
+ const by = [xf, cf].filter(Boolean)
32
+ return dataset(data)
33
+ .groupBy(...by)
34
+ .summarize((row) => row[yf], {
35
+ q1: (v) => sortedQuantile(v, 0.25),
36
+ median: (v) => sortedQuantile(v, 0.5),
37
+ q3: (v) => sortedQuantile(v, 0.75),
38
+ iqr_min: (v) => { const q1 = sortedQuantile(v, 0.25); const q3 = sortedQuantile(v, 0.75); return q1 - 1.5 * (q3 - q1) },
39
+ iqr_max: (v) => { const q1 = sortedQuantile(v, 0.25); const q3 = sortedQuantile(v, 0.75); return q3 + 1.5 * (q3 - q1) }
40
+ })
41
+ .rollup()
42
+ .select()
43
+ }
44
+
45
+ /**
46
+ * Aggregates data by one or more grouping fields, reducing the value field
47
+ * using the given stat. Accepts a built-in name or a custom function.
48
+ *
49
+ * @param {Object[]} data
50
+ * @param {{ by: string[], value: string, stat: string|Function }} opts
51
+ * @returns {Object[]}
52
+ */
53
+ function isIdentityOrEmpty(stat, by, value) {
54
+ return stat === 'identity' || by.length === 0 || value === null || value === undefined
55
+ }
56
+
57
+ export function applyAggregate(data, { by, value, stat }) {
58
+ if (isIdentityOrEmpty(stat, by, value)) return data
59
+ const fn = typeof stat === 'function' ? stat : STAT_FNS[stat]
60
+ if (fn === null || fn === undefined) return data
61
+ return dataset(data)
62
+ .groupBy(...by)
63
+ .summarize((row) => row[value], { [value]: fn })
64
+ .rollup()
65
+ .select()
66
+ }
@@ -0,0 +1,10 @@
1
+ export const SYMBOL_ORDER = ['circle', 'square', 'triangle', 'diamond', 'plus', 'cross', 'star']
2
+
3
+ /**
4
+ * Assigns shapes from SYMBOL_ORDER to an array of distinct values.
5
+ * @param {unknown[]} values
6
+ * @returns {Map<unknown, string>}
7
+ */
8
+ export function assignSymbols(values) {
9
+ return new Map(values.map((v, i) => [v, SYMBOL_ORDER[i % SYMBOL_ORDER.length]]))
10
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @typedef {Object} ChartMargin
3
+ * @property {number} top - Top margin
4
+ * @property {number} right - Right margin
5
+ * @property {number} bottom - Bottom margin
6
+ * @property {number} left - Left margin
7
+ */
8
+
9
+ /**
10
+ * @typedef {Object} ChartDimensions
11
+ * @property {number} width - Total chart width
12
+ * @property {number} height - Total chart height
13
+ * @property {ChartMargin} margin - Chart margins
14
+ * @property {number} innerWidth - Chart width without margins
15
+ * @property {number} innerHeight - Chart height without margins
16
+ */
17
+
18
+ /**
19
+ * @typedef {Object} ChartScales
20
+ * @property {Function} x - X-axis scale function
21
+ * @property {Function} y - Y-axis scale function
22
+ * @property {Function} color - Color scale function
23
+ */
24
+
25
+ /**
26
+ * @typedef {Object} ScaleFields
27
+ * @property {string} x - X-axis field
28
+ * @property {string} y - Y-axis field
29
+ * @property {string} color - Color field
30
+ */
31
+
32
+ /**
33
+ * @typedef {Object} TickData
34
+ * @property {*} value - Tick value
35
+ * @property {number} position - Tick position in pixels
36
+ * @property {string} formattedValue - Formatted tick label
37
+ */
38
+
39
+ /**
40
+ * @typedef {Object} AxisData
41
+ * @property {TickData[]} ticks - Tick data
42
+ * @property {string} label - Axis label
43
+ * @property {string} transform - SVG transform attribute value
44
+ * @property {string} labelTransform - SVG transform for the label
45
+ */
46
+
47
+ /**
48
+ * @typedef {Object} BarData
49
+ * @property {Object} data - Original data point
50
+ * @property {number} x - X position
51
+ * @property {number} y - Y position
52
+ * @property {number} width - Width of the bar
53
+ * @property {number} height - Height of the bar
54
+ * @property {string} color - Color of the bar
55
+ */
56
+
57
+ /**
58
+ * @typedef {Object} LegendItem
59
+ * @property {*} value - Legend item value
60
+ * @property {string} color - Item color
61
+ * @property {number} y - Y position
62
+ * @property {string} shape - Shape type ('rect' or 'circle')
63
+ * @property {number} markerSize - Size of the marker
64
+ */
65
+
66
+ /**
67
+ * @typedef {Object} LegendData
68
+ * @property {LegendItem[]} items - Legend items
69
+ * @property {string} title - Legend title
70
+ * @property {string} transform - SVG transform attribute value
71
+ */
72
+
73
+ export {}
@@ -0,0 +1,220 @@
1
+ import { min, max } from 'd3-array'
2
+ import { scaleBand, scaleLinear, scaleTime } from 'd3-scale'
3
+
4
+ function getOriginValue(scale) {
5
+ return scale.ticks ? scale(Math.max(0, Math.min(...scale.domain()))) : scale.range()[0]
6
+ }
7
+
8
+ function getScale(domain, range, padding = 0) {
9
+ if (domain.some(isNaN)) {
10
+ return scaleBand().domain(domain).range(range).padding(padding)
11
+ } else if (domain[0] instanceof Date) {
12
+ return scaleTime()
13
+ .domain([min(domain), max(domain)])
14
+ .range(range)
15
+ .nice()
16
+ }
17
+
18
+ return scaleLinear()
19
+ .domain([min([0, ...domain]), max([0, ...domain])])
20
+ .range(range)
21
+ .nice()
22
+ }
23
+
24
+ class Chart {
25
+ // data = []
26
+ // width = 512
27
+ // height = 512
28
+ // origin = { x: 0, y: 0 }
29
+ // range = {
30
+ // x: [0, this.width],
31
+ // y: [this.height, 0]
32
+ // }
33
+ // x
34
+ // y
35
+ // stat = 'identity'
36
+ // scale
37
+ // fill
38
+ // color
39
+ // value
40
+ // shape
41
+ // valueFormat
42
+ // valueLabel
43
+ // domain
44
+ // margin
45
+ // spacing
46
+ // padding
47
+ // flipCoords = false
48
+
49
+ #initFields(opts) {
50
+ this.width = Number(opts.width) || 2048
51
+ this.height = Number(opts.height) || 2048
52
+ this.flipCoords = opts.flipCoords || false
53
+ this.x = opts.x
54
+ this.y = opts.y
55
+ this.value = opts.value || opts.y
56
+ this.valueLabel = opts.valueLabel || this.value
57
+ this.valueFormat = opts.valueFormat || ((d) => d)
58
+ this.fill = opts.fill || opts.x
59
+ this.color = opts.color || opts.fill
60
+ this.shape = opts.shape || opts.fill
61
+ this.padding = opts.padding !== undefined ? Number(opts.padding) : 32
62
+ this.spacing = (Number(opts.spacing) >= 0 && Number(opts.spacing) <= 0.5) ? Number(opts.spacing) : 0
63
+ this.margin = {
64
+ top: Number(opts.margin?.top) || 0,
65
+ left: Number(opts.margin?.left) || 0,
66
+ right: Number(opts.margin?.right) || 0,
67
+ bottom: Number(opts.margin?.bottom) || 0
68
+ }
69
+ this.stat = opts.stat || 'identity'
70
+ }
71
+
72
+ #initDomain(data) {
73
+ this.domain = {
74
+ x: [...new Set(data.map((d) => d[this.x]))],
75
+ y: [...new Set(data.map((d) => d[this.y]))]
76
+ }
77
+ if (this.flipCoords) {
78
+ this.domain = { y: this.domain.x, x: this.domain.y }
79
+ }
80
+ }
81
+
82
+ #initData(data) {
83
+ this.data = data.map((d) => ({
84
+ x: this.flipCoords ? d[this.y] : d[this.x],
85
+ y: this.flipCoords ? d[this.x] : d[this.y],
86
+ // fill: d[this.fill],
87
+ color: d[this.color]
88
+ // shape: d[this.shape]
89
+ }))
90
+ }
91
+
92
+ constructor(data, opts) {
93
+ this.#initFields(opts)
94
+ this.#initDomain(data)
95
+ this.#initData(data)
96
+ this.refresh()
97
+ }
98
+
99
+ padding(value) {
100
+ this.padding = value
101
+ return this.refresh()
102
+ }
103
+
104
+ margin(value) {
105
+ this.margin = value
106
+ return this.refresh()
107
+ }
108
+
109
+ refresh() {
110
+ this.range = {
111
+ x: [this.margin.left + this.padding, this.width - this.margin.right - this.padding],
112
+ y: [this.height - this.padding - this.margin.bottom, this.margin.top + this.padding]
113
+ }
114
+
115
+ const scale = {
116
+ x: getScale(this.domain.x, this.range.x, this.spacing),
117
+ y: getScale(this.domain.y, this.range.y, this.spacing)
118
+ }
119
+
120
+ // scale['value'] = this.value === this.x ? scale.x : scale.y
121
+
122
+ this.origin = {
123
+ x: getOriginValue(scale.x),
124
+ y: getOriginValue(scale.y)
125
+ }
126
+
127
+ this.scale = scale
128
+
129
+ return this
130
+ }
131
+
132
+ // get scale() {
133
+ // return this.scale
134
+ // }
135
+ // get origin() {
136
+ // return this.origin
137
+ // }
138
+ // get margin() {
139
+ // return this.margin
140
+ // }
141
+ // get range() {
142
+ // const [x1, x2] = this.scale.x.range()
143
+ // const [y1, y2] = this.scale.y.range()
144
+
145
+ // return { x1, y1, x2, y2 }
146
+ // }
147
+ // get data() {
148
+ // // aggregate data group by x,y,fill,shape, color
149
+ // // stat = [min, max, avg, std, q1, q3, median, sum, count, box, all]
150
+
151
+ // return this.data
152
+ // }
153
+ // get width() {
154
+ // return this.width
155
+ // }
156
+ // get height() {
157
+ // return this.height
158
+ // }
159
+ // set width(value) {
160
+ // this.width = value
161
+ // }
162
+ // set height(value) {
163
+ // this.height = value
164
+ // }
165
+ // get domain() {
166
+ // return this.domain
167
+ // }
168
+ // get flipCoords() {
169
+ // return this.flipCoords
170
+ // }
171
+ aggregate(value, stat) {
172
+ this.value = value
173
+ this.stat = stat
174
+
175
+ // this.data = nest(this.data)
176
+ }
177
+
178
+ ticks(axis, count, fontSize = 8) {
179
+ const scale = this.scale[axis]
180
+ const [minRange, maxRange] = scale.range()
181
+ let ticks = []
182
+ let offset = 0
183
+
184
+ count = count || Math.abs((maxRange - minRange) / (fontSize * (axis === 'y' ? 8 : 8)))
185
+
186
+ if (scale.ticks) {
187
+ ticks = scale.ticks(Math.round(count))
188
+ } else {
189
+ offset = scale.bandwidth() / 2
190
+ count = Math.min(Math.round(count), scale.domain().length)
191
+
192
+ ticks = scale.domain()
193
+ if (count < scale.domain().length) {
194
+ const diff = scale.domain().length - count
195
+ ticks = ticks.filter((d, i) => i % diff === 0)
196
+ }
197
+ }
198
+
199
+ ticks = ticks
200
+ .map((t) => ({
201
+ label: t,
202
+ pos: scale(t)
203
+ }))
204
+ .map(({ label, pos }) => ({
205
+ label,
206
+ offset: {
207
+ x: axis === 'x' ? offset : 0,
208
+ y: axis === 'y' ? offset : 0
209
+ },
210
+ x: axis === 'x' ? pos : this.origin.x,
211
+ y: axis === 'y' ? pos : this.origin.y
212
+ }))
213
+
214
+ return ticks
215
+ }
216
+ }
217
+
218
+ export function chart(data, aes) {
219
+ return new Chart(data, aes)
220
+ }