I have this doLayout logic taken from gojs forums (not sure about the source)
im adding that in this way (line 102)
All i want to maintain the order of the lanes,
example, i have parendId: “l1” and parentId:“l2”
but due to the logic written in the do layout its repositioning itself based on the positions, so we can hardcode if parentId===“l1” then set it first and for l2 set it 2nd order,
Note: I tried to change the order by pushing elements to the array, but it doesnt seem to work.
Do layout is a part of CustomSwimlaneLayout class
doLayout(coll: go.Group): void {
const diagram = coll.diagram;
const isVertical = coll?.containingGroup?.data?.isVertical;
const minLaneWidth = coll?.data?.metaData?.minLaneWidth ?? 0;
const modelData = diagram.model.modelData as ISwimlaneModelData;
this._layoutCompletedListener(diagram);
let { positionInfo, lanesRankMap, lanes, maxWidth, maxHeight } = {
positionInfo: null,
lanesRankMap: {},
lanes: [],
maxWidth: minLaneWidth,
maxHeight: 0,
...(modelData ?? {}),
};
let maxX = 0;
if (!positionInfo) {
this._addTempLinks(diagram);
positionInfo = {};
const flowInfo = getFlowInfo(diagram);
const startNode = diagram.nodes
.filter((node) => isStartNode(node))
.first();
let start = startNode.key as string;
const end = diagram.nodes.filter((node) => isEndNode(node)).first()
.key as string;
if (coll?.containingGroup?.data?.skipNodeIndexRetain) {
start = startNode.findNodesOutOf().first().key as string;
}
const traversalInfo = traverseGraph(flowInfo, start);
const endTraversalInfo = traversalInfo[end];
/** Sort by longest path to shortest path from start to end nodes. */
const orderedTraversal = orderBy(
endTraversalInfo.info,
"distance",
"desc"
);
const otherTraversals: INodeTraversalInfo[] = [];
for (const infoNode in traversalInfo) {
if (infoNode !== end) {
otherTraversals.push(...traversalInfo[infoNode].info);
}
}
const traversals = [
...orderedTraversal,
/** Other nodes traversals from start, need this as some circular linking nodes will not be part of end node traversal list. */
...orderBy(otherTraversals, "distance", "desc"),
];
const nodesCount = diagram.nodes.filter(
(node) =>
node.category !== CanvasShapesCategory.SWIMLANE &&
node.category !== CanvasShapesCategory.POOL
).count;
const visitedNodesMap = {};
/** To stroke collection of nodes in x index. */
const positionsMap: {
nodeIds: string[];
lanesMap: { [laneId: string]: boolean };
}[] = [];
for (const traversal of traversals) {
if (Object.keys(visitedNodesMap).length === nodesCount - 1) {
break;
}
for (const [index, nodeId] of traversal.path.entries()) {
if (Object.keys(visitedNodesMap).length === nodesCount - 1) {
break;
}
const node = diagram.findNodeForKey(nodeId);
const { width, height } = node.actualBounds;
if (maxWidth < width) {
maxWidth = width;
}
if (maxHeight < height) {
maxHeight = height;
}
if (!visitedNodesMap[nodeId] && nodeId !== end && node) {
visitedNodesMap[nodeId] = true;
const parentId = node.data.parentID;
if (parentId) {
if (!lanesRankMap[parentId]) {
const keysLength = Object.keys(lanesRankMap).length;
diagram.findNodeForKey(parentId).data.rank = keysLength + 1;
lanesRankMap[parentId] = {
rank: keysLength,
xIndexInfo: {},
maxY: 0,
};
lanes.push(parentId);
}
const y = lanesRankMap[parentId].xIndexInfo[index]?.length ?? 0;
positionInfo[nodeId] = {
x: isVertical ? y : index,
y: isVertical ? index : y,
};
if (!positionsMap[index]) {
positionsMap[index] = {
nodeIds: [],
lanesMap: {},
};
}
positionsMap[index].nodeIds.push(nodeId);
positionsMap[index].lanesMap[parentId] = true;
if (maxX < index) {
maxX = index;
}
if (!lanesRankMap[parentId].xIndexInfo[index]) {
lanesRankMap[parentId].xIndexInfo[index] = [];
}
lanesRankMap[parentId].xIndexInfo[index].push(nodeId);
if (lanesRankMap[parentId].maxY < y) {
lanesRankMap[parentId].maxY = y;
}
}
}
}
}
/** To adjust nodes to left if there is space availabel to move left without effecting the links. */
if (!coll?.containingGroup?.data?.skipNodeIndexRetain) {
let positionIndex = 0;
while (
positionIndex !== positionsMap.length - 1 &&
positionsMap.length
) {
if (!positionsMap[positionIndex]) {
positionsMap[positionIndex] = {
nodeIds: [],
lanesMap: {},
};
}
const nodeIds = positionsMap[positionIndex]?.nodeIds ?? [];
if (nodeIds.length === 1) {
const currentNodeId = nodeIds[0];
const currentNode = diagram.findNodeForKey(currentNodeId);
const fromNodes = currentNode.findNodesInto();
const parentId = currentNode.data.parentID;
const fromNodeParentId = fromNodes.first()?.data?.parentID;
/** Checks if no of nodes in current index is only one and
* current node and its parent node are not in same lane and
* current node lane space is empty to move one step left */
const previousIndex = positionIndex - 1;
if (
fromNodes.count === 1 &&
parentId !== fromNodeParentId &&
!lanesRankMap[parentId].xIndexInfo[previousIndex]?.length &&
!positionsMap[previousIndex]?.lanesMap?.[parentId]
) {
positionsMap[previousIndex].nodeIds.push(currentNodeId);
positionsMap[previousIndex].lanesMap[parentId] = true;
positionsMap.splice(positionIndex, 1);
positionIndex--;
}
}
positionIndex++;
}
/** Updates position info based on shifted positions. */
for (const [index, { nodeIds }] of positionsMap.entries()) {
nodeIds.forEach((nodeId) => {
if (isVertical) {
positionInfo[nodeId].y = index;
} else {
positionInfo[nodeId].x = index;
}
});
}
/** To keep end node at the end. */
positionInfo[end] = {
y: isVertical ? positionsMap.length : 0,
x: isVertical ? 0 : positionsMap.length,
};
}
modelData.positionInfo = positionInfo;
modelData.lanesRankMap = lanesRankMap;
modelData.lanes = lanes;
modelData.maxHeight = maxHeight;
modelData.maxWidth = maxWidth;
}
this._updateLaneMembers(coll, modelData);
}
if you wanna know the data, then somewhat looks like this
p1 is pool, l1 and l2 are the lanes
{
nodeId:“p1”,
parentId:nul,
…data
}
{
nodeId:“l1”,
parentId:“p1”,
…data
}
{
nodeId:“l2”,
parentId:“p1”,
…data
}
…other nodes in similar way