@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.
- package/dist/components/axes/BandAxisTicks.svelte +63 -24
- package/dist/components/axes/BandAxisTicks.svelte.d.ts +5 -5
- package/dist/components/charts/ChartContainer.svelte +2 -1
- package/dist/components/guides/legend/ContinuousColorLegend.svelte +2 -1
- package/dist/components/guides/legend/OrdinalColorLegend.svelte +92 -34
- package/dist/components/marks/AreaMarkX.svelte +19 -6
- package/dist/components/marks/AreaMarkY.svelte +19 -6
- package/dist/components/marks/BarMarkX.svelte +3 -0
- package/dist/components/marks/BarMarkY.svelte +3 -0
- package/dist/components/marks/DotsMark.svelte +3 -0
- package/dist/components/marks/LineMark.svelte +3 -0
- package/dist/components/marks/ParallelCoordinatesMark.svelte +6 -0
- package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +3 -0
- package/dist/components/marks/RectMark.svelte +3 -0
- package/dist/components/marks/RectXMark.svelte +3 -0
- package/dist/components/marks/RectYMark.svelte +3 -0
- package/dist/components/marks/RuleMarkX.svelte +10 -2
- package/dist/components/marks/RuleMarkY.svelte +10 -2
- package/dist/components/marks/TextMark.svelte +68 -4
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +15 -1
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +116 -27
- package/dist/components/marks/lifecycle/domain-queries.js +13 -11
- 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 +3 -0
- package/dist/components/rendering/Areas.svelte +30 -6
- package/dist/components/rendering/Areas.svelte.d.ts +2 -0
- package/dist/components/rendering/BaseGPURenderer.svelte.js +4 -1
- 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 +20 -5
- package/dist/components/rendering/Rects.svelte.d.ts +2 -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 +93 -0
- package/dist/components/rendering/activity-reads.js +135 -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 +2 -0
- 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/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 +86 -7
- 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.js +1 -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 +2 -1
- package/dist/interactors/Interval2D.svelte +2 -1
- package/dist/interactors/ParallelAxisSource.svelte +4 -4
- 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 +17 -36
- package/dist/interactors/Toggle.svelte.d.ts +4 -3
- package/dist/interactors/Tooltip.svelte +2 -1
- package/dist/interactors/TooltipManager.svelte.js +8 -3
- 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 +79 -0
- package/dist/query-engine/FieldActivity.svelte.js +207 -0
- package/dist/query-engine/FilterGroup.svelte.d.ts +53 -29
- package/dist/query-engine/FilterGroup.svelte.js +181 -122
- package/dist/query-engine/FilterGroupRegistry.svelte.d.ts +42 -0
- package/dist/query-engine/FilterGroupRegistry.svelte.js +103 -1
- 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 +90 -28
- package/dist/query-engine/categorical-identity.d.ts +99 -27
- package/dist/query-engine/categorical-identity.js +210 -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 +9 -5
- package/dist/query-engine/preagg/plan.js +13 -11
- package/dist/query-engine/toggle-predicate.js +12 -1
- package/dist/scales/ActivityTable.d.ts +99 -0
- package/dist/scales/ActivityTable.js +211 -0
- package/dist/scales/BandScaleState.svelte.d.ts +28 -7
- package/dist/scales/BandScaleState.svelte.js +55 -20
- 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 +2 -0
- package/dist/transforms/index.d.ts +27 -0
- package/dist/transforms/index.js +97 -3
- package/dist/types.d.ts +30 -0
- package/package.json +1 -1
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
let {
|
|
48
48
|
table,
|
|
49
49
|
selection = undefined,
|
|
50
|
+
selectionResponse = undefined,
|
|
50
51
|
x = null,
|
|
51
52
|
x1: x1Prop = null,
|
|
52
53
|
x2: x2Prop = null,
|
|
@@ -95,6 +96,7 @@
|
|
|
95
96
|
opacity: () => opacity,
|
|
96
97
|
transforms: () => transforms,
|
|
97
98
|
selection: () => selection,
|
|
99
|
+
selectionResponse: () => selectionResponse,
|
|
98
100
|
limit: () => limit,
|
|
99
101
|
preferredXScaleType: () => preferredXScaleType,
|
|
100
102
|
preferredYScaleType: () => preferredYScaleType,
|
|
@@ -360,6 +362,7 @@
|
|
|
360
362
|
alphaData={opacityData}
|
|
361
363
|
colorMode={markColor.colorMode}
|
|
362
364
|
colorScaleIndex={markColor.colorScaleIndex}
|
|
365
|
+
groupIndex={setup?.groupIndex ?? 0}
|
|
363
366
|
constantColor={markColor.constantColor}
|
|
364
367
|
facetData={setup?.facetData}
|
|
365
368
|
dataVersion={setup?.dataVersion || 0}
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
let {
|
|
48
48
|
table,
|
|
49
49
|
selection = undefined,
|
|
50
|
+
selectionResponse = undefined,
|
|
50
51
|
x,
|
|
51
52
|
x1: x1Prop,
|
|
52
53
|
x2: x2Prop,
|
|
@@ -90,6 +91,7 @@
|
|
|
90
91
|
opacity: () => opacity,
|
|
91
92
|
transforms: () => transforms,
|
|
92
93
|
selection: () => selection,
|
|
94
|
+
selectionResponse: () => selectionResponse,
|
|
93
95
|
limit: () => limit,
|
|
94
96
|
preferredYScaleType: () => preferredYScaleType,
|
|
95
97
|
fitDomain: () => fitDomain,
|
|
@@ -351,6 +353,7 @@
|
|
|
351
353
|
alphaData={opacityData}
|
|
352
354
|
colorMode={markColor.colorMode}
|
|
353
355
|
colorScaleIndex={markColor.colorScaleIndex}
|
|
356
|
+
groupIndex={setup?.groupIndex ?? 0}
|
|
354
357
|
constantColor={markColor.constantColor}
|
|
355
358
|
facetData={setup?.facetData}
|
|
356
359
|
dataVersion={setup?.dataVersion || 0}
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
let {
|
|
49
49
|
table,
|
|
50
50
|
selection = undefined,
|
|
51
|
+
selectionResponse = undefined,
|
|
51
52
|
x = null,
|
|
52
53
|
x1: x1Prop = null,
|
|
53
54
|
x2: x2Prop = null,
|
|
@@ -92,6 +93,7 @@
|
|
|
92
93
|
opacity: () => opacity,
|
|
93
94
|
transforms: () => transforms,
|
|
94
95
|
selection: () => selection,
|
|
96
|
+
selectionResponse: () => selectionResponse,
|
|
95
97
|
limit: () => limit,
|
|
96
98
|
preferredXScaleType: () => preferredXScaleType,
|
|
97
99
|
fitDomain: () => fitDomain,
|
|
@@ -358,6 +360,7 @@
|
|
|
358
360
|
alphaData={opacityData}
|
|
359
361
|
colorMode={markColor.colorMode}
|
|
360
362
|
colorScaleIndex={markColor.colorScaleIndex}
|
|
363
|
+
groupIndex={setup?.groupIndex ?? 0}
|
|
361
364
|
constantColor={markColor.constantColor}
|
|
362
365
|
facetData={setup?.facetData}
|
|
363
366
|
dataVersion={setup?.dataVersion || 0}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { ruleHasExtent } from '../rendering/activity-reads';
|
|
2
3
|
import { getContext, onDestroy, untrack } from 'svelte';
|
|
3
4
|
import { MarkLifecycleV2 } from './lifecycle/MarkLifecycleV2.svelte';
|
|
4
5
|
import { RuleX, GridLine } from '../rendering';
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
let {
|
|
35
36
|
table = undefined,
|
|
36
37
|
selection = undefined,
|
|
38
|
+
selectionResponse = undefined,
|
|
37
39
|
x,
|
|
38
40
|
y: yShorthand = undefined,
|
|
39
41
|
y1: y1Prop = undefined,
|
|
@@ -93,7 +95,7 @@
|
|
|
93
95
|
return undefined;
|
|
94
96
|
});
|
|
95
97
|
|
|
96
|
-
const hasExtent = $derived(y1
|
|
98
|
+
const hasExtent = $derived(ruleHasExtent(y1, y2));
|
|
97
99
|
|
|
98
100
|
let setup = $state<MarkLifecycleV2 | null>(null);
|
|
99
101
|
|
|
@@ -121,6 +123,7 @@
|
|
|
121
123
|
opacity: () => opacity,
|
|
122
124
|
transforms: () => transforms,
|
|
123
125
|
selection: () => selection,
|
|
126
|
+
selectionResponse: () => selectionResponse,
|
|
124
127
|
limit: () => limit,
|
|
125
128
|
sort: () => sort,
|
|
126
129
|
domain: () => domain,
|
|
@@ -174,7 +177,11 @@
|
|
|
174
177
|
...(hasExtent && { y1: entry.y1, y2: entry.y2 })
|
|
175
178
|
}),
|
|
176
179
|
// When y shorthand is used, include 0 in domain
|
|
177
|
-
zeroBaseline: yShorthand !== undefined ? { y: true } : null
|
|
180
|
+
zeroBaseline: yShorthand !== undefined ? { y: true } : null,
|
|
181
|
+
// The value the renderer's y0Data is built from: routing must agree.
|
|
182
|
+
ruleExtent: hasExtent,
|
|
183
|
+
// Coloured by stroke: the lifecycle requests that channel's identity.
|
|
184
|
+
colorChannel: 'stroke'
|
|
178
185
|
};
|
|
179
186
|
});
|
|
180
187
|
|
|
@@ -301,6 +308,7 @@
|
|
|
301
308
|
alphaData={opacityData}
|
|
302
309
|
colorMode={markColor.colorMode}
|
|
303
310
|
colorScaleIndex={markColor.colorScaleIndex}
|
|
311
|
+
groupIndex={setup?.groupIndex ?? 0}
|
|
304
312
|
constantColor={markColor.constantColor}
|
|
305
313
|
{strokeWidth}
|
|
306
314
|
facetData={setup?.facetData}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { ruleHasExtent } from '../rendering/activity-reads';
|
|
2
3
|
import { getContext, onDestroy, untrack } from 'svelte';
|
|
3
4
|
import { MarkLifecycleV2 } from './lifecycle/MarkLifecycleV2.svelte';
|
|
4
5
|
import { RuleY, GridLine } from '../rendering';
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
let {
|
|
35
36
|
table = undefined,
|
|
36
37
|
selection = undefined,
|
|
38
|
+
selectionResponse = undefined,
|
|
37
39
|
y,
|
|
38
40
|
x: xShorthand = undefined,
|
|
39
41
|
x1: x1Prop = undefined,
|
|
@@ -92,7 +94,7 @@
|
|
|
92
94
|
return undefined;
|
|
93
95
|
});
|
|
94
96
|
|
|
95
|
-
const hasExtent = $derived(x1
|
|
97
|
+
const hasExtent = $derived(ruleHasExtent(x1, x2));
|
|
96
98
|
|
|
97
99
|
let setup = $state<MarkLifecycleV2 | null>(null);
|
|
98
100
|
|
|
@@ -119,6 +121,7 @@
|
|
|
119
121
|
opacity: () => opacity,
|
|
120
122
|
transforms: () => transforms,
|
|
121
123
|
selection: () => selection,
|
|
124
|
+
selectionResponse: () => selectionResponse,
|
|
122
125
|
limit: () => limit,
|
|
123
126
|
sort: () => sort,
|
|
124
127
|
domain: () => domain,
|
|
@@ -170,7 +173,11 @@
|
|
|
170
173
|
y: entry.y,
|
|
171
174
|
...(hasExtent && { x1: entry.x1, x2: entry.x2 })
|
|
172
175
|
}),
|
|
173
|
-
zeroBaseline: xShorthand !== undefined ? { x: true } : null
|
|
176
|
+
zeroBaseline: xShorthand !== undefined ? { x: true } : null,
|
|
177
|
+
// The value the renderer's x0Data is built from: routing must agree.
|
|
178
|
+
ruleExtent: hasExtent,
|
|
179
|
+
// Coloured by stroke: the lifecycle requests that channel's identity.
|
|
180
|
+
colorChannel: 'stroke'
|
|
174
181
|
};
|
|
175
182
|
});
|
|
176
183
|
|
|
@@ -297,6 +304,7 @@
|
|
|
297
304
|
alphaData={opacityData}
|
|
298
305
|
colorMode={markColor.colorMode}
|
|
299
306
|
colorScaleIndex={markColor.colorScaleIndex}
|
|
307
|
+
groupIndex={setup?.groupIndex ?? 0}
|
|
300
308
|
constantColor={markColor.constantColor}
|
|
301
309
|
{strokeWidth}
|
|
302
310
|
facetData={setup?.facetData}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { NO_GROUP } from '../rendering/activity-reads';
|
|
2
3
|
import { getContext, onDestroy, untrack } from 'svelte';
|
|
3
4
|
import { MarkLifecycleV2 } from './lifecycle/MarkLifecycleV2.svelte';
|
|
4
5
|
import { SCALE_PRIORITIES } from '../../scales/ScalePriorities';
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
let {
|
|
42
43
|
table,
|
|
43
44
|
selection = undefined,
|
|
45
|
+
selectionResponse = undefined,
|
|
44
46
|
x,
|
|
45
47
|
y,
|
|
46
48
|
text,
|
|
@@ -126,6 +128,9 @@
|
|
|
126
128
|
get sort() {
|
|
127
129
|
return sort;
|
|
128
130
|
},
|
|
131
|
+
get selectionResponse() {
|
|
132
|
+
return selectionResponse;
|
|
133
|
+
},
|
|
129
134
|
get limit() {
|
|
130
135
|
return limit;
|
|
131
136
|
},
|
|
@@ -203,18 +208,22 @@
|
|
|
203
208
|
|
|
204
209
|
// --- Color Resolution (pre-resolved once, not per-label) ---
|
|
205
210
|
|
|
206
|
-
|
|
211
|
+
/** The raw fill channel, once per data change: colours and dimming both read it. */
|
|
212
|
+
const fillRaw = $derived.by(() => {
|
|
207
213
|
if (!fill) return null;
|
|
208
214
|
if (typeof fill === 'string' && isColorConstant(fill)) return null;
|
|
209
215
|
if (!markColor.scaleState || !instanceCount) return null;
|
|
210
|
-
|
|
211
|
-
const raw = resolveChannel({
|
|
216
|
+
return resolveChannel({
|
|
212
217
|
value: fill,
|
|
213
218
|
channel: 'fill',
|
|
214
219
|
dataAccessor: setup?.data,
|
|
215
220
|
instanceCount
|
|
216
221
|
});
|
|
217
|
-
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const colorCSSValues = $derived.by(() => {
|
|
225
|
+
const raw = fillRaw;
|
|
226
|
+
if (!raw || !markColor.scaleState) return null;
|
|
218
227
|
|
|
219
228
|
const scale = markColor.scaleState;
|
|
220
229
|
const colors = new Array(instanceCount);
|
|
@@ -253,6 +262,60 @@
|
|
|
253
262
|
return result;
|
|
254
263
|
});
|
|
255
264
|
|
|
265
|
+
// --- Selection dimming ---
|
|
266
|
+
//
|
|
267
|
+
// A label dims with the bars it labels: by its band axes and its ordinal
|
|
268
|
+
// colour, in its own filter group, the fields activity-reads.ts lists for
|
|
269
|
+
// text. Routing reads the same list, so a categorical selection on one of
|
|
270
|
+
// these fields dims the labels instead of filtering them away.
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Per-label dimming sources, resolved once per data change, not per frame.
|
|
274
|
+
* Each is a field's identity key and the label's dim ids on it: a scale has an
|
|
275
|
+
* identity key only while its channel carries ids, so the raw column is the
|
|
276
|
+
* ids, and reading the fade by id is what reaches an Other bucket.
|
|
277
|
+
*/
|
|
278
|
+
const dimming = $derived.by(() => {
|
|
279
|
+
// A 'filter' mark (NO_GROUP) never dims, as its GPU marks do not.
|
|
280
|
+
if (!setup || !filterGroups || setup.groupIndex === NO_GROUP) return null;
|
|
281
|
+
const groups = [setup.groupIndex];
|
|
282
|
+
const sources: Array<{ key: string; ids: ArrayLike<unknown> }> = [];
|
|
283
|
+
for (const axis of ['x', 'y'] as const) {
|
|
284
|
+
const type = chartContext.scaleTypes?.[axis];
|
|
285
|
+
if (type !== 'band' && type !== 'point') continue;
|
|
286
|
+
const band = chartContext[`${axis}ScaleState`] as BandScaleState | null;
|
|
287
|
+
const raw = axis === 'x' ? xRaw : yRaw;
|
|
288
|
+
// Identity keys only, as routing reads them: a field without identity
|
|
289
|
+
// filters the labels, so they do not dim by it too.
|
|
290
|
+
const key = band?.identityKey;
|
|
291
|
+
if (!key || !raw) continue;
|
|
292
|
+
sources.push({ key, ids: raw as ArrayLike<unknown> });
|
|
293
|
+
}
|
|
294
|
+
const color = markColor.scaleState;
|
|
295
|
+
const raw = fillRaw;
|
|
296
|
+
const colorKey = color?.isOrdinal ? color.identityKey : null;
|
|
297
|
+
if (colorKey && raw) sources.push({ key: colorKey, ids: raw as ArrayLike<unknown> });
|
|
298
|
+
return sources.length ? { groups, sources } : null;
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
/** How visible label `idx` is now: the least visible of its categories. */
|
|
302
|
+
function labelOpacity(idx: number): number {
|
|
303
|
+
const d = dimming;
|
|
304
|
+
if (!d) return 1;
|
|
305
|
+
let opacity = 1;
|
|
306
|
+
for (const source of d.sources) {
|
|
307
|
+
const raw = source.ids[idx];
|
|
308
|
+
// An id reads its fade by id. Anything else (a string from a channel
|
|
309
|
+
// that never carried ids, a null) is a value, read as one: never coerced
|
|
310
|
+
// to a number, where a string becomes NaN and null becomes category 0.
|
|
311
|
+
const selected = Number.isInteger(raw)
|
|
312
|
+
? filterGroups.opacityOfId(source.key, d.groups, raw as number)
|
|
313
|
+
: filterGroups.opacityOfValue(source.key, d.groups, raw);
|
|
314
|
+
opacity = Math.min(opacity, selected);
|
|
315
|
+
}
|
|
316
|
+
return opacity;
|
|
317
|
+
}
|
|
318
|
+
|
|
256
319
|
// --- Animation-aware positioning ---
|
|
257
320
|
|
|
258
321
|
let useFadeMode = $derived(labelItems.length > FADE_THRESHOLD);
|
|
@@ -427,6 +490,7 @@
|
|
|
427
490
|
{#if pos}
|
|
428
491
|
<text
|
|
429
492
|
fill={item.color}
|
|
493
|
+
fill-opacity={labelOpacity(item.idx)}
|
|
430
494
|
font-size="{fontSize}px"
|
|
431
495
|
font-weight={fontWeight}
|
|
432
496
|
font-family={fontFamily}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* → setupComplete = true
|
|
12
12
|
*
|
|
13
13
|
* Registration: watches #resolvedQueryParts, registers with engine.
|
|
14
|
-
* Engine handles filter-driven re-queries via
|
|
14
|
+
* Engine handles filter-driven re-queries via onClauseChange() and
|
|
15
|
+
* onFilterChange().
|
|
15
16
|
*
|
|
16
17
|
* Params: watches sort/limit on ReactiveConfig.
|
|
17
18
|
* Changes trigger re-query for this mark only, no teardown.
|
|
@@ -23,6 +24,7 @@ import type { ColorScaleState } from '../../../scales/ColorScaleState.svelte';
|
|
|
23
24
|
import type { SizeScaleState } from '../../../scales/SizeScaleState.svelte';
|
|
24
25
|
import type { QueryEngine } from '../../../query-engine/QueryEngine';
|
|
25
26
|
import type { FilterGroup } from '../../../query-engine/FilterGroup.svelte';
|
|
27
|
+
import { type SelectionResponse } from '../../rendering/activity-reads';
|
|
26
28
|
import type { MarkLODSurface } from '../../../lod/rung-types';
|
|
27
29
|
import type { GPUCoordinator } from '../../../gpu/GPUCoordinator.svelte';
|
|
28
30
|
export interface MarkLifecycleV2Config {
|
|
@@ -63,6 +65,12 @@ export interface MarkLifecycleV2Config {
|
|
|
63
65
|
x?: boolean;
|
|
64
66
|
y?: boolean;
|
|
65
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* A rule's: whether it spans an extent on its cross axis (RuleX with
|
|
70
|
+
* y1/y2, RuleY with x1/x2, constants included). Decides whether it reads
|
|
71
|
+
* that axis's selection activity.
|
|
72
|
+
*/
|
|
73
|
+
ruleExtent?: boolean;
|
|
66
74
|
passiveScales?: boolean;
|
|
67
75
|
parallelMode?: boolean;
|
|
68
76
|
parallelColumns?: string[];
|
|
@@ -84,6 +92,7 @@ export interface ReactiveConfig {
|
|
|
84
92
|
};
|
|
85
93
|
readonly fitDomain: 'x' | 'y' | boolean | undefined;
|
|
86
94
|
readonly tip: (unknown | '*')[] | false | undefined;
|
|
95
|
+
readonly selectionResponse: SelectionResponse | undefined;
|
|
87
96
|
}
|
|
88
97
|
export declare class MarkLifecycleV2 {
|
|
89
98
|
#private;
|
|
@@ -125,6 +134,11 @@ export declare class MarkLifecycleV2 {
|
|
|
125
134
|
get tooltipTableName(): string;
|
|
126
135
|
get tooltipEnabled(): boolean;
|
|
127
136
|
get channelFieldMap(): Record<string, string>;
|
|
137
|
+
/**
|
|
138
|
+
* The page-wide index of this mark's filter group. Its renderer stores it in
|
|
139
|
+
* MarkConstants to read its group's run of each field's activity block.
|
|
140
|
+
*/
|
|
141
|
+
get groupIndex(): number;
|
|
128
142
|
get dataVersion(): number;
|
|
129
143
|
get hasCategoryReducingTransforms(): boolean;
|
|
130
144
|
data: DataAccessor | null;
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* → setupComplete = true
|
|
12
12
|
*
|
|
13
13
|
* Registration: watches #resolvedQueryParts, registers with engine.
|
|
14
|
-
* Engine handles filter-driven re-queries via
|
|
14
|
+
* Engine handles filter-driven re-queries via onClauseChange() and
|
|
15
|
+
* onFilterChange().
|
|
15
16
|
*
|
|
16
17
|
* Params: watches sort/limit on ReactiveConfig.
|
|
17
18
|
* Changes trigger re-query for this mark only, no teardown.
|
|
@@ -19,7 +20,7 @@
|
|
|
19
20
|
import { untrack } from 'svelte';
|
|
20
21
|
import { Query, isNode, isAggregateExpression, isColumnRef, isColumnParam, sql, column as sqlColumn, cast, min, max, isNotNull, and } from '@uwdata/mosaic-sql';
|
|
21
22
|
import { TransformCompiler } from '../../../transforms/TransformCompiler';
|
|
22
|
-
import { withDenseCategories, withRanking } from '../../../transforms';
|
|
23
|
+
import { withDenseCategories, withoutOtherMembers, withRanking } from '../../../transforms';
|
|
23
24
|
import { ArrowDataAccessor } from '../../../data/DataAccessor';
|
|
24
25
|
import { MarkHandle } from '../../../query-engine/MarkHandle.svelte';
|
|
25
26
|
import { isContinuousScale, isNumericScale } from '../../../scales/scale-categories';
|
|
@@ -28,6 +29,7 @@ import { isBinExpression } from '../../../sql/bin';
|
|
|
28
29
|
import { isRollingAvgExpression } from '../../../sql/rolling-avg';
|
|
29
30
|
import { isWindowExpression } from '../../../sql/expression-utils';
|
|
30
31
|
import { identityKeyFor } from '../../../query-engine/identity-key';
|
|
32
|
+
import { activityReads, groupIndexForResponse, NO_READS, readsFor, readsForResponse, rendererForMark } from '../../rendering/activity-reads';
|
|
31
33
|
import { AdaptiveBinRung } from '../../../lod/AdaptiveBinRung.svelte';
|
|
32
34
|
/** A SQL identifier, quoted, with any double quote in it doubled. */
|
|
33
35
|
function ident(name) {
|
|
@@ -39,6 +41,7 @@ import { detectChannelsNeedingDataDomain } from './domain-computation';
|
|
|
39
41
|
import { buildQueryParts, assembleQuery, castReal } from './query-builder';
|
|
40
42
|
import { setScalesReady, markDeferredScalesReady, setInitialContinuousDomains, registerDataDerivedDomains } from './domain-registration';
|
|
41
43
|
import { fetchCategoricalDomains, fetchGlobalAggregates } from './domain-queries';
|
|
44
|
+
import { transformScaleKey } from './transform-scale-key';
|
|
42
45
|
// ---------------------------------------------------------------------------
|
|
43
46
|
// MarkLifecycleV2
|
|
44
47
|
// ---------------------------------------------------------------------------
|
|
@@ -338,6 +341,10 @@ export class MarkLifecycleV2 {
|
|
|
338
341
|
if (this.#destroyed)
|
|
339
342
|
return;
|
|
340
343
|
this.#identityFields.clear();
|
|
344
|
+
// The Other ids name rows of the dropped dims. Every holder lets go, so
|
|
345
|
+
// the scale acquires afresh when setup re-runs.
|
|
346
|
+
for (const scale of [...this.#otherScales])
|
|
347
|
+
this.#releaseOther(scale);
|
|
341
348
|
// Every dim it names just went away — topN must not bucket into one.
|
|
342
349
|
this.#setIdentity(null);
|
|
343
350
|
this.setupComplete = false;
|
|
@@ -359,8 +366,12 @@ export class MarkLifecycleV2 {
|
|
|
359
366
|
let config = scaleRequirements.x.config || {};
|
|
360
367
|
if (scaleRequirements.x.type === 'band' && hasCatReducing) {
|
|
361
368
|
const key = this.#transformCompiler.getCategoryTransformKey();
|
|
362
|
-
if (key)
|
|
363
|
-
config = {
|
|
369
|
+
if (key) {
|
|
370
|
+
config = {
|
|
371
|
+
...config,
|
|
372
|
+
transformKey: transformScaleKey(table, key, this.#chartContext.config)
|
|
373
|
+
};
|
|
374
|
+
}
|
|
364
375
|
}
|
|
365
376
|
this.#chartContext.registerScaleType('x', scaleRequirements.x.type, scaleRequirements.x.priority, this.#markId, config, inferredScaleColumns?.x);
|
|
366
377
|
}
|
|
@@ -368,8 +379,12 @@ export class MarkLifecycleV2 {
|
|
|
368
379
|
let config = scaleRequirements.y.config || {};
|
|
369
380
|
if (scaleRequirements.y.type === 'band' && hasCatReducing) {
|
|
370
381
|
const key = this.#transformCompiler.getCategoryTransformKey();
|
|
371
|
-
if (key)
|
|
372
|
-
config = {
|
|
382
|
+
if (key) {
|
|
383
|
+
config = {
|
|
384
|
+
...config,
|
|
385
|
+
transformKey: transformScaleKey(table, key, this.#chartContext.config)
|
|
386
|
+
};
|
|
387
|
+
}
|
|
373
388
|
}
|
|
374
389
|
this.#chartContext.registerScaleType('y', scaleRequirements.y.type, scaleRequirements.y.priority, this.#markId, config, inferredScaleColumns?.y);
|
|
375
390
|
}
|
|
@@ -421,7 +436,14 @@ export class MarkLifecycleV2 {
|
|
|
421
436
|
const schema = this.#schema;
|
|
422
437
|
if (!schema)
|
|
423
438
|
return;
|
|
439
|
+
// Read after setup, once the scales have adopted identity. A change of
|
|
440
|
+
// reads or of selectionResponse re-registers the mark: a selection
|
|
441
|
+
// that dimmed it may now filter it. The key is a string, so reads
|
|
442
|
+
// rebuilt with the same contents do not.
|
|
443
|
+
void this.#readsKey;
|
|
444
|
+
const response = this.#reactiveConfig.selectionResponse;
|
|
424
445
|
untrack(() => {
|
|
446
|
+
this.#route = { reads: this.#reads, chart: this.#chartContext.config, response };
|
|
425
447
|
// registerMark handles cleanup of previous registration for this
|
|
426
448
|
// markId and reuses the existing handle (preserves arrowTable
|
|
427
449
|
// during re-query so the chart doesn't flash empty on rebin).
|
|
@@ -430,7 +452,8 @@ export class MarkLifecycleV2 {
|
|
|
430
452
|
baseTable: this.#config.table,
|
|
431
453
|
parts,
|
|
432
454
|
schema,
|
|
433
|
-
transformCompiler: this.#transformCompiler
|
|
455
|
+
transformCompiler: this.#transformCompiler,
|
|
456
|
+
route: this.#route
|
|
434
457
|
}, this.#filterGroup);
|
|
435
458
|
});
|
|
436
459
|
});
|
|
@@ -983,10 +1006,12 @@ export class MarkLifecycleV2 {
|
|
|
983
1006
|
// one id, which does not move a row limit that coarse.
|
|
984
1007
|
const identity = {};
|
|
985
1008
|
const describe = (channel, key, positional) => {
|
|
1009
|
+
const present = this.#queryEngine.presentTableFor(key, this.#config.table);
|
|
986
1010
|
identity[channel] = {
|
|
987
1011
|
dim: this.#queryEngine.dimTableFor(key),
|
|
988
1012
|
size: this.#queryEngine.identityFor(key).values.length,
|
|
989
|
-
positional
|
|
1013
|
+
positional,
|
|
1014
|
+
...(present ? { present } : {})
|
|
990
1015
|
};
|
|
991
1016
|
};
|
|
992
1017
|
for (const { axis, key } of axes)
|
|
@@ -1057,26 +1082,33 @@ export class MarkLifecycleV2 {
|
|
|
1057
1082
|
// be reserved before a transform can write it. Reserved for every
|
|
1058
1083
|
// categorical field of this mark rather than guessing which axis topN
|
|
1059
1084
|
// will pick — at most two, and the transform looks its own field up.
|
|
1085
|
+
const otherScales = new Set();
|
|
1060
1086
|
if (this.#transformCompiler.hasSyntheticOtherBucket()) {
|
|
1061
1087
|
const otherIds = {};
|
|
1062
1088
|
for (const { axis, key } of axes) {
|
|
1063
|
-
const id = await this.#queryEngine.reserveSyntheticIdentity(key, 'Other');
|
|
1064
|
-
otherIds[axis] = id;
|
|
1065
|
-
// Re-adopt: the reservation appended to the value array, and a scale
|
|
1066
|
-
// that adopted before it must see the new entry.
|
|
1067
1089
|
const scale = (axis === 'x' ? this.#chartContext.xScaleState : this.#chartContext.yScaleState);
|
|
1068
|
-
|
|
1069
|
-
//
|
|
1070
|
-
//
|
|
1071
|
-
//
|
|
1090
|
+
// Always there by now: `axes` holds only axes whose resolved type is
|
|
1091
|
+
// band or point, and ChartScales resolves the scale instance eagerly
|
|
1092
|
+
// from registerScaleType, in this mark's constructor, so the scale
|
|
1093
|
+
// exists before setup runs. The guard is for the type, not a case.
|
|
1094
|
+
if (!scale)
|
|
1095
|
+
continue;
|
|
1096
|
+
// One Other per band scale, shared by every mark on it (see
|
|
1097
|
+
// retainOther). The scale also takes it as its Other id, so an
|
|
1098
|
+
// entrance can come out of Other and an exit go into it rather than
|
|
1099
|
+
// past the end of the axis.
|
|
1072
1100
|
//
|
|
1073
|
-
//
|
|
1074
|
-
//
|
|
1075
|
-
//
|
|
1076
|
-
//
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
scale
|
|
1101
|
+
// Held on EVERY categorical axis rather than re-deriving which axis
|
|
1102
|
+
// topN picked — that derivation living in two places is what silently
|
|
1103
|
+
// dropped the Other row once already. The axes topN did not pick are
|
|
1104
|
+
// unaffected: their id is on no row, so it is never displayed.
|
|
1105
|
+
otherScales.add(scale);
|
|
1106
|
+
this.#otherScales.add(scale);
|
|
1107
|
+
const other = await scale.retainOther(this.#markId, () => this.#queryEngine.acquireSyntheticIdentity(key, 'Other'));
|
|
1108
|
+
otherIds[axis] = other.id;
|
|
1109
|
+
// Re-adopt: acquiring may have appended to the value array, and a
|
|
1110
|
+
// scale that adopted before it must see the new entry.
|
|
1111
|
+
scale.setIdentity(this.#queryEngine.identityFor(key));
|
|
1080
1112
|
}
|
|
1081
1113
|
// topN buckets past its cutoff into these ids. The query parts rebuild:
|
|
1082
1114
|
// anything assembled before the reservation had no id and left Other
|
|
@@ -1097,6 +1129,18 @@ export class MarkLifecycleV2 {
|
|
|
1097
1129
|
this.#setIdentity(withOther);
|
|
1098
1130
|
}
|
|
1099
1131
|
}
|
|
1132
|
+
// Scales this mark no longer draws an Other on (its axis or transform
|
|
1133
|
+
// changed) get their claim back.
|
|
1134
|
+
for (const scale of this.#otherScales) {
|
|
1135
|
+
if (!otherScales.has(scale))
|
|
1136
|
+
this.#releaseOther(scale);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
/** The band scales whose Other bucket this mark holds a claim on. */
|
|
1140
|
+
#otherScales = new Set();
|
|
1141
|
+
#releaseOther(scale) {
|
|
1142
|
+
this.#otherScales.delete(scale);
|
|
1143
|
+
scale.releaseOther(this.#markId, (other) => this.#queryEngine.releaseSyntheticIdentity(other));
|
|
1100
1144
|
}
|
|
1101
1145
|
/**
|
|
1102
1146
|
* The one writer of identity: the reactive copy the query parts read, and
|
|
@@ -1173,7 +1217,7 @@ export class MarkLifecycleV2 {
|
|
|
1173
1217
|
buildViewportCountQuery(xLo, xHi, yLo, yHi) {
|
|
1174
1218
|
if (!this.setupComplete || !this.#resolvedQueryParts)
|
|
1175
1219
|
return null;
|
|
1176
|
-
const filter = this.#filterGroup.selection.predicate(this.#
|
|
1220
|
+
const filter = this.#filterGroup.selection.predicate(this.#route) ?? [];
|
|
1177
1221
|
const q = assembleQuery({
|
|
1178
1222
|
table: this.#sourceTable,
|
|
1179
1223
|
parts: this.#resolvedQueryParts,
|
|
@@ -1210,6 +1254,43 @@ export class MarkLifecycleV2 {
|
|
|
1210
1254
|
get channelFieldMap() {
|
|
1211
1255
|
return buildChannelFieldMap(this.#config.select);
|
|
1212
1256
|
}
|
|
1257
|
+
// --- Selection routing ---
|
|
1258
|
+
/**
|
|
1259
|
+
* The identity keys of the categorical fields this mark's renderer reads
|
|
1260
|
+
* selection activity for, sorted: its ordinal colour and its band axes, as
|
|
1261
|
+
* activity-reads.ts says the renderer reads them. A categorical selection on
|
|
1262
|
+
* one of these dims the mark; on any other field it filters it.
|
|
1263
|
+
*/
|
|
1264
|
+
#reads = $derived.by(() => {
|
|
1265
|
+
const renderer = rendererForMark(this.#config.markType);
|
|
1266
|
+
if (!renderer)
|
|
1267
|
+
return NO_READS;
|
|
1268
|
+
const color = this.#colorScaleState;
|
|
1269
|
+
const bandKey = (axis) => {
|
|
1270
|
+
const type = axis === 'x' ? this.actualXScaleType : this.actualYScaleType;
|
|
1271
|
+
if (type !== 'band' && type !== 'point')
|
|
1272
|
+
return null;
|
|
1273
|
+
const band = this.#chartContext[`${axis}ScaleState`];
|
|
1274
|
+
return band?.identityKey ?? null;
|
|
1275
|
+
};
|
|
1276
|
+
return readsForResponse(this.#reactiveConfig.selectionResponse, readsFor(activityReads(renderer, this.#config.ruleExtent ?? false), {
|
|
1277
|
+
color: color?.isOrdinal ? color.identityKey : null,
|
|
1278
|
+
x: bandKey('x'),
|
|
1279
|
+
y: bandKey('y')
|
|
1280
|
+
}));
|
|
1281
|
+
});
|
|
1282
|
+
/** `#reads` as one string, so the registration effect re-runs only on a real change. */
|
|
1283
|
+
#readsKey = $derived(this.#reads.join('\u001f'));
|
|
1284
|
+
/** Built at registration; the engine and every predicate call hold this object. */
|
|
1285
|
+
#route = { reads: NO_READS, chart: null };
|
|
1286
|
+
/**
|
|
1287
|
+
* The page-wide index of this mark's filter group. Its renderer stores it in
|
|
1288
|
+
* MarkConstants to read its group's run of each field's activity block.
|
|
1289
|
+
*/
|
|
1290
|
+
get groupIndex() {
|
|
1291
|
+
// NO_GROUP for selectionResponse 'filter', which never dims.
|
|
1292
|
+
return groupIndexForResponse(this.#reactiveConfig.selectionResponse, this.#filterGroup.index);
|
|
1293
|
+
}
|
|
1213
1294
|
get dataVersion() {
|
|
1214
1295
|
return this.#handle?.dataVersion ?? 0;
|
|
1215
1296
|
}
|
|
@@ -1409,9 +1490,15 @@ export class MarkLifecycleV2 {
|
|
|
1409
1490
|
if (orderCol)
|
|
1410
1491
|
orderClause = ` ORDER BY ${orderCol}`;
|
|
1411
1492
|
}
|
|
1493
|
+
// Without Other's member lists: every hover query is `SELECT *` from
|
|
1494
|
+
// this table, so lists of up to the field's cardinality would ship on
|
|
1495
|
+
// each one. Only those, not every `__dplot_` column as Path B drops:
|
|
1496
|
+
// the rest are scalars (the rank, id columns), cheap to keep, and the
|
|
1497
|
+
// lists are the only ones whose size grows with the field.
|
|
1498
|
+
const withoutMembers = withoutOtherMembers(tooltipQuery);
|
|
1412
1499
|
await this.#queryEngine.exec([
|
|
1413
1500
|
`DROP TABLE IF EXISTS "${tableName}"`,
|
|
1414
|
-
`CREATE TEMP TABLE "${tableName}" AS SELECT *${replaceClause} FROM (${
|
|
1501
|
+
`CREATE TEMP TABLE "${tableName}" AS SELECT *${replaceClause} FROM (${withoutMembers})${orderClause}`
|
|
1415
1502
|
]);
|
|
1416
1503
|
}
|
|
1417
1504
|
else {
|
|
@@ -1480,7 +1567,7 @@ export class MarkLifecycleV2 {
|
|
|
1480
1567
|
#buildTooltipQuery() {
|
|
1481
1568
|
if (!this.#resolvedQueryParts)
|
|
1482
1569
|
return null;
|
|
1483
|
-
const filter = this.#filterGroup.selection.predicate(this.#
|
|
1570
|
+
const filter = this.#filterGroup.selection.predicate(this.#route) ?? [];
|
|
1484
1571
|
const q = assembleQuery({
|
|
1485
1572
|
table: this.#sourceTable,
|
|
1486
1573
|
parts: this.#resolvedQueryParts,
|
|
@@ -1537,7 +1624,7 @@ export class MarkLifecycleV2 {
|
|
|
1537
1624
|
}
|
|
1538
1625
|
Object.assign(sidecarSelect, tipExtras);
|
|
1539
1626
|
const sidecarParts = buildQueryParts(sidecarSelect, undefined, this.#transformCompiler.hasSQLTransforms());
|
|
1540
|
-
const filter = this.#filterGroup.selection.predicate(this.#
|
|
1627
|
+
const filter = this.#filterGroup.selection.predicate(this.#route) ?? [];
|
|
1541
1628
|
const sidecarQuery = assembleQuery({
|
|
1542
1629
|
table: this.#sourceTable,
|
|
1543
1630
|
parts: sidecarParts,
|
|
@@ -1735,6 +1822,8 @@ export class MarkLifecycleV2 {
|
|
|
1735
1822
|
this.#destroyed = true;
|
|
1736
1823
|
this.#identityResetOff?.();
|
|
1737
1824
|
this.#identityResetOff = null;
|
|
1825
|
+
for (const scale of [...this.#otherScales])
|
|
1826
|
+
this.#releaseOther(scale);
|
|
1738
1827
|
this.#handle?.destroy();
|
|
1739
1828
|
clearTimeout(this.#fitDomainTimeout);
|
|
1740
1829
|
clearTimeout(this.#tooltipWriteTimeout);
|