@rokkit/chart 1.0.0-next.14 → 1.0.0-next.141

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 (150) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +150 -46
  3. package/dist/Plot/index.d.ts +5 -0
  4. package/dist/elements/index.d.ts +6 -0
  5. package/dist/index.d.ts +14 -0
  6. package/dist/lib/brewing/axes.svelte.d.ts +66 -0
  7. package/dist/lib/brewing/bars.svelte.d.ts +56 -0
  8. package/dist/lib/brewing/dimensions.svelte.d.ts +35 -0
  9. package/dist/lib/brewing/index.svelte.d.ts +118 -0
  10. package/dist/lib/brewing/legends.svelte.d.ts +48 -0
  11. package/dist/lib/brewing/scales.svelte.d.ts +24 -0
  12. package/dist/lib/brewing/types.d.ts +162 -0
  13. package/dist/lib/context.d.ts +13 -0
  14. package/dist/lib/scales.svelte.d.ts +35 -0
  15. package/dist/lib/utils.d.ts +60 -0
  16. package/dist/old_lib/brewer.d.ts +9 -0
  17. package/dist/old_lib/chart.d.ts +40 -0
  18. package/dist/old_lib/grid.d.ts +72 -0
  19. package/dist/old_lib/index.d.ts +4 -0
  20. package/dist/old_lib/plots.d.ts +3 -0
  21. package/dist/old_lib/swatch.d.ts +285 -0
  22. package/dist/old_lib/ticks.d.ts +36 -0
  23. package/dist/old_lib/utils.d.ts +1 -0
  24. package/dist/patterns/index.d.ts +9 -0
  25. package/dist/patterns/paths/constants.d.ts +1 -0
  26. package/dist/symbols/constants/index.d.ts +1 -0
  27. package/dist/symbols/index.d.ts +5 -0
  28. package/dist/template/constants.d.ts +43 -0
  29. package/dist/template/shapes/index.d.ts +4 -0
  30. package/package.json +34 -44
  31. package/src/Plot/Axis.svelte +95 -0
  32. package/src/Plot/Bar.svelte +96 -0
  33. package/src/Plot/Grid.svelte +68 -0
  34. package/src/Plot/Legend.svelte +127 -0
  35. package/src/Plot/Root.svelte +107 -0
  36. package/src/Plot/index.js +5 -0
  37. package/src/Symbol.svelte +21 -0
  38. package/src/Texture.svelte +18 -0
  39. package/src/elements/Bar.svelte +22 -24
  40. package/src/elements/ColorRamp.svelte +20 -22
  41. package/src/elements/ContinuousLegend.svelte +20 -17
  42. package/src/elements/DefinePatterns.svelte +24 -0
  43. package/src/elements/DiscreteLegend.svelte +15 -15
  44. package/src/elements/Label.svelte +4 -8
  45. package/src/elements/SymbolGrid.svelte +23 -0
  46. package/src/elements/index.js +6 -0
  47. package/src/examples/BarChartExample.svelte +81 -0
  48. package/src/index.js +18 -16
  49. package/src/lib/brewing/axes.svelte.js +265 -0
  50. package/src/lib/brewing/bars.svelte.js +177 -0
  51. package/src/lib/brewing/dimensions.svelte.js +56 -0
  52. package/src/lib/brewing/index.svelte.js +205 -0
  53. package/src/lib/brewing/legends.svelte.js +137 -0
  54. package/src/lib/brewing/scales.svelte.js +106 -0
  55. package/src/lib/brewing/types.js +73 -0
  56. package/src/lib/context.js +133 -0
  57. package/src/lib/scales.svelte.js +172 -0
  58. package/src/lib/utils.js +107 -120
  59. package/src/old_lib/brewer.js +25 -0
  60. package/src/old_lib/chart.js +213 -0
  61. package/src/old_lib/grid.js +85 -0
  62. package/src/old_lib/index.js +4 -0
  63. package/src/old_lib/plots.js +27 -0
  64. package/src/old_lib/swatch.js +16 -0
  65. package/src/old_lib/ticks.js +46 -0
  66. package/src/old_lib/utils.js +8 -0
  67. package/src/patterns/Brick.svelte +15 -0
  68. package/src/patterns/Circles.svelte +18 -0
  69. package/src/patterns/CrossHatch.svelte +12 -0
  70. package/src/patterns/CurvedWave.svelte +7 -0
  71. package/src/patterns/Dots.svelte +20 -0
  72. package/src/patterns/OutlineCircles.svelte +13 -0
  73. package/src/patterns/README.md +3 -0
  74. package/src/patterns/Tile.svelte +16 -0
  75. package/src/patterns/Triangles.svelte +13 -0
  76. package/src/patterns/Waves.svelte +9 -0
  77. package/src/patterns/index.js +14 -0
  78. package/src/patterns/paths/NamedPattern.svelte +9 -0
  79. package/src/patterns/paths/constants.js +4 -0
  80. package/src/symbols/RoundedSquare.svelte +33 -0
  81. package/src/symbols/Shape.svelte +37 -0
  82. package/src/symbols/constants/index.js +4 -0
  83. package/src/symbols/index.js +9 -0
  84. package/src/symbols/outline.svelte +60 -0
  85. package/src/symbols/solid.svelte +60 -0
  86. package/src/template/Texture.svelte +13 -0
  87. package/src/template/constants.js +43 -0
  88. package/src/template/shapes/Circles.svelte +15 -0
  89. package/src/template/shapes/Lines.svelte +16 -0
  90. package/src/template/shapes/Path.svelte +9 -0
  91. package/src/template/shapes/Polygons.svelte +15 -0
  92. package/src/template/shapes/index.js +4 -0
  93. package/src/chart/FacetGrid.svelte +0 -51
  94. package/src/chart/Grid.svelte +0 -34
  95. package/src/chart/Legend.svelte +0 -16
  96. package/src/chart/PatternDefs.svelte +0 -13
  97. package/src/chart/Swatch.svelte +0 -93
  98. package/src/chart/SwatchButton.svelte +0 -29
  99. package/src/chart/SwatchGrid.svelte +0 -55
  100. package/src/chart/Symbol.svelte +0 -37
  101. package/src/chart/Texture.svelte +0 -16
  102. package/src/chart/TexturedShape.svelte +0 -27
  103. package/src/chart/TimelapseChart.svelte +0 -97
  104. package/src/chart/Timer.svelte +0 -27
  105. package/src/chart.js +0 -9
  106. package/src/components/charts/Axis.svelte +0 -66
  107. package/src/components/charts/Chart.svelte +0 -35
  108. package/src/components/index.js +0 -23
  109. package/src/components/lib/axis.js +0 -0
  110. package/src/components/lib/chart.js +0 -187
  111. package/src/components/lib/color.js +0 -327
  112. package/src/components/lib/funnel.js +0 -204
  113. package/src/components/lib/index.js +0 -19
  114. package/src/components/lib/pattern.js +0 -190
  115. package/src/components/lib/rollup.js +0 -55
  116. package/src/components/lib/shape.js +0 -199
  117. package/src/components/lib/summary.js +0 -145
  118. package/src/components/lib/theme.js +0 -23
  119. package/src/components/lib/timer.js +0 -41
  120. package/src/components/lib/utils.js +0 -165
  121. package/src/components/plots/BarPlot.svelte +0 -36
  122. package/src/components/plots/BoxPlot.svelte +0 -54
  123. package/src/components/plots/ScatterPlot.svelte +0 -30
  124. package/src/components/store.js +0 -70
  125. package/src/constants.js +0 -66
  126. package/src/elements/PatternDefs.svelte +0 -13
  127. package/src/elements/PatternMask.svelte +0 -20
  128. package/src/elements/Symbol.svelte +0 -38
  129. package/src/elements/Tooltip.svelte +0 -23
  130. package/src/funnel.svelte +0 -35
  131. package/src/geom.js +0 -105
  132. package/src/lib/axis.js +0 -75
  133. package/src/lib/colors.js +0 -32
  134. package/src/lib/geom.js +0 -4
  135. package/src/lib/shapes.js +0 -144
  136. package/src/lib/timer.js +0 -44
  137. package/src/lookup.js +0 -29
  138. package/src/plots/BarPlot.svelte +0 -55
  139. package/src/plots/BoxPlot.svelte +0 -0
  140. package/src/plots/FunnelPlot.svelte +0 -33
  141. package/src/plots/HeatMap.svelte +0 -5
  142. package/src/plots/HeatMapCalendar.svelte +0 -129
  143. package/src/plots/LinePlot.svelte +0 -55
  144. package/src/plots/Plot.svelte +0 -25
  145. package/src/plots/RankBarPlot.svelte +0 -38
  146. package/src/plots/ScatterPlot.svelte +0 -20
  147. package/src/plots/ViolinPlot.svelte +0 -11
  148. package/src/plots/heatmap.js +0 -70
  149. package/src/plots/index.js +0 -10
  150. package/src/swatch.js +0 -11
