@fjader/svelte 0.2.2 → 0.2.3

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.
Files changed (118) hide show
  1. package/dist/components/axes/BandAxisTicks.svelte +63 -24
  2. package/dist/components/axes/BandAxisTicks.svelte.d.ts +5 -5
  3. package/dist/components/charts/ChartContainer.svelte +2 -1
  4. package/dist/components/guides/legend/ContinuousColorLegend.svelte +2 -1
  5. package/dist/components/guides/legend/OrdinalColorLegend.svelte +92 -34
  6. package/dist/components/marks/AreaMarkX.svelte +19 -6
  7. package/dist/components/marks/AreaMarkY.svelte +19 -6
  8. package/dist/components/marks/BarMarkX.svelte +3 -0
  9. package/dist/components/marks/BarMarkY.svelte +3 -0
  10. package/dist/components/marks/DotsMark.svelte +3 -0
  11. package/dist/components/marks/LineMark.svelte +3 -0
  12. package/dist/components/marks/ParallelCoordinatesMark.svelte +6 -0
  13. package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +3 -0
  14. package/dist/components/marks/RectMark.svelte +3 -0
  15. package/dist/components/marks/RectXMark.svelte +3 -0
  16. package/dist/components/marks/RectYMark.svelte +3 -0
  17. package/dist/components/marks/RuleMarkX.svelte +10 -2
  18. package/dist/components/marks/RuleMarkY.svelte +10 -2
  19. package/dist/components/marks/TextMark.svelte +68 -4
  20. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +15 -1
  21. package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +116 -27
  22. package/dist/components/marks/lifecycle/domain-queries.js +13 -11
  23. package/dist/components/marks/lifecycle/transform-scale-key.d.ts +16 -0
  24. package/dist/components/marks/lifecycle/transform-scale-key.js +27 -0
  25. package/dist/components/marks/utils/area-outline.d.ts +24 -0
  26. package/dist/components/marks/utils/area-outline.js +10 -0
  27. package/dist/components/marks/utils/reactive-config.d.ts +1 -1
  28. package/dist/components/marks/utils/reactive-config.js +3 -0
  29. package/dist/components/rendering/Areas.svelte +30 -6
  30. package/dist/components/rendering/Areas.svelte.d.ts +2 -0
  31. package/dist/components/rendering/BaseGPURenderer.svelte.js +4 -1
  32. package/dist/components/rendering/Dots.svelte +24 -3
  33. package/dist/components/rendering/Dots.svelte.d.ts +2 -0
  34. package/dist/components/rendering/GridLine.svelte +2 -1
  35. package/dist/components/rendering/Lines.svelte +49 -7
  36. package/dist/components/rendering/Lines.svelte.d.ts +9 -0
  37. package/dist/components/rendering/ParallelLines.svelte +26 -7
  38. package/dist/components/rendering/ParallelLines.svelte.d.ts +2 -0
  39. package/dist/components/rendering/Rects.svelte +20 -5
  40. package/dist/components/rendering/Rects.svelte.d.ts +2 -0
  41. package/dist/components/rendering/RuleX.svelte +20 -5
  42. package/dist/components/rendering/RuleX.svelte.d.ts +2 -0
  43. package/dist/components/rendering/RuleY.svelte +20 -5
  44. package/dist/components/rendering/RuleY.svelte.d.ts +2 -0
  45. package/dist/components/rendering/activity-reads.d.ts +93 -0
  46. package/dist/components/rendering/activity-reads.js +135 -0
  47. package/dist/components/rendering/uniform-sizes.d.ts +26 -0
  48. package/dist/components/rendering/uniform-sizes.js +28 -0
  49. package/dist/config/ChartConfig.svelte.d.ts +2 -0
  50. package/dist/gpu/ColorScaleShaderLib.d.ts +13 -1
  51. package/dist/gpu/ColorScaleShaderLib.js +56 -5
  52. package/dist/gpu/GPUCoordinator.svelte.d.ts +8 -0
  53. package/dist/gpu/GPUCoordinator.svelte.js +68 -32
  54. package/dist/gpu/ResolveShader.d.ts +2 -2
  55. package/dist/gpu/ResolveShader.js +2 -2
  56. package/dist/gpu/ScaleTemplateSystem.d.ts +19 -0
  57. package/dist/gpu/ScaleTemplateSystem.js +86 -7
  58. package/dist/gpu/SizeScaleShaderLib.d.ts +4 -4
  59. package/dist/gpu/SizeScaleShaderLib.js +4 -4
  60. package/dist/gpu/identity-slot-packing.d.ts +157 -40
  61. package/dist/gpu/identity-slot-packing.js +262 -62
  62. package/dist/gpu/tracked-scales.d.ts +14 -0
  63. package/dist/gpu/tracked-scales.js +18 -0
  64. package/dist/index.js +1 -0
  65. package/dist/interactors/BandLabelToggle.svelte +5 -9
  66. package/dist/interactors/BandLabelToggle.svelte.d.ts +3 -3
  67. package/dist/interactors/DerivedInterval1D.svelte +26 -54
  68. package/dist/interactors/Interval1D.svelte +2 -1
  69. package/dist/interactors/Interval2D.svelte +2 -1
  70. package/dist/interactors/ParallelAxisSource.svelte +4 -4
  71. package/dist/interactors/ParallelAxisSource.svelte.d.ts +2 -1
  72. package/dist/interactors/ParallelBrushes.svelte +8 -9
  73. package/dist/interactors/ParallelBrushes.svelte.d.ts +3 -3
  74. package/dist/interactors/Toggle.svelte +17 -36
  75. package/dist/interactors/Toggle.svelte.d.ts +4 -3
  76. package/dist/interactors/Tooltip.svelte +2 -1
  77. package/dist/interactors/TooltipManager.svelte.js +8 -3
  78. package/dist/interactors/relay-clauses.d.ts +23 -0
  79. package/dist/interactors/relay-clauses.js +60 -0
  80. package/dist/query-engine/CrossfilterSelection.svelte.d.ts +88 -15
  81. package/dist/query-engine/CrossfilterSelection.svelte.js +94 -20
  82. package/dist/query-engine/FieldActivity.svelte.d.ts +79 -0
  83. package/dist/query-engine/FieldActivity.svelte.js +207 -0
  84. package/dist/query-engine/FilterGroup.svelte.d.ts +53 -29
  85. package/dist/query-engine/FilterGroup.svelte.js +181 -122
  86. package/dist/query-engine/FilterGroupRegistry.svelte.d.ts +42 -0
  87. package/dist/query-engine/FilterGroupRegistry.svelte.js +103 -1
  88. package/dist/query-engine/MarkHandle.svelte.d.ts +9 -1
  89. package/dist/query-engine/MarkHandle.svelte.js +12 -2
  90. package/dist/query-engine/QueryEngine.d.ts +52 -16
  91. package/dist/query-engine/QueryEngine.js +90 -28
  92. package/dist/query-engine/categorical-identity.d.ts +99 -27
  93. package/dist/query-engine/categorical-identity.js +210 -51
  94. package/dist/query-engine/index.d.ts +2 -1
  95. package/dist/query-engine/index.js +1 -0
  96. package/dist/query-engine/preagg/Preagg.d.ts +1 -1
  97. package/dist/query-engine/preagg/Preagg.js +11 -9
  98. package/dist/query-engine/preagg/plan.d.ts +9 -5
  99. package/dist/query-engine/preagg/plan.js +13 -11
  100. package/dist/query-engine/toggle-predicate.js +12 -1
  101. package/dist/scales/ActivityTable.d.ts +99 -0
  102. package/dist/scales/ActivityTable.js +211 -0
  103. package/dist/scales/BandScaleState.svelte.d.ts +28 -7
  104. package/dist/scales/BandScaleState.svelte.js +55 -20
  105. package/dist/scales/ChartScales.svelte.d.ts +2 -2
  106. package/dist/scales/ColorScaleState.svelte.d.ts +36 -8
  107. package/dist/scales/ColorScaleState.svelte.js +118 -24
  108. package/dist/scales/DomainResolver.d.ts +2 -0
  109. package/dist/scales/DomainResolver.js +15 -8
  110. package/dist/scales/ScaleRegistry.svelte.d.ts +9 -0
  111. package/dist/scales/ScaleRegistry.svelte.js +22 -1
  112. package/dist/scales/TimeScaleState.svelte.js +7 -1
  113. package/dist/spec/index.js +1 -0
  114. package/dist/spec/types.d.ts +2 -0
  115. package/dist/transforms/index.d.ts +27 -0
  116. package/dist/transforms/index.js +97 -3
  117. package/dist/types.d.ts +30 -0
  118. package/package.json +1 -1
