microsoft / automatic-graph-layout

A set of tools for graph layout and viewing
Other
1.35k stars 303 forks source link

Help Getting Started rendering SVG #237

Open Baudin999 opened 4 years ago

Baudin999 commented 4 years ago

Hi, I'm rewriting this question because I think the original question was not clear enough. I am trying to create an SVG, but not render directly to file. I've found some snippets of code but not a clear way of achieving my goals.

First: I'm using .NET core on a mac

I am using this "tutorial" to get started, I would really appreciate being pointed to better alternatives.

I'm setting up my graph like this:

GeometryGraph graph = new GeometryGraph();

graph.Nodes.Add(CreateNode("Bob"));
graph.Nodes.Add(CreateNode("Sally"));
graph.Nodes.Add(CreateNode("Rob"));
graph.Nodes.Add(CreateNode("Vincent"));

graph.Edges.Add(CreateEdge(graph.FindNodeByUserData("Bob"), graph.FindNodeByUserData("Sally")));
graph.Edges.Add(CreateEdge(graph.FindNodeByUserData("Sally"), graph.FindNodeByUserData("Vincent")));
graph.Edges.Add(CreateEdge(graph.FindNodeByUserData("Sally"), graph.FindNodeByUserData("Rob")));

var settings = new Microsoft.Msagl.Prototype.Ranking.RankingLayoutSettings();
settings.NodeSeparation = 50;

Microsoft.Msagl.Miscellaneous.LayoutHelpers.CalculateLayout(graph, settings, null);

private Edge CreateEdge(Node source, Node target)
{
     return new Edge(source, target);
}

private Node CreateNode(string id)
{
     return new Node(CurveFactory.CreateRectangle(200, 80, new Point()), id);
}

private ICurve CreateCurve(double w, double h)
{
     return CurveFactory.CreateRectangle(w, h, new Point());
}

Now the question is, how do I get an SVG (or XElement or something along those lines) so I can embed this SVG. I Cannot write it to file some my attempts at SvgGraphWriter.Write have failed. I can't use the constructor because I can't pass in my graph and I can't create a GraphRenderer beause I'm using dotnet core.

Any help would be much appreciated.

Baudin999 commented 4 years ago

Is there something I can do with the question to make it easier to help/answer? I'm really running against my own abilities and don't know how to continue...If there is anything I can do to make the question clearer, please let me know.

levnach commented 4 years ago

There is a project dot2svg inside of Msagl. Does it built on mac?

Baudin999 commented 4 years ago

When I build the dot2svg project I get the following errors:

PS D:\Projects\automatic-graph-layout\GraphLayout\tools\Dot2Svg> dotnet build

Welcome to .NET Core 3.1!
---------------------
SDK Version: 3.1.300

Telemetry
---------
The .NET Core tools collect usage data in order to help us improve your experience. The data is anonymous. It is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_CLI_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell.

Read more about .NET Core CLI Tools telemetry: https://aka.ms/dotnet-cli-telemetry

----------------
Explore documentation: https://aka.ms/dotnet-docs
Report issues and find source on GitHub: https://github.com/dotnet/core
Find out what's new: https://aka.ms/dotnet-whats-new
Learn about the installed HTTPS developer cert: https://aka.ms/aspnet-core-https
Use 'dotnet --help' to see available commands or visit: https://aka.ms/dotnet-cli-docs
Write your first app: https://aka.ms/first-net-core-app
--------------------------------------------------------------------------------------
Microsoft (R) Build Engine version 16.6.0+5ff7b0c9e for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  Restored D:\Projects\automatic-graph-layout\GraphLayout\Drawing\AutomaticGraphLayout.Drawing.csproj (in 206 ms).
  Restored D:\Projects\automatic-graph-layout\GraphLayout\MSAGL\AutomaticGraphLayout.csproj (in 206 ms).
  AutomaticGraphLayout -> D:\Projects\automatic-graph-layout\GraphLayout\MSAGL\bin\Debug\netstandard2.0\AutomaticGraphLayout.dll
  ArgsParser -> D:\Projects\automatic-graph-layout\GraphLayout\tools\ArgsParser\bin\Debug\ArgsParser.dll
  AutomaticGraphLayout.Drawing -> D:\Projects\automatic-graph-layout\GraphLayout\Drawing\bin\Debug\netstandard2.0\AutomaticGraphLayout.Drawing.dll
