@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.
Files changed (139) hide show
  1. package/LICENSE +376 -0
  2. package/README.md +49 -0
  3. package/dist/geometry.js +6486 -0
  4. package/dist/geometry.min.js +8 -0
  5. package/dist/joint.d.ts +5536 -0
  6. package/dist/joint.js +39629 -0
  7. package/dist/joint.min.js +8 -0
  8. package/dist/joint.nowrap.js +39626 -0
  9. package/dist/joint.nowrap.min.js +8 -0
  10. package/dist/vectorizer.js +9135 -0
  11. package/dist/vectorizer.min.js +8 -0
  12. package/dist/version.mjs +3 -0
  13. package/index.js +3 -0
  14. package/joint.mjs +27 -0
  15. package/package.json +192 -0
  16. package/src/V/annotation.mjs +0 -0
  17. package/src/V/index.mjs +2642 -0
  18. package/src/anchors/index.mjs +123 -0
  19. package/src/config/index.mjs +12 -0
  20. package/src/connectionPoints/index.mjs +202 -0
  21. package/src/connectionStrategies/index.mjs +73 -0
  22. package/src/connectors/curve.mjs +553 -0
  23. package/src/connectors/index.mjs +6 -0
  24. package/src/connectors/jumpover.mjs +452 -0
  25. package/src/connectors/normal.mjs +12 -0
  26. package/src/connectors/rounded.mjs +17 -0
  27. package/src/connectors/smooth.mjs +44 -0
  28. package/src/connectors/straight.mjs +110 -0
  29. package/src/dia/Cell.mjs +945 -0
  30. package/src/dia/CellView.mjs +1316 -0
  31. package/src/dia/Element.mjs +519 -0
  32. package/src/dia/ElementView.mjs +859 -0
  33. package/src/dia/Graph.mjs +1112 -0
  34. package/src/dia/HighlighterView.mjs +319 -0
  35. package/src/dia/Link.mjs +565 -0
  36. package/src/dia/LinkView.mjs +2207 -0
  37. package/src/dia/Paper.mjs +3171 -0
  38. package/src/dia/PaperLayer.mjs +75 -0
  39. package/src/dia/ToolView.mjs +69 -0
  40. package/src/dia/ToolsView.mjs +128 -0
  41. package/src/dia/attributes/calc.mjs +128 -0
  42. package/src/dia/attributes/connection.mjs +75 -0
  43. package/src/dia/attributes/defs.mjs +76 -0
  44. package/src/dia/attributes/eval.mjs +64 -0
  45. package/src/dia/attributes/index.mjs +69 -0
  46. package/src/dia/attributes/legacy.mjs +148 -0
  47. package/src/dia/attributes/offset.mjs +53 -0
  48. package/src/dia/attributes/props.mjs +30 -0
  49. package/src/dia/attributes/shape.mjs +92 -0
  50. package/src/dia/attributes/text.mjs +180 -0
  51. package/src/dia/index.mjs +13 -0
  52. package/src/dia/layers/GridLayer.mjs +176 -0
  53. package/src/dia/ports.mjs +874 -0
  54. package/src/elementTools/Control.mjs +153 -0
  55. package/src/elementTools/HoverConnect.mjs +37 -0
  56. package/src/elementTools/index.mjs +5 -0
  57. package/src/env/index.mjs +43 -0
  58. package/src/g/bezier.mjs +175 -0
  59. package/src/g/curve.mjs +956 -0
  60. package/src/g/ellipse.mjs +245 -0
  61. package/src/g/extend.mjs +64 -0
  62. package/src/g/geometry.helpers.mjs +58 -0
  63. package/src/g/index.mjs +17 -0
  64. package/src/g/intersection.mjs +511 -0
  65. package/src/g/line.bearing.mjs +30 -0
  66. package/src/g/line.length.mjs +5 -0
  67. package/src/g/line.mjs +356 -0
  68. package/src/g/line.squaredLength.mjs +10 -0
  69. package/src/g/path.mjs +2260 -0
  70. package/src/g/point.mjs +375 -0
  71. package/src/g/points.mjs +247 -0
  72. package/src/g/polygon.mjs +51 -0
  73. package/src/g/polyline.mjs +523 -0
  74. package/src/g/rect.mjs +556 -0
  75. package/src/g/types.mjs +10 -0
  76. package/src/highlighters/addClass.mjs +27 -0
  77. package/src/highlighters/index.mjs +5 -0
  78. package/src/highlighters/list.mjs +111 -0
  79. package/src/highlighters/mask.mjs +220 -0
  80. package/src/highlighters/opacity.mjs +17 -0
  81. package/src/highlighters/stroke.mjs +100 -0
  82. package/src/layout/index.mjs +4 -0
  83. package/src/layout/ports/port.mjs +188 -0
  84. package/src/layout/ports/portLabel.mjs +224 -0
  85. package/src/linkAnchors/index.mjs +76 -0
  86. package/src/linkTools/Anchor.mjs +235 -0
  87. package/src/linkTools/Arrowhead.mjs +103 -0
  88. package/src/linkTools/Boundary.mjs +48 -0
  89. package/src/linkTools/Button.mjs +121 -0
  90. package/src/linkTools/Connect.mjs +85 -0
  91. package/src/linkTools/HoverConnect.mjs +161 -0
  92. package/src/linkTools/Segments.mjs +393 -0
  93. package/src/linkTools/Vertices.mjs +253 -0
  94. package/src/linkTools/helpers.mjs +33 -0
  95. package/src/linkTools/index.mjs +8 -0
  96. package/src/mvc/Collection.mjs +560 -0
  97. package/src/mvc/Data.mjs +46 -0
  98. package/src/mvc/Dom/Dom.mjs +587 -0
  99. package/src/mvc/Dom/Event.mjs +130 -0
  100. package/src/mvc/Dom/animations.mjs +122 -0
  101. package/src/mvc/Dom/events.mjs +69 -0
  102. package/src/mvc/Dom/index.mjs +13 -0
  103. package/src/mvc/Dom/methods.mjs +392 -0
  104. package/src/mvc/Dom/props.mjs +77 -0
  105. package/src/mvc/Dom/vars.mjs +5 -0
  106. package/src/mvc/Events.mjs +337 -0
  107. package/src/mvc/Listener.mjs +33 -0
  108. package/src/mvc/Model.mjs +239 -0
  109. package/src/mvc/View.mjs +323 -0
  110. package/src/mvc/ViewBase.mjs +182 -0
  111. package/src/mvc/index.mjs +9 -0
  112. package/src/mvc/mvcUtils.mjs +90 -0
  113. package/src/polyfills/array.js +4 -0
  114. package/src/polyfills/base64.js +68 -0
  115. package/src/polyfills/index.mjs +5 -0
  116. package/src/polyfills/number.js +3 -0
  117. package/src/polyfills/string.js +3 -0
  118. package/src/polyfills/typedArray.js +47 -0
  119. package/src/routers/index.mjs +6 -0
  120. package/src/routers/manhattan.mjs +856 -0
  121. package/src/routers/metro.mjs +91 -0
  122. package/src/routers/normal.mjs +6 -0
  123. package/src/routers/oneSide.mjs +60 -0
  124. package/src/routers/orthogonal.mjs +323 -0
  125. package/src/routers/rightAngle.mjs +1056 -0
  126. package/src/shapes/index.mjs +3 -0
  127. package/src/shapes/standard.mjs +755 -0
  128. package/src/util/cloneCells.mjs +67 -0
  129. package/src/util/getRectPoint.mjs +65 -0
  130. package/src/util/index.mjs +5 -0
  131. package/src/util/svgTagTemplate.mjs +110 -0
  132. package/src/util/util.mjs +1754 -0
  133. package/src/util/utilHelpers.mjs +2402 -0
  134. package/src/util/wrappers.mjs +56 -0
  135. package/types/geometry.d.ts +815 -0
  136. package/types/index.d.ts +53 -0
  137. package/types/joint.d.ts +4391 -0
  138. package/types/joint.head.d.ts +12 -0
  139. 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
+ };