@rokkit/chart 1.3.8 → 1.3.10

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 (140) hide show
  1. package/dist/Chart.svelte +50 -64
  2. package/dist/Plot/Root.svelte +4 -1
  3. package/dist/Plot/Root.svelte.d.ts +2 -0
  4. package/dist/Plot/index.d.ts +1 -1
  5. package/dist/Plot/index.js +1 -1
  6. package/dist/Plot.svelte +4 -1
  7. package/dist/Plot.svelte.d.ts +2 -0
  8. package/dist/PlotState.svelte.d.ts +14 -2
  9. package/dist/PlotState.svelte.js +81 -5
  10. package/dist/charts/AreaChart.svelte +8 -1
  11. package/dist/charts/AreaChart.svelte.d.ts +5 -0
  12. package/dist/charts/BarChart.svelte +8 -1
  13. package/dist/charts/BarChart.svelte.d.ts +5 -0
  14. package/dist/charts/BoxPlot.svelte +3 -1
  15. package/dist/charts/BoxPlot.svelte.d.ts +1 -0
  16. package/dist/charts/BubbleChart.svelte +3 -1
  17. package/dist/charts/BubbleChart.svelte.d.ts +1 -0
  18. package/dist/charts/LineChart.svelte +3 -1
  19. package/dist/charts/LineChart.svelte.d.ts +1 -0
  20. package/dist/charts/PieChart.svelte +4 -2
  21. package/dist/charts/PieChart.svelte.d.ts +1 -0
  22. package/dist/charts/ScatterPlot.svelte +3 -1
  23. package/dist/charts/ScatterPlot.svelte.d.ts +1 -0
  24. package/dist/charts/ViolinPlot.svelte +3 -1
  25. package/dist/charts/ViolinPlot.svelte.d.ts +1 -0
  26. package/dist/geoms/Arc.svelte +21 -55
  27. package/dist/geoms/Arc.svelte.d.ts +4 -4
  28. package/dist/geoms/Area.svelte +45 -44
  29. package/dist/geoms/Area.svelte.d.ts +8 -1
  30. package/dist/geoms/Bar.svelte +33 -56
  31. package/dist/geoms/Bar.svelte.d.ts +9 -0
  32. package/dist/geoms/Box.svelte +118 -131
  33. package/dist/geoms/Box.svelte.d.ts +5 -3
  34. package/dist/geoms/Candlestick.svelte +22 -77
  35. package/dist/geoms/Candlestick.svelte.d.ts +2 -0
  36. package/dist/geoms/Heatmap.svelte +21 -66
  37. package/dist/geoms/Heatmap.svelte.d.ts +2 -0
  38. package/dist/geoms/Hexbin.svelte +20 -100
  39. package/dist/geoms/Hexbin.svelte.d.ts +2 -0
  40. package/dist/geoms/Jitter.svelte +21 -36
  41. package/dist/geoms/Jitter.svelte.d.ts +5 -3
  42. package/dist/geoms/Line.svelte +27 -31
  43. package/dist/geoms/Line.svelte.d.ts +5 -0
  44. package/dist/geoms/Point.svelte +31 -66
  45. package/dist/geoms/Point.svelte.d.ts +5 -1
  46. package/dist/geoms/Ribbon.svelte +30 -124
  47. package/dist/geoms/Ribbon.svelte.d.ts +2 -0
  48. package/dist/geoms/Rule.svelte +5 -1
  49. package/dist/geoms/Rule.svelte.d.ts +3 -0
  50. package/dist/geoms/Violin.svelte +21 -40
  51. package/dist/geoms/Violin.svelte.d.ts +5 -3
  52. package/dist/geoms/Waterfall.svelte +19 -80
  53. package/dist/geoms/Waterfall.svelte.d.ts +2 -0
  54. package/dist/geoms/lib/GeomState.svelte.d.ts +51 -0
  55. package/dist/geoms/lib/GeomState.svelte.js +79 -0
  56. package/dist/geoms/lib/aesthetics.d.ts +36 -0
  57. package/dist/geoms/lib/aesthetics.js +45 -0
  58. package/dist/geoms/lib/areas.d.ts +1 -1
  59. package/dist/geoms/lib/areas.js +18 -5
  60. package/dist/geoms/lib/bars.d.ts +2 -2
  61. package/dist/geoms/lib/bars.js +37 -17
  62. package/dist/geoms/lib/marks/arc.d.ts +24 -0
  63. package/dist/geoms/lib/marks/arc.js +28 -0
  64. package/dist/geoms/lib/marks/area.d.ts +23 -0
  65. package/dist/geoms/lib/marks/area.js +38 -0
  66. package/dist/geoms/lib/marks/bar.d.ts +16 -0
  67. package/dist/geoms/lib/marks/bar.js +43 -0
  68. package/dist/geoms/lib/marks/box.d.ts +14 -0
  69. package/dist/geoms/lib/marks/box.js +30 -0
  70. package/dist/geoms/lib/marks/candlestick.d.ts +27 -0
  71. package/dist/geoms/lib/marks/candlestick.js +52 -0
  72. package/dist/geoms/lib/marks/heatmap.d.ts +24 -0
  73. package/dist/geoms/lib/marks/heatmap.js +47 -0
  74. package/dist/geoms/lib/marks/hexbin.d.ts +29 -0
  75. package/dist/geoms/lib/marks/hexbin.js +62 -0
  76. package/dist/geoms/lib/marks/jitter.d.ts +21 -0
  77. package/dist/geoms/lib/marks/jitter.js +36 -0
  78. package/dist/geoms/lib/marks/line.d.ts +26 -0
  79. package/dist/geoms/lib/marks/line.js +25 -0
  80. package/dist/geoms/lib/marks/point.d.ts +25 -0
  81. package/dist/geoms/lib/marks/point.js +58 -0
  82. package/dist/geoms/lib/marks/ribbon.d.ts +32 -0
  83. package/dist/geoms/lib/marks/ribbon.js +80 -0
  84. package/dist/geoms/lib/marks/violin.d.ts +13 -0
  85. package/dist/geoms/lib/marks/violin.js +31 -0
  86. package/dist/geoms/lib/marks/waterfall.d.ts +25 -0
  87. package/dist/geoms/lib/marks/waterfall.js +58 -0
  88. package/dist/index.d.ts +1 -8
  89. package/dist/index.js +1 -8
  90. package/dist/lib/brewing/colors.d.ts +31 -1
  91. package/dist/lib/brewing/colors.js +34 -1
  92. package/dist/lib/brewing/marks/arcs.js +9 -4
  93. package/dist/lib/brewing/marks/boxes.js +8 -4
  94. package/dist/lib/brewing/marks/lines.js +13 -2
  95. package/dist/lib/brewing/marks/points.js +7 -2
  96. package/dist/lib/brewing/marks/swarm.js +6 -2
  97. package/dist/lib/brewing/marks/violins.js +8 -4
  98. package/dist/lib/preset.d.ts +11 -1
  99. package/dist/lib/preset.js +16 -4
  100. package/package.json +1 -1
  101. package/dist/Plot/Arc.svelte +0 -30
  102. package/dist/Plot/Arc.svelte.d.ts +0 -18
  103. package/dist/lib/brewer.d.ts +0 -9
  104. package/dist/lib/brewer.js +0 -25
  105. package/dist/lib/brewing/BoxBrewer.svelte.d.ts +0 -8
  106. package/dist/lib/brewing/BoxBrewer.svelte.js +0 -14
  107. package/dist/lib/brewing/CartesianBrewer.svelte.d.ts +0 -8
  108. package/dist/lib/brewing/CartesianBrewer.svelte.js +0 -21
  109. package/dist/lib/brewing/PieBrewer.svelte.d.ts +0 -8
  110. package/dist/lib/brewing/PieBrewer.svelte.js +0 -14
  111. package/dist/lib/brewing/QuartileBrewer.svelte.d.ts +0 -9
  112. package/dist/lib/brewing/QuartileBrewer.svelte.js +0 -51
  113. package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +0 -8
  114. package/dist/lib/brewing/ViolinBrewer.svelte.js +0 -14
  115. package/dist/lib/brewing/axes.svelte.d.ts +0 -66
  116. package/dist/lib/brewing/axes.svelte.js +0 -270
  117. package/dist/lib/brewing/bars.svelte.d.ts +0 -56
  118. package/dist/lib/brewing/bars.svelte.js +0 -201
  119. package/dist/lib/brewing/brewer.svelte.d.ts +0 -124
  120. package/dist/lib/brewing/brewer.svelte.js +0 -277
  121. package/dist/lib/brewing/dimensions.svelte.d.ts +0 -35
  122. package/dist/lib/brewing/dimensions.svelte.js +0 -56
  123. package/dist/lib/brewing/index.svelte.d.ts +0 -118
  124. package/dist/lib/brewing/index.svelte.js +0 -205
  125. package/dist/lib/brewing/legends.svelte.d.ts +0 -48
  126. package/dist/lib/brewing/legends.svelte.js +0 -137
  127. package/dist/lib/brewing/marks/areas.d.ts +0 -31
  128. package/dist/lib/brewing/marks/areas.js +0 -72
  129. package/dist/lib/brewing/marks/bars.d.ts +0 -1
  130. package/dist/lib/brewing/marks/bars.js +0 -49
  131. package/dist/lib/brewing/scales.svelte.d.ts +0 -24
  132. package/dist/lib/brewing/scales.svelte.js +0 -82
  133. package/dist/lib/context.d.ts +0 -13
  134. package/dist/lib/context.js +0 -131
  135. package/dist/lib/plot/chartProps.d.ts +0 -177
  136. package/dist/lib/plot/chartProps.js +0 -76
  137. package/dist/lib/scales.svelte.d.ts +0 -35
  138. package/dist/lib/scales.svelte.js +0 -151
  139. package/dist/lib/ticks.d.ts +0 -36
  140. package/dist/lib/ticks.js +0 -46
