@rokkit/chart 1.3.8 → 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/Chart.svelte +50 -64
- package/dist/Plot/Root.svelte +4 -1
- package/dist/Plot/Root.svelte.d.ts +2 -0
- package/dist/Plot/index.d.ts +1 -1
- package/dist/Plot/index.js +1 -1
- package/dist/PlotState.svelte.d.ts +14 -2
- package/dist/PlotState.svelte.js +46 -4
- package/dist/charts/AreaChart.svelte +8 -1
- package/dist/charts/AreaChart.svelte.d.ts +5 -0
- package/dist/charts/BarChart.svelte +8 -1
- package/dist/charts/BarChart.svelte.d.ts +5 -0
- package/dist/charts/BoxPlot.svelte +3 -1
- package/dist/charts/BoxPlot.svelte.d.ts +1 -0
- package/dist/charts/BubbleChart.svelte +3 -1
- package/dist/charts/BubbleChart.svelte.d.ts +1 -0
- package/dist/charts/LineChart.svelte +3 -1
- package/dist/charts/LineChart.svelte.d.ts +1 -0
- package/dist/charts/PieChart.svelte +4 -2
- package/dist/charts/PieChart.svelte.d.ts +1 -0
- package/dist/charts/ScatterPlot.svelte +3 -1
- package/dist/charts/ScatterPlot.svelte.d.ts +1 -0
- package/dist/charts/ViolinPlot.svelte +3 -1
- package/dist/charts/ViolinPlot.svelte.d.ts +1 -0
- package/dist/geoms/Arc.svelte +21 -55
- 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 -56
- package/dist/geoms/Bar.svelte.d.ts +9 -0
- package/dist/geoms/Box.svelte +118 -131
- package/dist/geoms/Box.svelte.d.ts +5 -3
- package/dist/geoms/Candlestick.svelte +22 -77
- 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 +21 -36
- package/dist/geoms/Jitter.svelte.d.ts +5 -3
- package/dist/geoms/Line.svelte +27 -31
- package/dist/geoms/Line.svelte.d.ts +5 -0
- package/dist/geoms/Point.svelte +31 -66
- 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 +5 -1
- package/dist/geoms/Rule.svelte.d.ts +3 -0
- package/dist/geoms/Violin.svelte +21 -40
- package/dist/geoms/Violin.svelte.d.ts +5 -3
- package/dist/geoms/Waterfall.svelte +19 -80
- 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 +1 -1
- package/dist/geoms/lib/areas.js +4 -2
- package/dist/geoms/lib/bars.d.ts +2 -2
- package/dist/geoms/lib/bars.js +13 -8
- 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 +1 -8
- package/dist/index.js +1 -8
- 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/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
|
@@ -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
|
+
}
|
|
@@ -49,7 +49,7 @@ export function buildStackedAreas(data: Object[], channels: {
|
|
|
49
49
|
}>, curve?: "linear" | "smooth" | "step", patterns?: Map<unknown, string>, place?: (x: any, y: any) => {
|
|
50
50
|
x: any;
|
|
51
51
|
y: any;
|
|
52
|
-
}): {
|
|
52
|
+
}, normalize?: boolean): {
|
|
53
53
|
d: string;
|
|
54
54
|
fill: string;
|
|
55
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
|
/**
|
|
@@ -92,7 +92,7 @@ export function buildAreas(data, channels, xScale, yScale, colors, curve, patter
|
|
|
92
92
|
* @param {Map<unknown, string>} [patterns]
|
|
93
93
|
* @returns {{ d: string, fill: string, stroke: string, key: unknown, patternId: string|null }[]}
|
|
94
94
|
*/
|
|
95
|
-
export function buildStackedAreas(data, channels, xScale, yScale, colors, curve, patterns, place = (x, y) => ({ x, y })) {
|
|
95
|
+
export function buildStackedAreas(data, channels, xScale, yScale, colors, curve, patterns, place = (x, y) => ({ x, y }), normalize = false) {
|
|
96
96
|
const { x: xf, y: yf, color: cf, pattern: pf } = channels
|
|
97
97
|
if (!cf) return buildAreas(data, channels, xScale, yScale, colors, curve, patterns)
|
|
98
98
|
|
|
@@ -132,6 +132,8 @@ export function buildStackedAreas(data, channels, xScale, yScale, colors, curve,
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
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)
|
|
135
137
|
const layers = stackGen(wide)
|
|
136
138
|
|
|
137
139
|
const orderedPatternKeys = pf && pf !== cf ? [...(patterns?.keys() ?? [])] : null
|
package/dist/geoms/lib/bars.d.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
export function buildBars(data: any, channels: any, xScale: any, yScale: any, colors: any, innerHeight: any, patterns: any, place?: (x: any, y: any) => {
|
|
12
12
|
x: any;
|
|
13
13
|
y: any;
|
|
14
|
-
}, continuousCategory?: boolean): any;
|
|
14
|
+
}, continuousCategory?: boolean, dodge?: boolean): any;
|
|
15
15
|
export function buildStackedBars(data: any, channels: any, xScale: any, yScale: any, colors: any, innerHeight: any, patterns: any, place?: (x: any, y: any) => {
|
|
16
16
|
x: any;
|
|
17
17
|
y: any;
|
|
18
|
-
}): any;
|
|
18
|
+
}, normalize?: boolean): any;
|
package/dist/geoms/lib/bars.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { scaleBand } from 'd3-scale'
|
|
2
|
-
import { stack } from 'd3-shape'
|
|
2
|
+
import { stack, stackOffsetExpand } from 'd3-shape'
|
|
3
3
|
import { toPatternId } from '../../lib/brewing/patterns.js'
|
|
4
4
|
import { isLiteralColor } from '../../lib/brewing/colors.js'
|
|
5
5
|
|
|
@@ -17,14 +17,15 @@ function ensureBandX(xScale, data, xField) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Returns the sub-band fields: distinct non-x fields among [color, pattern].
|
|
21
|
-
* These are the fields that cause multiple bars within a single x-band.
|
|
20
|
+
* Returns the sub-band fields: distinct non-x fields among [group, color, pattern].
|
|
21
|
+
* These are the fields that cause multiple bars within a single x-band (dodge/grouping).
|
|
22
|
+
* `group` (the explicit grouping channel) takes precedence as the first sub-band.
|
|
22
23
|
*/
|
|
23
24
|
function subBandFields(channels) {
|
|
24
|
-
const { x: xf, color: cf, pattern: pf } = channels
|
|
25
|
+
const { x: xf, group: gf, color: cf, pattern: pf } = channels
|
|
25
26
|
const seen = new Set()
|
|
26
27
|
const out = []
|
|
27
|
-
for (const f of [cf, pf]) {
|
|
28
|
+
for (const f of [gf, cf, pf]) {
|
|
28
29
|
if (f && f !== xf && !seen.has(f) && !isLiteralColor(f)) {
|
|
29
30
|
seen.add(f)
|
|
30
31
|
out.push(f)
|
|
@@ -52,11 +53,13 @@ export function buildBars(
|
|
|
52
53
|
innerHeight,
|
|
53
54
|
patterns,
|
|
54
55
|
place = (x, y) => ({ x, y }),
|
|
55
|
-
continuousCategory = false
|
|
56
|
+
continuousCategory = false,
|
|
57
|
+
dodge = true
|
|
56
58
|
) {
|
|
57
59
|
const { x: xf, y: yf, color: cf, pattern: pf } = channels
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
// No sub-banding for a continuous (race) axis or position='identity' (dodge=false → overlap).
|
|
62
|
+
const subFields = continuousCategory || !dodge ? [] : subBandFields(channels)
|
|
60
63
|
const getSubKey = (d) => subFields.map((f) => String(d[f])).join('::')
|
|
61
64
|
|
|
62
65
|
let positionOf
|
|
@@ -131,7 +134,7 @@ export function buildBars(
|
|
|
131
134
|
})
|
|
132
135
|
}
|
|
133
136
|
|
|
134
|
-
export function buildStackedBars(data, channels, xScale, yScale, colors, innerHeight, patterns, place = (x, y) => ({ x, y })) {
|
|
137
|
+
export function buildStackedBars(data, channels, xScale, yScale, colors, innerHeight, patterns, place = (x, y) => ({ x, y }), normalize = false) {
|
|
135
138
|
const { x: xf, y: yf, color: cf, pattern: pf } = channels
|
|
136
139
|
|
|
137
140
|
const bandScale = ensureBandX(xScale, data, xf)
|
|
@@ -159,6 +162,8 @@ export function buildStackedBars(data, channels, xScale, yScale, colors, innerHe
|
|
|
159
162
|
})
|
|
160
163
|
|
|
161
164
|
const stackGen = stack().keys(stackCategories)
|
|
165
|
+
// position='fill' → normalize each x column to [0,1] (100% stacked).
|
|
166
|
+
if (normalize) stackGen.offset(stackOffsetExpand)
|
|
162
167
|
const layers = stackGen(wide)
|
|
163
168
|
|
|
164
169
|
const bars = []
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build renderable pie/donut arcs. Slice interior + border come from `fill ?? color`
|
|
3
|
+
* through the shared palette (buildArcs already yields fill + a darker stroke). Adds the
|
|
4
|
+
* geom's fixed `alpha`.
|
|
5
|
+
* @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
|
|
6
|
+
*/
|
|
7
|
+
export function buildArcMarks({ 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
|
+
key: unknown;
|
|
20
|
+
patternId: string | null;
|
|
21
|
+
pct: number;
|
|
22
|
+
centroid: [number, number];
|
|
23
|
+
data: Record<string, unknown>;
|
|
24
|
+
}[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { buildArcs } from '../../../lib/brewing/marks/arcs.js'
|
|
2
|
+
import { resolveAlpha } from '../aesthetics.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build renderable pie/donut arcs. Slice interior + border come from `fill ?? color`
|
|
6
|
+
* through the shared palette (buildArcs already yields fill + a darker stroke). Adds the
|
|
7
|
+
* geom's fixed `alpha`.
|
|
8
|
+
* @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
|
|
9
|
+
*/
|
|
10
|
+
export function buildArcMarks({ data, plot, channels, options = {}, alpha, type = 'arc' }) {
|
|
11
|
+
if (!data?.length) return []
|
|
12
|
+
const interiorField = channels.fill ?? channels.color
|
|
13
|
+
// Guard: skip until data catches up after a fill-field change — otherwise a stale row set
|
|
14
|
+
// (missing the new field) produces all-undefined keys and duplicate-key errors.
|
|
15
|
+
if (interiorField && data[0] && !(interiorField in data[0])) return []
|
|
16
|
+
|
|
17
|
+
const arcs = buildArcs(
|
|
18
|
+
data,
|
|
19
|
+
{ color: interiorField, y: channels.y, pattern: channels.pattern },
|
|
20
|
+
plot.colors,
|
|
21
|
+
plot.innerWidth,
|
|
22
|
+
plot.innerHeight,
|
|
23
|
+
{ innerRadius: options.innerRadius ?? 0 },
|
|
24
|
+
plot.patterns
|
|
25
|
+
)
|
|
26
|
+
const a = resolveAlpha(alpha, type, plot.chartPreset)
|
|
27
|
+
return arcs.map((arc) => ({ ...arc, alpha: a }))
|
|
28
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build renderable area segments. Interior is grouped/filled by `fill ?? color`
|
|
3
|
+
* (ggplot geom_area's fill). A border stroke is drawn only when BOTH `fill` and `color`
|
|
4
|
+
* are set — so an old `color`-only area stays border-less (backward compatible); the
|
|
5
|
+
* border uses the interior group's darker stroke shade. Per-field border coloring
|
|
6
|
+
* (fill ≠ color, two legends) is the deferred dual-scale follow-up.
|
|
7
|
+
* @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
|
|
8
|
+
*/
|
|
9
|
+
export function buildAreaMarks({ data, plot, channels, options, alpha, type }: {
|
|
10
|
+
data: any[];
|
|
11
|
+
plot: any;
|
|
12
|
+
channels: any;
|
|
13
|
+
options?: any;
|
|
14
|
+
alpha?: number;
|
|
15
|
+
type?: string;
|
|
16
|
+
}): {
|
|
17
|
+
stroke: any;
|
|
18
|
+
alpha: number;
|
|
19
|
+
d: string;
|
|
20
|
+
fill: string;
|
|
21
|
+
key: unknown;
|
|
22
|
+
patternId: string | null;
|
|
23
|
+
}[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { buildAreas, buildStackedAreas } from '../areas.js'
|
|
2
|
+
import { resolveAlpha } from '../aesthetics.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build renderable area segments. Interior is grouped/filled by `fill ?? color`
|
|
6
|
+
* (ggplot geom_area's fill). A border stroke is drawn only when BOTH `fill` and `color`
|
|
7
|
+
* are set — so an old `color`-only area stays border-less (backward compatible); the
|
|
8
|
+
* border uses the interior group's darker stroke shade. Per-field border coloring
|
|
9
|
+
* (fill ≠ color, two legends) is the deferred dual-scale follow-up.
|
|
10
|
+
* @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
|
|
11
|
+
*/
|
|
12
|
+
export function buildAreaMarks({ data, plot, channels, options = {}, alpha, type = 'area' }) {
|
|
13
|
+
const { xScale, yScale, colors, patterns } = plot
|
|
14
|
+
if (!data?.length || !xScale || !yScale || !channels.x || !channels.y) return []
|
|
15
|
+
|
|
16
|
+
const interiorField = channels.fill ?? channels.color
|
|
17
|
+
const hasBorder = Boolean(channels.fill && channels.color)
|
|
18
|
+
const place = plot.place.bind(plot)
|
|
19
|
+
const areaChannels = {
|
|
20
|
+
x: channels.x,
|
|
21
|
+
y: channels.y,
|
|
22
|
+
color: interiorField,
|
|
23
|
+
pattern: channels.pattern
|
|
24
|
+
}
|
|
25
|
+
// Areas don't dodge; position selects stack | fill (100%) | identity (overlap, default).
|
|
26
|
+
const position = options.position ?? (options.stack ? 'stack' : 'identity')
|
|
27
|
+
const stacked = position === 'stack' || position === 'fill'
|
|
28
|
+
const raw = stacked
|
|
29
|
+
? buildStackedAreas(data, areaChannels, xScale, yScale, colors, options.curve, patterns, place, position === 'fill')
|
|
30
|
+
: buildAreas(data, areaChannels, xScale, yScale, colors, options.curve, patterns, place)
|
|
31
|
+
|
|
32
|
+
const a = resolveAlpha(alpha, type, plot.chartPreset)
|
|
33
|
+
return raw.map((seg) => ({
|
|
34
|
+
...seg,
|
|
35
|
+
stroke: hasBorder ? (colors.get(seg.key)?.stroke ?? 'none') : 'none',
|
|
36
|
+
alpha: a
|
|
37
|
+
}))
|
|
38
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build renderable bar marks. Interior is grouped/filled by `fill ?? color`; the `group`
|
|
3
|
+
* channel (default `fill ?? color`) drives the sub-series partitioned by `position`:
|
|
4
|
+
* - stack: cumulative segments - dodge: side-by-side sub-bands (default)
|
|
5
|
+
* - fill: 100% (normalized) stack - identity: overlapping full-width bars
|
|
6
|
+
* `options.stack: true` maps to position 'stack' (back-compat). Adds the geom's fixed `alpha`.
|
|
7
|
+
* @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
|
|
8
|
+
*/
|
|
9
|
+
export function buildBarMarks({ data, plot, channels, options, alpha, type }: {
|
|
10
|
+
data: any[];
|
|
11
|
+
plot: any;
|
|
12
|
+
channels: any;
|
|
13
|
+
options?: any;
|
|
14
|
+
alpha?: number;
|
|
15
|
+
type?: string;
|
|
16
|
+
}): any;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { buildBars, buildStackedBars } from '../bars.js'
|
|
2
|
+
import { resolveAlpha } from '../aesthetics.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build renderable bar marks. Interior is grouped/filled by `fill ?? color`; the `group`
|
|
6
|
+
* channel (default `fill ?? color`) drives the sub-series partitioned by `position`:
|
|
7
|
+
* - stack: cumulative segments - dodge: side-by-side sub-bands (default)
|
|
8
|
+
* - fill: 100% (normalized) stack - identity: overlapping full-width bars
|
|
9
|
+
* `options.stack: true` maps to position 'stack' (back-compat). Adds the geom's fixed `alpha`.
|
|
10
|
+
* @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
|
|
11
|
+
*/
|
|
12
|
+
export function buildBarMarks({ data, plot, channels, options = {}, alpha, type = 'bar' }) {
|
|
13
|
+
const { xScale, yScale, colors, patterns } = plot
|
|
14
|
+
if (!data?.length || !xScale || !yScale) return []
|
|
15
|
+
|
|
16
|
+
const interiorField = channels.fill ?? channels.color
|
|
17
|
+
const groupField = channels.group ?? channels.fill ?? channels.color
|
|
18
|
+
const barChannels = {
|
|
19
|
+
x: channels.x,
|
|
20
|
+
y: channels.y,
|
|
21
|
+
color: interiorField,
|
|
22
|
+
pattern: channels.pattern,
|
|
23
|
+
group: groupField
|
|
24
|
+
}
|
|
25
|
+
const place = plot.place.bind(plot)
|
|
26
|
+
const ih = plot.innerHeight
|
|
27
|
+
const position = options.position ?? (options.stack ? 'stack' : 'dodge')
|
|
28
|
+
|
|
29
|
+
let raw
|
|
30
|
+
if (position === 'stack') {
|
|
31
|
+
raw = buildStackedBars(data, barChannels, xScale, yScale, colors, ih, patterns, place, false)
|
|
32
|
+
} else if (position === 'fill') {
|
|
33
|
+
raw = buildStackedBars(data, barChannels, xScale, yScale, colors, ih, patterns, place, true)
|
|
34
|
+
} else if (position === 'identity') {
|
|
35
|
+
raw = buildBars(data, barChannels, xScale, yScale, colors, ih, patterns, place, plot.continuousCategory, false)
|
|
36
|
+
} else {
|
|
37
|
+
// dodge (default): side-by-side sub-bands
|
|
38
|
+
raw = buildBars(data, barChannels, xScale, yScale, colors, ih, patterns, place, plot.continuousCategory, true)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const a = resolveAlpha(alpha, type, plot.chartPreset)
|
|
42
|
+
return raw.map((bar) => ({ ...bar, alpha: a }))
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build renderable box marks. Body fill + outline (whiskers/median/caps) come from the
|
|
3
|
+
* `fill` channel (defaulting to `x`); an explicit `color` channel overrides the outline
|
|
4
|
+
* stroke (interior stays from `fill`). Adds the geom's fixed `alpha`.
|
|
5
|
+
* @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
|
|
6
|
+
*/
|
|
7
|
+
export function buildBoxMarks({ 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
|
+
}): any[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { buildBoxes } from '../../../lib/brewing/marks/boxes.js'
|
|
2
|
+
import { resolveAlpha } from '../aesthetics.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Build renderable box marks. Body fill + outline (whiskers/median/caps) come from the
|
|
6
|
+
* `fill` channel (defaulting to `x`); an explicit `color` channel overrides the outline
|
|
7
|
+
* stroke (interior stays from `fill`). Adds the geom's fixed `alpha`.
|
|
8
|
+
* @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
|
|
9
|
+
*/
|
|
10
|
+
export function buildBoxMarks({ data, plot, channels, options = {}, alpha, type = 'box' }) {
|
|
11
|
+
const { xScale, yScale, colors } = plot
|
|
12
|
+
if (!data?.length || !xScale || !yScale) return []
|
|
13
|
+
|
|
14
|
+
const fillField = channels.fill ?? channels.x
|
|
15
|
+
const boxes = buildBoxes(
|
|
16
|
+
data,
|
|
17
|
+
{ x: channels.x, fill: fillField, pattern: channels.pattern },
|
|
18
|
+
xScale,
|
|
19
|
+
yScale,
|
|
20
|
+
colors,
|
|
21
|
+
{ side: options.side, width: options.width, patterns: plot.patterns }
|
|
22
|
+
)
|
|
23
|
+
const a = resolveAlpha(alpha, type, plot.chartPreset)
|
|
24
|
+
const colorField = channels.color
|
|
25
|
+
return boxes.map((box) => ({
|
|
26
|
+
...box,
|
|
27
|
+
stroke: colorField ? (colors.get(box.data[colorField])?.stroke ?? box.stroke) : box.stroke,
|
|
28
|
+
alpha: a
|
|
29
|
+
}))
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build renderable candlesticks. Body + wick color is semantic (up/down from options), not
|
|
3
|
+
* the shared palette. Adds fixed `alpha`. Places (band, value) corners through place() so the
|
|
4
|
+
* candle transposes under orientation flip.
|
|
5
|
+
* @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
|
|
6
|
+
*/
|
|
7
|
+
export function buildCandleMarks({ 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
|
+
key: string;
|
|
16
|
+
bodyX: number;
|
|
17
|
+
bodyY: number;
|
|
18
|
+
bodyWidth: number;
|
|
19
|
+
bodyHeight: number;
|
|
20
|
+
fill: any;
|
|
21
|
+
alpha: number;
|
|
22
|
+
wickX1: any;
|
|
23
|
+
wickY1: any;
|
|
24
|
+
wickX2: any;
|
|
25
|
+
wickY2: any;
|
|
26
|
+
data: any;
|
|
27
|
+
}[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { resolveAlpha } from '../aesthetics.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build renderable candlesticks. Body + wick color is semantic (up/down from options), not
|
|
5
|
+
* the shared palette. Adds fixed `alpha`. Places (band, value) corners through place() so the
|
|
6
|
+
* candle transposes under orientation flip.
|
|
7
|
+
* @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
|
|
8
|
+
*/
|
|
9
|
+
export function buildCandleMarks({ data, plot, channels, options = {}, alpha, type = 'candlestick' }) {
|
|
10
|
+
const { xScale, yScale } = plot
|
|
11
|
+
if (!data?.length || !xScale || !yScale) return []
|
|
12
|
+
|
|
13
|
+
const open = options.open ?? 'open'
|
|
14
|
+
const high = options.high ?? 'high'
|
|
15
|
+
const low = options.low ?? 'low'
|
|
16
|
+
const close = options.close ?? 'close'
|
|
17
|
+
const upColor = options.upColor ?? '#22c55e'
|
|
18
|
+
const downColor = options.downColor ?? '#ef4444'
|
|
19
|
+
const a = resolveAlpha(alpha, type, plot.chartPreset)
|
|
20
|
+
|
|
21
|
+
const bw = typeof xScale.bandwidth === 'function' ? xScale.bandwidth() : 10
|
|
22
|
+
const bodyWidth = bw * 0.6
|
|
23
|
+
const bodyOffset = (bw - bodyWidth) / 2
|
|
24
|
+
|
|
25
|
+
return data.map((d, i) => {
|
|
26
|
+
const xPos = (xScale(d[channels.x ?? '']) ?? 0) + bodyOffset
|
|
27
|
+
const openVal = Number(d[open])
|
|
28
|
+
const closeVal = Number(d[close])
|
|
29
|
+
const isUp = closeVal >= openVal
|
|
30
|
+
const bodyTop = yScale(isUp ? closeVal : openVal)
|
|
31
|
+
const bodyBottom = yScale(isUp ? openVal : closeVal)
|
|
32
|
+
|
|
33
|
+
const b1 = plot.place(xPos, bodyTop)
|
|
34
|
+
const b2 = plot.place(xPos + bodyWidth, bodyBottom)
|
|
35
|
+
const wa = plot.place(xPos + bodyWidth / 2, yScale(Number(d[high])))
|
|
36
|
+
const wb = plot.place(xPos + bodyWidth / 2, yScale(Number(d[low])))
|
|
37
|
+
return {
|
|
38
|
+
key: `${d[channels.x ?? '']}-${i}`,
|
|
39
|
+
bodyX: Math.min(b1.x, b2.x),
|
|
40
|
+
bodyY: Math.min(b1.y, b2.y),
|
|
41
|
+
bodyWidth: Math.abs(b2.x - b1.x),
|
|
42
|
+
bodyHeight: Math.max(1, Math.abs(b2.y - b1.y)),
|
|
43
|
+
fill: isUp ? upColor : downColor,
|
|
44
|
+
alpha: a,
|
|
45
|
+
wickX1: wa.x,
|
|
46
|
+
wickY1: wa.y,
|
|
47
|
+
wickX2: wb.x,
|
|
48
|
+
wickY2: wb.y,
|
|
49
|
+
data: d
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build renderable heatmap cells. Cell fill is the continuous color scale (numeric color
|
|
3
|
+
* field) or the categorical palette entry, from `fill ?? color`. Adds fixed `alpha`.
|
|
4
|
+
* @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
|
|
5
|
+
*/
|
|
6
|
+
export function buildHeatmapMarks({ data, plot, channels, 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: any;
|
|
16
|
+
y: any;
|
|
17
|
+
width: any;
|
|
18
|
+
height: any;
|
|
19
|
+
fill: string;
|
|
20
|
+
alpha: number;
|
|
21
|
+
value: any;
|
|
22
|
+
display: string;
|
|
23
|
+
data: any;
|
|
24
|
+
}[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { resolveAlpha } from '../aesthetics.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build renderable heatmap cells. Cell fill is the continuous color scale (numeric color
|
|
5
|
+
* field) or the categorical palette entry, from `fill ?? color`. Adds fixed `alpha`.
|
|
6
|
+
* @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
|
|
7
|
+
*/
|
|
8
|
+
export function buildHeatmapMarks({ data, plot, channels, alpha, type = 'heatmap' }) {
|
|
9
|
+
const { xScale, yScale, colors } = plot
|
|
10
|
+
if (!data?.length || !xScale || !yScale) return []
|
|
11
|
+
|
|
12
|
+
const colorField = channels.fill ?? channels.color
|
|
13
|
+
const continuous = plot.continuousColorScale
|
|
14
|
+
const bwX = typeof xScale.bandwidth === 'function' ? xScale.bandwidth() : 0
|
|
15
|
+
const bwY = typeof yScale.bandwidth === 'function' ? yScale.bandwidth() : 0
|
|
16
|
+
const a = resolveAlpha(alpha, type, plot.chartPreset)
|
|
17
|
+
|
|
18
|
+
return data.map((d, i) => {
|
|
19
|
+
const xVal = d[channels.x ?? '']
|
|
20
|
+
const yVal = d[channels.y ?? '']
|
|
21
|
+
const colorVal = colorField ? d[colorField] : null
|
|
22
|
+
let fill = '#ccc'
|
|
23
|
+
if (continuous) fill = continuous.scale(Number(colorVal))
|
|
24
|
+
else {
|
|
25
|
+
const entry = colors?.get(colorVal)
|
|
26
|
+
if (entry) fill = entry.fill
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
key: `${xVal}-${yVal}-${i}`,
|
|
30
|
+
x: xScale(xVal) ?? 0,
|
|
31
|
+
y: yScale(yVal) ?? 0,
|
|
32
|
+
width: bwX,
|
|
33
|
+
height: bwY,
|
|
34
|
+
fill,
|
|
35
|
+
alpha: a,
|
|
36
|
+
value: colorVal,
|
|
37
|
+
display: String(colorVal ?? ''),
|
|
38
|
+
data: d
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** SVG path for a pointy-top hexagon of radius r, centered at the origin. */
|
|
2
|
+
export function hexPath(r: any): string;
|
|
3
|
+
/**
|
|
4
|
+
* Build renderable hex bins. Fill is the continuous color scale (by count) or a density-ramp
|
|
5
|
+
* rgba fallback. `alpha` is a flat multiplier on top (default 1 → unchanged).
|
|
6
|
+
* @param {{ data: any[], plot: any, channels: any, options?: any, alpha?: number, type?: string }} ctx
|
|
7
|
+
*/
|
|
8
|
+
export function buildHexbinMarks({ data, plot, channels, options, alpha, type }: {
|
|
9
|
+
data: any[];
|
|
10
|
+
plot: any;
|
|
11
|
+
channels: any;
|
|
12
|
+
options?: any;
|
|
13
|
+
alpha?: number;
|
|
14
|
+
type?: string;
|
|
15
|
+
}): {
|
|
16
|
+
key: string;
|
|
17
|
+
cx: any;
|
|
18
|
+
cy: any;
|
|
19
|
+
count: any;
|
|
20
|
+
fill: any;
|
|
21
|
+
alpha: number;
|
|
22
|
+
value: any;
|
|
23
|
+
display: string;
|
|
24
|
+
data: {
|
|
25
|
+
count: any;
|
|
26
|
+
x: any;
|
|
27
|
+
y: any;
|
|
28
|
+
};
|
|
29
|
+
}[];
|
|
@@ -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
|
+
}[];
|