@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
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ fjader needs Chrome 149 or later, or another Chromium browser on that version. I
|
|
|
23
23
|
Three things to know before using it in a deployed app:
|
|
24
24
|
|
|
25
25
|
- **Data goes to the browser in full.** The Python widget and the Streamlit component send every column of a DataFrame to the browser, where DuckDB-WASM queries it. Anyone viewing the app can extract it, so pass only the columns you mean to show. In Jupyter, saved widget state can also store the data in the notebook file.
|
|
26
|
-
- **DuckDB-WASM loads from jsDelivr at runtime.**
|
|
26
|
+
- **DuckDB-WASM loads from jsDelivr at runtime by default.** A Svelte app can serve the files itself with `initFjader({ bundles })`, or pass a DuckDB instance it already has with `initFjader(connectionFrom(conn))`. `Fjader.render()`, the Python widget and Streamlit always load from the CDN, so locked-down networks that block it cannot render charts with them.
|
|
27
27
|
- **Specs are trusted input.** A spec's `{ "sql": "…" }` field runs as raw SQL in the viewer's DuckDB-WASM. Don't build specs from untrusted input.
|
|
28
28
|
|
|
29
29
|
## Svelte
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holds a chart's entrance until its marks have their first data.
|
|
3
|
+
*
|
|
4
|
+
* Without it the entrance started on the FIRST mark to land, which had two
|
|
5
|
+
* visible costs. Later marks widened the domain while the chart was entering,
|
|
6
|
+
* so its axes animated to the wider extent: a small version of the camera
|
|
7
|
+
* move the entrance replaced. And a mark landing mid-entrance joined it part
|
|
8
|
+
* way. Held, every mark's extent is in before anything shows, the scales snap
|
|
9
|
+
* to the union, and the chart enters once, whole.
|
|
10
|
+
*
|
|
11
|
+
* A data mark HOLDS when it mounts and RELEASES when its first result has
|
|
12
|
+
* registered its domains, when its query fails, or when it unmounts. A mark
|
|
13
|
+
* the author knows to be slow can opt out (`holdsEntrance={false}`): it then
|
|
14
|
+
* joins the entrance where it is, or appears after it.
|
|
15
|
+
*
|
|
16
|
+
* Nothing needs hiding while a chart waits: before its entrance starts the
|
|
17
|
+
* chart's entrance value is 0, so marks that have landed draw at zero height
|
|
18
|
+
* or zero opacity.
|
|
19
|
+
*
|
|
20
|
+
* OPEN is one-way. The gate opens when its last hold is released, when the
|
|
21
|
+
* coordinator starts the entrance (a chart that never had a hold), or on a
|
|
22
|
+
* timeout. The timeout is armed by the FIRST release rather than the first
|
|
23
|
+
* hold: the wait for DuckDB to start, which can take seconds on a cold page,
|
|
24
|
+
* is not a slow mark, and a chart that has not answered at all has nothing to
|
|
25
|
+
* show yet anyway. A gate stuck closed would leave a chart invisible, which is
|
|
26
|
+
* far worse than a late mark appearing after the rest.
|
|
27
|
+
*/
|
|
28
|
+
/** How long the rest of a chart waits for its slowest mark, from its fastest. */
|
|
29
|
+
export declare const ENTRANCE_HOLD_TIMEOUT_MS = 1000;
|
|
30
|
+
export declare class EntranceGate {
|
|
31
|
+
#private;
|
|
32
|
+
/** True once the gate has opened. One-way. */
|
|
33
|
+
get opened(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Whether the chart may enter: open, or nothing is holding it. The second
|
|
36
|
+
* covers a chart with no data marks, which the coordinator then opens.
|
|
37
|
+
*/
|
|
38
|
+
get canEnter(): boolean;
|
|
39
|
+
/** A mark waits on its first data. Ignored once the gate has opened. */
|
|
40
|
+
hold(id: string): void;
|
|
41
|
+
/** A mark has its first data, failed, or left. Unknown ids are ignored. */
|
|
42
|
+
release(id: string): void;
|
|
43
|
+
/** Open now, and tell whoever is waiting. Idempotent. */
|
|
44
|
+
open(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Run `fn` when the gate opens: at once if it already has. Returns an
|
|
47
|
+
* unsubscribe, for a listener torn down first.
|
|
48
|
+
*/
|
|
49
|
+
onOpen(fn: () => void): () => void;
|
|
50
|
+
/** Drop the timer and listeners, for a chart being torn down. */
|
|
51
|
+
destroy(): void;
|
|
52
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holds a chart's entrance until its marks have their first data.
|
|
3
|
+
*
|
|
4
|
+
* Without it the entrance started on the FIRST mark to land, which had two
|
|
5
|
+
* visible costs. Later marks widened the domain while the chart was entering,
|
|
6
|
+
* so its axes animated to the wider extent: a small version of the camera
|
|
7
|
+
* move the entrance replaced. And a mark landing mid-entrance joined it part
|
|
8
|
+
* way. Held, every mark's extent is in before anything shows, the scales snap
|
|
9
|
+
* to the union, and the chart enters once, whole.
|
|
10
|
+
*
|
|
11
|
+
* A data mark HOLDS when it mounts and RELEASES when its first result has
|
|
12
|
+
* registered its domains, when its query fails, or when it unmounts. A mark
|
|
13
|
+
* the author knows to be slow can opt out (`holdsEntrance={false}`): it then
|
|
14
|
+
* joins the entrance where it is, or appears after it.
|
|
15
|
+
*
|
|
16
|
+
* Nothing needs hiding while a chart waits: before its entrance starts the
|
|
17
|
+
* chart's entrance value is 0, so marks that have landed draw at zero height
|
|
18
|
+
* or zero opacity.
|
|
19
|
+
*
|
|
20
|
+
* OPEN is one-way. The gate opens when its last hold is released, when the
|
|
21
|
+
* coordinator starts the entrance (a chart that never had a hold), or on a
|
|
22
|
+
* timeout. The timeout is armed by the FIRST release rather than the first
|
|
23
|
+
* hold: the wait for DuckDB to start, which can take seconds on a cold page,
|
|
24
|
+
* is not a slow mark, and a chart that has not answered at all has nothing to
|
|
25
|
+
* show yet anyway. A gate stuck closed would leave a chart invisible, which is
|
|
26
|
+
* far worse than a late mark appearing after the rest.
|
|
27
|
+
*/
|
|
28
|
+
/** How long the rest of a chart waits for its slowest mark, from its fastest. */
|
|
29
|
+
export const ENTRANCE_HOLD_TIMEOUT_MS = 1000;
|
|
30
|
+
export class EntranceGate {
|
|
31
|
+
#holds = new Set();
|
|
32
|
+
#opened = false;
|
|
33
|
+
#timer = null;
|
|
34
|
+
#listeners = new Set();
|
|
35
|
+
/** True once the gate has opened. One-way. */
|
|
36
|
+
get opened() {
|
|
37
|
+
return this.#opened;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Whether the chart may enter: open, or nothing is holding it. The second
|
|
41
|
+
* covers a chart with no data marks, which the coordinator then opens.
|
|
42
|
+
*/
|
|
43
|
+
get canEnter() {
|
|
44
|
+
return this.#opened || this.#holds.size === 0;
|
|
45
|
+
}
|
|
46
|
+
/** A mark waits on its first data. Ignored once the gate has opened. */
|
|
47
|
+
hold(id) {
|
|
48
|
+
if (this.#opened)
|
|
49
|
+
return;
|
|
50
|
+
this.#holds.add(id);
|
|
51
|
+
}
|
|
52
|
+
/** A mark has its first data, failed, or left. Unknown ids are ignored. */
|
|
53
|
+
release(id) {
|
|
54
|
+
if (!this.#holds.delete(id))
|
|
55
|
+
return;
|
|
56
|
+
if (this.#holds.size === 0)
|
|
57
|
+
this.open();
|
|
58
|
+
else
|
|
59
|
+
this.#timer ??= setTimeout(() => this.open(), ENTRANCE_HOLD_TIMEOUT_MS);
|
|
60
|
+
}
|
|
61
|
+
/** Open now, and tell whoever is waiting. Idempotent. */
|
|
62
|
+
open() {
|
|
63
|
+
if (this.#opened)
|
|
64
|
+
return;
|
|
65
|
+
this.#opened = true;
|
|
66
|
+
this.#holds.clear();
|
|
67
|
+
if (this.#timer !== null)
|
|
68
|
+
clearTimeout(this.#timer);
|
|
69
|
+
this.#timer = null;
|
|
70
|
+
for (const fn of this.#listeners)
|
|
71
|
+
fn();
|
|
72
|
+
this.#listeners.clear();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Run `fn` when the gate opens: at once if it already has. Returns an
|
|
76
|
+
* unsubscribe, for a listener torn down first.
|
|
77
|
+
*/
|
|
78
|
+
onOpen(fn) {
|
|
79
|
+
if (this.#opened) {
|
|
80
|
+
fn();
|
|
81
|
+
return () => { };
|
|
82
|
+
}
|
|
83
|
+
this.#listeners.add(fn);
|
|
84
|
+
return () => this.#listeners.delete(fn);
|
|
85
|
+
}
|
|
86
|
+
/** Drop the timer and listeners, for a chart being torn down. */
|
|
87
|
+
destroy() {
|
|
88
|
+
if (this.#timer !== null)
|
|
89
|
+
clearTimeout(this.#timer);
|
|
90
|
+
this.#timer = null;
|
|
91
|
+
this.#listeners.clear();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stiffness for a data-update transition, and how to think about changing it.
|
|
3
|
+
*
|
|
4
|
+
* The spring is critically damped, so it never overshoots and the only knob is
|
|
5
|
+
* how fast it converges: `omega = sqrt(stiffness)`, and it is within about a
|
|
6
|
+
* percent of its destination after roughly `5 / omega` seconds. So 120 settles
|
|
7
|
+
* in ~0.46s, 240 in ~0.32s, 400 in ~0.25s. Quadrupling the number halves the
|
|
8
|
+
* time; it is not a linear dial.
|
|
9
|
+
*
|
|
10
|
+
* ONE NUMBER FOR SLOT AND VALUE, deliberately. A bar's position and its height
|
|
11
|
+
* share a clock so they arrive together — separate springs desynchronise on a
|
|
12
|
+
* retarget and the bar reaches its rank visibly before or after it finishes
|
|
13
|
+
* growing. Wanting them to differ is a design change, not a constant.
|
|
14
|
+
*
|
|
15
|
+
* Note this is also the floor on how responsive a drag feels: `t` is
|
|
16
|
+
* normalised, so settle DURATION is independent of distance and a one-pixel
|
|
17
|
+
* correction takes as long as a full reorder.
|
|
18
|
+
*/
|
|
19
|
+
export declare const TRANSITION_STIFFNESS = 240;
|
|
20
|
+
/**
|
|
21
|
+
* Stiffness for an entrance FADE: softer than a transition, since opacity has
|
|
22
|
+
* no distance to travel and a fast fade reads as a pop. From rest it is a
|
|
23
|
+
* gentle S-curve: 30% at 100 ms, 64% at 200 ms, 96% by 450 ms.
|
|
24
|
+
*/
|
|
25
|
+
export declare const ENTRANCE_FADE_STIFFNESS = 120;
|
|
26
|
+
/**
|
|
27
|
+
* Stiffness for an entrance's GROWTH, launched at its own speed (see
|
|
28
|
+
* `restartEaseOut`): 1 − e^(−ωt), 67% at 100 ms, 89% at 200 ms, 96% by 300 ms.
|
|
29
|
+
* Slower than a data transition: growing from nothing is the chart's first
|
|
30
|
+
* statement of magnitude, and at TRANSITION_STIFFNESS it was over before the
|
|
31
|
+
* eye had settled on it.
|
|
32
|
+
*/
|
|
33
|
+
export declare const ENTRANCE_GROW_STIFFNESS = 120;
|
|
34
|
+
/** Sub-pixel, so rest is declared when the difference stops being visible. */
|
|
35
|
+
export declare const TRANSITION_PRECISION = 0.0005;
|
|
36
|
+
/**
|
|
37
|
+
* The animation loop, as a clock sees it. The coordinator's fixed-timestep loop
|
|
38
|
+
* is the one implementation; structurally typed so this module does not import
|
|
39
|
+
* the GPU layer.
|
|
40
|
+
*/
|
|
41
|
+
export interface ClockLoop {
|
|
42
|
+
addAnimatedSource(source: TransitionClock): void;
|
|
43
|
+
removeAnimatedSource(source: TransitionClock): void;
|
|
44
|
+
}
|
|
1
45
|
/**
|
|
2
46
|
* One data-update transition: a spring driving a normalised `t` from 0 to 1.
|
|
3
47
|
*
|
|
@@ -36,36 +80,6 @@
|
|
|
36
80
|
* restarting it every frame makes this consumer re-fold every frame, so it
|
|
37
81
|
* converges by decelerating rather than along the curve the spring intended.
|
|
38
82
|
*/
|
|
39
|
-
/**
|
|
40
|
-
* Stiffness for a data-update transition, and how to think about changing it.
|
|
41
|
-
*
|
|
42
|
-
* The spring is critically damped, so it never overshoots and the only knob is
|
|
43
|
-
* how fast it converges: `omega = sqrt(stiffness)`, and it is within about a
|
|
44
|
-
* percent of its destination after roughly `5 / omega` seconds. So 120 settles
|
|
45
|
-
* in ~0.46s, 240 in ~0.32s, 400 in ~0.25s. Quadrupling the number halves the
|
|
46
|
-
* time; it is not a linear dial.
|
|
47
|
-
*
|
|
48
|
-
* ONE NUMBER FOR SLOT AND VALUE, deliberately. A bar's position and its height
|
|
49
|
-
* share a clock so they arrive together — separate springs desynchronise on a
|
|
50
|
-
* retarget and the bar reaches its rank visibly before or after it finishes
|
|
51
|
-
* growing. Wanting them to differ is a design change, not a constant.
|
|
52
|
-
*
|
|
53
|
-
* Note this is also the floor on how responsive a drag feels: `t` is
|
|
54
|
-
* normalised, so settle DURATION is independent of distance and a one-pixel
|
|
55
|
-
* correction takes as long as a full reorder.
|
|
56
|
-
*/
|
|
57
|
-
export declare const TRANSITION_STIFFNESS = 240;
|
|
58
|
-
/** Sub-pixel, so rest is declared when the difference stops being visible. */
|
|
59
|
-
export declare const TRANSITION_PRECISION = 0.0005;
|
|
60
|
-
/**
|
|
61
|
-
* The animation loop, as a clock sees it. The coordinator's fixed-timestep loop
|
|
62
|
-
* is the one implementation; structurally typed so this module does not import
|
|
63
|
-
* the GPU layer.
|
|
64
|
-
*/
|
|
65
|
-
export interface ClockLoop {
|
|
66
|
-
addAnimatedSource(source: TransitionClock): void;
|
|
67
|
-
removeAnimatedSource(source: TransitionClock): void;
|
|
68
|
-
}
|
|
69
83
|
export declare class TransitionClock {
|
|
70
84
|
#private;
|
|
71
85
|
/**
|
|
@@ -106,6 +120,17 @@ export declare class TransitionClock {
|
|
|
106
120
|
* returns — the same end state the loop would reach, reached now.
|
|
107
121
|
*/
|
|
108
122
|
restart(): void;
|
|
123
|
+
/**
|
|
124
|
+
* Begin from 0 at the spring's own speed, ω, for an ENTRANCE.
|
|
125
|
+
*
|
|
126
|
+
* From rest a critically damped spring starts slowly: its position grows as
|
|
127
|
+
* t² for the first frames, about 18% after 50 ms at the default stiffness.
|
|
128
|
+
* Between two states that is the right shape; for something appearing it
|
|
129
|
+
* reads as lag. Launched at ω the same spring follows 1 − e^(−ωt) exactly:
|
|
130
|
+
* an exponential ease-out, 54% after 50 ms, still never overshooting, and
|
|
131
|
+
* settling in the same time.
|
|
132
|
+
*/
|
|
133
|
+
restartEaseOut(): void;
|
|
109
134
|
/**
|
|
110
135
|
* Leave the loop without settling, for an owner being torn down. Nothing
|
|
111
136
|
* reads the clock afterwards, so there is nothing to land.
|
|
@@ -153,6 +178,12 @@ export declare class TransitionClock {
|
|
|
153
178
|
* then tells the owner — which may restart it, and rejoin, from `onRest`.
|
|
154
179
|
*/
|
|
155
180
|
_markAtRest(): void;
|
|
181
|
+
/**
|
|
182
|
+
* End the transition now, as the loop would at rest: off the loop, `t` on
|
|
183
|
+
* 1, `onRest` called. For an update that lands without animating (a
|
|
184
|
+
* catch-up result), including one that lands mid-flight.
|
|
185
|
+
*/
|
|
186
|
+
finish(): void;
|
|
156
187
|
/**
|
|
157
188
|
* Whether the loop can stop driving this.
|
|
158
189
|
*
|
|
@@ -1,4 +1,47 @@
|
|
|
1
1
|
import { Spring } from './Spring';
|
|
2
|
+
/**
|
|
3
|
+
* Stiffness for a data-update transition, and how to think about changing it.
|
|
4
|
+
*
|
|
5
|
+
* The spring is critically damped, so it never overshoots and the only knob is
|
|
6
|
+
* how fast it converges: `omega = sqrt(stiffness)`, and it is within about a
|
|
7
|
+
* percent of its destination after roughly `5 / omega` seconds. So 120 settles
|
|
8
|
+
* in ~0.46s, 240 in ~0.32s, 400 in ~0.25s. Quadrupling the number halves the
|
|
9
|
+
* time; it is not a linear dial.
|
|
10
|
+
*
|
|
11
|
+
* ONE NUMBER FOR SLOT AND VALUE, deliberately. A bar's position and its height
|
|
12
|
+
* share a clock so they arrive together — separate springs desynchronise on a
|
|
13
|
+
* retarget and the bar reaches its rank visibly before or after it finishes
|
|
14
|
+
* growing. Wanting them to differ is a design change, not a constant.
|
|
15
|
+
*
|
|
16
|
+
* Note this is also the floor on how responsive a drag feels: `t` is
|
|
17
|
+
* normalised, so settle DURATION is independent of distance and a one-pixel
|
|
18
|
+
* correction takes as long as a full reorder.
|
|
19
|
+
*/
|
|
20
|
+
export const TRANSITION_STIFFNESS = 240;
|
|
21
|
+
/**
|
|
22
|
+
* Stiffness for an entrance FADE: softer than a transition, since opacity has
|
|
23
|
+
* no distance to travel and a fast fade reads as a pop. From rest it is a
|
|
24
|
+
* gentle S-curve: 30% at 100 ms, 64% at 200 ms, 96% by 450 ms.
|
|
25
|
+
*/
|
|
26
|
+
export const ENTRANCE_FADE_STIFFNESS = 120;
|
|
27
|
+
/**
|
|
28
|
+
* Stiffness for an entrance's GROWTH, launched at its own speed (see
|
|
29
|
+
* `restartEaseOut`): 1 − e^(−ωt), 67% at 100 ms, 89% at 200 ms, 96% by 300 ms.
|
|
30
|
+
* Slower than a data transition: growing from nothing is the chart's first
|
|
31
|
+
* statement of magnitude, and at TRANSITION_STIFFNESS it was over before the
|
|
32
|
+
* eye had settled on it.
|
|
33
|
+
*/
|
|
34
|
+
export const ENTRANCE_GROW_STIFFNESS = 120;
|
|
35
|
+
/** Sub-pixel, so rest is declared when the difference stops being visible. */
|
|
36
|
+
export const TRANSITION_PRECISION = 0.0005;
|
|
37
|
+
/**
|
|
38
|
+
* The empty redraw list, shared.
|
|
39
|
+
*
|
|
40
|
+
* A clock whose owner supplies no charts is asked for this on every frame the
|
|
41
|
+
* loop drives it, so a fresh array here was an allocation per frame for a
|
|
42
|
+
* reason that never changes. Never mutated: the loop only reads it.
|
|
43
|
+
*/
|
|
44
|
+
const NO_CHARTS = [];
|
|
2
45
|
/**
|
|
3
46
|
* One data-update transition: a spring driving a normalised `t` from 0 to 1.
|
|
4
47
|
*
|
|
@@ -37,35 +80,6 @@ import { Spring } from './Spring';
|
|
|
37
80
|
* restarting it every frame makes this consumer re-fold every frame, so it
|
|
38
81
|
* converges by decelerating rather than along the curve the spring intended.
|
|
39
82
|
*/
|
|
40
|
-
/**
|
|
41
|
-
* Stiffness for a data-update transition, and how to think about changing it.
|
|
42
|
-
*
|
|
43
|
-
* The spring is critically damped, so it never overshoots and the only knob is
|
|
44
|
-
* how fast it converges: `omega = sqrt(stiffness)`, and it is within about a
|
|
45
|
-
* percent of its destination after roughly `5 / omega` seconds. So 120 settles
|
|
46
|
-
* in ~0.46s, 240 in ~0.32s, 400 in ~0.25s. Quadrupling the number halves the
|
|
47
|
-
* time; it is not a linear dial.
|
|
48
|
-
*
|
|
49
|
-
* ONE NUMBER FOR SLOT AND VALUE, deliberately. A bar's position and its height
|
|
50
|
-
* share a clock so they arrive together — separate springs desynchronise on a
|
|
51
|
-
* retarget and the bar reaches its rank visibly before or after it finishes
|
|
52
|
-
* growing. Wanting them to differ is a design change, not a constant.
|
|
53
|
-
*
|
|
54
|
-
* Note this is also the floor on how responsive a drag feels: `t` is
|
|
55
|
-
* normalised, so settle DURATION is independent of distance and a one-pixel
|
|
56
|
-
* correction takes as long as a full reorder.
|
|
57
|
-
*/
|
|
58
|
-
export const TRANSITION_STIFFNESS = 240;
|
|
59
|
-
/** Sub-pixel, so rest is declared when the difference stops being visible. */
|
|
60
|
-
export const TRANSITION_PRECISION = 0.0005;
|
|
61
|
-
/**
|
|
62
|
-
* The empty redraw list, shared.
|
|
63
|
-
*
|
|
64
|
-
* A clock whose owner supplies no charts is asked for this on every frame the
|
|
65
|
-
* loop drives it, so a fresh array here was an allocation per frame for a
|
|
66
|
-
* reason that never changes. Never mutated: the loop only reads it.
|
|
67
|
-
*/
|
|
68
|
-
const NO_CHARTS = [];
|
|
69
83
|
export class TransitionClock {
|
|
70
84
|
#spring;
|
|
71
85
|
#precision;
|
|
@@ -149,6 +163,20 @@ export class TransitionClock {
|
|
|
149
163
|
this.#loop.addAnimatedSource(this);
|
|
150
164
|
}
|
|
151
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Begin from 0 at the spring's own speed, ω, for an ENTRANCE.
|
|
168
|
+
*
|
|
169
|
+
* From rest a critically damped spring starts slowly: its position grows as
|
|
170
|
+
* t² for the first frames, about 18% after 50 ms at the default stiffness.
|
|
171
|
+
* Between two states that is the right shape; for something appearing it
|
|
172
|
+
* reads as lag. Launched at ω the same spring follows 1 − e^(−ωt) exactly:
|
|
173
|
+
* an exponential ease-out, 54% after 50 ms, still never overshooting, and
|
|
174
|
+
* settling in the same time.
|
|
175
|
+
*/
|
|
176
|
+
restartEaseOut() {
|
|
177
|
+
this.restart();
|
|
178
|
+
this.#spring.velocity = this.#spring.omega;
|
|
179
|
+
}
|
|
152
180
|
/**
|
|
153
181
|
* Leave the loop without settling, for an owner being torn down. Nothing
|
|
154
182
|
* reads the clock afterwards, so there is nothing to land.
|
|
@@ -218,6 +246,16 @@ export class TransitionClock {
|
|
|
218
246
|
this.settle();
|
|
219
247
|
this.#onRest?.();
|
|
220
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* End the transition now, as the loop would at rest: off the loop, `t` on
|
|
251
|
+
* 1, `onRest` called. For an update that lands without animating (a
|
|
252
|
+
* catch-up result), including one that lands mid-flight.
|
|
253
|
+
*/
|
|
254
|
+
finish() {
|
|
255
|
+
if (this.#running)
|
|
256
|
+
this.#loop?.removeAnimatedSource(this);
|
|
257
|
+
this._markAtRest();
|
|
258
|
+
}
|
|
221
259
|
/**
|
|
222
260
|
* Whether the loop can stop driving this.
|
|
223
261
|
*
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A debug record of every transition that starts, and whether it animates.
|
|
3
|
+
*
|
|
4
|
+
* For test pages that check an animation claim, such as "a chart catching up
|
|
5
|
+
* on its return animates nothing of its own", without reading it off the
|
|
6
|
+
* screen: a spring that plays off screen and two springs to one place both
|
|
7
|
+
* look like nothing happened. Reported where a transition actually starts:
|
|
8
|
+
*
|
|
9
|
+
* - `domain`: a continuous scale's domain (data extents and fitDomain alike)
|
|
10
|
+
* - `morph`: a mark's morphed values (bars, dots on a band axis)
|
|
11
|
+
* - `slots`: a band axis's categories moving to new slots
|
|
12
|
+
*
|
|
13
|
+
* The cause is not recorded: the emitters do not know it, and the page does
|
|
14
|
+
* (it knows what it just did).
|
|
15
|
+
*
|
|
16
|
+
* MODULE-GLOBAL: one log per page (per module instance), not per coordinator.
|
|
17
|
+
* Two coordinators in one document (notebook cells, Streamlit iframes sharing
|
|
18
|
+
* it) see each other's records; filter by chart id. Fine for a debug hook.
|
|
19
|
+
*
|
|
20
|
+
* Free while nothing observes: each emitter asks `transitionsObserved()`
|
|
21
|
+
* before building a record.
|
|
22
|
+
*/
|
|
23
|
+
export type TransitionPart = 'domain' | 'morph' | 'slots';
|
|
24
|
+
export interface TransitionRecord {
|
|
25
|
+
part: TransitionPart;
|
|
26
|
+
/** True when it landed at once, false when it animates. */
|
|
27
|
+
snap: boolean;
|
|
28
|
+
/** The charts it moves something in. */
|
|
29
|
+
charts: readonly string[];
|
|
30
|
+
/** What moved: a scale name, a renderer label. */
|
|
31
|
+
label: string;
|
|
32
|
+
/** performance.now() */
|
|
33
|
+
time: number;
|
|
34
|
+
}
|
|
35
|
+
/** Listen to every transition started; returns the unsubscribe. */
|
|
36
|
+
export declare function observeTransitions(listener: (record: TransitionRecord) => void): () => void;
|
|
37
|
+
/** Whether anything listens: emitters check this before building a record. */
|
|
38
|
+
export declare function transitionsObserved(): boolean;
|
|
39
|
+
export declare function noteTransition(part: TransitionPart, snap: boolean,
|
|
40
|
+
/** Read untracked, now: the emitter is usually inside an effect. */
|
|
41
|
+
charts: () => readonly string[], label: string): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A debug record of every transition that starts, and whether it animates.
|
|
3
|
+
*
|
|
4
|
+
* For test pages that check an animation claim, such as "a chart catching up
|
|
5
|
+
* on its return animates nothing of its own", without reading it off the
|
|
6
|
+
* screen: a spring that plays off screen and two springs to one place both
|
|
7
|
+
* look like nothing happened. Reported where a transition actually starts:
|
|
8
|
+
*
|
|
9
|
+
* - `domain`: a continuous scale's domain (data extents and fitDomain alike)
|
|
10
|
+
* - `morph`: a mark's morphed values (bars, dots on a band axis)
|
|
11
|
+
* - `slots`: a band axis's categories moving to new slots
|
|
12
|
+
*
|
|
13
|
+
* The cause is not recorded: the emitters do not know it, and the page does
|
|
14
|
+
* (it knows what it just did).
|
|
15
|
+
*
|
|
16
|
+
* MODULE-GLOBAL: one log per page (per module instance), not per coordinator.
|
|
17
|
+
* Two coordinators in one document (notebook cells, Streamlit iframes sharing
|
|
18
|
+
* it) see each other's records; filter by chart id. Fine for a debug hook.
|
|
19
|
+
*
|
|
20
|
+
* Free while nothing observes: each emitter asks `transitionsObserved()`
|
|
21
|
+
* before building a record.
|
|
22
|
+
*/
|
|
23
|
+
import { untrack } from 'svelte';
|
|
24
|
+
const observers = new Set();
|
|
25
|
+
/** Noted and not yet delivered: see noteTransition. */
|
|
26
|
+
let queued = [];
|
|
27
|
+
/** Listen to every transition started; returns the unsubscribe. */
|
|
28
|
+
export function observeTransitions(listener) {
|
|
29
|
+
observers.add(listener);
|
|
30
|
+
return () => observers.delete(listener);
|
|
31
|
+
}
|
|
32
|
+
/** Whether anything listens: emitters check this before building a record. */
|
|
33
|
+
export function transitionsObserved() {
|
|
34
|
+
return observers.size > 0;
|
|
35
|
+
}
|
|
36
|
+
export function noteTransition(part, snap,
|
|
37
|
+
/** Read untracked, now: the emitter is usually inside an effect. */
|
|
38
|
+
charts, label) {
|
|
39
|
+
if (observers.size === 0)
|
|
40
|
+
return;
|
|
41
|
+
const list = untrack(() => [...charts()]);
|
|
42
|
+
queued.push({ part, snap, charts: list, label, time: performance.now() });
|
|
43
|
+
// Delivered in a microtask, never in the emitter's own call: transitions
|
|
44
|
+
// start inside effects (a renderer's write, a scale update), and a
|
|
45
|
+
// listener run there that reads state makes that effect depend on it. A
|
|
46
|
+
// page appending to a $state log then re-ran the emitting effect on every
|
|
47
|
+
// record, which logged again, and never stopped.
|
|
48
|
+
if (queued.length === 1)
|
|
49
|
+
queueMicrotask(deliver);
|
|
50
|
+
}
|
|
51
|
+
function deliver() {
|
|
52
|
+
const records = queued;
|
|
53
|
+
queued = [];
|
|
54
|
+
for (const record of records)
|
|
55
|
+
for (const observe of observers)
|
|
56
|
+
observe(record);
|
|
57
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { hasEntered } from '../../scales/entered';
|
|
2
3
|
import { getContext } from 'svelte';
|
|
3
4
|
import type { ChartContext } from '../../types';
|
|
4
5
|
import type { BandScaleState } from '../../scales/BandScaleState.svelte';
|
|
@@ -35,9 +36,10 @@
|
|
|
35
36
|
(s as ContinuousScaleState).setCustomFormat(tickFormat);
|
|
36
37
|
});
|
|
37
38
|
|
|
38
|
-
// Continuous scales (linear, time): use scaleState.getTicks()
|
|
39
|
+
// Continuous scales (linear, time): use scaleState.getTicks(). None until the
|
|
40
|
+
// scale has entered: before that its domain is a placeholder.
|
|
39
41
|
const continuousTicks = $derived(
|
|
40
|
-
c.xScaleState?.getTicks ? c.xScaleState.getTicks(ticksNumber) : []
|
|
42
|
+
c.xScaleState?.getTicks && hasEntered(c.xScaleState) ? c.xScaleState.getTicks(ticksNumber) : []
|
|
41
43
|
);
|
|
42
44
|
|
|
43
45
|
// Band scales: the categories on screen NOW, at their current fractional slots,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { hasEntered } from '../../scales/entered';
|
|
2
3
|
import { getContext } from 'svelte';
|
|
3
4
|
import type { ChartContext } from '../../types';
|
|
4
5
|
import type { BandScaleState } from '../../scales/BandScaleState.svelte';
|
|
@@ -39,9 +40,10 @@
|
|
|
39
40
|
const tickDir = $derived(labelSide === 'right' ? 1 : -1);
|
|
40
41
|
const textAnchor = $derived(labelSide === 'right' ? 'start' : 'end');
|
|
41
42
|
|
|
42
|
-
// Continuous scales (linear, time): use scaleState.getTicks()
|
|
43
|
+
// Continuous scales (linear, time): use scaleState.getTicks(). None until the
|
|
44
|
+
// scale has entered: before that its domain is a placeholder.
|
|
43
45
|
const continuousTicks = $derived(
|
|
44
|
-
c.yScaleState?.getTicks ? c.yScaleState.getTicks(ticksNumber) : []
|
|
46
|
+
c.yScaleState?.getTicks && hasEntered(c.yScaleState) ? c.yScaleState.getTicks(ticksNumber) : []
|
|
45
47
|
);
|
|
46
48
|
|
|
47
49
|
// Band scales: the categories on screen NOW, at their current fractional slots —
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { hasEntered } from '../../scales/entered';
|
|
2
3
|
import { getContext } from 'svelte';
|
|
3
4
|
import type { ChartContext } from '../../types';
|
|
4
5
|
import type { BandScaleState } from '../../scales/BandScaleState.svelte';
|
|
@@ -45,9 +46,10 @@
|
|
|
45
46
|
(s as ContinuousScaleState).setCustomFormat(tickFormat);
|
|
46
47
|
});
|
|
47
48
|
|
|
48
|
-
// Continuous scales (linear, time): use scaleState.getTicks()
|
|
49
|
+
// Continuous scales (linear, time): use scaleState.getTicks(). None until the
|
|
50
|
+
// scale has entered: before that its domain is a placeholder.
|
|
49
51
|
const continuousTicks = $derived(
|
|
50
|
-
c.yScaleState?.getTicks ? c.yScaleState.getTicks(ticksNumber) : []
|
|
52
|
+
c.yScaleState?.getTicks && hasEntered(c.yScaleState) ? c.yScaleState.getTicks(ticksNumber) : []
|
|
51
53
|
);
|
|
52
54
|
|
|
53
55
|
// Band scales: the categories on screen NOW, at their current fractional slots,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { ChartContext } from '../../types';
|
|
5
5
|
import { categoryLabel } from '../../query-engine/categorical-identity';
|
|
6
6
|
import type { FilterGroupRegistry } from '../../query-engine/FilterGroupRegistry.svelte';
|
|
7
|
+
import { dimsBySelection } from '../rendering/activity-reads';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* The labels on a band axis, wherever that axis is drawn.
|
|
@@ -71,8 +72,8 @@
|
|
|
71
72
|
if (!chart || !filterGroups) return [];
|
|
72
73
|
const indices: number[] = [];
|
|
73
74
|
for (const entry of chart.config.entries.values()) {
|
|
74
|
-
// A 'filter' mark never dims; its labels follow it.
|
|
75
|
-
if (entry.selectionResponse
|
|
75
|
+
// A 'filter' or 'ignore' mark never dims; its labels follow it.
|
|
76
|
+
if (!dimsBySelection(entry.selectionResponse)) continue;
|
|
76
77
|
const index = filterGroups.get(entry.selection).index;
|
|
77
78
|
if (!indices.includes(index)) indices.push(index);
|
|
78
79
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { ScaleGeneration } from '../../scales/ScaleGeneration.svelte';
|
|
3
|
+
import type { ChartVisibility } from './ChartVisibility.svelte';
|
|
4
|
+
import type { ChartHover } from './ChartHover.svelte';
|
|
2
5
|
import { setContext, getContext, onMount } from 'svelte';
|
|
3
6
|
import type { Snippet } from 'svelte';
|
|
4
7
|
import { ChartLayout } from '../../layout/ChartLayout.svelte';
|
|
@@ -43,6 +46,12 @@
|
|
|
43
46
|
height?: number;
|
|
44
47
|
clipPadding?: number;
|
|
45
48
|
scales: ChartScales;
|
|
49
|
+
/** The chart's scale replacements; owned by the container, which outlives its ChartScales. */
|
|
50
|
+
scaleGeneration: ScaleGeneration;
|
|
51
|
+
/** Whether any of the chart's cells is on screen; this cell reports into it. */
|
|
52
|
+
chartVisibility: ChartVisibility;
|
|
53
|
+
/** Whether the pointer is over any of the chart's cells; this cell reports into it. */
|
|
54
|
+
chartHover: ChartHover;
|
|
46
55
|
chartConfig: ChartConfig;
|
|
47
56
|
facetingProps?: FacetingProps;
|
|
48
57
|
gpuCoordinator: GPUCoordinator;
|
|
@@ -63,6 +72,9 @@
|
|
|
63
72
|
height = 250,
|
|
64
73
|
clipPadding = 0,
|
|
65
74
|
scales,
|
|
75
|
+
scaleGeneration,
|
|
76
|
+
chartVisibility,
|
|
77
|
+
chartHover,
|
|
66
78
|
chartConfig,
|
|
67
79
|
facetingProps = {
|
|
68
80
|
fx: null,
|
|
@@ -222,12 +234,15 @@
|
|
|
222
234
|
};
|
|
223
235
|
});
|
|
224
236
|
|
|
225
|
-
// --- Viewport visibility
|
|
226
|
-
const removeVisibilityCallback = gpuCoordinator.setVisibilityCallback(
|
|
227
|
-
layout.chartId,
|
|
228
|
-
(_visible) => {}
|
|
237
|
+
// --- Viewport visibility: this cell reports into the chart's ---
|
|
238
|
+
const removeVisibilityCallback = gpuCoordinator.setVisibilityCallback(layout.chartId, (visible) =>
|
|
239
|
+
chartVisibility.set(layout.chartId, visible)
|
|
229
240
|
);
|
|
230
|
-
onMount(() =>
|
|
241
|
+
onMount(() => () => {
|
|
242
|
+
removeVisibilityCallback();
|
|
243
|
+
chartVisibility.remove(layout.chartId);
|
|
244
|
+
chartHover.set(layout.chartId, false);
|
|
245
|
+
});
|
|
231
246
|
onMount(() => () => hoverSettle.leave());
|
|
232
247
|
|
|
233
248
|
// --- Scale readiness effect ---
|
|
@@ -358,6 +373,16 @@
|
|
|
358
373
|
get xScaleState() {
|
|
359
374
|
return scales?.xScaleState;
|
|
360
375
|
},
|
|
376
|
+
get scaleGeneration() {
|
|
377
|
+
return scaleGeneration.value;
|
|
378
|
+
},
|
|
379
|
+
get visible() {
|
|
380
|
+
return chartVisibility.visible;
|
|
381
|
+
},
|
|
382
|
+
shownElsewhere: (scale) => chartVisibility.shownElsewhere(scale),
|
|
383
|
+
get hovered() {
|
|
384
|
+
return chartHover.hovered;
|
|
385
|
+
},
|
|
361
386
|
get yScaleState() {
|
|
362
387
|
return scales?.yScaleState;
|
|
363
388
|
},
|
|
@@ -546,6 +571,7 @@
|
|
|
546
571
|
}
|
|
547
572
|
|
|
548
573
|
function handleChartPointerEnter(event: PointerEvent) {
|
|
574
|
+
chartHover.set(layout.chartId, true);
|
|
549
575
|
activateSources();
|
|
550
576
|
hoverSettle.enter();
|
|
551
577
|
refreshSvgOrigin();
|
|
@@ -564,6 +590,7 @@
|
|
|
564
590
|
}
|
|
565
591
|
|
|
566
592
|
function handleChartPointerLeave() {
|
|
593
|
+
chartHover.set(layout.chartId, false);
|
|
567
594
|
pointerInside = false;
|
|
568
595
|
pointerOverSvg = false;
|
|
569
596
|
hoverSettle.leave();
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { ScaleGeneration } from '../../scales/ScaleGeneration.svelte';
|
|
2
|
+
import type { ChartVisibility } from './ChartVisibility.svelte';
|
|
3
|
+
import type { ChartHover } from './ChartHover.svelte';
|
|
1
4
|
import type { Snippet } from 'svelte';
|
|
2
5
|
import type { Margin } from '../../types';
|
|
3
6
|
import type { FieldRef } from '../../types';
|
|
@@ -27,6 +30,12 @@ interface Props {
|
|
|
27
30
|
height?: number;
|
|
28
31
|
clipPadding?: number;
|
|
29
32
|
scales: ChartScales;
|
|
33
|
+
/** The chart's scale replacements; owned by the container, which outlives its ChartScales. */
|
|
34
|
+
scaleGeneration: ScaleGeneration;
|
|
35
|
+
/** Whether any of the chart's cells is on screen; this cell reports into it. */
|
|
36
|
+
chartVisibility: ChartVisibility;
|
|
37
|
+
/** Whether the pointer is over any of the chart's cells; this cell reports into it. */
|
|
38
|
+
chartHover: ChartHover;
|
|
30
39
|
chartConfig: ChartConfig;
|
|
31
40
|
facetingProps?: FacetingProps;
|
|
32
41
|
gpuCoordinator: GPUCoordinator;
|