@rokkit/chart 1.0.0-next.146 → 1.0.0-next.148

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 (157) hide show
  1. package/dist/Plot/index.d.ts +4 -0
  2. package/dist/PlotState.svelte.d.ts +47 -0
  3. package/dist/crossfilter/createCrossFilter.svelte.d.ts +15 -0
  4. package/dist/geoms/lib/areas.d.ts +52 -0
  5. package/dist/geoms/lib/bars.d.ts +3 -0
  6. package/dist/index.d.ts +38 -1
  7. package/dist/lib/brewing/BoxBrewer.svelte.d.ts +10 -0
  8. package/dist/lib/brewing/CartesianBrewer.svelte.d.ts +8 -0
  9. package/dist/lib/brewing/PieBrewer.svelte.d.ts +8 -0
  10. package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +9 -0
  11. package/dist/lib/brewing/brewer.svelte.d.ts +145 -0
  12. package/dist/lib/brewing/colors.d.ts +17 -0
  13. package/dist/lib/brewing/marks/arcs.d.ts +17 -0
  14. package/dist/lib/brewing/marks/areas.d.ts +31 -0
  15. package/dist/lib/brewing/marks/bars.d.ts +1 -0
  16. package/dist/lib/brewing/marks/boxes.d.ts +24 -0
  17. package/dist/lib/brewing/marks/lines.d.ts +24 -0
  18. package/dist/lib/brewing/marks/points.d.ts +40 -0
  19. package/dist/lib/brewing/marks/violins.d.ts +20 -0
  20. package/dist/lib/brewing/patterns.d.ts +14 -0
  21. package/dist/lib/brewing/scales.d.ts +28 -0
  22. package/dist/lib/brewing/stats.d.ts +31 -0
  23. package/dist/lib/brewing/symbols.d.ts +7 -0
  24. package/dist/lib/plot/chartProps.d.ts +177 -0
  25. package/dist/lib/plot/crossfilter.d.ts +13 -0
  26. package/dist/lib/plot/facet.d.ts +24 -0
  27. package/dist/lib/plot/frames.d.ts +45 -0
  28. package/dist/lib/plot/helpers.d.ts +3 -0
  29. package/dist/lib/plot/preset.d.ts +29 -0
  30. package/dist/lib/plot/scales.d.ts +5 -0
  31. package/dist/lib/plot/stat.d.ts +32 -0
  32. package/dist/lib/plot/types.d.ts +89 -0
  33. package/dist/lib/scales.svelte.d.ts +1 -1
  34. package/dist/lib/swatch.d.ts +12 -0
  35. package/dist/lib/utils.d.ts +1 -0
  36. package/dist/lib/xscale.d.ts +11 -0
  37. package/dist/patterns/index.d.ts +4 -9
  38. package/dist/patterns/patterns.d.ts +72 -0
  39. package/dist/patterns/scale.d.ts +30 -0
  40. package/package.json +9 -3
  41. package/src/AnimatedPlot.svelte +194 -0
  42. package/src/Chart.svelte +101 -0
  43. package/src/FacetPlot/Panel.svelte +23 -0
  44. package/src/FacetPlot.svelte +90 -0
  45. package/src/Plot/Arc.svelte +29 -0
  46. package/src/Plot/Area.svelte +25 -0
  47. package/src/Plot/Axis.svelte +62 -84
  48. package/src/Plot/Grid.svelte +20 -58
  49. package/src/Plot/Legend.svelte +160 -120
  50. package/src/Plot/Line.svelte +27 -0
  51. package/src/Plot/Point.svelte +27 -0
  52. package/src/Plot/Timeline.svelte +95 -0
  53. package/src/Plot/Tooltip.svelte +81 -0
  54. package/src/Plot/index.js +4 -0
  55. package/src/Plot.svelte +189 -0
  56. package/src/PlotState.svelte.js +278 -0
  57. package/src/Sparkline.svelte +69 -0
  58. package/src/charts/AreaChart.svelte +25 -0
  59. package/src/charts/BarChart.svelte +26 -0
  60. package/src/charts/BoxPlot.svelte +21 -0
  61. package/src/charts/BubbleChart.svelte +23 -0
  62. package/src/charts/LineChart.svelte +26 -0
  63. package/src/charts/PieChart.svelte +25 -0
  64. package/src/charts/ScatterPlot.svelte +25 -0
  65. package/src/charts/ViolinPlot.svelte +21 -0
  66. package/src/crossfilter/CrossFilter.svelte +38 -0
  67. package/src/crossfilter/FilterBar.svelte +32 -0
  68. package/src/crossfilter/FilterSlider.svelte +79 -0
  69. package/src/crossfilter/createCrossFilter.svelte.js +113 -0
  70. package/src/elements/SymbolGrid.svelte +6 -7
  71. package/src/geoms/Arc.svelte +81 -0
  72. package/src/geoms/Area.svelte +50 -0
  73. package/src/geoms/Bar.svelte +142 -0
  74. package/src/geoms/Box.svelte +101 -0
  75. package/src/geoms/LabelPill.svelte +17 -0
  76. package/src/geoms/Line.svelte +100 -0
  77. package/src/geoms/Point.svelte +100 -0
  78. package/src/geoms/Violin.svelte +44 -0
  79. package/src/geoms/lib/areas.js +131 -0
  80. package/src/geoms/lib/bars.js +172 -0
  81. package/src/index.js +52 -3
  82. package/src/lib/brewing/BoxBrewer.svelte.js +56 -0
  83. package/src/lib/brewing/CartesianBrewer.svelte.js +16 -0
  84. package/src/lib/brewing/PieBrewer.svelte.js +14 -0
  85. package/src/lib/brewing/ViolinBrewer.svelte.js +55 -0
  86. package/src/lib/brewing/brewer.svelte.js +229 -0
  87. package/src/lib/brewing/colors.js +22 -0
  88. package/src/lib/brewing/marks/arcs.js +43 -0
  89. package/src/lib/brewing/marks/areas.js +59 -0
  90. package/src/lib/brewing/marks/bars.js +49 -0
  91. package/src/lib/brewing/marks/boxes.js +75 -0
  92. package/src/lib/brewing/marks/lines.js +48 -0
  93. package/src/lib/brewing/marks/points.js +57 -0
  94. package/src/lib/brewing/marks/violins.js +90 -0
  95. package/src/lib/brewing/patterns.js +31 -0
  96. package/src/lib/brewing/scales.js +51 -0
  97. package/src/lib/brewing/scales.svelte.js +2 -26
  98. package/src/lib/brewing/stats.js +62 -0
  99. package/src/lib/brewing/symbols.js +10 -0
  100. package/src/lib/plot/chartProps.js +76 -0
  101. package/src/lib/plot/crossfilter.js +16 -0
  102. package/src/lib/plot/facet.js +58 -0
  103. package/src/lib/plot/frames.js +90 -0
  104. package/src/lib/plot/helpers.js +14 -0
  105. package/src/lib/plot/preset.js +53 -0
  106. package/src/lib/plot/scales.js +56 -0
  107. package/src/lib/plot/stat.js +92 -0
  108. package/src/lib/plot/types.js +65 -0
  109. package/src/lib/scales.svelte.js +2 -26
  110. package/src/lib/swatch.js +13 -0
  111. package/src/lib/utils.js +9 -0
  112. package/src/lib/xscale.js +31 -0
  113. package/src/patterns/DefinePatterns.svelte +32 -0
  114. package/src/patterns/PatternDef.svelte +27 -0
  115. package/src/patterns/index.js +4 -14
  116. package/src/patterns/patterns.js +208 -0
  117. package/src/patterns/scale.js +87 -0
  118. package/src/spec/chart-spec.js +29 -0
  119. package/src/symbols/Shape.svelte +1 -1
  120. package/src/symbols/constants/index.js +1 -1
  121. package/dist/old_lib/index.d.ts +0 -4
  122. package/dist/old_lib/plots.d.ts +0 -3
  123. package/dist/old_lib/swatch.d.ts +0 -285
  124. package/dist/old_lib/utils.d.ts +0 -1
  125. package/dist/patterns/paths/constants.d.ts +0 -1
  126. package/dist/template/constants.d.ts +0 -43
  127. package/dist/template/shapes/index.d.ts +0 -4
  128. package/src/old_lib/index.js +0 -4
  129. package/src/old_lib/plots.js +0 -27
  130. package/src/old_lib/swatch.js +0 -16
  131. package/src/old_lib/utils.js +0 -8
  132. package/src/patterns/Brick.svelte +0 -15
  133. package/src/patterns/Circles.svelte +0 -18
  134. package/src/patterns/CrossHatch.svelte +0 -12
  135. package/src/patterns/CurvedWave.svelte +0 -7
  136. package/src/patterns/Dots.svelte +0 -20
  137. package/src/patterns/OutlineCircles.svelte +0 -13
  138. package/src/patterns/Tile.svelte +0 -16
  139. package/src/patterns/Triangles.svelte +0 -13
  140. package/src/patterns/Waves.svelte +0 -9
  141. package/src/patterns/paths/NamedPattern.svelte +0 -9
  142. package/src/patterns/paths/constants.js +0 -4
  143. package/src/template/Texture.svelte +0 -13
  144. package/src/template/constants.js +0 -43
  145. package/src/template/shapes/Circles.svelte +0 -15
  146. package/src/template/shapes/Lines.svelte +0 -16
  147. package/src/template/shapes/Path.svelte +0 -9
  148. package/src/template/shapes/Polygons.svelte +0 -15
  149. package/src/template/shapes/index.js +0 -4
  150. /package/dist/{old_lib → lib}/brewer.d.ts +0 -0
  151. /package/dist/{old_lib → lib}/chart.d.ts +0 -0
  152. /package/dist/{old_lib → lib}/grid.d.ts +0 -0
  153. /package/dist/{old_lib → lib}/ticks.d.ts +0 -0
  154. /package/src/{old_lib → lib}/brewer.js +0 -0
  155. /package/src/{old_lib → lib}/chart.js +0 -0
  156. /package/src/{old_lib → lib}/grid.js +0 -0
  157. /package/src/{old_lib → lib}/ticks.js +0 -0