@@ -0,0 +1,99 @@
1
+ import { TransitionClock, type ClockLoop } from '../animation';
2
+ /**
3
+ * Stiffness for a selection fade: ~0.25 s to settle, against ~0.32 s for a data
4
+ * transition (TRANSITION_STIFFNESS, 240). A click should feel immediate; the
5
+ * charts it filters take their own time to re-query and morph.
6
+ */
7
+ export declare const SELECTION_STIFFNESS = 400;
8
+ /**
9
+ * How visible an inactive category is, by default: marks are drawn at this
10
+ * opacity (the shaders' SELECTION_DIM_STRENGTH is 1 - this), and
11
+ * band labels fade to it.
12
+ */
13
+ export declare const DEFAULT_INACTIVE_OPACITY = 0.25;
14
+ /**
15
+ * How visible a category is for its activity: `inactiveOpacity` when
16
+ * unselected, 1 when selected, and in between mid-fade.
17
+ *
18
+ * The CPU twin of the shaders' `selectionDim`, which computes the same thing as
19
+ * `1 - dimStrength * (1 - activity)` with `dimStrength = 1 - inactiveOpacity`.
20
+ * Band labels and legend entries use this, so they match the marks.
21
+ */
22
+ export declare function selectionOpacity(activity: number, inactiveOpacity?: number): number;
23
+ /**
24
+ * Which categories of a scale are selected, and how far each one has faded.
25
+ *
26
+ * One float per identity id: 1 active, 0 inactive. A categorical scale owns one
27
+ * of these beside its slots, and the shaders read it through the same id they
28
+ * already resolve to a slot, so every mark drawn against the scale dims its
29
+ * inactive categories without knowing whether it is the source of the
30
+ * selection, filtered by it, or outside the filter group.
31
+ *
32
+ * The same shape as SlotTransition, on its own clock: `from` and `to` are the
33
+ * two ends of the fade, the value NOW is `mix(from, to, t)`, the GPU computes it
34
+ * for every id, and `activityOf` for the few a CPU consumer asks about. A
35
+ * SEPARATE clock from the slots', because a click changes what is selected
36
+ * without changing any data, and a data update has nothing to do with dimming.
37
+ * One `t` for every id, not a spring per category: everything fading because of
38
+ * one click arrives together, and there is one moment of rest.
39
+ */
40
+ export declare class ActivityTable {
41
+ #private;
42
+ /**
43
+ * Bumped whenever `from` or `to` change: a selection, a resize, a settle. The
44
+ * packer compares this rather than the contents, so a frame where only `t`
45
+ * moved uploads nothing.
46
+ */
47
+ version: number;
48
+ readonly clock: TransitionClock;
49
+ /**
50
+ * `charts` is required: the loop redraws only the charts a clock names, so a
51
+ * fade whose clock named none would animate without anything redrawing it.
52
+ * `loop` is null in unit tests and before a coordinator exists, and then a
53
+ * fade lands at once. `onFrame` is called for every driven frame and once
54
+ * more at rest, for CPU consumers such as axis labels.
55
+ */
56
+ constructor(loop: ClockLoop | null, options: {
57
+ charts: () => string[];
58
+ onFrame?: () => void;
59
+ stiffness?: number;
60
+ });
61
+ /** How many ids the table covers. */
62
+ get count(): number;
63
+ get from(): Float32Array;
64
+ get to(): Float32Array;
65
+ /** Extrapolated for the frame being drawn. 1 when settled. */
66
+ get t(): number;
67
+ /** True while the clock is in the loop. */
68
+ get isAnimating(): boolean;
69
+ /**
70
+ * How active one id is now, 0..1. An id outside the table is fully active,
71
+ * the same answer the shader gives for an id past the block's count.
72
+ */
73
+ activityOf(id: number): number;
74
+ /**
75
+ * Cover `count` ids. Existing ids keep their fade; new ones snap to their
76
+ * state rather than fading in.
77
+ *
78
+ * `active` is the selection the new ids join, as ids: null for none, so
79
+ * every new id is active. Without it, a new id is inactive while something
80
+ * is selected, since a category that did not exist when the selection was
81
+ * made is not part of it. Passing it matters when the selection was made
82
+ * before the ids existed (before identity landed, or across a reload): the
83
+ * table has not seen it, and a new id it names must start selected rather
84
+ * than fade in from 0, and one it does not name must start dim rather than
85
+ * fade down from 1.
86
+ *
87
+ * `resize(0)` drops every id, for ids that stopped meaning their category.
88
+ */
89
+ resize(count: number, active?: readonly number[] | null): void;
90
+ /**
91
+ * Select exactly these ids; every other id fades out. Ids outside the table
92
+ * are ignored. Does nothing when the selection is the one already headed to.
93
+ */
94
+ setActive(ids: Iterable<number>): void;
95
+ /** Nothing selected: every id fades back to active. */
96
+ clear(): void;
97
+ /** Leave the loop, for an owner being torn down. Later changes land at once. */
98
+ destroy(): void;
99
+ }
@@ -0,0 +1,211 @@
1
+ import { TransitionClock } from '../animation';
2
+ /**
3
+ * Stiffness for a selection fade: ~0.25 s to settle, against ~0.32 s for a data
4
+ * transition (TRANSITION_STIFFNESS, 240). A click should feel immediate; the
5
+ * charts it filters take their own time to re-query and morph.
6
+ */
7
+ export const SELECTION_STIFFNESS = 400;
8
+ /**
9
+ * How visible an inactive category is, by default: marks are drawn at this
10
+ * opacity (the shaders' SELECTION_DIM_STRENGTH is 1 - this), and
11
+ * band labels fade to it.
12
+ */
13
+ export const DEFAULT_INACTIVE_OPACITY = 0.25;
14
+ /**
15
+ * How visible a category is for its activity: `inactiveOpacity` when
16
+ * unselected, 1 when selected, and in between mid-fade.
17
+ *
18
+ * The CPU twin of the shaders' `selectionDim`, which computes the same thing as
19
+ * `1 - dimStrength * (1 - activity)` with `dimStrength = 1 - inactiveOpacity`.
20
+ * Band labels and legend entries use this, so they match the marks.
21
+ */
22
+ export function selectionOpacity(activity, inactiveOpacity = DEFAULT_INACTIVE_OPACITY) {
23
+ return inactiveOpacity + (1 - inactiveOpacity) * activity;
24
+ }
25
+ /**
26
+ * Which categories of a scale are selected, and how far each one has faded.
27
+ *
28
+ * One float per identity id: 1 active, 0 inactive. A categorical scale owns one
29
+ * of these beside its slots, and the shaders read it through the same id they
30
+ * already resolve to a slot, so every mark drawn against the scale dims its
31
+ * inactive categories without knowing whether it is the source of the
32
+ * selection, filtered by it, or outside the filter group.
33
+ *
34
+ * The same shape as SlotTransition, on its own clock: `from` and `to` are the
35
+ * two ends of the fade, the value NOW is `mix(from, to, t)`, the GPU computes it
36
+ * for every id, and `activityOf` for the few a CPU consumer asks about. A
37
+ * SEPARATE clock from the slots', because a click changes what is selected
38
+ * without changing any data, and a data update has nothing to do with dimming.
39
+ * One `t` for every id, not a spring per category: everything fading because of
40
+ * one click arrives together, and there is one moment of rest.
41
+ */
42
+ export class ActivityTable {
43
+ #from = new Float32Array(0);
44
+ #to = new Float32Array(0);
45
+ /**
46
+ * Where a new selection is computed before it is compared with `to`. Reused,
47
+ * because `setActive` is called on every filter update, and most of those
48
+ * (every brush move on some other field) change nothing here.
49
+ */
50
+ #scratch = new Float32Array(0);
51
+ /** Whether some categories are selected, which decides what a new id starts as. */
52
+ #selecting = false;
53
+ /**
54
+ * Bumped whenever `from` or `to` change: a selection, a resize, a settle. The
55
+ * packer compares this rather than the contents, so a frame where only `t`
56
+ * moved uploads nothing.
57
+ */
58
+ version = 0;
59
+ clock;
60
+ #onFrame;
61
+ /**
62
+ * `charts` is required: the loop redraws only the charts a clock names, so a
63
+ * fade whose clock named none would animate without anything redrawing it.
64
+ * `loop` is null in unit tests and before a coordinator exists, and then a
65
+ * fade lands at once. `onFrame` is called for every driven frame and once
66
+ * more at rest, for CPU consumers such as axis labels.
67
+ */
68
+ constructor(loop, options) {
69
+ this.#onFrame = options.onFrame ?? null;
70
+ this.clock = new TransitionClock({
71
+ loop,
72
+ charts: options.charts,
73
+ stiffness: options.stiffness ?? SELECTION_STIFFNESS,
74
+ onRest: () => this.#onClockRest(),
75
+ onFrame: this.#onFrame ?? undefined
76
+ });
77
+ }
78
+ /** How many ids the table covers. */
79
+ get count() {
80
+ return this.#to.length;
81
+ }
82
+ get from() {
83
+ return this.#from;
84
+ }
85
+ get to() {
86
+ return this.#to;
87
+ }
88
+ /** Extrapolated for the frame being drawn. 1 when settled. */
89
+ get t() {
90
+ return this.clock.t;
91
+ }
92
+ /** True while the clock is in the loop. */
93
+ get isAnimating() {
94
+ return this.clock.running;
95
+ }
96
+ /**
97
+ * How active one id is now, 0..1. An id outside the table is fully active,
98
+ * the same answer the shader gives for an id past the block's count.
99
+ */
100
+ activityOf(id) {
101
+ const f = this.#from[id];
102
+ if (f === undefined)
103
+ return 1;
104
+ return f + (this.#to[id] - f) * this.clock.t;
105
+ }
106
+ /**
107
+ * Cover `count` ids. Existing ids keep their fade; new ones snap to their
108
+ * state rather than fading in.
109
+ *
110
+ * `active` is the selection the new ids join, as ids: null for none, so
111
+ * every new id is active. Without it, a new id is inactive while something
112
+ * is selected, since a category that did not exist when the selection was
113
+ * made is not part of it. Passing it matters when the selection was made
114
+ * before the ids existed (before identity landed, or across a reload): the
115
+ * table has not seen it, and a new id it names must start selected rather
116
+ * than fade in from 0, and one it does not name must start dim rather than
117
+ * fade down from 1.
118
+ *
119
+ * `resize(0)` drops every id, for ids that stopped meaning their category.
120
+ */
121
+ resize(count, active) {
122
+ if (count === this.#to.length)
123
+ return;
124
+ const keep = Math.min(this.#to.length, count);
125
+ const selecting = active === undefined ? this.#selecting : active !== null;
126
+ const fill = selecting ? 0 : 1;
127
+ const grown = (old) => {
128
+ const next = new Float32Array(count);
129
+ next.fill(fill);
130
+ next.set(old.subarray(0, keep), 0);
131
+ if (active) {
132
+ for (const id of active)
133
+ if (id >= keep && id < count)
134
+ next[id] = 1;
135
+ }
136
+ return next;
137
+ };
138
+ this.#from = grown(this.#from);
139
+ this.#to = grown(this.#to);
140
+ this.#scratch = new Float32Array(count);
141
+ this.version++;
142
+ }
143
+ /**
144
+ * Select exactly these ids; every other id fades out. Ids outside the table
145
+ * are ignored. Does nothing when the selection is the one already headed to.
146
+ */
147
+ setActive(ids) {
148
+ const next = this.#scratch;
149
+ next.fill(0);
150
+ for (const id of ids) {
151
+ if (id >= 0 && id < next.length)
152
+ next[id] = 1;
153
+ }
154
+ this.#selecting = true;
155
+ this.#retarget(next);
156
+ }
157
+ /** Nothing selected: every id fades back to active. */
158
+ clear() {
159
+ const next = this.#scratch;
160
+ next.fill(1);
161
+ this.#selecting = false;
162
+ this.#retarget(next);
163
+ }
164
+ /** Leave the loop, for an owner being torn down. Later changes land at once. */
165
+ destroy() {
166
+ this.clock.stop();
167
+ }
168
+ /**
169
+ * Fold, point every id at its new state, and wake the loop.
170
+ *
171
+ * Returns early when `next` equals `to`: the fade already in flight (or the
172
+ * rest already reached) is headed exactly there, and restarting it would
173
+ * re-fold and bump the version for nothing, on every filter update.
174
+ */
175
+ #retarget(next) {
176
+ const from = this.#from;
177
+ const to = this.#to;
178
+ let changed = false;
179
+ for (let id = 0; id < to.length; id++) {
180
+ if (to[id] !== next[id]) {
181
+ changed = true;
182
+ break;
183
+ }
184
+ }
185
+ if (!changed)
186
+ return;
187
+ // THE FOLD: every id continues from where it is now, so a click landing
188
+ // mid-fade reverses or redirects it in place instead of jumping.
189
+ const t = this.clock.t;
190
+ for (let id = 0; id < to.length; id++) {
191
+ from[id] += (to[id] - from[id]) * t;
192
+ to[id] = next[id];
193
+ }
194
+ this.clock.restart();
195
+ this.version++;
196
+ }
197
+ /**
198
+ * Land exactly on `to`. A spring rests at an epsilon, and a table left at
199
+ * 0.0003 would start its next fold from a value that was never a state.
200
+ */
201
+ #settle() {
202
+ this.clock.settle();
203
+ this.#from.set(this.#to);
204
+ }
205
+ #onClockRest() {
206
+ this.#settle();
207
+ this.version++;
208
+ // The last frame, at exactly t = 1, which the loop's final frame was not.
209
+ this.#onFrame?.();
210
+ }
211
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ScaleBand } from 'd3-scale';
2
- import type { DimValue, FieldIdentity } from '../query-engine/categorical-identity';
2
+ import type { DimValue, FieldIdentity, SyntheticId } from '../query-engine/categorical-identity';
3
3
  import type { ClockLoop } from '../animation';
