@sapui5/sap.suite.ui.commons 1.148.2 → 1.148.3
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/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/networkgraph/Graph.js +38 -8
- package/src/sap/suite/ui/commons/networkgraph/util/ConnectionPathUtils.js +315 -69
- 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/package.json
CHANGED
|
@@ -198,7 +198,7 @@ sap.ui.define([
|
|
|
198
198
|
"sap.suite.ui.commons.TargetFilterMeasureColumn",
|
|
199
199
|
"sap.suite.ui.commons.AriaProperties"
|
|
200
200
|
],
|
|
201
|
-
version: "1.148.
|
|
201
|
+
version: "1.148.3",
|
|
202
202
|
extensions: {
|
|
203
203
|
flChangeHandlers: {
|
|
204
204
|
"sap.suite.ui.commons.Timeline": "sap/suite/ui/commons/flexibility/Timeline"
|
|
@@ -295,7 +295,24 @@ sap.ui.define([
|
|
|
295
295
|
"LeftToRight": "Start to Finish",
|
|
296
296
|
"RightToLeft": "Finish to Start",
|
|
297
297
|
"RightToRight": "Finish to Finish"
|
|
298
|
-
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
/**
|
|
301
|
+
* Enables enhanced line routing for the network graph.
|
|
302
|
+
* When set to <code>true</code>, overlapping connections are visually separated.
|
|
303
|
+
* @public
|
|
304
|
+
*/
|
|
305
|
+
enableEnhancedLineRouting: {
|
|
306
|
+
type: "boolean", group: "Behavior", defaultValue: true
|
|
307
|
+
},
|
|
308
|
+
/**
|
|
309
|
+
* Custom labels for element types displayed in the SearchField suggestions and Legend section headers.
|
|
310
|
+
* Supported keys: nodeLabel, nodeLabelPlural, lineLabel, lineLabelPlural, groupLabel, groupLabelPlural.
|
|
311
|
+
* Reverts to the default values for keys that are not set.
|
|
312
|
+
* @public
|
|
313
|
+
*/
|
|
314
|
+
labels: {
|
|
315
|
+
type: "object", group: "Appearance", defaultValue: {}
|
|
299
316
|
}
|
|
300
317
|
},
|
|
301
318
|
aggregations: {
|
|
@@ -1015,6 +1032,13 @@ sap.ui.define([
|
|
|
1015
1032
|
this._createLegend();
|
|
1016
1033
|
};
|
|
1017
1034
|
|
|
1035
|
+
Graph.prototype.setLabels = function (oValue) {
|
|
1036
|
+
this.setProperty("labels", oValue, true);
|
|
1037
|
+
this._createSearchSuggestItems();
|
|
1038
|
+
this._createLegend();
|
|
1039
|
+
return this;
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1018
1042
|
|
|
1019
1043
|
/**
|
|
1020
1044
|
* Zooms in or out of the graph.
|
|
@@ -2889,6 +2913,11 @@ sap.ui.define([
|
|
|
2889
2913
|
// indicates current count of suggestion items when they are rendered
|
|
2890
2914
|
this._oSuggestionItemsModel.setSizeLimit(SUGGESTION_ITEMS_LIMIT);
|
|
2891
2915
|
|
|
2916
|
+
const oLabels = this.getLabels() || {};
|
|
2917
|
+
const sNodeLabel = oLabels.nodeLabel || oResourceBundle.getText("NETWORK_GRAPH_NODE");
|
|
2918
|
+
const sLineLabel = oLabels.lineLabel || oResourceBundle.getText("NETWORK_GRAPH_LINE");
|
|
2919
|
+
const sGroupLabel = oLabels.groupLabel || oResourceBundle.getText("NETWORK_GRAPH_GROUP");
|
|
2920
|
+
|
|
2892
2921
|
aNodes.forEach(function (oNode) {
|
|
2893
2922
|
var sTitle = Utils.trimText(oNode.getTitle(), TITLE_LENGTH);
|
|
2894
2923
|
oData.items.push({
|
|
@@ -2896,7 +2925,7 @@ sap.ui.define([
|
|
|
2896
2925
|
type: SUGGESTIONS.Node,
|
|
2897
2926
|
icon: oNode.getIcon(),
|
|
2898
2927
|
key: oNode.getKey(),
|
|
2899
|
-
description: "(" +
|
|
2928
|
+
description: "(" + sNodeLabel + ")"
|
|
2900
2929
|
});
|
|
2901
2930
|
});
|
|
2902
2931
|
|
|
@@ -2909,7 +2938,7 @@ sap.ui.define([
|
|
|
2909
2938
|
text: oLine._createSuggestionHelpText(),
|
|
2910
2939
|
type: SUGGESTIONS.Line,
|
|
2911
2940
|
key: oLine._getLineId(),
|
|
2912
|
-
description: "(" +
|
|
2941
|
+
description: "(" + sLineLabel + ")"
|
|
2913
2942
|
});
|
|
2914
2943
|
}
|
|
2915
2944
|
});
|
|
@@ -2921,7 +2950,7 @@ sap.ui.define([
|
|
|
2921
2950
|
text: sTitle ? sTitle : oGroup.getKey(),
|
|
2922
2951
|
key: oGroup.getKey(),
|
|
2923
2952
|
type: SUGGESTIONS.Group,
|
|
2924
|
-
description: "(" +
|
|
2953
|
+
description: "(" + sGroupLabel + ")"
|
|
2925
2954
|
});
|
|
2926
2955
|
}
|
|
2927
2956
|
});
|
|
@@ -3088,7 +3117,7 @@ sap.ui.define([
|
|
|
3088
3117
|
if (fnHasStatus(oCollection)) {
|
|
3089
3118
|
oRm.openStart("div").class(sClass).openEnd();
|
|
3090
3119
|
oRm.openStart("label").class("sapSuiteUiCommonsNetworkGraphLegendTitle").openEnd();
|
|
3091
|
-
oRm.text(
|
|
3120
|
+
oRm.text(sTitle);
|
|
3092
3121
|
oRm.close("label");
|
|
3093
3122
|
oRm.close("div");
|
|
3094
3123
|
// get the status labels for sorting (some statuses can have custom labels)
|
|
@@ -3123,12 +3152,13 @@ sap.ui.define([
|
|
|
3123
3152
|
return;
|
|
3124
3153
|
}
|
|
3125
3154
|
|
|
3155
|
+
const oLabels = this.getLabels() || {};
|
|
3126
3156
|
fnProcessElements(this.getNodes(), oNodeStatuses, "sapSuiteUiCommonsNetworkGraphLegendTitleNode",
|
|
3127
|
-
"NETWORK_GRAPH_NODES", StatusType.Node);
|
|
3157
|
+
oLabels.nodeLabelPlural || oResourceBundle.getText("NETWORK_GRAPH_NODES"), StatusType.Node);
|
|
3128
3158
|
fnProcessElements(this.getLines(), oLineStatuses, "sapSuiteUiCommonsNetworkGraphLegendTitleLine",
|
|
3129
|
-
"NETWORK_GRAPH_LINES", StatusType.Line);
|
|
3159
|
+
oLabels.lineLabelPlural || oResourceBundle.getText("NETWORK_GRAPH_LINES"), StatusType.Line);
|
|
3130
3160
|
fnProcessElements(this.getGroups(), oGroupStatuses, "sapSuiteUiCommonsNetworkGraphLegendTitleLine",
|
|
3131
|
-
"NETWORK_GRAPH_GROUPS", StatusType.Group);
|
|
3161
|
+
oLabels.groupLabelPlural || oResourceBundle.getText("NETWORK_GRAPH_GROUPS"), StatusType.Group);
|
|
3132
3162
|
oRm.flush($legend[0]);
|
|
3133
3163
|
oRm.destroy();
|
|
3134
3164
|
// $legend.html(sHtml);
|
|
@@ -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 = y1 < gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, 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 = y1 < gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, 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,7 +764,7 @@ 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;
|
|
767
|
+
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;
|
|
673
768
|
if (AY2 > y1 + sourceNode._iHeight) {
|
|
674
769
|
// Simple vertical connection if target anchor is below source node
|
|
675
770
|
// Visual: [SRC]→
|
|
@@ -702,7 +797,7 @@ sap.ui.define([
|
|
|
702
797
|
} else {
|
|
703
798
|
x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
704
799
|
}
|
|
705
|
-
const y3 = y2 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
|
|
800
|
+
const y3 = y2 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength)) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
|
|
706
801
|
if (AY1 > y2 + targetNode._iHeight) {
|
|
707
802
|
// Simple vertical connection if target anchor is below source node
|
|
708
803
|
// Visual: [SRC]→
|
|
@@ -738,7 +833,7 @@ sap.ui.define([
|
|
|
738
833
|
} else {
|
|
739
834
|
x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
740
835
|
}
|
|
741
|
-
const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
836
|
+
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;
|
|
742
837
|
if (AY2 > y1 + sourceNode._iHeight) {
|
|
743
838
|
// Simple vertical connection if target anchor is below source node
|
|
744
839
|
// Visual: [SRC]→
|
|
@@ -773,7 +868,7 @@ sap.ui.define([
|
|
|
773
868
|
} else {
|
|
774
869
|
x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
775
870
|
}
|
|
776
|
-
const y3 = y2 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
|
|
871
|
+
const y3 = y2 <= gridSize ? (bEnhanced ? this._getRoutingYBelow(sourceNode, gridSize) : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength)) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
|
|
777
872
|
if (AY1 > y2 + targetNode._iHeight) {
|
|
778
873
|
// Simple vertical connection if target anchor is below source node
|
|
779
874
|
// Visual: [SRC]→
|
|
@@ -867,6 +962,150 @@ sap.ui.define([
|
|
|
867
962
|
}
|
|
868
963
|
return { x: x, y: yIntersection };
|
|
869
964
|
},
|
|
965
|
+
/**
|
|
966
|
+
* Post-processing pass: detects horizontal line segments that pass through a node's
|
|
967
|
+
* bounding box and shifts them below the collision.
|
|
968
|
+
* Only interior segments are adjusted, source and target anchors are never moved.
|
|
969
|
+
*
|
|
970
|
+
* @param {sap.suite.ui.commons.networkgraph.Line[]} aLines
|
|
971
|
+
* @param {sap.suite.ui.commons.networkgraph.Node[]} aNodes
|
|
972
|
+
* @param {number} gridSize
|
|
973
|
+
* @private
|
|
974
|
+
*/
|
|
975
|
+
_resolveLineNodeCollisions: function(aLines, aNodes, gridSize) {
|
|
976
|
+
var aBounds = aNodes.map(function(oNode) {
|
|
977
|
+
return {
|
|
978
|
+
x: oNode.getX(),
|
|
979
|
+
y: oNode.getY(),
|
|
980
|
+
r: oNode.getX() + (oNode._iWidth || 0),
|
|
981
|
+
b: oNode.getY() + (oNode._iHeight || 0)
|
|
982
|
+
};
|
|
983
|
+
}).filter(function(b) { return b.r > b.x && b.b > b.y; });
|
|
984
|
+
|
|
985
|
+
aLines.forEach(function(oLine) {
|
|
986
|
+
if (oLine.getVisible && !oLine.getVisible()) { return; }
|
|
987
|
+
if (oLine._isIgnored && oLine._isIgnored()) { return; }
|
|
988
|
+
|
|
989
|
+
var aBends = oLine.getBends();
|
|
990
|
+
var aPoints = [{ x: oLine.getSource().getX(), y: oLine.getSource().getY() }];
|
|
991
|
+
aBends.forEach(function(oBend) { aPoints.push({ x: oBend.getX(), y: oBend.getY() }); });
|
|
992
|
+
aPoints.push({ x: oLine.getTarget().getX(), y: oLine.getTarget().getY() });
|
|
993
|
+
|
|
994
|
+
var iPointCount = aPoints.length;
|
|
995
|
+
var bChanged = false;
|
|
996
|
+
|
|
997
|
+
// Only check interior segments — never move source (0) or target (iPointCount-1)
|
|
998
|
+
for (var i = 1; i < iPointCount - 2; i++) {
|
|
999
|
+
var p1 = aPoints[i], p2 = aPoints[i + 1];
|
|
1000
|
+
if (p1.y !== p2.y) { continue; } // skip vertical segments
|
|
1001
|
+
|
|
1002
|
+
var y = p1.y;
|
|
1003
|
+
var xMin = Math.min(p1.x, p2.x);
|
|
1004
|
+
var xMax = Math.max(p1.x, p2.x);
|
|
1005
|
+
|
|
1006
|
+
var iMaxBottom = -Infinity;
|
|
1007
|
+
var bCollision = false;
|
|
1008
|
+
aBounds.forEach(function(oBound) {
|
|
1009
|
+
if (y > oBound.y && y <= oBound.b && xMin < oBound.r && xMax > oBound.x) {
|
|
1010
|
+
bCollision = true;
|
|
1011
|
+
if (oBound.b > iMaxBottom) { iMaxBottom = oBound.b; }
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
if (bCollision) {
|
|
1016
|
+
var iNewY = iMaxBottom + Math.round(gridSize * 0.5);
|
|
1017
|
+
aPoints[i].y = iNewY;
|
|
1018
|
+
aPoints[i + 1].y = iNewY;
|
|
1019
|
+
bChanged = true;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
if (bChanged) {
|
|
1024
|
+
oLine.clearBends();
|
|
1025
|
+
for (var j = 1; j < iPointCount - 1; j++) {
|
|
1026
|
+
oLine.addBend(aPoints[j]);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
},
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* Builds a WeakMap of per-node row boundary data used by _getRoutingYBelow.
|
|
1034
|
+
* Uses two passes to avoid the single-pass bug where iRowBottom is not yet finalised
|
|
1035
|
+
* when a next-row candidate is evaluated.
|
|
1036
|
+
*
|
|
1037
|
+
* @param {object[]} aNodes - All graph nodes
|
|
1038
|
+
* @param {number} gridSize - Grid size used for same-row proximity threshold
|
|
1039
|
+
* @returns {WeakMap} node → { rowBottom, nextRowTop }
|
|
1040
|
+
* @private
|
|
1041
|
+
*/
|
|
1042
|
+
_buildRoutingRowCache: function(aNodes, gridSize) {
|
|
1043
|
+
var mCache = new WeakMap();
|
|
1044
|
+
if (!aNodes.length) { return mCache; }
|
|
1045
|
+
|
|
1046
|
+
// Pass 1: group nodes by row (center within gridSize/2) and compute rowBottom per group.
|
|
1047
|
+
// The representative centerY is intentionally kept as the first node's center, a deliberate
|
|
1048
|
+
// approximation that avoids updating all prior comparisons when new nodes join.
|
|
1049
|
+
var aRows = [];
|
|
1050
|
+
aNodes.forEach(function(oNode) {
|
|
1051
|
+
var h = oNode._iHeight || 0;
|
|
1052
|
+
var centerY = oNode.getY() + h / 2;
|
|
1053
|
+
var oRow = null;
|
|
1054
|
+
for (var i = 0; i < aRows.length; i++) {
|
|
1055
|
+
if (Math.abs(aRows[i].centerY - centerY) < gridSize / 2) {
|
|
1056
|
+
oRow = aRows[i];
|
|
1057
|
+
break;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
if (oRow) {
|
|
1061
|
+
oRow.nodes.push(oNode);
|
|
1062
|
+
oRow.rowBottom = Math.max(oRow.rowBottom, oNode.getY() + h);
|
|
1063
|
+
oRow.rowTop = Math.min(oRow.rowTop, oNode.getY());
|
|
1064
|
+
} else {
|
|
1065
|
+
aRows.push({ centerY: centerY, nodes: [oNode], rowBottom: oNode.getY() + h, rowTop: oNode.getY() });
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
// Pass 2: for each row, find the nearest next rowTop. rowTop isn't monotonic with
|
|
1070
|
+
// rowBottom if the heights vary, so the scan can't stop at the first match. It must
|
|
1071
|
+
// keep the minimum qualifying rowTop across all subsequent rows.
|
|
1072
|
+
aRows.sort(function(a, b) { return a.rowBottom - b.rowBottom; });
|
|
1073
|
+
aRows.forEach(function(oRow, iRowIdx) {
|
|
1074
|
+
var iNextRowTop = Infinity;
|
|
1075
|
+
for (var k = iRowIdx + 1; k < aRows.length; k++) {
|
|
1076
|
+
if (aRows[k].rowTop > oRow.rowBottom && aRows[k].rowTop < iNextRowTop) {
|
|
1077
|
+
iNextRowTop = aRows[k].rowTop;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
oRow.nodes.forEach(function(oNode) {
|
|
1081
|
+
mCache.set(oNode, { rowBottom: oRow.rowBottom, nextRowTop: iNextRowTop });
|
|
1082
|
+
});
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
return mCache;
|
|
1086
|
+
},
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Returns the Y coordinate of the horizontal routing corridor below a node's row.
|
|
1090
|
+
* Uses pre-computed row boundary data from _buildRoutingRowCache (O(1) lookup).
|
|
1091
|
+
* Falls back to node-bottom + 0.75 * gridSize when called outside normalizeLines.
|
|
1092
|
+
*
|
|
1093
|
+
* @param {object} oNode - Source/target node
|
|
1094
|
+
* @param {number} gridSize - Grid size for fallback spacing
|
|
1095
|
+
* @returns {number} Y coordinate of the routing corridor
|
|
1096
|
+
* @private
|
|
1097
|
+
*/
|
|
1098
|
+
_getRoutingYBelow: function(oNode, gridSize) {
|
|
1099
|
+
var oData = this._routingRowCache && this._routingRowCache.get(oNode);
|
|
1100
|
+
var iRowBottom = oData ? oData.rowBottom : oNode.getY() + (oNode._iHeight || 0);
|
|
1101
|
+
var iNextRowTop = oData ? oData.nextRowTop : Infinity;
|
|
1102
|
+
|
|
1103
|
+
if (iNextRowTop === Infinity) {
|
|
1104
|
+
return iRowBottom + Math.round(gridSize * 0.75);
|
|
1105
|
+
}
|
|
1106
|
+
return Math.round((iRowBottom + iNextRowTop) / 2);
|
|
1107
|
+
},
|
|
1108
|
+
|
|
870
1109
|
/**
|
|
871
1110
|
* Gets the anchor coordinates for a node based on connection type and anchor type.
|
|
872
1111
|
* Enhanced to handle center positioning for single connections and offset positioning for mixed connections.
|
|
@@ -1012,6 +1251,13 @@ sap.ui.define([
|
|
|
1012
1251
|
return { leftIncoming: 0, leftOutgoing: 0, rightIncoming: 0, rightOutgoing: 0 };
|
|
1013
1252
|
}
|
|
1014
1253
|
|
|
1254
|
+
if (this._analysisCache) {
|
|
1255
|
+
var sCachedKey = oNode.getKey && oNode.getKey();
|
|
1256
|
+
if (sCachedKey !== undefined && this._analysisCache.has(String(sCachedKey))) {
|
|
1257
|
+
return this._analysisCache.get(String(sCachedKey));
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1015
1261
|
const oGraph = oNode.getParent();
|
|
1016
1262
|
const aLines = oGraph.getLines();
|
|
1017
1263
|
const sNodeKey = oNode.getKey();
|
|
@@ -95,7 +95,8 @@ sap.ui.define(["sap/suite/ui/commons/library", "sap/base/Log"], function (librar
|
|
|
95
95
|
horizontalSpacing: DEFAULT_HORIZONTAL_SPACING,
|
|
96
96
|
verticalSpacing: DEFAULT_VERTICAL_SPACING,
|
|
97
97
|
componentArrangement: oComponentArrangement.Horizontal,
|
|
98
|
-
clearCache: false
|
|
98
|
+
clearCache: false,
|
|
99
|
+
bEnhancedLineRouting: oGraph.getEnableEnhancedLineRouting ? oGraph.getEnableEnhancedLineRouting() : false
|
|
99
100
|
},
|
|
100
101
|
mConfig || {}
|
|
101
102
|
);
|
|
@@ -113,9 +114,11 @@ sap.ui.define(["sap/suite/ui/commons/library", "sap/base/Log"], function (librar
|
|
|
113
114
|
this._calculateCoordinates(oGraph, config);
|
|
114
115
|
this._adjustHorizontalSpacingForNodeWidths(oGraph, config);
|
|
115
116
|
this._adjustVerticalSpacingForNodeHeights(oGraph, config);
|
|
117
|
+
|
|
116
118
|
if (oGraph._bIsRtl) {
|
|
117
119
|
this._horizontalMirror(oGraph, config.horizontalSpacing);
|
|
118
120
|
}
|
|
121
|
+
|
|
119
122
|
this._storeFinalPositions(oGraph, config.horizontalSpacing);
|
|
120
123
|
} catch (e) {
|
|
121
124
|
Log.error("DependencyLayoutHelper error: " + e.message, e);
|
|
@@ -309,7 +312,7 @@ sap.ui.define(["sap/suite/ui/commons/library", "sap/base/Log"], function (librar
|
|
|
309
312
|
* @param {object} config - Configuration object
|
|
310
313
|
* @private
|
|
311
314
|
*/
|
|
312
|
-
_processComponents: function (aComponents, { componentArrangement }) {
|
|
315
|
+
_processComponents: function (aComponents, { componentArrangement, bEnhancedLineRouting }) {
|
|
313
316
|
const aIsolatedNodes = [];
|
|
314
317
|
const aConnectedComponents = [];
|
|
315
318
|
|
|
@@ -330,7 +333,7 @@ sap.ui.define(["sap/suite/ui/commons/library", "sap/base/Log"], function (librar
|
|
|
330
333
|
let iMaxLayerAcrossAll = 0;
|
|
331
334
|
|
|
332
335
|
aConnectedComponents.forEach((aComponentNodes, iCompIndex) => {
|
|
333
|
-
this._assignLayersForComponent(aComponentNodes, iCompIndex);
|
|
336
|
+
this._assignLayersForComponent(aComponentNodes, iCompIndex, bEnhancedLineRouting);
|
|
334
337
|
this._adjustParallelLayersForComponent(aComponentNodes);
|
|
335
338
|
this._assignVerticalPositionsForComponent(aComponentNodes);
|
|
336
339
|
|
|
@@ -394,7 +397,7 @@ sap.ui.define(["sap/suite/ui/commons/library", "sap/base/Log"], function (librar
|
|
|
394
397
|
* @param {number} iComponentId - Component identifier
|
|
395
398
|
* @private
|
|
396
399
|
*/
|
|
397
|
-
_assignLayersForComponent: function (aComponentNodes, iComponentId) {
|
|
400
|
+
_assignLayersForComponent: function (aComponentNodes, iComponentId, bEnhancedLineRouting) {
|
|
398
401
|
const aQueue = [];
|
|
399
402
|
const mInDegree = {};
|
|
400
403
|
|
|
@@ -464,18 +467,30 @@ sap.ui.define(["sap/suite/ui/commons/library", "sap/base/Log"], function (librar
|
|
|
464
467
|
) {
|
|
465
468
|
const iCurrentLayer = oNode._layoutData.layer;
|
|
466
469
|
let iMaxTargetLayer = -1;
|
|
470
|
+
let iMinTargetLayer = Infinity;
|
|
467
471
|
oNode._layoutData.outgoing.forEach((t) => {
|
|
468
472
|
if (t._layoutData.layer > iMaxTargetLayer) {
|
|
469
473
|
iMaxTargetLayer = t._layoutData.layer;
|
|
470
474
|
}
|
|
475
|
+
if (t._layoutData.layer < iMinTargetLayer) {
|
|
476
|
+
iMinTargetLayer = t._layoutData.layer;
|
|
477
|
+
}
|
|
471
478
|
});
|
|
472
479
|
|
|
473
480
|
const bTargetHasOtherIncoming = oNode._layoutData.outgoing.some(
|
|
474
481
|
(t) => t._layoutData.incoming.filter((n) => n !== oNode).length > 0
|
|
475
482
|
);
|
|
476
483
|
|
|
477
|
-
if
|
|
478
|
-
|
|
484
|
+
// Guard: skip if any target is at or below the proposed layer that would create same-layer edges.
|
|
485
|
+
const iProposedLayer = Math.max(0, iMaxTargetLayer - 1);
|
|
486
|
+
if (bEnhancedLineRouting) {
|
|
487
|
+
if (iMaxTargetLayer > iCurrentLayer && bTargetHasOtherIncoming && iMinTargetLayer > iProposedLayer) {
|
|
488
|
+
oNode._layoutData.layer = iProposedLayer;
|
|
489
|
+
}
|
|
490
|
+
} else {
|
|
491
|
+
if (iMaxTargetLayer > iCurrentLayer && bTargetHasOtherIncoming) {
|
|
492
|
+
oNode._layoutData.layer = iProposedLayer;
|
|
493
|
+
}
|
|
479
494
|
}
|
|
480
495
|
}
|
|
481
496
|
});
|