@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
|
@@ -0,0 +1,154 @@
|
|
|
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
|
+
const ALL = { color: true, x: true, y: true };
|
|
24
|
+
const COLOR_ONLY = { color: true, x: false, y: false };
|
|
25
|
+
/**
|
|
26
|
+
* What a renderer reads. `hasExtent` is a rule's: RuleX with y1/y2, RuleY with
|
|
27
|
+
* x1/x2. Lines, areas and parallel coordinates dim by colour only: a line over
|
|
28
|
+
* a band axis spans its categories.
|
|
29
|
+
*
|
|
30
|
+
* Not yet supported: a line or area grouped by `z` that is not its colour
|
|
31
|
+
* field. Its series ids reach the shader, but only to break the line between
|
|
32
|
+
* series; no activity is read through them, and the series field is not among
|
|
33
|
+
* the mark's reads. So a selection on that field filters the mark in other
|
|
34
|
+
* charts and does nothing in its own, instead of dimming the other series.
|
|
35
|
+
*/
|
|
36
|
+
export function activityReads(renderer, hasExtent = false) {
|
|
37
|
+
switch (renderer) {
|
|
38
|
+
case 'dots':
|
|
39
|
+
case 'rects':
|
|
40
|
+
case 'text':
|
|
41
|
+
return ALL;
|
|
42
|
+
case 'rulex':
|
|
43
|
+
return { color: true, x: true, y: hasExtent };
|
|
44
|
+
case 'ruley':
|
|
45
|
+
return { color: true, x: hasExtent, y: true };
|
|
46
|
+
case 'lines':
|
|
47
|
+
case 'areas':
|
|
48
|
+
case 'parallel':
|
|
49
|
+
return COLOR_ONLY;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Whether a rule spans an extent on its cross axis: both ends given, as a
|
|
54
|
+
* field or a constant. A rule mark builds its renderer's extent data from this
|
|
55
|
+
* and hands it to routing as `ruleExtent`, so the two cannot disagree; a
|
|
56
|
+
* constant end (y1={0}, or the y shorthand) counts like a field.
|
|
57
|
+
*/
|
|
58
|
+
export function ruleHasExtent(lo, hi) {
|
|
59
|
+
return lo !== undefined && hi !== undefined;
|
|
60
|
+
}
|
|
61
|
+
/** The renderer a mark type draws with, or null for one that reads no activity. */
|
|
62
|
+
export function rendererForMark(markType) {
|
|
63
|
+
switch (markType) {
|
|
64
|
+
case 'dot':
|
|
65
|
+
return 'dots';
|
|
66
|
+
case 'bary':
|
|
67
|
+
case 'barx':
|
|
68
|
+
case 'rect':
|
|
69
|
+
case 'rectx':
|
|
70
|
+
case 'recty':
|
|
71
|
+
return 'rects';
|
|
72
|
+
case 'rulex':
|
|
73
|
+
return 'rulex';
|
|
74
|
+
case 'ruley':
|
|
75
|
+
return 'ruley';
|
|
76
|
+
case 'line':
|
|
77
|
+
return 'lines';
|
|
78
|
+
case 'areax':
|
|
79
|
+
case 'areay':
|
|
80
|
+
return 'areas';
|
|
81
|
+
case 'parallel':
|
|
82
|
+
return 'parallel';
|
|
83
|
+
case 'text':
|
|
84
|
+
return 'text';
|
|
85
|
+
default:
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A mark's reads: the identity keys of the fields whose activity it reads,
|
|
91
|
+
* sorted, from what its renderer reads and the identity key of each of its
|
|
92
|
+
* categorical scales (null for a channel with no such scale, or a scale with
|
|
93
|
+
* no identity: a field with no identity has no activity to dim by, so a
|
|
94
|
+
* selection on it filters the mark).
|
|
95
|
+
*/
|
|
96
|
+
export function readsFor(reads, keys) {
|
|
97
|
+
const out = [];
|
|
98
|
+
if (reads.color && keys.color)
|
|
99
|
+
out.push(keys.color);
|
|
100
|
+
if (reads.x && keys.x)
|
|
101
|
+
out.push(keys.x);
|
|
102
|
+
if (reads.y && keys.y)
|
|
103
|
+
out.push(keys.y);
|
|
104
|
+
if (out.length === 0)
|
|
105
|
+
return NO_READS;
|
|
106
|
+
return [...new Set(out)].sort();
|
|
107
|
+
}
|
|
108
|
+
/** A mark that reads no activity. Shared, so its reads never look changed. */
|
|
109
|
+
export const NO_READS = [];
|
|
110
|
+
export { NO_GROUP };
|
|
111
|
+
/** A mark's reads under its selection response: none for 'filter', which never dims. */
|
|
112
|
+
export function readsForResponse(response, reads) {
|
|
113
|
+
return response === 'filter' ? NO_READS : reads;
|
|
114
|
+
}
|
|
115
|
+
/** The group index a mark's shader reads activity with: NO_GROUP for 'filter'. */
|
|
116
|
+
export function groupIndexForResponse(response, groupIndex) {
|
|
117
|
+
return response === 'filter' ? NO_GROUP : groupIndex;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The WGSL activity of a renderer that reads colour only (lines, areas,
|
|
121
|
+
* parallel coordinates): `markColorActivity(...)`, or 1.0 if the table says
|
|
122
|
+
* it reads nothing. Throws for a renderer that reads a band axis, whose call
|
|
123
|
+
* is markActivityWGSL.
|
|
124
|
+
*/
|
|
125
|
+
export function colorActivityWGSL(reads, args) {
|
|
126
|
+
if (reads.x || reads.y)
|
|
127
|
+
throw new Error('colorActivityWGSL: this renderer reads a band axis');
|
|
128
|
+
return reads.color ? `markColorActivity(${args.color}, ${args.group})` : '1.0';
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The WGSL `markActivity(...)` call for `reads`: each activity the renderer
|
|
132
|
+
* reads, and 1.0 for each it does not. `color` is markColorActivity's first
|
|
133
|
+
* three arguments; `x` and `y` are the band channel values; `group` the mark's
|
|
134
|
+
* group index.
|
|
135
|
+
*/
|
|
136
|
+
/**
|
|
137
|
+
* The activity of an Other row: the same terms as markActivityWGSL without the
|
|
138
|
+
* band axes', and without colour's when colour is the band's own field
|
|
139
|
+
* (`skipColor`, a WGSL bool). Those read the Other bucket's own fade, and the
|
|
140
|
+
* bar's split already shows how much of it is selected (Rects); multiplying
|
|
141
|
+
* them in as well would dim it twice. Activity on any other field stays.
|
|
142
|
+
*/
|
|
143
|
+
export function otherRowActivityWGSL(reads, args) {
|
|
144
|
+
const color = reads.color
|
|
145
|
+
? `select(markColorActivity(${args.color}, ${args.group}), 1.0, ${args.skipColor})`
|
|
146
|
+
: '1.0';
|
|
147
|
+
return `markActivity(${color}, 1.0, 1.0)`;
|
|
148
|
+
}
|
|
149
|
+
export function markActivityWGSL(reads, args) {
|
|
150
|
+
const color = reads.color ? `markColorActivity(${args.color}, ${args.group})` : '1.0';
|
|
151
|
+
const x = reads.x ? `bandActivityX(${args.x}, ${args.group})` : '1.0';
|
|
152
|
+
const y = reads.y ? `bandActivityY(${args.y}, ${args.group})` : '1.0';
|
|
153
|
+
return `markActivity(${color}, ${x}, ${y})`;
|
|
154
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The byte size of every renderer's uniform struct, as WGSL lays it out.
|
|
3
|
+
*
|
|
4
|
+
* Renderers allocate their uniform buffers from these, and BaseGPURenderer
|
|
5
|
+
* sizes its stand-in from the largest: a binding not yet written is bound to
|
|
6
|
+
* the stand-in, and WebGPU rejects a draw whose uniform buffer is smaller than
|
|
7
|
+
* the struct it declares, which loses every chart's frame in that submit. A
|
|
8
|
+
* comment saying "must fit the largest struct" went stale the first time a
|
|
9
|
+
* struct grew, so uniform-sizes.test.ts derives each size from the shader
|
|
10
|
+
* source and fails when one drifts.
|
|
11
|
+
*/
|
|
12
|
+
/** Axes a parallel coordinates chart can hold: its axis arrays are sized for it. */
|
|
13
|
+
export declare const PARALLEL_MAX_AXES = 16;
|
|
14
|
+
export declare const UNIFORM_BYTES: {
|
|
15
|
+
readonly dotsMarkConstants: 48;
|
|
16
|
+
readonly rectsMarkConstants: 48;
|
|
17
|
+
readonly ruleXMarkConstants: 48;
|
|
18
|
+
readonly ruleYMarkConstants: 48;
|
|
19
|
+
readonly linesMarkConstants: 64;
|
|
20
|
+
readonly areasMarkConstants: 64;
|
|
21
|
+
readonly gridLineRuleConstants: 32;
|
|
22
|
+
readonly parallelConfig: 176;
|
|
23
|
+
readonly parallelAxisScales: number;
|
|
24
|
+
};
|
|
25
|
+
/** The stand-in uniform buffer: as large as the largest struct any renderer binds. */
|
|
26
|
+
export declare const UNIFORM_STAND_IN_BYTES: number;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The byte size of every renderer's uniform struct, as WGSL lays it out.
|
|
3
|
+
*
|
|
4
|
+
* Renderers allocate their uniform buffers from these, and BaseGPURenderer
|
|
5
|
+
* sizes its stand-in from the largest: a binding not yet written is bound to
|
|
6
|
+
* the stand-in, and WebGPU rejects a draw whose uniform buffer is smaller than
|
|
7
|
+
* the struct it declares, which loses every chart's frame in that submit. A
|
|
8
|
+
* comment saying "must fit the largest struct" went stale the first time a
|
|
9
|
+
* struct grew, so uniform-sizes.test.ts derives each size from the shader
|
|
10
|
+
* source and fails when one drifts.
|
|
11
|
+
*/
|
|
12
|
+
/** Axes a parallel coordinates chart can hold: its axis arrays are sized for it. */
|
|
13
|
+
export const PARALLEL_MAX_AXES = 16;
|
|
14
|
+
/** One ScaleParam: four f32. */
|
|
15
|
+
const SCALE_PARAM_BYTES = 16;
|
|
16
|
+
export const UNIFORM_BYTES = {
|
|
17
|
+
dotsMarkConstants: 48,
|
|
18
|
+
rectsMarkConstants: 48,
|
|
19
|
+
ruleXMarkConstants: 48,
|
|
20
|
+
ruleYMarkConstants: 48,
|
|
21
|
+
linesMarkConstants: 64,
|
|
22
|
+
areasMarkConstants: 64,
|
|
23
|
+
gridLineRuleConstants: 32,
|
|
24
|
+
parallelConfig: 176,
|
|
25
|
+
parallelAxisScales: PARALLEL_MAX_AXES * SCALE_PARAM_BYTES
|
|
26
|
+
};
|
|
27
|
+
/** The stand-in uniform buffer: as large as the largest struct any renderer binds. */
|
|
28
|
+
export const UNIFORM_STAND_IN_BYTES = Math.max(...Object.values(UNIFORM_BYTES));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SelectionResponse } from '../components/rendering/activity-reads';
|
|
2
|
+
import type { FieldRef, LODConfig, SortConfig, ColorScheme, CurveType, NullsResponse, Transform } from '../types';
|
|
2
3
|
import type { MarkLODSurface } from '../lod/rung-types';
|
|
3
4
|
export interface MarkEntry {
|
|
4
5
|
readonly id: string;
|
|
@@ -23,6 +24,9 @@ export interface MarkEntry {
|
|
|
23
24
|
scheme?: ColorScheme;
|
|
24
25
|
transforms?: Transform[];
|
|
25
26
|
selection?: string;
|
|
27
|
+
selectionResponse?: SelectionResponse;
|
|
28
|
+
contributeDomain?: boolean;
|
|
29
|
+
nulls?: NullsResponse;
|
|
26
30
|
limit?: number;
|
|
27
31
|
preferredXScaleType?: string | null;
|
|
28
32
|
preferredYScaleType?: string | null;
|
|
@@ -15,6 +15,18 @@ export declare const COLOR_CONFIG_BYTES = 48;
|
|
|
15
15
|
export declare const COLOR_CONFIG_SLOT_BASE_OFFSET = 32;
|
|
16
16
|
/** `slotBase` meaning "this channel already carries a palette index". */
|
|
17
17
|
export declare const NO_SLOT_TABLE = 4294967295;
|
|
18
|
+
/**
|
|
19
|
+
* The rank of a category an ordinal colour scale draws in the null colour: it
|
|
20
|
+
* takes no palette slot, so it reads as missing data and shifts no category's
|
|
21
|
+
* colour. The NULL category and the Other buckets. -1, by contrast, is a category
|
|
22
|
+
* outside this scale's domain, which draws transparent.
|
|
23
|
+
*/
|
|
24
|
+
export declare const NULL_COLOR_RANK = -2;
|
|
25
|
+
/**
|
|
26
|
+
* How many colour configs the buffer holds. A uniform array has a fixed length
|
|
27
|
+
* in WGSL, so the shader declaration and the buffer size both read this.
|
|
28
|
+
*/
|
|
29
|
+
export declare const MAX_COLOR_SLOTS = 64;
|
|
18
30
|
export declare const COLOR_SCALE_STRUCTS = "\nstruct ColorConfig {\n domainMin: f32, // byte 0 \u2014 pre-transformed for non-linear types\n domainMax: f32, // byte 4 \u2014 pre-transformed for non-linear types\n rampOffset: u32, // byte 8\n paletteOffset: u32, // byte 12\n paletteSize: u32, // byte 16\n scaleType: u32, // byte 20 \u2014 scheme rendering type (ordinal/sequential/diverging/cyclical)\n transformType: u32, // byte 24 \u2014 normalization transform (linear/log/symlog/sqrt/pow)\n transformParam: f32, // byte 28 \u2014 symlog constant C, or pow exponent\n slotBase: u32, // byte 32 \u2014 base into identitySlots, or NO_SLOT_TABLE\n _pad0: u32, // byte 36\n _pad1: u32, // byte 40\n _pad2: u32, // byte 44\n}\n\n/**\n * slotBase when this scale's channel already carries a palette index rather\n * than an identity id. Distinct from base 0, which is a real block \u2014 the\n * permanently empty one \u2014 so it cannot be confused with \"not placed\".\n */\nconst NO_SLOT_TABLE: u32 = 0xFFFFFFFFu;\n\nconst COLOR_SCALE_ORDINAL: u32 = 0u;\nconst COLOR_SCALE_SEQUENTIAL: u32 = 1u;\nconst COLOR_SCALE_DIVERGING: u32 = 2u;\nconst COLOR_SCALE_CYCLICAL: u32 = 3u;\n\nconst COLOR_TRANSFORM_LINEAR: u32 = 0u;\nconst COLOR_TRANSFORM_LOG: u32 = 1u;\nconst COLOR_TRANSFORM_SYMLOG: u32 = 2u;\nconst COLOR_TRANSFORM_SQRT: u32 = 3u;\nconst COLOR_TRANSFORM_POW: u32 = 4u;\n";
|
|
19
|
-
export declare const COLOR_SCALE_BINDINGS = "\n@group(2) @binding(1) var<
|
|
31
|
+
export declare const COLOR_SCALE_BINDINGS = "\n@group(2) @binding(1) var<uniform> colorConfigs: array<ColorConfig, 64>;\n@group(2) @binding(2) var<storage, read> colorRamps: array<vec4<f32>>;\n@group(2) @binding(3) var<storage, read> ordinalPalettes: array<vec4<f32>>;\n";
|
|
20
32
|
export declare const COLOR_SCALE_FUNCTIONS: string;
|
|
@@ -36,6 +36,18 @@ export const COLOR_CONFIG_BYTES = 48;
|
|
|
36
36
|
export const COLOR_CONFIG_SLOT_BASE_OFFSET = 32;
|
|
37
37
|
/** `slotBase` meaning "this channel already carries a palette index". */
|
|
38
38
|
export const NO_SLOT_TABLE = 0xffffffff;
|
|
39
|
+
/**
|
|
40
|
+
* The rank of a category an ordinal colour scale draws in the null colour: it
|
|
41
|
+
* takes no palette slot, so it reads as missing data and shifts no category's
|
|
42
|
+
* colour. The NULL category and the Other buckets. -1, by contrast, is a category
|
|
43
|
+
* outside this scale's domain, which draws transparent.
|
|
44
|
+
*/
|
|
45
|
+
export const NULL_COLOR_RANK = -2;
|
|
46
|
+
/**
|
|
47
|
+
* How many colour configs the buffer holds. A uniform array has a fixed length
|
|
48
|
+
* in WGSL, so the shader declaration and the buffer size both read this.
|
|
49
|
+
*/
|
|
50
|
+
export const MAX_COLOR_SLOTS = 64;
|
|
39
51
|
export const COLOR_SCALE_STRUCTS = /* wgsl */ `
|
|
40
52
|
struct ColorConfig {
|
|
41
53
|
domainMin: f32, // byte 0 — pre-transformed for non-linear types
|
|
@@ -70,8 +82,14 @@ const COLOR_TRANSFORM_SYMLOG: u32 = 2u;
|
|
|
70
82
|
const COLOR_TRANSFORM_SQRT: u32 = 3u;
|
|
71
83
|
const COLOR_TRANSFORM_POW: u32 = 4u;
|
|
72
84
|
`;
|
|
85
|
+
// colorConfigs is a UNIFORM, visible to the vertex stage as well: selection
|
|
86
|
+
// dimming reads a colour scale's slotBase per vertex. As storage it would take
|
|
87
|
+
// the last of the vertex stage's default 8 storage buffers on Rects, which uses
|
|
88
|
+
// 7 (six channels and the identity slot table); as a uniform it leaves that
|
|
89
|
+
// slot free. 64 × 48 bytes is 3 KB, written when a scale changes, which is what
|
|
90
|
+
// uniform buffers are for.
|
|
73
91
|
export const COLOR_SCALE_BINDINGS = /* wgsl */ `
|
|
74
|
-
@group(2) @binding(1) var<
|
|
92
|
+
@group(2) @binding(1) var<uniform> colorConfigs: array<ColorConfig, ${MAX_COLOR_SLOTS}>;
|
|
75
93
|
@group(2) @binding(2) var<storage, read> colorRamps: array<vec4<f32>>;
|
|
76
94
|
@group(2) @binding(3) var<storage, read> ordinalPalettes: array<vec4<f32>>;
|
|
77
95
|
`;
|
|
@@ -120,7 +138,7 @@ fn sampleRamp(config: ColorConfig, t: f32) -> vec4<f32> {
|
|
|
120
138
|
return colorRamps[config.rampOffset + idx];
|
|
121
139
|
}
|
|
122
140
|
|
|
123
|
-
fn samplePalette(config: ColorConfig, categoryIndex: u32) -> vec4<f32> {
|
|
141
|
+
fn samplePalette(config: ColorConfig, categoryIndex: u32, nullColor: vec4<f32>) -> vec4<f32> {
|
|
124
142
|
// The channel carries an identity id, which is stable and has nothing to do
|
|
125
143
|
// with this scale's palette. Resolve it to this scale's own rank first; the
|
|
126
144
|
// palette is indexed by rank, so the plotted categories take palette colours
|
|
@@ -129,7 +147,12 @@ fn samplePalette(config: ColorConfig, categoryIndex: u32) -> vec4<f32> {
|
|
|
129
147
|
if (config.slotBase != NO_SLOT_TABLE) {
|
|
130
148
|
let slot = identitySlot(config.slotBase, categoryIndex);
|
|
131
149
|
if (slot < 0.0) {
|
|
132
|
-
//
|
|
150
|
+
// ${NULL_COLOR_RANK}: a category drawn in the null colour, which takes no
|
|
151
|
+
// palette slot (the NULL category, an Other bucket).
|
|
152
|
+
if (slot < -1.5) {
|
|
153
|
+
return nullColor;
|
|
154
|
+
}
|
|
155
|
+
// -1: not ranked by this scale, a category outside its domain.
|
|
133
156
|
return vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
|
134
157
|
}
|
|
135
158
|
idx = u32(slot);
|
|
@@ -146,7 +169,7 @@ fn getColorFromScale(scaleIndex: u32, value: f32, nullColor: vec4<f32>) -> vec4<
|
|
|
146
169
|
return nullColor;
|
|
147
170
|
}
|
|
148
171
|
let catIdx = u32(value);
|
|
149
|
-
return samplePalette(config, catIdx);
|
|
172
|
+
return samplePalette(config, catIdx, nullColor);
|
|
150
173
|
}
|
|
151
174
|
|
|
152
175
|
if (${isNanExpr('value')}) {
|
|
@@ -165,7 +188,7 @@ fn getColorFromScale(scaleIndex: u32, value: f32, nullColor: vec4<f32>) -> vec4<
|
|
|
165
188
|
fn getOrdinalColor(scaleIndex: u32, categoryIndex: u32, nullColor: vec4<f32>) -> vec4<f32> {
|
|
166
189
|
// Note: This function expects u32, so caller must handle -1 check before casting
|
|
167
190
|
let config = colorConfigs[scaleIndex];
|
|
168
|
-
return samplePalette(config, categoryIndex);
|
|
191
|
+
return samplePalette(config, categoryIndex, nullColor);
|
|
169
192
|
}
|
|
170
193
|
|
|
171
194
|
fn getContinuousColor(scaleIndex: u32, value: f32, nullColor: vec4<f32>) -> vec4<f32> {
|
|
@@ -179,4 +202,32 @@ fn getContinuousColor(scaleIndex: u32, value: f32, nullColor: vec4<f32>) -> vec4
|
|
|
179
202
|
let t = normalizeValue(config, value);
|
|
180
203
|
return sampleRamp(config, t);
|
|
181
204
|
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* How selected this value's category is on an ordinal colour scale, 0..1.
|
|
208
|
+
*
|
|
209
|
+
* Read per vertex for selection dimming. 1.0 for anything that has no category
|
|
210
|
+
* to select: a continuous scale, an ordinal scale whose channel does not carry
|
|
211
|
+
* identity ids yet (NO_SLOT_TABLE), and a missing category. The -1 and NaN
|
|
212
|
+
* checks come before the cast, because u32() of a negative or NaN float is
|
|
213
|
+
* indeterminate in WGSL and would read an arbitrary activity.
|
|
214
|
+
*/
|
|
215
|
+
fn colorActivity(scaleIndex: u32, value: f32, group: u32) -> f32 {
|
|
216
|
+
if (value < 0.0 || ${isNanExpr('value')}) {
|
|
217
|
+
return 1.0;
|
|
218
|
+
}
|
|
219
|
+
let config = colorConfigs[scaleIndex];
|
|
220
|
+
if (config.scaleType != COLOR_SCALE_ORDINAL || config.slotBase == NO_SLOT_TABLE) {
|
|
221
|
+
return 1.0;
|
|
222
|
+
}
|
|
223
|
+
return identityActivity(config.slotBase, group, u32(value));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** A mark's colour activity in its filter group: 1.0 unless it colours through a scale. */
|
|
227
|
+
fn markColorActivity(colorMode: u32, colorScaleIndex: u32, colorValue: f32, group: u32) -> f32 {
|
|
228
|
+
if (colorMode != 1u) {
|
|
229
|
+
return 1.0;
|
|
230
|
+
}
|
|
231
|
+
return colorActivity(colorScaleIndex, colorValue, group);
|
|
232
|
+
}
|
|
182
233
|
`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RendererDefinition } from './ScaleTemplateSystem';
|
|
2
2
|
import type { ScaleRegistry } from '../scales/ScaleRegistry.svelte';
|
|
3
|
+
import type { FilterGroupRegistry } from '../query-engine/FilterGroupRegistry.svelte';
|
|
3
4
|
/** Number of color stops per ramp slot. Shared with ColorScaleState for bakeGradient(). */
|
|
4
5
|
export declare const COLOR_RAMP_RESOLUTION = 256;
|
|
5
6
|
interface LayerConfig {
|
|
@@ -182,6 +183,13 @@ export declare class GPUCoordinator {
|
|
|
182
183
|
*/
|
|
183
184
|
releaseRenderer(rendererId: string): void;
|
|
184
185
|
setScaleRegistry(scaleRegistry: ScaleRegistry): void;
|
|
186
|
+
/**
|
|
187
|
+
* The page's filter groups, whose field activity the packer writes into the
|
|
188
|
+
* identity slot table. Their fades run on this loop and redraw the charts
|
|
189
|
+
* drawing any scale on their field. Set before any selection, so no fade is
|
|
190
|
+
* created without the loop.
|
|
191
|
+
*/
|
|
192
|
+
setFilterGroups(filterGroups: FilterGroupRegistry): void;
|
|
185
193
|
registerScale(scaleId: string, scaleType?: string): void;
|
|
186
194
|
unregisterScale(scaleId: string): void;
|
|
187
195
|
scheduleTopologyUpdate(): void;
|
|
@@ -7,7 +7,8 @@ import { RESOLVE_IMMEDIATE_SIZE, RESOLVE_WORKGROUP, resolvePipelineFor } from '.
|
|
|
7
7
|
import { IDENTITY_SLOT_CAPACITY, IDENTITY_SLOT_EMPTY, createIdentitySlotState, packIdentitySlots } from './identity-slot-packing';
|
|
8
8
|
import { frameUniformIsStale } from './frame-uniform-sync';
|
|
9
9
|
import { immediatesSupported, IMMEDIATES_MISSING_MESSAGE } from './immediates';
|
|
10
|
-
import { COLOR_CONFIG_BYTES, COLOR_CONFIG_SLOT_BASE_OFFSET } from './ColorScaleShaderLib';
|
|
10
|
+
import { COLOR_CONFIG_BYTES, COLOR_CONFIG_SLOT_BASE_OFFSET, MAX_COLOR_SLOTS } from './ColorScaleShaderLib';
|
|
11
|
+
import { trackScalesById } from './tracked-scales';
|
|
11
12
|
/** Number of color stops per ramp slot. Shared with ColorScaleState for bakeGradient(). */
|
|
12
13
|
export const COLOR_RAMP_RESOLUTION = 256;
|
|
13
14
|
/**
|
|
@@ -18,6 +19,8 @@ export const COLOR_RAMP_RESOLUTION = 256;
|
|
|
18
19
|
function layerCanDraw(layer, chartScalesKnown) {
|
|
19
20
|
return (!!layer.renderer?.perDataBindGroup && (chartScalesKnown || layer.readsChartScales === false));
|
|
20
21
|
}
|
|
22
|
+
/** No fields to pack, for a coordinator without filter groups. */
|
|
23
|
+
const NO_FIELDS = [];
|
|
21
24
|
// =============================================================================
|
|
22
25
|
// HELPER CLASSES
|
|
23
26
|
// =============================================================================
|
|
@@ -113,6 +116,7 @@ export class GPUCoordinator {
|
|
|
113
116
|
#animatedSources = new Set(); // Set for O(1) membership check
|
|
114
117
|
#animatedSourcesArray = []; // Array for O(1) iteration (no allocation)
|
|
115
118
|
#scaleRegistry = null;
|
|
119
|
+
#filterGroups = null;
|
|
116
120
|
// =========================================================================
|
|
117
121
|
// HOVER STATE (Keep loop warm while any chart is hovered)
|
|
118
122
|
// =========================================================================
|
|
@@ -196,7 +200,7 @@ export class GPUCoordinator {
|
|
|
196
200
|
#sizeConfigBuffer = null; // MAX_SIZE_SLOTS × 16 bytes = 1KB
|
|
197
201
|
#maxSizeSlots = MAX_SIZE_SLOTS;
|
|
198
202
|
// Constants
|
|
199
|
-
#maxColorSlots =
|
|
203
|
+
#maxColorSlots = MAX_COLOR_SLOTS;
|
|
200
204
|
#rampColorsPerSlot = COLOR_RAMP_RESOLUTION;
|
|
201
205
|
#paletteColorsPerSlot = 32;
|
|
202
206
|
// =========================================================================
|
|
@@ -363,10 +367,17 @@ export class GPUCoordinator {
|
|
|
363
367
|
label: 'scale-resources-layout',
|
|
364
368
|
entries: [
|
|
365
369
|
{
|
|
366
|
-
// Binding 1: Color scale configs
|
|
370
|
+
// Binding 1: Color scale configs.
|
|
371
|
+
//
|
|
372
|
+
// Uniform, and visible to the VERTEX stage as well: selection
|
|
373
|
+
// dimming reads a colour scale's slotBase per vertex. As storage
|
|
374
|
+
// it would take the last of the vertex stage's default 8 storage
|
|
375
|
+
// buffers on Rects, which uses 7; as a uniform it counts against
|
|
376
|
+
// the separate uniform limit and leaves that slot free. Same move
|
|
377
|
+
// as binding 4.
|
|
367
378
|
binding: 1,
|
|
368
|
-
visibility: GPUShaderStage.FRAGMENT,
|
|
369
|
-
buffer: { type: '
|
|
379
|
+
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
380
|
+
buffer: { type: 'uniform' }
|
|
370
381
|
},
|
|
371
382
|
{
|
|
372
383
|
// Binding 2: Color ramps (fragment only)
|
|
@@ -400,8 +411,9 @@ export class GPUCoordinator {
|
|
|
400
411
|
// buffer and do no interpolation.
|
|
401
412
|
//
|
|
402
413
|
// Storage, where it used to be two uniforms. It costs the vertex
|
|
403
|
-
// stage one binding where the pair cost none — Rects lands at
|
|
404
|
-
//
|
|
414
|
+
// stage one binding where the pair cost none — Rects lands at 7 of
|
|
415
|
+
// the default maxStorageBuffersPerShaderStage of 8, its six channels
|
|
416
|
+
// and this — and in exchange
|
|
405
417
|
// the vec4 packing, the 64 KB uniform binding cap and the
|
|
406
418
|
// 16384-float global ceiling all go away with the address space.
|
|
407
419
|
//
|
|
@@ -409,8 +421,9 @@ export class GPUCoordinator {
|
|
|
409
421
|
// the compute pipeline, which has its own budget.
|
|
410
422
|
//
|
|
411
423
|
// FRAGMENT as well as VERTEX: band axes resolve a slot in the
|
|
412
|
-
// vertex stage, ordinal colour
|
|
413
|
-
//
|
|
424
|
+
// vertex stage, and ordinal colour resolves its palette rank in
|
|
425
|
+
// the fragment stage. Both read their selection activity, the
|
|
426
|
+
// second half of each block, in the vertex stage.
|
|
414
427
|
binding: 5,
|
|
415
428
|
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
|
|
416
429
|
buffer: { type: 'read-only-storage' }
|
|
@@ -435,11 +448,12 @@ export class GPUCoordinator {
|
|
|
435
448
|
}
|
|
436
449
|
#createColorScaleBuffers() {
|
|
437
450
|
const device = this.#device;
|
|
438
|
-
// ColorConfig buffer: 64 slots × 48 bytes each
|
|
451
|
+
// ColorConfig buffer: 64 slots × 48 bytes each. A uniform, so the vertex
|
|
452
|
+
// stage can read it without a storage binding — see COLOR_SCALE_BINDINGS.
|
|
439
453
|
this.#colorConfigBuffer = device.createBuffer({
|
|
440
|
-
label: 'ColorConfig
|
|
454
|
+
label: 'ColorConfig uniform',
|
|
441
455
|
size: this.#maxColorSlots * COLOR_CONFIG_BYTES,
|
|
442
|
-
usage: GPUBufferUsage.
|
|
456
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
443
457
|
});
|
|
444
458
|
// Color ramp buffer: 64 slots × 256 colors × 4 floats × 4 bytes
|
|
445
459
|
this.#colorRampBuffer = device.createBuffer({
|
|
@@ -578,9 +592,16 @@ export class GPUCoordinator {
|
|
|
578
592
|
const slots = scale.slots;
|
|
579
593
|
if (!slots)
|
|
580
594
|
continue;
|
|
581
|
-
const entry = (pool[scales.length] ??= { id: '', slots });
|
|
595
|
+
const entry = (pool[scales.length] ??= { id: '', slots, field: null });
|
|
582
596
|
entry.id = scale.id;
|
|
583
597
|
entry.slots = slots;
|
|
598
|
+
// The field whose activity block this scale's header points at. Every
|
|
599
|
+
// field a scale draws has one, so a selection moves nothing; the
|
|
600
|
+
// registry creates its FieldActivity once (a Map lookup after that).
|
|
601
|
+
const field = scale.identityKey ?? null;
|
|
602
|
+
entry.field = field;
|
|
603
|
+
if (field)
|
|
604
|
+
this.#filterGroups?.field(field);
|
|
584
605
|
scales.push(entry);
|
|
585
606
|
// Index-aligned with `scales`, so the base can be handed back after
|
|
586
607
|
// packing without searching for the instance again.
|
|
@@ -588,7 +609,14 @@ export class GPUCoordinator {
|
|
|
588
609
|
? scale
|
|
589
610
|
: null);
|
|
590
611
|
}
|
|
591
|
-
|
|
612
|
+
// Every field follows its live identity here, where nothing is reactive:
|
|
613
|
+
// a reload snaps its fades, a grown dim extends them. Two comparisons a
|
|
614
|
+
// field when nothing moved.
|
|
615
|
+
const registry = this.#filterGroups;
|
|
616
|
+
const fields = registry?.fields ?? NO_FIELDS;
|
|
617
|
+
for (let i = 0; i < fields.length; i++)
|
|
618
|
+
fields[i].reconcile();
|
|
619
|
+
const { uploadStart, uploadEnd } = packIdentitySlots(scales, state, fields, registry?.groupCount ?? 0);
|
|
592
620
|
// Hand back the base to any scale that reads it from its own config rather
|
|
593
621
|
// than from p4. A band scale's base is refilled into p4 every frame; an
|
|
594
622
|
// ordinal colour scale's lives in a config uploaded only when the scale
|
|
@@ -960,12 +988,13 @@ export class GPUCoordinator {
|
|
|
960
988
|
}
|
|
961
989
|
const passEncoder = commandEncoder.beginRenderPass(this.#createRenderPassDescriptor(view, chartConfig));
|
|
962
990
|
// The chart's scales, written once for the whole pass and before the
|
|
963
|
-
// first draw. Every pipeline declares the same
|
|
964
|
-
// requires every declared byte to
|
|
965
|
-
// range, every pass. Immediate data
|
|
966
|
-
// layouts of the same size, which is what
|
|
967
|
-
// when the chart's scales are not both
|
|
968
|
-
// read them still needs every byte set
|
|
991
|
+
// first draw. Every pipeline declares the same
|
|
992
|
+
// CHART_SCALE_IMMEDIATE_SIZE and a draw requires every declared byte to
|
|
993
|
+
// have been set, so this is the whole range, every pass. Immediate data
|
|
994
|
+
// survives setPipeline between layouts of the same size, which is what
|
|
995
|
+
// makes once enough. Pushed even when the chart's scales are not both
|
|
996
|
+
// known: a layer that does not read them still needs every byte set
|
|
997
|
+
// before it may draw.
|
|
969
998
|
const scalesKnown = this.#writeChartScales(chartConfig, this.#chartScaleImmediates);
|
|
970
999
|
passEncoder.setImmediates(0, this.#chartScaleImmediates);
|
|
971
1000
|
if (chartConfig.perFrameBindGroup) {
|
|
@@ -1226,6 +1255,16 @@ export class GPUCoordinator {
|
|
|
1226
1255
|
setScaleRegistry(scaleRegistry) {
|
|
1227
1256
|
this.#scaleRegistry = scaleRegistry;
|
|
1228
1257
|
}
|
|
1258
|
+
/**
|
|
1259
|
+
* The page's filter groups, whose field activity the packer writes into the
|
|
1260
|
+
* identity slot table. Their fades run on this loop and redraw the charts
|
|
1261
|
+
* drawing any scale on their field. Set before any selection, so no fade is
|
|
1262
|
+
* created without the loop.
|
|
1263
|
+
*/
|
|
1264
|
+
setFilterGroups(filterGroups) {
|
|
1265
|
+
this.#filterGroups = filterGroups;
|
|
1266
|
+
filterGroups.setAnimation(this, (key, out) => this.#scaleRegistry ? this.#scaleRegistry.chartsDrawingField(key, out) : out);
|
|
1267
|
+
}
|
|
1229
1268
|
registerScale(scaleId, scaleType = 'linear') {
|
|
1230
1269
|
if (!this.#initialized)
|
|
1231
1270
|
return;
|
|
@@ -1249,18 +1288,15 @@ export class GPUCoordinator {
|
|
|
1249
1288
|
const usageMap = this.#scaleRegistry.scaleUsage;
|
|
1250
1289
|
if (!usageMap || typeof usageMap[Symbol.iterator] !== 'function')
|
|
1251
1290
|
return;
|
|
1252
|
-
// Clear and rebuild
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
// asks for it. Copying it from a second owner is how it went stale.
|
|
1262
|
-
this.#scaleIdToInstance.set(scale.id, scale);
|
|
1263
|
-
}
|
|
1291
|
+
// Clear and rebuild: every scale with an id — positional scales, and
|
|
1292
|
+
// ordinal colour, whose slots the packer needs too. The id map is only
|
|
1293
|
+
// looked up with a chart's x and y scale ids, so a colour scale in it is
|
|
1294
|
+
// never mistaken for a positional one.
|
|
1295
|
+
//
|
|
1296
|
+
// The redraw list is NOT rebuilt here any more. It belongs to the
|
|
1297
|
+
// registry, derived from the same usage map this reads, and a scale asks
|
|
1298
|
+
// for it. Copying it from a second owner is how it went stale.
|
|
1299
|
+
trackScalesById(usageMap.keys(), this.#scaleIdToInstance);
|
|
1264
1300
|
this.#scales = [...this.#scaleIdToInstance.values()];
|
|
1265
1301
|
}
|
|
1266
1302
|
// =========================================================================
|
|
@@ -1,35 +1,4 @@
|
|
|
1
1
|
import { TransitionClock, type ClockLoop } from '../animation/TransitionClock';
|
|
2
|
-
/**
|
|
3
|
-
* Channels of one mark that morph between data updates rather than replacing
|
|
4
|
-
* outright.
|
|
5
|
-
*
|
|
6
|
-
* Each holds three buffers where a static channel holds one: `from` and `to` are
|
|
7
|
-
* read by a compute pass, and `resolved = mix(from, to, t)` is what the vertex
|
|
8
|
-
* stage binds — in place of the static buffer, not in addition to it. So
|
|
9
|
-
* animating a channel costs the vertex stage nothing, which is what makes it
|
|
10
|
-
* affordable on a mark already at the device's storage-buffer limit.
|
|
11
|
-
*
|
|
12
|
-
* THE FOLD. When a transition restarts, whatever is on screen becomes the new
|
|
13
|
-
* `from`. On screen is the resolved buffer as last dispatched, so the fold is a
|
|
14
|
-
* buffer copy, and it reads neither `from` nor `to` — which is what lets data
|
|
15
|
-
* land at any moment, in any order relative to anything else. Writing it as
|
|
16
|
-
* `from = mix(from, to, t)` would need the OLD `to`, making the order of two
|
|
17
|
-
* writes load-bearing and silently wrong if they ever swap.
|
|
18
|
-
*
|
|
19
|
-
* THE SNAP. Some updates have nothing to travel from: the first one, a buffer
|
|
20
|
-
* that had to be reallocated, and a row set that is not the one being animated.
|
|
21
|
-
* Those copy `to` over `from`, which makes the mix the identity for every t.
|
|
22
|
-
*
|
|
23
|
-
* THE CLOCK. A mark ADOPTS the band scale's clock when it has one, so a bar's
|
|
24
|
-
* height travels on the same number as its slot — two clocks with two velocities
|
|
25
|
-
* desynchronise on a retarget, and the bar reaches its rank before or after it
|
|
26
|
-
* finishes growing. It OWNS one otherwise. Adoption is safe across marks because
|
|
27
|
-
* a mark with no new data folds to where it already is and does not move.
|
|
28
|
-
*/
|
|
29
|
-
/**
|
|
30
|
-
* The coordinator's slice this needs. Structural, so tests can supply a fake.
|
|
31
|
-
* It is also the loop an owned clock joins — the clock does that itself.
|
|
32
|
-
*/
|
|
33
2
|
export interface MorphHost extends ClockLoop {
|
|
34
3
|
registerComputeWork(key: string, fn: (encoder: GPUCommandEncoder) => void): void;
|
|
35
4
|
removeComputeWork(key: string): void;
|
|
@@ -85,8 +54,13 @@ export declare class MorphedChannels {
|
|
|
85
54
|
*
|
|
86
55
|
* All channels in one call, so one update starts one transition. Returns
|
|
87
56
|
* whether any buffer was (re)allocated, which the caller must rebind.
|
|
57
|
+
*
|
|
58
|
+
* `land`: bindings whose new values take effect at once while the rest
|
|
59
|
+
* travel. For a channel whose change is shown some other way at that moment
|
|
60
|
+
* (an Other bar's split, when both sides of it look the same): sliding it
|
|
61
|
+
* would draw a movement nothing asked for.
|
|
88
62
|
*/
|
|
89
|
-
write(rowIds: ArrayLike<number>, channels: readonly (readonly [number, AllowSharedBufferSource])[]): boolean;
|
|
63
|
+
write(rowIds: ArrayLike<number>, channels: readonly (readonly [number, AllowSharedBufferSource])[], land?: readonly number[]): boolean;
|
|
90
64
|
/**
|
|
91
65
|
* Stop morphing a channel and release its buffers. Returns whether it was
|
|
92
66
|
* morphing, in which case the caller's binding has lost its buffer.
|