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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +150 -46
  3. package/dist/Plot/index.d.ts +9 -0
  4. package/dist/PlotState.svelte.d.ts +47 -0
  5. package/dist/crossfilter/createCrossFilter.svelte.d.ts +15 -0
  6. package/dist/elements/index.d.ts +6 -0
  7. package/dist/geoms/lib/areas.d.ts +52 -0
  8. package/dist/geoms/lib/bars.d.ts +3 -0
  9. package/dist/index.d.ts +51 -0
  10. package/dist/lib/brewer.d.ts +9 -0
  11. package/dist/lib/brewing/BoxBrewer.svelte.d.ts +10 -0
  12. package/dist/lib/brewing/CartesianBrewer.svelte.d.ts +8 -0
  13. package/dist/lib/brewing/PieBrewer.svelte.d.ts +8 -0
  14. package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +9 -0
  15. package/dist/lib/brewing/axes.svelte.d.ts +66 -0
  16. package/dist/lib/brewing/bars.svelte.d.ts +56 -0
  17. package/dist/lib/brewing/brewer.svelte.d.ts +145 -0
  18. package/dist/lib/brewing/colors.d.ts +17 -0
  19. package/dist/lib/brewing/dimensions.svelte.d.ts +35 -0
  20. package/dist/lib/brewing/index.svelte.d.ts +118 -0
  21. package/dist/lib/brewing/legends.svelte.d.ts +48 -0
  22. package/dist/lib/brewing/marks/arcs.d.ts +17 -0
  23. package/dist/lib/brewing/marks/areas.d.ts +31 -0
  24. package/dist/lib/brewing/marks/bars.d.ts +1 -0
  25. package/dist/lib/brewing/marks/boxes.d.ts +24 -0
  26. package/dist/lib/brewing/marks/lines.d.ts +24 -0
  27. package/dist/lib/brewing/marks/points.d.ts +40 -0
  28. package/dist/lib/brewing/marks/violins.d.ts +20 -0
  29. package/dist/lib/brewing/patterns.d.ts +14 -0
  30. package/dist/lib/brewing/scales.d.ts +28 -0
  31. package/dist/lib/brewing/scales.svelte.d.ts +24 -0
  32. package/dist/lib/brewing/stats.d.ts +31 -0
  33. package/dist/lib/brewing/symbols.d.ts +7 -0
  34. package/dist/lib/brewing/types.d.ts +162 -0
  35. package/dist/lib/chart.d.ts +40 -0
  36. package/dist/lib/context.d.ts +13 -0
  37. package/dist/lib/grid.d.ts +72 -0
  38. package/dist/lib/plot/chartProps.d.ts +177 -0
  39. package/dist/lib/plot/crossfilter.d.ts +13 -0
  40. package/dist/lib/plot/facet.d.ts +24 -0
  41. package/dist/lib/plot/frames.d.ts +47 -0
  42. package/dist/lib/plot/helpers.d.ts +3 -0
  43. package/dist/lib/plot/preset.d.ts +29 -0
  44. package/dist/lib/plot/scales.d.ts +5 -0
  45. package/dist/lib/plot/stat.d.ts +32 -0
  46. package/dist/lib/plot/types.d.ts +89 -0
  47. package/dist/lib/scales.svelte.d.ts +35 -0
  48. package/dist/lib/swatch.d.ts +12 -0
  49. package/dist/lib/ticks.d.ts +36 -0
  50. package/dist/lib/utils.d.ts +61 -0
  51. package/dist/lib/xscale.d.ts +11 -0
  52. package/dist/patterns/index.d.ts +4 -0
  53. package/dist/patterns/patterns.d.ts +72 -0
  54. package/dist/patterns/scale.d.ts +30 -0
  55. package/dist/symbols/constants/index.d.ts +1 -0
  56. package/dist/symbols/index.d.ts +5 -0
  57. package/package.json +41 -45
  58. package/src/AnimatedPlot.svelte +214 -0
  59. package/src/Chart.svelte +101 -0
  60. package/src/FacetPlot/Panel.svelte +23 -0
  61. package/src/FacetPlot.svelte +90 -0
  62. package/src/Plot/Arc.svelte +29 -0
  63. package/src/Plot/Area.svelte +25 -0
  64. package/src/Plot/Axis.svelte +73 -0
  65. package/src/Plot/Bar.svelte +96 -0
  66. package/src/Plot/Grid.svelte +30 -0
  67. package/src/Plot/Legend.svelte +167 -0
  68. package/src/Plot/Line.svelte +27 -0
  69. package/src/Plot/Point.svelte +27 -0
  70. package/src/Plot/Root.svelte +107 -0
  71. package/src/Plot/Timeline.svelte +95 -0
  72. package/src/Plot/Tooltip.svelte +81 -0
  73. package/src/Plot/index.js +9 -0
  74. package/src/Plot.svelte +189 -0
  75. package/src/PlotState.svelte.js +278 -0
  76. package/src/Sparkline.svelte +69 -0
  77. package/src/Symbol.svelte +21 -0
  78. package/src/Texture.svelte +18 -0
  79. package/src/charts/AreaChart.svelte +25 -0
  80. package/src/charts/BarChart.svelte +26 -0
  81. package/src/charts/BoxPlot.svelte +21 -0
  82. package/src/charts/BubbleChart.svelte +23 -0
  83. package/src/charts/LineChart.svelte +26 -0
  84. package/src/charts/PieChart.svelte +25 -0
  85. package/src/charts/ScatterPlot.svelte +25 -0
  86. package/src/charts/ViolinPlot.svelte +21 -0
  87. package/src/crossfilter/CrossFilter.svelte +38 -0
  88. package/src/crossfilter/FilterBar.svelte +32 -0
  89. package/src/crossfilter/FilterSlider.svelte +79 -0
  90. package/src/crossfilter/createCrossFilter.svelte.js +113 -0
  91. package/src/elements/Bar.svelte +22 -24
  92. package/src/elements/ColorRamp.svelte +20 -22
  93. package/src/elements/ContinuousLegend.svelte +20 -17
  94. package/src/elements/DefinePatterns.svelte +24 -0
  95. package/src/elements/DiscreteLegend.svelte +15 -15
  96. package/src/elements/Label.svelte +4 -8
  97. package/src/elements/SymbolGrid.svelte +22 -0
  98. package/src/elements/index.js +6 -0
  99. package/src/examples/BarChartExample.svelte +81 -0
  100. package/src/geoms/Arc.svelte +81 -0
  101. package/src/geoms/Area.svelte +50 -0
  102. package/src/geoms/Bar.svelte +142 -0
  103. package/src/geoms/Box.svelte +101 -0
  104. package/src/geoms/LabelPill.svelte +17 -0
  105. package/src/geoms/Line.svelte +100 -0
  106. package/src/geoms/Point.svelte +100 -0
  107. package/src/geoms/Violin.svelte +44 -0
  108. package/src/geoms/lib/areas.js +131 -0
  109. package/src/geoms/lib/bars.js +172 -0
  110. package/src/index.js +67 -16
  111. package/src/lib/brewer.js +25 -0
  112. package/src/lib/brewing/BoxBrewer.svelte.js +56 -0
  113. package/src/lib/brewing/CartesianBrewer.svelte.js +16 -0
  114. package/src/lib/brewing/PieBrewer.svelte.js +14 -0
  115. package/src/lib/brewing/ViolinBrewer.svelte.js +55 -0
  116. package/src/lib/brewing/axes.svelte.js +270 -0
  117. package/src/lib/brewing/bars.svelte.js +201 -0
  118. package/src/lib/brewing/brewer.svelte.js +229 -0
  119. package/src/lib/brewing/colors.js +22 -0
  120. package/src/lib/brewing/dimensions.svelte.js +56 -0
  121. package/src/lib/brewing/index.svelte.js +205 -0
  122. package/src/lib/brewing/legends.svelte.js +137 -0
  123. package/src/lib/brewing/marks/arcs.js +43 -0
  124. package/src/lib/brewing/marks/areas.js +59 -0
  125. package/src/lib/brewing/marks/bars.js +49 -0
  126. package/src/lib/brewing/marks/boxes.js +75 -0
  127. package/src/lib/brewing/marks/lines.js +48 -0
  128. package/src/lib/brewing/marks/points.js +57 -0
  129. package/src/lib/brewing/marks/violins.js +90 -0
  130. package/src/lib/brewing/patterns.js +31 -0
  131. package/src/lib/brewing/scales.js +51 -0
  132. package/src/lib/brewing/scales.svelte.js +82 -0
  133. package/src/lib/brewing/stats.js +62 -0
  134. package/src/lib/brewing/symbols.js +10 -0
  135. package/src/lib/brewing/types.js +73 -0
  136. package/src/lib/chart.js +213 -0
  137. package/src/lib/context.js +131 -0
  138. package/src/lib/grid.js +85 -0
  139. package/src/lib/plot/chartProps.js +76 -0
  140. package/src/lib/plot/crossfilter.js +16 -0
  141. package/src/lib/plot/facet.js +58 -0
  142. package/src/lib/plot/frames.js +80 -0
  143. package/src/lib/plot/helpers.js +14 -0
  144. package/src/lib/plot/preset.js +53 -0
  145. package/src/lib/plot/scales.js +56 -0
  146. package/src/lib/plot/stat.js +92 -0
  147. package/src/lib/plot/types.js +65 -0
  148. package/src/lib/scales.svelte.js +151 -0
  149. package/src/lib/swatch.js +13 -0
  150. package/src/lib/ticks.js +46 -0
  151. package/src/lib/utils.js +111 -118
  152. package/src/lib/xscale.js +31 -0
  153. package/src/patterns/DefinePatterns.svelte +32 -0
  154. package/src/patterns/PatternDef.svelte +27 -0
  155. package/src/patterns/README.md +3 -0
  156. package/src/patterns/index.js +4 -0
  157. package/src/patterns/patterns.js +208 -0
  158. package/src/patterns/scale.js +87 -0
  159. package/src/spec/chart-spec.js +29 -0
  160. package/src/symbols/RoundedSquare.svelte +33 -0
  161. package/src/symbols/Shape.svelte +37 -0
  162. package/src/symbols/constants/index.js +4 -0
  163. package/src/symbols/index.js +9 -0
  164. package/src/symbols/outline.svelte +60 -0
  165. package/src/symbols/solid.svelte +60 -0
  166. package/src/chart/FacetGrid.svelte +0 -51
  167. package/src/chart/Grid.svelte +0 -34
  168. package/src/chart/Legend.svelte +0 -16
  169. package/src/chart/PatternDefs.svelte +0 -13
  170. package/src/chart/Swatch.svelte +0 -93
  171. package/src/chart/SwatchButton.svelte +0 -29
  172. package/src/chart/SwatchGrid.svelte +0 -55
  173. package/src/chart/Symbol.svelte +0 -37
  174. package/src/chart/Texture.svelte +0 -16
  175. package/src/chart/TexturedShape.svelte +0 -27
  176. package/src/chart/TimelapseChart.svelte +0 -97
  177. package/src/chart/Timer.svelte +0 -27
  178. package/src/chart.js +0 -9
  179. package/src/components/charts/Axis.svelte +0 -66
  180. package/src/components/charts/Chart.svelte +0 -35
  181. package/src/components/index.js +0 -23
  182. package/src/components/lib/axis.js +0 -0
  183. package/src/components/lib/chart.js +0 -187
  184. package/src/components/lib/color.js +0 -327
  185. package/src/components/lib/funnel.js +0 -204
  186. package/src/components/lib/index.js +0 -19
  187. package/src/components/lib/pattern.js +0 -190
  188. package/src/components/lib/rollup.js +0 -55
  189. package/src/components/lib/shape.js +0 -199
  190. package/src/components/lib/summary.js +0 -145
  191. package/src/components/lib/theme.js +0 -23
  192. package/src/components/lib/timer.js +0 -41
  193. package/src/components/lib/utils.js +0 -165
  194. package/src/components/plots/BarPlot.svelte +0 -36
  195. package/src/components/plots/BoxPlot.svelte +0 -54
  196. package/src/components/plots/ScatterPlot.svelte +0 -30
  197. package/src/components/store.js +0 -70
  198. package/src/constants.js +0 -66
  199. package/src/elements/PatternDefs.svelte +0 -13
  200. package/src/elements/PatternMask.svelte +0 -20
  201. package/src/elements/Symbol.svelte +0 -38
  202. package/src/elements/Tooltip.svelte +0 -23
  203. package/src/funnel.svelte +0 -35
  204. package/src/geom.js +0 -105
  205. package/src/lib/axis.js +0 -75
  206. package/src/lib/colors.js +0 -32
  207. package/src/lib/geom.js +0 -4
  208. package/src/lib/shapes.js +0 -144
  209. package/src/lib/timer.js +0 -44
  210. package/src/lookup.js +0 -29
  211. package/src/plots/BarPlot.svelte +0 -55
  212. package/src/plots/BoxPlot.svelte +0 -0
  213. package/src/plots/FunnelPlot.svelte +0 -33
  214. package/src/plots/HeatMap.svelte +0 -5
  215. package/src/plots/HeatMapCalendar.svelte +0 -129
  216. package/src/plots/LinePlot.svelte +0 -55
  217. package/src/plots/Plot.svelte +0 -25
  218. package/src/plots/RankBarPlot.svelte +0 -38
  219. package/src/plots/ScatterPlot.svelte +0 -20
  220. package/src/plots/ViolinPlot.svelte +0 -11
  221. package/src/plots/heatmap.js +0 -70
  222. package/src/plots/index.js +0 -10
  223. package/src/swatch.js +0 -11
