@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
|
@@ -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,53 @@ export class BandScaleState {
|
|
|
334
327
|
setOtherId(id) {
|
|
335
328
|
this.#slots.otherId = id;
|
|
336
329
|
}
|
|
330
|
+
/** This axis's Other bucket id, while a mark on it has one; null otherwise. */
|
|
331
|
+
get otherId() {
|
|
332
|
+
return this.#slots.otherId;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* This axis's Other bucket, shared by every mark on it that has one.
|
|
336
|
+
*
|
|
337
|
+
* One id per band scale, not per mark or per chart: marks share a band scale
|
|
338
|
+
* only when they agree on what Other is — the scale's key carries the table
|
|
339
|
+
* and the category-reducing transform (its `transformKey`), N included — so
|
|
340
|
+
* everything drawn on one axis lines up in one Other band. A bar and its
|
|
341
|
+
* value labels, or two charts with the identical topN, share it.
|
|
342
|
+
*
|
|
343
|
+
* Reference-counted by mark id. The first holder acquires; holders arriving
|
|
344
|
+
* while that is in flight share it; the last to release gives it back.
|
|
345
|
+
*/
|
|
346
|
+
retainOther(holder, acquire) {
|
|
347
|
+
this.#otherHolders.add(holder);
|
|
348
|
+
if (!this.#other) {
|
|
349
|
+
const pending = acquire();
|
|
350
|
+
this.#other = pending;
|
|
351
|
+
pending.then((other) => {
|
|
352
|
+
if (this.#other === pending)
|
|
353
|
+
this.setOtherId(other.id);
|
|
354
|
+
}, () => {
|
|
355
|
+
if (this.#other === pending)
|
|
356
|
+
this.#other = null;
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return this.#other;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Drop `holder`'s claim on the Other bucket. The last one out hands the id to
|
|
363
|
+
* `release` — once the acquire has landed, if it is still in flight.
|
|
364
|
+
*/
|
|
365
|
+
releaseOther(holder, release) {
|
|
366
|
+
if (!this.#otherHolders.delete(holder) || this.#otherHolders.size > 0)
|
|
367
|
+
return;
|
|
368
|
+
const pending = this.#other;
|
|
369
|
+
this.#other = null;
|
|
370
|
+
this.setOtherId(null);
|
|
371
|
+
pending?.then(release, () => { });
|
|
372
|
+
}
|
|
373
|
+
/** The marks holding this axis's Other bucket. */
|
|
374
|
+
#otherHolders = new Set();
|
|
375
|
+
/** The bucket they share, while any holds it. */
|
|
376
|
+
#other = null;
|
|
337
377
|
// -------------------------------------------------------------------------
|
|
338
378
|
// IDENTITY <-> DISPLAY, FOR CPU CONSUMERS
|
|
339
379
|
// -------------------------------------------------------------------------
|
|
@@ -585,8 +625,10 @@ export class BandScaleState {
|
|
|
585
625
|
/**
|
|
586
626
|
* Get D3 band scale configured for rendering
|
|
587
627
|
*/
|
|
588
|
-
getBaseScale(
|
|
589
|
-
|
|
628
|
+
getBaseScale(_axis, innerWidthOrHeight) {
|
|
629
|
+
// The same on both axes, not reversed on y: slot 0 is at the top, as in
|
|
630
|
+
// the shader (slotPosition).
|
|
631
|
+
this.#sharedScale.range([0, innerWidthOrHeight]);
|
|
590
632
|
if (this.#paddingInner !== null && this.#paddingOuter !== null) {
|
|
591
633
|
this.#sharedScale.paddingInner(this.#paddingInner).paddingOuter(this.#paddingOuter);
|
|
592
634
|
}
|
|
@@ -709,6 +751,9 @@ export class BandScaleState {
|
|
|
709
751
|
* so it fades out over the half slot before that centre reaches the axis end
|
|
710
752
|
* (the animated count), and is gone from there; the same at the start. An
|
|
711
753
|
* entrant's label appears as its band crosses in.
|
|
754
|
+
*
|
|
755
|
+
* A selection's dimming is not here: it belongs to the filter groups of the
|
|
756
|
+
* marks the axis labels, so the axis multiplies it in (BandAxisTicks).
|
|
712
757
|
*/
|
|
713
758
|
tickOpacity(key) {
|
|
714
759
|
void this.#slotFrame;
|
|
@@ -720,27 +765,25 @@ export class BandScaleState {
|
|
|
720
765
|
/**
|
|
721
766
|
* Where a tick's label sits now, in pixels along the axis: the centre of its
|
|
722
767
|
* category's current slot, so it travels with the bar. Slot 0 is at the start
|
|
723
|
-
* of the range on
|
|
724
|
-
*
|
|
768
|
+
* of the range on both axes: the left on x, the top on y, as in the shader
|
|
769
|
+
* and d3's range. Per frame, O(1).
|
|
725
770
|
*/
|
|
726
|
-
tickPosition(key, rangeSize
|
|
727
|
-
return this.slotPosition(this.tickSlot(key), rangeSize
|
|
771
|
+
tickPosition(key, rangeSize) {
|
|
772
|
+
return this.slotPosition(this.tickSlot(key), rangeSize);
|
|
728
773
|
}
|
|
729
774
|
/**
|
|
730
775
|
* The same, for a caller that already has the slot.
|
|
731
776
|
*
|
|
732
777
|
* A consumer that has to test the slot first — an SVG label, which must not
|
|
733
778
|
* draw a category the axis is not displaying — would otherwise ask for it
|
|
734
|
-
* twice per item per frame.
|
|
735
|
-
*
|
|
779
|
+
* twice per item per frame.
|
|
780
|
+
*
|
|
781
|
+
* The same on both axes. A y band axis reads top-down, like a list: slot 0
|
|
782
|
+
* at the top, so a ranking and an explicit domain need no reversing, and
|
|
783
|
+
* the exit one past the last slot is the end of the list on either axis.
|
|
736
784
|
*/
|
|
737
|
-
slotPosition(slot, rangeSize
|
|
738
|
-
|
|
739
|
-
return axis === 'y' ? rangeSize - centre : centre;
|
|
740
|
-
}
|
|
741
|
-
/** Whether a tick's category is outside the current selection. */
|
|
742
|
-
isTickDimmed(tick) {
|
|
743
|
-
return tick.value != null && !this.isActive(tick.value);
|
|
785
|
+
slotPosition(slot, rangeSize) {
|
|
786
|
+
return this.slotCenter(slot, rangeSize);
|
|
744
787
|
}
|
|
745
788
|
/**
|
|
746
789
|
* Centre of a (possibly fractional) slot, measured from the start of the
|
|
@@ -819,7 +862,9 @@ export class BandScaleState {
|
|
|
819
862
|
* Returns the 0-based index of the band under the cursor, or null if
|
|
820
863
|
* no bands exist.
|
|
821
864
|
*/
|
|
822
|
-
invertScreenToData(screenVal, rangeSize,
|
|
865
|
+
invertScreenToData(screenVal, rangeSize,
|
|
866
|
+
// Band slots run from the start of the range on both axes (slotPosition).
|
|
867
|
+
_isYAxis = false) {
|
|
823
868
|
const config = this.getConfig();
|
|
824
869
|
const n = config.categoryCount;
|
|
825
870
|
if (n === 0)
|
|
@@ -828,10 +873,7 @@ export class BandScaleState {
|
|
|
828
873
|
if (stepSize === 0)
|
|
829
874
|
return null;
|
|
830
875
|
const offset = config.paddingOuter * stepSize;
|
|
831
|
-
|
|
832
|
-
// so flip screen coordinate for Y axis inversion.
|
|
833
|
-
const pos = isYAxis ? rangeSize - screenVal : screenVal;
|
|
834
|
-
const bandIndex = Math.floor((pos - offset) / stepSize);
|
|
876
|
+
const bandIndex = Math.floor((screenVal - offset) / stepSize);
|
|
835
877
|
// Clamped to a WHOLE slot. Mid-transition the count is fractional, so a
|
|
836
878
|
// bare `n - 1` hands back a fraction for a pointer past the last band —
|
|
837
879
|
// and a fractional slot indexes nothing, so idAtSlot and the domain both
|
|
@@ -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").
|
|
69
|
-
baseYScale: import("d3-scale").
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { scaleOrdinal } from 'd3-scale';
|
|
3
3
|
import { createD3Scale, getOrdinalColors, bakeGradient, bakeInterpolator, bakePalette, getSchemeType, parseColorToRGBA, resolveColorScaleTypes, getResolvedInterpolator, transformTypeToGPU, applyTransform, colorScaleTicks } from './color-utils';
|
|
4
4
|
import { COLOR_RAMP_RESOLUTION } from '../gpu/GPUCoordinator.svelte';
|
|
5
|
-
import { COLOR_CONFIG_BYTES, COLOR_CONFIG_SLOT_BASE_OFFSET, NO_SLOT_TABLE } from '../gpu/ColorScaleShaderLib';
|
|
5
|
+
import { COLOR_CONFIG_BYTES, COLOR_CONFIG_SLOT_BASE_OFFSET, NO_SLOT_TABLE, NULL_COLOR_RANK } from '../gpu/ColorScaleShaderLib';
|
|
6
6
|
/**
|
|
7
7
|
* Reactive state for a GPU-backed color scale.
|
|
8
8
|
*
|
|
@@ -14,7 +14,7 @@ export class ColorScaleState {
|
|
|
14
14
|
#field;
|
|
15
15
|
#registry;
|
|
16
16
|
#gpuCoordinator;
|
|
17
|
-
#
|
|
17
|
+
#id = crypto.randomUUID();
|
|
18
18
|
// GPU slot indices (allocated from registry)
|
|
19
19
|
#configSlot = -1;
|
|
20
20
|
#rampSlot = -1; // Used by continuous scales
|
|
@@ -31,7 +31,6 @@ export class ColorScaleState {
|
|
|
31
31
|
// Reactive state
|
|
32
32
|
domain = $state([]);
|
|
33
33
|
ready = $state(false);
|
|
34
|
-
#activeValues = $state(null); // null = all active, Set = only these active
|
|
35
34
|
// Bumped on any visual-config change (transform type, constant, exponent, scheme).
|
|
36
35
|
// Legend components read this to know when to re-render the gradient.
|
|
37
36
|
_version = $state(0);
|
|
@@ -90,9 +89,37 @@ export class ColorScaleState {
|
|
|
90
89
|
* a change.
|
|
91
90
|
*/
|
|
92
91
|
#slots = { count: 0, from: new Float32Array(0), to: new Float32Array(0), t: 1, version: 0 };
|
|
92
|
+
/**
|
|
93
|
+
* Read by the packer every frame. The rank table covers the ids the field had
|
|
94
|
+
* when it was built, and the field's identity grows in place: another table
|
|
95
|
+
* appends categories, and a chart acquiring an Other bucket appends its id,
|
|
96
|
+
* any chart, since this scale is shared per field. An id past the table's end
|
|
97
|
+
* reads as unranked and draws transparent, so a grown identity rebuilds it
|
|
98
|
+
* here, once per growth.
|
|
99
|
+
*/
|
|
93
100
|
get slots() {
|
|
101
|
+
const identity = this.#identity;
|
|
102
|
+
if (identity?.present && identity.version !== this.#ranksVersion)
|
|
103
|
+
this.#refreshRanks();
|
|
94
104
|
return this.#slots;
|
|
95
105
|
}
|
|
106
|
+
/** The identity version the rank table was last built from. */
|
|
107
|
+
#ranksVersion = -1;
|
|
108
|
+
/**
|
|
109
|
+
* Bumped whenever the ranks change, so a reader of getColor (a legend's
|
|
110
|
+
* swatches) re-reads once identity lands. The ranks are a plain Map, and the
|
|
111
|
+
* domain usually arrives first: without this a legend kept the colours it
|
|
112
|
+
* computed before there were ranks.
|
|
113
|
+
*/
|
|
114
|
+
#ranksRevision = $state(0);
|
|
115
|
+
/**
|
|
116
|
+
* Reactive: changes whenever the palette ranks are rebuilt, which is also when
|
|
117
|
+
* identity is adopted. A reader of `identityKey` or `getColor` in a derived
|
|
118
|
+
* reads this to re-run then.
|
|
119
|
+
*/
|
|
120
|
+
get ranksRevision() {
|
|
121
|
+
return this.#ranksRevision;
|
|
122
|
+
}
|
|
96
123
|
/**
|
|
97
124
|
* Where this scale's block sits in the shared identity slot table. Assigned
|
|
98
125
|
* by the coordinator's packer.
|
|
@@ -132,9 +159,18 @@ export class ColorScaleState {
|
|
|
132
159
|
// Before the scale has a config slot there is nothing to patch; whichever
|
|
133
160
|
// upload path claims one writes the base with the rest of the struct.
|
|
134
161
|
if (this.#configSlot >= 0) {
|
|
135
|
-
this.#gpuCoordinator?.writeColorConfigSlotBase(this.#configSlot,
|
|
162
|
+
this.#gpuCoordinator?.writeColorConfigSlotBase(this.#configSlot, this.#configSlotBase);
|
|
136
163
|
}
|
|
137
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* The slot base the config carries: this scale's block while its channel
|
|
167
|
+
* carries ids, NO_SLOT_TABLE while it carries palette indices. The one rule
|
|
168
|
+
* for both writers (setSlotBase's patch and the full upload), so which wrote
|
|
169
|
+
* last cannot change what the GPU reads.
|
|
170
|
+
*/
|
|
171
|
+
get #configSlotBase() {
|
|
172
|
+
return this.hasIdentity && this.#slotBase >= 0 ? this.#slotBase : NO_SLOT_TABLE;
|
|
173
|
+
}
|
|
138
174
|
// ViewManager integration - tracks tables this scale has registered columns for
|
|
139
175
|
constructor(config, registry, gpuCoordinator) {
|
|
140
176
|
this.#field = config.field;
|
|
@@ -170,15 +206,22 @@ export class ColorScaleState {
|
|
|
170
206
|
get field() {
|
|
171
207
|
return this.#field;
|
|
172
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* The identity key of the dim this scale adopted, or null before it has one.
|
|
211
|
+
* Selection activity is kept per identity key, so this is what the packer
|
|
212
|
+
* points the scale's block at.
|
|
213
|
+
*/
|
|
214
|
+
get identityKey() {
|
|
215
|
+
// Only while the channel carries ids: a key without them would route a
|
|
216
|
+
// mark to dim by activity the shader cannot read.
|
|
217
|
+
return this.hasIdentity ? this.#identity.key || null : null;
|
|
218
|
+
}
|
|
173
219
|
get scheme() {
|
|
174
220
|
return this.#scheme;
|
|
175
221
|
}
|
|
176
222
|
get scaleType() {
|
|
177
223
|
return this.#scaleType;
|
|
178
224
|
}
|
|
179
|
-
get activeValues() {
|
|
180
|
-
return this.#activeValues;
|
|
181
|
-
}
|
|
182
225
|
get isOrdinal() {
|
|
183
226
|
return this.#renderType === 'ordinal';
|
|
184
227
|
}
|
|
@@ -208,10 +251,15 @@ export class ColorScaleState {
|
|
|
208
251
|
return this.#d3Scale;
|
|
209
252
|
}
|
|
210
253
|
/**
|
|
211
|
-
*
|
|
254
|
+
* The id the coordinator tracks this scale by, as it does every scale.
|
|
255
|
+
*
|
|
256
|
+
* Tracking is what puts the scale in front of the identity slot packer
|
|
257
|
+
* (see tracked-scales.ts). Without an id, its rank table and selection
|
|
258
|
+
* activity were never packed: the shader read the channel's dim id as a
|
|
259
|
+
* palette index, and selection dimming had nothing to read.
|
|
212
260
|
*/
|
|
213
|
-
get
|
|
214
|
-
return this.#
|
|
261
|
+
get id() {
|
|
262
|
+
return this.#id;
|
|
215
263
|
}
|
|
216
264
|
// ============================================================
|
|
217
265
|
// SLOT ALLOCATION
|
|
@@ -300,6 +348,10 @@ export class ColorScaleState {
|
|
|
300
348
|
return [lo, hi];
|
|
301
349
|
}
|
|
302
350
|
#uploadToGPU() {
|
|
351
|
+
// A destroyed scale's slots may already belong to another scale: a late
|
|
352
|
+
// upload (identity landing after the chart unmounted) would overwrite them.
|
|
353
|
+
if (this.#destroyed)
|
|
354
|
+
return;
|
|
303
355
|
if (!this.#gpuCoordinator) {
|
|
304
356
|
console.warn('[ColorScaleState] No GPU coordinator, skipping upload');
|
|
305
357
|
return;
|
|
@@ -388,15 +440,28 @@ export class ColorScaleState {
|
|
|
388
440
|
*/
|
|
389
441
|
setIdentity(identity) {
|
|
390
442
|
this.#warnIfDestroyed('setIdentity');
|
|
443
|
+
const hadIdentity = this.hasIdentity;
|
|
391
444
|
const previous = this.#identity;
|
|
392
445
|
this.#identity = identity;
|
|
393
446
|
if (previous && previous !== identity && !previous.present) {
|
|
394
447
|
this.#paletteRank.clear();
|
|
395
448
|
this.#nextRank = 0;
|
|
396
449
|
}
|
|
450
|
+
// Identity landing after the config was uploaded, or going (a reload
|
|
451
|
+
// empties it). The domain fetch and the identity columns are fetched in
|
|
452
|
+
// parallel, so either can arrive first; the config's slot base, its palette
|
|
453
|
+
// size and the CPU scale all depend on whether the channel carries ids,
|
|
454
|
+
// so a change of that uploads again. The ranks are refreshed here either
|
|
455
|
+
// way: they are CPU state, needed without a coordinator too.
|
|
397
456
|
this.#refreshRanks();
|
|
457
|
+
if (hadIdentity !== this.hasIdentity && this.domain.length > 0)
|
|
458
|
+
this.#uploadToGPU();
|
|
398
459
|
}
|
|
399
460
|
/** True once this scale's channel carries dim ids rather than palette indices. */
|
|
461
|
+
/** The field's live id <-> value bijection, once adopted. */
|
|
462
|
+
get identity() {
|
|
463
|
+
return this.#identity ?? null;
|
|
464
|
+
}
|
|
400
465
|
get hasIdentity() {
|
|
401
466
|
return this.isOrdinal && (this.#identity?.present ?? false);
|
|
402
467
|
}
|
|
@@ -419,10 +484,14 @@ export class ColorScaleState {
|
|
|
419
484
|
const identity = this.#identity;
|
|
420
485
|
if (!identity?.present)
|
|
421
486
|
return;
|
|
487
|
+
this.#ranksVersion = identity.version;
|
|
422
488
|
for (const value of this.domain) {
|
|
423
489
|
const id = identity.ids.get(value);
|
|
424
490
|
if (id === undefined || this.#paletteRank.has(id))
|
|
425
491
|
continue;
|
|
492
|
+
// The NULL category takes no palette slot (see below).
|
|
493
|
+
if (id === identity.nullId)
|
|
494
|
+
continue;
|
|
426
495
|
this.#paletteRank.set(id, this.#nextRank++);
|
|
427
496
|
}
|
|
428
497
|
const table = new Float32Array(identity.values.length);
|
|
@@ -431,6 +500,16 @@ export class ColorScaleState {
|
|
|
431
500
|
if (id >= 0 && id < table.length)
|
|
432
501
|
table[id] = rank;
|
|
433
502
|
}
|
|
503
|
+
// The NULL category and the Other buckets draw the null colour: no palette
|
|
504
|
+
// slot, so they read as missing data or "the rest" and shift no category's
|
|
505
|
+
// colour. Not -1, which the shader draws transparent: a category outside
|
|
506
|
+
// this scale's domain.
|
|
507
|
+
if (identity.nullId !== null)
|
|
508
|
+
table[identity.nullId] = NULL_COLOR_RANK;
|
|
509
|
+
for (const id of identity.synthetic)
|
|
510
|
+
if (id < table.length)
|
|
511
|
+
table[id] = NULL_COLOR_RANK;
|
|
512
|
+
this.#ranksRevision++;
|
|
434
513
|
const slots = this.#slots;
|
|
435
514
|
slots.from = table;
|
|
436
515
|
slots.to = table;
|
|
@@ -476,9 +555,8 @@ export class ColorScaleState {
|
|
|
476
555
|
return undefined;
|
|
477
556
|
return this.#identity.values[id];
|
|
478
557
|
}
|
|
558
|
+
/** Called from #uploadToGPU only, which has refreshed the ranks. */
|
|
479
559
|
#uploadOrdinal() {
|
|
480
|
-
// New categories in the domain need ranks before the palette is sized.
|
|
481
|
-
this.#refreshRanks();
|
|
482
560
|
// Bake and upload palette
|
|
483
561
|
const paletteData = bakePalette(this.#scheme, { reverse: this.#reverse });
|
|
484
562
|
this.#gpuCoordinator.writeColorPalette(this.#paletteSlot, paletteData);
|
|
@@ -497,7 +575,7 @@ export class ColorScaleState {
|
|
|
497
575
|
type: 0, // ORDINAL
|
|
498
576
|
paletteOffset: this.#paletteSlot * 32,
|
|
499
577
|
paletteSize: Math.min(Math.max(rankSpace, 1), 32),
|
|
500
|
-
slotBase: this
|
|
578
|
+
slotBase: this.#configSlotBase
|
|
501
579
|
});
|
|
502
580
|
this.#gpuCoordinator.writeColorConfig(this.#configSlot, config);
|
|
503
581
|
}
|
|
@@ -660,6 +738,14 @@ export class ColorScaleState {
|
|
|
660
738
|
* transform (e.g. non-positive values under log), mirroring the GPU shader.
|
|
661
739
|
*/
|
|
662
740
|
getColor(value) {
|
|
741
|
+
// With palette ranks, the colour is exactly the shader's: value -> id ->
|
|
742
|
+
// rank -> palette, and the null colour for the NULL category and the Other
|
|
743
|
+
// buckets. Not through the d3 scale, whose domain is stringified: a null
|
|
744
|
+
// (or a number) would miss it, and d3 would hand the value the next
|
|
745
|
+
// palette colour.
|
|
746
|
+
void this.#ranksRevision;
|
|
747
|
+
if (this.#hasRanks())
|
|
748
|
+
return this.#rankedColor(value);
|
|
663
749
|
if (!this.#d3Scale)
|
|
664
750
|
return '#000000';
|
|
665
751
|
if (this.#transformType === 'log' && typeof value === 'number' && value <= 0) {
|
|
@@ -667,6 +753,18 @@ export class ColorScaleState {
|
|
|
667
753
|
}
|
|
668
754
|
return this.#d3Scale(value);
|
|
669
755
|
}
|
|
756
|
+
#rankedColor(value) {
|
|
757
|
+
const identity = this.#identity;
|
|
758
|
+
const id = identity.ids.get(value);
|
|
759
|
+
const rank = id === undefined ? undefined : this.#paletteRank.get(id);
|
|
760
|
+
if (rank === undefined)
|
|
761
|
+
return ColorScaleState.NULL_COLOR;
|
|
762
|
+
let colors = getOrdinalColors(this.#scheme) ?? [];
|
|
763
|
+
if (this.#reverse)
|
|
764
|
+
colors = [...colors].reverse();
|
|
765
|
+
// Same wrap the shader applies, so an over-32 palette agrees too.
|
|
766
|
+
return colors.length ? colors[rank % colors.length] : ColorScaleState.NULL_COLOR;
|
|
767
|
+
}
|
|
670
768
|
/**
|
|
671
769
|
* Get color as RGBA array (0-1 range).
|
|
672
770
|
*/
|
|
@@ -674,18 +772,14 @@ export class ColorScaleState {
|
|
|
674
772
|
const cssColor = this.getColor(value);
|
|
675
773
|
return parseColorToRGBA(cssColor);
|
|
676
774
|
}
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
clearActiveValues() {
|
|
684
|
-
this.#activeValues = null;
|
|
685
|
-
}
|
|
686
|
-
isActive(value) {
|
|
687
|
-
return this.#activeValues == null || this.#activeValues.has(value);
|
|
775
|
+
/**
|
|
776
|
+
* Charts drawing this scale. Asked of the registry, as BandScaleState does,
|
|
777
|
+
* so it is current when charts come and go.
|
|
778
|
+
*/
|
|
779
|
+
get _affectedChartsArray() {
|
|
780
|
+
return this.#registry?.chartsUsing(this) ?? ColorScaleState.#noCharts;
|
|
688
781
|
}
|
|
782
|
+
static #noCharts = [];
|
|
689
783
|
// ============================================================
|
|
690
784
|
// CLEANUP
|
|
691
785
|
// ============================================================
|
|
@@ -141,6 +141,8 @@ export declare class DomainResolver {
|
|
|
141
141
|
channelValueFor(value: DimValue, displayed: readonly DimValue[]): number | null;
|
|
142
142
|
/** True once this field's dim exists and holds at least one value. */
|
|
143
143
|
get hasIdentity(): boolean;
|
|
144
|
+
/** The dim adopted last; a different object after a reload. */
|
|
145
|
+
get identity(): FieldIdentity | null;
|
|
144
146
|
/** How many ids the dim holds — the width of a slot table over it. */
|
|
145
147
|
get idCount(): number;
|
|
146
148
|
/** How many marks have registered. */
|
|
@@ -188,17 +188,18 @@ export class DomainResolver {
|
|
|
188
188
|
// #extents encodes at the boundary, which is the whole point of the two
|
|
189
189
|
// registration methods, so its input was already ids that had passed the
|
|
190
190
|
// same check, and the list it was given had just been deduplicated. Its
|
|
191
|
-
// range check is subsumed by the `value
|
|
192
|
-
// an id past the end, a negative one and a non-integer one alike,
|
|
193
|
-
// all three index the value array to undefined.
|
|
191
|
+
// range check is subsumed by the `value === undefined` test below, which
|
|
192
|
+
// drops an id past the end, a negative one and a non-integer one alike,
|
|
193
|
+
// because all three index the value array to undefined. Not `== null`:
|
|
194
|
+
// the NULL category's value IS null, and it is a category like any other.
|
|
194
195
|
//
|
|
195
196
|
// An id with no value is dropped from the IDS, before either projection.
|
|
196
197
|
// Filtering only the values would shorten the value list without
|
|
197
198
|
// shortening the permutation, and the two would disagree with the band
|
|
198
199
|
// index space d3 and the shader's categoryCount use — by one, with
|
|
199
|
-
// nothing failing. Unreachable today (
|
|
200
|
-
//
|
|
201
|
-
// rather than checked against each other.
|
|
200
|
+
// nothing failing. Unreachable today (every dim row has a value: a real
|
|
201
|
+
// one, a synthetic row's label, or the NULL category's null), which is
|
|
202
|
+
// why the two are built together rather than checked against each other.
|
|
202
203
|
const seen = new Set();
|
|
203
204
|
const orderedIds = [];
|
|
204
205
|
const resolved = [];
|
|
@@ -214,7 +215,7 @@ export class DomainResolver {
|
|
|
214
215
|
continue;
|
|
215
216
|
}
|
|
216
217
|
const value = values[category];
|
|
217
|
-
if (value
|
|
218
|
+
if (value === undefined)
|
|
218
219
|
continue;
|
|
219
220
|
orderedIds.push(category);
|
|
220
221
|
resolved.push(value);
|
|
@@ -293,7 +294,9 @@ export class DomainResolver {
|
|
|
293
294
|
const out = [];
|
|
294
295
|
const seen = new Set();
|
|
295
296
|
for (const item of items) {
|
|
296
|
-
|
|
297
|
+
// A value of null is the NULL category, which `ids` maps to its id; an
|
|
298
|
+
// encoded null is no id at all.
|
|
299
|
+
if (item === undefined || (encoded && item === null))
|
|
297
300
|
continue;
|
|
298
301
|
const id = encoded ? item : identity.ids.get(item);
|
|
299
302
|
if (id === undefined || !Number.isInteger(id) || id < 0 || id >= limit)
|
|
@@ -371,6 +374,10 @@ export class DomainResolver {
|
|
|
371
374
|
get hasIdentity() {
|
|
372
375
|
return this.#identity?.present ?? false;
|
|
373
376
|
}
|
|
377
|
+
/** The dim adopted last; a different object after a reload. */
|
|
378
|
+
get identity() {
|
|
379
|
+
return this.#identity;
|
|
380
|
+
}
|
|
374
381
|
/** How many ids the dim holds — the width of a slot table over it. */
|
|
375
382
|
get idCount() {
|
|
376
383
|
return this.#identity?.values.length ?? 0;
|
|
@@ -73,6 +73,15 @@ export declare class ScaleRegistry {
|
|
|
73
73
|
* a scale this registry has never seen.
|
|
74
74
|
*/
|
|
75
75
|
chartsUsing(scale: AnyScaleState): string[];
|
|
76
|
+
/**
|
|
77
|
+
* Fill `out` with every chart drawing a scale on the categorical field
|
|
78
|
+
* `key` (by identity key), each once: the charts a selection fade on that
|
|
79
|
+
* field redraws. A superset of the charts whose marks read the field's
|
|
80
|
+
* activity, since every such mark draws through a scale on it; the extra
|
|
81
|
+
* charts (other groups, marks holding the scale without reading it) redraw
|
|
82
|
+
* for the 0.25 s of a fade. Read on every frame a fade runs.
|
|
83
|
+
*/
|
|
84
|
+
chartsDrawingField(key: string, out: string[]): string[];
|
|
76
85
|
deregisterChart(scale: AnyScaleState, chartId: string): void;
|
|
77
86
|
removeChartUsage(chartId: string): void;
|
|
78
87
|
_removeScaleFromStorage(scaleInstance: AnyScaleState): void;
|