@joint/core 4.0.0
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/LICENSE +376 -0
- package/README.md +49 -0
- package/dist/geometry.js +6486 -0
- package/dist/geometry.min.js +8 -0
- package/dist/joint.d.ts +5536 -0
- package/dist/joint.js +39629 -0
- package/dist/joint.min.js +8 -0
- package/dist/joint.nowrap.js +39626 -0
- package/dist/joint.nowrap.min.js +8 -0
- package/dist/vectorizer.js +9135 -0
- package/dist/vectorizer.min.js +8 -0
- package/dist/version.mjs +3 -0
- package/index.js +3 -0
- package/joint.mjs +27 -0
- package/package.json +192 -0
- package/src/V/annotation.mjs +0 -0
- package/src/V/index.mjs +2642 -0
- package/src/anchors/index.mjs +123 -0
- package/src/config/index.mjs +12 -0
- package/src/connectionPoints/index.mjs +202 -0
- package/src/connectionStrategies/index.mjs +73 -0
- package/src/connectors/curve.mjs +553 -0
- package/src/connectors/index.mjs +6 -0
- package/src/connectors/jumpover.mjs +452 -0
- package/src/connectors/normal.mjs +12 -0
- package/src/connectors/rounded.mjs +17 -0
- package/src/connectors/smooth.mjs +44 -0
- package/src/connectors/straight.mjs +110 -0
- package/src/dia/Cell.mjs +945 -0
- package/src/dia/CellView.mjs +1316 -0
- package/src/dia/Element.mjs +519 -0
- package/src/dia/ElementView.mjs +859 -0
- package/src/dia/Graph.mjs +1112 -0
- package/src/dia/HighlighterView.mjs +319 -0
- package/src/dia/Link.mjs +565 -0
- package/src/dia/LinkView.mjs +2207 -0
- package/src/dia/Paper.mjs +3171 -0
- package/src/dia/PaperLayer.mjs +75 -0
- package/src/dia/ToolView.mjs +69 -0
- package/src/dia/ToolsView.mjs +128 -0
- package/src/dia/attributes/calc.mjs +128 -0
- package/src/dia/attributes/connection.mjs +75 -0
- package/src/dia/attributes/defs.mjs +76 -0
- package/src/dia/attributes/eval.mjs +64 -0
- package/src/dia/attributes/index.mjs +69 -0
- package/src/dia/attributes/legacy.mjs +148 -0
- package/src/dia/attributes/offset.mjs +53 -0
- package/src/dia/attributes/props.mjs +30 -0
- package/src/dia/attributes/shape.mjs +92 -0
- package/src/dia/attributes/text.mjs +180 -0
- package/src/dia/index.mjs +13 -0
- package/src/dia/layers/GridLayer.mjs +176 -0
- package/src/dia/ports.mjs +874 -0
- package/src/elementTools/Control.mjs +153 -0
- package/src/elementTools/HoverConnect.mjs +37 -0
- package/src/elementTools/index.mjs +5 -0
- package/src/env/index.mjs +43 -0
- package/src/g/bezier.mjs +175 -0
- package/src/g/curve.mjs +956 -0
- package/src/g/ellipse.mjs +245 -0
- package/src/g/extend.mjs +64 -0
- package/src/g/geometry.helpers.mjs +58 -0
- package/src/g/index.mjs +17 -0
- package/src/g/intersection.mjs +511 -0
- package/src/g/line.bearing.mjs +30 -0
- package/src/g/line.length.mjs +5 -0
- package/src/g/line.mjs +356 -0
- package/src/g/line.squaredLength.mjs +10 -0
- package/src/g/path.mjs +2260 -0
- package/src/g/point.mjs +375 -0
- package/src/g/points.mjs +247 -0
- package/src/g/polygon.mjs +51 -0
- package/src/g/polyline.mjs +523 -0
- package/src/g/rect.mjs +556 -0
- package/src/g/types.mjs +10 -0
- package/src/highlighters/addClass.mjs +27 -0
- package/src/highlighters/index.mjs +5 -0
- package/src/highlighters/list.mjs +111 -0
- package/src/highlighters/mask.mjs +220 -0
- package/src/highlighters/opacity.mjs +17 -0
- package/src/highlighters/stroke.mjs +100 -0
- package/src/layout/index.mjs +4 -0
- package/src/layout/ports/port.mjs +188 -0
- package/src/layout/ports/portLabel.mjs +224 -0
- package/src/linkAnchors/index.mjs +76 -0
- package/src/linkTools/Anchor.mjs +235 -0
- package/src/linkTools/Arrowhead.mjs +103 -0
- package/src/linkTools/Boundary.mjs +48 -0
- package/src/linkTools/Button.mjs +121 -0
- package/src/linkTools/Connect.mjs +85 -0
- package/src/linkTools/HoverConnect.mjs +161 -0
- package/src/linkTools/Segments.mjs +393 -0
- package/src/linkTools/Vertices.mjs +253 -0
- package/src/linkTools/helpers.mjs +33 -0
- package/src/linkTools/index.mjs +8 -0
- package/src/mvc/Collection.mjs +560 -0
- package/src/mvc/Data.mjs +46 -0
- package/src/mvc/Dom/Dom.mjs +587 -0
- package/src/mvc/Dom/Event.mjs +130 -0
- package/src/mvc/Dom/animations.mjs +122 -0
- package/src/mvc/Dom/events.mjs +69 -0
- package/src/mvc/Dom/index.mjs +13 -0
- package/src/mvc/Dom/methods.mjs +392 -0
- package/src/mvc/Dom/props.mjs +77 -0
- package/src/mvc/Dom/vars.mjs +5 -0
- package/src/mvc/Events.mjs +337 -0
- package/src/mvc/Listener.mjs +33 -0
- package/src/mvc/Model.mjs +239 -0
- package/src/mvc/View.mjs +323 -0
- package/src/mvc/ViewBase.mjs +182 -0
- package/src/mvc/index.mjs +9 -0
- package/src/mvc/mvcUtils.mjs +90 -0
- package/src/polyfills/array.js +4 -0
- package/src/polyfills/base64.js +68 -0
- package/src/polyfills/index.mjs +5 -0
- package/src/polyfills/number.js +3 -0
- package/src/polyfills/string.js +3 -0
- package/src/polyfills/typedArray.js +47 -0
- package/src/routers/index.mjs +6 -0
- package/src/routers/manhattan.mjs +856 -0
- package/src/routers/metro.mjs +91 -0
- package/src/routers/normal.mjs +6 -0
- package/src/routers/oneSide.mjs +60 -0
- package/src/routers/orthogonal.mjs +323 -0
- package/src/routers/rightAngle.mjs +1056 -0
- package/src/shapes/index.mjs +3 -0
- package/src/shapes/standard.mjs +755 -0
- package/src/util/cloneCells.mjs +67 -0
- package/src/util/getRectPoint.mjs +65 -0
- package/src/util/index.mjs +5 -0
- package/src/util/svgTagTemplate.mjs +110 -0
- package/src/util/util.mjs +1754 -0
- package/src/util/utilHelpers.mjs +2402 -0
- package/src/util/wrappers.mjs +56 -0
- package/types/geometry.d.ts +815 -0
- package/types/index.d.ts +53 -0
- package/types/joint.d.ts +4391 -0
- package/types/joint.head.d.ts +12 -0
- package/types/vectorizer.d.ts +327 -0
|
@@ -0,0 +1,856 @@
|
|
|
1
|
+
import * as g from '../g/index.mjs';
|
|
2
|
+
import * as util from '../util/index.mjs';
|
|
3
|
+
import { orthogonal } from './orthogonal.mjs';
|
|
4
|
+
|
|
5
|
+
var config = {
|
|
6
|
+
|
|
7
|
+
// size of the step to find a route (the grid of the manhattan pathfinder)
|
|
8
|
+
step: 10,
|
|
9
|
+
|
|
10
|
+
// the number of route finding loops that cause the router to abort
|
|
11
|
+
// returns fallback route instead
|
|
12
|
+
maximumLoops: 2000,
|
|
13
|
+
|
|
14
|
+
// the number of decimal places to round floating point coordinates
|
|
15
|
+
precision: 1,
|
|
16
|
+
|
|
17
|
+
// maximum change of direction
|
|
18
|
+
maxAllowedDirectionChange: 90,
|
|
19
|
+
|
|
20
|
+
// should the router use perpendicular linkView option?
|
|
21
|
+
// does not connect anchor of element but rather a point close-by that is orthogonal
|
|
22
|
+
// this looks much better
|
|
23
|
+
perpendicular: true,
|
|
24
|
+
|
|
25
|
+
// should the source and/or target not be considered as obstacles?
|
|
26
|
+
excludeEnds: [], // 'source', 'target'
|
|
27
|
+
|
|
28
|
+
// should certain types of elements not be considered as obstacles?
|
|
29
|
+
excludeTypes: [],
|
|
30
|
+
|
|
31
|
+
// possible starting directions from an element
|
|
32
|
+
startDirections: ['top', 'right', 'bottom', 'left'],
|
|
33
|
+
|
|
34
|
+
// possible ending directions to an element
|
|
35
|
+
endDirections: ['top', 'right', 'bottom', 'left'],
|
|
36
|
+
|
|
37
|
+
// specify the directions used above and what they mean
|
|
38
|
+
directionMap: {
|
|
39
|
+
top: { x: 0, y: -1 },
|
|
40
|
+
right: { x: 1, y: 0 },
|
|
41
|
+
bottom: { x: 0, y: 1 },
|
|
42
|
+
left: { x: -1, y: 0 }
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// cost of an orthogonal step
|
|
46
|
+
cost: function() {
|
|
47
|
+
|
|
48
|
+
return this.step;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// an array of directions to find next points on the route
|
|
52
|
+
// different from start/end directions
|
|
53
|
+
directions: function() {
|
|
54
|
+
|
|
55
|
+
var step = this.step;
|
|
56
|
+
var cost = this.cost();
|
|
57
|
+
|
|
58
|
+
return [
|
|
59
|
+
{ offsetX: step, offsetY: 0, cost: cost },
|
|
60
|
+
{ offsetX: -step, offsetY: 0, cost: cost },
|
|
61
|
+
{ offsetX: 0, offsetY: step, cost: cost },
|
|
62
|
+
{ offsetX: 0, offsetY: -step, cost: cost }
|
|
63
|
+
];
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// a penalty received for direction change
|
|
67
|
+
penalties: function() {
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
0: 0,
|
|
71
|
+
45: this.step / 2,
|
|
72
|
+
90: this.step / 2
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// padding applied on the element bounding boxes
|
|
77
|
+
paddingBox: function() {
|
|
78
|
+
|
|
79
|
+
var step = this.step;
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
x: -step,
|
|
83
|
+
y: -step,
|
|
84
|
+
width: 2 * step,
|
|
85
|
+
height: 2 * step
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
// A function that determines whether a given point is an obstacle or not.
|
|
90
|
+
// If used, the `padding`, `excludeEnds`and `excludeTypes` options are ignored.
|
|
91
|
+
// (point: dia.Point) => boolean;
|
|
92
|
+
isPointObstacle: null,
|
|
93
|
+
|
|
94
|
+
// a router to use when the manhattan router fails
|
|
95
|
+
// (one of the partial routes returns null)
|
|
96
|
+
fallbackRouter: function(vertices, opt, linkView) {
|
|
97
|
+
|
|
98
|
+
if (!util.isFunction(orthogonal)) {
|
|
99
|
+
throw new Error('Manhattan requires the orthogonal router as default fallback.');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return orthogonal(vertices, util.assign({}, config, opt), linkView);
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
/* Deprecated */
|
|
106
|
+
// a simple route used in situations when main routing method fails
|
|
107
|
+
// (exceed max number of loop iterations, inaccessible)
|
|
108
|
+
fallbackRoute: function(from, to, opt) {
|
|
109
|
+
|
|
110
|
+
return null; // null result will trigger the fallbackRouter
|
|
111
|
+
|
|
112
|
+
// left for reference:
|
|
113
|
+
/*// Find an orthogonal route ignoring obstacles.
|
|
114
|
+
|
|
115
|
+
var point = ((opt.previousDirAngle || 0) % 180 === 0)
|
|
116
|
+
? new g.Point(from.x, to.y)
|
|
117
|
+
: new g.Point(to.x, from.y);
|
|
118
|
+
|
|
119
|
+
return [point];*/
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// if a function is provided, it's used to route the link while dragging an end
|
|
123
|
+
// i.e. function(from, to, opt) { return []; }
|
|
124
|
+
draggingRoute: null
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// HELPER CLASSES //
|
|
128
|
+
|
|
129
|
+
// Map of obstacles
|
|
130
|
+
// Helper structure to identify whether a point lies inside an obstacle.
|
|
131
|
+
function ObstacleMap(opt) {
|
|
132
|
+
|
|
133
|
+
this.map = {};
|
|
134
|
+
this.options = opt;
|
|
135
|
+
// tells how to divide the paper when creating the elements map
|
|
136
|
+
this.mapGridSize = 100;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
ObstacleMap.prototype.build = function(graph, link) {
|
|
140
|
+
|
|
141
|
+
var opt = this.options;
|
|
142
|
+
|
|
143
|
+
// source or target element could be excluded from set of obstacles
|
|
144
|
+
var excludedEnds = util.toArray(opt.excludeEnds).reduce(function(res, item) {
|
|
145
|
+
|
|
146
|
+
var end = link.get(item);
|
|
147
|
+
if (end) {
|
|
148
|
+
var cell = graph.getCell(end.id);
|
|
149
|
+
if (cell) {
|
|
150
|
+
res.push(cell);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return res;
|
|
155
|
+
}, []);
|
|
156
|
+
|
|
157
|
+
// Exclude any embedded elements from the source and the target element.
|
|
158
|
+
var excludedAncestors = [];
|
|
159
|
+
|
|
160
|
+
var source = graph.getCell(link.get('source').id);
|
|
161
|
+
if (source) {
|
|
162
|
+
excludedAncestors = util.union(excludedAncestors, source.getAncestors().map(function(cell) {
|
|
163
|
+
return cell.id;
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
var target = graph.getCell(link.get('target').id);
|
|
168
|
+
if (target) {
|
|
169
|
+
excludedAncestors = util.union(excludedAncestors, target.getAncestors().map(function(cell) {
|
|
170
|
+
return cell.id;
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Builds a map of all elements for quicker obstacle queries (i.e. is a point contained
|
|
175
|
+
// in any obstacle?) (a simplified grid search).
|
|
176
|
+
// The paper is divided into smaller cells, where each holds information about which
|
|
177
|
+
// elements belong to it. When we query whether a point lies inside an obstacle we
|
|
178
|
+
// don't need to go through all obstacles, we check only those in a particular cell.
|
|
179
|
+
var mapGridSize = this.mapGridSize;
|
|
180
|
+
|
|
181
|
+
graph.getElements().reduce(function(map, element) {
|
|
182
|
+
|
|
183
|
+
var isExcludedType = util.toArray(opt.excludeTypes).includes(element.get('type'));
|
|
184
|
+
var isExcludedEnd = excludedEnds.find(function(excluded) {
|
|
185
|
+
return excluded.id === element.id;
|
|
186
|
+
});
|
|
187
|
+
var isExcludedAncestor = excludedAncestors.includes(element.id);
|
|
188
|
+
|
|
189
|
+
var isExcluded = isExcludedType || isExcludedEnd || isExcludedAncestor;
|
|
190
|
+
if (!isExcluded) {
|
|
191
|
+
var bbox = element.getBBox().moveAndExpand(opt.paddingBox);
|
|
192
|
+
|
|
193
|
+
var origin = bbox.origin().snapToGrid(mapGridSize);
|
|
194
|
+
var corner = bbox.corner().snapToGrid(mapGridSize);
|
|
195
|
+
|
|
196
|
+
for (var x = origin.x; x <= corner.x; x += mapGridSize) {
|
|
197
|
+
for (var y = origin.y; y <= corner.y; y += mapGridSize) {
|
|
198
|
+
var gridKey = x + '@' + y;
|
|
199
|
+
map[gridKey] = map[gridKey] || [];
|
|
200
|
+
map[gridKey].push(bbox);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return map;
|
|
206
|
+
}, this.map);
|
|
207
|
+
|
|
208
|
+
return this;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
ObstacleMap.prototype.isPointAccessible = function(point) {
|
|
212
|
+
|
|
213
|
+
var mapKey = point.clone().snapToGrid(this.mapGridSize).toString();
|
|
214
|
+
|
|
215
|
+
return util.toArray(this.map[mapKey]).every(function(obstacle) {
|
|
216
|
+
return !obstacle.containsPoint(point);
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Sorted Set
|
|
221
|
+
// Set of items sorted by given value.
|
|
222
|
+
function SortedSet() {
|
|
223
|
+
this.items = [];
|
|
224
|
+
this.hash = {};
|
|
225
|
+
this.values = {};
|
|
226
|
+
this.OPEN = 1;
|
|
227
|
+
this.CLOSE = 2;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
SortedSet.prototype.add = function(item, value) {
|
|
231
|
+
|
|
232
|
+
if (this.hash[item]) {
|
|
233
|
+
// item removal
|
|
234
|
+
this.items.splice(this.items.indexOf(item), 1);
|
|
235
|
+
} else {
|
|
236
|
+
this.hash[item] = this.OPEN;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
this.values[item] = value;
|
|
240
|
+
|
|
241
|
+
var index = util.sortedIndex(this.items, item, function(i) {
|
|
242
|
+
return this.values[i];
|
|
243
|
+
}.bind(this));
|
|
244
|
+
|
|
245
|
+
this.items.splice(index, 0, item);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
SortedSet.prototype.remove = function(item) {
|
|
249
|
+
|
|
250
|
+
this.hash[item] = this.CLOSE;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
SortedSet.prototype.isOpen = function(item) {
|
|
254
|
+
|
|
255
|
+
return this.hash[item] === this.OPEN;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
SortedSet.prototype.isClose = function(item) {
|
|
259
|
+
|
|
260
|
+
return this.hash[item] === this.CLOSE;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
SortedSet.prototype.isEmpty = function() {
|
|
264
|
+
|
|
265
|
+
return this.items.length === 0;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
SortedSet.prototype.pop = function() {
|
|
269
|
+
|
|
270
|
+
var item = this.items.shift();
|
|
271
|
+
this.remove(item);
|
|
272
|
+
return item;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// HELPERS //
|
|
276
|
+
|
|
277
|
+
// return source bbox
|
|
278
|
+
function getSourceBBox(linkView, opt) {
|
|
279
|
+
|
|
280
|
+
// expand by padding box
|
|
281
|
+
if (opt && opt.paddingBox) return linkView.sourceBBox.clone().moveAndExpand(opt.paddingBox);
|
|
282
|
+
|
|
283
|
+
return linkView.sourceBBox.clone();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// return target bbox
|
|
287
|
+
function getTargetBBox(linkView, opt) {
|
|
288
|
+
|
|
289
|
+
// expand by padding box
|
|
290
|
+
if (opt && opt.paddingBox) return linkView.targetBBox.clone().moveAndExpand(opt.paddingBox);
|
|
291
|
+
|
|
292
|
+
return linkView.targetBBox.clone();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// return source anchor
|
|
296
|
+
function getSourceAnchor(linkView, opt) {
|
|
297
|
+
|
|
298
|
+
if (linkView.sourceAnchor) return linkView.sourceAnchor;
|
|
299
|
+
|
|
300
|
+
// fallback: center of bbox
|
|
301
|
+
var sourceBBox = getSourceBBox(linkView, opt);
|
|
302
|
+
return sourceBBox.center();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// return target anchor
|
|
306
|
+
function getTargetAnchor(linkView, opt) {
|
|
307
|
+
|
|
308
|
+
if (linkView.targetAnchor) return linkView.targetAnchor;
|
|
309
|
+
|
|
310
|
+
// fallback: center of bbox
|
|
311
|
+
var targetBBox = getTargetBBox(linkView, opt);
|
|
312
|
+
return targetBBox.center(); // default
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// returns a direction index from start point to end point
|
|
316
|
+
// corrects for grid deformation between start and end
|
|
317
|
+
function getDirectionAngle(start, end, numDirections, grid, opt) {
|
|
318
|
+
|
|
319
|
+
var quadrant = 360 / numDirections;
|
|
320
|
+
var angleTheta = start.theta(fixAngleEnd(start, end, grid, opt));
|
|
321
|
+
var normalizedAngle = g.normalizeAngle(angleTheta + (quadrant / 2));
|
|
322
|
+
return quadrant * Math.floor(normalizedAngle / quadrant);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// helper function for getDirectionAngle()
|
|
326
|
+
// corrects for grid deformation
|
|
327
|
+
// (if a point is one grid steps away from another in both dimensions,
|
|
328
|
+
// it is considered to be 45 degrees away, even if the real angle is different)
|
|
329
|
+
// this causes visible angle discrepancies if `opt.step` is much larger than `paper.gridSize`
|
|
330
|
+
function fixAngleEnd(start, end, grid, opt) {
|
|
331
|
+
|
|
332
|
+
var step = opt.step;
|
|
333
|
+
|
|
334
|
+
var diffX = end.x - start.x;
|
|
335
|
+
var diffY = end.y - start.y;
|
|
336
|
+
|
|
337
|
+
var gridStepsX = diffX / grid.x;
|
|
338
|
+
var gridStepsY = diffY / grid.y;
|
|
339
|
+
|
|
340
|
+
var distanceX = gridStepsX * step;
|
|
341
|
+
var distanceY = gridStepsY * step;
|
|
342
|
+
|
|
343
|
+
return new g.Point(start.x + distanceX, start.y + distanceY);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// return the change in direction between two direction angles
|
|
347
|
+
function getDirectionChange(angle1, angle2) {
|
|
348
|
+
|
|
349
|
+
var directionChange = Math.abs(angle1 - angle2);
|
|
350
|
+
return (directionChange > 180) ? (360 - directionChange) : directionChange;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// fix direction offsets according to current grid
|
|
354
|
+
function getGridOffsets(directions, grid, opt) {
|
|
355
|
+
|
|
356
|
+
var step = opt.step;
|
|
357
|
+
|
|
358
|
+
util.toArray(opt.directions).forEach(function(direction) {
|
|
359
|
+
|
|
360
|
+
direction.gridOffsetX = (direction.offsetX / step) * grid.x;
|
|
361
|
+
direction.gridOffsetY = (direction.offsetY / step) * grid.y;
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// get grid size in x and y dimensions, adapted to source and target positions
|
|
366
|
+
function getGrid(step, source, target) {
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
source: source.clone(),
|
|
370
|
+
x: getGridDimension(target.x - source.x, step),
|
|
371
|
+
y: getGridDimension(target.y - source.y, step)
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// helper function for getGrid()
|
|
376
|
+
function getGridDimension(diff, step) {
|
|
377
|
+
|
|
378
|
+
// return step if diff = 0
|
|
379
|
+
if (!diff) return step;
|
|
380
|
+
|
|
381
|
+
var absDiff = Math.abs(diff);
|
|
382
|
+
var numSteps = Math.round(absDiff / step);
|
|
383
|
+
|
|
384
|
+
// return absDiff if less than one step apart
|
|
385
|
+
if (!numSteps) return absDiff;
|
|
386
|
+
|
|
387
|
+
// otherwise, return corrected step
|
|
388
|
+
var roundedDiff = numSteps * step;
|
|
389
|
+
var remainder = absDiff - roundedDiff;
|
|
390
|
+
var stepCorrection = remainder / numSteps;
|
|
391
|
+
|
|
392
|
+
return step + stepCorrection;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// return a clone of point snapped to grid
|
|
396
|
+
function snapToGrid(point, grid) {
|
|
397
|
+
|
|
398
|
+
var source = grid.source;
|
|
399
|
+
|
|
400
|
+
var snappedX = g.snapToGrid(point.x - source.x, grid.x) + source.x;
|
|
401
|
+
var snappedY = g.snapToGrid(point.y - source.y, grid.y) + source.y;
|
|
402
|
+
|
|
403
|
+
return new g.Point(snappedX, snappedY);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// round the point to opt.precision
|
|
407
|
+
function round(point, precision) {
|
|
408
|
+
|
|
409
|
+
return point.round(precision);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// snap to grid and then round the point
|
|
413
|
+
function align(point, grid, precision) {
|
|
414
|
+
|
|
415
|
+
return round(snapToGrid(point.clone(), grid), precision);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// return a string representing the point
|
|
419
|
+
// string is rounded in both dimensions
|
|
420
|
+
function getKey(point) {
|
|
421
|
+
|
|
422
|
+
return point.clone().toString();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// return a normalized vector from given point
|
|
426
|
+
// used to determine the direction of a difference of two points
|
|
427
|
+
function normalizePoint(point) {
|
|
428
|
+
|
|
429
|
+
return new g.Point(
|
|
430
|
+
point.x === 0 ? 0 : Math.abs(point.x) / point.x,
|
|
431
|
+
point.y === 0 ? 0 : Math.abs(point.y) / point.y
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// PATHFINDING //
|
|
436
|
+
|
|
437
|
+
// reconstructs a route by concatenating points with their parents
|
|
438
|
+
function reconstructRoute(parents, points, tailPoint, from, to, grid, opt) {
|
|
439
|
+
|
|
440
|
+
var route = [];
|
|
441
|
+
|
|
442
|
+
var prevDiff = normalizePoint(to.difference(tailPoint));
|
|
443
|
+
|
|
444
|
+
// tailPoint is assumed to be aligned already
|
|
445
|
+
var currentKey = getKey(tailPoint);
|
|
446
|
+
var parent = parents[currentKey];
|
|
447
|
+
|
|
448
|
+
var point;
|
|
449
|
+
while (parent) {
|
|
450
|
+
|
|
451
|
+
// point is assumed to be aligned already
|
|
452
|
+
point = points[currentKey];
|
|
453
|
+
|
|
454
|
+
var diff = normalizePoint(point.difference(parent));
|
|
455
|
+
if (!diff.equals(prevDiff)) {
|
|
456
|
+
route.unshift(point);
|
|
457
|
+
prevDiff = diff;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// parent is assumed to be aligned already
|
|
461
|
+
currentKey = getKey(parent);
|
|
462
|
+
parent = parents[currentKey];
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// leadPoint is assumed to be aligned already
|
|
466
|
+
var leadPoint = points[currentKey];
|
|
467
|
+
|
|
468
|
+
var fromDiff = normalizePoint(leadPoint.difference(from));
|
|
469
|
+
if (!fromDiff.equals(prevDiff)) {
|
|
470
|
+
route.unshift(leadPoint);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return route;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// heuristic method to determine the distance between two points
|
|
477
|
+
function estimateCost(from, endPoints) {
|
|
478
|
+
|
|
479
|
+
var min = Infinity;
|
|
480
|
+
|
|
481
|
+
for (var i = 0, len = endPoints.length; i < len; i++) {
|
|
482
|
+
var cost = from.manhattanDistance(endPoints[i]);
|
|
483
|
+
if (cost < min) min = cost;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return min;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// find points around the bbox taking given directions into account
|
|
490
|
+
// lines are drawn from anchor in given directions, intersections recorded
|
|
491
|
+
// if anchor is outside bbox, only those directions that intersect get a rect point
|
|
492
|
+
// the anchor itself is returned as rect point (representing some directions)
|
|
493
|
+
// (since those directions are unobstructed by the bbox)
|
|
494
|
+
function getRectPoints(anchor, bbox, directionList, grid, opt) {
|
|
495
|
+
|
|
496
|
+
var precision = opt.precision;
|
|
497
|
+
var directionMap = opt.directionMap;
|
|
498
|
+
|
|
499
|
+
var anchorCenterVector = anchor.difference(bbox.center());
|
|
500
|
+
|
|
501
|
+
var keys = util.isObject(directionMap) ? Object.keys(directionMap) : [];
|
|
502
|
+
var dirList = util.toArray(directionList);
|
|
503
|
+
var rectPoints = keys.reduce(function(res, key) {
|
|
504
|
+
|
|
505
|
+
if (dirList.includes(key)) {
|
|
506
|
+
var direction = directionMap[key];
|
|
507
|
+
|
|
508
|
+
// create a line that is guaranteed to intersect the bbox if bbox is in the direction
|
|
509
|
+
// even if anchor lies outside of bbox
|
|
510
|
+
var endpoint = new g.Point(
|
|
511
|
+
anchor.x + direction.x * (Math.abs(anchorCenterVector.x) + bbox.width),
|
|
512
|
+
anchor.y + direction.y * (Math.abs(anchorCenterVector.y) + bbox.height)
|
|
513
|
+
);
|
|
514
|
+
var intersectionLine = new g.Line(anchor, endpoint);
|
|
515
|
+
|
|
516
|
+
// get the farther intersection, in case there are two
|
|
517
|
+
// (that happens if anchor lies next to bbox)
|
|
518
|
+
var intersections = intersectionLine.intersect(bbox) || [];
|
|
519
|
+
var numIntersections = intersections.length;
|
|
520
|
+
var farthestIntersectionDistance;
|
|
521
|
+
var farthestIntersection = null;
|
|
522
|
+
for (var i = 0; i < numIntersections; i++) {
|
|
523
|
+
var currentIntersection = intersections[i];
|
|
524
|
+
var distance = anchor.squaredDistance(currentIntersection);
|
|
525
|
+
if ((farthestIntersectionDistance === undefined) || (distance > farthestIntersectionDistance)) {
|
|
526
|
+
farthestIntersectionDistance = distance;
|
|
527
|
+
farthestIntersection = currentIntersection;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// if an intersection was found in this direction, it is our rectPoint
|
|
532
|
+
if (farthestIntersection) {
|
|
533
|
+
var point = align(farthestIntersection, grid, precision);
|
|
534
|
+
|
|
535
|
+
// if the rectPoint lies inside the bbox, offset it by one more step
|
|
536
|
+
if (bbox.containsPoint(point)) {
|
|
537
|
+
point = align(point.offset(direction.x * grid.x, direction.y * grid.y), grid, precision);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// then add the point to the result array
|
|
541
|
+
// aligned
|
|
542
|
+
res.push(point);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return res;
|
|
547
|
+
}, []);
|
|
548
|
+
|
|
549
|
+
// if anchor lies outside of bbox, add it to the array of points
|
|
550
|
+
if (!bbox.containsPoint(anchor)) {
|
|
551
|
+
// aligned
|
|
552
|
+
rectPoints.push(align(anchor, grid, precision));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return rectPoints;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// finds the route between two points/rectangles (`from`, `to`) implementing A* algorithm
|
|
559
|
+
// rectangles get rect points assigned by getRectPoints()
|
|
560
|
+
function findRoute(from, to, isPointObstacle, opt) {
|
|
561
|
+
|
|
562
|
+
var precision = opt.precision;
|
|
563
|
+
|
|
564
|
+
// Get grid for this route.
|
|
565
|
+
|
|
566
|
+
var sourceAnchor, targetAnchor;
|
|
567
|
+
|
|
568
|
+
if (from instanceof g.Rect) { // `from` is sourceBBox
|
|
569
|
+
sourceAnchor = round(getSourceAnchor(this, opt).clone(), precision);
|
|
570
|
+
} else {
|
|
571
|
+
sourceAnchor = round(from.clone(), precision);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (to instanceof g.Rect) { // `to` is targetBBox
|
|
575
|
+
targetAnchor = round(getTargetAnchor(this, opt).clone(), precision);
|
|
576
|
+
} else {
|
|
577
|
+
targetAnchor = round(to.clone(), precision);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
var grid = getGrid(opt.step, sourceAnchor, targetAnchor);
|
|
581
|
+
|
|
582
|
+
// Get pathfinding points.
|
|
583
|
+
|
|
584
|
+
var start, end; // aligned with grid by definition
|
|
585
|
+
var startPoints, endPoints; // assumed to be aligned with grid already
|
|
586
|
+
|
|
587
|
+
// set of points we start pathfinding from
|
|
588
|
+
if (from instanceof g.Rect) { // `from` is sourceBBox
|
|
589
|
+
start = sourceAnchor;
|
|
590
|
+
startPoints = getRectPoints(start, from, opt.startDirections, grid, opt);
|
|
591
|
+
|
|
592
|
+
} else {
|
|
593
|
+
start = sourceAnchor;
|
|
594
|
+
startPoints = [start];
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// set of points we want the pathfinding to finish at
|
|
598
|
+
if (to instanceof g.Rect) { // `to` is targetBBox
|
|
599
|
+
end = targetAnchor;
|
|
600
|
+
endPoints = getRectPoints(targetAnchor, to, opt.endDirections, grid, opt);
|
|
601
|
+
|
|
602
|
+
} else {
|
|
603
|
+
end = targetAnchor;
|
|
604
|
+
endPoints = [end];
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// take into account only accessible rect points (those not under obstacles)
|
|
608
|
+
startPoints = startPoints.filter(p => !isPointObstacle(p));
|
|
609
|
+
endPoints = endPoints.filter(p => !isPointObstacle(p));
|
|
610
|
+
|
|
611
|
+
// Check that there is an accessible route point on both sides.
|
|
612
|
+
// Otherwise, use fallbackRoute().
|
|
613
|
+
if (startPoints.length > 0 && endPoints.length > 0) {
|
|
614
|
+
|
|
615
|
+
// The set of tentative points to be evaluated, initially containing the start points.
|
|
616
|
+
// Rounded to nearest integer for simplicity.
|
|
617
|
+
var openSet = new SortedSet();
|
|
618
|
+
// Keeps reference to actual points for given elements of the open set.
|
|
619
|
+
var points = {};
|
|
620
|
+
// Keeps reference to a point that is immediate predecessor of given element.
|
|
621
|
+
var parents = {};
|
|
622
|
+
// Cost from start to a point along best known path.
|
|
623
|
+
var costs = {};
|
|
624
|
+
|
|
625
|
+
for (var i = 0, n = startPoints.length; i < n; i++) {
|
|
626
|
+
// startPoint is assumed to be aligned already
|
|
627
|
+
var startPoint = startPoints[i];
|
|
628
|
+
|
|
629
|
+
var key = getKey(startPoint);
|
|
630
|
+
|
|
631
|
+
openSet.add(key, estimateCost(startPoint, endPoints));
|
|
632
|
+
points[key] = startPoint;
|
|
633
|
+
costs[key] = 0;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
var previousRouteDirectionAngle = opt.previousDirectionAngle; // undefined for first route
|
|
637
|
+
var isPathBeginning = (previousRouteDirectionAngle === undefined);
|
|
638
|
+
|
|
639
|
+
// directions
|
|
640
|
+
var direction, directionChange;
|
|
641
|
+
var directions = opt.directions;
|
|
642
|
+
getGridOffsets(directions, grid, opt);
|
|
643
|
+
|
|
644
|
+
var numDirections = directions.length;
|
|
645
|
+
|
|
646
|
+
var endPointsKeys = util.toArray(endPoints).reduce(function(res, endPoint) {
|
|
647
|
+
// endPoint is assumed to be aligned already
|
|
648
|
+
|
|
649
|
+
var key = getKey(endPoint);
|
|
650
|
+
res.push(key);
|
|
651
|
+
return res;
|
|
652
|
+
}, []);
|
|
653
|
+
|
|
654
|
+
// main route finding loop
|
|
655
|
+
var loopsRemaining = opt.maximumLoops;
|
|
656
|
+
while (!openSet.isEmpty() && loopsRemaining > 0) {
|
|
657
|
+
|
|
658
|
+
// remove current from the open list
|
|
659
|
+
var currentKey = openSet.pop();
|
|
660
|
+
var currentPoint = points[currentKey];
|
|
661
|
+
var currentParent = parents[currentKey];
|
|
662
|
+
var currentCost = costs[currentKey];
|
|
663
|
+
|
|
664
|
+
var isRouteBeginning = (currentParent === undefined); // undefined for route starts
|
|
665
|
+
var isStart = currentPoint.equals(start); // (is source anchor or `from` point) = can leave in any direction
|
|
666
|
+
|
|
667
|
+
var previousDirectionAngle;
|
|
668
|
+
if (!isRouteBeginning) previousDirectionAngle = getDirectionAngle(currentParent, currentPoint, numDirections, grid, opt); // a vertex on the route
|
|
669
|
+
else if (!isPathBeginning) previousDirectionAngle = previousRouteDirectionAngle; // beginning of route on the path
|
|
670
|
+
else if (!isStart) previousDirectionAngle = getDirectionAngle(start, currentPoint, numDirections, grid, opt); // beginning of path, start rect point
|
|
671
|
+
else previousDirectionAngle = null; // beginning of path, source anchor or `from` point
|
|
672
|
+
|
|
673
|
+
// check if we reached any endpoint
|
|
674
|
+
var samePoints = startPoints.length === endPoints.length;
|
|
675
|
+
if (samePoints) {
|
|
676
|
+
for (var j = 0; j < startPoints.length; j++) {
|
|
677
|
+
if (!startPoints[j].equals(endPoints[j])) {
|
|
678
|
+
samePoints = false;
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
var skipEndCheck = (isRouteBeginning && samePoints);
|
|
684
|
+
if (!skipEndCheck && (endPointsKeys.indexOf(currentKey) >= 0)) {
|
|
685
|
+
opt.previousDirectionAngle = previousDirectionAngle;
|
|
686
|
+
return reconstructRoute(parents, points, currentPoint, start, end, grid, opt);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// go over all possible directions and find neighbors
|
|
690
|
+
for (i = 0; i < numDirections; i++) {
|
|
691
|
+
direction = directions[i];
|
|
692
|
+
|
|
693
|
+
var directionAngle = direction.angle;
|
|
694
|
+
directionChange = getDirectionChange(previousDirectionAngle, directionAngle);
|
|
695
|
+
|
|
696
|
+
// if the direction changed rapidly, don't use this point
|
|
697
|
+
// any direction is allowed for starting points
|
|
698
|
+
if (!(isPathBeginning && isStart) && directionChange > opt.maxAllowedDirectionChange) continue;
|
|
699
|
+
|
|
700
|
+
var neighborPoint = align(currentPoint.clone().offset(direction.gridOffsetX, direction.gridOffsetY), grid, precision);
|
|
701
|
+
var neighborKey = getKey(neighborPoint);
|
|
702
|
+
|
|
703
|
+
// Closed points from the openSet were already evaluated.
|
|
704
|
+
if (openSet.isClose(neighborKey) || isPointObstacle(neighborPoint)) continue;
|
|
705
|
+
|
|
706
|
+
// We can only enter end points at an acceptable angle.
|
|
707
|
+
if (endPointsKeys.indexOf(neighborKey) >= 0) { // neighbor is an end point
|
|
708
|
+
|
|
709
|
+
var isNeighborEnd = neighborPoint.equals(end); // (is target anchor or `to` point) = can be entered in any direction
|
|
710
|
+
|
|
711
|
+
if (!isNeighborEnd) {
|
|
712
|
+
var endDirectionAngle = getDirectionAngle(neighborPoint, end, numDirections, grid, opt);
|
|
713
|
+
var endDirectionChange = getDirectionChange(directionAngle, endDirectionAngle);
|
|
714
|
+
|
|
715
|
+
if (endDirectionChange > opt.maxAllowedDirectionChange) continue;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// The current direction is ok.
|
|
720
|
+
|
|
721
|
+
var neighborCost = direction.cost;
|
|
722
|
+
var neighborPenalty = isStart ? 0 : opt.penalties[directionChange]; // no penalties for start point
|
|
723
|
+
var costFromStart = currentCost + neighborCost + neighborPenalty;
|
|
724
|
+
|
|
725
|
+
if (!openSet.isOpen(neighborKey) || (costFromStart < costs[neighborKey])) {
|
|
726
|
+
// neighbor point has not been processed yet
|
|
727
|
+
// or the cost of the path from start is lower than previously calculated
|
|
728
|
+
|
|
729
|
+
points[neighborKey] = neighborPoint;
|
|
730
|
+
parents[neighborKey] = currentPoint;
|
|
731
|
+
costs[neighborKey] = costFromStart;
|
|
732
|
+
openSet.add(neighborKey, costFromStart + estimateCost(neighborPoint, endPoints));
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
loopsRemaining--;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// no route found (`to` point either wasn't accessible or finding route took
|
|
741
|
+
// way too much calculation)
|
|
742
|
+
return opt.fallbackRoute.call(this, start, end, opt);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// resolve some of the options
|
|
746
|
+
function resolveOptions(opt) {
|
|
747
|
+
|
|
748
|
+
opt.directions = util.result(opt, 'directions');
|
|
749
|
+
opt.penalties = util.result(opt, 'penalties');
|
|
750
|
+
opt.paddingBox = util.result(opt, 'paddingBox');
|
|
751
|
+
opt.padding = util.result(opt, 'padding');
|
|
752
|
+
|
|
753
|
+
if (opt.padding) {
|
|
754
|
+
// if both provided, opt.padding wins over opt.paddingBox
|
|
755
|
+
var sides = util.normalizeSides(opt.padding);
|
|
756
|
+
opt.paddingBox = {
|
|
757
|
+
x: -sides.left,
|
|
758
|
+
y: -sides.top,
|
|
759
|
+
width: sides.left + sides.right,
|
|
760
|
+
height: sides.top + sides.bottom
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
util.toArray(opt.directions).forEach(function(direction) {
|
|
765
|
+
|
|
766
|
+
var point1 = new g.Point(0, 0);
|
|
767
|
+
var point2 = new g.Point(direction.offsetX, direction.offsetY);
|
|
768
|
+
|
|
769
|
+
direction.angle = g.normalizeAngle(point1.theta(point2));
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// initialization of the route finding
|
|
774
|
+
function router(vertices, opt, linkView) {
|
|
775
|
+
|
|
776
|
+
resolveOptions(opt);
|
|
777
|
+
|
|
778
|
+
// enable/disable linkView perpendicular option
|
|
779
|
+
linkView.options.perpendicular = !!opt.perpendicular;
|
|
780
|
+
|
|
781
|
+
var sourceBBox = getSourceBBox(linkView, opt);
|
|
782
|
+
var targetBBox = getTargetBBox(linkView, opt);
|
|
783
|
+
|
|
784
|
+
var sourceAnchor = getSourceAnchor(linkView, opt);
|
|
785
|
+
//var targetAnchor = getTargetAnchor(linkView, opt);
|
|
786
|
+
|
|
787
|
+
// pathfinding
|
|
788
|
+
let isPointObstacle;
|
|
789
|
+
if (typeof opt.isPointObstacle === 'function') {
|
|
790
|
+
isPointObstacle = opt.isPointObstacle;
|
|
791
|
+
} else {
|
|
792
|
+
const map = new ObstacleMap(opt);
|
|
793
|
+
map.build(linkView.paper.model, linkView.model);
|
|
794
|
+
isPointObstacle = (point) => !map.isPointAccessible(point);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
var oldVertices = util.toArray(vertices).map(g.Point);
|
|
798
|
+
var newVertices = [];
|
|
799
|
+
var tailPoint = sourceAnchor; // the origin of first route's grid, does not need snapping
|
|
800
|
+
|
|
801
|
+
// find a route by concatenating all partial routes (routes need to pass through vertices)
|
|
802
|
+
// source -> vertex[1] -> ... -> vertex[n] -> target
|
|
803
|
+
var to, from;
|
|
804
|
+
|
|
805
|
+
for (var i = 0, len = oldVertices.length; i <= len; i++) {
|
|
806
|
+
|
|
807
|
+
var partialRoute = null;
|
|
808
|
+
|
|
809
|
+
from = to || sourceBBox;
|
|
810
|
+
to = oldVertices[i];
|
|
811
|
+
|
|
812
|
+
if (!to) {
|
|
813
|
+
// this is the last iteration
|
|
814
|
+
// we ran through all vertices in oldVertices
|
|
815
|
+
// 'to' is not a vertex.
|
|
816
|
+
|
|
817
|
+
to = targetBBox;
|
|
818
|
+
|
|
819
|
+
// If the target is a point (i.e. it's not an element), we
|
|
820
|
+
// should use dragging route instead of main routing method if it has been provided.
|
|
821
|
+
var isEndingAtPoint = !linkView.model.get('source').id || !linkView.model.get('target').id;
|
|
822
|
+
|
|
823
|
+
if (isEndingAtPoint && util.isFunction(opt.draggingRoute)) {
|
|
824
|
+
// Make sure we are passing points only (not rects).
|
|
825
|
+
var dragFrom = (from === sourceBBox) ? sourceAnchor : from;
|
|
826
|
+
var dragTo = to.origin();
|
|
827
|
+
|
|
828
|
+
partialRoute = opt.draggingRoute.call(linkView, dragFrom, dragTo, opt);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// if partial route has not been calculated yet use the main routing method to find one
|
|
833
|
+
partialRoute = partialRoute || findRoute.call(linkView, from, to, isPointObstacle, opt);
|
|
834
|
+
|
|
835
|
+
if (partialRoute === null) { // the partial route cannot be found
|
|
836
|
+
return opt.fallbackRouter(vertices, opt, linkView);
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
var leadPoint = partialRoute[0];
|
|
840
|
+
|
|
841
|
+
// remove the first point if the previous partial route had the same point as last
|
|
842
|
+
if (leadPoint && leadPoint.equals(tailPoint)) partialRoute.shift();
|
|
843
|
+
|
|
844
|
+
// save tailPoint for next iteration
|
|
845
|
+
tailPoint = partialRoute[partialRoute.length - 1] || tailPoint;
|
|
846
|
+
|
|
847
|
+
Array.prototype.push.apply(newVertices, partialRoute);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
return newVertices;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// public function
|
|
854
|
+
export const manhattan = function(vertices, opt, linkView) {
|
|
855
|
+
return router(vertices, util.assign({}, config, opt), linkView);
|
|
856
|
+
};
|