@neo4j-nvl/interaction-handlers 0.2.53 → 0.2.55
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/index.d.ts +2 -2
- package/lib/interaction-handlers/base.js +43 -88
- package/lib/interaction-handlers/box-select-interaction.js +63 -106
- package/lib/interaction-handlers/click-interaction.js +92 -120
- package/lib/interaction-handlers/drag-node-interaction.js +88 -138
- package/lib/interaction-handlers/draw-interaction.d.ts +8 -2
- package/lib/interaction-handlers/draw-interaction.js +180 -231
- package/lib/interaction-handlers/hover-interaction.js +47 -64
- package/lib/interaction-handlers/lasso-interaction.js +92 -127
- package/lib/interaction-handlers/pan-interaction.js +46 -81
- package/lib/interaction-handlers/zoom-interaction.js +32 -47
- package/lib/overlay-renderer/overlay-renderer.js +14 -24
- package/package.json +4 -4
|
@@ -19,6 +19,9 @@ const shapeShowTime = 500;
|
|
|
19
19
|
* })
|
|
20
20
|
*/
|
|
21
21
|
export class LassoInteraction extends BaseInteraction {
|
|
22
|
+
active;
|
|
23
|
+
points;
|
|
24
|
+
overlayRenderer;
|
|
22
25
|
/**
|
|
23
26
|
* Creates a new instance of the lasso interaction handler.
|
|
24
27
|
* @param nvl - The NVL instance to attach the interaction handler to
|
|
@@ -26,141 +29,103 @@ export class LassoInteraction extends BaseInteraction {
|
|
|
26
29
|
*/
|
|
27
30
|
constructor(nvl, options = { selectOnRelease: false }) {
|
|
28
31
|
super(nvl, options);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
value: (event) => {
|
|
52
|
-
this.active = true;
|
|
53
|
-
this.points = [getCanvasPosition(this.containerInstance, event)];
|
|
54
|
-
this.callCallbackIfRegistered('onLassoStarted', event);
|
|
32
|
+
this.overlayRenderer = new OverlayRenderer(this.containerInstance);
|
|
33
|
+
this.active = false;
|
|
34
|
+
this.addEventListener('mousedown', this.handleMouseDown, true);
|
|
35
|
+
this.addEventListener('mousemove', this.handleDrag, true);
|
|
36
|
+
this.addEventListener('mouseup', this.handleMouseUp, true);
|
|
37
|
+
this.addEventListener('mouseleave', this.endLasso, true);
|
|
38
|
+
}
|
|
39
|
+
startLasso = (event) => {
|
|
40
|
+
this.active = true;
|
|
41
|
+
this.points = [getCanvasPosition(this.containerInstance, event)];
|
|
42
|
+
this.callCallbackIfRegistered('onLassoStarted', event);
|
|
43
|
+
};
|
|
44
|
+
handleMouseDown = (event) => {
|
|
45
|
+
if (event.button === 0 && !this.active) {
|
|
46
|
+
this.startLasso(event);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
handleDrag = (event) => {
|
|
50
|
+
if (this.active) {
|
|
51
|
+
const lastPoint = this.points[this.points.length - 1];
|
|
52
|
+
if (lastPoint === undefined) {
|
|
53
|
+
return;
|
|
55
54
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (event.button === 0 && !this.active) {
|
|
63
|
-
this.startLasso(event);
|
|
64
|
-
}
|
|
55
|
+
const pos = getCanvasPosition(this.containerInstance, event);
|
|
56
|
+
const dx = Math.abs(lastPoint.x - pos.x);
|
|
57
|
+
const dy = Math.abs(lastPoint.y - pos.y);
|
|
58
|
+
if (dx > pointDist || dy > pointDist) {
|
|
59
|
+
this.points.push(pos);
|
|
60
|
+
this.overlayRenderer.drawLasso(this.points, true, false);
|
|
65
61
|
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
handleMouseUp = (event) => {
|
|
65
|
+
this.points.push(getCanvasPosition(this.containerInstance, event));
|
|
66
|
+
this.endLasso(event);
|
|
67
|
+
};
|
|
68
|
+
getLassoItems = (points) => {
|
|
69
|
+
const inside = (x, y, vs) => {
|
|
70
|
+
// ray-casting algorithm based on
|
|
71
|
+
// https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
|
|
72
|
+
let isInside = false;
|
|
73
|
+
for (let i = 0, j = vs.length - 1; i < vs.length; j = i, i += 1) {
|
|
74
|
+
const vsI = vs[i];
|
|
75
|
+
const vsJ = vs[j];
|
|
76
|
+
if (vsI === undefined || vsJ === undefined) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const { x: xi, y: yi } = vsI;
|
|
80
|
+
const { x: xj, y: yj } = vsJ;
|
|
81
|
+
const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
|
|
82
|
+
if (intersect) {
|
|
83
|
+
isInside = !isInside;
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
return isInside;
|
|
87
|
+
};
|
|
88
|
+
const worldPoints = points.map((p) => getWorldPosition(this.nvlInstance, p));
|
|
89
|
+
const nodePositions = this.nvlInstance.getNodePositions();
|
|
90
|
+
const hitNodes = new Set();
|
|
91
|
+
for (const pos of nodePositions) {
|
|
92
|
+
if (pos.x === undefined || pos.y === undefined || pos.id === undefined) {
|
|
93
|
+
continue;
|
|
91
94
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
enumerable: true,
|
|
95
|
-
configurable: true,
|
|
96
|
-
writable: true,
|
|
97
|
-
value: (points) => {
|
|
98
|
-
const inside = (x, y, vs) => {
|
|
99
|
-
// ray-casting algorithm based on
|
|
100
|
-
// https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
|
|
101
|
-
let isInside = false;
|
|
102
|
-
for (let i = 0, j = vs.length - 1; i < vs.length; j = i, i += 1) {
|
|
103
|
-
const { x: xi, y: yi } = vs[i];
|
|
104
|
-
const { x: xj, y: yj } = vs[j];
|
|
105
|
-
const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
|
|
106
|
-
if (intersect) {
|
|
107
|
-
isInside = !isInside;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return isInside;
|
|
111
|
-
};
|
|
112
|
-
const worldPoints = points.map((p) => getWorldPosition(this.nvlInstance, p));
|
|
113
|
-
const nodePositions = this.nvlInstance.getNodePositions();
|
|
114
|
-
const hitNodes = new Set();
|
|
115
|
-
for (const pos of nodePositions) {
|
|
116
|
-
if (pos.x === undefined || pos.y === undefined || pos.id === undefined) {
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (inside(pos.x, pos.y, worldPoints)) {
|
|
120
|
-
hitNodes.add(pos.id);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
const rels = this.nvlInstance.getRelationships();
|
|
124
|
-
const hitRels = [];
|
|
125
|
-
for (const rel of rels) {
|
|
126
|
-
if (hitNodes.has(rel.from) && hitNodes.has(rel.to)) {
|
|
127
|
-
hitRels.push(rel);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
const hitNodeArray = Array.from(hitNodes).map((id) => this.nvlInstance.getNodeById(id));
|
|
131
|
-
return {
|
|
132
|
-
nodes: hitNodeArray,
|
|
133
|
-
rels: hitRels
|
|
134
|
-
};
|
|
95
|
+
if (inside(pos.x, pos.y, worldPoints)) {
|
|
96
|
+
hitNodes.add(pos.id);
|
|
135
97
|
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (!this.active) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
this.active = false;
|
|
146
|
-
const pointArrays = this.points.map((p) => [p.x, p.y]);
|
|
147
|
-
const hull = concaveman(pointArrays, 3).map((p) => ({ x: p[0], y: p[1] }));
|
|
148
|
-
this.overlayRenderer.drawLasso(hull, false, true);
|
|
149
|
-
setTimeout(() => this.overlayRenderer.clear(), shapeShowTime);
|
|
150
|
-
const hitItems = this.getLassoItems(hull);
|
|
151
|
-
if (this.currentOptions.selectOnRelease) {
|
|
152
|
-
this.nvlInstance.updateElementsInGraph(hitItems.nodes.map((node) => ({ id: node.id, selected: true })), hitItems.rels.map((rel) => ({ id: rel.id, selected: true })));
|
|
153
|
-
}
|
|
154
|
-
this.callCallbackIfRegistered('onLassoSelect', hitItems, event);
|
|
98
|
+
}
|
|
99
|
+
const rels = this.nvlInstance.getRelationships();
|
|
100
|
+
const hitRels = [];
|
|
101
|
+
for (const rel of rels) {
|
|
102
|
+
if (hitNodes.has(rel.from) && hitNodes.has(rel.to)) {
|
|
103
|
+
hitRels.push(rel);
|
|
155
104
|
}
|
|
156
|
-
}
|
|
157
|
-
|
|
105
|
+
}
|
|
106
|
+
const hitNodeArray = Array.from(hitNodes).map((id) => this.nvlInstance.getNodeById(id));
|
|
107
|
+
return {
|
|
108
|
+
nodes: hitNodeArray,
|
|
109
|
+
rels: hitRels
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
endLasso = (event) => {
|
|
113
|
+
if (!this.active) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
158
116
|
this.active = false;
|
|
159
|
-
this.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
117
|
+
const pointArrays = this.points.map((p) => [p.x, p.y]);
|
|
118
|
+
const hull = concaveman(pointArrays, 3)
|
|
119
|
+
.map((p) => ({ x: p[0], y: p[1] }))
|
|
120
|
+
.filter((point) => point.x !== undefined && point.y !== undefined);
|
|
121
|
+
this.overlayRenderer.drawLasso(hull, false, true);
|
|
122
|
+
setTimeout(() => this.overlayRenderer.clear(), shapeShowTime);
|
|
123
|
+
const hitItems = this.getLassoItems(hull);
|
|
124
|
+
if (this.currentOptions.selectOnRelease === true) {
|
|
125
|
+
this.nvlInstance.updateElementsInGraph(hitItems.nodes.map((node) => ({ id: node.id, selected: true })), hitItems.rels.map((rel) => ({ id: rel.id, selected: true })));
|
|
126
|
+
}
|
|
127
|
+
this.callCallbackIfRegistered('onLassoSelect', hitItems, event);
|
|
128
|
+
};
|
|
164
129
|
/**
|
|
165
130
|
* Removes all related event listeners and the overlay renderer for the box.
|
|
166
131
|
*/
|
|
@@ -15,93 +15,15 @@ import { BaseInteraction } from './base';
|
|
|
15
15
|
* })
|
|
16
16
|
*/
|
|
17
17
|
export class PanInteraction extends BaseInteraction {
|
|
18
|
+
mousePosition;
|
|
19
|
+
targets;
|
|
20
|
+
shouldPan;
|
|
18
21
|
/**
|
|
19
22
|
* Creates a new instance of the pan interaction handler.
|
|
20
23
|
* @param nvl - The NVL instance to attach the interaction handler to
|
|
21
24
|
*/
|
|
22
25
|
constructor(nvl, options = { excludeNodeMargin: false }) {
|
|
23
26
|
super(nvl, options);
|
|
24
|
-
Object.defineProperty(this, "mousePosition", {
|
|
25
|
-
enumerable: true,
|
|
26
|
-
configurable: true,
|
|
27
|
-
writable: true,
|
|
28
|
-
value: void 0
|
|
29
|
-
});
|
|
30
|
-
Object.defineProperty(this, "targets", {
|
|
31
|
-
enumerable: true,
|
|
32
|
-
configurable: true,
|
|
33
|
-
writable: true,
|
|
34
|
-
value: void 0
|
|
35
|
-
});
|
|
36
|
-
Object.defineProperty(this, "shouldPan", {
|
|
37
|
-
enumerable: true,
|
|
38
|
-
configurable: true,
|
|
39
|
-
writable: true,
|
|
40
|
-
value: void 0
|
|
41
|
-
});
|
|
42
|
-
/**
|
|
43
|
-
* Updates which type of graph elements should hinder panning.
|
|
44
|
-
* @param targets - The graph elements that should hinder panning
|
|
45
|
-
* @param excludeNodeMargin - If true, the node margin will not hinder panning
|
|
46
|
-
* @note By default, panning is hindered by nodes and relationships.
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* ```js
|
|
50
|
-
* // Pan canvas even when dragging on nodes and relationships
|
|
51
|
-
* panInteraction.updateTargets([], true)
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
|
-
Object.defineProperty(this, "updateTargets", {
|
|
55
|
-
enumerable: true,
|
|
56
|
-
configurable: true,
|
|
57
|
-
writable: true,
|
|
58
|
-
value: (targets, excludeNodeMargin) => {
|
|
59
|
-
this.targets = targets;
|
|
60
|
-
this.currentOptions.excludeNodeMargin = excludeNodeMargin;
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
Object.defineProperty(this, "handleMouseDown", {
|
|
64
|
-
enumerable: true,
|
|
65
|
-
configurable: true,
|
|
66
|
-
writable: true,
|
|
67
|
-
value: (event) => {
|
|
68
|
-
const hits = this.nvlInstance.getHits(event, difference(['node', 'relationship'], this.targets), {
|
|
69
|
-
hitNodeMarginWidth: this.currentOptions.excludeNodeMargin ? NODE_EDGE_WIDTH : 0
|
|
70
|
-
});
|
|
71
|
-
if (hits.nvlTargets.nodes.length > 0 || hits.nvlTargets.relationships.length > 0) {
|
|
72
|
-
this.shouldPan = false;
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
this.mousePosition = { x: event.clientX, y: event.clientY };
|
|
76
|
-
this.shouldPan = true;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
Object.defineProperty(this, "handleMouseMove", {
|
|
81
|
-
enumerable: true,
|
|
82
|
-
configurable: true,
|
|
83
|
-
writable: true,
|
|
84
|
-
value: (evt) => {
|
|
85
|
-
if (!this.shouldPan || evt.buttons !== 1) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
const zoom = this.nvlInstance.getScale();
|
|
89
|
-
const { x, y } = this.nvlInstance.getPan();
|
|
90
|
-
const dx = ((evt.clientX - this.mousePosition.x) / zoom) * window.devicePixelRatio;
|
|
91
|
-
const dy = ((evt.clientY - this.mousePosition.y) / zoom) * window.devicePixelRatio;
|
|
92
|
-
this.nvlInstance.setPan(x - dx, y - dy);
|
|
93
|
-
this.callCallbackIfRegistered('onPan', { x: x - dx, y: y - dy }, evt);
|
|
94
|
-
this.mousePosition = { x: evt.clientX, y: evt.clientY };
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
Object.defineProperty(this, "handleMouseUp", {
|
|
98
|
-
enumerable: true,
|
|
99
|
-
configurable: true,
|
|
100
|
-
writable: true,
|
|
101
|
-
value: () => {
|
|
102
|
-
this.shouldPan = false;
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
27
|
this.mousePosition = { x: 0, y: 0 };
|
|
106
28
|
this.targets = [];
|
|
107
29
|
this.shouldPan = false;
|
|
@@ -109,6 +31,49 @@ export class PanInteraction extends BaseInteraction {
|
|
|
109
31
|
this.addEventListener('mousemove', this.handleMouseMove, true);
|
|
110
32
|
this.addEventListener('mouseup', this.handleMouseUp, true);
|
|
111
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Updates which type of graph elements should hinder panning.
|
|
36
|
+
* @param targets - The graph elements that should hinder panning
|
|
37
|
+
* @param excludeNodeMargin - If true, the node margin will not hinder panning
|
|
38
|
+
* @note By default, panning is hindered by nodes and relationships.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```js
|
|
42
|
+
* // Pan canvas even when dragging on nodes and relationships
|
|
43
|
+
* panInteraction.updateTargets([], true)
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
updateTargets = (targets, excludeNodeMargin) => {
|
|
47
|
+
this.targets = targets;
|
|
48
|
+
this.currentOptions.excludeNodeMargin = excludeNodeMargin;
|
|
49
|
+
};
|
|
50
|
+
handleMouseDown = (event) => {
|
|
51
|
+
const hits = this.nvlInstance.getHits(event, difference(['node', 'relationship'], this.targets), {
|
|
52
|
+
hitNodeMarginWidth: this.currentOptions.excludeNodeMargin ? NODE_EDGE_WIDTH : 0
|
|
53
|
+
});
|
|
54
|
+
if (hits.nvlTargets.nodes.length > 0 || hits.nvlTargets.relationships.length > 0) {
|
|
55
|
+
this.shouldPan = false;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
this.mousePosition = { x: event.clientX, y: event.clientY };
|
|
59
|
+
this.shouldPan = true;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
handleMouseMove = (evt) => {
|
|
63
|
+
if (!this.shouldPan || evt.buttons !== 1) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const zoom = this.nvlInstance.getScale();
|
|
67
|
+
const { x, y } = this.nvlInstance.getPan();
|
|
68
|
+
const dx = ((evt.clientX - this.mousePosition.x) / zoom) * window.devicePixelRatio;
|
|
69
|
+
const dy = ((evt.clientY - this.mousePosition.y) / zoom) * window.devicePixelRatio;
|
|
70
|
+
this.nvlInstance.setPan(x - dx, y - dy);
|
|
71
|
+
this.callCallbackIfRegistered('onPan', { x: x - dx, y: y - dy }, evt);
|
|
72
|
+
this.mousePosition = { x: evt.clientX, y: evt.clientY };
|
|
73
|
+
};
|
|
74
|
+
handleMouseUp = () => {
|
|
75
|
+
this.shouldPan = false;
|
|
76
|
+
};
|
|
112
77
|
/**
|
|
113
78
|
* Removes the related event listeners from the canvas.
|
|
114
79
|
*/
|
|
@@ -22,53 +22,38 @@ export class ZoomInteraction extends BaseInteraction {
|
|
|
22
22
|
*/
|
|
23
23
|
constructor(nvl, options = {}) {
|
|
24
24
|
super(nvl, options);
|
|
25
|
-
/**
|
|
26
|
-
* Throttled zoom function to avoid events happening too fast.
|
|
27
|
-
* @param event - The original mouse wheel event
|
|
28
|
-
* @note "Wheel" with a touchpad, the wheel is triggered event a lot,
|
|
29
|
-
* especially a lot of very small values.
|
|
30
|
-
* However, updating values in NVL instance takes time.
|
|
31
|
-
* Sometimes it lost the track of multiple events happening too soon.
|
|
32
|
-
* As a result, the zoom might lose its anchor point under touch pad.
|
|
33
|
-
* Therefore, the throttle is needed to avoid events happening too fast.
|
|
34
|
-
* @note The throttle is set to 25ms.
|
|
35
|
-
*/
|
|
36
|
-
Object.defineProperty(this, "throttledZoom", {
|
|
37
|
-
enumerable: true,
|
|
38
|
-
configurable: true,
|
|
39
|
-
writable: true,
|
|
40
|
-
value: throttle((event) => {
|
|
41
|
-
const zoom = this.nvlInstance.getScale();
|
|
42
|
-
const { x, y } = this.nvlInstance.getPan();
|
|
43
|
-
const factor = event.deltaY / 500;
|
|
44
|
-
const newZoomTarget = zoom - factor * Math.min(1, zoom);
|
|
45
|
-
const offs = getCanvasCenterOffset(this.containerInstance, event);
|
|
46
|
-
const newPanX = x + (offs.x / zoom - offs.x / newZoomTarget);
|
|
47
|
-
const newPanY = y + (offs.y / zoom - offs.y / newZoomTarget);
|
|
48
|
-
this.nvlInstance.setZoomAndPan(newZoomTarget, newPanX, newPanY);
|
|
49
|
-
this.callCallbackIfRegistered('onZoom', newZoomTarget, event);
|
|
50
|
-
}, 25, { leading: true })
|
|
51
|
-
});
|
|
52
|
-
Object.defineProperty(this, "handleWheel", {
|
|
53
|
-
enumerable: true,
|
|
54
|
-
configurable: true,
|
|
55
|
-
writable: true,
|
|
56
|
-
value: (evt) => {
|
|
57
|
-
evt.preventDefault();
|
|
58
|
-
this.throttledZoom(evt);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
/**
|
|
62
|
-
* Removes the relevant event listeners from the canvas.
|
|
63
|
-
*/
|
|
64
|
-
Object.defineProperty(this, "destroy", {
|
|
65
|
-
enumerable: true,
|
|
66
|
-
configurable: true,
|
|
67
|
-
writable: true,
|
|
68
|
-
value: () => {
|
|
69
|
-
this.removeEventListener('wheel', this.handleWheel);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
25
|
this.addEventListener('wheel', this.handleWheel);
|
|
73
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Throttled zoom function to avoid events happening too fast.
|
|
29
|
+
* @param event - The original mouse wheel event
|
|
30
|
+
* @note "Wheel" with a touchpad, the wheel is triggered event a lot,
|
|
31
|
+
* especially a lot of very small values.
|
|
32
|
+
* However, updating values in NVL instance takes time.
|
|
33
|
+
* Sometimes it lost the track of multiple events happening too soon.
|
|
34
|
+
* As a result, the zoom might lose its anchor point under touch pad.
|
|
35
|
+
* Therefore, the throttle is needed to avoid events happening too fast.
|
|
36
|
+
* @note The throttle is set to 25ms.
|
|
37
|
+
*/
|
|
38
|
+
throttledZoom = throttle((event) => {
|
|
39
|
+
const zoom = this.nvlInstance.getScale();
|
|
40
|
+
const { x, y } = this.nvlInstance.getPan();
|
|
41
|
+
const factor = event.deltaY / 500;
|
|
42
|
+
const newZoomTarget = zoom - factor * Math.min(1, zoom);
|
|
43
|
+
const offs = getCanvasCenterOffset(this.containerInstance, event);
|
|
44
|
+
const newPanX = x + (offs.x / zoom - offs.x / newZoomTarget);
|
|
45
|
+
const newPanY = y + (offs.y / zoom - offs.y / newZoomTarget);
|
|
46
|
+
this.nvlInstance.setZoomAndPan(newZoomTarget, newPanX, newPanY);
|
|
47
|
+
this.callCallbackIfRegistered('onZoom', newZoomTarget, event);
|
|
48
|
+
}, 25, { leading: true });
|
|
49
|
+
handleWheel = (evt) => {
|
|
50
|
+
evt.preventDefault();
|
|
51
|
+
this.throttledZoom(evt);
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Removes the relevant event listeners from the canvas.
|
|
55
|
+
*/
|
|
56
|
+
destroy = () => {
|
|
57
|
+
this.removeEventListener('wheel', this.handleWheel);
|
|
58
|
+
};
|
|
74
59
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import onResize from 'resizelistener';
|
|
2
1
|
const marquee = {
|
|
3
2
|
frameWidth: 2,
|
|
4
3
|
frameColor: '#a9a9a9',
|
|
@@ -6,27 +5,11 @@ const marquee = {
|
|
|
6
5
|
opacity: 0.5
|
|
7
6
|
};
|
|
8
7
|
export class OverlayRenderer {
|
|
8
|
+
ctx;
|
|
9
|
+
canvas;
|
|
10
|
+
removeResizeListener;
|
|
9
11
|
constructor(canvasParent) {
|
|
10
|
-
Object.defineProperty(this, "ctx", {
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
writable: true,
|
|
14
|
-
value: void 0
|
|
15
|
-
});
|
|
16
|
-
Object.defineProperty(this, "canvas", {
|
|
17
|
-
enumerable: true,
|
|
18
|
-
configurable: true,
|
|
19
|
-
writable: true,
|
|
20
|
-
value: void 0
|
|
21
|
-
});
|
|
22
|
-
Object.defineProperty(this, "removeResizeListener", {
|
|
23
|
-
enumerable: true,
|
|
24
|
-
configurable: true,
|
|
25
|
-
writable: true,
|
|
26
|
-
value: void 0
|
|
27
|
-
});
|
|
28
12
|
const canvas = document.createElement('canvas');
|
|
29
|
-
canvas.id = 'OVERLAY';
|
|
30
13
|
canvas.style.position = 'absolute';
|
|
31
14
|
canvas.style.top = '0';
|
|
32
15
|
canvas.style.bottom = '0';
|
|
@@ -38,13 +21,18 @@ export class OverlayRenderer {
|
|
|
38
21
|
const context = canvas.getContext('2d');
|
|
39
22
|
this.ctx = context;
|
|
40
23
|
this.canvas = canvas;
|
|
41
|
-
|
|
24
|
+
const handleResize = () => {
|
|
42
25
|
this.fixCanvasSize(canvas);
|
|
43
|
-
}
|
|
26
|
+
};
|
|
27
|
+
canvasParent.addEventListener('resize', handleResize);
|
|
28
|
+
this.removeResizeListener = () => canvasParent.removeEventListener('resize', handleResize);
|
|
44
29
|
this.fixCanvasSize(canvas);
|
|
45
30
|
}
|
|
46
31
|
fixCanvasSize(canvas) {
|
|
47
32
|
const parent = canvas.parentElement;
|
|
33
|
+
if (!parent) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
48
36
|
const rect = parent.getBoundingClientRect();
|
|
49
37
|
const { width } = rect;
|
|
50
38
|
const { height } = rect;
|
|
@@ -80,14 +68,16 @@ export class OverlayRenderer {
|
|
|
80
68
|
ctx.save();
|
|
81
69
|
this.clear();
|
|
82
70
|
ctx.beginPath();
|
|
83
|
-
|
|
84
|
-
|
|
71
|
+
let i = 0;
|
|
72
|
+
for (const point of points) {
|
|
73
|
+
const { x, y } = point;
|
|
85
74
|
if (i === 0) {
|
|
86
75
|
ctx.moveTo(x, y);
|
|
87
76
|
}
|
|
88
77
|
else {
|
|
89
78
|
ctx.lineTo(x, y);
|
|
90
79
|
}
|
|
80
|
+
i += 1;
|
|
91
81
|
}
|
|
92
82
|
const devicePixelRatio = window.devicePixelRatio || 1;
|
|
93
83
|
ctx.strokeStyle = marquee.frameColor;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neo4j-nvl/interaction-handlers",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.55",
|
|
4
4
|
"license": "SEE LICENSE IN 'Neo4j Early Access Agreement - Visualization Library.pdf'",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"concaveman": "^1.2.1",
|
|
29
|
-
"lodash": "
|
|
30
|
-
"resizelistener": "^1.1.0"
|
|
29
|
+
"lodash": "4.17.21"
|
|
31
30
|
},
|
|
32
31
|
"devDependencies": {
|
|
33
32
|
"@testing-library/jest-dom": "^5.16.5",
|
|
34
33
|
"@testing-library/react": "^13.4.0",
|
|
35
|
-
"@types/
|
|
34
|
+
"@types/concaveman": "1.1.6",
|
|
35
|
+
"@types/lodash": "4.14.202"
|
|
36
36
|
}
|
|
37
37
|
}
|