microsoft / automatic-graph-layout

A set of tools for graph layout and viewing
Other
1.36k stars 304 forks source link

Setting edge ports pre layout run raising a NullReferenceException #250

Closed SMTStuck closed 4 years ago

SMTStuck commented 4 years ago

I am looking through the EdgeRoutingSample project, and notice that after the graph reader has read the file and built the GeometryGraph, ports are then added to the edges should they not exist.

However, in the code below, I recieve a NullReferenceException on the layout.Run(); call:

public void RunSample()
{
    GeometryGraph geometryGraph = new GeometryGraph();
    geometryGraph.Nodes.Add(new Node(CurveFactory.CreateRectangle(100, 100, new Microsoft.Msagl.Core.Geometry.Point(0, 0)), "1"));
    geometryGraph.Nodes.Add(new Node(CurveFactory.CreateRectangle(300, 300, new Microsoft.Msagl.Core.Geometry.Point(0, 0)), "2"));
    geometryGraph.Nodes.Add(new Node(CurveFactory.CreateRectangle(100, 100, new Microsoft.Msagl.Core.Geometry.Point(0, 0)), "3"));

    Edge edge = new Edge(geometryGraph.Nodes[0], geometryGraph.Nodes[1], 0, 0, 1);
    edge.UserData = "1 - 2";
    geometryGraph.Edges.Add(edge);

    edge = new Edge(geometryGraph.Nodes[1], geometryGraph.Nodes[2], 0, 0, 1);
    edge.UserData = "2 - 3";
    geometryGraph.Edges.Add(edge);

    edge = new Edge(geometryGraph.Nodes[0], geometryGraph.Nodes[2], 0, 0, 1);
    edge.UserData = "1 - 3";
    geometryGraph.Edges.Add(edge);

    SugiyamaLayoutSettings layoutSettings = new SugiyamaLayoutSettings();
    layoutSettings.EdgeRoutingSettings.EdgeRoutingMode = Microsoft.Msagl.Core.Routing.EdgeRoutingMode.Spline;
    layoutSettings.NodeSeparation = 125;
    layoutSettings.LayerSeparation = 125;

    foreach (Edge tEdge in geometryGraph.Edges)
    {
        if (tEdge.SourcePort == null)
            tEdge.SourcePort = new FloatingPort(tEdge.Source.BoundaryCurve, tEdge.Source.Center);
        if (tEdge.TargetPort == null)
            tEdge.TargetPort = new FloatingPort(tEdge.Target.BoundaryCurve, tEdge.Target.Center);
    }

    IEnumerable<GeometryGraph> subGraphs = GraphConnectedComponents.CreateComponents(geometryGraph.Nodes, geometryGraph.Edges);
    foreach (GeometryGraph subgraph in subGraphs)
    {
        LayeredLayout layout = new LayeredLayout(subgraph, layoutSettings);
        subgraph.Margins = layoutSettings.NodeSeparation + 600;
        layout.Run();
    }

    Microsoft.Msagl.Layout.MDS.MdsGraphLayout.PackGraphs(subGraphs, layoutSettings);
}

I've placed this method into the EdgeRoutingSample project and this is the stack trace:

AutomaticGraphLayout.dll!Microsoft.Msagl.Routing.SplineRouter.CalculatePortsToShapes() Line 648 C#
AutomaticGraphLayout.dll!Microsoft.Msagl.Routing.SplineRouter.RouteOnRoot() Line 180    C#
AutomaticGraphLayout.dll!Microsoft.Msagl.Routing.SplineRouter.RunInternal() Line 171    C#
AutomaticGraphLayout.dll!Microsoft.Msagl.Core.AlgorithmBase.Run() Line 24   C#
AutomaticGraphLayout.dll!Microsoft.Msagl.Layout.Layered.LayeredLayoutEngine.RunPostLayering() Line 290  C#
AutomaticGraphLayout.dll!Microsoft.Msagl.Layout.Layered.LayeredLayoutEngine.RunInternal() Line 248  C#
AutomaticGraphLayout.dll!Microsoft.Msagl.Core.AlgorithmBase.Run() Line 24   C#
AutomaticGraphLayout.dll!Microsoft.Msagl.Layout.Layered.LayeredLayout.RunInternal() Line 57 C#
AutomaticGraphLayout.dll!Microsoft.Msagl.Core.AlgorithmBase.Run() Line 24   C#
EdgeRoutingSample.exe!EdgeRoutingSample.Program.RunSample() Line 107    C#
EdgeRoutingSample.exe!EdgeRoutingSample.Program.Main() Line 23  C#
void CalculatePortsToShapes() {
    portsToShapes = new Dictionary<Port, Shape>();
    foreach (var shape in root.Descendants)
        foreach (var port in shape.Ports)
            portsToShapes[port] = shape;
    //assign all orphan ports to the root 
    foreach (var port in AllPorts().Where(p => !portsToShapes.ContainsKey(p))) {
        root.Ports.Insert(port);
        portsToShapes[port] = root;
    }
}

