@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,2207 @@
|
|
|
1
|
+
import { CellView } from './CellView.mjs';
|
|
2
|
+
import { Link } from './Link.mjs';
|
|
3
|
+
import V from '../V/index.mjs';
|
|
4
|
+
import { addClassNamePrefix, merge, assign, isObject, isFunction, clone, isPercentage, result, isEqual } from '../util/index.mjs';
|
|
5
|
+
import { Point, Line, Path, normalizeAngle, Rect, Polyline } from '../g/index.mjs';
|
|
6
|
+
import * as routers from '../routers/index.mjs';
|
|
7
|
+
import * as connectors from '../connectors/index.mjs';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const Flags = {
|
|
11
|
+
TOOLS: CellView.Flags.TOOLS,
|
|
12
|
+
RENDER: 'RENDER',
|
|
13
|
+
UPDATE: 'UPDATE',
|
|
14
|
+
LABELS: 'LABELS',
|
|
15
|
+
SOURCE: 'SOURCE',
|
|
16
|
+
TARGET: 'TARGET',
|
|
17
|
+
CONNECTOR: 'CONNECTOR'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Link base view and controller.
|
|
21
|
+
// ----------------------------------------
|
|
22
|
+
|
|
23
|
+
export const LinkView = CellView.extend({
|
|
24
|
+
|
|
25
|
+
className: function() {
|
|
26
|
+
|
|
27
|
+
var classNames = CellView.prototype.className.apply(this).split(' ');
|
|
28
|
+
|
|
29
|
+
classNames.push('link');
|
|
30
|
+
|
|
31
|
+
return classNames.join(' ');
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
_labelCache: null,
|
|
35
|
+
_labelSelectors: null,
|
|
36
|
+
_V: null,
|
|
37
|
+
_dragData: null, // deprecated
|
|
38
|
+
|
|
39
|
+
metrics: null,
|
|
40
|
+
decimalsRounding: 2,
|
|
41
|
+
|
|
42
|
+
initialize: function() {
|
|
43
|
+
|
|
44
|
+
CellView.prototype.initialize.apply(this, arguments);
|
|
45
|
+
|
|
46
|
+
// `_.labelCache` is a mapping of indexes of labels in the `this.get('labels')` array to
|
|
47
|
+
// `<g class="label">` nodes wrapped by Vectorizer. This allows for quick access to the
|
|
48
|
+
// nodes in `updateLabelPosition()` in order to update the label positions.
|
|
49
|
+
this._labelCache = {};
|
|
50
|
+
|
|
51
|
+
// a cache of label selectors
|
|
52
|
+
this._labelSelectors = {};
|
|
53
|
+
|
|
54
|
+
// cache of default markup nodes
|
|
55
|
+
this._V = {};
|
|
56
|
+
|
|
57
|
+
// connection path metrics
|
|
58
|
+
this.cleanNodesCache();
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
presentationAttributes: {
|
|
62
|
+
markup: [Flags.RENDER],
|
|
63
|
+
attrs: [Flags.UPDATE],
|
|
64
|
+
router: [Flags.UPDATE],
|
|
65
|
+
connector: [Flags.CONNECTOR],
|
|
66
|
+
labels: [Flags.LABELS],
|
|
67
|
+
labelMarkup: [Flags.LABELS],
|
|
68
|
+
vertices: [Flags.UPDATE],
|
|
69
|
+
source: [Flags.SOURCE, Flags.UPDATE],
|
|
70
|
+
target: [Flags.TARGET, Flags.UPDATE]
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
initFlag: [Flags.RENDER, Flags.SOURCE, Flags.TARGET, Flags.TOOLS],
|
|
74
|
+
|
|
75
|
+
UPDATE_PRIORITY: 1,
|
|
76
|
+
|
|
77
|
+
confirmUpdate: function(flags, opt) {
|
|
78
|
+
|
|
79
|
+
opt || (opt = {});
|
|
80
|
+
|
|
81
|
+
if (this.hasFlag(flags, Flags.SOURCE)) {
|
|
82
|
+
if (!this.updateEndProperties('source')) return flags;
|
|
83
|
+
flags = this.removeFlag(flags, Flags.SOURCE);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (this.hasFlag(flags, Flags.TARGET)) {
|
|
87
|
+
if (!this.updateEndProperties('target')) return flags;
|
|
88
|
+
flags = this.removeFlag(flags, Flags.TARGET);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const { paper, sourceView, targetView } = this;
|
|
92
|
+
if (paper && ((sourceView && !paper.isViewMounted(sourceView)) || (targetView && !paper.isViewMounted(targetView)))) {
|
|
93
|
+
// Wait for the sourceView and targetView to be rendered
|
|
94
|
+
return flags;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (this.hasFlag(flags, Flags.RENDER)) {
|
|
98
|
+
this.render();
|
|
99
|
+
this.updateHighlighters(true);
|
|
100
|
+
this.updateTools(opt);
|
|
101
|
+
flags = this.removeFlag(flags, [Flags.RENDER, Flags.UPDATE, Flags.LABELS, Flags.TOOLS, Flags.CONNECTOR]);
|
|
102
|
+
return flags;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let updateHighlighters = false;
|
|
106
|
+
|
|
107
|
+
const { model } = this;
|
|
108
|
+
const { attributes } = model;
|
|
109
|
+
let updateLabels = this.hasFlag(flags, Flags.LABELS);
|
|
110
|
+
|
|
111
|
+
if (updateLabels) {
|
|
112
|
+
this.onLabelsChange(model, attributes.labels, opt);
|
|
113
|
+
flags = this.removeFlag(flags, Flags.LABELS);
|
|
114
|
+
updateHighlighters = true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const updateAll = this.hasFlag(flags, Flags.UPDATE);
|
|
118
|
+
const updateConnector = this.hasFlag(flags, Flags.CONNECTOR);
|
|
119
|
+
if (updateAll || updateConnector) {
|
|
120
|
+
if (!updateAll) {
|
|
121
|
+
// Keep the current route and update the geometry
|
|
122
|
+
this.updatePath();
|
|
123
|
+
this.updateDOM();
|
|
124
|
+
} else if (opt.translateBy && model.isRelationshipEmbeddedIn(opt.translateBy)) {
|
|
125
|
+
// The link is being translated by an ancestor that will
|
|
126
|
+
// shift source point, target point and all vertices
|
|
127
|
+
// by an equal distance.
|
|
128
|
+
this.translate(opt.tx, opt.ty);
|
|
129
|
+
} else {
|
|
130
|
+
this.update();
|
|
131
|
+
}
|
|
132
|
+
this.updateTools(opt);
|
|
133
|
+
flags = this.removeFlag(flags, [Flags.UPDATE, Flags.TOOLS, Flags.CONNECTOR]);
|
|
134
|
+
updateLabels = false;
|
|
135
|
+
updateHighlighters = true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (updateLabels) {
|
|
139
|
+
this.updateLabelPositions();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (updateHighlighters) {
|
|
143
|
+
this.updateHighlighters();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (this.hasFlag(flags, Flags.TOOLS)) {
|
|
147
|
+
this.updateTools(opt);
|
|
148
|
+
flags = this.removeFlag(flags, Flags.TOOLS);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return flags;
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
requestConnectionUpdate: function(opt) {
|
|
155
|
+
this.requestUpdate(this.getFlag(Flags.UPDATE), opt);
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
isLabelsRenderRequired: function(opt = {}) {
|
|
159
|
+
|
|
160
|
+
const previousLabels = this.model.previous('labels');
|
|
161
|
+
if (!previousLabels) return true;
|
|
162
|
+
|
|
163
|
+
// Here is an optimization for cases when we know, that change does
|
|
164
|
+
// not require re-rendering of all labels.
|
|
165
|
+
if (('propertyPathArray' in opt) && ('propertyValue' in opt)) {
|
|
166
|
+
// The label is setting by `prop()` method
|
|
167
|
+
var pathArray = opt.propertyPathArray || [];
|
|
168
|
+
var pathLength = pathArray.length;
|
|
169
|
+
if (pathLength > 1) {
|
|
170
|
+
// We are changing a single label here e.g. 'labels/0/position'
|
|
171
|
+
var labelExists = !!previousLabels[pathArray[1]];
|
|
172
|
+
if (labelExists) {
|
|
173
|
+
if (pathLength === 2) {
|
|
174
|
+
// We are changing the entire label. Need to check if the
|
|
175
|
+
// markup is also being changed.
|
|
176
|
+
return ('markup' in Object(opt.propertyValue));
|
|
177
|
+
} else if (pathArray[2] !== 'markup') {
|
|
178
|
+
// We are changing a label property but not the markup
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return true;
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
onLabelsChange: function(_link, _labels, opt) {
|
|
189
|
+
|
|
190
|
+
// Note: this optimization works in async=false mode only
|
|
191
|
+
if (this.isLabelsRenderRequired(opt)) {
|
|
192
|
+
this.renderLabels();
|
|
193
|
+
} else {
|
|
194
|
+
this.updateLabels();
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
// Rendering.
|
|
199
|
+
// ----------
|
|
200
|
+
|
|
201
|
+
render: function() {
|
|
202
|
+
|
|
203
|
+
this.vel.empty();
|
|
204
|
+
this.unmountLabels();
|
|
205
|
+
this._V = {};
|
|
206
|
+
this.renderMarkup();
|
|
207
|
+
// rendering labels has to be run after the link is appended to DOM tree. (otherwise <Text> bbox
|
|
208
|
+
// returns zero values)
|
|
209
|
+
this.renderLabels();
|
|
210
|
+
this.update();
|
|
211
|
+
|
|
212
|
+
return this;
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
renderMarkup: function() {
|
|
216
|
+
|
|
217
|
+
var link = this.model;
|
|
218
|
+
var markup = link.get('markup') || link.markup;
|
|
219
|
+
if (!markup) throw new Error('dia.LinkView: markup required');
|
|
220
|
+
if (Array.isArray(markup)) return this.renderJSONMarkup(markup);
|
|
221
|
+
if (typeof markup === 'string') return this.renderStringMarkup(markup);
|
|
222
|
+
throw new Error('dia.LinkView: invalid markup');
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
renderJSONMarkup: function(markup) {
|
|
226
|
+
|
|
227
|
+
var doc = this.parseDOMJSON(markup, this.el);
|
|
228
|
+
// Selectors
|
|
229
|
+
this.selectors = doc.selectors;
|
|
230
|
+
// Fragment
|
|
231
|
+
this.vel.append(doc.fragment);
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
renderStringMarkup: function(markup) {
|
|
235
|
+
|
|
236
|
+
// A special markup can be given in the `properties.markup` property. This might be handy
|
|
237
|
+
// if e.g. arrowhead markers should be `<image>` elements or any other element than `<path>`s.
|
|
238
|
+
// `.connection`, `.connection-wrap`, `.marker-source` and `.marker-target` selectors
|
|
239
|
+
// of elements with special meaning though. Therefore, those classes should be preserved in any
|
|
240
|
+
// special markup passed in `properties.markup`.
|
|
241
|
+
var children = V(markup);
|
|
242
|
+
// custom markup may contain only one children
|
|
243
|
+
if (!Array.isArray(children)) children = [children];
|
|
244
|
+
|
|
245
|
+
this.vel.append(children);
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
_getLabelMarkup: function(labelMarkup) {
|
|
249
|
+
|
|
250
|
+
if (!labelMarkup) return undefined;
|
|
251
|
+
|
|
252
|
+
if (Array.isArray(labelMarkup)) return this.parseDOMJSON(labelMarkup, null);
|
|
253
|
+
if (typeof labelMarkup === 'string') return this._getLabelStringMarkup(labelMarkup);
|
|
254
|
+
throw new Error('dia.linkView: invalid label markup');
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
_getLabelStringMarkup: function(labelMarkup) {
|
|
258
|
+
|
|
259
|
+
var children = V(labelMarkup);
|
|
260
|
+
var fragment = document.createDocumentFragment();
|
|
261
|
+
|
|
262
|
+
if (!Array.isArray(children)) {
|
|
263
|
+
fragment.appendChild(children.node);
|
|
264
|
+
|
|
265
|
+
} else {
|
|
266
|
+
for (var i = 0, n = children.length; i < n; i++) {
|
|
267
|
+
var currentChild = children[i].node;
|
|
268
|
+
fragment.appendChild(currentChild);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { fragment: fragment, selectors: {}}; // no selectors
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
// Label markup fragment may come wrapped in <g class="label" />, or not.
|
|
276
|
+
// If it doesn't, add the <g /> container here.
|
|
277
|
+
_normalizeLabelMarkup: function(markup) {
|
|
278
|
+
|
|
279
|
+
if (!markup) return undefined;
|
|
280
|
+
|
|
281
|
+
var fragment = markup.fragment;
|
|
282
|
+
if (!(markup.fragment instanceof DocumentFragment) || !markup.fragment.hasChildNodes()) throw new Error('dia.LinkView: invalid label markup.');
|
|
283
|
+
|
|
284
|
+
var vNode;
|
|
285
|
+
var childNodes = fragment.childNodes;
|
|
286
|
+
|
|
287
|
+
if ((childNodes.length > 1) || childNodes[0].nodeName.toUpperCase() !== 'G') {
|
|
288
|
+
// default markup fragment is not wrapped in <g />
|
|
289
|
+
// add a <g /> container
|
|
290
|
+
vNode = V('g').append(fragment);
|
|
291
|
+
} else {
|
|
292
|
+
vNode = V(childNodes[0]);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
vNode.addClass('label');
|
|
296
|
+
|
|
297
|
+
return { node: vNode.node, selectors: markup.selectors };
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
renderLabels: function() {
|
|
301
|
+
|
|
302
|
+
var cache = this._V;
|
|
303
|
+
var vLabels = cache.labels;
|
|
304
|
+
var labelCache = this._labelCache = {};
|
|
305
|
+
var labelSelectors = this._labelSelectors = {};
|
|
306
|
+
var model = this.model;
|
|
307
|
+
var labels = model.attributes.labels || [];
|
|
308
|
+
var labelsCount = labels.length;
|
|
309
|
+
|
|
310
|
+
if (labelsCount === 0) {
|
|
311
|
+
if (vLabels) vLabels.remove();
|
|
312
|
+
return this;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (vLabels) {
|
|
316
|
+
vLabels.empty();
|
|
317
|
+
} else {
|
|
318
|
+
// there is no label container in the markup but some labels are defined
|
|
319
|
+
// add a <g class="labels" /> container
|
|
320
|
+
vLabels = cache.labels = V('g').addClass('labels');
|
|
321
|
+
if (this.options.labelsLayer) {
|
|
322
|
+
vLabels.addClass(addClassNamePrefix(result(this, 'className')));
|
|
323
|
+
vLabels.attr('model-id', model.id);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
for (var i = 0; i < labelsCount; i++) {
|
|
328
|
+
|
|
329
|
+
var label = labels[i];
|
|
330
|
+
var labelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(label.markup));
|
|
331
|
+
var labelNode;
|
|
332
|
+
var selectors;
|
|
333
|
+
if (labelMarkup) {
|
|
334
|
+
|
|
335
|
+
labelNode = labelMarkup.node;
|
|
336
|
+
selectors = labelMarkup.selectors;
|
|
337
|
+
|
|
338
|
+
} else {
|
|
339
|
+
|
|
340
|
+
var builtinDefaultLabel = model._builtins.defaultLabel;
|
|
341
|
+
var builtinDefaultLabelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(builtinDefaultLabel.markup));
|
|
342
|
+
var defaultLabel = model._getDefaultLabel();
|
|
343
|
+
var defaultLabelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(defaultLabel.markup));
|
|
344
|
+
var defaultMarkup = defaultLabelMarkup || builtinDefaultLabelMarkup;
|
|
345
|
+
|
|
346
|
+
labelNode = defaultMarkup.node;
|
|
347
|
+
selectors = defaultMarkup.selectors;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
labelNode.setAttribute('label-idx', i); // assign label-idx
|
|
351
|
+
vLabels.append(labelNode);
|
|
352
|
+
labelCache[i] = labelNode; // cache node for `updateLabels()` so it can just update label node positions
|
|
353
|
+
|
|
354
|
+
var rootSelector = this.selector;
|
|
355
|
+
if (selectors[rootSelector]) throw new Error('dia.LinkView: ambiguous label root selector.');
|
|
356
|
+
selectors[rootSelector] = labelNode;
|
|
357
|
+
|
|
358
|
+
labelSelectors[i] = selectors; // cache label selectors for `updateLabels()`
|
|
359
|
+
}
|
|
360
|
+
if (!vLabels.parent()) {
|
|
361
|
+
this.mountLabels();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
this.updateLabels();
|
|
365
|
+
|
|
366
|
+
return this;
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
mountLabels: function() {
|
|
370
|
+
const { el, paper, model, _V, options } = this;
|
|
371
|
+
const { labels: vLabels } = _V;
|
|
372
|
+
if (!vLabels || !model.hasLabels()) return;
|
|
373
|
+
const { node } = vLabels;
|
|
374
|
+
if (options.labelsLayer) {
|
|
375
|
+
paper.getLayerView(options.labelsLayer).insertSortedNode(node, model.get('z'));
|
|
376
|
+
} else {
|
|
377
|
+
if (node.parentNode !== el) {
|
|
378
|
+
el.appendChild(node);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
unmountLabels: function() {
|
|
384
|
+
const { options, _V } = this;
|
|
385
|
+
if (!_V) return;
|
|
386
|
+
const { labels: vLabels } = _V;
|
|
387
|
+
if (vLabels && options.labelsLayer) {
|
|
388
|
+
vLabels.remove();
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
findLabelNodes: function(labelIndex, selector) {
|
|
393
|
+
const labelRoot = this._labelCache[labelIndex];
|
|
394
|
+
if (!labelRoot) return [];
|
|
395
|
+
const labelSelectors = this._labelSelectors[labelIndex];
|
|
396
|
+
return this.findBySelector(selector, labelRoot, labelSelectors);
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
findLabelNode: function(labelIndex, selector) {
|
|
400
|
+
const [node = null] = this.findLabelNodes(labelIndex, selector);
|
|
401
|
+
return node;
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
// merge default label attrs into label attrs (or use built-in default label attrs if neither is provided)
|
|
405
|
+
// keep `undefined` or `null` because `{}` means something else
|
|
406
|
+
_mergeLabelAttrs: function(hasCustomMarkup, labelAttrs, defaultLabelAttrs, builtinDefaultLabelAttrs) {
|
|
407
|
+
|
|
408
|
+
if (labelAttrs === null) return null;
|
|
409
|
+
if (labelAttrs === undefined) {
|
|
410
|
+
|
|
411
|
+
if (defaultLabelAttrs === null) return null;
|
|
412
|
+
if (defaultLabelAttrs === undefined) {
|
|
413
|
+
|
|
414
|
+
if (hasCustomMarkup) return undefined;
|
|
415
|
+
return builtinDefaultLabelAttrs;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (hasCustomMarkup) return defaultLabelAttrs;
|
|
419
|
+
return merge({}, builtinDefaultLabelAttrs, defaultLabelAttrs);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (hasCustomMarkup) return merge({}, defaultLabelAttrs, labelAttrs);
|
|
423
|
+
return merge({}, builtinDefaultLabelAttrs, defaultLabelAttrs, labelAttrs);
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
// merge default label size into label size (no built-in default)
|
|
427
|
+
// keep `undefined` or `null` because `{}` means something else
|
|
428
|
+
_mergeLabelSize: function(labelSize, defaultLabelSize) {
|
|
429
|
+
|
|
430
|
+
if (labelSize === null) return null;
|
|
431
|
+
if (labelSize === undefined) {
|
|
432
|
+
|
|
433
|
+
if (defaultLabelSize === null) return null;
|
|
434
|
+
if (defaultLabelSize === undefined) return undefined;
|
|
435
|
+
|
|
436
|
+
return defaultLabelSize;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return merge({}, defaultLabelSize, labelSize);
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
updateLabels: function() {
|
|
443
|
+
|
|
444
|
+
if (!this._V.labels) return this;
|
|
445
|
+
|
|
446
|
+
var model = this.model;
|
|
447
|
+
var labels = model.get('labels') || [];
|
|
448
|
+
var canLabelMove = this.can('labelMove');
|
|
449
|
+
|
|
450
|
+
var builtinDefaultLabel = model._builtins.defaultLabel;
|
|
451
|
+
var builtinDefaultLabelAttrs = builtinDefaultLabel.attrs;
|
|
452
|
+
|
|
453
|
+
var defaultLabel = model._getDefaultLabel();
|
|
454
|
+
var defaultLabelMarkup = defaultLabel.markup;
|
|
455
|
+
var defaultLabelAttrs = defaultLabel.attrs;
|
|
456
|
+
var defaultLabelSize = defaultLabel.size;
|
|
457
|
+
|
|
458
|
+
for (var i = 0, n = labels.length; i < n; i++) {
|
|
459
|
+
|
|
460
|
+
var labelNode = this._labelCache[i];
|
|
461
|
+
labelNode.setAttribute('cursor', (canLabelMove ? 'move' : 'default'));
|
|
462
|
+
|
|
463
|
+
var selectors = this._labelSelectors[i];
|
|
464
|
+
|
|
465
|
+
var label = labels[i];
|
|
466
|
+
var labelMarkup = label.markup;
|
|
467
|
+
var labelAttrs = label.attrs;
|
|
468
|
+
var labelSize = label.size;
|
|
469
|
+
|
|
470
|
+
var attrs = this._mergeLabelAttrs(
|
|
471
|
+
(labelMarkup || defaultLabelMarkup),
|
|
472
|
+
labelAttrs,
|
|
473
|
+
defaultLabelAttrs,
|
|
474
|
+
builtinDefaultLabelAttrs
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
var size = this._mergeLabelSize(
|
|
478
|
+
labelSize,
|
|
479
|
+
defaultLabelSize
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
this.updateDOMSubtreeAttributes(labelNode, attrs, {
|
|
483
|
+
rootBBox: new Rect(size),
|
|
484
|
+
selectors: selectors
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return this;
|
|
489
|
+
},
|
|
490
|
+
|
|
491
|
+
// remove vertices that lie on (or nearly on) straight lines within the link
|
|
492
|
+
// return the number of removed points
|
|
493
|
+
removeRedundantLinearVertices: function(opt) {
|
|
494
|
+
|
|
495
|
+
const SIMPLIFY_THRESHOLD = 0.001;
|
|
496
|
+
|
|
497
|
+
const link = this.model;
|
|
498
|
+
const vertices = link.vertices();
|
|
499
|
+
const routePoints = [this.sourceAnchor, ...vertices, this.targetAnchor];
|
|
500
|
+
const numRoutePoints = routePoints.length;
|
|
501
|
+
|
|
502
|
+
// put routePoints into a polyline and try to simplify
|
|
503
|
+
const polyline = new Polyline(routePoints);
|
|
504
|
+
polyline.simplify({ threshold: SIMPLIFY_THRESHOLD });
|
|
505
|
+
const polylinePoints = polyline.points.map((point) => (point.toJSON())); // JSON of points after simplification
|
|
506
|
+
const numPolylinePoints = polylinePoints.length; // number of points after simplification
|
|
507
|
+
|
|
508
|
+
// shortcut if simplification did not remove any redundant vertices:
|
|
509
|
+
if (numRoutePoints === numPolylinePoints) return 0;
|
|
510
|
+
|
|
511
|
+
// else: set simplified polyline points as link vertices
|
|
512
|
+
// remove first and last polyline points again (= source/target anchors)
|
|
513
|
+
link.vertices(polylinePoints.slice(1, numPolylinePoints - 1), opt);
|
|
514
|
+
return (numRoutePoints - numPolylinePoints);
|
|
515
|
+
},
|
|
516
|
+
|
|
517
|
+
getEndView: function(type) {
|
|
518
|
+
switch (type) {
|
|
519
|
+
case 'source':
|
|
520
|
+
return this.sourceView || null;
|
|
521
|
+
case 'target':
|
|
522
|
+
return this.targetView || null;
|
|
523
|
+
default:
|
|
524
|
+
throw new Error('dia.LinkView: type parameter required.');
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
|
|
528
|
+
getEndAnchor: function(type) {
|
|
529
|
+
switch (type) {
|
|
530
|
+
case 'source':
|
|
531
|
+
return new Point(this.sourceAnchor);
|
|
532
|
+
case 'target':
|
|
533
|
+
return new Point(this.targetAnchor);
|
|
534
|
+
default:
|
|
535
|
+
throw new Error('dia.LinkView: type parameter required.');
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
|
|
539
|
+
getEndConnectionPoint: function(type) {
|
|
540
|
+
switch (type) {
|
|
541
|
+
case 'source':
|
|
542
|
+
return new Point(this.sourcePoint);
|
|
543
|
+
case 'target':
|
|
544
|
+
return new Point(this.targetPoint);
|
|
545
|
+
default:
|
|
546
|
+
throw new Error('dia.LinkView: type parameter required.');
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
|
|
550
|
+
getEndMagnet: function(type) {
|
|
551
|
+
switch (type) {
|
|
552
|
+
case 'source':
|
|
553
|
+
var sourceView = this.sourceView;
|
|
554
|
+
if (!sourceView) break;
|
|
555
|
+
return this.sourceMagnet || sourceView.el;
|
|
556
|
+
case 'target':
|
|
557
|
+
var targetView = this.targetView;
|
|
558
|
+
if (!targetView) break;
|
|
559
|
+
return this.targetMagnet || targetView.el;
|
|
560
|
+
default:
|
|
561
|
+
throw new Error('dia.LinkView: type parameter required.');
|
|
562
|
+
}
|
|
563
|
+
return null;
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
// Updating.
|
|
568
|
+
// ---------
|
|
569
|
+
|
|
570
|
+
update: function() {
|
|
571
|
+
this.updateRoute();
|
|
572
|
+
this.updatePath();
|
|
573
|
+
this.updateDOM();
|
|
574
|
+
return this;
|
|
575
|
+
},
|
|
576
|
+
|
|
577
|
+
translate: function(tx = 0, ty = 0) {
|
|
578
|
+
const { route, path } = this;
|
|
579
|
+
if (!route || !path) return;
|
|
580
|
+
// translate the route
|
|
581
|
+
const polyline = new Polyline(route);
|
|
582
|
+
polyline.translate(tx, ty);
|
|
583
|
+
this.route = polyline.points;
|
|
584
|
+
// translate source and target connection and anchor points.
|
|
585
|
+
this.sourcePoint.offset(tx, ty);
|
|
586
|
+
this.targetPoint.offset(tx, ty);
|
|
587
|
+
this.sourceAnchor.offset(tx, ty);
|
|
588
|
+
this.targetAnchor.offset(tx, ty);
|
|
589
|
+
// translate the geometry path
|
|
590
|
+
path.translate(tx, ty);
|
|
591
|
+
this.updateDOM();
|
|
592
|
+
},
|
|
593
|
+
|
|
594
|
+
updateDOM() {
|
|
595
|
+
const { el, model, selectors } = this;
|
|
596
|
+
this.cleanNodesCache();
|
|
597
|
+
// update SVG attributes defined by 'attrs/'.
|
|
598
|
+
this.updateDOMSubtreeAttributes(el, model.attr(), { selectors });
|
|
599
|
+
// update the label position etc.
|
|
600
|
+
this.updateLabelPositions();
|
|
601
|
+
// *Deprecated*
|
|
602
|
+
// Local perpendicular flag (as opposed to one defined on paper).
|
|
603
|
+
// Could be enabled inside a connector/router. It's valid only
|
|
604
|
+
// during the update execution.
|
|
605
|
+
this.options.perpendicular = null;
|
|
606
|
+
},
|
|
607
|
+
|
|
608
|
+
updateRoute: function() {
|
|
609
|
+
const { model } = this;
|
|
610
|
+
const vertices = model.vertices();
|
|
611
|
+
// 1. Find Anchors
|
|
612
|
+
const anchors = this.findAnchors(vertices);
|
|
613
|
+
const sourceAnchor = this.sourceAnchor = anchors.source;
|
|
614
|
+
const targetAnchor = this.targetAnchor = anchors.target;
|
|
615
|
+
// 2. Find Route
|
|
616
|
+
const route = this.findRoute(vertices);
|
|
617
|
+
this.route = route;
|
|
618
|
+
// 3. Find Connection Points
|
|
619
|
+
var connectionPoints = this.findConnectionPoints(route, sourceAnchor, targetAnchor);
|
|
620
|
+
this.sourcePoint = connectionPoints.source;
|
|
621
|
+
this.targetPoint = connectionPoints.target;
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
updatePath: function() {
|
|
625
|
+
const { route, sourcePoint, targetPoint } = this;
|
|
626
|
+
// 4. Find Connection
|
|
627
|
+
const path = this.findPath(route, sourcePoint.clone(), targetPoint.clone());
|
|
628
|
+
this.path = path;
|
|
629
|
+
},
|
|
630
|
+
|
|
631
|
+
findAnchorsOrdered: function(firstEndType, firstRef, secondEndType, secondRef) {
|
|
632
|
+
|
|
633
|
+
var firstAnchor, secondAnchor;
|
|
634
|
+
var firstAnchorRef, secondAnchorRef;
|
|
635
|
+
var model = this.model;
|
|
636
|
+
var firstDef = model.get(firstEndType);
|
|
637
|
+
var secondDef = model.get(secondEndType);
|
|
638
|
+
var firstView = this.getEndView(firstEndType);
|
|
639
|
+
var secondView = this.getEndView(secondEndType);
|
|
640
|
+
var firstMagnet = this.getEndMagnet(firstEndType);
|
|
641
|
+
var secondMagnet = this.getEndMagnet(secondEndType);
|
|
642
|
+
|
|
643
|
+
// Anchor first
|
|
644
|
+
if (firstView) {
|
|
645
|
+
if (firstRef) {
|
|
646
|
+
firstAnchorRef = new Point(firstRef);
|
|
647
|
+
} else if (secondView) {
|
|
648
|
+
firstAnchorRef = secondMagnet;
|
|
649
|
+
} else {
|
|
650
|
+
firstAnchorRef = new Point(secondDef);
|
|
651
|
+
}
|
|
652
|
+
firstAnchor = this.getAnchor(firstDef.anchor, firstView, firstMagnet, firstAnchorRef, firstEndType);
|
|
653
|
+
} else {
|
|
654
|
+
firstAnchor = new Point(firstDef);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Anchor second
|
|
658
|
+
if (secondView) {
|
|
659
|
+
secondAnchorRef = new Point(secondRef || firstAnchor);
|
|
660
|
+
secondAnchor = this.getAnchor(secondDef.anchor, secondView, secondMagnet, secondAnchorRef, secondEndType);
|
|
661
|
+
} else {
|
|
662
|
+
secondAnchor = new Point(secondDef);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
var res = {};
|
|
666
|
+
res[firstEndType] = firstAnchor;
|
|
667
|
+
res[secondEndType] = secondAnchor;
|
|
668
|
+
return res;
|
|
669
|
+
},
|
|
670
|
+
|
|
671
|
+
findAnchors: function(vertices) {
|
|
672
|
+
|
|
673
|
+
var model = this.model;
|
|
674
|
+
var firstVertex = vertices[0];
|
|
675
|
+
var lastVertex = vertices[vertices.length - 1];
|
|
676
|
+
|
|
677
|
+
if (model.target().priority && !model.source().priority) {
|
|
678
|
+
// Reversed order
|
|
679
|
+
return this.findAnchorsOrdered('target', lastVertex, 'source', firstVertex);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Usual order
|
|
683
|
+
return this.findAnchorsOrdered('source', firstVertex, 'target', lastVertex);
|
|
684
|
+
},
|
|
685
|
+
|
|
686
|
+
findConnectionPoints: function(route, sourceAnchor, targetAnchor) {
|
|
687
|
+
|
|
688
|
+
var firstWaypoint = route[0];
|
|
689
|
+
var lastWaypoint = route[route.length - 1];
|
|
690
|
+
var model = this.model;
|
|
691
|
+
var sourceDef = model.get('source');
|
|
692
|
+
var targetDef = model.get('target');
|
|
693
|
+
var sourceView = this.sourceView;
|
|
694
|
+
var targetView = this.targetView;
|
|
695
|
+
var paperOptions = this.paper.options;
|
|
696
|
+
var sourceMagnet, targetMagnet;
|
|
697
|
+
|
|
698
|
+
// Connection Point Source
|
|
699
|
+
var sourcePoint;
|
|
700
|
+
if (sourceView && !sourceView.isNodeConnection(this.sourceMagnet)) {
|
|
701
|
+
sourceMagnet = (this.sourceMagnet || sourceView.el);
|
|
702
|
+
var sourceConnectionPointDef = sourceDef.connectionPoint || paperOptions.defaultConnectionPoint;
|
|
703
|
+
var sourcePointRef = firstWaypoint || targetAnchor;
|
|
704
|
+
var sourceLine = new Line(sourcePointRef, sourceAnchor);
|
|
705
|
+
sourcePoint = this.getConnectionPoint(
|
|
706
|
+
sourceConnectionPointDef,
|
|
707
|
+
sourceView,
|
|
708
|
+
sourceMagnet,
|
|
709
|
+
sourceLine,
|
|
710
|
+
'source'
|
|
711
|
+
);
|
|
712
|
+
} else {
|
|
713
|
+
sourcePoint = sourceAnchor;
|
|
714
|
+
}
|
|
715
|
+
// Connection Point Target
|
|
716
|
+
var targetPoint;
|
|
717
|
+
if (targetView && !targetView.isNodeConnection(this.targetMagnet)) {
|
|
718
|
+
targetMagnet = (this.targetMagnet || targetView.el);
|
|
719
|
+
var targetConnectionPointDef = targetDef.connectionPoint || paperOptions.defaultConnectionPoint;
|
|
720
|
+
var targetPointRef = lastWaypoint || sourceAnchor;
|
|
721
|
+
var targetLine = new Line(targetPointRef, targetAnchor);
|
|
722
|
+
targetPoint = this.getConnectionPoint(
|
|
723
|
+
targetConnectionPointDef,
|
|
724
|
+
targetView,
|
|
725
|
+
targetMagnet,
|
|
726
|
+
targetLine,
|
|
727
|
+
'target'
|
|
728
|
+
);
|
|
729
|
+
} else {
|
|
730
|
+
targetPoint = targetAnchor;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
return {
|
|
734
|
+
source: sourcePoint,
|
|
735
|
+
target: targetPoint
|
|
736
|
+
};
|
|
737
|
+
},
|
|
738
|
+
|
|
739
|
+
getAnchor: function(anchorDef, cellView, magnet, ref, endType) {
|
|
740
|
+
|
|
741
|
+
var isConnection = cellView.isNodeConnection(magnet);
|
|
742
|
+
var paperOptions = this.paper.options;
|
|
743
|
+
if (!anchorDef) {
|
|
744
|
+
if (isConnection) {
|
|
745
|
+
anchorDef = paperOptions.defaultLinkAnchor;
|
|
746
|
+
} else {
|
|
747
|
+
if (this.options.perpendicular) {
|
|
748
|
+
// Backwards compatibility
|
|
749
|
+
// See `manhattan` router for more details
|
|
750
|
+
anchorDef = { name: 'perpendicular' };
|
|
751
|
+
} else {
|
|
752
|
+
anchorDef = paperOptions.defaultAnchor;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (!anchorDef) throw new Error('Anchor required.');
|
|
758
|
+
var anchorFn;
|
|
759
|
+
if (typeof anchorDef === 'function') {
|
|
760
|
+
anchorFn = anchorDef;
|
|
761
|
+
} else {
|
|
762
|
+
var anchorName = anchorDef.name;
|
|
763
|
+
var anchorNamespace = isConnection ? 'linkAnchorNamespace' : 'anchorNamespace';
|
|
764
|
+
anchorFn = paperOptions[anchorNamespace][anchorName];
|
|
765
|
+
if (typeof anchorFn !== 'function') throw new Error('Unknown anchor: ' + anchorName);
|
|
766
|
+
}
|
|
767
|
+
var anchor = anchorFn.call(
|
|
768
|
+
this,
|
|
769
|
+
cellView,
|
|
770
|
+
magnet,
|
|
771
|
+
ref,
|
|
772
|
+
anchorDef.args || {},
|
|
773
|
+
endType,
|
|
774
|
+
this
|
|
775
|
+
);
|
|
776
|
+
if (!anchor) return new Point();
|
|
777
|
+
return anchor.round(this.decimalsRounding);
|
|
778
|
+
},
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
getConnectionPoint: function(connectionPointDef, view, magnet, line, endType) {
|
|
782
|
+
|
|
783
|
+
var connectionPoint;
|
|
784
|
+
var anchor = line.end;
|
|
785
|
+
var paperOptions = this.paper.options;
|
|
786
|
+
|
|
787
|
+
if (!connectionPointDef) return anchor;
|
|
788
|
+
var connectionPointFn;
|
|
789
|
+
if (typeof connectionPointDef === 'function') {
|
|
790
|
+
connectionPointFn = connectionPointDef;
|
|
791
|
+
} else {
|
|
792
|
+
var connectionPointName = connectionPointDef.name;
|
|
793
|
+
connectionPointFn = paperOptions.connectionPointNamespace[connectionPointName];
|
|
794
|
+
if (typeof connectionPointFn !== 'function') throw new Error('Unknown connection point: ' + connectionPointName);
|
|
795
|
+
}
|
|
796
|
+
connectionPoint = connectionPointFn.call(this, line, view, magnet, connectionPointDef.args || {}, endType, this);
|
|
797
|
+
if (!connectionPoint) return anchor;
|
|
798
|
+
return connectionPoint.round(this.decimalsRounding);
|
|
799
|
+
},
|
|
800
|
+
|
|
801
|
+
// combine default label position with built-in default label position
|
|
802
|
+
_getDefaultLabelPositionProperty: function() {
|
|
803
|
+
|
|
804
|
+
var model = this.model;
|
|
805
|
+
|
|
806
|
+
var builtinDefaultLabel = model._builtins.defaultLabel;
|
|
807
|
+
var builtinDefaultLabelPosition = builtinDefaultLabel.position;
|
|
808
|
+
|
|
809
|
+
var defaultLabel = model._getDefaultLabel();
|
|
810
|
+
var defaultLabelPosition = this._normalizeLabelPosition(defaultLabel.position);
|
|
811
|
+
|
|
812
|
+
return merge({}, builtinDefaultLabelPosition, defaultLabelPosition);
|
|
813
|
+
},
|
|
814
|
+
|
|
815
|
+
// if label position is a number, normalize it to a position object
|
|
816
|
+
// this makes sure that label positions can be merged properly
|
|
817
|
+
_normalizeLabelPosition: function(labelPosition) {
|
|
818
|
+
|
|
819
|
+
if (typeof labelPosition === 'number') return { distance: labelPosition, offset: null, angle: 0, args: null };
|
|
820
|
+
return labelPosition;
|
|
821
|
+
},
|
|
822
|
+
|
|
823
|
+
// expects normalized position properties
|
|
824
|
+
// e.g. `this._normalizeLabelPosition(labelPosition)` and `this._getDefaultLabelPositionProperty()`
|
|
825
|
+
_mergeLabelPositionProperty: function(normalizedLabelPosition, normalizedDefaultLabelPosition) {
|
|
826
|
+
|
|
827
|
+
if (normalizedLabelPosition === null) return null;
|
|
828
|
+
if (normalizedLabelPosition === undefined) {
|
|
829
|
+
|
|
830
|
+
if (normalizedDefaultLabelPosition === null) return null;
|
|
831
|
+
return normalizedDefaultLabelPosition;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
return merge({}, normalizedDefaultLabelPosition, normalizedLabelPosition);
|
|
835
|
+
},
|
|
836
|
+
|
|
837
|
+
updateLabelPositions: function() {
|
|
838
|
+
|
|
839
|
+
if (!this._V.labels) return this;
|
|
840
|
+
|
|
841
|
+
var path = this.path;
|
|
842
|
+
if (!path) return this;
|
|
843
|
+
|
|
844
|
+
// This method assumes all the label nodes are stored in the `this._labelCache` hash table
|
|
845
|
+
// by their indices in the `this.get('labels')` array. This is done in the `renderLabels()` method.
|
|
846
|
+
|
|
847
|
+
var model = this.model;
|
|
848
|
+
var labels = model.get('labels') || [];
|
|
849
|
+
if (!labels.length) return this;
|
|
850
|
+
|
|
851
|
+
var defaultLabelPosition = this._getDefaultLabelPositionProperty();
|
|
852
|
+
|
|
853
|
+
for (var idx = 0, n = labels.length; idx < n; idx++) {
|
|
854
|
+
var labelNode = this._labelCache[idx];
|
|
855
|
+
if (!labelNode) continue;
|
|
856
|
+
var label = labels[idx];
|
|
857
|
+
var labelPosition = this._normalizeLabelPosition(label.position);
|
|
858
|
+
var position = this._mergeLabelPositionProperty(labelPosition, defaultLabelPosition);
|
|
859
|
+
var transformationMatrix = this._getLabelTransformationMatrix(position);
|
|
860
|
+
labelNode.setAttribute('transform', V.matrixToTransformString(transformationMatrix));
|
|
861
|
+
this._cleanLabelMatrices(idx);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return this;
|
|
865
|
+
},
|
|
866
|
+
|
|
867
|
+
_cleanLabelMatrices: function(index) {
|
|
868
|
+
// Clean magnetMatrix for all nodes of the label.
|
|
869
|
+
// Cached BoundingRect does not need to updated when the position changes
|
|
870
|
+
// TODO: this doesn't work for labels with XML String markups.
|
|
871
|
+
const { metrics, _labelSelectors } = this;
|
|
872
|
+
const selectors = _labelSelectors[index];
|
|
873
|
+
if (!selectors) return;
|
|
874
|
+
for (let selector in selectors) {
|
|
875
|
+
const { id } = selectors[selector];
|
|
876
|
+
if (id && (id in metrics)) delete metrics[id].magnetMatrix;
|
|
877
|
+
}
|
|
878
|
+
},
|
|
879
|
+
|
|
880
|
+
updateEndProperties: function(endType) {
|
|
881
|
+
|
|
882
|
+
const { model, paper } = this;
|
|
883
|
+
const endViewProperty = `${endType}View`;
|
|
884
|
+
const endDef = model.get(endType);
|
|
885
|
+
const endId = endDef && endDef.id;
|
|
886
|
+
|
|
887
|
+
if (!endId) {
|
|
888
|
+
// the link end is a point ~ rect 0x0
|
|
889
|
+
this[endViewProperty] = null;
|
|
890
|
+
this.updateEndMagnet(endType);
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const endModel = paper.getModelById(endId);
|
|
895
|
+
if (!endModel) throw new Error('LinkView: invalid ' + endType + ' cell.');
|
|
896
|
+
|
|
897
|
+
const endView = endModel.findView(paper);
|
|
898
|
+
if (!endView) {
|
|
899
|
+
// A view for a model should always exist
|
|
900
|
+
return false;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
this[endViewProperty] = endView;
|
|
904
|
+
this.updateEndMagnet(endType);
|
|
905
|
+
return true;
|
|
906
|
+
},
|
|
907
|
+
|
|
908
|
+
updateEndMagnet: function(endType) {
|
|
909
|
+
|
|
910
|
+
const endMagnetProperty = `${endType}Magnet`;
|
|
911
|
+
const endView = this.getEndView(endType);
|
|
912
|
+
if (endView) {
|
|
913
|
+
let connectedMagnet = endView.getMagnetFromLinkEnd(this.model.get(endType));
|
|
914
|
+
if (connectedMagnet === endView.el) connectedMagnet = null;
|
|
915
|
+
this[endMagnetProperty] = connectedMagnet;
|
|
916
|
+
} else {
|
|
917
|
+
this[endMagnetProperty] = null;
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
|
|
921
|
+
_getLabelPositionProperty: function(idx) {
|
|
922
|
+
|
|
923
|
+
return (this.model.label(idx).position || {});
|
|
924
|
+
},
|
|
925
|
+
|
|
926
|
+
_getLabelPositionAngle: function(idx) {
|
|
927
|
+
|
|
928
|
+
var labelPosition = this._getLabelPositionProperty(idx);
|
|
929
|
+
return (labelPosition.angle || 0);
|
|
930
|
+
},
|
|
931
|
+
|
|
932
|
+
_getLabelPositionArgs: function(idx) {
|
|
933
|
+
|
|
934
|
+
var labelPosition = this._getLabelPositionProperty(idx);
|
|
935
|
+
return labelPosition.args;
|
|
936
|
+
},
|
|
937
|
+
|
|
938
|
+
_getDefaultLabelPositionArgs: function() {
|
|
939
|
+
|
|
940
|
+
var defaultLabel = this.model._getDefaultLabel();
|
|
941
|
+
var defaultLabelPosition = defaultLabel.position || {};
|
|
942
|
+
return defaultLabelPosition.args;
|
|
943
|
+
},
|
|
944
|
+
|
|
945
|
+
// merge default label position args into label position args
|
|
946
|
+
// keep `undefined` or `null` because `{}` means something else
|
|
947
|
+
_mergeLabelPositionArgs: function(labelPositionArgs, defaultLabelPositionArgs) {
|
|
948
|
+
|
|
949
|
+
if (labelPositionArgs === null) return null;
|
|
950
|
+
if (labelPositionArgs === undefined) {
|
|
951
|
+
|
|
952
|
+
if (defaultLabelPositionArgs === null) return null;
|
|
953
|
+
return defaultLabelPositionArgs;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
return merge({}, defaultLabelPositionArgs, labelPositionArgs);
|
|
957
|
+
},
|
|
958
|
+
|
|
959
|
+
// Add default label at given position at end of `labels` array.
|
|
960
|
+
// Four signatures:
|
|
961
|
+
// - obj, obj = point, opt
|
|
962
|
+
// - obj, num, obj = point, angle, opt
|
|
963
|
+
// - num, num, obj = x, y, opt
|
|
964
|
+
// - num, num, num, obj = x, y, angle, opt
|
|
965
|
+
// Assigns relative coordinates by default:
|
|
966
|
+
// `opt.absoluteDistance` forces absolute coordinates.
|
|
967
|
+
// `opt.reverseDistance` forces reverse absolute coordinates (if absoluteDistance = true).
|
|
968
|
+
// `opt.absoluteOffset` forces absolute coordinates for offset.
|
|
969
|
+
// Additional args:
|
|
970
|
+
// `opt.keepGradient` auto-adjusts the angle of the label to match path gradient at position.
|
|
971
|
+
// `opt.ensureLegibility` rotates labels so they are never upside-down.
|
|
972
|
+
addLabel: function(p1, p2, p3, p4) {
|
|
973
|
+
|
|
974
|
+
// normalize data from the four possible signatures
|
|
975
|
+
var localX;
|
|
976
|
+
var localY;
|
|
977
|
+
var localAngle = 0;
|
|
978
|
+
var localOpt;
|
|
979
|
+
if (typeof p1 !== 'number') {
|
|
980
|
+
// {x, y} object provided as first parameter
|
|
981
|
+
localX = p1.x;
|
|
982
|
+
localY = p1.y;
|
|
983
|
+
if (typeof p2 === 'number') {
|
|
984
|
+
// angle and opt provided as second and third parameters
|
|
985
|
+
localAngle = p2;
|
|
986
|
+
localOpt = p3;
|
|
987
|
+
} else {
|
|
988
|
+
// opt provided as second parameter
|
|
989
|
+
localOpt = p2;
|
|
990
|
+
}
|
|
991
|
+
} else {
|
|
992
|
+
// x and y provided as first and second parameters
|
|
993
|
+
localX = p1;
|
|
994
|
+
localY = p2;
|
|
995
|
+
if (typeof p3 === 'number') {
|
|
996
|
+
// angle and opt provided as third and fourth parameters
|
|
997
|
+
localAngle = p3;
|
|
998
|
+
localOpt = p4;
|
|
999
|
+
} else {
|
|
1000
|
+
// opt provided as third parameter
|
|
1001
|
+
localOpt = p3;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// merge label position arguments
|
|
1006
|
+
var defaultLabelPositionArgs = this._getDefaultLabelPositionArgs();
|
|
1007
|
+
var labelPositionArgs = localOpt;
|
|
1008
|
+
var positionArgs = this._mergeLabelPositionArgs(labelPositionArgs, defaultLabelPositionArgs);
|
|
1009
|
+
|
|
1010
|
+
// append label to labels array
|
|
1011
|
+
var label = { position: this.getLabelPosition(localX, localY, localAngle, positionArgs) };
|
|
1012
|
+
var idx = -1;
|
|
1013
|
+
this.model.insertLabel(idx, label, localOpt);
|
|
1014
|
+
return idx;
|
|
1015
|
+
},
|
|
1016
|
+
|
|
1017
|
+
// Add a new vertex at calculated index to the `vertices` array.
|
|
1018
|
+
addVertex: function(x, y, opt) {
|
|
1019
|
+
|
|
1020
|
+
// accept input in form `{ x, y }, opt` or `x, y, opt`
|
|
1021
|
+
var isPointProvided = (typeof x !== 'number');
|
|
1022
|
+
var localX = isPointProvided ? x.x : x;
|
|
1023
|
+
var localY = isPointProvided ? x.y : y;
|
|
1024
|
+
var localOpt = isPointProvided ? y : opt;
|
|
1025
|
+
|
|
1026
|
+
var vertex = { x: localX, y: localY };
|
|
1027
|
+
var idx = this.getVertexIndex(localX, localY);
|
|
1028
|
+
this.model.insertVertex(idx, vertex, localOpt);
|
|
1029
|
+
return idx;
|
|
1030
|
+
},
|
|
1031
|
+
|
|
1032
|
+
// Send a token (an SVG element, usually a circle) along the connection path.
|
|
1033
|
+
// Example: `link.findView(paper).sendToken(V('circle', { r: 7, fill: 'green' }).node)`
|
|
1034
|
+
// `opt.duration` is optional and is a time in milliseconds that the token travels from the source to the target of the link. Default is `1000`.
|
|
1035
|
+
// `opt.direction` is optional and it determines whether the token goes from source to target or other way round (`reverse`)
|
|
1036
|
+
// `opt.connection` is an optional selector to the connection path.
|
|
1037
|
+
// `callback` is optional and is a function to be called once the token reaches the target.
|
|
1038
|
+
sendToken: function(token, opt, callback) {
|
|
1039
|
+
|
|
1040
|
+
function onAnimationEnd(vToken, callback) {
|
|
1041
|
+
return function() {
|
|
1042
|
+
vToken.remove();
|
|
1043
|
+
if (typeof callback === 'function') {
|
|
1044
|
+
callback();
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
var duration, isReversed, selector;
|
|
1050
|
+
if (isObject(opt)) {
|
|
1051
|
+
duration = opt.duration;
|
|
1052
|
+
isReversed = (opt.direction === 'reverse');
|
|
1053
|
+
selector = opt.connection;
|
|
1054
|
+
} else {
|
|
1055
|
+
// Backwards compatibility
|
|
1056
|
+
duration = opt;
|
|
1057
|
+
isReversed = false;
|
|
1058
|
+
selector = null;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
duration = duration || 1000;
|
|
1062
|
+
|
|
1063
|
+
var animationAttributes = {
|
|
1064
|
+
dur: duration + 'ms',
|
|
1065
|
+
repeatCount: 1,
|
|
1066
|
+
calcMode: 'linear',
|
|
1067
|
+
fill: 'freeze'
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
if (isReversed) {
|
|
1071
|
+
animationAttributes.keyPoints = '1;0';
|
|
1072
|
+
animationAttributes.keyTimes = '0;1';
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
var vToken = V(token);
|
|
1076
|
+
var connection;
|
|
1077
|
+
if (typeof selector === 'string') {
|
|
1078
|
+
// Use custom connection path.
|
|
1079
|
+
connection = this.findNode(selector);
|
|
1080
|
+
} else {
|
|
1081
|
+
// Select connection path automatically.
|
|
1082
|
+
var cache = this._V;
|
|
1083
|
+
connection = (cache.connection) ? cache.connection.node : this.el.querySelector('path');
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (!(connection instanceof SVGPathElement)) {
|
|
1087
|
+
throw new Error('dia.LinkView: token animation requires a valid connection path.');
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
vToken
|
|
1091
|
+
.appendTo(this.paper.cells)
|
|
1092
|
+
.animateAlongPath(animationAttributes, connection);
|
|
1093
|
+
|
|
1094
|
+
setTimeout(onAnimationEnd(vToken, callback), duration);
|
|
1095
|
+
},
|
|
1096
|
+
|
|
1097
|
+
findRoute: function(vertices) {
|
|
1098
|
+
|
|
1099
|
+
vertices || (vertices = []);
|
|
1100
|
+
|
|
1101
|
+
var namespace = this.paper.options.routerNamespace || routers;
|
|
1102
|
+
var router = this.model.router();
|
|
1103
|
+
var defaultRouter = this.paper.options.defaultRouter;
|
|
1104
|
+
|
|
1105
|
+
if (!router) {
|
|
1106
|
+
if (defaultRouter) router = defaultRouter;
|
|
1107
|
+
else return vertices.map(Point); // no router specified
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
var routerFn = isFunction(router) ? router : namespace[router.name];
|
|
1111
|
+
if (!isFunction(routerFn)) {
|
|
1112
|
+
throw new Error('dia.LinkView: unknown router: "' + router.name + '".');
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
var args = router.args || {};
|
|
1116
|
+
|
|
1117
|
+
var route = routerFn.call(
|
|
1118
|
+
this, // context
|
|
1119
|
+
vertices, // vertices
|
|
1120
|
+
args, // options
|
|
1121
|
+
this // linkView
|
|
1122
|
+
);
|
|
1123
|
+
|
|
1124
|
+
if (!route) return vertices.map(Point);
|
|
1125
|
+
return route;
|
|
1126
|
+
},
|
|
1127
|
+
|
|
1128
|
+
// Return the `d` attribute value of the `<path>` element representing the link
|
|
1129
|
+
// between `source` and `target`.
|
|
1130
|
+
findPath: function(route, sourcePoint, targetPoint) {
|
|
1131
|
+
|
|
1132
|
+
var namespace = this.paper.options.connectorNamespace || connectors;
|
|
1133
|
+
var connector = this.model.connector();
|
|
1134
|
+
var defaultConnector = this.paper.options.defaultConnector;
|
|
1135
|
+
|
|
1136
|
+
if (!connector) {
|
|
1137
|
+
connector = defaultConnector || {};
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
var connectorFn = isFunction(connector) ? connector : namespace[connector.name];
|
|
1141
|
+
if (!isFunction(connectorFn)) {
|
|
1142
|
+
throw new Error('dia.LinkView: unknown connector: "' + connector.name + '".');
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
var args = clone(connector.args || {});
|
|
1146
|
+
args.raw = true; // Request raw g.Path as the result.
|
|
1147
|
+
|
|
1148
|
+
var path = connectorFn.call(
|
|
1149
|
+
this, // context
|
|
1150
|
+
sourcePoint, // start point
|
|
1151
|
+
targetPoint, // end point
|
|
1152
|
+
route, // vertices
|
|
1153
|
+
args, // options
|
|
1154
|
+
this // linkView
|
|
1155
|
+
);
|
|
1156
|
+
|
|
1157
|
+
if (typeof path === 'string') {
|
|
1158
|
+
// Backwards compatibility for connectors not supporting `raw` option.
|
|
1159
|
+
path = new Path(V.normalizePathData(path));
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
return path;
|
|
1163
|
+
},
|
|
1164
|
+
|
|
1165
|
+
// Public API.
|
|
1166
|
+
// -----------
|
|
1167
|
+
|
|
1168
|
+
getConnection: function() {
|
|
1169
|
+
|
|
1170
|
+
var path = this.path;
|
|
1171
|
+
if (!path) return null;
|
|
1172
|
+
|
|
1173
|
+
return path.clone();
|
|
1174
|
+
},
|
|
1175
|
+
|
|
1176
|
+
getSerializedConnection: function() {
|
|
1177
|
+
|
|
1178
|
+
var path = this.path;
|
|
1179
|
+
if (!path) return null;
|
|
1180
|
+
|
|
1181
|
+
var metrics = this.metrics;
|
|
1182
|
+
if (metrics.hasOwnProperty('data')) return metrics.data;
|
|
1183
|
+
var data = path.serialize();
|
|
1184
|
+
metrics.data = data;
|
|
1185
|
+
return data;
|
|
1186
|
+
},
|
|
1187
|
+
|
|
1188
|
+
getConnectionSubdivisions: function() {
|
|
1189
|
+
|
|
1190
|
+
var path = this.path;
|
|
1191
|
+
if (!path) return null;
|
|
1192
|
+
|
|
1193
|
+
var metrics = this.metrics;
|
|
1194
|
+
if (metrics.hasOwnProperty('segmentSubdivisions')) return metrics.segmentSubdivisions;
|
|
1195
|
+
var subdivisions = path.getSegmentSubdivisions();
|
|
1196
|
+
metrics.segmentSubdivisions = subdivisions;
|
|
1197
|
+
return subdivisions;
|
|
1198
|
+
},
|
|
1199
|
+
|
|
1200
|
+
getConnectionLength: function() {
|
|
1201
|
+
|
|
1202
|
+
var path = this.path;
|
|
1203
|
+
if (!path) return 0;
|
|
1204
|
+
|
|
1205
|
+
var metrics = this.metrics;
|
|
1206
|
+
if (metrics.hasOwnProperty('length')) return metrics.length;
|
|
1207
|
+
var length = path.length({ segmentSubdivisions: this.getConnectionSubdivisions() });
|
|
1208
|
+
metrics.length = length;
|
|
1209
|
+
return length;
|
|
1210
|
+
},
|
|
1211
|
+
|
|
1212
|
+
getPointAtLength: function(length) {
|
|
1213
|
+
|
|
1214
|
+
var path = this.path;
|
|
1215
|
+
if (!path) return null;
|
|
1216
|
+
|
|
1217
|
+
return path.pointAtLength(length, { segmentSubdivisions: this.getConnectionSubdivisions() });
|
|
1218
|
+
},
|
|
1219
|
+
|
|
1220
|
+
getPointAtRatio: function(ratio) {
|
|
1221
|
+
|
|
1222
|
+
var path = this.path;
|
|
1223
|
+
if (!path) return null;
|
|
1224
|
+
if (isPercentage(ratio)) ratio = parseFloat(ratio) / 100;
|
|
1225
|
+
return path.pointAt(ratio, { segmentSubdivisions: this.getConnectionSubdivisions() });
|
|
1226
|
+
},
|
|
1227
|
+
|
|
1228
|
+
getTangentAtLength: function(length) {
|
|
1229
|
+
|
|
1230
|
+
var path = this.path;
|
|
1231
|
+
if (!path) return null;
|
|
1232
|
+
|
|
1233
|
+
return path.tangentAtLength(length, { segmentSubdivisions: this.getConnectionSubdivisions() });
|
|
1234
|
+
},
|
|
1235
|
+
|
|
1236
|
+
getTangentAtRatio: function(ratio) {
|
|
1237
|
+
|
|
1238
|
+
var path = this.path;
|
|
1239
|
+
if (!path) return null;
|
|
1240
|
+
|
|
1241
|
+
return path.tangentAt(ratio, { segmentSubdivisions: this.getConnectionSubdivisions() });
|
|
1242
|
+
},
|
|
1243
|
+
|
|
1244
|
+
getClosestPoint: function(point) {
|
|
1245
|
+
|
|
1246
|
+
var path = this.path;
|
|
1247
|
+
if (!path) return null;
|
|
1248
|
+
|
|
1249
|
+
return path.closestPoint(point, { segmentSubdivisions: this.getConnectionSubdivisions() });
|
|
1250
|
+
},
|
|
1251
|
+
|
|
1252
|
+
getClosestPointLength: function(point) {
|
|
1253
|
+
|
|
1254
|
+
var path = this.path;
|
|
1255
|
+
if (!path) return null;
|
|
1256
|
+
|
|
1257
|
+
return path.closestPointLength(point, { segmentSubdivisions: this.getConnectionSubdivisions() });
|
|
1258
|
+
},
|
|
1259
|
+
|
|
1260
|
+
getClosestPointRatio: function(point) {
|
|
1261
|
+
|
|
1262
|
+
var path = this.path;
|
|
1263
|
+
if (!path) return null;
|
|
1264
|
+
|
|
1265
|
+
return path.closestPointNormalizedLength(point, { segmentSubdivisions: this.getConnectionSubdivisions() });
|
|
1266
|
+
},
|
|
1267
|
+
|
|
1268
|
+
// Get label position object based on two provided coordinates, x and y.
|
|
1269
|
+
// (Used behind the scenes when user moves labels around.)
|
|
1270
|
+
// Two signatures:
|
|
1271
|
+
// - num, num, obj = x, y, options
|
|
1272
|
+
// - num, num, num, obj = x, y, angle, options
|
|
1273
|
+
// Accepts distance/offset options = `absoluteDistance: boolean`, `reverseDistance: boolean`, `absoluteOffset: boolean`
|
|
1274
|
+
// - `absoluteOffset` is necessary in order to move beyond connection endpoints
|
|
1275
|
+
// Additional options = `keepGradient: boolean`, `ensureLegibility: boolean`
|
|
1276
|
+
getLabelPosition: function(x, y, p3, p4) {
|
|
1277
|
+
|
|
1278
|
+
var position = {};
|
|
1279
|
+
|
|
1280
|
+
// normalize data from the two possible signatures
|
|
1281
|
+
var localAngle = 0;
|
|
1282
|
+
var localOpt;
|
|
1283
|
+
if (typeof p3 === 'number') {
|
|
1284
|
+
// angle and opt provided as third and fourth argument
|
|
1285
|
+
localAngle = p3;
|
|
1286
|
+
localOpt = p4;
|
|
1287
|
+
} else {
|
|
1288
|
+
// opt provided as third argument
|
|
1289
|
+
localOpt = p3;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// save localOpt as `args` of the position object that is passed along
|
|
1293
|
+
if (localOpt) position.args = localOpt;
|
|
1294
|
+
|
|
1295
|
+
// identify distance/offset settings
|
|
1296
|
+
var isDistanceRelative = !(localOpt && localOpt.absoluteDistance); // relative by default
|
|
1297
|
+
var isDistanceAbsoluteReverse = (localOpt && localOpt.absoluteDistance && localOpt.reverseDistance); // non-reverse by default
|
|
1298
|
+
var isOffsetAbsolute = localOpt && localOpt.absoluteOffset; // offset is non-absolute by default
|
|
1299
|
+
|
|
1300
|
+
// find closest point t
|
|
1301
|
+
var path = this.path;
|
|
1302
|
+
var pathOpt = { segmentSubdivisions: this.getConnectionSubdivisions() };
|
|
1303
|
+
var labelPoint = new Point(x, y);
|
|
1304
|
+
var t = path.closestPointT(labelPoint, pathOpt);
|
|
1305
|
+
|
|
1306
|
+
// DISTANCE:
|
|
1307
|
+
var labelDistance = path.lengthAtT(t, pathOpt);
|
|
1308
|
+
if (isDistanceRelative) labelDistance = (labelDistance / this.getConnectionLength()) || 0; // fix to prevent NaN for 0 length
|
|
1309
|
+
if (isDistanceAbsoluteReverse) labelDistance = (-1 * (this.getConnectionLength() - labelDistance)) || 1; // fix for end point (-0 => 1)
|
|
1310
|
+
position.distance = labelDistance;
|
|
1311
|
+
|
|
1312
|
+
// OFFSET:
|
|
1313
|
+
// use absolute offset if:
|
|
1314
|
+
// - opt.absoluteOffset is true,
|
|
1315
|
+
// - opt.absoluteOffset is not true but there is no tangent
|
|
1316
|
+
var tangent;
|
|
1317
|
+
if (!isOffsetAbsolute) tangent = path.tangentAtT(t);
|
|
1318
|
+
var labelOffset;
|
|
1319
|
+
if (tangent) {
|
|
1320
|
+
labelOffset = tangent.pointOffset(labelPoint);
|
|
1321
|
+
} else {
|
|
1322
|
+
var closestPoint = path.pointAtT(t);
|
|
1323
|
+
var labelOffsetDiff = labelPoint.difference(closestPoint);
|
|
1324
|
+
labelOffset = { x: labelOffsetDiff.x, y: labelOffsetDiff.y };
|
|
1325
|
+
}
|
|
1326
|
+
position.offset = labelOffset;
|
|
1327
|
+
|
|
1328
|
+
// ANGLE:
|
|
1329
|
+
position.angle = localAngle;
|
|
1330
|
+
|
|
1331
|
+
return position;
|
|
1332
|
+
},
|
|
1333
|
+
|
|
1334
|
+
_getLabelTransformationMatrix: function(labelPosition) {
|
|
1335
|
+
|
|
1336
|
+
var labelDistance;
|
|
1337
|
+
var labelAngle = 0;
|
|
1338
|
+
var args = {};
|
|
1339
|
+
if (typeof labelPosition === 'number') {
|
|
1340
|
+
labelDistance = labelPosition;
|
|
1341
|
+
} else if (typeof labelPosition.distance === 'number') {
|
|
1342
|
+
args = labelPosition.args || {};
|
|
1343
|
+
labelDistance = labelPosition.distance;
|
|
1344
|
+
labelAngle = labelPosition.angle || 0;
|
|
1345
|
+
} else {
|
|
1346
|
+
throw new Error('dia.LinkView: invalid label position distance.');
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
var isDistanceRelative = ((labelDistance > 0) && (labelDistance <= 1));
|
|
1350
|
+
|
|
1351
|
+
var labelOffset = 0;
|
|
1352
|
+
var labelOffsetCoordinates = { x: 0, y: 0 };
|
|
1353
|
+
if (labelPosition.offset) {
|
|
1354
|
+
var positionOffset = labelPosition.offset;
|
|
1355
|
+
if (typeof positionOffset === 'number') labelOffset = positionOffset;
|
|
1356
|
+
if (positionOffset.x) labelOffsetCoordinates.x = positionOffset.x;
|
|
1357
|
+
if (positionOffset.y) labelOffsetCoordinates.y = positionOffset.y;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
var isOffsetAbsolute = ((labelOffsetCoordinates.x !== 0) || (labelOffsetCoordinates.y !== 0) || labelOffset === 0);
|
|
1361
|
+
|
|
1362
|
+
var isKeepGradient = args.keepGradient;
|
|
1363
|
+
var isEnsureLegibility = args.ensureLegibility;
|
|
1364
|
+
|
|
1365
|
+
var path = this.path;
|
|
1366
|
+
var pathOpt = { segmentSubdivisions: this.getConnectionSubdivisions() };
|
|
1367
|
+
|
|
1368
|
+
var distance = isDistanceRelative ? (labelDistance * this.getConnectionLength()) : labelDistance;
|
|
1369
|
+
var tangent = path.tangentAtLength(distance, pathOpt);
|
|
1370
|
+
|
|
1371
|
+
var translation;
|
|
1372
|
+
var angle = labelAngle;
|
|
1373
|
+
if (tangent) {
|
|
1374
|
+
if (isOffsetAbsolute) {
|
|
1375
|
+
translation = tangent.start.clone();
|
|
1376
|
+
translation.offset(labelOffsetCoordinates);
|
|
1377
|
+
} else {
|
|
1378
|
+
var normal = tangent.clone();
|
|
1379
|
+
normal.rotate(tangent.start, -90);
|
|
1380
|
+
normal.setLength(labelOffset);
|
|
1381
|
+
translation = normal.end;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
if (isKeepGradient) {
|
|
1385
|
+
angle = (tangent.angle() + labelAngle);
|
|
1386
|
+
if (isEnsureLegibility) {
|
|
1387
|
+
angle = normalizeAngle(((angle + 90) % 180) - 90);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
} else {
|
|
1392
|
+
// fallback - the connection has zero length
|
|
1393
|
+
translation = path.start.clone();
|
|
1394
|
+
if (isOffsetAbsolute) translation.offset(labelOffsetCoordinates);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
return V.createSVGMatrix()
|
|
1398
|
+
.translate(translation.x, translation.y)
|
|
1399
|
+
.rotate(angle);
|
|
1400
|
+
},
|
|
1401
|
+
|
|
1402
|
+
getLabelCoordinates: function(labelPosition) {
|
|
1403
|
+
|
|
1404
|
+
var transformationMatrix = this._getLabelTransformationMatrix(labelPosition);
|
|
1405
|
+
return new Point(transformationMatrix.e, transformationMatrix.f);
|
|
1406
|
+
},
|
|
1407
|
+
|
|
1408
|
+
getVertexIndex: function(x, y) {
|
|
1409
|
+
|
|
1410
|
+
var model = this.model;
|
|
1411
|
+
var vertices = model.vertices();
|
|
1412
|
+
|
|
1413
|
+
var vertexLength = this.getClosestPointLength(new Point(x, y));
|
|
1414
|
+
|
|
1415
|
+
var idx = 0;
|
|
1416
|
+
for (var n = vertices.length; idx < n; idx++) {
|
|
1417
|
+
var currentVertex = vertices[idx];
|
|
1418
|
+
var currentVertexLength = this.getClosestPointLength(currentVertex);
|
|
1419
|
+
if (vertexLength < currentVertexLength) break;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
return idx;
|
|
1423
|
+
},
|
|
1424
|
+
|
|
1425
|
+
// Interaction. The controller part.
|
|
1426
|
+
// ---------------------------------
|
|
1427
|
+
|
|
1428
|
+
notifyPointerdown(evt, x, y) {
|
|
1429
|
+
CellView.prototype.pointerdown.call(this, evt, x, y);
|
|
1430
|
+
this.notify('link:pointerdown', evt, x, y);
|
|
1431
|
+
},
|
|
1432
|
+
|
|
1433
|
+
notifyPointermove(evt, x, y) {
|
|
1434
|
+
CellView.prototype.pointermove.call(this, evt, x, y);
|
|
1435
|
+
this.notify('link:pointermove', evt, x, y);
|
|
1436
|
+
},
|
|
1437
|
+
|
|
1438
|
+
notifyPointerup(evt, x, y) {
|
|
1439
|
+
this.notify('link:pointerup', evt, x, y);
|
|
1440
|
+
CellView.prototype.pointerup.call(this, evt, x, y);
|
|
1441
|
+
},
|
|
1442
|
+
|
|
1443
|
+
pointerdblclick: function(evt, x, y) {
|
|
1444
|
+
|
|
1445
|
+
CellView.prototype.pointerdblclick.apply(this, arguments);
|
|
1446
|
+
this.notify('link:pointerdblclick', evt, x, y);
|
|
1447
|
+
},
|
|
1448
|
+
|
|
1449
|
+
pointerclick: function(evt, x, y) {
|
|
1450
|
+
|
|
1451
|
+
CellView.prototype.pointerclick.apply(this, arguments);
|
|
1452
|
+
this.notify('link:pointerclick', evt, x, y);
|
|
1453
|
+
},
|
|
1454
|
+
|
|
1455
|
+
contextmenu: function(evt, x, y) {
|
|
1456
|
+
|
|
1457
|
+
CellView.prototype.contextmenu.apply(this, arguments);
|
|
1458
|
+
this.notify('link:contextmenu', evt, x, y);
|
|
1459
|
+
},
|
|
1460
|
+
|
|
1461
|
+
pointerdown: function(evt, x, y) {
|
|
1462
|
+
|
|
1463
|
+
this.notifyPointerdown(evt, x, y);
|
|
1464
|
+
this.dragStart(evt, x, y);
|
|
1465
|
+
},
|
|
1466
|
+
|
|
1467
|
+
pointermove: function(evt, x, y) {
|
|
1468
|
+
|
|
1469
|
+
// Backwards compatibility
|
|
1470
|
+
var dragData = this._dragData;
|
|
1471
|
+
if (dragData) this.eventData(evt, dragData);
|
|
1472
|
+
|
|
1473
|
+
var data = this.eventData(evt);
|
|
1474
|
+
switch (data.action) {
|
|
1475
|
+
|
|
1476
|
+
case 'label-move':
|
|
1477
|
+
this.dragLabel(evt, x, y);
|
|
1478
|
+
break;
|
|
1479
|
+
|
|
1480
|
+
case 'arrowhead-move':
|
|
1481
|
+
this.dragArrowhead(evt, x, y);
|
|
1482
|
+
break;
|
|
1483
|
+
|
|
1484
|
+
case 'move':
|
|
1485
|
+
this.drag(evt, x, y);
|
|
1486
|
+
break;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// Backwards compatibility
|
|
1490
|
+
if (dragData) assign(dragData, this.eventData(evt));
|
|
1491
|
+
|
|
1492
|
+
this.notifyPointermove(evt, x, y);
|
|
1493
|
+
},
|
|
1494
|
+
|
|
1495
|
+
pointerup: function(evt, x, y) {
|
|
1496
|
+
|
|
1497
|
+
// Backwards compatibility
|
|
1498
|
+
var dragData = this._dragData;
|
|
1499
|
+
if (dragData) {
|
|
1500
|
+
this.eventData(evt, dragData);
|
|
1501
|
+
this._dragData = null;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
var data = this.eventData(evt);
|
|
1505
|
+
switch (data.action) {
|
|
1506
|
+
|
|
1507
|
+
case 'label-move':
|
|
1508
|
+
this.dragLabelEnd(evt, x, y);
|
|
1509
|
+
break;
|
|
1510
|
+
|
|
1511
|
+
case 'arrowhead-move':
|
|
1512
|
+
this.dragArrowheadEnd(evt, x, y);
|
|
1513
|
+
break;
|
|
1514
|
+
|
|
1515
|
+
case 'move':
|
|
1516
|
+
this.dragEnd(evt, x, y);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
this.notifyPointerup(evt, x, y);
|
|
1520
|
+
this.checkMouseleave(evt);
|
|
1521
|
+
},
|
|
1522
|
+
|
|
1523
|
+
mouseover: function(evt) {
|
|
1524
|
+
|
|
1525
|
+
CellView.prototype.mouseover.apply(this, arguments);
|
|
1526
|
+
this.notify('link:mouseover', evt);
|
|
1527
|
+
},
|
|
1528
|
+
|
|
1529
|
+
mouseout: function(evt) {
|
|
1530
|
+
|
|
1531
|
+
CellView.prototype.mouseout.apply(this, arguments);
|
|
1532
|
+
this.notify('link:mouseout', evt);
|
|
1533
|
+
},
|
|
1534
|
+
|
|
1535
|
+
mouseenter: function(evt) {
|
|
1536
|
+
|
|
1537
|
+
CellView.prototype.mouseenter.apply(this, arguments);
|
|
1538
|
+
this.notify('link:mouseenter', evt);
|
|
1539
|
+
},
|
|
1540
|
+
|
|
1541
|
+
mouseleave: function(evt) {
|
|
1542
|
+
|
|
1543
|
+
CellView.prototype.mouseleave.apply(this, arguments);
|
|
1544
|
+
this.notify('link:mouseleave', evt);
|
|
1545
|
+
},
|
|
1546
|
+
|
|
1547
|
+
mousewheel: function(evt, x, y, delta) {
|
|
1548
|
+
|
|
1549
|
+
CellView.prototype.mousewheel.apply(this, arguments);
|
|
1550
|
+
this.notify('link:mousewheel', evt, x, y, delta);
|
|
1551
|
+
},
|
|
1552
|
+
|
|
1553
|
+
onlabel: function(evt, x, y) {
|
|
1554
|
+
|
|
1555
|
+
this.notifyPointerdown(evt, x, y);
|
|
1556
|
+
|
|
1557
|
+
this.dragLabelStart(evt, x, y);
|
|
1558
|
+
|
|
1559
|
+
var stopPropagation = this.eventData(evt).stopPropagation;
|
|
1560
|
+
if (stopPropagation) evt.stopPropagation();
|
|
1561
|
+
},
|
|
1562
|
+
|
|
1563
|
+
// Drag Start Handlers
|
|
1564
|
+
|
|
1565
|
+
dragLabelStart: function(evt, x, y) {
|
|
1566
|
+
|
|
1567
|
+
if (this.can('labelMove')) {
|
|
1568
|
+
|
|
1569
|
+
if (this.isDefaultInteractionPrevented(evt)) return;
|
|
1570
|
+
|
|
1571
|
+
var labelNode = evt.currentTarget;
|
|
1572
|
+
var labelIdx = parseInt(labelNode.getAttribute('label-idx'), 10);
|
|
1573
|
+
|
|
1574
|
+
var defaultLabelPosition = this._getDefaultLabelPositionProperty();
|
|
1575
|
+
var initialLabelPosition = this._normalizeLabelPosition(this._getLabelPositionProperty(labelIdx));
|
|
1576
|
+
var position = this._mergeLabelPositionProperty(initialLabelPosition, defaultLabelPosition);
|
|
1577
|
+
|
|
1578
|
+
var coords = this.getLabelCoordinates(position);
|
|
1579
|
+
var dx = coords.x - x; // how much needs to be added to cursor x to get to label x
|
|
1580
|
+
var dy = coords.y - y; // how much needs to be added to cursor y to get to label y
|
|
1581
|
+
|
|
1582
|
+
var positionAngle = this._getLabelPositionAngle(labelIdx);
|
|
1583
|
+
var labelPositionArgs = this._getLabelPositionArgs(labelIdx);
|
|
1584
|
+
var defaultLabelPositionArgs = this._getDefaultLabelPositionArgs();
|
|
1585
|
+
var positionArgs = this._mergeLabelPositionArgs(labelPositionArgs, defaultLabelPositionArgs);
|
|
1586
|
+
|
|
1587
|
+
this.eventData(evt, {
|
|
1588
|
+
action: 'label-move',
|
|
1589
|
+
labelIdx: labelIdx,
|
|
1590
|
+
dx: dx,
|
|
1591
|
+
dy: dy,
|
|
1592
|
+
positionAngle: positionAngle,
|
|
1593
|
+
positionArgs: positionArgs,
|
|
1594
|
+
stopPropagation: true
|
|
1595
|
+
});
|
|
1596
|
+
|
|
1597
|
+
} else {
|
|
1598
|
+
|
|
1599
|
+
// Backwards compatibility:
|
|
1600
|
+
// If labels can't be dragged no default action is triggered.
|
|
1601
|
+
this.eventData(evt, { stopPropagation: true });
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
this.paper.delegateDragEvents(this, evt.data);
|
|
1605
|
+
},
|
|
1606
|
+
|
|
1607
|
+
dragArrowheadStart: function(evt, x, y) {
|
|
1608
|
+
|
|
1609
|
+
if (!this.can('arrowheadMove')) return;
|
|
1610
|
+
|
|
1611
|
+
var arrowheadNode = evt.target;
|
|
1612
|
+
var arrowheadType = arrowheadNode.getAttribute('end');
|
|
1613
|
+
var data = this.startArrowheadMove(arrowheadType, { ignoreBackwardsCompatibility: true });
|
|
1614
|
+
|
|
1615
|
+
this.eventData(evt, data);
|
|
1616
|
+
},
|
|
1617
|
+
|
|
1618
|
+
dragStart: function(evt, x, y) {
|
|
1619
|
+
|
|
1620
|
+
if (this.isDefaultInteractionPrevented(evt)) return;
|
|
1621
|
+
|
|
1622
|
+
if (!this.can('linkMove')) return;
|
|
1623
|
+
|
|
1624
|
+
this.eventData(evt, {
|
|
1625
|
+
action: 'move',
|
|
1626
|
+
dx: x,
|
|
1627
|
+
dy: y
|
|
1628
|
+
});
|
|
1629
|
+
},
|
|
1630
|
+
|
|
1631
|
+
// Drag Handlers
|
|
1632
|
+
dragLabel: function(evt, x, y) {
|
|
1633
|
+
|
|
1634
|
+
var data = this.eventData(evt);
|
|
1635
|
+
var label = { position: this.getLabelPosition((x + data.dx), (y + data.dy), data.positionAngle, data.positionArgs) };
|
|
1636
|
+
if (this.paper.options.snapLabels) delete label.position.offset;
|
|
1637
|
+
// The `touchmove' events are not fired
|
|
1638
|
+
// when the original event target is removed from the DOM.
|
|
1639
|
+
// The labels are currently re-rendered completely when only
|
|
1640
|
+
// the position changes. This is why we need to make sure that
|
|
1641
|
+
// the label is updated synchronously.
|
|
1642
|
+
// TODO: replace `touchmove` with `pointermove` (breaking change).
|
|
1643
|
+
const setOptions = { ui: true };
|
|
1644
|
+
if (this.paper.isAsync() && evt.type === 'touchmove') {
|
|
1645
|
+
setOptions.async = false;
|
|
1646
|
+
}
|
|
1647
|
+
this.model.label(data.labelIdx, label, setOptions);
|
|
1648
|
+
},
|
|
1649
|
+
|
|
1650
|
+
dragArrowhead: function(evt, x, y) {
|
|
1651
|
+
if (this.paper.options.snapLinks) {
|
|
1652
|
+
const isSnapped = this._snapArrowhead(evt, x, y);
|
|
1653
|
+
if (!isSnapped && this.paper.options.snapLinksSelf) {
|
|
1654
|
+
this._snapArrowheadSelf(evt, x, y);
|
|
1655
|
+
}
|
|
1656
|
+
} else {
|
|
1657
|
+
if (this.paper.options.snapLinksSelf) {
|
|
1658
|
+
this._snapArrowheadSelf(evt, x, y);
|
|
1659
|
+
} else {
|
|
1660
|
+
this._connectArrowhead(this.getEventTarget(evt), x, y, this.eventData(evt));
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
},
|
|
1664
|
+
|
|
1665
|
+
drag: function(evt, x, y) {
|
|
1666
|
+
|
|
1667
|
+
var data = this.eventData(evt);
|
|
1668
|
+
this.model.translate(x - data.dx, y - data.dy, { ui: true });
|
|
1669
|
+
this.eventData(evt, {
|
|
1670
|
+
dx: x,
|
|
1671
|
+
dy: y
|
|
1672
|
+
});
|
|
1673
|
+
},
|
|
1674
|
+
|
|
1675
|
+
// Drag End Handlers
|
|
1676
|
+
|
|
1677
|
+
dragLabelEnd: function() {
|
|
1678
|
+
// noop
|
|
1679
|
+
},
|
|
1680
|
+
|
|
1681
|
+
dragArrowheadEnd: function(evt, x, y) {
|
|
1682
|
+
|
|
1683
|
+
var data = this.eventData(evt);
|
|
1684
|
+
var paper = this.paper;
|
|
1685
|
+
|
|
1686
|
+
if (paper.options.snapLinks) {
|
|
1687
|
+
this._snapArrowheadEnd(data);
|
|
1688
|
+
} else {
|
|
1689
|
+
this._connectArrowheadEnd(data, x, y);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
if (!paper.linkAllowed(this)) {
|
|
1693
|
+
// If the changed link is not allowed, revert to its previous state.
|
|
1694
|
+
this._disallow(data);
|
|
1695
|
+
} else {
|
|
1696
|
+
this._finishEmbedding(data);
|
|
1697
|
+
this._notifyConnectEvent(data, evt);
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
this._afterArrowheadMove(data);
|
|
1701
|
+
},
|
|
1702
|
+
|
|
1703
|
+
dragEnd: function() {
|
|
1704
|
+
// noop
|
|
1705
|
+
},
|
|
1706
|
+
|
|
1707
|
+
_disallow: function(data) {
|
|
1708
|
+
|
|
1709
|
+
switch (data.whenNotAllowed) {
|
|
1710
|
+
|
|
1711
|
+
case 'remove':
|
|
1712
|
+
this.model.remove({ ui: true });
|
|
1713
|
+
break;
|
|
1714
|
+
|
|
1715
|
+
case 'revert':
|
|
1716
|
+
default:
|
|
1717
|
+
this.model.set(data.arrowhead, data.initialEnd, { ui: true });
|
|
1718
|
+
break;
|
|
1719
|
+
}
|
|
1720
|
+
},
|
|
1721
|
+
|
|
1722
|
+
_finishEmbedding: function(data) {
|
|
1723
|
+
|
|
1724
|
+
// Reparent the link if embedding is enabled
|
|
1725
|
+
if (this.paper.options.embeddingMode && this.model.reparent()) {
|
|
1726
|
+
// Make sure we don't reverse to the original 'z' index (see afterArrowheadMove()).
|
|
1727
|
+
data.z = null;
|
|
1728
|
+
}
|
|
1729
|
+
},
|
|
1730
|
+
|
|
1731
|
+
_notifyConnectEvent: function(data, evt) {
|
|
1732
|
+
|
|
1733
|
+
var arrowhead = data.arrowhead;
|
|
1734
|
+
var initialEnd = data.initialEnd;
|
|
1735
|
+
var currentEnd = this.model.prop(arrowhead);
|
|
1736
|
+
var endChanged = currentEnd && !Link.endsEqual(initialEnd, currentEnd);
|
|
1737
|
+
if (endChanged) {
|
|
1738
|
+
var paper = this.paper;
|
|
1739
|
+
if (initialEnd.id) {
|
|
1740
|
+
this.notify('link:disconnect', evt, paper.findViewByModel(initialEnd.id), data.initialMagnet, arrowhead);
|
|
1741
|
+
}
|
|
1742
|
+
if (currentEnd.id) {
|
|
1743
|
+
this.notify('link:connect', evt, paper.findViewByModel(currentEnd.id), data.magnetUnderPointer, arrowhead);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
},
|
|
1747
|
+
|
|
1748
|
+
_snapToPoints: function(snapPoint, points, radius) {
|
|
1749
|
+
let closestPointX = null;
|
|
1750
|
+
let closestDistanceX = Infinity;
|
|
1751
|
+
|
|
1752
|
+
let closestPointY = null;
|
|
1753
|
+
let closestDistanceY = Infinity;
|
|
1754
|
+
|
|
1755
|
+
let x = snapPoint.x;
|
|
1756
|
+
let y = snapPoint.y;
|
|
1757
|
+
|
|
1758
|
+
for (let i = 0; i < points.length; i++) {
|
|
1759
|
+
const distX = Math.abs(points[i].x - snapPoint.x);
|
|
1760
|
+
if (distX < closestDistanceX) {
|
|
1761
|
+
closestDistanceX = distX;
|
|
1762
|
+
closestPointX = points[i];
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
const distY = Math.abs(points[i].y - snapPoint.y);
|
|
1766
|
+
if (distY < closestDistanceY) {
|
|
1767
|
+
closestDistanceY = distY;
|
|
1768
|
+
closestPointY = points[i];
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
if (closestDistanceX < radius) {
|
|
1773
|
+
x = closestPointX.x;
|
|
1774
|
+
}
|
|
1775
|
+
if (closestDistanceY < radius) {
|
|
1776
|
+
y = closestPointY.y;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
return { x, y };
|
|
1780
|
+
},
|
|
1781
|
+
|
|
1782
|
+
_snapArrowheadSelf: function(evt, x, y) {
|
|
1783
|
+
|
|
1784
|
+
const { paper, model } = this;
|
|
1785
|
+
const { snapLinksSelf } = paper.options;
|
|
1786
|
+
const data = this.eventData(evt);
|
|
1787
|
+
const radius = snapLinksSelf.radius || 20;
|
|
1788
|
+
|
|
1789
|
+
const anchor = this.getEndAnchor(data.arrowhead === 'source' ? 'target' : 'source');
|
|
1790
|
+
const vertices = model.vertices();
|
|
1791
|
+
const points = [anchor, ...vertices];
|
|
1792
|
+
|
|
1793
|
+
const snapPoint = this._snapToPoints({ x: x, y: y }, points, radius);
|
|
1794
|
+
|
|
1795
|
+
const point = paper.localToClientPoint(snapPoint);
|
|
1796
|
+
this._connectArrowhead(document.elementFromPoint(point.x, point.y), snapPoint.x, snapPoint.y, this.eventData(evt));
|
|
1797
|
+
},
|
|
1798
|
+
|
|
1799
|
+
_snapArrowhead: function(evt, x, y) {
|
|
1800
|
+
|
|
1801
|
+
const { paper } = this;
|
|
1802
|
+
const { snapLinks, connectionStrategy } = paper.options;
|
|
1803
|
+
const data = this.eventData(evt);
|
|
1804
|
+
let isSnapped = false;
|
|
1805
|
+
// checking view in close area of the pointer
|
|
1806
|
+
|
|
1807
|
+
var r = snapLinks.radius || 50;
|
|
1808
|
+
var viewsInArea = paper.findViewsInArea({ x: x - r, y: y - r, width: 2 * r, height: 2 * r });
|
|
1809
|
+
|
|
1810
|
+
var prevClosestView = data.closestView || null;
|
|
1811
|
+
var prevClosestMagnet = data.closestMagnet || null;
|
|
1812
|
+
var prevMagnetProxy = data.magnetProxy || null;
|
|
1813
|
+
|
|
1814
|
+
data.closestView = data.closestMagnet = data.magnetProxy = null;
|
|
1815
|
+
|
|
1816
|
+
var minDistance = Number.MAX_VALUE;
|
|
1817
|
+
var pointer = new Point(x, y);
|
|
1818
|
+
|
|
1819
|
+
viewsInArea.forEach(function(view) {
|
|
1820
|
+
const candidates = [];
|
|
1821
|
+
// skip connecting to the element in case '.': { magnet: false } attribute present
|
|
1822
|
+
if (view.el.getAttribute('magnet') !== 'false') {
|
|
1823
|
+
candidates.push({
|
|
1824
|
+
bbox: view.model.getBBox(),
|
|
1825
|
+
magnet: view.el
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
view.$('[magnet]').toArray().forEach(magnet => {
|
|
1830
|
+
candidates.push({
|
|
1831
|
+
bbox: view.getNodeBBox(magnet),
|
|
1832
|
+
magnet
|
|
1833
|
+
});
|
|
1834
|
+
});
|
|
1835
|
+
|
|
1836
|
+
candidates.forEach(candidate => {
|
|
1837
|
+
const { magnet, bbox } = candidate;
|
|
1838
|
+
// find distance from the center of the model to pointer coordinates
|
|
1839
|
+
const distance = bbox.center().squaredDistance(pointer);
|
|
1840
|
+
// the connection is looked up in a circle area by `distance < r`
|
|
1841
|
+
if (distance < minDistance) {
|
|
1842
|
+
const isAlreadyValidated = prevClosestMagnet === magnet;
|
|
1843
|
+
if (isAlreadyValidated || paper.options.validateConnection.apply(
|
|
1844
|
+
paper, data.validateConnectionArgs(view, (view.el === magnet) ? null : magnet)
|
|
1845
|
+
)) {
|
|
1846
|
+
minDistance = distance;
|
|
1847
|
+
data.closestView = view;
|
|
1848
|
+
data.closestMagnet = magnet;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
});
|
|
1852
|
+
|
|
1853
|
+
}, this);
|
|
1854
|
+
|
|
1855
|
+
var end;
|
|
1856
|
+
var magnetProxy = null;
|
|
1857
|
+
var closestView = data.closestView;
|
|
1858
|
+
var closestMagnet = data.closestMagnet;
|
|
1859
|
+
if (closestMagnet) {
|
|
1860
|
+
magnetProxy = data.magnetProxy = closestView.findProxyNode(closestMagnet, 'highlighter');
|
|
1861
|
+
}
|
|
1862
|
+
var endType = data.arrowhead;
|
|
1863
|
+
var newClosestMagnet = (prevClosestMagnet !== closestMagnet);
|
|
1864
|
+
if (prevClosestView && newClosestMagnet) {
|
|
1865
|
+
prevClosestView.unhighlight(prevMagnetProxy, {
|
|
1866
|
+
connecting: true,
|
|
1867
|
+
snapping: true
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
if (closestView) {
|
|
1872
|
+
const { prevEnd, prevX, prevY } = data;
|
|
1873
|
+
data.prevX = x;
|
|
1874
|
+
data.prevY = y;
|
|
1875
|
+
isSnapped = true;
|
|
1876
|
+
|
|
1877
|
+
if (!newClosestMagnet) {
|
|
1878
|
+
if (typeof connectionStrategy !== 'function' || (prevX === x && prevY === y)) {
|
|
1879
|
+
// the magnet has not changed and the link's end does not depend on the x and y
|
|
1880
|
+
return isSnapped;
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
end = closestView.getLinkEnd(closestMagnet, x, y, this.model, endType);
|
|
1885
|
+
if (!newClosestMagnet && isEqual(prevEnd, end)) {
|
|
1886
|
+
// the source/target json has not changed
|
|
1887
|
+
return isSnapped;
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
data.prevEnd = end;
|
|
1891
|
+
|
|
1892
|
+
if (newClosestMagnet) {
|
|
1893
|
+
closestView.highlight(magnetProxy, {
|
|
1894
|
+
connecting: true,
|
|
1895
|
+
snapping: true
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
} else {
|
|
1900
|
+
|
|
1901
|
+
end = { x: x, y: y };
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
this.model.set(endType, end || { x: x, y: y }, { ui: true });
|
|
1905
|
+
|
|
1906
|
+
if (prevClosestView) {
|
|
1907
|
+
this.notify('link:snap:disconnect', evt, prevClosestView, prevClosestMagnet, endType);
|
|
1908
|
+
}
|
|
1909
|
+
if (closestView) {
|
|
1910
|
+
this.notify('link:snap:connect', evt, closestView, closestMagnet, endType);
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
return isSnapped;
|
|
1914
|
+
},
|
|
1915
|
+
|
|
1916
|
+
_snapArrowheadEnd: function(data) {
|
|
1917
|
+
|
|
1918
|
+
// Finish off link snapping.
|
|
1919
|
+
// Everything except view unhighlighting was already done on pointermove.
|
|
1920
|
+
var closestView = data.closestView;
|
|
1921
|
+
var closestMagnet = data.closestMagnet;
|
|
1922
|
+
if (closestView && closestMagnet) {
|
|
1923
|
+
|
|
1924
|
+
closestView.unhighlight(data.magnetProxy, { connecting: true, snapping: true });
|
|
1925
|
+
data.magnetUnderPointer = closestView.findMagnet(closestMagnet);
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
data.closestView = data.closestMagnet = null;
|
|
1929
|
+
},
|
|
1930
|
+
|
|
1931
|
+
_connectArrowhead: function(target, x, y, data) {
|
|
1932
|
+
|
|
1933
|
+
// checking views right under the pointer
|
|
1934
|
+
const { paper, model } = this;
|
|
1935
|
+
|
|
1936
|
+
if (data.eventTarget !== target) {
|
|
1937
|
+
// Unhighlight the previous view under pointer if there was one.
|
|
1938
|
+
if (data.magnetProxy) {
|
|
1939
|
+
data.viewUnderPointer.unhighlight(data.magnetProxy, {
|
|
1940
|
+
connecting: true
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
const viewUnderPointer = data.viewUnderPointer = paper.findView(target);
|
|
1945
|
+
if (viewUnderPointer) {
|
|
1946
|
+
// If we found a view that is under the pointer, we need to find the closest
|
|
1947
|
+
// magnet based on the real target element of the event.
|
|
1948
|
+
const magnetUnderPointer = data.magnetUnderPointer = viewUnderPointer.findMagnet(target);
|
|
1949
|
+
const magnetProxy = data.magnetProxy = viewUnderPointer.findProxyNode(magnetUnderPointer, 'highlighter');
|
|
1950
|
+
|
|
1951
|
+
if (magnetUnderPointer && this.paper.options.validateConnection.apply(
|
|
1952
|
+
paper,
|
|
1953
|
+
data.validateConnectionArgs(viewUnderPointer, magnetUnderPointer)
|
|
1954
|
+
)) {
|
|
1955
|
+
// If there was no magnet found, do not highlight anything and assume there
|
|
1956
|
+
// is no view under pointer we're interested in reconnecting to.
|
|
1957
|
+
// This can only happen if the overall element has the attribute `'.': { magnet: false }`.
|
|
1958
|
+
if (magnetProxy) {
|
|
1959
|
+
viewUnderPointer.highlight(magnetProxy, {
|
|
1960
|
+
connecting: true
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
} else {
|
|
1964
|
+
// This type of connection is not valid. Disregard this magnet.
|
|
1965
|
+
data.magnetUnderPointer = null;
|
|
1966
|
+
data.magnetProxy = null;
|
|
1967
|
+
}
|
|
1968
|
+
} else {
|
|
1969
|
+
// Make sure we'll unset previous magnet.
|
|
1970
|
+
data.magnetUnderPointer = null;
|
|
1971
|
+
data.magnetProxy = null;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
data.eventTarget = target;
|
|
1976
|
+
|
|
1977
|
+
model.set(data.arrowhead, { x: x, y: y }, { ui: true });
|
|
1978
|
+
},
|
|
1979
|
+
|
|
1980
|
+
_connectArrowheadEnd: function(data = {}, x, y) {
|
|
1981
|
+
|
|
1982
|
+
const { model } = this;
|
|
1983
|
+
const { viewUnderPointer, magnetUnderPointer, magnetProxy, arrowhead } = data;
|
|
1984
|
+
|
|
1985
|
+
if (!magnetUnderPointer || !magnetProxy || !viewUnderPointer) return;
|
|
1986
|
+
|
|
1987
|
+
viewUnderPointer.unhighlight(magnetProxy, { connecting: true });
|
|
1988
|
+
|
|
1989
|
+
// The link end is taken from the magnet under the pointer, not the proxy.
|
|
1990
|
+
const end = viewUnderPointer.getLinkEnd(magnetUnderPointer, x, y, model, arrowhead);
|
|
1991
|
+
model.set(arrowhead, end, { ui: true });
|
|
1992
|
+
},
|
|
1993
|
+
|
|
1994
|
+
_beforeArrowheadMove: function(data) {
|
|
1995
|
+
|
|
1996
|
+
data.z = this.model.get('z');
|
|
1997
|
+
this.model.toFront();
|
|
1998
|
+
|
|
1999
|
+
// Let the pointer propagate through the link view elements so that
|
|
2000
|
+
// the `evt.target` is another element under the pointer, not the link itself.
|
|
2001
|
+
var style = this.el.style;
|
|
2002
|
+
data.pointerEvents = style.pointerEvents;
|
|
2003
|
+
style.pointerEvents = 'none';
|
|
2004
|
+
|
|
2005
|
+
if (this.paper.options.markAvailable) {
|
|
2006
|
+
this._markAvailableMagnets(data);
|
|
2007
|
+
}
|
|
2008
|
+
},
|
|
2009
|
+
|
|
2010
|
+
_afterArrowheadMove: function(data) {
|
|
2011
|
+
|
|
2012
|
+
if (data.z !== null) {
|
|
2013
|
+
this.model.set('z', data.z, { ui: true });
|
|
2014
|
+
data.z = null;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
// Put `pointer-events` back to its original value. See `_beforeArrowheadMove()` for explanation.
|
|
2018
|
+
this.el.style.pointerEvents = data.pointerEvents;
|
|
2019
|
+
|
|
2020
|
+
if (this.paper.options.markAvailable) {
|
|
2021
|
+
this._unmarkAvailableMagnets(data);
|
|
2022
|
+
}
|
|
2023
|
+
},
|
|
2024
|
+
|
|
2025
|
+
_createValidateConnectionArgs: function(arrowhead) {
|
|
2026
|
+
// It makes sure the arguments for validateConnection have the following form:
|
|
2027
|
+
// (source view, source magnet, target view, target magnet and link view)
|
|
2028
|
+
var args = [];
|
|
2029
|
+
|
|
2030
|
+
args[4] = arrowhead;
|
|
2031
|
+
args[5] = this;
|
|
2032
|
+
|
|
2033
|
+
var oppositeArrowhead;
|
|
2034
|
+
var i = 0;
|
|
2035
|
+
var j = 0;
|
|
2036
|
+
|
|
2037
|
+
if (arrowhead === 'source') {
|
|
2038
|
+
i = 2;
|
|
2039
|
+
oppositeArrowhead = 'target';
|
|
2040
|
+
} else {
|
|
2041
|
+
j = 2;
|
|
2042
|
+
oppositeArrowhead = 'source';
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
var end = this.model.get(oppositeArrowhead);
|
|
2046
|
+
|
|
2047
|
+
if (end.id) {
|
|
2048
|
+
var view = args[i] = this.paper.findViewByModel(end.id);
|
|
2049
|
+
var magnet = view.getMagnetFromLinkEnd(end);
|
|
2050
|
+
if (magnet === view.el) magnet = undefined;
|
|
2051
|
+
args[i + 1] = magnet;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
function validateConnectionArgs(cellView, magnet) {
|
|
2055
|
+
args[j] = cellView;
|
|
2056
|
+
args[j + 1] = cellView.el === magnet ? undefined : magnet;
|
|
2057
|
+
return args;
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
return validateConnectionArgs;
|
|
2061
|
+
},
|
|
2062
|
+
|
|
2063
|
+
_markAvailableMagnets: function(data) {
|
|
2064
|
+
|
|
2065
|
+
function isMagnetAvailable(view, magnet) {
|
|
2066
|
+
var paper = view.paper;
|
|
2067
|
+
var validate = paper.options.validateConnection;
|
|
2068
|
+
return validate.apply(paper, this.validateConnectionArgs(view, magnet));
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
var paper = this.paper;
|
|
2072
|
+
var elements = paper.model.getCells();
|
|
2073
|
+
data.marked = {};
|
|
2074
|
+
|
|
2075
|
+
for (var i = 0, n = elements.length; i < n; i++) {
|
|
2076
|
+
var view = elements[i].findView(paper);
|
|
2077
|
+
|
|
2078
|
+
if (!view) {
|
|
2079
|
+
continue;
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
var magnets = Array.prototype.slice.call(view.el.querySelectorAll('[magnet]'));
|
|
2083
|
+
if (view.el.getAttribute('magnet') !== 'false') {
|
|
2084
|
+
// Element wrapping group is also a magnet
|
|
2085
|
+
magnets.push(view.el);
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
var availableMagnets = magnets.filter(isMagnetAvailable.bind(data, view));
|
|
2089
|
+
|
|
2090
|
+
if (availableMagnets.length > 0) {
|
|
2091
|
+
// highlight all available magnets
|
|
2092
|
+
for (var j = 0, m = availableMagnets.length; j < m; j++) {
|
|
2093
|
+
view.highlight(availableMagnets[j], { magnetAvailability: true });
|
|
2094
|
+
}
|
|
2095
|
+
// highlight the entire view
|
|
2096
|
+
view.highlight(null, { elementAvailability: true });
|
|
2097
|
+
|
|
2098
|
+
data.marked[view.model.id] = availableMagnets;
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
},
|
|
2102
|
+
|
|
2103
|
+
_unmarkAvailableMagnets: function(data) {
|
|
2104
|
+
|
|
2105
|
+
var markedKeys = Object.keys(data.marked);
|
|
2106
|
+
var id;
|
|
2107
|
+
var markedMagnets;
|
|
2108
|
+
|
|
2109
|
+
for (var i = 0, n = markedKeys.length; i < n; i++) {
|
|
2110
|
+
id = markedKeys[i];
|
|
2111
|
+
markedMagnets = data.marked[id];
|
|
2112
|
+
|
|
2113
|
+
var view = this.paper.findViewByModel(id);
|
|
2114
|
+
if (view) {
|
|
2115
|
+
for (var j = 0, m = markedMagnets.length; j < m; j++) {
|
|
2116
|
+
view.unhighlight(markedMagnets[j], { magnetAvailability: true });
|
|
2117
|
+
}
|
|
2118
|
+
view.unhighlight(null, { elementAvailability: true });
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
data.marked = null;
|
|
2123
|
+
},
|
|
2124
|
+
|
|
2125
|
+
startArrowheadMove: function(end, opt) {
|
|
2126
|
+
|
|
2127
|
+
opt || (opt = {});
|
|
2128
|
+
|
|
2129
|
+
// Allow to delegate events from an another view to this linkView in order to trigger arrowhead
|
|
2130
|
+
// move without need to click on the actual arrowhead dom element.
|
|
2131
|
+
var data = {
|
|
2132
|
+
action: 'arrowhead-move',
|
|
2133
|
+
arrowhead: end,
|
|
2134
|
+
whenNotAllowed: opt.whenNotAllowed || 'revert',
|
|
2135
|
+
initialMagnet: this[end + 'Magnet'] || (this[end + 'View'] ? this[end + 'View'].el : null),
|
|
2136
|
+
initialEnd: clone(this.model.get(end)),
|
|
2137
|
+
validateConnectionArgs: this._createValidateConnectionArgs(end)
|
|
2138
|
+
};
|
|
2139
|
+
|
|
2140
|
+
this._beforeArrowheadMove(data);
|
|
2141
|
+
|
|
2142
|
+
if (opt.ignoreBackwardsCompatibility !== true) {
|
|
2143
|
+
this._dragData = data;
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
return data;
|
|
2147
|
+
},
|
|
2148
|
+
|
|
2149
|
+
// Lifecycle methods
|
|
2150
|
+
|
|
2151
|
+
onMount: function() {
|
|
2152
|
+
CellView.prototype.onMount.apply(this, arguments);
|
|
2153
|
+
this.mountLabels();
|
|
2154
|
+
},
|
|
2155
|
+
|
|
2156
|
+
onDetach: function() {
|
|
2157
|
+
CellView.prototype.onDetach.apply(this, arguments);
|
|
2158
|
+
this.unmountLabels();
|
|
2159
|
+
},
|
|
2160
|
+
|
|
2161
|
+
onRemove: function() {
|
|
2162
|
+
CellView.prototype.onRemove.apply(this, arguments);
|
|
2163
|
+
this.unmountLabels();
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
}, {
|
|
2167
|
+
|
|
2168
|
+
Flags: Flags,
|
|
2169
|
+
});
|
|
2170
|
+
|
|
2171
|
+
Object.defineProperty(LinkView.prototype, 'sourceBBox', {
|
|
2172
|
+
|
|
2173
|
+
enumerable: true,
|
|
2174
|
+
|
|
2175
|
+
get: function() {
|
|
2176
|
+
var sourceView = this.sourceView;
|
|
2177
|
+
if (!sourceView) {
|
|
2178
|
+
var sourceDef = this.model.source();
|
|
2179
|
+
return new Rect(sourceDef.x, sourceDef.y);
|
|
2180
|
+
}
|
|
2181
|
+
var sourceMagnet = this.sourceMagnet;
|
|
2182
|
+
if (sourceView.isNodeConnection(sourceMagnet)) {
|
|
2183
|
+
return new Rect(this.sourceAnchor);
|
|
2184
|
+
}
|
|
2185
|
+
return sourceView.getNodeBBox(sourceMagnet || sourceView.el);
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
});
|
|
2189
|
+
|
|
2190
|
+
Object.defineProperty(LinkView.prototype, 'targetBBox', {
|
|
2191
|
+
|
|
2192
|
+
enumerable: true,
|
|
2193
|
+
|
|
2194
|
+
get: function() {
|
|
2195
|
+
var targetView = this.targetView;
|
|
2196
|
+
if (!targetView) {
|
|
2197
|
+
var targetDef = this.model.target();
|
|
2198
|
+
return new Rect(targetDef.x, targetDef.y);
|
|
2199
|
+
}
|
|
2200
|
+
var targetMagnet = this.targetMagnet;
|
|
2201
|
+
if (targetView.isNodeConnection(targetMagnet)) {
|
|
2202
|
+
return new Rect(this.targetAnchor);
|
|
2203
|
+
}
|
|
2204
|
+
return targetView.getNodeBBox(targetMagnet || targetView.el);
|
|
2205
|
+
}
|
|
2206
|
+
});
|
|
2207
|
+
|