@@ -0,0 +1,62 @@
1
+ import { resolveAlpha } from '../aesthetics.js'
2
+
3
+ /** SVG path for a pointy-top hexagon of radius r, centered at the origin. */
4
+ export function hexPath(r) {
5
+ const angles = [0, 1, 2, 3, 4, 5].map((i) => ((i * 60 - 30) * Math.PI) / 180)
6
+ return `${angles.map((a, i) => `${i === 0 ? 'M' : 'L'}${r * Math.cos(a)},${r * Math.sin(a)}`).join('')}Z`
7
+ }
8
+
9
+ /** Group points into hexagonal bins and count per bin. */
10
+ function hexBin(rows, xField, yField, xScale, yScale, r) {
11
+ const dx = r * 2 * Math.sin(Math.PI / 3)
12
+ const dy = r * 1.5
13
+ const bins = new Map()
14
+ for (const d of rows) {
15
+ const px = xScale(d[xField ?? ''])
16
+ const py = yScale(d[yField ?? ''])
17
+ if (px === null || px === undefined || py === null || py === undefined) continue
18
+ const col = Math.round(px / dx)
19
+ const row = Math.round(py / dy)
20
+ const cx = col * dx + (row % 2 ? dx / 2 : 0)
21
+ const cy = row * dy
22
+ const key = `${col},${row}`
23
+ let bin = bins.get(key)
24
+ if (!bin) {
25
+ bin = { cx, cy, count: 0, points: [] }
26
+ bins.set(key, bin)
27
+ }
28
+ bin.count++
29
+ bin.points.push(d)
30
+ }
31
+ return [...bins.values()]
32
+ }
33
+
34
+ /**
35
+ * Build renderable hex bins. Fill is the continuous color scale (by count) or a density-ramp
36
+ * rgba fallback. `alpha` is a flat multiplier on top (default 1 → unchanged).
37
+ * @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
38
+ */
39
+ export function buildHexbinMarks({ data, plot, channels, options = {}, alpha, type = 'hexbin' }) {
40
+ const { xScale, yScale } = plot
41
+ if (!data?.length || !xScale || !yScale) return []
42
+
43
+ const radius = options.radius ?? 20
44
+ const bins = hexBin(data, channels.x, channels.y, xScale, yScale, radius)
45
+ const maxCount = Math.max(1, ...bins.map((b) => b.count))
46
+ const colorScale = plot.continuousColorScale?.scale
47
+ const a = resolveAlpha(alpha, type, plot.chartPreset)
48
+
49
+ return bins.map((bin, i) => ({
50
+ key: `hex-${i}`,
51
+ cx: bin.cx,
52
+ cy: bin.cy,
53
+ count: bin.count,
54
+ fill: colorScale
55
+ ? colorScale(bin.count)
56
+ : `rgba(66, 133, 244, ${0.1 + 0.9 * (bin.count / maxCount)})`,
57
+ alpha: a,
58
+ value: bin.count,
59
+ display: `${bin.count}`,
60
+ data: { count: bin.count, x: bin.cx, y: bin.cy }
61
+ }))
62
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Build renderable jitter/swarm points. Fill + outline come from `fill` (defaulting to
3
+ * `x`); an explicit `color` channel overrides the outline stroke. Adds fixed `alpha`.
4
+ * Horizontal orientation swaps each point's screen coords via place().
5
+ * @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
6
+ */
7
+ export function buildJitterMarks({ data, plot, channels, options, alpha, type }: {
8
+ data: any[];
9
+ plot: any;
10
+ channels: any;
11
+ options?: any;
12
+ alpha?: number;
13
+ type?: string;
14
+ }): {
15
+ cx: any;
16
+ cy: any;
17
+ stroke: any;
18
+ alpha: number;
19
+ fill: string;
20
+ data: Record<string, unknown>;
21
+ }[];
@@ -0,0 +1,36 @@
1
+ import { buildSwarm } from '../../../lib/brewing/marks/swarm.js'
2
+ import { resolveAlpha } from '../aesthetics.js'
3
+
4
+ /**
5
+ * Build renderable jitter/swarm points. Fill + outline come from `fill` (defaulting to
6
+ * `x`); an explicit `color` channel overrides the outline stroke. Adds fixed `alpha`.
7
+ * Horizontal orientation swaps each point's screen coords via place().
8
+ * @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
9
+ */
10
+ export function buildJitterMarks({ data, plot, channels, options = {}, alpha, type = 'jitter' }) {
11
+ const { xScale, yScale, colors } = plot
12
+ if (!data?.length || !xScale || !yScale) return []
13
+
14
+ const fillField = channels.fill ?? channels.x
15
+ const raw = buildSwarm(
16
+ data,
17
+ { x: channels.x, y: channels.y, fill: fillField },
18
+ xScale,
19
+ yScale,
20
+ colors,
21
+ { method: options.method, r: options.r, side: options.side }
22
+ )
23
+ const a = resolveAlpha(alpha, type, plot.chartPreset)
24
+ const colorField = channels.color
25
+ const flip = plot.isFlipped
26
+ return raw.map((p) => {
27
+ const s = flip ? plot.place(p.cx, p.cy) : { x: p.cx, y: p.cy }
28
+ return {
29
+ ...p,
30
+ cx: s.x,
31
+ cy: s.y,
32
+ stroke: colorField ? (colors.get(p.data[colorField])?.stroke ?? p.stroke) : p.stroke,
33
+ alpha: a
34
+ }
35
+ })
36
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Build renderable line segments. A line has no interior, so its single aesthetic is
3
+ * the stroke — driven by `color`, with `fill` accepted as an alias (ggplot geom_line
4
+ * uses `colour`). Each segment gets the geom's fixed `alpha`.
5
+ * @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
6
+ */
7
+ export function buildLineMarks({ data, plot, channels, options, alpha, type }: {
8
+ data: any[];
9
+ plot: any;
10
+ channels: any;
11
+ options?: any;
12
+ alpha?: number;
13
+ type?: string;
14
+ }): {
15
+ alpha: number;
16
+ d: string | null;
17
+ fill: string;
18
+ stroke: string;
19
+ points: {
20
+ x: number;
21
+ y: number;
22
+ data: Record<string, unknown>;
23
+ i: number;
24
+ }[];
25
+ key?: unknown;
26
+ }[];
@@ -0,0 +1,25 @@
1
+ import { buildLines } from '../../../lib/brewing/marks/lines.js'
2
+ import { resolveAlpha } from '../aesthetics.js'
3
+
4
+ /**
5
+ * Build renderable line segments. A line has no interior, so its single aesthetic is
6
+ * the stroke — driven by `color`, with `fill` accepted as an alias (ggplot geom_line
7
+ * uses `colour`). Each segment gets the geom's fixed `alpha`.
8
+ * @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
9
+ */
10
+ export function buildLineMarks({ data, plot, channels, options = {}, alpha, type = 'line' }) {
11
+ const { xScale, yScale, colors } = plot
12
+ if (!data?.length || !xScale || !yScale) return []
13
+ const lineColor = channels.color ?? channels.fill
14
+ const segs = buildLines(
15
+ data,
16
+ { x: channels.x, y: channels.y, color: lineColor },
17
+ xScale,
18
+ yScale,
19
+ colors,
20
+ options.curve,
21
+ plot.place.bind(plot)
22
+ )
23
+ const a = resolveAlpha(alpha, type, plot.chartPreset)
24
+ return segs.map((seg) => ({ ...seg, alpha: a }))
25
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Build renderable point marks: geometry (from buildPoints) + shared aesthetics
3
+ * (fill/color split + alpha) + value/display. The geom template just renders these.
4
+ * @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
5
+ */
6
+ export function buildPointMarks({ data, plot, channels, options, alpha, type }: {
7
+ data: any[];
8
+ plot: any;
9
+ channels: any;
10
+ options?: any;
11
+ alpha?: number;
12
+ type?: string;
13
+ }): {
14
+ cx: any;
15
+ cy: any;
16
+ fill: string;
17
+ stroke: string;
18
+ alpha: number;
19
+ value: unknown;
20
+ display: string;
21
+ key: string;
22
+ data: Record<string, unknown>;
23
+ r: number;
24
+ symbolPath: string | null;
25
+ }[];
@@ -0,0 +1,58 @@
1
+ import { scaleSqrt } from 'd3-scale'
2
+ import { buildPoints } from '../../../lib/brewing/marks/points.js'
3
+ import { resolveFillStroke, resolveAlpha } from '../aesthetics.js'
4
+
5
+ /**
6
+ * Size scale for a bubble/size channel — sqrt so area (not radius) is proportional.
7
+ * @returns {import('d3-scale').ScalePower<number, number> | null}
8
+ */
9
+ function buildSizeScale(data, sizeField, options) {
10
+ if (!sizeField || !data?.length) return null
11
+ const vals = data.map((d) => Number(d[sizeField])).filter((v) => !isNaN(v))
12
+ if (!vals.length) return null
13
+ return scaleSqrt()
14
+ .domain([Math.min(...vals), Math.max(...vals)])
15
+ .range([options?.minRadius ?? 3, options?.maxRadius ?? 20])
16
+ }
17
+
18
+ /**
19
+ * Build renderable point marks: geometry (from buildPoints) + shared aesthetics
20
+ * (fill/color split + alpha) + value/display. The geom template just renders these.
21
+ * @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
22
+ */
23
+ export function buildPointMarks({ data, plot, channels, options = {}, alpha, type = 'point' }) {
24
+ const { xScale, yScale, colors } = plot
25
+ if (!data?.length || !xScale || !yScale) return []
26
+
27
+ const sizeScale = buildSizeScale(data, channels.size, options)
28
+ const raw = buildPoints(
29
+ data,
30
+ channels,
31
+ xScale,
32
+ yScale,
33
+ colors,
34
+ sizeScale,
35
+ plot.symbols,
36
+ options.radius ?? 4,
37
+ options.jitter ?? null
38
+ )
39
+ const a = resolveAlpha(alpha, type, plot.chartPreset)
40
+ const yf = channels.y
41
+
42
+ return raw.map((p, i) => {
43
+ const { fill, stroke } = resolveFillStroke(p.data, channels, colors)
44
+ // Horizontal orientation: swap each point's screen coords via place().
45
+ const s = plot.isFlipped ? plot.place(p.cx, p.cy) : { x: p.cx, y: p.cy }
46
+ return {
47
+ ...p,
48
+ cx: s.x,
49
+ cy: s.y,
50
+ fill,
51
+ stroke,
52
+ alpha: a,
53
+ value: yf ? p.data[yf] : undefined,
54
+ display: yf ? String(p.data[yf] ?? '') : '',
55
+ key: `${i}::${p.data[channels.x ?? '']}::${p.data[yf ?? '']}`
56
+ }
57
+ })
58
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Build a Sankey-style ribbon layout: links (flows) + source/target node boxes. Link fill
3
+ * comes from the source (then target) palette entry; `alpha` is fixed (default 0.5). Returns
4
+ * an object rather than a flat array, so the geom template renders links + node labels.
5
+ * @param {{ data: any[], plot: any, channels?: any, options?: any, alpha?: number, type?: string }} ctx
6
+ */
7
+ export function buildRibbonMarks({ data, plot, channels, options, alpha, type }: {
8
+ data: any[];
9
+ plot: any;
10
+ channels?: any;
11
+ options?: any;
12
+ alpha?: number;
13
+ type?: string;
14
+ }): {
15
+ links: {
16
+ key: string;
17
+ d: string;
18
+ fill: any;
19
+ alpha: number;
20
+ data: any;
21
+ }[];
22
+ sourceNodes: {
23
+ name: any;
24
+ y: number;
25
+ height: number;
26
+ }[];
27
+ targetNodes: {
28
+ name: any;
29
+ y: number;
30
+ height: number;
31
+ }[];
32
+ };
@@ -0,0 +1,80 @@
1
+ import { resolveAlpha } from '../aesthetics.js'
2
+ import { literalColor } from '../../../lib/brewing/colors.js'
3
+
4
+ /**
5
+ * Build a Sankey-style ribbon layout: links (flows) + source/target node boxes. Link fill
6
+ * comes from the source (then target) palette entry; `alpha` is fixed (default 0.5). Returns
7
+ * an object rather than a flat array, so the geom template renders links + node labels.
8
+ * @param {{ data: any[], plot: any, channels?: any, options?: any, alpha?: number, type?: string }} ctx
9
+ */
10
+ export function buildRibbonMarks({ data, plot, channels = {}, options = {}, alpha, type = 'ribbon' }) {
11
+ const empty = { links: [], sourceNodes: [], targetNodes: [] }
12
+ if (!data?.length) return empty
13
+
14
+ const { colors, innerHeight, innerWidth } = plot
15
+ const sourceField = options.source ?? 'source'
16
+ const targetField = options.target ?? 'target'
17
+ const valueField = options.value ?? 'value'
18
+ // A literal color/fill paints every ribbon directly (else fill comes from the node palette).
19
+ const lit = literalColor(channels.color ?? channels.fill)
20
+ const a = resolveAlpha(alpha, type, plot.chartPreset)
21
+
22
+ const flows = data.map((d) => ({
23
+ source: String(d[sourceField]),
24
+ target: String(d[targetField]),
25
+ value: Number(d[valueField]) || 0,
26
+ data: d
27
+ }))
28
+
29
+ const sourceMap = new Map()
30
+ const targetMap = new Map()
31
+ for (const f of flows) {
32
+ sourceMap.set(f.source, (sourceMap.get(f.source) ?? 0) + f.value)
33
+ targetMap.set(f.target, (targetMap.get(f.target) ?? 0) + f.value)
34
+ }
35
+
36
+ const totalSource = Math.max(1, [...sourceMap.values()].reduce((s, v) => s + v, 0))
37
+ const totalTarget = Math.max(1, [...targetMap.values()].reduce((s, v) => s + v, 0))
38
+ const padding = 4
39
+ const availHeight = innerHeight - padding * (Math.max(sourceMap.size, targetMap.size) - 1)
40
+
41
+ let sourceY = 0
42
+ const sourceNodes = []
43
+ for (const [name, value] of sourceMap) {
44
+ const h = (value / totalSource) * availHeight
45
+ sourceNodes.push({ name, y: sourceY, height: h })
46
+ sourceY += h + padding
47
+ }
48
+
49
+ let targetY = 0
50
+ const targetNodes = []
51
+ for (const [name, value] of targetMap) {
52
+ const h = (value / totalTarget) * availHeight
53
+ targetNodes.push({ name, y: targetY, height: h })
54
+ targetY += h + padding
55
+ }
56
+
57
+ const sourceOffsets = new Map(sourceNodes.map((n) => [n.name, n.y]))
58
+ const targetOffsets = new Map(targetNodes.map((n) => [n.name, n.y]))
59
+ const x0 = 0
60
+ const x1 = innerWidth
61
+
62
+ const links = flows.map((f, i) => {
63
+ const sy0 = sourceOffsets.get(f.source) ?? 0
64
+ const ty0 = targetOffsets.get(f.target) ?? 0
65
+ const sourceTotal = sourceMap.get(f.source) ?? 1
66
+ const targetTotal = targetMap.get(f.target) ?? 1
67
+ const sh = (f.value / sourceTotal) * (sourceNodes.find((n) => n.name === f.source)?.height ?? 0)
68
+ const th = (f.value / targetTotal) * (targetNodes.find((n) => n.name === f.target)?.height ?? 0)
69
+
70
+ const path = `M${x0},${sy0} C${innerWidth * 0.4},${sy0} ${innerWidth * 0.6},${ty0} ${x1},${ty0} L${x1},${ty0 + th} C${innerWidth * 0.6},${ty0 + th} ${innerWidth * 0.4},${sy0 + sh} ${x0},${sy0 + sh} Z`
71
+
72
+ sourceOffsets.set(f.source, sy0 + sh)
73
+ targetOffsets.set(f.target, ty0 + th)
74
+
75
+ const fill = lit ?? (colors?.get(f.source)?.fill ?? colors?.get(f.target)?.fill ?? '#888')
76
+ return { key: `ribbon-${i}`, d: path, fill, alpha: a, data: f.data }
77
+ })
78
+
79
+ return { links, sourceNodes, targetNodes }
80
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Build renderable violin silhouettes. Interior + outline come from `fill` (defaulting to
3
+ * `x`); an explicit `color` channel overrides the outline stroke. Adds fixed `alpha`.
4
+ * @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
5
+ */
6
+ export function buildViolinMarks({ data, plot, channels, options, alpha, type }: {
7
+ data: any[];
8
+ plot: any;
9
+ channels: any;
10
+ options?: any;
11
+ alpha?: number;
12
+ type?: string;
13
+ }): any;
@@ -0,0 +1,31 @@
1
+ import { buildViolins } from '../../../lib/brewing/marks/violins.js'
2
+ import { resolveAlpha } from '../aesthetics.js'
3
+
4
+ /**
5
+ * Build renderable violin silhouettes. Interior + outline come from `fill` (defaulting to
6
+ * `x`); an explicit `color` channel overrides the outline stroke. Adds fixed `alpha`.
7
+ * @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
8
+ */
9
+ export function buildViolinMarks({ data, plot, channels, options = {}, alpha, type = 'violin' }) {
10
+ const { xScale, yScale, colors } = plot
11
+ if (!data?.length || !xScale || !yScale) return []
12
+
13
+ const fillField = channels.fill ?? channels.x
14
+ const violins = buildViolins(
15
+ data,
16
+ { x: channels.x, fill: fillField, pattern: channels.pattern },
17
+ xScale,
18
+ yScale,
19
+ colors,
20
+ plot.place.bind(plot),
21
+ options.side,
22
+ plot.patterns
23
+ )
24
+ const a = resolveAlpha(alpha, type, plot.chartPreset)
25
+ const colorField = channels.color
26
+ return violins.map((v) => ({
27
+ ...v,
28
+ stroke: colorField ? (colors.get(v.data[colorField])?.stroke ?? v.stroke) : v.stroke,
29
+ alpha: a
30
+ }))
31
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Build renderable waterfall bars. Bar color is semantic (positive/negative/total from
3
+ * options), not the shared palette. Adds fixed `alpha` and carries connector anchors.
4
+ * @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
5
+ */
6
+ export function buildWaterfallMarks({ data, plot, channels, options, alpha, type }: {
7
+ data: any[];
8
+ plot: any;
9
+ channels: any;
10
+ options?: any;
11
+ alpha?: number;
12
+ type?: string;
13
+ }): {
14
+ key: string;
15
+ x: number;
16
+ y: number;
17
+ width: number;
18
+ height: number;
19
+ fill: any;
20
+ alpha: number;
21
+ bandStart: any;
22
+ bandEnd: any;
23
+ cumY: any;
24
+ data: any;
25
+ }[];
@@ -0,0 +1,58 @@
1
+ import { resolveAlpha } from '../aesthetics.js'
2
+
3
+ /**
4
+ * Build renderable waterfall bars. Bar color is semantic (positive/negative/total from
5
+ * options), not the shared palette. Adds fixed `alpha` and carries connector anchors.
6
+ * @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
7
+ */
8
+ export function buildWaterfallMarks({ data, plot, channels, options = {}, alpha, type = 'waterfall' }) {
9
+ const { xScale, yScale } = plot
10
+ if (!data?.length || !xScale || !yScale) return []
11
+
12
+ const positiveColor = options.positiveColor ?? '#22c55e'
13
+ const negativeColor = options.negativeColor ?? '#ef4444'
14
+ const totalColor = options.totalColor ?? '#3b82f6'
15
+ const totalField = options.totalField ?? undefined
16
+ const a = resolveAlpha(alpha, type, plot.chartPreset)
17
+
18
+ const bw = typeof xScale.bandwidth === 'function' ? xScale.bandwidth() : 10
19
+ let cumulative = 0
20
+
21
+ return data.map((d, i) => {
22
+ const xVal = d[channels.x ?? '']
23
+ const yVal = Number(d[channels.y ?? ''])
24
+ const isTotal = totalField ? Boolean(d[totalField]) : false
25
+ const xPos = xScale(xVal) ?? 0
26
+ let barTop
27
+ let barBottom
28
+ let fill
29
+
30
+ if (isTotal) {
31
+ barTop = yScale(Math.max(0, cumulative)) ?? 0
32
+ barBottom = yScale(0) ?? 0
33
+ fill = totalColor
34
+ } else {
35
+ const start = cumulative
36
+ cumulative += yVal
37
+ barTop = yScale(Math.max(start, cumulative)) ?? 0
38
+ barBottom = yScale(Math.min(start, cumulative)) ?? 0
39
+ fill = yVal >= 0 ? positiveColor : negativeColor
40
+ }
41
+
42
+ const c1 = plot.place(xPos, barTop)
43
+ const c2 = plot.place(xPos + bw, barBottom)
44
+ return {
45
+ key: `${xVal}-${i}`,
46
+ x: Math.min(c1.x, c2.x),
47
+ y: Math.min(c1.y, c2.y),
48
+ width: Math.abs(c2.x - c1.x),
49
+ height: Math.max(1, Math.abs(c2.y - c1.y)),
50
+ fill,
51
+ alpha: a,
52
+ bandStart: xPos,
53
+ bandEnd: xPos + bw,
54
+ cumY: yScale(cumulative) ?? 0,
55
+ data: d
56
+ }
57
+ })
58
+ }
package/dist/index.d.ts CHANGED
@@ -54,13 +54,6 @@ export { default as BoxPlot } from "./charts/BoxPlot.svelte";
54
54
  export { default as ViolinPlot } from "./charts/ViolinPlot.svelte";
55
55
  export { default as BubbleChart } from "./charts/BubbleChart.svelte";
56
56
  export { PlotState } from "./PlotState.svelte.js";
57
- export { ChartBrewer } from "./lib/brewing/index.svelte.js";
58
- export * from "./lib/brewing/index.svelte.js";
59
- export { CartesianBrewer } from "./lib/brewing/CartesianBrewer.svelte.js";
60
- export { PieBrewer } from "./lib/brewing/PieBrewer.svelte.js";
61
- export { QuartileBrewer } from "./lib/brewing/QuartileBrewer.svelte.js";
62
- export { BoxBrewer } from "./lib/brewing/BoxBrewer.svelte.js";
63
- export { ViolinBrewer } from "./lib/brewing/ViolinBrewer.svelte.js";
64
57
  export { createCrossFilter } from "./crossfilter/createCrossFilter.svelte.js";
65
58
  export { default as CrossFilter } from "./crossfilter/CrossFilter.svelte";
66
59
  export { default as FilterBar } from "./crossfilter/FilterBar.svelte";
@@ -74,7 +67,7 @@ import Legend from './Plot/Legend.svelte';
74
67
  import Line from './geoms/Line.svelte';
75
68
  import Area from './geoms/Area.svelte';
76
69
  import Point from './geoms/Point.svelte';
77
- import Arc from './Plot/Arc.svelte';
70
+ import Arc from './geoms/Arc.svelte';
78
71
  import Highlight from './geoms/Highlight.svelte';
79
72
  import Trend from './geoms/Trend.svelte';
80
73
  import Box from './geoms/Box.svelte';
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import Legend from './Plot/Legend.svelte'
6
6
  import Line from './geoms/Line.svelte'
7
7
  import Area from './geoms/Area.svelte'
8
8
  import Point from './geoms/Point.svelte'
9
- import Arc from './Plot/Arc.svelte'
9
+ import Arc from './geoms/Arc.svelte'
10
10
  import Highlight from './geoms/Highlight.svelte'
11
11
  import Trend from './geoms/Trend.svelte'
12
12
  import Box from './geoms/Box.svelte'
@@ -91,14 +91,7 @@ export { default as BubbleChart } from './charts/BubbleChart.svelte'
91
91
  export { PlotState } from './PlotState.svelte.js'
92
92
 
93
93
  // Export utilities
94
- export { ChartBrewer } from './lib/brewing/index.svelte.js'
95
- export * from './lib/brewing/index.svelte.js'
96
94
  export { buildSequentialScale, buildDivergingScale } from './lib/brewing/colors.js'
97
- export { CartesianBrewer } from './lib/brewing/CartesianBrewer.svelte.js'
98
- export { PieBrewer } from './lib/brewing/PieBrewer.svelte.js'
99
- export { QuartileBrewer } from './lib/brewing/QuartileBrewer.svelte.js'
100
- export { BoxBrewer } from './lib/brewing/BoxBrewer.svelte.js'
101
- export { ViolinBrewer } from './lib/brewing/ViolinBrewer.svelte.js'
102
95
 
103
96
  // CrossFilter system
104
97
  export { createCrossFilter } from './crossfilter/createCrossFilter.svelte.js'
@@ -1,10 +1,40 @@
1
1
  /**
2
2
  * Returns true if the value looks like a CSS color literal (not a field name).
3
- * Supports hex (#rgb, #rrggbb, #rrggbbaa), and functional notations (rgb, hsl, oklch, etc.).
3
+ * Supports hex (#rgb, #rrggbb, #rrggbbaa), functional notations (rgb, hsl, oklch, etc.),
4
+ * CSS custom-property refs (var(--token)), and the `currentColor` keyword.
4
5
  * @param {unknown} value
5
6
  * @returns {boolean}
6
7
  */
7
8
  export function isLiteralColor(value: unknown): boolean;
9
+ /**
10
+ * A color-channel value as a literal CSS color (var()/oklch()/#hex/currentColor),
11
+ * or undefined when it names a data field. Pair with {@link markEntry}: a literal is
12
+ * painted directly on every mark; a field is grouped and looked up in the color scale.
13
+ * @param {unknown} value
14
+ * @returns {string | undefined}
15
+ */
16
+ export function literalColor(value: unknown): string | undefined;
17
+ /**
18
+ * Resolves the `{fill, stroke}` for one mark. A `literal` color IS the paint for every
19
+ * mark — applied directly, never grouped as a field or run through the shared `colors`
20
+ * scale (which holds a single value across all geoms). Otherwise `key` is looked up in
21
+ * `colors`, falling back to `fallback`.
22
+ * @param {string | undefined} literal
23
+ * @param {unknown} key
24
+ * @param {Map<unknown, {fill: string, stroke: string}> | undefined} colors
25
+ * @param {{fill: string, stroke: string}} fallback
26
+ * @returns {{fill: string, stroke: string}}
27
+ */
28
+ export function markEntry(literal: string | undefined, key: unknown, colors: Map<unknown, {
29
+ fill: string;
30
+ stroke: string;
31
+ }> | undefined, fallback: {
32
+ fill: string;
33
+ stroke: string;
34
+ }): {
35
+ fill: string;
36
+ stroke: string;
37
+ };
8
38
  /**
9
39
  * Extracts distinct values for a given field from the data array.
10
40
  * @param {Object[]} data
@@ -6,7 +6,8 @@ import { defaultPreset } from '../preset.js'
6
6
 
7
7
  /**
8
8
  * Returns true if the value looks like a CSS color literal (not a field name).
9
- * Supports hex (#rgb, #rrggbb, #rrggbbaa), and functional notations (rgb, hsl, oklch, etc.).
9
+ * Supports hex (#rgb, #rrggbb, #rrggbbaa), functional notations (rgb, hsl, oklch, etc.),
10
+ * CSS custom-property refs (var(--token)), and the `currentColor` keyword.
10
11
  * @param {unknown} value
11
12
  * @returns {boolean}
12
13
  */
@@ -14,9 +15,41 @@ export function isLiteralColor(value) {
14
15
  if (!value || typeof value !== 'string') return false
15
16
  if (/^#([0-9a-fA-F]{3,8})$/.test(value)) return true
16
17
  if (/^(rgb|rgba|hsl|hsla|oklch|oklab|hwb|lab|lch|color)\s*\(/i.test(value)) return true
18
+ // A `var(--token)` ref or `currentColor` resolves to a concrete color at paint time
19
+ // (and stays theme-reactive under [data-mode]), so it means "use this color", not
20
+ // "map the field with this name" — pass it straight through to fill/stroke.
21
+ if (/^var\(\s*--/.test(value)) return true
22
+ if (/^currentcolor$/i.test(value)) return true
17
23
  return false
18
24
  }
19
25
 
26
+ /**
27
+ * A color-channel value as a literal CSS color (var()/oklch()/#hex/currentColor),
28
+ * or undefined when it names a data field. Pair with {@link markEntry}: a literal is
29
+ * painted directly on every mark; a field is grouped and looked up in the color scale.
30
+ * @param {unknown} value
31
+ * @returns {string | undefined}
32
+ */
33
+ export function literalColor(value) {
34
+ return isLiteralColor(value) ? /** @type {string} */ (value) : undefined
35
+ }
36
+
37
+ /**
38
+ * Resolves the `{fill, stroke}` for one mark. A `literal` color IS the paint for every
39
+ * mark — applied directly, never grouped as a field or run through the shared `colors`
40
+ * scale (which holds a single value across all geoms). Otherwise `key` is looked up in
41
+ * `colors`, falling back to `fallback`.
42
+ * @param {string | undefined} literal
43
+ * @param {unknown} key
44
+ * @param {Map<unknown, {fill: string, stroke: string}> | undefined} colors
45
+ * @param {{fill: string, stroke: string}} fallback
46
+ * @returns {{fill: string, stroke: string}}
47
+ */
48
+ export function markEntry(literal, key, colors, fallback) {
49
+ if (literal) return { fill: literal, stroke: literal }
50
+ return colors?.get(key) ?? fallback
51
+ }
52
+
20
53
  /**
21
54
  * Extracts distinct values for a given field from the data array.
22
55
  * @param {Object[]} data