@genome-spy/core 0.47.0 → 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 +3276 -3177
- package/dist/bundle/index.js +83 -83
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +10 -3
- package/dist/src/utils/animator.d.ts +4 -2
- package/dist/src/utils/animator.d.ts.map +1 -1
- package/dist/src/utils/animator.js +15 -9
- package/dist/src/utils/inertia.d.ts +3 -1
- package/dist/src/utils/inertia.d.ts.map +1 -1
- 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 +3 -1
- package/dist/src/view/gridView.d.ts.map +1 -1
- package/dist/src/view/gridView.js +3 -2
- 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/zoom.d.ts +3 -9
- package/dist/src/view/zoom.d.ts.map +1 -1
- package/dist/src/view/zoom.js +125 -8
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"genomeSpy.d.ts","sourceRoot":"","sources":["../../src/genomeSpy.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"genomeSpy.d.ts","sourceRoot":"","sources":["../../src/genomeSpy.js"],"names":[],"mappings":"AA+CA;IACI;;;;;OAKG;IAEH;;;;;OAKG;IACH,uBAJW,WAAW,qDAEX,OAAO,qBAAqB,EAAE,YAAY,EA2FpD;IAxFG,uBAA0B;IAC1B,oDAAsB;IAItB,6BAA6B;IAC7B,uBADW,CAAC,MAAM,IAAI,CAAC,EAAE,CACM;IAE/B,sCAAsC;IACtC,wCAAgB;IAEhB,iCAA4C;IAC5C,yBAAoC;IAEpC,4CAA4C;IAC5C,oBADW,QAAU,MAAM,KAAE,MAAM,EAAE,CAAC,EAAE,CACZ;IAE5B,mBAAoD;IAEpD,0BAA0B;IAC1B,aADW,WAAW,CACM;IAE5B;;;;;OAKG;IACH,qEAF0B,OAAO,CAE8B;IAE/D,2CAA2C;IAC3C,mBADW,4BAA4B,CACL;IAClC,2CAA2C;IAC3C,iBADW,4BAA4B,CACP;IAEhC,oDAAoD;IACpD,6BAAgC;IAEhC;;;OAGG;IACH,eAFU;QAAE,IAAI,EAAE,OAAO,iBAAiB,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,oBAAoB,EAAE,KAAK,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAEpF;IAE9B,uBAA+C;IAE/C;;;OAGG;IACH,oBAFU,IAAI,MAAM,EAAE,QAAU,aAAa,KAAE,IAAI,CAAC,EAAE,CAAC,CAEpB;IAEnC;;;;;;OAMG;IACH,yCAFkC,GAAG,KAAK,IAAI,GAEd;IAEhC;;;OAGG;IACH,kDAFkC,GAAG,KAAK,IAAI,GAEL;IAEzC,oFAAoF;IACpF,iBADW,OAAO,MAAM,EAAE,OAAO,6BAA6B,EAAE,cAAc,CAAC,CAK9E;IAED,mBAAmB;IACnB,2CAAyB;IAEzB;;;;OAIG;IACH;gBAF8B,OAAO,wBAAwB,EAAE,iBAAiB;iBAAW,MAAM;OAEnE;IAE9B;;OAEG;IACH,wBAFU,WAAW,CAEkB;IA2C3C;;;OAGG;IACH,2CAFkB,MAAM,KAAK,GAAG,EAAE,QAIjC;IAED;;OAEG;IACH,+BAFW,MAAM,YAShB;IAED;;;;OAIG;IACH,sBAHW,MAAM,QACN,GAAG,EAAE,QAaf;IAED;;;;;OAKG;IACH,gBAHW,kBAAkB,YAClB,GAAG,QAQb;IAED;;;OAGG;IACH,iCAqDC;IA0DG,uBAQC;IAGD,mCAGE;IAOF,sCAEE;IAGF,iBAAyC;IAW7C;;OAEG;IACH,gBAuBC;IAED,sCA8MC;IAED;;;OAGG;IACH,UAFa,QAAQ,OAAO,CAAC,CA6B5B;IAED,4BAqJC;IA9He,iCAAoC;IAgIpD;;;OAGG;IACH,kBAHW,MAAM,KACN,MAAM,QAsEhB;IAED;;;;;;;OAOG;IACH,oDAHuB,QAAQ,MAAM,GAAG,WAAW,GAAG,OAAO,KAAK,EAAE,cAAc,CAAC,QAYlF;IAED,sBA6CC;IAED,kBAIC;IAED,iCAOC;IAED,iCASC;IAED,qFAWC;;CACJ;;;;iCAx7BY,eAAe,GAAG,YAAY,GAAG,QAAQ,GAAG,gBAAgB;4BAlC7C,uBAAuB;4BA2BP,uBAAuB;qBAZ9C,qBAAqB;wBAIlB,yBAAyB;yCARR,yDAAyD;oBAYvD,oBAAoB;wBAdvC,qBAAqB;oBAXzB,uBAAuB;qBAStB,oBAAoB"}
|
package/dist/src/genomeSpy.js
CHANGED
|
@@ -36,6 +36,7 @@ import { invalidatePrefix } from "./utils/propertyCacher.js";
|
|
|
36
36
|
import { VIEW_ROOT_NAME, ViewFactory } from "./view/viewFactory.js";
|
|
37
37
|
import { reconfigureScales } from "./view/scaleResolution.js";
|
|
38
38
|
import createBindingInputs from "./utils/inputBinding.js";
|
|
39
|
+
import { isStillZooming } from "./view/zoom.js";
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* Events that are broadcasted to all views.
|
|
@@ -687,8 +688,10 @@ export default class GenomeSpy {
|
|
|
687
688
|
this.tooltip.handleMouseMove(event);
|
|
688
689
|
this._tooltipUpdateRequested = false;
|
|
689
690
|
|
|
690
|
-
|
|
691
|
-
|
|
691
|
+
// Disable picking during dragging. Also postpone picking until
|
|
692
|
+
// the user has stopped zooming as reading pixels from the
|
|
693
|
+
// picking buffer is slow and ruins smooth animations.
|
|
694
|
+
if (event.buttons == 0 && !isStillZooming()) {
|
|
692
695
|
this.renderPickingFramebuffer();
|
|
693
696
|
this._handlePicking(point.x, point.y);
|
|
694
697
|
}
|
|
@@ -711,7 +714,11 @@ export default class GenomeSpy {
|
|
|
711
714
|
this._wheelInertia.cancel();
|
|
712
715
|
}
|
|
713
716
|
|
|
714
|
-
if (
|
|
717
|
+
if (
|
|
718
|
+
(event.type == "mousedown" || event.type == "mouseup") &&
|
|
719
|
+
!isStillZooming()
|
|
720
|
+
) {
|
|
721
|
+
// Actually, only needed when clicking on a mark
|
|
715
722
|
this.renderPickingFramebuffer();
|
|
716
723
|
} else if (event.type == "wheel") {
|
|
717
724
|
lastWheelEvent = now;
|
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
* @param {number} halfLife Time until half of the value is reached, in milliseconds
|
|
11
11
|
* @param {number} stopAt Stop animation when the value is within this distance from the target
|
|
12
12
|
* @param {number} [initialValue] Initial value
|
|
13
|
-
* @returns {(target: number) => void} Function that activates the transition with a new target value
|
|
13
|
+
* @returns {((target: number) => void) & { stop: () => void}} Function that activates the transition with a new target value
|
|
14
14
|
*/
|
|
15
|
-
export function makeLerpSmoother(animator: import("../utils/animator.js").default, callback: (value: number) => void, halfLife: number, stopAt: number, initialValue?: number): (target: number) => void
|
|
15
|
+
export function makeLerpSmoother(animator: import("../utils/animator.js").default, callback: (value: number) => void, halfLife: number, stopAt: number, initialValue?: number): ((target: number) => void) & {
|
|
16
|
+
stop: () => void;
|
|
17
|
+
};
|
|
16
18
|
export default class Animator {
|
|
17
19
|
/**
|
|
18
20
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animator.d.ts","sourceRoot":"","sources":["../../../src/utils/animator.js"],"names":[],"mappings":"AAoFA;;;;;;;;;;;;;GAaG;AACH,2CAPW,OAAO,sBAAsB,EAAE,OAAO,oBAC9B,MAAM,KAAK,IAAI,YACvB,MAAM,UACN,MAAM,iBACN,MAAM,
|
|
1
|
+
{"version":3,"file":"animator.d.ts","sourceRoot":"","sources":["../../../src/utils/animator.js"],"names":[],"mappings":"AAoFA;;;;;;;;;;;;;GAaG;AACH,2CAPW,OAAO,sBAAsB,EAAE,OAAO,oBAC9B,MAAM,KAAK,IAAI,YACvB,MAAM,UACN,MAAM,iBACN,MAAM,aACM,MAAM,KAAK,IAAI;UAAY,MAAM,IAAI;EA0D3D;AAxJD;IACI;;;OAGG;IACH,mCAFoB,MAAM,KAAE,IAAI,EAS/B;IANG,wBAHgB,MAAM,KAAE,IAAI,CAGS;IACrC,0BAA6B;IAC7B,eAAkB;IAElB,wCAAwC;IACxC,aADW,QAAU,MAAM,KAAE,IAAI,CAAC,EAAE,CACf;IAGzB;;;;;;;;;OASG;IACH,mCAFoB,MAAM,KAAE,IAAI,QAM/B;IAED;;OAEG;IACH,kCAFoB,MAAM,KAAE,IAAI,QAO/B;IAED;;;;OAIG;IACH,sBAoBC;IAED;;;;;OAKG;IACH,oBAFW,OAAO,iBAAiB,EAAE,iBAAiB,gBAQrD;CACJ"}
|
|
@@ -94,7 +94,7 @@ export default class Animator {
|
|
|
94
94
|
* @param {number} halfLife Time until half of the value is reached, in milliseconds
|
|
95
95
|
* @param {number} stopAt Stop animation when the value is within this distance from the target
|
|
96
96
|
* @param {number} [initialValue] Initial value
|
|
97
|
-
* @returns {(target: number) => void} Function that activates the transition with a new target value
|
|
97
|
+
* @returns {((target: number) => void) & { stop: () => void}} Function that activates the transition with a new target value
|
|
98
98
|
*/
|
|
99
99
|
export function makeLerpSmoother(
|
|
100
100
|
animator,
|
|
@@ -113,15 +113,13 @@ export function makeLerpSmoother(
|
|
|
113
113
|
* @param {number} [timestamp]
|
|
114
114
|
*/
|
|
115
115
|
function smoothUpdate(timestamp) {
|
|
116
|
-
|
|
116
|
+
if (settled) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
117
119
|
|
|
118
|
-
|
|
119
|
-
// wait until the next frame to get a proper time delta.
|
|
120
|
-
const tD = settled ? 0 : timestamp - lastTimeStamp;
|
|
120
|
+
const tD = timestamp - lastTimeStamp;
|
|
121
121
|
lastTimeStamp = timestamp;
|
|
122
122
|
|
|
123
|
-
settled = false;
|
|
124
|
-
|
|
125
123
|
// Lerp smoothing: https://twitter.com/FreyaHolmer/status/1757836988495847568
|
|
126
124
|
current = target + (current - target) * Math.pow(2, -tD / halfLife);
|
|
127
125
|
|
|
@@ -140,10 +138,18 @@ export function makeLerpSmoother(
|
|
|
140
138
|
/**
|
|
141
139
|
* @param {number} value
|
|
142
140
|
*/
|
|
143
|
-
|
|
141
|
+
function setTarget(value) {
|
|
144
142
|
target = value;
|
|
145
143
|
if (settled) {
|
|
146
|
-
|
|
144
|
+
settled = false;
|
|
145
|
+
lastTimeStamp = +document.timeline.currentTime;
|
|
146
|
+
smoothUpdate(lastTimeStamp);
|
|
147
147
|
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
setTarget.stop = () => {
|
|
151
|
+
settled = true;
|
|
148
152
|
};
|
|
153
|
+
|
|
154
|
+
return setTarget;
|
|
149
155
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inertia.d.ts","sourceRoot":"","sources":["../../../src/utils/inertia.js"],"names":[],"mappings":"AAmEA;;;GAGG;AACH,8EAiBC;AApFD;;GAEG;AACH;IACI;;;OAGG;IACH,sBAHW,OAAO,eAAe,EAAE,OAAO,aAC/B,OAAO,EAyBjB;IAtBG,0CAAwB;IACxB,kBAA0B;IAG1B,oBAAsB;IAEtB,oCAAoC;IACpC,iBADoB,MAAM,KAAE,IAAI,CACZ;IAEpB,oBAAoB;IACpB,kBAAkB;IAElB
|
|
1
|
+
{"version":3,"file":"inertia.d.ts","sourceRoot":"","sources":["../../../src/utils/inertia.js"],"names":[],"mappings":"AAmEA;;;GAGG;AACH,8EAiBC;AApFD;;GAEG;AACH;IACI;;;OAGG;IACH,sBAHW,OAAO,eAAe,EAAE,OAAO,aAC/B,OAAO,EAyBjB;IAtBG,0CAAwB;IACxB,kBAA0B;IAG1B,oBAAsB;IAEtB,oCAAoC;IACpC,iBADoB,MAAM,KAAE,IAAI,CACZ;IAEpB,oBAAoB;IACpB,kBAAkB;IAElB;;MASC;IAGL,eAIC;IAED;;;;OAIG;IACH,mBAHW,MAAM,mBACG,MAAM,KAAE,IAAI,QAkB/B;CACJ"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @template T
|
|
3
|
+
*/
|
|
4
|
+
export default class RingBuffer<T> {
|
|
5
|
+
/**
|
|
6
|
+
* @param {number} size
|
|
7
|
+
*/
|
|
8
|
+
constructor(size: number);
|
|
9
|
+
/** @param {T} value */
|
|
10
|
+
push(value: T): void;
|
|
11
|
+
/**
|
|
12
|
+
* @returns {T[]}
|
|
13
|
+
*/
|
|
14
|
+
get(): T[];
|
|
15
|
+
get size(): number;
|
|
16
|
+
get length(): number;
|
|
17
|
+
#private;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=ringBuffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ringBuffer.d.ts","sourceRoot":"","sources":["../../../src/utils/ringBuffer.js"],"names":[],"mappings":"AAAA;;GAEG;AACH;IAQI;;OAEG;IACH,kBAFW,MAAM,EAIhB;IAED,uBAAuB;IACvB,YADY,CAAC,QAKZ;IAED;;OAEG;IACH,OAFa,CAAC,EAAE,CAOf;IAED,mBAEC;IAED,qBAEC;;CACJ"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @template T
|
|
3
|
+
*/
|
|
4
|
+
export default class RingBuffer {
|
|
5
|
+
/** @type {T[]} */
|
|
6
|
+
#buffer;
|
|
7
|
+
|
|
8
|
+
#index = 0;
|
|
9
|
+
|
|
10
|
+
#length = 0;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {number} size
|
|
14
|
+
*/
|
|
15
|
+
constructor(size) {
|
|
16
|
+
this.#buffer = new Array(size);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** @param {T} value */
|
|
20
|
+
push(value) {
|
|
21
|
+
this.#buffer[this.#index] = value;
|
|
22
|
+
this.#index = (this.#index + 1) % this.size;
|
|
23
|
+
this.#length = Math.min(this.#length + 1, this.size);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @returns {T[]}
|
|
28
|
+
*/
|
|
29
|
+
get() {
|
|
30
|
+
const b = this.#buffer;
|
|
31
|
+
return this.#length < this.size
|
|
32
|
+
? b.slice(0, this.#length)
|
|
33
|
+
: b.slice(this.#index, this.size).concat(b.slice(0, this.#index));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get size() {
|
|
37
|
+
return this.#buffer.length;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get length() {
|
|
41
|
+
return this.#length;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import RingBuffer from "./ringBuffer.js";
|
|
3
|
+
|
|
4
|
+
describe("ringBuffer", () => {
|
|
5
|
+
test("Empty buffer", () => {
|
|
6
|
+
const buffer = new RingBuffer(10);
|
|
7
|
+
expect(buffer.length).toBe(0);
|
|
8
|
+
expect(buffer.get()).toEqual([]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("Partially filled buffer", () => {
|
|
12
|
+
const buffer = new RingBuffer(10);
|
|
13
|
+
buffer.push(1);
|
|
14
|
+
buffer.push(2);
|
|
15
|
+
buffer.push(3);
|
|
16
|
+
expect(buffer.length).toBe(3);
|
|
17
|
+
expect(buffer.get()).toEqual([1, 2, 3]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("Full buffer", () => {
|
|
21
|
+
const buffer = new RingBuffer(3);
|
|
22
|
+
buffer.push(1);
|
|
23
|
+
buffer.push(2);
|
|
24
|
+
buffer.push(3);
|
|
25
|
+
expect(buffer.length).toBe(3);
|
|
26
|
+
expect(buffer.get()).toEqual([1, 2, 3]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("Overfilled buffer", () => {
|
|
30
|
+
const buffer = new RingBuffer(3);
|
|
31
|
+
buffer.push(1);
|
|
32
|
+
buffer.push(2);
|
|
33
|
+
buffer.push(3);
|
|
34
|
+
buffer.push(4);
|
|
35
|
+
buffer.push(5);
|
|
36
|
+
expect(buffer.length).toBe(3);
|
|
37
|
+
expect(buffer.get()).toEqual([3, 4, 5]);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -122,7 +122,9 @@ declare class Scrollbar extends UnitView {
|
|
|
122
122
|
scrollbarSize: number;
|
|
123
123
|
scrollbarPadding: number;
|
|
124
124
|
};
|
|
125
|
-
interpolateViewportOffset: (target: number) => void
|
|
125
|
+
interpolateViewportOffset: ((target: number) => void) & {
|
|
126
|
+
stop: () => void;
|
|
127
|
+
};
|
|
126
128
|
get scrollOffset(): number;
|
|
127
129
|
/**
|
|
128
130
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gridView.d.ts","sourceRoot":"","sources":["../../../src/view/gridView.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gridView.d.ts","sourceRoot":"","sources":["../../../src/view/gridView.js"],"names":[],"mappings":"AAswBA;;;GAGG;AACH,iDAHW,OAAO,iBAAiB,EAAE,cAAc,GACtC,OAAO,iBAAiB,EAAE,QAAQ,CAwB9C;AAED;;;GAGG;AACH,uDAHW,OAAO,iBAAiB,EAAE,cAAc,GACtC,OAAO,iBAAiB,EAAE,QAAQ,CA6C9C;AA2BD;;GAEG;AACH,8EAUC;AAED;;;;;GAKG;AACH,4CAJW,OAAO,uBAAuB,EAAE,OAAO,UACvC,OAAO,iBAAiB,EAAE,UAAU,YACpC,QAAQ,aAmBlB;AA53BD;;;;;;;;;;;;;;;GAeG;AACH;IA6BI;;;;;;;;;OASG;IACH,kBARW,OAAO,iBAAiB,EAAE,aAAa,WACvC,OAAO,yBAAyB,EAAE,OAAO,gBACzC,aAAa,iDAEb,MAAM,WACN,MAAM,YACN,OAAO,WAAW,EAAE,WAAW,EAoBzC;IARG,8CAAgB;IAOhB,uBAA0B;IAG9B;;OAEG;IACH,qDAIC;IAeD;;OAEG;IACH,wDAKC;IAqBD;;OAEG;IACH,8CAEC;IAED,yBAEC;IAED;;OAEG;IACH,wCAmCC;IA2OD;;;;OAIG;IAEH,gBALW,OAAO,4CAA4C,EAAE,OAAO,UAC5D,OAAO,uBAAuB,EAAE,OAAO,YACvC,OAAO,uBAAuB,EAAE,gBAAgB,QA6O1D;;CAoGJ;AAgJD;IACI;;;;OAIG;IACH,6DAHW,aAAa,UACb,MAAM,EAoFhB;IAjFG,4BAAgC;IAChC,kCAAgB;IAChB,eAAoB;IAEpB,uBAAuB;IACvB,YADW,QAAQ,CACQ;IAE3B,uBAAuB;IACvB,kBADW,QAAQ,CACc;IAEjC,mFAAmF;IACnF,MADW,QAAQ,OAAO,OAAO,iBAAiB,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAC5D;IAEd,4FAA4F;IAC5F,WADW,QAAQ,OAAO,OAAO,iBAAiB,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAC3D;IAEnB,0DAA0D;IAC1D,kEAAoB;IAEpB,uBAAuB;IACvB,OADW,QAAQ,CACG;IAEtB,wBAAwB;IACxB,QADW,SAAS,CACQ;IA4DhC,uEAcC;IAED;;OAEG;IACH,4BAkKC;IAED,uBAqBC;IAED,iCAEC;CACJ;qBAlrC0D,eAAe;sBAFpD,uBAAuB;0BAGnB,oBAAoB;qBAGzB,eAAe;yBALX,mBAAmB;AAqrC5C;IAeI;;;OAGG;IACH,uBAHW,SAAS,8CA4FnB;IA/FD,uBAAmB;IAsCf;;;MAAoB;IAIpB;;MAQC;IA+CL,2BAKC;IAWD;;;;OAIG;IACH,gCAHW,SAAS,UACT,SAAS,QA8CnB;;CACJ;oBAv2CmB,qBAAqB"}
|
|
@@ -16,7 +16,7 @@ import ContainerView from "./containerView.js";
|
|
|
16
16
|
import LayerView from "./layerView.js";
|
|
17
17
|
import createTitle from "./title.js";
|
|
18
18
|
import UnitView from "./unitView.js";
|
|
19
|
-
import interactionToZoom from "./zoom.js";
|
|
19
|
+
import { interactionToZoom } from "./zoom.js";
|
|
20
20
|
import clamp from "../utils/clamp.js";
|
|
21
21
|
import { makeLerpSmoother } from "../utils/animator.js";
|
|
22
22
|
|
|
@@ -712,7 +712,8 @@ export default class GridView extends ContainerView {
|
|
|
712
712
|
pointedChild.view,
|
|
713
713
|
zoomEvent
|
|
714
714
|
),
|
|
715
|
-
this.context.getCurrentHover()
|
|
715
|
+
this.context.getCurrentHover(),
|
|
716
|
+
this.context.animator
|
|
716
717
|
);
|
|
717
718
|
}
|
|
718
719
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export default class Point {
|
|
2
|
+
/**
|
|
3
|
+
* @param {MouseEvent} event
|
|
4
|
+
*/
|
|
5
|
+
static fromMouseEvent(event: MouseEvent): Point;
|
|
2
6
|
/**
|
|
3
7
|
*
|
|
4
8
|
* @param {number} x
|
|
@@ -8,7 +12,19 @@ export default class Point {
|
|
|
8
12
|
/** @readonly */ readonly x: number;
|
|
9
13
|
/** @readonly */ readonly y: number;
|
|
10
14
|
/**
|
|
11
|
-
*
|
|
15
|
+
* @param {Point} point
|
|
16
|
+
*/
|
|
17
|
+
subtract(point: Point): Point;
|
|
18
|
+
/**
|
|
19
|
+
* @param {Point} point
|
|
20
|
+
*/
|
|
21
|
+
add(point: Point): Point;
|
|
22
|
+
/**
|
|
23
|
+
* @param {number} scalar
|
|
24
|
+
*/
|
|
25
|
+
multiply(scalar: number): Point;
|
|
26
|
+
get length(): number;
|
|
27
|
+
/**
|
|
12
28
|
* @param {Point} point
|
|
13
29
|
*/
|
|
14
30
|
equals(point: Point): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"point.d.ts","sourceRoot":"","sources":["../../../../src/view/layout/point.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"point.d.ts","sourceRoot":"","sources":["../../../../src/view/layout/point.js"],"names":[],"mappings":"AAIA;IACI;;OAEG;IACH,6BAFW,UAAU,SAIpB;IAED;;;;OAIG;IACH,eAHW,MAAM,KACN,MAAM,EAKhB;IAFG,gBAAgB,CAAC,mBAAU;IAC3B,gBAAgB,CAAC,mBAAU;IAG/B;;OAEG;IACH,gBAFW,KAAK,SAIf;IAED;;OAEG;IACH,WAFW,KAAK,SAIf;IAED;;OAEG;IACH,iBAFW,MAAM,SAIhB;IAED,qBAEC;IAED;;OAEG;IACH,cAFW,KAAK,WAQf;CACJ"}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hmm. This looks quite a bit like a two-dimensional vector.
|
|
3
|
+
* Maybe we should use a vector instead?
|
|
4
|
+
*/
|
|
1
5
|
export default class Point {
|
|
6
|
+
/**
|
|
7
|
+
* @param {MouseEvent} event
|
|
8
|
+
*/
|
|
9
|
+
static fromMouseEvent(event) {
|
|
10
|
+
return new Point(event.clientX, event.clientY);
|
|
11
|
+
}
|
|
12
|
+
|
|
2
13
|
/**
|
|
3
14
|
*
|
|
4
15
|
* @param {number} x
|
|
@@ -10,7 +21,31 @@ export default class Point {
|
|
|
10
21
|
}
|
|
11
22
|
|
|
12
23
|
/**
|
|
13
|
-
*
|
|
24
|
+
* @param {Point} point
|
|
25
|
+
*/
|
|
26
|
+
subtract(point) {
|
|
27
|
+
return new Point(this.x - point.x, this.y - point.y);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {Point} point
|
|
32
|
+
*/
|
|
33
|
+
add(point) {
|
|
34
|
+
return new Point(this.x - point.x, this.y - point.y);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {number} scalar
|
|
39
|
+
*/
|
|
40
|
+
multiply(scalar) {
|
|
41
|
+
return new Point(this.x * scalar, this.y * scalar);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get length() {
|
|
45
|
+
return Math.sqrt(this.x ** 2 + this.y ** 2);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
14
49
|
* @param {Point} point
|
|
15
50
|
*/
|
|
16
51
|
equals(point) {
|
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
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
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
|
}
|