@rokkit/chart 1.3.7 → 1.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AnimatedPlot.svelte +26 -8
- package/dist/AnimatedPlot.svelte.d.ts +5 -0
- package/dist/Chart.svelte +51 -65
- package/dist/FacetPlot.svelte +1 -1
- package/dist/Plot/Axis.svelte +19 -10
- package/dist/Plot/Root.svelte +67 -46
- package/dist/Plot/Root.svelte.d.ts +29 -2
- package/dist/Plot/Timeline.svelte +39 -13
- package/dist/Plot/index.d.ts +5 -5
- package/dist/Plot/index.js +5 -5
- package/dist/Plot.svelte +95 -178
- package/dist/Plot.svelte.d.ts +4 -0
- package/dist/PlotState.svelte.d.ts +29 -1
- package/dist/PlotState.svelte.js +163 -19
- package/dist/PlotSurface.svelte +162 -0
- package/dist/PlotSurface.svelte.d.ts +25 -0
- package/dist/charts/AreaChart.svelte +12 -2
- package/dist/charts/AreaChart.svelte.d.ts +6 -0
- package/dist/charts/BarChart.svelte +40 -4
- package/dist/charts/BarChart.svelte.d.ts +15 -1
- package/dist/charts/BoxPlot.svelte +7 -3
- package/dist/charts/BoxPlot.svelte.d.ts +2 -0
- package/dist/charts/BubbleChart.svelte +37 -4
- package/dist/charts/BubbleChart.svelte.d.ts +12 -1
- package/dist/charts/LineChart.svelte +4 -2
- package/dist/charts/LineChart.svelte.d.ts +1 -0
- package/dist/charts/PieChart.svelte +5 -3
- package/dist/charts/PieChart.svelte.d.ts +1 -0
- package/dist/charts/ScatterPlot.svelte +35 -4
- package/dist/charts/ScatterPlot.svelte.d.ts +11 -1
- package/dist/charts/ViolinPlot.svelte +7 -3
- package/dist/charts/ViolinPlot.svelte.d.ts +2 -0
- package/dist/examples/BarChartExample.svelte +1 -1
- package/dist/geoms/Arc.svelte +21 -54
- package/dist/geoms/Arc.svelte.d.ts +4 -4
- package/dist/geoms/Area.svelte +45 -44
- package/dist/geoms/Area.svelte.d.ts +8 -1
- package/dist/geoms/Bar.svelte +33 -45
- package/dist/geoms/Bar.svelte.d.ts +9 -0
- package/dist/geoms/Box.svelte +103 -61
- package/dist/geoms/Box.svelte.d.ts +12 -3
- package/dist/geoms/Candlestick.svelte +26 -75
- package/dist/geoms/Candlestick.svelte.d.ts +2 -0
- package/dist/geoms/Heatmap.svelte +21 -66
- package/dist/geoms/Heatmap.svelte.d.ts +2 -0
- package/dist/geoms/Hexbin.svelte +20 -100
- package/dist/geoms/Hexbin.svelte.d.ts +2 -0
- package/dist/geoms/Jitter.svelte +67 -0
- package/dist/geoms/Jitter.svelte.d.ts +17 -0
- package/dist/geoms/Line.svelte +27 -31
- package/dist/geoms/Line.svelte.d.ts +5 -0
- package/dist/geoms/Point.svelte +31 -60
- package/dist/geoms/Point.svelte.d.ts +5 -1
- package/dist/geoms/Ribbon.svelte +30 -124
- package/dist/geoms/Ribbon.svelte.d.ts +2 -0
- package/dist/geoms/Rule.svelte +117 -0
- package/dist/geoms/Rule.svelte.d.ts +19 -0
- package/dist/geoms/Violin.svelte +36 -31
- package/dist/geoms/Violin.svelte.d.ts +10 -3
- package/dist/geoms/Waterfall.svelte +25 -79
- package/dist/geoms/Waterfall.svelte.d.ts +2 -0
- package/dist/geoms/lib/GeomState.svelte.d.ts +51 -0
- package/dist/geoms/lib/GeomState.svelte.js +79 -0
- package/dist/geoms/lib/aesthetics.d.ts +36 -0
- package/dist/geoms/lib/aesthetics.js +45 -0
- package/dist/geoms/lib/areas.d.ts +6 -2
- package/dist/geoms/lib/areas.js +20 -12
- package/dist/geoms/lib/bars.d.ts +18 -3
- package/dist/geoms/lib/bars.js +80 -101
- package/dist/geoms/lib/marks/arc.d.ts +24 -0
- package/dist/geoms/lib/marks/arc.js +28 -0
- package/dist/geoms/lib/marks/area.d.ts +23 -0
- package/dist/geoms/lib/marks/area.js +38 -0
- package/dist/geoms/lib/marks/bar.d.ts +16 -0
- package/dist/geoms/lib/marks/bar.js +43 -0
- package/dist/geoms/lib/marks/box.d.ts +14 -0
- package/dist/geoms/lib/marks/box.js +30 -0
- package/dist/geoms/lib/marks/candlestick.d.ts +27 -0
- package/dist/geoms/lib/marks/candlestick.js +52 -0
- package/dist/geoms/lib/marks/heatmap.d.ts +24 -0
- package/dist/geoms/lib/marks/heatmap.js +41 -0
- package/dist/geoms/lib/marks/hexbin.d.ts +29 -0
- package/dist/geoms/lib/marks/hexbin.js +62 -0
- package/dist/geoms/lib/marks/jitter.d.ts +21 -0
- package/dist/geoms/lib/marks/jitter.js +36 -0
- package/dist/geoms/lib/marks/line.d.ts +26 -0
- package/dist/geoms/lib/marks/line.js +25 -0
- package/dist/geoms/lib/marks/point.d.ts +25 -0
- package/dist/geoms/lib/marks/point.js +58 -0
- package/dist/geoms/lib/marks/ribbon.d.ts +31 -0
- package/dist/geoms/lib/marks/ribbon.js +77 -0
- package/dist/geoms/lib/marks/violin.d.ts +13 -0
- package/dist/geoms/lib/marks/violin.js +31 -0
- package/dist/geoms/lib/marks/waterfall.d.ts +25 -0
- package/dist/geoms/lib/marks/waterfall.js +58 -0
- package/dist/index.d.ts +25 -12
- package/dist/index.js +26 -13
- package/dist/lib/brewing/marks/arcs.js +7 -1
- package/dist/lib/brewing/marks/boxes.d.ts +11 -2
- package/dist/lib/brewing/marks/boxes.js +32 -11
- package/dist/lib/brewing/marks/lines.d.ts +4 -1
- package/dist/lib/brewing/marks/lines.js +18 -15
- package/dist/lib/brewing/marks/swarm.d.ts +32 -0
- package/dist/lib/brewing/marks/swarm.js +125 -0
- package/dist/lib/brewing/marks/violins.d.ts +4 -20
- package/dist/lib/brewing/marks/violins.js +41 -25
- package/dist/lib/brewing/stats.d.ts +3 -1
- package/dist/lib/brewing/stats.js +28 -11
- package/dist/lib/plot/scales.js +12 -7
- package/dist/lib/plot/types.d.ts +9 -1
- package/dist/lib/plot/types.js +3 -1
- package/dist/lib/preset.d.ts +11 -1
- package/dist/lib/preset.js +16 -4
- package/package.json +1 -1
- package/dist/Plot/Arc.svelte +0 -30
- package/dist/Plot/Arc.svelte.d.ts +0 -18
- package/dist/Plot/Area.svelte +0 -54
- package/dist/Plot/Area.svelte.d.ts +0 -14
- package/dist/Plot/Bar.svelte +0 -69
- package/dist/Plot/Bar.svelte.d.ts +0 -11
- package/dist/Plot/Line.svelte +0 -54
- package/dist/Plot/Line.svelte.d.ts +0 -13
- package/dist/Plot/Point.svelte +0 -58
- package/dist/Plot/Point.svelte.d.ts +0 -13
- package/dist/lib/brewer.d.ts +0 -9
- package/dist/lib/brewer.js +0 -25
- package/dist/lib/brewing/BoxBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/BoxBrewer.svelte.js +0 -14
- package/dist/lib/brewing/CartesianBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/CartesianBrewer.svelte.js +0 -21
- package/dist/lib/brewing/PieBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/PieBrewer.svelte.js +0 -14
- package/dist/lib/brewing/QuartileBrewer.svelte.d.ts +0 -9
- package/dist/lib/brewing/QuartileBrewer.svelte.js +0 -51
- package/dist/lib/brewing/ViolinBrewer.svelte.d.ts +0 -8
- package/dist/lib/brewing/ViolinBrewer.svelte.js +0 -14
- package/dist/lib/brewing/axes.svelte.d.ts +0 -66
- package/dist/lib/brewing/axes.svelte.js +0 -270
- package/dist/lib/brewing/bars.svelte.d.ts +0 -56
- package/dist/lib/brewing/bars.svelte.js +0 -201
- package/dist/lib/brewing/brewer.svelte.d.ts +0 -124
- package/dist/lib/brewing/brewer.svelte.js +0 -277
- package/dist/lib/brewing/dimensions.svelte.d.ts +0 -35
- package/dist/lib/brewing/dimensions.svelte.js +0 -56
- package/dist/lib/brewing/index.svelte.d.ts +0 -118
- package/dist/lib/brewing/index.svelte.js +0 -205
- package/dist/lib/brewing/legends.svelte.d.ts +0 -48
- package/dist/lib/brewing/legends.svelte.js +0 -137
- package/dist/lib/brewing/marks/areas.d.ts +0 -31
- package/dist/lib/brewing/marks/areas.js +0 -72
- package/dist/lib/brewing/marks/bars.d.ts +0 -1
- package/dist/lib/brewing/marks/bars.js +0 -49
- package/dist/lib/brewing/scales.svelte.d.ts +0 -24
- package/dist/lib/brewing/scales.svelte.js +0 -82
- package/dist/lib/context.d.ts +0 -13
- package/dist/lib/context.js +0 -131
- package/dist/lib/plot/chartProps.d.ts +0 -177
- package/dist/lib/plot/chartProps.js +0 -76
- package/dist/lib/scales.svelte.d.ts +0 -35
- package/dist/lib/scales.svelte.js +0 -151
- package/dist/lib/ticks.d.ts +0 -36
- package/dist/lib/ticks.js +0 -46
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)
|
|
@@ -33,27 +34,65 @@ function subBandFields(channels) {
|
|
|
33
34
|
return out
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
/**
|
|
38
|
+
* The one bar builder. Expresses each bar in abstract (category, value) space and maps it to
|
|
39
|
+
* screen via `place()`, so it renders vertical or horizontal (flip) with the same code.
|
|
40
|
+
*
|
|
41
|
+
* Category (x) positioning has two modes:
|
|
42
|
+
* - band (default): a scaleBand slots each category; sub-bands split grouped series.
|
|
43
|
+
* - continuous (`continuousCategory`): a LINEAR position scale (e.g. a bar-chart race's tweened
|
|
44
|
+
* `_rank`). Each bar centres on `xScale(pos)` with a fixed thickness from the scale step, so a
|
|
45
|
+
* fractional position tweens smoothly (a band scale can only jump between slots). No sub-banding.
|
|
46
|
+
*/
|
|
47
|
+
export function buildBars(
|
|
48
|
+
data,
|
|
49
|
+
channels,
|
|
50
|
+
xScale,
|
|
51
|
+
yScale,
|
|
52
|
+
colors,
|
|
53
|
+
innerHeight,
|
|
54
|
+
patterns,
|
|
55
|
+
place = (x, y) => ({ x, y }),
|
|
56
|
+
continuousCategory = false,
|
|
57
|
+
dodge = true
|
|
58
|
+
) {
|
|
37
59
|
const { x: xf, y: yf, color: cf, pattern: pf } = channels
|
|
38
60
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// Sub-banding: only fields that differ from x drive grouping within a band
|
|
42
|
-
const subFields = subBandFields(channels)
|
|
61
|
+
// No sub-banding for a continuous (race) axis or position='identity' (dodge=false → overlap).
|
|
62
|
+
const subFields = continuousCategory || !dodge ? [] : subBandFields(channels)
|
|
43
63
|
const getSubKey = (d) => subFields.map((f) => String(d[f])).join('::')
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
|
|
65
|
+
let positionOf
|
|
66
|
+
let thicknessOf
|
|
67
|
+
if (continuousCategory) {
|
|
68
|
+
const [r0, r1] = xScale.range()
|
|
69
|
+
const [d0, d1] = xScale.domain()
|
|
70
|
+
const step = Math.max(Math.abs(r0 - r1) / Math.max(d1 - d0, 1), 1)
|
|
71
|
+
const thickness = step * 0.9
|
|
72
|
+
thicknessOf = () => thickness
|
|
73
|
+
positionOf = (d) => (xScale(Number(d[xf])) ?? 0) - thickness / 2
|
|
74
|
+
} else {
|
|
75
|
+
const bandScale = ensureBandX(xScale, data, xf)
|
|
76
|
+
const subDomain = subFields.length > 0 ? [...new Set(data.map(getSubKey))] : []
|
|
77
|
+
const subScale =
|
|
78
|
+
subDomain.length > 1
|
|
79
|
+
? scaleBand().domain(subDomain).range([0, bandScale.bandwidth()]).padding(0.05)
|
|
80
|
+
: null
|
|
81
|
+
thicknessOf = () => (subScale ? subScale.bandwidth() : bandScale.bandwidth())
|
|
82
|
+
positionOf = (d) => {
|
|
83
|
+
const bandX = bandScale(d[xf]) ?? 0
|
|
84
|
+
const subX = subScale ? (subScale(getSubKey(d)) ?? 0) : 0
|
|
85
|
+
return bandX + subX
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Value baseline: at value=0 when the domain spans zero (supports negative bars), else the
|
|
90
|
+
// value-axis origin (yScale.range()[0] — innerHeight when vertical, 0 when flipped).
|
|
52
91
|
const yDomain = typeof yScale.bandwidth !== 'function' ? yScale.domain?.() : null
|
|
53
92
|
const baseline =
|
|
54
93
|
yDomain && yDomain[0] <= 0 && yDomain[yDomain.length - 1] >= 0
|
|
55
|
-
? (yScale(0) ??
|
|
56
|
-
:
|
|
94
|
+
? (yScale(0) ?? yScale.range()[0])
|
|
95
|
+
: yScale.range()[0]
|
|
57
96
|
|
|
58
97
|
return data.map((d, i) => {
|
|
59
98
|
const xVal = d[xf]
|
|
@@ -68,19 +107,26 @@ export function buildGroupedBars(data, channels, xScale, yScale, colors, innerHe
|
|
|
68
107
|
? toPatternId(String(patternKey))
|
|
69
108
|
: null
|
|
70
109
|
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
const
|
|
110
|
+
const barBand = positionOf(d)
|
|
111
|
+
const barThickness = thicknessOf(d)
|
|
112
|
+
const barValue = yScale(d[yf]) ?? baseline
|
|
113
|
+
// Two opposite corners in (band, value) space, placed to screen — transposes under flip.
|
|
114
|
+
const c1 = place(barBand, baseline)
|
|
115
|
+
const c2 = place(barBand + barThickness, barValue)
|
|
116
|
+
|
|
117
|
+
// Continuous mode keys by _entity so Svelte reuses each bar as its position tweens (a
|
|
118
|
+
// race's rows reorder every frame); band mode keys by category + sub-band + index.
|
|
119
|
+
const key = continuousCategory
|
|
120
|
+
? `${String(d._entity ?? xVal)}::${String(colorKey ?? '')}`
|
|
121
|
+
: `${String(xVal)}::${subKey}::${i}`
|
|
76
122
|
|
|
77
123
|
return {
|
|
78
124
|
data: d,
|
|
79
|
-
key
|
|
80
|
-
x:
|
|
81
|
-
y: Math.min(
|
|
82
|
-
width:
|
|
83
|
-
height: Math.abs(
|
|
125
|
+
key,
|
|
126
|
+
x: Math.min(c1.x, c2.x),
|
|
127
|
+
y: Math.min(c1.y, c2.y),
|
|
128
|
+
width: Math.abs(c2.x - c1.x),
|
|
129
|
+
height: Math.abs(c2.y - c1.y),
|
|
84
130
|
fill: colorEntry.fill,
|
|
85
131
|
stroke: colorEntry.stroke,
|
|
86
132
|
patternId
|
|
@@ -88,7 +134,7 @@ export function buildGroupedBars(data, channels, xScale, yScale, colors, innerHe
|
|
|
88
134
|
})
|
|
89
135
|
}
|
|
90
136
|
|
|
91
|
-
export function buildStackedBars(data, channels, xScale, yScale, colors, innerHeight, patterns) {
|
|
137
|
+
export function buildStackedBars(data, channels, xScale, yScale, colors, innerHeight, patterns, place = (x, y) => ({ x, y }), normalize = false) {
|
|
92
138
|
const { x: xf, y: yf, color: cf, pattern: pf } = channels
|
|
93
139
|
|
|
94
140
|
const bandScale = ensureBandX(xScale, data, xf)
|
|
@@ -96,7 +142,7 @@ export function buildStackedBars(data, channels, xScale, yScale, colors, innerHe
|
|
|
96
142
|
// Stack dimension: first non-x grouping field (prefer pattern, then color)
|
|
97
143
|
const subFields = subBandFields(channels)
|
|
98
144
|
if (subFields.length === 0) {
|
|
99
|
-
return
|
|
145
|
+
return buildBars(data, channels, xScale, yScale, colors, innerHeight, patterns, place)
|
|
100
146
|
}
|
|
101
147
|
const stackField = subFields[0]
|
|
102
148
|
|
|
@@ -116,6 +162,8 @@ export function buildStackedBars(data, channels, xScale, yScale, colors, innerHe
|
|
|
116
162
|
})
|
|
117
163
|
|
|
118
164
|
const stackGen = stack().keys(stackCategories)
|
|
165
|
+
// position='fill' → normalize each x column to [0,1] (100% stacked).
|
|
166
|
+
if (normalize) stackGen.offset(stackOffsetExpand)
|
|
119
167
|
const layers = stackGen(wide)
|
|
120
168
|
|
|
121
169
|
const bars = []
|
|
@@ -152,72 +200,3 @@ export function buildStackedBars(data, channels, xScale, yScale, colors, innerHe
|
|
|
152
200
|
}
|
|
153
201
|
return bars
|
|
154
202
|
}
|
|
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
|
-
}
|
|
@@ -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
|
+
}[];
|