@neo4j-nvl/interaction-handlers 0.3.8-dfca089f → 0.3.8-e4acdb02
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/lib/__tests__/zoom-interaction.test.d.ts +1 -0
- package/lib/__tests__/zoom-interaction.test.js +104 -0
- package/lib/interaction-handlers/box-select-interaction.js +3 -6
- package/lib/interaction-handlers/click-interaction.js +2 -3
- package/lib/interaction-handlers/drag-node-interaction.js +6 -12
- package/lib/interaction-handlers/draw-interaction.js +11 -14
- package/lib/interaction-handlers/hover-interaction.js +6 -7
- package/lib/interaction-handlers/lasso-interaction.js +2 -4
- package/lib/interaction-handlers/pan-interaction.js +3 -6
- package/lib/interaction-handlers/zoom-interaction.d.ts +5 -0
- package/lib/interaction-handlers/zoom-interaction.js +10 -1
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import NVL from '@neo4j-nvl/base';
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
3
|
+
import { ZoomInteraction } from '../interaction-handlers/zoom-interaction';
|
|
4
|
+
jest.mock('@neo4j-nvl/layout-workers');
|
|
5
|
+
describe('ZoomInteraction', () => {
|
|
6
|
+
let zoomInteraction;
|
|
7
|
+
let myNVL;
|
|
8
|
+
let myNvlContainer;
|
|
9
|
+
const zoomCallbackMock = jest.fn();
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
myNVL = new NVL(document.createElement('div'), [], [], { disableWebGL: true, initialZoom: 1, layout: 'free' });
|
|
12
|
+
myNvlContainer = myNVL.getContainer();
|
|
13
|
+
zoomInteraction = new ZoomInteraction(myNVL);
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
zoomInteraction.destroy();
|
|
17
|
+
myNVL.destroy();
|
|
18
|
+
zoomCallbackMock.mockReset();
|
|
19
|
+
});
|
|
20
|
+
const simulateWheelEvent = async (container, deltaY, delay = 50, eventOptions = {}) => {
|
|
21
|
+
const wheelEvent = new WheelEvent('wheel', {
|
|
22
|
+
deltaY,
|
|
23
|
+
clientX: 150,
|
|
24
|
+
clientY: 150,
|
|
25
|
+
...eventOptions
|
|
26
|
+
});
|
|
27
|
+
container.dispatchEvent(wheelEvent);
|
|
28
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
29
|
+
return wheelEvent;
|
|
30
|
+
};
|
|
31
|
+
const simulateSequentialWheelEvents = async (container, events) => {
|
|
32
|
+
for (const event of events) {
|
|
33
|
+
// eslint-disable-next-line no-await-in-loop
|
|
34
|
+
await simulateWheelEvent(container, event.deltaY, event.delay);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
test('zooming in should increase the scale', async () => {
|
|
38
|
+
zoomInteraction.updateCallback('onZoom', zoomCallbackMock);
|
|
39
|
+
const initialScale = myNVL.getScale();
|
|
40
|
+
const container = myNVL.getContainer();
|
|
41
|
+
await simulateWheelEvent(container, -100);
|
|
42
|
+
expect(zoomCallbackMock).toHaveBeenCalledTimes(1);
|
|
43
|
+
const newScale = myNVL.getScale();
|
|
44
|
+
expect(newScale).toBeGreaterThan(initialScale);
|
|
45
|
+
expect(newScale).toBe(1.2);
|
|
46
|
+
});
|
|
47
|
+
test('zooming out should decrease the scale', async () => {
|
|
48
|
+
zoomInteraction.updateCallback('onZoom', zoomCallbackMock);
|
|
49
|
+
const initialScale = myNVL.getScale();
|
|
50
|
+
await simulateWheelEvent(myNvlContainer, 100);
|
|
51
|
+
expect(zoomCallbackMock).toHaveBeenCalledTimes(1);
|
|
52
|
+
const newScale = myNVL.getScale();
|
|
53
|
+
expect(newScale).toBeLessThan(initialScale);
|
|
54
|
+
expect(newScale).toBe(0.8);
|
|
55
|
+
});
|
|
56
|
+
test('zoom callback should be called with the correct zoom level', async () => {
|
|
57
|
+
zoomInteraction.updateCallback('onZoom', zoomCallbackMock);
|
|
58
|
+
const wheelEvent = await simulateWheelEvent(myNvlContainer, -100);
|
|
59
|
+
expect(zoomCallbackMock).toHaveBeenCalledTimes(1);
|
|
60
|
+
const zoomCallbackMockArguments = zoomCallbackMock.mock.calls[0] ?? [];
|
|
61
|
+
expect(zoomCallbackMockArguments[0]).toBe(1.2);
|
|
62
|
+
expect(zoomCallbackMockArguments[1]).toBe(wheelEvent);
|
|
63
|
+
});
|
|
64
|
+
test('zooming should adjust pan to mouse position', async () => {
|
|
65
|
+
const initialPan = myNVL.getPan();
|
|
66
|
+
const initialScale = myNVL.getScale();
|
|
67
|
+
await simulateWheelEvent(myNvlContainer, -100);
|
|
68
|
+
const newPan = myNVL.getPan();
|
|
69
|
+
const newScale = myNVL.getScale();
|
|
70
|
+
expect(newPan).not.toEqual(initialPan);
|
|
71
|
+
expect(newPan.x).toBe(25);
|
|
72
|
+
expect(newPan.y).toBe(25);
|
|
73
|
+
expect(newScale).toBeGreaterThan(initialScale);
|
|
74
|
+
expect(newScale).toBe(1.2);
|
|
75
|
+
});
|
|
76
|
+
test('zooming should be progressively faster at higher zoom levels', async () => {
|
|
77
|
+
const sequentialWheelEvents = Array(8).fill({ deltaY: -100 });
|
|
78
|
+
await simulateSequentialWheelEvents(myNvlContainer, sequentialWheelEvents);
|
|
79
|
+
const newScale = myNVL.getScale();
|
|
80
|
+
expect(newScale).toBeGreaterThan(3.0);
|
|
81
|
+
expect(newScale).toBeCloseTo(4.299);
|
|
82
|
+
});
|
|
83
|
+
test('handles touchpad-like small incremental scrolling', async () => {
|
|
84
|
+
const initialScale = myNVL.getScale();
|
|
85
|
+
const touchpadEvents = Array(25).fill({ deltaY: -20, delay: 25 });
|
|
86
|
+
await simulateSequentialWheelEvents(myNvlContainer, touchpadEvents);
|
|
87
|
+
const newScale = myNVL.getScale();
|
|
88
|
+
expect(newScale).toBeGreaterThan(initialScale);
|
|
89
|
+
expect(newScale).toBeGreaterThan(2.5);
|
|
90
|
+
expect(newScale).toBeLessThan(2.7);
|
|
91
|
+
});
|
|
92
|
+
test('should not zoom when ctrl key is pressed', async () => {
|
|
93
|
+
const initialScale = myNVL.getScale();
|
|
94
|
+
await simulateWheelEvent(myNvlContainer, -100, 50, { ctrlKey: true });
|
|
95
|
+
expect(zoomCallbackMock).toHaveBeenCalledTimes(0);
|
|
96
|
+
expect(myNVL.getScale()).toBe(initialScale);
|
|
97
|
+
});
|
|
98
|
+
test('should not zoom when meta key is pressed', async () => {
|
|
99
|
+
const initialScale = myNVL.getScale();
|
|
100
|
+
await simulateWheelEvent(myNvlContainer, -100, 50, { metaKey: true });
|
|
101
|
+
expect(zoomCallbackMock).toHaveBeenCalledTimes(0);
|
|
102
|
+
expect(myNVL.getScale()).toBe(initialScale);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -9,10 +9,10 @@ import { getCanvasPosition, getWorldPosition } from './utils';
|
|
|
9
9
|
* For examples, head to the {@link https://neo4j.com/docs/nvl/current/interaction-handlers/#_boxselectinteraction Box Select Interaction documentation page}.
|
|
10
10
|
*/
|
|
11
11
|
export class BoxSelectInteraction extends BaseInteraction {
|
|
12
|
-
mousePosition;
|
|
13
|
-
startWorldPosition;
|
|
12
|
+
mousePosition = { x: 0, y: 0 };
|
|
13
|
+
startWorldPosition = { x: 0, y: 0 };
|
|
14
14
|
overlayRenderer;
|
|
15
|
-
isBoxSelecting;
|
|
15
|
+
isBoxSelecting = false;
|
|
16
16
|
/**
|
|
17
17
|
* Creates a new instance of the multi-select interaction handler.
|
|
18
18
|
* @param nvl - The NVL instance to attach the interaction handler to
|
|
@@ -20,10 +20,7 @@ export class BoxSelectInteraction extends BaseInteraction {
|
|
|
20
20
|
*/
|
|
21
21
|
constructor(nvl, options = { selectOnRelease: false }) {
|
|
22
22
|
super(nvl, options);
|
|
23
|
-
this.mousePosition = { x: 0, y: 0 };
|
|
24
|
-
this.startWorldPosition = { x: 0, y: 0 };
|
|
25
23
|
this.overlayRenderer = new OverlayRenderer(this.containerInstance);
|
|
26
|
-
this.isBoxSelecting = false;
|
|
27
24
|
this.addEventListener('mousedown', this.handleMouseDown, true);
|
|
28
25
|
this.addEventListener('mousemove', this.handleDrag, true);
|
|
29
26
|
this.addEventListener('mouseup', this.endBoxSelect, true);
|
|
@@ -7,8 +7,8 @@ import { isDraggingMovement } from './utils';
|
|
|
7
7
|
* For examples, head to the {@link https://neo4j.com/docs/nvl/current/interaction-handlers/#_clickinteraction Click Interaction documentation page}.
|
|
8
8
|
*/
|
|
9
9
|
export class ClickInteraction extends BaseInteraction {
|
|
10
|
-
moved;
|
|
11
|
-
mousePosition;
|
|
10
|
+
moved = false;
|
|
11
|
+
mousePosition = { x: 0, y: 0 };
|
|
12
12
|
/**
|
|
13
13
|
* Creates a new click interaction handler.
|
|
14
14
|
* @param nvl - The NVL instance to attach the interaction handler to
|
|
@@ -16,7 +16,6 @@ export class ClickInteraction extends BaseInteraction {
|
|
|
16
16
|
*/
|
|
17
17
|
constructor(nvl, options = { selectOnClick: false }) {
|
|
18
18
|
super(nvl, options);
|
|
19
|
-
this.mousePosition = { x: 0, y: 0 };
|
|
20
19
|
this.addEventListener('mousedown', this.handleMouseDown, true);
|
|
21
20
|
this.addEventListener('click', this.handleClick, true);
|
|
22
21
|
this.addEventListener('dblclick', this.handleDoubleClick, true);
|
|
@@ -9,24 +9,18 @@ import { isDraggingMovement } from './utils';
|
|
|
9
9
|
* For examples, head to the {@link https://neo4j.com/docs/nvl/current/interaction-handlers/#_dragnodeinteraction Drag Node Interaction documentation page}.
|
|
10
10
|
*/
|
|
11
11
|
export class DragNodeInteraction extends BaseInteraction {
|
|
12
|
-
mousePosition;
|
|
13
|
-
mouseDownNode;
|
|
14
|
-
isDragging;
|
|
15
|
-
isDrawing;
|
|
16
|
-
selectedNodes;
|
|
17
|
-
moveSelectedNodes;
|
|
12
|
+
mousePosition = { x: 0, y: 0 };
|
|
13
|
+
mouseDownNode = null;
|
|
14
|
+
isDragging = false;
|
|
15
|
+
isDrawing = false;
|
|
16
|
+
selectedNodes = [];
|
|
17
|
+
moveSelectedNodes = false;
|
|
18
18
|
/**
|
|
19
19
|
* Creates a new instance of the drag node interaction handler.
|
|
20
20
|
* @param nvl - The NVL instance to attach the interaction handler to
|
|
21
21
|
*/
|
|
22
22
|
constructor(nvl, options = {}) {
|
|
23
23
|
super(nvl, options);
|
|
24
|
-
this.mouseDownNode = null;
|
|
25
|
-
this.mousePosition = { x: 0, y: 0 };
|
|
26
|
-
this.isDragging = false;
|
|
27
|
-
this.isDrawing = false;
|
|
28
|
-
this.selectedNodes = [];
|
|
29
|
-
this.moveSelectedNodes = false;
|
|
30
24
|
this.addEventListener('mousedown', this.handleMouseDown);
|
|
31
25
|
this.addEventListener('mousemove', this.handleMouseMove);
|
|
32
26
|
}
|
|
@@ -16,22 +16,19 @@ const DefaultGhostGraphStyling = {
|
|
|
16
16
|
* @internal
|
|
17
17
|
*/
|
|
18
18
|
export class DrawInteraction extends BaseInteraction {
|
|
19
|
-
isMoved;
|
|
20
|
-
isDrawing;
|
|
21
|
-
isDraggingNode;
|
|
22
|
-
mouseDownNode;
|
|
23
|
-
newTempTargetNode;
|
|
24
|
-
newTempRegularRelationshipToNewTempTargetNode;
|
|
25
|
-
newTempRegularRelationshipToExistingNode;
|
|
26
|
-
newTempSelfReferredRelationship;
|
|
27
|
-
newTargetNodeToAdd;
|
|
28
|
-
newRelationshipToAdd;
|
|
29
|
-
mouseOutsideOfNvlArea;
|
|
19
|
+
isMoved = false;
|
|
20
|
+
isDrawing = false;
|
|
21
|
+
isDraggingNode = false;
|
|
22
|
+
mouseDownNode = undefined;
|
|
23
|
+
newTempTargetNode = null;
|
|
24
|
+
newTempRegularRelationshipToNewTempTargetNode = null;
|
|
25
|
+
newTempRegularRelationshipToExistingNode = null;
|
|
26
|
+
newTempSelfReferredRelationship = null;
|
|
27
|
+
newTargetNodeToAdd = null;
|
|
28
|
+
newRelationshipToAdd = null;
|
|
29
|
+
mouseOutsideOfNvlArea = false;
|
|
30
30
|
constructor(nvl, options = {}) {
|
|
31
31
|
super(nvl, options);
|
|
32
|
-
this.isMoved = false;
|
|
33
|
-
this.isDrawing = false;
|
|
34
|
-
this.isDraggingNode = false;
|
|
35
32
|
this.nvlInstance.setLayout('free');
|
|
36
33
|
this.addEventListener('mousemove', this.handleMouseMove, true);
|
|
37
34
|
this.addEventListener('mousedown', this.handleMouseDown, true);
|
|
@@ -5,15 +5,14 @@ import { BaseInteraction } from './base';
|
|
|
5
5
|
* For examples, head to the {@link https://neo4j.com/docs/nvl/current/interaction-handlers/#_hoverinteraction Hover Interaction documentation page}.
|
|
6
6
|
*/
|
|
7
7
|
export class HoverInteraction extends BaseInteraction {
|
|
8
|
-
currentHoveredElementId;
|
|
9
|
-
currentHoveredElementIsNode;
|
|
10
|
-
updates
|
|
8
|
+
currentHoveredElementId = undefined;
|
|
9
|
+
currentHoveredElementIsNode = undefined;
|
|
10
|
+
updates = {
|
|
11
|
+
nodes: [],
|
|
12
|
+
relationships: []
|
|
13
|
+
};
|
|
11
14
|
constructor(nvl, options = { drawShadowOnHover: false }) {
|
|
12
15
|
super(nvl, options);
|
|
13
|
-
this.updates = {
|
|
14
|
-
nodes: [],
|
|
15
|
-
relationships: []
|
|
16
|
-
};
|
|
17
16
|
this.addEventListener('mousemove', this.handleHover, true);
|
|
18
17
|
}
|
|
19
18
|
/**
|
|
@@ -69,8 +69,8 @@ export const checkPointInside = (x, y, vs) => {
|
|
|
69
69
|
* For examples, head to the {@link https://neo4j.com/docs/nvl/current/interaction-handlers/#_lassointeraction Lasso Interaction documentation page}.
|
|
70
70
|
*/
|
|
71
71
|
export class LassoInteraction extends BaseInteraction {
|
|
72
|
-
active;
|
|
73
|
-
points;
|
|
72
|
+
active = false;
|
|
73
|
+
points = [];
|
|
74
74
|
overlayRenderer;
|
|
75
75
|
/**
|
|
76
76
|
* Creates a new instance of the lasso interaction handler.
|
|
@@ -80,8 +80,6 @@ export class LassoInteraction extends BaseInteraction {
|
|
|
80
80
|
constructor(nvl, options = { selectOnRelease: false }) {
|
|
81
81
|
super(nvl, options);
|
|
82
82
|
this.overlayRenderer = new OverlayRenderer(this.containerInstance);
|
|
83
|
-
this.active = false;
|
|
84
|
-
this.points = [];
|
|
85
83
|
this.addEventListener('mousedown', this.handleMouseDown, true);
|
|
86
84
|
this.addEventListener('mousemove', this.handleDrag, true);
|
|
87
85
|
this.addEventListener('mouseup', this.handleMouseUp, true);
|
|
@@ -7,9 +7,9 @@ import { BaseInteraction } from './base';
|
|
|
7
7
|
* For examples, head to the {@link https://neo4j.com/docs/nvl/current/interaction-handlers/#_paninteraction Pan Interaction documentation page}.
|
|
8
8
|
*/
|
|
9
9
|
export class PanInteraction extends BaseInteraction {
|
|
10
|
-
mousePosition;
|
|
11
|
-
targets;
|
|
12
|
-
shouldPan;
|
|
10
|
+
mousePosition = { x: 0, y: 0 };
|
|
11
|
+
targets = [];
|
|
12
|
+
shouldPan = false;
|
|
13
13
|
isPanning = false;
|
|
14
14
|
/**
|
|
15
15
|
* Creates a new instance of the pan interaction handler.
|
|
@@ -17,9 +17,6 @@ export class PanInteraction extends BaseInteraction {
|
|
|
17
17
|
*/
|
|
18
18
|
constructor(nvl, options = { excludeNodeMargin: false }) {
|
|
19
19
|
super(nvl, options);
|
|
20
|
-
this.mousePosition = { x: 0, y: 0 };
|
|
21
|
-
this.targets = [];
|
|
22
|
-
this.shouldPan = false;
|
|
23
20
|
this.addEventListener('mousedown', this.handleMouseDown, true);
|
|
24
21
|
this.addEventListener('mousemove', this.handleMouseMove, true);
|
|
25
22
|
this.addEventListener('mouseup', this.handleMouseUp, true);
|
|
@@ -34,6 +34,11 @@ export declare class ZoomInteraction extends BaseInteraction<ZoomInteractionCall
|
|
|
34
34
|
* The throttle is set to 25ms.
|
|
35
35
|
*/
|
|
36
36
|
private throttledZoom;
|
|
37
|
+
/**
|
|
38
|
+
* The function to be called on mouse wheel event on the canvas.
|
|
39
|
+
* @param evt - The mouse wheel event
|
|
40
|
+
* @note If the ctrl key or meta key is pressed, it does not zoom to avoid conflicts with default browser behavior.
|
|
41
|
+
*/
|
|
37
42
|
private handleWheel;
|
|
38
43
|
/**
|
|
39
44
|
* Removes the relevant event listeners from the canvas.
|
|
@@ -29,7 +29,8 @@ export class ZoomInteraction extends BaseInteraction {
|
|
|
29
29
|
throttledZoom = throttle((event) => {
|
|
30
30
|
const zoom = this.nvlInstance.getScale();
|
|
31
31
|
const { x, y } = this.nvlInstance.getPan();
|
|
32
|
-
const
|
|
32
|
+
const baseSpeed = event.deltaY / 500;
|
|
33
|
+
const factor = zoom >= 1 ? baseSpeed * Math.pow(zoom, 1) : baseSpeed;
|
|
33
34
|
const newZoomTarget = zoom - factor * Math.min(1, zoom);
|
|
34
35
|
const offs = getCanvasCenterOffset(this.containerInstance, event);
|
|
35
36
|
const newPanX = x + (offs.x / zoom - offs.x / newZoomTarget);
|
|
@@ -37,7 +38,15 @@ export class ZoomInteraction extends BaseInteraction {
|
|
|
37
38
|
this.nvlInstance.setZoomAndPan(newZoomTarget, newPanX, newPanY);
|
|
38
39
|
this.callCallbackIfRegistered('onZoom', newZoomTarget, event);
|
|
39
40
|
}, 25, { leading: true });
|
|
41
|
+
/**
|
|
42
|
+
* The function to be called on mouse wheel event on the canvas.
|
|
43
|
+
* @param evt - The mouse wheel event
|
|
44
|
+
* @note If the ctrl key or meta key is pressed, it does not zoom to avoid conflicts with default browser behavior.
|
|
45
|
+
*/
|
|
40
46
|
handleWheel = (evt) => {
|
|
47
|
+
if (evt.ctrlKey || evt.metaKey) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
41
50
|
evt.preventDefault();
|
|
42
51
|
this.throttledZoom(evt);
|
|
43
52
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neo4j-nvl/interaction-handlers",
|
|
3
|
-
"version": "0.3.8-
|
|
3
|
+
"version": "0.3.8-e4acdb02",
|
|
4
4
|
"license": "SEE LICENSE IN 'LICENSE.txt'",
|
|
5
5
|
"homepage": "https://neo4j.com/docs/nvl/current/",
|
|
6
6
|
"description": "Interaction handlers for the Neo4j Visualization Library",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"eslint": "yarn global:eslint ./src/"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@neo4j-nvl/base": "0.3.8-
|
|
30
|
+
"@neo4j-nvl/base": "0.3.8-e4acdb02",
|
|
31
31
|
"concaveman": "^1.2.1",
|
|
32
32
|
"lodash": "4.17.21"
|
|
33
33
|
},
|