4
4
  import { type BandSlots } from './SlotTransition';
5
5
  /**
@@ -77,10 +77,6 @@ export declare class BandScaleState {
77
77
  currentDataPoint: number | null;
78
78
  constructor(name: string, domain?: DimValue[], config?: BandScaleConfig);
79
79
  setReady(): void;
80
- get activeValues(): Set<DimValue> | null;
81
- setActiveValues(values: Iterable<DimValue>): void;
82
- clearActiveValues(): void;
83
- isActive(value: DimValue): boolean;
84
80
  /**
85
81
  * Marks the scale dead. Clears nothing, deliberately.
86
82
  *
@@ -121,6 +117,12 @@ export declare class BandScaleState {
121
117
  * where name is "__facet__species" but sourceField is "species").
122
118
  */
123
119
  get sourceField(): string;
120
+ /**
121
+ * The identity key of the dim this scale adopted, or null before it has one.
122
+ * Selection activity is kept per identity key, so this is what the packer
123
+ * points the scale's block at.
124
+ */
125
+ get identityKey(): string | null;
124
126
  /**
125
127
  * Enable or disable scale updates
126
128
  * When disabled, domain is frozen at current state
@@ -209,6 +211,24 @@ export declare class BandScaleState {
209
211
  * undisplayed Other is ignored.
210
212
  */
