@rokkit/chart 1.3.7 → 1.3.8
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 +1 -1
- package/dist/FacetPlot.svelte +1 -1
- package/dist/Plot/Axis.svelte +19 -10
- package/dist/Plot/Root.svelte +64 -46
- package/dist/Plot/Root.svelte.d.ts +27 -2
- package/dist/Plot/Timeline.svelte +39 -13
- package/dist/Plot/index.d.ts +4 -4
- package/dist/Plot/index.js +4 -4
- package/dist/Plot.svelte +95 -178
- package/dist/Plot.svelte.d.ts +4 -0
- package/dist/PlotState.svelte.d.ts +16 -0
- package/dist/PlotState.svelte.js +118 -16
- package/dist/PlotSurface.svelte +162 -0
- package/dist/PlotSurface.svelte.d.ts +25 -0
- package/dist/charts/AreaChart.svelte +4 -1
- package/dist/charts/AreaChart.svelte.d.ts +1 -0
- package/dist/charts/BarChart.svelte +32 -3
- package/dist/charts/BarChart.svelte.d.ts +10 -1
- package/dist/charts/BoxPlot.svelte +4 -2
- package/dist/charts/BoxPlot.svelte.d.ts +1 -0
- package/dist/charts/BubbleChart.svelte +34 -3
- package/dist/charts/BubbleChart.svelte.d.ts +11 -1
- package/dist/charts/LineChart.svelte +1 -1
- package/dist/charts/PieChart.svelte +1 -1
- package/dist/charts/ScatterPlot.svelte +32 -3
- package/dist/charts/ScatterPlot.svelte.d.ts +10 -1
- package/dist/charts/ViolinPlot.svelte +4 -2
- package/dist/charts/ViolinPlot.svelte.d.ts +1 -0
- package/dist/examples/BarChartExample.svelte +1 -1
- package/dist/geoms/Arc.svelte +3 -2
- package/dist/geoms/Area.svelte +2 -2
- package/dist/geoms/Bar.svelte +17 -6
- package/dist/geoms/Box.svelte +131 -76
- package/dist/geoms/Box.svelte.d.ts +7 -0
- package/dist/geoms/Candlestick.svelte +17 -11
- package/dist/geoms/Jitter.svelte +82 -0
- package/dist/geoms/Jitter.svelte.d.ts +15 -0
- package/dist/geoms/Line.svelte +1 -1
- package/dist/geoms/Point.svelte +7 -1
- package/dist/geoms/Rule.svelte +113 -0
- package/dist/geoms/Rule.svelte.d.ts +16 -0
- package/dist/geoms/Violin.svelte +30 -6
- package/dist/geoms/Violin.svelte.d.ts +5 -0
- package/dist/geoms/Waterfall.svelte +16 -9
- package/dist/geoms/lib/areas.d.ts +6 -2
- package/dist/geoms/lib/areas.js +17 -11
- package/dist/geoms/lib/bars.d.ts +18 -3
- package/dist/geoms/lib/bars.js +70 -96
- package/dist/index.d.ts +24 -4
- package/dist/index.js +25 -5
- package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +1 -1
- 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/package.json +1 -1
- 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/geoms/Violin.svelte
CHANGED
|
@@ -8,21 +8,30 @@
|
|
|
8
8
|
y?: string
|
|
9
9
|
fill?: string
|
|
10
10
|
stat?: string
|
|
11
|
+
/** Half-violin: 'left'/'right' draw one side (flat edge at the band centre) for
|
|
12
|
+
* raincloud/split plots; 'center' (default) is the full symmetric silhouette. */
|
|
13
|
+
side?: 'left' | 'right' | 'center'
|
|
14
|
+
/** Data field driving a texture fill (from the shared pattern set) instead of a solid fill. */
|
|
15
|
+
pattern?: string
|
|
11
16
|
options?: { opacity?: number }
|
|
12
17
|
}
|
|
13
18
|
|
|
14
|
-
let { x, y, fill, stat = 'boxplot', options = {} }: Props = $props()
|
|
19
|
+
let { x, y, fill, stat = 'boxplot', side = 'center', pattern, options = {} }: Props = $props()
|
|
15
20
|
|
|
16
21
|
const plotState = getContext<PlotState>('plot-state')
|
|
17
22
|
let id = $state<string | null>(null)
|
|
18
23
|
|
|
19
|
-
//
|
|
20
|
-
|
|
24
|
+
// Fall back to the root Plot channels when the geom omits x/y (composes under
|
|
25
|
+
// <Plot.Root x y> without silently NaN-ing).
|
|
26
|
+
const xf = $derived(x ?? plotState.channels?.x)
|
|
27
|
+
const yf = $derived(y ?? plotState.channels?.y)
|
|
28
|
+
// fill ?? effective-x drives the colors map for both violin interior and outline
|
|
29
|
+
const fillChannel = $derived(fill ?? xf)
|
|
21
30
|
|
|
22
31
|
onMount(() => {
|
|
23
32
|
id = plotState.registerGeom({
|
|
24
33
|
type: 'violin',
|
|
25
|
-
channels: { x, y, color: fillChannel },
|
|
34
|
+
channels: { x: xf, y: yf, color: fillChannel, pattern },
|
|
26
35
|
stat,
|
|
27
36
|
options
|
|
28
37
|
})
|
|
@@ -32,17 +41,28 @@
|
|
|
32
41
|
})
|
|
33
42
|
|
|
34
43
|
$effect(() => {
|
|
35
|
-
if (id)
|
|
44
|
+
if (id)
|
|
45
|
+
plotState.updateGeom(id, { channels: { x: xf, y: yf, color: fillChannel, pattern }, stat })
|
|
36
46
|
})
|
|
37
47
|
|
|
38
48
|
const data = $derived(id ? plotState.geomData(id) : [])
|
|
39
49
|
const xScale = $derived(plotState.xScale)
|
|
40
50
|
const yScale = $derived(plotState.yScale)
|
|
41
51
|
const colors = $derived(plotState.colors)
|
|
52
|
+
const patterns = $derived(plotState.patterns)
|
|
42
53
|
|
|
43
54
|
const violins = $derived.by(() => {
|
|
44
55
|
if (!data?.length || !xScale || !yScale) return []
|
|
45
|
-
return buildViolins(
|
|
56
|
+
return buildViolins(
|
|
57
|
+
data,
|
|
58
|
+
{ x: xf, fill: fillChannel, pattern },
|
|
59
|
+
xScale,
|
|
60
|
+
yScale,
|
|
61
|
+
colors,
|
|
62
|
+
plotState.place.bind(plotState),
|
|
63
|
+
side,
|
|
64
|
+
patterns
|
|
65
|
+
)
|
|
46
66
|
})
|
|
47
67
|
</script>
|
|
48
68
|
|
|
@@ -60,6 +80,10 @@
|
|
|
60
80
|
onmouseenter={() => plotState.setHovered(v.data)}
|
|
61
81
|
onmouseleave={() => plotState.clearHovered()}
|
|
62
82
|
/>
|
|
83
|
+
{#if v.patternId}
|
|
84
|
+
<!-- Texture fill overlay (matches the violin silhouette) -->
|
|
85
|
+
<path d={v.d} fill="url(#{v.patternId})" pointer-events="none" />
|
|
86
|
+
{/if}
|
|
63
87
|
{/each}
|
|
64
88
|
</g>
|
|
65
89
|
{/if}
|
|
@@ -3,6 +3,11 @@ type Props = {
|
|
|
3
3
|
y?: string;
|
|
4
4
|
fill?: string;
|
|
5
5
|
stat?: string;
|
|
6
|
+
/** Half-violin: 'left'/'right' draw one side (flat edge at the band centre) for
|
|
7
|
+
* raincloud/split plots; 'center' (default) is the full symmetric silhouette. */
|
|
8
|
+
side?: 'left' | 'right' | 'center';
|
|
9
|
+
/** Data field driving a texture fill (from the shared pattern set) instead of a solid fill. */
|
|
10
|
+
pattern?: string;
|
|
6
11
|
options?: {
|
|
7
12
|
opacity?: number;
|
|
8
13
|
};
|
|
@@ -87,14 +87,19 @@
|
|
|
87
87
|
fill = yVal >= 0 ? positiveColor : negativeColor
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// Place two (band, value) corners so the bar transposes under orientation flip.
|
|
91
|
+
const c1 = plotState.place(xPos, barTop)
|
|
92
|
+
const c2 = plotState.place(xPos + bw, barBottom)
|
|
90
93
|
return {
|
|
91
94
|
key: `${xVal}-${i}`,
|
|
92
|
-
x:
|
|
93
|
-
y:
|
|
94
|
-
width:
|
|
95
|
-
height: Math.max(1,
|
|
95
|
+
x: Math.min(c1.x, c2.x),
|
|
96
|
+
y: Math.min(c1.y, c2.y),
|
|
97
|
+
width: Math.abs(c2.x - c1.x),
|
|
98
|
+
height: Math.max(1, Math.abs(c2.y - c1.y)),
|
|
96
99
|
fill,
|
|
97
|
-
|
|
100
|
+
bandStart: xPos,
|
|
101
|
+
bandEnd: xPos + bw,
|
|
102
|
+
cumY: yScale(cumulative) ?? 0,
|
|
98
103
|
data: d
|
|
99
104
|
}
|
|
100
105
|
})
|
|
@@ -119,11 +124,13 @@
|
|
|
119
124
|
</rect>
|
|
120
125
|
<!-- Connector line to next bar -->
|
|
121
126
|
{#if i < bars.length - 1}
|
|
127
|
+
{@const cs = plotState.place(bar.bandEnd, bar.cumY)}
|
|
128
|
+
{@const ce = plotState.place(bars[i + 1].bandStart, bar.cumY)}
|
|
122
129
|
<line
|
|
123
|
-
x1={
|
|
124
|
-
y1={
|
|
125
|
-
x2={
|
|
126
|
-
y2={
|
|
130
|
+
x1={cs.x}
|
|
131
|
+
y1={cs.y}
|
|
132
|
+
x2={ce.x}
|
|
133
|
+
y2={ce.y}
|
|
127
134
|
stroke="currentColor"
|
|
128
135
|
stroke-width={connectorWidth}
|
|
129
136
|
stroke-dasharray="3 2"
|
|
@@ -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
|
+
}): {
|
|
49
53
|
d: string;
|
|
50
54
|
fill: string;
|
|
51
55
|
stroke: string;
|
package/dist/geoms/lib/areas.js
CHANGED
|
@@ -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 })) {
|
|
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,11 +119,13 @@ 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
|
|
@@ -144,7 +150,7 @@ export function buildStackedAreas(data, channels, xScale, yScale, colors, curve,
|
|
|
144
150
|
? toPatternId(String(patternKey))
|
|
145
151
|
: null
|
|
146
152
|
return {
|
|
147
|
-
d: makeGen()(layer) ?? '',
|
|
153
|
+
d: makeGen()(layer.map(toEdge)) ?? '',
|
|
148
154
|
fill: entry.fill,
|
|
149
155
|
stroke: 'none',
|
|
150
156
|
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): 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
|
+
}): any;
|
package/dist/geoms/lib/bars.js
CHANGED
|
@@ -33,27 +33,63 @@ function subBandFields(channels) {
|
|
|
33
33
|
return out
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* The one bar builder. Expresses each bar in abstract (category, value) space and maps it to
|
|
38
|
+
* screen via `place()`, so it renders vertical or horizontal (flip) with the same code.
|
|
39
|
+
*
|
|
40
|
+
* Category (x) positioning has two modes:
|
|
41
|
+
* - band (default): a scaleBand slots each category; sub-bands split grouped series.
|
|
42
|
+
* - continuous (`continuousCategory`): a LINEAR position scale (e.g. a bar-chart race's tweened
|
|
43
|
+
* `_rank`). Each bar centres on `xScale(pos)` with a fixed thickness from the scale step, so a
|
|
44
|
+
* fractional position tweens smoothly (a band scale can only jump between slots). No sub-banding.
|
|
45
|
+
*/
|
|
46
|
+
export function buildBars(
|
|
47
|
+
data,
|
|
48
|
+
channels,
|
|
49
|
+
xScale,
|
|
50
|
+
yScale,
|
|
51
|
+
colors,
|
|
52
|
+
innerHeight,
|
|
53
|
+
patterns,
|
|
54
|
+
place = (x, y) => ({ x, y }),
|
|
55
|
+
continuousCategory = false
|
|
56
|
+
) {
|
|
37
57
|
const { x: xf, y: yf, color: cf, pattern: pf } = channels
|
|
38
58
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
// Sub-banding: only fields that differ from x drive grouping within a band
|
|
42
|
-
const subFields = subBandFields(channels)
|
|
59
|
+
const subFields = continuousCategory ? [] : subBandFields(channels)
|
|
43
60
|
const getSubKey = (d) => subFields.map((f) => String(d[f])).join('::')
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
|
|
62
|
+
let positionOf
|
|
63
|
+
let thicknessOf
|
|
64
|
+
if (continuousCategory) {
|
|
65
|
+
const [r0, r1] = xScale.range()
|
|
66
|
+
const [d0, d1] = xScale.domain()
|
|
67
|
+
const step = Math.max(Math.abs(r0 - r1) / Math.max(d1 - d0, 1), 1)
|
|
68
|
+
const thickness = step * 0.9
|
|
69
|
+
thicknessOf = () => thickness
|
|
70
|
+
positionOf = (d) => (xScale(Number(d[xf])) ?? 0) - thickness / 2
|
|
71
|
+
} else {
|
|
72
|
+
const bandScale = ensureBandX(xScale, data, xf)
|
|
73
|
+
const subDomain = subFields.length > 0 ? [...new Set(data.map(getSubKey))] : []
|
|
74
|
+
const subScale =
|
|
75
|
+
subDomain.length > 1
|
|
76
|
+
? scaleBand().domain(subDomain).range([0, bandScale.bandwidth()]).padding(0.05)
|
|
77
|
+
: null
|
|
78
|
+
thicknessOf = () => (subScale ? subScale.bandwidth() : bandScale.bandwidth())
|
|
79
|
+
positionOf = (d) => {
|
|
80
|
+
const bandX = bandScale(d[xf]) ?? 0
|
|
81
|
+
const subX = subScale ? (subScale(getSubKey(d)) ?? 0) : 0
|
|
82
|
+
return bandX + subX
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Value baseline: at value=0 when the domain spans zero (supports negative bars), else the
|
|
87
|
+
// value-axis origin (yScale.range()[0] — innerHeight when vertical, 0 when flipped).
|
|
52
88
|
const yDomain = typeof yScale.bandwidth !== 'function' ? yScale.domain?.() : null
|
|
53
89
|
const baseline =
|
|
54
90
|
yDomain && yDomain[0] <= 0 && yDomain[yDomain.length - 1] >= 0
|
|
55
|
-
? (yScale(0) ??
|
|
56
|
-
:
|
|
91
|
+
? (yScale(0) ?? yScale.range()[0])
|
|
92
|
+
: yScale.range()[0]
|
|
57
93
|
|
|
58
94
|
return data.map((d, i) => {
|
|
59
95
|
const xVal = d[xf]
|
|
@@ -68,19 +104,26 @@ export function buildGroupedBars(data, channels, xScale, yScale, colors, innerHe
|
|
|
68
104
|
? toPatternId(String(patternKey))
|
|
69
105
|
: null
|
|
70
106
|
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
const
|
|
107
|
+
const barBand = positionOf(d)
|
|
108
|
+
const barThickness = thicknessOf(d)
|
|
109
|
+
const barValue = yScale(d[yf]) ?? baseline
|
|
110
|
+
// Two opposite corners in (band, value) space, placed to screen — transposes under flip.
|
|
111
|
+
const c1 = place(barBand, baseline)
|
|
112
|
+
const c2 = place(barBand + barThickness, barValue)
|
|
113
|
+
|
|
114
|
+
// Continuous mode keys by _entity so Svelte reuses each bar as its position tweens (a
|
|
115
|
+
// race's rows reorder every frame); band mode keys by category + sub-band + index.
|
|
116
|
+
const key = continuousCategory
|
|
117
|
+
? `${String(d._entity ?? xVal)}::${String(colorKey ?? '')}`
|
|
118
|
+
: `${String(xVal)}::${subKey}::${i}`
|
|
76
119
|
|
|
77
120
|
return {
|
|
78
121
|
data: d,
|
|
79
|
-
key
|
|
80
|
-
x:
|
|
81
|
-
y: Math.min(
|
|
82
|
-
width:
|
|
83
|
-
height: Math.abs(
|
|
122
|
+
key,
|
|
123
|
+
x: Math.min(c1.x, c2.x),
|
|
124
|
+
y: Math.min(c1.y, c2.y),
|
|
125
|
+
width: Math.abs(c2.x - c1.x),
|
|
126
|
+
height: Math.abs(c2.y - c1.y),
|
|
84
127
|
fill: colorEntry.fill,
|
|
85
128
|
stroke: colorEntry.stroke,
|
|
86
129
|
patternId
|
|
@@ -88,7 +131,7 @@ export function buildGroupedBars(data, channels, xScale, yScale, colors, innerHe
|
|
|
88
131
|
})
|
|
89
132
|
}
|
|
90
133
|
|
|
91
|
-
export function buildStackedBars(data, channels, xScale, yScale, colors, innerHeight, patterns) {
|
|
134
|
+
export function buildStackedBars(data, channels, xScale, yScale, colors, innerHeight, patterns, place = (x, y) => ({ x, y })) {
|
|
92
135
|
const { x: xf, y: yf, color: cf, pattern: pf } = channels
|
|
93
136
|
|
|
94
137
|
const bandScale = ensureBandX(xScale, data, xf)
|
|
@@ -96,7 +139,7 @@ export function buildStackedBars(data, channels, xScale, yScale, colors, innerHe
|
|
|
96
139
|
// Stack dimension: first non-x grouping field (prefer pattern, then color)
|
|
97
140
|
const subFields = subBandFields(channels)
|
|
98
141
|
if (subFields.length === 0) {
|
|
99
|
-
return
|
|
142
|
+
return buildBars(data, channels, xScale, yScale, colors, innerHeight, patterns, place)
|
|
100
143
|
}
|
|
101
144
|
const stackField = subFields[0]
|
|
102
145
|
|
|
@@ -152,72 +195,3 @@ export function buildStackedBars(data, channels, xScale, yScale, colors, innerHe
|
|
|
152
195
|
}
|
|
153
196
|
return bars
|
|
154
197
|
}
|
|
155
|
-
|
|
156
|
-
export function buildHorizontalBars(data, channels, xScale, yScale, colors, _innerHeight) {
|
|
157
|
-
const { x: xf, y: yf, color: cf } = channels
|
|
158
|
-
const isBand = typeof yScale?.bandwidth === 'function'
|
|
159
|
-
|
|
160
|
-
if (!isBand) {
|
|
161
|
-
// Linear y-scale: rank-based bar chart race.
|
|
162
|
-
// Each row has a numeric _rank that tweens smoothly between frames.
|
|
163
|
-
// No sub-grouping — each rank position holds exactly one entity.
|
|
164
|
-
const [r0, r1] = yScale.range() // [innerHeight, 0]
|
|
165
|
-
const [d0, d1] = yScale.domain() // [0, N-1]
|
|
166
|
-
const step = Math.max(Math.abs(r0 - r1) / Math.max(d1 - d0, 1), 1)
|
|
167
|
-
const barH = step * 0.9
|
|
168
|
-
|
|
169
|
-
return data.map((d) => {
|
|
170
|
-
const rankVal = Number(d[yf])
|
|
171
|
-
const colorKey = cf ? d[cf] : null
|
|
172
|
-
const colorEntry =
|
|
173
|
-
colors?.get(colorKey) ??
|
|
174
|
-
colors?.values().next().value ?? { fill: '#888', stroke: '#888' }
|
|
175
|
-
|
|
176
|
-
const centerY = yScale(rankVal) ?? 0
|
|
177
|
-
return {
|
|
178
|
-
data: d,
|
|
179
|
-
// Key by entity name (_entity) so Svelte reuses elements as rank tweens
|
|
180
|
-
key: `${String(d._entity ?? d[yf])}::${String(colorKey ?? '')}`,
|
|
181
|
-
x: 0,
|
|
182
|
-
y: centerY - barH / 2,
|
|
183
|
-
width: xScale(d[xf]) ?? 0,
|
|
184
|
-
height: barH,
|
|
185
|
-
fill: colorEntry.fill,
|
|
186
|
-
stroke: colorEntry.stroke,
|
|
187
|
-
patternId: null
|
|
188
|
-
}
|
|
189
|
-
})
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Band scale: standard grouped horizontal bars.
|
|
193
|
-
// Only create sub-bands when multiple entities share the same y-band (true grouping).
|
|
194
|
-
const yVals = new Set(data.map((d) => d[yf]))
|
|
195
|
-
const colorKeys = cf && !isLiteralColor(cf) ? [...new Set(data.map((d) => d[cf]))] : []
|
|
196
|
-
const hasSubBands = colorKeys.length > 1 && data.length > yVals.size
|
|
197
|
-
const subScale = hasSubBands
|
|
198
|
-
? scaleBand().domain(colorKeys).range([0, yScale.bandwidth()]).padding(0.05)
|
|
199
|
-
: null
|
|
200
|
-
|
|
201
|
-
return data.map((d) => {
|
|
202
|
-
const yVal = d[yf]
|
|
203
|
-
const colorKey = cf ? d[cf] : null
|
|
204
|
-
const colorEntry =
|
|
205
|
-
colors?.get(colorKey) ??
|
|
206
|
-
colors?.values().next().value ?? { fill: '#888', stroke: '#888' }
|
|
207
|
-
|
|
208
|
-
const bandY = yScale(yVal) ?? 0
|
|
209
|
-
const subY = subScale ? (subScale(colorKey) ?? 0) : 0
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
data: d,
|
|
213
|
-
key: `${String(yVal)}::${String(colorKey ?? '')}`,
|
|
214
|
-
x: 0,
|
|
215
|
-
y: bandY + subY,
|
|
216
|
-
width: xScale(d[xf]) ?? 0,
|
|
217
|
-
height: subScale ? subScale.bandwidth() : yScale.bandwidth(),
|
|
218
|
-
fill: colorEntry.fill,
|
|
219
|
-
stroke: colorEntry.stroke,
|
|
220
|
-
patternId: null
|
|
221
|
-
}
|
|
222
|
-
})
|
|
223
|
-
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,15 @@ export namespace Plot {
|
|
|
10
10
|
export { Arc };
|
|
11
11
|
export { Highlight };
|
|
12
12
|
export { Trend };
|
|
13
|
+
export { Box };
|
|
14
|
+
export { Violin };
|
|
15
|
+
export { Jitter };
|
|
16
|
+
export { Candlestick };
|
|
17
|
+
export { Heatmap };
|
|
18
|
+
export { Hexbin };
|
|
19
|
+
export { Ribbon };
|
|
20
|
+
export { Waterfall };
|
|
21
|
+
export { Rule };
|
|
13
22
|
}
|
|
14
23
|
export namespace PlotLayers { }
|
|
15
24
|
export { default as PlotChart } from "./Plot.svelte";
|
|
@@ -32,6 +41,8 @@ export { default as GeomHexbin } from "./geoms/Hexbin.svelte";
|
|
|
32
41
|
export { default as GeomRibbon } from "./geoms/Ribbon.svelte";
|
|
33
42
|
export { default as GeomHighlight } from "./geoms/Highlight.svelte";
|
|
34
43
|
export { default as GeomTrend } from "./geoms/Trend.svelte";
|
|
44
|
+
export { default as GeomJitter } from "./geoms/Jitter.svelte";
|
|
45
|
+
export { default as GeomRule } from "./geoms/Rule.svelte";
|
|
35
46
|
export { default as Chart } from "./Chart.svelte";
|
|
36
47
|
export { default as Sparkline } from "./Sparkline.svelte";
|
|
37
48
|
export { default as BarChart } from "./charts/BarChart.svelte";
|
|
@@ -57,14 +68,23 @@ export { default as FilterSlider } from "./crossfilter/FilterSlider.svelte";
|
|
|
57
68
|
export { default as FilterHistogram } from "./crossfilter/FilterHistogram.svelte";
|
|
58
69
|
import Root from './Plot/Root.svelte';
|
|
59
70
|
import Axis from './Plot/Axis.svelte';
|
|
60
|
-
import Bar from './
|
|
71
|
+
import Bar from './geoms/Bar.svelte';
|
|
61
72
|
import Grid from './Plot/Grid.svelte';
|
|
62
73
|
import Legend from './Plot/Legend.svelte';
|
|
63
|
-
import Line from './
|
|
64
|
-
import Area from './
|
|
65
|
-
import Point from './
|
|
74
|
+
import Line from './geoms/Line.svelte';
|
|
75
|
+
import Area from './geoms/Area.svelte';
|
|
76
|
+
import Point from './geoms/Point.svelte';
|
|
66
77
|
import Arc from './Plot/Arc.svelte';
|
|
67
78
|
import Highlight from './geoms/Highlight.svelte';
|
|
68
79
|
import Trend from './geoms/Trend.svelte';
|
|
80
|
+
import Box from './geoms/Box.svelte';
|
|
81
|
+
import Violin from './geoms/Violin.svelte';
|
|
82
|
+
import Jitter from './geoms/Jitter.svelte';
|
|
83
|
+
import Candlestick from './geoms/Candlestick.svelte';
|
|
84
|
+
import Heatmap from './geoms/Heatmap.svelte';
|
|
85
|
+
import Hexbin from './geoms/Hexbin.svelte';
|
|
86
|
+
import Ribbon from './geoms/Ribbon.svelte';
|
|
87
|
+
import Waterfall from './geoms/Waterfall.svelte';
|
|
88
|
+
import Rule from './geoms/Rule.svelte';
|
|
69
89
|
export { createChartPreset, defaultPreset } from "./lib/preset.js";
|
|
70
90
|
export { buildSequentialScale, buildDivergingScale } from "./lib/brewing/colors.js";
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import Root from './Plot/Root.svelte'
|
|
2
2
|
import Axis from './Plot/Axis.svelte'
|
|
3
|
-
import Bar from './
|
|
3
|
+
import Bar from './geoms/Bar.svelte'
|
|
4
4
|
import Grid from './Plot/Grid.svelte'
|
|
5
5
|
import Legend from './Plot/Legend.svelte'
|
|
6
|
-
import Line from './
|
|
7
|
-
import Area from './
|
|
8
|
-
import Point from './
|
|
6
|
+
import Line from './geoms/Line.svelte'
|
|
7
|
+
import Area from './geoms/Area.svelte'
|
|
8
|
+
import Point from './geoms/Point.svelte'
|
|
9
9
|
import Arc from './Plot/Arc.svelte'
|
|
10
10
|
import Highlight from './geoms/Highlight.svelte'
|
|
11
11
|
import Trend from './geoms/Trend.svelte'
|
|
12
|
+
import Box from './geoms/Box.svelte'
|
|
13
|
+
import Violin from './geoms/Violin.svelte'
|
|
14
|
+
import Jitter from './geoms/Jitter.svelte'
|
|
15
|
+
import Candlestick from './geoms/Candlestick.svelte'
|
|
16
|
+
import Heatmap from './geoms/Heatmap.svelte'
|
|
17
|
+
import Hexbin from './geoms/Hexbin.svelte'
|
|
18
|
+
import Ribbon from './geoms/Ribbon.svelte'
|
|
19
|
+
import Waterfall from './geoms/Waterfall.svelte'
|
|
20
|
+
import Rule from './geoms/Rule.svelte'
|
|
12
21
|
|
|
13
22
|
// Composable Plot primitives — use as <Plot.Root>, <Plot.Axis>, <Plot.Bar>, etc.
|
|
14
23
|
export const Plot = {
|
|
@@ -22,7 +31,16 @@ export const Plot = {
|
|
|
22
31
|
Point,
|
|
23
32
|
Arc,
|
|
24
33
|
Highlight,
|
|
25
|
-
Trend
|
|
34
|
+
Trend,
|
|
35
|
+
Box,
|
|
36
|
+
Violin,
|
|
37
|
+
Jitter,
|
|
38
|
+
Candlestick,
|
|
39
|
+
Heatmap,
|
|
40
|
+
Hexbin,
|
|
41
|
+
Ribbon,
|
|
42
|
+
Waterfall,
|
|
43
|
+
Rule
|
|
26
44
|
}
|
|
27
45
|
|
|
28
46
|
/** @deprecated Use Plot instead */
|
|
@@ -54,6 +72,8 @@ export { default as GeomHexbin } from './geoms/Hexbin.svelte'
|
|
|
54
72
|
export { default as GeomRibbon } from './geoms/Ribbon.svelte'
|
|
55
73
|
export { default as GeomHighlight } from './geoms/Highlight.svelte'
|
|
56
74
|
export { default as GeomTrend } from './geoms/Trend.svelte'
|
|
75
|
+
export { default as GeomJitter } from './geoms/Jitter.svelte'
|
|
76
|
+
export { default as GeomRule } from './geoms/Rule.svelte'
|
|
57
77
|
|
|
58
78
|
// Export standalone components
|
|
59
79
|
export { default as Chart } from './Chart.svelte'
|
|
@@ -15,7 +15,13 @@ import { toPatternId } from '../../brewing/patterns.js'
|
|
|
15
15
|
export function buildArcs(data, channels, colors, width, height, opts = {}, patterns) {
|
|
16
16
|
const { color: lf, y: yf } = channels
|
|
17
17
|
const radius = Math.min(width, height) / 2
|
|
18
|
-
|
|
18
|
+
// innerRadius: a value <= 1 is a fraction of the radius (responsive donut);
|
|
19
|
+
// a value > 1 is absolute pixels. Clamp below the outer radius so a bad value
|
|
20
|
+
// (e.g. a fraction/pixel mixup) can never invert the arc into a giant ring
|
|
21
|
+
// rendered outside the circle.
|
|
22
|
+
const rawInner = opts.innerRadius ?? 0
|
|
23
|
+
const innerPx = rawInner <= 1 ? rawInner * radius : rawInner
|
|
24
|
+
const innerRadius = Math.max(0, Math.min(innerPx, radius * 0.98))
|
|
19
25
|
const pieGen = pie().value((d) => Number(d[yf]))
|
|
20
26
|
const arcGen = arc().innerRadius(innerRadius).outerRadius(radius)
|
|
21
27
|
const slices = pieGen(data)
|
|
@@ -8,17 +8,26 @@
|
|
|
8
8
|
* Box body uses the lighter fill shade; whiskers and median use the darker stroke shade.
|
|
9
9
|
*
|
|
10
10
|
* @param {Object[]} data - Pre-aggregated rows with quartile fields
|
|
11
|
-
* @param {{ x?: string, fill?: string }} channels
|
|
11
|
+
* @param {{ x?: string, fill?: string, pattern?: string }} channels
|
|
12
12
|
* `fill` drives the box and whisker color (defaults to x-field).
|
|
13
13
|
* @param {import('d3-scale').ScaleBand} xScale
|
|
14
14
|
* @param {import('d3-scale').ScaleLinear} yScale
|
|
15
15
|
* @param {Map<unknown, {fill:string, stroke:string}>} colors
|
|
16
|
+
* @param {{ side?: 'left'|'right'|'center', width?: number, patterns?: Map<unknown, string> }} [opts]
|
|
17
|
+
* `side` offsets the box to one half of the band; `width` is the box thickness as a
|
|
18
|
+
* fraction of the (sub-)band (for a thin raincloud box); `patterns` maps a pattern-channel
|
|
19
|
+
* value to a pattern name for texture fills. Defaults preserve the full box.
|
|
16
20
|
* @returns {Array}
|
|
17
21
|
*/
|
|
18
22
|
export function buildBoxes(data: Object[], channels: {
|
|
19
23
|
x?: string;
|
|
20
24
|
fill?: string;
|
|
25
|
+
pattern?: string;
|
|
21
26
|
}, xScale: import("d3-scale").ScaleBand<any>, yScale: import("d3-scale").ScaleLinear<any, any, never>, colors: Map<unknown, {
|
|
22
27
|
fill: string;
|
|
23
28
|
stroke: string;
|
|
24
|
-
}
|
|
29
|
+
}>, opts?: {
|
|
30
|
+
side?: "left" | "right" | "center";
|
|
31
|
+
width?: number;
|
|
32
|
+
patterns?: Map<unknown, string>;
|
|
33
|
+
}): any[];
|