@rokkit/chart 1.3.6 → 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 +21 -10
- package/dist/Plot/Grid.svelte +32 -13
- package/dist/Plot/Grid.svelte.d.ts +6 -1
- 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 +128 -164
- package/dist/Plot.svelte.d.ts +18 -2
- package/dist/PlotState.svelte.d.ts +23 -0
- package/dist/PlotState.svelte.js +155 -16
- package/dist/PlotSurface.svelte +162 -0
- package/dist/PlotSurface.svelte.d.ts +25 -0
- package/dist/charts/AreaChart.svelte +37 -4
- package/dist/charts/AreaChart.svelte.d.ts +12 -2
- 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 +35 -4
- package/dist/charts/LineChart.svelte.d.ts +11 -2
- 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 +20 -3
- package/dist/geoms/Bar.svelte +57 -13
- 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/Highlight.svelte +105 -0
- package/dist/geoms/Highlight.svelte.d.ts +10 -0
- package/dist/geoms/Jitter.svelte +82 -0
- package/dist/geoms/Jitter.svelte.d.ts +15 -0
- package/dist/geoms/Line.svelte +26 -9
- package/dist/geoms/Point.svelte +37 -11
- package/dist/geoms/Rule.svelte +113 -0
- package/dist/geoms/Rule.svelte.d.ts +16 -0
- package/dist/geoms/Trend.svelte +66 -0
- package/dist/geoms/Trend.svelte.d.ts +12 -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 +30 -4
- package/dist/index.js +31 -5
- package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +1 -1
- package/dist/lib/brewing/brewer.svelte.d.ts +1 -0
- 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 +6 -2
- package/dist/lib/brewing/marks/lines.js +19 -12
- 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/highlight.d.ts +10 -0
- package/dist/lib/highlight.js +43 -0
- 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/scale.d.ts +10 -0
- package/dist/lib/scale.js +14 -0
- package/dist/lib/select.d.ts +22 -0
- package/dist/lib/select.js +22 -0
- package/dist/lib/trend.d.ts +27 -0
- package/dist/lib/trend.js +113 -0
- package/dist/patterns/README.md +1 -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/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
|
@@ -8,6 +8,17 @@ export namespace Plot {
|
|
|
8
8
|
export { Area };
|
|
9
9
|
export { Point };
|
|
10
10
|
export { Arc };
|
|
11
|
+
export { Highlight };
|
|
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 };
|
|
11
22
|
}
|
|
12
23
|
export namespace PlotLayers { }
|
|
13
24
|
export { default as PlotChart } from "./Plot.svelte";
|
|
@@ -28,6 +39,10 @@ export { default as GeomCandlestick } from "./geoms/Candlestick.svelte";
|
|
|
28
39
|
export { default as GeomWaterfall } from "./geoms/Waterfall.svelte";
|
|
29
40
|
export { default as GeomHexbin } from "./geoms/Hexbin.svelte";
|
|
30
41
|
export { default as GeomRibbon } from "./geoms/Ribbon.svelte";
|
|
42
|
+
export { default as GeomHighlight } from "./geoms/Highlight.svelte";
|
|
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";
|
|
31
46
|
export { default as Chart } from "./Chart.svelte";
|
|
32
47
|
export { default as Sparkline } from "./Sparkline.svelte";
|
|
33
48
|
export { default as BarChart } from "./charts/BarChart.svelte";
|
|
@@ -53,12 +68,23 @@ export { default as FilterSlider } from "./crossfilter/FilterSlider.svelte";
|
|
|
53
68
|
export { default as FilterHistogram } from "./crossfilter/FilterHistogram.svelte";
|
|
54
69
|
import Root from './Plot/Root.svelte';
|
|
55
70
|
import Axis from './Plot/Axis.svelte';
|
|
56
|
-
import Bar from './
|
|
71
|
+
import Bar from './geoms/Bar.svelte';
|
|
57
72
|
import Grid from './Plot/Grid.svelte';
|
|
58
73
|
import Legend from './Plot/Legend.svelte';
|
|
59
|
-
import Line from './
|
|
60
|
-
import Area from './
|
|
61
|
-
import Point from './
|
|
74
|
+
import Line from './geoms/Line.svelte';
|
|
75
|
+
import Area from './geoms/Area.svelte';
|
|
76
|
+
import Point from './geoms/Point.svelte';
|
|
62
77
|
import Arc from './Plot/Arc.svelte';
|
|
78
|
+
import Highlight from './geoms/Highlight.svelte';
|
|
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';
|
|
63
89
|
export { createChartPreset, defaultPreset } from "./lib/preset.js";
|
|
64
90
|
export { buildSequentialScale, buildDivergingScale } from "./lib/brewing/colors.js";
|
package/dist/index.js
CHANGED
|
@@ -1,12 +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
|
+
import Highlight from './geoms/Highlight.svelte'
|
|
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'
|
|
10
21
|
|
|
11
22
|
// Composable Plot primitives — use as <Plot.Root>, <Plot.Axis>, <Plot.Bar>, etc.
|
|
12
23
|
export const Plot = {
|
|
@@ -18,7 +29,18 @@ export const Plot = {
|
|
|
18
29
|
Line,
|
|
19
30
|
Area,
|
|
20
31
|
Point,
|
|
21
|
-
Arc
|
|
32
|
+
Arc,
|
|
33
|
+
Highlight,
|
|
34
|
+
Trend,
|
|
35
|
+
Box,
|
|
36
|
+
Violin,
|
|
37
|
+
Jitter,
|
|
38
|
+
Candlestick,
|
|
39
|
+
Heatmap,
|
|
40
|
+
Hexbin,
|
|
41
|
+
Ribbon,
|
|
42
|
+
Waterfall,
|
|
43
|
+
Rule
|
|
22
44
|
}
|
|
23
45
|
|
|
24
46
|
/** @deprecated Use Plot instead */
|
|
@@ -48,6 +70,10 @@ export { default as GeomCandlestick } from './geoms/Candlestick.svelte'
|
|
|
48
70
|
export { default as GeomWaterfall } from './geoms/Waterfall.svelte'
|
|
49
71
|
export { default as GeomHexbin } from './geoms/Hexbin.svelte'
|
|
50
72
|
export { default as GeomRibbon } from './geoms/Ribbon.svelte'
|
|
73
|
+
export { default as GeomHighlight } from './geoms/Highlight.svelte'
|
|
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'
|
|
51
77
|
|
|
52
78
|
// Export standalone components
|
|
53
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[];
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { toPatternId } from '../patterns.js'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Builds box geometry for box plot charts.
|
|
3
5
|
* Input data rows must already contain { q1, median, q3, iqr_min, iqr_max } —
|
|
@@ -8,30 +10,42 @@
|
|
|
8
10
|
* Box body uses the lighter fill shade; whiskers and median use the darker stroke shade.
|
|
9
11
|
*
|
|
10
12
|
* @param {Object[]} data - Pre-aggregated rows with quartile fields
|
|
11
|
-
* @param {{ x?: string, fill?: string }} channels
|
|
13
|
+
* @param {{ x?: string, fill?: string, pattern?: string }} channels
|
|
12
14
|
* `fill` drives the box and whisker color (defaults to x-field).
|
|
13
15
|
* @param {import('d3-scale').ScaleBand} xScale
|
|
14
16
|
* @param {import('d3-scale').ScaleLinear} yScale
|
|
15
17
|
* @param {Map<unknown, {fill:string, stroke:string}>} colors
|
|
18
|
+
* @param {{ side?: 'left'|'right'|'center', width?: number, patterns?: Map<unknown, string> }} [opts]
|
|
19
|
+
* `side` offsets the box to one half of the band; `width` is the box thickness as a
|
|
20
|
+
* fraction of the (sub-)band (for a thin raincloud box); `patterns` maps a pattern-channel
|
|
21
|
+
* value to a pattern name for texture fills. Defaults preserve the full box.
|
|
16
22
|
* @returns {Array}
|
|
17
23
|
*/
|
|
18
|
-
export function buildBoxes(data, channels, xScale, yScale, colors) {
|
|
19
|
-
const { x: xf, fill: ff } = channels
|
|
24
|
+
export function buildBoxes(data, channels, xScale, yScale, colors, opts = {}) {
|
|
25
|
+
const { x: xf, fill: ff, pattern: pf } = channels
|
|
26
|
+
const { side = 'center', width, patterns } = opts
|
|
20
27
|
const bw = typeof xScale.bandwidth === 'function' ? xScale.bandwidth() : 20
|
|
21
28
|
const grouped = ff && ff !== xf
|
|
29
|
+
// Offset the box centre to the middle of the chosen half-lane.
|
|
30
|
+
const laneOffset = (unit) => (side === 'left' ? -unit / 4 : side === 'right' ? unit / 4 : 0)
|
|
31
|
+
// Texture fill: patternId per box from the pattern channel value (null = solid fill).
|
|
32
|
+
const patternIdFor = (d) => {
|
|
33
|
+
const key = pf ? d[pf] : null
|
|
34
|
+
return key !== null && key !== undefined && patterns?.has(key) ? toPatternId(String(key)) : null
|
|
35
|
+
}
|
|
22
36
|
|
|
23
37
|
if (grouped) {
|
|
24
38
|
const fillValues = [...new Set(data.map((d) => d[ff]))]
|
|
25
39
|
const n = fillValues.length
|
|
26
40
|
const subBandWidth = bw / n
|
|
27
|
-
const boxWidth = subBandWidth * 0.75
|
|
28
|
-
const whiskerWidth = subBandWidth * 0.4
|
|
41
|
+
const boxWidth = width !== undefined ? subBandWidth * width : subBandWidth * 0.75
|
|
42
|
+
const whiskerWidth = width !== undefined ? boxWidth * 0.53 : subBandWidth * 0.4
|
|
29
43
|
|
|
30
44
|
return data.map((d) => {
|
|
31
45
|
const fillVal = d[ff]
|
|
32
46
|
const subIndex = fillValues.indexOf(fillVal)
|
|
33
47
|
const bandStart = xScale(d[xf]) ?? 0
|
|
34
|
-
const cx = bandStart + subIndex * subBandWidth + subBandWidth / 2
|
|
48
|
+
const cx = bandStart + subIndex * subBandWidth + subBandWidth / 2 + laneOffset(subBandWidth)
|
|
35
49
|
const colorEntry = colors?.get(fillVal) ?? { fill: '#aaa', stroke: '#666' }
|
|
36
50
|
|
|
37
51
|
return {
|
|
@@ -42,22 +56,27 @@ export function buildBoxes(data, channels, xScale, yScale, colors) {
|
|
|
42
56
|
q3: yScale(d.q3),
|
|
43
57
|
iqr_min: yScale(d.iqr_min),
|
|
44
58
|
iqr_max: yScale(d.iqr_max),
|
|
59
|
+
outliers: (d.outliers ?? []).map((v) => ({ cy: yScale(v), value: v })),
|
|
45
60
|
width: boxWidth,
|
|
46
61
|
whiskerWidth,
|
|
47
62
|
fill: colorEntry.fill,
|
|
48
|
-
stroke: colorEntry.stroke
|
|
63
|
+
stroke: colorEntry.stroke,
|
|
64
|
+
patternId: patternIdFor(d)
|
|
49
65
|
}
|
|
50
66
|
})
|
|
51
67
|
}
|
|
52
68
|
|
|
53
69
|
// Non-grouped: one box per x category
|
|
54
|
-
const boxWidth = bw * 0.6
|
|
55
|
-
const whiskerWidth = bw * 0.3
|
|
70
|
+
const boxWidth = width !== undefined ? bw * width : bw * 0.6
|
|
71
|
+
const whiskerWidth = width !== undefined ? boxWidth * 0.5 : bw * 0.3
|
|
56
72
|
|
|
57
73
|
return data.map((d) => {
|
|
58
74
|
const fillKey = ff ? d[ff] : d[xf]
|
|
59
75
|
const colorEntry = colors?.get(fillKey) ?? { fill: '#aaa', stroke: '#666' }
|
|
60
|
-
const cx =
|
|
76
|
+
const cx =
|
|
77
|
+
(xScale(d[xf]) ?? 0) +
|
|
78
|
+
(typeof xScale.bandwidth === 'function' ? bw / 2 : 0) +
|
|
79
|
+
laneOffset(bw)
|
|
61
80
|
return {
|
|
62
81
|
data: d,
|
|
63
82
|
cx,
|
|
@@ -66,10 +85,12 @@ export function buildBoxes(data, channels, xScale, yScale, colors) {
|
|
|
66
85
|
q3: yScale(d.q3),
|
|
67
86
|
iqr_min: yScale(d.iqr_min),
|
|
68
87
|
iqr_max: yScale(d.iqr_max),
|
|
88
|
+
outliers: (d.outliers ?? []).map((v) => ({ cy: yScale(v), value: v })),
|
|
69
89
|
width: boxWidth,
|
|
70
90
|
whiskerWidth,
|
|
71
91
|
fill: colorEntry.fill,
|
|
72
|
-
stroke: colorEntry.stroke
|
|
92
|
+
stroke: colorEntry.stroke,
|
|
93
|
+
patternId: patternIdFor(d)
|
|
73
94
|
}
|
|
74
95
|
})
|
|
75
96
|
}
|
|
@@ -5,13 +5,16 @@
|
|
|
5
5
|
* @param {Function} yScale
|
|
6
6
|
* @param {Map} colors
|
|
7
7
|
* @param {'linear'|'smooth'|'step'} [curve]
|
|
8
|
-
* @returns {{ d: string | null, fill: string, stroke: string, points: {x:number, y:number, data: Record<string, unknown
|
|
8
|
+
* @returns {{ d: string | null, fill: string, stroke: string, points: {x:number, y:number, data: Record<string, unknown>, i: number}[], key?: unknown }[]}
|
|
9
9
|
*/
|
|
10
10
|
export function buildLines(data: Object[], channels: {
|
|
11
11
|
x?: string;
|
|
12
12
|
y?: string;
|
|
13
13
|
color?: string;
|
|
14
|
-
}, xScale: Function, yScale: Function, colors: Map<any, any>, curve?: "linear" | "smooth" | "step"
|
|
14
|
+
}, xScale: Function, yScale: Function, colors: Map<any, any>, curve?: "linear" | "smooth" | "step", place?: (x: any, y: any) => {
|
|
15
|
+
x: any;
|
|
16
|
+
y: any;
|
|
17
|
+
}): {
|
|
15
18
|
d: string | null;
|
|
16
19
|
fill: string;
|
|
17
20
|
stroke: string;
|
|
@@ -19,6 +22,7 @@ export function buildLines(data: Object[], channels: {
|
|
|
19
22
|
x: number;
|
|
20
23
|
y: number;
|
|
21
24
|
data: Record<string, unknown>;
|
|
25
|
+
i: number;
|
|
22
26
|
}[];
|
|
23
27
|
key?: unknown;
|
|
24
28
|
}[];
|
|
@@ -7,38 +7,45 @@ import { line, curveCatmullRom, curveStep } from 'd3-shape'
|
|
|
7
7
|
* @param {Function} yScale
|
|
8
8
|
* @param {Map} colors
|
|
9
9
|
* @param {'linear'|'smooth'|'step'} [curve]
|
|
10
|
-
* @returns {{ d: string | null, fill: string, stroke: string, points: {x:number, y:number, data: Record<string, unknown
|
|
10
|
+
* @returns {{ d: string | null, fill: string, stroke: string, points: {x:number, y:number, data: Record<string, unknown>, i: number}[], key?: unknown }[]}
|
|
11
11
|
*/
|
|
12
|
-
export function buildLines(data, channels, xScale, yScale, colors, curve) {
|
|
12
|
+
export function buildLines(data, channels, xScale, yScale, colors, curve, place = (x, y) => ({ x, y })) {
|
|
13
13
|
const { x: xf, y: yf, color: cf } = channels
|
|
14
14
|
const xPos = (d) =>
|
|
15
15
|
typeof xScale.bandwidth === 'function' ? xScale(d[xf]) + xScale.bandwidth() / 2 : xScale(d[xf])
|
|
16
|
+
// Screen points, sorted by the x-channel position, each mapped through place() so the
|
|
17
|
+
// whole line transposes under orientation='horizontal' (identity default → unchanged).
|
|
18
|
+
const screenPoints = (rows) => {
|
|
19
|
+
const withIndices = rows.map((d, i) => ({ data: d, i }))
|
|
20
|
+
withIndices.sort((a, b) => xPos(a.data) - xPos(b.data))
|
|
21
|
+
return withIndices.map(({ data: d, i }) => {
|
|
22
|
+
const p = place(xPos(d), yScale(d[yf]))
|
|
23
|
+
return { x: p.x, y: p.y, data: d, i }
|
|
24
|
+
})
|
|
25
|
+
}
|
|
16
26
|
const makeGen = () => {
|
|
17
27
|
const gen = line()
|
|
18
|
-
.x(
|
|
19
|
-
.y((
|
|
28
|
+
.x((p) => p.x)
|
|
29
|
+
.y((p) => p.y)
|
|
20
30
|
if (curve === 'smooth') gen.curve(curveCatmullRom)
|
|
21
31
|
else if (curve === 'step') gen.curve(curveStep)
|
|
22
32
|
return gen
|
|
23
33
|
}
|
|
24
|
-
const toPoints = (rows) => rows.map((d) => ({ x: xPos(d), y: yScale(d[yf]), data: d }))
|
|
25
|
-
|
|
26
|
-
const sortByX = (rows) => [...rows].sort((a, b) => xPos(a) - xPos(b))
|
|
27
34
|
|
|
28
35
|
if (!cf) {
|
|
29
|
-
const
|
|
36
|
+
const pts = screenPoints(data)
|
|
30
37
|
const stroke = colors?.values().next().value?.stroke ?? '#888'
|
|
31
|
-
return [{ d: makeGen()(
|
|
38
|
+
return [{ d: makeGen()(pts), fill: 'none', stroke, points: pts }]
|
|
32
39
|
}
|
|
33
40
|
const groups = groupBy(data, cf)
|
|
34
41
|
return [...groups.entries()].map(([key, rows]) => {
|
|
35
|
-
const
|
|
42
|
+
const pts = screenPoints(rows)
|
|
36
43
|
const colorEntry = colors?.get(key) ?? { fill: 'none', stroke: '#888' }
|
|
37
44
|
return {
|
|
38
|
-
d: makeGen()(
|
|
45
|
+
d: makeGen()(pts),
|
|
39
46
|
fill: 'none',
|
|
40
47
|
stroke: colorEntry.stroke,
|
|
41
|
-
points:
|
|
48
|
+
points: pts,
|
|
42
49
|
key
|
|
43
50
|
}
|
|
44
51
|
})
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Places one point per datum within each x-band. Two layouts:
|
|
3
|
+
* - 'jitter': deterministic horizontal offset seeded by index-in-group.
|
|
4
|
+
* - 'swarm': 1-D beeswarm — sort by y, dodge horizontally to avoid overlap.
|
|
5
|
+
*
|
|
6
|
+
* @param {Object[]} data - raw rows (stat=identity)
|
|
7
|
+
* @param {{ x?: string, y?: string, fill?: string }} channels
|
|
8
|
+
* @param {import('d3-scale').ScaleBand} xScale
|
|
9
|
+
* @param {import('d3-scale').ScaleLinear} yScale
|
|
10
|
+
* @param {Map<unknown, {fill:string, stroke:string}>} colors
|
|
11
|
+
* @param {{ method?: 'jitter'|'swarm', r?: number, side?: 'left'|'right'|'center' }} opts
|
|
12
|
+
* `side` confines points to one half of the band (for raincloud/half-plots).
|
|
13
|
+
* @returns {Array<{ cx:number, cy:number, fill:string, stroke:string, data:Record<string, unknown> }>}
|
|
14
|
+
*/
|
|
15
|
+
export function buildSwarm(data: Object[], channels: {
|
|
16
|
+
x?: string;
|
|
17
|
+
y?: string;
|
|
18
|
+
fill?: string;
|
|
19
|
+
}, xScale: import("d3-scale").ScaleBand<any>, yScale: import("d3-scale").ScaleLinear<any, any, never>, colors: Map<unknown, {
|
|
20
|
+
fill: string;
|
|
21
|
+
stroke: string;
|
|
22
|
+
}>, opts?: {
|
|
23
|
+
method?: "jitter" | "swarm";
|
|
24
|
+
r?: number;
|
|
25
|
+
side?: "left" | "right" | "center";
|
|
26
|
+
}): Array<{
|
|
27
|
+
cx: number;
|
|
28
|
+
cy: number;
|
|
29
|
+
fill: string;
|
|
30
|
+
stroke: string;
|
|
31
|
+
data: Record<string, unknown>;
|
|
32
|
+
}>;
|