@rokkit/chart 1.0.0-next.151 → 1.0.0-next.155

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 (86) hide show
  1. package/dist/PlotState.svelte.d.ts +26 -0
  2. package/dist/index.d.ts +6 -1
  3. package/dist/lib/brewing/BoxBrewer.svelte.d.ts +3 -5
  4. package/dist/lib/brewing/QuartileBrewer.svelte.d.ts +9 -0
  5. package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +3 -4
  6. package/dist/lib/brewing/colors.d.ts +10 -1
  7. package/dist/lib/brewing/marks/points.d.ts +17 -2
  8. package/dist/lib/keyboard-nav.d.ts +15 -0
  9. package/dist/lib/plot/preset.d.ts +1 -1
  10. package/dist/lib/preset.d.ts +30 -0
  11. package/package.json +2 -1
  12. package/src/AnimatedPlot.svelte +375 -207
  13. package/src/Chart.svelte +81 -84
  14. package/src/ChartProvider.svelte +10 -0
  15. package/src/FacetPlot/Panel.svelte +30 -16
  16. package/src/FacetPlot.svelte +100 -76
  17. package/src/Plot/Area.svelte +26 -19
  18. package/src/Plot/Axis.svelte +81 -59
  19. package/src/Plot/Bar.svelte +47 -89
  20. package/src/Plot/Grid.svelte +23 -19
  21. package/src/Plot/Legend.svelte +213 -147
  22. package/src/Plot/Line.svelte +31 -21
  23. package/src/Plot/Point.svelte +35 -22
  24. package/src/Plot/Root.svelte +46 -91
  25. package/src/Plot/Timeline.svelte +82 -82
  26. package/src/Plot/Tooltip.svelte +68 -62
  27. package/src/Plot.svelte +290 -174
  28. package/src/PlotState.svelte.js +338 -265
  29. package/src/Sparkline.svelte +95 -56
  30. package/src/charts/AreaChart.svelte +22 -20
  31. package/src/charts/BarChart.svelte +23 -21
  32. package/src/charts/BoxPlot.svelte +15 -15
  33. package/src/charts/BubbleChart.svelte +17 -17
  34. package/src/charts/LineChart.svelte +20 -20
  35. package/src/charts/PieChart.svelte +30 -20
  36. package/src/charts/ScatterPlot.svelte +20 -19
  37. package/src/charts/ViolinPlot.svelte +15 -15
  38. package/src/crossfilter/CrossFilter.svelte +33 -29
  39. package/src/crossfilter/FilterBar.svelte +17 -25
  40. package/src/crossfilter/FilterHistogram.svelte +290 -0
  41. package/src/crossfilter/FilterSlider.svelte +69 -65
  42. package/src/crossfilter/createCrossFilter.svelte.js +94 -90
  43. package/src/geoms/Arc.svelte +114 -69
  44. package/src/geoms/Area.svelte +67 -39
  45. package/src/geoms/Bar.svelte +184 -126
  46. package/src/geoms/Box.svelte +101 -91
  47. package/src/geoms/LabelPill.svelte +11 -11
  48. package/src/geoms/Line.svelte +110 -86
  49. package/src/geoms/Point.svelte +130 -90
  50. package/src/geoms/Violin.svelte +51 -41
  51. package/src/geoms/lib/areas.js +122 -99
  52. package/src/geoms/lib/bars.js +195 -144
  53. package/src/index.js +21 -14
  54. package/src/lib/brewing/BoxBrewer.svelte.js +8 -50
  55. package/src/lib/brewing/CartesianBrewer.svelte.js +11 -7
  56. package/src/lib/brewing/PieBrewer.svelte.js +5 -5
  57. package/src/lib/brewing/QuartileBrewer.svelte.js +51 -0
  58. package/src/lib/brewing/ViolinBrewer.svelte.js +8 -49
  59. package/src/lib/brewing/brewer.svelte.js +242 -195
  60. package/src/lib/brewing/colors.js +34 -5
  61. package/src/lib/brewing/marks/arcs.js +28 -28
  62. package/src/lib/brewing/marks/areas.js +54 -41
  63. package/src/lib/brewing/marks/bars.js +34 -34
  64. package/src/lib/brewing/marks/boxes.js +51 -51
  65. package/src/lib/brewing/marks/lines.js +37 -30
  66. package/src/lib/brewing/marks/points.js +74 -26
  67. package/src/lib/brewing/marks/violins.js +57 -57
  68. package/src/lib/brewing/patterns.js +25 -11
  69. package/src/lib/brewing/scales.js +17 -17
  70. package/src/lib/brewing/stats.js +37 -29
  71. package/src/lib/brewing/symbols.js +1 -1
  72. package/src/lib/chart.js +2 -1
  73. package/src/lib/keyboard-nav.js +37 -0
  74. package/src/lib/plot/crossfilter.js +5 -5
  75. package/src/lib/plot/facet.js +30 -30
  76. package/src/lib/plot/frames.js +30 -29
  77. package/src/lib/plot/helpers.js +4 -4
  78. package/src/lib/plot/preset.js +48 -34
  79. package/src/lib/plot/scales.js +64 -39
  80. package/src/lib/plot/stat.js +47 -47
  81. package/src/lib/preset.js +41 -0
  82. package/src/patterns/DefinePatterns.svelte +24 -24
  83. package/src/patterns/README.md +3 -0
  84. package/src/patterns/patterns.js +328 -176
  85. package/src/patterns/scale.js +61 -32
  86. 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
