@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,6 +1,39 @@
|
|
|
1
1
|
import { RESOLVE_IMMEDIATE_SIZE, RESOLVE_WORKGROUP, resolvePipelineFor } from './ResolveShader';
|
|
2
2
|
import { TransitionClock } from '../animation/TransitionClock';
|
|
3
3
|
import { immediatesSupported } from './immediates';
|
|
4
|
+
/**
|
|
5
|
+
* Channels of one mark that morph between data updates rather than replacing
|
|
6
|
+
* outright.
|
|
7
|
+
*
|
|
8
|
+
* Each holds three buffers where a static channel holds one: `from` and `to` are
|
|
9
|
+
* read by a compute pass, and `resolved = mix(from, to, t)` is what the vertex
|
|
10
|
+
* stage binds — in place of the static buffer, not in addition to it. So
|
|
11
|
+
* animating a channel costs the vertex stage nothing, which is what makes it
|
|
12
|
+
* affordable on a mark already at the device's storage-buffer limit.
|
|
13
|
+
*
|
|
14
|
+
* THE FOLD. When a transition restarts, whatever is on screen becomes the new
|
|
15
|
+
* `from`. On screen is the resolved buffer as last dispatched, so the fold is a
|
|
16
|
+
* buffer copy, and it reads neither `from` nor `to` — which is what lets data
|
|
17
|
+
* land at any moment, in any order relative to anything else. Writing it as
|
|
18
|
+
* `from = mix(from, to, t)` would need the OLD `to`, making the order of two
|
|
19
|
+
* writes load-bearing and silently wrong if they ever swap.
|
|
20
|
+
*
|
|
21
|
+
* THE SNAP. Some updates have nothing to travel from: the first one, a buffer
|
|
22
|
+
* that had to be reallocated, and a row set that is not the one being animated.
|
|
23
|
+
* Those copy `to` over `from`, which makes the mix the identity for every t.
|
|
24
|
+
*
|
|
25
|
+
* THE CLOCK. A mark ADOPTS the band scale's clock when it has one, so a bar's
|
|
26
|
+
* height travels on the same number as its slot — two clocks with two velocities
|
|
27
|
+
* desynchronise on a retarget, and the bar reaches its rank before or after it
|
|
28
|
+
* finishes growing. It OWNS one otherwise. Adoption is safe across marks because
|
|
29
|
+
* a mark with no new data folds to where it already is and does not move.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* The coordinator's slice this needs. Structural, so tests can supply a fake.
|
|
33
|
+
* It is also the loop an owned clock joins — the clock does that itself.
|
|
34
|
+
*/
|
|
35
|
+
/** No bindings: the default for `write`'s `land`. */
|
|
36
|
+
const NO_BINDINGS = [];
|
|
4
37
|
/**
|
|
5
38
|
* A cheap fingerprint of the band ids, per update.
|
|
6
39
|
*
|
|
@@ -87,8 +120,13 @@ export class MorphedChannels {
|
|
|
87
120
|
*
|
|
88
121
|
* All channels in one call, so one update starts one transition. Returns
|
|
89
122
|
* whether any buffer was (re)allocated, which the caller must rebind.
|
|
123
|
+
*
|
|
124
|
+
* `land`: bindings whose new values take effect at once while the rest
|
|
125
|
+
* travel. For a channel whose change is shown some other way at that moment
|
|
126
|
+
* (an Other bar's split, when both sides of it look the same): sliding it
|
|
127
|
+
* would draw a movement nothing asked for.
|
|
90
128
|
*/
|
|
91
|
-
write(rowIds, channels) {
|
|
129
|
+
write(rowIds, channels, land = NO_BINDINGS) {
|
|
92
130
|
const key = alignmentKey(rowIds);
|
|
93
131
|
let travels = this.#alignmentKey === key;
|
|
94
132
|
this.#alignmentKey = key;
|
|
@@ -100,6 +138,7 @@ export class MorphedChannels {
|
|
|
100
138
|
reallocated = true;
|
|
101
139
|
}
|
|
102
140
|
channel.count = data.byteLength >> 2;
|
|
141
|
+
channel.land = land.includes(binding);
|
|
103
142
|
this.#device.queue.writeBuffer(channel.to, 0, data);
|
|
104
143
|
}
|
|
105
144
|
// A fresh buffer holds nothing that was ever on screen, so there is
|
|
@@ -158,8 +197,10 @@ export class MorphedChannels {
|
|
|
158
197
|
return;
|
|
159
198
|
for (let i = 0; i < channels.length; i++) {
|
|
160
199
|
const channel = channels[i];
|
|
161
|
-
if (snap) {
|
|
200
|
+
if (snap || channel.land) {
|
|
201
|
+
// A landed channel: from = to, so the mix is its new value at any t.
|
|
162
202
|
encoder.copyBufferToBuffer(channel.to, 0, channel.from, 0, channel.size);
|
|
203
|
+
channel.land = false;
|
|
163
204
|
}
|
|
164
205
|
else if (this.#foldPending) {
|
|
165
206
|
// A transition interrupted mid-flight continues from where it is,
|
|
@@ -225,7 +266,8 @@ export class MorphedChannels {
|
|
|
225
266
|
resolved: make('resolved'),
|
|
226
267
|
size,
|
|
227
268
|
count: 0,
|
|
228
|
-
bindGroup: null
|
|
269
|
+
bindGroup: null,
|
|
270
|
+
land: false
|
|
229
271
|
};
|
|
230
272
|
this.#channels.set(binding, channel);
|
|
231
273
|
this.#channelList = [...this.#channels.values()];
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* are bound to the compute pipeline; the render stages bind `resolved` alone.
|
|
7
7
|
* So an animated channel REPLACES a vertex binding rather than adding one, and
|
|
8
8
|
* animating any number of channels leaves that stage's budget where it was —
|
|
9
|
-
* which is what makes this affordable on a mark that already
|
|
10
|
-
* default
|
|
9
|
+
* which is what makes this affordable on a mark that already uses 7 of the
|
|
10
|
+
* default 8 storage buffers (Rects).
|
|
11
11
|
*
|
|
12
12
|
* ONE KERNEL FOR BOTH, because the two differ only in where a range starts. A
|
|
13
13
|
* value channel is one mark's rows in buffers of their own, so it starts at 0.
|
|
@@ -7,8 +7,8 @@ import { isNanExpr } from './NanShaderLib';
|
|
|
7
7
|
* are bound to the compute pipeline; the render stages bind `resolved` alone.
|
|
8
8
|
* So an animated channel REPLACES a vertex binding rather than adding one, and
|
|
9
9
|
* animating any number of channels leaves that stage's budget where it was —
|
|
10
|
-
* which is what makes this affordable on a mark that already
|
|
11
|
-
* default
|
|
10
|
+
* which is what makes this affordable on a mark that already uses 7 of the
|
|
11
|
+
* default 8 storage buffers (Rects).
|
|
12
12
|
*
|
|
13
13
|
* ONE KERNEL FOR BOTH, because the two differ only in where a range starts. A
|
|
14
14
|
* value channel is one mark's rows in buffers of their own, so it starts at 0.
|
|
@@ -36,6 +36,9 @@ interface ShaderResult {
|
|
|
36
36
|
* `ScaleParam`, 16 bytes each. Every layout declares the same size because a
|
|
37
37
|
* draw requires every declared byte to have been set, and the pass sets them
|
|
38
38
|
* once, before its first draw.
|
|
39
|
+
*
|
|
40
|
+
* Immediates carry state shared across the pass and read by several marks: the
|
|
41
|
+
* chart's scales and settings. 64 bytes are available on M-series Macs.
|
|
39
42
|
*/
|
|
40
43
|
export declare const CHART_SCALE_IMMEDIATE_SIZE = 32;
|
|
41
44
|
/**
|
|
@@ -66,6 +69,22 @@ export declare class ScaleTemplateSystem {
|
|
|
66
69
|
* @private
|
|
67
70
|
*/
|
|
68
71
|
_buildDeclarations(xFragment: ScaleFragment, yFragment: ScaleFragment): string;
|
|
72
|
+
/**
|
|
73
|
+
* `bandActivityX(value, group)` and `bandActivityY(value, group)`: how
|
|
74
|
+
* selected a band or point axis's category is in the mark's filter group,
|
|
75
|
+
* 0..1, for selection dimming.
|
|
76
|
+
*
|
|
77
|
+
* Emitted for every shader, so a renderer can call both without knowing its
|
|
78
|
+
* scale types: an axis that is not band or point returns 1.0. On a band or
|
|
79
|
+
* point axis the value is an identity id and the chart's scale carries its
|
|
80
|
+
* block base in p4, as for the slot lookup. A missing category (-1) or a NaN
|
|
81
|
+
* is fully active, checked before the cast, since u32() of a negative or NaN
|
|
82
|
+
* float is indeterminate in WGSL.
|
|
83
|
+
*/
|
|
84
|
+
_buildBandActivityHelpers(scaleTypes: {
|
|
85
|
+
x: string;
|
|
86
|
+
y: string;
|
|
87
|
+
}): string;
|
|
69
88
|
/**
|
|
70
89
|
* The shared slot table, and the lookup into it.
|
|
71
90
|
*
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// src/lib/gpu/ScaleTemplateSystem.ts
|
|
2
2
|
import { COLOR_SCALE_STRUCTS, COLOR_SCALE_BINDINGS, COLOR_SCALE_FUNCTIONS } from './ColorScaleShaderLib';
|
|
3
3
|
import { SIZE_SCALE_STRUCTS, SIZE_SCALE_BINDINGS, SIZE_SCALE_FUNCTIONS } from './SizeScaleShaderLib';
|
|
4
|
+
import { NO_GROUP } from './identity-slot-packing';
|
|
5
|
+
import { isNanExpr } from './NanShaderLib';
|
|
6
|
+
import { DEFAULT_INACTIVE_OPACITY } from '../scales/ActivityTable';
|
|
4
7
|
/**
|
|
5
8
|
* Scale fragment definitions
|
|
6
9
|
* Each scale type provides: declaration, function call, and implementation
|
|
@@ -175,10 +178,10 @@ const SCALE_FRAGMENTS = {
|
|
|
175
178
|
rangeMax
|
|
176
179
|
);
|
|
177
180
|
|
|
178
|
-
// Return center of band
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
181
|
+
// Return center of band, slot 0 at the start of the range on both
|
|
182
|
+
// axes: the left on x, the top on y, so a y band axis reads top-down
|
|
183
|
+
// like a list and its exit (one past the last slot) is the list's end.
|
|
184
|
+
return rangeMin + bandLayout.x + bandLayout.y * 0.5;
|
|
182
185
|
}
|
|
183
186
|
`,
|
|
184
187
|
bufferLayout: {
|
|
@@ -280,6 +283,9 @@ function _scaleFunctionName(scaleType, axis) {
|
|
|
280
283
|
* `ScaleParam`, 16 bytes each. Every layout declares the same size because a
|
|
281
284
|
* draw requires every declared byte to have been set, and the pass sets them
|
|
282
285
|
* once, before its first draw.
|
|
286
|
+
*
|
|
287
|
+
* Immediates carry state shared across the pass and read by several marks: the
|
|
288
|
+
* chart's scales and settings. 64 bytes are available on M-series Macs.
|
|
283
289
|
*/
|
|
284
290
|
export const CHART_SCALE_IMMEDIATE_SIZE = 32;
|
|
285
291
|
/**
|
|
@@ -370,6 +376,7 @@ export class ScaleTemplateSystem {
|
|
|
370
376
|
// ===== 3. HELPER FUNCTIONS =====
|
|
371
377
|
|
|
372
378
|
${this._buildSlotTableHelpers()}
|
|
379
|
+
${this._buildBandActivityHelpers(scaleTypes)}
|
|
373
380
|
${bandHelpers}
|
|
374
381
|
${implementations}
|
|
375
382
|
${scaleFunctions}
|
|
@@ -409,6 +416,24 @@ export class ScaleTemplateSystem {
|
|
|
409
416
|
};
|
|
410
417
|
var<immediate> chartScales: ChartScales;
|
|
411
418
|
|
|
419
|
+
// Selection dimming: a mark's activity mapped to an opacity, with
|
|
420
|
+
// inactive categories at DEFAULT_INACTIVE_OPACITY. selectionOpacity in
|
|
421
|
+
// ActivityTable.ts is its CPU twin, for band labels and legend entries,
|
|
422
|
+
// and both read the same constant so they cannot drift apart.
|
|
423
|
+
const SELECTION_DIM_STRENGTH: f32 = ${(1 - DEFAULT_INACTIVE_OPACITY).toFixed(6)};
|
|
424
|
+
fn selectionDim(activity: f32) -> f32 {
|
|
425
|
+
return 1.0 - SELECTION_DIM_STRENGTH * (1.0 - activity);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// A mark's activities combined: colour, and band for marks on band axes.
|
|
429
|
+
// The least active wins rather than the product, so a bar whose colour
|
|
430
|
+
// and band are the same field fades on the same curve as its label
|
|
431
|
+
// instead of twice as fast. At rest every activity is 0 or 1, where the
|
|
432
|
+
// two agree.
|
|
433
|
+
fn markActivity(color: f32, bandX: f32, bandY: f32) -> f32 {
|
|
434
|
+
return min(color, min(bandX, bandY));
|
|
435
|
+
}
|
|
436
|
+
|
|
412
437
|
// The ONE place that says where a chart's scales come from. Every
|
|
413
438
|
// consumer reads through these — scaleX/scaleY, the bandwidth helpers,
|
|
414
439
|
// and mark code that needs the raw params. A mark with scales of its own
|
|
@@ -423,6 +448,45 @@ export class ScaleTemplateSystem {
|
|
|
423
448
|
`;
|
|
424
449
|
return `${structDef}\n${chartScaleDeclaration}`;
|
|
425
450
|
}
|
|
451
|
+
/**
|
|
452
|
+
* `bandActivityX(value, group)` and `bandActivityY(value, group)`: how
|
|
453
|
+
* selected a band or point axis's category is in the mark's filter group,
|
|
454
|
+
* 0..1, for selection dimming.
|
|
455
|
+
*
|
|
456
|
+
* Emitted for every shader, so a renderer can call both without knowing its
|
|
457
|
+
* scale types: an axis that is not band or point returns 1.0. On a band or
|
|
458
|
+
* point axis the value is an identity id and the chart's scale carries its
|
|
459
|
+
* block base in p4, as for the slot lookup. A missing category (-1) or a NaN
|
|
460
|
+
* is fully active, checked before the cast, since u32() of a negative or NaN
|
|
461
|
+
* float is indeterminate in WGSL.
|
|
462
|
+
*/
|
|
463
|
+
_buildBandActivityHelpers(scaleTypes) {
|
|
464
|
+
const helper = (axis, type) => {
|
|
465
|
+
if (type !== 'band' && type !== 'point') {
|
|
466
|
+
return `fn bandActivity${axis}(value: f32, group: u32) -> f32 {
|
|
467
|
+
return 1.0;
|
|
468
|
+
}
|
|
469
|
+
fn bandFreeActivity${axis}(group: u32) -> f32 {
|
|
470
|
+
return 1.0;
|
|
471
|
+
}`;
|
|
472
|
+
}
|
|
473
|
+
return `fn bandActivity${axis}(value: f32, group: u32) -> f32 {
|
|
474
|
+
if (value < 0.0 || ${isNanExpr('value')}) {
|
|
475
|
+
return 1.0;
|
|
476
|
+
}
|
|
477
|
+
return identityActivity(u32(chartScale${axis}().p4), group, u32(value));
|
|
478
|
+
}
|
|
479
|
+
fn bandFreeActivity${axis}(group: u32) -> f32 {
|
|
480
|
+
return identityFreeActivity(u32(chartScale${axis}().p4), group);
|
|
481
|
+
}`;
|
|
482
|
+
};
|
|
483
|
+
return `
|
|
484
|
+
// ===== BAND SELECTION ACTIVITY =====
|
|
485
|
+
${helper('X', scaleTypes.x)}
|
|
486
|
+
|
|
487
|
+
${helper('Y', scaleTypes.y)}
|
|
488
|
+
`;
|
|
489
|
+
}
|
|
426
490
|
/**
|
|
427
491
|
* The shared slot table, and the lookup into it.
|
|
428
492
|
*
|
|
@@ -459,12 +523,12 @@ export class ScaleTemplateSystem {
|
|
|
459
523
|
}
|
|
460
524
|
|
|
461
525
|
fn identitySlot(base: u32, id: u32) -> f32 {
|
|
462
|
-
// Block layout: [count, slot0, slot1, ...]. The count is what
|
|
463
|
-
// block self-describing, so an id past this scale's end is
|
|
464
|
-
// instead of reading into the next scale's slots. It survives
|
|
465
|
-
// exactly because it holds the same value in both halves, and
|
|
466
|
-
// returns equal ends as they are — u32() truncates, so an
|
|
467
|
-
// would drop the last id.
|
|
526
|
+
// Block layout: [count, actRef, n, slot0, slot1, ...]. The count is what
|
|
527
|
+
// makes a block self-describing, so an id past this scale's end is
|
|
528
|
+
// discarded instead of reading into the next scale's slots. It survives
|
|
529
|
+
// the resolve exactly because it holds the same value in both halves, and
|
|
530
|
+
// the kernel returns equal ends as they are — u32() truncates, so an
|
|
531
|
+
// inexact mix would drop the last id.
|
|
468
532
|
let count = u32(identitySlots[base]);
|
|
469
533
|
if (id >= count) {
|
|
470
534
|
return -1.0;
|
|
@@ -473,7 +537,43 @@ export class ScaleTemplateSystem {
|
|
|
473
537
|
// both halves, so the resolve wrote -1 without a branch; one that is
|
|
474
538
|
// leaving holds a real slot in both, which is what lets it travel. The
|
|
475
539
|
// scale normalises that before packing, so nothing here re-derives it.
|
|
476
|
-
return identitySlots[base +
|
|
540
|
+
return identitySlots[base + 3u + id];
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
fn identityActivity(base: u32, group: u32, id: u32) -> f32 {
|
|
544
|
+
// How selected this id is in the mark's filter group, 1 active and 0
|
|
545
|
+
// inactive, already resolved at that group's selection clock. The scale
|
|
546
|
+
// block's header points at its field's activity block (actRef) and says
|
|
547
|
+
// how many ids it covers (n); the block holds one run of n per group.
|
|
548
|
+
// Header first, so this is two dependent reads. Past n, or in the empty
|
|
549
|
+
// block where n is 0, an id is fully active.
|
|
550
|
+
//
|
|
551
|
+
// NO_GROUP (a mark whose selectionResponse is 'filter') is fully active,
|
|
552
|
+
// checked before anything is read, so it never becomes an offset.
|
|
553
|
+
if (group == ${NO_GROUP}u) {
|
|
554
|
+
return 1.0;
|
|
555
|
+
}
|
|
556
|
+
let actRef = u32(identitySlots[base + 1u]);
|
|
557
|
+
let n = u32(identitySlots[base + 2u]);
|
|
558
|
+
if (id >= n) {
|
|
559
|
+
return 1.0;
|
|
560
|
+
}
|
|
561
|
+
return identitySlots[actRef + n * group + id];
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
fn identityFreeActivity(base: u32, group: u32) -> f32 {
|
|
565
|
+
// The last slot of the group's run, past every id: 1 while the group
|
|
566
|
+
// selects nothing on the field, 0 while it selects anything, at the
|
|
567
|
+
// selection clock (FieldActivity's FREE SLOT). 1 without a block.
|
|
568
|
+
if (group == ${NO_GROUP}u) {
|
|
569
|
+
return 1.0;
|
|
570
|
+
}
|
|
571
|
+
let actRef = u32(identitySlots[base + 1u]);
|
|
572
|
+
let n = u32(identitySlots[base + 2u]);
|
|
573
|
+
if (n == 0u) {
|
|
574
|
+
return 1.0;
|
|
575
|
+
}
|
|
576
|
+
return identitySlots[actRef + n * group + n - 1u];
|
|
477
577
|
}
|
|
478
578
|
`;
|
|
479
579
|
}
|
|
@@ -19,10 +19,10 @@ export declare const MAX_SIZE_SLOTS = 64;
|
|
|
19
19
|
* changes rather than per frame. Every property that makes a uniform buffer the
|
|
20
20
|
* right container was already true of it.
|
|
21
21
|
*
|
|
22
|
-
* What it buys: the vertex stage
|
|
23
|
-
* maxStorageBuffersPerShaderStage of 8
|
|
24
|
-
*
|
|
25
|
-
*
|
|
22
|
+
* What it buys: a slot in the vertex stage's storage budget, the default
|
|
23
|
+
* maxStorageBuffersPerShaderStage of 8, of which Rects uses 7 (six mark
|
|
24
|
+
* channels and the identity slot table). Uniform buffers are nowhere near
|
|
25
|
+
* their own default of 12.
|
|
26
26
|
*
|
|
27
27
|
* Dynamic indexing — `sizeConfigs[scaleIndex]` — is permitted on uniform arrays
|
|
28
28
|
* in WGSL, and the shared slot table already relies on the same thing.
|
|
@@ -28,10 +28,10 @@ export const MAX_SIZE_SLOTS = 64;
|
|
|
28
28
|
* changes rather than per frame. Every property that makes a uniform buffer the
|
|
29
29
|
* right container was already true of it.
|
|
30
30
|
*
|
|
31
|
-
* What it buys: the vertex stage
|
|
32
|
-
* maxStorageBuffersPerShaderStage of 8
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* What it buys: a slot in the vertex stage's storage budget, the default
|
|
32
|
+
* maxStorageBuffersPerShaderStage of 8, of which Rects uses 7 (six mark
|
|
33
|
+
* channels and the identity slot table). Uniform buffers are nowhere near
|
|
34
|
+
* their own default of 12.
|
|
35
35
|
*
|
|
36
36
|
* Dynamic indexing — `sizeConfigs[scaleIndex]` — is permitted on uniform arrays
|
|
37
37
|
* in WGSL, and the shared slot table already relies on the same thing.
|
|
@@ -18,12 +18,45 @@
|
|
|
18
18
|
* on it. It is a pure function over plain arrays and maps, which is
|
|
19
19
|
* what it should have been.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
21
|
+
* LAYOUT. Index 0 is a permanent empty block `[0, 0, 0]`. Then one SLOT
|
|
22
|
+
* block per scale, then one ACTIVITY block per live categorical field:
|
|
23
|
+
*
|
|
24
|
+
* scale block: [count, actRef, n, slot0 … slot(count-1)]
|
|
25
|
+
* field block: [group 0: act0 … act(n-1), group 1: act0 … act(n-1), …]
|
|
26
|
+
*
|
|
27
|
+
* Slots come first so that when the buffer runs out it is activity that is
|
|
28
|
+
* lost (a mark draws undimmed), never an axis. A field has a block while a
|
|
29
|
+
* scale draws it, selected on or not, and a field no chart draws any more
|
|
30
|
+
* frees its space. Tying the block to the selection instead saved that space
|
|
31
|
+
* on pages with nothing selected, but every select and clear then moved the
|
|
32
|
+
* block and rewrote every scale block pointing at it, slots included:
|
|
33
|
+
* measured at about 105000 floats uploaded per click for 20 scales on a
|
|
34
|
+
* 5000-category field, against 5000 when the block stays.
|
|
35
|
+
*
|
|
36
|
+
* KNOWN LIMIT: a field block that does not fit leaves a selection on that
|
|
37
|
+
* field doing nothing for the marks that dim by it. Routing (the mark's reads,
|
|
38
|
+
* see activity-reads.ts) still sends them to dim rather than filter, and their
|
|
39
|
+
* shader finds no activity. Routing does not ask the packer whether a field
|
|
40
|
+
* got a block, which would tie the query layer to the GPU layout; the packer
|
|
41
|
+
* warns instead, on load. It takes fields of tens of thousands of ids across
|
|
42
|
+
* several groups to reach. A buffer that grows on demand is the real fix.
|
|
43
|
+
*
|
|
44
|
+
* A scale block's header says how many ids its slots cover (`count`), where its
|
|
45
|
+
* field's activity block starts (`actRef`) and how many ids that block covers
|
|
46
|
+
* (`n`). The count is what makes a block self-describing, so the shader rejects
|
|
47
|
+
* an id past this scale's end instead of reading into the next scale's slots —
|
|
48
|
+
* ids and the slot table can disagree transiently while a dim appends.
|
|
49
|
+
*
|
|
50
|
+
* ACTIVITY says how selected each id is in each filter group (1 active, 0
|
|
51
|
+
* inactive; see FieldActivity). It belongs to the field, not to the scale:
|
|
52
|
+
* every scale on one field reads the same block, a mark choosing its group's
|
|
53
|
+
* half by its group index. So a click rewrites and resolves one half of one
|
|
54
|
+
* field block, however many scales draw that field. The header comes first so
|
|
55
|
+
* the shader finds `actRef` and `n` without reading `count`: the activity read
|
|
56
|
+
* is two dependent loads, as it was when the activity sat after the slots. No
|
|
57
|
+
* mark holds `actRef`; the packer writes it in the same pass that moves the
|
|
58
|
+
* field block, so it cannot go stale. A scale with no field to dim points at
|
|
59
|
+
* the empty block, where `n` is 0 and every id is active.
|
|
27
60
|
*
|
|
28
61
|
* TWO buffers share one set of offsets: `from` is where each id's current
|
|
29
62
|
* transition started and `to` where it is going, and the resolve kernel writes
|
|
@@ -31,28 +64,43 @@
|
|
|
31
64
|
* bindings rather than two runs in one buffer, so the halves line up at the
|
|
32
65
|
* same base and one dispatch per block covers both.
|
|
33
66
|
*
|
|
34
|
-
* `t` is not in the buffers. It is
|
|
35
|
-
*
|
|
36
|
-
* a frame where only `t` moved uploads nothing
|
|
67
|
+
* `t` is not in the buffers. It is per scale (its data clock) and per field
|
|
68
|
+
* half (that group's selection clock), and reaches the kernel as an immediate
|
|
69
|
+
* on that range's dispatch, so a frame where only `t` moved uploads nothing.
|
|
37
70
|
*/
|
|
38
71
|
/**
|
|
39
72
|
* Floats GLOBALLY across every band and point scale on the page, headers
|
|
40
|
-
* included.
|
|
73
|
+
* included, and every live field's activity.
|
|
41
74
|
*
|
|
42
75
|
* No longer a binding limit: the tables are storage buffers, bound only to the
|
|
43
|
-
* resolve pass, and the render stages read one resolved storage buffer.
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
76
|
+
* resolve pass, and the render stages read one resolved storage buffer. Making
|
|
77
|
+
* the allocation grow on demand (recreate the buffers, rebuild the bind groups)
|
|
78
|
+
* is what would retire it.
|
|
79
|
+
*
|
|
80
|
+
* Each scale takes `3 + count`; each live field `n × groups`. 65536 fits a
|
|
81
|
+
* 5000-category field selected on across 3 groups (15000) with its scales
|
|
82
|
+
* (about 5000 each) several times over. It costs 256 KB per buffer, 768 KB for
|
|
83
|
+
* the three; the resolve pass dispatches per block, sized by the block, so a
|
|
84
|
+
* larger buffer costs no GPU time.
|
|
47
85
|
*/
|
|
48
|
-
export declare const IDENTITY_SLOT_CAPACITY =
|
|
86
|
+
export declare const IDENTITY_SLOT_CAPACITY = 65536;
|
|
49
87
|
/**
|
|
50
|
-
* Base of the permanent
|
|
51
|
-
* here, so every lookup finds count 0 and discards
|
|
52
|
-
*
|
|
53
|
-
* rendering, from another scale's slots.
|
|
88
|
+
* Base of the permanent empty block `[0, 0, 0]`. A scale that cannot be placed
|
|
89
|
+
* points here, so every lookup finds count 0 and discards, and every activity
|
|
90
|
+
* read finds n 0 and is fully active. Pointing it at a stale base instead left
|
|
91
|
+
* the scale rendering, from another scale's slots.
|
|
54
92
|
*/
|
|
55
93
|
export declare const IDENTITY_SLOT_EMPTY = 0;
|
|
94
|
+
/**
|
|
95
|
+
* The group index of a mark that reads no selection activity: identityActivity
|
|
96
|
+
* returns fully active for it before computing any offset, so it never indexes
|
|
97
|
+
* past a field block. A mark whose selectionResponse is 'filter' gets it: in
|
|
98
|
+
* the chart holding a selection's control it keeps its data (the chart skips
|
|
99
|
+
* its own selection), and this keeps it from dimming there too.
|
|
100
|
+
*/
|
|
101
|
+
export declare const NO_GROUP = 4294967295;
|
|
102
|
+
/** Floats before a scale block's slots: count, actRef, n. */
|
|
103
|
+
export declare const IDENTITY_SLOT_HEADER = 3;
|
|
56
104
|
/**
|
|
57
105
|
* One scale's slot table as the packer reads it: identity id -> display slot,
|
|
58
106
|
* as the two ends of a transition and where between them it is.
|
|
@@ -73,17 +121,51 @@ export interface SlotSource {
|
|
|
73
121
|
/** Bumped whenever `from` or `to` move; the packer re-uploads on it alone. */
|
|
74
122
|
readonly version: number;
|
|
75
123
|
}
|
|
76
|
-
/**
|
|
124
|
+
/**
|
|
125
|
+
* One group's activity on one field as the packer reads it: 1 active, 0
|
|
126
|
+
* inactive, as the two ends of a fade. ActivityTable has this shape.
|
|
127
|
+
*
|
|
128
|
+
* Its length is not trusted: the packer takes `n` from the field and pads with
|
|
129
|
+
* 1 or truncates, so a moment where the two disagree cannot shift the half onto
|
|
130
|
+
* the wrong ids.
|
|
131
|
+
*/
|
|
132
|
+
export interface ActivitySource {
|
|
133
|
+
readonly from: Float32Array;
|
|
134
|
+
readonly to: Float32Array;
|
|
135
|
+
/** 0 -> from, 1 -> to. Its own clock, not the slots'. */
|
|
136
|
+
readonly t: number;
|
|
137
|
+
/** Bumped whenever `from` or `to` change. */
|
|
138
|
+
readonly version: number;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* A scale with a slot table, as the packer needs it: its slots, and the identity
|
|
142
|
+
* key of its field, whose activity block its header points at. No field (or a
|
|
143
|
+
* field with no activity block) points it at the empty block.
|
|
144
|
+
*/
|
|
77
145
|
export interface IdentitySlotScale {
|
|
78
146
|
id: string;
|
|
79
147
|
slots: SlotSource;
|
|
148
|
+
field?: string | null;
|
|
80
149
|
}
|
|
81
150
|
/**
|
|
82
|
-
*
|
|
151
|
+
* A categorical field's selection activity, as the packer reads it: one fade
|
|
152
|
+
* per filter group, over `count` ids. FieldActivity has this shape.
|
|
153
|
+
*/
|
|
154
|
+
export interface ActivityField {
|
|
155
|
+
readonly key: string;
|
|
156
|
+
/** Ids covered: the block's `n`. */
|
|
157
|
+
readonly count: number;
|
|
158
|
+
/** Group `index`'s fade, or null when that group never selected here (all 1). */
|
|
159
|
+
fadeOf(index: number): ActivitySource | null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* One range to resolve this frame: `length` floats from `base`, at `t`. A
|
|
163
|
+
* scale's slots and each group's half of a field block are separate ranges,
|
|
164
|
+
* since each has its own `t`.
|
|
83
165
|
*
|
|
84
|
-
* The
|
|
85
|
-
*
|
|
86
|
-
*
|
|
166
|
+
* The header is inside the slot range rather than skipped: it holds the same
|
|
167
|
+
* values in both buffers, so the kernel returns them exactly, and the render
|
|
168
|
+
* stages read it from the resolved copy. Pooled in the state and overwritten by
|
|
87
169
|
* the next pack.
|
|
88
170
|
*/
|
|
89
171
|
export interface ResolveBlock {
|
|
@@ -116,30 +198,65 @@ export interface IdentitySlotState {
|
|
|
116
198
|
offsets: Map<string, number>;
|
|
117
199
|
/** Last packed slotVersion per scale, so a static chart re-uploads nothing. */
|
|
118
200
|
versions: Map<string, number>;
|
|
201
|
+
/** Last packed count per scale. */
|
|
202
|
+
counts: Map<string, number>;
|
|
203
|
+
/** Last packed actRef and n per scale: a field block that moved rewrites the header. */
|
|
204
|
+
refs: Map<string, number>;
|
|
205
|
+
ns: Map<string, number>;
|
|
206
|
+
/**
|
|
207
|
+
* The `t` each scale's slots were last resolved at, so a still frame
|
|
208
|
+
* dispatches nothing. A typed array per scale, created once, rather than a
|
|
209
|
+
* `Map<string, number>`: a fractional `t` stored as a Map value is boxed,
|
|
210
|
+
* which allocated on every frame of every transition.
|
|
211
|
+
*/
|
|
212
|
+
resolvedT: Map<string, Float64Array>;
|
|
213
|
+
/** Per field: its block's base, `n` and the group count it was packed for. */
|
|
214
|
+
fieldBases: Map<string, number>;
|
|
215
|
+
fieldNs: Map<string, number>;
|
|
216
|
+
fieldGroups: Map<string, number>;
|
|
217
|
+
/** Per field, per group: the fade version last written and the `t` last resolved. */
|
|
218
|
+
fieldVersions: Map<string, Float64Array>;
|
|
219
|
+
fieldT: Map<string, Float64Array>;
|
|
220
|
+
/** The pass a scale last drew each field in: a field no scale draws gets no block. */
|
|
221
|
+
fieldSeen: Map<string, number>;
|
|
222
|
+
/** Fields whose block did not fit, warned about once until they fit again. */
|
|
223
|
+
warnedFields: Set<string>;
|
|
119
224
|
/**
|
|
120
|
-
* The
|
|
121
|
-
*
|
|
122
|
-
*
|
|
225
|
+
* The pass each scale was last seen in, for the eviction sweep.
|
|
226
|
+
*
|
|
227
|
+
* A pass counter rather than a Set cleared every pack: clearing a Set
|
|
228
|
+
* allocates a new table in V8, which made every frame allocate ~370 bytes
|
|
229
|
+
* (measured, 20 scales) even when nothing moved. Re-setting an existing key
|
|
230
|
+
* to a small integer allocates nothing.
|
|
123
231
|
*/
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
|
|
232
|
+
seenPass: Map<string, number>;
|
|
233
|
+
/** The current pass, a small integer that wraps. */
|
|
234
|
+
pass: number;
|
|
127
235
|
/** Returned by every pack and overwritten by the next — read it immediately. */
|
|
128
236
|
result: PackResult;
|
|
129
237
|
}
|
|
130
238
|
/** A state with the permanent empty block in place. */
|
|
131
239
|
export declare function createIdentitySlotState(capacity?: number): IdentitySlotState;
|
|
132
240
|
/**
|
|
133
|
-
* Assign every scale a base, write the
|
|
134
|
-
* upload and what to resolve.
|
|
241
|
+
* Assign every field and scale a base, write the ranges that moved, and list
|
|
242
|
+
* what to upload and what to resolve.
|
|
243
|
+
*
|
|
244
|
+
* | Range | Rewritten when | Resolved when |
|
|
245
|
+
* |---|---|---|
|
|
246
|
+
* | A field's group half | its base, `n` or the group count moved, or that group's fade version | rewritten, or that fade's `t` moved |
|
|
247
|
+
* | A scale's header + slots | its base, slot version, or its field block's base or `n` moved | rewritten, or slot `t` moved |
|
|
248
|
+
*
|
|
249
|
+
* A range that is rewritten must be resolved even when its `t` did not move:
|
|
250
|
+
* the render stages read the resolved buffer, not these. A parked scale is
|
|
251
|
+
* neither rewritten nor resolved: it has no block, and the empty block it
|
|
252
|
+
* points at is never written.
|
|
135
253
|
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* and the
|
|
254
|
+
* Offsets are recomputed on every call because a block appearing or
|
|
255
|
+
* disappearing shifts every block after it. That is a running sum over a
|
|
256
|
+
* handful of blocks, and the alternative — a free list — would have to survive
|
|
257
|
+
* a block changing size, which happens whenever a dim appends. A field block
|
|
258
|
+
* that moves rewrites the headers pointing at it in the same pass.
|
|
139
259
|
*
|
|
140
|
-
*
|
|
141
|
-
* disappearing shifts every scale after it. That is a running sum over a
|
|
142
|
-
* handful of scales, and the alternative — a free list — would have to survive
|
|
143
|
-
* a scale changing size, which happens whenever a dim appends.
|
|
260
|
+
* `fields` and `groups` default to none, which packs the slots alone.
|
|
144
261
|
*/
|
|
145
|
-
export declare function packIdentitySlots(scales: readonly IdentitySlotScale[], state: IdentitySlotState): PackResult;
|
|
262
|
+
export declare function packIdentitySlots(scales: readonly IdentitySlotScale[], state: IdentitySlotState, fields?: readonly ActivityField[], groups?: number): PackResult;
|