@fjader/svelte 0.2.3 → 0.2.5
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 +3 -1
- package/dist/animation/EntranceGate.d.ts +52 -0
- package/dist/animation/EntranceGate.js +93 -0
- package/dist/animation/TransitionClock.d.ts +61 -30
- package/dist/animation/TransitionClock.js +67 -29
- package/dist/animation/transition-log.d.ts +41 -0
- package/dist/animation/transition-log.js +57 -0
- package/dist/components/axes/AxisBottom.svelte +27 -8
- package/dist/components/axes/AxisBottom.svelte.d.ts +10 -0
- package/dist/components/axes/AxisCenter.svelte +4 -2
- package/dist/components/axes/AxisLeft.svelte +81 -10
- package/dist/components/axes/AxisLeft.svelte.d.ts +17 -0
- package/dist/components/axes/BandAxisTicks.svelte +4 -3
- 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 +33 -6
- package/dist/components/charts/ChartCell.svelte.d.ts +9 -0
- package/dist/components/charts/ChartContainer.svelte +55 -5
- package/dist/components/charts/ChartContainer.svelte.d.ts +14 -1
- package/dist/components/charts/ChartHover.svelte.d.ts +14 -0
- package/dist/components/charts/ChartHover.svelte.js +22 -0
- package/dist/components/charts/ChartVisibility.svelte.d.ts +43 -0
- package/dist/components/charts/ChartVisibility.svelte.js +67 -0
- package/dist/components/guides/Grid.svelte +12 -1
- package/dist/components/guides/legend/ContinuousColorLegend.svelte +7 -1
- package/dist/components/guides/legend/OrdinalColorLegend.svelte +8 -5
- package/dist/components/marks/AreaMarkX.svelte +18 -4
- package/dist/components/marks/AreaMarkY.svelte +18 -4
- package/dist/components/marks/BarMarkX.svelte +20 -4
- package/dist/components/marks/BarMarkY.svelte +20 -4
- package/dist/components/marks/DotsMark.svelte +8 -3
- package/dist/components/marks/LineMark.svelte +13 -6
- package/dist/components/marks/ParallelCoordinatesMark.svelte +11 -2
- package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +4 -0
- package/dist/components/marks/RectMark.svelte +8 -3
- package/dist/components/marks/RectXMark.svelte +9 -3
- package/dist/components/marks/RectYMark.svelte +9 -3
- package/dist/components/marks/RuleMarkX.svelte +31 -25
- package/dist/components/marks/RuleMarkY.svelte +31 -25
- package/dist/components/marks/TextMark.svelte +52 -14
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +22 -1
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +579 -66
- package/dist/components/marks/lifecycle/domain-queries.d.ts +13 -2
- package/dist/components/marks/lifecycle/domain-queries.js +43 -11
- package/dist/components/marks/lifecycle/domain-registration.d.ts +6 -1
- package/dist/components/marks/lifecycle/domain-registration.js +17 -20
- package/dist/components/marks/lifecycle/other-rows.svelte.d.ts +112 -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 +53 -3
- package/dist/components/marks/lifecycle/query-builder.js +46 -23
- package/dist/components/marks/utils/MarkColor.svelte.d.ts +3 -1
- package/dist/components/marks/utils/MarkColor.svelte.js +33 -3
- package/dist/components/marks/utils/MarkSeries.svelte.d.ts +14 -1
- package/dist/components/marks/utils/MarkSeries.svelte.js +25 -1
- package/dist/components/marks/utils/MarkSize.svelte.js +4 -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 +57 -19
- package/dist/components/rendering/Areas.svelte.d.ts +12 -0
- package/dist/components/rendering/BaseGPURenderer.svelte.d.ts +3 -1
- package/dist/components/rendering/BaseGPURenderer.svelte.js +4 -2
- package/dist/components/rendering/Dots.svelte +23 -3
- package/dist/components/rendering/Dots.svelte.d.ts +6 -0
- package/dist/components/rendering/GridLine.svelte +13 -1
- package/dist/components/rendering/GridLine.svelte.d.ts +5 -0
- package/dist/components/rendering/Lines.svelte +48 -13
- package/dist/components/rendering/Lines.svelte.d.ts +12 -0
- package/dist/components/rendering/ParallelLines.svelte +5 -5
- package/dist/components/rendering/Rects.svelte +164 -30
- package/dist/components/rendering/Rects.svelte.d.ts +23 -2
- package/dist/components/rendering/RuleX.svelte +1 -1
- package/dist/components/rendering/RuleY.svelte +1 -1
- package/dist/components/rendering/WebGPUCanvas.svelte +4 -2
- package/dist/components/rendering/activity-reads.d.ts +47 -12
- package/dist/components/rendering/activity-reads.js +61 -19
- package/dist/components/rendering/uniform-sizes.d.ts +1 -1
- package/dist/components/rendering/uniform-sizes.js +1 -1
- package/dist/config/ChartConfig.svelte.d.ts +10 -1
- package/dist/config/ChartConfig.svelte.js +17 -0
- package/dist/gpu/GPUCoordinator.svelte.d.ts +31 -1
- package/dist/gpu/GPUCoordinator.svelte.js +164 -7
- package/dist/gpu/MorphedChannels.d.ts +11 -28
- package/dist/gpu/MorphedChannels.js +72 -8
- package/dist/gpu/ScaleTemplateSystem.d.ts +20 -2
- package/dist/gpu/ScaleTemplateSystem.js +83 -6
- package/dist/index.d.ts +19 -4
- package/dist/index.js +22 -4
- package/dist/interactors/Interval1D.svelte +6 -1
- package/dist/interactors/Interval2D.svelte +6 -1
- package/dist/interactors/ParallelAxisSource.svelte +10 -1
- package/dist/interactors/Toggle.svelte +32 -3
- package/dist/interactors/Tooltip.svelte +138 -41
- package/dist/interactors/TooltipManager.svelte.d.ts +20 -5
- package/dist/interactors/TooltipManager.svelte.js +46 -12
- package/dist/interactors/other-tooltip.d.ts +16 -0
- package/dist/interactors/other-tooltip.js +22 -0
- package/dist/query-engine/CrossfilterSelection.svelte.d.ts +4 -2
- package/dist/query-engine/CrossfilterSelection.svelte.js +2 -0
- package/dist/query-engine/FieldActivity.svelte.d.ts +49 -1
- package/dist/query-engine/FieldActivity.svelte.js +175 -5
- package/dist/query-engine/FilterGroup.svelte.d.ts +23 -1
- package/dist/query-engine/FilterGroup.svelte.js +43 -3
- package/dist/query-engine/FilterGroupRegistry.svelte.d.ts +31 -3
- package/dist/query-engine/FilterGroupRegistry.svelte.js +62 -15
- package/dist/query-engine/MarkHandle.svelte.d.ts +28 -1
- package/dist/query-engine/MarkHandle.svelte.js +33 -1
- package/dist/query-engine/QueryEngine.d.ts +225 -5
- package/dist/query-engine/QueryEngine.js +697 -47
- package/dist/query-engine/categorical-identity.d.ts +41 -0
- package/dist/query-engine/categorical-identity.js +145 -65
- package/dist/query-engine/debug/query-log.d.ts +39 -0
- package/dist/query-engine/debug/query-log.js +90 -0
- package/dist/query-engine/duckdb.d.ts +55 -8
- package/dist/query-engine/duckdb.js +82 -30
- package/dist/query-engine/index.d.ts +7 -4
- package/dist/query-engine/index.js +3 -3
- package/dist/query-engine/preagg/BuildQueue.d.ts +33 -13
- package/dist/query-engine/preagg/BuildQueue.js +51 -15
- package/dist/query-engine/preagg/Preagg.d.ts +46 -3
- package/dist/query-engine/preagg/Preagg.js +159 -31
- package/dist/query-engine/preagg/plan.d.ts +33 -1
- package/dist/query-engine/preagg/plan.js +158 -14
- package/dist/query-engine/sliced-lane.d.ts +108 -0
- package/dist/query-engine/sliced-lane.js +299 -0
- package/dist/query-engine/source-routing.d.ts +106 -0
- package/dist/query-engine/source-routing.js +189 -0
- package/dist/query-engine/toggle-predicate.d.ts +21 -0
- package/dist/query-engine/toggle-predicate.js +29 -1
- package/dist/scales/ActivityTable.d.ts +5 -1
- package/dist/scales/ActivityTable.js +12 -3
- package/dist/scales/BandScaleState.svelte.d.ts +22 -9
- package/dist/scales/BandScaleState.svelte.js +39 -21
- package/dist/scales/ChartScales.svelte.d.ts +3 -0
- package/dist/scales/ChartScales.svelte.js +26 -8
- package/dist/scales/ColorScaleState.svelte.d.ts +18 -2
- package/dist/scales/ColorScaleState.svelte.js +138 -26
- package/dist/scales/LinearScaleState.svelte.d.ts +7 -10
- package/dist/scales/LinearScaleState.svelte.js +21 -39
- package/dist/scales/ScaleGeneration.svelte.d.ts +48 -0
- package/dist/scales/ScaleGeneration.svelte.js +42 -0
- package/dist/scales/ScaleRegistry.svelte.js +3 -1
- package/dist/scales/SlotTransition.d.ts +15 -1
- package/dist/scales/SlotTransition.js +33 -4
- package/dist/scales/TimeScaleState.svelte.d.ts +7 -3
- package/dist/scales/TimeScaleState.svelte.js +7 -7
- package/dist/scales/color-utils.d.ts +0 -3
- package/dist/scales/color-utils.js +0 -3
- package/dist/scales/entered.d.ts +8 -0
- package/dist/scales/entered.js +10 -0
- package/dist/spec/LazyQueryEngine.js +2 -19
- package/dist/spec/MarkRegistry.d.ts +3 -1
- package/dist/spec/MarkRegistry.js +13 -1
- package/dist/spec/PlotFromSpec.svelte +1 -0
- package/dist/spec/export.js +11 -2
- package/dist/spec/types.d.ts +43 -13
- package/dist/sql/bin.d.ts +25 -0
- package/dist/sql/bin.js +108 -5
- package/dist/theme.css +5 -4
- package/dist/transforms/TransformCompiler.d.ts +47 -2
- package/dist/transforms/TransformCompiler.js +70 -1
- package/dist/transforms/index.js +176 -42
- package/dist/types.d.ts +177 -2
- package/package.json +4 -3
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Other bar's selected share, per row, and when a bar has one: the pure
|
|
3
|
+
* half of MarkLifecycleV2's `otherSplit`, so it can be tested without a mark.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The split channel for the bar renderer, per row: share = Σ values · selected
|
|
7
|
+
* / Σ values over the row's own member lists. Selection is by value (`decode`
|
|
8
|
+
* maps an id to its value), so the NULL category needs nothing special. A row
|
|
9
|
+
* whose members mix signs gets no split (0): a share of a sum that cancels
|
|
10
|
+
* means nothing.
|
|
11
|
+
*
|
|
12
|
+
* `previous`: the last split for the SAME rows (the same result), when only the
|
|
13
|
+
* selection changed. A flat row then holds its previous position, so going flat
|
|
14
|
+
* (a selection cleared, or one that no longer reaches into Other) is a fade,
|
|
15
|
+
* not a slide. Without it (a new result) every row takes its value, and the
|
|
16
|
+
* split travels with the bar's own morph.
|
|
17
|
+
*
|
|
18
|
+
* Cost, measured: about 22 ns per Other member per call (the `selected.has`),
|
|
19
|
+
* so 0.1 ms at 5 000 members and about 1.2 ms at 50 000. Fine per click. If
|
|
20
|
+
* selection ever changes continuously (a brush on a band axis) over a very large
|
|
21
|
+
* Other, test membership against a Uint8Array indexed by id, built once per
|
|
22
|
+
* change, rather than a Set. The arrays allocated here are sized by rows.
|
|
23
|
+
*/
|
|
24
|
+
export function otherSplit(opts) {
|
|
25
|
+
const { rows, band, otherId, ids, values, selected, decode } = opts;
|
|
26
|
+
const previous = opts.previous && opts.previous.split.length === rows ? opts.previous : null;
|
|
27
|
+
const split = new Float32Array(rows);
|
|
28
|
+
const flat = new Uint8Array(rows);
|
|
29
|
+
const pickedSums = new Float64Array(rows);
|
|
30
|
+
const totals = new Float64Array(rows);
|
|
31
|
+
const pickedCount = new Int32Array(rows);
|
|
32
|
+
const memberCount = new Int32Array(rows);
|
|
33
|
+
let slides = true;
|
|
34
|
+
for (let row = 0; row < rows; row++) {
|
|
35
|
+
if (Number(band.at(row)) !== otherId)
|
|
36
|
+
continue;
|
|
37
|
+
const memberIds = ids.at(row);
|
|
38
|
+
const memberValues = values.at(row);
|
|
39
|
+
if (!memberIds || !memberValues)
|
|
40
|
+
continue;
|
|
41
|
+
let total = 0;
|
|
42
|
+
let picked = 0;
|
|
43
|
+
let count = 0;
|
|
44
|
+
let positive = false;
|
|
45
|
+
let negative = false;
|
|
46
|
+
for (let k = 0; k < memberIds.length; k++) {
|
|
47
|
+
const v = memberValues[k];
|
|
48
|
+
if (v > 0)
|
|
49
|
+
positive = true;
|
|
50
|
+
else if (v < 0)
|
|
51
|
+
negative = true;
|
|
52
|
+
total += v;
|
|
53
|
+
if (selected == null || selected.has(decode[memberIds[k]])) {
|
|
54
|
+
picked += v;
|
|
55
|
+
count++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (positive && negative)
|
|
59
|
+
continue;
|
|
60
|
+
pickedSums[row] = picked;
|
|
61
|
+
totals[row] = total;
|
|
62
|
+
pickedCount[row] = count;
|
|
63
|
+
memberCount[row] = memberIds.length;
|
|
64
|
+
const share = total === 0 ? (selected == null ? 1 : 0) : Math.min(1, Math.max(0, picked / total));
|
|
65
|
+
const isFlat = selected == null || share === 0;
|
|
66
|
+
flat[row] = isFlat ? 1 : 0;
|
|
67
|
+
if (isFlat && previous) {
|
|
68
|
+
const held = previous.split[row];
|
|
69
|
+
split[row] = held > 0.5 ? held : 2;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
split[row] = 1 + share;
|
|
73
|
+
}
|
|
74
|
+
if (previous && previous.flat[row] === 1 && !isFlat)
|
|
75
|
+
slides = false;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
split,
|
|
79
|
+
flat,
|
|
80
|
+
slides,
|
|
81
|
+
picked: pickedSums,
|
|
82
|
+
total: totals,
|
|
83
|
+
pickedCount,
|
|
84
|
+
memberCount,
|
|
85
|
+
active: selected != null
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Whether a bar's Other bucket can be split: its length must be what Other
|
|
90
|
+
* sums, and a total. `length` is the bar's length channel, `by` the channel
|
|
91
|
+
* topN ranks by (null: its default, the length), `expr` the length channel's
|
|
92
|
+
* select expression.
|
|
93
|
+
*
|
|
94
|
+
* Other sums `by`, so a length channel that is not `by` is 0 on the Other row.
|
|
95
|
+
* A bare COUNT or SUM, or a raw column (an ungrouped mark), is a total; a mean,
|
|
96
|
+
* a max, a ratio or a distinct count summed into Other is not, and a share of
|
|
97
|
+
* it would look exact while meaning nothing.
|
|
98
|
+
*/
|
|
99
|
+
export function splittable(length, by, expr) {
|
|
100
|
+
if (!length || (by ?? length) !== length)
|
|
101
|
+
return false;
|
|
102
|
+
if (typeof expr === 'string')
|
|
103
|
+
return true;
|
|
104
|
+
const node = expr;
|
|
105
|
+
if (!node || typeof node !== 'object')
|
|
106
|
+
return false;
|
|
107
|
+
if (node.type === 'COLUMN_REF')
|
|
108
|
+
return true;
|
|
109
|
+
if (node.type !== 'AGGREGATE' || node.isDistinct)
|
|
110
|
+
return false;
|
|
111
|
+
return node.name === 'count' || node.name === 'sum';
|
|
112
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sum, Query, WindowNode, type ExprNode } from '@uwdata/mosaic-sql';
|
|
2
2
|
import type { TransformCompiler } from '../../../transforms/TransformCompiler';
|
|
3
3
|
import type { ColumnSchema } from '../../../query-engine/QueryEngine';
|
|
4
|
+
import type { BinPadding } from '../../../types';
|
|
4
5
|
interface OrderConfig {
|
|
5
6
|
seriesChannel: string | null;
|
|
6
7
|
dimensionChannel: string;
|
|
@@ -18,6 +19,14 @@ export interface QueryParts {
|
|
|
18
19
|
* combination, and cannot recover from a query topN has already wrapped.
|
|
19
20
|
*/
|
|
20
21
|
groupKeys: string[] | null;
|
|
22
|
+
/**
|
|
23
|
+
* The numeric `bin()` channels and their bins, set by the mark from the
|
|
24
|
+
* same resolution its bin expressions came from. With them `dense()` pads a
|
|
25
|
+
* histogram to every bin; preagg reads copy them, so both paths pad alike.
|
|
26
|
+
*/
|
|
27
|
+
bins?: Record<string, BinPadding> | null;
|
|
28
|
+
/** The query reads a preagg table that already holds its padding rows. */
|
|
29
|
+
padded?: boolean;
|
|
21
30
|
orderChannels: string[] | null;
|
|
22
31
|
aliasMap: Map<string, string>;
|
|
23
32
|
/** Channels whose raw config value was a column-name passthrough (string
|
|
@@ -26,7 +35,31 @@ export interface QueryParts {
|
|
|
26
35
|
* CAST for ORDER BY, ordinal CASE for PARTITION BY). Used by preagg to
|
|
27
36
|
* fold redundant window col refs into channel aliases. */
|
|
28
37
|
passthroughChannels: Map<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* `nulls="drop"`: the categorical channels whose NULL the mark leaves out.
|
|
40
|
+
* Stated by CHANNEL, not by source column, so every path filters in the
|
|
41
|
+
* terms of its own select: `assembleQuery` reads each channel's expression
|
|
42
|
+
* from the `selectDef` it is given — the id column on the base table, the
|
|
43
|
+
* preagg table's column when preagg rewrote it — and ANDs the predicate
|
|
44
|
+
* into the base scan, before any transform.
|
|
45
|
+
*/
|
|
46
|
+
nullDrops?: readonly NullDrop[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* One channel whose NULL a mark drops. With `nullId`, the channel carries dim
|
|
50
|
+
* ids and the NULL category is that id: `<channel> <> nullId`. Without (a raw
|
|
51
|
+
* value, or a dim with no NULL row): `<channel> IS NOT NULL`.
|
|
52
|
+
*/
|
|
53
|
+
export interface NullDrop {
|
|
54
|
+
channel: string;
|
|
55
|
+
nullId: number | null;
|
|
29
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The predicates for `drops`, each on its channel's expression in `selectDef`.
|
|
59
|
+
* A dropped channel must be a row-level group key of the query; one the query
|
|
60
|
+
* does not select would be left unfiltered, so that is an error, not a skip.
|
|
61
|
+
*/
|
|
62
|
+
export declare function nullDropPredicates(drops: readonly NullDrop[], selectDef: Record<string, unknown>): ExprNode[];
|
|
30
63
|
/** Window functions to apply in an outer CTE over the assembled inner query. */
|
|
31
64
|
export interface WindowWrap {
|
|
32
65
|
/** channel → outer WindowNode (already rewritten to reference channel aliases). */
|
|
@@ -38,9 +71,10 @@ export interface AssembleOptions {
|
|
|
38
71
|
parts: QueryParts;
|
|
39
72
|
/** Per-call filter predicates (crossfilter, bin filters). */
|
|
40
73
|
filter?: unknown[];
|
|
41
|
-
/** Columns to add IS NOT NULL predicates for. */
|
|
42
74
|
transformCompiler: TransformCompiler;
|
|
43
75
|
limit?: number | null;
|
|
76
|
+
/** The table already has the mark's row filters (`filter()`) applied: a preagg table. */
|
|
77
|
+
skipRowFilters?: boolean;
|
|
44
78
|
/** When set, the inner query is wrapped in a CTE and these window
|
|
45
79
|
* functions are applied in the outer SELECT. ORDER BY / LIMIT move to
|
|
46
80
|
* the outer query. */
|
|
@@ -49,8 +83,8 @@ export interface AssembleOptions {
|
|
|
49
83
|
/**
|
|
50
84
|
* Wrap an expression so DuckDB returns a Float32-compatible REAL with NULLs
|
|
51
85
|
* mapped to NaN: COALESCE(CAST(expr AS REAL), 'NaN'::REAL). Arrow columns then
|
|
52
|
-
* have nullCount=0 and clean buffers
|
|
53
|
-
* NaN and discard.
|
|
86
|
+
* have nullCount=0 and clean Float32 buffers that upload to the GPU as they are,
|
|
87
|
+
* with no per-row JS pass; shaders detect NaN and discard.
|
|
54
88
|
*/
|
|
55
89
|
export declare function castReal(expr: unknown): ExprNode;
|
|
56
90
|
/**
|
|
@@ -134,5 +168,21 @@ export declare function buildQueryParts(select: Record<string, unknown>, orderCo
|
|
|
134
168
|
* discarded one level up.
|
|
135
169
|
*/
|
|
136
170
|
export declare function outerOrder(preference: string[] | null, alignment: string[] | null): string[] | null;
|
|
171
|
+
/**
|
|
172
|
+
* Assemble a Query from cached parts + per-call filter.
|
|
173
|
+
*
|
|
174
|
+
* Order is fixed and shared by the flat, window-CTE, and preagg paths:
|
|
175
|
+
*
|
|
176
|
+
* SELECT parts.selectDef FROM table [GROUP BY dims]
|
|
177
|
+
* WHERE filter AND nullDrops — on the base scan, so transform CTEs
|
|
178
|
+
* (topN ranking etc.) see filtered rows
|
|
179
|
+
* → SQL transforms (CTE wrapping)
|
|
180
|
+
* → [window CTE: outer SELECT applies window fns over the inner result]
|
|
181
|
+
* → ORDER BY outerOrder(...) — alignment, then orderChannels; outermost
|
|
182
|
+
* → LIMIT
|
|
183
|
+
*
|
|
184
|
+
* Callers decide what is CAST-wrapped by preparing parts.selectDef with
|
|
185
|
+
* castNumeric(); assembly never inspects expression types.
|
|
186
|
+
*/
|
|
137
187
|
export declare function assembleQuery(opts: AssembleOptions): Query;
|
|
138
188
|
export {};
|
|
@@ -1,13 +1,30 @@
|
|
|
1
|
-
import { isNode, isAggregateExpression, isColumnRef, isColumnParam, column as sqlColumn, cast, coalesce, collectColumns, rewrite, sql, isNotNull, and, sum, avg, min, max, Query, WindowNode } from '@uwdata/mosaic-sql';
|
|
1
|
+
import { isNode, isAggregateExpression, isColumnRef, isColumnParam, column as sqlColumn, cast, coalesce, collectColumns, rewrite, sql, isNotNull, neq, literal, and, sum, avg, min, max, Query, WindowNode } from '@uwdata/mosaic-sql';
|
|
2
2
|
import { isWindowExpression, isVerbatimWindowExpression } from '../../../sql/expression-utils';
|
|
3
|
+
/**
|
|
4
|
+
* The predicates for `drops`, each on its channel's expression in `selectDef`.
|
|
5
|
+
* A dropped channel must be a row-level group key of the query; one the query
|
|
6
|
+
* does not select would be left unfiltered, so that is an error, not a skip.
|
|
7
|
+
*/
|
|
8
|
+
export function nullDropPredicates(drops, selectDef) {
|
|
9
|
+
const out = [];
|
|
10
|
+
for (const { channel, nullId } of drops) {
|
|
11
|
+
const value = selectDef[channel];
|
|
12
|
+
if (value === undefined) {
|
|
13
|
+
throw new Error(`nulls="drop" filters the "${channel}" channel, which this query does not select.`);
|
|
14
|
+
}
|
|
15
|
+
const expr = (typeof value === 'string' ? sqlColumn(value) : value);
|
|
16
|
+
out.push(nullId === null ? isNotNull(expr) : neq(expr, literal(nullId)));
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
3
20
|
// ---------------------------------------------------------------------------
|
|
4
21
|
// Numeric CAST policy (single rule for every assembly path)
|
|
5
22
|
// ---------------------------------------------------------------------------
|
|
6
23
|
/**
|
|
7
24
|
* Wrap an expression so DuckDB returns a Float32-compatible REAL with NULLs
|
|
8
25
|
* mapped to NaN: COALESCE(CAST(expr AS REAL), 'NaN'::REAL). Arrow columns then
|
|
9
|
-
* have nullCount=0 and clean buffers
|
|
10
|
-
* NaN and discard.
|
|
26
|
+
* have nullCount=0 and clean Float32 buffers that upload to the GPU as they are,
|
|
27
|
+
* with no per-row JS pass; shaders detect NaN and discard.
|
|
11
28
|
*/
|
|
12
29
|
export function castReal(expr) {
|
|
13
30
|
return coalesce(cast(expr, 'REAL'), sql `'NaN'::REAL`);
|
|
@@ -202,22 +219,6 @@ export function buildQueryParts(select, orderConfig, hasSQLTransforms, passthrou
|
|
|
202
219
|
// ---------------------------------------------------------------------------
|
|
203
220
|
// Assembly — the one sequence every query path follows
|
|
204
221
|
// ---------------------------------------------------------------------------
|
|
205
|
-
/**
|
|
206
|
-
* Assemble a Query from cached parts + per-call filter.
|
|
207
|
-
*
|
|
208
|
-
* Order is fixed and shared by the flat, window-CTE, and preagg paths:
|
|
209
|
-
*
|
|
210
|
-
* SELECT parts.selectDef FROM table [GROUP BY dims]
|
|
211
|
-
* WHERE filter AND nulls — on the base scan, so transform CTEs
|
|
212
|
-
* (topN ranking etc.) see filtered rows
|
|
213
|
-
* → SQL transforms (CTE wrapping)
|
|
214
|
-
* → [window CTE: outer SELECT applies window fns over the inner result]
|
|
215
|
-
* → ORDER BY outerOrder(...) — alignment, then orderChannels; outermost
|
|
216
|
-
* → LIMIT
|
|
217
|
-
*
|
|
218
|
-
* Callers decide what is CAST-wrapped by preparing parts.selectDef with
|
|
219
|
-
* castNumeric(); assembly never inspects expression types.
|
|
220
|
-
*/
|
|
221
222
|
/** Output aliases a query exposes to whatever wraps it. */
|
|
222
223
|
function selectAliases(query) {
|
|
223
224
|
const sel = query._select;
|
|
@@ -266,20 +267,39 @@ export function outerOrder(preference, alignment) {
|
|
|
266
267
|
add(channel);
|
|
267
268
|
return order;
|
|
268
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Assemble a Query from cached parts + per-call filter.
|
|
272
|
+
*
|
|
273
|
+
* Order is fixed and shared by the flat, window-CTE, and preagg paths:
|
|
274
|
+
*
|
|
275
|
+
* SELECT parts.selectDef FROM table [GROUP BY dims]
|
|
276
|
+
* WHERE filter AND nullDrops — on the base scan, so transform CTEs
|
|
277
|
+
* (topN ranking etc.) see filtered rows
|
|
278
|
+
* → SQL transforms (CTE wrapping)
|
|
279
|
+
* → [window CTE: outer SELECT applies window fns over the inner result]
|
|
280
|
+
* → ORDER BY outerOrder(...) — alignment, then orderChannels; outermost
|
|
281
|
+
* → LIMIT
|
|
282
|
+
*
|
|
283
|
+
* Callers decide what is CAST-wrapped by preparing parts.selectDef with
|
|
284
|
+
* castNumeric(); assembly never inspects expression types.
|
|
285
|
+
*/
|
|
269
286
|
export function assembleQuery(opts) {
|
|
270
|
-
const { table, parts, filter = [], transformCompiler, limit, window } = opts;
|
|
287
|
+
const { table, parts, filter = [], transformCompiler, limit, window, skipRowFilters } = opts;
|
|
271
288
|
const { selectDef, nonAggregateGroupBy, hasAggregates, hasSQLTransforms, orderChannels } = parts;
|
|
272
289
|
const q = Query.from(table).select(selectDef);
|
|
273
290
|
if (hasAggregates && nonAggregateGroupBy.length > 0) {
|
|
274
291
|
q.groupby(nonAggregateGroupBy);
|
|
275
292
|
}
|
|
276
|
-
const allFilters = [
|
|
293
|
+
const allFilters = [
|
|
294
|
+
...filter,
|
|
295
|
+
...nullDropPredicates(parts.nullDrops ?? [], selectDef)
|
|
296
|
+
];
|
|
277
297
|
if (allFilters.length > 0) {
|
|
278
298
|
q.where(and(allFilters));
|
|
279
299
|
}
|
|
280
300
|
let result = q;
|
|
281
301
|
if (hasSQLTransforms) {
|
|
282
|
-
result = transformCompiler.applyToQuery(q, { groupKeys: parts.groupKeys });
|
|
302
|
+
result = transformCompiler.applyToQuery(q, { groupKeys: parts.groupKeys, bins: parts.bins ?? null, padded: parts.padded ?? false }, { skipRowFilters });
|
|
283
303
|
}
|
|
284
304
|
if (window) {
|
|
285
305
|
const outerSelect = {};
|
|
@@ -307,7 +327,10 @@ export function assembleQuery(opts) {
|
|
|
307
327
|
.with({ [window.cteName]: result });
|
|
308
328
|
}
|
|
309
329
|
const alignment = hasSQLTransforms
|
|
310
|
-
? transformCompiler.alignment(Object.keys(selectDef), {
|
|
330
|
+
? transformCompiler.alignment(Object.keys(selectDef), {
|
|
331
|
+
groupKeys: parts.groupKeys,
|
|
332
|
+
bins: parts.bins ?? null
|
|
333
|
+
})
|
|
311
334
|
: null;
|
|
312
335
|
const order = outerOrder(orderChannels, alignment);
|
|
313
336
|
if (order) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ColorScaleState } from '../../../scales/ColorScaleState.svelte';
|
|
2
|
-
import type { ColorProp, ChartContext } from '../../../types';
|
|
2
|
+
import type { ColorDomainValue, ColorProp, ChartContext } from '../../../types';
|
|
3
3
|
import type { ScaleRegistry } from '../../../scales/ScaleRegistry.svelte';
|
|
4
4
|
import type { MarkSetupState } from './mark-types';
|
|
5
5
|
interface ColorConfig {
|
|
@@ -11,6 +11,8 @@ interface ColorConfig {
|
|
|
11
11
|
explicitType?: string;
|
|
12
12
|
constant?: number;
|
|
13
13
|
exponent?: number;
|
|
14
|
+
range?: readonly string[];
|
|
15
|
+
pinnedDomain?: readonly ColorDomainValue[];
|
|
14
16
|
}
|
|
15
17
|
export declare class MarkColor {
|
|
16
18
|
#private;
|
|
@@ -30,6 +30,10 @@ export class MarkColor {
|
|
|
30
30
|
const mode = this.#mode;
|
|
31
31
|
const field = this.#field;
|
|
32
32
|
const type = this.config.scaleType;
|
|
33
|
+
// Again when the chart's scales are replaced: the registry dropped
|
|
34
|
+
// this one with the old ChartScales (its GPU slots may already be
|
|
35
|
+
// someone else's), and holding it would write to a destroyed scale.
|
|
36
|
+
void this.#chartContext.scaleGeneration;
|
|
33
37
|
// Only create scale when type is known (not null)
|
|
34
38
|
if (mode === 'field' && field && this.#scaleRegistry && type !== null) {
|
|
35
39
|
untrack(() => {
|
|
@@ -42,7 +46,9 @@ export class MarkColor {
|
|
|
42
46
|
type: type,
|
|
43
47
|
scheme: this.config.scheme,
|
|
44
48
|
constant: this.config.constant,
|
|
45
|
-
exponent: this.config.exponent
|
|
49
|
+
exponent: this.config.exponent,
|
|
50
|
+
range: this.config.range,
|
|
51
|
+
pinnedDomain: this.config.pinnedDomain
|
|
46
52
|
}, this.#chartContext.chartId);
|
|
47
53
|
});
|
|
48
54
|
}
|
|
@@ -66,6 +72,21 @@ export class MarkColor {
|
|
|
66
72
|
this.scaleState.setScheme(scheme);
|
|
67
73
|
}
|
|
68
74
|
});
|
|
75
|
+
// 2a. Lifecycle: Update explicit colours (Fast Path - no scale recreation)
|
|
76
|
+
// Like the scheme, a mark without them leaves a shared scale's alone; one
|
|
77
|
+
// that drops them (an edited prop) clears what it set.
|
|
78
|
+
let hadRange = false;
|
|
79
|
+
$effect(() => {
|
|
80
|
+
const range = this.config.range;
|
|
81
|
+
const pinned = this.config.pinnedDomain;
|
|
82
|
+
const scale = this.scaleState;
|
|
83
|
+
if (!scale?.isOrdinal)
|
|
84
|
+
return;
|
|
85
|
+
const has = !!(range?.length || pinned?.length);
|
|
86
|
+
if (has || hadRange)
|
|
87
|
+
scale.setRange(range ?? null, pinned ?? null);
|
|
88
|
+
hadRange = has;
|
|
89
|
+
});
|
|
69
90
|
// 2b. Lifecycle: Update Transform (Fast Path - no scale recreation)
|
|
70
91
|
$effect(() => {
|
|
71
92
|
const type = this.config.explicitType;
|
|
@@ -148,6 +169,8 @@ export class MarkColor {
|
|
|
148
169
|
let explicitType;
|
|
149
170
|
let constant;
|
|
150
171
|
let exponent;
|
|
172
|
+
let range;
|
|
173
|
+
let pinnedDomain;
|
|
151
174
|
// Handle object wrapper format { field: "species", scheme: "dark2", type: "symlog" }
|
|
152
175
|
if (typeof c === 'object' && 'field' in c && !isNode(c)) {
|
|
153
176
|
field = c.field;
|
|
@@ -155,10 +178,15 @@ export class MarkColor {
|
|
|
155
178
|
explicitType = c.type;
|
|
156
179
|
constant = c.constant;
|
|
157
180
|
exponent = c.exponent;
|
|
181
|
+
range = c.range;
|
|
182
|
+
pinnedDomain = c.domain;
|
|
158
183
|
}
|
|
159
184
|
// 1. Determine Type from field semantics + schema (may return null if waiting for schema)
|
|
160
185
|
// If an explicit type is given (log/symlog/sqrt/pow), use it directly.
|
|
161
|
-
|
|
186
|
+
// Explicit colours or pinned values mean categories, whatever the column
|
|
187
|
+
// type: a 0/1 integer column would otherwise read as continuous.
|
|
188
|
+
const scaleType = explicitType ||
|
|
189
|
+
(range?.length || pinnedDomain?.length ? 'ordinal' : this.#resolveScaleType(field));
|
|
162
190
|
// 2. Pass explicit scheme if provided, otherwise undefined (registry will apply default)
|
|
163
191
|
return {
|
|
164
192
|
mode: 'field',
|
|
@@ -167,7 +195,9 @@ export class MarkColor {
|
|
|
167
195
|
scheme: explicitScheme, // undefined if not specified
|
|
168
196
|
explicitType,
|
|
169
197
|
constant,
|
|
170
|
-
exponent
|
|
198
|
+
exponent,
|
|
199
|
+
range,
|
|
200
|
+
pinnedDomain
|
|
171
201
|
};
|
|
172
202
|
});
|
|
173
203
|
// Separate derived for mode (independent of scheme to avoid unnecessary tracking)
|
|
@@ -12,8 +12,21 @@ import type { MarkSetupState } from './mark-types';
|
|
|
12
12
|
export declare class MarkSeries {
|
|
13
13
|
#private;
|
|
14
14
|
scaleState: ColorScaleState | null;
|
|
15
|
-
constructor(getSeries: () => string | object | null, channelName: string, getSetup: () => MarkSetupState, scaleRegistry: ScaleRegistry, chartContext: ChartContext);
|
|
15
|
+
constructor(getSeries: () => string | object | null, channelName: string, getSetup: () => MarkSetupState, scaleRegistry: ScaleRegistry, chartContext: ChartContext, getColorScale?: () => ColorScaleState | null);
|
|
16
16
|
selectField: string | object | null;
|
|
17
17
|
data: Float32Array | null;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the renderer reads selection activity through the series scale:
|
|
20
|
+
* 1 for a `z` of its own, a field that carries ids, so a selection on it
|
|
21
|
+
* dims the other series. 0 without `z` (the series is the colour, read
|
|
22
|
+
* already), for a `z` on the colour field (the registry hands both
|
|
23
|
+
* channels one scale, and reading it twice is a load for nothing), and
|
|
24
|
+
* before the scale has an identity, while its values are not ids yet.
|
|
25
|
+
* Routing (MarkLifecycleV2's reads) lists the same field in the same
|
|
26
|
+
* cases, so the mark dims rather than filters exactly when this is 1.
|
|
27
|
+
*/
|
|
28
|
+
activityMode: number;
|
|
29
|
+
/** The series scale's colour config index, read when `activityMode` is 1. */
|
|
30
|
+
activityScaleIndex: number;
|
|
18
31
|
ready: boolean;
|
|
19
32
|
}
|
|
@@ -15,17 +15,23 @@ export class MarkSeries {
|
|
|
15
15
|
#setupState;
|
|
16
16
|
#scaleRegistry;
|
|
17
17
|
#chartContext;
|
|
18
|
+
#colorScale;
|
|
18
19
|
scaleState = $state(null);
|
|
19
|
-
constructor(getSeries, channelName, getSetup, scaleRegistry, chartContext) {
|
|
20
|
+
constructor(getSeries, channelName, getSetup, scaleRegistry, chartContext, getColorScale = () => null) {
|
|
20
21
|
this.#seriesProp = getSeries;
|
|
21
22
|
this.#channelName = channelName;
|
|
22
23
|
this.#setupState = getSetup;
|
|
23
24
|
this.#scaleRegistry = scaleRegistry;
|
|
24
25
|
this.#chartContext = chartContext;
|
|
26
|
+
this.#colorScale = getColorScale;
|
|
25
27
|
// Lifecycle: Create/Destroy Scale
|
|
26
28
|
// Series grouping is always ordinal
|
|
27
29
|
$effect(() => {
|
|
28
30
|
const field = this.#field;
|
|
31
|
+
// Again when the chart's scales are replaced: the registry dropped
|
|
32
|
+
// this one with the old ChartScales (its GPU slots may already be
|
|
33
|
+
// someone else's), and holding it would write to a destroyed scale.
|
|
34
|
+
void this.#chartContext.scaleGeneration;
|
|
29
35
|
if (field && this.#scaleRegistry) {
|
|
30
36
|
untrack(() => {
|
|
31
37
|
// Use raw field name - registry deduplicates automatically
|
|
@@ -89,6 +95,24 @@ export class MarkSeries {
|
|
|
89
95
|
}
|
|
90
96
|
return this.scaleState.processDataForGPU(raw);
|
|
91
97
|
});
|
|
98
|
+
/**
|
|
99
|
+
* Whether the renderer reads selection activity through the series scale:
|
|
100
|
+
* 1 for a `z` of its own, a field that carries ids, so a selection on it
|
|
101
|
+
* dims the other series. 0 without `z` (the series is the colour, read
|
|
102
|
+
* already), for a `z` on the colour field (the registry hands both
|
|
103
|
+
* channels one scale, and reading it twice is a load for nothing), and
|
|
104
|
+
* before the scale has an identity, while its values are not ids yet.
|
|
105
|
+
* Routing (MarkLifecycleV2's reads) lists the same field in the same
|
|
106
|
+
* cases, so the mark dims rather than filters exactly when this is 1.
|
|
107
|
+
*/
|
|
108
|
+
activityMode = $derived.by(() => {
|
|
109
|
+
const scale = this.scaleState;
|
|
110
|
+
if (this.#channelName !== 'z' || !scale?.hasIdentity)
|
|
111
|
+
return 0;
|
|
112
|
+
return scale === this.#colorScale() ? 0 : 1;
|
|
113
|
+
});
|
|
114
|
+
/** The series scale's colour config index, read when `activityMode` is 1. */
|
|
115
|
+
activityScaleIndex = $derived(this.scaleState?.gpuIndex ?? 0);
|
|
92
116
|
// Ready when: no field (single series) or scale is ready
|
|
93
117
|
ready = $derived(!this.#field || (this.scaleState?.ready ?? false));
|
|
94
118
|
}
|
|
@@ -18,6 +18,10 @@ export class MarkSize {
|
|
|
18
18
|
$effect(() => {
|
|
19
19
|
const mode = this.#mode;
|
|
20
20
|
const field = this.#field;
|
|
21
|
+
// Again when the chart's scales are replaced: the registry dropped
|
|
22
|
+
// this one with the old ChartScales (its GPU slots may already be
|
|
23
|
+
// someone else's), and holding it would write to a destroyed scale.
|
|
24
|
+
void this.#chartContext.scaleGeneration;
|
|
21
25
|
if (mode === 'field' && field && this.#scaleRegistry) {
|
|
22
26
|
untrack(() => {
|
|
23
27
|
this.scaleState = this.#scaleRegistry.getOrCreateScale(field, 'size', { range: this.#rangeProp() }, this.#chartContext.chartId);
|
|
@@ -12,6 +12,6 @@ import type { MarkEntry } from '../../../config/ChartConfig.svelte';
|
|
|
12
12
|
* @param entry - The mark entry (reactive props from ChartConfig)
|
|
13
13
|
* @param opts.resolveFitDomain - Getter for resolved fitDomain (deferred read)
|
|
14
14
|
*/
|
|
15
|
-
export declare function buildReactiveConfig(entry: Partial<Pick<MarkEntry, 'sort' | 'limit' | 'domain' | 'tooltip' | 'tip' | 'fitDomain' | 'selectionResponse'>>, opts?: {
|
|
15
|
+
export declare function buildReactiveConfig(entry: Partial<Pick<MarkEntry, 'sort' | 'limit' | 'domain' | 'tooltip' | 'tip' | 'fitDomain' | 'selectionResponse' | 'nulls' | 'contributeDomain' | 'holdsEntrance'>>, opts?: {
|
|
16
16
|
resolveFitDomain?: () => 'x' | 'y' | boolean | undefined;
|
|
17
17
|
}): ReactiveConfig;
|
|
@@ -19,6 +19,9 @@ export function buildReactiveConfig(entry, opts) {
|
|
|
19
19
|
get limit() {
|
|
20
20
|
return entry.limit;
|
|
21
21
|
},
|
|
22
|
+
get holdsEntrance() {
|
|
23
|
+
return entry.holdsEntrance;
|
|
24
|
+
},
|
|
22
25
|
get domain() {
|
|
23
26
|
return entry.domain ?? null;
|
|
24
27
|
},
|
|
@@ -33,6 +36,12 @@ export function buildReactiveConfig(entry, opts) {
|
|
|
33
36
|
},
|
|
34
37
|
get selectionResponse() {
|
|
35
38
|
return entry.selectionResponse;
|
|
39
|
+
},
|
|
40
|
+
get nulls() {
|
|
41
|
+
return entry.nulls;
|
|
42
|
+
},
|
|
43
|
+
get contributeDomain() {
|
|
44
|
+
return entry.contributeDomain;
|
|
36
45
|
}
|
|
37
46
|
};
|
|
38
47
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { OtherRowStats } from '../lifecycle/other-rows.svelte';
|
|
1
2
|
import type { ChartContext } from '../../../types';
|
|
2
3
|
import type { ColorScaleState } from '../../../scales/ColorScaleState.svelte';
|
|
3
4
|
import type { DimValue } from '../../../query-engine/categorical-identity';
|
|
@@ -34,6 +35,7 @@ interface TooltipConfigParams {
|
|
|
34
35
|
constantRadius?: number;
|
|
35
36
|
sizeScaleState?: SizeScaleState | null;
|
|
36
37
|
displayFields?: string[] | null;
|
|
38
|
+
otherStats?: TooltipConfig['otherStats'];
|
|
37
39
|
}
|
|
38
40
|
interface TooltipConfig {
|
|
39
41
|
tempTable: string;
|
|
@@ -61,7 +63,8 @@ interface TooltipConfig {
|
|
|
61
63
|
constantRadius?: number;
|
|
62
64
|
sizeScaleState?: SizeScaleState | null;
|
|
63
65
|
displayFields?: string[] | null;
|
|
66
|
+
otherStats?: (point: Record<string, unknown>) => OtherRowStats | null;
|
|
64
67
|
enabled: boolean;
|
|
65
68
|
}
|
|
66
|
-
export declare function buildTooltipConfig({ tempTable, markType, chartContext, channelFields, columns, bandAxes, colorColumn, markColor, seriesColumn, markSeries, proximityAxis, constantRadius, sizeScaleState, displayFields }: TooltipConfigParams): TooltipConfig;
|
|
69
|
+
export declare function buildTooltipConfig({ tempTable, markType, chartContext, channelFields, columns, bandAxes, colorColumn, markColor, seriesColumn, markSeries, proximityAxis, constantRadius, sizeScaleState, displayFields, otherStats }: TooltipConfigParams): TooltipConfig;
|
|
67
70
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function buildTooltipConfig({ tempTable, markType, chartContext, channelFields, columns, bandAxes, colorColumn, markColor, seriesColumn, markSeries, proximityAxis, constantRadius, sizeScaleState, displayFields }) {
|
|
1
|
+
export function buildTooltipConfig({ tempTable, markType, chartContext, channelFields, columns, bandAxes, colorColumn, markColor, seriesColumn, markSeries, proximityAxis, constantRadius, sizeScaleState, displayFields, otherStats }) {
|
|
2
2
|
const colorScaleState = markColor.scaleState;
|
|
3
3
|
const constantColor = markColor.constantCssColor;
|
|
4
4
|
return {
|
|
@@ -33,6 +33,7 @@ export function buildTooltipConfig({ tempTable, markType, chartContext, channelF
|
|
|
33
33
|
...(constantRadius != null && { constantRadius }),
|
|
34
34
|
...(sizeScaleState && { sizeScaleState }),
|
|
35
35
|
...(displayFields && { displayFields }),
|
|
36
|
+
...(otherStats && { otherStats }),
|
|
36
37
|
enabled: true
|
|
37
38
|
};
|
|
38
39
|
}
|