@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', 50, 0),
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 (inside(pos.x, pos.y, worldPoints)) {
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 hull = concaveman(pointArrays, 3)
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.2",
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": "^0.3.2",
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
  }