@rokkit/chart 1.0.0-next.136 → 1.0.0-next.138
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.
- package/dist/lib/brewing/axes.svelte.d.ts +3 -9
- package/dist/lib/brewing/bars.svelte.d.ts +14 -12
- package/dist/lib/brewing/legends.svelte.d.ts +4 -10
- package/dist/lib/brewing/scales.svelte.d.ts +0 -5
- package/dist/lib/scales.svelte.d.ts +13 -13
- package/dist/lib/utils.d.ts +5 -3
- package/package.json +1 -1
- package/src/lib/brewing/axes.svelte.js +174 -86
- package/src/lib/brewing/bars.svelte.js +109 -46
- package/src/lib/brewing/index.svelte.js +5 -2
- package/src/lib/brewing/legends.svelte.js +76 -33
- package/src/lib/brewing/scales.svelte.js +62 -41
- package/src/lib/context.js +62 -61
- package/src/lib/scales.svelte.js +107 -57
- package/src/lib/utils.js +23 -12
package/src/lib/scales.svelte.js
CHANGED
|
@@ -1,64 +1,90 @@
|
|
|
1
|
+
import { SvelteSet } from 'svelte/reactivity'
|
|
1
2
|
import { scaleBand, scaleLinear, scaleTime, scaleOrdinal } from 'd3-scale'
|
|
2
3
|
import { schemeCategory10 } from 'd3-scale-chromatic'
|
|
3
4
|
import { min, max } from 'd3-array'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @param {
|
|
9
|
-
* @
|
|
10
|
-
* @param {string} yKey Field to use for y-axis
|
|
11
|
-
* @param {Object} dimensions Chart dimensions
|
|
12
|
-
* @param {Object} options Additional options
|
|
13
|
-
* @returns {Object} Object containing xScale, yScale, and colorScale
|
|
7
|
+
* @param {Array} xValues
|
|
8
|
+
* @param {Object} dimensions
|
|
9
|
+
* @param {number} padding
|
|
10
|
+
* @returns {Object}
|
|
14
11
|
*/
|
|
15
|
-
|
|
16
|
-
if (!data || data.length === 0) return {}
|
|
17
|
-
|
|
18
|
-
const { colorKey = null, padding = 0.2 } = options
|
|
19
|
-
|
|
20
|
-
// Determine if x values are numeric, dates, or categorical
|
|
21
|
-
const xValues = data.map((d) => d[xKey])
|
|
12
|
+
function buildXScale(xValues, dimensions, padding) {
|
|
22
13
|
const xIsDate = xValues.some((v) => v instanceof Date)
|
|
23
14
|
const xIsNumeric = !xIsDate && xValues.every((v) => !isNaN(parseFloat(v)))
|
|
24
15
|
|
|
25
|
-
// Create x-scale based on data type
|
|
26
|
-
let xScale
|
|
27
16
|
if (xIsDate) {
|
|
28
|
-
|
|
17
|
+
return scaleTime()
|
|
29
18
|
.domain([min(xValues), max(xValues)])
|
|
30
19
|
.range([0, dimensions.innerWidth])
|
|
31
20
|
.nice()
|
|
32
|
-
}
|
|
33
|
-
|
|
21
|
+
}
|
|
22
|
+
if (xIsNumeric) {
|
|
23
|
+
return scaleLinear()
|
|
34
24
|
.domain([min([0, ...xValues]), max(xValues)])
|
|
35
25
|
.range([0, dimensions.innerWidth])
|
|
36
26
|
.nice()
|
|
37
|
-
} else {
|
|
38
|
-
xScale = scaleBand().domain(xValues).range([0, dimensions.innerWidth]).padding(padding)
|
|
39
27
|
}
|
|
28
|
+
return scaleBand().domain(xValues).range([0, dimensions.innerWidth]).padding(padding)
|
|
29
|
+
}
|
|
40
30
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
+
}
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
53
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
|
|
54
74
|
|
|
55
|
-
return {
|
|
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
|
+
}
|
|
56
80
|
}
|
|
57
81
|
|
|
58
82
|
/**
|
|
59
83
|
* Calculates the actual chart dimensions after applying margins
|
|
60
84
|
*
|
|
61
|
-
* @param {
|
|
85
|
+
* @param {number} width
|
|
86
|
+
* @param {number} height
|
|
87
|
+
* @param {Object} margin
|
|
62
88
|
* @returns {Object} Dimensions with calculated inner width and height
|
|
63
89
|
*/
|
|
64
90
|
export function calculateChartDimensions(width, height, margin) {
|
|
@@ -81,6 +107,44 @@ export function getOriginValue(scale) {
|
|
|
81
107
|
return scale.ticks ? scale(Math.max(0, min(scale.domain()))) : scale.range()[0]
|
|
82
108
|
}
|
|
83
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
|
+
|
|
84
148
|
/**
|
|
85
149
|
* Creates axis ticks
|
|
86
150
|
*
|
|
@@ -92,31 +156,17 @@ export function getOriginValue(scale) {
|
|
|
92
156
|
*/
|
|
93
157
|
export function createTicks(scale, axis, count = null, fontSize = 12) {
|
|
94
158
|
const [minRange, maxRange] = scale.range()
|
|
95
|
-
|
|
96
|
-
let offset = 0
|
|
159
|
+
const tickCount = count ?? defaultTickCount(maxRange - minRange, axis, fontSize)
|
|
97
160
|
|
|
98
|
-
|
|
99
|
-
if (!count) {
|
|
100
|
-
count = Math.abs((maxRange - minRange) / (fontSize * (axis === 'y' ? 3 : 6)))
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Get ticks based on scale type
|
|
161
|
+
let ticks, offset
|
|
104
162
|
if (scale.ticks) {
|
|
105
|
-
ticks = scale.ticks(Math.round(
|
|
163
|
+
ticks = scale.ticks(Math.round(tickCount))
|
|
164
|
+
offset = 0
|
|
106
165
|
} else {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
ticks = scale.domain()
|
|
111
|
-
if (count < scale.domain().length) {
|
|
112
|
-
const step = Math.ceil(scale.domain().length / count)
|
|
113
|
-
ticks = ticks.filter((_, i) => i % step === 0)
|
|
114
|
-
}
|
|
166
|
+
const band = bandTicks(scale, tickCount)
|
|
167
|
+
ticks = band.ticks
|
|
168
|
+
offset = band.offset
|
|
115
169
|
}
|
|
116
170
|
|
|
117
|
-
|
|
118
|
-
return ticks.map((t) => ({
|
|
119
|
-
value: t,
|
|
120
|
-
position: scale(t) + (axis === 'x' ? offset : 0)
|
|
121
|
-
}))
|
|
171
|
+
return formatTicks(ticks, scale, offset, axis === 'x')
|
|
122
172
|
}
|
package/src/lib/utils.js
CHANGED
|
@@ -6,16 +6,18 @@ import { max } from 'd3-array'
|
|
|
6
6
|
* Creates appropriate scales based on data and dimensions
|
|
7
7
|
*
|
|
8
8
|
* @param {Array} data The dataset
|
|
9
|
-
* @param {string} xKey Field to use for x-axis
|
|
10
|
-
* @param {string} yKey Field to use for y-axis
|
|
11
9
|
* @param {Object} dimensions Chart dimensions
|
|
12
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
13
|
* @param {string} [options.colorKey] Field to use for color mapping
|
|
14
14
|
* @returns {Object} Object containing xScale, yScale, and colorScale
|
|
15
15
|
*/
|
|
16
|
-
export function createScales(data,
|
|
16
|
+
export function createScales(data, dimensions, options = {}) {
|
|
17
17
|
if (!data || data.length === 0) return {}
|
|
18
18
|
|
|
19
|
+
const { xKey, yKey, colorKey } = options
|
|
20
|
+
|
|
19
21
|
const xScale = scaleBand()
|
|
20
22
|
.domain(data.map((d) => d[xKey]))
|
|
21
23
|
.range([0, dimensions.width])
|
|
@@ -28,8 +30,8 @@ export function createScales(data, xKey, yKey, dimensions, options = {}) {
|
|
|
28
30
|
|
|
29
31
|
let colorScale = null
|
|
30
32
|
|
|
31
|
-
if (
|
|
32
|
-
const uniqueCategories = [...new Set(data.map((d) => d[
|
|
33
|
+
if (colorKey) {
|
|
34
|
+
const uniqueCategories = [...new Set(data.map((d) => d[colorKey]))]
|
|
33
35
|
colorScale = scaleOrdinal().domain(uniqueCategories).range(schemeCategory10)
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -84,6 +86,18 @@ export function uniqueId(prefix = 'chart') {
|
|
|
84
86
|
return `${prefix}-${Math.random().toString(36).substring(2, 10)}`
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
/**
|
|
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
|
+
|
|
87
101
|
/**
|
|
88
102
|
* Formats tooltip content for a data point
|
|
89
103
|
*
|
|
@@ -97,13 +111,10 @@ export function formatTooltipContent(d, options = {}) {
|
|
|
97
111
|
const { xKey, yKey, xFormat, yFormat } = options
|
|
98
112
|
|
|
99
113
|
if (xKey && yKey) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const yFormatted = yFormat ? yFormat(yValue) : yValue
|
|
105
|
-
|
|
106
|
-
return `${xKey}: ${xFormatted}<br>${yKey}: ${yFormatted}`
|
|
114
|
+
return [
|
|
115
|
+
formatField(xKey, d[xKey], xFormat),
|
|
116
|
+
formatField(yKey, d[yKey], yFormat)
|
|
117
|
+
].join('<br>')
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
return Object.entries(d)
|