@fjader/svelte 0.2.2 → 0.2.4
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/README.md +2 -0
- package/dist/components/axes/AxisBottom.svelte +23 -6
- package/dist/components/axes/AxisBottom.svelte.d.ts +10 -0
- package/dist/components/axes/AxisLeft.svelte +77 -8
- package/dist/components/axes/AxisLeft.svelte.d.ts +17 -0
- package/dist/components/axes/BandAxisTicks.svelte +64 -25
- package/dist/components/axes/BandAxisTicks.svelte.d.ts +5 -5
- package/dist/components/axes/axis-title.d.ts +30 -0
- package/dist/components/axes/axis-title.js +77 -0
- package/dist/components/charts/ChartCell.svelte +1 -1
- package/dist/components/charts/ChartContainer.svelte +12 -3
- package/dist/components/charts/ChartContainer.svelte.d.ts +7 -0
- package/dist/components/guides/legend/ContinuousColorLegend.svelte +9 -2
- package/dist/components/guides/legend/OrdinalColorLegend.svelte +96 -35
- package/dist/components/marks/AreaMarkX.svelte +21 -6
- package/dist/components/marks/AreaMarkY.svelte +21 -6
- package/dist/components/marks/BarMarkX.svelte +16 -1
- package/dist/components/marks/BarMarkY.svelte +16 -1
- package/dist/components/marks/DotsMark.svelte +5 -0
- package/dist/components/marks/LineMark.svelte +8 -1
- package/dist/components/marks/ParallelCoordinatesMark.svelte +10 -0
- package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +5 -0
- package/dist/components/marks/RectMark.svelte +5 -0
- package/dist/components/marks/RectXMark.svelte +5 -0
- package/dist/components/marks/RectYMark.svelte +5 -0
- package/dist/components/marks/RuleMarkX.svelte +12 -2
- package/dist/components/marks/RuleMarkY.svelte +12 -2
- package/dist/components/marks/TextMark.svelte +76 -13
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +21 -2
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +300 -42
- package/dist/components/marks/lifecycle/domain-queries.d.ts +3 -1
- package/dist/components/marks/lifecycle/domain-queries.js +22 -15
- package/dist/components/marks/lifecycle/domain-registration.js +2 -6
- package/dist/components/marks/lifecycle/other-rows.svelte.d.ts +104 -0
- package/dist/components/marks/lifecycle/other-rows.svelte.js +193 -0
- package/dist/components/marks/lifecycle/other-split.d.ts +80 -0
- package/dist/components/marks/lifecycle/other-split.js +112 -0
- package/dist/components/marks/lifecycle/query-builder.d.ts +26 -1
- package/dist/components/marks/lifecycle/query-builder.js +25 -5
- package/dist/components/marks/lifecycle/transform-scale-key.d.ts +16 -0
- package/dist/components/marks/lifecycle/transform-scale-key.js +27 -0
- package/dist/components/marks/utils/area-outline.d.ts +24 -0
- package/dist/components/marks/utils/area-outline.js +10 -0
- package/dist/components/marks/utils/reactive-config.d.ts +1 -1
- package/dist/components/marks/utils/reactive-config.js +9 -0
- package/dist/components/marks/utils/tooltip-helpers.d.ts +4 -1
- package/dist/components/marks/utils/tooltip-helpers.js +2 -1
- package/dist/components/rendering/Areas.svelte +30 -6
- package/dist/components/rendering/Areas.svelte.d.ts +2 -0
- package/dist/components/rendering/BaseGPURenderer.svelte.d.ts +3 -1
- package/dist/components/rendering/BaseGPURenderer.svelte.js +8 -3
- package/dist/components/rendering/Dots.svelte +24 -3
- package/dist/components/rendering/Dots.svelte.d.ts +2 -0
- package/dist/components/rendering/GridLine.svelte +2 -1
- package/dist/components/rendering/Lines.svelte +49 -7
- package/dist/components/rendering/Lines.svelte.d.ts +9 -0
- package/dist/components/rendering/ParallelLines.svelte +26 -7
- package/dist/components/rendering/ParallelLines.svelte.d.ts +2 -0
- package/dist/components/rendering/Rects.svelte +129 -20
- package/dist/components/rendering/Rects.svelte.d.ts +9 -0
- package/dist/components/rendering/RuleX.svelte +20 -5
- package/dist/components/rendering/RuleX.svelte.d.ts +2 -0
- package/dist/components/rendering/RuleY.svelte +20 -5
- package/dist/components/rendering/RuleY.svelte.d.ts +2 -0
- package/dist/components/rendering/activity-reads.d.ts +111 -0
- package/dist/components/rendering/activity-reads.js +154 -0
- package/dist/components/rendering/uniform-sizes.d.ts +26 -0
- package/dist/components/rendering/uniform-sizes.js +28 -0
- package/dist/config/ChartConfig.svelte.d.ts +5 -1
- package/dist/gpu/ColorScaleShaderLib.d.ts +13 -1
- package/dist/gpu/ColorScaleShaderLib.js +56 -5
- package/dist/gpu/GPUCoordinator.svelte.d.ts +8 -0
- package/dist/gpu/GPUCoordinator.svelte.js +68 -32
- package/dist/gpu/MorphedChannels.d.ts +6 -32
- package/dist/gpu/MorphedChannels.js +45 -3
- package/dist/gpu/ResolveShader.d.ts +2 -2
- package/dist/gpu/ResolveShader.js +2 -2
- package/dist/gpu/ScaleTemplateSystem.d.ts +19 -0
- package/dist/gpu/ScaleTemplateSystem.js +111 -11
- package/dist/gpu/SizeScaleShaderLib.d.ts +4 -4
- package/dist/gpu/SizeScaleShaderLib.js +4 -4
- package/dist/gpu/identity-slot-packing.d.ts +157 -40
- package/dist/gpu/identity-slot-packing.js +262 -62
- package/dist/gpu/tracked-scales.d.ts +14 -0
- package/dist/gpu/tracked-scales.js +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/interactors/BandLabelToggle.svelte +5 -9
- package/dist/interactors/BandLabelToggle.svelte.d.ts +3 -3
- package/dist/interactors/DerivedInterval1D.svelte +26 -54
- package/dist/interactors/Interval1D.svelte +8 -2
- package/dist/interactors/Interval2D.svelte +8 -2
- package/dist/interactors/ParallelAxisSource.svelte +14 -5
- package/dist/interactors/ParallelAxisSource.svelte.d.ts +2 -1
- package/dist/interactors/ParallelBrushes.svelte +8 -9
- package/dist/interactors/ParallelBrushes.svelte.d.ts +3 -3
- package/dist/interactors/Toggle.svelte +48 -38
- package/dist/interactors/Toggle.svelte.d.ts +4 -3
- package/dist/interactors/Tooltip.svelte +140 -42
- package/dist/interactors/TooltipManager.svelte.d.ts +20 -5
- package/dist/interactors/TooltipManager.svelte.js +40 -10
- package/dist/interactors/other-tooltip.d.ts +16 -0
- package/dist/interactors/other-tooltip.js +22 -0
- package/dist/interactors/relay-clauses.d.ts +23 -0
- package/dist/interactors/relay-clauses.js +60 -0
- package/dist/query-engine/CrossfilterSelection.svelte.d.ts +88 -15
- package/dist/query-engine/CrossfilterSelection.svelte.js +94 -20
- package/dist/query-engine/FieldActivity.svelte.d.ts +128 -0
- package/dist/query-engine/FieldActivity.svelte.js +378 -0
- package/dist/query-engine/FilterGroup.svelte.d.ts +76 -30
- package/dist/query-engine/FilterGroup.svelte.js +223 -124
- package/dist/query-engine/FilterGroupRegistry.svelte.d.ts +73 -3
- package/dist/query-engine/FilterGroupRegistry.svelte.js +161 -12
- package/dist/query-engine/MarkHandle.svelte.d.ts +9 -1
- package/dist/query-engine/MarkHandle.svelte.js +12 -2
- package/dist/query-engine/QueryEngine.d.ts +52 -16
- package/dist/query-engine/QueryEngine.js +116 -34
- package/dist/query-engine/categorical-identity.d.ts +114 -27
- package/dist/query-engine/categorical-identity.js +225 -51
- package/dist/query-engine/index.d.ts +2 -1
- package/dist/query-engine/index.js +1 -0
- package/dist/query-engine/preagg/Preagg.d.ts +1 -1
- package/dist/query-engine/preagg/Preagg.js +11 -9
- package/dist/query-engine/preagg/plan.d.ts +14 -5
- package/dist/query-engine/preagg/plan.js +44 -18
- package/dist/query-engine/toggle-predicate.d.ts +21 -0
- package/dist/query-engine/toggle-predicate.js +41 -2
- package/dist/scales/ActivityTable.d.ts +103 -0
- package/dist/scales/ActivityTable.js +220 -0
- package/dist/scales/BandScaleState.svelte.d.ts +41 -15
- package/dist/scales/BandScaleState.svelte.js +78 -36
- package/dist/scales/ChartScales.svelte.d.ts +2 -2
- package/dist/scales/ColorScaleState.svelte.d.ts +36 -8
- package/dist/scales/ColorScaleState.svelte.js +118 -24
- package/dist/scales/DomainResolver.d.ts +2 -0
- package/dist/scales/DomainResolver.js +15 -8
- package/dist/scales/ScaleRegistry.svelte.d.ts +9 -0
- package/dist/scales/ScaleRegistry.svelte.js +22 -1
- package/dist/scales/TimeScaleState.svelte.js +7 -1
- package/dist/spec/index.js +1 -0
- package/dist/spec/types.d.ts +4 -0
- package/dist/theme.css +5 -4
- package/dist/transforms/TransformCompiler.d.ts +37 -2
- package/dist/transforms/TransformCompiler.js +44 -1
- package/dist/transforms/index.d.ts +27 -0
- package/dist/transforms/index.js +127 -19
- package/dist/types.d.ts +71 -0
- package/package.json +3 -2
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { UNIFORM_BYTES } from './uniform-sizes';
|
|
2
3
|
import { getContext, onMount, onDestroy } from 'svelte';
|
|
3
4
|
import { RendererHost } from './RendererHost.svelte';
|
|
4
5
|
import { getGPUContext } from '../../gpu';
|
|
5
6
|
import { DEFAULT_FILL_COLOR } from '../../scales/color-utils';
|
|
6
7
|
import type { ChartContext, WebGPUCanvasContext } from '../../types';
|
|
8
|
+
import type { SplitProp } from '../marks/lifecycle/other-rows.svelte';
|
|
9
|
+
import { activityReads, markActivityWGSL, otherRowActivityWGSL } from './activity-reads';
|
|
7
10
|
|
|
8
11
|
const rectRendererDefinition = {
|
|
9
12
|
x: { concept: 'extent', channels: ['x0', 'x1'] },
|
|
@@ -19,7 +22,18 @@
|
|
|
19
22
|
Y1: 3,
|
|
20
23
|
COLOR: 4,
|
|
21
24
|
ALPHA: 5,
|
|
22
|
-
CONSTANTS: 6
|
|
25
|
+
CONSTANTS: 6,
|
|
26
|
+
/**
|
|
27
|
+
* An Other bar's selected share, per row: 0 on rows that are not an Other
|
|
28
|
+
* (so the zero-filled stand-in buffer means "no split anywhere"), `1 +
|
|
29
|
+
* share` on one. Morphed with the bar's length.
|
|
30
|
+
*
|
|
31
|
+
* The vertex stage's 8th storage buffer: six channels, the identity slot
|
|
32
|
+
* table and this fill WebGPU's default maxStorageBuffersPerShaderStage. A
|
|
33
|
+
* further per-row channel needs another container (an instance vertex
|
|
34
|
+
* attribute) or a raised device limit.
|
|
35
|
+
*/
|
|
36
|
+
SHARE: 7
|
|
23
37
|
};
|
|
24
38
|
|
|
25
39
|
// Facet uses vertex buffer slot 1 (not a storage binding)
|
|
@@ -30,11 +44,12 @@
|
|
|
30
44
|
COLOR_MODE: 0,
|
|
31
45
|
COLOR_SCALE_INDEX: 4,
|
|
32
46
|
INSET: 8,
|
|
33
|
-
|
|
47
|
+
GROUP_INDEX: 12,
|
|
34
48
|
CONSTANT_COLOR: 16,
|
|
35
49
|
DX: 32,
|
|
36
|
-
DY: 36
|
|
37
|
-
|
|
50
|
+
DY: 36,
|
|
51
|
+
OTHER_COLOR_ON_BAND: 40,
|
|
52
|
+
SPLIT_ALONG_X: 44
|
|
38
53
|
};
|
|
39
54
|
|
|
40
55
|
interface Props {
|
|
@@ -44,11 +59,19 @@
|
|
|
44
59
|
y1Data: Float32Array;
|
|
45
60
|
colorValueData: Float32Array | null;
|
|
46
61
|
alphaData: Float32Array | null;
|
|
62
|
+
/**
|
|
63
|
+
* A bar's selected share per row (see BINDINGS.SHARE), or null for none: a
|
|
64
|
+
* bar mark with a topN Other bucket passes its lifecycle's `otherRows.split`.
|
|
65
|
+
* The fragment draws the bar above the share dimmed.
|
|
66
|
+
*/
|
|
67
|
+
share?: SplitProp | null;
|
|
47
68
|
facetData?: Float32Array | null;
|
|
48
69
|
dataVersion: number;
|
|
49
70
|
zIndex?: number;
|
|
50
71
|
colorMode?: number;
|
|
51
72
|
colorScaleIndex?: number;
|
|
73
|
+
/** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
|
|
74
|
+
groupIndex?: number;
|
|
52
75
|
constantColor?: [number, number, number, number];
|
|
53
76
|
inset?: number;
|
|
54
77
|
dx?: number;
|
|
@@ -73,11 +96,13 @@
|
|
|
73
96
|
y1Data,
|
|
74
97
|
colorValueData,
|
|
75
98
|
alphaData,
|
|
99
|
+
share = null,
|
|
76
100
|
facetData = null,
|
|
77
101
|
dataVersion,
|
|
78
102
|
zIndex = 0,
|
|
79
103
|
colorMode = 0,
|
|
80
104
|
colorScaleIndex = 0,
|
|
105
|
+
groupIndex = 0,
|
|
81
106
|
constantColor = DEFAULT_FILL_COLOR,
|
|
82
107
|
inset = 0,
|
|
83
108
|
dx = 0,
|
|
@@ -86,6 +111,11 @@
|
|
|
86
111
|
bandAxis = null
|
|
87
112
|
}: Props = $props();
|
|
88
113
|
|
|
114
|
+
const splitData = $derived(share?.data ?? null);
|
|
115
|
+
const splitSlides = $derived(share?.slides ?? true);
|
|
116
|
+
// Bars grouped on y run along x, so their split does too.
|
|
117
|
+
const splitAlongX = $derived(share?.axis === 'y');
|
|
118
|
+
const otherColorOnBand = $derived(share?.colorOnBand ?? false);
|
|
89
119
|
const morphX = $derived(bandAxis === 'y');
|
|
90
120
|
const morphY = $derived(bandAxis === 'x');
|
|
91
121
|
|
|
@@ -119,18 +149,21 @@
|
|
|
119
149
|
|
|
120
150
|
const rectVertexCount = 6;
|
|
121
151
|
|
|
152
|
+
/** The selection activities this shader reads; routing reads the same table. */
|
|
153
|
+
const ACTIVITY_READS = activityReads('rects');
|
|
154
|
+
|
|
122
155
|
const rectsShaderCode = `
|
|
123
156
|
// MarkConstants uniform for per-mark configuration
|
|
124
157
|
struct MarkConstants {
|
|
125
158
|
colorMode: u32, // 0 = constant, 1 = scale
|
|
126
159
|
colorScaleIndex: u32, // Index into colorConfigs
|
|
127
160
|
inset: f32, // Pixel inset on all sides
|
|
128
|
-
|
|
161
|
+
groupIndex: u32, // Page-wide filter group index, for activity reads
|
|
129
162
|
constantColor: vec4<f32>, // Used when colorMode = 0
|
|
130
163
|
dx: f32, // Horizontal pixel offset
|
|
131
164
|
dy: f32, // Vertical pixel offset
|
|
132
|
-
|
|
133
|
-
|
|
165
|
+
otherColorOnBand: u32, // 1: an Other row skips its colour activity too
|
|
166
|
+
splitAlongX: u32, // 1: an Other bar splits along x (horizontal bars)
|
|
134
167
|
};
|
|
135
168
|
@group(1) @binding(6) var<uniform> markConstants: MarkConstants;
|
|
136
169
|
|
|
@@ -138,6 +171,13 @@
|
|
|
138
171
|
@builtin(position) position: vec4<f32>,
|
|
139
172
|
@location(0) colorValue: f32, // Raw value for scale lookup
|
|
140
173
|
@location(1) alpha: f32,
|
|
174
|
+
// The Other bar's selected share, or -1 on every other row; and where this
|
|
175
|
+
// fragment sits along the bar, 0 at its base, 1 at its end.
|
|
176
|
+
@location(2) @interpolate(flat) share: f32,
|
|
177
|
+
@location(3) along: f32,
|
|
178
|
+
// An Other bar's unselected part: dimmed by the other fields' activity and
|
|
179
|
+
// by the field's free slot, so it fades as the rest of the chart does.
|
|
180
|
+
@location(4) alphaRest: f32,
|
|
141
181
|
};
|
|
142
182
|
|
|
143
183
|
@group(1) @binding(0) var<storage, read> x0Values: array<f32>;
|
|
@@ -146,6 +186,7 @@
|
|
|
146
186
|
@group(1) @binding(3) var<storage, read> y1Values: array<f32>;
|
|
147
187
|
@group(1) @binding(4) var<storage, read> colorValues: array<f32>;
|
|
148
188
|
@group(1) @binding(5) var<storage, read> alphaValues: array<f32>;
|
|
189
|
+
@group(1) @binding(7) var<storage, read> splitValues: array<f32>;
|
|
149
190
|
|
|
150
191
|
@vertex
|
|
151
192
|
fn vertexMain(
|
|
@@ -182,12 +223,47 @@
|
|
|
182
223
|
var output: VertexOutput;
|
|
183
224
|
output.position = vec4<f32>(normalizedPos.x, -normalizedPos.y, 0.0, 1.0);
|
|
184
225
|
output.colorValue = colorValue;
|
|
185
|
-
|
|
226
|
+
// An Other row (split > 0) is drawn in two parts, mixed in the fragment at
|
|
227
|
+
// its share. Below: as any bar, by its full activity, its own field's
|
|
228
|
+
// included, so it dims and lights with the bucket's lit state (#23). Above:
|
|
229
|
+
// by the other fields' activity and the field's free slot, so it is lit
|
|
230
|
+
// only while nothing is selected. Both fade on the selection clock, and the
|
|
231
|
+
// split only moves when the share moves between two parts that differ.
|
|
232
|
+
let split = splitValues[instanceIdx];
|
|
233
|
+
let isOther = split > 0.5;
|
|
234
|
+
output.share = select(-1.0, split - 1.0, isOther);
|
|
235
|
+
output.along = select(rectCorner.y, rectCorner.x, markConstants.splitAlongX == 1u);
|
|
236
|
+
let activity = ${markActivityWGSL(ACTIVITY_READS, {
|
|
237
|
+
color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValue',
|
|
238
|
+
x: 'x0',
|
|
239
|
+
y: 'y0',
|
|
240
|
+
group: 'markConstants.groupIndex'
|
|
241
|
+
})};
|
|
242
|
+
output.alpha = alpha * selectionDim(activity);
|
|
243
|
+
// The unselected part is drawn only on an Other row: every other bar skips
|
|
244
|
+
// its activity reads.
|
|
245
|
+
output.alphaRest = output.alpha;
|
|
246
|
+
if (isOther) {
|
|
247
|
+
let otherActivity = ${otherRowActivityWGSL(ACTIVITY_READS, {
|
|
248
|
+
color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValue',
|
|
249
|
+
group: 'markConstants.groupIndex',
|
|
250
|
+
skipColor: 'markConstants.otherColorOnBand == 1u'
|
|
251
|
+
})};
|
|
252
|
+
let free = select(
|
|
253
|
+
bandFreeActivityX(markConstants.groupIndex),
|
|
254
|
+
bandFreeActivityY(markConstants.groupIndex),
|
|
255
|
+
markConstants.splitAlongX == 1u
|
|
256
|
+
);
|
|
257
|
+
output.alphaRest = alpha * selectionDim(min(otherActivity, free));
|
|
258
|
+
}
|
|
186
259
|
return output;
|
|
187
260
|
}
|
|
188
261
|
|
|
189
262
|
@fragment
|
|
190
263
|
fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
264
|
+
// Before any branch: a derivative must be taken in uniform control flow.
|
|
265
|
+
let aa = max(fwidth(input.along), 1e-4);
|
|
266
|
+
|
|
191
267
|
// Get color based on mode
|
|
192
268
|
var baseColor: vec4<f32>;
|
|
193
269
|
if (markConstants.colorMode == 0u) {
|
|
@@ -202,7 +278,14 @@
|
|
|
202
278
|
);
|
|
203
279
|
}
|
|
204
280
|
|
|
205
|
-
|
|
281
|
+
var finalAlpha = baseColor.a * input.alpha;
|
|
282
|
+
// An Other bar's split: its own part up to the selected share, from its
|
|
283
|
+
// base, its unselected part above, with an anti-aliased edge. At share 1
|
|
284
|
+
// the whole bar is its own part, with no half-dimmed top row.
|
|
285
|
+
if (input.share >= 0.0 && input.share < 1.0) {
|
|
286
|
+
let below = 1.0 - smoothstep(input.share - aa, input.share + aa, input.along);
|
|
287
|
+
finalAlpha = baseColor.a * mix(input.alphaRest, input.alpha, below);
|
|
288
|
+
}
|
|
206
289
|
return vec4<f32>(baseColor.rgb * finalAlpha, finalAlpha);
|
|
207
290
|
}
|
|
208
291
|
`
|
|
@@ -233,6 +316,7 @@
|
|
|
233
316
|
{ binding: BINDINGS.Y1, type: 'read-only-storage', visibility: GPUShaderStage.VERTEX },
|
|
234
317
|
{ binding: BINDINGS.COLOR, type: 'read-only-storage', visibility: GPUShaderStage.VERTEX },
|
|
235
318
|
{ binding: BINDINGS.ALPHA, type: 'read-only-storage', visibility: GPUShaderStage.VERTEX },
|
|
319
|
+
{ binding: BINDINGS.SHARE, type: 'read-only-storage', visibility: GPUShaderStage.VERTEX },
|
|
236
320
|
{
|
|
237
321
|
binding: BINDINGS.CONSTANTS,
|
|
238
322
|
type: 'uniform',
|
|
@@ -251,16 +335,40 @@
|
|
|
251
335
|
// a fraction that the slot table resolves to a DIFFERENT category.
|
|
252
336
|
$effect(() => {
|
|
253
337
|
if (!renderer?.resourcesCreated || !bandAxis) return;
|
|
338
|
+
// The Other share travels with the bar's length, on the same clock, so the
|
|
339
|
+
// split and the bar land together. A share that changes alone (a click)
|
|
340
|
+
// starts a transition of its own on that clock.
|
|
341
|
+
const split = splitData ? ([[BINDINGS.SHARE, splitData]] as const) : [];
|
|
342
|
+
const land = splitData && !splitSlides ? [BINDINGS.SHARE] : undefined;
|
|
254
343
|
if (bandAxis === 'x') {
|
|
255
|
-
renderer.writeMorphed(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
344
|
+
renderer.writeMorphed(
|
|
345
|
+
'x',
|
|
346
|
+
x0Data,
|
|
347
|
+
[[BINDINGS.Y0, y0Data], [BINDINGS.Y1, y1Data], ...split],
|
|
348
|
+
land
|
|
349
|
+
);
|
|
259
350
|
} else {
|
|
260
|
-
renderer.writeMorphed(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
351
|
+
renderer.writeMorphed(
|
|
352
|
+
'y',
|
|
353
|
+
y0Data,
|
|
354
|
+
[[BINDINGS.X0, x0Data], [BINDINGS.X1, x1Data], ...split],
|
|
355
|
+
land
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
// Without a band axis the share is written as is. A mark that had shares and
|
|
360
|
+
// has none now writes one array of zeros, which also drops a morphed channel:
|
|
361
|
+
// left alone, the shader would keep reading the last shares. A mark that
|
|
362
|
+
// never had any writes nothing and reads the zero-filled stand-in.
|
|
363
|
+
let splitWritten = false;
|
|
364
|
+
$effect(() => {
|
|
365
|
+
if (!renderer?.resourcesCreated) return;
|
|
366
|
+
if (splitData) {
|
|
367
|
+
if (!bandAxis) renderer.writeAttribute(BINDINGS.SHARE, splitData);
|
|
368
|
+
splitWritten = true;
|
|
369
|
+
} else if (splitWritten) {
|
|
370
|
+
renderer.writeAttribute(BINDINGS.SHARE, new Float32Array(Math.max(1, x0Data.length)));
|
|
371
|
+
splitWritten = false;
|
|
264
372
|
}
|
|
265
373
|
});
|
|
266
374
|
$effect(() => {
|
|
@@ -295,21 +403,22 @@
|
|
|
295
403
|
// re-fires once data loads.
|
|
296
404
|
if (colorMode === 1 && !colorValueData) return;
|
|
297
405
|
|
|
298
|
-
const buffer = new ArrayBuffer(
|
|
406
|
+
const buffer = new ArrayBuffer(UNIFORM_BYTES.rectsMarkConstants);
|
|
299
407
|
const view = new DataView(buffer);
|
|
300
408
|
const LE = true;
|
|
301
409
|
|
|
302
410
|
view.setUint32(UNIFORM_OFFSETS.COLOR_MODE, colorMode, LE);
|
|
303
411
|
view.setUint32(UNIFORM_OFFSETS.COLOR_SCALE_INDEX, colorScaleIndex, LE);
|
|
412
|
+
view.setUint32(UNIFORM_OFFSETS.GROUP_INDEX, groupIndex, LE);
|
|
304
413
|
view.setFloat32(UNIFORM_OFFSETS.INSET, inset, LE);
|
|
305
|
-
// padding at 12
|
|
306
414
|
view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 0, constantColor[0], LE);
|
|
307
415
|
view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 4, constantColor[1], LE);
|
|
308
416
|
view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 8, constantColor[2], LE);
|
|
309
417
|
view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 12, constantColor[3], LE);
|
|
310
418
|
view.setFloat32(UNIFORM_OFFSETS.DX, dx, LE);
|
|
311
419
|
view.setFloat32(UNIFORM_OFFSETS.DY, dy, LE);
|
|
312
|
-
|
|
420
|
+
view.setUint32(UNIFORM_OFFSETS.OTHER_COLOR_ON_BAND, otherColorOnBand ? 1 : 0, LE);
|
|
421
|
+
view.setUint32(UNIFORM_OFFSETS.SPLIT_ALONG_X, splitAlongX ? 1 : 0, LE);
|
|
313
422
|
|
|
314
423
|
renderer.writeUniform(BINDINGS.CONSTANTS, new Uint8Array(buffer));
|
|
315
424
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SplitProp } from '../marks/lifecycle/other-rows.svelte';
|
|
1
2
|
interface Props {
|
|
2
3
|
x0Data: Float32Array;
|
|
3
4
|
x1Data: Float32Array;
|
|
@@ -5,11 +6,19 @@ interface Props {
|
|
|
5
6
|
y1Data: Float32Array;
|
|
6
7
|
colorValueData: Float32Array | null;
|
|
7
8
|
alphaData: Float32Array | null;
|
|
9
|
+
/**
|
|
10
|
+
* A bar's selected share per row (see BINDINGS.SHARE), or null for none: a
|
|
11
|
+
* bar mark with a topN Other bucket passes its lifecycle's `otherRows.split`.
|
|
12
|
+
* The fragment draws the bar above the share dimmed.
|
|
13
|
+
*/
|
|
14
|
+
share?: SplitProp | null;
|
|
8
15
|
facetData?: Float32Array | null;
|
|
9
16
|
dataVersion: number;
|
|
10
17
|
zIndex?: number;
|
|
11
18
|
colorMode?: number;
|
|
12
19
|
colorScaleIndex?: number;
|
|
20
|
+
/** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
|
|
21
|
+
groupIndex?: number;
|
|
13
22
|
constantColor?: [number, number, number, number];
|
|
14
23
|
inset?: number;
|
|
15
24
|
dx?: number;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { UNIFORM_BYTES } from './uniform-sizes';
|
|
2
3
|
import { getContext, onMount, onDestroy } from 'svelte';
|
|
3
4
|
import { RendererHost } from './RendererHost.svelte';
|
|
4
5
|
import { getGPUContext } from '../../gpu';
|
|
5
6
|
import { DEFAULT_FILL_COLOR } from '../../scales/color-utils';
|
|
6
7
|
import type { ChartContext, WebGPUCanvasContext } from '../../types';
|
|
8
|
+
import { activityReads, markActivityWGSL } from './activity-reads';
|
|
7
9
|
|
|
8
10
|
// Named Bindings — stable indices regardless of hasExtent
|
|
9
11
|
const BINDINGS = {
|
|
@@ -23,7 +25,7 @@
|
|
|
23
25
|
COLOR_MODE: 0,
|
|
24
26
|
COLOR_SCALE_INDEX: 4,
|
|
25
27
|
STROKE_WIDTH: 8,
|
|
26
|
-
|
|
28
|
+
GROUP_INDEX: 12,
|
|
27
29
|
CONSTANT_COLOR: 16,
|
|
28
30
|
_PAD1: 32,
|
|
29
31
|
_PAD2: 36,
|
|
@@ -42,6 +44,8 @@
|
|
|
42
44
|
zIndex?: number;
|
|
43
45
|
colorMode?: number;
|
|
44
46
|
colorScaleIndex?: number;
|
|
47
|
+
/** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
|
|
48
|
+
groupIndex?: number;
|
|
45
49
|
constantColor?: [number, number, number, number];
|
|
46
50
|
strokeWidth?: number;
|
|
47
51
|
markId?: string;
|
|
@@ -58,6 +62,7 @@
|
|
|
58
62
|
zIndex = 0,
|
|
59
63
|
colorMode = 0,
|
|
60
64
|
colorScaleIndex = 0,
|
|
65
|
+
groupIndex = 0,
|
|
61
66
|
constantColor = DEFAULT_FILL_COLOR,
|
|
62
67
|
strokeWidth = 1,
|
|
63
68
|
markId = ''
|
|
@@ -113,12 +118,15 @@
|
|
|
113
118
|
const initializationError = $derived(host.error);
|
|
114
119
|
let lineGeometryBuffer: GPUBuffer;
|
|
115
120
|
|
|
121
|
+
/** The selection activities this shader reads; routing reads the same table. */
|
|
122
|
+
const ACTIVITY_READS = activityReads('rulex', hasExtent);
|
|
123
|
+
|
|
116
124
|
const ruleXShaderCode = `
|
|
117
125
|
struct MarkConstants {
|
|
118
126
|
colorMode: u32,
|
|
119
127
|
colorScaleIndex: u32,
|
|
120
128
|
strokeWidth: f32,
|
|
121
|
-
|
|
129
|
+
groupIndex: u32,
|
|
122
130
|
constantColor: vec4<f32>,
|
|
123
131
|
_pad1: f32,
|
|
124
132
|
_pad2: f32,
|
|
@@ -173,7 +181,14 @@
|
|
|
173
181
|
var output: VertexOutput;
|
|
174
182
|
output.position = vec4<f32>(normalizedPos.x, -normalizedPos.y, 0.0, 1.0);
|
|
175
183
|
output.colorValue = colorValue;
|
|
176
|
-
output.alpha = alpha
|
|
184
|
+
output.alpha = alpha * selectionDim(
|
|
185
|
+
${markActivityWGSL(ACTIVITY_READS, {
|
|
186
|
+
color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValue',
|
|
187
|
+
x: 'x',
|
|
188
|
+
y: 'y0',
|
|
189
|
+
group: 'markConstants.groupIndex'
|
|
190
|
+
})}
|
|
191
|
+
);
|
|
177
192
|
return output;
|
|
178
193
|
}
|
|
179
194
|
|
|
@@ -266,14 +281,14 @@
|
|
|
266
281
|
if (renderer?.resourcesCreated) {
|
|
267
282
|
if (colorMode === 1 && !colorValueData) return;
|
|
268
283
|
|
|
269
|
-
const buffer = new ArrayBuffer(
|
|
284
|
+
const buffer = new ArrayBuffer(UNIFORM_BYTES.ruleXMarkConstants);
|
|
270
285
|
const view = new DataView(buffer);
|
|
271
286
|
const LE = true;
|
|
272
287
|
|
|
273
288
|
view.setUint32(UNIFORM_OFFSETS.COLOR_MODE, colorMode, LE);
|
|
274
289
|
view.setUint32(UNIFORM_OFFSETS.COLOR_SCALE_INDEX, colorScaleIndex, LE);
|
|
290
|
+
view.setUint32(UNIFORM_OFFSETS.GROUP_INDEX, groupIndex, LE);
|
|
275
291
|
view.setFloat32(UNIFORM_OFFSETS.STROKE_WIDTH, strokeWidth, LE);
|
|
276
|
-
// padding at 12
|
|
277
292
|
view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 0, constantColor[0], LE);
|
|
278
293
|
view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 4, constantColor[1], LE);
|
|
279
294
|
view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 8, constantColor[2], LE);
|
|
@@ -9,6 +9,8 @@ interface Props {
|
|
|
9
9
|
zIndex?: number;
|
|
10
10
|
colorMode?: number;
|
|
11
11
|
colorScaleIndex?: number;
|
|
12
|
+
/** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
|
|
13
|
+
groupIndex?: number;
|
|
12
14
|
constantColor?: [number, number, number, number];
|
|
13
15
|
strokeWidth?: number;
|
|
14
16
|
markId?: string;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { UNIFORM_BYTES } from './uniform-sizes';
|
|
2
3
|
import { getContext, onMount, onDestroy } from 'svelte';
|
|
3
4
|
import { RendererHost } from './RendererHost.svelte';
|
|
4
5
|
import { getGPUContext } from '../../gpu';
|
|
5
6
|
import { DEFAULT_FILL_COLOR } from '../../scales/color-utils';
|
|
6
7
|
import type { ChartContext, WebGPUCanvasContext } from '../../types';
|
|
8
|
+
import { activityReads, markActivityWGSL } from './activity-reads';
|
|
7
9
|
|
|
8
10
|
// Named Bindings — stable indices regardless of hasExtent
|
|
9
11
|
const BINDINGS = {
|
|
@@ -23,7 +25,7 @@
|
|
|
23
25
|
COLOR_MODE: 0,
|
|
24
26
|
COLOR_SCALE_INDEX: 4,
|
|
25
27
|
STROKE_WIDTH: 8,
|
|
26
|
-
|
|
28
|
+
GROUP_INDEX: 12,
|
|
27
29
|
CONSTANT_COLOR: 16,
|
|
28
30
|
_PAD1: 32,
|
|
29
31
|
_PAD2: 36,
|
|
@@ -42,6 +44,8 @@
|
|
|
42
44
|
zIndex?: number;
|
|
43
45
|
colorMode?: number;
|
|
44
46
|
colorScaleIndex?: number;
|
|
47
|
+
/** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
|
|
48
|
+
groupIndex?: number;
|
|
45
49
|
constantColor?: [number, number, number, number];
|
|
46
50
|
strokeWidth?: number;
|
|
47
51
|
markId?: string;
|
|
@@ -58,6 +62,7 @@
|
|
|
58
62
|
zIndex = 0,
|
|
59
63
|
colorMode = 0,
|
|
60
64
|
colorScaleIndex = 0,
|
|
65
|
+
groupIndex = 0,
|
|
61
66
|
constantColor = DEFAULT_FILL_COLOR,
|
|
62
67
|
strokeWidth = 1,
|
|
63
68
|
markId = ''
|
|
@@ -112,12 +117,15 @@
|
|
|
112
117
|
const initializationError = $derived(host.error);
|
|
113
118
|
let lineGeometryBuffer: GPUBuffer;
|
|
114
119
|
|
|
120
|
+
/** The selection activities this shader reads; routing reads the same table. */
|
|
121
|
+
const ACTIVITY_READS = activityReads('ruley', hasExtent);
|
|
122
|
+
|
|
115
123
|
const ruleYShaderCode = `
|
|
116
124
|
struct MarkConstants {
|
|
117
125
|
colorMode: u32,
|
|
118
126
|
colorScaleIndex: u32,
|
|
119
127
|
strokeWidth: f32,
|
|
120
|
-
|
|
128
|
+
groupIndex: u32,
|
|
121
129
|
constantColor: vec4<f32>,
|
|
122
130
|
_pad1: f32,
|
|
123
131
|
_pad2: f32,
|
|
@@ -172,7 +180,14 @@
|
|
|
172
180
|
var output: VertexOutput;
|
|
173
181
|
output.position = vec4<f32>(normalizedPos.x, -normalizedPos.y, 0.0, 1.0);
|
|
174
182
|
output.colorValue = colorValue;
|
|
175
|
-
output.alpha = alpha
|
|
183
|
+
output.alpha = alpha * selectionDim(
|
|
184
|
+
${markActivityWGSL(ACTIVITY_READS, {
|
|
185
|
+
color: 'markConstants.colorMode, markConstants.colorScaleIndex, colorValue',
|
|
186
|
+
x: 'x0',
|
|
187
|
+
y: 'y',
|
|
188
|
+
group: 'markConstants.groupIndex'
|
|
189
|
+
})}
|
|
190
|
+
);
|
|
176
191
|
return output;
|
|
177
192
|
}
|
|
178
193
|
|
|
@@ -265,14 +280,14 @@
|
|
|
265
280
|
if (renderer?.resourcesCreated) {
|
|
266
281
|
if (colorMode === 1 && !colorValueData) return;
|
|
267
282
|
|
|
268
|
-
const buffer = new ArrayBuffer(
|
|
283
|
+
const buffer = new ArrayBuffer(UNIFORM_BYTES.ruleYMarkConstants);
|
|
269
284
|
const view = new DataView(buffer);
|
|
270
285
|
const LE = true;
|
|
271
286
|
|
|
272
287
|
view.setUint32(UNIFORM_OFFSETS.COLOR_MODE, colorMode, LE);
|
|
273
288
|
view.setUint32(UNIFORM_OFFSETS.COLOR_SCALE_INDEX, colorScaleIndex, LE);
|
|
289
|
+
view.setUint32(UNIFORM_OFFSETS.GROUP_INDEX, groupIndex, LE);
|
|
274
290
|
view.setFloat32(UNIFORM_OFFSETS.STROKE_WIDTH, strokeWidth, LE);
|
|
275
|
-
// padding at 12
|
|
276
291
|
view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 0, constantColor[0], LE);
|
|
277
292
|
view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 4, constantColor[1], LE);
|
|
278
293
|
view.setFloat32(UNIFORM_OFFSETS.CONSTANT_COLOR + 8, constantColor[2], LE);
|
|
@@ -9,6 +9,8 @@ interface Props {
|
|
|
9
9
|
zIndex?: number;
|
|
10
10
|
colorMode?: number;
|
|
11
11
|
colorScaleIndex?: number;
|
|
12
|
+
/** The page-wide index of this mark's filter group: which run of a field's activity block it reads. */
|
|
13
|
+
groupIndex?: number;
|
|
12
14
|
constantColor?: [number, number, number, number];
|
|
13
15
|
strokeWidth?: number;
|
|
14
16
|
markId?: string;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which selection activities a mark reads: its ordinal colour, its band x,
|
|
3
|
+
* its band y. One answer per renderer, used twice:
|
|
4
|
+
*
|
|
5
|
+
* - The renderer builds its shader's `markActivity` call from it, so a read
|
|
6
|
+
* the table does not list is passed as 1.0 and cannot happen.
|
|
7
|
+
* - MarkLifecycleV2 turns it into the mark's `reads`, the identity keys of the
|
|
8
|
+
* fields it dims by, which decide whether a categorical selection on a field
|
|
9
|
+
* dims the mark or filters it.
|
|
10
|
+
*
|
|
11
|
+
* The two must agree exactly. A mark routed to dim on a field its shader does
|
|
12
|
+
* not read would be neither dimmed nor filtered; one whose shader reads a field
|
|
13
|
+
* routing missed would be filtered and dimmed at once. Deriving both from this
|
|
14
|
+
* table is what keeps them together. One case they still disagree on: a field
|
|
15
|
+
* whose activity block did not fit in the identity slot buffer (see the known
|
|
16
|
+
* limit in identity-slot-packing.ts).
|
|
17
|
+
*
|
|
18
|
+
* A function of the config, not a constant per renderer, because a rule's
|
|
19
|
+
* answer depends on a prop: a rule without an extent spans the whole cross
|
|
20
|
+
* axis, belongs to no category on it, and must not dim by it.
|
|
21
|
+
*/
|
|
22
|
+
import { NO_GROUP } from '../../gpu/identity-slot-packing';
|
|
23
|
+
/** The renderers that read selection activity. Text reads it on the CPU. */
|
|
24
|
+
export type ActivityRenderer = 'dots' | 'rects' | 'rulex' | 'ruley' | 'lines' | 'areas' | 'parallel' | 'text';
|
|
25
|
+
export interface ActivityReads {
|
|
26
|
+
readonly color: boolean;
|
|
27
|
+
readonly x: boolean;
|
|
28
|
+
readonly y: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* What a renderer reads. `hasExtent` is a rule's: RuleX with y1/y2, RuleY with
|
|
32
|
+
* x1/x2. Lines, areas and parallel coordinates dim by colour only: a line over
|
|
33
|
+
* a band axis spans its categories.
|
|
34
|
+
*
|
|
35
|
+
* Not yet supported: a line or area grouped by `z` that is not its colour
|
|
36
|
+
* field. Its series ids reach the shader, but only to break the line between
|
|
37
|
+
* series; no activity is read through them, and the series field is not among
|
|
38
|
+
* the mark's reads. So a selection on that field filters the mark in other
|
|
39
|
+
* charts and does nothing in its own, instead of dimming the other series.
|
|
40
|
+
*/
|
|
41
|
+
export declare function activityReads(renderer: ActivityRenderer, hasExtent?: boolean): ActivityReads;
|
|
42
|
+
/**
|
|
43
|
+
* Whether a rule spans an extent on its cross axis: both ends given, as a
|
|
44
|
+
* field or a constant. A rule mark builds its renderer's extent data from this
|
|
45
|
+
* and hands it to routing as `ruleExtent`, so the two cannot disagree; a
|
|
46
|
+
* constant end (y1={0}, or the y shorthand) counts like a field.
|
|
47
|
+
*/
|
|
48
|
+
export declare function ruleHasExtent(lo: unknown, hi: unknown): boolean;
|
|
49
|
+
/** The renderer a mark type draws with, or null for one that reads no activity. */
|
|
50
|
+
export declare function rendererForMark(markType: string): ActivityRenderer | null;
|
|
51
|
+
/**
|
|
52
|
+
* A mark's reads: the identity keys of the fields whose activity it reads,
|
|
53
|
+
* sorted, from what its renderer reads and the identity key of each of its
|
|
54
|
+
* categorical scales (null for a channel with no such scale, or a scale with
|
|
55
|
+
* no identity: a field with no identity has no activity to dim by, so a
|
|
56
|
+
* selection on it filters the mark).
|
|
57
|
+
*/
|
|
58
|
+
export declare function readsFor(reads: ActivityReads, keys: {
|
|
59
|
+
color: string | null;
|
|
60
|
+
x: string | null;
|
|
61
|
+
y: string | null;
|
|
62
|
+
}): readonly string[];
|
|
63
|
+
/** A mark that reads no activity. Shared, so its reads never look changed. */
|
|
64
|
+
export declare const NO_READS: readonly string[];
|
|
65
|
+
/**
|
|
66
|
+
* How a mark responds to selections in its filter group (the `selectionResponse`
|
|
67
|
+
* prop). 'auto', the default: a categorical selection on a field the mark draws
|
|
68
|
+
* by (its band axes, its ordinal colour) dims it, and any other selection
|
|
69
|
+
* filters it. 'filter': every selection filters it, even on a field it draws
|
|
70
|
+
* by, so its aggregates are recomputed for the subset (top N within a region).
|
|
71
|
+
*/
|
|
72
|
+
export type SelectionResponse = 'auto' | 'filter';
|
|
73
|
+
export { NO_GROUP };
|
|
74
|
+
/** A mark's reads under its selection response: none for 'filter', which never dims. */
|
|
75
|
+
export declare function readsForResponse(response: SelectionResponse | undefined, reads: readonly string[]): readonly string[];
|
|
76
|
+
/** The group index a mark's shader reads activity with: NO_GROUP for 'filter'. */
|
|
77
|
+
export declare function groupIndexForResponse(response: SelectionResponse | undefined, groupIndex: number): number;
|
|
78
|
+
/**
|
|
79
|
+
* The WGSL activity of a renderer that reads colour only (lines, areas,
|
|
80
|
+
* parallel coordinates): `markColorActivity(...)`, or 1.0 if the table says
|
|
81
|
+
* it reads nothing. Throws for a renderer that reads a band axis, whose call
|
|
82
|
+
* is markActivityWGSL.
|
|
83
|
+
*/
|
|
84
|
+
export declare function colorActivityWGSL(reads: ActivityReads, args: {
|
|
85
|
+
color: string;
|
|
86
|
+
group: string;
|
|
87
|
+
}): string;
|
|
88
|
+
/**
|
|
89
|
+
* The WGSL `markActivity(...)` call for `reads`: each activity the renderer
|
|
90
|
+
* reads, and 1.0 for each it does not. `color` is markColorActivity's first
|
|
91
|
+
* three arguments; `x` and `y` are the band channel values; `group` the mark's
|
|
92
|
+
* group index.
|
|
93
|
+
*/
|
|
94
|
+
/**
|
|
95
|
+
* The activity of an Other row: the same terms as markActivityWGSL without the
|
|
96
|
+
* band axes', and without colour's when colour is the band's own field
|
|
97
|
+
* (`skipColor`, a WGSL bool). Those read the Other bucket's own fade, and the
|
|
98
|
+
* bar's split already shows how much of it is selected (Rects); multiplying
|
|
99
|
+
* them in as well would dim it twice. Activity on any other field stays.
|
|
100
|
+
*/
|
|
101
|
+
export declare function otherRowActivityWGSL(reads: ActivityReads, args: {
|
|
102
|
+
color: string;
|
|
103
|
+
group: string;
|
|
104
|
+
skipColor: string;
|
|
105
|
+
}): string;
|
|
106
|
+
export declare function markActivityWGSL(reads: ActivityReads, args: {
|
|
107
|
+
color: string;
|
|
108
|
+
x: string;
|
|
109
|
+
y: string;
|
|
110
|
+
group: string;
|
|
111
|
+
}): string;
|