@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.
Files changed (69) hide show
  1. package/package.json +1 -1
  2. package/src/sap/suite/ui/commons/.library +1 -1
  3. package/src/sap/suite/ui/commons/AriaProperties.js +1 -1
  4. package/src/sap/suite/ui/commons/CalculationBuilder.js +1 -1
  5. package/src/sap/suite/ui/commons/CalculationBuilderExpression.js +1 -1
  6. package/src/sap/suite/ui/commons/CalculationBuilderFunction.js +1 -1
  7. package/src/sap/suite/ui/commons/CalculationBuilderGroup.js +1 -1
  8. package/src/sap/suite/ui/commons/CalculationBuilderItem.js +1 -1
  9. package/src/sap/suite/ui/commons/CalculationBuilderValidationResult.js +1 -1
  10. package/src/sap/suite/ui/commons/CalculationBuilderVariable.js +1 -1
  11. package/src/sap/suite/ui/commons/CloudFilePicker.js +1 -1
  12. package/src/sap/suite/ui/commons/MicroProcessFlow.js +1 -1
  13. package/src/sap/suite/ui/commons/MicroProcessFlowItem.js +1 -1
  14. package/src/sap/suite/ui/commons/TimelineItem.js +2 -3
  15. package/src/sap/suite/ui/commons/TimelineNavigator.js +12 -9
  16. package/src/sap/suite/ui/commons/collaboration/UniversalCollaborationBar.js +475 -360
  17. package/src/sap/suite/ui/commons/flexibility/changeHandler/PropertyChangeMapper.js +1 -1
  18. package/src/sap/suite/ui/commons/imageeditor/CropCustomShapeHistoryItem.js +1 -1
  19. package/src/sap/suite/ui/commons/imageeditor/CropEllipseHistoryItem.js +1 -1
  20. package/src/sap/suite/ui/commons/imageeditor/CropRectangleHistoryItem.js +1 -1
  21. package/src/sap/suite/ui/commons/imageeditor/CustomSizeItem.js +1 -1
  22. package/src/sap/suite/ui/commons/imageeditor/FilterHistoryItem.js +1 -1
  23. package/src/sap/suite/ui/commons/imageeditor/FlipHistoryItem.js +1 -1
  24. package/src/sap/suite/ui/commons/imageeditor/HistoryItem.js +1 -1
  25. package/src/sap/suite/ui/commons/imageeditor/ImageEditor.js +1 -1
  26. package/src/sap/suite/ui/commons/imageeditor/ImageEditorContainer.js +1 -1
  27. package/src/sap/suite/ui/commons/imageeditor/ImageEditorResponsiveContainer.js +1 -1
  28. package/src/sap/suite/ui/commons/imageeditor/ResizeHistoryItem.js +1 -1
  29. package/src/sap/suite/ui/commons/imageeditor/RotateHistoryItem.js +1 -1
  30. package/src/sap/suite/ui/commons/library.js +1 -1
  31. package/src/sap/suite/ui/commons/messagebundle_de.properties +2 -2
  32. package/src/sap/suite/ui/commons/messagebundle_en_US_saptrc.properties +616 -537
  33. package/src/sap/suite/ui/commons/messagebundle_mk.properties +1 -1
  34. package/src/sap/suite/ui/commons/messagebundle_pl.properties +1 -1
  35. package/src/sap/suite/ui/commons/networkgraph/ElementBase.js +7 -2
  36. package/src/sap/suite/ui/commons/networkgraph/Graph.js +19 -7
  37. package/src/sap/suite/ui/commons/networkgraph/GraphRenderer.js +1 -1
  38. package/src/sap/suite/ui/commons/networkgraph/Group.js +1 -1
  39. package/src/sap/suite/ui/commons/networkgraph/KeyboardNavigator.js +2 -0
  40. package/src/sap/suite/ui/commons/networkgraph/Line.js +24 -0
  41. package/src/sap/suite/ui/commons/networkgraph/Node.js +1 -1
  42. package/src/sap/suite/ui/commons/networkgraph/Tooltip.js +3 -1
  43. package/src/sap/suite/ui/commons/networkgraph/util/ConnectionPathUtils.js +323 -129
  44. package/src/sap/suite/ui/commons/networkgraph/util/DependencyLayoutHelper.js +21 -6
  45. package/src/sap/suite/ui/commons/statusindicator/Circle.js +1 -1
  46. package/src/sap/suite/ui/commons/statusindicator/CustomShape.js +1 -1
  47. package/src/sap/suite/ui/commons/statusindicator/DiscreteThreshold.js +1 -1
  48. package/src/sap/suite/ui/commons/statusindicator/FillingOption.js +1 -1
  49. package/src/sap/suite/ui/commons/statusindicator/LibraryShape.js +1 -1
  50. package/src/sap/suite/ui/commons/statusindicator/Path.js +1 -1
  51. package/src/sap/suite/ui/commons/statusindicator/PropertyThreshold.js +1 -1
  52. package/src/sap/suite/ui/commons/statusindicator/Rectangle.js +1 -1
  53. package/src/sap/suite/ui/commons/statusindicator/Shape.js +1 -1
  54. package/src/sap/suite/ui/commons/statusindicator/ShapeGroup.js +1 -1
  55. package/src/sap/suite/ui/commons/statusindicator/SimpleShape.js +1 -1
  56. package/src/sap/suite/ui/commons/statusindicator/StatusIndicator.js +1 -1
  57. package/src/sap/suite/ui/commons/taccount/TAccount.js +1 -1
  58. package/src/sap/suite/ui/commons/taccount/TAccountGroup.js +1 -1
  59. package/src/sap/suite/ui/commons/taccount/TAccountItem.js +1 -1
  60. package/src/sap/suite/ui/commons/taccount/TAccountItemProperty.js +1 -1
  61. package/src/sap/suite/ui/commons/taccount/TAccountPanel.js +1 -1
  62. package/src/sap/suite/ui/commons/themes/base/MicroProcessFlow.less +2 -1
  63. package/src/sap/suite/ui/commons/themes/base/NetworkGraph.less +23 -6
  64. package/src/sap/suite/ui/commons/themes/base/Timeline.less +2 -2
  65. package/src/sap/suite/ui/commons/themes/sap_belize_base/NetworkGraph.less +23 -6
  66. package/src/sap/suite/ui/commons/themes/sap_belize_base/Timeline.less +2 -2
  67. package/src/sap/suite/ui/commons/themes/sap_belize_hcb/NetworkGraph.less +23 -2
  68. package/src/sap/suite/ui/commons/themes/sap_belize_hcw/NetworkGraph.less +23 -2
  69. 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