C:\Program Files\dotnet\sdk\3.1.300\Microsoft.Common.CurrentVersion.targets(3032,5): error MSB3823: Non-string resources require the property GenerateResourceUsePreserializedResources to be set to true. [D:\Projects\automatic-graph-layout\GraphLayout\tools\GraphViewerGDI\GraphViewerGDI.csproj]
C:\Program Files\dotnet\sdk\3.1.300\Microsoft.Common.CurrentVersion.targets(3032,5): error MSB3822: Non-string resources require the System.Resources.Extensions assembly at runtime, but it was not found in this project's references. [D:\Projects\automatic-graph-layout\GraphLayout\tools\GraphViewerGDI\GraphViewerGDI.csproj]
C:\Program Files\dotnet\sdk\3.1.300\Microsoft.Common.CurrentVersion.targets(3032,5): error MSB3823: Non-string resources require the property GenerateResourceUsePreserializedResources to be set to true. [D:\Projects\automatic-graph-layout\GraphLayout\tools\GraphViewerGDI\GraphViewerGDI.csproj]
C:\Program Files\dotnet\sdk\3.1.300\Microsoft.Common.CurrentVersion.targets(3032,5): error MSB3822: Non-string resources require the System.Resources.Extensions assembly at runtime, but it was not found in this project's references. [D:\Projects\automatic-graph-layout\GraphLayout\tools\GraphViewerGDI\GraphViewerGDI.csproj]
  Dot2Graph -> D:\Projects\automatic-graph-layout\GraphLayout\tools\Dot2Graph\bin\Debug\Dot2Graph.dll

Build FAILED.

C:\Program Files\dotnet\sdk\3.1.300\Microsoft.Common.CurrentVersion.targets(3032,5): error MSB3823: Non-string resources require the property GenerateResourceUsePreserializedResources to be set to true. [D:\Projects\automatic-graph-layout\GraphLayout\tools\GraphViewerGDI\GraphViewerGDI.csproj]
C:\Program Files\dotnet\sdk\3.1.300\Microsoft.Common.CurrentVersion.targets(3032,5): error MSB3822: Non-string resources require the System.Resources.Extensions assembly at runtime, but it was not found in this project's references. [D:\Projects\automatic-graph-layout\GraphLayout\tools\GraphViewerGDI\GraphViewerGDI.csproj]
C:\Program Files\dotnet\sdk\3.1.300\Microsoft.Common.CurrentVersion.targets(3032,5): error MSB3823: Non-string resources require the property GenerateResourceUsePreserializedResources to be set to true. [D:\Projects\automatic-graph-layout\GraphLayout\tools\GraphViewerGDI\GraphViewerGDI.csproj]
C:\Program Files\dotnet\sdk\3.1.300\Microsoft.Common.CurrentVersion.targets(3032,5): error MSB3822: Non-string resources require the System.Resources.Extensions assembly at runtime, but it was not found in this project's references. [D:\Projects\automatic-graph-layout\GraphLayout\tools\GraphViewerGDI\GraphViewerGDI.csproj]
    0 Warning(s)
    4 Error(s)

Time Elapsed 00:00:06.45

Following these steps do not seem to remove the issue: msbuild 4704

quick note: I've switched to Windows for this project, too frustrating to work on mac, but I still have the same limitations (cause eventually I'll need to compile on OSX) with dotnet core 3.1.

Baudin999 commented 4 years ago

Is there something I can do to help this along? I don't mind putting in some time but I have no clue where to start...

levnach commented 4 years ago

It seems a relevant discussion https://github.com/microsoft/msbuild/issues/4704