211
213
  setOtherId(id: number | null): void;
214
+ /**
215
+ * This axis's Other bucket, shared by every mark on it that has one.
216
+ *
217
+ * One id per band scale, not per mark or per chart: marks share a band scale
218
+ * only when they agree on what Other is — the scale's key carries the table
219
+ * and the category-reducing transform (its `transformKey`), N included — so
220
+ * everything drawn on one axis lines up in one Other band. A bar and its
221
+ * value labels, or two charts with the identical topN, share it.
222
+ *
223
+ * Reference-counted by mark id. The first holder acquires; holders arriving
224
+ * while that is in flight share it; the last to release gives it back.
225
+ */
226
+ retainOther(holder: string, acquire: () => Promise<SyntheticId>): Promise<SyntheticId>;
227
+ /**
228
+ * Drop `holder`'s claim on the Other bucket. The last one out hands the id to
229
+ * `release` — once the acquire has landed, if it is still in flight.
230
+ */
231
+ releaseOther(holder: string, release: (other: SyntheticId) => void): void;
212
232
  /**
213
233
  * Identity id currently displayed at `slot`, or null when the slot holds no
214
234
  * category. Use this to compare a display position — what invertScreenToData
@@ -375,6 +395,9 @@ export declare class BandScaleState {
375
395
  * so it fades out over the half slot before that centre reaches the axis end
376
396
  * (the animated count), and is gone from there; the same at the start. An
377
397
  * entrant's label appears as its band crosses in.
398
+ *
399
+ * A selection's dimming is not here: it belongs to the filter groups of the
400
+ * marks the axis labels, so the axis multiplies it in (BandAxisTicks).
378
401
  */
