@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,1144 @@
|
|
|
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([
|
|
8
|
+
"sap/suite/ui/commons/library"
|
|
9
|
+
], function (library) {
|
|
10
|
+
"use strict";
|
|
11
|
+
const ConnectionType = library.networkgraph.ConnectionType;
|
|
12
|
+
const GRID_SIZE = 54; // Size of each grid cell
|
|
13
|
+
const MIN_EDGE_LENGTH = 44; // Minimum length of an edge to avoid too short lines
|
|
14
|
+
/**
|
|
15
|
+
* Utility class for calculating connection paths between nodes in network graphs.
|
|
16
|
+
* This utility provides a method to normalize lines and calculate path coordinates
|
|
17
|
+
* based on different connection types.
|
|
18
|
+
*
|
|
19
|
+
* @namespace sap.suite.ui.commons.networkgraph.util.ConnectionPathUtils
|
|
20
|
+
* @public
|
|
21
|
+
* @since 1.144
|
|
22
|
+
*/
|
|
23
|
+
var ConnectionPathUtils = {
|
|
24
|
+
/**
|
|
25
|
+
* Normalizes lines in a network graph by calculating path coordinates
|
|
26
|
+
* between source and target nodes based on connection types.
|
|
27
|
+
*
|
|
28
|
+
* @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The network graph instance
|
|
29
|
+
* @param {object} [mConfig] - Configuration object
|
|
30
|
+
* @param {number} [mConfig.gridSize=50] - Size of each grid cell
|
|
31
|
+
* @param {number} [mConfig.minEdgeLength=20] - Minimum length of an edge
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
normalizeLines: function (oGraph, mConfig) {
|
|
35
|
+
const config = Object.assign({
|
|
36
|
+
gridSize: GRID_SIZE,
|
|
37
|
+
minEdgeLength: MIN_EDGE_LENGTH
|
|
38
|
+
}, mConfig || {});
|
|
39
|
+
|
|
40
|
+
// Enable multiple directed arrows feature when using ConnectionPathUtils
|
|
41
|
+
oGraph._enableMultipleDirectedArrows = true;
|
|
42
|
+
|
|
43
|
+
// Iterate over all lines in the graph
|
|
44
|
+
oGraph.getLines().forEach((oLine) => {
|
|
45
|
+
const connectionType = oLine.getConnectionType(); // Get the connection type, if not configured at Line level then the default is Right to Left from source to target.
|
|
46
|
+
const oSourceNode = oLine.getFromNode();
|
|
47
|
+
const oTargetNode = oLine.getToNode();
|
|
48
|
+
// Get anchor coordinates for source and target nodes, usually the center of the respective sides
|
|
49
|
+
const oSourceAnchor = this._getNodeAnchorCoordinates(oLine.getFromNode(), connectionType, "source");
|
|
50
|
+
const oTargetAnchor = this._getNodeAnchorCoordinates(oLine.getToNode(), connectionType, "target");
|
|
51
|
+
const mParams = {
|
|
52
|
+
sourceAnchor: oSourceAnchor,
|
|
53
|
+
targetAnchor: oTargetAnchor,
|
|
54
|
+
sourceNode: oSourceNode,
|
|
55
|
+
targetNode: oTargetNode,
|
|
56
|
+
sConnectionType: connectionType,
|
|
57
|
+
gridSize: config.gridSize,
|
|
58
|
+
minEdgeLength: config.minEdgeLength
|
|
59
|
+
};
|
|
60
|
+
// Calculate the path coordinates between the source and target anchors
|
|
61
|
+
const aCoordinates = this._calculatePathCoordinates(mParams);
|
|
62
|
+
// set the anchor points of the source and target nodes on the line as source and target for drawing the path.
|
|
63
|
+
oLine.setSource({
|
|
64
|
+
x: oSourceAnchor.x,
|
|
65
|
+
y: oSourceAnchor.y
|
|
66
|
+
});
|
|
67
|
+
oLine.setTarget({
|
|
68
|
+
x: oTargetAnchor.x,
|
|
69
|
+
y: oTargetAnchor.y
|
|
70
|
+
});
|
|
71
|
+
// Set the calculated bend points on the line
|
|
72
|
+
oLine.clearBends();
|
|
73
|
+
// Set the calculated bend points on the line, determined by the connection type.
|
|
74
|
+
if (aCoordinates && aCoordinates.length) {
|
|
75
|
+
aCoordinates.forEach((oBend) => {
|
|
76
|
+
oLine.addBend(oBend);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
/**
|
|
82
|
+
* Calculates path coordinates between source and target anchors based on connection type.
|
|
83
|
+
*
|
|
84
|
+
* @param {object} mParams - Parameters for path calculation
|
|
85
|
+
* @param {object} mParams.sourceAnchor - Source anchor coordinates {x, y}
|
|
86
|
+
* @param {object} mParams.targetAnchor - Target anchor coordinates {x, y}
|
|
87
|
+
* @param {object} mParams.sourceNode - Source node object
|
|
88
|
+
* @param {object} mParams.targetNode - Target node object
|
|
89
|
+
* @param {string} mParams.sConnectionType - Connection type
|
|
90
|
+
* @param {number} [mParams.gridSize=50] - Grid size for calculations
|
|
91
|
+
* @param {number} [mParams.minEdgeLength=20] - Minimum edge length
|
|
92
|
+
* @returns {Array<object>} Array of bend point coordinates
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
_calculatePathCoordinates: function (mParams) {
|
|
96
|
+
const { sConnectionType } = mParams;
|
|
97
|
+
switch (sConnectionType) {
|
|
98
|
+
case ConnectionType.RightToLeft:
|
|
99
|
+
return this._handleRightToLeftConnection(mParams);
|
|
100
|
+
case ConnectionType.LeftToRight:
|
|
101
|
+
return this._handleLeftToRightConnection(mParams);
|
|
102
|
+
case ConnectionType.LeftToLeft:
|
|
103
|
+
return this._handleLeftToLeftConnection(mParams);
|
|
104
|
+
case ConnectionType.RightToRight:
|
|
105
|
+
return this._handleRightToRightConnection(mParams);
|
|
106
|
+
default:
|
|
107
|
+
// default RightToLeft case
|
|
108
|
+
// This is a fallback for any unrecognized connection type
|
|
109
|
+
return this._handleRightToLeftConnection(mParams);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
/**
|
|
113
|
+
* ╔══════════════════════════════════════════════════════════════════════════════╗
|
|
114
|
+
* ║ RIGHT-TO-LEFT CONNECTION HANDLER ║
|
|
115
|
+
* ╠══════════════════════════════════════════════════════════════════════════════╣
|
|
116
|
+
* ║ ║
|
|
117
|
+
* ║ [SRC NODE]→ ········→ ←[TGT NODE] ║
|
|
118
|
+
* ║ ║
|
|
119
|
+
* ║ Calculates optimal path from right anchor of source to left anchor of ║
|
|
120
|
+
* ║ target, creating L-shaped, U-shaped, Z-bend, or S-shaped connections ║
|
|
121
|
+
* ║ based on node positions and grid constraints. ║
|
|
122
|
+
* ║ ║
|
|
123
|
+
* ╚══════════════════════════════════════════════════════════════════════════════╝
|
|
124
|
+
*/
|
|
125
|
+
/**
|
|
126
|
+
* Handles RightToLeft connection path calculation.
|
|
127
|
+
* Connection flows from the right side of source node to the left side of target node.
|
|
128
|
+
*
|
|
129
|
+
* @param {object} mParams - Parameters for path calculation
|
|
130
|
+
* @returns {Array<object>} Array of bend point coordinates
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
_handleRightToLeftConnection: function (mParams) {
|
|
134
|
+
const { sourceNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
|
|
135
|
+
const { x1, y1, x2, y2, AX1, AY1, AX2, AY2, dx } = this._transformSourceTargetCoordinates(mParams);
|
|
136
|
+
|
|
137
|
+
// CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
|
|
138
|
+
if (y1 === y2) {
|
|
139
|
+
if (x2 > x1) {
|
|
140
|
+
// SCENARIO 1A: Direct horizontal connection (source right → target left)
|
|
141
|
+
// Check if anchors are aligned and connection pattern allows straight line
|
|
142
|
+
const anchorVerticalOffset = Math.abs(AY1 - AY2);
|
|
143
|
+
const shouldUseStraightLine = this._shouldUseHorizontalStraightLine(mParams.sourceNode, mParams.targetNode, mParams.sConnectionType);
|
|
144
|
+
|
|
145
|
+
if (shouldUseStraightLine && anchorVerticalOffset <= 5) {
|
|
146
|
+
// Visual: [SRC]----→[TGT] (perfect alignment)
|
|
147
|
+
return [];
|
|
148
|
+
} else if (anchorVerticalOffset > 5) {
|
|
149
|
+
// Visual: [SRC]→ ←[TGT] (offset anchors - add horizontal correction)
|
|
150
|
+
// | |
|
|
151
|
+
// └─────┘
|
|
152
|
+
const midX = AX1 + (AX2 - AX1) / 2;
|
|
153
|
+
return [
|
|
154
|
+
{ x: midX, y: AY1 },
|
|
155
|
+
{ x: midX, y: AY2 }
|
|
156
|
+
];
|
|
157
|
+
} else {
|
|
158
|
+
// Multiple connections - use straight line anyway for now
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
} else if (x1 >= x2) {
|
|
162
|
+
// SCENARIO 1B: Backward horizontal connection (source overlaps/behind target)
|
|
163
|
+
// Visual: [TGT] [SRC]
|
|
164
|
+
// ↑----→ |
|
|
165
|
+
// | |
|
|
166
|
+
// ←------↓
|
|
167
|
+
// Create U-shaped path going around both nodes
|
|
168
|
+
const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
169
|
+
const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
170
|
+
const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
171
|
+
|
|
172
|
+
return [
|
|
173
|
+
{ x: x3, y: AY1 }, // Right from source
|
|
174
|
+
{ x: x3, y: y3 }, // Down below both nodes
|
|
175
|
+
{ x: x4, y: y3 }, // Left to target column
|
|
176
|
+
{ x: x4, y: AY2 } // Up to target
|
|
177
|
+
];
|
|
178
|
+
}
|
|
179
|
+
} else if (y1 != y2) {
|
|
180
|
+
if (x1 <= x2) {
|
|
181
|
+
// SCENARIO 2A: Source is to the left or aligned with target
|
|
182
|
+
if (dx >= minEdgeLength + sourceNode._iWidth) {
|
|
183
|
+
if (Math.abs(x1 + sourceNode._iWidth - x2) <= gridSize) {
|
|
184
|
+
// SCENARIO 2A1: Nodes are close - use midpoint connection
|
|
185
|
+
// Visual: [SRC]→ ←[TGT]
|
|
186
|
+
// | |
|
|
187
|
+
// ↓ ↑
|
|
188
|
+
// (midpoint)
|
|
189
|
+
const x3 = AX1 + (x2 - (x1 + sourceNode._iWidth)) / 2;
|
|
190
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
191
|
+
} else {
|
|
192
|
+
// SCENARIO 2A2: Nodes are far apart - use grid-aligned Z-bend
|
|
193
|
+
// Visual: [SRC]→ |
|
|
194
|
+
// |
|
|
195
|
+
// ↓
|
|
196
|
+
// ←[TGT]
|
|
197
|
+
const x3 = this.getRightGridIntersectionPoint(x1 + sourceNode._iWidth, AY1, gridSize, minEdgeLength).x;
|
|
198
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
199
|
+
}
|
|
200
|
+
} else if (dx <= minEdgeLength + sourceNode._iWidth) {
|
|
201
|
+
// SCENARIO 2B: Nodes are too close - create S-shaped path
|
|
202
|
+
if (y1 < y2) {
|
|
203
|
+
// SCENARIO 2B1: Source above target - S-bend going down
|
|
204
|
+
// Visual: [SRC]→ |
|
|
205
|
+
// |
|
|
206
|
+
// ↓----→ |
|
|
207
|
+
// |
|
|
208
|
+
// ↓
|
|
209
|
+
// ←[TGT]
|
|
210
|
+
const x3 = this.getRightGridIntersectionPoint(x1 + sourceNode._iWidth, y1, gridSize, minEdgeLength).x;
|
|
211
|
+
const x4 = this.getLeftGridIntersectionPoint(x2, AY2, gridSize, minEdgeLength).x;
|
|
212
|
+
const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
213
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
214
|
+
} else if (y1 > y2) {
|
|
215
|
+
// SCENARIO 2B2: Source below target - S-bend going up
|
|
216
|
+
// Visual: ←[TGT]
|
|
217
|
+
// ↑
|
|
218
|
+
// ↑----→ |
|
|
219
|
+
// |
|
|
220
|
+
// |
|
|
221
|
+
// [SRC]→ |
|
|
222
|
+
const x3 = this.getRightGridIntersectionPoint(AX1, AY2, gridSize, minEdgeLength).x;
|
|
223
|
+
const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
224
|
+
const y3 = this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
225
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} else if (x1 > x2) {
|
|
229
|
+
// SCENARIO 3: Source is to the right of target - create S-shaped path
|
|
230
|
+
if (y1 < y2) {
|
|
231
|
+
// SCENARIO 3A: Source above and right of target - S-bend down and left
|
|
232
|
+
// Visual: [SRC]→ |
|
|
233
|
+
// |
|
|
234
|
+
// ↓
|
|
235
|
+
// ←[TGT] ←---------
|
|
236
|
+
const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
237
|
+
const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
238
|
+
const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
239
|
+
return [
|
|
240
|
+
{ x: x3, y: AY1 }, // Right from source
|
|
241
|
+
{ x: x3, y: y3 }, // Down past both nodes
|
|
242
|
+
{ x: x4, y: y3 }, // Left to target column
|
|
243
|
+
{ x: x4, y: AY2 } // Up to target
|
|
244
|
+
];
|
|
245
|
+
} else if (y1 > y2) {
|
|
246
|
+
// SCENARIO 3B: Source below and right of target - S-bend up and left
|
|
247
|
+
// Visual: ←[TGT] ←---------
|
|
248
|
+
// ↑
|
|
249
|
+
// |
|
|
250
|
+
// [SRC]→ |
|
|
251
|
+
const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
252
|
+
const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
253
|
+
const y3 = this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
254
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// DEFAULT: No bends needed (direct connection possible)
|
|
259
|
+
return [];
|
|
260
|
+
},
|
|
261
|
+
/**
|
|
262
|
+
* ╔══════════════════════════════════════════════════════════════════════════════╗
|
|
263
|
+
* ║ LEFT-TO-RIGHT CONNECTION HANDLER ║
|
|
264
|
+
* ╠══════════════════════════════════════════════════════════════════════════════╣
|
|
265
|
+
* ║ ║
|
|
266
|
+
* ║ ←[SRC NODE] ········→ [TGT NODE]→ ║
|
|
267
|
+
* ║ ║
|
|
268
|
+
* ║ Calculates optimal path from left anchor of source to right anchor of ║
|
|
269
|
+
* ║ target, creating direct, U-shaped, Z-bend, or S-shaped connections ║
|
|
270
|
+
* ║ based on node positions and grid alignment. ║
|
|
271
|
+
* ║ ║
|
|
272
|
+
* ╚══════════════════════════════════════════════════════════════════════════════╝
|
|
273
|
+
*/
|
|
274
|
+
/**
|
|
275
|
+
* Handles LeftToRight connection path calculation.
|
|
276
|
+
* Connection flows from the left side of source node to the right side of target node.
|
|
277
|
+
*
|
|
278
|
+
* @param {object} mParams - Parameters for path calculation
|
|
279
|
+
* @returns {Array<object>} Array of bend point coordinates
|
|
280
|
+
* @private
|
|
281
|
+
*/
|
|
282
|
+
_handleLeftToRightConnection: function (mParams) {
|
|
283
|
+
const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
|
|
284
|
+
const { x1, y1, x2, y2, AX1, AY1, AX2, AY2, dx } = this._transformSourceTargetCoordinates(mParams);
|
|
285
|
+
// CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
|
|
286
|
+
if (y1 === y2) {
|
|
287
|
+
if (x1 > x2) {
|
|
288
|
+
// SCENARIO 1A: Direct horizontal connection (source left → target right, but source is actually to the right)
|
|
289
|
+
// Check if anchors are aligned and connection pattern allows straight line
|
|
290
|
+
const anchorVerticalOffset = Math.abs(AY1 - AY2);
|
|
291
|
+
const shouldUseStraightLine = this._shouldUseHorizontalStraightLine(mParams.sourceNode, mParams.targetNode, mParams.sConnectionType);
|
|
292
|
+
|
|
293
|
+
if (shouldUseStraightLine && anchorVerticalOffset <= 5) {
|
|
294
|
+
// Visual: [TGT]←----[SRC] (perfect alignment)
|
|
295
|
+
return [];
|
|
296
|
+
} else if (anchorVerticalOffset > 5) {
|
|
297
|
+
// Visual: ←[SRC] [TGT]→ (offset anchors - add horizontal correction)
|
|
298
|
+
// | |
|
|
299
|
+
// └───────────┘
|
|
300
|
+
const midX = AX1 + (AX2 - AX1) / 2;
|
|
301
|
+
return [
|
|
302
|
+
{ x: midX, y: AY1 },
|
|
303
|
+
{ x: midX, y: AY2 }
|
|
304
|
+
];
|
|
305
|
+
} else {
|
|
306
|
+
// Multiple connections - use straight line anyway for now
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
} else if (x1 <= x2) {
|
|
310
|
+
// SCENARIO 1B: Forward horizontal connection but needs U-shape (source overlaps/in front of target)
|
|
311
|
+
// Visual: [SRC] [TGT]
|
|
312
|
+
// ↓----→ |
|
|
313
|
+
// | |
|
|
314
|
+
// ↑------↓
|
|
315
|
+
// Create U-shaped path going around both nodes
|
|
316
|
+
const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
317
|
+
const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
318
|
+
const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
319
|
+
|
|
320
|
+
return [
|
|
321
|
+
{ x: x3, y: AY1 }, // Right from source
|
|
322
|
+
{ x: x3, y: y3 }, // Down below both nodes
|
|
323
|
+
{ x: x4, y: y3 }, // Left to target column
|
|
324
|
+
{ x: x4, y: AY2 } // Up to target
|
|
325
|
+
];
|
|
326
|
+
}
|
|
327
|
+
} else if (y1 != y2) {
|
|
328
|
+
// CASE 2: Source and target nodes are on different horizontal lines (y1 != y2)
|
|
329
|
+
if (x1 >= x2) {
|
|
330
|
+
// SCENARIO 2A: Source is to the right or aligned with target
|
|
331
|
+
if (dx >= minEdgeLength + targetNode._iWidth) {
|
|
332
|
+
if (Math.abs(x1 - (x2 + targetNode._iWidth)) <= gridSize) {
|
|
333
|
+
// SCENARIO 2A1: Nodes are close - use midpoint connection
|
|
334
|
+
// Visual: ←[SRC] [TGT]→
|
|
335
|
+
// | |
|
|
336
|
+
// ↓ ↑
|
|
337
|
+
// (midpoint)
|
|
338
|
+
const x3 = (AX1 + AX2) / 2;
|
|
339
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
340
|
+
} else {
|
|
341
|
+
// SCENARIO 2A2: Nodes are far apart - use grid-aligned Z-bend
|
|
342
|
+
// Visual: ←[SRC] |
|
|
343
|
+
// |
|
|
344
|
+
// ↓
|
|
345
|
+
// [TGT]→
|
|
346
|
+
const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
347
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
348
|
+
}
|
|
349
|
+
} else if (dx <= minEdgeLength + targetNode._iWidth) {
|
|
350
|
+
// SCENARIO 2B: Nodes are too close - create S-shaped path
|
|
351
|
+
if (y1 < y2) {
|
|
352
|
+
// SCENARIO 2B1: Source above target - S-bend going down
|
|
353
|
+
// Visual: ←[SRC] |
|
|
354
|
+
// |
|
|
355
|
+
// ←---- ↓
|
|
356
|
+
// |
|
|
357
|
+
// ↓
|
|
358
|
+
// [TGT]→
|
|
359
|
+
const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
360
|
+
const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
361
|
+
const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
362
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
363
|
+
} else if (y1 > y2) {
|
|
364
|
+
// SCENARIO 2B2: Source below target - S-bend going up
|
|
365
|
+
// Visual: [TGT]→
|
|
366
|
+
// ↑
|
|
367
|
+
// ←------ |
|
|
368
|
+
// |
|
|
369
|
+
// |
|
|
370
|
+
// ←[SRC] |
|
|
371
|
+
const x3 = this.getLeftGridIntersectionPoint(AX1, AY2, gridSize, minEdgeLength).x;
|
|
372
|
+
const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
373
|
+
const y3 = this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
374
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} else if (x1 < x2) {
|
|
378
|
+
// SCENARIO 3: Source is to the left of target - create S-shaped path
|
|
379
|
+
if (y1 < y2) {
|
|
380
|
+
// SCENARIO 3A: Source above and left of target - S-bend down and right
|
|
381
|
+
// Visual: ←[SRC] |
|
|
382
|
+
// |
|
|
383
|
+
// ↓
|
|
384
|
+
// --------→[TGT]
|
|
385
|
+
const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
386
|
+
const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
387
|
+
const y3 = this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
388
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
389
|
+
} else if (y1 > y2) {
|
|
390
|
+
// SCENARIO 3B: Source below and left of target - S-bend up and right
|
|
391
|
+
// Visual: --------→[TGT]
|
|
392
|
+
// ↑
|
|
393
|
+
// |
|
|
394
|
+
// ←[SRC] |
|
|
395
|
+
const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
396
|
+
const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
397
|
+
const y3 = this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
398
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// DEFAULT: No bends needed (direct connection possible)
|
|
403
|
+
return [];
|
|
404
|
+
},
|
|
405
|
+
/**
|
|
406
|
+
* ╔══════════════════════════════════════════════════════════════════════════════╗
|
|
407
|
+
* ║ LEFT-TO-LEFT CONNECTION HANDLER ║
|
|
408
|
+
* ╠══════════════════════════════════════════════════════════════════════════════╣
|
|
409
|
+
* ║ ║
|
|
410
|
+
* ║ ←[SRC NODE] ········→ ←[TGT NODE] ║
|
|
411
|
+
* ║ ║
|
|
412
|
+
* ║ Calculates optimal path from left anchor of source to left anchor of ║
|
|
413
|
+
* ║ target, creating C-shaped or stepped connections that route around ║
|
|
414
|
+
* ║ the left side of both nodes to avoid overlaps. ║
|
|
415
|
+
* ║ ║
|
|
416
|
+
* ╚══════════════════════════════════════════════════════════════════════════════╝
|
|
417
|
+
*/
|
|
418
|
+
/**
|
|
419
|
+
* Handles LeftToLeft connection path calculation.
|
|
420
|
+
* Connection flows from the left side of source node to the left side of target node.
|
|
421
|
+
*
|
|
422
|
+
* @param {object} mParams - Parameters for path calculation
|
|
423
|
+
* @returns {Array<object>} Array of bend point coordinates
|
|
424
|
+
* @private
|
|
425
|
+
*/
|
|
426
|
+
_handleLeftToLeftConnection: function (mParams) {
|
|
427
|
+
const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
|
|
428
|
+
const { x1, y1, x2, y2, AX1, AY1, AX2, AY2 } = this._transformSourceTargetCoordinates(mParams);
|
|
429
|
+
// CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
|
|
430
|
+
if (y1 === y2) {
|
|
431
|
+
if (x1 > x2) {
|
|
432
|
+
// SCENARIO 1A: Source to the right of target - create C-shaped path on left side
|
|
433
|
+
// Visual: ←[TGT] ←[SRC]
|
|
434
|
+
// | |
|
|
435
|
+
// ↓ |
|
|
436
|
+
// ←-----------↓
|
|
437
|
+
// Check if nodes are close enough for midpoint, else use grid-aligned path
|
|
438
|
+
const x3 = Math.abs(x1 - x2 + targetNode._iWidth) <= gridSize ?
|
|
439
|
+
(AX1 + (x2 + targetNode._iWidth)) / 2 :
|
|
440
|
+
this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
441
|
+
const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
442
|
+
const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
443
|
+
return [
|
|
444
|
+
{ x: x3, y: AY1 }, // Left from source
|
|
445
|
+
{ x: x3, y: y3 }, // Down/up to avoid nodes
|
|
446
|
+
{ x: x4, y: y3 }, // Across to target column
|
|
447
|
+
{ x: x4, y: AY2 } // Up/down to target
|
|
448
|
+
];
|
|
449
|
+
} else if (x1 <= x2) {
|
|
450
|
+
// SCENARIO 1B: Source to the left of target - create C-shaped path on left side
|
|
451
|
+
// Visual: ←[SRC] ←[TGT]
|
|
452
|
+
// | |
|
|
453
|
+
// ↓ |
|
|
454
|
+
// →-------------↓
|
|
455
|
+
const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
456
|
+
const x4 = Math.abs(x1 + sourceNode._iWidth - x2) <= gridSize ?
|
|
457
|
+
(AX2 + x2) / 2 :
|
|
458
|
+
this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
459
|
+
const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
460
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
461
|
+
}
|
|
462
|
+
} else if (y1 != y2) {
|
|
463
|
+
// CASE 2: Source and target nodes are on different horizontal lines (y1 != y2)
|
|
464
|
+
/**
|
|
465
|
+
* When the source and target nodes are not on the same horizontal line (y1 != y2)
|
|
466
|
+
* Then nodes can be placed in either x1 > x2 or x1 < x2
|
|
467
|
+
* with these positions there can be two cases:
|
|
468
|
+
* 1. y1 < y2: source node is above the target node
|
|
469
|
+
* 2. y1 > y2: source node is below the target node
|
|
470
|
+
* In both cases we need to calculate the vertical bend points based on node's y so that the lines do not overlap with the target node.
|
|
471
|
+
* If x1 < x2 and y1 < y2 then we know that source node is above the target node and to the left of it. Based on AY2 we can calculate above or below bend points.
|
|
472
|
+
*/
|
|
473
|
+
if (x1 > x2) {
|
|
474
|
+
// SCENARIO 2A: Source is to the right of target
|
|
475
|
+
if (y1 < y2) {
|
|
476
|
+
// SCENARIO 2A1: Source above and right of target
|
|
477
|
+
if (AY1 < y2) {
|
|
478
|
+
// Simple vertical connection if source anchor is above target node
|
|
479
|
+
// Visual: ←[SRC]
|
|
480
|
+
// |
|
|
481
|
+
// |
|
|
482
|
+
// ↓
|
|
483
|
+
// ←[TGT]
|
|
484
|
+
const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
485
|
+
return [{ x: x4, y: AY1 }, { x: x4, y: AY2 }];
|
|
486
|
+
} else if (AY1 >= y2) {
|
|
487
|
+
// Need to avoid target node - create stepped path
|
|
488
|
+
// Visual: ←[SRC]
|
|
489
|
+
// |
|
|
490
|
+
// ←[TGT] ←-----↓
|
|
491
|
+
const iDiff = Math.abs(AX1 - (x2 + targetNode._iWidth));
|
|
492
|
+
let x3 = null;
|
|
493
|
+
if (iDiff <= gridSize) {
|
|
494
|
+
x3 = iDiff < minEdgeLength ? (AX1 + (x2 + targetNode._iWidth) / 2) : AX1 - minEdgeLength;
|
|
495
|
+
} else {
|
|
496
|
+
x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
497
|
+
}
|
|
498
|
+
const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
499
|
+
const y3 = this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
|
|
500
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
501
|
+
}
|
|
502
|
+
} else if (y1 > y2) {
|
|
503
|
+
// SCENARIO 2A2: Source below and right of target
|
|
504
|
+
if (AY1 > y2 + targetNode._iHeight) {
|
|
505
|
+
// Simple vertical connection if source anchor is below target node
|
|
506
|
+
// Visual: ←[TGT]
|
|
507
|
+
// |
|
|
508
|
+
// |
|
|
509
|
+
// ↑
|
|
510
|
+
// ←[SRC]
|
|
511
|
+
const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
512
|
+
return [{ x: x4, y: AY1 }, { x: x4, y: AY2 }];
|
|
513
|
+
} else if (AY1 <= y2 + targetNode._iHeight) {
|
|
514
|
+
// Need to avoid target node - create stepped path
|
|
515
|
+
// Visual: ←[TGT] ←-----↑
|
|
516
|
+
// |
|
|
517
|
+
// ←[SRC]
|
|
518
|
+
const iDiff = Math.abs(AX1 - (x2 + targetNode._iWidth));
|
|
519
|
+
let x3 = null;
|
|
520
|
+
if (iDiff <= gridSize) {
|
|
521
|
+
x3 = iDiff < AX1 + minEdgeLength ? (AX1 + (x2 + targetNode._iWidth) / 2) : AX1 + minEdgeLength;
|
|
522
|
+
} else {
|
|
523
|
+
x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
524
|
+
}
|
|
525
|
+
const x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
526
|
+
const y3 = this.getBottomGridIntersectionPoint(x4, y2 + targetNode._iHeight, gridSize, minEdgeLength).y;
|
|
527
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
} else if (x1 <= x2) {
|
|
531
|
+
// SCENARIO 2B: Source is to the left of target
|
|
532
|
+
if (y1 < y2) {
|
|
533
|
+
// SCENARIO 2B1: Source above and left of target
|
|
534
|
+
// Visual: ←[SRC]
|
|
535
|
+
// |
|
|
536
|
+
// ↓-------→ |
|
|
537
|
+
// ↓
|
|
538
|
+
// ←[TGT]
|
|
539
|
+
const x3 = this.getLeftGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
540
|
+
const iDiff = Math.abs(x1 + sourceNode._iWidth - x2);
|
|
541
|
+
let x4 = null;
|
|
542
|
+
if (iDiff <= gridSize) {
|
|
543
|
+
x4 = iDiff < minEdgeLength ? (AX2 + (x1 + sourceNode._iWidth)) / 2 : AX2 + minEdgeLength;
|
|
544
|
+
} else {
|
|
545
|
+
x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
546
|
+
}
|
|
547
|
+
const y3 = (AY2 > (y1 + sourceNode._iHeight)) ? AY2 : this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y;
|
|
548
|
+
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 }];
|
|
549
|
+
return aCoordinates;
|
|
550
|
+
} else if (y1 > y2) {
|
|
551
|
+
// SCENARIO 2B2: Source below and left of target
|
|
552
|
+
// Visual: ←[TGT]
|
|
553
|
+
// ↑
|
|
554
|
+
// ←-------→ |
|
|
555
|
+
// |
|
|
556
|
+
// ←[SRC]
|
|
557
|
+
const x3 = this.getLeftGridIntersectionPoint(AX1, AY2, gridSize, minEdgeLength).x;
|
|
558
|
+
const iDiff = Math.abs(x1 + sourceNode._iWidth - x2);
|
|
559
|
+
let x4 = null;
|
|
560
|
+
if (iDiff <= gridSize) {
|
|
561
|
+
x4 = iDiff < minEdgeLength ? (AX2 + (x1 + sourceNode._iWidth)) / 2 : AX2 + minEdgeLength;
|
|
562
|
+
} else {
|
|
563
|
+
x4 = this.getLeftGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
564
|
+
}
|
|
565
|
+
const y3 = (AY2 < y1) ? AY2 : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
566
|
+
const aCoordinates = AY2 < y1 ? [{ x: x3, y: AY1 }, { x: x3, y: y3 }] : [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
567
|
+
return aCoordinates;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// DEFAULT: No bends needed (direct connection possible)
|
|
572
|
+
return [];
|
|
573
|
+
},
|
|
574
|
+
/**
|
|
575
|
+
* ╔══════════════════════════════════════════════════════════════════════════════╗
|
|
576
|
+
* ║ RIGHT-TO-RIGHT CONNECTION HANDLER ║
|
|
577
|
+
* ╠══════════════════════════════════════════════════════════════════════════════╣
|
|
578
|
+
* ║ ║
|
|
579
|
+
* ║ [SRC NODE]→ ········→ [TGT NODE]→ ║
|
|
580
|
+
* ║ ║
|
|
581
|
+
* ║ Calculates optimal path from right anchor of source to right anchor of ║
|
|
582
|
+
* ║ target, creating C-shaped or stepped connections that route around ║
|
|
583
|
+
* ║ the right side of both nodes to avoid overlaps. ║
|
|
584
|
+
* ║ ║
|
|
585
|
+
* ╚══════════════════════════════════════════════════════════════════════════════╝
|
|
586
|
+
*/
|
|
587
|
+
/**
|
|
588
|
+
* Handles RightToRight connection path calculation.
|
|
589
|
+
* Connection flows from the right side of source node to the right side of target node.
|
|
590
|
+
*
|
|
591
|
+
* @param {object} mParams - Parameters for path calculation
|
|
592
|
+
* @returns {Array<object>} Array of bend point coordinates
|
|
593
|
+
* @private
|
|
594
|
+
*/
|
|
595
|
+
_handleRightToRightConnection: function (mParams) {
|
|
596
|
+
const { sourceNode, targetNode, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH } = mParams;
|
|
597
|
+
const { x1, y1, x2, y2, AX1, AY1, AX2, AY2 } = this._transformSourceTargetCoordinates(mParams);
|
|
598
|
+
// CASE 1: Source and target nodes are on the same horizontal line (y1 === y2)
|
|
599
|
+
if (y1 === y2) {
|
|
600
|
+
if (x1 > x2) {
|
|
601
|
+
// SCENARIO 1A: Source to the right of target - create C-shaped path on right side
|
|
602
|
+
// Visual: [TGT]→ [SRC]→
|
|
603
|
+
// | |
|
|
604
|
+
// ↓ |
|
|
605
|
+
// →---------↓
|
|
606
|
+
const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
607
|
+
const iDiff = Math.abs(x1 - AX2);
|
|
608
|
+
const x4 = iDiff <= gridSize ? (x1 + x2) / 2 : this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
609
|
+
const y3 = y1 < gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
610
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
611
|
+
} else if (x1 <= x2) {
|
|
612
|
+
// SCENARIO 1B: Source to the left of target - create C-shaped path on right side
|
|
613
|
+
// Visual: [SRC]→ [TGT]→
|
|
614
|
+
// | |
|
|
615
|
+
// ↓ |
|
|
616
|
+
// →-----------------↓
|
|
617
|
+
const iDff = Math.abs(AX1 - x2);
|
|
618
|
+
const x3 = iDff <= gridSize ? (AX1 + x2) / 2 : this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
619
|
+
const x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
620
|
+
const y3 = y1 < gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
621
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
622
|
+
}
|
|
623
|
+
} else if (y1 != y2) {
|
|
624
|
+
// CASE 2: Source and target nodes are on different horizontal lines (y1 != y2)
|
|
625
|
+
if (x1 > x2) {
|
|
626
|
+
// SCENARIO 2A: Source is to the right of target
|
|
627
|
+
if (y1 < y2) {
|
|
628
|
+
// SCENARIO 2A1: Source above and right of target
|
|
629
|
+
// Visual: [SRC]→
|
|
630
|
+
// |
|
|
631
|
+
// ↓--------→ |
|
|
632
|
+
// ↓
|
|
633
|
+
// [TGT]→
|
|
634
|
+
const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
635
|
+
const iDiff = Math.abs(AX2 - x1);
|
|
636
|
+
let x4 = null;
|
|
637
|
+
if (iDiff <= gridSize) {
|
|
638
|
+
x4 = iDiff < minEdgeLength ? (AX2 + x1) / 2 : AX2 + minEdgeLength;
|
|
639
|
+
} else {
|
|
640
|
+
x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
641
|
+
}
|
|
642
|
+
const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
643
|
+
if (AY2 > y1 + sourceNode._iHeight) {
|
|
644
|
+
// Simple vertical connection if target anchor is below source node
|
|
645
|
+
// Visual: [SRC]→
|
|
646
|
+
// |
|
|
647
|
+
// ↓
|
|
648
|
+
// [TGT]→
|
|
649
|
+
const aCords = AX2 > AX1 ? [{x: x4, y: AY1}, {x: x4, y: AY2}] : [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
650
|
+
return aCords;
|
|
651
|
+
} else {
|
|
652
|
+
// Need to avoid target node - create stepped path
|
|
653
|
+
// Visual: [SRC]→
|
|
654
|
+
// |
|
|
655
|
+
// ↓---- →
|
|
656
|
+
// |
|
|
657
|
+
// [TGT]→
|
|
658
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
659
|
+
}
|
|
660
|
+
} else if (y1 > y2) {
|
|
661
|
+
// SCENARIO 2A2: Source below and right of target
|
|
662
|
+
// Visual: [TGT]→
|
|
663
|
+
// ↑
|
|
664
|
+
// ↑--------→ |
|
|
665
|
+
// |
|
|
666
|
+
// [SRC]→
|
|
667
|
+
const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
668
|
+
const iDiff = Math.abs(AX2 - x1);
|
|
669
|
+
let x4 = null;
|
|
670
|
+
if (iDiff <= gridSize) {
|
|
671
|
+
x4 = iDiff < minEdgeLength ? (AX2 + x1) / 2 : AX2 + minEdgeLength;
|
|
672
|
+
} else {
|
|
673
|
+
x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
674
|
+
}
|
|
675
|
+
const y3 = y2 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
|
|
676
|
+
if (AY1 > y2 + targetNode._iHeight) {
|
|
677
|
+
// Simple vertical connection if target anchor is below source node
|
|
678
|
+
// Visual: [SRC]→
|
|
679
|
+
// |
|
|
680
|
+
// ↓
|
|
681
|
+
// [TGT]→
|
|
682
|
+
const aCords = AX2 > AX1 ? [{x: x4, y: AY1}, {x: x4, y: AY2}] : [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
683
|
+
return aCords;
|
|
684
|
+
} else {
|
|
685
|
+
// Need to avoid target node - create stepped path
|
|
686
|
+
// Visual: [SRC]→
|
|
687
|
+
// |
|
|
688
|
+
// ↓---- →
|
|
689
|
+
// |
|
|
690
|
+
// [TGT]→
|
|
691
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
} else if (x1 <= x2) {
|
|
695
|
+
// SCENARIO 2B: Source is to the left of target
|
|
696
|
+
if (y1 < y2) {
|
|
697
|
+
// SCENARIO 2B1: Source above and left of target
|
|
698
|
+
// Visual: [SRC]→
|
|
699
|
+
// |
|
|
700
|
+
// ↓----------→ |
|
|
701
|
+
// ↓
|
|
702
|
+
// [TGT]→
|
|
703
|
+
const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
704
|
+
const iDiff = Math.abs(x2 - AX1);
|
|
705
|
+
let x4 = null;
|
|
706
|
+
if (iDiff <= gridSize && !(AY2 > y1 + sourceNode._iHeight)) {
|
|
707
|
+
x4 = iDiff < minEdgeLength ? (x2 + AX1) / 2 : AX1 - minEdgeLength;
|
|
708
|
+
} else {
|
|
709
|
+
x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
710
|
+
}
|
|
711
|
+
const y3 = y1 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength).y : this.getTopGridIntersectionPoint(x3, y1, gridSize, minEdgeLength).y;
|
|
712
|
+
if (AY2 > y1 + sourceNode._iHeight) {
|
|
713
|
+
// Simple vertical connection if target anchor is below source node
|
|
714
|
+
// Visual: [SRC]→
|
|
715
|
+
// |
|
|
716
|
+
// ↓
|
|
717
|
+
// [TGT]→
|
|
718
|
+
const aCords = AX2 > AX1 ? [{x: x4, y: AY1}, {x: x4, y: AY2}] : [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
719
|
+
return aCords;
|
|
720
|
+
} else {
|
|
721
|
+
// Need to avoid target node - create stepped path
|
|
722
|
+
// Visual: [SRC]→
|
|
723
|
+
// |
|
|
724
|
+
// ↓---- →
|
|
725
|
+
// |
|
|
726
|
+
// [TGT]→
|
|
727
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
728
|
+
}
|
|
729
|
+
// 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 }];
|
|
730
|
+
// return aCoordinates;
|
|
731
|
+
} else if (y1 > y2) {
|
|
732
|
+
// SCENARIO 2B2: Source below and left of target
|
|
733
|
+
// Visual: [TGT]→
|
|
734
|
+
// ↑
|
|
735
|
+
// ↑----------→ |
|
|
736
|
+
// |
|
|
737
|
+
// [SRC]→
|
|
738
|
+
const x3 = this.getRightGridIntersectionPoint(AX1, AY1, gridSize, minEdgeLength).x;
|
|
739
|
+
const iDiff = Math.abs(x2 - AX1);
|
|
740
|
+
let x4 = null;
|
|
741
|
+
if (iDiff <= gridSize && !(AY1 > y2 + targetNode._iHeight)) {
|
|
742
|
+
x4 = iDiff < minEdgeLength ? (x2 + AX1) / 2 : AX1 - minEdgeLength;
|
|
743
|
+
} else {
|
|
744
|
+
x4 = this.getRightGridIntersectionPoint(AX2, AY2, gridSize, minEdgeLength).x;
|
|
745
|
+
}
|
|
746
|
+
const y3 = y2 <= gridSize ? this.getBottomGridIntersectionPoint(x3, y1 + sourceNode._iHeight, gridSize, minEdgeLength) : this.getTopGridIntersectionPoint(x4, y2, gridSize, minEdgeLength).y;
|
|
747
|
+
if (AY1 > y2 + targetNode._iHeight) {
|
|
748
|
+
// Simple vertical connection if target anchor is below source node
|
|
749
|
+
// Visual: [SRC]→
|
|
750
|
+
// |
|
|
751
|
+
// ↓
|
|
752
|
+
// [TGT]→
|
|
753
|
+
const aCords = AX2 > AX1 ? [{x: x4, y: AY1}, {x: x4, y: AY2}] : [{ x: x3, y: AY1 }, { x: x3, y: AY2 }];
|
|
754
|
+
return aCords;
|
|
755
|
+
} else {
|
|
756
|
+
// Need to avoid target node - create stepped path
|
|
757
|
+
// Visual: [SRC]→
|
|
758
|
+
// |
|
|
759
|
+
// ↓---- →
|
|
760
|
+
// |
|
|
761
|
+
// [TGT]→
|
|
762
|
+
return [{ x: x3, y: AY1 }, { x: x3, y: y3 }, { x: x4, y: y3 }, { x: x4, y: AY2 }];
|
|
763
|
+
}
|
|
764
|
+
// 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 }];
|
|
765
|
+
// return aCoordinates;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
// DEFAULT: No bends needed (direct connection possible)
|
|
770
|
+
return [];
|
|
771
|
+
},
|
|
772
|
+
/**
|
|
773
|
+
* Calculates the right grid intersection point.
|
|
774
|
+
*
|
|
775
|
+
* @param {number} x - X coordinate
|
|
776
|
+
* @param {number} y - Y coordinate
|
|
777
|
+
* @param {number} [gridSize=50] - Size of grid cells
|
|
778
|
+
* @param {number} [minEdgeLength=20] - Minimum edge length
|
|
779
|
+
* @returns {object} Intersection point {x, y}
|
|
780
|
+
* @public
|
|
781
|
+
*/
|
|
782
|
+
getRightGridIntersectionPoint: function (x, y, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH) {
|
|
783
|
+
let xIntersection = Math.ceil(x / gridSize) * gridSize;
|
|
784
|
+
if (xIntersection - x < minEdgeLength) {
|
|
785
|
+
xIntersection += gridSize; // Ensure the intersection is at least MIN_EDGE_LENGTH away
|
|
786
|
+
}
|
|
787
|
+
return { x: xIntersection, y: y };
|
|
788
|
+
},
|
|
789
|
+
/**
|
|
790
|
+
* Calculates the left grid intersection point.
|
|
791
|
+
*
|
|
792
|
+
* @param {number} x - X coordinate
|
|
793
|
+
* @param {number} y - Y coordinate
|
|
794
|
+
* @param {number} [gridSize=50] - Size of grid cells
|
|
795
|
+
* @param {number} [minEdgeLength=20] - Minimum edge length
|
|
796
|
+
* @returns {object} Intersection point {x, y}
|
|
797
|
+
* @public
|
|
798
|
+
*/
|
|
799
|
+
getLeftGridIntersectionPoint: function (x, y, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH) {
|
|
800
|
+
let xIntersection = Math.floor(x / gridSize) * gridSize;
|
|
801
|
+
if (x - xIntersection < minEdgeLength) {
|
|
802
|
+
xIntersection -= gridSize; // Ensure the intersection is at least MIN_EDGE_LENGTH away
|
|
803
|
+
}
|
|
804
|
+
return { x: xIntersection, y: y };
|
|
805
|
+
},
|
|
806
|
+
/**
|
|
807
|
+
* Calculates the top grid intersection point.
|
|
808
|
+
*
|
|
809
|
+
* @param {number} x - X coordinate
|
|
810
|
+
* @param {number} y - Y coordinate
|
|
811
|
+
* @param {number} [gridSize=50] - Size of grid cells
|
|
812
|
+
* @param {number} [minEdgeLength=20] - Minimum edge length
|
|
813
|
+
* @returns {object} Intersection point {x, y}
|
|
814
|
+
* @public
|
|
815
|
+
*/
|
|
816
|
+
getTopGridIntersectionPoint: function (x, y, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH) {
|
|
817
|
+
let yIntersection = Math.floor(y / gridSize) * gridSize;
|
|
818
|
+
if (y - yIntersection < minEdgeLength) {
|
|
819
|
+
yIntersection -= gridSize; // Ensure the intersection is at least MIN_EDGE_LENGTH away
|
|
820
|
+
}
|
|
821
|
+
return { x: x, y: yIntersection };
|
|
822
|
+
},
|
|
823
|
+
/**
|
|
824
|
+
* Calculates the bottom grid intersection point.
|
|
825
|
+
*
|
|
826
|
+
* @param {number} x - X coordinate
|
|
827
|
+
* @param {number} y - Y coordinate
|
|
828
|
+
* @param {number} [gridSize=50] - Size of grid cells
|
|
829
|
+
* @param {number} [minEdgeLength=20] - Minimum edge length
|
|
830
|
+
* @returns {object} Intersection point {x, y}
|
|
831
|
+
* @public
|
|
832
|
+
*/
|
|
833
|
+
getBottomGridIntersectionPoint: function (x, y, gridSize = GRID_SIZE, minEdgeLength = MIN_EDGE_LENGTH) {
|
|
834
|
+
let yIntersection = Math.ceil(y / gridSize) * gridSize;
|
|
835
|
+
if (yIntersection - y < minEdgeLength) {
|
|
836
|
+
yIntersection += gridSize; // Ensure the intersection is at least MIN_EDGE_LENGTH away
|
|
837
|
+
}
|
|
838
|
+
return { x: x, y: yIntersection };
|
|
839
|
+
},
|
|
840
|
+
/**
|
|
841
|
+
* Gets the anchor coordinates for a node based on connection type and anchor type.
|
|
842
|
+
* Enhanced to handle center positioning for single connections and offset positioning for mixed connections.
|
|
843
|
+
*
|
|
844
|
+
* @param {object} oNode - The node object
|
|
845
|
+
* @param {string} sConnectionType - Connection type
|
|
846
|
+
* @param {string} sAnchorType - Anchor type ("source" or "target")
|
|
847
|
+
* @returns {object} Anchor coordinates {x, y}
|
|
848
|
+
* @private
|
|
849
|
+
*/
|
|
850
|
+
_getNodeAnchorCoordinates: function (oNode, sConnectionType, sAnchorType) {
|
|
851
|
+
const x = oNode.getX();
|
|
852
|
+
const y = oNode.getY();
|
|
853
|
+
const width = oNode._iWidth;
|
|
854
|
+
const height = oNode._iHeight;
|
|
855
|
+
const centerY = y + (height / 2);
|
|
856
|
+
|
|
857
|
+
// Enhanced anchor calculation with connection pattern analysis
|
|
858
|
+
const oConnectionAnalysis = this._analyzeNodeConnectionPatterns(oNode);
|
|
859
|
+
// Determine the port position based on the connection type and analysis
|
|
860
|
+
switch (sConnectionType) {
|
|
861
|
+
case "LeftToLeft":
|
|
862
|
+
if (sAnchorType === "source") {
|
|
863
|
+
// Source anchor on left side
|
|
864
|
+
return this._calculateEnhancedAnchor({
|
|
865
|
+
node: { x, y, width, height, centerY },
|
|
866
|
+
side: "left",
|
|
867
|
+
analysis: oConnectionAnalysis,
|
|
868
|
+
anchorType: sAnchorType
|
|
869
|
+
});
|
|
870
|
+
} else {
|
|
871
|
+
// Target anchor on left side
|
|
872
|
+
return this._calculateEnhancedAnchor({
|
|
873
|
+
node: { x, y, width, height, centerY },
|
|
874
|
+
side: "left",
|
|
875
|
+
analysis: oConnectionAnalysis,
|
|
876
|
+
anchorType: sAnchorType
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
case "LeftToRight":
|
|
880
|
+
if (sAnchorType === "source") {
|
|
881
|
+
// Source anchor on left side
|
|
882
|
+
return this._calculateEnhancedAnchor({
|
|
883
|
+
node: { x, y, width, height, centerY },
|
|
884
|
+
side: "left",
|
|
885
|
+
analysis: oConnectionAnalysis,
|
|
886
|
+
anchorType: sAnchorType
|
|
887
|
+
});
|
|
888
|
+
} else {
|
|
889
|
+
// Target anchor on right side
|
|
890
|
+
return this._calculateEnhancedAnchor({
|
|
891
|
+
node: { x, y, width, height, centerY },
|
|
892
|
+
side: "right",
|
|
893
|
+
analysis: oConnectionAnalysis,
|
|
894
|
+
anchorType: sAnchorType
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
case "RightToLeft":
|
|
898
|
+
if (sAnchorType === "source") {
|
|
899
|
+
// Source anchor on right side
|
|
900
|
+
return this._calculateEnhancedAnchor({
|
|
901
|
+
node: { x, y, width, height, centerY },
|
|
902
|
+
side: "right",
|
|
903
|
+
analysis: oConnectionAnalysis,
|
|
904
|
+
anchorType: sAnchorType
|
|
905
|
+
});
|
|
906
|
+
} else {
|
|
907
|
+
// Target anchor on left side
|
|
908
|
+
return this._calculateEnhancedAnchor({
|
|
909
|
+
node: { x, y, width, height, centerY },
|
|
910
|
+
side: "left",
|
|
911
|
+
analysis: oConnectionAnalysis,
|
|
912
|
+
anchorType: sAnchorType
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
case "RightToRight":
|
|
916
|
+
if (sAnchorType === "source") {
|
|
917
|
+
// Source anchor on right side
|
|
918
|
+
return this._calculateEnhancedAnchor({
|
|
919
|
+
node: { x, y, width, height, centerY },
|
|
920
|
+
side: "right",
|
|
921
|
+
analysis: oConnectionAnalysis,
|
|
922
|
+
anchorType: sAnchorType
|
|
923
|
+
});
|
|
924
|
+
} else {
|
|
925
|
+
// Target anchor on right side
|
|
926
|
+
return this._calculateEnhancedAnchor({
|
|
927
|
+
node: { x, y, width, height, centerY },
|
|
928
|
+
side: "right",
|
|
929
|
+
analysis: oConnectionAnalysis,
|
|
930
|
+
anchorType: sAnchorType
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
default:
|
|
934
|
+
// Default RightToLeft case
|
|
935
|
+
if (sAnchorType === "source") {
|
|
936
|
+
return this._calculateEnhancedAnchor({
|
|
937
|
+
node: { x, y, width, height, centerY },
|
|
938
|
+
side: "right",
|
|
939
|
+
analysis: oConnectionAnalysis,
|
|
940
|
+
anchorType: sAnchorType
|
|
941
|
+
});
|
|
942
|
+
} else {
|
|
943
|
+
return this._calculateEnhancedAnchor({
|
|
944
|
+
node: { x, y, width, height, centerY },
|
|
945
|
+
side: "left",
|
|
946
|
+
analysis: oConnectionAnalysis,
|
|
947
|
+
anchorType: sAnchorType
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
},
|
|
952
|
+
/**
|
|
953
|
+
* Transforms source and target coordinates into a normalized format for calculations.
|
|
954
|
+
*
|
|
955
|
+
* @param {object} mParams - Parameters containing node and anchor information
|
|
956
|
+
* @returns {object} Transformed coordinates object
|
|
957
|
+
* @private
|
|
958
|
+
*/
|
|
959
|
+
_transformSourceTargetCoordinates: function (mParams) {
|
|
960
|
+
return {
|
|
961
|
+
x1: mParams.sourceNode.getX(), // x coordinate of the source node
|
|
962
|
+
y1: mParams.sourceNode.getY(), // y coordinate of the source node
|
|
963
|
+
x2: mParams.targetNode.getX(), // x cordinate of the target node
|
|
964
|
+
y2: mParams.targetNode.getY(), // y coordinate of the target node
|
|
965
|
+
AX1: mParams.sourceAnchor.x, // x coordinate of the source anchor
|
|
966
|
+
AY1: mParams.sourceAnchor.y, // y coordinate of the source anchor
|
|
967
|
+
AX2: mParams.targetAnchor.x, // x coordinate of the target anchor
|
|
968
|
+
AY2: mParams.targetAnchor.y, // y coordinate of the target anchor
|
|
969
|
+
connectionType: mParams.connectionType, // type of the connection
|
|
970
|
+
dx: Math.abs(mParams.targetNode.getX() - mParams.sourceNode.getX()), // horizontal distance between source and target nodes
|
|
971
|
+
dy: Math.abs(mParams.targetNode.getY() - mParams.sourceNode.getY()) // vertical distance between source and target nodes
|
|
972
|
+
};
|
|
973
|
+
},
|
|
974
|
+
/**
|
|
975
|
+
* Analyzes the connection patterns for a node to determine optimal anchor placement
|
|
976
|
+
* @param {object} oNode - The node to analyze
|
|
977
|
+
* @returns {object} Analysis object with connection counts per side
|
|
978
|
+
* @private
|
|
979
|
+
*/
|
|
980
|
+
_analyzeNodeConnectionPatterns: function (oNode) {
|
|
981
|
+
if (!oNode || !oNode.getParent || !oNode.getParent()) {
|
|
982
|
+
return { leftIncoming: 0, leftOutgoing: 0, rightIncoming: 0, rightOutgoing: 0 };
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
const oGraph = oNode.getParent();
|
|
986
|
+
const aLines = oGraph.getLines();
|
|
987
|
+
const sNodeKey = oNode.getKey();
|
|
988
|
+
|
|
989
|
+
let leftIncoming = 0, leftOutgoing = 0, rightIncoming = 0, rightOutgoing = 0;
|
|
990
|
+
|
|
991
|
+
aLines.forEach(function(oLine) {
|
|
992
|
+
if (!oLine.getVisible() || oLine._isIgnored()) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
const sFromKey = oLine.getFrom();
|
|
997
|
+
const sToKey = oLine.getTo();
|
|
998
|
+
const sLineConnectionType = oLine.getConnectionType();
|
|
999
|
+
|
|
1000
|
+
// Check if this line involves our node
|
|
1001
|
+
if (sFromKey === sNodeKey) {
|
|
1002
|
+
// Outgoing connection from our node
|
|
1003
|
+
switch (sLineConnectionType) {
|
|
1004
|
+
case "LeftToLeft":
|
|
1005
|
+
case "LeftToRight":
|
|
1006
|
+
leftOutgoing++;
|
|
1007
|
+
break;
|
|
1008
|
+
case "RightToLeft":
|
|
1009
|
+
case "RightToRight":
|
|
1010
|
+
rightOutgoing++;
|
|
1011
|
+
break;
|
|
1012
|
+
default:
|
|
1013
|
+
// Default to right for unrecognized connection types
|
|
1014
|
+
rightOutgoing++;
|
|
1015
|
+
break;
|
|
1016
|
+
}
|
|
1017
|
+
} else if (sToKey === sNodeKey) {
|
|
1018
|
+
// Incoming connection to our node
|
|
1019
|
+
switch (sLineConnectionType) {
|
|
1020
|
+
case "LeftToLeft":
|
|
1021
|
+
case "RightToLeft":
|
|
1022
|
+
leftIncoming++;
|
|
1023
|
+
break;
|
|
1024
|
+
case "LeftToRight":
|
|
1025
|
+
case "RightToRight":
|
|
1026
|
+
rightIncoming++;
|
|
1027
|
+
break;
|
|
1028
|
+
default:
|
|
1029
|
+
// Default to left for unrecognized connection types
|
|
1030
|
+
leftIncoming++;
|
|
1031
|
+
break;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
return {
|
|
1037
|
+
leftIncoming: leftIncoming,
|
|
1038
|
+
leftOutgoing: leftOutgoing,
|
|
1039
|
+
rightIncoming: rightIncoming,
|
|
1040
|
+
rightOutgoing: rightOutgoing
|
|
1041
|
+
};
|
|
1042
|
+
},
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Calculates enhanced anchor position based on connection analysis
|
|
1046
|
+
* @param {object} mParams - Parameters for anchor calculation
|
|
1047
|
+
* @param {object} mParams.node - Node position and size info
|
|
1048
|
+
* @param {string} mParams.side - Side of the node ("left" or "right")
|
|
1049
|
+
* @param {object} mParams.analysis - Connection pattern analysis
|
|
1050
|
+
* @param {string} mParams.anchorType - Anchor type ("source" or "target")
|
|
1051
|
+
* @returns {object} Anchor coordinates {x, y}
|
|
1052
|
+
* @private
|
|
1053
|
+
*/
|
|
1054
|
+
_calculateEnhancedAnchor: function (mParams) {
|
|
1055
|
+
const { node, side, analysis, anchorType } = mParams;
|
|
1056
|
+
const { x, y, width, height, centerY } = node;
|
|
1057
|
+
|
|
1058
|
+
const offsetDistance = height * 0.15; // 15% of node height for offset positioning
|
|
1059
|
+
|
|
1060
|
+
// Determine base X coordinate for the side
|
|
1061
|
+
const baseX = side === "left" ? x : (x + width);
|
|
1062
|
+
|
|
1063
|
+
// Get connection counts for this side
|
|
1064
|
+
const sideIncoming = side === "left" ? analysis.leftIncoming : analysis.rightIncoming;
|
|
1065
|
+
const sideOutgoing = side === "left" ? analysis.leftOutgoing : analysis.rightOutgoing;
|
|
1066
|
+
|
|
1067
|
+
// Calculate Y position based on connection patterns
|
|
1068
|
+
let anchorY = centerY; // Default to center
|
|
1069
|
+
|
|
1070
|
+
// ENHANCEMENT 1: Single connection type on this side -> use center
|
|
1071
|
+
if ((sideIncoming === 0 && sideOutgoing === 1) || (sideIncoming === 1 && sideOutgoing === 0)) {
|
|
1072
|
+
anchorY = centerY; // Center position for single connection
|
|
1073
|
+
} else if (sideIncoming > 0 && sideOutgoing > 0) {
|
|
1074
|
+
// ENHANCEMENT 2: Mixed connections on this side -> offset positioning
|
|
1075
|
+
if (anchorType === "target") {
|
|
1076
|
+
// Incoming connections (targets) go above center
|
|
1077
|
+
anchorY = centerY - offsetDistance;
|
|
1078
|
+
} else {
|
|
1079
|
+
// Outgoing connections (sources) go below center
|
|
1080
|
+
anchorY = centerY + offsetDistance;
|
|
1081
|
+
}
|
|
1082
|
+
} else {
|
|
1083
|
+
// Multiple connections of same type -> use center (for now)
|
|
1084
|
+
anchorY = centerY;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// Ensure anchor stays within node boundaries
|
|
1088
|
+
const minY = y + (height * 0.1); // 10% margin from top
|
|
1089
|
+
const maxY = y + (height * 0.9); // 10% margin from bottom
|
|
1090
|
+
anchorY = Math.max(minY, Math.min(maxY, anchorY));
|
|
1091
|
+
|
|
1092
|
+
return {
|
|
1093
|
+
x: baseX,
|
|
1094
|
+
y: Math.floor(anchorY)
|
|
1095
|
+
};
|
|
1096
|
+
},
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* Determines if a horizontal connection should use a straight line based on connection patterns.
|
|
1100
|
+
* Returns true only when both nodes have single dedicated connections (source has single outgoing, target has single incoming).
|
|
1101
|
+
*
|
|
1102
|
+
* @param {object} sourceNode - The source node
|
|
1103
|
+
* @param {object} targetNode - The target node
|
|
1104
|
+
* @param {string} connectionType - The connection type
|
|
1105
|
+
* @returns {boolean} True if straight line should be used, false if bends are needed
|
|
1106
|
+
* @private
|
|
1107
|
+
*/
|
|
1108
|
+
_shouldUseHorizontalStraightLine: function (sourceNode, targetNode, connectionType) {
|
|
1109
|
+
const sourceAnalysis = this._analyzeNodeConnectionPatterns(sourceNode);
|
|
1110
|
+
const targetAnalysis = this._analyzeNodeConnectionPatterns(targetNode);
|
|
1111
|
+
|
|
1112
|
+
// Determine which sides are involved based on connection type
|
|
1113
|
+
let sourceOutgoingCount, targetIncomingCount;
|
|
1114
|
+
|
|
1115
|
+
switch (connectionType) {
|
|
1116
|
+
case "RightToLeft":
|
|
1117
|
+
sourceOutgoingCount = sourceAnalysis.rightOutgoing;
|
|
1118
|
+
targetIncomingCount = targetAnalysis.leftIncoming;
|
|
1119
|
+
break;
|
|
1120
|
+
case "LeftToRight":
|
|
1121
|
+
sourceOutgoingCount = sourceAnalysis.leftOutgoing;
|
|
1122
|
+
targetIncomingCount = targetAnalysis.rightIncoming;
|
|
1123
|
+
break;
|
|
1124
|
+
case "LeftToLeft":
|
|
1125
|
+
sourceOutgoingCount = sourceAnalysis.leftOutgoing;
|
|
1126
|
+
targetIncomingCount = targetAnalysis.leftIncoming;
|
|
1127
|
+
break;
|
|
1128
|
+
case "RightToRight":
|
|
1129
|
+
sourceOutgoingCount = sourceAnalysis.rightOutgoing;
|
|
1130
|
+
targetIncomingCount = targetAnalysis.rightIncoming;
|
|
1131
|
+
break;
|
|
1132
|
+
default:
|
|
1133
|
+
// Default to RightToLeft
|
|
1134
|
+
sourceOutgoingCount = sourceAnalysis.rightOutgoing;
|
|
1135
|
+
targetIncomingCount = targetAnalysis.leftIncoming;
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// Allow straight line only when both nodes have single dedicated connections
|
|
1140
|
+
return (sourceOutgoingCount === 1 && targetIncomingCount === 1);
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
return ConnectionPathUtils;
|
|
1144
|
+
});
|