Blazor.Diagrams icon indicating copy to clipboard operation
Blazor.Diagrams copied to clipboard

[Contribution] Horizontal graph auto-layout code

Open ud-waffle opened this issue 3 years ago • 8 comments

Altought this code is specific for the model of my project (w/ InPorts & OutPorts) I've made a recursive method to organize the graph layout horizontally. Example: Before auto layout call & After layout call

In case this helps someone integration I'll drop the code here.

The function itself:

 //NodeData is the custom NodeModel implementation
 public delegate void SearchAction(NodeData node, int depthLevel, int branchesOffset);

public int DFS_SearchGraph(SearchAction searchAction, NodeData currentNode, int depthLevel, int maxBranchesPrevChilds)
        {
            var maxBranchesCurrent = 0;
            int maxBranchesPrevChild = 0;


            for (int i = 0; i < currentNode.OutPort.connectedNodes.Count; i++)
            {
                maxBranchesPrevChild = DFS_SearchGraph(searchAction, currentNode.OutPort.connectedNodes[i], depthLevel + 1, maxBranchesPrevChilds + maxBranchesCurrent);

                if (i < currentNode.OutPort.connectedNodes.Count-1) //Has next branch
                    maxBranchesCurrent += maxBranchesPrevChild + 1;
                else
                    maxBranchesCurrent += maxBranchesPrevChild;
            }
            searchAction(currentNode, depthLevel, maxBranchesPrevChilds);
            return maxBranchesCurrent;
        }

The delegate implementation & function call:


public void OrganizeLayout()
        {
            const int marginX = 400;
            const int marginY = 120;


            var rootNode = FindRootNode();
            if (rootNode is null) return;


            Dictionary<int, (double?, double?)> nodesPos = new();

            foreach (NodeData node in diagram.Nodes)
                nodesPos.Add(node.DataId, (null, null));


            SearchAction organizeLayoutAction = (n, l, m) =>
            {

                var xFromCurrent = l * marginX;
                var yFromCurrent = m * marginY;
                if (nodesPos[n.DataId].Item1.HasValue) //If node already visited
                {
                    var xSet = nodesPos[n.DataId].Item1.Value;

                    var ySet = nodesPos[n.DataId].Item2.Value;

                    nodesPos[n.DataId] = (xFromCurrent > xSet ? xFromCurrent : xSet, yFromCurrent < ySet ? yFromCurrent : ySet);
                }
                else
                {
                    nodesPos[n.DataId] = (xFromCurrent, yFromCurrent);
                }
            };

            DFS_SearchGraph(organizeLayoutAction, rootNode, 0, 0);


            foreach (var nodePos in nodesPos)
            {
                var test = nodePos;
                var node = diagram.Nodes.Where(x => ((NodeData)x).DataId == nodePos.Key).FirstOrDefault();
                node?.SetPosition(nodePos.Value.Item1.Value, nodePos.Value.Item2.Value);
            }

            diagram.Refresh();
        }

  public NodeData FindRootNode()
        {
            foreach (var node in diagram.Nodes)
                if (((NodeData)node).InPort.IsPortEmpty())
                    return (NodeData)node;
            return null;
        }

ud-waffle avatar Jun 25 '21 08:06 ud-waffle

Hello, would it be possible to see how this looks like?

Thank you for the initiative!

zHaytam avatar Jun 25 '21 12:06 zHaytam

Hi, I've already placed two pictures above with the previous & after result (in the above comment). Later on I can try to simplify the project and make public branch of it.

ud-waffle avatar Jun 25 '21 15:06 ud-waffle

That's looking very neat! Was there any article you based yourself on? I'd like to implement these algorithms, but I kind of want the same for multiple directions/orientations.

zHaytam avatar Jun 26 '21 14:06 zHaytam

The graph search algorithm is based on DFS but the rest was made by head w/ some trial and error, if that makes sense? To have multiple directions/orientations I'm guessing: You can probably just alter the marginX & marginY values to negative and invert the flow direction. Or use depthLevel for Y position & branchesOffset for X position to make it flow vertically. But never tried it out tho

ud-waffle avatar Jun 28 '21 10:06 ud-waffle

Thanks so much for posting this. I was thinking of how to get this to align in an 'org chart' type manner, and this will be a good starting point!

glinkot avatar Nov 25 '22 00:11 glinkot

Hi there,

I began trying to put this in place, but there are some properties I'm not seeing in the standard node implementation:

  • 'Outport' on nodes
  • 'connectedNodes' on ports
  • dataId on nodes

@ud-waffle would you mind providing an example node that implements these?

glinkot avatar Dec 07 '22 05:12 glinkot

Never mind @ud-waffle, I implemented those. Thanks for the very handy code!

glinkot avatar Dec 07 '22 21:12 glinkot

Hi @glinkot , would you mind sharing your implementation?

petarvu avatar Feb 22 '23 10:02 petarvu