forked from KaotoIO/kaoto
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feate(Canvas): Extend DagreGroupsLayout
Currently, `DagreLayout` is used to render the graph, and while it does work, it doesn't support nested groups properly due to a limitation in Dagre itself. In the `@patternfly/react-topology` there's another layout called `DagreGroupsLayout` that performs individual layout inside each group, providing a better result when the graph contains nested groups. This commit brings that class over and uses the initial node index for sorting the graph node, this way the order is kept when expanding/collapsing nodes in the Canvas. Related issue: patternfly/react-topology#230
- Loading branch information
Showing
7 changed files
with
167 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 108 additions & 0 deletions
108
packages/ui/src/components/Visualization/Custom/Layout/DagreGroupsExtendedLayout.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import * as dagre from '@dagrejs/dagre'; | ||
import { | ||
DagreGroupsLayout, | ||
getGroupChildrenDimensions, | ||
Graph, | ||
GRAPH_LAYOUT_END_EVENT, | ||
LAYOUT_DEFAULTS, | ||
LayoutGroup, | ||
Point, | ||
} from '@patternfly/react-topology'; | ||
import { DagreLink } from './DagreLink'; | ||
import { DagreNode } from './DagreNode'; | ||
|
||
/** | ||
* This class extends the DagreGroupsLayout class to provide a consistent | ||
* layout for groups and nodes in a graph. It uses an index provided by the | ||
* canvas.service.ts to determine the order of the nodes and groups in the | ||
* graph. | ||
* | ||
* Related issue: https://github.com/patternfly/react-topology/issues/230 | ||
*/ | ||
export class DagreGroupsExtendedLayout extends DagreGroupsLayout { | ||
protected startLayout(graph: Graph, initialRun: boolean, addingNodes: boolean): void { | ||
if (initialRun || addingNodes) { | ||
const doLayout = (parentGroup?: LayoutGroup) => { | ||
const dagreGraph = new dagre.graphlib.Graph({ compound: true }); | ||
const options = { ...this.dagreOptions }; | ||
|
||
Object.keys(LAYOUT_DEFAULTS).forEach((key) => delete options[key as keyof typeof options]); | ||
dagreGraph.setGraph(options); | ||
|
||
// Determine the groups, nodes, and edges that belong in this layout | ||
const layerGroups = this.groups.filter( | ||
(group) => group.parent?.id === parentGroup?.id || (!parentGroup && group.parent?.id === graph.getId()), | ||
); | ||
const layerNodes = this.nodes.filter( | ||
(n) => | ||
n.element.getParent()?.getId() === parentGroup?.id || | ||
(!parentGroup && n.element.getParent()?.getId() === graph.getId()), | ||
); | ||
const layerEdges = this.edges.filter( | ||
(edge) => | ||
(layerGroups.find((n) => n.id === edge.sourceNode.id) || | ||
layerNodes.find((n) => n.id === edge.sourceNode.id)) && | ||
(layerGroups.find((n) => n.id === edge.targetNode.id) || | ||
layerNodes.find((n) => n.id === edge.targetNode.id)), | ||
); | ||
|
||
const nodesOrder: { id: string; index: number; node: ReturnType<DagreNode['getUpdatableNode']> }[] = []; | ||
|
||
// Layout any child groups first | ||
layerGroups.forEach((group) => { | ||
doLayout(group); | ||
|
||
// Add the child group node (now with the correct dimensions) to the graph | ||
const dagreNode = new DagreNode(group.element, group.padding); | ||
const updateNode = dagreNode.getUpdatableNode(); | ||
nodesOrder.push({ id: group.id, index: group.element.getData().index, node: updateNode }); | ||
}); | ||
|
||
layerNodes?.forEach((node) => { | ||
const updateNode = (node as DagreNode).getUpdatableNode(); | ||
nodesOrder.push({ id: node.id, index: node.element.getData().index, node: updateNode }); | ||
}); | ||
|
||
// Sort the nodes by their index | ||
nodesOrder.sort((a, b) => a.index - b.index); | ||
|
||
// Set the nodes in the order they were sorted | ||
nodesOrder.forEach((node) => { | ||
dagreGraph.setNode(node.id, node.node); | ||
}); | ||
|
||
layerEdges?.forEach((dagreEdge) => { | ||
dagreGraph.setEdge(dagreEdge.source.id, dagreEdge.target.id, dagreEdge); | ||
}); | ||
|
||
dagre.layout(dagreGraph); | ||
|
||
// Update the node element positions | ||
layerNodes.forEach((node) => { | ||
(node as DagreNode).updateToNode(dagreGraph.node(node.id)); | ||
}); | ||
|
||
// Update the group element positions (setting the group's positions updates its children) | ||
layerGroups.forEach((node) => { | ||
const dagreNode = dagreGraph.node(node.id); | ||
node.element.setPosition(new Point(dagreNode.x, dagreNode.y)); | ||
}); | ||
|
||
this.updateEdgeBendpoints(this.edges as DagreLink[]); | ||
|
||
// now that we've laid out the children, set the dimensions on the group (not on the graph) | ||
if (parentGroup) { | ||
parentGroup.element.setDimensions(getGroupChildrenDimensions(parentGroup.element)); | ||
} | ||
}; | ||
|
||
doLayout(); | ||
} | ||
|
||
if (this.dagreOptions.layoutOnDrag) { | ||
this.forceSimulation.useForceSimulation(this.nodes, this.edges, this.getFixedNodeDistance); | ||
} else { | ||
this.graph.getController().fireEvent(GRAPH_LAYOUT_END_EVENT, { graph: this.graph }); | ||
} | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
packages/ui/src/components/Visualization/Custom/Layout/DagreLink.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { LayoutLink, Point } from '@patternfly/react-topology'; | ||
|
||
/** | ||
* This class extends the LayoutLink class since DagreLink is not exported from | ||
* the react-topology library. | ||
* | ||
* Related issue: https://github.com/patternfly/react-topology/issues/230 | ||
*/ | ||
export class DagreLink extends LayoutLink { | ||
public points?: { x: number; y: number }[]; | ||
|
||
updateBendpoints(): void { | ||
if (this.points && !this.isFalse && this.points.length > 2) { | ||
this.element.setBendpoints(this.points.slice(1, -1).map((point) => new Point(point.x, point.y))); | ||
} | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
packages/ui/src/components/Visualization/Custom/Layout/DagreNode.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import * as dagre from '@dagrejs/dagre'; | ||
import { LayoutNode } from '@patternfly/react-topology'; | ||
|
||
/** | ||
* This class extends the LayoutNode class since DagreNode is not exported from | ||
* the react-topology library. | ||
* | ||
* Related issue: https://github.com/patternfly/react-topology/issues/230 | ||
*/ | ||
export class DagreNode extends LayoutNode implements dagre.Node { | ||
getUpdatableNode(): dagre.Node { | ||
return { | ||
width: this.width, | ||
height: this.height, | ||
x: this.x, | ||
y: this.y, | ||
}; | ||
} | ||
|
||
updateToNode(updatedNode: dagre.Node | undefined): void { | ||
if (updatedNode) { | ||
this.x = updatedNode.x; | ||
this.y = updatedNode.y; | ||
this.update(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters