@rokkit/chart 1.3.7 → 1.3.9
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/AnimatedPlot.svelte +26 -8
- package/dist/AnimatedPlot.svelte.d.ts +5 -0
- package/dist/Chart.svelte +51 -65
- package/dist/FacetPlot.svelte +1 -1
- package/dist/Plot/Axis.svelte +19 -10
- package/dist/Plot/Root.svelte +67 -46
- package/dist/Plot/Root.svelte.d.ts +29 -2
- package/dist/Plot/Timeline.svelte +39 -13
- package/dist/Plot/index.d.ts +5 -5
- package/dist/Plot/index.js +5 -5
- package/dist/Plot.svelte +95 -178
- package/dist/Plot.svelte.d.ts +4 -0
- package/dist/PlotState.svelte.d.ts +29 -1
- package/dist/PlotState.svelte.js +163 -19
- package/dist/PlotSurface.svelte +162 -0
- package/dist/PlotSurface.svelte.d.ts +25 -0
- package/dist/charts/AreaChart.svelte +12 -2
- package/dist/charts/AreaChart.svelte.d.ts +6 -0
- package/dist/charts/BarChart.svelte +40 -4
- package/dist/charts/BarChart.svelte.d.ts +15 -1
- package/dist/charts/BoxPlot.svelte +7 -3
- package/dist/charts/BoxPlot.svelte.d.ts +2 -0
- package/dist/charts/BubbleChart.svelte +37 -4
- package/dist/charts/BubbleChart.svelte.d.ts +12 -1
- package/dist/charts/LineChart.svelte +4 -2
- package/dist/charts/LineChart.svelte.d.ts +1 -0
- package/dist/charts/PieChart.svelte +5 -3
- package/dist/charts/PieChart.svelte.d.ts +1 -0
- package/dist/charts/ScatterPlot.svelte +35 -4
- package/dist/charts/ScatterPlot.svelte.d.ts +11 -1
- package/dist/charts/ViolinPlot.svelte +7 -3
- package/dist/charts/ViolinPlot.svelte.d.ts +2 -0
- package/dist/examples/BarChartExample.svelte +1 -1
- package/dist/geoms/Arc.svelte +21 -54
- package/dist/geoms/Arc.svelte.d.ts +4 -4
- package/dist/geoms/Area.svelte +45 -44
- package/dist/geoms/Area.svelte.d.ts +8 -1
- package/dist/geoms/Bar.svelte +33 -45
- package/dist/geoms/Bar.svelte.d.ts +9 -0
- package/dist/geoms/Box.svelte +103 -61
- package/dist/geoms/Box.svelte.d.ts +12 -3
- package/dist/geoms/Candlestick.svelte +26 -75
- package/dist/geoms/Candlestick.svelte.d.ts +2 -0
- package/dist/geoms/Heatmap.svelte +21 -66
- package/dist/geoms/Heatmap.svelte.d.ts +2 -0
- package/dist/geoms/Hexbin.svelte +20 -100
- package/dist/geoms/Hexbin.svelte.d.ts +2 -0
- package/dist/geoms/Jitter.svelte +67 -0
- package/dist/geoms/Jitter.svelte.d.ts +17 -0
- package/dist/geoms/Line.svelte +27 -31
- package/dist/geoms/Line.svelte.d.ts +5 -0
- package/dist/geoms/Point.svelte +31 -60
- package/dist/geoms/Point.svelte.d.ts +5 -1
- package/dist/geoms/Ribbon.svelte +30 -124
- package/dist/geoms/Ribbon.svelte.d.ts +2 -0
- package/dist/geoms/Rule.svelte +117 -0
- package/dist/geoms/Rule.svelte.d.ts +19 -0
- package/dist/geoms/Violin.svelte +36 -31
- package/dist/geoms/Violin.svelte.d.ts +10 -3
- package/dist/geoms/Waterfall.svelte +25 -79
- package/dist/geoms/Waterfall.svelte.d.ts +2 -0
- package/dist/geoms/lib/GeomState.svelte.d.ts +51 -0
- package/dist/geoms/lib/GeomState.svelte.js +79 -0
- package/dist/geoms/lib/aesthetics.d.ts +36 -0
- package/dist/geoms/lib/aesthetics.js +45 -0
- package/dist/geoms/lib/areas.d.ts +6 -2
- package/dist/geoms/lib/areas.js +20 -12
- package/dist/geoms/lib/bars.d.ts +18 -3
- package/dist/geoms/lib/bars.js +80 -101
- package/dist/geoms/lib/marks/arc.d.ts +24 -0
- package/dist/geoms/lib/marks/arc.js +28 -0
- package/dist/geoms/lib/marks/area.d.ts +23 -0
- package/dist/geoms/lib/marks/area.js +38 -0
- package/dist/geoms/lib/marks/bar.d.ts +16 -0
- package/dist/geoms/lib/marks/bar.js +43 -0
- package/dist/geoms/lib/marks/box.d.ts +14 -0
- package/dist/geoms/lib/marks/box.js +30 -0
- package/dist/geoms/lib/marks/candlestick.d.ts +27 -0
- package/dist/geoms/lib/marks/candlestick.js +52 -0
- package/dist/geoms/lib/marks/heatmap.d.ts +24 -0
- package/dist/geoms/lib/marks/heatmap.js +41 -0
- package/dist/geoms/lib/marks/hexbin.d.ts +29 -0
- package/dist/geoms/lib/marks/hexbin.js +62 -0
- package/dist/geoms/lib/marks/jitter.d.ts +21 -0
- package/dist/geoms/lib/marks/jitter.js +36 -0
- package/dist/geoms/lib/marks/line.d.ts +26 -0
- package/dist/geoms/lib/marks/line.js +25 -0
- package/dist/geoms/lib/marks/point.d.ts +25 -0
- package/dist/geoms/lib/marks/point.js +58 -0
- package/dist/geoms/lib/marks/ribbon.d.ts +31 -0
- package/dist/geoms/lib/marks/ribbon.js +77 -0
- package/dist/geoms/lib/marks/violin.d.ts +13 -0
- package/dist/geoms/lib/marks/violin.js +31 -0
- package/dist/geoms/lib/marks/waterfall.d.ts +25 -0
- package/dist/geoms/lib/marks/waterfall.js +58 -0
- package/dist/index.d.ts +25 -12
- package/dist/index.js +26 -13
- package/dist/lib/brewing/marks/arcs.js +7 -1
- package/dist/lib/brewing/marks/boxes.d.ts +11 -2
- package/dist/lib/brewing/marks/boxes.js +32 -11
- package/dist/lib/brewing/marks/lines.d.ts +4 -1
- package/dist/lib/brewing/marks/lines.js +18 -15
- package/dist/lib/brewing/marks/swarm.d.ts +32 -0
- package/dist/lib/brewing/marks/swarm.js +125 -0
- package/dist/lib/brewing/marks/violins.d.ts +4 -20
- package/dist/lib/brewing/marks/violins.js +41 -25
- package/dist/lib/brewing/stats.d.ts +3 -1
- package/dist/lib/brewing/stats.js +28 -11
- package/dist/lib/plot/scales.js +12 -7
- package/dist/lib/plot/types.d.ts +9 -1
- package/dist/lib/plot/types.js +3 -1
- package/dist/lib/preset.d.ts +11 -1
- package/dist/lib/preset.js +16 -4
- package/package.json +1 -1
- package/dist/Plot/Arc.svelte +0 -30
- package/dist/Plot/Arc.svelte.d.ts +0 -18
- package/dist/Plot/Area.svelte +0 -54
- package/dist/Plot/Area.svelte.d.ts +0 -14
- package/dist/Plot/Bar.svelte +0 -69
- package/dist/Plot/Bar.svelte.d.ts +0 -11
- package/dist/Plot/Line.svelte +0 -54
- package/dist/Plot/Line.svelte.d.ts +0 -13
- package/dist/Plot/Point.svelte +0 -58
- package/dist/Plot/Point.svelte.d.ts +0 -13
- package/dist/lib/brewer.d.ts +0 -9
- package/dist/lib/brewer.js +0 -25
- package/dist/lib/brewing/BoxBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/BoxBrewer.svelte.js +0 -14
- package/dist/lib/brewing/CartesianBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/CartesianBrewer.svelte.js +0 -21
- package/dist/lib/brewing/PieBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/PieBrewer.svelte.js +0 -14
- package/dist/lib/brewing/QuartileBrewer.svelte.d.ts +0 -9
- package/dist/lib/brewing/QuartileBrewer.svelte.js +0 -51
- package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/ViolinBrewer.svelte.js +0 -14
- package/dist/lib/brewing/axes.svelte.d.ts +0 -66
- package/dist/lib/brewing/axes.svelte.js +0 -270
- package/dist/lib/brewing/bars.svelte.d.ts +0 -56
- package/dist/lib/brewing/bars.svelte.js +0 -201
- package/dist/lib/brewing/brewer.svelte.d.ts +0 -124
- package/dist/lib/brewing/brewer.svelte.js +0 -277
- package/dist/lib/brewing/dimensions.svelte.d.ts +0 -35
- package/dist/lib/brewing/dimensions.svelte.js +0 -56
- package/dist/lib/brewing/index.svelte.d.ts +0 -118
- package/dist/lib/brewing/index.svelte.js +0 -205
- package/dist/lib/brewing/legends.svelte.d.ts +0 -48
- package/dist/lib/brewing/legends.svelte.js +0 -137
- package/dist/lib/brewing/marks/areas.d.ts +0 -31
- package/dist/lib/brewing/marks/areas.js +0 -72
- package/dist/lib/brewing/marks/bars.d.ts +0 -1
- package/dist/lib/brewing/marks/bars.js +0 -49
- package/dist/lib/brewing/scales.svelte.d.ts +0 -24
- package/dist/lib/brewing/scales.svelte.js +0 -82
- package/dist/lib/context.d.ts +0 -13
- package/dist/lib/context.js +0 -131
- package/dist/lib/plot/chartProps.d.ts +0 -177
- package/dist/lib/plot/chartProps.js +0 -76
- package/dist/lib/scales.svelte.d.ts +0 -35
- package/dist/lib/scales.svelte.js +0 -151
- package/dist/lib/ticks.d.ts +0 -36
- package/dist/lib/ticks.js +0 -46
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getContext, onMount, onDestroy } from 'svelte'
|
|
3
3
|
import type { PlotState } from '../PlotState.svelte.js'
|
|
4
|
+
import { GeomState } from './lib/GeomState.svelte.js'
|
|
5
|
+
import { buildWaterfallMarks } from './lib/marks/waterfall.js'
|
|
4
6
|
|
|
5
7
|
type Options = {
|
|
6
8
|
positiveColor?: string
|
|
@@ -15,90 +17,31 @@
|
|
|
15
17
|
y?: string
|
|
16
18
|
color?: string
|
|
17
19
|
fill?: string
|
|
20
|
+
/** Fixed bar opacity 0–1; defaults to the per-geom preset (waterfall = 1). */
|
|
21
|
+
alpha?: number
|
|
18
22
|
stat?: string
|
|
19
23
|
options?: Options
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
let {
|
|
23
|
-
x,
|
|
24
|
-
y,
|
|
25
|
-
color = undefined,
|
|
26
|
-
fill: fillProp = undefined,
|
|
27
|
-
stat = 'identity',
|
|
28
|
-
options = {}
|
|
29
|
-
}: Props = $props()
|
|
26
|
+
let { x, y, color, fill, alpha, stat = 'identity', options = {} }: Props = $props()
|
|
30
27
|
|
|
31
|
-
const
|
|
32
|
-
const negativeColor = $derived(options.negativeColor ?? '#ef4444')
|
|
33
|
-
const totalColor = $derived(options.totalColor ?? '#3b82f6')
|
|
34
|
-
const totalField = $derived(options.totalField ?? undefined)
|
|
28
|
+
const colorField = $derived(fill ?? color)
|
|
35
29
|
const connectorWidth = $derived(options.connectorWidth ?? 0.5)
|
|
36
|
-
|
|
37
30
|
const plotState = getContext<PlotState>('plot-state')
|
|
38
|
-
let id = $state<string | null>(null)
|
|
39
|
-
|
|
40
|
-
onMount(() => {
|
|
41
|
-
id = plotState.registerGeom({
|
|
42
|
-
type: 'waterfall',
|
|
43
|
-
channels: { x, y, color: fillProp ?? color },
|
|
44
|
-
stat
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
onDestroy(() => {
|
|
48
|
-
if (id) plotState.unregisterGeom(id)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
$effect(() => {
|
|
52
|
-
if (id)
|
|
53
|
-
plotState.updateGeom(id, {
|
|
54
|
-
channels: { x, y, color: fillProp ?? color },
|
|
55
|
-
stat
|
|
56
|
-
})
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
const data = $derived(id ? plotState.geomData(id) : [])
|
|
60
|
-
const xScale = $derived(plotState.xScale)
|
|
61
|
-
const yScale = $derived(plotState.yScale)
|
|
62
|
-
|
|
63
|
-
// eslint-disable-next-line max-lines-per-function
|
|
64
|
-
const bars = $derived.by(() => {
|
|
65
|
-
if (!data?.length || !xScale || !yScale) return []
|
|
66
|
-
const bw = typeof xScale.bandwidth === 'function' ? xScale.bandwidth() : 10
|
|
67
|
-
|
|
68
|
-
let cumulative = 0
|
|
69
|
-
return data.map((d, i) => {
|
|
70
|
-
const xVal = d[x ?? '']
|
|
71
|
-
const yVal = Number(d[y ?? ''])
|
|
72
|
-
const isTotal = totalField ? Boolean(d[totalField]) : false
|
|
73
|
-
const xPos = xScale(xVal) ?? 0
|
|
74
|
-
let barTop: number
|
|
75
|
-
let barBottom: number
|
|
76
|
-
let fill: string
|
|
77
31
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
32
|
+
const geom = new GeomState(plotState, () => ({
|
|
33
|
+
type: 'waterfall',
|
|
34
|
+
channels: { x, y, color: colorField },
|
|
35
|
+
stat,
|
|
36
|
+
options,
|
|
37
|
+
alpha,
|
|
38
|
+
build: buildWaterfallMarks
|
|
39
|
+
}))
|
|
40
|
+
onMount(geom.register)
|
|
41
|
+
onDestroy(geom.destroy)
|
|
42
|
+
$effect(geom.sync)
|
|
89
43
|
|
|
90
|
-
|
|
91
|
-
key: `${xVal}-${i}`,
|
|
92
|
-
x: xPos,
|
|
93
|
-
y: barTop,
|
|
94
|
-
width: bw,
|
|
95
|
-
height: Math.max(1, barBottom - barTop),
|
|
96
|
-
fill,
|
|
97
|
-
cumulative,
|
|
98
|
-
data: d
|
|
99
|
-
}
|
|
100
|
-
})
|
|
101
|
-
})
|
|
44
|
+
const bars = $derived(geom.marks)
|
|
102
45
|
</script>
|
|
103
46
|
|
|
104
47
|
{#if bars.length > 0}
|
|
@@ -110,6 +53,7 @@
|
|
|
110
53
|
width={Math.max(0, bar.width)}
|
|
111
54
|
height={bar.height}
|
|
112
55
|
fill={bar.fill}
|
|
56
|
+
fill-opacity={bar.alpha}
|
|
113
57
|
data-plot-element="waterfall-bar"
|
|
114
58
|
role="img"
|
|
115
59
|
onmouseenter={() => plotState.setHovered(bar.data)}
|
|
@@ -119,11 +63,13 @@
|
|
|
119
63
|
</rect>
|
|
120
64
|
<!-- Connector line to next bar -->
|
|
121
65
|
{#if i < bars.length - 1}
|
|
66
|
+
{@const cs = plotState.place(bar.bandEnd, bar.cumY)}
|
|
67
|
+
{@const ce = plotState.place(bars[i + 1].bandStart, bar.cumY)}
|
|
122
68
|
<line
|
|
123
|
-
x1={
|
|
124
|
-
y1={
|
|
125
|
-
x2={
|
|
126
|
-
y2={
|
|
69
|
+
x1={cs.x}
|
|
70
|
+
y1={cs.y}
|
|
71
|
+
x2={ce.x}
|
|
72
|
+
y2={ce.y}
|
|
127
73
|
stroke="currentColor"
|
|
128
74
|
stroke-width={connectorWidth}
|
|
129
75
|
stroke-dasharray="3 2"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-geom reactive state, fed by the shared `PlotState` context. Owns the geom's
|
|
3
|
+
* registration lifecycle and derives the renderable `marks` array, so a geom component
|
|
4
|
+
* only has to render marks + wire interactivity. This is the integrated version of the
|
|
5
|
+
* old brewer idea — one shared path, not a parallel one.
|
|
6
|
+
* See docs/backlog/2026-08-17-chart-aesthetics-unification.md §11.
|
|
7
|
+
*
|
|
8
|
+
* @typedef {Object} GeomConfig
|
|
9
|
+
* @property {string} type
|
|
10
|
+
* @property {Record<string, string | undefined>} channels
|
|
11
|
+
* @property {string} [stat]
|
|
12
|
+
* @property {Record<string, unknown>} [options]
|
|
13
|
+
* @property {number} [alpha]
|
|
14
|
+
* @property {(ctx: { data: any[], plot: any, channels: any, options: any, alpha: number|undefined, type: string }) => any} build
|
|
15
|
+
*/
|
|
16
|
+
export class GeomState {
|
|
17
|
+
/**
|
|
18
|
+
* @param {any} plot - the PlotState from context
|
|
19
|
+
* @param {() => GeomConfig} config - a getter thunk (Svelte passes reactive values to
|
|
20
|
+
* classes via getters), read reactively on every marks recompute.
|
|
21
|
+
*/
|
|
22
|
+
constructor(plot: any, config: () => GeomConfig);
|
|
23
|
+
register: () => void;
|
|
24
|
+
sync: () => void;
|
|
25
|
+
destroy: () => void;
|
|
26
|
+
get data(): any;
|
|
27
|
+
marks: any;
|
|
28
|
+
#private;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Per-geom reactive state, fed by the shared `PlotState` context. Owns the geom's
|
|
32
|
+
* registration lifecycle and derives the renderable `marks` array, so a geom component
|
|
33
|
+
* only has to render marks + wire interactivity. This is the integrated version of the
|
|
34
|
+
* old brewer idea — one shared path, not a parallel one.
|
|
35
|
+
* See docs/backlog/2026-08-17-chart-aesthetics-unification.md §11.
|
|
36
|
+
*/
|
|
37
|
+
export type GeomConfig = {
|
|
38
|
+
type: string;
|
|
39
|
+
channels: Record<string, string | undefined>;
|
|
40
|
+
stat?: string | undefined;
|
|
41
|
+
options?: Record<string, unknown> | undefined;
|
|
42
|
+
alpha?: number | undefined;
|
|
43
|
+
build: (ctx: {
|
|
44
|
+
data: any[];
|
|
45
|
+
plot: any;
|
|
46
|
+
channels: any;
|
|
47
|
+
options: any;
|
|
48
|
+
alpha: number | undefined;
|
|
49
|
+
type: string;
|
|
50
|
+
}) => any;
|
|
51
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-geom reactive state, fed by the shared `PlotState` context. Owns the geom's
|
|
3
|
+
* registration lifecycle and derives the renderable `marks` array, so a geom component
|
|
4
|
+
* only has to render marks + wire interactivity. This is the integrated version of the
|
|
5
|
+
* old brewer idea — one shared path, not a parallel one.
|
|
6
|
+
* See docs/backlog/2026-08-17-chart-aesthetics-unification.md §11.
|
|
7
|
+
*
|
|
8
|
+
* @typedef {Object} GeomConfig
|
|
9
|
+
* @property {string} type
|
|
10
|
+
* @property {Record<string, string | undefined>} channels
|
|
11
|
+
* @property {string} [stat]
|
|
12
|
+
* @property {Record<string, unknown>} [options]
|
|
13
|
+
* @property {number} [alpha]
|
|
14
|
+
* @property {(ctx: { data: any[], plot: any, channels: any, options: any, alpha: number|undefined, type: string }) => any} build
|
|
15
|
+
*/
|
|
16
|
+
export class GeomState {
|
|
17
|
+
#plot
|
|
18
|
+
#config
|
|
19
|
+
#id = $state(null)
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {any} plot - the PlotState from context
|
|
23
|
+
* @param {() => GeomConfig} config - a getter thunk (Svelte passes reactive values to
|
|
24
|
+
* classes via getters), read reactively on every marks recompute.
|
|
25
|
+
*/
|
|
26
|
+
constructor(plot, config) {
|
|
27
|
+
this.#plot = plot
|
|
28
|
+
this.#config = config
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Arrow methods so they can be passed straight to onMount/onDestroy/$effect without
|
|
32
|
+
// losing `this` (see Svelte $state "Classes" docs).
|
|
33
|
+
register = () => {
|
|
34
|
+
const c = this.#config()
|
|
35
|
+
this.#id = this.#plot.registerGeom({
|
|
36
|
+
type: c.type,
|
|
37
|
+
channels: c.channels,
|
|
38
|
+
stat: c.stat ?? 'identity',
|
|
39
|
+
options: c.options ?? {}
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
sync = () => {
|
|
44
|
+
if (this.#id === null) return
|
|
45
|
+
const c = this.#config()
|
|
46
|
+
this.#plot.updateGeom(this.#id, {
|
|
47
|
+
channels: c.channels,
|
|
48
|
+
stat: c.stat ?? 'identity',
|
|
49
|
+
options: c.options ?? {}
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
destroy = () => {
|
|
54
|
+
if (this.#id !== null) this.#plot.unregisterGeom(this.#id)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#data = $derived(this.#id !== null ? this.#plot.geomData(this.#id) : [])
|
|
58
|
+
|
|
59
|
+
// The geom's rows (post-stat). Exposed for templates that render per-row overlays
|
|
60
|
+
// (e.g. an area's invisible tooltip hit-circles) alongside the computed `marks`.
|
|
61
|
+
get data() {
|
|
62
|
+
return this.#data
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// The renderable array. `build` reads scales/colors/preset from `plot` synchronously,
|
|
66
|
+
// so those reads register as dependencies and `marks` recomputes on any change.
|
|
67
|
+
marks = $derived.by(() => {
|
|
68
|
+
const c = this.#config()
|
|
69
|
+
if (!this.#data.length) return []
|
|
70
|
+
return c.build({
|
|
71
|
+
data: this.#data,
|
|
72
|
+
plot: this.#plot,
|
|
73
|
+
channels: c.channels,
|
|
74
|
+
options: c.options ?? {},
|
|
75
|
+
alpha: c.alpha,
|
|
76
|
+
type: c.type
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a mark's interior (fill) and outline (stroke) from the `fill` and `color`
|
|
3
|
+
* channels through the shared categorical `colors` palette (Map<value, {fill, stroke}>).
|
|
4
|
+
*
|
|
5
|
+
* ggplot fallback:
|
|
6
|
+
* - color-only → fill AND stroke from the color value's entry (backward compatible)
|
|
7
|
+
* - fill-only → fill AND stroke from the fill value's entry
|
|
8
|
+
* - both → interior from the fill value, outline from the color value
|
|
9
|
+
* - neither → the first palette entry (single-series)
|
|
10
|
+
*
|
|
11
|
+
* @param {Record<string, unknown>} row
|
|
12
|
+
* @param {{ fill?: string, color?: string }} channels
|
|
13
|
+
* @param {Map<unknown, { fill: string, stroke: string }>} colors
|
|
14
|
+
* @returns {{ fill: string, stroke: string }}
|
|
15
|
+
*/
|
|
16
|
+
export function resolveFillStroke(row: Record<string, unknown>, channels: {
|
|
17
|
+
fill?: string;
|
|
18
|
+
color?: string;
|
|
19
|
+
}, colors: Map<unknown, {
|
|
20
|
+
fill: string;
|
|
21
|
+
stroke: string;
|
|
22
|
+
}>): {
|
|
23
|
+
fill: string;
|
|
24
|
+
stroke: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a geom's mark opacity: an explicit fixed `alpha`, else the per-geom preset
|
|
28
|
+
* default, else fully opaque.
|
|
29
|
+
* @param {number|undefined} alpha
|
|
30
|
+
* @param {string} type - geom type key into preset.opacity
|
|
31
|
+
* @param {{ opacity?: Record<string, number> }} [preset]
|
|
32
|
+
* @returns {number}
|
|
33
|
+
*/
|
|
34
|
+
export function resolveAlpha(alpha: number | undefined, type: string, preset?: {
|
|
35
|
+
opacity?: Record<string, number>;
|
|
36
|
+
}): number;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Shared aesthetic resolution for all geoms. Done ONCE here so every geom's build
|
|
2
|
+
// function inherits the same ggplot-aligned fill/color and alpha rules.
|
|
3
|
+
// See docs/backlog/2026-08-17-chart-aesthetics-unification.md §2, §3, §11.
|
|
4
|
+
|
|
5
|
+
const GRAY = { fill: '#888', stroke: '#888' }
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a mark's interior (fill) and outline (stroke) from the `fill` and `color`
|
|
9
|
+
* channels through the shared categorical `colors` palette (Map<value, {fill, stroke}>).
|
|
10
|
+
*
|
|
11
|
+
* ggplot fallback:
|
|
12
|
+
* - color-only → fill AND stroke from the color value's entry (backward compatible)
|
|
13
|
+
* - fill-only → fill AND stroke from the fill value's entry
|
|
14
|
+
* - both → interior from the fill value, outline from the color value
|
|
15
|
+
* - neither → the first palette entry (single-series)
|
|
16
|
+
*
|
|
17
|
+
* @param {Record<string, unknown>} row
|
|
18
|
+
* @param {{ fill?: string, color?: string }} channels
|
|
19
|
+
* @param {Map<unknown, { fill: string, stroke: string }>} colors
|
|
20
|
+
* @returns {{ fill: string, stroke: string }}
|
|
21
|
+
*/
|
|
22
|
+
export function resolveFillStroke(row, channels, colors) {
|
|
23
|
+
const { fill, color } = channels ?? {}
|
|
24
|
+
const first = colors?.values?.().next?.().value ?? GRAY
|
|
25
|
+
const fillEntry = fill ? (colors?.get(row?.[fill]) ?? first) : null
|
|
26
|
+
const colorEntry = color ? (colors?.get(row?.[color]) ?? first) : null
|
|
27
|
+
const base = fillEntry ?? colorEntry ?? first
|
|
28
|
+
return {
|
|
29
|
+
fill: (fillEntry ?? base).fill,
|
|
30
|
+
stroke: (colorEntry ?? base).stroke
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolve a geom's mark opacity: an explicit fixed `alpha`, else the per-geom preset
|
|
36
|
+
* default, else fully opaque.
|
|
37
|
+
* @param {number|undefined} alpha
|
|
38
|
+
* @param {string} type - geom type key into preset.opacity
|
|
39
|
+
* @param {{ opacity?: Record<string, number> }} [preset]
|
|
40
|
+
* @returns {number}
|
|
41
|
+
*/
|
|
42
|
+
export function resolveAlpha(alpha, type, preset) {
|
|
43
|
+
if (typeof alpha === 'number') return alpha
|
|
44
|
+
return preset?.opacity?.[type] ?? 1
|
|
45
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @param {Map<unknown, {fill: string, stroke: string}>} colors
|
|
9
9
|
* @param {'linear'|'smooth'|'step'} [curve]
|
|
10
10
|
* @param {Map<unknown, string>} [patterns]
|
|
11
|
+
* @param {Function} [place] - orientation mapper (x, y) => {x, y}
|
|
11
12
|
* @returns {{ d: string, fill: string, stroke: string, key: unknown, patternId: string|null }[]}
|
|
12
13
|
*/
|
|
13
14
|
export function buildAreas(data: Object[], channels: {
|
|
@@ -18,7 +19,7 @@ export function buildAreas(data: Object[], channels: {
|
|
|
18
19
|
}, xScale: Function, yScale: Function, colors: Map<unknown, {
|
|
19
20
|
fill: string;
|
|
20
21
|
stroke: string;
|
|
21
|
-
}>, curve?: "linear" | "smooth" | "step", patterns?: Map<unknown, string
|
|
22
|
+
}>, curve?: "linear" | "smooth" | "step", patterns?: Map<unknown, string>, place?: Function): {
|
|
22
23
|
d: string;
|
|
23
24
|
fill: string;
|
|
24
25
|
stroke: string;
|
|
@@ -45,7 +46,10 @@ export function buildStackedAreas(data: Object[], channels: {
|
|
|
45
46
|
}, xScale: Function, yScale: Function, colors: Map<unknown, {
|
|
46
47
|
fill: string;
|
|
47
48
|
stroke: string;
|
|
48
|
-
}>, curve?: "linear" | "smooth" | "step", patterns?: Map<unknown, string
|
|
49
|
+
}>, curve?: "linear" | "smooth" | "step", patterns?: Map<unknown, string>, place?: (x: any, y: any) => {
|
|
50
|
+
x: any;
|
|
51
|
+
y: any;
|
|
52
|
+
}, normalize?: boolean): {
|
|
49
53
|
d: string;
|
|
50
54
|
fill: string;
|
|
51
55
|
stroke: string;
|
package/dist/geoms/lib/areas.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { area, stack, curveCatmullRom, curveStep } from 'd3-shape'
|
|
1
|
+
import { area, stack, stackOffsetExpand, curveCatmullRom, curveStep } from 'd3-shape'
|
|
2
2
|
import { toPatternId } from '../../lib/brewing/patterns.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -11,20 +11,24 @@ import { toPatternId } from '../../lib/brewing/patterns.js'
|
|
|
11
11
|
* @param {Map<unknown, {fill: string, stroke: string}>} colors
|
|
12
12
|
* @param {'linear'|'smooth'|'step'} [curve]
|
|
13
13
|
* @param {Map<unknown, string>} [patterns]
|
|
14
|
+
* @param {Function} [place] - orientation mapper (x, y) => {x, y}
|
|
14
15
|
* @returns {{ d: string, fill: string, stroke: string, key: unknown, patternId: string|null }[]}
|
|
15
16
|
*/
|
|
16
|
-
export function buildAreas(data, channels, xScale, yScale, colors, curve, patterns) {
|
|
17
|
+
export function buildAreas(data, channels, xScale, yScale, colors, curve, patterns, place = (x, y) => ({ x, y })) {
|
|
17
18
|
const { x: xf, y: yf, color: cf, pattern: pf } = channels
|
|
18
19
|
const baseline = yScale.range()[0] // bottom of the chart (y pixel max)
|
|
19
20
|
|
|
20
21
|
const xPos = (d) =>
|
|
21
22
|
typeof xScale.bandwidth === 'function' ? xScale(d[xf]) + xScale.bandwidth() / 2 : xScale(d[xf])
|
|
22
23
|
|
|
24
|
+
// Each area edge (baseline + top) is placed so the area transposes under orientation.
|
|
25
|
+
const toEdge = (d) => ({ base: place(xPos(d), baseline), top: place(xPos(d), yScale(d[yf])) })
|
|
23
26
|
const makeGen = () => {
|
|
24
27
|
const gen = area()
|
|
25
|
-
.
|
|
26
|
-
.y0(
|
|
27
|
-
.
|
|
28
|
+
.x0((p) => p.base.x)
|
|
29
|
+
.y0((p) => p.base.y)
|
|
30
|
+
.x1((p) => p.top.x)
|
|
31
|
+
.y1((p) => p.top.y)
|
|
28
32
|
if (curve === 'smooth') gen.curve(curveCatmullRom)
|
|
29
33
|
else if (curve === 'step') gen.curve(curveStep)
|
|
30
34
|
return gen
|
|
@@ -47,7 +51,7 @@ export function buildAreas(data, channels, xScale, yScale, colors, curve, patter
|
|
|
47
51
|
patternKey !== null && patternKey !== undefined && patterns?.has(patternKey)
|
|
48
52
|
? toPatternId(String(patternKey))
|
|
49
53
|
: null
|
|
50
|
-
return [{ d: makeGen()(sortByX(data)), fill: entry.fill, stroke: 'none', key: null, patternId }]
|
|
54
|
+
return [{ d: makeGen()(sortByX(data).map(toEdge)), fill: entry.fill, stroke: 'none', key: null, patternId }]
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
// Group by color field
|
|
@@ -72,7 +76,7 @@ export function buildAreas(data, channels, xScale, yScale, colors, curve, patter
|
|
|
72
76
|
patternKey !== null && patternKey !== undefined && patterns?.has(patternKey)
|
|
73
77
|
? toPatternId(String(patternKey))
|
|
74
78
|
: null
|
|
75
|
-
return { d: makeGen()(sortByX(rows)), fill: entry.fill, stroke: 'none', key, patternId }
|
|
79
|
+
return { d: makeGen()(sortByX(rows).map(toEdge)), fill: entry.fill, stroke: 'none', key, patternId }
|
|
76
80
|
})
|
|
77
81
|
}
|
|
78
82
|
|
|
@@ -88,7 +92,7 @@ export function buildAreas(data, channels, xScale, yScale, colors, curve, patter
|
|
|
88
92
|
* @param {Map<unknown, string>} [patterns]
|
|
89
93
|
* @returns {{ d: string, fill: string, stroke: string, key: unknown, patternId: string|null }[]}
|
|
90
94
|
*/
|
|
91
|
-
export function buildStackedAreas(data, channels, xScale, yScale, colors, curve, patterns) {
|
|
95
|
+
export function buildStackedAreas(data, channels, xScale, yScale, colors, curve, patterns, place = (x, y) => ({ x, y }), normalize = false) {
|
|
92
96
|
const { x: xf, y: yf, color: cf, pattern: pf } = channels
|
|
93
97
|
if (!cf) return buildAreas(data, channels, xScale, yScale, colors, curve, patterns)
|
|
94
98
|
|
|
@@ -115,17 +119,21 @@ export function buildStackedAreas(data, channels, xScale, yScale, colors, curve,
|
|
|
115
119
|
? xScale(d.data[xf]) + xScale.bandwidth() / 2
|
|
116
120
|
: xScale(d.data[xf])
|
|
117
121
|
|
|
122
|
+
const toEdge = (d) => ({ base: place(xPos(d), yScale(d[0])), top: place(xPos(d), yScale(d[1])) })
|
|
118
123
|
const makeGen = () => {
|
|
119
124
|
const gen = area()
|
|
120
|
-
.
|
|
121
|
-
.y0((
|
|
122
|
-
.
|
|
125
|
+
.x0((p) => p.base.x)
|
|
126
|
+
.y0((p) => p.base.y)
|
|
127
|
+
.x1((p) => p.top.x)
|
|
128
|
+
.y1((p) => p.top.y)
|
|
123
129
|
if (curve === 'smooth') gen.curve(curveCatmullRom)
|
|
124
130
|
else if (curve === 'step') gen.curve(curveStep)
|
|
125
131
|
return gen
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
const stackGen = stack().keys(colorCategories)
|
|
135
|
+
// position='fill' → normalize each x column to [0,1] (100% stacked area).
|
|
136
|
+
if (normalize) stackGen.offset(stackOffsetExpand)
|
|
129
137
|
const layers = stackGen(wide)
|
|
130
138
|
|
|
131
139
|
const orderedPatternKeys = pf && pf !== cf ? [...(patterns?.keys() ?? [])] : null
|
|
@@ -144,7 +152,7 @@ export function buildStackedAreas(data, channels, xScale, yScale, colors, curve,
|
|
|
144
152
|
? toPatternId(String(patternKey))
|
|
145
153
|
: null
|
|
146
154
|
return {
|
|
147
|
-
d: makeGen()(layer) ?? '',
|
|
155
|
+
d: makeGen()(layer.map(toEdge)) ?? '',
|
|
148
156
|
fill: entry.fill,
|
|
149
157
|
stroke: 'none',
|
|
150
158
|
key: colorKey,
|
package/dist/geoms/lib/bars.d.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The one bar builder. Expresses each bar in abstract (category, value) space and maps it to
|
|
3
|
+
* screen via `place()`, so it renders vertical or horizontal (flip) with the same code.
|
|
4
|
+
*
|
|
5
|
+
* Category (x) positioning has two modes:
|
|
6
|
+
* - band (default): a scaleBand slots each category; sub-bands split grouped series.
|
|
7
|
+
* - continuous (`continuousCategory`): a LINEAR position scale (e.g. a bar-chart race's tweened
|
|
8
|
+
* `_rank`). Each bar centres on `xScale(pos)` with a fixed thickness from the scale step, so a
|
|
9
|
+
* fractional position tweens smoothly (a band scale can only jump between slots). No sub-banding.
|
|
10
|
+
*/
|
|
11
|
+
export function buildBars(data: any, channels: any, xScale: any, yScale: any, colors: any, innerHeight: any, patterns: any, place?: (x: any, y: any) => {
|
|
12
|
+
x: any;
|
|
13
|
+
y: any;
|
|
14
|
+
}, continuousCategory?: boolean, dodge?: boolean): any;
|
|
15
|
+
export function buildStackedBars(data: any, channels: any, xScale: any, yScale: any, colors: any, innerHeight: any, patterns: any, place?: (x: any, y: any) => {
|
|
16
|
+
x: any;
|
|
17
|
+
y: any;
|
|
18
|
+
}, normalize?: boolean): any;
|