@@ -0,0 +1,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
+ /**
25
+ * Builds a y linear scale from 0 to max, extended by any layer overrides.
26
+ * @param {Object[]} data
27
+ * @param {string} field
28
+ * @param {number} height - inner height (pixels)
29
+ * @param {Array<{data?: Object[], y?: string}>} layers
30
+ */
31
+ export function buildYScale(data, field, height, layers = []) {
32
+ let maxVal = max(data, (d) => Number(d[field])) ?? 0
33
+ for (const layer of layers) {
34
+ if (layer.data && layer.y) {
35
+ const layerMax = max(layer.data, (d) => Number(d[layer.y])) ?? 0
36
+ if (layerMax > maxVal) maxVal = layerMax
37
+ }
38
+ }
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,62 @@
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
+ export function applyAggregate(data, { by, value, stat }) {
54
+ if (stat === 'identity' || by.length === 0 || value === null || value === undefined) return data
55
+ const fn = typeof stat === 'function' ? stat : STAT_FNS[stat]
56
+ if (fn === null || fn === undefined) return data
57
+ return dataset(data)
58
+ .groupBy(...by)
59
+ .summarize((row) => row[value], { [value]: fn })
60
+ .rollup()
61
+ .select()
62
+ }
@@ -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,213 @@
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
+ constructor(data, 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
+
62
+ this.padding = opts.padding !== undefined ? Number(opts.padding) : 32
63
+
64
+ this.spacing =
65
+ Number(opts.spacing) >= 0 && Number(opts.spacing) <= 0.5 ? Number(opts.spacing) : 0
66
+ this.margin = {
67
+ top: Number(opts.margin?.top) || 0,
68
+ left: Number(opts.margin?.left) || 0,
69
+ right: Number(opts.margin?.right) || 0,
70
+ bottom: Number(opts.margin?.bottom) || 0
71
+ }
72
+ this.domain = {
73
+ x: [...new Set(data.map((d) => d[this.x]))],
74
+ y: [...new Set(data.map((d) => d[this.y]))]
75
+ }
76
+ if (this.flipCoords) {
77
+ this.domain = { y: this.domain.x, x: this.domain.y }
78
+ }
79
+ this.stat = opts.stat || 'identity'
80
+
81
+ this.data = data.map((d) => ({
82
+ x: this.flipCoords ? d[this.y] : d[this.x],
83
+ y: this.flipCoords ? d[this.x] : d[this.y],
84
+ // fill: d[this.fill],
85
+ color: d[this.color]
86
+ // shape: d[this.shape]
87
+ }))
88
+
89
+ this.refresh()
90
+ }
91
+
92
+ padding(value) {
93
+ this.padding = value
94
+ return this.refresh()
95
+ }
96
+
97
+ margin(value) {
98
+ this.margin = value
99
+ return this.refresh()
100
+ }
101
+
102
+ refresh() {
103
+ this.range = {
104
+ x: [this.margin.left + this.padding, this.width - this.margin.right - this.padding],
105
+ y: [this.height - this.padding - this.margin.bottom, this.margin.top + this.padding]
106
+ }
107
+
108
+ const scale = {
109
+ x: getScale(this.domain.x, this.range.x, this.spacing),
110
+ y: getScale(this.domain.y, this.range.y, this.spacing)
111
+ }
112
+
113
+ // scale['value'] = this.value === this.x ? scale.x : scale.y
114
+
115
+ this.origin = {
116
+ x: getOriginValue(scale.x),
117
+ y: getOriginValue(scale.y)
118
+ }
119
+
120
+ this.scale = scale
121
+
122
+ return this
123
+ }
124
+
125
+ // get scale() {
126
+ // return this.scale
127
+ // }
128
+ // get origin() {
129
+ // return this.origin
130
+ // }
131
+ // get margin() {
132
+ // return this.margin
133
+ // }
134
+ // get range() {
135
+ // const [x1, x2] = this.scale.x.range()
136
+ // const [y1, y2] = this.scale.y.range()
137
+
138
+ // return { x1, y1, x2, y2 }
139
+ // }
140
+ // get data() {
141
+ // // aggregate data group by x,y,fill,shape, color
142
+ // // stat = [min, max, avg, std, q1, q3, median, sum, count, box, all]
143
+
144
+ // return this.data
145
+ // }
146
+ // get width() {
147
+ // return this.width
148
+ // }
149
+ // get height() {
150
+ // return this.height
151
+ // }
152
+ // set width(value) {
153
+ // this.width = value
154
+ // }
155
+ // set height(value) {
156
+ // this.height = value
157
+ // }
158
+ // get domain() {
159
+ // return this.domain
160
+ // }
161
+ // get flipCoords() {
162
+ // return this.flipCoords
163
+ // }
164
+ aggregate(value, stat) {
165
+ this.value = value
166
+ this.stat = stat
167
+
168
+ // this.data = nest(this.data)
169
+ }
170
+
171
+ ticks(axis, count, fontSize = 8) {
172
+ const scale = this.scale[axis]
173
+ const [minRange, maxRange] = scale.range()
174
+ let ticks = []
175
+ let offset = 0
176
+
177
+ count = count || Math.abs((maxRange - minRange) / (fontSize * (axis === 'y' ? 8 : 8)))
178
+
179
+ if (scale.ticks) {
180
+ ticks = scale.ticks(Math.round(count))
181
+ } else {
182
+ offset = scale.bandwidth() / 2
183
+ count = Math.min(Math.round(count), scale.domain().length)
184
+
185
+ ticks = scale.domain()
186
+ if (count < scale.domain().length) {
187
+ const diff = scale.domain().length - count
188
+ ticks = ticks.filter((d, i) => i % diff === 0)
189
+ }
190
+ }
191
+
192
+ ticks = ticks
193
+ .map((t) => ({
194
+ label: t,
195
+ pos: scale(t)
196
+ }))
197
+ .map(({ label, pos }) => ({
198
+ label,
199
+ offset: {
200
+ x: axis === 'x' ? offset : 0,
201
+ y: axis === 'y' ? offset : 0
202
+ },
203
+ x: axis === 'x' ? pos : this.origin.x,
204
+ y: axis === 'y' ? pos : this.origin.y
205
+ }))
206
+
207
+ return ticks
208
+ }
209
+ }
210
+
211
+ export function chart(data, aes) {
212
+ return new Chart(data, aes)
213
+ }