@@ -0,0 +1,90 @@
1
+ import { extent } from 'd3-array'
2
+
3
+ /**
4
+ * Extracts animation frames from data, keyed by time field value.
5
+ * Preserves insertion order of time values.
6
+ *
7
+ * @param {Object[]} data
8
+ * @param {string} timeField
9
+ * @returns {Map<unknown, Object[]>}
10
+ */
11
+ export function extractFrames(data, timeField) {
12
+ const map = new Map()
13
+ for (const row of data) {
14
+ const key = row[timeField]
15
+ if (!map.has(key)) map.set(key, [])
16
+ map.get(key).push(row)
17
+ }
18
+ return map
19
+ }
20
+
21
+ /**
22
+ * Ensures all (x, color) combinations exist in the frame data.
23
+ * Missing combinations are filled with y=0 so the animation
24
+ * starts/ends smoothly without bars jumping in from nowhere.
25
+ *
26
+ * @param {Object[]} frameData - rows for a single frame
27
+ * @param {{ x: string, y: string, color?: string }} channels
28
+ * @param {unknown[]} allXValues - all x values across all frames
29
+ * @param {unknown[] | null} allColorValues - all color values across frames (null if no color)
30
+ * @returns {Object[]}
31
+ */
32
+ export function normalizeFrame(frameData, channels, allXValues, allColorValues) {
33
+ const { x: xf, y: yf, color: cf } = channels
34
+
35
+ if (cf && !allColorValues?.length) {
36
+ throw new Error('normalizeFrame: allColorValues must be provided when color channel is set')
37
+ }
38
+
39
+ // Build lookup of existing (x, color?) keys
40
+ const existing = new Set(
41
+ frameData.map((d) => (cf ? `${d[xf]}::${d[cf]}` : String(d[xf])))
42
+ )
43
+
44
+ const filled = [...frameData]
45
+
46
+ const colorValues = cf && allColorValues ? allColorValues : [null]
47
+
48
+ for (const xVal of allXValues) {
49
+ for (const colorVal of colorValues) {
50
+ const key = cf ? `${xVal}::${colorVal}` : String(xVal)
51
+ if (!existing.has(key)) {
52
+ const row = { [xf]: xVal, [yf]: 0 }
53
+ if (cf && colorVal !== null) row[cf] = colorVal
54
+ filled.push(row)
55
+ }
56
+ }
57
+ }
58
+
59
+ return filled
60
+ }
61
+
62
+ /**
63
+ * Computes static x/y domains across all frames combined.
64
+ * These domains stay constant throughout the animation so bars
65
+ * can be compared across frames by absolute height.
66
+ *
67
+ * NOTE: y domain is pinned to [0, max] — assumes bar chart semantics where
68
+ * the baseline is always 0. If used with scatter or line charts where y can
69
+ * be negative, pass an explicit `yDomain` override instead.
70
+ *
71
+ * @param {Map<unknown, Object[]>} frames
72
+ * @param {{ x: string, y: string }} channels
73
+ * @returns {{ xDomain: unknown[], yDomain: [number, number] }}
74
+ */
75
+ export function computeStaticDomains(frames, channels) {
76
+ const { x: xf, y: yf } = channels
77
+ const allData = [...frames.values()].flat()
78
+
79
+ const sampleX = allData[0]?.[xf]
80
+ const xIsCategorical = typeof sampleX === 'string'
81
+
82
+ const xDomain = xIsCategorical
83
+ ? [...new Set(allData.map((d) => d[xf]))]
84
+ : extent(allData, (d) => Number(d[xf]))
85
+
86
+ const [, yMax] = extent(allData, (d) => Number(d[yf]))
87
+ const yDomain = [0, yMax ?? 0] // pin to 0 (bar chart default)
88
+
89
+ return { xDomain, yDomain }
90
+ }
@@ -0,0 +1,14 @@
1
+ const BUILT_IN_GEOMS = new Set(['bar', 'line', 'area', 'point', 'box', 'violin', 'arc'])
2
+
3
+ export function resolveFormat(field, helpers = {}) {
4
+ return helpers?.format?.[field] ?? ((v) => String(v))
5
+ }
6
+
7
+ export function resolveTooltip(helpers = {}) {
8
+ return helpers?.tooltip ?? null
9
+ }
10
+
11
+ export function resolveGeom(type, helpers = {}) {
12
+ if (BUILT_IN_GEOMS.has(type)) return null
13
+ return helpers?.geoms?.[type] ?? null
14
+ }
@@ -0,0 +1,53 @@
1
+ import palette from '../brewing/palette.json'
2
+ import { PATTERN_ORDER } from '../brewing/patterns.js'
3
+ import { SYMBOL_ORDER } from '../brewing/symbols.js'
4
+
5
+ /** @typedef {{ colors: string[], patterns: string[], symbols: string[] }} PlotPreset */
6
+
7
+ export const DEFAULT_PRESET = {
8
+ colors: palette.map((p) => p.shades.light.fill),
9
+ patterns: PATTERN_ORDER,
10
+ symbols: SYMBOL_ORDER
11
+ }
12
+
13
+ export const ACCESSIBLE_PRESET = {
14
+ colors: ['#66c2a5','#fc8d62','#8da0cb','#e78ac3','#a6d854','#ffd92f','#e5c494','#b3b3b3'],
15
+ patterns: PATTERN_ORDER,
16
+ symbols: SYMBOL_ORDER
17
+ }
18
+
19
+ 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
23
+ }
24
+
25
+ const BUILT_IN_PRESETS = {
26
+ default: DEFAULT_PRESET,
27
+ accessible: ACCESSIBLE_PRESET,
28
+ print: PRINT_PRESET
29
+ }
30
+
31
+ 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
+ }
53
+ }
@@ -0,0 +1,56 @@
1
+ import { scaleBand, scaleLinear } from 'd3-scale'
2
+ import { extent } from 'd3-array'
3
+
4
+ export function inferFieldType(data, field) {
5
+ const values = data.map((d) => d[field]).filter((v) => v !== null && v !== undefined)
6
+ if (values.length === 0) return 'band'
7
+ const isNumeric = values.every((v) => typeof v === 'number' || (!isNaN(Number(v)) && String(v).trim() !== ''))
8
+ return isNumeric ? 'continuous' : 'band'
9
+ }
10
+
11
+ export function inferOrientation(xType, yType) {
12
+ if (xType === 'band' && yType === 'continuous') return 'vertical'
13
+ if (yType === 'band' && xType === 'continuous') return 'horizontal'
14
+ return 'none'
15
+ }
16
+
17
+ export function buildUnifiedXScale(datasets, field, width, opts = {}) {
18
+ const allValues = datasets.flatMap((d) => d.map((r) => r[field]))
19
+ const isNumeric = allValues.every((v) => typeof v === 'number' || (!isNaN(Number(v)) && String(v).trim() !== ''))
20
+
21
+ // opts.band forces scaleBand even for numeric data (e.g. bar charts with year on X).
22
+ if (opts.domain) {
23
+ const domainIsNumeric = opts.domain.every((v) => typeof v === 'number')
24
+ if (!opts.band && (domainIsNumeric || isNumeric)) {
25
+ return scaleLinear().domain(opts.domain).range([0, width]).nice()
26
+ }
27
+ return scaleBand().domain(opts.domain).range([0, width]).padding(opts.padding ?? 0.2)
28
+ }
29
+
30
+ if (isNumeric && !opts.band) {
31
+ const numericValues = allValues.map(Number)
32
+ const [minVal, maxVal] = extent(numericValues)
33
+ const domainMin = (opts.includeZero ?? false) ? 0 : (minVal ?? 0)
34
+ return scaleLinear().domain([domainMin, maxVal ?? 0]).range([0, width]).nice()
35
+ }
36
+
37
+ const domain = [...new Set(allValues)].filter((v) => v !== null && v !== undefined)
38
+ return scaleBand().domain(domain).range([0, width]).padding(opts.padding ?? 0.2)
39
+ }
40
+
41
+ export function buildUnifiedYScale(datasets, field, height, opts = {}) {
42
+ if (opts.domain) {
43
+ return scaleLinear().domain(opts.domain).range([height, 0]).nice()
44
+ }
45
+ const allValues = datasets.flatMap((d) => d.map((r) => Number(r[field]))).filter((v) => !isNaN(v))
46
+ const [minVal, maxVal] = extent(allValues)
47
+ const domainMin = (opts.includeZero ?? true) ? 0 : (minVal ?? 0)
48
+ return scaleLinear().domain([domainMin, maxVal ?? 0]).range([height, 0]).nice()
49
+ }
50
+
51
+ export function inferColorScaleType(data, field, spec = {}) {
52
+ if (spec.colorScale) return spec.colorScale
53
+ if (spec.colorMidpoint !== undefined) return 'diverging'
54
+ const type = inferFieldType(data, field)
55
+ return type === 'continuous' ? 'sequential' : 'categorical'
56
+ }
@@ -0,0 +1,92 @@
1
+ import { sum, mean, min, max, median } from 'd3-array'
2
+ import { applyAggregate, applyBoxStat } from '../brewing/stats.js'
3
+
4
+ const BUILT_IN_STATS = {
5
+ sum,
6
+ mean,
7
+ min,
8
+ max,
9
+ count: (values) => values.length,
10
+ median
11
+ }
12
+
13
+ /**
14
+ * Resolves a stat name to an aggregation function.
15
+ * Checks built-ins first, then helpers.stats, then warns and falls back to identity.
16
+ *
17
+ * @param {string} name
18
+ * @param {Object} helpers
19
+ * @returns {Function}
20
+ */
21
+ export function resolveStat(name, helpers = {}) {
22
+ if (name === 'identity') return (data) => data
23
+ if (BUILT_IN_STATS[name]) return BUILT_IN_STATS[name]
24
+ if (helpers?.stats?.[name]) return helpers.stats[name]
25
+ // eslint-disable-next-line no-console
26
+ console.warn(
27
+ `[Plot] Unknown stat "${name}" — falling back to identity. Add it to helpers.stats to suppress this warning.`
28
+ )
29
+ return (data) => data
30
+ }
31
+
32
+ /**
33
+ * Infers group-by fields from channels by excluding value fields.
34
+ * valueFields may contain channel keys (e.g. ['y', 'size']) OR field values (e.g. ['cty']).
35
+ * A channel's field is excluded if either the channel key OR the field value is in valueFields.
36
+ *
37
+ * @param {Record<string, string|undefined>} channels
38
+ * @param {string[]} valueFields
39
+ * @returns {string[]}
40
+ */
41
+ export function inferGroupByFields(channels, valueFields) {
42
+ const seen = new Set()
43
+ const result = []
44
+ for (const [key, field] of Object.entries(channels)) {
45
+ if (!field) continue
46
+ if (valueFields.includes(key) || valueFields.includes(field)) continue
47
+ if (seen.has(field)) continue
48
+ seen.add(field)
49
+ result.push(field)
50
+ }
51
+ return result
52
+ }
53
+
54
+ /**
55
+ * Applies a stat aggregation to data based on a geom config.
56
+ * Returns data unchanged for identity stat.
57
+ *
58
+ * @param {Object[]} data
59
+ * @param {{ stat?: string, channels?: Record<string, string> }} geomConfig
60
+ * @param {Object} helpers
61
+ * @returns {Object[]}
62
+ */
63
+ export function applyGeomStat(data, geomConfig, helpers = {}) {
64
+ const { stat = 'identity', channels = {} } = geomConfig
65
+ if (stat === 'identity') return data
66
+ if (stat === 'boxplot') return applyBoxStat(data, channels)
67
+
68
+ const statFn = resolveStat(stat, helpers)
69
+
70
+ const VALUE_CHANNEL_KEYS = ['y', 'size', 'theta']
71
+ const groupByFields = inferGroupByFields(channels, VALUE_CHANNEL_KEYS)
72
+ const primaryKey = VALUE_CHANNEL_KEYS.find((k) => channels[k])
73
+ if (!primaryKey) return data
74
+
75
+ let result = applyAggregate(data, {
76
+ by: groupByFields,
77
+ value: channels[primaryKey],
78
+ stat: statFn
79
+ })
80
+
81
+ for (const key of VALUE_CHANNEL_KEYS.filter((k) => k !== primaryKey && channels[k])) {
82
+ const extra = applyAggregate(data, { by: groupByFields, value: channels[key], stat: statFn })
83
+ const index = new Map(extra.map((r) => [groupByFields.map((f) => r[f]).join('|'), r]))
84
+ result = result.map((r) => {
85
+ const mapKey = groupByFields.map((f) => r[f]).join('|')
86
+ const extraRow = index.get(mapKey)
87
+ return extraRow ? { ...r, [channels[key]]: extraRow[channels[key]] } : r
88
+ })
89
+ }
90
+
91
+ return result
92
+ }
@@ -0,0 +1,65 @@
1
+ // packages/chart/src/lib/plot/types.js
2
+ // JSDoc typedefs for the Plot system. No runtime code.
3
+
4
+ /**
5
+ * @typedef {Object} GeomSpec
6
+ * @property {string} type - Geom type: 'bar'|'line'|'area'|'point'|'box'|'violin'|'arc' or custom
7
+ * @property {string} [x]
8
+ * @property {string} [y]
9
+ * @property {string} [color]
10
+ * @property {string} [fill]
11
+ * @property {string} [size]
12
+ * @property {string} [symbol]
13
+ * @property {string} [pattern]
14
+ * @property {string} [stat] - Built-in or helpers.stats key
15
+ * @property {Record<string, unknown>} [options]
16
+ */
17
+
18
+ /**
19
+ * @typedef {Object} PlotSpec
20
+ * @property {Record<string, unknown>[]} data
21
+ * @property {string} [x]
22
+ * @property {string} [y]
23
+ * @property {string} [color]
24
+ * @property {string} [fill]
25
+ * @property {string} [size]
26
+ * @property {string} [symbol]
27
+ * @property {string} [pattern]
28
+ * @property {string} [theta]
29
+ * @property {Record<string, string>} [labels]
30
+ * @property {unknown[]} [xDomain]
31
+ * @property {number[]} [yDomain]
32
+ * @property {string} [xLabel]
33
+ * @property {string} [yLabel]
34
+ * @property {[number, number]} [axisOrigin]
35
+ * @property {'categorical'|'sequential'|'diverging'} [colorScale]
36
+ * @property {string} [colorScheme]
37
+ * @property {number} [colorMidpoint]
38
+ * @property {unknown[]} [colorDomain]
39
+ * @property {GeomSpec[]} geoms
40
+ * @property {{ by: string, cols?: number, scales?: 'fixed'|'free'|'free_x'|'free_y' }} [facet]
41
+ * @property {{ by: string, duration?: number, loop?: boolean }} [animate]
42
+ * @property {boolean} [grid]
43
+ * @property {boolean} [legend]
44
+ * @property {boolean} [tooltip]
45
+ * @property {string} [title]
46
+ * @property {string} [preset]
47
+ * @property {number} [width]
48
+ * @property {number} [height]
49
+ * @property {'light'|'dark'} [mode]
50
+ */
51
+
52
+ /**
53
+ * @typedef {Object} PlotHelpers
54
+ * @property {Record<string, (values: unknown[]) => unknown>} [stats]
55
+ * @property {Record<string, (v: unknown) => string>} [format]
56
+ * @property {(d: Record<string, unknown>) => string} [tooltip]
57
+ * @property {Record<string, unknown>} [geoms] Svelte components keyed by type name
58
+ * @property {unknown} [colorScale] d3 scale override
59
+ * @property {{ colors: string[], patterns: string[], symbols: string[] }} [preset]
60
+ * @property {Record<string, { colors: string[], patterns: string[], symbols: string[] }>} [presets]
61
+ * @property {Record<string, unknown>} [patterns] custom SVG pattern components keyed by name
62
+ * @property {Record<string, unknown>} [symbols] custom symbol shape components keyed by name
63
+ */
64
+
65
+ export {}
@@ -1,32 +1,8 @@
1
1
  import { SvelteSet } from 'svelte/reactivity'
