@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
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* mark registration for batch filter-driven re-queries.
|
|
4
4
|
*
|
|
5
5
|
* The mark layer's only interface to DuckDB. All query execution goes through
|
|
6
|
-
* queryEngine.query(sql, opts) for ad-hoc queries, or registerMark()
|
|
7
|
-
*
|
|
6
|
+
* queryEngine.query(sql, opts) for ad-hoc queries, or registerMark() for the
|
|
7
|
+
* reactive registration path: FilterGroup calls onClauseChange() when one
|
|
8
|
+
* clause changes, and onFilterChange() when all of a group's do (clearAll).
|
|
8
9
|
*
|
|
9
10
|
* v1: Direct execution (await connection.query(sql))
|
|
10
11
|
* v2: Cancel-on-supersede — keyed on callerId, drops stale in-flight queries.
|
|
@@ -22,8 +23,9 @@ import { type QueryParts } from '../components/marks/lifecycle/query-builder';
|
|
|
22
23
|
import type { ActivationSource } from './preagg/plan';
|
|
23
24
|
export type { ActiveDim, ActivationSource } from './preagg/plan';
|
|
24
25
|
import { MarkHandle } from './MarkHandle.svelte';
|
|
25
|
-
import { type DimValue, type FieldIdentity, type IdentityField } from './categorical-identity';
|
|
26
|
+
import { type DimValue, type FieldIdentity, type IdentityField, type SyntheticId } from './categorical-identity';
|
|
26
27
|
import type { FilterGroup } from './FilterGroup.svelte';
|
|
28
|
+
import { type Clause, type MarkRoute } from './CrossfilterSelection.svelte';
|
|
27
29
|
import type { TransformCompiler } from '../transforms/TransformCompiler';
|
|
28
30
|
export interface QueryOpts {
|
|
29
31
|
/** Caller identifier for cancel-on-supersede. When a new query arrives
|
|
@@ -82,6 +84,8 @@ export interface QueryDescriptor {
|
|
|
82
84
|
parts: QueryParts;
|
|
83
85
|
schema: Map<string, ColumnSchema>;
|
|
84
86
|
transformCompiler: TransformCompiler;
|
|
87
|
+
/** What routing needs to know about the mark. Absent in tests that route nothing. */
|
|
88
|
+
route?: MarkRoute;
|
|
85
89
|
}
|
|
86
90
|
export declare class QueryEngine {
|
|
87
91
|
#private;
|
|
@@ -127,27 +131,40 @@ export declare class QueryEngine {
|
|
|
127
131
|
*/
|
|
128
132
|
dimTableFor(field: string): string;
|
|
129
133
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
134
|
+
* The field's categories as of now, in id order, without the synthetic Other
|
|
135
|
+
* buckets: a bucket is not a category of the data, and another chart's bucket
|
|
136
|
+
* must not appear on an axis, a facet or a colour legend that has none.
|
|
132
137
|
*
|
|
133
|
-
*
|
|
134
|
-
* registering the
|
|
135
|
-
*
|
|
138
|
+
* With `table`, only the categories that table holds: the dim spans every
|
|
139
|
+
* table registering the field, and an axis over one table must not show a
|
|
140
|
+
* slot for a category only another has. Without it (a scale shared per field,
|
|
141
|
+
* like ordinal colour), every category of the dim.
|
|
142
|
+
*
|
|
143
|
+
* A snapshot, not the live array: for decoding ids, hold `identityFor(field)`.
|
|
144
|
+
*/
|
|
145
|
+
categoryValues(field: string, table?: string): DimValue[];
|
|
146
|
+
/**
|
|
147
|
+
* The table of ids `table` holds for `field`, for `dense()` to pad from, or
|
|
148
|
+
* null before the table has folded into the field's dim.
|
|
136
149
|
*/
|
|
137
|
-
|
|
150
|
+
presentTableFor(field: string, table: string): string | null;
|
|
138
151
|
/**
|
|
139
152
|
* The field's id <-> value bijection. One live object owning both
|
|
140
153
|
* directions, so a holder cannot end up with a value array and a reverse map
|
|
141
|
-
* that disagree. Prefer this over
|
|
154
|
+
* that disagree. Prefer this over categoryValues() anywhere the reverse
|
|
142
155
|
* direction is needed; do not rebuild it locally.
|
|
143
156
|
*/
|
|
144
157
|
identityFor(field: string): FieldIdentity;
|
|
158
|
+
/** The field's current bijection, or null; never creates one (see CategoricalIdentity). */
|
|
159
|
+
peekIdentity(field: string): FieldIdentity | null;
|
|
145
160
|
/**
|
|
146
|
-
*
|
|
147
|
-
* bucket from `topN({ includeOthers })
|
|
148
|
-
*
|
|
161
|
+
* Acquire an id for a value present in no table — the synthetic "Other"
|
|
162
|
+
* bucket from `topN({ includeOthers })` — with the label it displays. Call
|
|
163
|
+
* after ensureColumns; release it when its holder goes.
|
|
149
164
|
*/
|
|
150
|
-
|
|
165
|
+
acquireSyntheticIdentity(field: string, label: string): Promise<SyntheticId>;
|
|
166
|
+
/** Give back an id from `acquireSyntheticIdentity`, for the next acquire to reuse. */
|
|
167
|
+
releaseSyntheticIdentity(synthetic: SyntheticId): void;
|
|
151
168
|
/**
|
|
152
169
|
* Subscribe to identity resets. Returns an unsubscribe function.
|
|
153
170
|
*
|
|
@@ -234,10 +251,29 @@ export declare class QueryEngine {
|
|
|
234
251
|
/** When a filter last changed, in any group, for settling (performance.now()). */
|
|
235
252
|
get lastFilterChangeAt(): number;
|
|
236
253
|
/**
|
|
237
|
-
* Called by FilterGroup when
|
|
238
|
-
*
|
|
254
|
+
* Called by FilterGroup when its clauses change wholesale (clearAll):
|
|
255
|
+
* re-queries every mark in the group.
|
|
239
256
|
*/
|
|
240
257
|
onFilterChange(group: FilterGroup): void;
|
|
258
|
+
/**
|
|
259
|
+
* Called by FilterGroup when the clause of `sourceId` changes: `prev` is the
|
|
260
|
+
* clause it replaced or removed, `next` the one now in force (null when
|
|
261
|
+
* removed).
|
|
262
|
+
*
|
|
263
|
+
* Re-queries only the group's marks that either one filters. A mark neither
|
|
264
|
+
* filters has the same SQL as before, so querying it would only assemble that
|
|
265
|
+
* SQL to find the cached result, on every brush frame. A mark that reads the
|
|
266
|
+
* clause's categorical field dims by it instead, and a mark in a chart that
|
|
267
|
+
* skips the source is not filtered by it: neither is re-queried.
|
|
268
|
+
*/
|
|
269
|
+
onClauseChange(group: FilterGroup, sourceId: string, prev: Clause | undefined, next: Clause | null): void;
|
|
270
|
+
/**
|
|
271
|
+
* Called by FilterGroup when a registration that skips `chart` comes or
|
|
272
|
+
* goes while its source has a clause: re-queries the group's marks in that
|
|
273
|
+
* chart, whose filters may have changed. A mark whose SQL did not change
|
|
274
|
+
* stops at the cache and the same-Table guard.
|
|
275
|
+
*/
|
|
276
|
+
onChartRouteChange(group: FilterGroup, chart: object): void;
|
|
241
277
|
/**
|
|
242
278
|
* Pre-aggregate marks for a filter group before a source is used: builds a
|
|
243
279
|
* preagg table per mark the source filters (see Preagg.activate). Called
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* mark registration for batch filter-driven re-queries.
|
|
4
4
|
*
|
|
5
5
|
* The mark layer's only interface to DuckDB. All query execution goes through
|
|
6
|
-
* queryEngine.query(sql, opts) for ad-hoc queries, or registerMark()
|
|
7
|
-
*
|
|
6
|
+
* queryEngine.query(sql, opts) for ad-hoc queries, or registerMark() for the
|
|
7
|
+
* reactive registration path: FilterGroup calls onClauseChange() when one
|
|
8
|
+
* clause changes, and onFilterChange() when all of a group's do (clearAll).
|
|
8
9
|
*
|
|
9
10
|
* v1: Direct execution (await connection.query(sql))
|
|
10
11
|
* v2: Cancel-on-supersede — keyed on callerId, drops stale in-flight queries.
|
|
@@ -21,6 +22,7 @@ import { assembleQuery, castNumeric, splitWindowSelect } from '../components/mar
|
|
|
21
22
|
import { Preagg } from './preagg/Preagg';
|
|
22
23
|
import { MarkHandle } from './MarkHandle.svelte';
|
|
23
24
|
import { CategoricalIdentity } from './categorical-identity';
|
|
25
|
+
import { NO_ROUTE } from './CrossfilterSelection.svelte';
|
|
24
26
|
/**
|
|
25
27
|
* How long a queued build waits behind mark queries before it is sent anyway,
|
|
26
28
|
* by default. During a drag on the base table a mark query is always waiting
|
|
@@ -197,32 +199,64 @@ export class QueryEngine {
|
|
|
197
199
|
return this.#identity.dimTable(field);
|
|
198
200
|
}
|
|
199
201
|
/**
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
+
* The field's categories as of now, in id order, without the synthetic Other
|
|
203
|
+
* buckets: a bucket is not a category of the data, and another chart's bucket
|
|
204
|
+
* must not appear on an axis, a facet or a colour legend that has none.
|
|
202
205
|
*
|
|
203
|
-
*
|
|
204
|
-
* registering the
|
|
205
|
-
*
|
|
206
|
+
* With `table`, only the categories that table holds: the dim spans every
|
|
207
|
+
* table registering the field, and an axis over one table must not show a
|
|
208
|
+
* slot for a category only another has. Without it (a scale shared per field,
|
|
209
|
+
* like ordinal colour), every category of the dim.
|
|
210
|
+
*
|
|
211
|
+
* A snapshot, not the live array: for decoding ids, hold `identityFor(field)`.
|
|
206
212
|
*/
|
|
207
|
-
|
|
208
|
-
|
|
213
|
+
categoryValues(field, table) {
|
|
214
|
+
const identity = this.#identity.identity(field);
|
|
215
|
+
const values = identity.values;
|
|
216
|
+
const present = table ? this.#identity.presentIds(field, table) : null;
|
|
217
|
+
if (identity.synthetic.size === 0 && !present)
|
|
218
|
+
return values.slice();
|
|
219
|
+
const out = [];
|
|
220
|
+
for (let id = 0; id < values.length; id++) {
|
|
221
|
+
if (identity.synthetic.has(id))
|
|
222
|
+
continue;
|
|
223
|
+
if (present && !present.has(id))
|
|
224
|
+
continue;
|
|
225
|
+
out.push(values[id]);
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The table of ids `table` holds for `field`, for `dense()` to pad from, or
|
|
231
|
+
* null before the table has folded into the field's dim.
|
|
232
|
+
*/
|
|
233
|
+
presentTableFor(field, table) {
|
|
234
|
+
return this.#identity.presentTable(field, table);
|
|
209
235
|
}
|
|
210
236
|
/**
|
|
211
237
|
* The field's id <-> value bijection. One live object owning both
|
|
212
238
|
* directions, so a holder cannot end up with a value array and a reverse map
|
|
213
|
-
* that disagree. Prefer this over
|
|
239
|
+
* that disagree. Prefer this over categoryValues() anywhere the reverse
|
|
214
240
|
* direction is needed; do not rebuild it locally.
|
|
215
241
|
*/
|
|
216
242
|
identityFor(field) {
|
|
217
243
|
return this.#identity.identity(field);
|
|
218
244
|
}
|
|
245
|
+
/** The field's current bijection, or null; never creates one (see CategoricalIdentity). */
|
|
246
|
+
peekIdentity(field) {
|
|
247
|
+
return this.#identity.peekIdentity(field);
|
|
248
|
+
}
|
|
219
249
|
/**
|
|
220
|
-
*
|
|
221
|
-
* bucket from `topN({ includeOthers })
|
|
222
|
-
*
|
|
250
|
+
* Acquire an id for a value present in no table — the synthetic "Other"
|
|
251
|
+
* bucket from `topN({ includeOthers })` — with the label it displays. Call
|
|
252
|
+
* after ensureColumns; release it when its holder goes.
|
|
223
253
|
*/
|
|
224
|
-
|
|
225
|
-
return this.#identity.
|
|
254
|
+
acquireSyntheticIdentity(field, label) {
|
|
255
|
+
return this.#identity.acquireSynthetic(field, label);
|
|
256
|
+
}
|
|
257
|
+
/** Give back an id from `acquireSyntheticIdentity`, for the next acquire to reuse. */
|
|
258
|
+
releaseSyntheticIdentity(synthetic) {
|
|
259
|
+
this.#identity.releaseSynthetic(synthetic);
|
|
226
260
|
}
|
|
227
261
|
/**
|
|
228
262
|
* Subscribe to identity resets. Returns an unsubscribe function.
|
|
@@ -559,27 +593,55 @@ export class QueryEngine {
|
|
|
559
593
|
return this.#lastFilterChangeAt;
|
|
560
594
|
}
|
|
561
595
|
/**
|
|
562
|
-
* Called by FilterGroup when
|
|
563
|
-
*
|
|
596
|
+
* Called by FilterGroup when its clauses change wholesale (clearAll):
|
|
597
|
+
* re-queries every mark in the group.
|
|
564
598
|
*/
|
|
565
599
|
onFilterChange(group) {
|
|
566
600
|
this.#lastFilterChangeAt = performance.now();
|
|
567
|
-
const batch = [];
|
|
568
601
|
for (const [markId, reg] of this.#marks) {
|
|
569
|
-
if (reg.filterGroup === group)
|
|
570
|
-
|
|
571
|
-
}
|
|
602
|
+
if (reg.filterGroup === group)
|
|
603
|
+
this.#queryMark(markId, reg);
|
|
572
604
|
}
|
|
573
|
-
|
|
574
|
-
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Called by FilterGroup when the clause of `sourceId` changes: `prev` is the
|
|
608
|
+
* clause it replaced or removed, `next` the one now in force (null when
|
|
609
|
+
* removed).
|
|
610
|
+
*
|
|
611
|
+
* Re-queries only the group's marks that either one filters. A mark neither
|
|
612
|
+
* filters has the same SQL as before, so querying it would only assemble that
|
|
613
|
+
* SQL to find the cached result, on every brush frame. A mark that reads the
|
|
614
|
+
* clause's categorical field dims by it instead, and a mark in a chart that
|
|
615
|
+
* skips the source is not filtered by it: neither is re-queried.
|
|
616
|
+
*/
|
|
617
|
+
onClauseChange(group, sourceId, prev, next) {
|
|
618
|
+
this.#lastFilterChangeAt = performance.now();
|
|
619
|
+
const { selection } = group;
|
|
620
|
+
for (const [markId, reg] of this.#marks) {
|
|
621
|
+
if (reg.filterGroup !== group)
|
|
622
|
+
continue;
|
|
623
|
+
const route = reg.descriptor.route ?? NO_ROUTE;
|
|
624
|
+
if ((prev && selection.applies(sourceId, prev, route)) ||
|
|
625
|
+
(next && selection.applies(sourceId, next, route))) {
|
|
626
|
+
this.#queryMark(markId, reg);
|
|
627
|
+
}
|
|
575
628
|
}
|
|
576
629
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
630
|
+
/**
|
|
631
|
+
* Called by FilterGroup when a registration that skips `chart` comes or
|
|
632
|
+
* goes while its source has a clause: re-queries the group's marks in that
|
|
633
|
+
* chart, whose filters may have changed. A mark whose SQL did not change
|
|
634
|
+
* stops at the cache and the same-Table guard.
|
|
635
|
+
*/
|
|
636
|
+
onChartRouteChange(group, chart) {
|
|
637
|
+
for (const [markId, reg] of this.#marks) {
|
|
638
|
+
if (reg.filterGroup === group && reg.descriptor.route?.chart === chart) {
|
|
639
|
+
this.#queryMark(markId, reg);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
580
642
|
}
|
|
581
643
|
/**
|
|
582
|
-
* Query a single mark: on a filter change
|
|
644
|
+
* Query a single mark: on a filter change or a params
|
|
583
645
|
* change (via _onParamsChange). Reads a preagg table when one answers; if
|
|
584
646
|
* that read fails, the table is set aside and the base table is queried once.
|
|
585
647
|
*/
|
|
@@ -611,7 +673,7 @@ export class QueryEngine {
|
|
|
611
673
|
/** The query a mark runs on its base table, applying every clause. */
|
|
612
674
|
#baseSQL(markId, reg) {
|
|
613
675
|
const { descriptor, handle } = reg;
|
|
614
|
-
return this.#assembleSQL(descriptor, descriptor.table, reg.filterGroup.selection.predicate(
|
|
676
|
+
return this.#assembleSQL(descriptor, descriptor.table, reg.filterGroup.selection.predicate(reg.descriptor.route ?? NO_ROUTE), handle.params);
|
|
615
677
|
}
|
|
616
678
|
/**
|
|
617
679
|
* Assemble SQL from a query descriptor + filter + per-mark params.
|
|
@@ -11,14 +11,16 @@
|
|
|
11
11
|
* later tables APPEND unseen values with continuing ids. It never reorders and
|
|
12
12
|
* never renumbers, so an id already baked into a vertex buffer stays valid and
|
|
13
13
|
* no earlier mark has to requery. `value` carries the source column's own type
|
|
14
|
-
* and `label` is NULL, except on a synthetic row (`
|
|
15
|
-
* that is inverted.
|
|
14
|
+
* and `label` is NULL, except on a synthetic row (`acquireSynthetic`), where
|
|
15
|
+
* that is inverted, and on the NULL category's row, where both are NULL.
|
|
16
16
|
*
|
|
17
17
|
* 2. A **per-table view**, `<table>_v_s<session>`, selecting `*` plus one
|
|
18
18
|
* `__dplot_<field>_idx` column per requested field. Always LEFT JOIN with
|
|
19
19
|
* COALESCE(-1): an inner join silently drops rows whose value is not in the
|
|
20
|
-
* dim (
|
|
21
|
-
* rendering bug. -1 is the unmapped sentinel, unified in `1ac4fd4`.
|
|
20
|
+
* dim (a value a later append will add), which is data loss presenting as a
|
|
21
|
+
* rendering bug. -1 is the unmapped sentinel, unified in `1ac4fd4`. A NULL
|
|
22
|
+
* value is not unmapped: the dim has a row for it (value NULL, label NULL),
|
|
23
|
+
* joined with IS NOT DISTINCT FROM, so NULL is a category like any other.
|
|
22
24
|
*
|
|
23
25
|
* 3. An **additive coalescer**, `ensureColumns()`. Concurrent callers union into
|
|
24
26
|
* a pending set, one CREATE OR REPLACE VIEW runs with the full union, and a
|
|
@@ -41,6 +43,18 @@
|
|
|
41
43
|
* the series scale all read the same columns from the same dim, and none of
|
|
42
44
|
* them is special-cased here.
|
|
43
45
|
*/
|
|
46
|
+
/**
|
|
47
|
+
* A synthetic id handed out by `acquireSynthetic`: what to release it with.
|
|
48
|
+
* `generation` ties it to the dims it was acquired from, so a release after a
|
|
49
|
+
* reset does not offer a dropped row to the next acquire.
|
|
50
|
+
*/
|
|
51
|
+
export interface SyntheticId {
|
|
52
|
+
readonly field: string;
|
|
53
|
+
readonly id: number;
|
|
54
|
+
/** The label it displays: the requested one, or `"<label> (rest)"` beside a real one. */
|
|
55
|
+
readonly label: string;
|
|
56
|
+
readonly generation: number;
|
|
57
|
+
}
|
|
44
58
|
/** The subset of QueryEngine this module needs. Keeps it testable in isolation. */
|
|
45
59
|
export interface IdentityExec {
|
|
46
60
|
exec(sql: string | string[]): Promise<void>;
|
|
@@ -89,7 +103,15 @@ export interface IdentityField {
|
|
|
89
103
|
* (SameValueZero, which covers bigint). That is why the round trip is correct
|
|
90
104
|
* today; it is the declared types that were wrong.
|
|
91
105
|
*/
|
|
92
|
-
export type DimValue = string | number | bigint;
|
|
106
|
+
export type DimValue = string | number | bigint | null;
|
|
107
|
+
/**
|
|
108
|
+
* How a category value is shown: as itself, except the NULL category, which has
|
|
109
|
+
* no text of its own. The one formatter for axis ticks, legend entries, facet
|
|
110
|
+
* labels and tooltips, so NULL reads the same everywhere. Selections keep the
|
|
111
|
+
* raw `null`: this is display only.
|
|
112
|
+
*/
|
|
113
|
+
export declare const NULL_CATEGORY_LABEL = "(null)";
|
|
114
|
+
export declare function categoryLabel<T>(value: T): T | string;
|
|
93
115
|
/**
|
|
94
116
|
* A field's id <-> value bijection, both directions in one object.
|
|
95
117
|
*
|
|
@@ -105,11 +127,32 @@ export type DimValue = string | number | bigint;
|
|
|
105
127
|
* once and stay correct when a second table appends to the dim.
|
|
106
128
|
*/
|
|
107
129
|
export declare class FieldIdentity {
|
|
108
|
-
/**
|
|
130
|
+
/**
|
|
131
|
+
* id -> source value. Dense and 0-based, so position is the id. A synthetic
|
|
132
|
+
* id (an Other bucket) holds its display label here.
|
|
133
|
+
*/
|
|
109
134
|
readonly values: DimValue[];
|
|
110
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* source value -> id, for real values only: the inverse of `values` with the
|
|
137
|
+
* synthetic ids left out. A label is not a value, and several synthetic ids
|
|
138
|
+
* may share one, so nothing resolves a bucket by its label.
|
|
139
|
+
*/
|
|
111
140
|
readonly ids: Map<DimValue, number>;
|
|
112
|
-
|
|
141
|
+
/** The synthetic ids (Other buckets): rows of the dim no source row joins to. */
|
|
142
|
+
readonly synthetic: Set<number>;
|
|
143
|
+
/**
|
|
144
|
+
* The NULL category's id, or null while the data has no NULL. An ordinary
|
|
145
|
+
* category: ranked, drawn, selectable; `values[nullId]` is `null`, and `ids`
|
|
146
|
+
* maps `null` to it, so value lookups need no special case.
|
|
147
|
+
*/
|
|
148
|
+
nullId: number | null;
|
|
149
|
+
/**
|
|
150
|
+
* The identity key this is the bijection for, as `identityKeyFor` derives
|
|
151
|
+
* it: what a scale that adopted it is keyed by for selection activity.
|
|
152
|
+
* Empty for one built outside the engine, as tests do.
|
|
153
|
+
*/
|
|
154
|
+
readonly key: string;
|
|
155
|
+
constructor(values?: readonly DimValue[], key?: string);
|
|
113
156
|
/**
|
|
114
157
|
* Bumped by `reset`, so a holder of this LIVE object can tell whether it
|
|
115
158
|
* moved since it last looked. Appends, a same-length replacement and an
|
|
@@ -117,16 +160,21 @@ export declare class FieldIdentity {
|
|
|
117
160
|
* comparing `values.length` is not.
|
|
118
161
|
*/
|
|
119
162
|
version: number;
|
|
120
|
-
/**
|
|
121
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Replace both directions. The only writer. `synthetic` names the ids that
|
|
165
|
+
* are buckets rather than values: they keep their label in `values` and stay
|
|
166
|
+
* out of `ids`.
|
|
167
|
+
*/
|
|
168
|
+
reset(values: readonly DimValue[], synthetic?: Iterable<number>): void;
|
|
122
169
|
/** True once the dim has been built and holds at least one value. */
|
|
123
170
|
get present(): boolean;
|
|
124
171
|
}
|
|
125
172
|
/**
|
|
126
|
-
* Above this many distinct values a band axis is refused outright.
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
173
|
+
* Above this many distinct values a band axis is refused outright. The shared
|
|
174
|
+
* slot buffer (IDENTITY_SLOT_CAPACITY floats) holds every band and point
|
|
175
|
+
* scale's slots and every selected field's activity, one run per filter group,
|
|
176
|
+
* so an axis this large would crowd out the rest of the page — the limit is
|
|
177
|
+
* capacity, not taste.
|
|
130
178
|
*/
|
|
131
179
|
export declare const MAX_IDENTITY_CARDINALITY = 10000;
|
|
132
180
|
/**
|
|
@@ -148,9 +196,9 @@ export declare class CategoricalIdentity {
|
|
|
148
196
|
/** SQL producing `field`'s value, once a mark has registered it; undefined before. */
|
|
149
197
|
sqlFor(field: string): string | undefined;
|
|
150
198
|
/**
|
|
151
|
-
* Whether `field`'s dim has folded in `table`, so every
|
|
152
|
-
*
|
|
153
|
-
*
|
|
199
|
+
* Whether `field`'s dim has folded in `table`, so every value the table holds,
|
|
200
|
+
* NULL included, has an id and `-1` never appears. A dim another table built
|
|
201
|
+
* may lack this table's values; a refused dim covers nothing.
|
|
154
202
|
*/
|
|
155
203
|
dimCovers(field: string, table: string): boolean;
|
|
156
204
|
/** The field whose id column is `column`, or null if no dim has that column. */
|
|
@@ -159,6 +207,14 @@ export declare class CategoricalIdentity {
|
|
|
159
207
|
viewHas(table: string, field: string): boolean;
|
|
160
208
|
/** Name of the dim table for `field`. */
|
|
161
209
|
dimTable(field: string): string;
|
|
210
|
+
/**
|
|
211
|
+
* The table of ids `table` holds for `field`, or null before that table has
|
|
212
|
+
* folded into the dim. What `dense()` pads from, so a chart never pads a
|
|
213
|
+
* category only another table has.
|
|
214
|
+
*/
|
|
215
|
+
presentTable(field: string, table: string): string | null;
|
|
216
|
+
/** The ids `table` holds for `field`, or null before that table has folded in. */
|
|
217
|
+
presentIds(field: string, table: string): ReadonlySet<number> | null;
|
|
162
218
|
/** Name of the view that carries id columns for `table`. */
|
|
163
219
|
viewTable(table: string): string;
|
|
164
220
|
/**
|
|
@@ -188,6 +244,12 @@ export declare class CategoricalIdentity {
|
|
|
188
244
|
* it before the dim exists.
|
|
189
245
|
*/
|
|
190
246
|
identity(field: string): FieldIdentity;
|
|
247
|
+
/**
|
|
248
|
+
* The field's current bijection, or null if nothing has asked for it since
|
|
249
|
+
* the last reset. Unlike `identity`, never creates one: for a holder checking
|
|
250
|
+
* whether the identity it applied is still the live one, on every frame.
|
|
251
|
+
*/
|
|
252
|
+
peekIdentity(field: string): FieldIdentity | null;
|
|
191
253
|
/** Fields the view for `table` currently exposes. Empty when there is no view. */
|
|
192
254
|
columnsFor(table: string): string[];
|
|
193
255
|
/**
|
|
@@ -200,24 +262,34 @@ export declare class CategoricalIdentity {
|
|
|
200
262
|
categorical?: (string | IdentityField)[];
|
|
201
263
|
}): Promise<void>;
|
|
202
264
|
/**
|
|
203
|
-
*
|
|
204
|
-
* bucket a `topN({ includeOthers })` produces
|
|
265
|
+
* Acquire an id for a value that exists in no table — the synthetic "Other"
|
|
266
|
+
* bucket a `topN({ includeOthers })` produces — and return it with the label
|
|
267
|
+
* it displays.
|
|
268
|
+
*
|
|
269
|
+
* A released id with the same label is reused before a dim row is inserted,
|
|
270
|
+
* so remounting a chart does not grow the dim or the field's activity block.
|
|
271
|
+
* Keyed by label too, so a reused id never needs relabelling.
|
|
205
272
|
*
|
|
206
|
-
* Goes through the same writer as every other id, so `values(f)[
|
|
207
|
-
*
|
|
208
|
-
* per-field chain, so it cannot interleave
|
|
273
|
+
* Goes through the same writer as every other id, so `values(f)[id]` is the
|
|
274
|
+
* label; `synthetic` records that it is a bucket, which keeps it out of the
|
|
275
|
+
* value -> id map. Serialized on the per-field chain, so it cannot interleave
|
|
276
|
+
* with an append.
|
|
209
277
|
*
|
|
210
278
|
* Non-negative by construction: `scaleBand` discards `value < 0.0` before the
|
|
211
279
|
* slot lookup, so a negative sentinel could never render.
|
|
212
280
|
*
|
|
213
|
-
* **
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* into rather than append beside.
|
|
281
|
+
* **A real category carrying the label stays a category.** The bucket takes
|
|
282
|
+
* `"<label> (rest)"` instead, so two bars never share a label and the real
|
|
283
|
+
* one stays selectable. The check runs at acquire; a reload re-acquires.
|
|
217
284
|
*
|
|
218
285
|
* Requires the dim to exist: call after `ensureColumns` has resolved.
|
|
219
286
|
*/
|
|
220
|
-
|
|
287
|
+
acquireSynthetic(field: string, label: string): Promise<SyntheticId>;
|
|
288
|
+
/**
|
|
289
|
+
* Give an acquired id back for the next acquire of the same label. One from
|
|
290
|
+
* before a reset names a row of a dropped dim, and is ignored.
|
|
291
|
+
*/
|
|
292
|
+
releaseSynthetic(synthetic: SyntheticId): void;
|
|
221
293
|
/**
|
|
222
294
|
* Drop every view and dim and forget all state. Called on data reload, where
|
|
223
295
|
* the ids are stale by definition — the same values may not exist any more,
|