379
402
  tickOpacity(key: number): number;
380
403
  /**
@@ -393,8 +416,6 @@ export declare class BandScaleState {
393
416
  * re-derived by such a caller.
394
417
  */
395
418
  slotPosition(slot: number, rangeSize: number, axis: 'x' | 'y'): number;
396
- /** Whether a tick's category is outside the current selection. */
397
- isTickDimmed(tick: BandTick): boolean;
398
419
  /**
399
420
  * Centre of a (possibly fractional) slot, measured from the start of the
400
421
  * range — the same layout the shader's computeBandLayout uses, so a label
@@ -47,7 +47,6 @@ export class BandScaleState {
47
47
  // =========================================================================
48
48
  // SELECTION STATE
49
49
  // =========================================================================
50
- #activeValues = $state(null); // null = all active, Set = only these active
51
50
  // =========================================================================
52
51
  // POINTER STATE
53
52
  // =========================================================================
@@ -104,21 +103,6 @@ export class BandScaleState {
104
103
  setReady() {
105
104
  this.ready = true;
106
105
  }
107
- // =========================================================================
108
- // ACTIVE VALUES (selection feedback)
109
- // =========================================================================
110
- get activeValues() {
111
- return this.#activeValues;
112
- }
113
- setActiveValues(values) {
114
- this.#activeValues = values instanceof Set ? values : new Set(values);
115
- }
116
- clearActiveValues() {
117
- this.#activeValues = null;
118
- }
119
- isActive(value) {
120
- return this.#activeValues == null || this.#activeValues.has(value);
121
- }
122
106
  /**
123
107
  * Marks the scale dead. Clears nothing, deliberately.
124
108
  *
@@ -186,6 +170,15 @@ export class BandScaleState {
186
170
  get sourceField() {
187
171
  return this.#sourceField ?? this.name;
188
172
  }
173
+ /**
174
+ * The identity key of the dim this scale adopted, or null before it has one.
175
+ * Selection activity is kept per identity key, so this is what the packer
176
+ * points the scale's block at.
177
+ */
178
+ get identityKey() {
179
+ // Only while the channel carries ids, as for colour.
180
+ return this.hasIdentity ? this.#resolver.identity.key || null : null;
181
+ }
189
182
  // =========================================================================
