@sapui5/sap.suite.ui.commons 1.150.0 → 1.152.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/package.json +1 -1
- package/src/sap/suite/ui/commons/.library +1 -1
- package/src/sap/suite/ui/commons/AriaProperties.js +1 -1
- package/src/sap/suite/ui/commons/CalculationBuilder.js +1 -1
- package/src/sap/suite/ui/commons/CalculationBuilderExpression.js +1 -1
- package/src/sap/suite/ui/commons/CalculationBuilderFunction.js +1 -1
- package/src/sap/suite/ui/commons/CalculationBuilderGroup.js +1 -1
- package/src/sap/suite/ui/commons/CalculationBuilderItem.js +1 -1
- package/src/sap/suite/ui/commons/CalculationBuilderValidationResult.js +1 -1
- package/src/sap/suite/ui/commons/CalculationBuilderVariable.js +1 -1
- package/src/sap/suite/ui/commons/CloudFilePicker.js +1 -1
- package/src/sap/suite/ui/commons/MicroProcessFlow.js +1 -1
- package/src/sap/suite/ui/commons/MicroProcessFlowItem.js +1 -1
- package/src/sap/suite/ui/commons/TimelineItem.js +2 -3
- package/src/sap/suite/ui/commons/TimelineNavigator.js +12 -9
- package/src/sap/suite/ui/commons/collaboration/UniversalCollaborationBar.js +475 -360
- package/src/sap/suite/ui/commons/flexibility/changeHandler/PropertyChangeMapper.js +1 -1
- package/src/sap/suite/ui/commons/imageeditor/CropCustomShapeHistoryItem.js +1 -1
- package/src/sap/suite/ui/commons/imageeditor/CropEllipseHistoryItem.js +1 -1
- package/src/sap/suite/ui/commons/imageeditor/CropRectangleHistoryItem.js +1 -1
- package/src/sap/suite/ui/commons/imageeditor/CustomSizeItem.js +1 -1
- package/src/sap/suite/ui/commons/imageeditor/FilterHistoryItem.js +1 -1
- package/src/sap/suite/ui/commons/imageeditor/FlipHistoryItem.js +1 -1
- package/src/sap/suite/ui/commons/imageeditor/HistoryItem.js +1 -1
- package/src/sap/suite/ui/commons/imageeditor/ImageEditor.js +1 -1
- package/src/sap/suite/ui/commons/imageeditor/ImageEditorContainer.js +1 -1
- package/src/sap/suite/ui/commons/imageeditor/ImageEditorResponsiveContainer.js +1 -1
- package/src/sap/suite/ui/commons/imageeditor/ResizeHistoryItem.js +1 -1
- package/src/sap/suite/ui/commons/imageeditor/RotateHistoryItem.js +1 -1
- package/src/sap/suite/ui/commons/library.js +1 -1
- package/src/sap/suite/ui/commons/messagebundle_de.properties +2 -2
- package/src/sap/suite/ui/commons/messagebundle_en_US_saptrc.properties +616 -537
- package/src/sap/suite/ui/commons/messagebundle_mk.properties +1 -1
- package/src/sap/suite/ui/commons/messagebundle_pl.properties +1 -1
- package/src/sap/suite/ui/commons/networkgraph/ElementBase.js +7 -2
- package/src/sap/suite/ui/commons/networkgraph/Graph.js +19 -7
- package/src/sap/suite/ui/commons/networkgraph/GraphRenderer.js +1 -1
- package/src/sap/suite/ui/commons/networkgraph/Group.js +1 -1
- package/src/sap/suite/ui/commons/networkgraph/KeyboardNavigator.js +2 -0
- package/src/sap/suite/ui/commons/networkgraph/Line.js +24 -0
- package/src/sap/suite/ui/commons/networkgraph/Node.js +1 -1
- package/src/sap/suite/ui/commons/networkgraph/Tooltip.js +3 -1
- package/src/sap/suite/ui/commons/networkgraph/util/ConnectionPathUtils.js +323 -129
- package/src/sap/suite/ui/commons/networkgraph/util/DependencyLayoutHelper.js +21 -6
- package/src/sap/suite/ui/commons/statusindicator/Circle.js +1 -1
- package/src/sap/suite/ui/commons/statusindicator/CustomShape.js +1 -1
- package/src/sap/suite/ui/commons/statusindicator/DiscreteThreshold.js +1 -1
- package/src/sap/suite/ui/commons/statusindicator/FillingOption.js +1 -1
- package/src/sap/suite/ui/commons/statusindicator/LibraryShape.js +1 -1
- package/src/sap/suite/ui/commons/statusindicator/Path.js +1 -1
- package/src/sap/suite/ui/commons/statusindicator/PropertyThreshold.js +1 -1
- package/src/sap/suite/ui/commons/statusindicator/Rectangle.js +1 -1
- package/src/sap/suite/ui/commons/statusindicator/Shape.js +1 -1
- package/src/sap/suite/ui/commons/statusindicator/ShapeGroup.js +1 -1
- package/src/sap/suite/ui/commons/statusindicator/SimpleShape.js +1 -1
- package/src/sap/suite/ui/commons/statusindicator/StatusIndicator.js +1 -1
- package/src/sap/suite/ui/commons/taccount/TAccount.js +1 -1
- package/src/sap/suite/ui/commons/taccount/TAccountGroup.js +1 -1
- package/src/sap/suite/ui/commons/taccount/TAccountItem.js +1 -1
- package/src/sap/suite/ui/commons/taccount/TAccountItemProperty.js +1 -1
- package/src/sap/suite/ui/commons/taccount/TAccountPanel.js +1 -1
- package/src/sap/suite/ui/commons/themes/base/MicroProcessFlow.less +2 -1
- package/src/sap/suite/ui/commons/themes/base/NetworkGraph.less +23 -6
- package/src/sap/suite/ui/commons/themes/base/Timeline.less +2 -2
- package/src/sap/suite/ui/commons/themes/sap_belize_base/NetworkGraph.less +23 -6
- package/src/sap/suite/ui/commons/themes/sap_belize_base/Timeline.less +2 -2
- package/src/sap/suite/ui/commons/themes/sap_belize_hcb/NetworkGraph.less +23 -2
- package/src/sap/suite/ui/commons/themes/sap_belize_hcw/NetworkGraph.less +23 -2
- package/src/sap/suite/ui/commons/themes/sap_hcb/base_NetworkGraph.less +23 -6
|
@@ -67,46 +67,89 @@ sap.ui.define([
|
|
|
67
67
|
|
|
68
68
|
// Detect RTL mode from graph
|
|
69
69
|
const bIsRTL = oGraph._bIsRtl || false;
|
|
70
|
+
const bEnhanced = oGraph.getEnableEnhancedLineRouting ? oGraph.getEnableEnhancedLineRouting() : false;
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
oLine.clearBends();
|
|
103
|
-
// Set the calculated bend points on the line, determined by the connection type.
|
|
104
|
-
if (aCoordinates && aCoordinates.length) {
|
|
105
|
-
aCoordinates.forEach((oBend) => {
|
|
106
|
-
oLine.addBend(oBend);
|
|
107
|
-
});
|
|
72
|
+
try {
|
|
73
|
+
if (bEnhanced) {
|
|
74
|
+
this._routingRowCache = this._buildRoutingRowCache(oGraph.getNodes(), config.gridSize);
|
|
75
|
+
|
|
76
|
+
// Precompute per-node connection counts once to avoid rescanning all lines on each call.
|
|
77
|
+
// Keys are normalized to strings on both write and read — node keys may be numeric
|
|
78
|
+
// while Line#getFrom/getTo return them as-is, and Map.get() uses strict key identity.
|
|
79
|
+
this._analysisCache = new Map();
|
|
80
|
+
oGraph.getNodes().forEach(function(oNode) {
|
|
81
|
+
this._analysisCache.set(String(oNode.getKey()), { leftIncoming: 0, leftOutgoing: 0, rightIncoming: 0, rightOutgoing: 0 });
|
|
82
|
+
}, this);
|
|
83
|
+
oGraph.getLines().forEach(function(oLine) {
|
|
84
|
+
if (!oLine.getVisible() || oLine._isIgnored()) { return; }
|
|
85
|
+
var sFrom = oLine.getFrom();
|
|
86
|
+
var sTo = oLine.getTo();
|
|
87
|
+
var sType = oLine.getConnectionType();
|
|
88
|
+
var oFrom = this._analysisCache.get(String(sFrom));
|
|
89
|
+
var oTo = this._analysisCache.get(String(sTo));
|
|
90
|
+
if (oFrom) {
|
|
91
|
+
switch (sType) {
|
|
92
|
+
case "LeftToLeft": case "LeftToRight": oFrom.leftOutgoing++; break;
|
|
93
|
+
default: oFrom.rightOutgoing++; break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (oTo) {
|
|
97
|
+
switch (sType) {
|
|
98
|
+
case "LeftToLeft": case "RightToLeft": oTo.leftIncoming++; break;
|
|
99
|
+
default: oTo.rightIncoming++; break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}, this);
|
|
108
103
|
}
|
|
109
|
-
|
|
104
|
+
// Iterate over all lines in the graph
|
|
105
|
+
oGraph.getLines().forEach((oLine) => {
|
|
106
|
+
const configuredConnectionType = oLine.getConnectionType(); // Get the connection type, if not configured at Line level then the default is Right to Left from source to target.
|
|
107
|
+
// In RTL mode, use effective connection type (semantically reversed)
|
|
108
|
+
const effectiveConnectionType = this._getEffectiveConnectionType(configuredConnectionType, bIsRTL);
|
|
109
|
+
const oSourceNode = oLine.getFromNode();
|
|
110
|
+
const oTargetNode = oLine.getToNode();
|
|
111
|
+
// Get anchor coordinates for source and target nodes, usually the center of the respective sides
|
|
112
|
+
const oSourceAnchor = this._getNodeAnchorCoordinates(oLine.getFromNode(), effectiveConnectionType, "source");
|
|
113
|
+
const oTargetAnchor = this._getNodeAnchorCoordinates(oLine.getToNode(), effectiveConnectionType, "target");
|
|
114
|
+
|
|
115
|
+
const mParams = {
|
|
116
|
+
sourceAnchor: oSourceAnchor,
|
|
117
|
+
targetAnchor: oTargetAnchor,
|
|
118
|
+
sourceNode: oSourceNode,
|
|
119
|
+
targetNode: oTargetNode,
|
|
120
|
+
sConnectionType: effectiveConnectionType,
|
|
121
|
+
gridSize: config.gridSize,
|
|
122
|
+
minEdgeLength: config.minEdgeLength,
|
|
123
|
+
bEnhanced: bEnhanced
|
|
124
|
+
};
|
|
125
|
+
// Calculate the path coordinates between the source and target anchors
|
|
126
|
+
const aCoordinates = this._calculatePathCoordinates(mParams);
|
|
127
|
+
// set the anchor points of the source and target nodes on the line as source and target for drawing the path.
|
|
128
|
+
oLine.setSource({
|
|
129
|
+
x: oSourceAnchor.x,
|
|
130
|
+
y: oSourceAnchor.y
|
|
131
|
+
});
|
|
132
|
+
oLine.setTarget({
|
|
133
|
+
x: oTargetAnchor.x,
|
|
134
|
+
y: oTargetAnchor.y
|
|
135
|
+
});
|
|
136
|
+
// Set the calculated bend points on the line
|
|
137
|
+
oLine.clearBends();
|
|
138
|
+
// Set the calculated bend points on the line, determined by the connection type.
|
|
139
|
+
if (aCoordinates && aCoordinates.length) {
|
|
140
|
+
aCoordinates.forEach((oBend) => {
|
|
141
|
+
oLine.addBend(oBend);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
} finally {
|
|
146
|
+
this._routingRowCache = null;
|
|
147
|
+
this._analysisCache = null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (bEnhanced) {
|
|
151
|
+
this._resolveLineNodeCollisions(oGraph.getLines(), oGraph.getNodes(), config.gridSize);
|
|
152
|
+
}
|
|
110
153
|
},
|
|
111
154
|
/**
|
|
112
155
|
* Calculates path coordinates between source and target anchors based on connection type.
|
|
@@ -161,7 +204,7 @@ sap.ui.define([
|
|
|
161
204
|
* @private
|
|
162
205
|
*/
|
|
163
206
|
_handleRightToLeftConnection: function (mParams) {
|
|
164
|
-
const { sourceNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
|
|
207
|
+
const { sourceNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH, bEnhanced } = mParams;
|
|
165
208
|
const { x1, y1, x2, y2, AX1, AY1, AX2, AY2, dx } = this._transformSourceTargetCoordinates(mParams);
|
|
166
209
|
|
|
167
210
|
// CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
|
|
@@ -176,16 +219,39 @@ sap.ui.define([
|
|
|
176
219
|
// Visual: [SRC]----→[TGT] (perfect alignment)
|
|
177
220
|
return [];
|
|
178
221
|
} else if (anchorVerticalOffset > 5) {
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
222
|
+
// Offset anchors due to different node heights in the same row.
|
|
223
|
+
// For skip-layer (intermediate nodes exist), route using corridor to avoid
|
|
224
|
+
// the vertical Z-bend passing through intermediate nodes.
|
|
225
|
+
if (bEnhanced) {
|
|
226
|
+
const iNodeWidthOff = sourceNode._iWidth || 0;
|
|
227
|
+
const iHorizontalGapOff = x2 - (x1 + iNodeWidthOff);
|
|
228
|
+
if (iHorizontalGapOff > Math.max(iNodeWidthOff, gridSize)) {
|
|
229
|
+
const xDrop = this.getRightGridIntersectionPoint(x1 + iNodeWidthOff, AY1, gridSize, minEdgeLength).x;
|
|
230
|
+
const xRise = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
231
|
+
const y3 = this._getRoutingYBelow(sourceNode, gridSize);
|
|
232
|
+
return [{ x: xDrop, y: AY1 }, { x: xDrop, y: y3 }, { x: xRise, y: y3 }, { x: xRise, y: AY2 }];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Adjacent nodes — simple Z-bend through the gap between them
|
|
182
236
|
const midX = AX1 + (AX2 - AX1) / 2;
|
|
183
237
|
return [
|
|
184
238
|
{ x: midX, y: AY1 },
|
|
185
239
|
{ x: midX, y: AY2 }
|
|
186
240
|
];
|
|
187
241
|
} else {
|
|
188
|
-
// Multiple connections
|
|
242
|
+
// Multiple connections from same side.
|
|
243
|
+
if (bEnhanced) {
|
|
244
|
+
// If the gap is large enough to suggest intermediate nodes, route below
|
|
245
|
+
// to avoid the line passing through them visually.
|
|
246
|
+
const iNodeWidth = sourceNode._iWidth || 0;
|
|
247
|
+
const iHorizontalGap = x2 - (x1 + iNodeWidth);
|
|
248
|
+
if (iHorizontalGap > Math.max(iNodeWidth, gridSize)) {
|
|
249
|
+
const xDrop = this.getRightGridIntersectionPoint(x1 + iNodeWidth, AY1, gridSize, minEdgeLength).x;
|
|
250
|
+
const xRise = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
251
|
+
const y3 = this._getRoutingYBelow(sourceNode, gridSize);
|
|
252
|
+
return [{ x: xDrop, y: AY1 }, { x: xDrop, y: y3 }, { x: xRise, y: y3 }, { x: xRise, y: AY2 }];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
189
255
|
return [];
|
|
190
256
|
}
|
|
191
257
|
} else if (x1 >= x2) {
|
|
@@ -197,7 +263,7 @@ sap.ui.define([
|
|
|
197
263
|
// Create U-shaped path going around both nodes
|
|
198
264
|
const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
199
265
|
const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
200
|
-
const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
266
|
+
const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
201
267
|
|
|
202
268
|
return [
|
|
203
269
|
{ x: x3, y: AY1 }, // Right from source
|
|
@@ -219,13 +285,42 @@ sap.ui.define([
|
|
|
219
285
|
const x3 = AX1 + (x2 - (x1 + sourceNode._iWidth)) / 2;
|
|
220
286
|
return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
221
287
|
} else {
|
|
222
|
-
// SCENARIO 2A2: Nodes are far apart
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
288
|
+
// SCENARIO 2A2: Nodes are far apart
|
|
289
|
+
// If the horizontal gap is larger than one node width, intermediate nodes
|
|
290
|
+
// likely occupy the space — route the vertical segments at source/target
|
|
291
|
+
// column boundaries to avoid passing through them.
|
|
292
|
+
const iGap = x2 - (x1 + sourceNode._iWidth);
|
|
293
|
+
if (bEnhanced && iGap > sourceNode._iWidth) {
|
|
294
|
+
// SCENARIO 2A2-SKIP: Skip-layer connection.
|
|
295
|
+
// First go right to the nearest grid point (so the line visually exits the
|
|
296
|
+
// source from the center-right side), then drop/rise outside intermediate
|
|
297
|
+
// columns, then go horizontally to the target.
|
|
298
|
+
// Visual (y1 ≤ y2): [SRC]→——| Visual (y1 > y2): [SRC]→——|
|
|
299
|
+
// | ↑
|
|
300
|
+
// ↓————————→ | ←———————————|
|
|
301
|
+
// ↑
|
|
302
|
+
// ←[TGT]
|
|
303
|
+
const xDrop = this.getRightGridIntersectionPoint(x1 + sourceNode._iWidth, AY1, gridSize, minEdgeLength).x;
|
|
304
|
+
const xRise = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
305
|
+
let y3;
|
|
306
|
+
if (y1 <= y2) {
|
|
307
|
+
y3 = this._getRoutingYBelow(sourceNode, gridSize);
|
|
308
|
+
} else {
|
|
309
|
+
y3 = this.getTopGridIntersectionPoint(xDrop, Math.min(y1, y2), gridSize, minEdgeLength).y;
|
|
310
|
+
if (y3 <= 0) {
|
|
311
|
+
y3 = this._getRoutingYBelow(sourceNode, gridSize);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return [{ x: xDrop, y: AY1 }, { x: xDrop, y: y3 }, { x: xRise, y: y3 }, { x: xRise, y: AY2 }];
|
|
315
|
+
} else {
|
|
316
|
+
// SCENARIO 2A2: Adjacent layers — grid-aligned Z-bend
|
|
317
|
+
// Visual: [SRC]→ |
|
|
318
|
+
// |
|
|
319
|
+
// ↓
|
|
320
|
+
// ←[TGT]
|
|
321
|
+
const x3 = this.getRightGridIntersectionPoint(x1 + sourceNode._iWidth, AY1, gridSize, minEdgeLength).x;
|
|
322
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
323
|
+
}
|
|
229
324
|
}
|
|
230
325
|
} else if (dx <= minEdgeLength + sourceNode._iWidth) {
|
|
231
326
|
// SCENARIO 2B: Nodes are too close - create S-shaped path
|
|
@@ -239,7 +334,7 @@ sap.ui.define([
|
|
|
239
334
|
// ←[TGT]
|
|
240
335
|
const x3 = this.getRightGridIntersectionPoint(x1 + sourceNode._iWidth, y1, gridSize, minEdgeLength).x;
|
|
241
336
|
const x4 = this.getLeftGridIntersectionPoint(x2, AY2, gridSize, minEdgeLength).x;
|
|
242
|
-
const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
337
|
+
const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
243
338
|
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
244
339
|
} else if (y1 > y2) {
|
|
245
340
|
// SCENARIO 2B2: Source below target - S-bend going up
|
|
@@ -265,7 +360,7 @@ sap.ui.define([
|
|
|
265
360
|
// ←[TGT] ←---------
|
|
266
361
|
const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
267
362
|
const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
268
|
-
const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
363
|
+
const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
269
364
|
return [
|
|
270
365
|
{ x: x3, y: AY1 }, // Right from source
|
|
271
366
|
{ x: x3, y: y3 }, // Down past both nodes
|
|
@@ -310,7 +405,7 @@ sap.ui.define([
|
|
|
310
405
|
* @private
|
|
311
406
|
*/
|
|
312
407
|
_handleLeftToRightConnection: function (mParams) {
|
|
313
|
-
const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
|
|
408
|
+
const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH, bEnhanced } = mParams;
|
|
314
409
|
const { x1, y1, x2, y2, AX1, AY1, AX2, AY2, dx } = this._transformSourceTargetCoordinates(mParams);
|
|
315
410
|
// CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
|
|
316
411
|
if (y1 === y2) {
|
|
@@ -345,7 +440,7 @@ sap.ui.define([
|
|
|
345
440
|
// Create U-shaped path going around both nodes
|
|
346
441
|
const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
347
442
|
const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
348
|
-
const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
443
|
+
const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
349
444
|
|
|
350
445
|
return [
|
|
351
446
|
{ x: x3, y: AY1 }, // Right from source
|
|
@@ -388,7 +483,7 @@ sap.ui.define([
|
|
|
388
483
|
// [TGT]→
|
|
389
484
|
const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
390
485
|
const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
391
|
-
const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
486
|
+
const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
392
487
|
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
393
488
|
} else if (y1 > y2) {
|
|
394
489
|
// SCENARIO 2B2: Source below target - S-bend going up
|
|
@@ -414,7 +509,7 @@ sap.ui.define([
|
|
|
414
509
|
// --------→[TGT]
|
|
415
510
|
const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
416
511
|
const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
417
|
-
const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
512
|
+
const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
418
513
|
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
419
514
|
} else if (y1 > y2) {
|
|
420
515
|
// SCENARIO 3B: Source below and left of target - S-bend up and right
|
|
@@ -454,7 +549,7 @@ sap.ui.define([
|
|
|
454
549
|
* @private
|
|
455
550
|
*/
|
|
456
551
|
_handleLeftToLeftConnection: function (mParams) {
|
|
457
|
-
const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
|
|
552
|
+
const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH, bEnhanced } = mParams;
|
|
458
553
|
const { x1, y1, x2, y2, AX1, AY1, AX2, AY2 } = this._transformSourceTargetCoordinates(mParams);
|
|
459
554
|
// CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
|
|
460
555
|
if (y1 === y2) {
|
|
@@ -469,7 +564,7 @@ sap.ui.define([
|
|
|
469
564
|
(AX1 + (x2 + targetNode._iWidth)) / 2 :
|
|
470
565
|
this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
471
566
|
const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
472
|
-
const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
567
|
+
const y3 = y1 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y) : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
473
568
|
return [
|
|
474
569
|
{ x: x3, y: AY1 }, // Left from source
|
|
475
570
|
{ x: x3, y: y3 }, // Down/up to avoid nodes
|
|
@@ -486,7 +581,7 @@ sap.ui.define([
|
|
|
486
581
|
const x4 = Math.abs(x1 + sourceNode._iWidth - x2) <= gridSize ?
|
|
487
582
|
(AX2 + x2) / 2 :
|
|
488
583
|
this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
489
|
-
const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
584
|
+
const y3 = y1 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y) : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
490
585
|
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
491
586
|
}
|
|
492
587
|
} else if (y1 != y2) {
|
|
@@ -553,7 +648,7 @@ sap.ui.define([
|
|
|
553
648
|
x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
554
649
|
}
|
|
555
650
|
const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
556
|
-
const y3 = this.getBottomGridIntersectionPoint(x4, y2 + targetNode._iHeight, gridSize, minEdgeLength).y;
|
|
651
|
+
const y3 = bEnhanced ? this._getRoutingYBelow(targetNode, gridSize) : this.getBottomGridIntersectionPoint(x4, y2 + targetNode._iHeight, gridSize, minEdgeLength).y;
|
|
557
652
|
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
558
653
|
}
|
|
559
654
|
}
|
|
@@ -574,7 +669,7 @@ sap.ui.define([
|
|
|
574
669
|
} else {
|
|
575
670
|
x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
576
671
|
}
|
|
577
|
-
const y3 = (AY2 > (y1 + sourceNode._iHeight)) ? AY2 : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
672
|
+
const y3 = (AY2 > (y1 + sourceNode._iHeight)) ? AY2 : (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y);
|
|
578
673
|
const aCoordinates = AY2 > (y1 + sourceNode._iHeight) ? [{ x: x3, y: AY1 }, { x: x3, y: y3 }] : [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
579
674
|
return aCoordinates;
|
|
580
675
|
} else if (y1 > y2) {
|
|
@@ -623,7 +718,7 @@ sap.ui.define([
|
|
|
623
718
|
* @private
|
|
624
719
|
*/
|
|
625
720
|
_handleRightToRightConnection: function (mParams) {
|
|
626
|
-
const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
|
|
721
|
+
const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH, bEnhanced } = mParams;
|
|
627
722
|
const { x1, y1, x2, y2, AX1, AY1, AX2, AY2 } = this._transformSourceTargetCoordinates(mParams);
|
|
628
723
|
// CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
|
|
629
724
|
if (y1 === y2) {
|
|
@@ -636,7 +731,7 @@ sap.ui.define([
|
|
|
636
731
|
const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
637
732
|
const iDiff = Math.abs(x1 - AX2);
|
|
638
733
|
const x4 = iDiff <= gridSize ? (x1 + x2) / 2 : this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
639
|
-
const y3 = y1 < gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
734
|
+
const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : y1 < gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
640
735
|
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
641
736
|
} else if (x1 <= x2) {
|
|
642
737
|
// SCENARIO 1B: Source to the left of target - create C-shaped path on right side
|
|
@@ -647,7 +742,7 @@ sap.ui.define([
|
|
|
647
742
|
const iDff = Math.abs(AX1 - x2);
|
|
648
743
|
const x3 = iDff <= gridSize ? (AX1 + x2) / 2 : this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
649
744
|
const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
650
|
-
const y3 = y1 < gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
745
|
+
const y3 = bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : y1 < gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
651
746
|
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
652
747
|
}
|
|
653
748
|
} else if (y1 != y2) {
|
|
@@ -669,24 +764,12 @@ sap.ui.define([
|
|
|
669
764
|
} else {
|
|
670
765
|
x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
671
766
|
}
|
|
672
|
-
const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
673
|
-
if (AY2 > y1 + sourceNode._iHeight) {
|
|
674
|
-
// Simple vertical connection if target anchor is below source node
|
|
675
|
-
// Visual: [SRC]→
|
|
676
|
-
// |
|
|
677
|
-
// ↓
|
|
678
|
-
// [TGT]→
|
|
767
|
+
const y3 = y1 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y) : (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y);
|
|
768
|
+
if (!bEnhanced && AY2 > y1 + sourceNode._iHeight) {
|
|
679
769
|
const aCords = AX2 > AX1 ? [{x: x4, y: AY1}, {x: x4, y: AY2}] : [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
680
770
|
return aCords;
|
|
681
|
-
} else {
|
|
682
|
-
// Need to avoid target node - create stepped path
|
|
683
|
-
// Visual: [SRC]→
|
|
684
|
-
// |
|
|
685
|
-
// ↓---- →
|
|
686
|
-
// |
|
|
687
|
-
// [TGT]→
|
|
688
|
-
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
689
771
|
}
|
|
772
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
690
773
|
} else if (y1 > y2) {
|
|
691
774
|
// SCENARIO 2A2: Source below and right of target
|
|
692
775
|
// Visual: [TGT]→
|
|
@@ -702,24 +785,12 @@ sap.ui.define([
|
|
|
702
785
|
} else {
|
|
703
786
|
x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
704
787
|
}
|
|
705
|
-
const y3 = y2 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
|
|
706
|
-
if (AY1 > y2 + targetNode._iHeight) {
|
|
707
|
-
// Simple vertical connection if target anchor is below source node
|
|
708
|
-
// Visual: [SRC]→
|
|
709
|
-
// |
|
|
710
|
-
// ↓
|
|
711
|
-
// [TGT]→
|
|
788
|
+
const y3 = y2 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength)) : (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y);
|
|
789
|
+
if (!bEnhanced && AY1 > y2 + targetNode._iHeight) {
|
|
712
790
|
const aCords = AX2 > AX1 ? [{x: x4, y: AY1}, {x: x4, y: AY2}] : [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
713
791
|
return aCords;
|
|
714
|
-
} else {
|
|
715
|
-
// Need to avoid target node - create stepped path
|
|
716
|
-
// Visual: [SRC]→
|
|
717
|
-
// |
|
|
718
|
-
// ↓---- →
|
|
719
|
-
// |
|
|
720
|
-
// [TGT]→
|
|
721
|
-
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
722
792
|
}
|
|
793
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
723
794
|
}
|
|
724
795
|
} else if (x1 <= x2) {
|
|
725
796
|
// SCENARIO 2B: Source is to the left of target
|
|
@@ -738,26 +809,12 @@ sap.ui.define([
|
|
|
738
809
|
} else {
|
|
739
810
|
x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
740
811
|
}
|
|
741
|
-
const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
742
|
-
if (AY2 > y1 + sourceNode._iHeight) {
|
|
743
|
-
// Simple vertical connection if target anchor is below source node
|
|
744
|
-
// Visual: [SRC]→
|
|
745
|
-
// |
|
|
746
|
-
// ↓
|
|
747
|
-
// [TGT]→
|
|
812
|
+
const y3 = y1 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y) : (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y);
|
|
813
|
+
if (!bEnhanced && AY2 > y1 + sourceNode._iHeight) {
|
|
748
814
|
const aCords = AX2 > AX1 ? [{x: x4, y: AY1}, {x: x4, y: AY2}] : [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
749
815
|
return aCords;
|
|
750
|
-
} else {
|
|
751
|
-
// Need to avoid target node - create stepped path
|
|
752
|
-
// Visual: [SRC]→
|
|
753
|
-
// |
|
|
754
|
-
// ↓---- →
|
|
755
|
-
// |
|
|
756
|
-
// [TGT]→
|
|
757
|
-
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
758
816
|
}
|
|
759
|
-
|
|
760
|
-
// return aCoordinates;
|
|
817
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
761
818
|
} else if (y1 > y2) {
|
|
762
819
|
// SCENARIO 2B2: Source below and left of target
|
|
763
820
|
// Visual: [TGT]→
|
|
@@ -773,26 +830,12 @@ sap.ui.define([
|
|
|
773
830
|
} else {
|
|
774
831
|
x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
775
832
|
}
|
|
776
|
-
const y3 = y2 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
|
|
777
|
-
if (AY1 > y2 + targetNode._iHeight) {
|
|
778
|
-
// Simple vertical connection if target anchor is below source node
|
|
779
|
-
// Visual: [SRC]→
|
|
780
|
-
// |
|
|
781
|
-
// ↓
|
|
782
|
-
// [TGT]→
|
|
833
|
+
const y3 = y2 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength)) : (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y);
|
|
834
|
+
if (!bEnhanced && AY1 > y2 + targetNode._iHeight) {
|
|
783
835
|
const aCords = AX2 > AX1 ? [{x: x4, y: AY1}, {x: x4, y: AY2}] : [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
784
836
|
return aCords;
|
|
785
|
-
} else {
|
|
786
|
-
// Need to avoid target node - create stepped path
|
|
787
|
-
// Visual: [SRC]→
|
|
788
|
-
// |
|
|
789
|
-
// ↓---- →
|
|
790
|
-
// |
|
|
791
|
-
// [TGT]→
|
|
792
|
-
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
793
837
|
}
|
|
794
|
-
|
|
795
|
-
// return aCoordinates;
|
|
838
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
796
839
|
}
|
|
797
840
|
}
|
|
798
841
|
}
|
|
@@ -867,6 +910,150 @@ sap.ui.define([
|
|
|
867
910
|
}
|
|
868
911
|
return { x: x, y: yIntersection };
|
|
869
912
|
},
|
|
913
|
+
/**
|
|
914
|
+
* Post-processing pass: detects horizontal line segments that pass through a node's
|
|
915
|
+
* bounding box and shifts them below the collision.
|
|
916
|
+
* Only interior segments are adjusted, source and target anchors are never moved.
|
|
917
|
+
*
|
|
918
|
+
* @param {sap.suite.ui.commons.networkgraph.Line[]} aLines
|
|
919
|
+
* @param {sap.suite.ui.commons.networkgraph.Node[]} aNodes
|
|
920
|
+
* @param {number} gridSize
|
|
921
|
+
* @private
|
|
922
|
+
*/
|
|
923
|
+
_resolveLineNodeCollisions: function(aLines, aNodes, gridSize) {
|
|
924
|
+
var aBounds = aNodes.map(function(oNode) {
|
|
925
|
+
return {
|
|
926
|
+
x: oNode.getX(),
|
|
927
|
+
y: oNode.getY(),
|
|
928
|
+
r: oNode.getX() + (oNode._iWidth || 0),
|
|
929
|
+
b: oNode.getY() + (oNode._iHeight || 0)
|
|
930
|
+
};
|
|
931
|
+
}).filter(function(b) { return b.r > b.x && b.b > b.y; });
|
|
932
|
+
|
|
933
|
+
aLines.forEach(function(oLine) {
|
|
934
|
+
if (oLine.getVisible && !oLine.getVisible()) { return; }
|
|
935
|
+
if (oLine._isIgnored && oLine._isIgnored()) { return; }
|
|
936
|
+
|
|
937
|
+
var aBends = oLine.getBends();
|
|
938
|
+
var aPoints = [{ x: oLine.getSource().getX(), y: oLine.getSource().getY() }];
|
|
939
|
+
aBends.forEach(function(oBend) { aPoints.push({ x: oBend.getX(), y: oBend.getY() }); });
|
|
940
|
+
aPoints.push({ x: oLine.getTarget().getX(), y: oLine.getTarget().getY() });
|
|
941
|
+
|
|
942
|
+
var iPointCount = aPoints.length;
|
|
943
|
+
var bChanged = false;
|
|
944
|
+
|
|
945
|
+
// Only check interior segments — never move source (0) or target (iPointCount-1)
|
|
946
|
+
for (var i = 1; i < iPointCount - 2; i++) {
|
|
947
|
+
var p1 = aPoints[i], p2 = aPoints[i + 1];
|
|
948
|
+
if (p1.y !== p2.y) { continue; } // skip vertical segments
|
|
949
|
+
|
|
950
|
+
var y = p1.y;
|
|
951
|
+
var xMin = Math.min(p1.x, p2.x);
|
|
952
|
+
var xMax = Math.max(p1.x, p2.x);
|
|
953
|
+
|
|
954
|
+
var iMaxBottom = -Infinity;
|
|
955
|
+
var bCollision = false;
|
|
956
|
+
aBounds.forEach(function(oBound) {
|
|
957
|
+
if (y > oBound.y && y <= oBound.b && xMin < oBound.r && xMax > oBound.x) {
|
|
958
|
+
bCollision = true;
|
|
959
|
+
if (oBound.b > iMaxBottom) { iMaxBottom = oBound.b; }
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
if (bCollision) {
|
|
964
|
+
var iNewY = iMaxBottom + Math.round(gridSize * 0.5);
|
|
965
|
+
aPoints[i].y = iNewY;
|
|
966
|
+
aPoints[i + 1].y = iNewY;
|
|
967
|
+
bChanged = true;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
if (bChanged) {
|
|
972
|
+
oLine.clearBends();
|
|
973
|
+
for (var j = 1; j < iPointCount - 1; j++) {
|
|
974
|
+
oLine.addBend(aPoints[j]);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
},
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* Builds a WeakMap of per-node row boundary data used by _getRoutingYBelow.
|
|
982
|
+
* Uses two passes to avoid the single-pass bug where iRowBottom is not yet finalised
|
|
983
|
+
* when a next-row candidate is evaluated.
|
|
984
|
+
*
|
|
985
|
+
* @param {object[]} aNodes - All graph nodes
|
|
986
|
+
* @param {number} gridSize - Grid size used for same-row proximity threshold
|
|
987
|
+
* @returns {WeakMap} node → { rowBottom, nextRowTop }
|
|
988
|
+
* @private
|
|
989
|
+
*/
|
|
990
|
+
_buildRoutingRowCache: function(aNodes, gridSize) {
|
|
991
|
+
var mCache = new WeakMap();
|
|
992
|
+
if (!aNodes.length) { return mCache; }
|
|
993
|
+
|
|
994
|
+
// Pass 1: group nodes by row (center within gridSize/2) and compute rowBottom per group.
|
|
995
|
+
// The representative centerY is intentionally kept as the first node's center, a deliberate
|
|
996
|
+
// approximation that avoids updating all prior comparisons when new nodes join.
|
|
997
|
+
var aRows = [];
|
|
998
|
+
aNodes.forEach(function(oNode) {
|
|
999
|
+
var h = oNode._iHeight || 0;
|
|
1000
|
+
var centerY = oNode.getY() + h / 2;
|
|
1001
|
+
var oRow = null;
|
|
1002
|
+
for (var i = 0; i < aRows.length; i++) {
|
|
1003
|
+
if (Math.abs(aRows[i].centerY - centerY) < gridSize / 2) {
|
|
1004
|
+
oRow = aRows[i];
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (oRow) {
|
|
1009
|
+
oRow.nodes.push(oNode);
|
|
1010
|
+
oRow.rowBottom = Math.max(oRow.rowBottom, oNode.getY() + h);
|
|
1011
|
+
oRow.rowTop = Math.min(oRow.rowTop, oNode.getY());
|
|
1012
|
+
} else {
|
|
1013
|
+
aRows.push({ centerY: centerY, nodes: [oNode], rowBottom: oNode.getY() + h, rowTop: oNode.getY() });
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
// Pass 2: for each row, find the nearest next rowTop. rowTop isn't monotonic with
|
|
1018
|
+
// rowBottom if the heights vary, so the scan can't stop at the first match. It must
|
|
1019
|
+
// keep the minimum qualifying rowTop across all subsequent rows.
|
|
1020
|
+
aRows.sort(function(a, b) { return a.rowBottom - b.rowBottom; });
|
|
1021
|
+
aRows.forEach(function(oRow, iRowIdx) {
|
|
1022
|
+
var iNextRowTop = Infinity;
|
|
1023
|
+
for (var k = iRowIdx + 1; k < aRows.length; k++) {
|
|
1024
|
+
if (aRows[k].rowTop > oRow.rowBottom && aRows[k].rowTop < iNextRowTop) {
|
|
1025
|
+
iNextRowTop = aRows[k].rowTop;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
oRow.nodes.forEach(function(oNode) {
|
|
1029
|
+
mCache.set(oNode, { rowBottom: oRow.rowBottom, nextRowTop: iNextRowTop });
|
|
1030
|
+
});
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
return mCache;
|
|
1034
|
+
},
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* Returns the Y coordinate of the horizontal routing corridor below a node's row.
|
|
1038
|
+
* Uses pre-computed row boundary data from _buildRoutingRowCache (O(1) lookup).
|
|
1039
|
+
* Falls back to node-bottom + 0.75 * gridSize when called outside normalizeLines.
|
|
1040
|
+
*
|
|
1041
|
+
* @param {object} oNode - Source/target node
|
|
1042
|
+
* @param {number} gridSize - Grid size for fallback spacing
|
|
1043
|
+
* @returns {number} Y coordinate of the routing corridor
|
|
1044
|
+
* @private
|
|
1045
|
+
*/
|
|
1046
|
+
_getRoutingYBelow: function(oNode, gridSize) {
|
|
1047
|
+
var oData = this._routingRowCache && this._routingRowCache.get(oNode);
|
|
1048
|
+
var iRowBottom = oData ? oData.rowBottom : oNode.getY() + (oNode._iHeight || 0);
|
|
1049
|
+
var iNextRowTop = oData ? oData.nextRowTop : Infinity;
|
|
1050
|
+
|
|
1051
|
+
if (iNextRowTop === Infinity) {
|
|
1052
|
+
return iRowBottom + Math.round(gridSize * 0.75);
|
|
1053
|
+
}
|
|
1054
|
+
return Math.round((iRowBottom + iNextRowTop) / 2);
|
|
1055
|
+
},
|
|
1056
|
+
|
|
870
1057
|
/**
|
|
871
1058
|
* Gets the anchor coordinates for a node based on connection type and anchor type.
|
|
872
1059
|
* Enhanced to handle center positioning for single connections and offset positioning for mixed connections.
|
|
@@ -1012,6 +1199,13 @@ sap.ui.define([
|
|
|
1012
1199
|
return { leftIncoming: 0, leftOutgoing: 0, rightIncoming: 0, rightOutgoing: 0 };
|
|
1013
1200
|
}
|
|
1014
1201
|
|
|
1202
|
+
if (this._analysisCache) {
|
|
1203
|
+
var sCachedKey = oNode.getKey && oNode.getKey();
|
|
1204
|
+
if (sCachedKey !== undefined && this._analysisCache.has(String(sCachedKey))) {
|
|
1205
|
+
return this._analysisCache.get(String(sCachedKey));
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1015
1209
|
const oGraph = oNode.getParent();
|
|
1016
1210
|
const aLines = oGraph.getLines();
|
|
1017
1211
|
const sNodeKey = oNode.getKey();
|