@genome-spy/core 0.46.1 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle/index.es.js +5310 -5113
- package/dist/bundle/index.js +105 -94
- package/dist/schema.json +38 -0
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +31 -16
- package/dist/src/gl/webGLHelper.d.ts +2 -1
- package/dist/src/gl/webGLHelper.d.ts.map +1 -1
- package/dist/src/gl/webGLHelper.js +8 -1
- package/dist/src/marks/mark.d.ts +24 -12
- package/dist/src/marks/mark.d.ts.map +1 -1
- package/dist/src/marks/mark.js +27 -13
- package/dist/src/marks/point.d.ts +0 -1
- package/dist/src/marks/point.d.ts.map +1 -1
- package/dist/src/marks/point.js +6 -2
- package/dist/src/marks/text.d.ts.map +1 -1
- package/dist/src/marks/text.js +4 -1
- package/dist/src/scale/scale.js +2 -0
- package/dist/src/spec/parameter.d.ts +20 -1
- package/dist/src/types/embedApi.d.ts +7 -0
- package/dist/src/types/rendering.d.ts +6 -0
- package/dist/src/utils/animator.d.ts +17 -0
- package/dist/src/utils/animator.d.ts.map +1 -1
- package/dist/src/utils/animator.js +72 -0
- package/dist/src/utils/inertia.d.ts +6 -15
- package/dist/src/utils/inertia.d.ts.map +1 -1
- package/dist/src/utils/inertia.js +28 -63
- package/dist/src/utils/inputBinding.d.ts.map +1 -1
- package/dist/src/utils/inputBinding.js +26 -2
- package/dist/src/utils/ringBuffer.d.ts +19 -0
- package/dist/src/utils/ringBuffer.d.ts.map +1 -0
- package/dist/src/utils/ringBuffer.js +43 -0
- package/dist/src/utils/ringBuffer.test.js +39 -0
- package/dist/src/view/gridView.d.ts +6 -6
- package/dist/src/view/gridView.d.ts.map +1 -1
- package/dist/src/view/gridView.js +48 -29
- package/dist/src/view/layout/point.d.ts +17 -1
- package/dist/src/view/layout/point.d.ts.map +1 -1
- package/dist/src/view/layout/point.js +36 -1
- package/dist/src/view/unitView.d.ts +3 -14
- package/dist/src/view/unitView.d.ts.map +1 -1
- package/dist/src/view/unitView.js +26 -8
- package/dist/src/view/view.d.ts +14 -5
- package/dist/src/view/view.d.ts.map +1 -1
- package/dist/src/view/view.js +26 -7
- package/dist/src/view/zoom.d.ts +4 -10
- package/dist/src/view/zoom.d.ts.map +1 -1
- package/dist/src/view/zoom.js +126 -9
- package/package.json +2 -2
- package/dist/src/view/renderingContext/layoutRecorderViewRenderingContext.d.ts +0 -60
- package/dist/src/view/renderingContext/layoutRecorderViewRenderingContext.d.ts.map +0 -1
- package/dist/src/view/renderingContext/layoutRecorderViewRenderingContext.js +0 -128
|
@@ -4,7 +4,6 @@ import RuleMark from "../marks/rule.js";
|
|
|
4
4
|
import LinkMark from "../marks/link.js";
|
|
5
5
|
import TextMark from "../marks/text.js";
|
|
6
6
|
|
|
7
|
-
import ContainerView from "./containerView.js";
|
|
8
7
|
import ScaleResolution from "./scaleResolution.js";
|
|
9
8
|
import {
|
|
10
9
|
isSecondaryChannel,
|
|
@@ -18,6 +17,7 @@ import {
|
|
|
18
17
|
} from "../encoder/encoder.js";
|
|
19
18
|
import createDomain from "../utils/domainArray.js";
|
|
20
19
|
import AxisResolution from "./axisResolution.js";
|
|
20
|
+
import View from "./view.js";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
*
|
|
@@ -32,15 +32,20 @@ export const markTypes = {
|
|
|
32
32
|
text: TextMark,
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
export default class UnitView extends
|
|
35
|
+
export default class UnitView extends View {
|
|
36
36
|
/**
|
|
37
37
|
* @typedef {import("../spec/channel.js").Channel} Channel
|
|
38
|
-
* @typedef {import("./view.js").default} View
|
|
39
|
-
* @typedef {import("./layerView.js").default} LayerView
|
|
40
38
|
* @typedef {import("../utils/domainArray.js").DomainArray} DomainArray
|
|
41
39
|
* @typedef {import("../spec/view.js").ResolutionTarget} ResolutionTarget
|
|
42
40
|
*
|
|
43
41
|
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sets the zoom level parameter.
|
|
45
|
+
* @type {(zoomLevel: number) => void}
|
|
46
|
+
*/
|
|
47
|
+
#zoomLevelSetter;
|
|
48
|
+
|
|
44
49
|
/**
|
|
45
50
|
*
|
|
46
51
|
* @param {import("../spec/view.js").UnitSpec} spec
|
|
@@ -65,6 +70,19 @@ export default class UnitView extends ContainerView {
|
|
|
65
70
|
|
|
66
71
|
this.resolve();
|
|
67
72
|
|
|
73
|
+
this.#zoomLevelSetter = this.paramMediator.allocateSetter(
|
|
74
|
+
"zoomLevel",
|
|
75
|
+
1.0
|
|
76
|
+
);
|
|
77
|
+
/** @type {import("../spec/channel.js").ChannelWithScale[]} */ ([
|
|
78
|
+
"x",
|
|
79
|
+
"y",
|
|
80
|
+
]).forEach((channel) =>
|
|
81
|
+
this.getScaleResolution(channel)?.addEventListener("domain", () =>
|
|
82
|
+
this.#zoomLevelSetter(Math.sqrt(this.getZoomLevel()))
|
|
83
|
+
)
|
|
84
|
+
);
|
|
85
|
+
|
|
68
86
|
this.needsAxes = { x: true, y: true };
|
|
69
87
|
}
|
|
70
88
|
|
|
@@ -129,7 +147,7 @@ export default class UnitView extends ContainerView {
|
|
|
129
147
|
while (
|
|
130
148
|
(view.getConfiguredOrDefaultResolution(targetChannel, type) ==
|
|
131
149
|
"forced" ||
|
|
132
|
-
(view.dataParent
|
|
150
|
+
(view.dataParent &&
|
|
133
151
|
["shared", "excluded", "forced"].includes(
|
|
134
152
|
view.dataParent.getConfiguredOrDefaultResolution(
|
|
135
153
|
targetChannel,
|
|
@@ -220,7 +238,7 @@ export default class UnitView extends ContainerView {
|
|
|
220
238
|
/**
|
|
221
239
|
* @param {Channel} channel A primary channel
|
|
222
240
|
*/
|
|
223
|
-
|
|
241
|
+
#validateDomainQuery(channel) {
|
|
224
242
|
if (isSecondaryChannel(channel)) {
|
|
225
243
|
throw new Error(
|
|
226
244
|
`getDomain(${channel}), must only be called for primary channels!`
|
|
@@ -243,7 +261,7 @@ export default class UnitView extends ContainerView {
|
|
|
243
261
|
* @returns {DomainArray}
|
|
244
262
|
*/
|
|
245
263
|
getConfiguredDomain(channel) {
|
|
246
|
-
const channelDef = this
|
|
264
|
+
const channelDef = this.#validateDomainQuery(channel);
|
|
247
265
|
|
|
248
266
|
const specDomain =
|
|
249
267
|
channelDef && channelDef.scale && channelDef.scale.domain;
|
|
@@ -273,7 +291,7 @@ export default class UnitView extends ContainerView {
|
|
|
273
291
|
* @returns {DomainArray}
|
|
274
292
|
*/
|
|
275
293
|
extractDataDomain(channel) {
|
|
276
|
-
const channelDef = this
|
|
294
|
+
const channelDef = this.#validateDomainQuery(channel);
|
|
277
295
|
const type = channelDef.type ?? "nominal"; // TODO: Should check that this is a channel without scale
|
|
278
296
|
|
|
279
297
|
/** @param {Channel} channel */
|
package/dist/src/view/view.d.ts
CHANGED
|
@@ -47,11 +47,12 @@ export default class View {
|
|
|
47
47
|
*/
|
|
48
48
|
opacityFunction: (arg0: number) => number;
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* Coords of the view for each facet, recorded during the last layout rendering pass.
|
|
51
|
+
* Most views have only one facet, so the map is usually of size 1.
|
|
52
|
+
*
|
|
53
|
+
* @type {Map<any, import("./layout/rectangle.js").default>}
|
|
53
54
|
*/
|
|
54
|
-
|
|
55
|
+
facetCoords: Map<any, import("./layout/rectangle.js").default>;
|
|
55
56
|
context: import("../types/viewContext.js").default;
|
|
56
57
|
layoutParent: import("./containerView.js").default;
|
|
57
58
|
dataParent: View;
|
|
@@ -86,6 +87,13 @@ export default class View {
|
|
|
86
87
|
needsAxes: Record<import("../spec/channel.js").PrimaryPositionalChannel, boolean>;
|
|
87
88
|
/** @type {ParamMediator} */
|
|
88
89
|
paramMediator: ParamMediator;
|
|
90
|
+
/**
|
|
91
|
+
* Returns the coords of the view. If view has been faceted, returns the coords
|
|
92
|
+
* of an arbitrary facet. If all or specific facet coords are needed, use `facetCoords`.
|
|
93
|
+
*
|
|
94
|
+
* @returns {import("./layout/rectangle.js").default}
|
|
95
|
+
*/
|
|
96
|
+
get coords(): import("./layout/rectangle.js").default;
|
|
89
97
|
getPadding(): Padding;
|
|
90
98
|
/**
|
|
91
99
|
* Returns a padding that indicates how much axes and titles extend over the plot area.
|
|
@@ -160,8 +168,9 @@ export default class View {
|
|
|
160
168
|
* Coordinates of the view
|
|
161
169
|
* @param {import("../utils/interactionEvent.js").default} event
|
|
162
170
|
* @param {boolean} capturing
|
|
171
|
+
* @protected
|
|
163
172
|
*/
|
|
164
|
-
handleInteractionEvent(coords: import("./layout/rectangle.js").default, event: import("../utils/interactionEvent.js").default, capturing: boolean): void;
|
|
173
|
+
protected handleInteractionEvent(coords: import("./layout/rectangle.js").default, event: import("../utils/interactionEvent.js").default, capturing: boolean): void;
|
|
165
174
|
/**
|
|
166
175
|
* Add an "interaction" event listener that mimics DOM's event model inside
|
|
167
176
|
* the view hierarchy.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../../src/view/view.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../../src/view/view.js"],"names":[],"mappings":"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"}
|
package/dist/src/view/view.js
CHANGED
|
@@ -17,6 +17,7 @@ import { isDiscrete, bandSpace } from "vega-scale";
|
|
|
17
17
|
import { peek } from "../utils/arrayUtils.js";
|
|
18
18
|
import ViewError from "./viewError.js";
|
|
19
19
|
import ParamMediator, { isExprRef } from "./paramMediator.js";
|
|
20
|
+
import { InternMap } from "internmap";
|
|
20
21
|
|
|
21
22
|
// TODO: View classes have too many responsibilities. Come up with a way
|
|
22
23
|
// to separate the concerns. However, most concerns are tightly tied to
|
|
@@ -75,11 +76,12 @@ export default class View {
|
|
|
75
76
|
opacityFunction = defaultOpacityFunction;
|
|
76
77
|
|
|
77
78
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
79
|
+
* Coords of the view for each facet, recorded during the last layout rendering pass.
|
|
80
|
+
* Most views have only one facet, so the map is usually of size 1.
|
|
81
|
+
*
|
|
82
|
+
* @type {Map<any, import("./layout/rectangle.js").default>}
|
|
81
83
|
*/
|
|
82
|
-
|
|
84
|
+
facetCoords = new InternMap([], JSON.stringify);
|
|
83
85
|
|
|
84
86
|
/**
|
|
85
87
|
*
|
|
@@ -141,6 +143,16 @@ export default class View {
|
|
|
141
143
|
}
|
|
142
144
|
}
|
|
143
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Returns the coords of the view. If view has been faceted, returns the coords
|
|
148
|
+
* of an arbitrary facet. If all or specific facet coords are needed, use `facetCoords`.
|
|
149
|
+
*
|
|
150
|
+
* @returns {import("./layout/rectangle.js").default}
|
|
151
|
+
*/
|
|
152
|
+
get coords() {
|
|
153
|
+
return this.facetCoords.values().next().value;
|
|
154
|
+
}
|
|
155
|
+
|
|
144
156
|
getPadding() {
|
|
145
157
|
return this._cache("size/padding", () =>
|
|
146
158
|
Padding.createFromConfig(this.spec.padding)
|
|
@@ -379,6 +391,7 @@ export default class View {
|
|
|
379
391
|
* Coordinates of the view
|
|
380
392
|
* @param {import("../utils/interactionEvent.js").default} event
|
|
381
393
|
* @param {boolean} capturing
|
|
394
|
+
* @protected
|
|
382
395
|
*/
|
|
383
396
|
handleInteractionEvent(coords, event, capturing) {
|
|
384
397
|
const listenersByType = capturing
|
|
@@ -480,9 +493,15 @@ export default class View {
|
|
|
480
493
|
* @type {import("../types/rendering.js").RenderMethod}
|
|
481
494
|
*/
|
|
482
495
|
render(context, coords, options = {}) {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
496
|
+
// TODO: When using sample faceting, all facets have the same coords.
|
|
497
|
+
// It would be better to save only single coords with an `undefined` facetId.
|
|
498
|
+
if (options.firstFacet) {
|
|
499
|
+
this.facetCoords.clear();
|
|
500
|
+
}
|
|
501
|
+
this.facetCoords.set(
|
|
502
|
+
options.facetId,
|
|
503
|
+
options.clipRect ? coords.intersect(options.clipRect) : coords
|
|
504
|
+
);
|
|
486
505
|
|
|
487
506
|
// override
|
|
488
507
|
}
|
package/dist/src/view/zoom.d.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
* @typedef {object} ZoomEvent
|
|
3
|
-
* @prop {number} x
|
|
4
|
-
* @prop {number} y
|
|
5
|
-
* @prop {number} xDelta
|
|
6
|
-
* @prop {number} yDelta
|
|
7
|
-
* @prop {number} zDelta
|
|
8
|
-
*/
|
|
1
|
+
export function isStillZooming(): boolean;
|
|
9
2
|
/**
|
|
10
3
|
* @param {import("../utils/interactionEvent.js").default} event
|
|
11
|
-
* @param {import("./
|
|
4
|
+
* @param {import("./layout/rectangle.js").default} coords
|
|
12
5
|
* @param {(zoomEvent: ZoomEvent) => void} handleZoom
|
|
13
6
|
* @param {import("../types/viewContext.js").Hover} [hover]
|
|
7
|
+
* @param {import("../utils/animator.js").default} [animator]
|
|
14
8
|
*/
|
|
15
|
-
export
|
|
9
|
+
export function interactionToZoom(event: import("../utils/interactionEvent.js").default, coords: import("./layout/rectangle.js").default, handleZoom: (zoomEvent: ZoomEvent) => void, hover?: import("../types/viewContext.js").Hover, animator?: import("../utils/animator.js").default): void;
|
|
16
10
|
export type ZoomEvent = {
|
|
17
11
|
x: number;
|
|
18
12
|
y: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zoom.d.ts","sourceRoot":"","sources":["../../../src/view/zoom.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"zoom.d.ts","sourceRoot":"","sources":["../../../src/view/zoom.js"],"names":[],"mappings":"AAkBA,0CAGC;AAgBD;;;;;;GAMG;AACH,yCANW,OAAO,8BAA8B,EAAE,OAAO,UAC9C,OAAO,uBAAuB,EAAE,OAAO,0BAC3B,SAAS,KAAK,IAAI,UAC9B,OAAO,yBAAyB,EAAE,KAAK,aACvC,OAAO,sBAAsB,EAAE,OAAO,QA0IhD;;OAlLS,MAAM;OACN,MAAM;YACN,MAAM;YACN,MAAM;YACN,MAAM"}
|
package/dist/src/view/zoom.js
CHANGED
|
@@ -7,19 +7,54 @@
|
|
|
7
7
|
* @prop {number} zDelta
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { makeLerpSmoother } from "../utils/animator.js";
|
|
11
|
+
import RingBuffer from "../utils/ringBuffer.js";
|
|
12
|
+
import Point from "./layout/point.js";
|
|
13
|
+
|
|
14
|
+
/** @type {ReturnType<typeof makeLerpSmoother>} */
|
|
15
|
+
let smoother;
|
|
16
|
+
|
|
17
|
+
let lastTimestamp = 0;
|
|
18
|
+
|
|
19
|
+
export function isStillZooming() {
|
|
20
|
+
const delta = performance.now() - lastTimestamp;
|
|
21
|
+
return delta < 50;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @param {T} fn
|
|
27
|
+
* @returns {T}
|
|
28
|
+
* @template {Function} T
|
|
29
|
+
*/
|
|
30
|
+
function recordTimeStamp(fn) {
|
|
31
|
+
// @ts-ignore
|
|
32
|
+
return function (...args) {
|
|
33
|
+
lastTimestamp = performance.now();
|
|
34
|
+
fn(...args);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
10
38
|
/**
|
|
11
39
|
* @param {import("../utils/interactionEvent.js").default} event
|
|
12
|
-
* @param {import("./
|
|
40
|
+
* @param {import("./layout/rectangle.js").default} coords
|
|
13
41
|
* @param {(zoomEvent: ZoomEvent) => void} handleZoom
|
|
14
42
|
* @param {import("../types/viewContext.js").Hover} [hover]
|
|
43
|
+
* @param {import("../utils/animator.js").default} [animator]
|
|
15
44
|
*/
|
|
16
|
-
export
|
|
45
|
+
export function interactionToZoom(event, coords, handleZoom, hover, animator) {
|
|
46
|
+
handleZoom = recordTimeStamp(handleZoom);
|
|
47
|
+
|
|
17
48
|
if (event.type == "wheel") {
|
|
18
49
|
event.uiEvent.preventDefault(); // TODO: Only if there was something zoomable
|
|
19
50
|
|
|
20
51
|
const wheelEvent = /** @type {WheelEvent} */ (event.uiEvent);
|
|
21
52
|
const wheelMultiplier = wheelEvent.deltaMode ? 120 : 1;
|
|
22
53
|
|
|
54
|
+
if (wheelEvent.deltaX === 0 && wheelEvent.deltaY === 0) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
23
58
|
let { x, y } = event.point;
|
|
24
59
|
|
|
25
60
|
// Snapping to the hovered item:
|
|
@@ -28,6 +63,9 @@ export default function interactionToZoom(event, coords, handleZoom, hover) {
|
|
|
28
63
|
// This allows the user to rapidly zoom closer without having to
|
|
29
64
|
// continuously adjust the cursor position.
|
|
30
65
|
|
|
66
|
+
// Stop drag-to-pan inertia
|
|
67
|
+
smoother?.stop();
|
|
68
|
+
|
|
31
69
|
if (hover) {
|
|
32
70
|
const e = hover.mark.encoders;
|
|
33
71
|
if (e.x && !e.x2 && !e.x.constantValue) {
|
|
@@ -59,31 +97,110 @@ export default function interactionToZoom(event, coords, handleZoom, hover) {
|
|
|
59
97
|
event.type == "mousedown" &&
|
|
60
98
|
/** @type {MouseEvent} */ (event.uiEvent).button === 0
|
|
61
99
|
) {
|
|
100
|
+
if (smoother) {
|
|
101
|
+
smoother.stop();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** @type {RingBuffer<{point: Point, timestamp: number}>} */
|
|
105
|
+
const buffer = new RingBuffer(30);
|
|
106
|
+
|
|
62
107
|
const mouseEvent = /** @type {MouseEvent} */ (event.uiEvent);
|
|
63
108
|
mouseEvent.preventDefault();
|
|
64
109
|
|
|
65
|
-
let
|
|
110
|
+
let prevPoint = Point.fromMouseEvent(mouseEvent);
|
|
66
111
|
|
|
67
112
|
const onMousemove = /** @param {MouseEvent} moveEvent */ (
|
|
68
113
|
moveEvent
|
|
69
114
|
) => {
|
|
115
|
+
const point = Point.fromMouseEvent(moveEvent);
|
|
116
|
+
buffer.push({ point, timestamp: performance.now() });
|
|
117
|
+
|
|
118
|
+
const delta = point.subtract(prevPoint);
|
|
119
|
+
|
|
70
120
|
handleZoom({
|
|
71
|
-
x:
|
|
72
|
-
y:
|
|
73
|
-
xDelta:
|
|
74
|
-
yDelta:
|
|
121
|
+
x: prevPoint.x,
|
|
122
|
+
y: prevPoint.y,
|
|
123
|
+
xDelta: delta.x,
|
|
124
|
+
yDelta: delta.y,
|
|
75
125
|
zDelta: 0,
|
|
76
126
|
});
|
|
77
127
|
|
|
78
|
-
|
|
128
|
+
prevPoint = point;
|
|
79
129
|
};
|
|
80
130
|
|
|
81
|
-
const
|
|
131
|
+
const animateInertia = () => {
|
|
132
|
+
const lastMillisToInclude = 160;
|
|
133
|
+
|
|
134
|
+
const now = performance.now();
|
|
135
|
+
const arr = buffer
|
|
136
|
+
.get()
|
|
137
|
+
.filter((p) => now - p.timestamp < lastMillisToInclude);
|
|
138
|
+
|
|
139
|
+
if (arr.length < 5 || !animator || isDecelerating(arr)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const a = arr.at(-1);
|
|
144
|
+
const b = arr[0];
|
|
145
|
+
|
|
146
|
+
const v = a.point
|
|
147
|
+
.subtract(b.point)
|
|
148
|
+
.multiply(1 / (a.timestamp - b.timestamp));
|
|
149
|
+
|
|
150
|
+
let x = prevPoint.x;
|
|
151
|
+
|
|
152
|
+
smoother = makeLerpSmoother(
|
|
153
|
+
animator,
|
|
154
|
+
(a) => {
|
|
155
|
+
handleZoom({
|
|
156
|
+
x: a,
|
|
157
|
+
y: prevPoint.y,
|
|
158
|
+
xDelta: x - a,
|
|
159
|
+
yDelta: 0,
|
|
160
|
+
zDelta: 0,
|
|
161
|
+
});
|
|
162
|
+
x = a;
|
|
163
|
+
},
|
|
164
|
+
150,
|
|
165
|
+
0.5,
|
|
166
|
+
x
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
smoother(prevPoint.x - v.x * 250);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const onMouseup = () => {
|
|
82
173
|
document.removeEventListener("mousemove", onMousemove);
|
|
83
174
|
document.removeEventListener("mouseup", onMouseup);
|
|
175
|
+
animateInertia();
|
|
84
176
|
};
|
|
85
177
|
|
|
86
178
|
document.addEventListener("mouseup", onMouseup, false);
|
|
87
179
|
document.addEventListener("mousemove", onMousemove, false);
|
|
88
180
|
}
|
|
89
181
|
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Split the array into two vectors and compare their lengths to find out if
|
|
185
|
+
* the mouse movement is decelerating.
|
|
186
|
+
* @param {{point: Point, timestamp: number}[]} arr
|
|
187
|
+
*/
|
|
188
|
+
function isDecelerating(arr) {
|
|
189
|
+
const mid = arr[Math.floor(arr.length / 2)];
|
|
190
|
+
|
|
191
|
+
const ap = mid.point
|
|
192
|
+
.subtract(arr[0].point)
|
|
193
|
+
.multiply(mid.timestamp - arr[0].timestamp);
|
|
194
|
+
const bp = arr
|
|
195
|
+
.at(-1)
|
|
196
|
+
.point.subtract(mid.point)
|
|
197
|
+
.multiply(arr.at(-1).timestamp - mid.timestamp);
|
|
198
|
+
|
|
199
|
+
const a = ap.length;
|
|
200
|
+
const b = bp.length;
|
|
201
|
+
|
|
202
|
+
// Found by trial and error
|
|
203
|
+
const maxRatio = 0.4;
|
|
204
|
+
|
|
205
|
+
return b / a < maxRatio;
|
|
206
|
+
}
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"contributors": [],
|
|
9
9
|
"license": "MIT",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.48.0",
|
|
11
11
|
"jsdelivr": "dist/bundle/index.js",
|
|
12
12
|
"unpkg": "dist/bundle/index.js",
|
|
13
13
|
"browser": "dist/bundle/index.js",
|
|
@@ -64,5 +64,5 @@
|
|
|
64
64
|
"vega-scale": "^7.3.1",
|
|
65
65
|
"vega-util": "^1.17.2"
|
|
66
66
|
},
|
|
67
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "64cd3335607b2608db3c85c74a4fb571302e77d0"
|
|
68
68
|
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A Rendering context that doesn't render anything. It creates a hierarchy
|
|
3
|
-
* of view coordinates, including faceted views that are repeated multiple times.
|
|
4
|
-
* The coordinates can be used for mouse events / interactions, for example.
|
|
5
|
-
*
|
|
6
|
-
* @typedef {import("../view.js").default} View
|
|
7
|
-
* @typedef {import("../layout/rectangle.js").default} Rectangle
|
|
8
|
-
*
|
|
9
|
-
*/
|
|
10
|
-
export default class LayoutRecorderViewRenderingContext extends ViewRenderingContext {
|
|
11
|
-
/** @type {ViewCoords} */
|
|
12
|
-
root: ViewCoords;
|
|
13
|
-
/** @type {ViewCoords[]} */
|
|
14
|
-
stack: ViewCoords[];
|
|
15
|
-
/** @type {ViewCoords} */
|
|
16
|
-
lastAddition: ViewCoords;
|
|
17
|
-
getLayout(): ViewCoords;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* A Rendering context that doesn't render anything. It creates a hierarchy
|
|
21
|
-
* of view coordinates, including faceted views that are repeated multiple times.
|
|
22
|
-
* The coordinates can be used for mouse events / interactions, for example.
|
|
23
|
-
*/
|
|
24
|
-
export type View = import("../view.js").default;
|
|
25
|
-
/**
|
|
26
|
-
* A Rendering context that doesn't render anything. It creates a hierarchy
|
|
27
|
-
* of view coordinates, including faceted views that are repeated multiple times.
|
|
28
|
-
* The coordinates can be used for mouse events / interactions, for example.
|
|
29
|
-
*/
|
|
30
|
-
export type Rectangle = import("../layout/rectangle.js").default;
|
|
31
|
-
import ViewRenderingContext from "./viewRenderingContext.js";
|
|
32
|
-
/**
|
|
33
|
-
* Represents coordinates of view instances. Faceted views objects may have
|
|
34
|
-
* been rendered at multiple locations.
|
|
35
|
-
*/
|
|
36
|
-
declare class ViewCoords {
|
|
37
|
-
/**
|
|
38
|
-
* @param {View} view
|
|
39
|
-
* @param {Rectangle} coords
|
|
40
|
-
*/
|
|
41
|
-
constructor(view: View, coords: Rectangle);
|
|
42
|
-
view: import("../view.js").default;
|
|
43
|
-
coords: import("../layout/rectangle.js").default;
|
|
44
|
-
/** @type {ViewCoords[]} */
|
|
45
|
-
children: ViewCoords[];
|
|
46
|
-
/**
|
|
47
|
-
*
|
|
48
|
-
* @param {ViewCoords} viewCoords
|
|
49
|
-
*/
|
|
50
|
-
addChild(viewCoords: ViewCoords): void;
|
|
51
|
-
/**
|
|
52
|
-
* Broadcasts a message to views that include the given (x, y) point.
|
|
53
|
-
* This is mainly intended for mouse events.
|
|
54
|
-
*
|
|
55
|
-
* @param {import("../../utils/interactionEvent.js").default} event
|
|
56
|
-
*/
|
|
57
|
-
dispatchInteractionEvent(event: import("../../utils/interactionEvent.js").default): void;
|
|
58
|
-
}
|
|
59
|
-
export {};
|
|
60
|
-
//# sourceMappingURL=layoutRecorderViewRenderingContext.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"layoutRecorderViewRenderingContext.d.ts","sourceRoot":"","sources":["../../../../src/view/renderingContext/layoutRecorderViewRenderingContext.js"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH;IAOQ,yBAAyB;IACzB,MADW,UAAU,CACA;IAErB,2BAA2B;IAC3B,OADW,UAAU,EAAE,CACR;IAEf,yBAAyB;IACzB,cADW,UAAU,CACQ;IAgCjC,wBAEC;CACJ;;;;;;mBArDY,OAAO,YAAY,EAAE,OAAO;;;;;;wBAC5B,OAAO,wBAAwB,EAAE,OAAO;iCARpB,2BAA2B;AA8D5D;;;GAGG;AACH;IACI;;;OAGG;IACH,kBAHW,IAAI,UACJ,SAAS,EAOnB;IAJG,mCAAgB;IAChB,iDAAoB;IACpB,2BAA2B;IAC3B,UADW,UAAU,EAAE,CACL;IAGtB;;;OAGG;IACH,qBAFW,UAAU,QAcpB;IAED;;;;;OAKG;IACH,gCAFW,OAAO,iCAAiC,EAAE,OAAO,QAyB3D;CACJ"}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { peek } from "../../utils/arrayUtils.js";
|
|
2
|
-
import ViewRenderingContext from "./viewRenderingContext.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* A Rendering context that doesn't render anything. It creates a hierarchy
|
|
6
|
-
* of view coordinates, including faceted views that are repeated multiple times.
|
|
7
|
-
* The coordinates can be used for mouse events / interactions, for example.
|
|
8
|
-
*
|
|
9
|
-
* @typedef {import("../view.js").default} View
|
|
10
|
-
* @typedef {import("../layout/rectangle.js").default} Rectangle
|
|
11
|
-
*
|
|
12
|
-
*/
|
|
13
|
-
export default class LayoutRecorderViewRenderingContext extends ViewRenderingContext {
|
|
14
|
-
/**
|
|
15
|
-
* @param {import("../../types/rendering.js").GlobalRenderingOptions} globalOptions
|
|
16
|
-
*/
|
|
17
|
-
constructor(globalOptions) {
|
|
18
|
-
super(globalOptions);
|
|
19
|
-
|
|
20
|
-
/** @type {ViewCoords} */
|
|
21
|
-
this.root = undefined;
|
|
22
|
-
|
|
23
|
-
/** @type {ViewCoords[]} */
|
|
24
|
-
this.stack = [];
|
|
25
|
-
|
|
26
|
-
/** @type {ViewCoords} */
|
|
27
|
-
this.lastAddition = undefined;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Must be called when a view's render() method is entered
|
|
32
|
-
*
|
|
33
|
-
* @param {View} view
|
|
34
|
-
* @param {Rectangle} coords View coordinates
|
|
35
|
-
* inside the padding.
|
|
36
|
-
*/
|
|
37
|
-
pushView(view, coords) {
|
|
38
|
-
// TODO: Facet id
|
|
39
|
-
|
|
40
|
-
const viewCoords = new ViewCoords(view, coords);
|
|
41
|
-
|
|
42
|
-
if (!this.root) {
|
|
43
|
-
this.root = viewCoords;
|
|
44
|
-
} else {
|
|
45
|
-
peek(this.stack).addChild(viewCoords);
|
|
46
|
-
}
|
|
47
|
-
this.stack.push(viewCoords);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Must be called when a view's render() method is being exited
|
|
52
|
-
*
|
|
53
|
-
* @param {View} view
|
|
54
|
-
*/
|
|
55
|
-
popView(view) {
|
|
56
|
-
this.stack.pop();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
getLayout() {
|
|
60
|
-
return this.root;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Represents coordinates of view instances. Faceted views objects may have
|
|
66
|
-
* been rendered at multiple locations.
|
|
67
|
-
*/
|
|
68
|
-
class ViewCoords {
|
|
69
|
-
/**
|
|
70
|
-
* @param {View} view
|
|
71
|
-
* @param {Rectangle} coords
|
|
72
|
-
*/
|
|
73
|
-
constructor(view, coords) {
|
|
74
|
-
this.view = view;
|
|
75
|
-
this.coords = coords;
|
|
76
|
-
/** @type {ViewCoords[]} */
|
|
77
|
-
this.children = [];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
*
|
|
82
|
-
* @param {ViewCoords} viewCoords
|
|
83
|
-
*/
|
|
84
|
-
addChild(viewCoords) {
|
|
85
|
-
const last = peek(this.children);
|
|
86
|
-
if (
|
|
87
|
-
last &&
|
|
88
|
-
viewCoords.view === last.view &&
|
|
89
|
-
viewCoords.coords.equals(last.coords)
|
|
90
|
-
) {
|
|
91
|
-
// Skip extra copies of sample facets. They all have the same coords.
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
this.children.push(viewCoords);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Broadcasts a message to views that include the given (x, y) point.
|
|
100
|
-
* This is mainly intended for mouse events.
|
|
101
|
-
*
|
|
102
|
-
* @param {import("../../utils/interactionEvent.js").default} event
|
|
103
|
-
*/
|
|
104
|
-
dispatchInteractionEvent(event) {
|
|
105
|
-
if (this.coords.containsPoint(event.point.x, event.point.y)) {
|
|
106
|
-
this.view.handleInteractionEvent(this.coords, event, true);
|
|
107
|
-
if (event.stopped) {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (this.children.length == 0) {
|
|
112
|
-
event.target = this.view;
|
|
113
|
-
} else {
|
|
114
|
-
for (const child of this.children) {
|
|
115
|
-
child.dispatchInteractionEvent(event);
|
|
116
|
-
if (event.target) {
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
if (event.stopped) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
this.view.handleInteractionEvent(this.coords, event, false);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|