/
SnapLinkReshapingTool.js
213 lines (212 loc) · 8.73 KB
/
SnapLinkReshapingTool.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/*
* Copyright (C) 1998-2024 by Northwoods Software Corporation. All Rights Reserved.
*/
/*
* This is an extension and not part of the main GoJS library.
* Note that the API for this class may change with any version, even point releases.
* If you intend to use an extension in production, you should copy the code to your own source directory.
* Extensions can be found in the GoJS kit under the extensions or extensionsJSM folders.
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
*/
/**
* The SnapLinkReshapingTool class lets the user snap link reshaping handles to the nearest grid point.
* If {@link avoidsNodes} is true and the link is orthogonal,
* it also avoids reshaping the link so that any adjacent segments cross over any avoidable nodes.
*
* If you want to experiment with this extension, try the <a href="../../samples/SnapLinkReshaping.html">Snap Link Reshaping</a> sample.
* @category Tool Extension
*/
class SnapLinkReshapingTool extends go.LinkReshapingTool {
constructor(init) {
super();
this.name = 'SnapLinkReshaping';
this._gridCellSize = new go.Size(NaN, NaN);
this._gridOrigin = new go.Point(NaN, NaN);
this._isGridSnapEnabled = true;
this._avoidsNodes = true;
// internal state
this._safePoint = new go.Point(NaN, NaN);
this._prevSegHoriz = false;
this._nextSegHoriz = false;
if (init)
Object.assign(this, init);
}
/**
* Gets or sets the {@link go.Size} of each grid cell to which link points will be snapped.
*
* The default value is NaNxNaN, which means use the {@link go.Diagram.grid}'s {@link go.Panel.gridCellSize}.
*/
get gridCellSize() {
return this._gridCellSize;
}
set gridCellSize(val) {
if (!(val instanceof go.Size))
throw new Error('new value for SnapLinkReshapingTool.gridCellSize must be a Size, not: ' + val);
this._gridCellSize = val.copy();
}
/**
* Gets or sets the {@link go.Point} origin for the grid to which link points will be snapped.
*
* The default value is NaN,NaN, which means use the {@link go.Diagram.grid}'s {@link go.Panel.gridOrigin}.
*/
get gridOrigin() {
return this._gridOrigin;
}
set gridOrigin(val) {
if (!(val instanceof go.Point))
throw new Error('new value for SnapLinkReshapingTool.gridOrigin must be a Point, not: ' + val);
this._gridOrigin = val.copy();
}
/**
* Gets or sets whether a reshape handle's position should be snapped to a grid point.
* This affects the behavior of {@link computeReshape}.
*
* The default value is true.
*/
get isGridSnapEnabled() {
return this._isGridSnapEnabled;
}
set isGridSnapEnabled(val) {
if (typeof val !== 'boolean')
throw new Error('new value for SnapLinkReshapingTool.isGridSnapEnabled must be a boolean, not: ' + val);
this._isGridSnapEnabled = val;
}
/**
* Gets or sets whether a reshape handle's position should only be dragged where the
* adjacent segments do not cross over any nodes.
* This affects the behavior of {@link computeReshape}.
*
* The default value is true.
*/
get avoidsNodes() {
return this._avoidsNodes;
}
set avoidsNodes(val) {
if (typeof val !== 'boolean')
throw new Error('new value for SnapLinkReshapingTool.avoidsNodes must be a boolean, not: ' + val);
this._avoidsNodes = val;
}
/**
* This override records information about the original point of the handle being dragged,
* if the {@link adornedLink} is Orthogonal and if {@link avoidsNodes} is true.
*/
doActivate() {
super.doActivate();
if (this.isActive &&
this.avoidsNodes &&
this.adornedLink !== null &&
this.adornedLink.isOrthogonal &&
this.handle !== null) {
// assume the Link's route starts off correctly avoiding all nodes
this._safePoint = this.diagram.lastInput.documentPoint.copy();
const link = this.adornedLink;
const idx = this.handle.segmentIndex;
this._prevSegHoriz = Math.abs(link.getPoint(idx - 1).y - link.getPoint(idx).y) < 0.5;
this._nextSegHoriz = Math.abs(link.getPoint(idx + 1).y - link.getPoint(idx).y) < 0.5;
}
}
/**
* Pretend while dragging a reshape handle the mouse point is at the nearest grid point, if {@link isGridSnapEnabled} is true.
* This uses {@link gridCellSize} and {@link gridOrigin}, unless those are not real values,
* in which case this uses the {@link go.Diagram.grid}'s {@link go.Panel.gridCellSize} and {@link go.Panel.gridOrigin}.
*
* If {@link avoidsNodes} is true and the adorned Link is {@link go.Link.isOrthogonal},
* this method also avoids returning a Point that causes the adjacent segments, both before and after
* the current handle's index, to cross over any Nodes that are {@link go.Node.avoidable}.
*/
computeReshape(p) {
let pt = p;
const diagram = this.diagram;
if (this.isGridSnapEnabled) {
// first, find the grid to which we should snap
let cell = this.gridCellSize;
let orig = this.gridOrigin;
if (!cell.isReal() || cell.width === 0 || cell.height === 0)
cell = diagram.grid.gridCellSize;
if (!orig.isReal())
orig = diagram.grid.gridOrigin;
// second, compute the closest grid point
pt = p.copy().snapToGrid(orig.x, orig.y, cell.width, cell.height);
}
if (this.avoidsNodes && this.adornedLink !== null && this.adornedLink.isOrthogonal) {
if (this._checkSegmentsOverlap(pt)) {
this._safePoint = pt.copy();
}
else {
pt = this._safePoint.copy();
}
}
// then do whatever LinkReshapingTool would normally do as if the mouse were at that point
return super.computeReshape(pt);
}
/**
* @hidden @internal
* Internal method for seeing whether a moved handle will cause any
* adjacent orthogonal segments to cross over any avoidable nodes.
* Returns true if everything would be OK.
*/
_checkSegmentsOverlap(pt) {
if (this.handle === null)
return true;
if (this.adornedLink === null)
return true;
const index = this.handle.segmentIndex;
if (index >= 1) {
const p1 = this.adornedLink.getPoint(index - 1);
const r = new go.Rect(pt.x, pt.y, 0, 0);
const q1 = p1.copy();
if (this._prevSegHoriz) {
q1.y = pt.y;
}
else {
q1.x = pt.x;
}
r.unionPoint(q1);
let overlaps = this.diagram.findPartsIn(r, true, false);
if (overlaps.any((p) => p instanceof go.Node && p.avoidable))
return false;
if (index >= 2) {
const p0 = this.adornedLink.getPoint(index - 2);
const r2 = new go.Rect(q1.x, q1.y, 0, 0);
if (this._prevSegHoriz) {
r2.unionPoint(new go.Point(q1.x, p0.y));
}
else {
r2.unionPoint(new go.Point(p0.x, q1.y));
}
overlaps = this.diagram.findPartsIn(r2, true, false);
if (overlaps.any((p) => p instanceof go.Node && p.avoidable))
return false;
}
}
if (index < this.adornedLink.pointsCount - 1) {
const p2 = this.adornedLink.getPoint(index + 1);
const r = new go.Rect(pt.x, pt.y, 0, 0);
const q2 = p2.copy();
if (this._nextSegHoriz) {
q2.y = pt.y;
}
else {
q2.x = pt.x;
}
r.unionPoint(q2);
let overlaps = this.diagram.findPartsIn(r, true, false);
if (overlaps.any((p) => p instanceof go.Node && p.avoidable))
return false;
if (index < this.adornedLink.pointsCount - 2) {
const p3 = this.adornedLink.getPoint(index + 2);
const r2 = new go.Rect(q2.x, q2.y, 0, 0);
if (this._nextSegHoriz) {
r2.unionPoint(new go.Point(q2.x, p3.y));
}
else {
r2.unionPoint(new go.Point(p3.x, q2.y));
}
overlaps = this.diagram.findPartsIn(r2, true, false);
if (overlaps.any((p) => p instanceof go.Node && p.avoidable))
return false;
}
}
return true;
}
}