- // Iterate over all lines in the graph
72
- oGraph.getLines().forEach((oLine) => {
73
- 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.
74
- // In RTL mode, use effective connection type (semantically reversed)
75
- const effectiveConnectionType = this._getEffectiveConnectionType(configuredConnectionType, bIsRTL);
76
- const oSourceNode = oLine.getFromNode();
77
- const oTargetNode = oLine.getToNode();
78
- // Get anchor coordinates for source and target nodes, usually the center of the respective sides
79
- const oSourceAnchor = this._getNodeAnchorCoordinates(oLine.getFromNode(), effectiveConnectionType, "source");
80
- const oTargetAnchor = this._getNodeAnchorCoordinates(oLine.getToNode(), effectiveConnectionType, "target");
81
- const mParams = {
82
- sourceAnchor: oSourceAnchor,
83
- targetAnchor: oTargetAnchor,
84
- sourceNode: oSourceNode,
85
- targetNode: oTargetNode,
86
- sConnectionType: effectiveConnectionType,
87
- gridSize: config.gridSize,
88
- minEdgeLength: config.minEdgeLength
89
- };
90
- // Calculate the path coordinates between the source and target anchors
91
- const aCoordinates = this._calculatePathCoordinates(mParams);
92
- // set the anchor points of the source and target nodes on the line as source and target for drawing the path.
93
- oLine.setSource({
94
- x: oSourceAnchor.x,
95
- y: oSourceAnchor.y
96
- });
97
- oLine.setTarget({
98
- x: oTargetAnchor.x,
99
- y: oTargetAnchor.y
100
- });
101
- // Set the calculated bend points on the line
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
- // Visual: [SRC]→ ←[TGT] (offset anchors - add horizontal correction)
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 - use straight line anyway for now
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 - use grid-aligned Z-bend
223
- // Visual: [SRC]→ |
224
- // |
225
- //
226
- // ←[TGT]
227
- const x3 = this.getRightGridIntersectionPoint(x1 + sourceNode._iWidth, AY1, gridSize, minEdgeLength).x;
228
- return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
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
- // const aCoordinates = (AY2 > y1 + sourceNode._iHeight) ? [{ x: x4, y: AY1 }, { x: x4, y: AY2 }] : [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
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
- // const aCoordinates = (AY1 > y2 + targetNode._iHeight) ? [{ x: x4, y: AY1 }, { x: x4, y: AY2 }] : [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
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();