- return `chart-pat-${String(key).replace(/\s+/g, '-').replace(/[^\w-]/g, '_')}`
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
- 'diagonal',
14
- 'dots',
15
- 'triangles',
16
- 'hatch',
17
- 'lattice',
18
- 'swell',
19
- 'checkerboard',
20
- 'waves',
21
- 'petals'
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
- return new Map(values.map((v, i) => [v, PATTERN_ORDER[i % PATTERN_ORDER.length]]))
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
- 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)
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
- if (layer.data && layer.y) return max(layer.data, (d) => Number(d[layer.y])) ?? 0
26
- return 0
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
- 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()
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
- const maxVal = max(data, (d) => Number(d[field])) ?? 1
50
- return scaleSqrt().domain([0, maxVal]).range([0, maxRadius])
49
+ const maxVal = max(data, (d) => Number(d[field])) ?? 1
50
+ return scaleSqrt().domain([0, maxVal]).range([0, maxRadius])
51
51
  }
@@ -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
- return quantile([...values].sort(ascending), p)
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
- sum,
14
- mean,
15
- min,
16
- max,
17
- count: (values) => values.length
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
- 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) => { const q1 = sortedQuantile(v, 0.25); const q3 = sortedQuantile(v, 0.75); return q1 - 1.5 * (q3 - q1) },
39
- iqr_max: (v) => { const q1 = sortedQuantile(v, 0.25); const q3 = sortedQuantile(v, 0.75); return q3 + 1.5 * (q3 - q1) }
40
- })
41
- .rollup()
42
- .select()
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
- return stat === 'identity' || by.length === 0 || value === null || value === undefined
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
- if (isIdentityOrEmpty(stat, by, value)) return data
59
- const fn = typeof stat === 'function' ? stat : STAT_FNS[stat]
60
- if (fn === null || fn === undefined) return data
61
- return dataset(data)
62
- .groupBy(...by)
63
- .summarize((row) => row[value], { [value]: fn })
64
- .rollup()
65
- .select()
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
- return new Map(values.map((v, i) => [v, SYMBOL_ORDER[i % SYMBOL_ORDER.length]]))
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 = (Number(opts.spacing) >= 0 && Number(opts.spacing) <= 0.5) ? Number(opts.spacing) : 0
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
- 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
- })
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
  }
@@ -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
- 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
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
- const { x: xf, y: yf } = channels
31
- const allData = [...panels.values()].flat()
30
+ const { x: xf, y: yf } = channels
31
+ const allData = [...panels.values()].flat()
32
32
 
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'
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
- // 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]))
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
- 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'
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
- const xDomain = freeX
49
- ? (xIsCategorical ? [...new Set(rows.map((d) => d[xf]))] : extent(rows, (d) => Number(d[xf])))
50
- : globalXDomain
51
- const yDomain = freeY
52
- ? extent(rows, (d) => Number(d[yf]))
53
- : globalYDomain
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
- result.set(key, { xDomain, yDomain })
56
- }
57
- return result
55
+ result.set(key, { xDomain, yDomain })
56
+ }
57
+ return result
58
58
  }