The exception is in the line foreach (var port in AllPorts().Where(p => !portsToShapes.ContainsKey(p))) { and is caused by p being null.

I'm thinking this bug is possibly between the keyboard and screen, but you never know... If so, could some pointers be given how to get edges to use ports?

Thanks.

SMTStuck commented 4 years ago

Anybody?

levnach commented 4 years ago

If you comment out the part below then everything works. The code below sets the port at centers of the nodes, but then the layout moves the nodes and the ports are now outside of the nodes. So, the routing fails. foreach (Edge tEdge in geometryGraph.Edges) { if (tEdge.SourcePort == null) tEdge.SourcePort = new FloatingPort(tEdge.Source.BoundaryCurve, tEdge.Source.Center); if (tEdge.TargetPort == null) tEdge.TargetPort = new FloatingPort(tEdge.Target.BoundaryCurve, tEdge.Target.Center); }

The code that works for me is just public void RunSample() { GeometryGraph geometryGraph = new GeometryGraph(); geometryGraph.Nodes.Add(new Node(CurveFactory.CreateRectangle(100, 100, new Microsoft.Msagl.Core.Geometry.Point(0, 0)), "1")); geometryGraph.Nodes.Add(new Node(CurveFactory.CreateRectangle(300, 300, new Microsoft.Msagl.Core.Geometry.Point(0, 0)), "2")); geometryGraph.Nodes.Add(new Node(CurveFactory.CreateRectangle(100, 100, new Microsoft.Msagl.Core.Geometry.Point(0, 0)), "3"));

Edge edge = new Edge(geometryGraph.Nodes[0], geometryGraph.Nodes[1], 0, 0, 1);
edge.UserData = "1 - 2";
geometryGraph.Edges.Add(edge);

edge = new Edge(geometryGraph.Nodes[1], geometryGraph.Nodes[2], 0, 0, 1);
edge.UserData = "2 - 3";
geometryGraph.Edges.Add(edge);

edge = new Edge(geometryGraph.Nodes[0], geometryGraph.Nodes[2], 0, 0, 1);
edge.UserData = "1 - 3";
geometryGraph.Edges.Add(edge);

SugiyamaLayoutSettings layoutSettings = new SugiyamaLayoutSettings();
layoutSettings.EdgeRoutingSettings.EdgeRoutingMode = Microsoft.Msagl.Core.Routing.EdgeRoutingMode.Spline;
layoutSettings.NodeSeparation = 125;
layoutSettings.LayerSeparation = 125;

IEnumerable<GeometryGraph> subGraphs = GraphConnectedComponents.CreateComponents(geometryGraph.Nodes, geometryGraph.Edges);
foreach (GeometryGraph subgraph in subGraphs)
{
    LayeredLayout layout = new LayeredLayout(subgraph, layoutSettings);
    subgraph.Margins = layoutSettings.NodeSeparation + 600;
    layout.Run();
}

Microsoft.Msagl.Layout.MDS.MdsGraphLayout.PackGraphs(subGraphs, layoutSettings);
SMTStuck commented 4 years ago

Hello Lev,

Thanks for the reply.

The code below sets the port at centers of the nodes, but then the layout moves the nodes and the ports are now outside of the nodes. So, the routing fails. I was under the impression that the layout would respect the position of the ports.

Much appreciated. :)


Just in case anybody stumbles upon this question, I have modified the code and appended the following and the routing to ports now works:

Microsoft.Msagl.Layout.MDS.MdsGraphLayout.PackGraphs(subGraphs, layoutSettings);

foreach (GeometryGraph subgraph in subGraphs)
{
    InteractiveEdgeRouter portRouter = new InteractiveEdgeRouter(subgraph.Nodes.Select(n => n.BoundaryCurve), 3, 0.65 * 3, 0);
    portRouter.Run();
    foreach (Edge tEdge in subgraph.Edges)
    {
        FloatingPort port1 = new FloatingPort(tEdge.Source.BoundaryCurve, new Microsoft.Msagl.Core.Geometry.Point(tEdge.Source.BoundingBox.Center.X, tEdge.Source.BoundingBox.Bottom));
        FloatingPort port2 = new FloatingPort(tEdge.Target.BoundaryCurve, new Microsoft.Msagl.Core.Geometry.Point(tEdge.Target.BoundingBox.Center.X, tEdge.Target.BoundingBox.Top));
        ICurve spline = portRouter.RouteSplineFromPortToPortWhenTheWholeGraphIsReady(port1, port2, false, out _);
        DrawSpline(spline);
    }
}