@@ -0,0 +1,137 @@
1
+ import { SvelteSet } from 'svelte/reactivity'
2
+ import {} from './types.js'
3
+
4
+ /**
5
+ * @typedef {import('./types').LegendItem} LegendItem
6
+ * @typedef {import('./types').LegendData} LegendData
7
+ * @typedef {import('./types').ScaleFields} ScaleFields
8
+ * @typedef {import('./types').ChartScales} ChartScales
9
+ * @typedef {import('./types').ChartDimensions} ChartDimensions
10
+ */
11
+
12
+ const LEGEND_DEFAULTS = { title: '', align: 'right', shape: 'rect', markerSize: 10 }
13
+
14
+ /**
15
+ * Compute the x-position for legend alignment
16
+ * @param {string} align
17
+ * @param {number} innerWidth
18
+ * @param {number} approximateWidth
19
+ * @returns {number}
20
+ */
21
+ function legendX(align, innerWidth, approximateWidth) {
22
+ if (align === 'right') return innerWidth - approximateWidth
23
+ if (align === 'center') return (innerWidth - approximateWidth) / 2
24
+ return 0
25
+ }
26
+
27
+ /**
28
+ * @param {Array} colorValues
29
+ * @param {Function} colorScale
30
+ * @param {{ shape: string, markerSize: number, titleOffset: number }} style
31
+ * @returns {LegendItem[]}
32
+ */
33
+ function buildLegendItems(colorValues, colorScale, style) {
34
+ const { shape, markerSize, titleOffset } = style
35
+ return colorValues.map((value, index) => ({
36
+ value,
37
+ color: colorScale(value),
38
+ y: index * (markerSize + 5) + titleOffset,
39
+ shape,
40
+ markerSize
41
+ }))
42
+ }
43
+
44
+ /**
45
+ * @param {Array} colorValues
46
+ * @param {number} markerSize
47
+ * @returns {number}
48
+ */
49
+ function approximateLegendWidth(colorValues, markerSize) {
50
+ return Math.max(...colorValues.map((v) => v.toString().length)) * 8 + markerSize + 10
51
+ }
52
+
53
+ /**
54
+ * @param {Object} options
55
+ * @returns {{ dimensions: Object, title: string, align: string, shape: string, markerSize: number }}
56
+ */
57
+ function parseLegendOptions(options) {
58
+ const merged = Object.assign({}, LEGEND_DEFAULTS, options || {})
59
+ return {
60
+ dimensions: merged.dimensions,
61
+ title: merged.title,
62
+ align: merged.align,
63
+ shape: merged.shape,
64
+ markerSize: merged.markerSize
65
+ }
66
+ }
67
+
68
+ /**
69
+ * @param {Object|undefined} dimensions
70
+ * @returns {number}
71
+ */
72
+ function innerWidth(dimensions) {
73
+ return dimensions ? dimensions.innerWidth : 0
74
+ }
75
+
76
+ /**
77
+ * Creates legend data for rendering
78
+ *
79
+ * @param {Array} data - Chart data
80
+ * @param {Object} fields - Field mappings
81
+ * @param {string} fields.color - Color field
82
+ * @param {Object} scales - Chart scales
83
+ * @param {Function} scales.color - Color scale
84
+ * @param {Object} options - Legend options including dimensions
85
+ * @param {Object} options.dimensions - Chart dimensions
86
+ * @param {string} [options.title=''] - Legend title
87
+ * @param {string} [options.align='right'] - Legend alignment ('left', 'center', or 'right')
88
+ * @param {string} [options.shape='rect'] - Legend marker shape ('rect' or 'circle')
89
+ * @param {number} [options.markerSize=10] - Size of legend markers
90
+ * @returns {LegendData} Legend rendering data
91
+ */
92
+ export function createLegend(data, fields, scales, options) {
93
+ if (!data || !fields.color || !scales.color) {
94
+ return { items: [], title: '', transform: 'translate(0, 0)' }
95
+ }
96
+
97
+ const { dimensions, title, align, shape, markerSize } = parseLegendOptions(options)
98
+ const colorValues = [...new SvelteSet(data.map((d) => d[fields.color]))]
99
+ const titleOffset = title ? 15 : 0
100
+ const style = { shape, markerSize, titleOffset }
101
+ const items = buildLegendItems(colorValues, scales.color, style)
102
+ const approxWidth = approximateLegendWidth(colorValues, markerSize)
103
+ const x = legendX(align, innerWidth(dimensions), approxWidth)
104
+
105
+ return { items, title, transform: `translate(${x}, 0)` }
106
+ }
107
+
108
+ /**
109
+ * Filter data based on legend selection
110
+ *
111
+ * @param {Array} data - Chart data
112
+ * @param {string} colorField - Field used for color mapping
113
+ * @param {Array} selectedValues - Selected legend values
114
+ * @returns {Array} Filtered data
115
+ */
116
+ export function filterByLegend(data, colorField, selectedValues) {
117
+ if (!selectedValues || selectedValues.length === 0) {
118
+ return data
119
+ }
120
+
121
+ return data.filter((d) => selectedValues.includes(d[colorField]))
122
+ }
123
+
124
+ /**
125
+ * Create attributes for legend items
126
+ *
127
+ * @param {LegendItem} item - Legend item
128
+ * @returns {Object} Attributes for the legend item
129
+ */
130
+ export function createLegendItemAttributes(item) {
131
+ return {
132
+ 'data-plot-legend-item': '',
133
+ transform: `translate(0, ${item.y})`,
134
+ role: 'img',
135
+ 'aria-label': `Legend item for ${item.value}`
136
+ }
137
+ }
@@ -0,0 +1,106 @@
1
+ import { SvelteSet } from 'svelte/reactivity'
2
+ import { min, max } from 'd3-array'
3
+ import { scaleBand, scaleLinear, scaleTime, scaleOrdinal } from 'd3-scale'
4
+ import { schemeCategory10 } from 'd3-scale-chromatic'
5
+ import {} from './types.js'
6
+
7
+ /**
8
+ * @typedef {import('./types').ChartScales} ChartScales
9
+ * @typedef {import('./types').ScaleFields} ScaleFields
10
+ * @typedef {import('./types').ChartDimensions} ChartDimensions
11
+ */
12
+
13
+ /**
14
+ * @param {Array} xValues
15
+ * @param {Object} dimensions
16
+ * @param {number} padding
17
+ * @returns {import('d3-scale').ScaleContinuousNumeric|import('d3-scale').ScaleBand}
18
+ */
19
+ function buildXScale(xValues, dimensions, padding) {
20
+ const xIsDate = xValues.some((v) => v instanceof Date)
21
+ const xIsNumeric = !xIsDate && xValues.every((v) => !isNaN(parseFloat(v)))
22
+
23
+ if (xIsDate) {
24
+ return scaleTime()
25
+ .domain([min(xValues), max(xValues)])
26
+ .range([0, dimensions.innerWidth])
27
+ .nice()
28
+ }
29
+ if (xIsNumeric) {
30
+ return scaleLinear()
31
+ .domain([min([0, ...xValues]), max(xValues)])
32
+ .range([0, dimensions.innerWidth])
33
+ .nice()
34
+ }
35
+ return scaleBand().domain(xValues).range([0, dimensions.innerWidth]).padding(padding)
36
+ }
37
+
38
+ /**
39
+ * @param {Array} data
40
+ * @param {string} colorField
41
+ * @returns {import('d3-scale').ScaleOrdinal}
42
+ */
43
+ function buildColorScale(data, colorField) {
44
+ const colorValues = [...new SvelteSet(data.map((d) => d[colorField]))]
45
+ return scaleOrdinal().domain(colorValues).range(schemeCategory10)
46
+ }
47
+
48
+ /**
49
+ * @param {Array} data
50
+ * @param {string} yField
51
+ * @param {Object} dimensions
52
+ * @returns {import('d3-scale').ScaleContinuousNumeric}
53
+ */
54
+ function buildYScale(data, yField, dimensions) {
55
+ const yValues = data.map((d) => d[yField])
56
+ return scaleLinear()
57
+ .domain([0, max(yValues) * 1.1])
58
+ .nice()
59
+ .range([dimensions.innerHeight, 0])
60
+ }
61
+
62
+ /**
63
+ * @param {Array} data
64
+ * @param {ScaleFields} fields
65
+ * @returns {boolean}
66
+ */
67
+ function hasRequiredFields(data, fields) {
68
+ return Boolean(data && data.length && fields.x && fields.y)
69
+ }
70
+
71
+ /**
72
+ * Creates scales based on data, fields, and dimensions
73
+ *
74
+ * @param {Array} data - Chart data
75
+ * @param {ScaleFields} fields - Field mappings
76
+ * @param {Object} dimensions - Chart dimensions
77
+ * @param {Object} options - Scale options
78
+ * @param {number} [options.padding=0.2] - Padding for band scales
79
+ * @returns {ChartScales} Chart scales
80
+ */
81
+ export function createScales(data, fields, dimensions, options = {}) {
82
+ if (!hasRequiredFields(data, fields)) return { x: null, y: null, color: null }
83
+
84
+ const padding = options.padding ?? 0.2
85
+ const xValues = data.map((d) => d[fields.x])
86
+
87
+ return {
88
+ x: buildXScale(xValues, dimensions, padding),
89
+ y: buildYScale(data, fields.y, dimensions),
90
+ color: fields.color ? buildColorScale(data, fields.color) : null
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Gets the origin coordinates for the axes
96
+ *
97
+ * @param {ChartScales} scales - Chart scales
98
+ * @param {Object} dimensions - Chart dimensions
99
+ * @returns {Object} Origin coordinates
100
+ */
101
+ export function getOrigin(scales, dimensions) {
102
+ return {
103
+ x: scales.y ? scales.y(0) : dimensions.innerHeight,
104
+ y: scales.x ? (scales.x.ticks ? scales.x(Math.max(0, min(scales.x.domain()))) : 0) : 0
105
+ }
106
+ }
@@ -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,133 @@
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:
37
+ $d.width - $d.margin.left - $d.margin.right - $d.padding.left - $d.padding.right,
38
+ height:
39
+ $d.height - $d.margin.top - $d.margin.bottom - $d.padding.top - $d.padding.bottom
40
+ }))
41
+ }
42
+
43
+ /**
44
+ * @param {import('svelte/store').Writable} plots
45
+ * @returns {(plot: unknown) => () => void}
46
+ */
47
+ function makeAddPlot(plots) {
48
+ return function addPlot(plot) {
49
+ plots.update((currentPlots) => [...currentPlots, plot])
50
+ return () => {
51
+ plots.update((currentPlots) => currentPlots.filter((p) => p !== plot))
52
+ }
53
+ }
54
+ }
55
+
56
+ /**
57
+ * @param {import('svelte/store').Writable} data
58
+ * @param {import('svelte/store').Readable} innerDimensions
59
+ * @returns {(xKey: string, yKey: string, colorKey?: string|null) => import('svelte/store').Readable}
60
+ */
61
+ function makeUpdateScales(data, innerDimensions) {
62
+ return function updateScales(xKey, yKey, colorKey = null) {
63
+ return derived([data, innerDimensions], ([$data, $innerDimensions]) => {
64
+ if (!$data || $data.length === 0) return null
65
+
66
+ const xScale = d3
67
+ .scaleBand()
68
+ .domain($data.map((d) => d[xKey]))
69
+ .range([0, $innerDimensions.width])
70
+ .padding(0.2)
71
+
72
+ const yScale = d3
73
+ .scaleLinear()
74
+ .domain([0, d3.max($data, (d) => d[yKey])])
75
+ .nice()
76
+ .range([$innerDimensions.height, 0])
77
+
78
+ let colorScale = null
79
+ if (colorKey) {
80
+ const uniqueCategories = [...new Set($data.map((d) => d[colorKey]))]
81
+ colorScale = d3.scaleOrdinal().domain(uniqueCategories).range(d3.schemeCategory10)
82
+ }
83
+
84
+ return { xScale, yScale, colorScale }
85
+ })
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Creates chart context and provides it to child components
91
+ *
92
+ * @param {Object} options Initial chart options
93
+ * @returns {Object} Chart context with all stores and methods
94
+ */
95
+ export function createChartContext(options = {}) {
96
+ const config = { ...DEFAULT_OPTIONS, ...options }
97
+
98
+ const dimensions = createDimensionsStore(config)
99
+ const data = writable(config.data)
100
+ const scales = writable({})
101
+ const innerDimensions = createInnerDimensions(dimensions)
102
+ const plots = writable([])
103
+ const axes = writable({ x: null, y: null })
104
+ const legend = writable({ enabled: false, items: [] })
105
+
106
+ const addPlot = makeAddPlot(plots)
107
+ const updateScales = makeUpdateScales(data, innerDimensions)
108
+
109
+ const chartContext = {
110
+ dimensions,
111
+ innerDimensions,
112
+ data,
113
+ scales,
114
+ plots,
115
+ axes,
116
+ legend,
117
+ addPlot,
118
+ updateScales
119
+ }
120
+
121
+ setContext(CHART_CONTEXT, chartContext)
122
+
123
+ return chartContext
124
+ }
125
+
126
+ /**
127
+ * Gets chart context provided by parent component
128
+ *
129
+ * @returns {Object} Chart context
130
+ */
131
+ export function getChartContext() {
132
+ return getContext(CHART_CONTEXT)
133
+ }
@@ -0,0 +1,172 @@
1
+ import { SvelteSet } from 'svelte/reactivity'
2
+ import { scaleBand, scaleLinear, scaleTime, scaleOrdinal } from 'd3-scale'
3
+ import { schemeCategory10 } from 'd3-scale-chromatic'
4
+ import { min, max } from 'd3-array'
5
+
6
+ /**
7
+ * @param {Array} xValues
8
+ * @param {Object} dimensions
9
+ * @param {number} padding
10
+ * @returns {Object}
11
+ */
12
+ function buildXScale(xValues, dimensions, padding) {
13
+ const xIsDate = xValues.some((v) => v instanceof Date)
14
+ const xIsNumeric = !xIsDate && xValues.every((v) => !isNaN(parseFloat(v)))
15
+
16
+ if (xIsDate) {
17
+ return scaleTime()
18
+ .domain([min(xValues), max(xValues)])
19
+ .range([0, dimensions.innerWidth])
20
+ .nice()
21
+ }
22
+ if (xIsNumeric) {
23
+ return scaleLinear()
24
+ .domain([min([0, ...xValues]), max(xValues)])
25
+ .range([0, dimensions.innerWidth])
26
+ .nice()
27
+ }
28
+ return scaleBand().domain(xValues).range([0, dimensions.innerWidth]).padding(padding)
29
+ }
30
+
31
+ /**
32
+ * Creates appropriate scales based on data and dimensions
33
+ *
34
+ * @param {Array} data The dataset
35
+ * @param {Object} dimensions Chart dimensions
36
+ * @param {Object} options Additional options
37
+ * @param {string} options.xKey Field to use for x-axis
38
+ * @param {string} options.yKey Field to use for y-axis
39
+ * @param {string} [options.colorKey] Field to use for color mapping
40
+ * @param {number} [options.padding=0.2] Padding for band scales
41
+ * @returns {Object} Object containing xScale, yScale, and colorScale
42
+ */
43
+ /**
44
+ * @param {Array} data
45
+ * @param {string} colorKey
46
+ * @returns {Object}
47
+ */
48
+ function buildColorScale(data, colorKey) {
49
+ const uniqueCategories = [...new SvelteSet(data.map((d) => d[colorKey]))]
50
+ return scaleOrdinal().domain(uniqueCategories).range(schemeCategory10)
51
+ }
52
+
53
+ /**
54
+ * @param {Object} options
55
+ * @returns {{ xKey: string, yKey: string, colorKey: string|undefined, padding: number }}
56
+ */
57
+ function parseScaleOptions(options) {
58
+ const opts = options || {}
59
+ return {
60
+ xKey: opts.xKey,
61
+ yKey: opts.yKey,
62
+ colorKey: opts.colorKey,
63
+ padding: opts.padding !== undefined ? opts.padding : 0.2
64
+ }
65
+ }
66
+
67
+ export function createScales(data, dimensions, options) {
68
+ if (!data || !data.length) return {}
69
+
70
+ const { xKey, yKey, colorKey, padding } = parseScaleOptions(options)
71
+ const xValues = data.map((d) => d[xKey])
72
+ const yValues = data.map((d) => d[yKey])
73
+ const colorScale = colorKey ? buildColorScale(data, colorKey) : null
74
+
75
+ return {
76
+ xScale: buildXScale(xValues, dimensions, padding),
77
+ yScale: scaleLinear().domain([0, max(yValues) * 1.1]).nice().range([dimensions.innerHeight, 0]),
78
+ colorScale
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Calculates the actual chart dimensions after applying margins
84
+ *
85
+ * @param {number} width
86
+ * @param {number} height
87
+ * @param {Object} margin
88
+ * @returns {Object} Dimensions with calculated inner width and height
89
+ */
90
+ export function calculateChartDimensions(width, height, margin) {
91
+ return {
92
+ width,
93
+ height,
94
+ margin,
95
+ innerWidth: width - margin.left - margin.right,
96
+ innerHeight: height - margin.top - margin.bottom
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Gets the axis origin value
102
+ *
103
+ * @param {Object} scale D3 scale
104
+ * @returns {number} Origin value
105
+ */
106
+ export function getOriginValue(scale) {
107
+ return scale.ticks ? scale(Math.max(0, min(scale.domain()))) : scale.range()[0]
108
+ }
109
+
110
+ /**
111
+ * Resolve tick values for a band scale, applying downsampling when needed
112
+ * @param {Object} scale
113
+ * @param {number} count
114
+ * @returns {{ ticks: Array, offset: number }}
115
+ */
116
+ function bandTicks(scale, count) {
117
+ const offset = scale.bandwidth() / 2
118
+ const domain = scale.domain()
119
+ const cappedCount = Math.min(Math.round(count), domain.length)
120
+ const step = Math.ceil(domain.length / cappedCount)
121
+ const ticks = cappedCount < domain.length ? domain.filter((_, i) => i % step === 0) : domain
122
+ return { ticks, offset }
123
+ }
124
+
125
+ /**
126
+ * @param {number} rangeSize
127
+ * @param {string} axis
128
+ * @param {number} fontSize
129
+ * @returns {number}
130
+ */
131
+ function defaultTickCount(rangeSize, axis, fontSize) {
132
+ const divisor = fontSize * (axis === 'y' ? 3 : 6)
133
+ return Math.abs(rangeSize / divisor)
134
+ }
135
+
136
+ /**
137
+ * @param {Array} ticks
138
+ * @param {Object} scale
139
+ * @param {number} offset
140
+ * @param {boolean} isXAxis
141
+ * @returns {Array}
142
+ */
143
+ function formatTicks(ticks, scale, offset, isXAxis) {
144
+ const pos = isXAxis ? offset : 0
145
+ return ticks.map((t) => ({ value: t, position: scale(t) + pos }))
146
+ }
147
+
148
+ /**
149
+ * Creates axis ticks
150
+ *
151
+ * @param {Object} scale D3 scale
152
+ * @param {string} axis Axis type ('x' or 'y')
153
+ * @param {number} count Number of ticks
154
+ * @param {number} fontSize Font size for determining tick density
155
+ * @returns {Array} Array of tick objects
156
+ */
157
+ export function createTicks(scale, axis, count = null, fontSize = 12) {
158
+ const [minRange, maxRange] = scale.range()
159
+ const tickCount = count ?? defaultTickCount(maxRange - minRange, axis, fontSize)
160
+
161
+ let ticks, offset
162
+ if (scale.ticks) {
163
+ ticks = scale.ticks(Math.round(tickCount))
164
+ offset = 0
165
+ } else {
166
+ const band = bandTicks(scale, tickCount)
167
+ ticks = band.ticks
168
+ offset = band.offset
169
+ }
170
+
171
+ return formatTicks(ticks, scale, offset, axis === 'x')
172
+ }