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

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,41 @@
1
+ // packages/chart/src/lib/preset.js
2
+
3
+ export const defaultPreset = {
4
+ colors: ['blue', 'emerald', 'rose', 'amber', 'violet', 'sky', 'pink', 'teal',
5
+ 'orange', 'indigo', 'lime', 'cyan', 'gold', 'lavender'],
6
+ shades: {
7
+ light: { fill: '300', stroke: '700' },
8
+ dark: { fill: '500', stroke: '200' }
9
+ },
10
+ opacity: {
11
+ area: 0.6,
12
+ box: 0.5,
13
+ violin: 0.5,
14
+ point: 0.8
15
+ },
16
+ patterns: ['diagonal', 'dots', 'triangles', 'hatch', 'lattice', 'swell',
17
+ 'checkerboard', 'waves', 'petals', 'brick', 'diamonds', 'tile',
18
+ 'scales', 'circles', 'pip', 'rings', 'chevrons', 'shards',
19
+ 'wedge', 'argyle', 'shell'],
20
+ symbols: ['circle', 'square', 'triangle', 'diamond', 'cross', 'star']
21
+ }
22
+
23
+ /**
24
+ * Creates a chart preset by deep-merging overrides with the default preset.
25
+ * All fields are optional. `opacity` is merged key-by-key so partial overrides work.
26
+ * @param {Partial<typeof defaultPreset>} [overrides]
27
+ * @returns {typeof defaultPreset}
28
+ */
29
+ export function createChartPreset(overrides = {}) {
30
+ return {
31
+ ...defaultPreset,
32
+ ...overrides,
33
+ shades: overrides.shades
34
+ ? {
35
+ light: { ...defaultPreset.shades.light, ...overrides.shades.light },
36
+ dark: { ...defaultPreset.shades.dark, ...overrides.shades.dark }
37
+ }
38
+ : defaultPreset.shades,
39
+ opacity: { ...defaultPreset.opacity, ...overrides.opacity }
40
+ }
41
+ }
@@ -0,0 +1,151 @@
1
+ import { SvelteSet } from 'svelte/reactivity'
2
+ import { scaleLinear, scaleOrdinal } from 'd3-scale'
3
+ import { schemeCategory10 } from 'd3-scale-chromatic'
4
+ import { min, max } from 'd3-array'
5
+ import { buildXScale } from './xscale.js'
6
+
7
+ /**
8
+ * Creates appropriate scales based on data and dimensions
9
+ *
10
+ * @param {Array} data The dataset
11
+ * @param {Object} dimensions Chart dimensions
12
+ * @param {Object} options Additional options
13
+ * @param {string} options.xKey Field to use for x-axis
14
+ * @param {string} options.yKey Field to use for y-axis
15
+ * @param {string} [options.colorKey] Field to use for color mapping
16
+ * @param {number} [options.padding=0.2] Padding for band scales
17
+ * @returns {Object} Object containing xScale, yScale, and colorScale
18
+ */
19
+ /**
20
+ * @param {Array} data
21
+ * @param {string} colorKey
22
+ * @returns {Object}
23
+ */
24
+ function buildColorScale(data, colorKey) {
25
+ const uniqueCategories = [...new SvelteSet(data.map((d) => d[colorKey]))]
26
+ return scaleOrdinal().domain(uniqueCategories).range(schemeCategory10)
27
+ }
28
+
29
+ /**
30
+ * @param {Object} options
31
+ * @returns {{ xKey: string, yKey: string, colorKey: string|undefined, padding: number }}
32
+ */
33
+ function parseScaleOptions(options) {
34
+ const opts = options || {}
35
+ return {
36
+ xKey: opts.xKey,
37
+ yKey: opts.yKey,
38
+ colorKey: opts.colorKey,
39
+ padding: opts.padding !== undefined ? opts.padding : 0.2
40
+ }
41
+ }
42
+
43
+ export function createScales(data, dimensions, options) {
44
+ if (!data || !data.length) return {}
45
+
46
+ const { xKey, yKey, colorKey, padding } = parseScaleOptions(options)
47
+ const xValues = data.map((d) => d[xKey])
48
+ const yValues = data.map((d) => d[yKey])
49
+ const colorScale = colorKey ? buildColorScale(data, colorKey) : null
50
+
51
+ return {
52
+ xScale: buildXScale(xValues, dimensions, padding),
53
+ yScale: scaleLinear()
54
+ .domain([0, max(yValues) * 1.1])
55
+ .nice()
56
+ .range([dimensions.innerHeight, 0]),
57
+ colorScale
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Calculates the actual chart dimensions after applying margins
63
+ *
64
+ * @param {number} width
65
+ * @param {number} height
66
+ * @param {Object} margin
67
+ * @returns {Object} Dimensions with calculated inner width and height
68
+ */
69
+ export function calculateChartDimensions(width, height, margin) {
70
+ return {
71
+ width,
72
+ height,
73
+ margin,
74
+ innerWidth: width - margin.left - margin.right,
75
+ innerHeight: height - margin.top - margin.bottom
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Gets the axis origin value
81
+ *
82
+ * @param {Object} scale D3 scale
83
+ * @returns {number} Origin value
84
+ */
85
+ export function getOriginValue(scale) {
86
+ return scale.ticks ? scale(Math.max(0, min(scale.domain()))) : scale.range()[0]
87
+ }
88
+
89
+ /**
90
+ * Resolve tick values for a band scale, applying downsampling when needed
91
+ * @param {Object} scale
92
+ * @param {number} count
93
+ * @returns {{ ticks: Array, offset: number }}
94
+ */
95
+ function bandTicks(scale, count) {
96
+ const offset = scale.bandwidth() / 2
97
+ const domain = scale.domain()
98
+ const cappedCount = Math.min(Math.round(count), domain.length)
99
+ const step = Math.ceil(domain.length / cappedCount)
100
+ const ticks = cappedCount < domain.length ? domain.filter((_, i) => i % step === 0) : domain
101
+ return { ticks, offset }
102
+ }
103
+
104
+ /**
105
+ * @param {number} rangeSize
106
+ * @param {string} axis
107
+ * @param {number} fontSize
108
+ * @returns {number}
109
+ */
110
+ function defaultTickCount(rangeSize, axis, fontSize) {
111
+ const divisor = fontSize * (axis === 'y' ? 3 : 6)
112
+ return Math.abs(rangeSize / divisor)
113
+ }
114
+
115
+ /**
116
+ * @param {Array} ticks
117
+ * @param {Object} scale
118
+ * @param {number} offset
119
+ * @param {boolean} isXAxis
120
+ * @returns {Array}
121
+ */
122
+ function formatTicks(ticks, scale, offset, isXAxis) {
123
+ const pos = isXAxis ? offset : 0
124
+ return ticks.map((t) => ({ value: t, position: scale(t) + pos }))
125
+ }
126
+
127
+ /**
128
+ * Creates axis ticks
129
+ *
130
+ * @param {Object} scale D3 scale
131
+ * @param {string} axis Axis type ('x' or 'y')
132
+ * @param {number} count Number of ticks
133
+ * @param {number} fontSize Font size for determining tick density
134
+ * @returns {Array} Array of tick objects
135
+ */
136
+ export function createTicks(scale, axis, count = null, fontSize = 12) {
137
+ const [minRange, maxRange] = scale.range()
138
+ const tickCount = count ?? defaultTickCount(maxRange - minRange, axis, fontSize)
139
+
140
+ let ticks, offset
141
+ if (scale.ticks) {
142
+ ticks = scale.ticks(Math.round(tickCount))
143
+ offset = 0
144
+ } else {
145
+ const band = bandTicks(scale, tickCount)
146
+ ticks = band.ticks
147
+ offset = band.offset
148
+ }
149
+
150
+ return formatTicks(ticks, scale, offset, axis === 'x')
151
+ }
@@ -0,0 +1,13 @@
1
+ import palette from './palette.json'
2
+ import { PATTERN_ORDER } from './brewing/patterns.js'
3
+ import { shapes } from '../symbols'
4
+
5
+ export const swatch = {
6
+ palette,
7
+ keys: {
8
+ gray: ['gray'],
9
+ color: Object.keys(palette).filter((name) => name !== 'gray'),
10
+ symbol: shapes,
11
+ pattern: PATTERN_ORDER
12
+ }
13
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @typedef {Object} TickSteps
3
+ * @property {number} major - count of major ticks
4
+ * @property {number} minor - count of minor ticks
5
+ */
6
+
7
+ /**
8
+ * Generate an array of ticks for a given axis and the tick type
9
+ *
10
+ * @param {number} lower - The lower bound of the axis
11
+ * @param {number} upper - The upper bound of the axis
12
+ * @param {TickSteps} steps - The number of steps between major and minor ticks
13
+ * @param {string} type - The type of tick to generate
14
+ *
15
+ * @returns {Array} - An array of objects representing the ticks
16
+ */
17
+ export function ticksByType(lower, upper, steps, type) {
18
+ if (steps <= 0) return []
19
+ return Array.from({ length: Math.floor((upper - lower) / steps) + 1 }, (_, i) => ({
20
+ position: i * steps,
21
+ label: i * steps,
22
+ type
23
+ })).filter((tick) => tick.position > lower && tick.position < upper)
24
+ }
25
+
26
+ /**
27
+ * Generate an array of ticks for a given axis
28
+ *
29
+ * @param {number} lower - The lower bound of the axis
30
+ * @param {number} upper - The upper bound of the axis
31
+ * @param {TickSteps} steps - The number of steps between major and minor ticks
32
+ *
33
+ * @returns {Array} - An array of objects representing the ticks
34
+ */
35
+ export function getTicks(lower, upper, steps = { major: 10, minor: 0 }) {
36
+ const majorTicks = ticksByType(lower, upper, steps.major, 'major')
37
+ const minorTicks = ticksByType(lower, upper, steps.minor, 'minor').filter(
38
+ (tick) => !majorTicks.find((major) => major.position === tick.position)
39
+ )
40
+ const end = [
41
+ { position: upper, label: upper, type: 'end' },
42
+ { position: lower, label: lower, type: 'end' }
43
+ ]
44
+
45
+ return [...minorTicks, ...majorTicks, ...end].sort((a, b) => a.position - b.position)
46
+ }
package/src/lib/utils.js CHANGED
@@ -1,157 +1,150 @@
1
- import { scaleBand, scaleLinear } from 'd3-scale'
2
- import { min, max } from 'd3-array'
3
- import { ascending, quantile } from 'd3-array'
4
- import { nest } from 'd3-collection'
5
- import { omit } from 'ramda'
1
+ import { scaleBand, scaleLinear, scaleOrdinal } from 'd3-scale'
2
+ import { schemeCategory10 } from 'd3-scale-chromatic'
3
+ import { max } from 'd3-array'
6
4
 
7
5
  /**
8
- * Calculates a grid of centres to fit a list of items of `size` within the number of `columns` and `rows`.
6
+ * Creates appropriate scales based on data and dimensions
9
7
  *
10
- * - Attempts to find a best fit square if both columns and rows are not specified
11
- * - Value in columns is prioritized over rows for recalculating the grid
12
- * - Supports padding between the items
13
- *
14
- * @param {number} count
15
- * @param {number} size
16
- * @param {number} pad
17
- * @param {number} columns
18
- * @param {number} rows
19
- * @returns
8
+ * @param {Array} data The dataset
9
+ * @param {Object} dimensions Chart dimensions
10
+ * @param {Object} [options] Additional options
11
+ * @param {string} options.xKey Field to use for x-axis
12
+ * @param {string} options.yKey Field to use for y-axis
13
+ * @param {string} [options.colorKey] Field to use for color mapping
14
+ * @returns {Object} Object containing xScale, yScale, and colorScale
20
15
  */
21
- export function swatch(count, size, pad = 0, columns, rows) {
22
- if (columns > 0) {
23
- rows = Math.ceil(count / columns)
24
- } else if (rows > 0) {
25
- columns = Math.ceil(count / rows)
26
- } else {
27
- columns = Math.ceil(Math.sqrt(count))
28
- rows = Math.ceil(count / columns)
29
- }
16
+ export function createScales(data, dimensions, options = {}) {
17
+ if (!data || data.length === 0) return {}
18
+
19
+ const { xKey, yKey, colorKey } = options
20
+
21
+ const xScale = scaleBand()
22
+ .domain(data.map((d) => d[xKey]))
23
+ .range([0, dimensions.width])
24
+ .padding(0.2)
30
25
 
31
- const width = (size + pad) * columns + pad
32
- const height = (size + pad) * rows + pad
33
- const data = [...Array(count).keys()].map((index) => ({
34
- cx: (size + pad) / 2 + (index % columns) * (size + pad),
35
- cy: (size + pad) / 2 + Math.floor(index / columns) * (size + pad),
36
- r: size / 2
37
- }))
26
+ const yScale = scaleLinear()
27
+ .domain([0, max(data, (d) => d[yKey]) * 1.1]) // Add 10% padding on top
28
+ .nice()
29
+ .range([dimensions.height, 0])
38
30
 
39
- return { width, height, data }
31
+ let colorScale = null
32
+
33
+ if (colorKey) {
34
+ const uniqueCategories = [...new Set(data.map((d) => d[colorKey]))]
35
+ colorScale = scaleOrdinal().domain(uniqueCategories).range(schemeCategory10)
36
+ }
37
+
38
+ return { xScale, yScale, colorScale }
40
39
  }
40
+
41
41
  /**
42
- * Get a scale function mapping the values between a range of lower and upper values
42
+ * Calculates the actual chart dimensions after applying margins
43
43
  *
44
- * @param {Array} values
45
- * @param {Array[2]} bounds
46
- * @param {number} buffer
47
- * @returns
44
+ * @param {Object} dimensions Original dimensions
45
+ * @returns {Object} Dimensions with calculated inner width and height
48
46
  */
49
- export function getScale(values, bounds, buffer = 0, ordinal = false) {
50
- if (ordinal || values.some(isNaN)) {
51
- return scaleBand().range(bounds).domain(values).padding(0.5)
52
- } else {
53
- values = values.map((x) => +x)
54
-
55
- let minValue = min([...values, 0])
56
- let maxValue = max(values)
57
-
58
- if (minValue < 0 && maxValue > 0) {
59
- maxValue = max([-1 * minValue, maxValue])
60
- minValue = -1 * maxValue
61
- }
62
- const margin = (maxValue - minValue) * buffer
63
- return scaleLinear()
64
- .domain([minValue - margin, maxValue + margin])
65
- .range(bounds)
47
+ export function calculateChartDimensions(dimensions) {
48
+ const { width, height, margin } = dimensions
49
+
50
+ return {
51
+ ...dimensions,
52
+ innerWidth: width - margin.left - margin.right,
53
+ innerHeight: height - margin.top - margin.bottom
66
54
  }
67
55
  }
56
+
68
57
  /**
69
- * Obtain the scale function for the `x` and `y` fields in the data set.
58
+ * Normalizes data for use with D3 charts
70
59
  *
71
- * @param {array<dict>} data
72
- * @param {string} x
73
- * @param {string} y
74
- * @param {number} width
75
- * @param {number} height
76
- * @returns
60
+ * @param {Array|Object} inputData Raw data or dataset object
61
+ * @returns {Array} Normalized data array
77
62
  */
78
- export function getScales(data, x, y, width, height, margin, ordinal) {
79
- const xValues = [...new Set(data.map((item) => item[x]))]
80
- const yValues = [...new Set(data.map((item) => item[y]))]
63
+ export function normalizeData(inputData) {
64
+ if (!inputData) return []
81
65
 
82
- return {
83
- x: getScale(xValues, [0 + margin.left, width - margin.right], 0, ordinal.x),
84
- y: getScale(yValues, [height - margin.top, margin.bottom], 0.1, ordinal.y)
66
+ // If it's a dataset class instance, call select() to get the data
67
+ if (inputData.select && typeof inputData.select === 'function') {
68
+ return inputData.select()
85
69
  }
70
+
71
+ // If it's already an array, return as is
72
+ if (Array.isArray(inputData)) {
73
+ return inputData
74
+ }
75
+
76
+ return []
86
77
  }
87
78
 
88
79
  /**
89
- * Summarize `data` by fields `x` and `y` and return a nested array with
90
- * key as unique `x` values and value as statistical summaries of `y` values
80
+ * Generates a unique ID for SVG elements
91
81
  *
92
- * @param {*} data
93
- * @param {*} x
94
- * @param {*} y
95
- * @returns
82
+ * @param {string} prefix Prefix for the ID
83
+ * @returns {string} A unique ID
96
84
  */
97
- export function aggregate(data, x, y) {
98
- const summary = nest()
99
- .key((d) => d[x])
100
- .rollup((d) => {
101
- let values = d.map((g) => g[y]).sort(ascending)
102
- let q1 = quantile(values, 0.25)
103
- let q3 = quantile(values, 0.75)
104
- let median = quantile(values, 0.5)
105
- let interQuantileRange = q3 - q1
106
- let min = q1 - 1.5 * interQuantileRange
107
- let max = q3 + 1.5 * interQuantileRange
108
- return { q1, q3, median, interQuantileRange, min, max }
109
- })
110
- .entries(data)
111
- return summary
85
+ export function uniqueId(prefix = 'chart') {
86
+ return `${prefix}-${Math.random().toString(36).substring(2, 10)}`
112
87
  }
113
88
 
114
89
  /**
115
- * Generate a palette with same size as input data containing values from palette array.
116
- * After the palette array is exhausted the fallback value is used
90
+ * Format a single key-value pair for tooltip
91
+ * @param {string} key
92
+ * @param {unknown} value
93
+ * @param {Function|undefined} formatter
94
+ * @returns {string}
95
+ */
96
+ function formatField(key, value, formatter) {
97
+ const formatted = formatter ? formatter(value) : value
98
+ return `${key}: ${formatted}`
99
+ }
100
+
101
+ /**
102
+ * Formats tooltip content for a data point
117
103
  *
118
- * @param {Array} values
119
- * @param {Array} palette
120
- * @param {*} fallback
121
- * @returns
104
+ * @param {Object} d Data point
105
+ * @param {Object} options Tooltip format options
106
+ * @returns {string} Formatted tooltip HTML content
122
107
  */
123
- export function getPaletteForValues(values, palette, fallback) {
124
- return values.map((_, index) =>
125
- index < palette.length ? palette[index] : fallback
126
- )
108
+ export function formatTooltipContent(d, options = {}) {
109
+ if (!d) return ''
110
+
111
+ const { xKey, yKey, xFormat, yFormat } = options
112
+
113
+ if (xKey && yKey) {
114
+ return [formatField(xKey, d[xKey], xFormat), formatField(yKey, d[yKey], yFormat)].join('<br>')
115
+ }
116
+
117
+ return Object.entries(d)
118
+ .map(([key, value]) => `${key}: ${value}`)
119
+ .join('<br>')
127
120
  }
128
121
 
129
122
  /**
130
- * Converts input object array into a nested key,value array.
131
- * 'key' contains unique values for the attribute specified by the key parameter
132
- * and value contains array of all remaining attributes in an array.
123
+ * Generates a tooltip formatter function
133
124
  *
134
- * @param {Array<Object>} data
135
- * @param {string} key
136
- * @param {string} label
137
- * @returns
125
+ * @param {Object} options Tooltip format options
126
+ * @returns {Function} A function that formats tooltip content
138
127
  */
139
- export function toNested(data, key, label) {
140
- return nest()
141
- .key((d) => d[key])
142
- .rollup((values) => values.map((value) => omit([key], value)))
143
- .entries(data.sort((a, b) => ascending(a[label], b[label])))
128
+ export function createTooltipFormatter(options = {}) {
129
+ return (d) => formatTooltipContent(d, options)
144
130
  }
131
+
145
132
  /**
146
- * Repeats array items of b using array items of a ask keys
133
+ * Calculates the transform attribute for SVG elements
147
134
  *
148
- * @param {Array} b
149
- * @param {Array} a
150
- * @returns {Object} with keys as items in a and values as items in b
135
+ * @param {number} x X position
136
+ * @param {number} y Y position
137
+ * @returns {string} Transform attribute value
151
138
  */
152
- export function repeatAcross(b, a) {
153
- return a.reduce(
154
- (acc, item, index) => ({ ...acc, [item]: b[index % b.length] }),
139
+ export function transform(x, y) {
140
+ return `translate(${x}, ${y})`
141
+ }
142
+
143
+ import { scaledPath } from '@rokkit/core'
144
+
145
+ export function scaledPathCollection(paths) {
146
+ return Object.entries(paths).reduce(
147
+ (acc, [key, value]) => ({ ...acc, [key]: (s) => scaledPath(s, value) }),
155
148
  {}
156
149
  )
157
150
  }
@@ -0,0 +1,31 @@
1
+ import { scaleBand, scaleLinear, scaleTime } from 'd3-scale'
2
+ import { min, max } from 'd3-array'
3
+
4
+ /**
5
+ * Builds an appropriate D3 x-axis scale based on value types.
6
+ * Returns scaleTime for Date values, scaleLinear for numeric values,
7
+ * or scaleBand for categorical values.
8
+ *
9
+ * @param {Array} xValues
10
+ * @param {Object} dimensions
11
+ * @param {number} padding
12
+ * @returns {import('d3-scale').ScaleContinuousNumeric|import('d3-scale').ScaleBand}
13
+ */
14
+ export function buildXScale(xValues, dimensions, padding) {
15
+ const xIsDate = xValues.some((v) => v instanceof Date)
16
+ const xIsNumeric = !xIsDate && xValues.every((v) => !isNaN(parseFloat(v)))
17
+
18
+ if (xIsDate) {
19
+ return scaleTime()
20
+ .domain([min(xValues), max(xValues)])
21
+ .range([0, dimensions.innerWidth])
22
+ .nice()
23
+ }
24
+ if (xIsNumeric) {
25
+ return scaleLinear()
26
+ .domain([min([0, ...xValues]), max(xValues)])
27
+ .range([0, dimensions.innerWidth])
28
+ .nice()
29
+ }
30
+ return scaleBand().domain(xValues).range([0, dimensions.innerWidth]).padding(padding)
31
+ }
@@ -0,0 +1,32 @@
1
+ <script>
2
+ import { getContext } from 'svelte'
3
+ import { toPatternId } from '../lib/brewing/patterns.js'
4
+ import { PATTERNS } from './patterns.js'
5
+ import PatternDef from './PatternDef.svelte'
6
+
7
+ /** @type {{ patterns?: Record<string, import('./patterns.js').PatternMark[]> }} */
8
+ let { patterns = PATTERNS } = $props()
9
+
10
+ const state = getContext('plot-state')
11
+
12
+ const patternDefs = $derived.by(() => {
13
+ const defs = []
14
+ for (const [key, patternName] of (state.patterns ?? new Map()).entries()) {
15
+ const colorEntry = state.colors?.get(key) ?? { stroke: '#444' }
16
+ defs.push({
17
+ id: toPatternId(String(key)),
18
+ marks: patterns[patternName] ?? [],
19
+ stroke: colorEntry.stroke ?? '#444'
20
+ })
21
+ }
22
+ return defs
23
+ })
24
+ </script>
25
+
26
+ {#if patternDefs.length > 0}
27
+ <defs data-plot-pattern-defs>
28
+ {#each patternDefs as def (def.id)}
29
+ <PatternDef id={def.id} marks={def.marks} stroke={def.stroke} />
30
+ {/each}
31
+ </defs>
32
+ {/if}
@@ -0,0 +1,27 @@
1
+ <script>
2
+ import { scaleMark, resolveMarkAttrs } from './scale.js'
3
+
4
+ /** @type {{ id: string, marks?: import('./patterns.js').PatternMark[], size?: number, stroke?: string, thickness?: number }} */
5
+ let { id, marks = [], size = 10, stroke = '#444', thickness = 0.5 } = $props()
6
+
7
+ const resolvedMarks = $derived(
8
+ marks.map((m) => resolveMarkAttrs(scaleMark(m, size), { fill: stroke, stroke, thickness }))
9
+ )
10
+ </script>
11
+
12
+ <pattern {id} patternUnits="userSpaceOnUse" width={size} height={size}>
13
+ <rect width={size} height={size} fill="none" />
14
+ {#each resolvedMarks as { type, attrs }, i (i)}
15
+ {#if type === 'line'}
16
+ <line {...attrs} />
17
+ {:else if type === 'circle'}
18
+ <circle {...attrs} />
19
+ {:else if type === 'rect'}
20
+ <rect {...attrs} />
21
+ {:else if type === 'polygon'}
22
+ <polygon {...attrs} />
23
+ {:else if type === 'path'}
24
+ <path {...attrs} />
25
+ {/if}
26
+ {/each}
27
+ </pattern>
@@ -0,0 +1,4 @@
1
+ export { default as PatternDef } from './PatternDef.svelte'
2
+ export { default as DefinePatterns } from './DefinePatterns.svelte'
3
+ export { PATTERNS } from './patterns.js'
4
+ export { scaleMark, resolveMarkAttrs } from './scale.js'