@sapui5/sap.suite.ui.commons 1.143.0 → 1.145.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 +26 -14
- package/src/sap/suite/ui/commons/CalculationBuilderExpression.js +2 -2
- 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/CalculationBuilderInput.js +5 -5
- 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 -3
- package/src/sap/suite/ui/commons/MicroProcessFlow.js +77 -73
- package/src/sap/suite/ui/commons/MicroProcessFlowItem.js +3 -3
- package/src/sap/suite/ui/commons/Timeline.js +9 -0
- package/src/sap/suite/ui/commons/TimelineNavigator.js +37 -5
- package/src/sap/suite/ui/commons/TimelineRenderer.js +3 -3
- package/src/sap/suite/ui/commons/collaboration/BaseHelperService.js +0 -1
- package/src/sap/suite/ui/commons/collaboration/CollaborationHelper.js +0 -3
- package/src/sap/suite/ui/commons/collaboration/CollaborationManagerService.js +0 -1
- package/src/sap/suite/ui/commons/collaboration/ContactHelper.js +7 -0
- package/src/sap/suite/ui/commons/collaboration/ContactPopover.fragment.xml +4 -1
- package/src/sap/suite/ui/commons/collaboration/MinimalContactPopover.fragment.xml +2 -1
- package/src/sap/suite/ui/commons/collaboration/TeamsHelperService.js +0 -6
- package/src/sap/suite/ui/commons/collaboration/channels/MessageChannel.js +0 -2
- package/src/sap/suite/ui/commons/collaboration/flpplugins/msplugin/Component-preload.js +1 -1
- package/src/sap/suite/ui/commons/collaboration/flpplugins/msplugin/Component.js +1 -2
- 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 +100 -3
- package/src/sap/suite/ui/commons/messagebundle.properties +76 -10
- package/src/sap/suite/ui/commons/messagebundle_ar.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_bg.properties +62 -16
- package/src/sap/suite/ui/commons/messagebundle_ca.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_cnr.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_cs.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_cy.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_da.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_de.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_el.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_en.properties +50 -7
- package/src/sap/suite/ui/commons/messagebundle_en_GB.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_en_US_saprigi.properties +54 -6
- package/src/sap/suite/ui/commons/messagebundle_es.properties +53 -7
- package/src/sap/suite/ui/commons/messagebundle_es_MX.properties +54 -8
- package/src/sap/suite/ui/commons/messagebundle_et.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_fi.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_fr.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_fr_CA.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_hi.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_hr.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_hu.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_id.properties +60 -14
- package/src/sap/suite/ui/commons/messagebundle_it.properties +53 -7
- package/src/sap/suite/ui/commons/messagebundle_iw.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_ja.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_kk.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_ko.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_lt.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_lv.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_mk.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_ms.properties +59 -13
- package/src/sap/suite/ui/commons/messagebundle_nl.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_no.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_pl.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_pt.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_pt_PT.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_ro.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_ru.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_sh.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_sk.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_sl.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_sr.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_sv.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_th.properties +52 -6
- package/src/sap/suite/ui/commons/messagebundle_tr.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_uk.properties +53 -7
- package/src/sap/suite/ui/commons/messagebundle_vi.properties +55 -9
- package/src/sap/suite/ui/commons/messagebundle_zh_CN.properties +51 -5
- package/src/sap/suite/ui/commons/messagebundle_zh_TW.properties +53 -7
- package/src/sap/suite/ui/commons/networkgraph/ElementBase.js +19 -1
- package/src/sap/suite/ui/commons/networkgraph/Graph.js +371 -29
- package/src/sap/suite/ui/commons/networkgraph/GraphRenderer.js +23 -10
- package/src/sap/suite/ui/commons/networkgraph/Group.js +43 -22
- package/src/sap/suite/ui/commons/networkgraph/KeyboardNavigator.js +54 -6
- package/src/sap/suite/ui/commons/networkgraph/Line.js +736 -31
- package/src/sap/suite/ui/commons/networkgraph/Node.js +546 -96
- package/src/sap/suite/ui/commons/networkgraph/Tooltip.js +5 -0
- package/src/sap/suite/ui/commons/networkgraph/layout/NoopLayout.js +28 -5
- package/src/sap/suite/ui/commons/networkgraph/util/ConnectionPathUtils.js +1144 -0
- package/src/sap/suite/ui/commons/networkgraph/util/CreateConnectionPopover.js +374 -0
- package/src/sap/suite/ui/commons/networkgraph/util/DependencyLayoutHelper.js +870 -0
- package/src/sap/suite/ui/commons/networkgraph/util/DragDropManager.js +563 -41
- package/src/sap/suite/ui/commons/networkgraph/util/PortManager.js +573 -0
- 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/NetworkGraph.less +13 -13
- package/src/sap/suite/ui/commons/themes/base/NetworkGroup.less +34 -2
- package/src/sap/suite/ui/commons/themes/base/NetworkLine.less +58 -13
- package/src/sap/suite/ui/commons/themes/base/NetworkNode.less +206 -1
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
*
|
|
3
|
+
SAP UI development toolkit for HTML5 (SAPUI5)
|
|
4
|
+
(c) Copyright 2009-2015 SAP SE. All rights reserved
|
|
5
|
+
|
|
6
|
+
*/
|
|
7
|
+
sap.ui.define(["sap/suite/ui/commons/library", "sap/base/Log"], function (library, Log) {
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const ConnectionType = library.networkgraph.ConnectionType,
|
|
11
|
+
ComponentArrangement = library.networkgraph.ComponentArrangement;
|
|
12
|
+
const DEFAULT_HORIZONTAL_SPACING = 240;
|
|
13
|
+
const DEFAULT_VERTICAL_SPACING = 100;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Utility class for calculating node positions based on dependency relationships.
|
|
17
|
+
* This utility arranges nodes based on their dependency relationships using topological sorting,
|
|
18
|
+
* cycle detection, and layer-based positioning.
|
|
19
|
+
*
|
|
20
|
+
* @namespace sap.suite.ui.commons.networkgraph.util.DependencyLayoutHelper
|
|
21
|
+
* @public
|
|
22
|
+
* @since 1.144
|
|
23
|
+
*/
|
|
24
|
+
const DependencyLayoutHelper = {
|
|
25
|
+
/**
|
|
26
|
+
* WeakMap to store last calculated positions and origin flag for automatic drag detection.
|
|
27
|
+
* Structure: {x: number, y: number, bIsUserPositioned: boolean}
|
|
28
|
+
*/
|
|
29
|
+
_lastCalculated: new WeakMap(),
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Calculates and sets positions for nodes without coordinates based on their dependency relationships.
|
|
33
|
+
* Uses topological sorting (Kahn's algorithm) and DFS-based cycle detection to arrange nodes
|
|
34
|
+
* in layers from left to right, ensuring no overlaps and minimal line crossings.
|
|
35
|
+
*
|
|
36
|
+
* @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
|
|
37
|
+
* @param {object} [mConfig] - Configuration object
|
|
38
|
+
* @param {number} [mConfig.horizontalSpacing=240] - Horizontal spacing between layers
|
|
39
|
+
* @param {number} [mConfig.verticalSpacing=100] - Vertical spacing between nodes
|
|
40
|
+
* @param {sap.suite.ui.commons.networkgraph.ComponentArrangement} [mConfig.componentArrangement=Horizontal] - Component arrangement: "Horizontal" or "Vertical"
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
calculatePositions: function (oGraph, mConfig) {
|
|
44
|
+
if (!oGraph) {
|
|
45
|
+
throw new Error("Graph instance is required");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const config = Object.assign(
|
|
49
|
+
{
|
|
50
|
+
horizontalSpacing: DEFAULT_HORIZONTAL_SPACING,
|
|
51
|
+
verticalSpacing: DEFAULT_VERTICAL_SPACING,
|
|
52
|
+
componentArrangement: ComponentArrangement.Horizontal,
|
|
53
|
+
},
|
|
54
|
+
mConfig || {}
|
|
55
|
+
);
|
|
56
|
+
try {
|
|
57
|
+
this._detectUserChanges(oGraph);
|
|
58
|
+
this._buildDependencyGraph(oGraph);
|
|
59
|
+
const aComponents = this._identifyComponents(oGraph);
|
|
60
|
+
this._processComponents(aComponents, config);
|
|
61
|
+
this._calculateCoordinates(oGraph, config);
|
|
62
|
+
this._adjustHorizontalSpacingForNodeWidths(oGraph, config);
|
|
63
|
+
this._adjustVerticalSpacingForNodeHeights(oGraph, config);
|
|
64
|
+
this._storeFinalPositions(oGraph);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
Log.error("DependencyLayoutHelper error: " + e.message, e);
|
|
67
|
+
throw new Error("DependencyLayoutHelper error: " + e.message);
|
|
68
|
+
} finally {
|
|
69
|
+
if (this._lineMap) {
|
|
70
|
+
this._lineMap.clear();
|
|
71
|
+
this._lineMap = null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Builds the dependency graph structure by analyzing node relationships.
|
|
78
|
+
* Initializes layout data for each node and categorizes connections.
|
|
79
|
+
*
|
|
80
|
+
* @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
_buildDependencyGraph: function (oGraph) {
|
|
84
|
+
const aNodes = oGraph.getNodes();
|
|
85
|
+
const aLines = oGraph.getLines();
|
|
86
|
+
const mNodeMap = new Map();
|
|
87
|
+
|
|
88
|
+
aNodes.forEach((oNode) => {
|
|
89
|
+
mNodeMap.set(oNode.getKey(), oNode);
|
|
90
|
+
const fX = oNode.getX();
|
|
91
|
+
const fY = oNode.getY();
|
|
92
|
+
const oLastPos = this._lastCalculated.get(oNode);
|
|
93
|
+
// Only preserve coordinates if they were original or user-dragged
|
|
94
|
+
const bShouldPreserve = oLastPos?.bIsUserPositioned;
|
|
95
|
+
|
|
96
|
+
oNode._layoutData = {
|
|
97
|
+
layer: -1,
|
|
98
|
+
position: -1,
|
|
99
|
+
incoming: [],
|
|
100
|
+
outgoing: [],
|
|
101
|
+
parallelNodes: [],
|
|
102
|
+
backwardEdges: [],
|
|
103
|
+
backwardEdgeLines: [],
|
|
104
|
+
isBackwardSource: false,
|
|
105
|
+
bShouldPreserve,
|
|
106
|
+
userX: fX,
|
|
107
|
+
userY: fY
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (this._lineMap) {
|
|
112
|
+
this._lineMap.clear();
|
|
113
|
+
}
|
|
114
|
+
// Store line references for backward edge detection
|
|
115
|
+
this._lineMap = new Map();
|
|
116
|
+
|
|
117
|
+
aLines.forEach((oLine) => {
|
|
118
|
+
const oFromNode = mNodeMap.get(oLine.getFrom());
|
|
119
|
+
const oToNode = mNodeMap.get(oLine.getTo());
|
|
120
|
+
|
|
121
|
+
if (oFromNode && oToNode) {
|
|
122
|
+
const sConnectionType = oLine.getConnectionType();
|
|
123
|
+
const sFromKey = oFromNode.getKey();
|
|
124
|
+
const sToKey = oToNode.getKey();
|
|
125
|
+
|
|
126
|
+
if (!this._lineMap.has(sFromKey)) {
|
|
127
|
+
this._lineMap.set(sFromKey, new Map());
|
|
128
|
+
}
|
|
129
|
+
this._lineMap.get(sFromKey).set(sToKey, oLine);
|
|
130
|
+
|
|
131
|
+
switch (sConnectionType) {
|
|
132
|
+
case ConnectionType.RightToRight:
|
|
133
|
+
case ConnectionType.LeftToLeft:
|
|
134
|
+
oFromNode._layoutData.parallelNodes.push(oToNode);
|
|
135
|
+
oToNode._layoutData.parallelNodes.push(oFromNode);
|
|
136
|
+
break;
|
|
137
|
+
case ConnectionType.RightToLeft:
|
|
138
|
+
case ConnectionType.LeftToRight:
|
|
139
|
+
default:
|
|
140
|
+
oFromNode._layoutData.outgoing.push(oToNode);
|
|
141
|
+
oToNode._layoutData.incoming.push(oFromNode);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
this._detectAndRemoveBackwardEdges(aNodes);
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Detects and temporarily removes backward edges that create cycles using DFS.
|
|
152
|
+
* Identifies edges that point to already visited ancestors in the DFS traversal.
|
|
153
|
+
*
|
|
154
|
+
* @param {sap.suite.ui.commons.networkgraph.Node[]} aNodes - Array of graph nodes
|
|
155
|
+
* @private
|
|
156
|
+
*/
|
|
157
|
+
_detectAndRemoveBackwardEdges: function (aNodes) {
|
|
158
|
+
const mVisiting = {};
|
|
159
|
+
const mVisited = {};
|
|
160
|
+
|
|
161
|
+
const dfsVisit = (oNode) => {
|
|
162
|
+
const sKey = oNode.getKey();
|
|
163
|
+
if (mVisited[sKey]) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
mVisiting[sKey] = true;
|
|
168
|
+
|
|
169
|
+
const aRemainingOutgoing = [];
|
|
170
|
+
oNode._layoutData.outgoing.forEach((oTargetNode) => {
|
|
171
|
+
const sTargetKey = oTargetNode.getKey();
|
|
172
|
+
|
|
173
|
+
if (mVisiting[sTargetKey]) {
|
|
174
|
+
// Backward edge detected
|
|
175
|
+
oNode._layoutData.isBackwardSource = true;
|
|
176
|
+
oNode._layoutData.backwardEdges.push(oTargetNode);
|
|
177
|
+
|
|
178
|
+
const oLine = this._lineMap.get(sKey)?.get(sTargetKey);
|
|
179
|
+
if (oLine) {
|
|
180
|
+
oNode._layoutData.backwardEdgeLines.push(oLine);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const idx = oTargetNode._layoutData.incoming.indexOf(oNode);
|
|
184
|
+
if (idx > -1) {
|
|
185
|
+
oTargetNode._layoutData.incoming.splice(idx, 1);
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
aRemainingOutgoing.push(oTargetNode);
|
|
189
|
+
if (!mVisited[sTargetKey]) {
|
|
190
|
+
dfsVisit(oTargetNode);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
oNode._layoutData.outgoing = aRemainingOutgoing;
|
|
196
|
+
mVisiting[sKey] = false;
|
|
197
|
+
mVisited[sKey] = true;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
aNodes.forEach((oNode) => {
|
|
201
|
+
if (!mVisited[oNode.getKey()]) {
|
|
202
|
+
dfsVisit(oNode);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Identifies separate connected components in the graph using BFS.
|
|
209
|
+
* A component is a group of nodes that are connected by dependencies.
|
|
210
|
+
*
|
|
211
|
+
* @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
|
|
212
|
+
* @returns {Array<sap.suite.ui.commons.networkgraph.Node[]>} Array of components (groups of connected nodes)
|
|
213
|
+
* @private
|
|
214
|
+
*/
|
|
215
|
+
_identifyComponents: function (oGraph) {
|
|
216
|
+
const aNodes = oGraph.getNodes();
|
|
217
|
+
const mVisited = {};
|
|
218
|
+
const aComponents = [];
|
|
219
|
+
|
|
220
|
+
const addConnectedNodesToQueue = (oNode, aQueue) => {
|
|
221
|
+
const aConnectionTypes = ["incoming", "outgoing", "parallelNodes"];
|
|
222
|
+
aConnectionTypes.forEach((sType) => {
|
|
223
|
+
oNode._layoutData[sType].forEach((oConnectedNode) => {
|
|
224
|
+
const sKey = oConnectedNode.getKey();
|
|
225
|
+
if (!mVisited[sKey]) {
|
|
226
|
+
aQueue.push(oConnectedNode);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
aNodes.forEach((oNode) => {
|
|
233
|
+
const sKey = oNode.getKey();
|
|
234
|
+
if (mVisited[sKey]) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const aComponent = [];
|
|
239
|
+
const aQueue = [oNode];
|
|
240
|
+
|
|
241
|
+
while (aQueue.length > 0) {
|
|
242
|
+
const oCurrent = aQueue.shift();
|
|
243
|
+
const sCurrentKey = oCurrent.getKey();
|
|
244
|
+
|
|
245
|
+
if (!mVisited[sCurrentKey]) {
|
|
246
|
+
mVisited[sCurrentKey] = true;
|
|
247
|
+
aComponent.push(oCurrent);
|
|
248
|
+
addConnectedNodesToQueue(oCurrent, aQueue);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
aComponents.push(aComponent);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return aComponents;
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Processes all components: separates isolated nodes and arranges connected components.
|
|
260
|
+
*
|
|
261
|
+
* @param {Array<sap.suite.ui.commons.networkgraph.Node[]>} aComponents - Array of components (groups of connected nodes)
|
|
262
|
+
* @param {object} config - Configuration object
|
|
263
|
+
* @private
|
|
264
|
+
*/
|
|
265
|
+
_processComponents: function (aComponents, { componentArrangement }) {
|
|
266
|
+
const aIsolatedNodes = [];
|
|
267
|
+
const aConnectedComponents = [];
|
|
268
|
+
|
|
269
|
+
aComponents.forEach((aComponentNodes) => {
|
|
270
|
+
const bIsIsolated =
|
|
271
|
+
aComponentNodes.length === 1 &&
|
|
272
|
+
aComponentNodes[0]._layoutData.incoming.length === 0 &&
|
|
273
|
+
aComponentNodes[0]._layoutData.outgoing.length === 0 &&
|
|
274
|
+
!aComponentNodes[0]._layoutData.bShouldPreserve;
|
|
275
|
+
|
|
276
|
+
if (bIsIsolated) {
|
|
277
|
+
aIsolatedNodes.push(aComponentNodes[0]);
|
|
278
|
+
} else {
|
|
279
|
+
aConnectedComponents.push(aComponentNodes);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
const bSideBySide = componentArrangement !== ComponentArrangement.Vertical;
|
|
283
|
+
let iMaxVerticalPosition = 0;
|
|
284
|
+
let iMaxLayerAcrossAll = 0;
|
|
285
|
+
|
|
286
|
+
aConnectedComponents.forEach((aComponentNodes, iCompIndex) => {
|
|
287
|
+
this._assignLayersForComponent(aComponentNodes, iCompIndex);
|
|
288
|
+
this._adjustParallelLayersForComponent(aComponentNodes);
|
|
289
|
+
this._assignVerticalPositionsForComponent(aComponentNodes);
|
|
290
|
+
|
|
291
|
+
if (bSideBySide) {
|
|
292
|
+
aComponentNodes.forEach((oNode) => {
|
|
293
|
+
oNode._layoutData.layer += iMaxLayerAcrossAll;
|
|
294
|
+
oNode._layoutData.componentOffset = 0;
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
let iComponentMaxLayer = -1;
|
|
298
|
+
aComponentNodes.forEach((n) => {
|
|
299
|
+
if (n._layoutData.layer > iComponentMaxLayer) {
|
|
300
|
+
iComponentMaxLayer = n._layoutData.layer;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
iMaxLayerAcrossAll = iComponentMaxLayer + 1;
|
|
304
|
+
} else {
|
|
305
|
+
aComponentNodes.forEach((oNode) => {
|
|
306
|
+
oNode._layoutData.position += iMaxVerticalPosition;
|
|
307
|
+
oNode._layoutData.componentOffset = 0;
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
let iComponentMaxPosition = -1;
|
|
311
|
+
aComponentNodes.forEach((n) => {
|
|
312
|
+
if (n._layoutData.position > iComponentMaxPosition) {
|
|
313
|
+
iComponentMaxPosition = n._layoutData.position;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
iMaxVerticalPosition = iComponentMaxPosition + 1;
|
|
317
|
+
|
|
318
|
+
let iComponentMaxLayer = -1;
|
|
319
|
+
aComponentNodes.forEach((n) => {
|
|
320
|
+
if (n._layoutData.layer > iComponentMaxLayer) {
|
|
321
|
+
iComponentMaxLayer = n._layoutData.layer;
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
iMaxLayerAcrossAll = Math.max(iMaxLayerAcrossAll, iComponentMaxLayer);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
if (aIsolatedNodes.length > 0) {
|
|
329
|
+
const iRightSideOffset = iMaxLayerAcrossAll + 1;
|
|
330
|
+
|
|
331
|
+
aIsolatedNodes.forEach((oNode, idx) => {
|
|
332
|
+
if (bSideBySide) {
|
|
333
|
+
oNode._layoutData.layer = Math.floor(idx / 2);
|
|
334
|
+
oNode._layoutData.position = idx % 2;
|
|
335
|
+
oNode._layoutData.componentOffset = iMaxLayerAcrossAll;
|
|
336
|
+
} else {
|
|
337
|
+
oNode._layoutData.layer = idx % 2;
|
|
338
|
+
oNode._layoutData.position = Math.floor(idx / 2);
|
|
339
|
+
oNode._layoutData.componentOffset = iRightSideOffset;
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Assigns layer numbers to nodes in a component (group of connected nodes) using Kahn's algorithm (topological sorting).
|
|
347
|
+
*
|
|
348
|
+
* @param {sap.suite.ui.commons.networkgraph.Node[]} aComponentNodes - Array of nodes belonging to the same component
|
|
349
|
+
* @param {number} iComponentId - Component identifier
|
|
350
|
+
* @private
|
|
351
|
+
*/
|
|
352
|
+
_assignLayersForComponent: function (aComponentNodes, iComponentId) {
|
|
353
|
+
const aQueue = [];
|
|
354
|
+
const mInDegree = {};
|
|
355
|
+
|
|
356
|
+
aComponentNodes.forEach((oNode) => {
|
|
357
|
+
oNode._layoutData.componentId = iComponentId;
|
|
358
|
+
const iInDegree = oNode._layoutData.incoming.length;
|
|
359
|
+
mInDegree[oNode.getKey()] = iInDegree;
|
|
360
|
+
|
|
361
|
+
if (iInDegree === 0) {
|
|
362
|
+
oNode._layoutData.layer = 0;
|
|
363
|
+
aQueue.push(oNode);
|
|
364
|
+
|
|
365
|
+
oNode._layoutData.parallelNodes.forEach((oParallelNode) => {
|
|
366
|
+
if (oParallelNode._layoutData.layer === -1) {
|
|
367
|
+
oParallelNode._layoutData.layer = 0;
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
while (aQueue.length > 0) {
|
|
374
|
+
const oCurrentNode = aQueue.shift();
|
|
375
|
+
|
|
376
|
+
oCurrentNode._layoutData.outgoing.forEach((oTargetNode) => {
|
|
377
|
+
const sTargetKey = oTargetNode.getKey();
|
|
378
|
+
mInDegree[sTargetKey]--;
|
|
379
|
+
|
|
380
|
+
oTargetNode._layoutData.layer = Math.max(
|
|
381
|
+
oTargetNode._layoutData.layer,
|
|
382
|
+
oCurrentNode._layoutData.layer + 1
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
if (mInDegree[sTargetKey] === 0) {
|
|
386
|
+
aQueue.push(oTargetNode);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const aUnprocessedNodes = aComponentNodes.filter(
|
|
392
|
+
(n) => n._layoutData.layer === -1
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
if (aUnprocessedNodes.length > 0) {
|
|
396
|
+
const aProcessedNodes = aComponentNodes.filter(
|
|
397
|
+
(n) => n._layoutData.layer >= 0
|
|
398
|
+
);
|
|
399
|
+
let iMinLayer = 0;
|
|
400
|
+
if (aProcessedNodes.length > 0) {
|
|
401
|
+
let iMaxLayer = -1;
|
|
402
|
+
aProcessedNodes.forEach((n) => {
|
|
403
|
+
if (n._layoutData.layer > iMaxLayer) {
|
|
404
|
+
iMaxLayer = n._layoutData.layer;
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
iMinLayer = iMaxLayer + 1;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
aUnprocessedNodes.forEach((oNode) => {
|
|
411
|
+
oNode._layoutData.layer = iMinLayer;
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
aComponentNodes.forEach((oNode) => {
|
|
416
|
+
if (
|
|
417
|
+
oNode._layoutData.incoming.length === 0 &&
|
|
418
|
+
oNode._layoutData.outgoing.length > 0
|
|
419
|
+
) {
|
|
420
|
+
const iCurrentLayer = oNode._layoutData.layer;
|
|
421
|
+
let iMaxTargetLayer = -1;
|
|
422
|
+
oNode._layoutData.outgoing.forEach((t) => {
|
|
423
|
+
if (t._layoutData.layer > iMaxTargetLayer) {
|
|
424
|
+
iMaxTargetLayer = t._layoutData.layer;
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
const bTargetHasOtherIncoming = oNode._layoutData.outgoing.some(
|
|
429
|
+
(t) => t._layoutData.incoming.filter((n) => n !== oNode).length > 0
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
if (iMaxTargetLayer > iCurrentLayer && bTargetHasOtherIncoming) {
|
|
433
|
+
oNode._layoutData.layer = Math.max(0, iMaxTargetLayer - 1);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Ensures parallel nodes (same-layer dependencies) stay in the same layer.
|
|
441
|
+
*
|
|
442
|
+
* @param {sap.suite.ui.commons.networkgraph.Node[]} aComponentNodes - Array of nodes belonging to the same component (group of connected nodes)
|
|
443
|
+
* @private
|
|
444
|
+
*/
|
|
445
|
+
_adjustParallelLayersForComponent: function (aComponentNodes) {
|
|
446
|
+
const ensureParallelNodesInSameLayer = (oNode) => {
|
|
447
|
+
oNode._layoutData.parallelNodes.forEach((oParallelNode) => {
|
|
448
|
+
if (
|
|
449
|
+
oParallelNode._layoutData.layer === -1 ||
|
|
450
|
+
oParallelNode._layoutData.layer > oNode._layoutData.layer
|
|
451
|
+
) {
|
|
452
|
+
oParallelNode._layoutData.layer = oNode._layoutData.layer;
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
aComponentNodes.forEach((oNode) => {
|
|
458
|
+
if (oNode._layoutData.layer >= 0) {
|
|
459
|
+
ensureParallelNodesInSameLayer(oNode);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
let bChanged = true;
|
|
464
|
+
while (bChanged) {
|
|
465
|
+
bChanged = false;
|
|
466
|
+
|
|
467
|
+
aComponentNodes.forEach((oNode) => {
|
|
468
|
+
oNode._layoutData.parallelNodes.forEach((oParallelNode) => {
|
|
469
|
+
if (oNode._layoutData.layer !== oParallelNode._layoutData.layer) {
|
|
470
|
+
const iMinLayer = Math.min(
|
|
471
|
+
oNode._layoutData.layer,
|
|
472
|
+
oParallelNode._layoutData.layer
|
|
473
|
+
);
|
|
474
|
+
if (
|
|
475
|
+
oNode._layoutData.layer !== iMinLayer ||
|
|
476
|
+
oParallelNode._layoutData.layer !== iMinLayer
|
|
477
|
+
) {
|
|
478
|
+
oNode._layoutData.layer = iMinLayer;
|
|
479
|
+
oParallelNode._layoutData.layer = iMinLayer;
|
|
480
|
+
bChanged = true;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Assigns vertical positions within layers, inheriting parent positions where possible.
|
|
490
|
+
*
|
|
491
|
+
* @param {sap.suite.ui.commons.networkgraph.Node[]} aComponentNodes - Array of nodes belonging to the same component (group of connected nodes)
|
|
492
|
+
* @private
|
|
493
|
+
*/
|
|
494
|
+
_assignVerticalPositionsForComponent: function (aComponentNodes) {
|
|
495
|
+
const mLayers = this._groupNodesByLayer(aComponentNodes);
|
|
496
|
+
const aLayerKeys = Object.keys(mLayers)
|
|
497
|
+
.map((k) => parseInt(k))
|
|
498
|
+
.sort((a, b) => a - b);
|
|
499
|
+
|
|
500
|
+
aLayerKeys.forEach((iLayer) => {
|
|
501
|
+
if (iLayer > 0) {
|
|
502
|
+
this._positionLayerNodes(mLayers, iLayer);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
if (mLayers[0]) {
|
|
507
|
+
const mUsedPositions = {};
|
|
508
|
+
let iNextAvailablePosition = 0;
|
|
509
|
+
|
|
510
|
+
const updateChainPositions = (
|
|
511
|
+
oNode,
|
|
512
|
+
iNewPosition,
|
|
513
|
+
mVisitedNodes = {}
|
|
514
|
+
) => {
|
|
515
|
+
if (mVisitedNodes[oNode.getKey()]) {
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
mVisitedNodes[oNode.getKey()] = true;
|
|
519
|
+
|
|
520
|
+
oNode._layoutData.position = iNewPosition;
|
|
521
|
+
|
|
522
|
+
oNode._layoutData.outgoing.forEach((oSuccessor) => {
|
|
523
|
+
const aSuccessorIncoming = oSuccessor._layoutData.incoming;
|
|
524
|
+
if (
|
|
525
|
+
aSuccessorIncoming.length === 1 &&
|
|
526
|
+
aSuccessorIncoming[0] === oNode
|
|
527
|
+
) {
|
|
528
|
+
updateChainPositions(oSuccessor, iNewPosition, mVisitedNodes);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const aRootNodes = mLayers[0].sort((a, b) => {
|
|
534
|
+
const aHasTarget = a._layoutData.outgoing.length > 0;
|
|
535
|
+
const bHasTarget = b._layoutData.outgoing.length > 0;
|
|
536
|
+
|
|
537
|
+
if (!aHasTarget && !bHasTarget) return 0;
|
|
538
|
+
if (!aHasTarget) return 1;
|
|
539
|
+
if (!bHasTarget) return -1;
|
|
540
|
+
|
|
541
|
+
const aTargetPos = a._layoutData.outgoing[0]._layoutData.position;
|
|
542
|
+
const bTargetPos = b._layoutData.outgoing[0]._layoutData.position;
|
|
543
|
+
return aTargetPos - bTargetPos;
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
aRootNodes.forEach((oRootNode) => {
|
|
547
|
+
if (oRootNode._layoutData.outgoing.length > 0) {
|
|
548
|
+
const oFirstTarget = oRootNode._layoutData.outgoing[0];
|
|
549
|
+
const iPreferredPosition = oFirstTarget._layoutData.position;
|
|
550
|
+
|
|
551
|
+
if (!mUsedPositions[iPreferredPosition]) {
|
|
552
|
+
oRootNode._layoutData.position = iPreferredPosition;
|
|
553
|
+
mUsedPositions[iPreferredPosition] = true;
|
|
554
|
+
iNextAvailablePosition = Math.max(
|
|
555
|
+
iNextAvailablePosition,
|
|
556
|
+
iPreferredPosition + 1
|
|
557
|
+
);
|
|
558
|
+
} else {
|
|
559
|
+
const aTargetIncoming = oFirstTarget._layoutData.incoming;
|
|
560
|
+
if (
|
|
561
|
+
aTargetIncoming.length === 1 &&
|
|
562
|
+
aTargetIncoming[0] === oRootNode
|
|
563
|
+
) {
|
|
564
|
+
let iNewPosition = iPreferredPosition;
|
|
565
|
+
while (mUsedPositions[iNewPosition]) {
|
|
566
|
+
iNewPosition++;
|
|
567
|
+
}
|
|
568
|
+
updateChainPositions(oFirstTarget, iNewPosition);
|
|
569
|
+
oRootNode._layoutData.position = iNewPosition;
|
|
570
|
+
mUsedPositions[iNewPosition] = true;
|
|
571
|
+
iNextAvailablePosition = Math.max(
|
|
572
|
+
iNextAvailablePosition,
|
|
573
|
+
iNewPosition + 1
|
|
574
|
+
);
|
|
575
|
+
} else {
|
|
576
|
+
let iAdjacentPosition = iPreferredPosition + 1;
|
|
577
|
+
while (mUsedPositions[iAdjacentPosition]) {
|
|
578
|
+
iAdjacentPosition++;
|
|
579
|
+
}
|
|
580
|
+
oRootNode._layoutData.position = iAdjacentPosition;
|
|
581
|
+
mUsedPositions[iAdjacentPosition] = true;
|
|
582
|
+
iNextAvailablePosition = Math.max(
|
|
583
|
+
iNextAvailablePosition,
|
|
584
|
+
iAdjacentPosition + 1
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
} else {
|
|
589
|
+
while (mUsedPositions[iNextAvailablePosition]) {
|
|
590
|
+
iNextAvailablePosition++;
|
|
591
|
+
}
|
|
592
|
+
oRootNode._layoutData.position = iNextAvailablePosition;
|
|
593
|
+
mUsedPositions[iNextAvailablePosition] = true;
|
|
594
|
+
iNextAvailablePosition++;
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Groups nodes by their assigned layer.
|
|
602
|
+
*
|
|
603
|
+
* @param {sap.suite.ui.commons.networkgraph.Node[]} aComponentNodes - Array of nodes belonging to the same component (group of connected nodes)
|
|
604
|
+
* @returns {object} Map of layer number to array of nodes
|
|
605
|
+
* @private
|
|
606
|
+
*/
|
|
607
|
+
_groupNodesByLayer: function (aComponentNodes) {
|
|
608
|
+
const mLayers = {};
|
|
609
|
+
|
|
610
|
+
aComponentNodes.forEach((oNode) => {
|
|
611
|
+
const iLayer = oNode._layoutData.layer;
|
|
612
|
+
if (!mLayers[iLayer]) {
|
|
613
|
+
mLayers[iLayer] = [];
|
|
614
|
+
}
|
|
615
|
+
mLayers[iLayer].push(oNode);
|
|
616
|
+
oNode._layoutData.position = -1;
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
return mLayers;
|
|
620
|
+
},
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Positions nodes within a specific layer based on their predecessors.
|
|
624
|
+
*
|
|
625
|
+
* @param {object} mLayers - Map of layer number to array of nodes
|
|
626
|
+
* @param {number} iLayer - The layer to position
|
|
627
|
+
* @private
|
|
628
|
+
*/
|
|
629
|
+
_positionLayerNodes: function (mLayers, iLayer) {
|
|
630
|
+
const aLayerNodes = mLayers[iLayer];
|
|
631
|
+
const mUsedPositions = {};
|
|
632
|
+
let iNextAvailablePosition = 0;
|
|
633
|
+
|
|
634
|
+
const aPrevLayerNodes = (mLayers[iLayer - 1] || [])
|
|
635
|
+
.filter((oNode) => oNode._layoutData.position >= 0)
|
|
636
|
+
.sort((a, b) => a._layoutData.position - b._layoutData.position);
|
|
637
|
+
|
|
638
|
+
aPrevLayerNodes.forEach((oPrevNode) => {
|
|
639
|
+
const aSuccessors = oPrevNode._layoutData.outgoing
|
|
640
|
+
.filter((oNode) => oNode._layoutData.layer === iLayer)
|
|
641
|
+
.sort((a, b) => a.getKey().localeCompare(b.getKey()));
|
|
642
|
+
|
|
643
|
+
if (aSuccessors.length === 0) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const iPreferredPosition = oPrevNode._layoutData.position;
|
|
648
|
+
if (
|
|
649
|
+
!mUsedPositions[iPreferredPosition] &&
|
|
650
|
+
aSuccessors[0]._layoutData.position === -1
|
|
651
|
+
) {
|
|
652
|
+
aSuccessors[0]._layoutData.position = iPreferredPosition;
|
|
653
|
+
mUsedPositions[iPreferredPosition] = true;
|
|
654
|
+
iNextAvailablePosition = Math.max(
|
|
655
|
+
iNextAvailablePosition,
|
|
656
|
+
iPreferredPosition + 1
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
aSuccessors.forEach((oNode) => {
|
|
661
|
+
if (oNode._layoutData.position === -1) {
|
|
662
|
+
while (mUsedPositions[iNextAvailablePosition]) {
|
|
663
|
+
iNextAvailablePosition++;
|
|
664
|
+
}
|
|
665
|
+
oNode._layoutData.position = iNextAvailablePosition;
|
|
666
|
+
mUsedPositions[iNextAvailablePosition] = true;
|
|
667
|
+
iNextAvailablePosition++;
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
aLayerNodes.forEach((oNode) => {
|
|
673
|
+
if (oNode._layoutData.position === -1) {
|
|
674
|
+
while (mUsedPositions[iNextAvailablePosition]) {
|
|
675
|
+
iNextAvailablePosition++;
|
|
676
|
+
}
|
|
677
|
+
oNode._layoutData.position = iNextAvailablePosition;
|
|
678
|
+
mUsedPositions[iNextAvailablePosition] = true;
|
|
679
|
+
iNextAvailablePosition++;
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
},
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Calculates final X and Y coordinates for all nodes based on layer and position.
|
|
686
|
+
*
|
|
687
|
+
* @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
|
|
688
|
+
* @param {object} config - Configuration with spacing values
|
|
689
|
+
* @private
|
|
690
|
+
*/
|
|
691
|
+
_calculateCoordinates: function (oGraph, { horizontalSpacing, verticalSpacing }) {
|
|
692
|
+
const aNodes = oGraph.getNodes();
|
|
693
|
+
const iHSpacing = horizontalSpacing;
|
|
694
|
+
const iVSpacing = verticalSpacing;
|
|
695
|
+
|
|
696
|
+
aNodes.forEach((oNode) => {
|
|
697
|
+
const iComponentOffset = oNode._layoutData.componentOffset || 0;
|
|
698
|
+
oNode.setX(
|
|
699
|
+
iHSpacing + (oNode._layoutData.layer + iComponentOffset) * iHSpacing
|
|
700
|
+
);
|
|
701
|
+
oNode.setY(iVSpacing + oNode._layoutData.position * iVSpacing);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
aNodes.forEach((oNode) => {
|
|
705
|
+
if (oNode._layoutData.bShouldPreserve) {
|
|
706
|
+
const { userX, userY } = oNode._layoutData;
|
|
707
|
+
oNode.setX(userX);
|
|
708
|
+
oNode.setY(userY);
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
},
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Adjusts horizontal spacing to accommodate nodes with varying widths.
|
|
715
|
+
*
|
|
716
|
+
* @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
|
|
717
|
+
* @private
|
|
718
|
+
*/
|
|
719
|
+
_adjustHorizontalSpacingForNodeWidths: function (oGraph) {
|
|
720
|
+
const aNodes = oGraph.getNodes();
|
|
721
|
+
const mLayerWidths = {};
|
|
722
|
+
let iMaxLayer = -1;
|
|
723
|
+
|
|
724
|
+
aNodes.forEach((oNode) => {
|
|
725
|
+
if (!oNode._layoutData.bShouldPreserve) {
|
|
726
|
+
const iLayer =
|
|
727
|
+
oNode._layoutData.layer + (oNode._layoutData.componentOffset || 0);
|
|
728
|
+
iMaxLayer = Math.max(iMaxLayer, iLayer);
|
|
729
|
+
|
|
730
|
+
if (oNode._iWidth) {
|
|
731
|
+
const iWidth = oNode._iWidth;
|
|
732
|
+
if (!mLayerWidths[iLayer] || mLayerWidths[iLayer] < iWidth) {
|
|
733
|
+
mLayerWidths[iLayer] = iWidth;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
const mLayerOffsets = {};
|
|
740
|
+
let iCumulativeOffset = 0;
|
|
741
|
+
|
|
742
|
+
for (let iLayer = 0; iLayer <= iMaxLayer; iLayer++) {
|
|
743
|
+
mLayerOffsets[iLayer] = iCumulativeOffset;
|
|
744
|
+
const iActualWidth = mLayerWidths[iLayer];
|
|
745
|
+
if (iActualWidth) {
|
|
746
|
+
iCumulativeOffset += iActualWidth;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
aNodes.forEach((oNode) => {
|
|
751
|
+
if (!oNode._layoutData.bShouldPreserve) {
|
|
752
|
+
const iLayer =
|
|
753
|
+
oNode._layoutData.layer + (oNode._layoutData.componentOffset || 0);
|
|
754
|
+
const iOffset = mLayerOffsets[iLayer] || 0;
|
|
755
|
+
oNode.setX(oNode.getX() + iOffset);
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
},
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Adjusts vertical spacing to accommodate nodes with varying heights.
|
|
762
|
+
*
|
|
763
|
+
* @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
|
|
764
|
+
* @private
|
|
765
|
+
*/
|
|
766
|
+
_adjustVerticalSpacingForNodeHeights: function (oGraph) {
|
|
767
|
+
const aNodes = oGraph.getNodes();
|
|
768
|
+
const mPositionHeights = {};
|
|
769
|
+
const mAllPositions = {};
|
|
770
|
+
|
|
771
|
+
aNodes.forEach((oNode) => {
|
|
772
|
+
if (!oNode._layoutData.bShouldPreserve) {
|
|
773
|
+
const iPosition = oNode._layoutData.position;
|
|
774
|
+
mAllPositions[iPosition] = true;
|
|
775
|
+
|
|
776
|
+
if (oNode._iHeight) {
|
|
777
|
+
const iHeight = oNode._iHeight;
|
|
778
|
+
if (
|
|
779
|
+
!mPositionHeights[iPosition] ||
|
|
780
|
+
mPositionHeights[iPosition] < iHeight
|
|
781
|
+
) {
|
|
782
|
+
mPositionHeights[iPosition] = iHeight;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
const aAllPositionKeys = Object.keys(mAllPositions)
|
|
789
|
+
.map((k) => parseInt(k))
|
|
790
|
+
.sort((a, b) => a - b);
|
|
791
|
+
const mPositionOffsets = {};
|
|
792
|
+
let iCumulativeOffset = 0;
|
|
793
|
+
|
|
794
|
+
aAllPositionKeys.forEach((iPosition) => {
|
|
795
|
+
mPositionOffsets[iPosition] = iCumulativeOffset;
|
|
796
|
+
const iActualHeight = mPositionHeights[iPosition];
|
|
797
|
+
if (iActualHeight) {
|
|
798
|
+
iCumulativeOffset += iActualHeight;
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
aNodes.forEach((oNode) => {
|
|
803
|
+
if (!oNode._layoutData.bShouldPreserve) {
|
|
804
|
+
const iPosition = oNode._layoutData.position;
|
|
805
|
+
const iOffset = mPositionOffsets[iPosition] || 0;
|
|
806
|
+
oNode.setY(oNode.getY() + iOffset);
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
},
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Stores final calculated positions for automatic drag detection on next call.
|
|
813
|
+
* Only stores positions for nodes that were calculated (not original/dragged).
|
|
814
|
+
*
|
|
815
|
+
* @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
|
|
816
|
+
* @private
|
|
817
|
+
*/
|
|
818
|
+
_storeFinalPositions: function (oGraph) {
|
|
819
|
+
const aNodes = oGraph.getNodes();
|
|
820
|
+
|
|
821
|
+
aNodes.forEach((oNode) => {
|
|
822
|
+
if (!oNode._layoutData.bShouldPreserve) {
|
|
823
|
+
this._lastCalculated.set(oNode, {
|
|
824
|
+
x: oNode.getX(),
|
|
825
|
+
y: oNode.getY(),
|
|
826
|
+
bIsUserPositioned: false
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
},
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Detects user-made position changes by comparing current positions with last calculated positions.
|
|
834
|
+
* Marks nodes with original coordinates and tracks drag operations.
|
|
835
|
+
*
|
|
836
|
+
* @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
|
|
837
|
+
* @private
|
|
838
|
+
*/
|
|
839
|
+
_detectUserChanges: function (oGraph) {
|
|
840
|
+
const aNodes = oGraph.getNodes();
|
|
841
|
+
|
|
842
|
+
aNodes.forEach((oNode) => {
|
|
843
|
+
const fX = oNode.getX();
|
|
844
|
+
const fY = oNode.getY();
|
|
845
|
+
// Constraint: Nodes with coordinates (0,0) or negative values are treated as unpositioned.
|
|
846
|
+
// For origin positioning, use (0.1, 0.1) or (1, 1).
|
|
847
|
+
const bHasCoordinates = fX !== undefined && fY !== undefined && (fX !== 0 || fY !== 0);
|
|
848
|
+
const oLastPos = this._lastCalculated.get(oNode);
|
|
849
|
+
|
|
850
|
+
if (!oLastPos && bHasCoordinates) {
|
|
851
|
+
// First time seeing this node with coordinates - mark as original
|
|
852
|
+
this._lastCalculated.set(oNode, {
|
|
853
|
+
x: fX,
|
|
854
|
+
y: fY,
|
|
855
|
+
bIsUserPositioned: true
|
|
856
|
+
});
|
|
857
|
+
} else if (oLastPos) {
|
|
858
|
+
// Check if user dragged it
|
|
859
|
+
if (fX !== oLastPos.x || fY !== oLastPos.y) {
|
|
860
|
+
oLastPos.x = fX;
|
|
861
|
+
oLastPos.y = fY;
|
|
862
|
+
oLastPos.bIsUserPositioned = true;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
},
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
return DependencyLayoutHelper;
|
|
870
|
+
});
|