7
7
8
8
import { NodePlacement } from './layout.type' ;
9
9
import { groupIdSuffix , LABELED_GROUP_TYPE } from './nodes/labeled-group-node.type' ;
10
- import { CurrentTreeNode , NetworkModificationNodeType } from './tree-node.type' ;
10
+ import { CurrentTreeNode , isSecurityModificationNode , NetworkModificationNodeType } from './tree-node.type' ;
11
11
12
12
export const nodeWidth = 230 ;
13
13
export const nodeHeight = 110 ;
@@ -109,8 +109,8 @@ function getNodePlacements(nodes: CurrentTreeNode[]): PlacementGrid {
109
109
}
110
110
111
111
/**
112
- * Create a Map using row number as keys and column number as value. The column value
113
- * for each row is either the lowest or highest value among column values of the same row, for the provided nodes.
112
+ * Create a Map using row number as keys and column number as value. The column value for each row is either
113
+ * the lowest or highest (extreme) value among column values of the same row for the provided nodes.
114
114
*
115
115
* Example nodes and placements :
116
116
* - NodeA {row:0, column:30}
@@ -119,36 +119,102 @@ function getNodePlacements(nodes: CurrentTreeNode[]): PlacementGrid {
119
119
* - NodeD {row:2, column:40}
120
120
* - NodeE {row:2, column:80}
121
121
*
122
- * For these placements, the returned Map would be like this : {0 => 30, 1 => 10, 2 => 40}
122
+ * For these placements, the returned Map would be, with getMax=false, like this : {0 => 30, 1 => 10, 2 => 40}
123
+ * and with getMax=true, like this : {0 => 30, 1 => 50, 2 => 80}
124
+ *
125
+ * If there are security nodes in the tree, we consider that each group of security nodes share the
126
+ * same lowest or highest value for each of their rows.
123
127
*
124
128
* @param nodes The nodes to process
125
129
* @param placements The grid placements
130
+ * @param nodeMap The map used to find a node's parent
126
131
* @param getMax If true, returns maximum columns, if false returns minimum columns
127
132
*/
128
- function getColumnsByRows ( nodes : CurrentTreeNode [ ] , placements : PlacementGrid , getMax : boolean ) : Map < number , number > {
133
+ function getColumnsByRows (
134
+ nodes : CurrentTreeNode [ ] ,
135
+ placements : PlacementGrid ,
136
+ nodeMap : Map < string , { index : number ; node : CurrentTreeNode } > ,
137
+ getMax : boolean
138
+ ) : Map < number , number > {
129
139
const columnsByRow : Map < number , number > = new Map ( ) ;
140
+
141
+ // These two variables are used to process the groups of security nodes, to make them all match the same value.
142
+ // When in a security group, we store the extreme value in currentExtremeValue, and the rows of the current
143
+ // security group in rowsToRetroactivelyUpdate.
144
+ // When we find a new extreme in a security group, we update all its rows with the new value.
145
+ let currentExtremeValue : number ;
146
+ let rowsToRetroactivelyUpdate = [ ] ;
147
+
148
+ function isExtreme ( contender : number , competition : number | undefined ) : boolean {
149
+ if ( competition === undefined ) {
150
+ return true ;
151
+ }
152
+ return getMax ? contender > competition : contender < competition ;
153
+ }
154
+
155
+ function resetSecurityGroupContext ( ) {
156
+ rowsToRetroactivelyUpdate . length = 0 ;
157
+ currentExtremeValue = getMax ? - Infinity : Infinity ;
158
+ }
159
+
160
+ resetSecurityGroupContext ( ) ;
130
161
nodes . forEach ( ( node ) => {
131
162
const nodePlacement = placements . getPlacement ( node . id ) ;
132
- if ( nodePlacement ) {
133
- if (
134
- ! columnsByRow . has ( nodePlacement . row ) ||
135
- ( getMax
136
- ? nodePlacement . column > columnsByRow . get ( nodePlacement . row ) !
137
- : nodePlacement . column < columnsByRow . get ( nodePlacement . row ) ! )
138
- ) {
139
- columnsByRow . set ( nodePlacement . row , nodePlacement . column ) ;
163
+ if ( ! nodePlacement || ! node . parentId ) {
164
+ return ;
165
+ }
166
+ // Security nodes are grouped together and enclosed in a rectangle. Each of those rectangles
167
+ // must not be overlapped by the compression algorithm. To do so, when we are in a security
168
+ // group of nodes, we consider that every row of this security group has the extreme value
169
+ // of the whole group, and when we assign a new extreme, we do so to all the rows of the group.
170
+
171
+ if ( isSecurityModificationNode ( node ) ) {
172
+ // This test determines if we changed from a security group to another. If this is the case,
173
+ // we reset the currentExtremeValue to only update rows for the new group and not the old one.
174
+ if ( ! isSecurityModificationNode ( nodeMap . get ( node . parentId ) ?. node ) ) {
175
+ resetSecurityGroupContext ( ) ;
176
+ }
177
+
178
+ // We mark each row of a security group in order to update them all when we find a new extreme.
179
+ rowsToRetroactivelyUpdate . push ( nodePlacement . row ) ;
180
+
181
+ const contender = getMax
182
+ ? Math . max ( currentExtremeValue , nodePlacement . column )
183
+ : Math . min ( currentExtremeValue , nodePlacement . column ) ;
184
+
185
+ if ( isExtreme ( contender , columnsByRow . get ( nodePlacement . row ) ) ) {
186
+ rowsToRetroactivelyUpdate . forEach ( ( row ) => {
187
+ columnsByRow . set ( row , contender ) ;
188
+ } ) ;
189
+ }
190
+
191
+ currentExtremeValue = contender ;
192
+ } else {
193
+ resetSecurityGroupContext ( ) ;
194
+
195
+ const contender = nodePlacement . column ;
196
+ if ( isExtreme ( contender , columnsByRow . get ( nodePlacement . row ) ) ) {
197
+ columnsByRow . set ( nodePlacement . row , contender ) ;
140
198
}
141
199
}
142
200
} ) ;
143
201
return columnsByRow ;
144
202
}
145
203
146
- function getMinimumColumnByRows ( nodes : CurrentTreeNode [ ] , placements : PlacementGrid ) : Map < number , number > {
147
- return getColumnsByRows ( nodes , placements , false ) ;
204
+ function getMinimumColumnByRows (
205
+ nodes : CurrentTreeNode [ ] ,
206
+ placements : PlacementGrid ,
207
+ nodeMap : Map < string , { index : number ; node : CurrentTreeNode } >
208
+ ) : Map < number , number > {
209
+ return getColumnsByRows ( nodes , placements , nodeMap , false ) ;
148
210
}
149
211
150
- function getMaximumColumnByRows ( nodes : CurrentTreeNode [ ] , placements : PlacementGrid ) : Map < number , number > {
151
- return getColumnsByRows ( nodes , placements , true ) ;
212
+ function getMaximumColumnByRows (
213
+ nodes : CurrentTreeNode [ ] ,
214
+ placements : PlacementGrid ,
215
+ nodeMap : Map < string , { index : number ; node : CurrentTreeNode } >
216
+ ) : Map < number , number > {
217
+ return getColumnsByRows ( nodes , placements , nodeMap , true ) ;
152
218
}
153
219
154
220
/**
@@ -226,7 +292,7 @@ function compressTreePlacements(
226
292
for ( let i = nodes . length - 1 ; i >= 0 ; i -- ) {
227
293
const node = nodes [ i ] ;
228
294
const children = childrenMap . get ( node . id ) || [ ] ;
229
- const childrenSize = children . reduce ( ( sum , child ) => sum + ( subTreeSizeMap . get ( child . id ) || 0 ) , 0 ) ;
295
+ const childrenSize = children . reduce ( ( sum , child ) => sum + ( subTreeSizeMap . get ( child . id ) ?? 0 ) , 0 ) ;
230
296
subTreeSizeMap . set ( node . id , 1 + childrenSize ) ;
231
297
}
232
298
@@ -251,8 +317,9 @@ function compressTreePlacements(
251
317
// cases other branches could go under the left neighbor and make edges cross.
252
318
const leftNodes = nodes . slice ( 0 , currentNodeIndex ) ;
253
319
254
- const currentBranchMinimumColumnByRow = getMinimumColumnByRows ( currentBranchNodes , placements ) ;
255
- const leftBranchMaximumColumnByRow = getMaximumColumnByRows ( leftNodes , placements ) ;
320
+ const currentBranchMinimumColumnByRow = getMinimumColumnByRows ( currentBranchNodes , placements , nodeMap ) ;
321
+
322
+ const leftBranchMaximumColumnByRow = getMaximumColumnByRows ( leftNodes , placements , nodeMap ) ;
256
323
257
324
const availableSpace = calculateAvailableSpace ( leftBranchMaximumColumnByRow , currentBranchMinimumColumnByRow ) ;
258
325
@@ -341,7 +408,7 @@ function computeSecurityGroupNodes(
341
408
}
342
409
343
410
// If currently not in a security group, and a security node is found, we initiate securityGroupBuilder
344
- if ( ! securityGroupBuilder . isInSecurityGroup && node . data . nodeType === NetworkModificationNodeType . SECURITY ) {
411
+ if ( ! securityGroupBuilder . isInSecurityGroup && isSecurityModificationNode ( node ) ) {
345
412
securityGroupBuilder = {
346
413
isInSecurityGroup : true ,
347
414
securityGroupTopLeftPosition : nodePlacement ,
0 commit comments