@rokkit/chart 1.0.0-next.151 → 1.0.0-next.158
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/PlotState.svelte.d.ts +26 -0
- package/dist/index.d.ts +6 -1
- package/dist/lib/brewing/BoxBrewer.svelte.d.ts +3 -5
- package/dist/lib/brewing/QuartileBrewer.svelte.d.ts +9 -0
- package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +3 -4
- package/dist/lib/brewing/colors.d.ts +10 -1
- package/dist/lib/brewing/marks/points.d.ts +17 -2
- package/dist/lib/keyboard-nav.d.ts +15 -0
- package/dist/lib/plot/preset.d.ts +1 -1
- package/dist/lib/preset.d.ts +30 -0
- package/package.json +2 -1
- package/src/AnimatedPlot.svelte +375 -207
- package/src/Chart.svelte +81 -84
- package/src/ChartProvider.svelte +10 -0
- package/src/FacetPlot/Panel.svelte +30 -16
- package/src/FacetPlot.svelte +100 -76
- package/src/Plot/Area.svelte +26 -19
- package/src/Plot/Axis.svelte +81 -59
- package/src/Plot/Bar.svelte +47 -89
- package/src/Plot/Grid.svelte +23 -19
- package/src/Plot/Legend.svelte +213 -147
- package/src/Plot/Line.svelte +31 -21
- package/src/Plot/Point.svelte +35 -22
- package/src/Plot/Root.svelte +46 -91
- package/src/Plot/Timeline.svelte +82 -82
- package/src/Plot/Tooltip.svelte +68 -62
- package/src/Plot.svelte +290 -174
- package/src/PlotState.svelte.js +338 -265
- package/src/Sparkline.svelte +95 -56
- package/src/charts/AreaChart.svelte +22 -20
- package/src/charts/BarChart.svelte +23 -21
- package/src/charts/BoxPlot.svelte +15 -15
- package/src/charts/BubbleChart.svelte +17 -17
- package/src/charts/LineChart.svelte +20 -20
- package/src/charts/PieChart.svelte +30 -20
- package/src/charts/ScatterPlot.svelte +20 -19
- package/src/charts/ViolinPlot.svelte +15 -15
- package/src/crossfilter/CrossFilter.svelte +33 -29
- package/src/crossfilter/FilterBar.svelte +17 -25
- package/src/crossfilter/FilterHistogram.svelte +290 -0
- package/src/crossfilter/FilterSlider.svelte +69 -65
- package/src/crossfilter/createCrossFilter.svelte.js +94 -90
- package/src/geoms/Arc.svelte +114 -69
- package/src/geoms/Area.svelte +67 -39
- package/src/geoms/Bar.svelte +184 -126
- package/src/geoms/Box.svelte +101 -91
- package/src/geoms/LabelPill.svelte +11 -11
- package/src/geoms/Line.svelte +110 -86
- package/src/geoms/Point.svelte +130 -90
- package/src/geoms/Violin.svelte +51 -41
- package/src/geoms/lib/areas.js +122 -99
- package/src/geoms/lib/bars.js +195 -144
- package/src/index.js +21 -14
- package/src/lib/brewing/BoxBrewer.svelte.js +8 -50
- package/src/lib/brewing/CartesianBrewer.svelte.js +11 -7
- package/src/lib/brewing/PieBrewer.svelte.js +5 -5
- package/src/lib/brewing/QuartileBrewer.svelte.js +51 -0
- package/src/lib/brewing/ViolinBrewer.svelte.js +8 -49
- package/src/lib/brewing/brewer.svelte.js +242 -195
- package/src/lib/brewing/colors.js +34 -5
- package/src/lib/brewing/marks/arcs.js +28 -28
- package/src/lib/brewing/marks/areas.js +54 -41
- package/src/lib/brewing/marks/bars.js +34 -34
- package/src/lib/brewing/marks/boxes.js +51 -51
- package/src/lib/brewing/marks/lines.js +37 -30
- package/src/lib/brewing/marks/points.js +74 -26
- package/src/lib/brewing/marks/violins.js +57 -57
- package/src/lib/brewing/patterns.js +25 -11
- package/src/lib/brewing/scales.js +17 -17
- package/src/lib/brewing/stats.js +37 -29
- package/src/lib/brewing/symbols.js +1 -1
- package/src/lib/chart.js +2 -1
- package/src/lib/keyboard-nav.js +37 -0
- package/src/lib/plot/crossfilter.js +5 -5
- package/src/lib/plot/facet.js +30 -30
- package/src/lib/plot/frames.js +30 -29
- package/src/lib/plot/helpers.js +4 -4
- package/src/lib/plot/preset.js +48 -34
- package/src/lib/plot/scales.js +64 -39
- package/src/lib/plot/stat.js +47 -47
- package/src/lib/preset.js +41 -0
- package/src/patterns/DefinePatterns.svelte +24 -24
- package/src/patterns/README.md +3 -0
- package/src/patterns/patterns.js +328 -176
- package/src/patterns/scale.js +61 -32
- package/src/spec/chart-spec.js +64 -21
|
@@ -5,20 +5,34 @@
|
|
|
5
5
|
* @returns {string}
|
|
6
6
|
*/
|
|
7
7
|
export function toPatternId(key) {
|
|
8
|
-
|
|
8
|
+
return `chart-pat-${String(key)
|
|
9
|
+
.replace(/\s+/g, '-')
|
|
10
|
+
.replace(/[^\w-]/g, '_')}`
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
// Keys must match the keys in packages/chart/src/patterns/patterns.js
|
|
12
14
|
export const PATTERN_ORDER = [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
'diagonal',
|
|
16
|
+
'dots',
|
|
17
|
+
'triangles',
|
|
18
|
+
'hatch',
|
|
19
|
+
'lattice',
|
|
20
|
+
'swell',
|
|
21
|
+
'checkerboard',
|
|
22
|
+
'waves',
|
|
23
|
+
'petals',
|
|
24
|
+
'brick',
|
|
25
|
+
'diamonds',
|
|
26
|
+
'tile',
|
|
27
|
+
'scales',
|
|
28
|
+
'circles',
|
|
29
|
+
'pip',
|
|
30
|
+
'rings',
|
|
31
|
+
'chevrons',
|
|
32
|
+
'shards',
|
|
33
|
+
'wedge',
|
|
34
|
+
'argyle',
|
|
35
|
+
'shell'
|
|
22
36
|
]
|
|
23
37
|
|
|
24
38
|
/**
|
|
@@ -27,5 +41,5 @@ export const PATTERN_ORDER = [
|
|
|
27
41
|
* @returns {Map<unknown, string>}
|
|
28
42
|
*/
|
|
29
43
|
export function assignPatterns(values) {
|
|
30
|
-
|
|
44
|
+
return new Map(values.map((v, i) => [v, PATTERN_ORDER[i % PATTERN_ORDER.length]]))
|
|
31
45
|
}
|
|
@@ -9,21 +9,21 @@ import { max, extent } from 'd3-array'
|
|
|
9
9
|
* @param {{ padding?: number }} opts
|
|
10
10
|
*/
|
|
11
11
|
export function buildXScale(data, field, width, opts = {}) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
22
|
}
|
|
23
23
|
|
|
24
24
|
function maxFromLayer(layer) {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if (layer.data && layer.y) return max(layer.data, (d) => Number(d[layer.y])) ?? 0
|
|
26
|
+
return 0
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
@@ -34,9 +34,9 @@ function maxFromLayer(layer) {
|
|
|
34
34
|
* @param {Array<{data?: Object[], y?: string}>} layers
|
|
35
35
|
*/
|
|
36
36
|
export function buildYScale(data, field, height, layers = []) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -46,6 +46,6 @@ export function buildYScale(data, field, height, layers = []) {
|
|
|
46
46
|
* @param {number} maxRadius
|
|
47
47
|
*/
|
|
48
48
|
export function buildSizeScale(data, field, maxRadius = 20) {
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
const maxVal = max(data, (d) => Number(d[field])) ?? 1
|
|
50
|
+
return scaleSqrt().domain([0, maxVal]).range([0, maxRadius])
|
|
51
51
|
}
|
package/src/lib/brewing/stats.js
CHANGED
|
@@ -2,7 +2,7 @@ import { sum, mean, min, max, quantile, ascending } from 'd3-array'
|
|
|
2
2
|
import { dataset } from '@rokkit/data'
|
|
3
3
|
|
|
4
4
|
function sortedQuantile(values, p) {
|
|
5
|
-
|
|
5
|
+
return quantile([...values].sort(ascending), p)
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -10,11 +10,11 @@ function sortedQuantile(values, p) {
|
|
|
10
10
|
* @type {Record<string, (values: number[]) => number>}
|
|
11
11
|
*/
|
|
12
12
|
export const STAT_FNS = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
sum,
|
|
14
|
+
mean,
|
|
15
|
+
min,
|
|
16
|
+
max,
|
|
17
|
+
count: (values) => values.length
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -26,20 +26,28 @@ export const STAT_FNS = {
|
|
|
26
26
|
* @returns {Object[]}
|
|
27
27
|
*/
|
|
28
28
|
export function applyBoxStat(data, channels) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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()
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
/**
|
|
@@ -51,16 +59,16 @@ export function applyBoxStat(data, channels) {
|
|
|
51
59
|
* @returns {Object[]}
|
|
52
60
|
*/
|
|
53
61
|
function isIdentityOrEmpty(stat, by, value) {
|
|
54
|
-
|
|
62
|
+
return stat === 'identity' || by.length === 0 || value === null || value === undefined
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
export function applyAggregate(data, { by, value, stat }) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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()
|
|
66
74
|
}
|
|
@@ -6,5 +6,5 @@ export const SYMBOL_ORDER = ['circle', 'square', 'triangle', 'diamond', 'plus',
|
|
|
6
6
|
* @returns {Map<unknown, string>}
|
|
7
7
|
*/
|
|
8
8
|
export function assignSymbols(values) {
|
|
9
|
-
|
|
9
|
+
return new Map(values.map((v, i) => [v, SYMBOL_ORDER[i % SYMBOL_ORDER.length]]))
|
|
10
10
|
}
|
package/src/lib/chart.js
CHANGED
|
@@ -59,7 +59,8 @@ class Chart {
|
|
|
59
59
|
this.color = opts.color || opts.fill
|
|
60
60
|
this.shape = opts.shape || opts.fill
|
|
61
61
|
this.padding = opts.padding !== undefined ? Number(opts.padding) : 32
|
|
62
|
-
this.spacing =
|
|
62
|
+
this.spacing =
|
|
63
|
+
Number(opts.spacing) >= 0 && Number(opts.spacing) <= 0.5 ? Number(opts.spacing) : 0
|
|
63
64
|
this.margin = {
|
|
64
65
|
top: Number(opts.margin?.top) || 0,
|
|
65
66
|
left: Number(opts.margin?.left) || 0,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte action: arrow key navigation between sibling data elements within a geom.
|
|
3
|
+
*
|
|
4
|
+
* When applied to a focusable SVG element with `enabled=true`, ArrowLeft/ArrowRight
|
|
5
|
+
* move focus between elements sharing the same `[data-plot-geom]` container.
|
|
6
|
+
*
|
|
7
|
+
* Usage: `<circle use:keyboardNav={keyboard} ...>`
|
|
8
|
+
*
|
|
9
|
+
* @param {Element} node
|
|
10
|
+
* @param {boolean} enabled
|
|
11
|
+
*/
|
|
12
|
+
export function keyboardNav(node, enabled) {
|
|
13
|
+
function handleKeydown(e) {
|
|
14
|
+
if (!enabled) return
|
|
15
|
+
if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return
|
|
16
|
+
const container = node.closest('[data-plot-geom]')
|
|
17
|
+
if (!container) return
|
|
18
|
+
const elements = [...container.querySelectorAll('[data-plot-element]')].filter(
|
|
19
|
+
(el) => /** @type {HTMLElement|SVGElement} */ (el).tabIndex >= 0
|
|
20
|
+
)
|
|
21
|
+
const idx = elements.indexOf(node)
|
|
22
|
+
if (idx === -1) return
|
|
23
|
+
const nextIdx =
|
|
24
|
+
e.key === 'ArrowRight' ? Math.min(idx + 1, elements.length - 1) : Math.max(idx - 1, 0)
|
|
25
|
+
if (nextIdx !== idx) {
|
|
26
|
+
e.preventDefault()
|
|
27
|
+
/** @type {HTMLElement|SVGElement} */ (elements[nextIdx]).focus()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
node.addEventListener('keydown', handleKeydown)
|
|
31
|
+
return {
|
|
32
|
+
update(newEnabled) {
|
|
33
|
+
enabled = newEnabled
|
|
34
|
+
},
|
|
35
|
+
destroy: () => node.removeEventListener('keydown', handleKeydown)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* @returns {{ data: Object, dimmed: boolean }[]}
|
|
9
9
|
*/
|
|
10
10
|
export function applyDimming(data, cf, channels) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
const fields = Object.values(channels).filter(Boolean)
|
|
12
|
+
return data.map((row) => {
|
|
13
|
+
const dimmed = fields.some((field) => cf.isDimmed(field, row[field]))
|
|
14
|
+
return { data: row, dimmed }
|
|
15
|
+
})
|
|
16
16
|
}
|
package/src/lib/plot/facet.js
CHANGED
|
@@ -9,13 +9,13 @@ import { extent } from 'd3-array'
|
|
|
9
9
|
* @returns {Map<unknown, Object[]>}
|
|
10
10
|
*/
|
|
11
11
|
export function splitByField(data, field) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
const map = new Map()
|
|
13
|
+
for (const row of data) {
|
|
14
|
+
const key = row[field]
|
|
15
|
+
if (!map.has(key)) map.set(key, [])
|
|
16
|
+
map.get(key).push(row)
|
|
17
|
+
}
|
|
18
|
+
return map
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -27,32 +27,32 @@ export function splitByField(data, field) {
|
|
|
27
27
|
* @returns {Map<unknown, { xDomain: unknown[], yDomain: [number, number] }>}
|
|
28
28
|
*/
|
|
29
29
|
export function getFacetDomains(panels, channels, scalesMode = 'fixed') {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const { x: xf, y: yf } = channels
|
|
31
|
+
const allData = [...panels.values()].flat()
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
// Determine if x is categorical (string) or numeric
|
|
34
|
+
const sampleXVal = allData.find((d) => d[xf] !== null && d[xf] !== undefined)?.[xf]
|
|
35
|
+
const xIsCategorical = typeof sampleXVal === 'string'
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
// Global domains (for fixed mode)
|
|
38
|
+
const globalXDomain = xIsCategorical
|
|
39
|
+
? [...new Set(allData.map((d) => d[xf]))]
|
|
40
|
+
: extent(allData, (d) => Number(d[xf]))
|
|
41
|
+
const globalYDomain = extent(allData, (d) => Number(d[yf]))
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
const result = new Map()
|
|
44
|
+
for (const [key, rows] of panels.entries()) {
|
|
45
|
+
const freeX = scalesMode === 'free' || scalesMode === 'free_x'
|
|
46
|
+
const freeY = scalesMode === 'free' || scalesMode === 'free_y'
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
const xDomain = freeX
|
|
49
|
+
? xIsCategorical
|
|
50
|
+
? [...new Set(rows.map((d) => d[xf]))]
|
|
51
|
+
: extent(rows, (d) => Number(d[xf]))
|
|
52
|
+
: globalXDomain
|
|
53
|
+
const yDomain = freeY ? extent(rows, (d) => Number(d[yf])) : globalYDomain
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
result.set(key, { xDomain, yDomain })
|
|
56
|
+
}
|
|
57
|
+
return result
|
|
58
58
|
}
|
package/src/lib/plot/frames.js
CHANGED
|
@@ -10,13 +10,13 @@ import { dataset } from '@rokkit/data'
|
|
|
10
10
|
* @returns {Map<unknown, Object[]>}
|
|
11
11
|
*/
|
|
12
12
|
export function extractFrames(data, timeField) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
const map = new Map()
|
|
14
|
+
for (const row of data) {
|
|
15
|
+
const key = row[timeField]
|
|
16
|
+
if (!map.has(key)) map.set(key, [])
|
|
17
|
+
map.get(key).push(row)
|
|
18
|
+
}
|
|
19
|
+
return map
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -33,23 +33,23 @@ export function extractFrames(data, timeField) {
|
|
|
33
33
|
* @returns {Object[]}
|
|
34
34
|
*/
|
|
35
35
|
export function completeFrames(data, channels, byField) {
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
const { x: xf, y: yf, color: cf } = channels
|
|
37
|
+
const groupFields = [xf, ...(cf ? [cf] : [])].filter(Boolean)
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
if (groupFields.length === 0) return data
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
const nested = dataset(data)
|
|
42
|
+
.groupBy(...groupFields)
|
|
43
|
+
.alignBy(byField)
|
|
44
|
+
.usingTemplate({ [yf]: 0 })
|
|
45
|
+
.rollup()
|
|
46
|
+
.select()
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
return nested.flatMap((row) => {
|
|
49
|
+
const groupKey = groupFields.reduce((acc, f) => ({ ...acc, [f]: row[f] }), {})
|
|
50
|
+
// strip the actual_flag marker added by alignBy
|
|
51
|
+
return row.children.map(({ actual_flag: _af, ...child }) => ({ ...groupKey, ...child }))
|
|
52
|
+
})
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
@@ -66,15 +66,16 @@ export function completeFrames(data, channels, byField) {
|
|
|
66
66
|
* @returns {{ xDomain: unknown[], yDomain: [number, number] }}
|
|
67
67
|
*/
|
|
68
68
|
export function computeStaticDomains(data, channels) {
|
|
69
|
-
|
|
69
|
+
const { x: xf, y: yf } = channels
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
const sampleX = data[0]?.[xf]
|
|
72
|
+
const xDomain =
|
|
73
|
+
typeof sampleX === 'string'
|
|
74
|
+
? [...new Set(data.map((d) => d[xf]))]
|
|
75
|
+
: extent(data, (d) => Number(d[xf]))
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
const [, yMax] = extent(data, (d) => Number(d[yf]))
|
|
78
|
+
const yDomain = [0, yMax ?? 0]
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
return { xDomain, yDomain }
|
|
80
81
|
}
|
package/src/lib/plot/helpers.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
const BUILT_IN_GEOMS = new Set(['bar', 'line', 'area', 'point', 'box', 'violin', 'arc'])
|
|
2
2
|
|
|
3
3
|
export function resolveFormat(field, helpers = {}) {
|
|
4
|
-
|
|
4
|
+
return helpers?.format?.[field] ?? ((v) => String(v))
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function resolveTooltip(helpers = {}) {
|
|
8
|
-
|
|
8
|
+
return helpers?.tooltip ?? null
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function resolveGeom(type, helpers = {}) {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
if (BUILT_IN_GEOMS.has(type)) return null
|
|
13
|
+
return helpers?.geoms?.[type] ?? null
|
|
14
14
|
}
|
package/src/lib/plot/preset.js
CHANGED
|
@@ -1,53 +1,67 @@
|
|
|
1
|
-
import
|
|
1
|
+
import masterPalette from '../palette.json'
|
|
2
|
+
import { defaultPreset } from '../preset.js'
|
|
2
3
|
import { PATTERN_ORDER } from '../brewing/patterns.js'
|
|
3
4
|
import { SYMBOL_ORDER } from '../brewing/symbols.js'
|
|
4
5
|
|
|
5
6
|
/** @typedef {{ colors: string[], patterns: string[], symbols: string[] }} PlotPreset */
|
|
6
7
|
|
|
7
8
|
export const DEFAULT_PRESET = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
colors: defaultPreset.colors.map((name) => {
|
|
10
|
+
const group = masterPalette[name]
|
|
11
|
+
return group?.[defaultPreset.shades.light.fill] ?? '#888'
|
|
12
|
+
}),
|
|
13
|
+
patterns: PATTERN_ORDER,
|
|
14
|
+
symbols: SYMBOL_ORDER
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
export const ACCESSIBLE_PRESET = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
colors: ['#66c2a5', '#fc8d62', '#8da0cb', '#e78ac3', '#a6d854', '#ffd92f', '#e5c494', '#b3b3b3'],
|
|
19
|
+
patterns: PATTERN_ORDER,
|
|
20
|
+
symbols: SYMBOL_ORDER
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
export const PRINT_PRESET = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
colors: ['#f0f0f0', '#bdbdbd', '#969696', '#737373', '#525252', '#252525', '#000000'],
|
|
25
|
+
patterns: [
|
|
26
|
+
'CrossHatch',
|
|
27
|
+
'DiagonalLines',
|
|
28
|
+
'Dots',
|
|
29
|
+
'Brick',
|
|
30
|
+
'Waves',
|
|
31
|
+
'Triangles',
|
|
32
|
+
'HorizontalLines'
|
|
33
|
+
],
|
|
34
|
+
symbols: SYMBOL_ORDER
|
|
23
35
|
}
|
|
24
36
|
|
|
25
37
|
const BUILT_IN_PRESETS = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
default: DEFAULT_PRESET,
|
|
39
|
+
accessible: ACCESSIBLE_PRESET,
|
|
40
|
+
print: PRINT_PRESET
|
|
29
41
|
}
|
|
30
42
|
|
|
31
43
|
export function resolvePreset(name, helpers = {}) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
let resolved = null
|
|
45
|
+
|
|
46
|
+
if (name && BUILT_IN_PRESETS[name]) {
|
|
47
|
+
resolved = BUILT_IN_PRESETS[name]
|
|
48
|
+
} else if (name && helpers?.presets?.[name]) {
|
|
49
|
+
resolved = helpers.presets[name]
|
|
50
|
+
} else if (name) {
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.warn(
|
|
53
|
+
`[Plot] Unknown preset "${name}" — falling back to default. Add it to helpers.presets to suppress this warning.`
|
|
54
|
+
)
|
|
55
|
+
resolved = DEFAULT_PRESET
|
|
56
|
+
} else if (helpers?.preset) {
|
|
57
|
+
resolved = helpers.preset
|
|
58
|
+
} else {
|
|
59
|
+
resolved = DEFAULT_PRESET
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
colors: resolved.colors ?? DEFAULT_PRESET.colors,
|
|
64
|
+
patterns: resolved.patterns ?? DEFAULT_PRESET.patterns,
|
|
65
|
+
symbols: resolved.symbols ?? DEFAULT_PRESET.symbols
|
|
66
|
+
}
|
|
53
67
|
}
|