@@ -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
- 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
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
- const { x: xf, y: yf, color: cf } = channels
37
- const groupFields = [xf, ...(cf ? [cf] : [])].filter(Boolean)
36
+ const { x: xf, y: yf, color: cf } = channels
37
+ const groupFields = [xf, ...(cf ? [cf] : [])].filter(Boolean)
38
38
 
39
- if (groupFields.length === 0) return data
39
+ if (groupFields.length === 0) return data
40
40
 
41
- const nested = dataset(data)
42
- .groupBy(...groupFields)
43
- .alignBy(byField)
44
- .usingTemplate({ [yf]: 0 })
45
- .rollup()
46
- .select()
41
+ const nested = dataset(data)
42
+ .groupBy(...groupFields)
43
+ .alignBy(byField)
44
+ .usingTemplate({ [yf]: 0 })
45
+ .rollup()
46
+ .select()
47
47
 
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
- })
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
- const { x: xf, y: yf } = channels
69
+ const { x: xf, y: yf } = channels
70
70
 
71
- const sampleX = data[0]?.[xf]
72
- const xDomain = typeof sampleX === 'string'
73
- ? [...new Set(data.map((d) => d[xf]))]
74
- : extent(data, (d) => Number(d[xf]))
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
- const [, yMax] = extent(data, (d) => Number(d[yf]))
77
- const yDomain = [0, yMax ?? 0]
77
+ const [, yMax] = extent(data, (d) => Number(d[yf]))
78
+ const yDomain = [0, yMax ?? 0]
78
79
 
79
- return { xDomain, yDomain }
80
+ return { xDomain, yDomain }
80
81
  }
@@ -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
- return helpers?.format?.[field] ?? ((v) => String(v))
4
+ return helpers?.format?.[field] ?? ((v) => String(v))
5
5
  }
6
6
 
7
7
  export function resolveTooltip(helpers = {}) {
8
- return helpers?.tooltip ?? null
8
+ return helpers?.tooltip ?? null
9
9
  }
10
10
 
11
11
  export function resolveGeom(type, helpers = {}) {
12
- if (BUILT_IN_GEOMS.has(type)) return null
13
- return helpers?.geoms?.[type] ?? null
12
+ if (BUILT_IN_GEOMS.has(type)) return null
13
+ return helpers?.geoms?.[type] ?? null
14
14
  }
@@ -1,53 +1,67 @@
1
- import palette from '../brewing/palette.json'
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
- colors: palette.map((p) => p.shades.light.fill),
9
- patterns: PATTERN_ORDER,
10
- symbols: SYMBOL_ORDER
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
- colors: ['#66c2a5','#fc8d62','#8da0cb','#e78ac3','#a6d854','#ffd92f','#e5c494','#b3b3b3'],
15
- patterns: PATTERN_ORDER,
16
- symbols: SYMBOL_ORDER
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
- colors: ['#f0f0f0','#bdbdbd','#969696','#737373','#525252','#252525','#000000'],
21
- patterns: ['CrossHatch','DiagonalLines','Dots','Brick','Waves','Triangles','HorizontalLines'],
22
- symbols: SYMBOL_ORDER
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
- default: DEFAULT_PRESET,
27
- accessible: ACCESSIBLE_PRESET,
28
- print: PRINT_PRESET
38
+ default: DEFAULT_PRESET,
39
+ accessible: ACCESSIBLE_PRESET,
40
+ print: PRINT_PRESET
29
41
  }
30
42
 
31
43
  export function resolvePreset(name, helpers = {}) {
32
- let resolved = null
33
-
34
- if (name && BUILT_IN_PRESETS[name]) {
35
- resolved = BUILT_IN_PRESETS[name]
36
- } else if (name && helpers?.presets?.[name]) {
37
- resolved = helpers.presets[name]
38
- } else if (name) {
39
- // eslint-disable-next-line no-console
40
- console.warn(`[Plot] Unknown preset "${name}" — falling back to default. Add it to helpers.presets to suppress this warning.`)
41
- resolved = DEFAULT_PRESET
42
- } else if (helpers?.preset) {
43
- resolved = helpers.preset
44
- } else {
45
- resolved = DEFAULT_PRESET
46
- }
47
-
48
- return {
49
- colors: resolved.colors ?? DEFAULT_PRESET.colors,
50
- patterns: resolved.patterns ?? DEFAULT_PRESET.patterns,
51
- symbols: resolved.symbols ?? DEFAULT_PRESET.symbols
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
  }