2
- import { scaleBand, scaleLinear, scaleTime, scaleOrdinal } from 'd3-scale'
2
+ import { scaleLinear, scaleOrdinal } from 'd3-scale'
3
3
  import { schemeCategory10 } from 'd3-scale-chromatic'
4
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
- }
5
+ import { buildXScale } from './xscale.js'
30
6
 
31
7
  /**
32
8
  * Creates appropriate scales based on data and dimensions
@@ -0,0 +1,13 @@
1
+ import palette from './palette.json'
2
+ import { PATTERN_ORDER } from './brewing/patterns.js'
3
+ import { shapes } from '../symbols'
4
+
5
+ export const swatch = {
6
+ palette,
7
+ keys: {
8
+ gray: ['gray'],
9
+ color: Object.keys(palette).filter((name) => name !== 'gray'),
10
+ symbol: shapes,
11
+ pattern: PATTERN_ORDER
12
+ }
13
+ }
package/src/lib/utils.js CHANGED
@@ -139,3 +139,12 @@ export function createTooltipFormatter(options = {}) {
139
139
  export function transform(x, y) {
140
140
  return `translate(${x}, ${y})`
141
141
  }
142
+
143
+ import { scaledPath } from '@rokkit/core'
144
+
145
+ export function scaledPathCollection(paths) {
146
+ return Object.entries(paths).reduce(
147
+ (acc, [key, value]) => ({ ...acc, [key]: (s) => scaledPath(s, value) }),
148
+ {}
149
+ )
150
+ }
@@ -0,0 +1,31 @@
1
+ import { scaleBand, scaleLinear, scaleTime } from 'd3-scale'
2
+ import { min, max } from 'd3-array'
3
+
4
+ /**
5
+ * Builds an appropriate D3 x-axis scale based on value types.
6
+ * Returns scaleTime for Date values, scaleLinear for numeric values,
7
+ * or scaleBand for categorical values.
8
+ *
9
+ * @param {Array} xValues
10
+ * @param {Object} dimensions
11
+ * @param {number} padding
12
+ * @returns {import('d3-scale').ScaleContinuousNumeric|import('d3-scale').ScaleBand}
13
+ */
14
+ export function buildXScale(xValues, dimensions, padding) {
15
+ const xIsDate = xValues.some((v) => v instanceof Date)
16
+ const xIsNumeric = !xIsDate && xValues.every((v) => !isNaN(parseFloat(v)))
17
+
18
+ if (xIsDate) {
19
+ return scaleTime()
20
+ .domain([min(xValues), max(xValues)])
21
+ .range([0, dimensions.innerWidth])
22
+ .nice()
23
+ }
24
+ if (xIsNumeric) {
25
+ return scaleLinear()
26
+ .domain([min([0, ...xValues]), max(xValues)])
27
+ .range([0, dimensions.innerWidth])
28
+ .nice()
29
+ }
30
+ return scaleBand().domain(xValues).range([0, dimensions.innerWidth]).padding(padding)
31
+ }
@@ -0,0 +1,32 @@
1
+ <script>
2
+ import { getContext } from 'svelte'
3
+ import { toPatternId } from '../lib/brewing/patterns.js'
4
+ import { PATTERNS } from './patterns.js'
5
+ import PatternDef from './PatternDef.svelte'
6
+
7
+ /** @type {{ patterns?: Record<string, import('./patterns.js').PatternMark[]> }} */
8
+ let { patterns = PATTERNS } = $props()
9
+
10
+ const state = getContext('plot-state')
11
+
12
+ const patternDefs = $derived.by(() => {
13
+ const defs = []
14
+ for (const [key, patternName] of (state.patterns ?? new Map()).entries()) {
15
+ const colorEntry = state.colors?.get(key) ?? { stroke: '#444' }
16
+ defs.push({
17
+ id: toPatternId(String(key)),
18
+ marks: patterns[patternName] ?? [],
19
+ stroke: colorEntry.stroke ?? '#444'
20
+ })
21
+ }
22
+ return defs
23
+ })
24
+ </script>
25
+
26
+ {#if patternDefs.length > 0}
27
+ <defs data-plot-pattern-defs>
28
+ {#each patternDefs as def (def.id)}
29
+ <PatternDef id={def.id} marks={def.marks} stroke={def.stroke} />
30
+ {/each}
31
+ </defs>
32
+ {/if}
@@ -0,0 +1,27 @@
1
+ <script>
2
+ import { scaleMark, resolveMarkAttrs } from './scale.js'
3
+
4
+ /** @type {{ id: string, marks?: import('./patterns.js').PatternMark[], size?: number, stroke?: string, thickness?: number }} */
5
+ let { id, marks = [], size = 10, stroke = '#444', thickness = 0.5 } = $props()
6
+
7
+ const resolvedMarks = $derived(
8
+ marks.map((m) => resolveMarkAttrs(scaleMark(m, size), { fill: stroke, stroke, thickness }))
9
+ )
10
+ </script>
11
+
12
+ <pattern {id} patternUnits="userSpaceOnUse" width={size} height={size}>
13
+ <rect width={size} height={size} fill="none" />
14
+ {#each resolvedMarks as { type, attrs }}
15
+ {#if type === 'line'}
16
+ <line {...attrs} />
17
+ {:else if type === 'circle'}
18
+ <circle {...attrs} />
19
+ {:else if type === 'rect'}
20
+ <rect {...attrs} />
21
+ {:else if type === 'polygon'}
22
+ <polygon {...attrs} />
23
+ {:else if type === 'path'}
24
+ <path {...attrs} />
25
+ {/if}
26
+ {/each}
27
+ </pattern>
@@ -1,14 +1,4 @@
1
- export { default as Brick } from './Brick.svelte'
2
- export { default as Circles } from './Circles.svelte'
3
- export { default as Dots } from './Dots.svelte'
4
- export { default as CrossHatch } from './CrossHatch.svelte'
5
- export { default as Waves } from './Waves.svelte'
6
- // export { default as Squares } from './Squares.svelte'
7
- export { default as Tile } from './Tile.svelte'
8
- export { default as Triangles } from './Triangles.svelte'
9
- // export { default as Waves } from './Waves.svelte'
10
- // export { default as ZigZag } from './ZigZag.svelte'
11
- // export { default as Flower } from './Flower.svelte'
12
- // export { default as NamedPath } from './NamedPath.svelte'
13
- export { default as CurvedWave } from './CurvedWave.svelte'
14
- export { default as OutlineCircles } from './OutlineCircles.svelte'
1
+ export { default as PatternDef } from './PatternDef.svelte'
2
+ export { default as DefinePatterns } from './DefinePatterns.svelte'
3
+ export { PATTERNS } from './patterns.js'
4
+ export { scaleMark, resolveMarkAttrs } from './scale.js'