@neo4j-nvl/interaction-handlers 0.3.2 → 0.3.3-9ea79f41
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import NVL from '@neo4j-nvl/base';
|
|
2
2
|
import '@testing-library/jest-dom';
|
|
3
|
-
import { LassoInteraction } from '../interaction-handlers/lasso-interaction';
|
|
3
|
+
import { LassoInteraction, checkIntersection, checkLinesCrossing, checkPointInside } from '../interaction-handlers/lasso-interaction';
|
|
4
4
|
jest.mock('@neo4j-nvl/layout-workers');
|
|
5
5
|
const testNodes = [
|
|
6
6
|
{ id: '0', x: 10, y: 10 },
|
|
@@ -64,7 +64,7 @@ describe('LassoInteraction', () => {
|
|
|
64
64
|
createMouseEvent('mousemove', 250, 0),
|
|
65
65
|
createMouseEvent('mousemove', 240, 240),
|
|
66
66
|
createMouseEvent('mousemove', 100, 200),
|
|
67
|
-
createMouseEvent('mousemove',
|
|
67
|
+
createMouseEvent('mousemove', 0, 50),
|
|
68
68
|
createMouseEvent('mouseup', 0, 0)
|
|
69
69
|
];
|
|
70
70
|
const container = myNVL.getContainer();
|
|
@@ -76,4 +76,49 @@ describe('LassoInteraction', () => {
|
|
|
76
76
|
resolve();
|
|
77
77
|
});
|
|
78
78
|
});
|
|
79
|
+
test('test for line crossing utility function', () => {
|
|
80
|
+
const res1 = checkLinesCrossing([0, 0], [10, 10], [10, 0], [0, 10]);
|
|
81
|
+
expect(res1).toBeTruthy();
|
|
82
|
+
const res2 = checkLinesCrossing([10, 10], [227, 182], [390, 10], [100, 100]);
|
|
83
|
+
expect(res2).toBeTruthy();
|
|
84
|
+
const res3 = checkLinesCrossing([5, 5], [5, 20], [10, 10], [20, 10]);
|
|
85
|
+
expect(res3).toBeFalsy();
|
|
86
|
+
const res4 = checkLinesCrossing([10, 10], [20, 10], [15, 10], [25, 10]);
|
|
87
|
+
expect(res4).toBeFalsy();
|
|
88
|
+
const res5 = checkLinesCrossing([880, 416], [872, 404], [868, 392], [856, 378]);
|
|
89
|
+
expect(res5).toBeFalsy();
|
|
90
|
+
});
|
|
91
|
+
test('test for line segment crossing utility function', () => {
|
|
92
|
+
const res1 = checkIntersection([
|
|
93
|
+
[0, 0],
|
|
94
|
+
[10, 0],
|
|
95
|
+
[100, 10],
|
|
96
|
+
[50, 10],
|
|
97
|
+
[50, -10]
|
|
98
|
+
]);
|
|
99
|
+
expect(res1).toBeTruthy();
|
|
100
|
+
const res2 = checkIntersection([
|
|
101
|
+
[10, 10],
|
|
102
|
+
[227, 182],
|
|
103
|
+
[390, 10],
|
|
104
|
+
[500, 100]
|
|
105
|
+
]);
|
|
106
|
+
expect(res2).toBeFalsy();
|
|
107
|
+
});
|
|
108
|
+
test('test for point inside polygon utility function', () => {
|
|
109
|
+
const res1 = checkPointInside(10, 10, [
|
|
110
|
+
{ x: 0, y: 0 },
|
|
111
|
+
{ x: 10, y: -10 },
|
|
112
|
+
{ x: 20, y: 0 },
|
|
113
|
+
{ x: 10, y: 20 }
|
|
114
|
+
]);
|
|
115
|
+
expect(res1).toBeTruthy();
|
|
116
|
+
const res2 = checkPointInside(10, 10, [
|
|
117
|
+
{ x: 0, y: 0 },
|
|
118
|
+
{ x: 10, y: -10 },
|
|
119
|
+
{ x: 20, y: 0 },
|
|
120
|
+
{ x: 10, y: 5 }
|
|
121
|
+
]);
|
|
122
|
+
expect(res2).toBeFalsy();
|
|
123
|
+
});
|
|
79
124
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NVL, Node, Relationship } from '@neo4j-nvl/base';
|
|
1
|
+
import type { NVL, Node, Point, Relationship } from '@neo4j-nvl/base';
|
|
2
2
|
import { BaseInteraction } from './base';
|
|
3
3
|
/**
|
|
4
4
|
* Options for the lasso interaction handler to customize its behavior.
|
|
@@ -26,6 +26,25 @@ export type LassoInteractionCallbacks = {
|
|
|
26
26
|
rels: Relationship[];
|
|
27
27
|
}, event: MouseEvent) => void) | boolean;
|
|
28
28
|
};
|
|
29
|
+
/**
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export type Coords = [number, number];
|
|
33
|
+
/**
|
|
34
|
+
* Checks if two lines defined by p1->p2 and p3->p4 are crossing.
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export declare const checkLinesCrossing: (p1: Coords, p2: Coords, p3: Coords, p4: Coords) => boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Checks if any line segments in the polygon intersect.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export declare const checkIntersection: (polygon: Coords[]) => boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Checks if the point (x, y) is inside the polygon defined by vs.
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export declare const checkPointInside: (x: number, y: number, vs: Point[]) => boolean;
|
|
29
48
|
/**
|
|
30
49
|
* Interaction handler for selecting nodes and relationships by drawing a lasso around them.
|
|
31
50
|
* When dragging, a line is drawn in the visualisation, and when selecting all nodes inside the drawn
|
|
@@ -4,6 +4,61 @@ import { BaseInteraction } from './base';
|
|
|
4
4
|
import { getCanvasPosition, getWorldPosition } from './utils';
|
|
5
5
|
const pointDist = 10;
|
|
6
6
|
const shapeShowTime = 500;
|
|
7
|
+
/**
|
|
8
|
+
* Checks if two lines defined by p1->p2 and p3->p4 are crossing.
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export const checkLinesCrossing = (p1, p2, p3, p4) => {
|
|
12
|
+
const denom = (p4[1] - p3[1]) * (p2[0] - p1[0]) - (p4[0] - p3[0]) * (p2[1] - p1[1]);
|
|
13
|
+
if (denom === 0) {
|
|
14
|
+
// This means lines are parallell
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const t1 = ((p1[1] - p3[1]) * (p4[0] - p3[0]) - (p1[0] - p3[0]) * (p4[1] - p3[1])) / denom;
|
|
18
|
+
const t2 = ((p3[0] - p1[0]) * (p2[1] - p1[1]) - (p3[1] - p1[1]) * (p2[0] - p1[0])) / denom;
|
|
19
|
+
return t1 > 0 && t1 < 1 && t2 > 0 && t2 < 1;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Checks if any line segments in the polygon intersect.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export const checkIntersection = (polygon) => {
|
|
26
|
+
for (let i = 0; i < polygon.length - 1; i++) {
|
|
27
|
+
for (let j = i + 2; j < polygon.length - 1; j++) {
|
|
28
|
+
const line1p1 = polygon[i] ?? [0, 0];
|
|
29
|
+
const line1p2 = polygon[i + 1] ?? [0, 0];
|
|
30
|
+
const line2p1 = polygon[j] ?? [0, 0];
|
|
31
|
+
const line2p2 = polygon[j + 1] ?? [0, 0];
|
|
32
|
+
if (checkLinesCrossing(line1p1, line1p2, line2p1, line2p2)) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Checks if the point (x, y) is inside the polygon defined by vs.
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
export const checkPointInside = (x, y, vs) => {
|
|
44
|
+
// ray-casting algorithm based on
|
|
45
|
+
// https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
|
|
46
|
+
let isInside = false;
|
|
47
|
+
for (let i = 0, j = vs.length - 1; i < vs.length; j = i, i += 1) {
|
|
48
|
+
const vsI = vs[i];
|
|
49
|
+
const vsJ = vs[j];
|
|
50
|
+
if (vsI === undefined || vsJ === undefined) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const { x: xi, y: yi } = vsI;
|
|
54
|
+
const { x: xj, y: yj } = vsJ;
|
|
55
|
+
const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
|
|
56
|
+
if (intersect) {
|
|
57
|
+
isInside = !isInside;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return isInside;
|
|
61
|
+
};
|
|
7
62
|
/**
|
|
8
63
|
* Interaction handler for selecting nodes and relationships by drawing a lasso around them.
|
|
9
64
|
* When dragging, a line is drawn in the visualisation, and when selecting all nodes inside the drawn
|
|
@@ -70,25 +125,6 @@ export class LassoInteraction extends BaseInteraction {
|
|
|
70
125
|
this.endLasso(event);
|
|
71
126
|
};
|
|
72
127
|
getLassoItems = (points) => {
|
|
73
|
-
const inside = (x, y, vs) => {
|
|
74
|
-
// ray-casting algorithm based on
|
|
75
|
-
// https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
|
|
76
|
-
let isInside = false;
|
|
77
|
-
for (let i = 0, j = vs.length - 1; i < vs.length; j = i, i += 1) {
|
|
78
|
-
const vsI = vs[i];
|
|
79
|
-
const vsJ = vs[j];
|
|
80
|
-
if (vsI === undefined || vsJ === undefined) {
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
const { x: xi, y: yi } = vsI;
|
|
84
|
-
const { x: xj, y: yj } = vsJ;
|
|
85
|
-
const intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
|
|
86
|
-
if (intersect) {
|
|
87
|
-
isInside = !isInside;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return isInside;
|
|
91
|
-
};
|
|
92
128
|
const worldPoints = points.map((p) => getWorldPosition(this.nvlInstance, p));
|
|
93
129
|
const nodePositions = this.nvlInstance.getNodePositions();
|
|
94
130
|
const hitNodes = new Set();
|
|
@@ -96,7 +132,7 @@ export class LassoInteraction extends BaseInteraction {
|
|
|
96
132
|
if (pos.x === undefined || pos.y === undefined || pos.id === undefined) {
|
|
97
133
|
continue;
|
|
98
134
|
}
|
|
99
|
-
if (
|
|
135
|
+
if (checkPointInside(pos.x, pos.y, worldPoints)) {
|
|
100
136
|
hitNodes.add(pos.id);
|
|
101
137
|
}
|
|
102
138
|
}
|
|
@@ -119,7 +155,8 @@ export class LassoInteraction extends BaseInteraction {
|
|
|
119
155
|
}
|
|
120
156
|
this.active = false;
|
|
121
157
|
const pointArrays = this.points.map((p) => [p.x, p.y]);
|
|
122
|
-
const
|
|
158
|
+
const hasCrossings = checkIntersection(pointArrays);
|
|
159
|
+
const hull = (hasCrossings ? concaveman(pointArrays, 2) : pointArrays)
|
|
123
160
|
.map((p) => ({ x: p[0], y: p[1] }))
|
|
124
161
|
.filter((point) => point.x !== undefined && point.y !== undefined);
|
|
125
162
|
this.overlayRenderer.drawLasso(hull, false, true);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neo4j-nvl/interaction-handlers",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3-9ea79f41",
|
|
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",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"tsconfig": "./tsconfig.json"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@neo4j-nvl/base": "
|
|
36
|
+
"@neo4j-nvl/base": "0.3.3-9ea79f41",
|
|
37
37
|
"concaveman": "^1.2.1",
|
|
38
38
|
"lodash": "4.17.21"
|
|
39
39
|
},
|
|
@@ -50,5 +50,6 @@
|
|
|
50
50
|
"eslint": "*",
|
|
51
51
|
"jest": "*",
|
|
52
52
|
"typescript": "*"
|
|
53
|
-
}
|
|
53
|
+
},
|
|
54
|
+
"stableVersion": "0.3.3"
|
|
54
55
|
}
|