190
183
  // FREEZE/DEBOUNCE CONTROL
191
184
  // =========================================================================
@@ -334,6 +327,49 @@ export class BandScaleState {
334
327
  setOtherId(id) {
335
328
  this.#slots.otherId = id;
336
329
  }
330
+ /**
331
+ * This axis's Other bucket, shared by every mark on it that has one.
332
+ *
333
+ * One id per band scale, not per mark or per chart: marks share a band scale
334
+ * only when they agree on what Other is — the scale's key carries the table
335
+ * and the category-reducing transform (its `transformKey`), N included — so
336
+ * everything drawn on one axis lines up in one Other band. A bar and its
337
+ * value labels, or two charts with the identical topN, share it.
338
+ *
339
+ * Reference-counted by mark id. The first holder acquires; holders arriving
340
+ * while that is in flight share it; the last to release gives it back.
341
+ */
342
+ retainOther(holder, acquire) {
343
+ this.#otherHolders.add(holder);
344
+ if (!this.#other) {
345
+ const pending = acquire();
346
+ this.#other = pending;
347
+ pending.then((other) => {
348
+ if (this.#other === pending)
349
+ this.setOtherId(other.id);
350
+ }, () => {
351
+ if (this.#other === pending)
352
+ this.#other = null;
353
+ });
354
+ }
355
+ return this.#other;
356
+ }
357
+ /**
358
+ * Drop `holder`'s claim on the Other bucket. The last one out hands the id to
359
+ * `release` — once the acquire has landed, if it is still in flight.
360
+ */
361
+ releaseOther(holder, release) {
362
+ if (!this.#otherHolders.delete(holder) || this.#otherHolders.size > 0)
363
+ return;
364
+ const pending = this.#other;
365
+ this.#other = null;
366
+ this.setOtherId(null);
367
+ pending?.then(release, () => { });
368
+ }
369
+ /** The marks holding this axis's Other bucket. */
370
+ #otherHolders = new Set();
371
+ /** The bucket they share, while any holds it. */
372
+ #other = null;
337
373
  // -------------------------------------------------------------------------
338
374
  // IDENTITY <-> DISPLAY, FOR CPU CONSUMERS
339
375
  // -------------------------------------------------------------------------
@@ -709,6 +745,9 @@ export class BandScaleState {
709
745
  * so it fades out over the half slot before that centre reaches the axis end
710
746
  * (the animated count), and is gone from there; the same at the start. An
711
747
  * entrant's label appears as its band crosses in.
748
+ *
749
+ * A selection's dimming is not here: it belongs to the filter groups of the
750
+ * marks the axis labels, so the axis multiplies it in (BandAxisTicks).
712
751
  */
713
752
  tickOpacity(key) {
714
753
  void this.#slotFrame;
@@ -738,10 +777,6 @@ export class BandScaleState {
738
777
  const centre = this.slotCenter(slot, rangeSize);
739
778
  return axis === 'y' ? rangeSize - centre : centre;
740
779
  }
741
- /** Whether a tick's category is outside the current selection. */
742
- isTickDimmed(tick) {
743
- return tick.value != null && !this.isActive(tick.value);
744
- }
745
780
  /**
746
781
  * Centre of a (possibly fractional) slot, measured from the start of the
747
782
  * range — the same layout the shader's computeBandLayout uses, so a label
@@ -65,8 +65,8 @@ export declare class ChartScales {
65
65
  };
66
66
  get xScaleState(): ScaleStateInstance | null;
67
67
  get yScaleState(): ScaleStateInstance | null;
68
- baseXScale: import("d3-scale").ScaleContinuousNumeric<number, number, never> | import("d3-scale").ScaleBand<import("../query-engine/categorical-identity").DimValue>;
69
- baseYScale: import("d3-scale").ScaleContinuousNumeric<number, number, never> | import("d3-scale").ScaleBand<import("../query-engine/categorical-identity").DimValue>;
68
+ baseXScale: import("d3-scale").ScaleBand<import("../query-engine/categorical-identity").DimValue> | import("d3-scale").ScaleContinuousNumeric<number, number, never>;
69
+ baseYScale: import("d3-scale").ScaleBand<import("../query-engine/categorical-identity").DimValue> | import("d3-scale").ScaleContinuousNumeric<number, number, never>;
70
70
  get xScale(): D3Scale | null;
71
71
  get yScale(): D3Scale | null;
72
72
  get xTargetScale(): D3Scale | null;
@@ -25,7 +25,21 @@ export declare class ColorScaleState {
25
25
  domain: (string | number)[];
26
26
  ready: boolean;
27
27
  _version: number;
28
+ /**
29
+ * Read by the packer every frame. The rank table covers the ids the field had
30
+ * when it was built, and the field's identity grows in place: another table
31
+ * appends categories, and a chart acquiring an Other bucket appends its id,
32
+ * any chart, since this scale is shared per field. An id past the table's end
33
+ * reads as unranked and draws transparent, so a grown identity rebuilds it
34
+ * here, once per growth.
35
+ */
28
36
  get slots(): SlotSource;
37
+ /**
38
+ * Reactive: changes whenever the palette ranks are rebuilt, which is also when
39
+ * identity is adopted. A reader of `identityKey` or `getColor` in a derived
40
+ * reads this to re-run then.
41
+ */
42
+ get ranksRevision(): number;
29
43
  /**
30
44
  * Called by the packer when this scale's block moves, and told its own base
31
45
  * rather than having it written into its config from outside.
@@ -51,9 +65,14 @@ export declare class ColorScaleState {
51
65
  setSlotBase(base: number): void;
52
66
  constructor(config: ColorScaleConfig, registry: ScaleRegistry, gpuCoordinator: GPUCoordinator | null);
53
67
  get field(): string;
68
+ /**
69
+ * The identity key of the dim this scale adopted, or null before it has one.
70
+ * Selection activity is kept per identity key, so this is what the packer
71
+ * points the scale's block at.
72
+ */
73
+ get identityKey(): string | null;
54
74
  get scheme(): string;
55
75
  get scaleType(): ColorScaleType;
56
- get activeValues(): Set<string | number> | null;
57
76
  get isOrdinal(): boolean;
58
77
  get isContinuous(): boolean;
59
78
  /**
@@ -73,9 +92,14 @@ export declare class ColorScaleState {
73
92
  */
74
93
  get d3Scale(): D3ColorScale | null;
75
94
  /**
76
- * Unique scale ID for ViewManager registration.
95
+ * The id the coordinator tracks this scale by, as it does every scale.
96
+ *
97
+ * Tracking is what puts the scale in front of the identity slot packer
98
+ * (see tracked-scales.ts). Without an id, its rank table and selection
99
+ * activity were never packed: the shader read the channel's dim id as a
100
+ * palette index, and selection dimming had nothing to read.
77
101
  */
78
- get scaleId(): string;
102
+ get id(): string;
79
103
  /**
80
104
  * Converts raw data into GPU-ready values.
81
105
  * - Ordinal: string -> 0-based index (-1 = null/unknown, 0+ = categories)
@@ -95,13 +119,15 @@ export declare class ColorScaleState {
95
119
  */
96
120
  setIdentity(identity: FieldIdentity): void;
97
121
  /** True once this scale's channel carries dim ids rather than palette indices. */
122
+ /** The field's live id <-> value bijection, once adopted. */
123
+ get identity(): FieldIdentity | null;
98
124
  get hasIdentity(): boolean;
99
125
  /**
100
126
  * Source value for a channel id — the decode side of the encode the query
101
127
  * performs, for tooltips and legends. Undefined without identity, where the
102
128
  * channel carries a palette index and the caller should read the domain.
103
129
  */
104
- categoryAt(id: number): string | number | undefined;
130
+ categoryAt(id: number): string | number | null | undefined;
105
131
  /** Update the domain. Triggers GPU re-upload. */
106
132
  setDomain(newDomain: (string | number)[]): void;
107
133
  /**
@@ -139,14 +165,16 @@ export declare class ColorScaleState {
139
165
  * Returns NULL_COLOR for values that are unmappable under the current
140
166
  * transform (e.g. non-positive values under log), mirroring the GPU shader.
141
167
  */
142
- getColor(value: string | number): string;
168
+ getColor(value: string | number | null): string;
143
169
  /**
144
170
  * Get color as RGBA array (0-1 range).
145
171
  */
146
172
  getColorRGBA(value: string | number): RGBA;
147
- setActiveValues(values: Iterable<string | number>): void;
148
- clearActiveValues(): void;
149
- isActive(value: string | number): boolean;
173
+ /**
174
+ * Charts drawing this scale. Asked of the registry, as BandScaleState does,
175
+ * so it is current when charts come and go.
176
+ */
177
+ get _affectedChartsArray(): string[];
150
178
  /**
151
179
  * Marks the scale dead. Clears nothing, deliberately — see
152
180
  * BandScaleState.destroy for the full argument.