Baudin999 commented 4 years ago

Where in the library can I find the transformation from a GeometryGraph to Svg? I keep bumping into position difficulties. There is a transformation needed plus the a flip to make it work but I haven't found it yet:

// Create a graph.
GeometryGraph graph = new GeometryGraph();
graph.UpdateBoundingBox();

graph.Nodes.Add(CreateNode("Bob"));
graph.Nodes.Add(CreateNode("Sally"));
graph.Nodes.Add(CreateNode("Rob"));
graph.Nodes.Add(CreateNode("Vincent"));

graph.Edges.Add(CreateEdge(graph.FindNodeByUserData("Bob"), graph.FindNodeByUserData("Sally")));
graph.Edges.Add(CreateEdge(graph.FindNodeByUserData("Sally"), graph.FindNodeByUserData("Vincent")));
graph.Edges.Add(CreateEdge(graph.FindNodeByUserData("Sally"), graph.FindNodeByUserData("Rob")));

var settings = new Microsoft.Msagl.Layout.Layered.SugiyamaLayoutSettings();
settings.EdgeRoutingSettings = new Microsoft.Msagl.Core.Routing.EdgeRoutingSettings
{
    UseObstacleRectangles = true,
    EdgeRoutingMode = Microsoft.Msagl.Core.Routing.EdgeRoutingMode.Spline
};

Microsoft.Msagl.Miscellaneous.LayoutHelpers.CalculateLayout(graph, settings, null);

// here be home-brew...
// render the root svg
var svg = new Svg
{
    Height = graph.Height,   
    Width = graph.Width      
};

foreach (var node in graph.Nodes)
{
   // render the flow-chart sub-process
    var subprocess = new Subprocess(node.UserData.ToString())
    {
        Height = node.Height,
        Width = node.Width,
        X = node.Center.X,
        Y = node.Center.Y
    };

    svg.Children.Add(subprocess);
}

var stringSvg = svg.ToString();

Could you point me to a bit of relevant code to do the right rendering transformations?

levnach commented 4 years ago

Why don't you reuse the code from SvgGraphWriter?

Baudin999 commented 4 years ago

I am trying but my biggest problem there is that it takes a Graph and I have a GeometryGraph and can't figure out how to transform the geometry object to the drawing object.

I did find something in Dot.cs which seems to go the other way:

static void CreateGeometryGraph(Graph graph1)
{
    var geomGraph = graph1.GeometryGraph = new GeometryGraph();
    foreach (var n in graph1.Nodes)
        geomGraph.Nodes.Add(n.GeometryNode);

    foreach (var de in graph1.Edges)
    {
        var ge = de.GeometryEdge;
        ge.Source = de.SourceNode.GeometryNode;
        ge.Target = de.TargetNode.GeometryNode;
        geomGraph.Edges.Add(de.GeometryEdge);
        if (de.Label != null)
            ge.Label = de.Label.GeometryLabel;
    }
    geomGraph.UpdateBoundingBox();
    // geomGraph.Transform(GetTransformForDotSquashedGeometry());

}

The actual Shift reduce parser which parses the dotfile and generates the drawing graph is not in the source and so I can't find how the actual drawing graph is created.

Baudin999 commented 4 years ago

I've seemed to inch into the right direction (@levnach thank you for the hints!) I have started with changing the SvgGraphWriter:

Constructor now changes the xmlWriterSettings to take a StringBuilder so that I can output a string:

var xmlWriterSettings = new XmlWriterSettings { Indent = true };
xmlWriter = XmlWriter.Create(stringBuilder, xmlWriterSettings);

I've also flipped the implementation, I now start with a Graph (the DrawingObject) and create a GeometryGraph from this:

private void CreateGeometryGraph(Graph graph)
{
    var geometryGraph = graph.GeometryGraph = new GeometryGraph();
    foreach (var n in graph.Nodes)
        geometryGraph.Nodes.Add(n.GeometryNode);

    foreach (var edge in graph.Edges)
    {
        if (edge.GeometryEdge is null)
        {
            edge.GeometryEdge = new Microsoft.Msagl.Core.Layout.Edge();
        }

        var gEdge = edge.GeometryEdge;
        gEdge.Source = edge.SourceNode.GeometryNode;
        gEdge.Target = edge.TargetNode.GeometryNode;
        geometryGraph.Edges.Add(edge.GeometryEdge);
        if (edge.Label != null)
            gEdge.Label = edge.Label.GeometryLabel;
    }

    var settings = new SugiyamaLayoutSettings()
    {
        //
    };
    var lGraph = new LayeredLayout(geometryGraph, settings);
    lGraph.Run();
    geometryGraph.UpdateBoundingBox();

}

This outputs a correct graph, lot's of little things to tweak, for example:

but I seem to be inching closer.

If you don't mind I will leave this one open for a bit longer until 4704 is closed. After I've gotten it to work I'll write it up and submit it as documentation. Might help others.

ghost commented 4 years ago

hi @Baudin999 , did you ever work out the labels? I have been reading SvgWriter and I cant see why it would skip the edge or node labels.

levnach commented 4 years ago

No, it would not skip labels.

ghost commented 4 years ago

@levnach based on the code in https://github.com/microsoft/automatic-graph-layout/issues/248#issuecomment-663658274 , I see the labels on the nodes in code, but when the SVG is written I get the following verbatim, which seems to skip the label?:

<?xml version="1.0" encoding="utf-8"?>
<!--SvgWriter version 0.0.0.0-->
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="40" height="91.6875" id="svg2" version="1.1" xmlns="http://www.w3.org/2000/svg">
  <g transform="translate(20,591.6875)">
    <!--Edges-->
    <path fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" d="M 0 -561.6875 L 0 -540" />
    <polygon stroke="#000000" stroke-opacity="1" stroke-width="1" fill="#000000" fill-opacity="1" points="-2.21694663167 -540 0 -530 2.21694663167 -540" />
    <!--nodes-->
    <ellipse fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" cx="0" cy="-520" rx="10" ry="10" />
    <ellipse fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" cx="0" cy="-571.6875" rx="10" ry="10" />
    <!--end of nodes-->
  </g>
</svg>
ghost commented 4 years ago

It seems to be due to Width=0 on the label, so LabelIsValid returns false.

ghost commented 4 years ago

Setting width = 0.01 works, but the SvgWriter mispositions the labels. Fix is to add dominant-baseline="middle" text-anchor="middle" to the svg text attribute Without attrs: without_center

With new attrs: with_center

levnach commented 4 years ago

@tick-rick, have you tried running dot2svg foo.dot, where dot2svg comes from GraphLayout\Tools? It sets the geometry correctly.

ghost commented 4 years ago

@levnach i tried compiling that but it didnt seem to compile on linux " Windows is required to build Windows desktop applications",

I was trying to avoid dot format as my current graph is from QuickGraph, and turning that into a string then into SVG seems taxing for bigger graphs. I will test out the webmsagl stuff to see if its a bit easier

ghost commented 4 years ago

I think I have this working as needed, my solution was to copy SvgGraphWriter and add the text anchoring.

FWIW, I noticed edge labels might not be correctly placed at https://github.com/microsoft/automatic-graph-layout/blob/master/GraphLayout/Drawing/SvgGraphWriter.cs#L252 as edge.Label.Center is empty (leading to y=0 in text node for edge), but label.GeometryLabel.Center is not, I fixed it by just checking if edge.Label.GeometryLabel is not null then using the geometry label for x,y respectively, else using x,y values from label.Center.

Baudin999 commented 4 years ago

@tick-rick Thank you for the information, would it be possible to give a very small example solution with a dot2svg example? I con;t seem to get it to work properly.

Baudin999 commented 3 years ago

I am still running into a wall when it comes to rendering a graph in SVG. Maybe a little context; I'm trying to recreate C4 diagrams using MSAGL instead of PlantUML.

