@fjader/svelte 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/components/axes/AxisBottom.svelte +23 -6
- package/dist/components/axes/AxisBottom.svelte.d.ts +10 -0
- package/dist/components/axes/AxisLeft.svelte +77 -8
- package/dist/components/axes/AxisLeft.svelte.d.ts +17 -0
- package/dist/components/axes/BandAxisTicks.svelte +64 -25
- package/dist/components/axes/BandAxisTicks.svelte.d.ts +5 -5
- package/dist/components/axes/axis-title.d.ts +30 -0
- package/dist/components/axes/axis-title.js +77 -0
- package/dist/components/charts/ChartCell.svelte +1 -1
- package/dist/components/charts/ChartContainer.svelte +12 -3
- package/dist/components/charts/ChartContainer.svelte.d.ts +7 -0
- package/dist/components/guides/legend/ContinuousColorLegend.svelte +9 -2
- package/dist/components/guides/legend/OrdinalColorLegend.svelte +96 -35
- package/dist/components/marks/AreaMarkX.svelte +21 -6
- package/dist/components/marks/AreaMarkY.svelte +21 -6
- package/dist/components/marks/BarMarkX.svelte +16 -1
- package/dist/components/marks/BarMarkY.svelte +16 -1
- package/dist/components/marks/DotsMark.svelte +5 -0
- package/dist/components/marks/LineMark.svelte +8 -1
- package/dist/components/marks/ParallelCoordinatesMark.svelte +10 -0
- package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +5 -0
- package/dist/components/marks/RectMark.svelte +5 -0
- package/dist/components/marks/RectXMark.svelte +5 -0
- package/dist/components/marks/RectYMark.svelte +5 -0
- package/dist/components/marks/RuleMarkX.svelte +12 -2
- package/dist/components/marks/RuleMarkY.svelte +12 -2
- package/dist/components/marks/TextMark.svelte +76 -13
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +21 -2
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +300 -42
- package/dist/components/marks/lifecycle/domain-queries.d.ts +3 -1
- package/dist/components/marks/lifecycle/domain-queries.js +22 -15
- package/dist/components/marks/lifecycle/domain-registration.js +2 -6
- package/dist/components/marks/lifecycle/other-rows.svelte.d.ts +104 -0
- package/dist/components/marks/lifecycle/other-rows.svelte.js +193 -0
- package/dist/components/marks/lifecycle/other-split.d.ts +80 -0
- package/dist/components/marks/lifecycle/other-split.js +112 -0
- package/dist/components/marks/lifecycle/query-builder.d.ts +26 -1
- package/dist/components/marks/lifecycle/query-builder.js +25 -5
- package/dist/components/marks/lifecycle/transform-scale-key.d.ts +16 -0
- package/dist/components/marks/lifecycle/transform-scale-key.js +27 -0
- package/dist/components/marks/utils/area-outline.d.ts +24 -0
- package/dist/components/marks/utils/area-outline.js +10 -0
- package/dist/components/marks/utils/reactive-config.d.ts +1 -1
- package/dist/components/marks/utils/reactive-config.js +9 -0
- package/dist/components/marks/utils/tooltip-helpers.d.ts +4 -1
- package/dist/components/marks/utils/tooltip-helpers.js +2 -1
- package/dist/components/rendering/Areas.svelte +30 -6
- package/dist/components/rendering/Areas.svelte.d.ts +2 -0
- package/dist/components/rendering/BaseGPURenderer.svelte.d.ts +3 -1
- package/dist/components/rendering/BaseGPURenderer.svelte.js +8 -3
- package/dist/components/rendering/Dots.svelte +24 -3
- package/dist/components/rendering/Dots.svelte.d.ts +2 -0
- package/dist/components/rendering/GridLine.svelte +2 -1
- package/dist/components/rendering/Lines.svelte +49 -7
- package/dist/components/rendering/Lines.svelte.d.ts +9 -0
- package/dist/components/rendering/ParallelLines.svelte +26 -7
- package/dist/components/rendering/ParallelLines.svelte.d.ts +2 -0
- package/dist/components/rendering/Rects.svelte +129 -20
- package/dist/components/rendering/Rects.svelte.d.ts +9 -0
- package/dist/components/rendering/RuleX.svelte +20 -5
- package/dist/components/rendering/RuleX.svelte.d.ts +2 -0
- package/dist/components/rendering/RuleY.svelte +20 -5
- package/dist/components/rendering/RuleY.svelte.d.ts +2 -0
- package/dist/components/rendering/activity-reads.d.ts +111 -0
- package/dist/components/rendering/activity-reads.js +154 -0
- package/dist/components/rendering/uniform-sizes.d.ts +26 -0
- package/dist/components/rendering/uniform-sizes.js +28 -0
- package/dist/config/ChartConfig.svelte.d.ts +5 -1
- package/dist/gpu/ColorScaleShaderLib.d.ts +13 -1
- package/dist/gpu/ColorScaleShaderLib.js +56 -5
- package/dist/gpu/GPUCoordinator.svelte.d.ts +8 -0
- package/dist/gpu/GPUCoordinator.svelte.js +68 -32
- package/dist/gpu/MorphedChannels.d.ts +6 -32
- package/dist/gpu/MorphedChannels.js +45 -3
- package/dist/gpu/ResolveShader.d.ts +2 -2
- package/dist/gpu/ResolveShader.js +2 -2
- package/dist/gpu/ScaleTemplateSystem.d.ts +19 -0
- package/dist/gpu/ScaleTemplateSystem.js +111 -11
- package/dist/gpu/SizeScaleShaderLib.d.ts +4 -4
- package/dist/gpu/SizeScaleShaderLib.js +4 -4
- package/dist/gpu/identity-slot-packing.d.ts +157 -40
- package/dist/gpu/identity-slot-packing.js +262 -62
- package/dist/gpu/tracked-scales.d.ts +14 -0
- package/dist/gpu/tracked-scales.js +18 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/interactors/BandLabelToggle.svelte +5 -9
- package/dist/interactors/BandLabelToggle.svelte.d.ts +3 -3
- package/dist/interactors/DerivedInterval1D.svelte +26 -54
- package/dist/interactors/Interval1D.svelte +8 -2
- package/dist/interactors/Interval2D.svelte +8 -2
- package/dist/interactors/ParallelAxisSource.svelte +14 -5
- package/dist/interactors/ParallelAxisSource.svelte.d.ts +2 -1
- package/dist/interactors/ParallelBrushes.svelte +8 -9
- package/dist/interactors/ParallelBrushes.svelte.d.ts +3 -3
- package/dist/interactors/Toggle.svelte +48 -38
- package/dist/interactors/Toggle.svelte.d.ts +4 -3
- package/dist/interactors/Tooltip.svelte +140 -42
- package/dist/interactors/TooltipManager.svelte.d.ts +20 -5
- package/dist/interactors/TooltipManager.svelte.js +40 -10
- package/dist/interactors/other-tooltip.d.ts +16 -0
- package/dist/interactors/other-tooltip.js +22 -0
- package/dist/interactors/relay-clauses.d.ts +23 -0
- package/dist/interactors/relay-clauses.js +60 -0
- package/dist/query-engine/CrossfilterSelection.svelte.d.ts +88 -15
- package/dist/query-engine/CrossfilterSelection.svelte.js +94 -20
- package/dist/query-engine/FieldActivity.svelte.d.ts +128 -0
- package/dist/query-engine/FieldActivity.svelte.js +378 -0
- package/dist/query-engine/FilterGroup.svelte.d.ts +76 -30
- package/dist/query-engine/FilterGroup.svelte.js +223 -124
- package/dist/query-engine/FilterGroupRegistry.svelte.d.ts +73 -3
- package/dist/query-engine/FilterGroupRegistry.svelte.js +161 -12
- package/dist/query-engine/MarkHandle.svelte.d.ts +9 -1
- package/dist/query-engine/MarkHandle.svelte.js +12 -2
- package/dist/query-engine/QueryEngine.d.ts +52 -16
- package/dist/query-engine/QueryEngine.js +116 -34
- package/dist/query-engine/categorical-identity.d.ts +114 -27
- package/dist/query-engine/categorical-identity.js +225 -51
- package/dist/query-engine/index.d.ts +2 -1
- package/dist/query-engine/index.js +1 -0
- package/dist/query-engine/preagg/Preagg.d.ts +1 -1
- package/dist/query-engine/preagg/Preagg.js +11 -9
- package/dist/query-engine/preagg/plan.d.ts +14 -5
- package/dist/query-engine/preagg/plan.js +44 -18
- package/dist/query-engine/toggle-predicate.d.ts +21 -0
- package/dist/query-engine/toggle-predicate.js +41 -2
- package/dist/scales/ActivityTable.d.ts +103 -0
- package/dist/scales/ActivityTable.js +220 -0
- package/dist/scales/BandScaleState.svelte.d.ts +41 -15
- package/dist/scales/BandScaleState.svelte.js +78 -36
- package/dist/scales/ChartScales.svelte.d.ts +2 -2
- package/dist/scales/ColorScaleState.svelte.d.ts +36 -8
- package/dist/scales/ColorScaleState.svelte.js +118 -24
- package/dist/scales/DomainResolver.d.ts +2 -0
- package/dist/scales/DomainResolver.js +15 -8
- package/dist/scales/ScaleRegistry.svelte.d.ts +9 -0
- package/dist/scales/ScaleRegistry.svelte.js +22 -1
- package/dist/scales/TimeScaleState.svelte.js +7 -1
- package/dist/spec/index.js +1 -0
- package/dist/spec/types.d.ts +4 -0
- package/dist/theme.css +5 -4
- package/dist/transforms/TransformCompiler.d.ts +37 -2
- package/dist/transforms/TransformCompiler.js +44 -1
- package/dist/transforms/index.d.ts +27 -0
- package/dist/transforms/index.js +127 -19
- package/dist/types.d.ts +71 -0
- package/package.json +3 -2
|
@@ -9,6 +9,7 @@ import { BandScaleState } from './BandScaleState.svelte';
|
|
|
9
9
|
import { ColorScaleState } from './ColorScaleState.svelte';
|
|
10
10
|
import { SizeScaleState } from './SizeScaleState.svelte';
|
|
11
11
|
import { scaleOrdinal } from 'd3-scale';
|
|
12
|
+
import { MAX_COLOR_SLOTS } from '../gpu/ColorScaleShaderLib';
|
|
12
13
|
import { isTransformType } from './color-utils';
|
|
13
14
|
// Helper to stringify sorted
|
|
14
15
|
const stableStringify = (obj) => {
|
|
@@ -50,7 +51,8 @@ export class ScaleRegistry {
|
|
|
50
51
|
#nextColorSlot = 0;
|
|
51
52
|
#nextRampSlot = 0;
|
|
52
53
|
#nextPaletteSlot = 0;
|
|
53
|
-
|
|
54
|
+
// The shader's array length and the coordinator's buffer size, stated once.
|
|
55
|
+
#maxColorSlots = MAX_COLOR_SLOTS;
|
|
54
56
|
#maxRampSlots = 64;
|
|
55
57
|
#maxPaletteSlots = 64;
|
|
56
58
|
// Size scale slot management
|
|
@@ -348,6 +350,25 @@ export class ScaleRegistry {
|
|
|
348
350
|
chartsUsing(scale) {
|
|
349
351
|
return this.#chartsFor.get(scale) ?? NO_CHARTS;
|
|
350
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* Fill `out` with every chart drawing a scale on the categorical field
|
|
355
|
+
* `key` (by identity key), each once: the charts a selection fade on that
|
|
356
|
+
* field redraws. A superset of the charts whose marks read the field's
|
|
357
|
+
* activity, since every such mark draws through a scale on it; the extra
|
|
358
|
+
* charts (other groups, marks holding the scale without reading it) redraw
|
|
359
|
+
* for the 0.25 s of a fade. Read on every frame a fade runs.
|
|
360
|
+
*/
|
|
361
|
+
chartsDrawingField(key, out) {
|
|
362
|
+
for (const [scale, charts] of this.#chartsFor) {
|
|
363
|
+
if (scale.identityKey !== key)
|
|
364
|
+
continue;
|
|
365
|
+
for (let i = 0; i < charts.length; i++) {
|
|
366
|
+
if (!out.includes(charts[i]))
|
|
367
|
+
out.push(charts[i]);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return out;
|
|
371
|
+
}
|
|
351
372
|
/** Re-derive one scale's redraw list. The only writer. */
|
|
352
373
|
#publishCharts(scale, chartIds) {
|
|
353
374
|
this.#chartsFor.set(scale, Array.from(chartIds));
|
|
@@ -20,7 +20,13 @@ export class TimeScaleState extends LinearScaleState {
|
|
|
20
20
|
return 'time';
|
|
21
21
|
}
|
|
22
22
|
// --- Time Metadata ---
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Origin Date (raw global min datetime). State, because SQL is built from
|
|
25
|
+
* it: a mark's offset select and a brush's predicate are $derived, and one
|
|
26
|
+
* computed before the first datetime extent landed must rebuild when it
|
|
27
|
+
* does, or it keeps comparing the raw column with offsets.
|
|
28
|
+
*/
|
|
29
|
+
#t0 = $state.raw(null);
|
|
24
30
|
#unit = 1; // Seconds per offset unit (1 = seconds)
|
|
25
31
|
// --- Datetime Extent Tracking ---
|
|
26
32
|
#datetimeExtents = new Map(); // Map<markId, [minDate, maxDate]>
|
package/dist/spec/index.js
CHANGED
|
@@ -47,6 +47,7 @@ export async function render(target, spec, options = {}) {
|
|
|
47
47
|
// 4. Build context map — injected into Svelte mount() so child components
|
|
48
48
|
// find these via getContext() calls (same keys as initFjader())
|
|
49
49
|
const { filterGroups } = createQueryServices(queryEngine);
|
|
50
|
+
gpuCoordinator.setFilterGroups(filterGroups);
|
|
50
51
|
const context = new Map();
|
|
51
52
|
context.set('query-engine', queryEngine);
|
|
52
53
|
context.set('filter-groups', filterGroups);
|
package/dist/spec/types.d.ts
CHANGED
|
@@ -133,6 +133,10 @@ export type MarkSpec = DotMarkSpec | BarXMarkSpec | BarYMarkSpec | LineMarkSpec
|
|
|
133
133
|
interface BaseMarkSpec {
|
|
134
134
|
table: string;
|
|
135
135
|
selection?: string;
|
|
136
|
+
/** How the mark responds to selections in its group; see BaseMarkProps. Default 'auto'. */
|
|
137
|
+
selectionResponse?: 'auto' | 'filter';
|
|
138
|
+
/** What the mark does with NULL in its categorical channels; see BaseMarkProps. Default 'keep'. */
|
|
139
|
+
nulls?: 'keep' | 'drop';
|
|
136
140
|
scheme?: ColorScheme;
|
|
137
141
|
limit?: number;
|
|
138
142
|
transforms?: TransformSpec[];
|
package/dist/theme.css
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
* Or copy and customize the variables below in your own CSS.
|
|
8
8
|
* All variables are optional — Fjader components have sensible
|
|
9
9
|
* inline fallbacks and work without any CSS imports.
|
|
10
|
+
*
|
|
11
|
+
* The tooltip is not set here: it follows the color-scheme it inherits (the
|
|
12
|
+
* one below, or the app's own) by itself, in contrast or follow mode
|
|
13
|
+
* (data-fjader-tooltip on any ancestor, or ChartContainer's tooltipScheme).
|
|
14
|
+
* Set --fjader-tooltip-bg and --fjader-tooltip-text to override both modes.
|
|
10
15
|
*/
|
|
11
16
|
|
|
12
17
|
:where(:root) {
|
|
@@ -16,8 +21,6 @@
|
|
|
16
21
|
--fjader-bg: #ffffff;
|
|
17
22
|
--fjader-primary: #3b82f6;
|
|
18
23
|
--fjader-accent: #93c5fd;
|
|
19
|
-
--fjader-tooltip-bg: rgba(0, 0, 0, 0.88);
|
|
20
|
-
--fjader-tooltip-text: #ffffff;
|
|
21
24
|
--fjader-palette-bg: rgba(0, 0, 0, 0.92);
|
|
22
25
|
--fjader-palette-text: #ffffff;
|
|
23
26
|
--fjader-palette-text-secondary: rgba(255, 255, 255, 0.5);
|
|
@@ -34,8 +37,6 @@
|
|
|
34
37
|
--fjader-bg: #111827;
|
|
35
38
|
--fjader-primary: #60a5fa;
|
|
36
39
|
--fjader-accent: #3b82f6;
|
|
37
|
-
--fjader-tooltip-bg: rgba(255, 255, 255, 0.92);
|
|
38
|
-
--fjader-tooltip-text: #111827;
|
|
39
40
|
--fjader-palette-bg: rgba(255, 255, 255, 0.95);
|
|
40
41
|
--fjader-palette-text: #111827;
|
|
41
42
|
--fjader-palette-text-secondary: rgba(17, 24, 39, 0.5);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SelectQuery } from '@uwdata/mosaic-sql';
|
|
1
|
+
import type { ExprNode, SelectQuery } from '@uwdata/mosaic-sql';
|
|
2
2
|
import type { Transform, SqlTransform, JsTransform, MarkConfig, ScaleMap } from '../types';
|
|
3
3
|
import type { DataAccessor } from '../data/DataAccessor';
|
|
4
4
|
export declare class TransformCompiler {
|
|
@@ -19,7 +19,40 @@ export declare class TransformCompiler {
|
|
|
19
19
|
* than of the mark — its GROUP BY keys, say — merged over the mark config for
|
|
20
20
|
* this call only, so the shared config object is never written per query.
|
|
21
21
|
*/
|
|
22
|
-
applyToQuery(query: SelectQuery, facts?: Partial<MarkConfig
|
|
22
|
+
applyToQuery(query: SelectQuery, facts?: Partial<MarkConfig>,
|
|
23
|
+
/** The row filters are already in the table the query reads (a preagg table). */
|
|
24
|
+
options?: {
|
|
25
|
+
skipRowFilters?: boolean;
|
|
26
|
+
}): SelectQuery;
|
|
27
|
+
/**
|
|
28
|
+
* Whether a SQL transform filters on the groups' aggregates (`having`). Its
|
|
29
|
+
* condition names aggregates over the source rows, which a pre-aggregated table
|
|
30
|
+
* would recompute over its own groups: `count(*)` counts the table's groups, not
|
|
31
|
+
* the rows, so it gives a different answer with no error.
|
|
32
|
+
*
|
|
33
|
+
* Only the user-facing `having()` does this. The built-ins (topN, rank, stack,
|
|
34
|
+
* normalize, dense) add no HAVING: each wraps the grouped query in a subquery
|
|
35
|
+
* (`Query.from({ __topn_base: query })`) and adds window functions or joins, and
|
|
36
|
+
* those work the same on a preagg table's aggregated result, which is why a topN
|
|
37
|
+
* mark is served from preagg. The only CTE is assembleQuery's window wrapper for
|
|
38
|
+
* window aggregates, applied after the transforms. A test in transforms.test.ts
|
|
39
|
+
* fails if a built-in starts to emit a HAVING; this then has to know it.
|
|
40
|
+
*
|
|
41
|
+
* Supporting `having()` on a preagg table would mean rewriting its condition the
|
|
42
|
+
* way a select channel is rewritten (sufficient-statistics: collectAggregates,
|
|
43
|
+
* sufficientStats, rewrite; an aggregate not in the select adds a partial
|
|
44
|
+
* column) and applying it to the inner grouped query BEFORE the transforms wrap
|
|
45
|
+
* it, so it ends up inside their subquery as it does on the base table.
|
|
46
|
+
*/
|
|
47
|
+
hasHaving(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The row predicates of the SQL transforms (`filter`), for a pre-aggregated
|
|
50
|
+
* table to be built with: its read cannot apply them (see SqlTransform.where).
|
|
51
|
+
* A row filter applies before grouping, so building the table with it gives the
|
|
52
|
+
* same rows the base query sees; only `filter()` is one (see hasHaving for the
|
|
53
|
+
* transforms that are not).
|
|
54
|
+
*/
|
|
55
|
+
rowFilters(): ExprNode[];
|
|
23
56
|
/**
|
|
24
57
|
* Which channels a query with these aliases comes back aligned on, leading
|
|
25
58
|
* term first, or null. Asked with the same facts as `applyToQuery`, so the
|
|
@@ -60,5 +93,7 @@ export declare class TransformCompiler {
|
|
|
60
93
|
* before the transform can write it.
|
|
61
94
|
*/
|
|
62
95
|
hasSyntheticOtherBucket(): boolean;
|
|
96
|
+
/** The `by` a topN Other bucket ranks by when stated explicitly, else null (its default). */
|
|
97
|
+
otherBy(): string | null;
|
|
63
98
|
getCategoryTransformKey(): string | null;
|
|
64
99
|
}
|
|
@@ -35,10 +35,14 @@ export class TransformCompiler {
|
|
|
35
35
|
* than of the mark — its GROUP BY keys, say — merged over the mark config for
|
|
36
36
|
* this call only, so the shared config object is never written per query.
|
|
37
37
|
*/
|
|
38
|
-
applyToQuery(query, facts
|
|
38
|
+
applyToQuery(query, facts,
|
|
39
|
+
/** The row filters are already in the table the query reads (a preagg table). */
|
|
40
|
+
options) {
|
|
39
41
|
let result = query;
|
|
40
42
|
const config = this.#configFor(facts);
|
|
41
43
|
for (const transform of this.sqlTransforms) {
|
|
44
|
+
if (options?.skipRowFilters && transform.where !== undefined)
|
|
45
|
+
continue;
|
|
42
46
|
try {
|
|
43
47
|
result = transform.apply(result, config);
|
|
44
48
|
}
|
|
@@ -48,6 +52,39 @@ export class TransformCompiler {
|
|
|
48
52
|
}
|
|
49
53
|
return result;
|
|
50
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Whether a SQL transform filters on the groups' aggregates (`having`). Its
|
|
57
|
+
* condition names aggregates over the source rows, which a pre-aggregated table
|
|
58
|
+
* would recompute over its own groups: `count(*)` counts the table's groups, not
|
|
59
|
+
* the rows, so it gives a different answer with no error.
|
|
60
|
+
*
|
|
61
|
+
* Only the user-facing `having()` does this. The built-ins (topN, rank, stack,
|
|
62
|
+
* normalize, dense) add no HAVING: each wraps the grouped query in a subquery
|
|
63
|
+
* (`Query.from({ __topn_base: query })`) and adds window functions or joins, and
|
|
64
|
+
* those work the same on a preagg table's aggregated result, which is why a topN
|
|
65
|
+
* mark is served from preagg. The only CTE is assembleQuery's window wrapper for
|
|
66
|
+
* window aggregates, applied after the transforms. A test in transforms.test.ts
|
|
67
|
+
* fails if a built-in starts to emit a HAVING; this then has to know it.
|
|
68
|
+
*
|
|
69
|
+
* Supporting `having()` on a preagg table would mean rewriting its condition the
|
|
70
|
+
* way a select channel is rewritten (sufficient-statistics: collectAggregates,
|
|
71
|
+
* sufficientStats, rewrite; an aggregate not in the select adds a partial
|
|
72
|
+
* column) and applying it to the inner grouped query BEFORE the transforms wrap
|
|
73
|
+
* it, so it ends up inside their subquery as it does on the base table.
|
|
74
|
+
*/
|
|
75
|
+
hasHaving() {
|
|
76
|
+
return this.sqlTransforms.some((t) => t.type === 'having');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The row predicates of the SQL transforms (`filter`), for a pre-aggregated
|
|
80
|
+
* table to be built with: its read cannot apply them (see SqlTransform.where).
|
|
81
|
+
* A row filter applies before grouping, so building the table with it gives the
|
|
82
|
+
* same rows the base query sees; only `filter()` is one (see hasHaving for the
|
|
83
|
+
* transforms that are not).
|
|
84
|
+
*/
|
|
85
|
+
rowFilters() {
|
|
86
|
+
return this.sqlTransforms.flatMap((t) => (t.where === undefined ? [] : [t.where]));
|
|
87
|
+
}
|
|
51
88
|
/**
|
|
52
89
|
* Which channels a query with these aliases comes back aligned on, leading
|
|
53
90
|
* term first, or null. Asked with the same facts as `applyToQuery`, so the
|
|
@@ -147,6 +184,12 @@ export class TransformCompiler {
|
|
|
147
184
|
hasSyntheticOtherBucket() {
|
|
148
185
|
return this.transforms.some((t) => t.type === 'topN' && t.spec?.includeOthers === true);
|
|
149
186
|
}
|
|
187
|
+
/** The `by` a topN Other bucket ranks by when stated explicitly, else null (its default). */
|
|
188
|
+
otherBy() {
|
|
189
|
+
const t = this.transforms.find((t) => t.type === 'topN' && t.spec?.includeOthers === true);
|
|
190
|
+
const by = t?.spec?.by;
|
|
191
|
+
return typeof by === 'string' ? by : null;
|
|
192
|
+
}
|
|
150
193
|
getCategoryTransformKey() {
|
|
151
194
|
const parts = [];
|
|
152
195
|
for (const t of this.transforms) {
|
|
@@ -45,6 +45,33 @@ export declare function topN(n: number | (() => number), options?: TopNOptions):
|
|
|
45
45
|
* the identity index columns in the same result.
|
|
46
46
|
*/
|
|
47
47
|
export declare const RANK_COLUMN = "__dplot_rank";
|
|
48
|
+
/**
|
|
49
|
+
* The Other row's members: the ids of the categories folded into it, and each
|
|
50
|
+
* one's measure, as two aligned lists ordered by id. NULL on every other row.
|
|
51
|
+
*
|
|
52
|
+
* Returned by the query rather than worked out on the client, because only the
|
|
53
|
+
* query knows exactly what Other summed: under every filter the chart receives,
|
|
54
|
+
* NULL (`-1`) included when it was folded. Clicking Other selects these, and
|
|
55
|
+
* its selected share is computed from them.
|
|
56
|
+
*
|
|
57
|
+
* `__dplot_`-prefixed, like the rank, so tooltips never show them; and the SQL
|
|
58
|
+
* tooltip table leaves out every `OTHER_MEMBER_PREFIX` column, so a hover never
|
|
59
|
+
* ships them back.
|
|
60
|
+
*/
|
|
61
|
+
export declare const OTHER_MEMBER_PREFIX = "__dplot_other_";
|
|
62
|
+
export declare const OTHER_IDS_COLUMN = "__dplot_other_ids";
|
|
63
|
+
export declare const OTHER_VALUES_COLUMN = "__dplot_other_values";
|
|
64
|
+
/**
|
|
65
|
+
* `sql` without Other's member lists, for tables queried on every hover. A
|
|
66
|
+
* COLUMNS filter rather than EXCLUDE, which errors on a column that is absent,
|
|
67
|
+
* as it is on every mark without an Other bucket.
|
|
68
|
+
*
|
|
69
|
+
* The `lambda c:` form, not `c ->`: DuckDB deprecated the arrow lambda in 1.3,
|
|
70
|
+
* and every SQL tooltip table goes through this, so an upgrade dropping it would
|
|
71
|
+
* break tooltips on every mark. Both DuckDB-WASM 1.30 (DuckDB 1.3.2) and the
|
|
72
|
+
* DuckDB the tests run accept it.
|
|
73
|
+
*/
|
|
74
|
+
export declare function withoutOtherMembers(sql: string): string;
|
|
48
75
|
/**
|
|
49
76
|
* Most padded rows `dense()` will produce before refusing.
|
|
50
77
|
*
|
package/dist/transforms/index.js
CHANGED
|
@@ -14,6 +14,7 @@ export function filter(condition) {
|
|
|
14
14
|
stage: 'sql',
|
|
15
15
|
type: 'filter',
|
|
16
16
|
spec: { transform: 'filter', condition: condition.toString() },
|
|
17
|
+
where: condition,
|
|
17
18
|
apply(query) {
|
|
18
19
|
return query.where(condition);
|
|
19
20
|
},
|
|
@@ -135,6 +136,37 @@ function resolveTopNColumns(aliases, options, config) {
|
|
|
135
136
|
* the identity index columns in the same result.
|
|
136
137
|
*/
|
|
137
138
|
export const RANK_COLUMN = '__dplot_rank';
|
|
139
|
+
/**
|
|
140
|
+
* The Other row's members: the ids of the categories folded into it, and each
|
|
141
|
+
* one's measure, as two aligned lists ordered by id. NULL on every other row.
|
|
142
|
+
*
|
|
143
|
+
* Returned by the query rather than worked out on the client, because only the
|
|
144
|
+
* query knows exactly what Other summed: under every filter the chart receives,
|
|
145
|
+
* NULL (`-1`) included when it was folded. Clicking Other selects these, and
|
|
146
|
+
* its selected share is computed from them.
|
|
147
|
+
*
|
|
148
|
+
* `__dplot_`-prefixed, like the rank, so tooltips never show them; and the SQL
|
|
149
|
+
* tooltip table leaves out every `OTHER_MEMBER_PREFIX` column, so a hover never
|
|
150
|
+
* ships them back.
|
|
151
|
+
*/
|
|
152
|
+
export const OTHER_MEMBER_PREFIX = '__dplot_other_';
|
|
153
|
+
export const OTHER_IDS_COLUMN = `${OTHER_MEMBER_PREFIX}ids`;
|
|
154
|
+
export const OTHER_VALUES_COLUMN = `${OTHER_MEMBER_PREFIX}values`;
|
|
155
|
+
/**
|
|
156
|
+
* `sql` without Other's member lists, for tables queried on every hover. A
|
|
157
|
+
* COLUMNS filter rather than EXCLUDE, which errors on a column that is absent,
|
|
158
|
+
* as it is on every mark without an Other bucket.
|
|
159
|
+
*
|
|
160
|
+
* The `lambda c:` form, not `c ->`: DuckDB deprecated the arrow lambda in 1.3,
|
|
161
|
+
* and every SQL tooltip table goes through this, so an upgrade dropping it would
|
|
162
|
+
* break tooltips on every mark. Both DuckDB-WASM 1.30 (DuckDB 1.3.2) and the
|
|
163
|
+
* DuckDB the tests run accept it.
|
|
164
|
+
*/
|
|
165
|
+
export function withoutOtherMembers(sql) {
|
|
166
|
+
return `SELECT COLUMNS(lambda c: NOT starts_with(c, '${OTHER_MEMBER_PREFIX}')) FROM (${sql})`;
|
|
167
|
+
}
|
|
168
|
+
/** The original band id, carried through bucketing, which rewrites the band column. */
|
|
169
|
+
const BAND_SOURCE_COLUMN = '__dplot_band_src';
|
|
138
170
|
/**
|
|
139
171
|
* Give every category in the dim a row, at zero past the cutoff.
|
|
140
172
|
*
|
|
@@ -189,6 +221,12 @@ function densify(inner, aliases, plan) {
|
|
|
189
221
|
// missing (category, series) pair carries that series' real id.
|
|
190
222
|
select[alias] = rawSql(`${crossed}."id"`);
|
|
191
223
|
}
|
|
224
|
+
else if (alias.startsWith(OTHER_MEMBER_PREFIX)) {
|
|
225
|
+
// Other's member lists: NULL on a padded row, as on every row but
|
|
226
|
+
// Other's. Never COALESCEd to 0 like a measure, which is a type error on
|
|
227
|
+
// a list.
|
|
228
|
+
select[alias] = rawSql(`a.${column(alias)}`);
|
|
229
|
+
}
|
|
192
230
|
else if (plan.identity[alias]?.dim === dim) {
|
|
193
231
|
// Same dim as the band, so this channel IS the category — the ordinary
|
|
194
232
|
// "colour the bars by their own category" chart. Its value for a row
|
|
@@ -235,13 +273,13 @@ function densify(inner, aliases, plan) {
|
|
|
235
273
|
// a missing category. Each key's ids get -1 appended: a row whose series is
|
|
236
274
|
// NULL carries -1, and a join over the dim's ids alone would drop it.
|
|
237
275
|
const joins = cross
|
|
238
|
-
.map(([, keyDim], i) => ` CROSS JOIN (SELECT "id" FROM ${
|
|
276
|
+
.map(([key, keyDim], i) => ` CROSS JOIN (SELECT "id" FROM ${padIds(plan.identity[key], keyDim)} UNION ALL SELECT -1) AS k${i}`)
|
|
239
277
|
.join('');
|
|
240
278
|
const on = [
|
|
241
279
|
`a.${column(band)} = d."id"`,
|
|
242
280
|
...cross.map(([key], i) => `a.${column(key)} = k${i}."id"`)
|
|
243
281
|
].join(' AND ');
|
|
244
|
-
return Query.from(rawSql(`${
|
|
282
|
+
return Query.from(rawSql(`${padIds(plan.identity[band], dim)} AS d${joins} LEFT JOIN (${inner}) AS a ON ${on}`)).select(select);
|
|
245
283
|
}
|
|
246
284
|
/**
|
|
247
285
|
* Most padded rows `dense()` will produce before refusing.
|
|
@@ -254,6 +292,25 @@ function densify(inner, aliases, plan) {
|
|
|
254
292
|
* before any of this existed.
|
|
255
293
|
*/
|
|
256
294
|
export const DENSE_ROW_LIMIT = 10_000;
|
|
295
|
+
/**
|
|
296
|
+
* The ids a channel pads from: the ids its mark's own table holds, plus every
|
|
297
|
+
* Other bucket of the dim, when the table's ids are known; the whole dim before.
|
|
298
|
+
*
|
|
299
|
+
* The dim spans every table registering the field, so padding from all of it
|
|
300
|
+
* gave a chart a zero row, and a slot on a sorted axis, for every category only
|
|
301
|
+
* another table has. Other buckets stay in: an Other row must find its padding
|
|
302
|
+
* row, and another mark's bucket is kept off the axis by its NaN rank, as
|
|
303
|
+
* before.
|
|
304
|
+
*/
|
|
305
|
+
function padIds(id, dim) {
|
|
306
|
+
const present = id?.present;
|
|
307
|
+
// A mark that drops NULL pads no row for it: its rows were filtered out.
|
|
308
|
+
const notNull = id?.dropNullId != null ? ` WHERE "id" <> ${id.dropNullId}` : '';
|
|
309
|
+
if (!present)
|
|
310
|
+
return `(SELECT "id" FROM ${quoteTable(dim)}${notNull})`;
|
|
311
|
+
return (`(SELECT "id" FROM ${quoteTable(present)}${notNull} UNION ALL ` +
|
|
312
|
+
`SELECT "id" FROM ${quoteTable(dim)} WHERE "label" IS NOT NULL)`);
|
|
313
|
+
}
|
|
257
314
|
/**
|
|
258
315
|
* Whether `dense()` applies to a query with these aliases, and how.
|
|
259
316
|
*
|
|
@@ -680,7 +737,37 @@ function collapseIntoOther(ranked, aliases, o) {
|
|
|
680
737
|
}
|
|
681
738
|
}
|
|
682
739
|
bucket[RANK_COLUMN] = sqlTag `CASE WHEN ${kept} THEN ${rank} ELSE ${n + 1} END`;
|
|
740
|
+
// The band column is rewritten to the Other id above, so the category each
|
|
741
|
+
// folded row came from is carried beside it for the member lists.
|
|
742
|
+
bucket[BAND_SOURCE_COLUMN] = column(band);
|
|
683
743
|
const bucketed = Query.from({ __topn_ranked: ranked }).select(bucket);
|
|
744
|
+
// PER MEMBER first: one row per group key and folded category. A grouped mark
|
|
745
|
+
// already has one row per (category, series), so this changes nothing there;
|
|
746
|
+
// an ungrouped one (raw rows, no groupKeys) would otherwise list one entry per
|
|
747
|
+
// RAW ROW past the cutoff — ids repeated, and a list that grows with the table
|
|
748
|
+
// rather than the field. Every aggregate below (SUM, MAX, MIN) gives the same
|
|
749
|
+
// answer over these rows as over the raw ones, so Other's values are unchanged.
|
|
750
|
+
const src = column(BAND_SOURCE_COLUMN);
|
|
751
|
+
const member = {};
|
|
752
|
+
const memberKeys = [src];
|
|
753
|
+
for (const alias of aliases) {
|
|
754
|
+
const col = column(alias);
|
|
755
|
+
if (isKey(alias)) {
|
|
756
|
+
member[alias] = col;
|
|
757
|
+
memberKeys.push(col);
|
|
758
|
+
}
|
|
759
|
+
else if (alias === by) {
|
|
760
|
+
member[alias] = sqlTag `SUM(${col})`;
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
member[alias] = sqlTag `MAX(${col})`;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
member[RANK_COLUMN] = sqlTag `MIN(${rank})`;
|
|
767
|
+
member[BAND_SOURCE_COLUMN] = src;
|
|
768
|
+
const members = Query.from({ __topn_bucketed: bucketed })
|
|
769
|
+
.select(member)
|
|
770
|
+
.groupby(...memberKeys);
|
|
684
771
|
const collapse = {};
|
|
685
772
|
const keys = [];
|
|
686
773
|
for (const alias of aliases) {
|
|
@@ -697,7 +784,17 @@ function collapseIntoOther(ranked, aliases, o) {
|
|
|
697
784
|
}
|
|
698
785
|
}
|
|
699
786
|
collapse[RANK_COLUMN] = sqlTag `MIN(${rank})`;
|
|
700
|
-
|
|
787
|
+
// Other's members, from the rows the bucket step moved past the cutoff (their
|
|
788
|
+
// rank is n + 1 there): one entry per folded category, from the step above. A
|
|
789
|
+
// kept row's group has none, so its lists are NULL. Cast so they arrive as
|
|
790
|
+
// Int32Array and Float64Array children: ids are REAL in the channel, and a
|
|
791
|
+
// COUNT measure is BIGINT, which would arrive as BigInt64Array.
|
|
792
|
+
const past = sqlTag `${rank} > ${n}`;
|
|
793
|
+
collapse[OTHER_IDS_COLUMN] =
|
|
794
|
+
sqlTag `list(CAST(${src} AS INTEGER) ORDER BY ${src}) FILTER (WHERE ${past})`;
|
|
795
|
+
collapse[OTHER_VALUES_COLUMN] =
|
|
796
|
+
sqlTag `list(CAST(${column(by)} AS DOUBLE) ORDER BY ${src}) FILTER (WHERE ${past})`;
|
|
797
|
+
return Query.from({ __topn_members: members })
|
|
701
798
|
.select(collapse)
|
|
702
799
|
.groupby(...keys);
|
|
703
800
|
}
|
|
@@ -712,7 +809,9 @@ export function stackX(options = {}) {
|
|
|
712
809
|
return stackSQL('x', options);
|
|
713
810
|
}
|
|
714
811
|
/** Resolve band (group) / value column aliases for a stack direction, validated against the select list. */
|
|
715
|
-
function resolveStackColumns(aliases, config, direction
|
|
812
|
+
function resolveStackColumns(aliases, config, direction,
|
|
813
|
+
/** Called when there is no series column; the caller decides whether that is worth saying. */
|
|
814
|
+
onNoSeries) {
|
|
716
815
|
const bandAxis = direction === 'y' ? 'x' : 'y';
|
|
717
816
|
const band = config?.[bandAxis];
|
|
718
817
|
const value = config?.[direction];
|
|
@@ -726,20 +825,31 @@ function resolveStackColumns(aliases, config, direction) {
|
|
|
726
825
|
}
|
|
727
826
|
const fill = config?.fill;
|
|
728
827
|
const series = typeof fill === 'string' && aliases.includes(fill) ? fill : null;
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
// identical to an unstacked mark. That is the correct result, but it is
|
|
732
|
-
// indistinguishable from a broken stack to anyone looking at the chart, so say
|
|
733
|
-
// what is missing rather than silently drawing an ordinary bar.
|
|
734
|
-
if (!series) {
|
|
735
|
-
console.warn(`stack${direction === 'y' ? 'Y' : 'X'}: no series column, so stacking has no effect ` +
|
|
736
|
-
`(one row per band). Stacking groups rows within a band by the ordinal ` +
|
|
737
|
-
`fill channel — add fill="<field>" to the mark. Select list: [${aliases.join(', ')}].`);
|
|
738
|
-
}
|
|
828
|
+
if (!series)
|
|
829
|
+
onNoSeries(aliases);
|
|
739
830
|
return { band, value, series };
|
|
740
831
|
}
|
|
741
|
-
|
|
832
|
+
/**
|
|
833
|
+
* `implicit`: added by the mark (withImplicitStack), not written by the user.
|
|
834
|
+
*
|
|
835
|
+
* With no series column there is one row per band, so the cumulative sum equals
|
|
836
|
+
* that row's own value and the stack is arithmetically a no-op. For a stack the
|
|
837
|
+
* user wrote, that is indistinguishable from a broken one on screen, so it says
|
|
838
|
+
* what is missing, once per transform rather than on every query assembled. An
|
|
839
|
+
* implicit stack says nothing: every bar mark without a fill gets one, and it is
|
|
840
|
+
* the ordinary bar it draws, not a mistake.
|
|
841
|
+
*/
|
|
842
|
+
function stackSQL(direction, options, implicit = false) {
|
|
742
843
|
const [loName, hiName] = direction === 'y' ? ['y0', 'y1'] : ['x0', 'x1'];
|
|
844
|
+
let warned = implicit;
|
|
845
|
+
const onNoSeries = (aliases) => {
|
|
846
|
+
if (warned)
|
|
847
|
+
return;
|
|
848
|
+
warned = true;
|
|
849
|
+
console.warn(`stack${direction === 'y' ? 'Y' : 'X'}: no series column, so stacking has no effect ` +
|
|
850
|
+
`(one row per band). Stacking groups rows within a band by the ordinal ` +
|
|
851
|
+
`fill channel — add fill="<field>" to the mark. Select list: [${aliases.join(', ')}].`);
|
|
852
|
+
};
|
|
743
853
|
return {
|
|
744
854
|
stage: 'sql',
|
|
745
855
|
type: 'stack',
|
|
@@ -750,7 +860,7 @@ function stackSQL(direction, options) {
|
|
|
750
860
|
channels: [loName, hiName],
|
|
751
861
|
apply(query, config) {
|
|
752
862
|
const aliases = getSelectAliases(query);
|
|
753
|
-
const { band, value, series } = resolveStackColumns(aliases, config, direction);
|
|
863
|
+
const { band, value, series } = resolveStackColumns(aliases, config, direction, onNoSeries);
|
|
754
864
|
const valueCol = column(value);
|
|
755
865
|
const bandCol = column(band);
|
|
756
866
|
const notNaN = sqlTag `NOT isnan(${valueCol})`;
|
|
@@ -1091,9 +1201,7 @@ export function withImplicitStack(transforms, hasExplicitBounds, direction) {
|
|
|
1091
1201
|
? direction === 'y'
|
|
1092
1202
|
? stackY({ engine: 'js' })
|
|
1093
1203
|
: stackX({ engine: 'js' })
|
|
1094
|
-
: direction
|
|
1095
|
-
? stackY()
|
|
1096
|
-
: stackX();
|
|
1204
|
+
: stackSQL(direction, {}, true);
|
|
1097
1205
|
return [...transforms, implicitStack];
|
|
1098
1206
|
}
|
|
1099
1207
|
export { TransformCompiler } from './TransformCompiler';
|
package/dist/types.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ import type { DataAccessor } from './data/DataAccessor';
|
|
|
3
3
|
import type { ChartConfig } from './config/ChartConfig.svelte';
|
|
4
4
|
import type { DimValue } from './query-engine/categorical-identity';
|
|
5
5
|
import type { AutoBinNode } from './sql/bin';
|
|
6
|
+
import type { SelectionResponse } from './components/rendering/activity-reads';
|
|
7
|
+
/** What a mark does with NULL in its categorical channels; see BaseMarkProps.nulls. */
|
|
8
|
+
export type NullsResponse = 'keep' | 'drop';
|
|
6
9
|
import type { RollingAvgNode } from './sql/rolling-avg';
|
|
7
10
|
export type ContinuousScaleType = 'linear' | 'log' | 'symlog' | 'pow' | 'identity';
|
|
8
11
|
export type DiscreteScaleType = 'band' | 'point';
|
|
@@ -57,6 +60,13 @@ export interface SqlTransform {
|
|
|
57
60
|
apply(query: SelectQuery, config?: MarkConfig): SelectQuery;
|
|
58
61
|
/** Channel aliases this transform adds to the result beyond the base select (e.g. stack's y0/y1). */
|
|
59
62
|
channels?: string[];
|
|
63
|
+
/**
|
|
64
|
+
* The row predicate this transform adds to the query, when that is all it does
|
|
65
|
+
* (`filter`). It applies to the source rows, before any grouping, so a query
|
|
66
|
+
* over a pre-aggregated table cannot apply it (the columns it names are gone):
|
|
67
|
+
* the table is built with it instead, and the read leaves it out.
|
|
68
|
+
*/
|
|
69
|
+
where?: unknown;
|
|
60
70
|
/**
|
|
61
71
|
* The channels whose values identify each row, leading term first, when this
|
|
62
72
|
* transform guarantees exactly one row per combination of them — or null when
|
|
@@ -96,6 +106,18 @@ export interface ChannelIdentity {
|
|
|
96
106
|
positional: boolean;
|
|
97
107
|
/** The reserved id past-the-cutoff rows are bucketed into, once reserved. */
|
|
98
108
|
otherId?: number;
|
|
109
|
+
/**
|
|
110
|
+
* The table of ids the mark's own table holds, which `dense()` pads from: the
|
|
111
|
+
* dim spans every table on the field, and a chart must not pad a slot for a
|
|
112
|
+
* category only another table has. Absent before that table has folded in,
|
|
113
|
+
* when dense() pads from the whole dim as it did before.
|
|
114
|
+
*/
|
|
115
|
+
present?: string;
|
|
116
|
+
/**
|
|
117
|
+
* The NULL category's id, when the mark drops NULL (`nulls="drop"`): `dense()`
|
|
118
|
+
* pads no row for it, as its rows are filtered out.
|
|
119
|
+
*/
|
|
120
|
+
dropNullId?: number;
|
|
99
121
|
}
|
|
100
122
|
export interface MarkConfig {
|
|
101
123
|
markType: string;
|
|
@@ -166,6 +188,55 @@ export interface DomainConfig {
|
|
|
166
188
|
export interface BaseMarkProps {
|
|
167
189
|
table: string;
|
|
168
190
|
selection?: string;
|
|
191
|
+
/**
|
|
192
|
+
* How this mark responds to selections in its filter group.
|
|
193
|
+
*
|
|
194
|
+
* - `'auto'` (the default): a categorical selection (a legend, a Toggle, a
|
|
195
|
+
* band label) on a field this mark draws by, its band axis or its ordinal
|
|
196
|
+
* colour, dims it: it keeps its data and the unselected categories fade.
|
|
197
|
+
* Any other selection filters it.
|
|
198
|
+
* - `'filter'`: every selection filters it, even on a field it draws by, so
|
|
199
|
+
* its aggregates are recomputed for the subset: top N within the selected
|
|
200
|
+
* region, shares within the selected groups.
|
|
201
|
+
*
|
|
202
|
+
* A `'filter'` mark never dims. A legend, Toggle or band label filters it
|
|
203
|
+
* in its own chart too: every such control on the same fields shares one
|
|
204
|
+
* selection, so any chart with one would otherwise ignore it, and this
|
|
205
|
+
* chart's legend re-ranks its own top N. A Toggle on its own bars therefore
|
|
206
|
+
* filters them: the unselected bars leave the chart. Known limit: on a band
|
|
207
|
+
* axis drawn from the data (a sorted mark), clicking a label filters the
|
|
208
|
+
* other categories out, their labels go with them, and a second category
|
|
209
|
+
* cannot be added from this chart; clicking the label again clears it. A brush still skips its
|
|
210
|
+
* own chart, so the marks under it do not move while it is dragged.
|
|
211
|
+
*/
|
|
212
|
+
selectionResponse?: SelectionResponse;
|
|
213
|
+
/**
|
|
214
|
+
* Whether this mark adds its categories to a band axis's domain (default
|
|
215
|
+
* `true`). `false`: the mark follows the axis the other marks on it decide
|
|
216
|
+
* (their categories, their order); rows of categories the axis does not show
|
|
217
|
+
* are not drawn. For a layer that gives context to another, such as the whole
|
|
218
|
+
* behind a part. It cannot rank or reduce its own categories, so `topN`, `sort`
|
|
219
|
+
* and an explicit `domain` are refused with it (an error is logged and the
|
|
220
|
+
* mark contributes).
|
|
221
|
+
*
|
|
222
|
+
* Known limit: when the axis shows an Other bucket (another mark's topN with
|
|
223
|
+
* `includeOthers`), a follower has no Other row of its own. Its bars of
|
|
224
|
+
* categories that leave the axis walk to Other's slot and are gone when the
|
|
225
|
+
* transition ends, and nothing draws the whole behind Other. Folding such
|
|
226
|
+
* categories into an Other row would need the follower's query to depend on
|
|
227
|
+
* the axis's current categories.
|
|
228
|
+
*/
|
|
229
|
+
contributeDomain?: boolean;
|
|
230
|
+
/**
|
|
231
|
+
* What this mark does with NULL in its categorical channels: its band axis,
|
|
232
|
+
* ordinal colour, series and facet.
|
|
233
|
+
*
|
|
234
|
+
* - `'keep'` (the default): NULL is a category like any other. It is ranked
|
|
235
|
+
* by its measure, drawn, and labelled "(null)", in the null colour.
|
|
236
|
+
* - `'drop'`: rows whose value in any of those channels is NULL are left out
|
|
237
|
+
* before any transform, so a top N ranks, and Other sums, without them.
|
|
238
|
+
*/
|
|
239
|
+
nulls?: NullsResponse;
|
|
169
240
|
scheme?: ColorScheme;
|
|
170
241
|
limit?: number;
|
|
171
242
|
transforms?: Transform[];
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"type": "git",
|
|
19
19
|
"url": "git+https://github.com/dlyngman/fjader.git"
|
|
20
20
|
},
|
|
21
|
-
"version": "0.2.
|
|
21
|
+
"version": "0.2.4",
|
|
22
22
|
"type": "module",
|
|
23
23
|
"svelte": "./dist/index.js",
|
|
24
24
|
"exports": {
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"@types/d3-color": "^3.1.3",
|
|
61
61
|
"@types/d3-scale": "^4.0.9",
|
|
62
62
|
"@types/d3-scale-chromatic": "^3.1.0",
|
|
63
|
+
"@uwdata/mosaic-sql": "^0.35.0",
|
|
63
64
|
"@webgpu/types": "^0.1.69",
|
|
64
65
|
"duckdb": "^1.4.4",
|
|
65
66
|
"eslint": "^10.0.0",
|
|
@@ -80,12 +81,12 @@
|
|
|
80
81
|
"vitest": "^4.0.18"
|
|
81
82
|
},
|
|
82
83
|
"peerDependencies": {
|
|
84
|
+
"@uwdata/mosaic-sql": "^0.34.2 || ^0.35.0",
|
|
83
85
|
"svelte": "^5.0.0"
|
|
84
86
|
},
|
|
85
87
|
"dependencies": {
|
|
86
88
|
"@duckdb/duckdb-wasm": "1.30.0",
|
|
87
89
|
"@uwdata/flechette": "2.3.0",
|
|
88
|
-
"@uwdata/mosaic-sql": "^0.34.2",
|
|
89
90
|
"d3-array": "^3.2.4",
|
|
90
91
|
"d3-color": "^3.1.0",
|
|
91
92
|
"d3-format": "^3.1.0",
|