@fjader/svelte 0.2.4 → 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 +1 -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 +4 -2
- package/dist/components/axes/AxisCenter.svelte +4 -2
- package/dist/components/axes/AxisLeft.svelte +4 -2
- package/dist/components/axes/BandAxisTicks.svelte +3 -2
- package/dist/components/charts/ChartCell.svelte +32 -5
- package/dist/components/charts/ChartCell.svelte.d.ts +9 -0
- package/dist/components/charts/ChartContainer.svelte +45 -3
- package/dist/components/charts/ChartContainer.svelte.d.ts +7 -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/marks/AreaMarkX.svelte +16 -4
- package/dist/components/marks/AreaMarkY.svelte +16 -4
- package/dist/components/marks/BarMarkX.svelte +7 -3
- package/dist/components/marks/BarMarkY.svelte +7 -3
- package/dist/components/marks/DotsMark.svelte +6 -3
- package/dist/components/marks/LineMark.svelte +11 -8
- package/dist/components/marks/ParallelCoordinatesMark.svelte +7 -2
- package/dist/components/marks/ParallelCoordinatesMark.svelte.d.ts +2 -0
- package/dist/components/marks/RectMark.svelte +6 -3
- package/dist/components/marks/RectXMark.svelte +7 -3
- package/dist/components/marks/RectYMark.svelte +7 -3
- package/dist/components/marks/RuleMarkX.svelte +30 -26
- package/dist/components/marks/RuleMarkY.svelte +30 -26
- package/dist/components/marks/TextMark.svelte +44 -5
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.d.ts +16 -0
- package/dist/components/marks/lifecycle/MarkLifecycleV2.svelte.js +398 -54
- package/dist/components/marks/lifecycle/domain-queries.d.ts +11 -2
- package/dist/components/marks/lifecycle/domain-queries.js +33 -6
- package/dist/components/marks/lifecycle/domain-registration.d.ts +6 -1
- package/dist/components/marks/lifecycle/domain-registration.js +15 -14
- package/dist/components/marks/lifecycle/other-rows.svelte.d.ts +9 -1
- package/dist/components/marks/lifecycle/other-rows.svelte.js +2 -2
- package/dist/components/marks/lifecycle/query-builder.d.ts +27 -2
- package/dist/components/marks/lifecycle/query-builder.js +23 -20
- 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 +3 -0
- 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 +1 -1
- 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 +51 -11
- package/dist/components/rendering/Rects.svelte.d.ts +16 -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 +41 -24
- package/dist/components/rendering/activity-reads.js +54 -31
- 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 +7 -0
- 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 -2
- package/dist/gpu/MorphedChannels.js +33 -11
- package/dist/gpu/ScaleTemplateSystem.d.ts +20 -2
- package/dist/gpu/ScaleTemplateSystem.js +58 -2
- package/dist/index.d.ts +17 -4
- package/dist/index.js +19 -4
- package/dist/interactors/TooltipManager.svelte.js +14 -5
- 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 +2 -3
- package/dist/query-engine/FieldActivity.svelte.js +1 -2
- 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 +671 -41
- package/dist/query-engine/categorical-identity.d.ts +34 -8
- package/dist/query-engine/categorical-identity.js +138 -73
- 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 +28 -1
- package/dist/query-engine/preagg/plan.js +128 -8
- 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/scales/BandScaleState.svelte.d.ts +9 -1
- package/dist/scales/BandScaleState.svelte.js +16 -5
- 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 +41 -13
- package/dist/sql/bin.d.ts +25 -0
- package/dist/sql/bin.js +108 -5
- package/dist/transforms/TransformCompiler.d.ts +10 -0
- package/dist/transforms/TransformCompiler.js +26 -0
- package/dist/transforms/index.js +144 -24
- package/dist/types.d.ts +136 -2
- package/package.json +2 -2
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { observeTransitions } from '../animation/transition-log';
|
|
1
2
|
import { setContext, getContext } from 'svelte';
|
|
2
3
|
import { AnimationLoop } from '../animation';
|
|
4
|
+
import { TransitionClock, ENTRANCE_FADE_STIFFNESS, ENTRANCE_GROW_STIFFNESS } from '../animation/TransitionClock';
|
|
3
5
|
import { scaleTemplateSystem, CHART_SCALE_IMMEDIATE_SIZE } from './ScaleTemplateSystem';
|
|
4
6
|
import { isCategoricalScale } from '../scales/scale-categories';
|
|
5
7
|
import { MAX_SIZE_SLOTS } from './SizeScaleShaderLib';
|
|
@@ -19,8 +21,23 @@ export const COLOR_RAMP_RESOLUTION = 256;
|
|
|
19
21
|
function layerCanDraw(layer, chartScalesKnown) {
|
|
20
22
|
return (!!layer.renderer?.perDataBindGroup && (chartScalesKnown || layer.readsChartScales === false));
|
|
21
23
|
}
|
|
24
|
+
/** A guide (a grid line) rather than data: it neither enters nor starts the entrance. */
|
|
25
|
+
function isGuideLayer(layer) {
|
|
26
|
+
return layer.markType.startsWith('grid-line-');
|
|
27
|
+
}
|
|
22
28
|
/** No fields to pack, for a coordinator without filter groups. */
|
|
23
29
|
const NO_FIELDS = [];
|
|
30
|
+
/**
|
|
31
|
+
* A chart's entrance once the viewer's preference is applied: under reduced
|
|
32
|
+
* motion, 'auto' fades rather than grows. Opacity is not motion, so a fade
|
|
33
|
+
* stays; an author's explicit 'fade' or 'none' is kept as is.
|
|
34
|
+
*/
|
|
35
|
+
function resolveEntrance(mode) {
|
|
36
|
+
const chosen = mode ?? 'auto';
|
|
37
|
+
if (chosen !== 'auto' || typeof matchMedia === 'undefined')
|
|
38
|
+
return chosen;
|
|
39
|
+
return matchMedia('(prefers-reduced-motion: reduce)').matches ? 'fade' : 'auto';
|
|
40
|
+
}
|
|
24
41
|
// =============================================================================
|
|
25
42
|
// HELPER CLASSES
|
|
26
43
|
// =============================================================================
|
|
@@ -477,10 +494,6 @@ export class GPUCoordinator {
|
|
|
477
494
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
478
495
|
});
|
|
479
496
|
}
|
|
480
|
-
/**
|
|
481
|
-
* Fill CPU buffer with current scale GPU state.
|
|
482
|
-
* Zero-allocation hot path.
|
|
483
|
-
*/
|
|
484
497
|
/**
|
|
485
498
|
* Buffers for the slot table and its per-frame resolve.
|
|
486
499
|
*
|
|
@@ -746,8 +759,115 @@ export class GPUCoordinator {
|
|
|
746
759
|
this.#device.queue.writeBuffer(buffer, 0, view);
|
|
747
760
|
last.set(view);
|
|
748
761
|
}
|
|
749
|
-
/** Reused per pass: the chart's x and y ScaleParam, pushed as immediates. */
|
|
762
|
+
/** Reused per pass: the chart's x and y ScaleParam and entrance, pushed as immediates. */
|
|
750
763
|
#chartScaleImmediates = new Float32Array(CHART_SCALE_IMMEDIATE_SIZE / 4);
|
|
764
|
+
/** Debug listeners for chart entrances (observeEntrances). */
|
|
765
|
+
#entranceObservers = new Set();
|
|
766
|
+
/**
|
|
767
|
+
* Listen to every chart's entrance as it starts: when its marks have their
|
|
768
|
+
* first data and it first draws. For measuring load (how soon charts enter);
|
|
769
|
+
* returns the unsubscribe. Costs nothing while no one listens.
|
|
770
|
+
*/
|
|
771
|
+
observeEntrances(listener) {
|
|
772
|
+
this.#entranceObservers.add(listener);
|
|
773
|
+
return () => this.#entranceObservers.delete(listener);
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Begin a chart's entrance on the first pass that draws data in it.
|
|
777
|
+
*
|
|
778
|
+
* Per chart, not per mark: every mark in the chart reads the same immediate,
|
|
779
|
+
* so a bar chart and the line drawn over it enter together. Marks normally
|
|
780
|
+
* land in one query batch; one landing mid-entrance joins at the current t,
|
|
781
|
+
* and one landing after it simply appears.
|
|
782
|
+
*
|
|
783
|
+
* Started here rather than on registration, so an off-screen chart enters
|
|
784
|
+
* when it is first drawn, which is when it can be seen.
|
|
785
|
+
*/
|
|
786
|
+
#startEntranceIfDrawing(chart, scalesKnown) {
|
|
787
|
+
// Every mark's first data first, so the chart enters once, whole. Only
|
|
788
|
+
// this chart's marks: a chart sharing a scale that lands later springs
|
|
789
|
+
// the domain to the combined extent, as any later data would.
|
|
790
|
+
const gate = chart.entranceGate;
|
|
791
|
+
if (gate) {
|
|
792
|
+
if (!gate.canEnter)
|
|
793
|
+
return;
|
|
794
|
+
gate.open();
|
|
795
|
+
}
|
|
796
|
+
const plan = chart.renderPlan;
|
|
797
|
+
let drawing = false;
|
|
798
|
+
for (let i = 0; i < plan.length; i++) {
|
|
799
|
+
// Data marks only: a grid line draws as soon as the scales exist, which
|
|
800
|
+
// is usually before the query has answered, and would start the
|
|
801
|
+
// entrance with nothing in it.
|
|
802
|
+
if (isGuideLayer(plan[i]))
|
|
803
|
+
continue;
|
|
804
|
+
if (layerCanDraw(plan[i], scalesKnown)) {
|
|
805
|
+
drawing = true;
|
|
806
|
+
break;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (!drawing)
|
|
810
|
+
return;
|
|
811
|
+
chart.entranceStarted = true;
|
|
812
|
+
if (this.#entranceObservers.size > 0) {
|
|
813
|
+
const entrance = {
|
|
814
|
+
chartId: chart.chartId,
|
|
815
|
+
canvas: chart.canvas,
|
|
816
|
+
time: performance.now()
|
|
817
|
+
};
|
|
818
|
+
for (const observe of this.#entranceObservers) {
|
|
819
|
+
try {
|
|
820
|
+
observe(entrance);
|
|
821
|
+
}
|
|
822
|
+
catch (e) {
|
|
823
|
+
console.error('[GPUCoordinator] entrance observer threw:', e);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (chart.entranceMode === 'none')
|
|
828
|
+
return;
|
|
829
|
+
const clock = new TransitionClock({
|
|
830
|
+
loop: this,
|
|
831
|
+
stiffness: ENTRANCE_GROW_STIFFNESS,
|
|
832
|
+
charts: () => chart.entranceCharts,
|
|
833
|
+
onRest: () => {
|
|
834
|
+
if (chart.entranceClock === clock)
|
|
835
|
+
chart.entranceClock = null;
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
chart.entranceClock = clock;
|
|
839
|
+
// Ease-out: growth should move from its first frame.
|
|
840
|
+
clock.restartEaseOut();
|
|
841
|
+
const fade = new TransitionClock({
|
|
842
|
+
loop: this,
|
|
843
|
+
stiffness: ENTRANCE_FADE_STIFFNESS,
|
|
844
|
+
charts: () => chart.entranceCharts,
|
|
845
|
+
onRest: () => {
|
|
846
|
+
if (chart.entranceFadeClock === fade)
|
|
847
|
+
chart.entranceFadeClock = null;
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
chart.entranceFadeClock = fade;
|
|
851
|
+
// From rest: a soft S-curve for opacity.
|
|
852
|
+
fade.restart();
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* The entrance immediates: x = growth, y = a growing mark's opacity, z = a
|
|
856
|
+
* fading mark's opacity. The mode is folded in here, so a mark only says
|
|
857
|
+
* whether it grows or fades.
|
|
858
|
+
*/
|
|
859
|
+
#writeEntrance(chart, view) {
|
|
860
|
+
// Before the entrance starts the chart draws nothing, so 0 is never
|
|
861
|
+
// seen; after it, the clocks are gone and everything is at 1.
|
|
862
|
+
const idle = chart.entranceStarted ? 1 : 0;
|
|
863
|
+
const grow = chart.entranceClock ? chart.entranceClock.t : idle;
|
|
864
|
+
const fade = chart.entranceFadeClock ? chart.entranceFadeClock.t : idle;
|
|
865
|
+
const mode = chart.entranceMode;
|
|
866
|
+
view[8] = mode === 'auto' ? grow : 1;
|
|
867
|
+
view[9] = mode === 'fade' ? fade : 1;
|
|
868
|
+
view[10] = mode === 'none' ? 1 : fade;
|
|
869
|
+
view[11] = 0;
|
|
870
|
+
}
|
|
751
871
|
// =========================================================================
|
|
752
872
|
// READBACK (test-only)
|
|
753
873
|
// =========================================================================
|
|
@@ -996,6 +1116,9 @@ export class GPUCoordinator {
|
|
|
996
1116
|
// known: a layer that does not read them still needs every byte set
|
|
997
1117
|
// before it may draw.
|
|
998
1118
|
const scalesKnown = this.#writeChartScales(chartConfig, this.#chartScaleImmediates);
|
|
1119
|
+
if (!chartConfig.entranceStarted)
|
|
1120
|
+
this.#startEntranceIfDrawing(chartConfig, scalesKnown);
|
|
1121
|
+
this.#writeEntrance(chartConfig, this.#chartScaleImmediates);
|
|
999
1122
|
passEncoder.setImmediates(0, this.#chartScaleImmediates);
|
|
1000
1123
|
if (chartConfig.perFrameBindGroup) {
|
|
1001
1124
|
passEncoder.setBindGroup(0, chartConfig.perFrameBindGroup);
|
|
@@ -1501,8 +1624,19 @@ export class GPUCoordinator {
|
|
|
1501
1624
|
renderPassDescriptor: null,
|
|
1502
1625
|
msaa: null,
|
|
1503
1626
|
_lastRenderFrame: -1,
|
|
1504
|
-
_isDirty: false
|
|
1627
|
+
_isDirty: false,
|
|
1628
|
+
entranceMode: resolveEntrance(config.entrance),
|
|
1629
|
+
entranceStarted: false,
|
|
1630
|
+
entranceClock: null,
|
|
1631
|
+
entranceFadeClock: null,
|
|
1632
|
+
entranceCharts: [chartId],
|
|
1633
|
+
entranceGate: config.entranceGate ?? null,
|
|
1634
|
+
entranceGateOff: null
|
|
1505
1635
|
};
|
|
1636
|
+
// The gate opens on a mark's data or a timeout, neither of which need
|
|
1637
|
+
// redraw this chart. Without a frame the entrance would wait for one.
|
|
1638
|
+
chartConfig.entranceGateOff =
|
|
1639
|
+
chartConfig.entranceGate?.onOpen(() => this.invalidateChart(chartId)) ?? null;
|
|
1506
1640
|
this.#registeredCharts.set(chartId, chartConfig);
|
|
1507
1641
|
// Observe canvas for viewport visibility
|
|
1508
1642
|
if (this.#visibilityObserver) {
|
|
@@ -1552,6 +1686,12 @@ export class GPUCoordinator {
|
|
|
1552
1686
|
const chartConfig = this.#registeredCharts.get(chartId);
|
|
1553
1687
|
if (chartConfig) {
|
|
1554
1688
|
chartConfig._isDirty = false;
|
|
1689
|
+
chartConfig.entranceClock?.stop();
|
|
1690
|
+
chartConfig.entranceClock = null;
|
|
1691
|
+
chartConfig.entranceFadeClock?.stop();
|
|
1692
|
+
chartConfig.entranceFadeClock = null;
|
|
1693
|
+
chartConfig.entranceGateOff?.();
|
|
1694
|
+
chartConfig.entranceGateOff = null;
|
|
1555
1695
|
if (chartConfig.frameUniformsBuffer) {
|
|
1556
1696
|
chartConfig.frameUniformsBuffer.destroy();
|
|
1557
1697
|
}
|
|
@@ -1580,13 +1720,30 @@ export class GPUCoordinator {
|
|
|
1580
1720
|
// =========================================================================
|
|
1581
1721
|
// VIEWPORT VISIBILITY
|
|
1582
1722
|
// =========================================================================
|
|
1723
|
+
/**
|
|
1724
|
+
* Debug: every transition that starts, and whether it animates or lands at
|
|
1725
|
+
* once (see transition-log). Returns the unsubscribe. Forwards to the
|
|
1726
|
+
* page-wide log: records from every coordinator in the document arrive
|
|
1727
|
+
* here, so filter by chart id.
|
|
1728
|
+
*/
|
|
1729
|
+
observeTransitions(listener) {
|
|
1730
|
+
return observeTransitions(listener);
|
|
1731
|
+
}
|
|
1732
|
+
/** Whether a registered chart is visible: in view (with the 200 px margin) and the page shown. */
|
|
1733
|
+
isChartVisible(chartId) {
|
|
1734
|
+
return this.#registeredCharts.get(chartId)?.visible ?? false;
|
|
1735
|
+
}
|
|
1583
1736
|
/**
|
|
1584
1737
|
* Register a callback invoked when a chart's effective visibility changes.
|
|
1585
1738
|
* Effective visibility = IntersectionObserver visible AND page visible.
|
|
1586
|
-
* Used by ChartCell to
|
|
1739
|
+
* Used by ChartCell to report into the chart's ChartVisibility.
|
|
1587
1740
|
*/
|
|
1588
1741
|
setVisibilityCallback(chartId, callback) {
|
|
1589
1742
|
this.#visibilityCallbacks.set(chartId, callback);
|
|
1743
|
+
// Callbacks hear changes only. One set after the chart was already found
|
|
1744
|
+
// off screen would otherwise go on assuming it visible.
|
|
1745
|
+
if (this.#registeredCharts.get(chartId)?.visible === false)
|
|
1746
|
+
callback(false);
|
|
1590
1747
|
return () => this.#visibilityCallbacks.delete(chartId);
|
|
1591
1748
|
}
|
|
1592
1749
|
#handleVisibilityChange(chartId, ioVisible) {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { TransitionClock, type ClockLoop } from '../animation/TransitionClock';
|
|
2
|
+
/**
|
|
3
|
+
* The coordinator's slice this needs. Structural, so tests can supply a fake.
|
|
4
|
+
* It is also the loop an owned clock joins — the clock does that itself.
|
|
5
|
+
*/
|
|
2
6
|
export interface MorphHost extends ClockLoop {
|
|
3
7
|
registerComputeWork(key: string, fn: (encoder: GPUCommandEncoder) => void): void;
|
|
4
8
|
removeComputeWork(key: string): void;
|
|
@@ -30,6 +34,10 @@ export interface MorphedChannelsOptions {
|
|
|
30
34
|
* travelling to its neighbour's value and back. So the claim is checked, and a
|
|
31
35
|
* change snaps rather than interpolates. A hash rather than a comparison
|
|
32
36
|
* because it costs one pass and no retained copy.
|
|
37
|
+
*
|
|
38
|
+
* Over the values' bits, read through a view of the same buffer: a
|
|
39
|
+
* histogram's rows are keyed by bin edges, fractions a sum would truncate
|
|
40
|
+
* away, so a re-bin could otherwise hash like the bins it replaced.
|
|
33
41
|
*/
|
|
34
42
|
export declare function alignmentKey(ids: ArrayLike<number>): number;
|
|
35
43
|
export declare class MorphedChannels {
|
|
@@ -58,9 +66,10 @@ export declare class MorphedChannels {
|
|
|
58
66
|
* `land`: bindings whose new values take effect at once while the rest
|
|
59
67
|
* travel. For a channel whose change is shown some other way at that moment
|
|
60
68
|
* (an Other bar's split, when both sides of it look the same): sliding it
|
|
61
|
-
* would draw a movement nothing asked for.
|
|
69
|
+
* would draw a movement nothing asked for. `'all'`: the whole update lands,
|
|
70
|
+
* for a catch-up result (MarkHandle.resultCause).
|
|
62
71
|
*/
|
|
63
|
-
write(rowIds: ArrayLike<number>, channels: readonly (readonly [number, AllowSharedBufferSource])[], land?: readonly number[]): boolean;
|
|
72
|
+
write(rowIds: ArrayLike<number>, channels: readonly (readonly [number, AllowSharedBufferSource])[], land?: readonly number[] | 'all'): boolean;
|
|
64
73
|
/**
|
|
65
74
|
* Stop morphing a channel and release its buffers. Returns whether it was
|
|
66
75
|
* morphing, in which case the caller's binding has lost its buffer.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { noteTransition, transitionsObserved } from '../animation/transition-log';
|
|
1
2
|
import { RESOLVE_IMMEDIATE_SIZE, RESOLVE_WORKGROUP, resolvePipelineFor } from './ResolveShader';
|
|
2
3
|
import { TransitionClock } from '../animation/TransitionClock';
|
|
3
4
|
import { immediatesSupported } from './immediates';
|
|
@@ -28,10 +29,6 @@ import { immediatesSupported } from './immediates';
|
|
|
28
29
|
* finishes growing. It OWNS one otherwise. Adoption is safe across marks because
|
|
29
30
|
* a mark with no new data folds to where it already is and does not move.
|
|
30
31
|
*/
|
|
31
|
-
/**
|
|
32
|
-
* The coordinator's slice this needs. Structural, so tests can supply a fake.
|
|
33
|
-
* It is also the loop an owned clock joins — the clock does that itself.
|
|
34
|
-
*/
|
|
35
32
|
/** No bindings: the default for `write`'s `land`. */
|
|
36
33
|
const NO_BINDINGS = [];
|
|
37
34
|
/**
|
|
@@ -43,11 +40,16 @@ const NO_BINDINGS = [];
|
|
|
43
40
|
* travelling to its neighbour's value and back. So the claim is checked, and a
|
|
44
41
|
* change snaps rather than interpolates. A hash rather than a comparison
|
|
45
42
|
* because it costs one pass and no retained copy.
|
|
43
|
+
*
|
|
44
|
+
* Over the values' bits, read through a view of the same buffer: a
|
|
45
|
+
* histogram's rows are keyed by bin edges, fractions a sum would truncate
|
|
46
|
+
* away, so a re-bin could otherwise hash like the bins it replaced.
|
|
46
47
|
*/
|
|
47
48
|
export function alignmentKey(ids) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
const bits = ids instanceof Float32Array ? new Uint32Array(ids.buffer, ids.byteOffset, ids.length) : ids;
|
|
50
|
+
let hash = bits.length;
|
|
51
|
+
for (let i = 0; i < bits.length; i++)
|
|
52
|
+
hash = (Math.imul(hash, 31) + bits[i]) | 0;
|
|
51
53
|
return hash;
|
|
52
54
|
}
|
|
53
55
|
export class MorphedChannels {
|
|
@@ -124,7 +126,8 @@ export class MorphedChannels {
|
|
|
124
126
|
* `land`: bindings whose new values take effect at once while the rest
|
|
125
127
|
* travel. For a channel whose change is shown some other way at that moment
|
|
126
128
|
* (an Other bar's split, when both sides of it look the same): sliding it
|
|
127
|
-
* would draw a movement nothing asked for.
|
|
129
|
+
* would draw a movement nothing asked for. `'all'`: the whole update lands,
|
|
130
|
+
* for a catch-up result (MarkHandle.resultCause).
|
|
128
131
|
*/
|
|
129
132
|
write(rowIds, channels, land = NO_BINDINGS) {
|
|
130
133
|
const key = alignmentKey(rowIds);
|
|
@@ -138,7 +141,7 @@ export class MorphedChannels {
|
|
|
138
141
|
reallocated = true;
|
|
139
142
|
}
|
|
140
143
|
channel.count = data.byteLength >> 2;
|
|
141
|
-
channel.land = land.includes(binding);
|
|
144
|
+
channel.land = land === 'all' || land.includes(binding);
|
|
142
145
|
this.#device.queue.writeBuffer(channel.to, 0, data);
|
|
143
146
|
}
|
|
144
147
|
// A fresh buffer holds nothing that was ever on screen, so there is
|
|
@@ -146,10 +149,29 @@ export class MorphedChannels {
|
|
|
146
149
|
if (reallocated)
|
|
147
150
|
travels = false;
|
|
148
151
|
this.#dirty = true;
|
|
149
|
-
|
|
152
|
+
// Every channel landing travels nothing, so it starts no transition: on
|
|
153
|
+
// a shared band clock a restart would set every sibling folding for a
|
|
154
|
+
// transition with nowhere to go.
|
|
155
|
+
// A loop, not `every`: this runs on each data update of a brush, and
|
|
156
|
+
// the callback would allocate per write.
|
|
157
|
+
let allLand = land === 'all';
|
|
158
|
+
if (land !== 'all' && land.length > 0) {
|
|
159
|
+
allLand = true;
|
|
160
|
+
for (let i = 0; i < channels.length; i++) {
|
|
161
|
+
if (!land.includes(channels[i][0])) {
|
|
162
|
+
allLand = false;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (travels && !allLand)
|
|
150
168
|
this.#startTransition();
|
|
151
|
-
else
|
|
169
|
+
else if (!travels)
|
|
152
170
|
this.#snapPending = true;
|
|
171
|
+
if (transitionsObserved()) {
|
|
172
|
+
// Snaps when the row set changed or a buffer grew, or every channel lands.
|
|
173
|
+
noteTransition('morph', !travels || allLand, this.#affectedCharts, this.#label);
|
|
174
|
+
}
|
|
153
175
|
if (!this.#registered) {
|
|
154
176
|
this.#registered = true;
|
|
155
177
|
this.#host.registerComputeWork(this.#label, (encoder) => this.encode(encoder));
|
|
@@ -33,14 +33,14 @@ interface ShaderResult {
|
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* Bytes of immediate data every render pipeline declares: the chart's x and y
|
|
36
|
-
* `ScaleParam`, 16 bytes each. Every layout declares the same size because a
|
|
36
|
+
* `ScaleParam`, 16 bytes each, and its entrance, a vec4. Every layout declares the same size because a
|
|
37
37
|
* draw requires every declared byte to have been set, and the pass sets them
|
|
38
38
|
* once, before its first draw.
|
|
39
39
|
*
|
|
40
40
|
* Immediates carry state shared across the pass and read by several marks: the
|
|
41
41
|
* chart's scales and settings. 64 bytes are available on M-series Macs.
|
|
42
42
|
*/
|
|
43
|
-
export declare const CHART_SCALE_IMMEDIATE_SIZE =
|
|
43
|
+
export declare const CHART_SCALE_IMMEDIATE_SIZE = 48;
|
|
44
44
|
/**
|
|
45
45
|
* Main template system class
|
|
46
46
|
*/
|
|
@@ -69,6 +69,24 @@ export declare class ScaleTemplateSystem {
|
|
|
69
69
|
* @private
|
|
70
70
|
*/
|
|
71
71
|
_buildDeclarations(xFragment: ScaleFragment, yFragment: ScaleFragment): string;
|
|
72
|
+
/**
|
|
73
|
+
* The chart's entrance, for marks to apply in the vertex stage.
|
|
74
|
+
*
|
|
75
|
+
* A mark either GROWS along one axis (bars, histograms and areas, from the
|
|
76
|
+
* value 0 on their measure axis) or FADES (everything else). Which one is
|
|
77
|
+
* the mark's to say, as `axis`: 0 fades, 1 grows along x, 2 along y. The
|
|
78
|
+
* chart's mode is already folded into the immediates, so a chart set to
|
|
79
|
+
* fade hands a growing mark g = 1 and its opacity instead.
|
|
80
|
+
*
|
|
81
|
+
* The base a mark grows from is where 0 sits on screen, clamped to the plot:
|
|
82
|
+
* a domain that leaves 0 out grows from the edge, which is where its bars
|
|
83
|
+
* visibly start anyway. A log axis has no 0 and grows from its low edge.
|
|
84
|
+
* Decided per scale type here, so no shader evaluates log(0).
|
|
85
|
+
*/
|
|
86
|
+
_buildEntranceHelpers(scaleTypes: {
|
|
87
|
+
x: string;
|
|
88
|
+
y: string;
|
|
89
|
+
}): string;
|
|
72
90
|
/**
|
|
73
91
|
* `bandActivityX(value, group)` and `bandActivityY(value, group)`: how
|
|
74
92
|
* selected a band or point axis's category is in the mark's filter group,
|
|
@@ -280,14 +280,14 @@ function _scaleFunctionName(scaleType, axis) {
|
|
|
280
280
|
}
|
|
281
281
|
/**
|
|
282
282
|
* Bytes of immediate data every render pipeline declares: the chart's x and y
|
|
283
|
-
* `ScaleParam`, 16 bytes each. Every layout declares the same size because a
|
|
283
|
+
* `ScaleParam`, 16 bytes each, and its entrance, a vec4. Every layout declares the same size because a
|
|
284
284
|
* draw requires every declared byte to have been set, and the pass sets them
|
|
285
285
|
* once, before its first draw.
|
|
286
286
|
*
|
|
287
287
|
* Immediates carry state shared across the pass and read by several marks: the
|
|
288
288
|
* chart's scales and settings. 64 bytes are available on M-series Macs.
|
|
289
289
|
*/
|
|
290
|
-
export const CHART_SCALE_IMMEDIATE_SIZE =
|
|
290
|
+
export const CHART_SCALE_IMMEDIATE_SIZE = 48;
|
|
291
291
|
/**
|
|
292
292
|
* Main template system class
|
|
293
293
|
*/
|
|
@@ -376,6 +376,7 @@ export class ScaleTemplateSystem {
|
|
|
376
376
|
// ===== 3. HELPER FUNCTIONS =====
|
|
377
377
|
|
|
378
378
|
${this._buildSlotTableHelpers()}
|
|
379
|
+
${this._buildEntranceHelpers(scaleTypes)}
|
|
379
380
|
${this._buildBandActivityHelpers(scaleTypes)}
|
|
380
381
|
${bandHelpers}
|
|
381
382
|
${implementations}
|
|
@@ -413,6 +414,10 @@ export class ScaleTemplateSystem {
|
|
|
413
414
|
struct ChartScales {
|
|
414
415
|
x: ScaleParam,
|
|
415
416
|
y: ScaleParam,
|
|
417
|
+
// The chart's entrance, written per pass from its entrance clock:
|
|
418
|
+
// x = how far a growing mark has grown, y = a growing mark's opacity,
|
|
419
|
+
// z = a fading mark's opacity. All 1 once the chart has entered.
|
|
420
|
+
entrance: vec4<f32>,
|
|
416
421
|
};
|
|
417
422
|
var<immediate> chartScales: ChartScales;
|
|
418
423
|
|
|
@@ -448,6 +453,57 @@ export class ScaleTemplateSystem {
|
|
|
448
453
|
`;
|
|
449
454
|
return `${structDef}\n${chartScaleDeclaration}`;
|
|
450
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* The chart's entrance, for marks to apply in the vertex stage.
|
|
458
|
+
*
|
|
459
|
+
* A mark either GROWS along one axis (bars, histograms and areas, from the
|
|
460
|
+
* value 0 on their measure axis) or FADES (everything else). Which one is
|
|
461
|
+
* the mark's to say, as `axis`: 0 fades, 1 grows along x, 2 along y. The
|
|
462
|
+
* chart's mode is already folded into the immediates, so a chart set to
|
|
463
|
+
* fade hands a growing mark g = 1 and its opacity instead.
|
|
464
|
+
*
|
|
465
|
+
* The base a mark grows from is where 0 sits on screen, clamped to the plot:
|
|
466
|
+
* a domain that leaves 0 out grows from the edge, which is where its bars
|
|
467
|
+
* visibly start anyway. A log axis has no 0 and grows from its low edge.
|
|
468
|
+
* Decided per scale type here, so no shader evaluates log(0).
|
|
469
|
+
*/
|
|
470
|
+
_buildEntranceHelpers(scaleTypes) {
|
|
471
|
+
const base = (axis, type) => {
|
|
472
|
+
const a = axis.toLowerCase();
|
|
473
|
+
const lo = `frameUniforms.rangeMin.${a}`;
|
|
474
|
+
const hi = `frameUniforms.rangeMax.${a}`;
|
|
475
|
+
if (type === 'band' || type === 'point')
|
|
476
|
+
return '0.0';
|
|
477
|
+
// Screen y runs downward: the low edge of a y axis is rangeMax.
|
|
478
|
+
if (type === 'log')
|
|
479
|
+
return axis === 'X' ? lo : hi;
|
|
480
|
+
return `clamp(${_scaleFunctionName(type, axis)}From(0.0, chartScale${axis}()), ${lo}, ${hi})`;
|
|
481
|
+
};
|
|
482
|
+
return `
|
|
483
|
+
// ===== ENTRANCE =====
|
|
484
|
+
fn entranceBaseX() -> f32 {
|
|
485
|
+
return ${base('X', scaleTypes.x)};
|
|
486
|
+
}
|
|
487
|
+
fn entranceBaseY() -> f32 {
|
|
488
|
+
return ${base('Y', scaleTypes.y)};
|
|
489
|
+
}
|
|
490
|
+
// A screen position as it is during the entrance: pulled toward the base
|
|
491
|
+
// on the growing axis, untouched otherwise.
|
|
492
|
+
fn entrancePosition(p: vec2<f32>, axis: u32) -> vec2<f32> {
|
|
493
|
+
let g = chartScales.entrance.x;
|
|
494
|
+
if (axis == 1u) {
|
|
495
|
+
return vec2<f32>(mix(entranceBaseX(), p.x, g), p.y);
|
|
496
|
+
}
|
|
497
|
+
if (axis == 2u) {
|
|
498
|
+
return vec2<f32>(p.x, mix(entranceBaseY(), p.y, g));
|
|
499
|
+
}
|
|
500
|
+
return p;
|
|
501
|
+
}
|
|
502
|
+
fn entranceAlpha(axis: u32) -> f32 {
|
|
503
|
+
return select(chartScales.entrance.z, chartScales.entrance.y, axis != 0u);
|
|
504
|
+
}
|
|
505
|
+
`;
|
|
506
|
+
}
|
|
451
507
|
/**
|
|
452
508
|
* `bandActivityX(value, group)` and `bandActivityY(value, group)`: how
|
|
453
509
|
* selected a band or point axis's category is in the mark's filter group,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import type { DuckDBConnection } from './query-engine';
|
|
1
|
+
import type { DuckDBConnection, InitDuckDBOptions, QueryEngineOptions } from './query-engine';
|
|
2
|
+
export interface FjaderOptions extends InitDuckDBOptions {
|
|
3
|
+
/** An existing connection (or a promise of one); skips internal DuckDB-WASM init. */
|
|
4
|
+
connection?: DuckDBConnection | Promise<DuckDBConnection>;
|
|
5
|
+
/** Advanced: query engine tuning (see QueryEngineOptions). */
|
|
6
|
+
engine?: QueryEngineOptions;
|
|
7
|
+
}
|
|
8
|
+
type FjaderInit = DuckDBConnection | Promise<DuckDBConnection> | FjaderOptions;
|
|
2
9
|
/**
|
|
3
10
|
* Initialize Fjader: QueryEngine, ScaleRegistry, GPUCoordinator, FilterGroups.
|
|
4
11
|
* Call once in a top-level component's <script> block.
|
|
@@ -6,10 +13,14 @@ import type { DuckDBConnection } from './query-engine';
|
|
|
6
13
|
* import { initFjader } from '@fjader/svelte';
|
|
7
14
|
* const { queryEngine } = initFjader();
|
|
8
15
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
16
|
+
* Serve the DuckDB-WASM bundles yourself instead of loading them from jsDelivr:
|
|
17
|
+
* initFjader({ bundles: { mvp: { mainModule, mainWorker }, eh: { … } } });
|
|
18
|
+
*
|
|
19
|
+
* Pass an existing DuckDB connection to skip internal DuckDB-WASM init, e.g. to
|
|
20
|
+
* keep the AsyncDuckDB instance for registerFileBuffer:
|
|
21
|
+
* const { queryEngine } = initFjader(connectionFrom(await db.connect()));
|
|
11
22
|
*/
|
|
12
|
-
export declare function initFjader(
|
|
23
|
+
export declare function initFjader(init?: FjaderInit): {
|
|
13
24
|
queryEngine: import("./query-engine").QueryEngine;
|
|
14
25
|
filterGroups: import("./query-engine").FilterGroupRegistry;
|
|
15
26
|
scaleRegistry: import("./scales").ScaleRegistry;
|
|
@@ -20,6 +31,8 @@ export * from './transforms';
|
|
|
20
31
|
export * from './sql/bin';
|
|
21
32
|
export * from './types';
|
|
22
33
|
export { getFilterGroups } from './query-engine';
|
|
34
|
+
export { connectionFrom } from './query-engine';
|
|
35
|
+
export type { DuckDBConnection, InitDuckDBOptions } from './query-engine';
|
|
23
36
|
export type { FilterGroupRegistry, FilterGroup, FilterItem } from './query-engine';
|
|
24
37
|
export { Fjader, render } from './spec';
|
|
25
38
|
export type { FjaderSpec, FjaderHandle, RenderOptions } from './spec';
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
import { setScaleRegistry } from './scales';
|
|
3
3
|
import { setGPUCoordinator } from './gpu';
|
|
4
4
|
import { setQueryContext, initDuckDB } from './query-engine';
|
|
5
|
+
/** A connection has query(), a promise has then(); anything else is options. */
|
|
6
|
+
function toOptions(init) {
|
|
7
|
+
if (!init)
|
|
8
|
+
return {};
|
|
9
|
+
if ('query' in init || 'then' in init)
|
|
10
|
+
return { connection: init };
|
|
11
|
+
return init;
|
|
12
|
+
}
|
|
5
13
|
/**
|
|
6
14
|
* Initialize Fjader: QueryEngine, ScaleRegistry, GPUCoordinator, FilterGroups.
|
|
7
15
|
* Call once in a top-level component's <script> block.
|
|
@@ -9,11 +17,16 @@ import { setQueryContext, initDuckDB } from './query-engine';
|
|
|
9
17
|
* import { initFjader } from '@fjader/svelte';
|
|
10
18
|
* const { queryEngine } = initFjader();
|
|
11
19
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
20
|
+
* Serve the DuckDB-WASM bundles yourself instead of loading them from jsDelivr:
|
|
21
|
+
* initFjader({ bundles: { mvp: { mainModule, mainWorker }, eh: { … } } });
|
|
22
|
+
*
|
|
23
|
+
* Pass an existing DuckDB connection to skip internal DuckDB-WASM init, e.g. to
|
|
24
|
+
* keep the AsyncDuckDB instance for registerFileBuffer:
|
|
25
|
+
* const { queryEngine } = initFjader(connectionFrom(await db.connect()));
|
|
14
26
|
*/
|
|
15
|
-
export function initFjader(
|
|
16
|
-
const {
|
|
27
|
+
export function initFjader(init) {
|
|
28
|
+
const { connection, bundles, engine } = toOptions(init);
|
|
29
|
+
const { queryEngine, filterGroups } = setQueryContext(connection ?? initDuckDB({ bundles }), engine);
|
|
17
30
|
const scaleRegistry = setScaleRegistry();
|
|
18
31
|
const gpuCoordinator = setGPUCoordinator({ msaaSampleCount: 4 });
|
|
19
32
|
gpuCoordinator.setScaleRegistry(scaleRegistry);
|
|
@@ -28,5 +41,7 @@ export * from './types';
|
|
|
28
41
|
// Selection state: the registry initFjader() returns, for code outside it (is anything
|
|
29
42
|
// brushed? which groups exist?). Read from the same context.
|
|
30
43
|
export { getFilterGroups } from './query-engine';
|
|
44
|
+
// DuckDB: adapt an app-owned duckdb-wasm connection for initFjader(connection).
|
|
45
|
+
export { connectionFrom } from './query-engine';
|
|
31
46
|
// Spec-driven rendering API
|
|
32
47
|
export { Fjader, render } from './spec';
|
|
@@ -186,7 +186,16 @@ export class TooltipManager {
|
|
|
186
186
|
if (!sql)
|
|
187
187
|
continue;
|
|
188
188
|
queryPromises.push(this.#queryEngine
|
|
189
|
-
|
|
189
|
+
// Per mark: one shared caller id let execute-latest drop every
|
|
190
|
+
// lookup but the first and last when a chart has three or more
|
|
191
|
+
// marks. Not cached: the tooltip table is rewritten on each data
|
|
192
|
+
// change while a lookup's SQL can stay the same.
|
|
193
|
+
.query(sql, {
|
|
194
|
+
callerId: `tooltip:${markId}`,
|
|
195
|
+
cache: false,
|
|
196
|
+
// The tooltip table is TEMP, on the main connection.
|
|
197
|
+
connectionLocal: true
|
|
198
|
+
})
|
|
190
199
|
.then((result) => {
|
|
191
200
|
const rows = Array.from(result);
|
|
192
201
|
return rows.length > 0 ? { markId, config, rows } : null;
|
|
@@ -500,10 +509,6 @@ export class TooltipManager {
|
|
|
500
509
|
// =========================================================================
|
|
501
510
|
// LABEL RESOLUTION
|
|
502
511
|
// =========================================================================
|
|
503
|
-
/**
|
|
504
|
-
* Resolve band indices, time offsets, and color indices back to human-readable labels.
|
|
505
|
-
* Renames columns from aliases (x, y, color) to original field names.
|
|
506
|
-
*/
|
|
507
512
|
/** Resolve a single value: band index → label, time offset → formatted date. */
|
|
508
513
|
#resolveValue(axis, key, value, config) {
|
|
509
514
|
const { scaleTypes, bandDomains } = config;
|
|
@@ -534,6 +539,10 @@ export class TooltipManager {
|
|
|
534
539
|
}
|
|
535
540
|
return String(value ?? '');
|
|
536
541
|
}
|
|
542
|
+
/**
|
|
543
|
+
* Resolve band indices, time offsets, and color indices back to human-readable labels.
|
|
544
|
+
* Renames columns from aliases (x, y, color) to original field names.
|
|
545
|
+
*/
|
|
537
546
|
#resolveLabels(row, config) {
|
|
538
547
|
const { scaleTypes, channelFields, bandDomains, colorDomain, columns } = config;
|
|
539
548
|
const point = {};
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* Both are computed when asked, from the clause, the mark's route and the
|
|
17
17
|
* live registrations: nothing is cached, so nothing goes stale.
|
|
18
18
|
*/
|
|
19
|
+
import type { SelectionResponse } from '../components/rendering/activity-reads';
|
|
19
20
|
import type { ExprNode } from '@uwdata/mosaic-sql';
|
|
20
21
|
export type FilterExpr = ExprNode | string | boolean;
|
|
21
22
|
export interface FilterSchema {
|
|
@@ -53,12 +54,13 @@ export interface Clause {
|
|
|
53
54
|
* faceted chart's cells have ids of their own, but share one config.
|
|
54
55
|
* `response` 'filter' makes every selection filter the mark, and a click
|
|
55
56
|
* control's (a discrete registration's) in its own chart too; brushes still
|
|
56
|
-
* skip their own chart.
|
|
57
|
+
* skip their own chart. 'ignore' makes no selection filter it, so no clause
|
|
58
|
+
* change re-queries it and preagg builds nothing for it.
|
|
57
59
|
*/
|
|
58
60
|
export interface MarkRoute {
|
|
59
61
|
readonly reads: readonly string[];
|
|
60
62
|
readonly chart: object | null;
|
|
61
|
-
readonly response?:
|
|
63
|
+
readonly response?: SelectionResponse;
|
|
62
64
|
}
|
|
63
65
|
/**
|
|
64
66
|
* What routing reads from one registration of a source: the chart it skips,
|
|
@@ -57,6 +57,8 @@ export class CrossfilterSelection {
|
|
|
57
57
|
* no allocation, as it runs per mark per clause on every brush frame.
|
|
58
58
|
*/
|
|
59
59
|
filters(route, sourceId, key) {
|
|
60
|
+
if (route.response === 'ignore')
|
|
61
|
+
return false;
|
|
60
62
|
if (key !== null && route.reads.includes(key))
|
|
61
63
|
return false;
|
|
62
64
|
const chart = route.chart;
|