I can't seem to get a grip on the API, the drawing part, the layout part, the SVG Writer part. Is there any documentation or some example project on getting simple graphs to render to SVG? Not write it to a file, but just the SVG string I can serve back up through my web endpoints, render it in an img tag ?

levnach commented 3 years ago

I added a required sample: https://github.com/microsoft/automatic-graph-layout/commit/5c3e0ad3d4c741c5827b45c26b2c322c1ff9c32b But https://github.com/microsoft/automatic-graph-layout/issues/237#issuecomment-664065653 needs to be addressed.

Baudin999 commented 3 years ago

@levnach it works, now I can try and get the graphs to look like I want. Thank you for the effort!

Baudin999 commented 3 years ago

@levnach

I've started work on "rendering the SVG diagrams". My first steps resulted in this:

image

Is this something you might be interested in me sharing the code-base for? My goal is to recreate some of the functionality in PlantUML over to MSAGL.

levnach commented 3 years ago

@Baudin999 Sure, my code for rendering labels in SVG is not great. You seem to figured it out. Please share the relevant code, or maybe just submit a pull request to MSAGL. Thanks!

Baudin999 commented 3 years ago

@levnach Would something like this work?

Example Project: ZDragon.Graphing

It's just a quick "sketch" of what I intend to do with the SvgRenderer. I can imagine this example is not in line with the vision of MSAGL. If this is something you'd be interested in adding to MSAGL I will try and create a pull request.

Just a note: Still missing a lot, like rendering subgraphs as container rectangles, setting padding....those basic things. My inspiration for the rendering comes from (and should eventually resemble): C4 PlantUml

levnach commented 3 years ago

@Baudin999 I think your code will be very useful for people using SVG output of MSAGL. So, yes, please go ahead with the pull request. Just a note that in the current state the output of the ZDragon.Graphing does not render the labels somehow. Only the edges are visible. Thank you!

Baudin999 commented 3 years ago

@levnach
I've added the labels of the edges back in, but it has become a bit messy. Is there a way for me to clean it up before pushing a pull request?

image

There should be space around the labels of the Edges. I'll drill down a bit into the Sugiyama layout and see if I can find out how to render the example more like this:

image

My gut feeling is telling me that I should add the Edge labels as elements to the graph, but not sure if I'll be able to manage that by myself. I'm quite new when it comes to these types of algorithms.

levnach commented 3 years ago

GeometryGraph has exact positions for the labels. It is GeometryEdge.Label.Center. I wonder if it is possible to use them. @Baudin999, I modified WriteSvgSample, so the edge labels are rendered now.

Baudin999 commented 3 years ago

@levnach Those centers help, but I'm still working on creating more space, really filling out the diagram so that it looks a bit better:

image

There is still not enough padding and can't figure out how to properly make this work.

For example:

I have a feeling that these points can be fixed using the settings, so I've been playing with the settings:

var routingSettings = new Microsoft.Msagl.Core.Routing.EdgeRoutingSettings {
    UseObstacleRectangles = true,
    BendPenalty = 100,
    EdgeRoutingMode = Microsoft.Msagl.Core.Routing.EdgeRoutingMode.StraightLine
};
var settings = new SugiyamaLayoutSettings {
    ClusterMargin = 50,
    PackingAspectRatio = 3,
    PackingMethod = Microsoft.Msagl.Core.Layout.PackingMethod.Columns,
    RepetitionCoefficientForOrdering = 0,
    EdgeRoutingSettings = routingSettings,
    NodeSeparation = 50,
    LayerSeparation = 150
};

None of these settings seem to really hit the sweetspot. I haven't been able to work out the issues if you could point me in the right direction I can make the example look pretty and do a merge request.

levnach commented 3 years ago

@Baudin999 , it is getting close to accepted quality. Why don't you create a pull request and I will try to improve the layout with some settings? When we are both happy with the result I will merge the request.

Baudin999 commented 3 years ago

@levnach pull request created!