@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,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,74 @@
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) => {
39
+ const q1 = sortedQuantile(v, 0.25)
40
+ const q3 = sortedQuantile(v, 0.75)
41
+ return q1 - 1.5 * (q3 - q1)
42
+ },
43
+ iqr_max: (v) => {
44
+ const q1 = sortedQuantile(v, 0.25)
45
+ const q3 = sortedQuantile(v, 0.75)
46
+ return q3 + 1.5 * (q3 - q1)
47
+ }
48
+ })
49
+ .rollup()
50
+ .select()
51
+ }
52
+
53
+ /**
54
+ * Aggregates data by one or more grouping fields, reducing the value field
55
+ * using the given stat. Accepts a built-in name or a custom function.
56
+ *
57
+ * @param {Object[]} data
58
+ * @param {{ by: string[], value: string, stat: string|Function }} opts
59
+ * @returns {Object[]}
60
+ */
61
+ function isIdentityOrEmpty(stat, by, value) {
62
+ return stat === 'identity' || by.length === 0 || value === null || value === undefined
63
+ }
64
+
65
+ export function applyAggregate(data, { by, value, stat }) {
66
+ if (isIdentityOrEmpty(stat, by, value)) return data
67
+ const fn = typeof stat === 'function' ? stat : STAT_FNS[stat]
68
+ if (fn === null || fn === undefined) return data
69
+ return dataset(data)
70
+ .groupBy(...by)
71
+ .summarize((row) => row[value], { [value]: fn })
72
+ .rollup()
73
+ .select()
74
+ }
@@ -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,221 @@
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 =
63
+ Number(opts.spacing) >= 0 && Number(opts.spacing) <= 0.5 ? Number(opts.spacing) : 0
64
+ this.margin = {
65
+ top: Number(opts.margin?.top) || 0,
66
+ left: Number(opts.margin?.left) || 0,
67
+ right: Number(opts.margin?.right) || 0,
68
+ bottom: Number(opts.margin?.bottom) || 0
69
+ }
70
+ this.stat = opts.stat || 'identity'
71
+ }
72
+
73
+ #initDomain(data) {
74
+ this.domain = {
75
+ x: [...new Set(data.map((d) => d[this.x]))],
76
+ y: [...new Set(data.map((d) => d[this.y]))]
77
+ }
78
+ if (this.flipCoords) {
79
+ this.domain = { y: this.domain.x, x: this.domain.y }
80
+ }
81
+ }
82
+
83
+ #initData(data) {
84
+ this.data = data.map((d) => ({
85
+ x: this.flipCoords ? d[this.y] : d[this.x],
86
+ y: this.flipCoords ? d[this.x] : d[this.y],
87
+ // fill: d[this.fill],
88
+ color: d[this.color]
89
+ // shape: d[this.shape]
90
+ }))
91
+ }
92
+
93
+ constructor(data, opts) {
94
+ this.#initFields(opts)
95
+ this.#initDomain(data)
96
+ this.#initData(data)
97
+ this.refresh()
98
+ }
99
+
100
+ padding(value) {
101
+ this.padding = value
102
+ return this.refresh()
103
+ }
104
+
105
+ margin(value) {
106
+ this.margin = value
107
+ return this.refresh()
108
+ }
109
+
110
+ refresh() {
111
+ this.range = {
112
+ x: [this.margin.left + this.padding, this.width - this.margin.right - this.padding],
113
+ y: [this.height - this.padding - this.margin.bottom, this.margin.top + this.padding]
114
+ }
115
+
116
+ const scale = {
117
+ x: getScale(this.domain.x, this.range.x, this.spacing),
118
+ y: getScale(this.domain.y, this.range.y, this.spacing)
119
+ }
120
+
121
+ // scale['value'] = this.value === this.x ? scale.x : scale.y
122
+
123
+ this.origin = {
124
+ x: getOriginValue(scale.x),
125
+ y: getOriginValue(scale.y)
126
+ }
127
+
128
+ this.scale = scale
129
+
130
+ return this
131
+ }
132
+
133
+ // get scale() {
134
+ // return this.scale
135
+ // }
136
+ // get origin() {
137
+ // return this.origin
138
+ // }
139
+ // get margin() {
140
+ // return this.margin
141
+ // }
142
+ // get range() {
143
+ // const [x1, x2] = this.scale.x.range()
144
+ // const [y1, y2] = this.scale.y.range()
145
+
146
+ // return { x1, y1, x2, y2 }
147
+ // }
148
+ // get data() {
149
+ // // aggregate data group by x,y,fill,shape, color
150
+ // // stat = [min, max, avg, std, q1, q3, median, sum, count, box, all]
151
+
152
+ // return this.data
153
+ // }
154
+ // get width() {
155
+ // return this.width
156
+ // }
157
+ // get height() {
158
+ // return this.height
159
+ // }
160
+ // set width(value) {
161
+ // this.width = value
162
+ // }
163
+ // set height(value) {
164
+ // this.height = value
165
+ // }
166
+ // get domain() {
167
+ // return this.domain
168
+ // }
169
+ // get flipCoords() {
170
+ // return this.flipCoords
171
+ // }
172
+ aggregate(value, stat) {
173
+ this.value = value
174
+ this.stat = stat
175
+
176
+ // this.data = nest(this.data)
177
+ }
178
+
179
+ ticks(axis, count, fontSize = 8) {
180
+ const scale = this.scale[axis]
181
+ const [minRange, maxRange] = scale.range()
182
+ let ticks = []
183
+ let offset = 0
184
+
185
+ count = count || Math.abs((maxRange - minRange) / (fontSize * (axis === 'y' ? 8 : 8)))
186
+
187
+ if (scale.ticks) {
188
+ ticks = scale.ticks(Math.round(count))
189
+ } else {
190
+ offset = scale.bandwidth() / 2
191
+ count = Math.min(Math.round(count), scale.domain().length)
192
+
193
+ ticks = scale.domain()
194
+ if (count < scale.domain().length) {
195
+ const diff = scale.domain().length - count
196
+ ticks = ticks.filter((d, i) => i % diff === 0)
197
+ }
198
+ }
199
+
200
+ ticks = ticks
201
+ .map((t) => ({
202
+ label: t,
203
+ pos: scale(t)
204
+ }))
205
+ .map(({ label, pos }) => ({
206
+ label,
207
+ offset: {
208
+ x: axis === 'x' ? offset : 0,
209
+ y: axis === 'y' ? offset : 0
210
+ },
211
+ x: axis === 'x' ? pos : this.origin.x,
212
+ y: axis === 'y' ? pos : this.origin.y
213
+ }))
214
+
215
+ return ticks
216
+ }
217
+ }
218
+
219
+ export function chart(data, aes) {
220
+ return new Chart(data, aes)
221
+ }
@@ -0,0 +1,131 @@
1
+ import { getContext, setContext } from 'svelte'
2
+ import { writable, derived } from 'svelte/store'
3
+ import * as d3 from 'd3'
4
+
5
+ const CHART_CONTEXT = 'chart-context'
6
+
7
+ const DEFAULT_OPTIONS = {
8
+ width: 600,
9
+ height: 400,
10
+ margin: { top: 20, right: 30, bottom: 40, left: 50 },
11
+ padding: { top: 0, right: 0, bottom: 0, left: 0 },
12
+ responsive: true,
13
+ animationDuration: 300,
14
+ data: []
15
+ }
16
+
17
+ /**
18
+ * @param {Object} config
19
+ * @returns {import('svelte/store').Writable}
20
+ */
21
+ function createDimensionsStore(config) {
22
+ return writable({
23
+ width: config.width,
24
+ height: config.height,
25
+ margin: { ...config.margin },
26
+ padding: { ...config.padding }
27
+ })
28
+ }
29
+
30
+ /**
31
+ * @param {import('svelte/store').Writable} dimensions
32
+ * @returns {import('svelte/store').Readable}
33
+ */
34
+ function createInnerDimensions(dimensions) {
35
+ return derived(dimensions, ($d) => ({
36
+ width: $d.width - $d.margin.left - $d.margin.right - $d.padding.left - $d.padding.right,
37
+ height: $d.height - $d.margin.top - $d.margin.bottom - $d.padding.top - $d.padding.bottom
38
+ }))
39
+ }
40
+
41
+ /**
42
+ * @param {import('svelte/store').Writable} plots
43
+ * @returns {(plot: unknown) => () => void}
44
+ */
45
+ function makeAddPlot(plots) {
46
+ return function addPlot(plot) {
47
+ plots.update((currentPlots) => [...currentPlots, plot])
48
+ return () => {
49
+ plots.update((currentPlots) => currentPlots.filter((p) => p !== plot))
50
+ }
51
+ }
52
+ }
53
+
54
+ /**
55
+ * @param {import('svelte/store').Writable} data
56
+ * @param {import('svelte/store').Readable} innerDimensions
57
+ * @returns {(xKey: string, yKey: string, colorKey?: string|null) => import('svelte/store').Readable}
58
+ */
59
+ function makeUpdateScales(data, innerDimensions) {
60
+ return function updateScales(xKey, yKey, colorKey = null) {
61
+ return derived([data, innerDimensions], ([$data, $innerDimensions]) => {
62
+ if (!$data || $data.length === 0) return null
63
+
64
+ const xScale = d3
65
+ .scaleBand()
66
+ .domain($data.map((d) => d[xKey]))
67
+ .range([0, $innerDimensions.width])
68
+ .padding(0.2)
69
+
70
+ const yScale = d3
71
+ .scaleLinear()
72
+ .domain([0, d3.max($data, (d) => d[yKey])])
73
+ .nice()
74
+ .range([$innerDimensions.height, 0])
75
+
76
+ let colorScale = null
77
+ if (colorKey) {
78
+ const uniqueCategories = [...new Set($data.map((d) => d[colorKey]))]
79
+ colorScale = d3.scaleOrdinal().domain(uniqueCategories).range(d3.schemeCategory10)
80
+ }
81
+
82
+ return { xScale, yScale, colorScale }
83
+ })
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Creates chart context and provides it to child components
89
+ *
90
+ * @param {Object} options Initial chart options
91
+ * @returns {Object} Chart context with all stores and methods
92
+ */
93
+ export function createChartContext(options = {}) {
94
+ const config = { ...DEFAULT_OPTIONS, ...options }
95
+
96
+ const dimensions = createDimensionsStore(config)
97
+ const data = writable(config.data)
98
+ const scales = writable({})
99
+ const innerDimensions = createInnerDimensions(dimensions)
100
+ const plots = writable([])
101
+ const axes = writable({ x: null, y: null })
102
+ const legend = writable({ enabled: false, items: [] })
103
+
104
+ const addPlot = makeAddPlot(plots)
105
+ const updateScales = makeUpdateScales(data, innerDimensions)
106
+
107
+ const chartContext = {
108
+ dimensions,
109
+ innerDimensions,
110
+ data,
111
+ scales,
112
+ plots,
113
+ axes,
114
+ legend,
115
+ addPlot,
116
+ updateScales
117
+ }
118
+
119
+ setContext(CHART_CONTEXT, chartContext)
120
+
121
+ return chartContext
122
+ }
123
+
124
+ /**
125
+ * Gets chart context provided by parent component
126
+ *
127
+ * @returns {Object} Chart context
128
+ */
129
+ export function getChartContext() {
130
+ return getContext(CHART_CONTEXT)
131
+ }