diff --git a/README.md b/README.md
index 091d8ac..b40e9f1 100644
--- a/README.md
+++ b/README.md
@@ -53,220 +53,219 @@ using NUnit.Framework;
using System.Drawing;
using System.Linq;
-namespace Rubjerg.Graphviz.Test
+namespace Rubjerg.Graphviz.Test;
+
+[TestFixture()]
+public class Tutorial
{
- [TestFixture()]
- public class Tutorial
+ public const string PointPattern = @"{X=[\d.]+, Y=[\d.]+}";
+ public const string RectPattern = @"{X=[\d.]+,Y=[\d.]+,Width=[\d.]+,Height=[\d.]+}";
+ public const string SplinePattern =
+ @"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";
+
+ [Test, Order(1)]
+ public void GraphConstruction()
{
- public const string PointPattern = @"{X=[\d.]+, Y=[\d.]+}";
- public const string RectPattern = @"{X=[\d.]+,Y=[\d.]+,Width=[\d.]+,Height=[\d.]+}";
- public const string SplinePattern =
- @"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";
+ // You can programmatically construct graphs as follows
+ RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Some Unique Identifier");
+ // The graph name is optional, and can be omitted. The name is not interpreted by Graphviz,
+ // except it is recorded and preserved when the graph is written as a file.
+
+ // The node names are unique identifiers within a graph in Graphviz
+ Node nodeA = root.GetOrAddNode("A");
+ Node nodeB = root.GetOrAddNode("B");
+ Node nodeC = root.GetOrAddNode("C");
+
+ // The edge name is only unique between two nodes
+ Edge edgeAB = root.GetOrAddEdge(nodeA, nodeB, "Some edge name");
+ Edge edgeBC = root.GetOrAddEdge(nodeB, nodeC, "Some edge name");
+ Edge anotherEdgeBC = root.GetOrAddEdge(nodeB, nodeC, "Another edge name");
+
+ // An edge name is optional and omitting it will result in a new nameless edge.
+ // There can be multiple nameless edges between any two nodes.
+ Edge edgeAB1 = root.GetOrAddEdge(nodeA, nodeB);
+ Edge edgeAB2 = root.GetOrAddEdge(nodeA, nodeB);
+ Assert.AreNotEqual(edgeAB1, edgeAB2);
+
+ // We can attach attributes to nodes, edges and graphs to store information and instruct
+ // Graphviz by specifying layout parameters. At the moment we only support string
+ // attributes. Cgraph assumes that all objects of a given kind (graphs/subgraphs, nodes,
+ // or edges) have the same attributes. An attribute has to be introduced with a default value
+ // first for a certain kind, before we can use it.
+ Node.IntroduceAttribute(root, "my attribute", "defaultvalue");
+ nodeA.SetAttribute("my attribute", "othervalue");
+
+ // Attributes are introduced per kind (Node, Edge, Graph) per root graph.
+ // So to be able to use "my attribute" on edges, we first have to introduce it as well.
+ Edge.IntroduceAttribute(root, "my attribute", "defaultvalue");
+ edgeAB.SetAttribute("my attribute", "othervalue");
+
+ // To introduce and set an attribute at the same time, there are convenience wrappers
+ edgeBC.SafeSetAttribute("arrowsize", "2.0", "1.0");
+ // If we set an unintroduced attribute, the attribute will be introduced with an empty default value.
+ edgeBC.SetAttribute("new attr", "value");
+
+ // Some attributes - like "label" - accept HTML strings as value
+ // To tell Graphviz that a string should be interpreted as HTML use the designated methods
+ Node.IntroduceAttribute(root, "label", "defaultlabel");
+ nodeB.SetAttributeHtml("label", "Some HTML string");
+
+ // We can simply export this graph to a text file in dot format
+ root.ToDotFile(TestContext.CurrentContext.TestDirectory + "/out.dot");
+
+ // A word of advice, Graphviz doesn't play very well with empty strings.
+ // Try to avoid them when possible. (https://gitlab.com/graphviz/graphviz/-/issues/1887)
+ }
- [Test, Order(1)]
- public void GraphConstruction()
+ [Test, Order(2)]
+ public void Layouting()
+ {
+ // If we have a given dot file (in this case the one we generated above), we can also read it back in
+ RootGraph root = RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory + "/out.dot");
+
+ // We can ask Graphviz to compute a layout and render it to svg
+ root.ToSvgFile(TestContext.CurrentContext.TestDirectory + "/dot_out.svg");
+
+ // We can use layout engines other than dot by explicitly passing the engine we want
+ root.ToSvgFile(TestContext.CurrentContext.TestDirectory + "/neato_out.svg", LayoutEngines.Neato);
+
+ // Or we can ask Graphviz to compute the layout and programatically read out the layout attributes
+ // This will create a copy of our original graph with layout information attached to it in the form
+ // of attributes.
+ RootGraph layout = root.CreateLayout();
+
+ // There are convenience methods available that parse these attributes for us and give
+ // back the layout information in an accessible form.
+ Node nodeA = layout.GetNode("A");
+ PointF position = nodeA.GetPosition();
+ Utils.AssertPattern(PointPattern, position.ToString());
+
+ RectangleF nodeboundingbox = nodeA.GetBoundingBox();
+ Utils.AssertPattern(RectPattern, nodeboundingbox.ToString());
+
+ // Or splines between nodes
+ Node nodeB = layout.GetNode("B");
+ Edge edge = layout.GetEdge(nodeA, nodeB, "Some edge name");
+ PointF[] spline = edge.GetFirstSpline();
+ string splineString = string.Join(", ", spline.Select(p => p.ToString()));
+ Utils.AssertPattern(SplinePattern, splineString);
+
+ // If we require detailed drawing information for any object, we can retrieve the so called "xdot"
+ // operations. See https://graphviz.org/docs/outputs/canon/#xdot for a specification.
+ var activeColor = Color.Black;
+ foreach (var op in nodeA.GetDrawing())
{
- // You can programmatically construct graphs as follows
- RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Some Unique Identifier");
- // The graph name is optional, and can be omitted. The name is not interpreted by Graphviz,
- // except it is recorded and preserved when the graph is written as a file.
-
- // The node names are unique identifiers within a graph in Graphviz
- Node nodeA = root.GetOrAddNode("A");
- Node nodeB = root.GetOrAddNode("B");
- Node nodeC = root.GetOrAddNode("C");
-
- // The edge name is only unique between two nodes
- Edge edgeAB = root.GetOrAddEdge(nodeA, nodeB, "Some edge name");
- Edge edgeBC = root.GetOrAddEdge(nodeB, nodeC, "Some edge name");
- Edge anotherEdgeBC = root.GetOrAddEdge(nodeB, nodeC, "Another edge name");
-
- // An edge name is optional and omitting it will result in a new nameless edge.
- // There can be multiple nameless edges between any two nodes.
- Edge edgeAB1 = root.GetOrAddEdge(nodeA, nodeB);
- Edge edgeAB2 = root.GetOrAddEdge(nodeA, nodeB);
- Assert.AreNotEqual(edgeAB1, edgeAB2);
-
- // We can attach attributes to nodes, edges and graphs to store information and instruct
- // Graphviz by specifying layout parameters. At the moment we only support string
- // attributes. Cgraph assumes that all objects of a given kind (graphs/subgraphs, nodes,
- // or edges) have the same attributes. An attribute has to be introduced with a default value
- // first for a certain kind, before we can use it.
- Node.IntroduceAttribute(root, "my attribute", "defaultvalue");
- nodeA.SetAttribute("my attribute", "othervalue");
-
- // Attributes are introduced per kind (Node, Edge, Graph) per root graph.
- // So to be able to use "my attribute" on edges, we first have to introduce it as well.
- Edge.IntroduceAttribute(root, "my attribute", "defaultvalue");
- edgeAB.SetAttribute("my attribute", "othervalue");
-
- // To introduce and set an attribute at the same time, there are convenience wrappers
- edgeBC.SafeSetAttribute("arrowsize", "2.0", "1.0");
- // If we set an unintroduced attribute, the attribute will be introduced with an empty default value.
- edgeBC.SetAttribute("new attr", "value");
-
- // Some attributes - like "label" - accept HTML strings as value
- // To tell Graphviz that a string should be interpreted as HTML use the designated methods
- Node.IntroduceAttribute(root, "label", "defaultlabel");
- nodeB.SetAttributeHtml("label", "Some HTML string");
-
- // We can simply export this graph to a text file in dot format
- root.ToDotFile(TestContext.CurrentContext.TestDirectory + "/out.dot");
-
- // A word of advice, Graphviz doesn't play very well with empty strings.
- // Try to avoid them when possible. (https://gitlab.com/graphviz/graphviz/-/issues/1887)
+ if (op is XDotOp.FillColor { Value: string htmlColor })
+ {
+ activeColor = ColorTranslator.FromHtml(htmlColor);
+ }
+ else if (op is XDotOp.FilledEllipse { Value: var filledEllipse })
+ {
+ var boundingBox = filledEllipse.ToRectangleF();
+ Utils.AssertPattern(RectPattern, boundingBox.ToString());
+ }
+ // Handle any xdot operation you require
}
- [Test, Order(2)]
- public void Layouting()
+ var activeFont = XDotFont.Default;
+ foreach (var op in nodeA.GetDrawing())
{
- // If we have a given dot file (in this case the one we generated above), we can also read it back in
- RootGraph root = RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory + "/out.dot");
-
- // We can ask Graphviz to compute a layout and render it to svg
- root.ToSvgFile(TestContext.CurrentContext.TestDirectory + "/dot_out.svg");
-
- // We can use layout engines other than dot by explicitly passing the engine we want
- root.ToSvgFile(TestContext.CurrentContext.TestDirectory + "/neato_out.svg", LayoutEngines.Neato);
-
- // Or we can ask Graphviz to compute the layout and programatically read out the layout attributes
- // This will create a copy of our original graph with layout information attached to it in the form
- // of attributes.
- RootGraph layout = root.CreateLayout();
-
- // There are convenience methods available that parse these attributes for us and give
- // back the layout information in an accessible form.
- Node nodeA = layout.GetNode("A");
- PointF position = nodeA.GetPosition();
- Utils.AssertPattern(PointPattern, position.ToString());
-
- RectangleF nodeboundingbox = nodeA.GetBoundingBox();
- Utils.AssertPattern(RectPattern, nodeboundingbox.ToString());
-
- // Or splines between nodes
- Node nodeB = layout.GetNode("B");
- Edge edge = layout.GetEdge(nodeA, nodeB, "Some edge name");
- PointF[] spline = edge.GetFirstSpline();
- string splineString = string.Join(", ", spline.Select(p => p.ToString()));
- Utils.AssertPattern(SplinePattern, splineString);
-
- // If we require detailed drawing information for any object, we can retrieve the so called "xdot"
- // operations. See https://graphviz.org/docs/outputs/canon/#xdot for a specification.
- var activeColor = Color.Black;
- foreach (var op in nodeA.GetDrawing())
+ if (op is XDotOp.Font { Value: var font })
{
- if (op is XDotOp.FillColor { Value: string htmlColor })
- {
- activeColor = ColorTranslator.FromHtml(htmlColor);
- }
- else if (op is XDotOp.FilledEllipse { Value: var filledEllipse })
- {
- var boundingBox = filledEllipse.ToRectangleF();
- Utils.AssertPattern(RectPattern, boundingBox.ToString());
- }
- // Handle any xdot operation you require
+ activeFont = font;
+ Utils.AssertPattern(@"Times-Roman", font.Name);
}
-
- var activeFont = XDotFont.Default;
- foreach (var op in nodeA.GetDrawing())
+ else if (op is XDotOp.Text { Value: var text })
{
- if (op is XDotOp.Font { Value: var font })
- {
- activeFont = font;
- Utils.AssertPattern(@"Times-Roman", font.Name);
- }
- else if (op is XDotOp.Text { Value: var text })
- {
- var anchor = text.Anchor();
- Utils.AssertPattern(PointPattern, anchor.ToString());
- var boundingBox = text.TextBoundingBox(activeFont);
- Utils.AssertPattern(RectPattern, boundingBox.ToString());
- Assert.AreEqual(text.Text, "A");
- }
- // Handle any xdot operation you require
+ var anchor = text.Anchor();
+ Utils.AssertPattern(PointPattern, anchor.ToString());
+ var boundingBox = text.TextBoundingBox(activeFont);
+ Utils.AssertPattern(RectPattern, boundingBox.ToString());
+ Assert.AreEqual(text.Text, "A");
}
-
- // These are just simple examples to showcase the structure of xdot operations.
- // In reality the information can be much richer and more complex.
+ // Handle any xdot operation you require
}
- [Test, Order(3)]
- public void Clusters()
- {
- RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with clusters");
- Node nodeA = root.GetOrAddNode("A");
- Node nodeB = root.GetOrAddNode("B");
- Node nodeC = root.GetOrAddNode("C");
- Node nodeD = root.GetOrAddNode("D");
-
- // When a subgraph name is prefixed with cluster,
- // the dot layout engine will render it as a box around the containing nodes.
- SubGraph cluster1 = root.GetOrAddSubgraph("cluster_1");
- cluster1.AddExisting(nodeB);
- cluster1.AddExisting(nodeC);
- SubGraph cluster2 = root.GetOrAddSubgraph("cluster_2");
- cluster2.AddExisting(nodeD);
-
- // COMPOUND EDGES
- // Graphviz does not really support edges from and to clusters. However, by adding an
- // invisible dummynode and setting the ltail or lhead attributes of an edge this
- // behavior can be faked. Graphviz will then draw an edge to the dummy node but clip it
- // at the border of the cluster. We provide convenience methods for this.
- // To enable this feature, Graphviz requires us to set the "compound" attribute to "true".
- Graph.IntroduceAttribute(root, "compound", "true"); // Allow lhead/ltail
- // The boolean indicates whether the dummy node should take up any space. When you pass
- // false and you have a lot of edges, the edges may start to overlap a lot.
- _ = root.GetOrAddEdge(nodeA, cluster1, false, "edge to a cluster");
- _ = root.GetOrAddEdge(cluster1, nodeD, false, "edge from a cluster");
- _ = root.GetOrAddEdge(cluster1, cluster1, false, "edge between clusters");
-
- var layout = root.CreateLayout();
-
- SubGraph cluster = layout.GetSubgraph("cluster_1");
- RectangleF clusterbox = cluster.GetBoundingBox();
- RectangleF rootgraphbox = layout.GetBoundingBox();
- Utils.AssertPattern(RectPattern, clusterbox.ToString());
- Utils.AssertPattern(RectPattern, rootgraphbox.ToString());
- }
+ // These are just simple examples to showcase the structure of xdot operations.
+ // In reality the information can be much richer and more complex.
+ }
- [Test, Order(4)]
- public void Records()
- {
- RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with records");
- Node nodeA = root.GetOrAddNode("A");
- nodeA.SafeSetAttribute("shape", "record", "");
- nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
+ [Test, Order(3)]
+ public void Clusters()
+ {
+ RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with clusters");
+ Node nodeA = root.GetOrAddNode("A");
+ Node nodeB = root.GetOrAddNode("B");
+ Node nodeC = root.GetOrAddNode("C");
+ Node nodeD = root.GetOrAddNode("D");
+
+ // When a subgraph name is prefixed with cluster,
+ // the dot layout engine will render it as a box around the containing nodes.
+ SubGraph cluster1 = root.GetOrAddSubgraph("cluster_1");
+ cluster1.AddExisting(nodeB);
+ cluster1.AddExisting(nodeC);
+ SubGraph cluster2 = root.GetOrAddSubgraph("cluster_2");
+ cluster2.AddExisting(nodeD);
+
+ // COMPOUND EDGES
+ // Graphviz does not really support edges from and to clusters. However, by adding an
+ // invisible dummynode and setting the ltail or lhead attributes of an edge this
+ // behavior can be faked. Graphviz will then draw an edge to the dummy node but clip it
+ // at the border of the cluster. We provide convenience methods for this.
+ // To enable this feature, Graphviz requires us to set the "compound" attribute to "true".
+ Graph.IntroduceAttribute(root, "compound", "true"); // Allow lhead/ltail
+ // The boolean indicates whether the dummy node should take up any space. When you pass
+ // false and you have a lot of edges, the edges may start to overlap a lot.
+ _ = root.GetOrAddEdge(nodeA, cluster1, false, "edge to a cluster");
+ _ = root.GetOrAddEdge(cluster1, nodeD, false, "edge from a cluster");
+ _ = root.GetOrAddEdge(cluster1, cluster1, false, "edge between clusters");
+
+ var layout = root.CreateLayout();
+
+ SubGraph cluster = layout.GetSubgraph("cluster_1");
+ RectangleF clusterbox = cluster.GetBoundingBox();
+ RectangleF rootgraphbox = layout.GetBoundingBox();
+ Utils.AssertPattern(RectPattern, clusterbox.ToString());
+ Utils.AssertPattern(RectPattern, rootgraphbox.ToString());
+ }
- var layout = root.CreateLayout();
+ [Test, Order(4)]
+ public void Records()
+ {
+ RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with records");
+ Node nodeA = root.GetOrAddNode("A");
+ nodeA.SafeSetAttribute("shape", "record", "");
+ nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
- // The order of the list matches the order in which the labels occur in the label string above.
- var rects = layout.GetNode("A").GetRecordRectangles().ToList();
- Assert.AreEqual(9, rects.Count);
- }
+ var layout = root.CreateLayout();
- [Test, Order(5)]
- public void StringEscaping()
- {
- RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with escaped strings");
- Node.IntroduceAttribute(root, "label", "\\N");
- Node nodeA = root.GetOrAddNode("A");
-
- // Several characters and character sequences can have special meanings in labels, like \N.
- // When you want to have a literal string in a label, we provide a convenience function for you to do just that.
- nodeA.SetAttribute("label", CGraphThing.EscapeLabel("Some string literal \\N \\n |}>"));
-
- // When defining portnames, some characters, like ':' and '|', are not allowed and they can't be escaped either.
- // This can be troubling if you have an externally defined ID for such a port.
- // We provide a function that maps strings to valid portnames.
- var somePortId = "port id with :| special characters";
- var validPortName = Edge.ConvertUidToPortName(somePortId);
- Node nodeB = root.GetOrAddNode("B");
- nodeB.SafeSetAttribute("shape", "record", "");
- nodeB.SafeSetAttribute("label", $"<{validPortName}>1|2", "\\N");
-
- // The conversion function makes sure different strings don't accidentally map onto the same portname
- Assert.AreNotEqual(Edge.ConvertUidToPortName(":"), Edge.ConvertUidToPortName("|"));
- }
+ // The order of the list matches the order in which the labels occur in the label string above.
+ var rects = layout.GetNode("A").GetRecordRectangles().ToList();
+ Assert.AreEqual(9, rects.Count);
+ }
+
+ [Test, Order(5)]
+ public void StringEscaping()
+ {
+ RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with escaped strings");
+ Node.IntroduceAttribute(root, "label", "\\N");
+ Node nodeA = root.GetOrAddNode("A");
+
+ // Several characters and character sequences can have special meanings in labels, like \N.
+ // When you want to have a literal string in a label, we provide a convenience function for you to do just that.
+ nodeA.SetAttribute("label", CGraphThing.EscapeLabel("Some string literal \\N \\n |}>"));
+
+ // When defining portnames, some characters, like ':' and '|', are not allowed and they can't be escaped either.
+ // This can be troubling if you have an externally defined ID for such a port.
+ // We provide a function that maps strings to valid portnames.
+ var somePortId = "port id with :| special characters";
+ var validPortName = Edge.ConvertUidToPortName(somePortId);
+ Node nodeB = root.GetOrAddNode("B");
+ nodeB.SafeSetAttribute("shape", "record", "");
+ nodeB.SafeSetAttribute("label", $"<{validPortName}>1|2", "\\N");
+
+ // The conversion function makes sure different strings don't accidentally map onto the same portname
+ Assert.AreNotEqual(Edge.ConvertUidToPortName(":"), Edge.ConvertUidToPortName("|"));
}
}
```
diff --git a/Rubjerg.Graphviz.Test/CGraphBasicOperations.cs b/Rubjerg.Graphviz.Test/CGraphBasicOperations.cs
index c29c85a..53bad57 100644
--- a/Rubjerg.Graphviz.Test/CGraphBasicOperations.cs
+++ b/Rubjerg.Graphviz.Test/CGraphBasicOperations.cs
@@ -2,155 +2,154 @@
using System.Linq;
using NUnit.Framework;
-namespace Rubjerg.Graphviz.Test
+namespace Rubjerg.Graphviz.Test;
+
+[TestFixture()]
+public class CGraphBasicOperations
{
- [TestFixture()]
- public class CGraphBasicOperations
+ [Test()]
+ public void TestCopyAttributes()
{
- [Test()]
- public void TestCopyAttributes()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- Node n1 = root.GetOrAddNode("1");
- Node.IntroduceAttribute(root, "test", "foo");
- Assert.AreEqual("foo", n1.GetAttribute("test"));
- n1.SetAttribute("test", "bar");
- Assert.AreEqual("bar", n1.GetAttribute("test"));
-
- RootGraph root2 = Utils.CreateUniqueTestGraph();
- Node n2 = root2.GetOrAddNode("2");
- Assert.AreEqual(null, n2.GetAttribute("test"));
- Assert.AreEqual(0, n1.CopyAttributesTo(n2));
- Assert.AreEqual("bar", n2.GetAttribute("test"));
- }
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Node n1 = root.GetOrAddNode("1");
+ Node.IntroduceAttribute(root, "test", "foo");
+ Assert.AreEqual("foo", n1.GetAttribute("test"));
+ n1.SetAttribute("test", "bar");
+ Assert.AreEqual("bar", n1.GetAttribute("test"));
+
+ RootGraph root2 = Utils.CreateUniqueTestGraph();
+ Node n2 = root2.GetOrAddNode("2");
+ Assert.AreEqual(null, n2.GetAttribute("test"));
+ Assert.AreEqual(0, n1.CopyAttributesTo(n2));
+ Assert.AreEqual("bar", n2.GetAttribute("test"));
+ }
- [Test()]
- public void TestDeletions()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
+ [Test()]
+ public void TestDeletions()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
- Node tail = root.GetOrAddNode("1");
- Node head = root.GetOrAddNode("2");
- Node other = root.GetOrAddNode("3");
+ Node tail = root.GetOrAddNode("1");
+ Node head = root.GetOrAddNode("2");
+ Node other = root.GetOrAddNode("3");
- Edge edge = root.GetOrAddEdge(tail, head, "edge");
- Edge tailout = root.GetOrAddEdge(tail, other, "tailout");
- Edge headout = root.GetOrAddEdge(head, other, "headout");
- Edge tailin = root.GetOrAddEdge(other, tail, "tailin");
- Edge headin = root.GetOrAddEdge(other, head, "headin");
+ Edge edge = root.GetOrAddEdge(tail, head, "edge");
+ Edge tailout = root.GetOrAddEdge(tail, other, "tailout");
+ Edge headout = root.GetOrAddEdge(head, other, "headout");
+ Edge tailin = root.GetOrAddEdge(other, tail, "tailin");
+ Edge headin = root.GetOrAddEdge(other, head, "headin");
- Assert.IsTrue(root.Equals(root.MyRootGraph));
- Assert.IsTrue(root.Equals(tail.MyRootGraph));
- Assert.IsTrue(root.Equals(edge.MyRootGraph));
+ Assert.IsTrue(root.Equals(root.MyRootGraph));
+ Assert.IsTrue(root.Equals(tail.MyRootGraph));
+ Assert.IsTrue(root.Equals(edge.MyRootGraph));
- Assert.AreEqual(3, tail.TotalDegree());
- Assert.AreEqual(3, head.TotalDegree());
- Assert.AreEqual(3, root.Nodes().Count());
+ Assert.AreEqual(3, tail.TotalDegree());
+ Assert.AreEqual(3, head.TotalDegree());
+ Assert.AreEqual(3, root.Nodes().Count());
- root.Delete(edge);
+ root.Delete(edge);
- Assert.AreEqual(2, tail.TotalDegree());
- Assert.AreEqual(2, head.TotalDegree());
- Assert.AreEqual(3, root.Nodes().Count());
+ Assert.AreEqual(2, tail.TotalDegree());
+ Assert.AreEqual(2, head.TotalDegree());
+ Assert.AreEqual(3, root.Nodes().Count());
- root.Delete(tail);
+ root.Delete(tail);
- Assert.AreEqual(2, root.Nodes().Count());
- Assert.AreEqual(2, other.TotalDegree());
- }
+ Assert.AreEqual(2, root.Nodes().Count());
+ Assert.AreEqual(2, other.TotalDegree());
+ }
- [Test()]
- public void TestNodeMerge()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- Node merge = root.GetOrAddNode("merge");
- Node target = root.GetOrAddNode("target");
- Node other = root.GetOrAddNode("other");
-
- Edge selfloop = root.GetOrAddEdge(merge, merge, "selfloop");
- Edge contracted = root.GetOrAddEdge(merge, target, "contracted");
- Edge counter = root.GetOrAddEdge(target, merge, "counter");
- Edge mergeout = root.GetOrAddEdge(merge, other, "mergeout");
- Edge targetout = root.GetOrAddEdge(target, other, "targetout");
- Edge mergein = root.GetOrAddEdge(other, merge, "mergein");
- Edge targetin = root.GetOrAddEdge(other, target, "targetin");
-
- Assert.AreEqual(6, merge.TotalDegree());
- Assert.AreEqual(4, target.TotalDegree());
- Assert.AreEqual(3, root.Nodes().Count());
-
- //root.ComputeDotLayout();
- //root.ToSvgFile("dump1.svg");
- //root.FreeLayout();
- //root.ToDotFile("dump1.dot");
-
- root.Merge(merge, target);
-
- //root.ComputeDotLayout();
- //root.ToSvgFile("dump2.svg");
- //root.FreeLayout();
- //root.ToDotFile("dump2.dot");
-
- Assert.AreEqual(2, root.Nodes().Count());
- Assert.AreEqual(3, target.InDegree());
- Assert.AreEqual(3, target.OutDegree());
- Assert.AreEqual(2, other.InDegree());
- Assert.AreEqual(2, other.OutDegree());
- }
+ [Test()]
+ public void TestNodeMerge()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Node merge = root.GetOrAddNode("merge");
+ Node target = root.GetOrAddNode("target");
+ Node other = root.GetOrAddNode("other");
+
+ Edge selfloop = root.GetOrAddEdge(merge, merge, "selfloop");
+ Edge contracted = root.GetOrAddEdge(merge, target, "contracted");
+ Edge counter = root.GetOrAddEdge(target, merge, "counter");
+ Edge mergeout = root.GetOrAddEdge(merge, other, "mergeout");
+ Edge targetout = root.GetOrAddEdge(target, other, "targetout");
+ Edge mergein = root.GetOrAddEdge(other, merge, "mergein");
+ Edge targetin = root.GetOrAddEdge(other, target, "targetin");
+
+ Assert.AreEqual(6, merge.TotalDegree());
+ Assert.AreEqual(4, target.TotalDegree());
+ Assert.AreEqual(3, root.Nodes().Count());
+
+ //root.ComputeDotLayout();
+ //root.ToSvgFile("dump1.svg");
+ //root.FreeLayout();
+ //root.ToDotFile("dump1.dot");
+
+ root.Merge(merge, target);
+
+ //root.ComputeDotLayout();
+ //root.ToSvgFile("dump2.svg");
+ //root.FreeLayout();
+ //root.ToDotFile("dump2.dot");
+
+ Assert.AreEqual(2, root.Nodes().Count());
+ Assert.AreEqual(3, target.InDegree());
+ Assert.AreEqual(3, target.OutDegree());
+ Assert.AreEqual(2, other.InDegree());
+ Assert.AreEqual(2, other.OutDegree());
+ }
- [Test()]
- public void TestEdgeContraction()
+ [Test()]
+ public void TestEdgeContraction()
+ {
+ //NativeMethods.AllocConsole();
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Node tail = root.GetOrAddNode("x");
+ Node head = root.GetOrAddNode("xx");
+ Node other = root.GetOrAddNode("xxx");
+
+ Edge contracted = root.GetOrAddEdge(tail, head, "tocontract");
+ Edge parallel = root.GetOrAddEdge(tail, head, "parallel");
+ Edge counterparallel = root.GetOrAddEdge(head, tail, "counterparallel");
+ Edge tailout = root.GetOrAddEdge(tail, other, "tailout");
+ Edge headout = root.GetOrAddEdge(head, other, "headout");
+ Edge tailin = root.GetOrAddEdge(other, tail, "tailin");
+ Edge headin = root.GetOrAddEdge(other, head, "headin");
+
+ foreach (Node n in root.Nodes())
{
- //NativeMethods.AllocConsole();
- RootGraph root = Utils.CreateUniqueTestGraph();
- Node tail = root.GetOrAddNode("x");
- Node head = root.GetOrAddNode("xx");
- Node other = root.GetOrAddNode("xxx");
-
- Edge contracted = root.GetOrAddEdge(tail, head, "tocontract");
- Edge parallel = root.GetOrAddEdge(tail, head, "parallel");
- Edge counterparallel = root.GetOrAddEdge(head, tail, "counterparallel");
- Edge tailout = root.GetOrAddEdge(tail, other, "tailout");
- Edge headout = root.GetOrAddEdge(head, other, "headout");
- Edge tailin = root.GetOrAddEdge(other, tail, "tailin");
- Edge headin = root.GetOrAddEdge(other, head, "headin");
-
- foreach (Node n in root.Nodes())
+ n.SafeSetAttribute("label", n.GetName(), "no");
+ n.SafeSetAttribute("fontname", "Arial", "Arial");
+ foreach (Edge e in n.EdgesOut())
{
- n.SafeSetAttribute("label", n.GetName(), "no");
- n.SafeSetAttribute("fontname", "Arial", "Arial");
- foreach (Edge e in n.EdgesOut())
- {
- e.SafeSetAttribute("label", e.GetName(), "no");
- e.SafeSetAttribute("fontname", "Arial", "Arial");
- }
+ e.SafeSetAttribute("label", e.GetName(), "no");
+ e.SafeSetAttribute("fontname", "Arial", "Arial");
}
+ }
- Assert.AreEqual(5, tail.TotalDegree());
- Assert.AreEqual(5, head.TotalDegree());
- Assert.AreEqual(3, root.Nodes().Count());
+ Assert.AreEqual(5, tail.TotalDegree());
+ Assert.AreEqual(5, head.TotalDegree());
+ Assert.AreEqual(3, root.Nodes().Count());
- Node contraction = root.Contract(contracted, "contraction result");
+ Node contraction = root.Contract(contracted, "contraction result");
- foreach (Node n in root.Nodes())
+ foreach (Node n in root.Nodes())
+ {
+ n.SafeSetAttribute("label", n.GetName(), "no");
+ n.SafeSetAttribute("fontname", "Arial", "Arial");
+ foreach (Edge e in n.EdgesOut())
{
- n.SafeSetAttribute("label", n.GetName(), "no");
- n.SafeSetAttribute("fontname", "Arial", "Arial");
- foreach (Edge e in n.EdgesOut())
- {
- e.SafeSetAttribute("label", e.GetName(), "no");
- e.SafeSetAttribute("fontname", "Arial", "Arial");
- }
+ e.SafeSetAttribute("label", e.GetName(), "no");
+ e.SafeSetAttribute("fontname", "Arial", "Arial");
}
-
- //Console.Read();
- Assert.AreEqual(2, root.Nodes().Count());
- Assert.AreEqual(2, contraction.InDegree());
- Assert.AreEqual(2, contraction.OutDegree());
- Assert.AreEqual(2, other.InDegree());
- Assert.AreEqual(2, other.OutDegree());
}
+ //Console.Read();
+ Assert.AreEqual(2, root.Nodes().Count());
+ Assert.AreEqual(2, contraction.InDegree());
+ Assert.AreEqual(2, contraction.OutDegree());
+ Assert.AreEqual(2, other.InDegree());
+ Assert.AreEqual(2, other.OutDegree());
}
+
}
diff --git a/Rubjerg.Graphviz.Test/CGraphEdgeCases.cs b/Rubjerg.Graphviz.Test/CGraphEdgeCases.cs
index 3305001..0aee358 100644
--- a/Rubjerg.Graphviz.Test/CGraphEdgeCases.cs
+++ b/Rubjerg.Graphviz.Test/CGraphEdgeCases.cs
@@ -1,23 +1,23 @@
using System.Linq;
using NUnit.Framework;
-namespace Rubjerg.Graphviz.Test
+namespace Rubjerg.Graphviz.Test;
+
+[TestFixture()]
+public class CGraphEdgeCases
{
- [TestFixture()]
- public class CGraphEdgeCases
+ [Test()]
+ public void TestUniqueGraphs()
{
- [Test()]
- public void TestUniqueGraphs()
- {
- var g1 = RootGraph.CreateNew(GraphType.Directed, "test");
- var g2 = RootGraph.CreateNew(GraphType.Directed, "test");
- Assert.AreNotEqual(g1, g2);
- }
+ var g1 = RootGraph.CreateNew(GraphType.Directed, "test");
+ var g2 = RootGraph.CreateNew(GraphType.Directed, "test");
+ Assert.AreNotEqual(g1, g2);
+ }
- [Test()]
- public void TestReadDotFile()
- {
- RootGraph root = RootGraph.FromDotString(@"
+ [Test()]
+ public void TestReadDotFile()
+ {
+ RootGraph root = RootGraph.FromDotString(@"
digraph test {
"""";
A;
@@ -28,41 +28,41 @@ digraph test {
A -> B;
}
");
- var nodes = root.Nodes().ToList();
- var edges = root.Edges().ToList();
- var names = edges.Select(e => e.GetName());
- // The attribute 'key' maps to the edgename
- // Note that omitted key values will result in unique edges
- Assert.AreEqual(1, names.Count(n => n == "edgename"));
- Assert.AreEqual(2, names.Count(n => n == null));
- Assert.AreEqual(3, nodes.Count);
- }
+ var nodes = root.Nodes().ToList();
+ var edges = root.Edges().ToList();
+ var names = edges.Select(e => e.GetName());
+ // The attribute 'key' maps to the edgename
+ // Note that omitted key values will result in unique edges
+ Assert.AreEqual(1, names.Count(n => n == "edgename"));
+ Assert.AreEqual(2, names.Count(n => n == null));
+ Assert.AreEqual(3, nodes.Count);
+ }
- [Test()]
- public void TestWriteDotFile()
- {
- var root = RootGraph.CreateNew(GraphType.Directed);
-
- // Vice versa, empty edge names and node names will result in unique objects as well
- var E = root.GetOrAddNode("");
- var E2 = root.GetOrAddNode("");
- var N = root.GetOrAddNode(null);
- var N2 = root.GetOrAddNode(null);
- var A = root.GetOrAddNode("A");
- var B = root.GetOrAddNode("B");
- _ = root.GetOrAddEdge(A, B, null);
- _ = root.GetOrAddEdge(A, B, null);
- _ = root.GetOrAddEdge(A, B, "");
- _ = root.GetOrAddEdge(A, B, "");
- _ = root.GetOrAddEdge(A, B, "edge2");
- _ = root.GetOrAddEdge(A, B, "edge2");
- var dot = root.ToDotString();
-
- // Passing null to GetEdge will return any edge between the given endpoints.
- var edge = root.GetEdge(A, B, null);
- Assert.Contains(edge.GetName(), new[] { null, "edge2" });
-
- Utils.AssertPattern(@"digraph {
+ [Test()]
+ public void TestWriteDotFile()
+ {
+ var root = RootGraph.CreateNew(GraphType.Directed);
+
+ // Vice versa, empty edge names and node names will result in unique objects as well
+ var E = root.GetOrAddNode("");
+ var E2 = root.GetOrAddNode("");
+ var N = root.GetOrAddNode(null);
+ var N2 = root.GetOrAddNode(null);
+ var A = root.GetOrAddNode("A");
+ var B = root.GetOrAddNode("B");
+ _ = root.GetOrAddEdge(A, B, null);
+ _ = root.GetOrAddEdge(A, B, null);
+ _ = root.GetOrAddEdge(A, B, "");
+ _ = root.GetOrAddEdge(A, B, "");
+ _ = root.GetOrAddEdge(A, B, "edge2");
+ _ = root.GetOrAddEdge(A, B, "edge2");
+ var dot = root.ToDotString();
+
+ // Passing null to GetEdge will return any edge between the given endpoints.
+ var edge = root.GetEdge(A, B, null);
+ Assert.Contains(edge.GetName(), new[] { null, "edge2" });
+
+ Utils.AssertPattern(@"digraph {
node \[label=""\\N""\];
""%\d+"";
""%\d+"";
@@ -75,311 +75,310 @@ public void TestWriteDotFile()
A -> B \[key=edge2\];
}
".Replace("\r", ""), dot);
- }
+ }
- [Test()]
- public void TestAttributeReintroduction()
- {
- // Reintroducing graph attributes resets all values.
- RootGraph root = Utils.CreateUniqueTestGraph();
- Graph.IntroduceAttribute(root, "test", "default");
- root.SetAttribute("test", "1");
- Assert.AreEqual("1", root.GetAttribute("test"));
- Graph.IntroduceAttribute(root, "test", "default");
- // Now the value has been reset!
- Assert.AreEqual("default", root.GetAttribute("test"));
-
- // This is not the case for nodes
- Node node = root.GetOrAddNode("nodename");
- Node.IntroduceAttribute(root, "test", "default");
- node.SetAttribute("test", "1");
- Assert.AreEqual("1", node.GetAttribute("test"));
- Node.IntroduceAttribute(root, "test", "default");
- Assert.AreEqual("1", node.GetAttribute("test"));
- }
+ [Test()]
+ public void TestAttributeReintroduction()
+ {
+ // Reintroducing graph attributes resets all values.
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Graph.IntroduceAttribute(root, "test", "default");
+ root.SetAttribute("test", "1");
+ Assert.AreEqual("1", root.GetAttribute("test"));
+ Graph.IntroduceAttribute(root, "test", "default");
+ // Now the value has been reset!
+ Assert.AreEqual("default", root.GetAttribute("test"));
+
+ // This is not the case for nodes
+ Node node = root.GetOrAddNode("nodename");
+ Node.IntroduceAttribute(root, "test", "default");
+ node.SetAttribute("test", "1");
+ Assert.AreEqual("1", node.GetAttribute("test"));
+ Node.IntroduceAttribute(root, "test", "default");
+ Assert.AreEqual("1", node.GetAttribute("test"));
+ }
- [Test()]
- public void TestAttributeDefaults()
+ [Test()]
+ public void TestAttributeDefaults()
+ {
{
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- Node.IntroduceAttribute(root, "label", "");
- Node nodeA = root.GetOrAddNode("A");
- Node nodeB = root.GetOrAddNode("B");
- nodeA.SetAttribute("label", "1");
- Assert.AreEqual("1", nodeA.GetAttribute("label"));
- Assert.AreEqual("", nodeB.GetAttribute("label"));
- root.ToDotFile(TestContext.CurrentContext.TestDirectory + "/out.gv");
- }
-
- // The empty label default is not exported, and the default default is \N.
- // Related issue: https://gitlab.com/graphviz/graphviz/-/issues/1887
- {
- var root = RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory + "/out.gv");
- Node nodeA = root.GetNode("A");
- Node nodeB = root.GetNode("B");
- Assert.AreEqual("1", nodeA.GetAttribute("label"));
- Assert.AreEqual("\\N", nodeB.GetAttribute("label"));
-
- root.ComputeLayout();
- Assert.AreEqual("1", nodeA.GetAttribute("label"));
- Assert.AreEqual("\\N", nodeB.GetAttribute("label"));
- root.ToSvgFile(TestContext.CurrentContext.TestDirectory + "/out.svg");
- }
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Node.IntroduceAttribute(root, "label", "");
+ Node nodeA = root.GetOrAddNode("A");
+ Node nodeB = root.GetOrAddNode("B");
+ nodeA.SetAttribute("label", "1");
+ Assert.AreEqual("1", nodeA.GetAttribute("label"));
+ Assert.AreEqual("", nodeB.GetAttribute("label"));
+ root.ToDotFile(TestContext.CurrentContext.TestDirectory + "/out.gv");
}
- [Test()]
- public void TestGetUnintroducedAttributes()
+ // The empty label default is not exported, and the default default is \N.
+ // Related issue: https://gitlab.com/graphviz/graphviz/-/issues/1887
{
- RootGraph root = Utils.CreateUniqueTestGraph();
- Assert.AreEqual(null, root.GetAttribute("test"));
- Graph.IntroduceAttribute(root, "test", "foo");
- Assert.AreEqual("foo", root.GetAttribute("test"));
+ var root = RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory + "/out.gv");
+ Node nodeA = root.GetNode("A");
+ Node nodeB = root.GetNode("B");
+ Assert.AreEqual("1", nodeA.GetAttribute("label"));
+ Assert.AreEqual("\\N", nodeB.GetAttribute("label"));
- // If we call set attribute first, the attribute is automatically introduced
- root.SetAttribute("test2", "foo");
- Assert.AreEqual("foo", root.GetAttribute("test2"));
+ root.ComputeLayout();
+ Assert.AreEqual("1", nodeA.GetAttribute("label"));
+ Assert.AreEqual("\\N", nodeB.GetAttribute("label"));
+ root.ToSvgFile(TestContext.CurrentContext.TestDirectory + "/out.svg");
}
+ }
- [Test()]
- public void TestCopyUnintroducedAttributes()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- Node n1 = root.GetOrAddNode("1");
- Node.IntroduceAttribute(root, "test", "foo");
- Assert.AreEqual("foo", n1.GetAttribute("test"));
- n1.SetAttribute("test", "bar");
- Assert.AreEqual("bar", n1.GetAttribute("test"));
- Node.IntroduceAttribute(root, "test2", "foo2");
- Assert.AreEqual("foo2", n1.GetAttribute("test2"));
- n1.SetAttribute("test2", "bar2");
- Assert.AreEqual("bar2", n1.GetAttribute("test2"));
-
- RootGraph root2 = Utils.CreateUniqueTestGraph();
- Node n2 = root2.GetOrAddNode("2");
- // Only introduce the second attr, and see whether it gets copied
- Node.IntroduceAttribute(root, "test2", "foo2");
- Assert.AreEqual(null, n2.GetAttribute("test"));
- // While one would expect test2 to be copied correctly, it isn't, as copying test failed before that.
- //Assert.AreEqual("bar2", n2.GetAttribute("test2"));
- Assert.AreEqual(null, n2.GetAttribute("test2"));
- }
+ [Test()]
+ public void TestGetUnintroducedAttributes()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Assert.AreEqual(null, root.GetAttribute("test"));
+ Graph.IntroduceAttribute(root, "test", "foo");
+ Assert.AreEqual("foo", root.GetAttribute("test"));
+
+ // If we call set attribute first, the attribute is automatically introduced
+ root.SetAttribute("test2", "foo");
+ Assert.AreEqual("foo", root.GetAttribute("test2"));
+ }
+ [Test()]
+ public void TestCopyUnintroducedAttributes()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Node n1 = root.GetOrAddNode("1");
+ Node.IntroduceAttribute(root, "test", "foo");
+ Assert.AreEqual("foo", n1.GetAttribute("test"));
+ n1.SetAttribute("test", "bar");
+ Assert.AreEqual("bar", n1.GetAttribute("test"));
+ Node.IntroduceAttribute(root, "test2", "foo2");
+ Assert.AreEqual("foo2", n1.GetAttribute("test2"));
+ n1.SetAttribute("test2", "bar2");
+ Assert.AreEqual("bar2", n1.GetAttribute("test2"));
+
+ RootGraph root2 = Utils.CreateUniqueTestGraph();
+ Node n2 = root2.GetOrAddNode("2");
+ // Only introduce the second attr, and see whether it gets copied
+ Node.IntroduceAttribute(root, "test2", "foo2");
+ Assert.AreEqual(null, n2.GetAttribute("test"));
+ // While one would expect test2 to be copied correctly, it isn't, as copying test failed before that.
+ //Assert.AreEqual("bar2", n2.GetAttribute("test2"));
+ Assert.AreEqual(null, n2.GetAttribute("test2"));
+ }
- [Test()]
- public void TestCopyToNewRoot()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- Node n1 = root.GetOrAddNode("1");
- Node.IntroduceAttribute(root, "test", "foo");
- Assert.AreEqual("foo", n1.GetAttribute("test"));
- n1.SetAttribute("test", "bar");
- Assert.AreEqual("bar", n1.GetAttribute("test"));
-
- RootGraph root2 = Utils.CreateUniqueTestGraph();
- Node.IntroduceAttribute(root2, "test", "foo");
- Node n2 = n1.CopyToOtherRoot(root2);
- Assert.AreEqual("1", n2.GetName());
- Assert.AreEqual("bar", n2.GetAttribute("test"));
- }
- [Test()]
- public void TestNodeAndGraphWithSameName()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- SubGraph sub = root.GetOrAddSubgraph("name");
- Node node = sub.GetOrAddNode("name");
- Assert.True(root.Contains(sub));
- Assert.True(sub.Contains(node));
- }
+ [Test()]
+ public void TestCopyToNewRoot()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Node n1 = root.GetOrAddNode("1");
+ Node.IntroduceAttribute(root, "test", "foo");
+ Assert.AreEqual("foo", n1.GetAttribute("test"));
+ n1.SetAttribute("test", "bar");
+ Assert.AreEqual("bar", n1.GetAttribute("test"));
+
+ RootGraph root2 = Utils.CreateUniqueTestGraph();
+ Node.IntroduceAttribute(root2, "test", "foo");
+ Node n2 = n1.CopyToOtherRoot(root2);
+ Assert.AreEqual("1", n2.GetName());
+ Assert.AreEqual("bar", n2.GetAttribute("test"));
+ }
- [Test()]
- public void TestRootOfRoot()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- RootGraph root2 = root.MyRootGraph;
- }
+ [Test()]
+ public void TestNodeAndGraphWithSameName()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ SubGraph sub = root.GetOrAddSubgraph("name");
+ Node node = sub.GetOrAddNode("name");
+ Assert.True(root.Contains(sub));
+ Assert.True(sub.Contains(node));
+ }
- [Test()]
- public void TestSelfLoopEnumeration()
- {
- RootGraph graph = Utils.CreateUniqueTestGraph();
- Node node = graph.GetOrAddNode("node 1");
- Node node2 = graph.GetOrAddNode("node 2");
- Edge edgein = graph.GetOrAddEdge(node2, node, "in");
- Edge edgeout = graph.GetOrAddEdge(node, node2, "out");
- Edge edgeself = graph.GetOrAddEdge(node, node, "self");
-
- Assert.AreEqual(2, node.InDegree());
- Assert.AreEqual(2, node.OutDegree());
- Assert.AreEqual(4, node.TotalDegree());
-
- Assert.AreEqual(2, node.EdgesIn().Count());
- Assert.AreEqual(2, node.EdgesOut().Count());
- Assert.AreEqual(3, node.Edges().Count());
- }
+ [Test()]
+ public void TestRootOfRoot()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ RootGraph root2 = root.MyRootGraph;
+ }
- [Test()]
- public void TestNonStrictGraph()
- {
- RootGraph graph = Utils.CreateUniqueTestGraph();
- Assert.False(graph.IsStrict());
- Assert.False(graph.IsUndirected());
- Assert.True(graph.IsDirected());
+ [Test()]
+ public void TestSelfLoopEnumeration()
+ {
+ RootGraph graph = Utils.CreateUniqueTestGraph();
+ Node node = graph.GetOrAddNode("node 1");
+ Node node2 = graph.GetOrAddNode("node 2");
+ Edge edgein = graph.GetOrAddEdge(node2, node, "in");
+ Edge edgeout = graph.GetOrAddEdge(node, node2, "out");
+ Edge edgeself = graph.GetOrAddEdge(node, node, "self");
+
+ Assert.AreEqual(2, node.InDegree());
+ Assert.AreEqual(2, node.OutDegree());
+ Assert.AreEqual(4, node.TotalDegree());
+
+ Assert.AreEqual(2, node.EdgesIn().Count());
+ Assert.AreEqual(2, node.EdgesOut().Count());
+ Assert.AreEqual(3, node.Edges().Count());
+ }
- Node node = graph.GetOrAddNode("node 1");
- Node node2 = graph.GetOrAddNode("node 2");
+ [Test()]
+ public void TestNonStrictGraph()
+ {
+ RootGraph graph = Utils.CreateUniqueTestGraph();
+ Assert.False(graph.IsStrict());
+ Assert.False(graph.IsUndirected());
+ Assert.True(graph.IsDirected());
- Edge edge = graph.GetOrAddEdge(node, node, "edge 1");
- Edge edge2 = graph.GetOrAddEdge(node, node, "edge 2");
- Assert.AreNotEqual(edge.GetName(), edge2.GetName());
+ Node node = graph.GetOrAddNode("node 1");
+ Node node2 = graph.GetOrAddNode("node 2");
- Edge edge3 = graph.GetOrAddEdge(node, node2, "edge 3");
- Edge edge4 = graph.GetOrAddEdge(node, node2, "edge 4");
- Assert.AreNotEqual(edge3.GetName(), edge4.GetName());
- }
+ Edge edge = graph.GetOrAddEdge(node, node, "edge 1");
+ Edge edge2 = graph.GetOrAddEdge(node, node, "edge 2");
+ Assert.AreNotEqual(edge.GetName(), edge2.GetName());
- [Test()]
- public void TestEdgeEquals()
- {
- RootGraph graph = Utils.CreateUniqueTestGraph();
- Node node = graph.GetOrAddNode("node 1");
- Node node2 = graph.GetOrAddNode("node 2");
- Node node3 = graph.GetOrAddNode("node 3");
- Edge edge = graph.GetOrAddEdge(node, node, "edge 1");
- Edge edge2 = graph.GetOrAddEdge(node, node, "edge 2");
- Edge edge3 = graph.GetOrAddEdge(node, node2, "edge 3");
- Edge edge4 = graph.GetOrAddEdge(node2, node3, "edge 4");
-
- Assert.AreEqual(edge, edge);
- Assert.AreNotEqual(edge, edge2);
- Assert.AreNotEqual(edge, edge3);
- Assert.AreNotEqual(edge, edge4);
- Assert.AreEqual(edge.GetHashCode(), edge.GetHashCode());
- Assert.AreNotEqual(edge.GetHashCode(), edge2.GetHashCode());
- Assert.AreNotEqual(edge.GetHashCode(), edge3.GetHashCode());
- Assert.AreNotEqual(edge.GetHashCode(), edge4.GetHashCode());
- }
+ Edge edge3 = graph.GetOrAddEdge(node, node2, "edge 3");
+ Edge edge4 = graph.GetOrAddEdge(node, node2, "edge 4");
+ Assert.AreNotEqual(edge3.GetName(), edge4.GetName());
+ }
+
+ [Test()]
+ public void TestEdgeEquals()
+ {
+ RootGraph graph = Utils.CreateUniqueTestGraph();
+ Node node = graph.GetOrAddNode("node 1");
+ Node node2 = graph.GetOrAddNode("node 2");
+ Node node3 = graph.GetOrAddNode("node 3");
+ Edge edge = graph.GetOrAddEdge(node, node, "edge 1");
+ Edge edge2 = graph.GetOrAddEdge(node, node, "edge 2");
+ Edge edge3 = graph.GetOrAddEdge(node, node2, "edge 3");
+ Edge edge4 = graph.GetOrAddEdge(node2, node3, "edge 4");
+
+ Assert.AreEqual(edge, edge);
+ Assert.AreNotEqual(edge, edge2);
+ Assert.AreNotEqual(edge, edge3);
+ Assert.AreNotEqual(edge, edge4);
+ Assert.AreEqual(edge.GetHashCode(), edge.GetHashCode());
+ Assert.AreNotEqual(edge.GetHashCode(), edge2.GetHashCode());
+ Assert.AreNotEqual(edge.GetHashCode(), edge3.GetHashCode());
+ Assert.AreNotEqual(edge.GetHashCode(), edge4.GetHashCode());
+ }
- [Test()]
- public void TestCreateNestedStructures()
- {
- // Documentation:
- // Subgraphs are an important construct in Cgraph.They are intended for organizing subsets of
- // graph objects and can be used interchangeably with top - level graphs in almost all Cgraph
- // functions. A subgraph may contain any nodes or edges of its parent. (When an edge is
- // inserted in a subgraph, its nodes are also implicitly inserted if necessary.Similarly,
- // insertion of a node or edge automatically implies insertion in all containing subgraphs up
- // to the root.) Subgraphs of a graph form a hierarchy(a tree).Cgraph has functions to
- // create, search, and iterate over subgraphs.
-
- // Conclusion: the hierarchical tree structure is maintained in a sane way across all
- // operations we can do w.r.t. subgraphs.
-
-
- // If a node is created in a subgraph, it should also be contained in all supergraphs
- RootGraph graph = Utils.CreateUniqueTestGraph();
- SubGraph supergraph = graph.GetOrAddSubgraph("level 1");
- string subgraphname = "level 2";
- SubGraph subgraph = supergraph.GetOrAddSubgraph(subgraphname);
- string nodename = "test node";
- Node node = subgraph.GetOrAddNode(nodename);
-
- // Node must be contained in super graph
- Assert.True(node.MyRootGraph.Equals(graph));
- Assert.True(supergraph.Contains(node));
- Assert.True(supergraph.Nodes().Contains(node));
- Assert.NotNull(supergraph.GetNode(nodename));
- // Node must be contained in root graph
- Assert.True(graph.Contains(node));
- Assert.True(graph.Nodes().Contains(node));
- Assert.NotNull(graph.GetNode(nodename));
-
- // Subgraph must be contained in super graph
- Assert.True(supergraph.Contains(subgraph));
- Assert.True(supergraph.Descendants().Contains(subgraph));
- Assert.NotNull(supergraph.GetSubgraph(subgraphname));
- // Subgraph must be contained in root graph
- Assert.True(graph.Contains(subgraph));
- Assert.True(graph.Descendants().Contains(subgraph));
- // Subgraph cannot be obtained in the following way:
- //graph.GetSubgraph(subgraphname)
- Assert.Null(graph.GetSubgraph(subgraphname));
- // Use a utility function instead:
- Assert.NotNull(graph.GetDescendantByName(subgraphname));
- }
+ [Test()]
+ public void TestCreateNestedStructures()
+ {
+ // Documentation:
+ // Subgraphs are an important construct in Cgraph.They are intended for organizing subsets of
+ // graph objects and can be used interchangeably with top - level graphs in almost all Cgraph
+ // functions. A subgraph may contain any nodes or edges of its parent. (When an edge is
+ // inserted in a subgraph, its nodes are also implicitly inserted if necessary.Similarly,
+ // insertion of a node or edge automatically implies insertion in all containing subgraphs up
+ // to the root.) Subgraphs of a graph form a hierarchy(a tree).Cgraph has functions to
+ // create, search, and iterate over subgraphs.
+
+ // Conclusion: the hierarchical tree structure is maintained in a sane way across all
+ // operations we can do w.r.t. subgraphs.
+
+
+ // If a node is created in a subgraph, it should also be contained in all supergraphs
+ RootGraph graph = Utils.CreateUniqueTestGraph();
+ SubGraph supergraph = graph.GetOrAddSubgraph("level 1");
+ string subgraphname = "level 2";
+ SubGraph subgraph = supergraph.GetOrAddSubgraph(subgraphname);
+ string nodename = "test node";
+ Node node = subgraph.GetOrAddNode(nodename);
+
+ // Node must be contained in super graph
+ Assert.True(node.MyRootGraph.Equals(graph));
+ Assert.True(supergraph.Contains(node));
+ Assert.True(supergraph.Nodes().Contains(node));
+ Assert.NotNull(supergraph.GetNode(nodename));
+ // Node must be contained in root graph
+ Assert.True(graph.Contains(node));
+ Assert.True(graph.Nodes().Contains(node));
+ Assert.NotNull(graph.GetNode(nodename));
+
+ // Subgraph must be contained in super graph
+ Assert.True(supergraph.Contains(subgraph));
+ Assert.True(supergraph.Descendants().Contains(subgraph));
+ Assert.NotNull(supergraph.GetSubgraph(subgraphname));
+ // Subgraph must be contained in root graph
+ Assert.True(graph.Contains(subgraph));
+ Assert.True(graph.Descendants().Contains(subgraph));
+ // Subgraph cannot be obtained in the following way:
+ //graph.GetSubgraph(subgraphname)
+ Assert.Null(graph.GetSubgraph(subgraphname));
+ // Use a utility function instead:
+ Assert.NotNull(graph.GetDescendantByName(subgraphname));
+ }
- [Test()]
- public void TestEdgesInSubgraphs()
- {
- RootGraph graph = Utils.CreateUniqueTestGraph();
- Node node = graph.GetOrAddNode("node");
- Edge edge = graph.GetOrAddEdge(node, node, "edge 1");
-
- SubGraph subgraph = graph.GetOrAddSubgraph("sub graph");
- Node subnode = subgraph.GetOrAddNode("subnode");
- Edge subedge_between_node = subgraph.GetOrAddEdge(node, node, "edge 2");
- Edge subedge_between_subnode = subgraph.GetOrAddEdge(subnode, subnode, "edge 3");
- Edge edge_between_subnode = graph.GetOrAddEdge(subnode, subnode, "edge 4");
-
- Assert.True(graph.Contains(edge));
- Assert.True(graph.Contains(subedge_between_node));
- Assert.True(graph.Contains(subedge_between_subnode));
- Assert.True(graph.Contains(edge_between_subnode));
-
- Assert.False(subgraph.Contains(edge));
- Assert.True(subgraph.Contains(subedge_between_node));
- Assert.True(subgraph.Contains(subedge_between_subnode));
- Assert.False(subgraph.Contains(edge_between_subnode));
-
- // Conclusion:
- // Subgraphs can contain edges, independently of their endpoints.
- // This affects enumeration as follows:
- Assert.AreEqual(2, node.EdgesOut(graph).Count());
- Assert.AreEqual(1, node.EdgesOut(subgraph).Count());
- Assert.AreEqual(2, subnode.EdgesOut(graph).Count());
- Assert.AreEqual(1, subnode.EdgesOut(subgraph).Count());
- }
+ [Test()]
+ public void TestEdgesInSubgraphs()
+ {
+ RootGraph graph = Utils.CreateUniqueTestGraph();
+ Node node = graph.GetOrAddNode("node");
+ Edge edge = graph.GetOrAddEdge(node, node, "edge 1");
+
+ SubGraph subgraph = graph.GetOrAddSubgraph("sub graph");
+ Node subnode = subgraph.GetOrAddNode("subnode");
+ Edge subedge_between_node = subgraph.GetOrAddEdge(node, node, "edge 2");
+ Edge subedge_between_subnode = subgraph.GetOrAddEdge(subnode, subnode, "edge 3");
+ Edge edge_between_subnode = graph.GetOrAddEdge(subnode, subnode, "edge 4");
+
+ Assert.True(graph.Contains(edge));
+ Assert.True(graph.Contains(subedge_between_node));
+ Assert.True(graph.Contains(subedge_between_subnode));
+ Assert.True(graph.Contains(edge_between_subnode));
+
+ Assert.False(subgraph.Contains(edge));
+ Assert.True(subgraph.Contains(subedge_between_node));
+ Assert.True(subgraph.Contains(subedge_between_subnode));
+ Assert.False(subgraph.Contains(edge_between_subnode));
+
+ // Conclusion:
+ // Subgraphs can contain edges, independently of their endpoints.
+ // This affects enumeration as follows:
+ Assert.AreEqual(2, node.EdgesOut(graph).Count());
+ Assert.AreEqual(1, node.EdgesOut(subgraph).Count());
+ Assert.AreEqual(2, subnode.EdgesOut(graph).Count());
+ Assert.AreEqual(1, subnode.EdgesOut(subgraph).Count());
+ }
- [Test()]
- public void TestRecursiveSubgraphDeletion()
- {
- RootGraph graph = Utils.CreateUniqueTestGraph();
- Graph.IntroduceAttribute(graph, "label", "");
- graph.SetAttribute("label", "xx");
- Node node = graph.GetOrAddNode("node");
- SubGraph subgraph = graph.GetOrAddSubgraph("subgraph");
- subgraph.SetAttribute("label", "x");
- Node subnode = subgraph.GetOrAddNode("subnode");
- SubGraph subsubgraph = subgraph.GetOrAddSubgraph("subsubgraph");
- subsubgraph.SetAttribute("label", "x");
- Node subsubnode = subsubgraph.GetOrAddNode("subsubnode");
-
- // Now deleting subgraph should also delete subsubgraph
- Assert.AreNotEqual(null, subgraph.GetSubgraph("subsubgraph"));
- Assert.AreNotEqual(null, graph.GetDescendantByName("subsubgraph"));
- subgraph.Delete();
- Assert.AreEqual(null, graph.GetDescendantByName("subsubgraph"));
- }
+ [Test()]
+ public void TestRecursiveSubgraphDeletion()
+ {
+ RootGraph graph = Utils.CreateUniqueTestGraph();
+ Graph.IntroduceAttribute(graph, "label", "");
+ graph.SetAttribute("label", "xx");
+ Node node = graph.GetOrAddNode("node");
+ SubGraph subgraph = graph.GetOrAddSubgraph("subgraph");
+ subgraph.SetAttribute("label", "x");
+ Node subnode = subgraph.GetOrAddNode("subnode");
+ SubGraph subsubgraph = subgraph.GetOrAddSubgraph("subsubgraph");
+ subsubgraph.SetAttribute("label", "x");
+ Node subsubnode = subsubgraph.GetOrAddNode("subsubnode");
+
+ // Now deleting subgraph should also delete subsubgraph
+ Assert.AreNotEqual(null, subgraph.GetSubgraph("subsubgraph"));
+ Assert.AreNotEqual(null, graph.GetDescendantByName("subsubgraph"));
+ subgraph.Delete();
+ Assert.AreEqual(null, graph.GetDescendantByName("subsubgraph"));
+ }
- [Test()]
- public void DotOutputConsistency()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- Node nodeA = root.GetOrAddNode("A");
+ [Test()]
+ public void DotOutputConsistency()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Node nodeA = root.GetOrAddNode("A");
- nodeA.SafeSetAttribute("shape", "record", "");
- nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
+ nodeA.SafeSetAttribute("shape", "record", "");
+ nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
- root.ComputeLayout();
- var dotstr = root.ToDotString();
- var root2 = RootGraph.FromDotString(dotstr);
- var dotstr2 = root2.ToDotString();
- Assert.AreEqual(dotstr, dotstr2);
- }
+ root.ComputeLayout();
+ var dotstr = root.ToDotString();
+ var root2 = RootGraph.FromDotString(dotstr);
+ var dotstr2 = root2.ToDotString();
+ Assert.AreEqual(dotstr, dotstr2);
}
}
diff --git a/Rubjerg.Graphviz.Test/CGraphIntegrationTests.cs b/Rubjerg.Graphviz.Test/CGraphIntegrationTests.cs
index 5265e7b..351d6b4 100644
--- a/Rubjerg.Graphviz.Test/CGraphIntegrationTests.cs
+++ b/Rubjerg.Graphviz.Test/CGraphIntegrationTests.cs
@@ -2,151 +2,150 @@
using System.Linq;
using NUnit.Framework;
-namespace Rubjerg.Graphviz.Test
+namespace Rubjerg.Graphviz.Test;
+
+using static Utils;
+
+[TestFixture()]
+public class CGraphIntegrationTests
{
- using static Utils;
+ protected virtual int SizeMultiplier => 1;
- [TestFixture()]
- public class CGraphIntegrationTests
+ [TestCase(10, 5)]
+ public void TestTopologicalEqualsIdentity(int nodes, int degree)
{
- protected virtual int SizeMultiplier => 1;
-
- [TestCase(10, 5)]
- public void TestTopologicalEqualsIdentity(int nodes, int degree)
- {
- var root = CreateRandomConnectedGraph(nodes * SizeMultiplier, degree);
- Assert.IsTrue(GraphComparer.CheckTopologicallyEquals(root, root, Log));
- }
+ var root = CreateRandomConnectedGraph(nodes * SizeMultiplier, degree);
+ Assert.IsTrue(GraphComparer.CheckTopologicallyEquals(root, root, Log));
+ }
- [TestCase(10, 5)]
- public void TestTopologicalEqualsClone(int nodes, int degree)
- {
- var root = CreateRandomConnectedGraph(nodes * SizeMultiplier, degree);
- var clone = root.Clone(root.GetName() + "clone");
- Assert.IsTrue(GraphComparer.CheckTopologicallyEquals(root, clone, Log));
- }
+ [TestCase(10, 5)]
+ public void TestTopologicalEqualsClone(int nodes, int degree)
+ {
+ var root = CreateRandomConnectedGraph(nodes * SizeMultiplier, degree);
+ var clone = root.Clone(root.GetName() + "clone");
+ Assert.IsTrue(GraphComparer.CheckTopologicallyEquals(root, clone, Log));
+ }
- [TestCase(10, 5)]
- public void TestTopologicalEqualsCloneWithSubgraphs(int nodes, int degree)
- {
- var root = CreateRandomConnectedGraph(nodes * SizeMultiplier, degree);
- var nodeSelection = new HashSet(root.Nodes().Take(nodes / 2));
- var sub = root.AddSubgraphFromNodes("sub", nodeSelection);
-
- var subNodeCount = sub.Nodes().Count();
- var nodeSelection2 = new HashSet(sub.Nodes().Take(subNodeCount / 2));
- var sub2 = root.AddSubgraphFromNodes("sub2", nodeSelection2);
-
- var edgeCount = sub2.Edges().Count();
- var edgeSelection = new HashSet(sub.Edges().Take(edgeCount / 2));
- var sub3 = root.AddSubgraphFromEdgeSet("sub3", edgeSelection);
-
- RootGraph subclone = sub.Clone("subclone");
- Assert.IsTrue(GraphComparer.CheckTopologicallyEquals(sub, subclone, Log));
- Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub, root, Log));
- Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub, sub2, Log));
- Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub, sub3, Log));
-
- RootGraph sub2clone = sub2.Clone("sub2clone");
- Assert.IsTrue(GraphComparer.CheckTopologicallyEquals(sub2, sub2clone, Log));
- Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub2, root, Log));
- Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub2, sub, Log));
- Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub2, sub3, Log));
-
- RootGraph sub3clone = sub3.Clone("sub3clone");
- Assert.IsTrue(GraphComparer.CheckTopologicallyEquals(sub3, sub3clone, Log));
- Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub3, root, Log));
- Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub3, sub, Log));
- Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub3, sub2, Log));
- }
+ [TestCase(10, 5)]
+ public void TestTopologicalEqualsCloneWithSubgraphs(int nodes, int degree)
+ {
+ var root = CreateRandomConnectedGraph(nodes * SizeMultiplier, degree);
+ var nodeSelection = new HashSet(root.Nodes().Take(nodes / 2));
+ var sub = root.AddSubgraphFromNodes("sub", nodeSelection);
+
+ var subNodeCount = sub.Nodes().Count();
+ var nodeSelection2 = new HashSet(sub.Nodes().Take(subNodeCount / 2));
+ var sub2 = root.AddSubgraphFromNodes("sub2", nodeSelection2);
+
+ var edgeCount = sub2.Edges().Count();
+ var edgeSelection = new HashSet(sub.Edges().Take(edgeCount / 2));
+ var sub3 = root.AddSubgraphFromEdgeSet("sub3", edgeSelection);
+
+ RootGraph subclone = sub.Clone("subclone");
+ Assert.IsTrue(GraphComparer.CheckTopologicallyEquals(sub, subclone, Log));
+ Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub, root, Log));
+ Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub, sub2, Log));
+ Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub, sub3, Log));
+
+ RootGraph sub2clone = sub2.Clone("sub2clone");
+ Assert.IsTrue(GraphComparer.CheckTopologicallyEquals(sub2, sub2clone, Log));
+ Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub2, root, Log));
+ Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub2, sub, Log));
+ Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub2, sub3, Log));
+
+ RootGraph sub3clone = sub3.Clone("sub3clone");
+ Assert.IsTrue(GraphComparer.CheckTopologicallyEquals(sub3, sub3clone, Log));
+ Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub3, root, Log));
+ Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub3, sub, Log));
+ Assert.IsFalse(GraphComparer.CheckTopologicallyEquals(sub3, sub2, Log));
+ }
- ///
- /// This test fails if the locking doesn't work, and the GC runs async.
- ///
- [TestCase(100, 10000)]
- public void TestGetNode(int initial_graphs, int get_attempts)
+ ///
+ /// This test fails if the locking doesn't work, and the GC runs async.
+ ///
+ [TestCase(100, 10000)]
+ public void TestGetNode(int initial_graphs, int get_attempts)
+ {
+ for (int j = 0; j < initial_graphs; j++)
{
- for (int j = 0; j < initial_graphs; j++)
+ var pre = CreateUniqueTestGraph();
+ for (int i = 0; i < 10; i++)
{
- var pre = CreateUniqueTestGraph();
- for (int i = 0; i < 10; i++)
- {
- _ = pre.GetOrAddNode(i.ToString());
- }
+ _ = pre.GetOrAddNode(i.ToString());
}
+ }
- var root = CreateUniqueTestGraph();
- _ = root.GetOrAddNode("node1");
- for (int i = 0; i < get_attempts * SizeMultiplier; i++)
- {
- var node = root.GetNode("node1");
- Assert.IsNotNull(node);
- }
+ var root = CreateUniqueTestGraph();
+ _ = root.GetOrAddNode("node1");
+ for (int i = 0; i < get_attempts * SizeMultiplier; i++)
+ {
+ var node = root.GetNode("node1");
+ Assert.IsNotNull(node);
}
+ }
- [TestCase(500, 10)]
- public void TestAddNode(int nodes, int degree)
+ [TestCase(500, 10)]
+ public void TestAddNode(int nodes, int degree)
+ {
+ int initcount = nodes * SizeMultiplier;
+ var root = CreateRandomConnectedGraph(initcount, degree);
+ int addcount = nodes * SizeMultiplier;
+ var watch = System.Diagnostics.Stopwatch.StartNew();
+ for (int i = initcount; i < initcount + addcount; i++)
{
- int initcount = nodes * SizeMultiplier;
- var root = CreateRandomConnectedGraph(initcount, degree);
- int addcount = nodes * SizeMultiplier;
- var watch = System.Diagnostics.Stopwatch.StartNew();
- for (int i = initcount; i < initcount + addcount; i++)
- {
- _ = root.GetOrAddNode(i.ToString());
- }
- watch.Stop();
- var elapsedms = watch.ElapsedMilliseconds;
- Log($"Elapsed ms: {elapsedms}");
- Assert.AreEqual(initcount + addcount, root.Nodes().Count());
+ _ = root.GetOrAddNode(i.ToString());
}
+ watch.Stop();
+ var elapsedms = watch.ElapsedMilliseconds;
+ Log($"Elapsed ms: {elapsedms}");
+ Assert.AreEqual(initcount + addcount, root.Nodes().Count());
+ }
- [TestCase(500, 10)]
- public void TestDeleteNode(int nodes, int degree)
+ [TestCase(500, 10)]
+ public void TestDeleteNode(int nodes, int degree)
+ {
+ int initcount = nodes * 2 * SizeMultiplier;
+ var root = CreateRandomConnectedGraph(initcount, degree);
+ int delcount = nodes * SizeMultiplier;
+ var watch = System.Diagnostics.Stopwatch.StartNew();
+ for (int i = initcount - delcount; i < initcount; i++)
{
- int initcount = nodes * 2 * SizeMultiplier;
- var root = CreateRandomConnectedGraph(initcount, degree);
- int delcount = nodes * SizeMultiplier;
- var watch = System.Diagnostics.Stopwatch.StartNew();
- for (int i = initcount - delcount; i < initcount; i++)
- {
- var node = root.GetOrAddNode(i.ToString());
- root.Delete(node);
- }
- watch.Stop();
- var elapsedms = watch.ElapsedMilliseconds;
- Log($"Elapsed ms: {elapsedms}");
- Assert.AreEqual(initcount - delcount, root.Nodes().Count());
+ var node = root.GetOrAddNode(i.ToString());
+ root.Delete(node);
}
+ watch.Stop();
+ var elapsedms = watch.ElapsedMilliseconds;
+ Log($"Elapsed ms: {elapsedms}");
+ Assert.AreEqual(initcount - delcount, root.Nodes().Count());
+ }
- [TestCase(100, 10)]
- public void TestBFS(int nodes, int degree)
+ [TestCase(100, 10)]
+ public void TestBFS(int nodes, int degree)
+ {
+ int initcount = nodes * SizeMultiplier;
+ var root = CreateRandomConnectedGraph(initcount, degree);
+ var watch = System.Diagnostics.Stopwatch.StartNew();
+
+ var start = root.GetOrAddNode(0.ToString());
+ var visited = new HashSet();
+ var front = new Queue();
+ front.Enqueue(start);
+ while (front.Any())
{
- int initcount = nodes * SizeMultiplier;
- var root = CreateRandomConnectedGraph(initcount, degree);
- var watch = System.Diagnostics.Stopwatch.StartNew();
-
- var start = root.GetOrAddNode(0.ToString());
- var visited = new HashSet();
- var front = new Queue();
- front.Enqueue(start);
- while (front.Any())
+ Node current = front.Dequeue();
+ foreach (var n in current.NeighborsOut())
{
- Node current = front.Dequeue();
- foreach (var n in current.NeighborsOut())
+ if (!visited.Contains(n))
{
- if (!visited.Contains(n))
- {
- _ = visited.Add(n);
- front.Enqueue(n);
- }
+ _ = visited.Add(n);
+ front.Enqueue(n);
}
}
-
- watch.Stop();
- var elapsedms = watch.ElapsedMilliseconds;
- Log($"Elapsed ms: {elapsedms}");
- Assert.AreEqual(initcount, visited.Count);
}
+
+ watch.Stop();
+ var elapsedms = watch.ElapsedMilliseconds;
+ Log($"Elapsed ms: {elapsedms}");
+ Assert.AreEqual(initcount, visited.Count);
}
}
diff --git a/Rubjerg.Graphviz.Test/CGraphStressTests.cs b/Rubjerg.Graphviz.Test/CGraphStressTests.cs
index e1b0ae0..913a7c1 100644
--- a/Rubjerg.Graphviz.Test/CGraphStressTests.cs
+++ b/Rubjerg.Graphviz.Test/CGraphStressTests.cs
@@ -1,11 +1,10 @@
using NUnit.Framework;
-namespace Rubjerg.Graphviz.Test
+namespace Rubjerg.Graphviz.Test;
+
+[TestFixture()]
+[Category("Slow")]
+public class CGraphStressTests : CGraphIntegrationTests
{
- [TestFixture()]
- [Category("Slow")]
- public class CGraphStressTests : CGraphIntegrationTests
- {
- protected override int SizeMultiplier => 100;
- }
+ protected override int SizeMultiplier => 100;
}
diff --git a/Rubjerg.Graphviz.Test/OldTutorial.cs b/Rubjerg.Graphviz.Test/OldTutorial.cs
index 70c137e..18d1523 100644
--- a/Rubjerg.Graphviz.Test/OldTutorial.cs
+++ b/Rubjerg.Graphviz.Test/OldTutorial.cs
@@ -4,184 +4,183 @@
#pragma warning disable CS0618 // Type or member is obsolete
-namespace Rubjerg.Graphviz.Test
+namespace Rubjerg.Graphviz.Test;
+
+///
+/// This used to be the tutorial, and this still exists for test coverage of the old code that deals with
+/// in-place layout computation.
+///
+[TestFixture()]
+public class OldTutorial
{
- ///
- /// This used to be the tutorial, and this still exists for test coverage of the old code that deals with
- /// in-place layout computation.
- ///
- [TestFixture()]
- public class OldTutorial
+ [Test, Order(1)]
+ public void GraphConstruction()
{
- [Test, Order(1)]
- public void GraphConstruction()
- {
- // You can programmatically construct graphs as follows
- RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Some Unique Identifier");
-
- // The node names are unique identifiers within a graph in Graphviz
- Node nodeA = root.GetOrAddNode("A");
- Node nodeB = root.GetOrAddNode("B");
- Node nodeC = root.GetOrAddNode("C");
- Node nodeD = root.GetOrAddNode("D");
-
- // The edge name is only unique between two nodes
- Edge edgeAB = root.GetOrAddEdge(nodeA, nodeB, "Some edge name");
- Edge edgeBC = root.GetOrAddEdge(nodeB, nodeC, "Some edge name");
- Edge anotherEdgeBC = root.GetOrAddEdge(nodeB, nodeC, "Another edge name");
-
- // We can attach attributes to nodes, edges and graphs to store information and instruct
- // graphviz by specifying layout parameters. At the moment we only support string
- // attributes. Cgraph assumes that all objects of a given kind (graphs/subgraphs, nodes,
- // or edges) have the same attributes. The attributes first have to be introduced for a
- // certain kind, before we can use it.
- Node.IntroduceAttribute(root, "my attribute", "defaultvalue");
- nodeA.SetAttribute("my attribute", "othervalue");
-
- // Attributes are introduced per kind (Node, Edge, Graph) per root graph.
- // So to be able to use "my attribute" on edges, we first have to introduce it as well.
- Edge.IntroduceAttribute(root, "my attribute", "defaultvalue");
- edgeAB.SetAttribute("my attribute", "othervalue");
-
- // To introduce and set an attribute at the same time, there are convenience wrappers
- edgeBC.SafeSetAttribute("arrowsize", "2.0", "1.0");
-
- // Some attributes - like "label" - accept HTML strings as value
- // To tell graphviz that a string should be interpreted as HTML use the designated methods
- Node.IntroduceAttribute(root, "label", "defaultlabel");
- nodeB.SetAttributeHtml("label", "Some HTML string");
-
- // We can simply export this graph to a text file in dot format
- root.ToDotFile(TestContext.CurrentContext.TestDirectory + "/out.dot");
- }
-
- [Test, Order(2)]
- public void Layouting()
- {
- // If we have a given dot file (in this case the one we generated above), we can also read it back in
- RootGraph root = RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory + "/out.dot");
-
- // Let's have graphviz compute a dot layout for us
- root.ComputeLayout();
-
- // We can export this to svg
- root.RenderToFile(TestContext.CurrentContext.TestDirectory + "/dot_out.svg", "svg");
-
- // Or programatically read out the layout attributes
- Node nodeA = root.GetNode("A");
- PointF position = nodeA.GetPosition();
- Utils.AssertPattern(@"{X=[\d.]+, Y=[\d.]+}", position.ToString());
-
- // Like a bounding box of an object
- RectangleF nodeboundingbox = nodeA.GetBoundingBox();
- Utils.AssertPattern(@"{X=[\d.]+,Y=[\d.]+,Width=[\d.]+,Height=[\d.]+}", nodeboundingbox.ToString());
-
- // Or splines between nodes
- Node nodeB = root.GetNode("B");
- Edge edge = root.GetEdge(nodeA, nodeB, "Some edge name");
- PointF[] spline = edge.GetFirstSpline();
- string splineString = string.Join(", ", spline.Select(p => p.ToString()));
- string expectedSplinePattern =
- @"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+},"
- + @" {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";
- Utils.AssertPattern(expectedSplinePattern, splineString);
-
- GraphvizLabel nodeLabel = nodeA.GetLabel();
- Utils.AssertPattern(@"{X=[\d.]+,Y=[\d.]+,Width=[\d.]+,Height=[\d.]+}",
- nodeLabel.BoundingBox().ToString());
- Utils.AssertPattern(@"Times-Roman", nodeLabel.FontName().ToString());
-
- // Once all layout information is obtained from the graph, the resources should be
- // reclaimed. To do this, the application should call the cleanup routine associated
- // with the layout algorithm used to draw the graph. This is done by a call to
- // FreeLayout(). A given graph can be laid out multiple times. The application, however,
- // must clean up the earlier layout's information with a call to FreeLayout before
- // invoking a new layout function.
- root.FreeLayout();
-
- // We can use layout engines other than dot by explicitly passing the engine we want
- root.ComputeLayout(LayoutEngines.Neato);
- root.RenderToFile(TestContext.CurrentContext.TestDirectory + "/neato_out.svg", "svg");
- }
-
- [Test, Order(3)]
- public void Clusters()
- {
- RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with clusters");
- Node nodeA = root.GetOrAddNode("A");
- Node nodeB = root.GetOrAddNode("B");
- Node nodeC = root.GetOrAddNode("C");
- Node nodeD = root.GetOrAddNode("D");
-
- // When a subgraph name is prefixed with cluster,
- // the dot layout engine will render it as a box around the containing nodes.
- SubGraph cluster1 = root.GetOrAddSubgraph("cluster_1");
- cluster1.AddExisting(nodeB);
- cluster1.AddExisting(nodeC);
- SubGraph cluster2 = root.GetOrAddSubgraph("cluster_2");
- cluster2.AddExisting(nodeD);
-
- // COMPOUND EDGES
- // Graphviz does not really support edges from and to clusters. However, by adding an
- // invisible dummynode and setting the ltail or lhead attributes of an edge this
- // behavior can be faked. Graphviz will then draw an edge to the dummy node but clip it
- // at the border of the cluster. We provide convenience methods for this.
- // To enable this feature, Graphviz requires us to set the "compound" attribute to "true".
- Graph.IntroduceAttribute(root, "compound", "true"); // Allow lhead/ltail
- // The boolean indicates whether the dummy node should take up any space. When you pass
- // false and you have a lot of edges, the edges may start to overlap a lot.
- _ = root.GetOrAddEdge(nodeA, cluster1, false, "edge to a cluster");
- _ = root.GetOrAddEdge(cluster1, nodeD, false, "edge from a cluster");
- _ = root.GetOrAddEdge(cluster1, cluster1, false, "edge between clusters");
-
- root.ComputeLayout();
-
- SubGraph cluster = root.GetSubgraph("cluster_1");
- RectangleF clusterbox = cluster.GetBoundingBox();
- RectangleF rootgraphbox = root.GetBoundingBox();
- Utils.AssertPattern(@"{X=[\d.]+,Y=[\d.]+,Width=[\d.]+,Height=[\d.]+}", clusterbox.ToString());
- Utils.AssertPattern(@"{X=[\d.]+,Y=[\d.]+,Width=[\d.]+,Height=[\d.]+}", rootgraphbox.ToString());
- }
-
- [Test, Order(4)]
- public void Records()
- {
- RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with records");
- Node nodeA = root.GetOrAddNode("A");
- nodeA.SafeSetAttribute("shape", "record", "");
- nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
-
- root.ComputeLayout();
-
- // The order of the list matches the order in which the labels occur in the label string above.
- var rects = nodeA.GetRecordRectangles().ToList();
- Assert.That(rects.Count, Is.EqualTo(9));
- }
-
- [Test, Order(5)]
- public void StringEscaping()
- {
- RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with escaped strings");
- Node.IntroduceAttribute(root, "label", "\\N");
- Node nodeA = root.GetOrAddNode("A");
-
- // Several characters and character sequences can have special meanings in labels, like \N.
- // When you want to have a literal string in a label, we provide a convenience function for you to do just that.
- nodeA.SetAttribute("label", CGraphThing.EscapeLabel("Some string literal \\N \\n |}>"));
-
- root.ComputeLayout();
-
- // When defining portnames, some characters, like ':' and '|', are not allowed and they can't be escaped either.
- // This can be troubling if you have an externally defined ID for such a port.
- // We provide a function that maps strings to valid portnames.
- var somePortId = "port id with :| special characters";
- var validPortName = Edge.ConvertUidToPortName(somePortId);
- Node nodeB = root.GetOrAddNode("B");
- nodeB.SafeSetAttribute("shape", "record", "");
- nodeB.SafeSetAttribute("label", $"<{validPortName}>1|2", "\\N");
-
- // The function makes sure different strings don't accidentally map onto the same portname
- Assert.That(Edge.ConvertUidToPortName(":"), Is.Not.EqualTo(Edge.ConvertUidToPortName("|")));
- }
+ // You can programmatically construct graphs as follows
+ RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Some Unique Identifier");
+
+ // The node names are unique identifiers within a graph in Graphviz
+ Node nodeA = root.GetOrAddNode("A");
+ Node nodeB = root.GetOrAddNode("B");
+ Node nodeC = root.GetOrAddNode("C");
+ Node nodeD = root.GetOrAddNode("D");
+
+ // The edge name is only unique between two nodes
+ Edge edgeAB = root.GetOrAddEdge(nodeA, nodeB, "Some edge name");
+ Edge edgeBC = root.GetOrAddEdge(nodeB, nodeC, "Some edge name");
+ Edge anotherEdgeBC = root.GetOrAddEdge(nodeB, nodeC, "Another edge name");
+
+ // We can attach attributes to nodes, edges and graphs to store information and instruct
+ // graphviz by specifying layout parameters. At the moment we only support string
+ // attributes. Cgraph assumes that all objects of a given kind (graphs/subgraphs, nodes,
+ // or edges) have the same attributes. The attributes first have to be introduced for a
+ // certain kind, before we can use it.
+ Node.IntroduceAttribute(root, "my attribute", "defaultvalue");
+ nodeA.SetAttribute("my attribute", "othervalue");
+
+ // Attributes are introduced per kind (Node, Edge, Graph) per root graph.
+ // So to be able to use "my attribute" on edges, we first have to introduce it as well.
+ Edge.IntroduceAttribute(root, "my attribute", "defaultvalue");
+ edgeAB.SetAttribute("my attribute", "othervalue");
+
+ // To introduce and set an attribute at the same time, there are convenience wrappers
+ edgeBC.SafeSetAttribute("arrowsize", "2.0", "1.0");
+
+ // Some attributes - like "label" - accept HTML strings as value
+ // To tell graphviz that a string should be interpreted as HTML use the designated methods
+ Node.IntroduceAttribute(root, "label", "defaultlabel");
+ nodeB.SetAttributeHtml("label", "Some HTML string");
+
+ // We can simply export this graph to a text file in dot format
+ root.ToDotFile(TestContext.CurrentContext.TestDirectory + "/out.dot");
+ }
+
+ [Test, Order(2)]
+ public void Layouting()
+ {
+ // If we have a given dot file (in this case the one we generated above), we can also read it back in
+ RootGraph root = RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory + "/out.dot");
+
+ // Let's have graphviz compute a dot layout for us
+ root.ComputeLayout();
+
+ // We can export this to svg
+ root.RenderToFile(TestContext.CurrentContext.TestDirectory + "/dot_out.svg", "svg");
+
+ // Or programatically read out the layout attributes
+ Node nodeA = root.GetNode("A");
+ PointF position = nodeA.GetPosition();
+ Utils.AssertPattern(@"{X=[\d.]+, Y=[\d.]+}", position.ToString());
+
+ // Like a bounding box of an object
+ RectangleF nodeboundingbox = nodeA.GetBoundingBox();
+ Utils.AssertPattern(@"{X=[\d.]+,Y=[\d.]+,Width=[\d.]+,Height=[\d.]+}", nodeboundingbox.ToString());
+
+ // Or splines between nodes
+ Node nodeB = root.GetNode("B");
+ Edge edge = root.GetEdge(nodeA, nodeB, "Some edge name");
+ PointF[] spline = edge.GetFirstSpline();
+ string splineString = string.Join(", ", spline.Select(p => p.ToString()));
+ string expectedSplinePattern =
+ @"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+},"
+ + @" {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";
+ Utils.AssertPattern(expectedSplinePattern, splineString);
+
+ GraphvizLabel nodeLabel = nodeA.GetLabel();
+ Utils.AssertPattern(@"{X=[\d.]+,Y=[\d.]+,Width=[\d.]+,Height=[\d.]+}",
+ nodeLabel.BoundingBox().ToString());
+ Utils.AssertPattern(@"Times-Roman", nodeLabel.FontName().ToString());
+
+ // Once all layout information is obtained from the graph, the resources should be
+ // reclaimed. To do this, the application should call the cleanup routine associated
+ // with the layout algorithm used to draw the graph. This is done by a call to
+ // FreeLayout(). A given graph can be laid out multiple times. The application, however,
+ // must clean up the earlier layout's information with a call to FreeLayout before
+ // invoking a new layout function.
+ root.FreeLayout();
+
+ // We can use layout engines other than dot by explicitly passing the engine we want
+ root.ComputeLayout(LayoutEngines.Neato);
+ root.RenderToFile(TestContext.CurrentContext.TestDirectory + "/neato_out.svg", "svg");
+ }
+
+ [Test, Order(3)]
+ public void Clusters()
+ {
+ RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with clusters");
+ Node nodeA = root.GetOrAddNode("A");
+ Node nodeB = root.GetOrAddNode("B");
+ Node nodeC = root.GetOrAddNode("C");
+ Node nodeD = root.GetOrAddNode("D");
+
+ // When a subgraph name is prefixed with cluster,
+ // the dot layout engine will render it as a box around the containing nodes.
+ SubGraph cluster1 = root.GetOrAddSubgraph("cluster_1");
+ cluster1.AddExisting(nodeB);
+ cluster1.AddExisting(nodeC);
+ SubGraph cluster2 = root.GetOrAddSubgraph("cluster_2");
+ cluster2.AddExisting(nodeD);
+
+ // COMPOUND EDGES
+ // Graphviz does not really support edges from and to clusters. However, by adding an
+ // invisible dummynode and setting the ltail or lhead attributes of an edge this
+ // behavior can be faked. Graphviz will then draw an edge to the dummy node but clip it
+ // at the border of the cluster. We provide convenience methods for this.
+ // To enable this feature, Graphviz requires us to set the "compound" attribute to "true".
+ Graph.IntroduceAttribute(root, "compound", "true"); // Allow lhead/ltail
+ // The boolean indicates whether the dummy node should take up any space. When you pass
+ // false and you have a lot of edges, the edges may start to overlap a lot.
+ _ = root.GetOrAddEdge(nodeA, cluster1, false, "edge to a cluster");
+ _ = root.GetOrAddEdge(cluster1, nodeD, false, "edge from a cluster");
+ _ = root.GetOrAddEdge(cluster1, cluster1, false, "edge between clusters");
+
+ root.ComputeLayout();
+
+ SubGraph cluster = root.GetSubgraph("cluster_1");
+ RectangleF clusterbox = cluster.GetBoundingBox();
+ RectangleF rootgraphbox = root.GetBoundingBox();
+ Utils.AssertPattern(@"{X=[\d.]+,Y=[\d.]+,Width=[\d.]+,Height=[\d.]+}", clusterbox.ToString());
+ Utils.AssertPattern(@"{X=[\d.]+,Y=[\d.]+,Width=[\d.]+,Height=[\d.]+}", rootgraphbox.ToString());
+ }
+
+ [Test, Order(4)]
+ public void Records()
+ {
+ RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with records");
+ Node nodeA = root.GetOrAddNode("A");
+ nodeA.SafeSetAttribute("shape", "record", "");
+ nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
+
+ root.ComputeLayout();
+
+ // The order of the list matches the order in which the labels occur in the label string above.
+ var rects = nodeA.GetRecordRectangles().ToList();
+ Assert.That(rects.Count, Is.EqualTo(9));
+ }
+
+ [Test, Order(5)]
+ public void StringEscaping()
+ {
+ RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with escaped strings");
+ Node.IntroduceAttribute(root, "label", "\\N");
+ Node nodeA = root.GetOrAddNode("A");
+
+ // Several characters and character sequences can have special meanings in labels, like \N.
+ // When you want to have a literal string in a label, we provide a convenience function for you to do just that.
+ nodeA.SetAttribute("label", CGraphThing.EscapeLabel("Some string literal \\N \\n |}>"));
+
+ root.ComputeLayout();
+
+ // When defining portnames, some characters, like ':' and '|', are not allowed and they can't be escaped either.
+ // This can be troubling if you have an externally defined ID for such a port.
+ // We provide a function that maps strings to valid portnames.
+ var somePortId = "port id with :| special characters";
+ var validPortName = Edge.ConvertUidToPortName(somePortId);
+ Node nodeB = root.GetOrAddNode("B");
+ nodeB.SafeSetAttribute("shape", "record", "");
+ nodeB.SafeSetAttribute("label", $"<{validPortName}>1|2", "\\N");
+
+ // The function makes sure different strings don't accidentally map onto the same portname
+ Assert.That(Edge.ConvertUidToPortName(":"), Is.Not.EqualTo(Edge.ConvertUidToPortName("|")));
}
}
diff --git a/Rubjerg.Graphviz.Test/Reproductions.cs b/Rubjerg.Graphviz.Test/Reproductions.cs
index fe81f9f..6cf8797 100644
--- a/Rubjerg.Graphviz.Test/Reproductions.cs
+++ b/Rubjerg.Graphviz.Test/Reproductions.cs
@@ -3,111 +3,106 @@
using System.IO;
using System.Linq;
-namespace Rubjerg.Graphviz.Test
+namespace Rubjerg.Graphviz.Test;
+
+///
+/// Test various scenarios that have caused problems in the past.
+///
+[TestFixture()]
+public class Reproductions
{
- ///
- /// Test various scenarios that have caused problems in the past.
- ///
- [TestFixture()]
- public class Reproductions
+ private string _testDir;
+
+ [SetUp]
+ public void SetUp()
+ {
+ // Store the test directory.
+ _testDir = TestContext.CurrentContext.TestDirectory;
+ }
+
+ [Test()]
+ [TestCase("Times-Roman", 7, 0.01)]
+ [TestCase("Times-Roman", 7, 0.5)]
+ public void TestRecordShapeAlignment(string fontname, double fontsize, double margin)
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ // Margin between label and node boundary in inches
+ Node.IntroduceAttribute(root, "margin", margin.ToString(CultureInfo.InvariantCulture));
+ Node.IntroduceAttribute(root, "fontsize", fontsize.ToString(CultureInfo.InvariantCulture));
+ Node.IntroduceAttribute(root, "fontname", fontname);
+
+ Node nodeA = root.GetOrAddNode("A");
+
+ nodeA.SafeSetAttribute("shape", "record", "");
+ nodeA.SafeSetAttribute("label", "{20 VH|{1|2}}", "");
+
+ //TestContext.Write(root.ToDotString());
+ root.ComputeLayout();
+ //TestContext.Write(root.ToDotString());
+
+ var rects = nodeA.GetRecordRectangles().ToList();
+ Assert.That(rects[0].Right, Is.EqualTo(rects[2].Right));
+ }
+
+ // This test only failed when running in isolation
+ [Test()]
+ public void MissingLabelRepro()
+ {
+ var graph = RootGraph.FromDotFile($"{_testDir}/missing-label-repro.dot");
+ graph.ComputeLayout();
+ graph.ToSvgFile($"{_testDir}/test.svg");
+ string svgString = File.ReadAllText($"{_testDir}/test.svg");
+ Assert.IsTrue(svgString.Contains(">OpenNode"));
+ }
+
+ [Test()]
+ public void StackOverflowRepro()
+ {
+ var graph = RootGraph.FromDotFile($"{_testDir}/stackoverflow-repro.dot");
+ graph.ComputeLayout();
+ }
+
+ [Test()]
+ public void TestFromDotFile()
+ {
+ _ = RootGraph.FromDotFile($"{_testDir}/missing-label-repro.dot");
+ }
+
+ [Test()]
+ public void TestDotNewlines()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Node nodeA = root.GetOrAddNode("A");
+
+ nodeA.SafeSetAttribute("shape", "record", "");
+ nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
+
+ var dotString = root.ToDotString();
+ Assert.IsFalse(dotString.Contains("\r"));
+ }
+
+
+ [Test()]
+ public void TestDotNewlines2()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Node nodeA = root.GetOrAddNode("A");
+
+ nodeA.SafeSetAttribute("shape", "record", "");
+ nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
+
+ var xdotGraph = root.CreateLayout();
+ var xNodeA = xdotGraph.GetNode("A");
+ var ldraw = xNodeA.GetAttribute("_ldraw_");
+ Assert.IsFalse(ldraw.Contains("\n"));
+ Assert.IsFalse(ldraw.Contains("\r"));
+ Assert.IsFalse(ldraw.Contains("\\"));
+ }
+
+ [Test()]
+ public void TestDotNewlines3()
{
- private string _testDir;
-
- [SetUp]
- public void SetUp()
- {
- // Store the test directory.
- _testDir = TestContext.CurrentContext.TestDirectory;
- }
-
- ///
- /// This test used to fail: https://gitlab.com/graphviz/graphviz/-/issues/1894
- /// It still fails on github hosted VMs: https://gitlab.com/graphviz/graphviz/-/issues/1905
- ///
- [Test()]
- [TestCase("Times-Roman", 7, 0.01)]
- [TestCase("Times-Roman", 7, 0.5)]
- [Category("Flaky")]
- public void TestRecordShapeAlignment(string fontname, double fontsize, double margin)
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- // Margin between label and node boundary in inches
- Node.IntroduceAttribute(root, "margin", margin.ToString(CultureInfo.InvariantCulture));
- Node.IntroduceAttribute(root, "fontsize", fontsize.ToString(CultureInfo.InvariantCulture));
- Node.IntroduceAttribute(root, "fontname", fontname);
-
- Node nodeA = root.GetOrAddNode("A");
-
- nodeA.SafeSetAttribute("shape", "record", "");
- nodeA.SafeSetAttribute("label", "{20 VH|{1|2}}", "");
-
- //TestContext.Write(root.ToDotString());
- root.ComputeLayout();
- //TestContext.Write(root.ToDotString());
-
- var rects = nodeA.GetRecordRectangles().ToList();
- Assert.That(rects[0].Right, Is.EqualTo(rects[2].Right));
- }
-
- // This test only failed when running in isolation
- [Test()]
- public void MissingLabelRepro()
- {
- var graph = RootGraph.FromDotFile($"{_testDir}/missing-label-repro.dot");
- graph.ComputeLayout();
- graph.ToSvgFile($"{_testDir}/test.svg");
- string svgString = File.ReadAllText($"{_testDir}/test.svg");
- Assert.IsTrue(svgString.Contains(">OpenNode"));
- }
-
- [Test()]
- public void StackOverflowRepro()
- {
- var graph = RootGraph.FromDotFile($"{_testDir}/stackoverflow-repro.dot");
- graph.ComputeLayout();
- }
-
- [Test()]
- public void TestFromDotFile()
- {
- _ = RootGraph.FromDotFile($"{_testDir}/missing-label-repro.dot");
- }
-
- [Test()]
- public void TestDotNewlines()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- Node nodeA = root.GetOrAddNode("A");
-
- nodeA.SafeSetAttribute("shape", "record", "");
- nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
-
- var dotString = root.ToDotString();
- Assert.IsFalse(dotString.Contains("\r"));
- }
-
-
- [Test()]
- public void TestDotNewlines2()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- Node nodeA = root.GetOrAddNode("A");
-
- nodeA.SafeSetAttribute("shape", "record", "");
- nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
-
- var xdotGraph = root.CreateLayout();
- var xNodeA = xdotGraph.GetNode("A");
- var ldraw = xNodeA.GetAttribute("_ldraw_");
- Assert.IsFalse(ldraw.Contains("\n"));
- Assert.IsFalse(ldraw.Contains("\r"));
- Assert.IsFalse(ldraw.Contains("\\"));
- }
-
- [Test()]
- public void TestDotNewlines3()
- {
- var dotstrCrLf = @"
+ var dotstrCrLf = @"
digraph ""test graph 1"" {
graph[_draw_ = ""c 9 -#fffffe00 C 7 -#ffffff P 4 0 0 0 72.25 136.5 72.25 136.5 0 "",
bb = ""0,0,136.5,72.25"",
@@ -130,14 +125,13 @@ 54.45 0 6.75 1 -7 F 14 11 -Times-Roman c 7 -#000000 T 125.12 30.7 0 6.75 1 -8 F
shape = record,
width = 1.8958];
}";
- var graph = RootGraph.FromDotString(dotstrCrLf);
- var nodeA = graph.GetNode("A");
- var ldraw = nodeA.GetAttribute("_ldraw_");
-
- Assert.IsFalse(ldraw.Contains("\n"));
- Assert.IsFalse(ldraw.Contains("\r"));
- Assert.IsFalse(ldraw.Contains("\\"));
- }
+ var graph = RootGraph.FromDotString(dotstrCrLf);
+ var nodeA = graph.GetNode("A");
+ var ldraw = nodeA.GetAttribute("_ldraw_");
+ Assert.IsFalse(ldraw.Contains("\n"));
+ Assert.IsFalse(ldraw.Contains("\r"));
+ Assert.IsFalse(ldraw.Contains("\\"));
}
+
}
diff --git a/Rubjerg.Graphviz.Test/TestDotLayout.cs b/Rubjerg.Graphviz.Test/TestDotLayout.cs
index b8f7557..624043a 100644
--- a/Rubjerg.Graphviz.Test/TestDotLayout.cs
+++ b/Rubjerg.Graphviz.Test/TestDotLayout.cs
@@ -6,303 +6,302 @@
using NUnit.Framework;
using static Rubjerg.Graphviz.Test.Utils;
-namespace Rubjerg.Graphviz.Test
+namespace Rubjerg.Graphviz.Test;
+
+[TestFixture()]
+public class TestDotLayout
{
- [TestFixture()]
- public class TestDotLayout
+ private static void CreateSimpleTestGraph(out RootGraph root, out Node nodeA, out Edge edge)
{
- private static void CreateSimpleTestGraph(out RootGraph root, out Node nodeA, out Edge edge)
- {
- root = CreateUniqueTestGraph();
- root.SetAttribute("label", "g");
- nodeA = root.GetOrAddNode("A");
- nodeA.SetAttribute("shape", "record");
- nodeA.SetAttribute("label", "{a|b}");
- nodeA.SetAttribute("color", "red");
- Node nodeB = root.GetOrAddNode("B");
- edge = root.GetOrAddEdge(nodeA, nodeB, "");
- edge.SetAttribute("label", "e");
- edge.SetAttribute("headlabel", "h");
- edge.SetAttribute("taillabel", "t");
- edge.SetAttribute("dir", "both");
- edge.SetAttribute("arrowtail", "vee");
- edge.SetAttribute("arrowhead", "vee");
- }
+ root = CreateUniqueTestGraph();
+ root.SetAttribute("label", "g");
+ nodeA = root.GetOrAddNode("A");
+ nodeA.SetAttribute("shape", "record");
+ nodeA.SetAttribute("label", "{a|b}");
+ nodeA.SetAttribute("color", "red");
+ Node nodeB = root.GetOrAddNode("B");
+ edge = root.GetOrAddEdge(nodeA, nodeB, "");
+ edge.SetAttribute("label", "e");
+ edge.SetAttribute("headlabel", "h");
+ edge.SetAttribute("taillabel", "t");
+ edge.SetAttribute("dir", "both");
+ edge.SetAttribute("arrowtail", "vee");
+ edge.SetAttribute("arrowhead", "vee");
+ }
- [Test()]
- public void TestLayoutMethodsWithoutLayout()
- {
- CreateSimpleTestGraph(out RootGraph root, out Node nodeA, out Edge edge);
-
- Assert.AreEqual(root.GetBoundingBox(), default(RectangleF));
- Assert.AreEqual(root.GetColor(), Color.Black);
- Assert.AreEqual(root.GetDrawing().Count, 0);
- Assert.AreEqual(root.GetLabelDrawing().Count, 0);
-
- Assert.AreEqual(nodeA.GetPosition(), default(PointF));
- Assert.AreEqual(nodeA.GetBoundingBox(), default(RectangleF));
- Assert.AreEqual(nodeA.GetSize(), default(SizeF));
- Assert.AreEqual(nodeA.GetRecordRectangles().Count(), 0);
- Assert.AreEqual(nodeA.GetDrawing().Count, 0);
- Assert.AreEqual(nodeA.GetLabelDrawing().Count, 0);
-
- Assert.AreEqual(edge.GetFirstSpline(), null);
- Assert.AreEqual(edge.GetSplines().Count(), 0);
- Assert.AreEqual(edge.GetDrawing().Count, 0);
- Assert.AreEqual(edge.GetLabelDrawing().Count, 0);
- Assert.AreEqual(edge.GetHeadArrowDrawing().Count, 0);
- Assert.AreEqual(edge.GetTailArrowDrawing().Count, 0);
- Assert.AreEqual(edge.GetHeadLabelDrawing().Count, 0);
- Assert.AreEqual(edge.GetTailLabelDrawing().Count, 0);
-
- //root.ToSvgFile("xxx.svg");
- }
+ [Test()]
+ public void TestLayoutMethodsWithoutLayout()
+ {
+ CreateSimpleTestGraph(out RootGraph root, out Node nodeA, out Edge edge);
+
+ Assert.AreEqual(root.GetBoundingBox(), default(RectangleF));
+ Assert.AreEqual(root.GetColor(), Color.Black);
+ Assert.AreEqual(root.GetDrawing().Count, 0);
+ Assert.AreEqual(root.GetLabelDrawing().Count, 0);
+
+ Assert.AreEqual(nodeA.GetPosition(), default(PointF));
+ Assert.AreEqual(nodeA.GetBoundingBox(), default(RectangleF));
+ Assert.AreEqual(nodeA.GetSize(), default(SizeF));
+ Assert.AreEqual(nodeA.GetRecordRectangles().Count(), 0);
+ Assert.AreEqual(nodeA.GetDrawing().Count, 0);
+ Assert.AreEqual(nodeA.GetLabelDrawing().Count, 0);
+
+ Assert.AreEqual(edge.GetFirstSpline(), null);
+ Assert.AreEqual(edge.GetSplines().Count(), 0);
+ Assert.AreEqual(edge.GetDrawing().Count, 0);
+ Assert.AreEqual(edge.GetLabelDrawing().Count, 0);
+ Assert.AreEqual(edge.GetHeadArrowDrawing().Count, 0);
+ Assert.AreEqual(edge.GetTailArrowDrawing().Count, 0);
+ Assert.AreEqual(edge.GetHeadLabelDrawing().Count, 0);
+ Assert.AreEqual(edge.GetTailLabelDrawing().Count, 0);
+
+ //root.ToSvgFile("xxx.svg");
+ }
- [Test()]
- public void TestLayoutMethodsWithInProcessLayout()
- {
- CreateSimpleTestGraph(out RootGraph root, out Node nodeA, out Edge edge);
+ [Test()]
+ public void TestLayoutMethodsWithInProcessLayout()
+ {
+ CreateSimpleTestGraph(out RootGraph root, out Node nodeA, out Edge edge);
+
+ root.ComputeLayout();
+
+ Assert.AreEqual(root.GetColor(), Color.Black);
+ Assert.AreNotEqual(root.GetBoundingBox(), default(RectangleF));
+ Assert.AreNotEqual(root.GetDrawing().Count, 0);
+ Assert.AreNotEqual(root.GetLabelDrawing().Count, 0);
+
+ Assert.AreEqual(nodeA.GetColor(), Color.Red);
+ Assert.AreEqual(nodeA.GetRecordRectangles().Count(), 2);
+ Assert.AreNotEqual(nodeA.GetPosition(), default(PointF));
+ Assert.AreNotEqual(nodeA.GetBoundingBox(), default(RectangleF));
+ Assert.AreNotEqual(nodeA.GetSize(), default(SizeF));
+ Assert.AreNotEqual(nodeA.GetDrawing().Count, 0);
+ Assert.AreNotEqual(nodeA.GetLabelDrawing().Count, 0);
+
+ Assert.AreNotEqual(edge.GetFirstSpline(), null);
+ Assert.AreNotEqual(edge.GetSplines().Count(), 0);
+ Assert.AreNotEqual(edge.GetDrawing().Count, 0);
+ Assert.AreNotEqual(edge.GetLabelDrawing().Count, 0);
+ Assert.AreNotEqual(edge.GetHeadArrowDrawing().Count, 0);
+ Assert.AreNotEqual(edge.GetTailArrowDrawing().Count, 0);
+ Assert.AreNotEqual(edge.GetHeadLabelDrawing().Count, 0);
+ Assert.AreNotEqual(edge.GetTailLabelDrawing().Count, 0);
+ }
- root.ComputeLayout();
+ [Test()]
+ public void TestLayoutMethodsWithLayout()
+ {
+ CreateSimpleTestGraph(out RootGraph root, out Node nodeA, out Edge edge);
+
+ var xroot = root.CreateLayout();
+ var xnodeA = xroot.GetNode("A");
+ var xnodeB = xroot.GetNode("B");
+ Edge xedge = xroot.GetEdge(xnodeA, xnodeB, "");
+
+ Assert.AreEqual(xroot.GetColor(), Color.Black);
+ Assert.AreNotEqual(xroot.GetBoundingBox(), default(RectangleF));
+ Assert.AreNotEqual(xroot.GetDrawing().Count, 0);
+ Assert.AreNotEqual(xroot.GetLabelDrawing().Count, 0);
+
+ Assert.AreEqual(xnodeA.GetColor(), Color.Red);
+ Assert.AreEqual(xnodeA.GetRecordRectangles().Count(), 2);
+ Assert.AreNotEqual(xnodeA.GetPosition(), default(PointF));
+ Assert.AreNotEqual(xnodeA.GetBoundingBox(), default(RectangleF));
+ Assert.AreNotEqual(xnodeA.GetSize(), default(SizeF));
+ Assert.AreNotEqual(xnodeA.GetDrawing().Count, 0);
+ Assert.AreNotEqual(xnodeA.GetLabelDrawing().Count, 0);
+
+ Assert.AreNotEqual(xedge.GetFirstSpline(), null);
+ Assert.AreNotEqual(xedge.GetSplines().Count(), 0);
+ Assert.AreNotEqual(xedge.GetDrawing().Count, 0);
+ Assert.AreNotEqual(xedge.GetLabelDrawing().Count, 0);
+ Assert.AreNotEqual(xedge.GetHeadArrowDrawing().Count, 0);
+ Assert.AreNotEqual(xedge.GetTailArrowDrawing().Count, 0);
+ Assert.AreNotEqual(xedge.GetHeadLabelDrawing().Count, 0);
+ Assert.AreNotEqual(xedge.GetTailLabelDrawing().Count, 0);
+ }
- Assert.AreEqual(root.GetColor(), Color.Black);
- Assert.AreNotEqual(root.GetBoundingBox(), default(RectangleF));
- Assert.AreNotEqual(root.GetDrawing().Count, 0);
- Assert.AreNotEqual(root.GetLabelDrawing().Count, 0);
-
- Assert.AreEqual(nodeA.GetColor(), Color.Red);
- Assert.AreEqual(nodeA.GetRecordRectangles().Count(), 2);
- Assert.AreNotEqual(nodeA.GetPosition(), default(PointF));
- Assert.AreNotEqual(nodeA.GetBoundingBox(), default(RectangleF));
- Assert.AreNotEqual(nodeA.GetSize(), default(SizeF));
- Assert.AreNotEqual(nodeA.GetDrawing().Count, 0);
- Assert.AreNotEqual(nodeA.GetLabelDrawing().Count, 0);
-
- Assert.AreNotEqual(edge.GetFirstSpline(), null);
- Assert.AreNotEqual(edge.GetSplines().Count(), 0);
- Assert.AreNotEqual(edge.GetDrawing().Count, 0);
- Assert.AreNotEqual(edge.GetLabelDrawing().Count, 0);
- Assert.AreNotEqual(edge.GetHeadArrowDrawing().Count, 0);
- Assert.AreNotEqual(edge.GetTailArrowDrawing().Count, 0);
- Assert.AreNotEqual(edge.GetHeadLabelDrawing().Count, 0);
- Assert.AreNotEqual(edge.GetTailLabelDrawing().Count, 0);
- }
+ [Test()]
+ public void TestHtmlLabels()
+ {
+ RootGraph root = CreateUniqueTestGraph();
+ const string labelKey = "label";
+ Node.IntroduceAttribute(root, labelKey, "");
+ Graph.IntroduceAttribute(root, labelKey, "");
- [Test()]
- public void TestLayoutMethodsWithLayout()
- {
- CreateSimpleTestGraph(out RootGraph root, out Node nodeA, out Edge edge);
-
- var xroot = root.CreateLayout();
- var xnodeA = xroot.GetNode("A");
- var xnodeB = xroot.GetNode("B");
- Edge xedge = xroot.GetEdge(xnodeA, xnodeB, "");
-
- Assert.AreEqual(xroot.GetColor(), Color.Black);
- Assert.AreNotEqual(xroot.GetBoundingBox(), default(RectangleF));
- Assert.AreNotEqual(xroot.GetDrawing().Count, 0);
- Assert.AreNotEqual(xroot.GetLabelDrawing().Count, 0);
-
- Assert.AreEqual(xnodeA.GetColor(), Color.Red);
- Assert.AreEqual(xnodeA.GetRecordRectangles().Count(), 2);
- Assert.AreNotEqual(xnodeA.GetPosition(), default(PointF));
- Assert.AreNotEqual(xnodeA.GetBoundingBox(), default(RectangleF));
- Assert.AreNotEqual(xnodeA.GetSize(), default(SizeF));
- Assert.AreNotEqual(xnodeA.GetDrawing().Count, 0);
- Assert.AreNotEqual(xnodeA.GetLabelDrawing().Count, 0);
-
- Assert.AreNotEqual(xedge.GetFirstSpline(), null);
- Assert.AreNotEqual(xedge.GetSplines().Count(), 0);
- Assert.AreNotEqual(xedge.GetDrawing().Count, 0);
- Assert.AreNotEqual(xedge.GetLabelDrawing().Count, 0);
- Assert.AreNotEqual(xedge.GetHeadArrowDrawing().Count, 0);
- Assert.AreNotEqual(xedge.GetTailArrowDrawing().Count, 0);
- Assert.AreNotEqual(xedge.GetHeadLabelDrawing().Count, 0);
- Assert.AreNotEqual(xedge.GetTailLabelDrawing().Count, 0);
- }
+ Node n1 = root.GetOrAddNode("1");
+ Node n2 = root.GetOrAddNode("2");
+ root.SetAttributeHtml(labelKey, "");
- [Test()]
- public void TestHtmlLabels()
- {
- RootGraph root = CreateUniqueTestGraph();
- const string labelKey = "label";
- Node.IntroduceAttribute(root, labelKey, "");
- Graph.IntroduceAttribute(root, labelKey, "");
+ n1.SetAttribute(labelKey, "plain 1");
+ n2.SetAttributeHtml(labelKey, "");
- Node n1 = root.GetOrAddNode("1");
- Node n2 = root.GetOrAddNode("2");
- root.SetAttributeHtml(labelKey, "");
+ var result = root.ToDotString();
- n1.SetAttribute(labelKey, "plain 1");
- n2.SetAttributeHtml(labelKey, "");
+ Assert.That(result, Does.Contain("\"plain 1\""));
+ AssertContainsHtml(result, "");
+ AssertContainsHtml(result, "");
+ }
- var result = root.ToDotString();
+ [Test()]
+ public void TestHtmlLabelsDefault()
+ {
+ RootGraph root = CreateUniqueTestGraph();
+ const string labelKey = "label";
+ Node.IntroduceAttributeHtml(root, labelKey, "");
- Assert.That(result, Does.Contain("\"plain 1\""));
- AssertContainsHtml(result, "");
- AssertContainsHtml(result, "");
- }
+ Node n1 = root.GetOrAddNode("1");
+ Node n2 = root.GetOrAddNode("2");
+ Node n3 = root.GetOrAddNode("3");
- [Test()]
- public void TestHtmlLabelsDefault()
- {
- RootGraph root = CreateUniqueTestGraph();
- const string labelKey = "label";
- Node.IntroduceAttributeHtml(root, labelKey, "");
+ n1.SetAttribute(labelKey, "plain 1");
+ n2.SetAttributeHtml(labelKey, "");
- Node n1 = root.GetOrAddNode("1");
- Node n2 = root.GetOrAddNode("2");
- Node n3 = root.GetOrAddNode("3");
+ var result = root.ToDotString();
- n1.SetAttribute(labelKey, "plain 1");
- n2.SetAttributeHtml(labelKey, "");
+ Assert.That(result, Does.Contain("\"plain 1\""));
+ AssertContainsHtml(result, "");
+ AssertContainsHtml(result, "");
+ }
- var result = root.ToDotString();
+ private static void AssertContainsHtml(string result, string html)
+ {
+ // Html labels are not string quoted in dot file
+ Assert.That(result, Does.Not.Contain($"\"{html}\""));
+ Assert.That(result, Does.Not.Contain($"\"<{html}>\""));
+ // Htmls labels have additional angel bracket delimeters added
+ Assert.That(result, Does.Contain($"<{html}>"));
+ }
- Assert.That(result, Does.Contain("\"plain 1\""));
- AssertContainsHtml(result, "");
- AssertContainsHtml(result, "");
- }
+ [Test()]
+ public void TestRecordShapeOrder()
+ {
+ RootGraph root = CreateUniqueTestGraph();
+ Node nodeA = root.GetOrAddNode("A");
+
+ nodeA.SafeSetAttribute("shape", "record", "");
+ nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
+
+ root.ComputeLayout();
+
+ var rects = nodeA.GetRecordRectangles().ToList();
+
+ // Because Graphviz uses a lower-left originated coordinate system, we need to flip the y coordinates
+ Utils.AssertOrder(rects, r => (r.Left, -r.Top));
+ Assert.That(rects.Count, Is.EqualTo(9));
+ }
- private static void AssertContainsHtml(string result, string html)
+ [Test()]
+ public void TestEmptyRecordShapes()
+ {
+ RootGraph root = CreateUniqueTestGraph();
+ Node nodeA = root.GetOrAddNode("A");
+ nodeA.SafeSetAttribute("shape", "record", "");
+ nodeA.SafeSetAttribute("label", "||||", "");
+
+ root.ComputeLayout();
+
+ var rects = nodeA.GetRecordRectangles().ToList();
+ Assert.That(rects.Count, Is.EqualTo(5));
+ root.ToSvgFile(GetTestFilePath("out.svg"));
+ }
+
+ [Test()]
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestPortNameConversion(bool escape)
+ {
+ string port1 = ">|<";
+ string port2 = "B";
+ if (escape)
{
- // Html labels are not string quoted in dot file
- Assert.That(result, Does.Not.Contain($"\"{html}\""));
- Assert.That(result, Does.Not.Contain($"\"<{html}>\""));
- // Htmls labels have additional angel bracket delimeters added
- Assert.That(result, Does.Contain($"<{html}>"));
+ port1 = Edge.ConvertUidToPortName(port1);
+ port2 = Edge.ConvertUidToPortName(port2);
}
+ string label = $"{{<{port1}>1|<{port2}>2}}";
- [Test()]
- public void TestRecordShapeOrder()
{
RootGraph root = CreateUniqueTestGraph();
- Node nodeA = root.GetOrAddNode("A");
+ Node node = root.GetOrAddNode("N");
+ node.SafeSetAttribute("shape", "record", "");
+ node.SafeSetAttribute("label", label, "");
+ Edge edge = root.GetOrAddEdge(node, node, "");
+ edge.SafeSetAttribute("tailport", port1 + ":n", "");
+ edge.SafeSetAttribute("headport", port2 + ":s", "");
+ root.ToDotFile(GetTestFilePath("out.gv"));
+ }
+
+ {
+ var root = RootGraph.FromDotFile(GetTestFilePath("out.gv"));
- nodeA.SafeSetAttribute("shape", "record", "");
- nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
+ Node node = root.GetNode("N");
+ Assert.That(node.GetAttribute("label"), Is.EqualTo(label));
+ Edge edge = root.Edges().First();
+ Assert.That(edge.GetAttribute("tailport"), Is.EqualTo(port1 + ":n"));
+ Assert.That(edge.GetAttribute("headport"), Is.EqualTo(port2 + ":s"));
root.ComputeLayout();
+ root.ToSvgFile(GetTestFilePath("out.svg"));
+ root.ToDotFile(GetTestFilePath("out.dot"));
- var rects = nodeA.GetRecordRectangles().ToList();
+ var rects = node.GetRecordRectangles();
+ if (escape)
+ Assert.That(rects.Count, Is.EqualTo(2));
+ else
+ Assert.That(rects.Count, Is.EqualTo(3));
+ }
+ }
- // Because Graphviz uses a lower-left originated coordinate system, we need to flip the y coordinates
- Utils.AssertOrder(rects, r => (r.Left, -r.Top));
- Assert.That(rects.Count, Is.EqualTo(9));
+ [Test()]
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestLabelEscaping(bool escape)
+ {
+ string label1 = "|";
+ string label2 = @"\N\n\L";
+ string label3 = "3";
+ if (escape)
+ {
+ label1 = CGraphThing.EscapeLabel(label1);
+ label2 = CGraphThing.EscapeLabel(label2);
}
- [Test()]
- public void TestEmptyRecordShapes()
{
RootGraph root = CreateUniqueTestGraph();
- Node nodeA = root.GetOrAddNode("A");
- nodeA.SafeSetAttribute("shape", "record", "");
- nodeA.SafeSetAttribute("label", "||||", "");
-
- root.ComputeLayout();
-
- var rects = nodeA.GetRecordRectangles().ToList();
- Assert.That(rects.Count, Is.EqualTo(5));
- root.ToSvgFile(GetTestFilePath("out.svg"));
+ Node node1 = root.GetOrAddNode("1");
+ node1.SafeSetAttribute("shape", "record", "");
+ node1.SafeSetAttribute("label", label1, "");
+ Node node2 = root.GetOrAddNode("2");
+ node2.SafeSetAttribute("label", label2, "");
+ Node node3 = root.GetOrAddNode("3");
+ node3.SafeSetAttribute("label", label3, "");
+ root.ToDotFile(GetTestFilePath("out.gv"));
}
- [Test()]
- [TestCase(true)]
- [TestCase(false)]
- public void TestPortNameConversion(bool escape)
{
- string port1 = ">|<";
- string port2 = "B";
- if (escape)
- {
- port1 = Edge.ConvertUidToPortName(port1);
- port2 = Edge.ConvertUidToPortName(port2);
- }
- string label = $"{{<{port1}>1|<{port2}>2}}";
+ var root = RootGraph.FromDotFile(GetTestFilePath("out.gv"));
- {
- RootGraph root = CreateUniqueTestGraph();
- Node node = root.GetOrAddNode("N");
- node.SafeSetAttribute("shape", "record", "");
- node.SafeSetAttribute("label", label, "");
- Edge edge = root.GetOrAddEdge(node, node, "");
- edge.SafeSetAttribute("tailport", port1 + ":n", "");
- edge.SafeSetAttribute("headport", port2 + ":s", "");
- root.ToDotFile(GetTestFilePath("out.gv"));
- }
+ Node node1 = root.GetNode("1");
+ Assert.That(node1.GetAttribute("label"), Is.EqualTo(label1));
+ Node node2 = root.GetNode("2");
+ Assert.That(node2.GetAttribute("label"), Is.EqualTo(label2));
+ Node node3 = root.GetNode("3");
+ Assert.That(node3.GetAttribute("label"), Is.EqualTo(label3));
- {
- var root = RootGraph.FromDotFile(GetTestFilePath("out.gv"));
-
- Node node = root.GetNode("N");
- Assert.That(node.GetAttribute("label"), Is.EqualTo(label));
- Edge edge = root.Edges().First();
- Assert.That(edge.GetAttribute("tailport"), Is.EqualTo(port1 + ":n"));
- Assert.That(edge.GetAttribute("headport"), Is.EqualTo(port2 + ":s"));
-
- root.ComputeLayout();
- root.ToSvgFile(GetTestFilePath("out.svg"));
- root.ToDotFile(GetTestFilePath("out.dot"));
-
- var rects = node.GetRecordRectangles();
- if (escape)
- Assert.That(rects.Count, Is.EqualTo(2));
- else
- Assert.That(rects.Count, Is.EqualTo(3));
- }
- }
+ root.ComputeLayout();
+ root.ToSvgFile(GetTestFilePath("out.svg"));
+ root.ToDotFile(GetTestFilePath("out.dot"));
- [Test()]
- [TestCase(true)]
- [TestCase(false)]
- public void TestLabelEscaping(bool escape)
- {
- string label1 = "|";
- string label2 = @"\N\n\L";
- string label3 = "3";
+ var rects = node1.GetRecordRectangles();
if (escape)
{
- label1 = CGraphThing.EscapeLabel(label1);
- label2 = CGraphThing.EscapeLabel(label2);
+ Assert.That(rects.Count, Is.EqualTo(1));
+ Assert.That(node2.GetBoundingBox().Height, Is.EqualTo(node3.GetBoundingBox().Height));
}
-
- {
- RootGraph root = CreateUniqueTestGraph();
- Node node1 = root.GetOrAddNode("1");
- node1.SafeSetAttribute("shape", "record", "");
- node1.SafeSetAttribute("label", label1, "");
- Node node2 = root.GetOrAddNode("2");
- node2.SafeSetAttribute("label", label2, "");
- Node node3 = root.GetOrAddNode("3");
- node3.SafeSetAttribute("label", label3, "");
- root.ToDotFile(GetTestFilePath("out.gv"));
- }
-
+ else
{
- var root = RootGraph.FromDotFile(GetTestFilePath("out.gv"));
-
- Node node1 = root.GetNode("1");
- Assert.That(node1.GetAttribute("label"), Is.EqualTo(label1));
- Node node2 = root.GetNode("2");
- Assert.That(node2.GetAttribute("label"), Is.EqualTo(label2));
- Node node3 = root.GetNode("3");
- Assert.That(node3.GetAttribute("label"), Is.EqualTo(label3));
-
- root.ComputeLayout();
- root.ToSvgFile(GetTestFilePath("out.svg"));
- root.ToDotFile(GetTestFilePath("out.dot"));
-
- var rects = node1.GetRecordRectangles();
- if (escape)
- {
- Assert.That(rects.Count, Is.EqualTo(1));
- Assert.That(node2.GetBoundingBox().Height, Is.EqualTo(node3.GetBoundingBox().Height));
- }
- else
- {
- Assert.That(rects.Count, Is.EqualTo(2));
- Assert.That(node2.GetBoundingBox().Height, Is.Not.EqualTo(node3.GetBoundingBox().Height));
- }
+ Assert.That(rects.Count, Is.EqualTo(2));
+ Assert.That(node2.GetBoundingBox().Height, Is.Not.EqualTo(node3.GetBoundingBox().Height));
}
}
}
diff --git a/Rubjerg.Graphviz.Test/TestInterop.cs b/Rubjerg.Graphviz.Test/TestInterop.cs
index b92439f..a2ebdac 100644
--- a/Rubjerg.Graphviz.Test/TestInterop.cs
+++ b/Rubjerg.Graphviz.Test/TestInterop.cs
@@ -1,37 +1,36 @@
using NUnit.Framework;
using static Rubjerg.Graphviz.ForeignFunctionInterface;
-namespace Rubjerg.Graphviz.Test
+namespace Rubjerg.Graphviz.Test;
+
+[TestFixture()]
+public class TestInterop
{
- [TestFixture()]
- public class TestInterop
+ [Test()]
+ public void TestMarshaling()
{
- [Test()]
- public void TestMarshaling()
- {
- Assert.True(echobool(true));
- Assert.False(echobool(false));
- Assert.True(return_true());
- Assert.False(return_false());
+ Assert.True(echobool(true));
+ Assert.False(echobool(false));
+ Assert.True(return_true());
+ Assert.False(return_false());
- Assert.AreEqual(0, echoint(0));
- Assert.AreEqual(1, echoint(1));
- Assert.AreEqual(-1, echoint(-1));
- Assert.AreEqual(1, return1());
- Assert.AreEqual(-1, return_1());
+ Assert.AreEqual(0, echoint(0));
+ Assert.AreEqual(1, echoint(1));
+ Assert.AreEqual(-1, echoint(-1));
+ Assert.AreEqual(1, return1());
+ Assert.AreEqual(-1, return_1());
- Assert.AreEqual(TestEnum.Val1, return_enum1());
- Assert.AreEqual(TestEnum.Val2, return_enum2());
- Assert.AreEqual(TestEnum.Val5, return_enum5());
- Assert.AreEqual(TestEnum.Val1, echo_enum(TestEnum.Val1));
- Assert.AreEqual(TestEnum.Val2, echo_enum(TestEnum.Val2));
- Assert.AreEqual(TestEnum.Val5, echo_enum(TestEnum.Val5));
+ Assert.AreEqual(TestEnum.Val1, return_enum1());
+ Assert.AreEqual(TestEnum.Val2, return_enum2());
+ Assert.AreEqual(TestEnum.Val5, return_enum5());
+ Assert.AreEqual(TestEnum.Val1, echo_enum(TestEnum.Val1));
+ Assert.AreEqual(TestEnum.Val2, echo_enum(TestEnum.Val2));
+ Assert.AreEqual(TestEnum.Val5, echo_enum(TestEnum.Val5));
- Assert.AreEqual("", ReturnEmptyString());
- Assert.AreEqual("hello", ReturnHello());
- Assert.AreEqual("1", EchoString("1"));
- Assert.AreEqual("", EchoString(""));
- Assert.AreEqual("hello", EchoString("hello"));
- }
+ Assert.AreEqual("", ReturnEmptyString());
+ Assert.AreEqual("hello", ReturnHello());
+ Assert.AreEqual("1", EchoString("1"));
+ Assert.AreEqual("", EchoString(""));
+ Assert.AreEqual("hello", EchoString("hello"));
}
}
diff --git a/Rubjerg.Graphviz.Test/TestXDotLayout.cs b/Rubjerg.Graphviz.Test/TestXDotLayout.cs
index dfc08b7..3acd2fd 100644
--- a/Rubjerg.Graphviz.Test/TestXDotLayout.cs
+++ b/Rubjerg.Graphviz.Test/TestXDotLayout.cs
@@ -2,16 +2,16 @@
using System.Linq;
using NUnit.Framework;
-namespace Rubjerg.Graphviz.Test
+namespace Rubjerg.Graphviz.Test;
+
+[TestFixture()]
+public class TestXDotLayout
{
- [TestFixture()]
- public class TestXDotLayout
+ [Test()]
+ public void TestXDotTranslateFromString()
{
- [Test()]
- public void TestXDotTranslateFromString()
- {
- // Test case containing all possible operations
- var testcase = @"
+ // Test case containing all possible operations
+ var testcase = @"
E 10 10 5 3
e 20 10 5 3
P 4 30 10 30 15 35 15 35 10
@@ -27,87 +27,86 @@ F 12 5 -Arial
S 6 -dashed
I 90 10 5 5 8 -image.png
";
- var result = XDotParser.ParseXDot(testcase);
- Assert.AreEqual(14, result.Count);
-
- }
-
- [Test()]
- public void TestXDotRecordNode()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- Node nodeA = root.GetOrAddNode("A");
-
- nodeA.SafeSetAttribute("shape", "record", "");
- // FIXNOW: document that newlines are not supported in record labels
- nodeA.SafeSetAttribute("label", "1|{2\n3}", "\\N");
-
- var xdotGraph = root.CreateLayout();
- var xNodeA = xdotGraph.GetNode("A");
- var ldraw = xNodeA.GetLabelDrawing();
- Assert.IsTrue(ldraw.OfType().Any(t => t.Value.Text == "23"));
- // Even though the attribute still contains the newline
- Assert.IsTrue(xNodeA.GetAttribute("label") == "1|{2\n3}");
- Assert.AreEqual(6, ldraw.Count);
- }
-
- [Test()]
- public void TestXDotNewLines()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- SubGraph cluster = root.GetOrAddSubgraph("cluster_1");
- cluster.SafeSetAttribute("label", "1\n2", "");
- Node nodeA = cluster.GetOrAddNode("A");
- nodeA.SafeSetAttribute("label", "a\nb", "");
-
- var xdotGraph = root.CreateLayout();
-
- // New lines result in separate text operations
- var xCluster = xdotGraph.GetSubgraph("cluster_1");
- var ldraw = xCluster.GetLabelDrawing();
- Assert.AreEqual(6, ldraw.Count);
-
- var xNodeA = xdotGraph.GetNode("A");
- ldraw = xNodeA.GetLabelDrawing();
- Assert.AreEqual(6, ldraw.Count);
- }
-
- [Test()]
- public void TestRecordShapeOrder()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- Node nodeA = root.GetOrAddNode("A");
-
- nodeA.SafeSetAttribute("shape", "record", "");
- nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
-
-
- var xdotGraph = root.CreateLayout();
-
- var xNodeA = xdotGraph.GetNode("A");
- var rects = xNodeA.GetRecordRectangles().ToList();
-
- // Because Graphviz uses a lower-left originated coordinate system, we need to flip the y coordinates
- Utils.AssertOrder(rects, r => (r.Left, -r.Top));
- Assert.That(rects.Count, Is.EqualTo(9));
-
- // Test xdot translation
- var xdotDraw = xdotGraph.GetDrawing();
- }
-
- [Test()]
- public void TestEmptyRecordShapes()
- {
- RootGraph root = Utils.CreateUniqueTestGraph();
- Node nodeA = root.GetOrAddNode("A");
- nodeA.SafeSetAttribute("shape", "record", "");
- nodeA.SafeSetAttribute("label", "||||", "");
-
- var xdotGraph = root.CreateLayout();
-
- var xNodeA = xdotGraph.GetNode("A");
- var rects = xNodeA.GetRecordRectangles().ToList();
- Assert.That(rects.Count, Is.EqualTo(5));
- }
+ var result = XDotParser.ParseXDot(testcase);
+ Assert.AreEqual(14, result.Count);
+
+ }
+
+ [Test()]
+ public void TestXDotRecordNode()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Node nodeA = root.GetOrAddNode("A");
+
+ nodeA.SafeSetAttribute("shape", "record", "");
+ // FIXNOW: document that newlines are not supported in record labels
+ nodeA.SafeSetAttribute("label", "1|{2\n3}", "\\N");
+
+ var xdotGraph = root.CreateLayout();
+ var xNodeA = xdotGraph.GetNode("A");
+ var ldraw = xNodeA.GetLabelDrawing();
+ Assert.IsTrue(ldraw.OfType().Any(t => t.Value.Text == "23"));
+ // Even though the attribute still contains the newline
+ Assert.IsTrue(xNodeA.GetAttribute("label") == "1|{2\n3}");
+ Assert.AreEqual(6, ldraw.Count);
+ }
+
+ [Test()]
+ public void TestXDotNewLines()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ SubGraph cluster = root.GetOrAddSubgraph("cluster_1");
+ cluster.SafeSetAttribute("label", "1\n2", "");
+ Node nodeA = cluster.GetOrAddNode("A");
+ nodeA.SafeSetAttribute("label", "a\nb", "");
+
+ var xdotGraph = root.CreateLayout();
+
+ // New lines result in separate text operations
+ var xCluster = xdotGraph.GetSubgraph("cluster_1");
+ var ldraw = xCluster.GetLabelDrawing();
+ Assert.AreEqual(6, ldraw.Count);
+
+ var xNodeA = xdotGraph.GetNode("A");
+ ldraw = xNodeA.GetLabelDrawing();
+ Assert.AreEqual(6, ldraw.Count);
+ }
+
+ [Test()]
+ public void TestRecordShapeOrder()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Node nodeA = root.GetOrAddNode("A");
+
+ nodeA.SafeSetAttribute("shape", "record", "");
+ nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
+
+
+ var xdotGraph = root.CreateLayout();
+
+ var xNodeA = xdotGraph.GetNode("A");
+ var rects = xNodeA.GetRecordRectangles().ToList();
+
+ // Because Graphviz uses a lower-left originated coordinate system, we need to flip the y coordinates
+ Utils.AssertOrder(rects, r => (r.Left, -r.Top));
+ Assert.That(rects.Count, Is.EqualTo(9));
+
+ // Test xdot translation
+ var xdotDraw = xdotGraph.GetDrawing();
+ }
+
+ [Test()]
+ public void TestEmptyRecordShapes()
+ {
+ RootGraph root = Utils.CreateUniqueTestGraph();
+ Node nodeA = root.GetOrAddNode("A");
+ nodeA.SafeSetAttribute("shape", "record", "");
+ nodeA.SafeSetAttribute("label", "||||", "");
+
+ var xdotGraph = root.CreateLayout();
+
+ var xNodeA = xdotGraph.GetNode("A");
+ var rects = xNodeA.GetRecordRectangles().ToList();
+ Assert.That(rects.Count, Is.EqualTo(5));
}
}
diff --git a/Rubjerg.Graphviz.Test/Tutorial.cs b/Rubjerg.Graphviz.Test/Tutorial.cs
index 91f879f..6a24f5d 100644
--- a/Rubjerg.Graphviz.Test/Tutorial.cs
+++ b/Rubjerg.Graphviz.Test/Tutorial.cs
@@ -2,219 +2,218 @@
using System.Drawing;
using System.Linq;
-namespace Rubjerg.Graphviz.Test
+namespace Rubjerg.Graphviz.Test;
+
+[TestFixture()]
+public class Tutorial
{
- [TestFixture()]
- public class Tutorial
+ public const string PointPattern = @"{X=[\d.]+, Y=[\d.]+}";
+ public const string RectPattern = @"{X=[\d.]+,Y=[\d.]+,Width=[\d.]+,Height=[\d.]+}";
+ public const string SplinePattern =
+ @"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";
+
+ [Test, Order(1)]
+ public void GraphConstruction()
{
- public const string PointPattern = @"{X=[\d.]+, Y=[\d.]+}";
- public const string RectPattern = @"{X=[\d.]+,Y=[\d.]+,Width=[\d.]+,Height=[\d.]+}";
- public const string SplinePattern =
- @"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";
+ // You can programmatically construct graphs as follows
+ RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Some Unique Identifier");
+ // The graph name is optional, and can be omitted. The name is not interpreted by Graphviz,
+ // except it is recorded and preserved when the graph is written as a file.
+
+ // The node names are unique identifiers within a graph in Graphviz
+ Node nodeA = root.GetOrAddNode("A");
+ Node nodeB = root.GetOrAddNode("B");
+ Node nodeC = root.GetOrAddNode("C");
+
+ // The edge name is only unique between two nodes
+ Edge edgeAB = root.GetOrAddEdge(nodeA, nodeB, "Some edge name");
+ Edge edgeBC = root.GetOrAddEdge(nodeB, nodeC, "Some edge name");
+ Edge anotherEdgeBC = root.GetOrAddEdge(nodeB, nodeC, "Another edge name");
+
+ // An edge name is optional and omitting it will result in a new nameless edge.
+ // There can be multiple nameless edges between any two nodes.
+ Edge edgeAB1 = root.GetOrAddEdge(nodeA, nodeB);
+ Edge edgeAB2 = root.GetOrAddEdge(nodeA, nodeB);
+ Assert.AreNotEqual(edgeAB1, edgeAB2);
+
+ // We can attach attributes to nodes, edges and graphs to store information and instruct
+ // Graphviz by specifying layout parameters. At the moment we only support string
+ // attributes. Cgraph assumes that all objects of a given kind (graphs/subgraphs, nodes,
+ // or edges) have the same attributes. An attribute has to be introduced with a default value
+ // first for a certain kind, before we can use it.
+ Node.IntroduceAttribute(root, "my attribute", "defaultvalue");
+ nodeA.SetAttribute("my attribute", "othervalue");
+
+ // Attributes are introduced per kind (Node, Edge, Graph) per root graph.
+ // So to be able to use "my attribute" on edges, we first have to introduce it as well.
+ Edge.IntroduceAttribute(root, "my attribute", "defaultvalue");
+ edgeAB.SetAttribute("my attribute", "othervalue");
+
+ // To introduce and set an attribute at the same time, there are convenience wrappers
+ edgeBC.SafeSetAttribute("arrowsize", "2.0", "1.0");
+ // If we set an unintroduced attribute, the attribute will be introduced with an empty default value.
+ edgeBC.SetAttribute("new attr", "value");
+
+ // Some attributes - like "label" - accept HTML strings as value
+ // To tell Graphviz that a string should be interpreted as HTML use the designated methods
+ Node.IntroduceAttribute(root, "label", "defaultlabel");
+ nodeB.SetAttributeHtml("label", "Some HTML string");
+
+ // We can simply export this graph to a text file in dot format
+ root.ToDotFile(TestContext.CurrentContext.TestDirectory + "/out.dot");
+
+ // A word of advice, Graphviz doesn't play very well with empty strings.
+ // Try to avoid them when possible. (https://gitlab.com/graphviz/graphviz/-/issues/1887)
+ }
- [Test, Order(1)]
- public void GraphConstruction()
+ [Test, Order(2)]
+ public void Layouting()
+ {
+ // If we have a given dot file (in this case the one we generated above), we can also read it back in
+ RootGraph root = RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory + "/out.dot");
+
+ // We can ask Graphviz to compute a layout and render it to svg
+ root.ToSvgFile(TestContext.CurrentContext.TestDirectory + "/dot_out.svg");
+
+ // We can use layout engines other than dot by explicitly passing the engine we want
+ root.ToSvgFile(TestContext.CurrentContext.TestDirectory + "/neato_out.svg", LayoutEngines.Neato);
+
+ // Or we can ask Graphviz to compute the layout and programatically read out the layout attributes
+ // This will create a copy of our original graph with layout information attached to it in the form
+ // of attributes.
+ RootGraph layout = root.CreateLayout();
+
+ // There are convenience methods available that parse these attributes for us and give
+ // back the layout information in an accessible form.
+ Node nodeA = layout.GetNode("A");
+ PointF position = nodeA.GetPosition();
+ Utils.AssertPattern(PointPattern, position.ToString());
+
+ RectangleF nodeboundingbox = nodeA.GetBoundingBox();
+ Utils.AssertPattern(RectPattern, nodeboundingbox.ToString());
+
+ // Or splines between nodes
+ Node nodeB = layout.GetNode("B");
+ Edge edge = layout.GetEdge(nodeA, nodeB, "Some edge name");
+ PointF[] spline = edge.GetFirstSpline();
+ string splineString = string.Join(", ", spline.Select(p => p.ToString()));
+ Utils.AssertPattern(SplinePattern, splineString);
+
+ // If we require detailed drawing information for any object, we can retrieve the so called "xdot"
+ // operations. See https://graphviz.org/docs/outputs/canon/#xdot for a specification.
+ var activeColor = Color.Black;
+ foreach (var op in nodeA.GetDrawing())
{
- // You can programmatically construct graphs as follows
- RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Some Unique Identifier");
- // The graph name is optional, and can be omitted. The name is not interpreted by Graphviz,
- // except it is recorded and preserved when the graph is written as a file.
-
- // The node names are unique identifiers within a graph in Graphviz
- Node nodeA = root.GetOrAddNode("A");
- Node nodeB = root.GetOrAddNode("B");
- Node nodeC = root.GetOrAddNode("C");
-
- // The edge name is only unique between two nodes
- Edge edgeAB = root.GetOrAddEdge(nodeA, nodeB, "Some edge name");
- Edge edgeBC = root.GetOrAddEdge(nodeB, nodeC, "Some edge name");
- Edge anotherEdgeBC = root.GetOrAddEdge(nodeB, nodeC, "Another edge name");
-
- // An edge name is optional and omitting it will result in a new nameless edge.
- // There can be multiple nameless edges between any two nodes.
- Edge edgeAB1 = root.GetOrAddEdge(nodeA, nodeB);
- Edge edgeAB2 = root.GetOrAddEdge(nodeA, nodeB);
- Assert.AreNotEqual(edgeAB1, edgeAB2);
-
- // We can attach attributes to nodes, edges and graphs to store information and instruct
- // Graphviz by specifying layout parameters. At the moment we only support string
- // attributes. Cgraph assumes that all objects of a given kind (graphs/subgraphs, nodes,
- // or edges) have the same attributes. An attribute has to be introduced with a default value
- // first for a certain kind, before we can use it.
- Node.IntroduceAttribute(root, "my attribute", "defaultvalue");
- nodeA.SetAttribute("my attribute", "othervalue");
-
- // Attributes are introduced per kind (Node, Edge, Graph) per root graph.
- // So to be able to use "my attribute" on edges, we first have to introduce it as well.
- Edge.IntroduceAttribute(root, "my attribute", "defaultvalue");
- edgeAB.SetAttribute("my attribute", "othervalue");
-
- // To introduce and set an attribute at the same time, there are convenience wrappers
- edgeBC.SafeSetAttribute("arrowsize", "2.0", "1.0");
- // If we set an unintroduced attribute, the attribute will be introduced with an empty default value.
- edgeBC.SetAttribute("new attr", "value");
-
- // Some attributes - like "label" - accept HTML strings as value
- // To tell Graphviz that a string should be interpreted as HTML use the designated methods
- Node.IntroduceAttribute(root, "label", "defaultlabel");
- nodeB.SetAttributeHtml("label", "Some HTML string");
-
- // We can simply export this graph to a text file in dot format
- root.ToDotFile(TestContext.CurrentContext.TestDirectory + "/out.dot");
-
- // A word of advice, Graphviz doesn't play very well with empty strings.
- // Try to avoid them when possible. (https://gitlab.com/graphviz/graphviz/-/issues/1887)
+ if (op is XDotOp.FillColor { Value: string htmlColor })
+ {
+ activeColor = ColorTranslator.FromHtml(htmlColor);
+ }
+ else if (op is XDotOp.FilledEllipse { Value: var filledEllipse })
+ {
+ var boundingBox = filledEllipse.ToRectangleF();
+ Utils.AssertPattern(RectPattern, boundingBox.ToString());
+ }
+ // Handle any xdot operation you require
}
- [Test, Order(2)]
- public void Layouting()
+ var activeFont = XDotFont.Default;
+ foreach (var op in nodeA.GetDrawing())
{
- // If we have a given dot file (in this case the one we generated above), we can also read it back in
- RootGraph root = RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory + "/out.dot");
-
- // We can ask Graphviz to compute a layout and render it to svg
- root.ToSvgFile(TestContext.CurrentContext.TestDirectory + "/dot_out.svg");
-
- // We can use layout engines other than dot by explicitly passing the engine we want
- root.ToSvgFile(TestContext.CurrentContext.TestDirectory + "/neato_out.svg", LayoutEngines.Neato);
-
- // Or we can ask Graphviz to compute the layout and programatically read out the layout attributes
- // This will create a copy of our original graph with layout information attached to it in the form
- // of attributes.
- RootGraph layout = root.CreateLayout();
-
- // There are convenience methods available that parse these attributes for us and give
- // back the layout information in an accessible form.
- Node nodeA = layout.GetNode("A");
- PointF position = nodeA.GetPosition();
- Utils.AssertPattern(PointPattern, position.ToString());
-
- RectangleF nodeboundingbox = nodeA.GetBoundingBox();
- Utils.AssertPattern(RectPattern, nodeboundingbox.ToString());
-
- // Or splines between nodes
- Node nodeB = layout.GetNode("B");
- Edge edge = layout.GetEdge(nodeA, nodeB, "Some edge name");
- PointF[] spline = edge.GetFirstSpline();
- string splineString = string.Join(", ", spline.Select(p => p.ToString()));
- Utils.AssertPattern(SplinePattern, splineString);
-
- // If we require detailed drawing information for any object, we can retrieve the so called "xdot"
- // operations. See https://graphviz.org/docs/outputs/canon/#xdot for a specification.
- var activeColor = Color.Black;
- foreach (var op in nodeA.GetDrawing())
+ if (op is XDotOp.Font { Value: var font })
{
- if (op is XDotOp.FillColor { Value: string htmlColor })
- {
- activeColor = ColorTranslator.FromHtml(htmlColor);
- }
- else if (op is XDotOp.FilledEllipse { Value: var filledEllipse })
- {
- var boundingBox = filledEllipse.ToRectangleF();
- Utils.AssertPattern(RectPattern, boundingBox.ToString());
- }
- // Handle any xdot operation you require
+ activeFont = font;
+ Utils.AssertPattern(@"Times-Roman", font.Name);
}
-
- var activeFont = XDotFont.Default;
- foreach (var op in nodeA.GetDrawing())
+ else if (op is XDotOp.Text { Value: var text })
{
- if (op is XDotOp.Font { Value: var font })
- {
- activeFont = font;
- Utils.AssertPattern(@"Times-Roman", font.Name);
- }
- else if (op is XDotOp.Text { Value: var text })
- {
- var anchor = text.Anchor();
- Utils.AssertPattern(PointPattern, anchor.ToString());
- var boundingBox = text.TextBoundingBox(activeFont);
- Utils.AssertPattern(RectPattern, boundingBox.ToString());
- Assert.AreEqual(text.Text, "A");
- }
- // Handle any xdot operation you require
+ var anchor = text.Anchor();
+ Utils.AssertPattern(PointPattern, anchor.ToString());
+ var boundingBox = text.TextBoundingBox(activeFont);
+ Utils.AssertPattern(RectPattern, boundingBox.ToString());
+ Assert.AreEqual(text.Text, "A");
}
-
- // These are just simple examples to showcase the structure of xdot operations.
- // In reality the information can be much richer and more complex.
+ // Handle any xdot operation you require
}
- [Test, Order(3)]
- public void Clusters()
- {
- RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with clusters");
- Node nodeA = root.GetOrAddNode("A");
- Node nodeB = root.GetOrAddNode("B");
- Node nodeC = root.GetOrAddNode("C");
- Node nodeD = root.GetOrAddNode("D");
-
- // When a subgraph name is prefixed with cluster,
- // the dot layout engine will render it as a box around the containing nodes.
- SubGraph cluster1 = root.GetOrAddSubgraph("cluster_1");
- cluster1.AddExisting(nodeB);
- cluster1.AddExisting(nodeC);
- SubGraph cluster2 = root.GetOrAddSubgraph("cluster_2");
- cluster2.AddExisting(nodeD);
-
- // COMPOUND EDGES
- // Graphviz does not really support edges from and to clusters. However, by adding an
- // invisible dummynode and setting the ltail or lhead attributes of an edge this
- // behavior can be faked. Graphviz will then draw an edge to the dummy node but clip it
- // at the border of the cluster. We provide convenience methods for this.
- // To enable this feature, Graphviz requires us to set the "compound" attribute to "true".
- Graph.IntroduceAttribute(root, "compound", "true"); // Allow lhead/ltail
- // The boolean indicates whether the dummy node should take up any space. When you pass
- // false and you have a lot of edges, the edges may start to overlap a lot.
- _ = root.GetOrAddEdge(nodeA, cluster1, false, "edge to a cluster");
- _ = root.GetOrAddEdge(cluster1, nodeD, false, "edge from a cluster");
- _ = root.GetOrAddEdge(cluster1, cluster1, false, "edge between clusters");
-
- var layout = root.CreateLayout();
-
- SubGraph cluster = layout.GetSubgraph("cluster_1");
- RectangleF clusterbox = cluster.GetBoundingBox();
- RectangleF rootgraphbox = layout.GetBoundingBox();
- Utils.AssertPattern(RectPattern, clusterbox.ToString());
- Utils.AssertPattern(RectPattern, rootgraphbox.ToString());
- }
+ // These are just simple examples to showcase the structure of xdot operations.
+ // In reality the information can be much richer and more complex.
+ }
- [Test, Order(4)]
- public void Records()
- {
- RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with records");
- Node nodeA = root.GetOrAddNode("A");
- nodeA.SafeSetAttribute("shape", "record", "");
- nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
+ [Test, Order(3)]
+ public void Clusters()
+ {
+ RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with clusters");
+ Node nodeA = root.GetOrAddNode("A");
+ Node nodeB = root.GetOrAddNode("B");
+ Node nodeC = root.GetOrAddNode("C");
+ Node nodeD = root.GetOrAddNode("D");
+
+ // When a subgraph name is prefixed with cluster,
+ // the dot layout engine will render it as a box around the containing nodes.
+ SubGraph cluster1 = root.GetOrAddSubgraph("cluster_1");
+ cluster1.AddExisting(nodeB);
+ cluster1.AddExisting(nodeC);
+ SubGraph cluster2 = root.GetOrAddSubgraph("cluster_2");
+ cluster2.AddExisting(nodeD);
+
+ // COMPOUND EDGES
+ // Graphviz does not really support edges from and to clusters. However, by adding an
+ // invisible dummynode and setting the ltail or lhead attributes of an edge this
+ // behavior can be faked. Graphviz will then draw an edge to the dummy node but clip it
+ // at the border of the cluster. We provide convenience methods for this.
+ // To enable this feature, Graphviz requires us to set the "compound" attribute to "true".
+ Graph.IntroduceAttribute(root, "compound", "true"); // Allow lhead/ltail
+ // The boolean indicates whether the dummy node should take up any space. When you pass
+ // false and you have a lot of edges, the edges may start to overlap a lot.
+ _ = root.GetOrAddEdge(nodeA, cluster1, false, "edge to a cluster");
+ _ = root.GetOrAddEdge(cluster1, nodeD, false, "edge from a cluster");
+ _ = root.GetOrAddEdge(cluster1, cluster1, false, "edge between clusters");
+
+ var layout = root.CreateLayout();
+
+ SubGraph cluster = layout.GetSubgraph("cluster_1");
+ RectangleF clusterbox = cluster.GetBoundingBox();
+ RectangleF rootgraphbox = layout.GetBoundingBox();
+ Utils.AssertPattern(RectPattern, clusterbox.ToString());
+ Utils.AssertPattern(RectPattern, rootgraphbox.ToString());
+ }
- var layout = root.CreateLayout();
+ [Test, Order(4)]
+ public void Records()
+ {
+ RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with records");
+ Node nodeA = root.GetOrAddNode("A");
+ nodeA.SafeSetAttribute("shape", "record", "");
+ nodeA.SafeSetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}", "\\N");
- // The order of the list matches the order in which the labels occur in the label string above.
- var rects = layout.GetNode("A").GetRecordRectangles().ToList();
- Assert.AreEqual(9, rects.Count);
- }
+ var layout = root.CreateLayout();
- [Test, Order(5)]
- public void StringEscaping()
- {
- RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with escaped strings");
- Node.IntroduceAttribute(root, "label", "\\N");
- Node nodeA = root.GetOrAddNode("A");
-
- // Several characters and character sequences can have special meanings in labels, like \N.
- // When you want to have a literal string in a label, we provide a convenience function for you to do just that.
- nodeA.SetAttribute("label", CGraphThing.EscapeLabel("Some string literal \\N \\n |}>"));
-
- // When defining portnames, some characters, like ':' and '|', are not allowed and they can't be escaped either.
- // This can be troubling if you have an externally defined ID for such a port.
- // We provide a function that maps strings to valid portnames.
- var somePortId = "port id with :| special characters";
- var validPortName = Edge.ConvertUidToPortName(somePortId);
- Node nodeB = root.GetOrAddNode("B");
- nodeB.SafeSetAttribute("shape", "record", "");
- nodeB.SafeSetAttribute("label", $"<{validPortName}>1|2", "\\N");
-
- // The conversion function makes sure different strings don't accidentally map onto the same portname
- Assert.AreNotEqual(Edge.ConvertUidToPortName(":"), Edge.ConvertUidToPortName("|"));
- }
+ // The order of the list matches the order in which the labels occur in the label string above.
+ var rects = layout.GetNode("A").GetRecordRectangles().ToList();
+ Assert.AreEqual(9, rects.Count);
+ }
+
+ [Test, Order(5)]
+ public void StringEscaping()
+ {
+ RootGraph root = RootGraph.CreateNew(GraphType.Directed, "Graph with escaped strings");
+ Node.IntroduceAttribute(root, "label", "\\N");
+ Node nodeA = root.GetOrAddNode("A");
+
+ // Several characters and character sequences can have special meanings in labels, like \N.
+ // When you want to have a literal string in a label, we provide a convenience function for you to do just that.
+ nodeA.SetAttribute("label", CGraphThing.EscapeLabel("Some string literal \\N \\n |}>"));
+
+ // When defining portnames, some characters, like ':' and '|', are not allowed and they can't be escaped either.
+ // This can be troubling if you have an externally defined ID for such a port.
+ // We provide a function that maps strings to valid portnames.
+ var somePortId = "port id with :| special characters";
+ var validPortName = Edge.ConvertUidToPortName(somePortId);
+ Node nodeB = root.GetOrAddNode("B");
+ nodeB.SafeSetAttribute("shape", "record", "");
+ nodeB.SafeSetAttribute("label", $"<{validPortName}>1|2", "\\N");
+
+ // The conversion function makes sure different strings don't accidentally map onto the same portname
+ Assert.AreNotEqual(Edge.ConvertUidToPortName(":"), Edge.ConvertUidToPortName("|"));
}
}
diff --git a/Rubjerg.Graphviz.Test/Utils.cs b/Rubjerg.Graphviz.Test/Utils.cs
index aa06092..19397bf 100644
--- a/Rubjerg.Graphviz.Test/Utils.cs
+++ b/Rubjerg.Graphviz.Test/Utils.cs
@@ -3,92 +3,91 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
-namespace Rubjerg.Graphviz.Test
+namespace Rubjerg.Graphviz.Test;
+
+public static class Utils
{
- public static class Utils
+ private static readonly Random _rand = new Random();
+
+ internal static int _rootGraphCounter = 0;
+ public static RootGraph CreateUniqueTestGraph()
{
- private static readonly Random _rand = new Random();
+ _rootGraphCounter += 1;
+ return RootGraph.CreateNew(GraphType.Directed, "test graph " + _rootGraphCounter.ToString());
+ }
- internal static int _rootGraphCounter = 0;
- public static RootGraph CreateUniqueTestGraph()
- {
- _rootGraphCounter += 1;
- return RootGraph.CreateNew(GraphType.Directed, "test graph " + _rootGraphCounter.ToString());
- }
+ public static string GetTestFilePath(string fileName)
+ {
+ return $"{TestContext.CurrentContext.TestDirectory}/{fileName}";
+ }
+
+ public static RootGraph CreateRandomConnectedGraph(int size, double out_degree)
+ {
+ RootGraph root = CreateUniqueTestGraph();
- public static string GetTestFilePath(string fileName)
+ // First generate a star of requested size
+ Node centernode = root.GetOrAddNode(0.ToString());
+ for (int i = 1; i < size; i++)
{
- return $"{TestContext.CurrentContext.TestDirectory}/{fileName}";
+ var node = root.GetOrAddNode(i.ToString());
+ _ = root.GetOrAddEdge(centernode, node, $"{0} to {i}");
}
- public static RootGraph CreateRandomConnectedGraph(int size, double out_degree)
+ // For each node pick requested number of random neighbors
+ for (int i = 0; i < size; i++)
{
- RootGraph root = CreateUniqueTestGraph();
-
- // First generate a star of requested size
- Node centernode = root.GetOrAddNode(0.ToString());
- for (int i = 1; i < size; i++)
+ // We already have one out edge for each node
+ for (int x = 0; x < out_degree - 2; x++)
{
- var node = root.GetOrAddNode(i.ToString());
- _ = root.GetOrAddEdge(centernode, node, $"{0} to {i}");
+ var node = root.GetNode(i.ToString());
+ int j = _rand.Next(size - 1);
+ var neighbor = root.GetNode(j.ToString());
+ _ = root.GetOrAddEdge(node, neighbor, $"{i} to {j}");
}
-
- // For each node pick requested number of random neighbors
- for (int i = 0; i < size; i++)
- {
- // We already have one out edge for each node
- for (int x = 0; x < out_degree - 2; x++)
- {
- var node = root.GetNode(i.ToString());
- int j = _rand.Next(size - 1);
- var neighbor = root.GetNode(j.ToString());
- _ = root.GetOrAddEdge(node, neighbor, $"{i} to {j}");
- }
- }
-
- return root;
}
- public static void Log(string message)
- {
+ return root;
+ }
+
+ public static void Log(string message)
+ {
#if debug
- TestContext.WriteLine(message);
+ TestContext.WriteLine(message);
#endif
- }
+ }
- public static void AssertPattern(string expectedRegex, string actual)
- {
- Assert.IsTrue(Regex.IsMatch(actual, expectedRegex));
- }
+ public static void AssertPattern(string expectedRegex, string actual)
+ {
+ Assert.IsTrue(Regex.IsMatch(actual, expectedRegex));
+ }
- public static void AssertOrder(this IEnumerable source, Func keySelector)
- {
- Assert.IsTrue(IsOrdered(source, keySelector));
- }
+ public static void AssertOrder(this IEnumerable source, Func keySelector)
+ {
+ Assert.IsTrue(IsOrdered(source, keySelector));
+ }
- public static bool IsOrdered(this IEnumerable source, Func keySelector)
- {
- _ = source ?? throw new ArgumentNullException("source");
+ public static bool IsOrdered(this IEnumerable source, Func keySelector)
+ {
+ _ = source ?? throw new ArgumentNullException("source");
- var comparer = Comparer.Default;
- using (var iterator = source.GetEnumerator())
- {
- if (!iterator.MoveNext())
- return true;
+ var comparer = Comparer.Default;
+ using (var iterator = source.GetEnumerator())
+ {
+ if (!iterator.MoveNext())
+ return true;
- TKey current = keySelector(iterator.Current);
+ TKey current = keySelector(iterator.Current);
- while (iterator.MoveNext())
- {
- TKey next = keySelector(iterator.Current);
- if (comparer.Compare(current, next) > 0)
- return false;
+ while (iterator.MoveNext())
+ {
+ TKey next = keySelector(iterator.Current);
+ if (comparer.Compare(current, next) > 0)
+ return false;
- current = next;
- }
+ current = next;
}
-
- return true;
}
+
+ return true;
}
}
diff --git a/Rubjerg.Graphviz.TransitiveTest/Rubjerg.Graphviz.TransitiveTest.csproj b/Rubjerg.Graphviz.TransitiveTest/Rubjerg.Graphviz.TransitiveTest.csproj
index 3e4f77e..00c6c76 100644
--- a/Rubjerg.Graphviz.TransitiveTest/Rubjerg.Graphviz.TransitiveTest.csproj
+++ b/Rubjerg.Graphviz.TransitiveTest/Rubjerg.Graphviz.TransitiveTest.csproj
@@ -9,6 +9,7 @@
Rubjerg
..\packages
true
+ latest
diff --git a/Rubjerg.Graphviz.TransitiveTest/TestReference.cs b/Rubjerg.Graphviz.TransitiveTest/TestReference.cs
index da9fbdf..8e34e7b 100644
--- a/Rubjerg.Graphviz.TransitiveTest/TestReference.cs
+++ b/Rubjerg.Graphviz.TransitiveTest/TestReference.cs
@@ -1,15 +1,15 @@
using System.Linq;
using NUnit.Framework;
-namespace Rubjerg.Graphviz.NugetTest
+namespace Rubjerg.Graphviz.NugetTest;
+
+[TestFixture()]
+public class TestReference
{
- [TestFixture()]
- public class TestReference
+ [Test()]
+ public void TestReadDotFile()
{
- [Test()]
- public void TestReadDotFile()
- {
- RootGraph root = RootGraph.FromDotString(@"
+ RootGraph root = RootGraph.FromDotString(@"
digraph test {
A;
B;
@@ -19,12 +19,11 @@ digraph test {
A -> B[name = edgename];
}
");
- var A = root.GetNode("A");
- Assert.AreEqual(3, A.EdgesOut().Count());
+ var A = root.GetNode("A");
+ Assert.AreEqual(3, A.EdgesOut().Count());
- var B = root.GetNode("B");
- _ = root.GetOrAddEdge(A, B, "");
- Assert.AreEqual(4, A.EdgesOut().Count());
- }
+ var B = root.GetNode("B");
+ _ = root.GetOrAddEdge(A, B, "");
+ Assert.AreEqual(4, A.EdgesOut().Count());
}
}
diff --git a/Rubjerg.Graphviz/CGraphThing.cs b/Rubjerg.Graphviz/CGraphThing.cs
index ee7db89..b0b220b 100644
--- a/Rubjerg.Graphviz/CGraphThing.cs
+++ b/Rubjerg.Graphviz/CGraphThing.cs
@@ -5,221 +5,220 @@
using System.Drawing;
using static Rubjerg.Graphviz.ForeignFunctionInterface;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+// NOTE ABOUT ATTRIBUTES:
+// Cgraph assumes that all objects of a given kind(graphs/subgraphs, nodes, or edges) have the same attributes
+// - there’s no notion of subtyping within attributes. Information about attributes is stored in data dictionaries.
+// Each graph has three (for graphs/subgraphs, nodes, and edges) for which you'll need the predefined constants
+// AGRAPH, AGNODE and AGEDGE in calls to create, search and walk these dictionaries.
+public abstract class CGraphThing : GraphvizThing
{
- // NOTE ABOUT ATTRIBUTES:
- // Cgraph assumes that all objects of a given kind(graphs/subgraphs, nodes, or edges) have the same attributes
- // - there’s no notion of subtyping within attributes. Information about attributes is stored in data dictionaries.
- // Each graph has three (for graphs/subgraphs, nodes, and edges) for which you'll need the predefined constants
- // AGRAPH, AGNODE and AGEDGE in calls to create, search and walk these dictionaries.
- public abstract class CGraphThing : GraphvizThing
- {
- public readonly RootGraph MyRootGraph;
-
- ///
- /// Argument root may be null.
- /// In that case, it is assumed this is a RootGraph, and MyRootGraph is set to `this`.
- ///
- internal CGraphThing(IntPtr ptr, RootGraph root) : base(ptr)
- {
- if (root == null)
- MyRootGraph = (RootGraph)this;
- else
- MyRootGraph = root;
- }
+ public readonly RootGraph MyRootGraph;
- protected static string NameString(string name)
- {
- // Because graphviz does not properly export empty strings to dot, this opens a can of worms.
- // So we disallow it, and map it onto null.
- // Related issue: https://gitlab.com/graphviz/graphviz/-/issues/1887
- return name == string.Empty ? null : name;
- }
+ ///
+ /// Argument root may be null.
+ /// In that case, it is assumed this is a RootGraph, and MyRootGraph is set to `this`.
+ ///
+ internal CGraphThing(IntPtr ptr, RootGraph root) : base(ptr)
+ {
+ if (root == null)
+ MyRootGraph = (RootGraph)this;
+ else
+ MyRootGraph = root;
+ }
- ///
- /// Identifier for this object. Used to distinghuish multi edges.
- /// Edges can be nameless, and in that case this method returns null.
- ///
- public string GetName()
- {
- return NameString(Rjagnameof(_ptr));
- }
+ protected static string NameString(string name)
+ {
+ // Because graphviz does not properly export empty strings to dot, this opens a can of worms.
+ // So we disallow it, and map it onto null.
+ // Related issue: https://gitlab.com/graphviz/graphviz/-/issues/1887
+ return name == string.Empty ? null : name;
+ }
- public bool HasAttribute(string name)
- {
- return !new[] { null, "" }.Contains(GetAttribute(name));
- }
+ ///
+ /// Identifier for this object. Used to distinghuish multi edges.
+ /// Edges can be nameless, and in that case this method returns null.
+ ///
+ public string GetName()
+ {
+ return NameString(Rjagnameof(_ptr));
+ }
- ///
- /// Set attribute, and introduce it with the given default if it is not introduced yet.
- ///
- public void SafeSetAttribute(string name, string value, string deflt)
- {
- _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
- Agsafeset(_ptr, name, value, deflt);
- }
+ public bool HasAttribute(string name)
+ {
+ return !new[] { null, "" }.Contains(GetAttribute(name));
+ }
- ///
- /// Set attribute, and introduce it with the empty string if it does not exist yet.
- ///
- public void SetAttribute(string name, string value)
- {
- Agsafeset(_ptr, name, value, "");
- }
+ ///
+ /// Set attribute, and introduce it with the given default if it is not introduced yet.
+ ///
+ public void SafeSetAttribute(string name, string value, string deflt)
+ {
+ _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
+ Agsafeset(_ptr, name, value, deflt);
+ }
- ///
- /// Get the attribute value for this object, or the default value of the attribute if no explicit value was set.
- /// If the attribute was not introduced, return null.
- ///
- public string GetAttribute(string name)
- {
- return Agget(_ptr, name);
- }
+ ///
+ /// Set attribute, and introduce it with the empty string if it does not exist yet.
+ ///
+ public void SetAttribute(string name, string value)
+ {
+ Agsafeset(_ptr, name, value, "");
+ }
- ///
- /// Get the attribute if it was introduced and contains a non-empty value, otherwise return deflt.
- ///
- public string SafeGetAttribute(string name, string deflt)
- {
- if (HasAttribute(name))
- return GetAttribute(name);
- return deflt;
- }
+ ///
+ /// Get the attribute value for this object, or the default value of the attribute if no explicit value was set.
+ /// If the attribute was not introduced, return null.
+ ///
+ public string GetAttribute(string name)
+ {
+ return Agget(_ptr, name);
+ }
- public void SetAttributeHtml(string name, string value)
- {
- AgsetHtml(_ptr, name, value);
- }
+ ///
+ /// Get the attribute if it was introduced and contains a non-empty value, otherwise return deflt.
+ ///
+ public string SafeGetAttribute(string name, string deflt)
+ {
+ if (HasAttribute(name))
+ return GetAttribute(name);
+ return deflt;
+ }
- ///
- /// Get all attributes as dictionary.
- ///
- ///
- public Dictionary GetAttributes()
+ public void SetAttributeHtml(string name, string value)
+ {
+ AgsetHtml(_ptr, name, value);
+ }
+
+ ///
+ /// Get all attributes as dictionary.
+ ///
+ ///
+ public Dictionary GetAttributes()
+ {
+ Dictionary attributes = new Dictionary();
+ for (int kind = 0; kind < 3; ++kind)
{
- Dictionary attributes = new Dictionary();
- for (int kind = 0; kind < 3; ++kind)
+ IntPtr sym = Agnxtattr(MyRootGraph._ptr, kind, IntPtr.Zero);
+ while (sym != IntPtr.Zero)
{
- IntPtr sym = Agnxtattr(MyRootGraph._ptr, kind, IntPtr.Zero);
- while (sym != IntPtr.Zero)
+ string key = ImsymKey(sym);
+ if (HasAttribute(key))
{
- string key = ImsymKey(sym);
- if (HasAttribute(key))
+ string value = GetAttribute(key);
+ if (!string.IsNullOrEmpty(value))
{
- string value = GetAttribute(key);
- if (!string.IsNullOrEmpty(value))
- {
- attributes[key] = value;
- }
+ attributes[key] = value;
}
- sym = Agnxtattr(MyRootGraph._ptr, kind, sym);
}
+ sym = Agnxtattr(MyRootGraph._ptr, kind, sym);
}
- return attributes;
- }
-
- public override string ToString()
- {
- return $"Name: {GetName()}, root name: {MyRootGraph.GetName()}";
}
+ return attributes;
+ }
- ///
- /// Copy attributes from one cgraph object to another.
- /// Throw argument exception if self and destination are not of the same type.
- /// Also copies attributes that haven't been introduced in the destination object,
- /// unless introduce_new_attrs is false.
- /// Return code indicates success or failure.
- ///
- public int CopyAttributesTo(CGraphThing destination, bool introduce_new_attrs = true)
- {
- if (!((this is Node && destination is Node)
- || (this is Edge && destination is Edge)
- || (this is Graph && destination is Graph)))
- throw new ArgumentException("Argument must be of the same type as self.");
-
- // Copying the attributes doesn't work if they have not been introduced in the graph
- // Moreover, the copying may just stop at some point if copying of a single attribute fails
- if (introduce_new_attrs && MyRootGraph._ptr != destination.MyRootGraph._ptr)
- CloneAttributeDeclarations(MyRootGraph._ptr, destination.MyRootGraph._ptr);
-
- // agcopyattr returns non-zero number on failure.
-
- // Problems have been observed while copying between edges (of the same graph)
- // Hypothesis: are the pointers different in this case?
- // Hypothesis 2: does it have something to do with the comment "Do not copy key attribute for edges,
- // as this must be distinct." in the graphviz source code?
- // Returncode 1 has been observed while copying rootgraphs, while the number of attributes was 0
- Debug.Assert(_ptr != destination._ptr);
- int success = Agcopyattr(_ptr, destination._ptr);
- // We implement a workaround for copying between edges
- if (success != 0)
- {
- // Fail for unknown failing cases
- if (GetType() != typeof(Edge) && GetAttributes().Count != 0)
- Debug.Fail("Copying attributes failed");
-
- // We work around this doing the following:
- var attrs = GetAttributes();
- foreach (var key in attrs.Keys)
- destination.SetAttribute(key, attrs[key]);
- }
+ public override string ToString()
+ {
+ return $"Name: {GetName()}, root name: {MyRootGraph.GetName()}";
+ }
- return 0;
- }
+ ///
+ /// Copy attributes from one cgraph object to another.
+ /// Throw argument exception if self and destination are not of the same type.
+ /// Also copies attributes that haven't been introduced in the destination object,
+ /// unless introduce_new_attrs is false.
+ /// Return code indicates success or failure.
+ ///
+ public int CopyAttributesTo(CGraphThing destination, bool introduce_new_attrs = true)
+ {
+ if (!((this is Node && destination is Node)
+ || (this is Edge && destination is Edge)
+ || (this is Graph && destination is Graph)))
+ throw new ArgumentException("Argument must be of the same type as self.");
+
+ // Copying the attributes doesn't work if they have not been introduced in the graph
+ // Moreover, the copying may just stop at some point if copying of a single attribute fails
+ if (introduce_new_attrs && MyRootGraph._ptr != destination.MyRootGraph._ptr)
+ CloneAttributeDeclarations(MyRootGraph._ptr, destination.MyRootGraph._ptr);
+
+ // agcopyattr returns non-zero number on failure.
+
+ // Problems have been observed while copying between edges (of the same graph)
+ // Hypothesis: are the pointers different in this case?
+ // Hypothesis 2: does it have something to do with the comment "Do not copy key attribute for edges,
+ // as this must be distinct." in the graphviz source code?
+ // Returncode 1 has been observed while copying rootgraphs, while the number of attributes was 0
+ Debug.Assert(_ptr != destination._ptr);
+ int success = Agcopyattr(_ptr, destination._ptr);
+ // We implement a workaround for copying between edges
+ if (success != 0)
+ {
+ // Fail for unknown failing cases
+ if (GetType() != typeof(Edge) && GetAttributes().Count != 0)
+ Debug.Fail("Copying attributes failed");
+
+ // We work around this doing the following:
+ var attrs = GetAttributes();
+ foreach (var key in attrs.Keys)
+ destination.SetAttribute(key, attrs[key]);
+ }
+
+ return 0;
+ }
- ///
- /// Some characters and character sequences have a special meaning.
- /// If you intend to display a literal string, use this function to properly escape the string.
- /// See also
- /// https://www.graphviz.org/doc/info/shapes.html#record
- /// https://www.graphviz.org/doc/info/attrs.html#k:escString
- ///
- public static string EscapeLabel(string label)
+ ///
+ /// Some characters and character sequences have a special meaning.
+ /// If you intend to display a literal string, use this function to properly escape the string.
+ /// See also
+ /// https://www.graphviz.org/doc/info/shapes.html#record
+ /// https://www.graphviz.org/doc/info/attrs.html#k:escString
+ ///
+ public static string EscapeLabel(string label)
+ {
+ // From the graphviz docs:
+ // Braces, vertical bars and angle brackets must be escaped with a backslash character if
+ // you wish them to appear as a literal character. Spaces are interpreted as separators
+ // between tokens, so they must be escaped if you want spaces in the text.
+ string result = label;
+ foreach (char c in new[] { '\\', '<', '>', '{', '}', ' ', '|' })
{
- // From the graphviz docs:
- // Braces, vertical bars and angle brackets must be escaped with a backslash character if
- // you wish them to appear as a literal character. Spaces are interpreted as separators
- // between tokens, so they must be escaped if you want spaces in the text.
- string result = label;
- foreach (char c in new[] { '\\', '<', '>', '{', '}', ' ', '|' })
- {
- result = result.Replace(c.ToString(), "\\" + c);
- }
- return result;
+ result = result.Replace(c.ToString(), "\\" + c);
}
+ return result;
+ }
- #region layout functions
-
- public Color GetColor()
- {
- string colorstring = SafeGetAttribute("color", "Black");
- return Color.FromName(colorstring);
- }
+ #region layout functions
- public bool HasPosition()
- {
- return HasAttribute("pos");
- }
+ public Color GetColor()
+ {
+ string colorstring = SafeGetAttribute("color", "Black");
+ return Color.FromName(colorstring);
+ }
- public void MakeInvisible()
- {
- SafeSetAttribute("style", "invis", "");
- }
+ public bool HasPosition()
+ {
+ return HasAttribute("pos");
+ }
- public bool IsInvisible()
- {
- return SafeGetAttribute("style", "") == "invis";
- }
+ public void MakeInvisible()
+ {
+ SafeSetAttribute("style", "invis", "");
+ }
- protected static List GetXDotValue(CGraphThing obj, string attrName)
- {
- var xdotString = obj.SafeGetAttribute(attrName, null);
- if (xdotString is null)
- return new List();
+ public bool IsInvisible()
+ {
+ return SafeGetAttribute("style", "") == "invis";
+ }
- return XDotParser.ParseXDot(xdotString);
- }
+ protected static List GetXDotValue(CGraphThing obj, string attrName)
+ {
+ var xdotString = obj.SafeGetAttribute(attrName, null);
+ if (xdotString is null)
+ return new List();
- #endregion
+ return XDotParser.ParseXDot(xdotString);
}
+
+ #endregion
}
diff --git a/Rubjerg.Graphviz/Edge.cs b/Rubjerg.Graphviz/Edge.cs
index dbb31d5..01a2bd9 100644
--- a/Rubjerg.Graphviz/Edge.cs
+++ b/Rubjerg.Graphviz/Edge.cs
@@ -5,177 +5,176 @@
using System.Linq;
using static Rubjerg.Graphviz.ForeignFunctionInterface;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+public class Edge : CGraphThing
{
- public class Edge : CGraphThing
- {
- ///
- /// rootgraph must not be null
- ///
- internal Edge(IntPtr ptr, RootGraph rootgraph) : base(ptr, rootgraph) { }
+ ///
+ /// rootgraph must not be null
+ ///
+ internal Edge(IntPtr ptr, RootGraph rootgraph) : base(ptr, rootgraph) { }
- internal static Edge Get(Graph graph, Node tail, Node head, string name)
- {
- name = NameString(name);
- IntPtr ptr = Agedge(graph._ptr, tail._ptr, head._ptr, name, 0);
- if (ptr == IntPtr.Zero)
- return null;
- return new Edge(ptr, graph.MyRootGraph);
- }
+ internal static Edge Get(Graph graph, Node tail, Node head, string name)
+ {
+ name = NameString(name);
+ IntPtr ptr = Agedge(graph._ptr, tail._ptr, head._ptr, name, 0);
+ if (ptr == IntPtr.Zero)
+ return null;
+ return new Edge(ptr, graph.MyRootGraph);
+ }
- internal static Edge GetOrCreate(Graph graph, Node tail, Node head, string name)
- {
- name = NameString(name);
- IntPtr ptr = Agedge(graph._ptr, tail._ptr, head._ptr, name, 1);
- return new Edge(ptr, graph.MyRootGraph);
- }
+ internal static Edge GetOrCreate(Graph graph, Node tail, Node head, string name)
+ {
+ name = NameString(name);
+ IntPtr ptr = Agedge(graph._ptr, tail._ptr, head._ptr, name, 1);
+ return new Edge(ptr, graph.MyRootGraph);
+ }
- ///
- /// Introduces an attribute for edges in the given graph by given a default.
- /// A given default can be overwritten by calling this method again.
- ///
- public static void IntroduceAttribute(RootGraph root, string name, string deflt)
- {
- _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
- Agattr(root._ptr, 2, name, deflt);
- }
+ ///
+ /// Introduces an attribute for edges in the given graph by given a default.
+ /// A given default can be overwritten by calling this method again.
+ ///
+ public static void IntroduceAttribute(RootGraph root, string name, string deflt)
+ {
+ _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
+ Agattr(root._ptr, 2, name, deflt);
+ }
- public static void IntroduceAttributeHtml(RootGraph root, string name, string deflt)
- {
- _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
- AgattrHtml(root._ptr, 2, name, deflt);
- }
+ public static void IntroduceAttributeHtml(RootGraph root, string name, string deflt)
+ {
+ _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
+ AgattrHtml(root._ptr, 2, name, deflt);
+ }
- protected internal IntPtr HeadPtr()
- {
- return Aghead(_ptr);
- }
+ protected internal IntPtr HeadPtr()
+ {
+ return Aghead(_ptr);
+ }
- protected internal IntPtr TailPtr()
- {
- return Agtail(_ptr);
- }
+ protected internal IntPtr TailPtr()
+ {
+ return Agtail(_ptr);
+ }
- public Node Head()
- {
- return new Node(HeadPtr(), MyRootGraph);
- }
+ public Node Head()
+ {
+ return new Node(HeadPtr(), MyRootGraph);
+ }
- public Node Tail()
- {
- return new Node(TailPtr(), MyRootGraph);
- }
+ public Node Tail()
+ {
+ return new Node(TailPtr(), MyRootGraph);
+ }
- public Node OppositeEndpoint(Node node)
- {
- var tail = Tail();
- var head = Head();
- Debug.Assert(node == tail || node == head);
- return node == tail ? head : tail;
- }
+ public Node OppositeEndpoint(Node node)
+ {
+ var tail = Tail();
+ var head = Head();
+ Debug.Assert(node == tail || node == head);
+ return node == tail ? head : tail;
+ }
- public bool IsAdjacentTo(Node node)
- {
- return node.Equals(Head()) || node.Equals(Tail());
- }
+ public bool IsAdjacentTo(Node node)
+ {
+ return node.Equals(Head()) || node.Equals(Tail());
+ }
- public bool IsBetween(Node node1, Node node2)
- {
- return IsAdjacentTo(node1) && IsAdjacentTo(node2);
- }
+ public bool IsBetween(Node node1, Node node2)
+ {
+ return IsAdjacentTo(node1) && IsAdjacentTo(node2);
+ }
- ///
- /// An edge can define a cluster as logical tail.
- /// This is used to fake edges to and from clusters by clipping the edge on the borders of the logical tail.
- ///
- ///
- public void SetLogicalTail(SubGraph ltail)
- {
- if (!ltail.IsCluster())
- throw new InvalidOperationException("ltail must be a cluster");
- if (!MyRootGraph.IsCompound())
- throw new InvalidOperationException("rootgraph must be compound for lheads/ltails to be used");
- string ltailname = ltail.GetName();
- SafeSetAttribute("ltail", ltailname, "");
- }
+ ///
+ /// An edge can define a cluster as logical tail.
+ /// This is used to fake edges to and from clusters by clipping the edge on the borders of the logical tail.
+ ///
+ ///
+ public void SetLogicalTail(SubGraph ltail)
+ {
+ if (!ltail.IsCluster())
+ throw new InvalidOperationException("ltail must be a cluster");
+ if (!MyRootGraph.IsCompound())
+ throw new InvalidOperationException("rootgraph must be compound for lheads/ltails to be used");
+ string ltailname = ltail.GetName();
+ SafeSetAttribute("ltail", ltailname, "");
+ }
- ///
- /// An edge can define a cluster as logical head.
- /// This is used to fake edges to and from clusters by clipping the edge on the borders of the logical head.
- ///
- public void SetLogicalHead(SubGraph lhead)
- {
- if (!lhead.IsCluster())
- throw new InvalidOperationException("ltail must be a cluster");
- if (!MyRootGraph.IsCompound())
- throw new InvalidOperationException("rootgraph must be compound for lheads/ltails to be used");
- string lheadname = lhead.GetName();
- SafeSetAttribute("lhead", lheadname, "");
- }
+ ///
+ /// An edge can define a cluster as logical head.
+ /// This is used to fake edges to and from clusters by clipping the edge on the borders of the logical head.
+ ///
+ public void SetLogicalHead(SubGraph lhead)
+ {
+ if (!lhead.IsCluster())
+ throw new InvalidOperationException("ltail must be a cluster");
+ if (!MyRootGraph.IsCompound())
+ throw new InvalidOperationException("rootgraph must be compound for lheads/ltails to be used");
+ string lheadname = lhead.GetName();
+ SafeSetAttribute("lhead", lheadname, "");
+ }
- ///
- /// Port names cannot contain certain characters, and other characters must be escaped.
- /// This function converts a string to an ID that is valid as a port name.
- /// It makes sure there are no collisions.
- ///
- public static string ConvertUidToPortName(string id)
+ ///
+ /// Port names cannot contain certain characters, and other characters must be escaped.
+ /// This function converts a string to an ID that is valid as a port name.
+ /// It makes sure there are no collisions.
+ ///
+ public static string ConvertUidToPortName(string id)
+ {
+ string result = id;
+ foreach (char c in new[] { '<', '>', '{', '}', '|', ':' })
{
- string result = id;
- foreach (char c in new[] { '<', '>', '{', '}', '|', ':' })
- {
- result = result.Replace("+", "[+]");
- result = result.Replace(c, '+');
- }
- return result;
+ result = result.Replace("+", "[+]");
+ result = result.Replace(c, '+');
}
+ return result;
+ }
- // Because there are two valid pointers to each edge, we have to override the default equals behaviour
- // which simply compares the wrapped pointers.
- public override bool Equals(GraphvizThing obj)
- {
- if (obj is Edge)
- return Ageqedge(_ptr, obj._ptr);
- return false;
- }
+ // Because there are two valid pointers to each edge, we have to override the default equals behaviour
+ // which simply compares the wrapped pointers.
+ public override bool Equals(GraphvizThing obj)
+ {
+ if (obj is Edge)
+ return Ageqedge(_ptr, obj._ptr);
+ return false;
+ }
- public override int GetHashCode()
- {
- // Return the ptr to the in-edge, which is unique and consistent for each edge.
- // The following line can result in an OverflowException:
- //return (int) agmkin(ptr);
- return (int)(long)Agmkin(_ptr);
- }
+ public override int GetHashCode()
+ {
+ // Return the ptr to the in-edge, which is unique and consistent for each edge.
+ // The following line can result in an OverflowException:
+ //return (int) agmkin(ptr);
+ return (int)(long)Agmkin(_ptr);
+ }
- #region layout attributes
+ #region layout attributes
- ///
- /// This method only returns the first spline that is defined.
- /// Returns null if no splines exist.
- ///
- public PointF[] GetFirstSpline()
- {
- return GetSplines().FirstOrDefault();
- }
+ ///
+ /// This method only returns the first spline that is defined.
+ /// Returns null if no splines exist.
+ ///
+ public PointF[] GetFirstSpline()
+ {
+ return GetSplines().FirstOrDefault();
+ }
- ///
- /// The splines contain 3n+1 points, just like expected by .net drawing methods.
- /// Sometimes there are multiple splines per edge. However, this is not always correct:
- /// https://github.com/ellson/graphviz/issues/1277
- /// Edge arrows are ignored.
- ///
- public IEnumerable GetSplines()
- {
- return GetDrawing().OfType()
- .Select(x => x.Value.Points.Select(p => new PointF((float)p.X, (float)p.Y)).ToArray());
- }
+ ///
+ /// The splines contain 3n+1 points, just like expected by .net drawing methods.
+ /// Sometimes there are multiple splines per edge. However, this is not always correct:
+ /// https://github.com/ellson/graphviz/issues/1277
+ /// Edge arrows are ignored.
+ ///
+ public IEnumerable GetSplines()
+ {
+ return GetDrawing().OfType()
+ .Select(x => x.Value.Points.Select(p => new PointF((float)p.X, (float)p.Y)).ToArray());
+ }
- public IReadOnlyList GetDrawing() => GetXDotValue(this, "_draw_");
- public IReadOnlyList GetLabelDrawing() => GetXDotValue(this, "_ldraw_");
- public IReadOnlyList GetHeadArrowDrawing() => GetXDotValue(this, "_hdraw_");
- public IReadOnlyList GetTailArrowDrawing() => GetXDotValue(this, "_tdraw_");
- public IReadOnlyList GetHeadLabelDrawing() => GetXDotValue(this, "_hldraw_");
- public IReadOnlyList GetTailLabelDrawing() => GetXDotValue(this, "_tldraw_");
+ public IReadOnlyList GetDrawing() => GetXDotValue(this, "_draw_");
+ public IReadOnlyList GetLabelDrawing() => GetXDotValue(this, "_ldraw_");
+ public IReadOnlyList GetHeadArrowDrawing() => GetXDotValue(this, "_hdraw_");
+ public IReadOnlyList GetTailArrowDrawing() => GetXDotValue(this, "_tdraw_");
+ public IReadOnlyList GetHeadLabelDrawing() => GetXDotValue(this, "_hldraw_");
+ public IReadOnlyList GetTailLabelDrawing() => GetXDotValue(this, "_tldraw_");
- #endregion
- }
+ #endregion
}
diff --git a/Rubjerg.Graphviz/ForeignFunctionInterface.cs b/Rubjerg.Graphviz/ForeignFunctionInterface.cs
index 23b77e5..2653eb0 100644
--- a/Rubjerg.Graphviz/ForeignFunctionInterface.cs
+++ b/Rubjerg.Graphviz/ForeignFunctionInterface.cs
@@ -1,707 +1,706 @@
using System;
using System.Runtime.InteropServices;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+///
+/// Graphviz is thread unsafe, so we wrap all function calls inside a lock to make sure we don't run into
+/// issues caused by multiple threads accessing the graphviz datastructures (like the GC executing a destructor).
+///
+internal static class ForeignFunctionInterface
{
- ///
- /// Graphviz is thread unsafe, so we wrap all function calls inside a lock to make sure we don't run into
- /// issues caused by multiple threads accessing the graphviz datastructures (like the GC executing a destructor).
- ///
- internal static class ForeignFunctionInterface
- {
- private static readonly object _mutex = new object();
+ private static readonly object _mutex = new object();
- public static IntPtr GvContext()
+ public static IntPtr GvContext()
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return gvContext();
- }
+ return gvContext();
}
- public static int GvFreeContext(IntPtr gvc)
+ }
+ public static int GvFreeContext(IntPtr gvc)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return gvFreeContext(gvc);
- }
+ return gvFreeContext(gvc);
}
- public static int GvLayout(IntPtr gvc, IntPtr graph, string engine)
+ }
+ public static int GvLayout(IntPtr gvc, IntPtr graph, string engine)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return gvLayout(gvc, graph, engine);
- }
+ return gvLayout(gvc, graph, engine);
}
- public static int GvFreeLayout(IntPtr gvc, IntPtr graph)
+ }
+ public static int GvFreeLayout(IntPtr gvc, IntPtr graph)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return gvFreeLayout(gvc, graph);
- }
+ return gvFreeLayout(gvc, graph);
}
- public static int GvRender(IntPtr gvc, IntPtr graph, string format, IntPtr @out)
+ }
+ public static int GvRender(IntPtr gvc, IntPtr graph, string format, IntPtr @out)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return gvRender(gvc, graph, format, @out);
- }
+ return gvRender(gvc, graph, format, @out);
}
- public static int GvRenderFilename(IntPtr gvc, IntPtr graph, string format, string filename)
+ }
+ public static int GvRenderFilename(IntPtr gvc, IntPtr graph, string format, string filename)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return gvRenderFilename(gvc, graph, format, filename);
- }
+ return gvRenderFilename(gvc, graph, format, filename);
}
- public static IntPtr Agnode(IntPtr graph, string name, int create)
+ }
+ public static IntPtr Agnode(IntPtr graph, string name, int create)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agnode(graph, name, create);
- }
+ return agnode(graph, name, create);
}
- public static int Agdegree(IntPtr graph, IntPtr node, int inset, int outset)
+ }
+ public static int Agdegree(IntPtr graph, IntPtr node, int inset, int outset)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agdegree(graph, node, inset, outset);
- }
+ return agdegree(graph, node, inset, outset);
}
- public static IntPtr Agfstout(IntPtr graph, IntPtr node)
+ }
+ public static IntPtr Agfstout(IntPtr graph, IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agfstout(graph, node);
- }
+ return agfstout(graph, node);
}
- public static IntPtr Agnxtout(IntPtr graph, IntPtr edge)
+ }
+ public static IntPtr Agnxtout(IntPtr graph, IntPtr edge)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agnxtout(graph, edge);
- }
+ return agnxtout(graph, edge);
}
- public static IntPtr Agfstin(IntPtr graph, IntPtr node)
+ }
+ public static IntPtr Agfstin(IntPtr graph, IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agfstin(graph, node);
- }
+ return agfstin(graph, node);
}
- public static IntPtr Agnxtin(IntPtr graph, IntPtr edge)
+ }
+ public static IntPtr Agnxtin(IntPtr graph, IntPtr edge)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agnxtin(graph, edge);
- }
+ return agnxtin(graph, edge);
}
- public static IntPtr Agfstedge(IntPtr graph, IntPtr node)
+ }
+ public static IntPtr Agfstedge(IntPtr graph, IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agfstedge(graph, node);
- }
+ return agfstedge(graph, node);
}
- public static IntPtr Agnxtedge(IntPtr graph, IntPtr edge, IntPtr node)
+ }
+ public static IntPtr Agnxtedge(IntPtr graph, IntPtr edge, IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agnxtedge(graph, edge, node);
- }
+ return agnxtedge(graph, edge, node);
}
- public static void Agattr(IntPtr graph, int type, string name, string deflt)
+ }
+ public static void Agattr(IntPtr graph, int type, string name, string deflt)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- agattr(graph, type, name, deflt);
- }
+ agattr(graph, type, name, deflt);
}
- public static void AgattrHtml(IntPtr graph, int type, string name, string deflt)
+ }
+ public static void AgattrHtml(IntPtr graph, int type, string name, string deflt)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- var ptr = agstrdup_html(agroot(graph), deflt);
- agattr(graph, type, name, ptr);
- }
+ var ptr = agstrdup_html(agroot(graph), deflt);
+ agattr(graph, type, name, ptr);
}
+ }
- public static void Agset(IntPtr obj, string name, string value)
+ public static void Agset(IntPtr obj, string name, string value)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- agset(obj, name, value);
- }
+ agset(obj, name, value);
}
+ }
- public static void AgsetHtml(IntPtr obj, string name, string value)
+ public static void AgsetHtml(IntPtr obj, string name, string value)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- var ptr = agstrdup_html(agroot(obj), value);
- agset(obj, name, ptr);
- }
+ var ptr = agstrdup_html(agroot(obj), value);
+ agset(obj, name, ptr);
}
+ }
- public static void Agsafeset(IntPtr obj, string name, string val, string deflt)
+ public static void Agsafeset(IntPtr obj, string name, string val, string deflt)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- agsafeset(obj, name, val, deflt);
- }
+ agsafeset(obj, name, val, deflt);
}
- public static void AgsafesetHtml(IntPtr obj, string name, string val, string deflt)
+ }
+ public static void AgsafesetHtml(IntPtr obj, string name, string val, string deflt)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- var ptr = agstrdup_html(agroot(obj), deflt);
- agsafeset(obj, name, val, ptr);
- }
+ var ptr = agstrdup_html(agroot(obj), deflt);
+ agsafeset(obj, name, val, ptr);
}
- public static IntPtr Agroot(IntPtr obj)
+ }
+ public static IntPtr Agroot(IntPtr obj)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agroot(obj);
- }
+ return agroot(obj);
}
- public static IntPtr Agnxtattr(IntPtr obj, int kind, IntPtr attribute)
+ }
+ public static IntPtr Agnxtattr(IntPtr obj, int kind, IntPtr attribute)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agnxtattr(obj, kind, attribute);
- }
+ return agnxtattr(obj, kind, attribute);
}
- public static int Agcopyattr(IntPtr from, IntPtr to)
+ }
+ public static int Agcopyattr(IntPtr from, IntPtr to)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agcopyattr(from, to);
- }
+ return agcopyattr(from, to);
}
- public static bool Ageqedge(IntPtr edge1, IntPtr edge2)
+ }
+ public static bool Ageqedge(IntPtr edge1, IntPtr edge2)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return rj_ageqedge(edge1, edge2);
- }
+ return rj_ageqedge(edge1, edge2);
}
- public static IntPtr Agtail(IntPtr node)
+ }
+ public static IntPtr Agtail(IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return rj_agtail(node);
- }
+ return rj_agtail(node);
}
- public static IntPtr Aghead(IntPtr node)
+ }
+ public static IntPtr Aghead(IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return rj_aghead(node);
- }
+ return rj_aghead(node);
}
- public static IntPtr Agedge(IntPtr graph, IntPtr tail, IntPtr head, string name, int create)
+ }
+ public static IntPtr Agedge(IntPtr graph, IntPtr tail, IntPtr head, string name, int create)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agedge(graph, tail, head, name, create);
- }
+ return agedge(graph, tail, head, name, create);
}
- public static IntPtr Agmkin(IntPtr edge)
+ }
+ public static IntPtr Agmkin(IntPtr edge)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return rj_agmkin(edge);
- }
+ return rj_agmkin(edge);
}
- public static IntPtr Agmkout(IntPtr edge)
+ }
+ public static IntPtr Agmkout(IntPtr edge)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return rj_agmkout(edge);
- }
+ return rj_agmkout(edge);
}
- public static IntPtr Agparent(IntPtr obj)
+ }
+ public static IntPtr Agparent(IntPtr obj)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agparent(obj);
- }
+ return agparent(obj);
}
- public static int Agclose(IntPtr graph)
+ }
+ public static int Agclose(IntPtr graph)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agclose(graph);
- }
+ return agclose(graph);
}
- public static int Agdelete(IntPtr graph, IntPtr item)
+ }
+ public static int Agdelete(IntPtr graph, IntPtr item)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agdelete(graph, item);
- }
+ return agdelete(graph, item);
}
- public static IntPtr Agfstnode(IntPtr graph)
+ }
+ public static IntPtr Agfstnode(IntPtr graph)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agfstnode(graph);
- }
+ return agfstnode(graph);
}
- public static IntPtr Agnxtnode(IntPtr graph, IntPtr node)
+ }
+ public static IntPtr Agnxtnode(IntPtr graph, IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agnxtnode(graph, node);
- }
+ return agnxtnode(graph, node);
}
- public static int Agcontains(IntPtr graph, IntPtr obj)
+ }
+ public static int Agcontains(IntPtr graph, IntPtr obj)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agcontains(graph, obj);
- }
+ return agcontains(graph, obj);
}
- public static IntPtr Agsubg(IntPtr graph, string name, int create)
+ }
+ public static IntPtr Agsubg(IntPtr graph, string name, int create)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agsubg(graph, name, create);
- }
+ return agsubg(graph, name, create);
}
- public static IntPtr Agfstsubg(IntPtr graph)
+ }
+ public static IntPtr Agfstsubg(IntPtr graph)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agfstsubg(graph);
- }
+ return agfstsubg(graph);
}
- public static IntPtr Agnxtsubg(IntPtr graph)
+ }
+ public static IntPtr Agnxtsubg(IntPtr graph)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agnxtsubg(graph);
- }
+ return agnxtsubg(graph);
}
- public static int Agisstrict(IntPtr ptr)
+ }
+ public static int Agisstrict(IntPtr ptr)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agisstrict(ptr);
- }
+ return agisstrict(ptr);
}
- public static int Agisdirected(IntPtr ptr)
+ }
+ public static int Agisdirected(IntPtr ptr)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agisdirected(ptr);
- }
+ return agisdirected(ptr);
}
- public static int Agisundirected(IntPtr ptr)
+ }
+ public static int Agisundirected(IntPtr ptr)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agisundirected(ptr);
- }
+ return agisundirected(ptr);
}
- public static IntPtr Agsubedge(IntPtr graph, IntPtr edge, int create)
+ }
+ public static IntPtr Agsubedge(IntPtr graph, IntPtr edge, int create)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agsubedge(graph, edge, create);
- }
+ return agsubedge(graph, edge, create);
}
- public static IntPtr Agsubnode(IntPtr graph, IntPtr node, int create)
+ }
+ public static IntPtr Agsubnode(IntPtr graph, IntPtr node, int create)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return agsubnode(graph, node, create);
- }
+ return agsubnode(graph, node, create);
}
- public static IntPtr EdgeLabel(IntPtr node)
+ }
+ public static IntPtr EdgeLabel(IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return edge_label(node);
- }
+ return edge_label(node);
}
- public static string Rjagmemwrite(IntPtr graph)
+ }
+ public static string Rjagmemwrite(IntPtr graph)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- var ptr = rj_agmemwrite(graph);
- var result = Marshal.PtrToStringAnsi(ptr);
- free_str(ptr);
- return result;
- }
+ var ptr = rj_agmemwrite(graph);
+ var result = Marshal.PtrToStringAnsi(ptr);
+ free_str(ptr);
+ return result;
}
- public static IntPtr GraphLabel(IntPtr node)
+ }
+ public static IntPtr GraphLabel(IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return graph_label(node);
- }
+ return graph_label(node);
}
- public static string Agget(IntPtr obj, string name)
+ }
+ public static string Agget(IntPtr obj, string name)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return Marshal.PtrToStringAnsi(agget(obj, name));
- }
+ return Marshal.PtrToStringAnsi(agget(obj, name));
}
- public static string Rjagnameof(IntPtr obj)
+ }
+ public static string Rjagnameof(IntPtr obj)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return Marshal.PtrToStringAnsi(agnameof(obj));
- }
+ return Marshal.PtrToStringAnsi(agnameof(obj));
}
- public static void CloneAttributeDeclarations(IntPtr graphfrom, IntPtr graphto)
+ }
+ public static void CloneAttributeDeclarations(IntPtr graphfrom, IntPtr graphto)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- clone_attribute_declarations(graphfrom, graphto);
- }
+ clone_attribute_declarations(graphfrom, graphto);
}
- public static string ImsymKey(IntPtr sym)
+ }
+ public static string ImsymKey(IntPtr sym)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return Marshal.PtrToStringAnsi(rj_sym_key(sym));
- }
+ return Marshal.PtrToStringAnsi(rj_sym_key(sym));
}
- public static double LabelX(IntPtr label)
+ }
+ public static double LabelX(IntPtr label)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return label_x(label);
- }
+ return label_x(label);
}
- public static double LabelY(IntPtr label)
+ }
+ public static double LabelY(IntPtr label)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return label_y(label);
- }
+ return label_y(label);
}
- public static double LabelWidth(IntPtr label)
+ }
+ public static double LabelWidth(IntPtr label)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return label_width(label);
- }
+ return label_width(label);
}
- public static double LabelHeight(IntPtr label)
+ }
+ public static double LabelHeight(IntPtr label)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return label_height(label);
- }
+ return label_height(label);
}
- public static string LabelText(IntPtr label)
+ }
+ public static string LabelText(IntPtr label)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return Marshal.PtrToStringAnsi(label_text(label));
- }
+ return Marshal.PtrToStringAnsi(label_text(label));
}
- public static double LabelFontsize(IntPtr label)
+ }
+ public static double LabelFontsize(IntPtr label)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return label_fontsize(label);
- }
+ return label_fontsize(label);
}
- public static string LabelFontname(IntPtr label)
+ }
+ public static string LabelFontname(IntPtr label)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return Marshal.PtrToStringAnsi(label_fontname(label));
- }
+ return Marshal.PtrToStringAnsi(label_fontname(label));
}
- public static double NodeX(IntPtr node)
+ }
+ public static double NodeX(IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return node_x(node);
- }
+ return node_x(node);
}
- public static double NodeY(IntPtr node)
+ }
+ public static double NodeY(IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return node_y(node);
- }
+ return node_y(node);
}
- public static double NodeWidth(IntPtr node)
+ }
+ public static double NodeWidth(IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return node_width(node);
- }
+ return node_width(node);
}
- public static double NodeHeight(IntPtr node)
+ }
+ public static double NodeHeight(IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return node_height(node);
- }
+ return node_height(node);
}
- public static IntPtr NodeLabel(IntPtr node)
+ }
+ public static IntPtr NodeLabel(IntPtr node)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return node_label(node);
- }
+ return node_label(node);
}
- public static void ConvertToUndirected(IntPtr graph)
+ }
+ public static void ConvertToUndirected(IntPtr graph)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- convert_to_undirected(graph);
- }
+ convert_to_undirected(graph);
}
- public static IntPtr Rjagmemread(string input)
+ }
+ public static IntPtr Rjagmemread(string input)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return rj_agmemread(input);
- }
+ return rj_agmemread(input);
}
- public static IntPtr Rjagopen(string name, int graphtype)
+ }
+ public static IntPtr Rjagopen(string name, int graphtype)
+ {
+ lock (_mutex)
{
- lock (_mutex)
- {
- return rj_agopen(name, graphtype);
- }
+ return rj_agopen(name, graphtype);
}
+ }
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern void free_str(IntPtr ptr);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern void free_str(IntPtr ptr);
- [DllImport("gvc.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr gvContext();
- [DllImport("gvc.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern int gvFreeContext(IntPtr gvc);
- [DllImport("gvc.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern int gvLayout(IntPtr gvc, IntPtr graph, [MarshalAs(UnmanagedType.LPStr)] string engine);
- [DllImport("gvc.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern int gvFreeLayout(IntPtr gvc, IntPtr graph);
- [DllImport("gvc.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern int gvRender(IntPtr gvc, IntPtr graph, [MarshalAs(UnmanagedType.LPStr)] string format, IntPtr @out);
- [DllImport("gvc.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern int gvRenderFilename(IntPtr gvc, IntPtr graph, [MarshalAs(UnmanagedType.LPStr)] string format, [MarshalAs(UnmanagedType.LPStr)] string filename);
+ [DllImport("gvc.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr gvContext();
+ [DllImport("gvc.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern int gvFreeContext(IntPtr gvc);
+ [DllImport("gvc.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern int gvLayout(IntPtr gvc, IntPtr graph, [MarshalAs(UnmanagedType.LPStr)] string engine);
+ [DllImport("gvc.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern int gvFreeLayout(IntPtr gvc, IntPtr graph);
+ [DllImport("gvc.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern int gvRender(IntPtr gvc, IntPtr graph, [MarshalAs(UnmanagedType.LPStr)] string format, IntPtr @out);
+ [DllImport("gvc.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern int gvRenderFilename(IntPtr gvc, IntPtr graph, [MarshalAs(UnmanagedType.LPStr)] string format, [MarshalAs(UnmanagedType.LPStr)] string filename);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agnode(IntPtr graph, [MarshalAs(UnmanagedType.LPStr)] string name, int create);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern int agdegree(IntPtr graph, IntPtr node, int inset, int outset);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agfstout(IntPtr graph, IntPtr node);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agnxtout(IntPtr graph, IntPtr edge);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agfstin(IntPtr graph, IntPtr node);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agnxtin(IntPtr graph, IntPtr edge);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agfstedge(IntPtr graph, IntPtr node);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agnxtedge(IntPtr graph, IntPtr edge, IntPtr node);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agnode(IntPtr graph, [MarshalAs(UnmanagedType.LPStr)] string name, int create);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern int agdegree(IntPtr graph, IntPtr node, int inset, int outset);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agfstout(IntPtr graph, IntPtr node);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agnxtout(IntPtr graph, IntPtr edge);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agfstin(IntPtr graph, IntPtr node);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agnxtin(IntPtr graph, IntPtr edge);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agfstedge(IntPtr graph, IntPtr node);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agnxtedge(IntPtr graph, IntPtr edge, IntPtr node);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern void agattr(IntPtr graph, int type, [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string deflt);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern void agattr(IntPtr graph, int type, [MarshalAs(UnmanagedType.LPStr)] string name, IntPtr deflt);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern void agset(IntPtr obj, [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern void agset(IntPtr obj, [MarshalAs(UnmanagedType.LPStr)] string name, IntPtr value);
- [DllImport("cgraph.dll", SetLastError = false, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agstrdup_html(IntPtr obj, [MarshalAs(UnmanagedType.LPStr)] string html);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern void agsafeset(IntPtr obj, [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string val, [MarshalAs(UnmanagedType.LPStr)] string deflt);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern void agsafeset(IntPtr obj, [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string val, IntPtr deflt);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agroot(IntPtr obj);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agnxtattr(IntPtr obj, int kind, IntPtr attribute);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern int agcopyattr(IntPtr from, IntPtr to);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern void agattr(IntPtr graph, int type, [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string deflt);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern void agattr(IntPtr graph, int type, [MarshalAs(UnmanagedType.LPStr)] string name, IntPtr deflt);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern void agset(IntPtr obj, [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern void agset(IntPtr obj, [MarshalAs(UnmanagedType.LPStr)] string name, IntPtr value);
+ [DllImport("cgraph.dll", SetLastError = false, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agstrdup_html(IntPtr obj, [MarshalAs(UnmanagedType.LPStr)] string html);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern void agsafeset(IntPtr obj, [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string val, [MarshalAs(UnmanagedType.LPStr)] string deflt);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern void agsafeset(IntPtr obj, [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string val, IntPtr deflt);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agroot(IntPtr obj);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agnxtattr(IntPtr obj, int kind, IntPtr attribute);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern int agcopyattr(IntPtr from, IntPtr to);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- [return: MarshalAs(UnmanagedType.U1)]
- private static extern bool rj_ageqedge(IntPtr edge1, IntPtr edge2);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr rj_agtail(IntPtr node);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr rj_aghead(IntPtr node);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agedge(IntPtr graph, IntPtr tail, IntPtr head, [MarshalAs(UnmanagedType.LPStr)] string name, int create);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr rj_agmkin(IntPtr edge);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr rj_agmkout(IntPtr edge);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.U1)]
+ private static extern bool rj_ageqedge(IntPtr edge1, IntPtr edge2);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr rj_agtail(IntPtr node);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr rj_aghead(IntPtr node);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agedge(IntPtr graph, IntPtr tail, IntPtr head, [MarshalAs(UnmanagedType.LPStr)] string name, int create);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr rj_agmkin(IntPtr edge);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr rj_agmkout(IntPtr edge);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agparent(IntPtr obj);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agparent(IntPtr obj);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern int agclose(IntPtr graph);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern int agdelete(IntPtr graph, IntPtr item);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agfstnode(IntPtr graph);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agnxtnode(IntPtr graph, IntPtr node);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern int agcontains(IntPtr graph, IntPtr obj);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern int agclose(IntPtr graph);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern int agdelete(IntPtr graph, IntPtr item);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agfstnode(IntPtr graph);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agnxtnode(IntPtr graph, IntPtr node);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern int agcontains(IntPtr graph, IntPtr obj);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agsubg(IntPtr graph, [MarshalAs(UnmanagedType.LPStr)] string name, int create);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agfstsubg(IntPtr graph);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agnxtsubg(IntPtr graph);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern int agisstrict(IntPtr ptr);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern int agisdirected(IntPtr ptr);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern int agisundirected(IntPtr ptr);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agsubg(IntPtr graph, [MarshalAs(UnmanagedType.LPStr)] string name, int create);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agfstsubg(IntPtr graph);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agnxtsubg(IntPtr graph);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern int agisstrict(IntPtr ptr);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern int agisdirected(IntPtr ptr);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern int agisundirected(IntPtr ptr);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agsubedge(IntPtr graph, IntPtr edge, int create);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agsubnode(IntPtr graph, IntPtr node, int create);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agsubedge(IntPtr graph, IntPtr edge, int create);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agsubnode(IntPtr graph, IntPtr node, int create);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr edge_label(IntPtr node);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr rj_agmemwrite(IntPtr graph);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr graph_label(IntPtr node);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr edge_label(IntPtr node);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr rj_agmemwrite(IntPtr graph);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr graph_label(IntPtr node);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agget(IntPtr obj, [MarshalAs(UnmanagedType.LPStr)] string name);
- [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr agnameof(IntPtr obj);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern void clone_attribute_declarations(IntPtr graphfrom, IntPtr graphto);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr rj_sym_key(IntPtr sym);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agget(IntPtr obj, [MarshalAs(UnmanagedType.LPStr)] string name);
+ [DllImport("cgraph.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr agnameof(IntPtr obj);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern void clone_attribute_declarations(IntPtr graphfrom, IntPtr graphto);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr rj_sym_key(IntPtr sym);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern double label_x(IntPtr label);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern double label_y(IntPtr label);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern double label_width(IntPtr label);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern double label_height(IntPtr label);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr label_text(IntPtr label);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern double label_fontsize(IntPtr label);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr label_fontname(IntPtr label);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern double label_x(IntPtr label);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern double label_y(IntPtr label);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern double label_width(IntPtr label);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern double label_height(IntPtr label);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr label_text(IntPtr label);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern double label_fontsize(IntPtr label);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr label_fontname(IntPtr label);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern double node_x(IntPtr node);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern double node_y(IntPtr node);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern double node_width(IntPtr node);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern double node_height(IntPtr node);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr node_label(IntPtr node);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern double node_x(IntPtr node);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern double node_y(IntPtr node);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern double node_width(IntPtr node);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern double node_height(IntPtr node);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr node_label(IntPtr node);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern void convert_to_undirected(IntPtr graph);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr rj_agmemread([MarshalAs(UnmanagedType.LPStr)] string input);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr rj_agopen([MarshalAs(UnmanagedType.LPStr)] string name, int graphtype);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern void convert_to_undirected(IntPtr graph);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr rj_agmemread([MarshalAs(UnmanagedType.LPStr)] string input);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr rj_agopen([MarshalAs(UnmanagedType.LPStr)] string name, int graphtype);
- ///
- /// A GraphvizContext is used to store various layout
- /// information that is independent of a particular graph and
- /// its attributes. It holds the data associated with plugins,
- /// parsed - command lines, script engines, and anything else
- /// with a scope potentially larger than one graph, up to the
- /// scope of the application. In addition, it maintains lists of
- /// the available layout algorithms and renderers; it also
- /// records the most recent layout algorithm applied to a graph.
- /// It can be used to specify multiple renderings of a given
- /// graph layout into different associated files.It is also used
- /// to store various global information used during rendering.
- /// There should be just one GVC created for the entire
- /// duration of an application. A single GVC value can be used
- /// with multiple graphs, though with only one graph at a
- /// time. In addition, if gvLayout() was invoked for a graph and
- /// GVC, then gvFreeLayout() should be called before using
- /// gvLayout() again, even on the same graph.
- ///
- public static IntPtr GVC { get; private set; }
- static ForeignFunctionInterface()
- {
- // We initialize the gvc here before interacting with graphviz
- // https://gitlab.com/graphviz/graphviz/-/issues/2434
- GVC = GvContext();
- }
+ ///
+ /// A GraphvizContext is used to store various layout
+ /// information that is independent of a particular graph and
+ /// its attributes. It holds the data associated with plugins,
+ /// parsed - command lines, script engines, and anything else
+ /// with a scope potentially larger than one graph, up to the
+ /// scope of the application. In addition, it maintains lists of
+ /// the available layout algorithms and renderers; it also
+ /// records the most recent layout algorithm applied to a graph.
+ /// It can be used to specify multiple renderings of a given
+ /// graph layout into different associated files.It is also used
+ /// to store various global information used during rendering.
+ /// There should be just one GVC created for the entire
+ /// duration of an application. A single GVC value can be used
+ /// with multiple graphs, though with only one graph at a
+ /// time. In addition, if gvLayout() was invoked for a graph and
+ /// GVC, then gvFreeLayout() should be called before using
+ /// gvLayout() again, even on the same graph.
+ ///
+ public static IntPtr GVC { get; private set; }
+ static ForeignFunctionInterface()
+ {
+ // We initialize the gvc here before interacting with graphviz
+ // https://gitlab.com/graphviz/graphviz/-/issues/2434
+ GVC = GvContext();
+ }
- #region debugging and testing
+ #region debugging and testing
- // .NET uses UnmanagedType.Bool by default for P/Invoke, but our C++ code uses UnmanagedType.U1
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- [return: MarshalAs(UnmanagedType.U1)]
- public static extern bool echobool([MarshalAs(UnmanagedType.U1)] bool arg);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- [return: MarshalAs(UnmanagedType.U1)]
- public static extern bool return_true();
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- [return: MarshalAs(UnmanagedType.U1)]
- public static extern bool return_false();
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern int echoint(int arg);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern int return1();
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern int return_1();
+ // .NET uses UnmanagedType.Bool by default for P/Invoke, but our C++ code uses UnmanagedType.U1
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.U1)]
+ public static extern bool echobool([MarshalAs(UnmanagedType.U1)] bool arg);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.U1)]
+ public static extern bool return_true();
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.U1)]
+ public static extern bool return_false();
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int echoint(int arg);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int return1();
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int return_1();
- public enum TestEnum
- {
- Val1, Val2, Val3, Val4, Val5
- }
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern TestEnum return_enum1();
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern TestEnum return_enum2();
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern TestEnum return_enum5();
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern TestEnum echo_enum(TestEnum e);
+ public enum TestEnum
+ {
+ Val1, Val2, Val3, Val4, Val5
+ }
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern TestEnum return_enum1();
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern TestEnum return_enum2();
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern TestEnum return_enum5();
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern TestEnum echo_enum(TestEnum e);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr echo_string([MarshalAs(UnmanagedType.LPStr)] string str);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr return_empty_string();
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr return_hello();
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr echo_string([MarshalAs(UnmanagedType.LPStr)] string str);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr return_empty_string();
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr return_hello();
- public static string EchoString(string str)
- {
- // echo_string gives us ownership over the string, which means that we have to free it.
- var ptr = echo_string(str);
- var result = Marshal.PtrToStringAnsi(ptr);
- free_str(ptr);
- return result;
- }
- public static string ReturnEmptyString() => Marshal.PtrToStringAnsi(return_empty_string());
- public static string ReturnHello() => Marshal.PtrToStringAnsi(return_hello());
- #endregion
+ public static string EchoString(string str)
+ {
+ // echo_string gives us ownership over the string, which means that we have to free it.
+ var ptr = echo_string(str);
+ var result = Marshal.PtrToStringAnsi(ptr);
+ free_str(ptr);
+ return result;
}
+ public static string ReturnEmptyString() => Marshal.PtrToStringAnsi(return_empty_string());
+ public static string ReturnHello() => Marshal.PtrToStringAnsi(return_hello());
+ #endregion
}
diff --git a/Rubjerg.Graphviz/Graph.cs b/Rubjerg.Graphviz/Graph.cs
index 3ad436b..d5f1635 100644
--- a/Rubjerg.Graphviz/Graph.cs
+++ b/Rubjerg.Graphviz/Graph.cs
@@ -7,658 +7,657 @@
using System.Linq;
using static Rubjerg.Graphviz.ForeignFunctionInterface;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+///
+/// Wraps a cgraph graph object - either a subgraph or a rootgraph.
+///
+public class Graph : CGraphThing
{
///
- /// Wraps a cgraph graph object - either a subgraph or a rootgraph.
+ /// rootgraph may be null
///
- public class Graph : CGraphThing
- {
- ///
- /// rootgraph may be null
- ///
- protected Graph(IntPtr ptr, RootGraph rootgraph) : base(ptr, rootgraph) { }
- public bool IsStrict() { return Agisstrict(_ptr) != 0; }
- public bool IsDirected() { return Agisdirected(_ptr) != 0; }
- public bool IsUndirected() { return Agisundirected(_ptr) != 0; }
- public GraphType GetGraphType()
- {
- if (IsDirected() && !IsStrict())
- return GraphType.Directed;
- if (IsDirected() && IsStrict())
- return GraphType.StrictDirected;
- if (IsUndirected() && !IsStrict())
- return GraphType.Undirected;
- return GraphType.StrictUndirected;
- }
+ protected Graph(IntPtr ptr, RootGraph rootgraph) : base(ptr, rootgraph) { }
+ public bool IsStrict() { return Agisstrict(_ptr) != 0; }
+ public bool IsDirected() { return Agisdirected(_ptr) != 0; }
+ public bool IsUndirected() { return Agisundirected(_ptr) != 0; }
+ public GraphType GetGraphType()
+ {
+ if (IsDirected() && !IsStrict())
+ return GraphType.Directed;
+ if (IsDirected() && IsStrict())
+ return GraphType.StrictDirected;
+ if (IsUndirected() && !IsStrict())
+ return GraphType.Undirected;
+ return GraphType.StrictUndirected;
+ }
- ///
- /// Introduces an attribute for subgraphs of the given graph by given a default.
- /// A given default can be overwritten by calling this method again.
- ///
- public static void IntroduceAttribute(RootGraph root, string name, string deflt)
- {
- _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
- Agattr(root._ptr, 0, name, deflt);
- }
+ ///
+ /// Introduces an attribute for subgraphs of the given graph by given a default.
+ /// A given default can be overwritten by calling this method again.
+ ///
+ public static void IntroduceAttribute(RootGraph root, string name, string deflt)
+ {
+ _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
+ Agattr(root._ptr, 0, name, deflt);
+ }
- public static void IntroduceAttributeHtml(RootGraph root, string name, string deflt)
- {
- _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
- AgattrHtml(root._ptr, 0, name, deflt);
- }
+ public static void IntroduceAttributeHtml(RootGraph root, string name, string deflt)
+ {
+ _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
+ AgattrHtml(root._ptr, 0, name, deflt);
+ }
- public bool Contains(CGraphThing thing)
- {
- return Agcontains(_ptr, thing._ptr) != 0;
- }
+ public bool Contains(CGraphThing thing)
+ {
+ return Agcontains(_ptr, thing._ptr) != 0;
+ }
- ///
- /// Delete the given thing from the graph.
- /// Is this is a root graph, and thing is a node, its edges are delete too.
- ///
- public void Delete(CGraphThing thing)
- {
- int return_code = Agdelete(_ptr, thing._ptr);
- Debug.Assert(return_code == 0);
- }
+ ///
+ /// Delete the given thing from the graph.
+ /// Is this is a root graph, and thing is a node, its edges are delete too.
+ ///
+ public void Delete(CGraphThing thing)
+ {
+ int return_code = Agdelete(_ptr, thing._ptr);
+ Debug.Assert(return_code == 0);
+ }
- public IEnumerable Nodes()
+ public IEnumerable Nodes()
+ {
+ var current = Agfstnode(_ptr);
+ while (current != IntPtr.Zero)
{
- var current = Agfstnode(_ptr);
- while (current != IntPtr.Zero)
- {
- yield return new Node(current, MyRootGraph);
- current = Agnxtnode(_ptr, current);
- }
+ yield return new Node(current, MyRootGraph);
+ current = Agnxtnode(_ptr, current);
}
+ }
- public IEnumerable Edges()
- {
- return Nodes().SelectMany(n => n.EdgesOut());
- }
+ public IEnumerable Edges()
+ {
+ return Nodes().SelectMany(n => n.EdgesOut());
+ }
- public IEnumerable Children()
+ public IEnumerable Children()
+ {
+ var current = Agfstsubg(_ptr);
+ while (current != IntPtr.Zero)
{
- var current = Agfstsubg(_ptr);
- while (current != IntPtr.Zero)
- {
- yield return new SubGraph(current, MyRootGraph);
- current = Agnxtsubg(current);
- }
+ yield return new SubGraph(current, MyRootGraph);
+ current = Agnxtsubg(current);
}
+ }
- ///
- /// Return all subgraphs recursively in a depthfirst order.
- /// Don't delete subgraphs while iterating. Instead, use the method SafeDeleteSubgraphs.
- ///
- public IEnumerable Descendants()
+ ///
+ /// Return all subgraphs recursively in a depthfirst order.
+ /// Don't delete subgraphs while iterating. Instead, use the method SafeDeleteSubgraphs.
+ ///
+ public IEnumerable Descendants()
+ {
+ foreach (var child in Children())
{
- foreach (var child in Children())
- {
- yield return child;
- foreach (SubGraph descendant in child.Descendants())
- yield return descendant;
- }
+ yield return child;
+ foreach (SubGraph descendant in child.Descendants())
+ yield return descendant;
}
+ }
- public Graph Parent()
- {
- IntPtr p = Agparent(_ptr);
- if (p == IntPtr.Zero)
- return null;
- return new Graph(p, MyRootGraph);
- }
+ public Graph Parent()
+ {
+ IntPtr p = Agparent(_ptr);
+ if (p == IntPtr.Zero)
+ return null;
+ return new Graph(p, MyRootGraph);
+ }
- public IEnumerable GetNodesByAttribute(string attr_name, string attr_value)
- {
- return Nodes().Where(n => n.GetAttribute(attr_name) == attr_value);
- }
+ public IEnumerable GetNodesByAttribute(string attr_name, string attr_value)
+ {
+ return Nodes().Where(n => n.GetAttribute(attr_name) == attr_value);
+ }
- public SubGraph GetDescendantByName(string name)
- {
- return Descendants().FirstOrDefault(d => d.GetName() == name);
- }
+ public SubGraph GetDescendantByName(string name)
+ {
+ return Descendants().FirstOrDefault(d => d.GetName() == name);
+ }
- public SubGraph GetOrAddSubgraph(string name)
- {
- return SubGraph.GetOrCreate(this, name);
- }
+ public SubGraph GetOrAddSubgraph(string name)
+ {
+ return SubGraph.GetOrCreate(this, name);
+ }
- public SubGraph GetSubgraph(string name)
- {
- return SubGraph.Get(this, name);
- }
+ public SubGraph GetSubgraph(string name)
+ {
+ return SubGraph.Get(this, name);
+ }
- public Node GetOrAddNode(string name)
- {
- return Node.GetOrCreate(this, name);
- }
+ public Node GetOrAddNode(string name)
+ {
+ return Node.GetOrCreate(this, name);
+ }
- public Node GetNode(string name)
- {
- return Node.Get(this, name);
- }
+ public Node GetNode(string name)
+ {
+ return Node.Get(this, name);
+ }
- ///
- /// Passing null as edge name (the default) will result in a new unique edge without a name.
- /// Passing the empty string has the same effect as passing null.
- ///
- public Edge GetOrAddEdge(Node tail, Node head, string name = null)
- {
- return Edge.GetOrCreate(this, tail, head, name);
- }
+ ///
+ /// Passing null as edge name (the default) will result in a new unique edge without a name.
+ /// Passing the empty string has the same effect as passing null.
+ ///
+ public Edge GetOrAddEdge(Node tail, Node head, string name = null)
+ {
+ return Edge.GetOrCreate(this, tail, head, name);
+ }
- ///
- /// Passing null as edge name will return any edge between the given endpoints, regardless of their name.
- /// Passing the empty string has the same effect as passing null.
- ///
- public Edge GetEdge(Node tail, Node head, string name = null)
- {
- return Edge.Get(this, tail, head, name);
- }
+ ///
+ /// Passing null as edge name will return any edge between the given endpoints, regardless of their name.
+ /// Passing the empty string has the same effect as passing null.
+ ///
+ public Edge GetEdge(Node tail, Node head, string name = null)
+ {
+ return Edge.Get(this, tail, head, name);
+ }
- ///Return an edge between two nodes, disregarding it's direction.
- ///
- /// Passing null as edge name will return any edge between the given endpoints, regardless of their name.
- /// Passing the empty string has the same effect as passing null.
- ///
- public Edge GetEdgeBetween(Node node1, Node node2, string name = null)
- {
- return Edge.Get(this, node1, node2, name) ?? Edge.Get(this, node2, node1, name);
- }
+ ///Return an edge between two nodes, disregarding it's direction.
+ ///
+ /// Passing null as edge name will return any edge between the given endpoints, regardless of their name.
+ /// Passing the empty string has the same effect as passing null.
+ ///
+ public Edge GetEdgeBetween(Node node1, Node node2, string name = null)
+ {
+ return Edge.Get(this, node1, node2, name) ?? Edge.Get(this, node2, node1, name);
+ }
- public IEnumerable GetEdgesBetween(Node node1, Node node2)
- {
- return node1.Edges().Where(edge => edge.IsBetween(node1, node2));
- }
+ public IEnumerable GetEdgesBetween(Node node1, Node node2)
+ {
+ return node1.Edges().Where(edge => edge.IsBetween(node1, node2));
+ }
- public override string ToString()
- {
- return $"Graph {GetName()} with {Nodes().Count()} nodes.";
- }
+ public override string ToString()
+ {
+ return $"Graph {GetName()} with {Nodes().Count()} nodes.";
+ }
- ///
- /// Attributes with the empty string as default are not correctly exported.
- /// https://gitlab.com/graphviz/graphviz/-/issues/1887
- ///
- public string ToDotString()
- {
- return Rjagmemwrite(_ptr);
- }
+ ///
+ /// Attributes with the empty string as default are not correctly exported.
+ /// https://gitlab.com/graphviz/graphviz/-/issues/1887
+ ///
+ public string ToDotString()
+ {
+ return Rjagmemwrite(_ptr);
+ }
- ///
- /// Attributes with the empty string as default are not correctly exported.
- /// https://gitlab.com/graphviz/graphviz/-/issues/1887
- ///
- public void ToDotFile(string filename)
- {
- File.WriteAllText(filename, ToDotString());
- }
+ ///
+ /// Attributes with the empty string as default are not correctly exported.
+ /// https://gitlab.com/graphviz/graphviz/-/issues/1887
+ ///
+ public void ToDotFile(string filename)
+ {
+ File.WriteAllText(filename, ToDotString());
+ }
- ///
- /// Create and return a subgraph containing the given edges and their endpoints.
- ///
- public SubGraph AddSubgraphFromEdgeSet(string name, HashSet edges)
- {
- var result = SubGraph.GetOrCreate(this, name);
- result.AddExisting(edges);
- // Since subgraphs can contain edges independently of their endpoints,
- // we need to add the endpoints explicitly.
- result.AddExisting(edges.SelectMany(e => new[] { e.Tail(), e.Head() }));
- return result;
+ ///
+ /// Create and return a subgraph containing the given edges and their endpoints.
+ ///
+ public SubGraph AddSubgraphFromEdgeSet(string name, HashSet edges)
+ {
+ var result = SubGraph.GetOrCreate(this, name);
+ result.AddExisting(edges);
+ // Since subgraphs can contain edges independently of their endpoints,
+ // we need to add the endpoints explicitly.
+ result.AddExisting(edges.SelectMany(e => new[] { e.Tail(), e.Head() }));
+ return result;
+ }
+
+ ///
+ /// Create a subgraph consisting of nodes from the given nodes.
+ /// Edges are added to the result if both endpoints are among the nodes.
+ /// Subgraphs are added to the result if they have nodes in the given nodelist.
+ /// The names of the Subgraphs are of the form "name:subgraphname".
+ ///
+ /// Side effect: adds the returned subgraph (and its children) to self.
+ ///
+ public SubGraph AddSubgraphFromNodes(string name, IEnumerable nodes)
+ {
+ // Freeze the list of descendants,
+ // since we are going to add subgraphs while iterating over existing subgraphs
+ List descendants = Descendants().ToList();
+
+ SubGraph result = GetOrAddSubgraph(name);
+ foreach (var node in nodes)
+ result.AddExisting(node);
+
+ Debug.Assert(result.Nodes().Count() == nodes.Count());
+
+ // All that remains to do is to patch up the result by adding edges and subgraphs
+ foreach (var node in result.Nodes())
+ foreach (var edge in node.EdgesOut(this))
+ if (result.Contains(edge.Head()))
+ result.AddExisting(edge);
+
+ Debug.Assert(result.Nodes().Count() == nodes.Count());
+
+ // Iterate over the (frozen) existing subgraphs and add new filtered subgraphs
+ // in the same hierarchical position as their unfiltered counterparts.
+ foreach (var subgraph in descendants)
+ {
+ string filteredsubgraphname = name + ":" + subgraph.GetName();
+ Debug.WriteLine("Adding filtered subgraph {0}", filteredsubgraphname);
+ Graph parent = subgraph.Parent();
+ Graph filteredparent;
+ if (parent.Equals(this))
+ filteredparent = result;
+ else
+ {
+ string parentname = name + ":" + parent.GetName();
+ filteredparent = result.GetDescendantByName(parentname);
+ Debug.Assert(filteredparent != null);
+ }
+
+ _ = filteredparent.AddSubgraphFilteredByNodes(filteredsubgraphname, subgraph, nodes);
}
- ///
- /// Create a subgraph consisting of nodes from the given nodes.
- /// Edges are added to the result if both endpoints are among the nodes.
- /// Subgraphs are added to the result if they have nodes in the given nodelist.
- /// The names of the Subgraphs are of the form "name:subgraphname".
- ///
- /// Side effect: adds the returned subgraph (and its children) to self.
- ///
- public SubGraph AddSubgraphFromNodes(string name, IEnumerable nodes)
- {
- // Freeze the list of descendants,
- // since we are going to add subgraphs while iterating over existing subgraphs
- List descendants = Descendants().ToList();
+ Debug.Assert(result.Nodes().Count() == nodes.Count());
- SubGraph result = GetOrAddSubgraph(name);
- foreach (var node in nodes)
- result.AddExisting(node);
+ // Remove subgraphs again if they are empty
+ // Again, we have to freeze the descendants we are enumerating, since we are disposing on the fly
+ result.SafeDeleteSubgraphs(s => !s.Nodes().Any());
- Debug.Assert(result.Nodes().Count() == nodes.Count());
+ Debug.Assert(result.Nodes().Count() == nodes.Count());
- // All that remains to do is to patch up the result by adding edges and subgraphs
- foreach (var node in result.Nodes())
- foreach (var edge in node.EdgesOut(this))
- if (result.Contains(edge.Head()))
- result.AddExisting(edge);
+ return result;
+ }
- Debug.Assert(result.Nodes().Count() == nodes.Count());
+ ///
+ /// Delete all subgraphs in self fulfilling the predicate, without running into AccessViolationExceptions.
+ ///
+ public void SafeDeleteSubgraphs(Func predicate)
+ {
+ // Everytime we delete something, we restart the loop.
+ // This is not efficient, but easy to implement for now.
+ bool work_to_do = true;
- // Iterate over the (frozen) existing subgraphs and add new filtered subgraphs
- // in the same hierarchical position as their unfiltered counterparts.
- foreach (var subgraph in descendants)
+ while (work_to_do)
+ {
+ work_to_do = false;
+ foreach (var subgraph in Descendants())
{
- string filteredsubgraphname = name + ":" + subgraph.GetName();
- Debug.WriteLine("Adding filtered subgraph {0}", filteredsubgraphname);
- Graph parent = subgraph.Parent();
- Graph filteredparent;
- if (parent.Equals(this))
- filteredparent = result;
- else
- {
- string parentname = name + ":" + parent.GetName();
- filteredparent = result.GetDescendantByName(parentname);
- Debug.Assert(filteredparent != null);
- }
-
- _ = filteredparent.AddSubgraphFilteredByNodes(filteredsubgraphname, subgraph, nodes);
+ if (!predicate(subgraph)) continue;
+ subgraph.Delete();
+ work_to_do = true;
+ break;
}
+ }
+ }
- Debug.Assert(result.Nodes().Count() == nodes.Count());
-
- // Remove subgraphs again if they are empty
- // Again, we have to freeze the descendants we are enumerating, since we are disposing on the fly
- result.SafeDeleteSubgraphs(s => !s.Nodes().Any());
+ ///
+ /// Add a plain subgraph with given name to self, containing the nodes that occur both in origin
+ /// and the filter. In other words, filter the origin subgraph by the filter subgraph on node-level.
+ /// Attributes are copied from origin to the result.
+ ///
+ /// Side effect: adds the returned subgraph to self.
+ ///
+ public SubGraph AddSubgraphFilteredByNodes(string name, SubGraph origin, IEnumerable filter)
+ {
+ SubGraph result = GetOrAddSubgraph(name);
+ foreach (var node in origin.Nodes().Where(filter.Contains))
+ result.AddExisting(node);
- Debug.Assert(result.Nodes().Count() == nodes.Count());
+ _ = origin.CopyAttributesTo(result);
+ return result;
+ }
- return result;
- }
+ ///
+ /// Create a deepcopy of the graph as a new root graph.
+ /// All nodes, edges and subgraphs contained in self are copied.
+ ///
+ /// No side effects to self.
+ ///
+ ///
+ public RootGraph Clone(string resultname)
+ {
+ RootGraph result = RootGraph.CreateNew(GetGraphType(), resultname);
+ _ = CopyAttributesTo(result);
+ CloneInto(result);
+ result.UpdateMemoryPressure();
+ return result;
+ }
- ///
- /// Delete all subgraphs in self fulfilling the predicate, without running into AccessViolationExceptions.
- ///
- public void SafeDeleteSubgraphs(Func predicate)
+ public void CloneInto(RootGraph target)
+ {
+ // Copy all nodes and edges
+ foreach (var node in Nodes())
{
- // Everytime we delete something, we restart the loop.
- // This is not efficient, but easy to implement for now.
- bool work_to_do = true;
+ string nodename = node.GetName();
+ Node newnode = target.GetOrAddNode(nodename);
- while (work_to_do)
+ foreach (var edge in node.EdgesOut(this))
{
- work_to_do = false;
- foreach (var subgraph in Descendants())
- {
- if (!predicate(subgraph)) continue;
- subgraph.Delete();
- work_to_do = true;
- break;
- }
+ Node head = edge.Head();
+ Debug.Assert(Contains(head));
+ Node tail = edge.Tail();
+ Debug.Assert(node.Equals(tail));
+ string headname = head.GetName();
+ Node newhead = target.GetOrAddNode(headname);
+ string tailname = tail.GetName();
+ Node newtail = target.GetNode(tailname);
+
+ string edgename = edge.GetName();
+ Edge newedge = target.GetOrAddEdge(newtail, newhead, edgename);
+ _ = edge.CopyAttributesTo(newedge);
}
+ _ = node.CopyAttributesTo(newnode);
}
- ///
- /// Add a plain subgraph with given name to self, containing the nodes that occur both in origin
- /// and the filter. In other words, filter the origin subgraph by the filter subgraph on node-level.
- /// Attributes are copied from origin to the result.
- ///
- /// Side effect: adds the returned subgraph to self.
- ///
- public SubGraph AddSubgraphFilteredByNodes(string name, SubGraph origin, IEnumerable filter)
+ // Copy all subgraphs
+ foreach (var subgraph in Descendants())
{
- SubGraph result = GetOrAddSubgraph(name);
- foreach (var node in origin.Nodes().Where(filter.Contains))
- result.AddExisting(node);
-
- _ = origin.CopyAttributesTo(result);
- return result;
- }
-
- ///
- /// Create a deepcopy of the graph as a new root graph.
- /// All nodes, edges and subgraphs contained in self are copied.
- ///
- /// No side effects to self.
- ///
- ///
- public RootGraph Clone(string resultname)
- {
- RootGraph result = RootGraph.CreateNew(GetGraphType(), resultname);
- _ = CopyAttributesTo(result);
- CloneInto(result);
- result.UpdateMemoryPressure();
- return result;
- }
+ string subgraphname = subgraph.GetName();
+ Graph parent = subgraph.Parent();
+ Graph newparent;
+ if (parent.Equals(this))
+ newparent = target;
+ else
+ {
+ string parentname = parent.GetName();
+ newparent = target.GetDescendantByName(parentname);
+ Debug.Assert(newparent != null);
+ }
+ SubGraph newsubgraph = newparent.GetOrAddSubgraph(subgraphname);
+ _ = subgraph.CopyAttributesTo(newsubgraph);
- public void CloneInto(RootGraph target)
- {
- // Copy all nodes and edges
- foreach (var node in Nodes())
+ // Add the (already created) nodes and edges to newly created subgraph
+ foreach (var node in subgraph.Nodes())
{
string nodename = node.GetName();
- Node newnode = target.GetOrAddNode(nodename);
+ Node newnode = target.GetNode(nodename);
+ Debug.Assert(newnode != null);
+ newsubgraph.AddExisting(newnode);
- foreach (var edge in node.EdgesOut(this))
+ foreach (var edge in node.EdgesOut(subgraph))
{
Node head = edge.Head();
- Debug.Assert(Contains(head));
Node tail = edge.Tail();
Debug.Assert(node.Equals(tail));
+
string headname = head.GetName();
- Node newhead = target.GetOrAddNode(headname);
+ Node newhead = target.GetNode(headname);
string tailname = tail.GetName();
Node newtail = target.GetNode(tailname);
string edgename = edge.GetName();
- Edge newedge = target.GetOrAddEdge(newtail, newhead, edgename);
- _ = edge.CopyAttributesTo(newedge);
+ Edge newedge = target.GetEdge(newtail, newhead, edgename);
+ newsubgraph.AddExisting(newedge);
}
_ = node.CopyAttributesTo(newnode);
}
-
- // Copy all subgraphs
- foreach (var subgraph in Descendants())
- {
- string subgraphname = subgraph.GetName();
- Graph parent = subgraph.Parent();
- Graph newparent;
- if (parent.Equals(this))
- newparent = target;
- else
- {
- string parentname = parent.GetName();
- newparent = target.GetDescendantByName(parentname);
- Debug.Assert(newparent != null);
- }
- SubGraph newsubgraph = newparent.GetOrAddSubgraph(subgraphname);
- _ = subgraph.CopyAttributesTo(newsubgraph);
-
- // Add the (already created) nodes and edges to newly created subgraph
- foreach (var node in subgraph.Nodes())
- {
- string nodename = node.GetName();
- Node newnode = target.GetNode(nodename);
- Debug.Assert(newnode != null);
- newsubgraph.AddExisting(newnode);
-
- foreach (var edge in node.EdgesOut(subgraph))
- {
- Node head = edge.Head();
- Node tail = edge.Tail();
- Debug.Assert(node.Equals(tail));
-
- string headname = head.GetName();
- Node newhead = target.GetNode(headname);
- string tailname = tail.GetName();
- Node newtail = target.GetNode(tailname);
-
- string edgename = edge.GetName();
- Edge newedge = target.GetEdge(newtail, newhead, edgename);
- newsubgraph.AddExisting(newedge);
- }
- _ = node.CopyAttributesTo(newnode);
- }
- }
}
+ }
- ///
- /// Contract an edge into a newly created node with given target name.
- /// The attributes of the endpoints are merged and copied to the target node,
- /// with head attributes taking precedence over tail attributes.
- ///
- /// The end points of the given edge are removed, as well as the edge itself.
- /// Then all the neighbours of both endpoints are attached to the target,
- /// preserving direction and attributes.
- /// The new edges will be added to the root graph.
- /// If the graph is strict, no multiple edges will be added between nodes.
- ///
- /// target
- public Node Contract(Edge edge, string targetname)
- {
- return Contract(edge.Head(), edge.Tail(), targetname);
- }
+ ///
+ /// Contract an edge into a newly created node with given target name.
+ /// The attributes of the endpoints are merged and copied to the target node,
+ /// with head attributes taking precedence over tail attributes.
+ ///
+ /// The end points of the given edge are removed, as well as the edge itself.
+ /// Then all the neighbours of both endpoints are attached to the target,
+ /// preserving direction and attributes.
+ /// The new edges will be added to the root graph.
+ /// If the graph is strict, no multiple edges will be added between nodes.
+ ///
+ /// target
+ public Node Contract(Edge edge, string targetname)
+ {
+ return Contract(edge.Head(), edge.Tail(), targetname);
+ }
- ///
- /// Perform a node contraction (also: node identification) on two nodes.
- /// The resulting node will have targetname as name.
- /// The attributes of the endpoints are merged and copied to the target node,
- /// with head attributes taking precedence over tail attributes.
- ///
- /// Both node1 and node2 will be removed from the graph.
- /// Then all the neighbours of both endpoints are attached to the target,
- /// preserving direction and attributes.
- /// The new edges will be added to the root graph.
- /// If the graph is strict, no multiple edges will be added between nodes.
- ///
- /// target
- public Node Contract(Node node1, Node node2, string targetname)
- {
- Node target = MyRootGraph.GetOrAddNode(targetname);
- _ = node1.CopyAttributesTo(target);
- _ = node2.CopyAttributesTo(target);
- Merge(node1, target);
- Merge(node2, target);
- return target;
- }
+ ///
+ /// Perform a node contraction (also: node identification) on two nodes.
+ /// The resulting node will have targetname as name.
+ /// The attributes of the endpoints are merged and copied to the target node,
+ /// with head attributes taking precedence over tail attributes.
+ ///
+ /// Both node1 and node2 will be removed from the graph.
+ /// Then all the neighbours of both endpoints are attached to the target,
+ /// preserving direction and attributes.
+ /// The new edges will be added to the root graph.
+ /// If the graph is strict, no multiple edges will be added between nodes.
+ ///
+ /// target
+ public Node Contract(Node node1, Node node2, string targetname)
+ {
+ Node target = MyRootGraph.GetOrAddNode(targetname);
+ _ = node1.CopyAttributesTo(target);
+ _ = node2.CopyAttributesTo(target);
+ Merge(node1, target);
+ Merge(node2, target);
+ return target;
+ }
- ///
- /// Merge a node into a target node.
- /// Basically, add the neighborhood of the node to the neighborhood of the target.
- /// The merge node will be removed from the graph.
- /// The new edges will be added to the root graph.
- ///
- /// If the graph is strict, no multiple edges will be added between nodes.
- ///
- /// If add_self_loops is true, edges between the merge node and the target node will be
- /// added as self loops to the target node. Self loops that already exist as such are always added.
- ///
- public void Merge(Node merge, Node target, bool add_self_loops = false)
- {
- // .Edges() won't iterate twice over self loops
- foreach (var e in merge.Edges())
- if (!e.IsBetween(merge, target) || add_self_loops) // Only add self loops if we want that
- {
- Node newtail = e.Tail().Equals(merge) ? target : e.Tail();
- Node newhead = e.Head().Equals(merge) ? target : e.Head();
- Edge newedge = MyRootGraph.GetOrAddEdge(newtail, newhead, e.GetName());
- int returncode = e.CopyAttributesTo(newedge);
- // For some reason this may fail, even when the copying seems to have succeeded.
- Debug.Assert(returncode == 0);
- }
+ ///
+ /// Merge a node into a target node.
+ /// Basically, add the neighborhood of the node to the neighborhood of the target.
+ /// The merge node will be removed from the graph.
+ /// The new edges will be added to the root graph.
+ ///
+ /// If the graph is strict, no multiple edges will be added between nodes.
+ ///
+ /// If add_self_loops is true, edges between the merge node and the target node will be
+ /// added as self loops to the target node. Self loops that already exist as such are always added.
+ ///
+ public void Merge(Node merge, Node target, bool add_self_loops = false)
+ {
+ // .Edges() won't iterate twice over self loops
+ foreach (var e in merge.Edges())
+ if (!e.IsBetween(merge, target) || add_self_loops) // Only add self loops if we want that
+ {
+ Node newtail = e.Tail().Equals(merge) ? target : e.Tail();
+ Node newhead = e.Head().Equals(merge) ? target : e.Head();
+ Edge newedge = MyRootGraph.GetOrAddEdge(newtail, newhead, e.GetName());
+ int returncode = e.CopyAttributesTo(newedge);
+ // For some reason this may fail, even when the copying seems to have succeeded.
+ Debug.Assert(returncode == 0);
+ }
- // The following will delete all edges connected to merge.
- MyRootGraph.Delete(merge);
- }
+ // The following will delete all edges connected to merge.
+ MyRootGraph.Delete(merge);
+ }
- public bool IsCluster()
- {
- return GetName().StartsWith("cluster");
- }
+ public bool IsCluster()
+ {
+ return GetName().StartsWith("cluster");
+ }
- ///
- /// Must be true for logical tails/heads to be used in drawing.
- ///
- public bool IsCompound()
- {
- return GetAttribute("compound") == "true";
- }
+ ///
+ /// Must be true for logical tails/heads to be used in drawing.
+ ///
+ public bool IsCompound()
+ {
+ return GetAttribute("compound") == "true";
+ }
- public SubGraph GetOrCreateCluster(string name)
- {
- string clustername = "cluster_" + name;
- SubGraph gvCluster = GetOrAddSubgraph(clustername);
- return gvCluster;
- }
+ public SubGraph GetOrCreateCluster(string name)
+ {
+ string clustername = "cluster_" + name;
+ SubGraph gvCluster = GetOrAddSubgraph(clustername);
+ return gvCluster;
+ }
- private static int _dummyNodeIdCounter = 0;
- private int GetNextDummyNodeId()
- {
- return _dummyNodeIdCounter++;
- }
+ private static int _dummyNodeIdCounter = 0;
+ private int GetNextDummyNodeId()
+ {
+ return _dummyNodeIdCounter++;
+ }
- public Node CreateInvisibleDummyNode()
- {
- var result = GetOrAddNode("dummynode-" + GetNextDummyNodeId().ToString());
- result.MakeInvisible();
- return result;
- }
+ public Node CreateInvisibleDummyNode()
+ {
+ var result = GetOrAddNode("dummynode-" + GetNextDummyNodeId().ToString());
+ result.MakeInvisible();
+ return result;
+ }
- public Node CreateSmallInvisibleDummyNode()
- {
- var result = GetOrAddNode("dummynode-" + GetNextDummyNodeId().ToString());
- result.MakeInvisibleAndSmall();
- return result;
- }
+ public Node CreateSmallInvisibleDummyNode()
+ {
+ var result = GetOrAddNode("dummynode-" + GetNextDummyNodeId().ToString());
+ result.MakeInvisibleAndSmall();
+ return result;
+ }
- ///
- /// Creates an invisble dummy node as landingpoint for the cluster.
- ///
- public Edge GetOrAddEdge(Node gvNode, SubGraph gvCluster, bool makeLandingSpace, string edgeName)
- {
- // If there are any edges to a cluster, we need the an invisble dummy node as endpoint,
- // because Graphviz does not support edges to clusters. We make it invisible but still
- // take it up some space because there needs to be space for the edge to land on the
- // cluster. Otherwise the edge will overlap with other edges too much, because if the
- // invisible node takes no space it will be squeezed against another node.
- Node invisibleHead;
- if (makeLandingSpace)
- invisibleHead = gvCluster.CreateInvisibleDummyNode();
- else
- invisibleHead = gvCluster.CreateSmallInvisibleDummyNode();
- var edge = GetOrAddEdge(gvNode, invisibleHead, edgeName);
- edge.SetLogicalHead(gvCluster);
- return edge;
- }
+ ///
+ /// Creates an invisble dummy node as landingpoint for the cluster.
+ ///
+ public Edge GetOrAddEdge(Node gvNode, SubGraph gvCluster, bool makeLandingSpace, string edgeName)
+ {
+ // If there are any edges to a cluster, we need the an invisble dummy node as endpoint,
+ // because Graphviz does not support edges to clusters. We make it invisible but still
+ // take it up some space because there needs to be space for the edge to land on the
+ // cluster. Otherwise the edge will overlap with other edges too much, because if the
+ // invisible node takes no space it will be squeezed against another node.
+ Node invisibleHead;
+ if (makeLandingSpace)
+ invisibleHead = gvCluster.CreateInvisibleDummyNode();
+ else
+ invisibleHead = gvCluster.CreateSmallInvisibleDummyNode();
+ var edge = GetOrAddEdge(gvNode, invisibleHead, edgeName);
+ edge.SetLogicalHead(gvCluster);
+ return edge;
+ }
- ///
- /// Creates an invisble dummy node as landingpoint for the cluster.
- ///
- public Edge GetOrAddEdge(SubGraph gvCluster, Node gvNode, bool makeLandingSpace, string edgeName)
- {
- Node invisibleTail;
- if (makeLandingSpace)
- invisibleTail = gvCluster.CreateInvisibleDummyNode();
- else
- invisibleTail = gvCluster.CreateSmallInvisibleDummyNode();
- var edge = GetOrAddEdge(invisibleTail, gvNode, edgeName);
- edge.SetLogicalTail(gvCluster);
- return edge;
- }
+ ///
+ /// Creates an invisble dummy node as landingpoint for the cluster.
+ ///
+ public Edge GetOrAddEdge(SubGraph gvCluster, Node gvNode, bool makeLandingSpace, string edgeName)
+ {
+ Node invisibleTail;
+ if (makeLandingSpace)
+ invisibleTail = gvCluster.CreateInvisibleDummyNode();
+ else
+ invisibleTail = gvCluster.CreateSmallInvisibleDummyNode();
+ var edge = GetOrAddEdge(invisibleTail, gvNode, edgeName);
+ edge.SetLogicalTail(gvCluster);
+ return edge;
+ }
- ///
- /// Creates an invisble dummy node as landingpoint for the cluster.
- ///
- public Edge GetOrAddEdge(SubGraph gvClusterTail, SubGraph gvClusterHead, bool makeLandingSpace, string edgeName)
+ ///
+ /// Creates an invisble dummy node as landingpoint for the cluster.
+ ///
+ public Edge GetOrAddEdge(SubGraph gvClusterTail, SubGraph gvClusterHead, bool makeLandingSpace, string edgeName)
+ {
+ Node invisibleTail;
+ Node invisibleHead;
+ if (makeLandingSpace)
{
- Node invisibleTail;
- Node invisibleHead;
- if (makeLandingSpace)
- {
- invisibleTail = gvClusterTail.CreateInvisibleDummyNode();
- invisibleHead = gvClusterHead.CreateInvisibleDummyNode();
- }
- else
- {
- invisibleTail = gvClusterTail.CreateSmallInvisibleDummyNode();
- invisibleHead = gvClusterHead.CreateSmallInvisibleDummyNode();
- }
- var edge = GetOrAddEdge(invisibleTail, invisibleHead, edgeName);
- edge.SetLogicalTail(gvClusterTail);
- edge.SetLogicalHead(gvClusterHead);
- return edge;
+ invisibleTail = gvClusterTail.CreateInvisibleDummyNode();
+ invisibleHead = gvClusterHead.CreateInvisibleDummyNode();
}
-
- #region layout functions and attributes
-
- ///
- /// Compute the layout in a separate process by calling dot.exe, and return a new graph, which is a copy of the old
- /// graph with the xdot information added to it.
- ///
- public RootGraph CreateLayout(string engine = LayoutEngines.Dot)
+ else
{
- return GraphvizCommand.CreateLayout(this, engine: engine);
+ invisibleTail = gvClusterTail.CreateSmallInvisibleDummyNode();
+ invisibleHead = gvClusterHead.CreateSmallInvisibleDummyNode();
}
+ var edge = GetOrAddEdge(invisibleTail, invisibleHead, edgeName);
+ edge.SetLogicalTail(gvClusterTail);
+ edge.SetLogicalHead(gvClusterHead);
+ return edge;
+ }
- public RectangleF GetBoundingBox()
- {
- string bb_string = Agget(_ptr, "bb");
- if (string.IsNullOrEmpty(bb_string))
- return default;
- // x and y are the topleft point of the bb
- char sep = ',';
- string[] bb = bb_string.Split(sep);
- float x = float.Parse(bb[0], NumberStyles.Any, CultureInfo.InvariantCulture);
- float y = float.Parse(bb[1], NumberStyles.Any, CultureInfo.InvariantCulture);
- float w = float.Parse(bb[2], NumberStyles.Any, CultureInfo.InvariantCulture) - x;
- float h = float.Parse(bb[3], NumberStyles.Any, CultureInfo.InvariantCulture) - y;
- return new RectangleF(x, y, w, h);
- }
+ #region layout functions and attributes
- public IReadOnlyList GetDrawing() => GetXDotValue(this, "_draw_");
- public IReadOnlyList GetLabelDrawing() => GetXDotValue(this, "_ldraw_");
+ ///
+ /// Compute the layout in a separate process by calling dot.exe, and return a new graph, which is a copy of the old
+ /// graph with the xdot information added to it.
+ ///
+ public RootGraph CreateLayout(string engine = LayoutEngines.Dot)
+ {
+ return GraphvizCommand.CreateLayout(this, engine: engine);
+ }
- private void ToFile(string filepath, string format, string engine)
- {
- _ = GraphvizCommand.Exec(this, format: format, filepath, engine: engine);
- }
+ public RectangleF GetBoundingBox()
+ {
+ string bb_string = Agget(_ptr, "bb");
+ if (string.IsNullOrEmpty(bb_string))
+ return default;
+ // x and y are the topleft point of the bb
+ char sep = ',';
+ string[] bb = bb_string.Split(sep);
+ float x = float.Parse(bb[0], NumberStyles.Any, CultureInfo.InvariantCulture);
+ float y = float.Parse(bb[1], NumberStyles.Any, CultureInfo.InvariantCulture);
+ float w = float.Parse(bb[2], NumberStyles.Any, CultureInfo.InvariantCulture) - x;
+ float h = float.Parse(bb[3], NumberStyles.Any, CultureInfo.InvariantCulture) - y;
+ return new RectangleF(x, y, w, h);
+ }
- public void ToSvgFile(string filepath, string engine = LayoutEngines.Dot) => ToFile(filepath, "svg", engine);
- public void ToPngFile(string filepath, string engine = LayoutEngines.Dot) => ToFile(filepath, "png", engine);
- public void ToPdfFile(string filepath, string engine = LayoutEngines.Dot) => ToFile(filepath, "pdf", engine);
- public void ToPsFile(string filepath, string engine = LayoutEngines.Dot) => ToFile(filepath, "ps", engine);
- #endregion
+ public IReadOnlyList GetDrawing() => GetXDotValue(this, "_draw_");
+ public IReadOnlyList GetLabelDrawing() => GetXDotValue(this, "_ldraw_");
+ private void ToFile(string filepath, string format, string engine)
+ {
+ _ = GraphvizCommand.Exec(this, format: format, filepath, engine: engine);
+ }
- #region in-place layout computation
+ public void ToSvgFile(string filepath, string engine = LayoutEngines.Dot) => ToFile(filepath, "svg", engine);
+ public void ToPngFile(string filepath, string engine = LayoutEngines.Dot) => ToFile(filepath, "png", engine);
+ public void ToPdfFile(string filepath, string engine = LayoutEngines.Dot) => ToFile(filepath, "pdf", engine);
+ public void ToPsFile(string filepath, string engine = LayoutEngines.Dot) => ToFile(filepath, "ps", engine);
+ #endregion
- ///
- /// Compute a layout for this graph, in-process, on the given graph.
- /// It is recommended to use instead, as that comes with less footguns and a better API.
- /// Moreover, experience shows it is less likely to trip over lingering graphviz bugs as well.
- /// NB: The method FreeLayout should always be called as soon as the layout information
- /// of a graph is not needed anymore.
- ///
- public void ComputeLayout(string engine = LayoutEngines.Dot)
- {
- int layout_rc = GvLayout(GVC, _ptr, engine);
- if (layout_rc != 0)
- throw new ApplicationException($"Graphviz layout returned error code {layout_rc}");
-
- // Calling gvRender this way sets attributes to the graph etc
- // The engine specified here doesn't have to be the same as the above.
- // We always want to use xdot here, independently of the layout algorithm,
- // to ensure a consistent attribute layout.
- int render_rc = GvRender(GVC, _ptr, "xdot", IntPtr.Zero);
- if (render_rc != 0)
- throw new ApplicationException($"Graphviz render returned error code {render_rc}");
- }
- ///
- /// Clean up the layout information stored in this graph. This does not include the attributes set by GvRender.
- /// This method should always be called as soon as the layout information of a graph is not needed anymore.
- /// NB: this method must not be called after modifications to the graph have been made!
- /// This could result an AccessViolationException.
- ///
- public void FreeLayout()
- {
- var free_rc = GvFreeLayout(GVC, _ptr);
- if (free_rc != 0)
- throw new ApplicationException($"Graphviz render returned error code {free_rc}");
- }
+ #region in-place layout computation
- ///
- /// Should only be called after has been called.
- ///
- [Obsolete("This method is only available after ComputeLayout(), and may crash otherwise. It is obsoleted by the other ToXXXFile methods.")]
- public void RenderToFile(string filename, string format)
- {
- var render_rc = GvRenderFilename(GVC, _ptr, format, filename);
- if (render_rc != 0)
- throw new ApplicationException($"Graphviz render returned error code {render_rc}");
- }
+ ///
+ /// Compute a layout for this graph, in-process, on the given graph.
+ /// It is recommended to use instead, as that comes with less footguns and a better API.
+ /// Moreover, experience shows it is less likely to trip over lingering graphviz bugs as well.
+ /// NB: The method FreeLayout should always be called as soon as the layout information
+ /// of a graph is not needed anymore.
+ ///
+ public void ComputeLayout(string engine = LayoutEngines.Dot)
+ {
+ int layout_rc = GvLayout(GVC, _ptr, engine);
+ if (layout_rc != 0)
+ throw new ApplicationException($"Graphviz layout returned error code {layout_rc}");
+
+ // Calling gvRender this way sets attributes to the graph etc
+ // The engine specified here doesn't have to be the same as the above.
+ // We always want to use xdot here, independently of the layout algorithm,
+ // to ensure a consistent attribute layout.
+ int render_rc = GvRender(GVC, _ptr, "xdot", IntPtr.Zero);
+ if (render_rc != 0)
+ throw new ApplicationException($"Graphviz render returned error code {render_rc}");
+ }
- [Obsolete("This method is only available after ComputeLayout(), and may crash otherwise. It is obsoleted by GetLabelDrawing(). Refer to tutorial.")]
- public GraphvizLabel GetLabel()
- {
- IntPtr labelptr = GraphLabel(_ptr);
- if (labelptr == IntPtr.Zero)
- return null;
- return new GraphvizLabel(labelptr, BoundingBoxCoords.Centered);
- }
+ ///
+ /// Clean up the layout information stored in this graph. This does not include the attributes set by GvRender.
+ /// This method should always be called as soon as the layout information of a graph is not needed anymore.
+ /// NB: this method must not be called after modifications to the graph have been made!
+ /// This could result an AccessViolationException.
+ ///
+ public void FreeLayout()
+ {
+ var free_rc = GvFreeLayout(GVC, _ptr);
+ if (free_rc != 0)
+ throw new ApplicationException($"Graphviz render returned error code {free_rc}");
+ }
+
+ ///
+ /// Should only be called after has been called.
+ ///
+ [Obsolete("This method is only available after ComputeLayout(), and may crash otherwise. It is obsoleted by the other ToXXXFile methods.")]
+ public void RenderToFile(string filename, string format)
+ {
+ var render_rc = GvRenderFilename(GVC, _ptr, format, filename);
+ if (render_rc != 0)
+ throw new ApplicationException($"Graphviz render returned error code {render_rc}");
+ }
- #endregion
+ [Obsolete("This method is only available after ComputeLayout(), and may crash otherwise. It is obsoleted by GetLabelDrawing(). Refer to tutorial.")]
+ public GraphvizLabel GetLabel()
+ {
+ IntPtr labelptr = GraphLabel(_ptr);
+ if (labelptr == IntPtr.Zero)
+ return null;
+ return new GraphvizLabel(labelptr, BoundingBoxCoords.Centered);
}
+
+ #endregion
}
diff --git a/Rubjerg.Graphviz/GraphComparer.cs b/Rubjerg.Graphviz/GraphComparer.cs
index 77bfab2..6657d50 100644
--- a/Rubjerg.Graphviz/GraphComparer.cs
+++ b/Rubjerg.Graphviz/GraphComparer.cs
@@ -2,96 +2,95 @@
using System.Collections.Generic;
using System.Linq;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+public static class GraphComparer
{
- public static class GraphComparer
+ public static bool CheckTopologicallyEquals(Graph A, Graph B, Action logger)
{
- public static bool CheckTopologicallyEquals(Graph A, Graph B, Action logger)
- {
- logger($"Comparing graph A = '{A.GetName()}' with graph B = '{B.GetName()}'");
- logger("");
-
- bool result = true;
- var common_nodenames = new List();
- foreach (var node in A.Nodes())
- {
- var othernode = B.GetNode(node.GetName());
- if (othernode == null)
- {
- logger($"graph B does not contain node {node.GetName()}");
- result = false;
- continue;
- }
- common_nodenames.Add(node.GetName());
- }
+ logger($"Comparing graph A = '{A.GetName()}' with graph B = '{B.GetName()}'");
+ logger("");
- foreach (var node in B.Nodes())
+ bool result = true;
+ var common_nodenames = new List();
+ foreach (var node in A.Nodes())
+ {
+ var othernode = B.GetNode(node.GetName());
+ if (othernode == null)
{
- var othernode = A.GetNode(node.GetName());
- if (othernode == null)
- {
- logger($"graph A does not contain node {node.GetName()}");
- result = false;
- }
+ logger($"graph B does not contain node {node.GetName()}");
+ result = false;
+ continue;
}
+ common_nodenames.Add(node.GetName());
+ }
- foreach (var nodename in common_nodenames)
+ foreach (var node in B.Nodes())
+ {
+ var othernode = A.GetNode(node.GetName());
+ if (othernode == null)
{
- result &= CheckNode(A, B, A.GetNode(nodename), B.GetNode(nodename), logger);
+ logger($"graph A does not contain node {node.GetName()}");
+ result = false;
}
-
- logger("");
- logger($"A and B are {(result ? "" : "NOT")} topologically equivalent");
- return result;
}
- private static bool CheckNode(Graph A, Graph B, Node nA, Node nB, Action logger)
+ foreach (var nodename in common_nodenames)
{
- return InnerCheckNode(A, B, nA, nB, logger, "B") & InnerCheckNode(B, A, nA, nB, logger, "A");
+ result &= CheckNode(A, B, A.GetNode(nodename), B.GetNode(nodename), logger);
}
- private static bool InnerCheckNode(Graph A, Graph B, Node nA, Node nB, Action logger, string nameOfGraphOfNodeB)
+ logger("");
+ logger($"A and B are {(result ? "" : "NOT")} topologically equivalent");
+ return result;
+ }
+
+ private static bool CheckNode(Graph A, Graph B, Node nA, Node nB, Action logger)
+ {
+ return InnerCheckNode(A, B, nA, nB, logger, "B") & InnerCheckNode(B, A, nA, nB, logger, "A");
+ }
+
+ private static bool InnerCheckNode(Graph A, Graph B, Node nA, Node nB, Action logger, string nameOfGraphOfNodeB)
+ {
+ bool result = true;
+ foreach (var eA in nA.EdgesOut(A))
{
- bool result = true;
- foreach (var eA in nA.EdgesOut(A))
+ var expected_endpoint = eA.OppositeEndpoint(nA);
+ bool diff = false;
+ if (!nB.EdgesOut(B).Any(eB => CheckEdgeName(eA, eB)))
{
- var expected_endpoint = eA.OppositeEndpoint(nA);
- bool diff = false;
- if (!nB.EdgesOut(B).Any(eB => CheckEdgeName(eA, eB)))
- {
- logger($@"In graph {nameOfGraphOfNodeB} the node '{nB.GetName()}' does not have an outgoing edge with name '{eA.GetName()}'");
- result = false;
- diff = true;
- }
- if (!nB.EdgesOut(B).Any(eB => CheckEdgeEndpoints(eA, eB)))
- {
- logger($@"In graph {nameOfGraphOfNodeB} the node '{nB.GetName()}' does not have an outgoing edge with head '{expected_endpoint.GetName()}'");
- result = false;
- diff = true;
- }
- if (!diff && !nB.EdgesOut(B).Any(eB => CheckEdge(eA, eB)))
- {
- logger($@"In graph {nameOfGraphOfNodeB} the node '{nB.GetName()}' does not have an outgoing edge with **both** name '{eA.GetName()}' and head '{expected_endpoint.GetName()}'");
- result = false;
- }
+ logger($@"In graph {nameOfGraphOfNodeB} the node '{nB.GetName()}' does not have an outgoing edge with name '{eA.GetName()}'");
+ result = false;
+ diff = true;
+ }
+ if (!nB.EdgesOut(B).Any(eB => CheckEdgeEndpoints(eA, eB)))
+ {
+ logger($@"In graph {nameOfGraphOfNodeB} the node '{nB.GetName()}' does not have an outgoing edge with head '{expected_endpoint.GetName()}'");
+ result = false;
+ diff = true;
+ }
+ if (!diff && !nB.EdgesOut(B).Any(eB => CheckEdge(eA, eB)))
+ {
+ logger($@"In graph {nameOfGraphOfNodeB} the node '{nB.GetName()}' does not have an outgoing edge with **both** name '{eA.GetName()}' and head '{expected_endpoint.GetName()}'");
+ result = false;
}
- return result;
- }
-
- public static bool CheckEdge(Edge eA, Edge eB)
- {
- return CheckEdgeName(eA, eB) && CheckEdgeEndpoints(eA, eB);
}
+ return result;
+ }
- public static bool CheckEdgeName(Edge eA, Edge eB)
- {
- return eA.GetName() == eB.GetName();
- }
+ public static bool CheckEdge(Edge eA, Edge eB)
+ {
+ return CheckEdgeName(eA, eB) && CheckEdgeEndpoints(eA, eB);
+ }
- public static bool CheckEdgeEndpoints(Edge eA, Edge eB)
- {
- return eA.Head().GetName() == eB.Head().GetName() && eA.Tail().GetName() == eB.Tail().GetName();
- }
+ public static bool CheckEdgeName(Edge eA, Edge eB)
+ {
+ return eA.GetName() == eB.GetName();
+ }
+ public static bool CheckEdgeEndpoints(Edge eA, Edge eB)
+ {
+ return eA.Head().GetName() == eB.Head().GetName() && eA.Tail().GetName() == eB.Tail().GetName();
}
+
}
diff --git a/Rubjerg.Graphviz/GraphVizLabel.cs b/Rubjerg.Graphviz/GraphVizLabel.cs
index fc4588b..0a69b52 100644
--- a/Rubjerg.Graphviz/GraphVizLabel.cs
+++ b/Rubjerg.Graphviz/GraphVizLabel.cs
@@ -2,68 +2,67 @@
using System.Drawing;
using static Rubjerg.Graphviz.ForeignFunctionInterface;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+///
+/// In Graphviz the way coordinates of bounding boxes are represented may differ.
+/// We want to provide a uniform API with bottom left coords only, so we use this enum to
+/// keep track of the current internal representation and convert if needed.
+///
+internal enum BoundingBoxCoords
{
+ Centered,
+ BottomLeft
+}
+
+///
+/// Wraps a graphviz label for any kind of graphviz object.
+///
+[Obsolete("This object is only available after ComputeLayout(). It is obsoleted by GetLabelDrawing(). Refer to tutorial.")]
+public class GraphvizLabel : GraphvizThing
+{
+ private readonly BoundingBoxCoords representation;
+ private readonly PointF offset;
+
///
- /// In Graphviz the way coordinates of bounding boxes are represented may differ.
- /// We want to provide a uniform API with bottom left coords only, so we use this enum to
- /// keep track of the current internal representation and convert if needed.
+ /// Unfortunately the way the bounding box is stored differs per object that the label belongs to.
+ /// Therefore some extra information is needed to uniformly define a Label object.
///
- internal enum BoundingBoxCoords
+ internal GraphvizLabel(IntPtr ptr, BoundingBoxCoords representation, PointF offset = default)
+ : base(ptr)
{
- Centered,
- BottomLeft
+ this.representation = representation;
+ this.offset = offset;
+ }
+
+ public string FontName()
+ {
+ return LabelFontname(_ptr);
}
///
- /// Wraps a graphviz label for any kind of graphviz object.
+ /// Label size in points.
///
- [Obsolete("This object is only available after ComputeLayout(). It is obsoleted by GetLabelDrawing(). Refer to tutorial.")]
- public class GraphvizLabel : GraphvizThing
+ public float FontSize()
{
- private readonly BoundingBoxCoords representation;
- private readonly PointF offset;
-
- ///
- /// Unfortunately the way the bounding box is stored differs per object that the label belongs to.
- /// Therefore some extra information is needed to uniformly define a Label object.
- ///
- internal GraphvizLabel(IntPtr ptr, BoundingBoxCoords representation, PointF offset = default)
- : base(ptr)
- {
- this.representation = representation;
- this.offset = offset;
- }
-
- public string FontName()
- {
- return LabelFontname(_ptr);
- }
-
- ///
- /// Label size in points.
- ///
- public float FontSize()
- {
- return Convert.ToSingle(LabelFontsize(_ptr));
- }
-
- public string Text()
- {
- return LabelText(_ptr);
- }
+ return Convert.ToSingle(LabelFontsize(_ptr));
+ }
- public RectangleF BoundingBox()
- {
- float x = Convert.ToSingle(LabelX(_ptr)) + offset.X;
- float y = Convert.ToSingle(LabelY(_ptr)) + offset.Y;
- float w = Convert.ToSingle(LabelWidth(_ptr));
- float h = Convert.ToSingle(LabelHeight(_ptr));
- if (representation == BoundingBoxCoords.Centered)
- return new RectangleF(x - w / 2, y - h / 2, w, h);
- else
- return new RectangleF(x, y, w, h);
- }
+ public string Text()
+ {
+ return LabelText(_ptr);
+ }
+ public RectangleF BoundingBox()
+ {
+ float x = Convert.ToSingle(LabelX(_ptr)) + offset.X;
+ float y = Convert.ToSingle(LabelY(_ptr)) + offset.Y;
+ float w = Convert.ToSingle(LabelWidth(_ptr));
+ float h = Convert.ToSingle(LabelHeight(_ptr));
+ if (representation == BoundingBoxCoords.Centered)
+ return new RectangleF(x - w / 2, y - h / 2, w, h);
+ else
+ return new RectangleF(x, y, w, h);
}
+
}
diff --git a/Rubjerg.Graphviz/GraphVizThing.cs b/Rubjerg.Graphviz/GraphVizThing.cs
index 72ef211..3da813e 100644
--- a/Rubjerg.Graphviz/GraphVizThing.cs
+++ b/Rubjerg.Graphviz/GraphVizThing.cs
@@ -1,54 +1,53 @@
using System;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+///
+/// This is the most basic entity for our graphviz wrapper. It wraps a C pointer to a managed
+/// resource, and wraps C functions with object oriented methods. Everything that is wrapped,
+/// derives from this. Since a graphviz thing is a dumb wrapper around a managed pointer, there
+/// can be multiple wrappers for the same pointer. Ideally we would want to be a simple struct,
+/// but structs can't be subclassed in C#, so we must be a class and live with the
+/// overhead in code and performance. This implies that we need to override what it means for
+/// two wrappers to be equal (i.e. not reference equality for wrappers, but equality of the
+/// pointers they wrap) to allow usage of common functions (like linq contains) in a way that
+/// makes sense.
+///
+/// Invariant: ptr member is never 0.
+///
+public abstract class GraphvizThing
{
- ///
- /// This is the most basic entity for our graphviz wrapper. It wraps a C pointer to a managed
- /// resource, and wraps C functions with object oriented methods. Everything that is wrapped,
- /// derives from this. Since a graphviz thing is a dumb wrapper around a managed pointer, there
- /// can be multiple wrappers for the same pointer. Ideally we would want to be a simple struct,
- /// but structs can't be subclassed in C#, so we must be a class and live with the
- /// overhead in code and performance. This implies that we need to override what it means for
- /// two wrappers to be equal (i.e. not reference equality for wrappers, but equality of the
- /// pointers they wrap) to allow usage of common functions (like linq contains) in a way that
- /// makes sense.
- ///
- /// Invariant: ptr member is never 0.
- ///
- public abstract class GraphvizThing
+ internal readonly IntPtr _ptr;
+
+ protected GraphvizThing(IntPtr ptr)
+ {
+ if (ptr == IntPtr.Zero)
+ throw new ArgumentException("Can't have a null pointer.");
+ _ptr = ptr;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as GraphvizThing);
+ }
+
+ public virtual bool Equals(GraphvizThing obj)
+ {
+ return obj != null && _ptr == obj._ptr;
+ }
+
+ public override int GetHashCode()
+ {
+ return _ptr.GetHashCode();
+ }
+
+ public static bool operator ==(GraphvizThing a, GraphvizThing b)
+ {
+ return Equals(a, b);
+ }
+
+ public static bool operator !=(GraphvizThing a, GraphvizThing b)
{
- internal readonly IntPtr _ptr;
-
- protected GraphvizThing(IntPtr ptr)
- {
- if (ptr == IntPtr.Zero)
- throw new ArgumentException("Can't have a null pointer.");
- _ptr = ptr;
- }
-
- public override bool Equals(object obj)
- {
- return Equals(obj as GraphvizThing);
- }
-
- public virtual bool Equals(GraphvizThing obj)
- {
- return obj != null && _ptr == obj._ptr;
- }
-
- public override int GetHashCode()
- {
- return _ptr.GetHashCode();
- }
-
- public static bool operator ==(GraphvizThing a, GraphvizThing b)
- {
- return Equals(a, b);
- }
-
- public static bool operator !=(GraphvizThing a, GraphvizThing b)
- {
- return !(a == b);
- }
+ return !(a == b);
}
}
diff --git a/Rubjerg.Graphviz/GraphvizCommand.cs b/Rubjerg.Graphviz/GraphvizCommand.cs
index b7665d4..9d7e872 100644
--- a/Rubjerg.Graphviz/GraphvizCommand.cs
+++ b/Rubjerg.Graphviz/GraphvizCommand.cs
@@ -2,77 +2,76 @@
using System.Diagnostics;
using System.IO;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+///
+/// See https://graphviz.org/doc/info/command.html
+///
+public class GraphvizCommand
{
- ///
- /// See https://graphviz.org/doc/info/command.html
- ///
- public class GraphvizCommand
+ public static RootGraph CreateLayout(Graph input, string engine = LayoutEngines.Dot)
{
- public static RootGraph CreateLayout(Graph input, string engine = LayoutEngines.Dot)
- {
- var output = Exec(input, engine: engine);
- var resultGraph = RootGraph.FromDotString(output);
- return resultGraph;
- }
+ var output = Exec(input, engine: engine);
+ var resultGraph = RootGraph.FromDotString(output);
+ return resultGraph;
+ }
- public static string Exec(Graph input, string format = "xdot", string outputPath = null, string engine = LayoutEngines.Dot)
+ public static string Exec(Graph input, string format = "xdot", string outputPath = null, string engine = LayoutEngines.Dot)
+ {
+ string exeName = "dot.exe";
+ string arguments = $"-T{format} -K{engine}";
+ if (outputPath != null)
{
- string exeName = "dot.exe";
- string arguments = $"-T{format} -K{engine}";
- if (outputPath != null)
- {
- arguments = $"{arguments} -o{outputPath}";
- }
- string inputToStdin = input.ToDotString();
+ arguments = $"{arguments} -o{outputPath}";
+ }
+ string inputToStdin = input.ToDotString();
- // Get the location of the currently executing DLL
- string exeDirectory = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
+ // Get the location of the currently executing DLL
+ string exeDirectory = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
- // Construct the path to the executable
- string exePath = Path.Combine(exeDirectory, exeName);
+ // Construct the path to the executable
+ string exePath = Path.Combine(exeDirectory, exeName);
- Process process = new Process();
+ Process process = new Process();
- process.StartInfo.FileName = exePath;
- process.StartInfo.Arguments = arguments;
+ process.StartInfo.FileName = exePath;
+ process.StartInfo.Arguments = arguments;
- // Redirect the input/output streams
- process.StartInfo.UseShellExecute = false;
- process.StartInfo.RedirectStandardOutput = true;
- process.StartInfo.RedirectStandardInput = true;
- process.StartInfo.RedirectStandardError = true;
+ // Redirect the input/output streams
+ process.StartInfo.UseShellExecute = false;
+ process.StartInfo.RedirectStandardOutput = true;
+ process.StartInfo.RedirectStandardInput = true;
+ process.StartInfo.RedirectStandardError = true;
- _ = process.Start();
+ _ = process.Start();
- // Write to stdin
- using (StreamWriter sw = process.StandardInput)
- sw.WriteLine(inputToStdin);
+ // Write to stdin
+ using (StreamWriter sw = process.StandardInput)
+ sw.WriteLine(inputToStdin);
- // Read from stdout
- string output;
- using (StreamReader sr = process.StandardOutput)
- output = sr.ReadToEnd()
- .Replace("\r\n", "\n"); // File operations do this automatically, but stream operations do not
+ // Read from stdout
+ string output;
+ using (StreamReader sr = process.StandardOutput)
+ output = sr.ReadToEnd()
+ .Replace("\r\n", "\n"); // File operations do this automatically, but stream operations do not
- // Read from stderr
- string error;
- using (StreamReader sr = process.StandardError)
- error = sr.ReadToEnd()
- .Replace("\r\n", "\n"); // File operations do this automatically, but stream operations do not
+ // Read from stderr
+ string error;
+ using (StreamReader sr = process.StandardError)
+ error = sr.ReadToEnd()
+ .Replace("\r\n", "\n"); // File operations do this automatically, but stream operations do not
- process.WaitForExit();
+ process.WaitForExit();
- if (process.ExitCode != 0)
- {
- // Something went wrong.
- throw new ApplicationException($"Process exited with code {process.ExitCode}. Error details: {error}");
- }
- else
- {
- // Process completed successfully.
- return output;
- }
+ if (process.ExitCode != 0)
+ {
+ // Something went wrong.
+ throw new ApplicationException($"Process exited with code {process.ExitCode}. Error details: {error}");
+ }
+ else
+ {
+ // Process completed successfully.
+ return output;
}
}
}
diff --git a/Rubjerg.Graphviz/LayoutEngines.cs b/Rubjerg.Graphviz/LayoutEngines.cs
index 9bfddb1..0b50cf1 100644
--- a/Rubjerg.Graphviz/LayoutEngines.cs
+++ b/Rubjerg.Graphviz/LayoutEngines.cs
@@ -1,14 +1,13 @@
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+public static class LayoutEngines
{
- public static class LayoutEngines
- {
- public const string Dot = "dot";
- public const string Neato = "neato";
- public const string Fdp = "fdp";
- public const string Sfdp = "sfdp";
- public const string Twopi = "twopi";
- public const string Circo = "circo";
- public const string Patchwork = "patchwork";
- public const string Osage = "osage";
- }
+ public const string Dot = "dot";
+ public const string Neato = "neato";
+ public const string Fdp = "fdp";
+ public const string Sfdp = "sfdp";
+ public const string Twopi = "twopi";
+ public const string Circo = "circo";
+ public const string Patchwork = "patchwork";
+ public const string Osage = "osage";
}
diff --git a/Rubjerg.Graphviz/NativeMethods.cs b/Rubjerg.Graphviz/NativeMethods.cs
index 74f6c49..aaf766f 100644
--- a/Rubjerg.Graphviz/NativeMethods.cs
+++ b/Rubjerg.Graphviz/NativeMethods.cs
@@ -2,35 +2,34 @@
using System.IO;
using System.Runtime.InteropServices;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+public static class NativeMethods
{
- public static class NativeMethods
+ public static void CreateConsole()
{
- public static void CreateConsole()
- {
- _ = AllocConsole();
-
- // stdout's handle seems to always be equal to 7
- IntPtr defaultStdout = new IntPtr(7);
- IntPtr currentStdout = GetStdHandle(StdOutputHandle);
+ _ = AllocConsole();
- if (currentStdout != defaultStdout)
- // reset stdout
- SetStdHandle(StdOutputHandle, defaultStdout);
+ // stdout's handle seems to always be equal to 7
+ IntPtr defaultStdout = new IntPtr(7);
+ IntPtr currentStdout = GetStdHandle(StdOutputHandle);
- // reopen stdout
- TextWriter writer = new StreamWriter(Console.OpenStandardOutput())
- { AutoFlush = true };
- Console.SetOut(writer);
- }
+ if (currentStdout != defaultStdout)
+ // reset stdout
+ SetStdHandle(StdOutputHandle, defaultStdout);
- // P/Invoke required:
- private const uint StdOutputHandle = 0xFFFFFFF5;
- [DllImport("kernel32.dll")]
- private static extern IntPtr GetStdHandle(uint nStdHandle);
- [DllImport("kernel32.dll")]
- private static extern void SetStdHandle(uint nStdHandle, IntPtr handle);
- [DllImport("kernel32")]
- static extern bool AllocConsole();
+ // reopen stdout
+ TextWriter writer = new StreamWriter(Console.OpenStandardOutput())
+ { AutoFlush = true };
+ Console.SetOut(writer);
}
+
+ // P/Invoke required:
+ private const uint StdOutputHandle = 0xFFFFFFF5;
+ [DllImport("kernel32.dll")]
+ private static extern IntPtr GetStdHandle(uint nStdHandle);
+ [DllImport("kernel32.dll")]
+ private static extern void SetStdHandle(uint nStdHandle, IntPtr handle);
+ [DllImport("kernel32")]
+ static extern bool AllocConsole();
}
diff --git a/Rubjerg.Graphviz/Node.cs b/Rubjerg.Graphviz/Node.cs
index 39eaa41..8783816 100644
--- a/Rubjerg.Graphviz/Node.cs
+++ b/Rubjerg.Graphviz/Node.cs
@@ -5,284 +5,283 @@
using System.Linq;
using static Rubjerg.Graphviz.ForeignFunctionInterface;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+public class Node : CGraphThing
{
- public class Node : CGraphThing
+ ///
+ /// rootgraph must not be null
+ ///
+ internal Node(IntPtr ptr, RootGraph rootgraph) : base(ptr, rootgraph) { }
+
+ internal static Node Get(Graph graph, string name)
{
- ///
- /// rootgraph must not be null
- ///
- internal Node(IntPtr ptr, RootGraph rootgraph) : base(ptr, rootgraph) { }
+ name = NameString(name);
+ IntPtr ptr = Agnode(graph._ptr, name, 0);
+ if (ptr != IntPtr.Zero)
+ return new Node(ptr, graph.MyRootGraph);
+ return null;
+ }
- internal static Node Get(Graph graph, string name)
- {
- name = NameString(name);
- IntPtr ptr = Agnode(graph._ptr, name, 0);
- if (ptr != IntPtr.Zero)
- return new Node(ptr, graph.MyRootGraph);
- return null;
- }
+ internal static Node GetOrCreate(Graph graph, string name)
+ {
+ name = NameString(name);
+ IntPtr ptr = Agnode(graph._ptr, name, 1);
+ return new Node(ptr, graph.MyRootGraph);
+ }
- internal static Node GetOrCreate(Graph graph, string name)
- {
- name = NameString(name);
- IntPtr ptr = Agnode(graph._ptr, name, 1);
- return new Node(ptr, graph.MyRootGraph);
- }
+ ///
+ /// Introduces an attribute for nodes in the given graph by giving a default value.
+ /// A given default can be overwritten by calling this method again.
+ ///
+ public static void IntroduceAttribute(RootGraph root, string name, string deflt)
+ {
+ _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
+ Agattr(root._ptr, 1, name, deflt);
+ }
- ///
- /// Introduces an attribute for nodes in the given graph by giving a default value.
- /// A given default can be overwritten by calling this method again.
- ///
- public static void IntroduceAttribute(RootGraph root, string name, string deflt)
- {
- _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
- Agattr(root._ptr, 1, name, deflt);
- }
+ public static void IntroduceAttributeHtml(RootGraph root, string name, string deflt)
+ {
+ _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
+ AgattrHtml(root._ptr, 1, name, deflt);
+ }
- public static void IntroduceAttributeHtml(RootGraph root, string name, string deflt)
+ public IEnumerable EdgesOut(Graph graph = null)
+ {
+ IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr;
+ var current = Agfstout(graph_ptr, _ptr);
+ while (current != IntPtr.Zero)
{
- _ = deflt ?? throw new ArgumentNullException(nameof(deflt));
- AgattrHtml(root._ptr, 1, name, deflt);
+ yield return new Edge(current, MyRootGraph);
+ current = Agnxtout(graph_ptr, current);
}
+ }
- public IEnumerable EdgesOut(Graph graph = null)
+ public IEnumerable EdgesIn(Graph graph = null)
+ {
+ IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr;
+ var current = Agfstin(graph_ptr, _ptr);
+ while (current != IntPtr.Zero)
{
- IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr;
- var current = Agfstout(graph_ptr, _ptr);
- while (current != IntPtr.Zero)
- {
- yield return new Edge(current, MyRootGraph);
- current = Agnxtout(graph_ptr, current);
- }
+ yield return new Edge(current, MyRootGraph);
+ current = Agnxtin(graph_ptr, current);
}
+ }
- public IEnumerable EdgesIn(Graph graph = null)
+ ///
+ /// Iterate over both in and out edges. This will not yield self loops twice.
+ ///
+ public IEnumerable Edges(Graph graph = null)
+ {
+ IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr;
+ var current = Agfstedge(graph_ptr, _ptr);
+ while (current != IntPtr.Zero)
{
- IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr;
- var current = Agfstin(graph_ptr, _ptr);
- while (current != IntPtr.Zero)
- {
- yield return new Edge(current, MyRootGraph);
- current = Agnxtin(graph_ptr, current);
- }
+ yield return new Edge(current, MyRootGraph);
+ current = Agnxtedge(graph_ptr, current, _ptr); // This line crashes at some point
}
+ }
- ///
- /// Iterate over both in and out edges. This will not yield self loops twice.
- ///
- public IEnumerable Edges(Graph graph = null)
- {
- IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr;
- var current = Agfstedge(graph_ptr, _ptr);
- while (current != IntPtr.Zero)
- {
- yield return new Edge(current, MyRootGraph);
- current = Agnxtedge(graph_ptr, current, _ptr); // This line crashes at some point
- }
- }
+ ///
+ /// Get all neighbors connected via an out edge.
+ ///
+ public IEnumerable NeighborsOut(Graph graph = null)
+ {
+ return EdgesOut(graph).Select(e => e.OppositeEndpoint(this));
+ }
- ///
- /// Get all neighbors connected via an out edge.
- ///
- public IEnumerable NeighborsOut(Graph graph = null)
- {
- return EdgesOut(graph).Select(e => e.OppositeEndpoint(this));
- }
+ ///
+ /// Get all neighbors connected via an in edge.
+ ///
+ public IEnumerable NeighborsIn(Graph graph = null)
+ {
+ return EdgesIn(graph).Select(e => e.OppositeEndpoint(this));
+ }
- ///
- /// Get all neighbors connected via an in edge.
- ///
- public IEnumerable NeighborsIn(Graph graph = null)
- {
- return EdgesIn(graph).Select(e => e.OppositeEndpoint(this));
- }
+ ///
+ /// Get all neighbors.
+ ///
+ public IEnumerable Neighbors(Graph graph = null)
+ {
+ return Edges(graph).Select(e => e.OppositeEndpoint(this));
+ }
- ///
- /// Get all neighbors.
- ///
- public IEnumerable Neighbors(Graph graph = null)
- {
- return Edges(graph).Select(e => e.OppositeEndpoint(this));
- }
+ ///
+ /// Get all neighbors fullfilling a given attribute constraint.
+ ///
+ public IEnumerable NeighborsByAttribute(string attr_name, string attr_value, Graph graph = null)
+ {
+ return Neighbors(graph).Where(n => n.GetAttribute(attr_name) == attr_value);
+ }
- ///
- /// Get all neighbors fullfilling a given attribute constraint.
- ///
- public IEnumerable NeighborsByAttribute(string attr_name, string attr_value, Graph graph = null)
- {
- return Neighbors(graph).Where(n => n.GetAttribute(attr_name) == attr_value);
- }
+ ///
+ /// Get all neighbors connected by an edge with given name.
+ ///
+ public IEnumerable NeighborsByEdgeName(string edgename, Graph graph = null)
+ {
+ return Edges(graph).Where(e => e.GetName() == edgename).Select(e => e.OppositeEndpoint(this));
+ }
- ///
- /// Get all neighbors connected by an edge with given name.
- ///
- public IEnumerable NeighborsByEdgeName(string edgename, Graph graph = null)
- {
- return Edges(graph).Where(e => e.GetName() == edgename).Select(e => e.OppositeEndpoint(this));
- }
+ ///
+ /// Copy the node to another root graph.
+ /// Copies the attributes as well, as far as the attributes have been
+ /// introduced in the destination graph.
+ ///
+ public Node CopyToOtherRoot(RootGraph destination)
+ {
+ Node result = destination.GetOrAddNode(GetName());
+ _ = CopyAttributesTo(result);
+ return result;
+ }
- ///
- /// Copy the node to another root graph.
- /// Copies the attributes as well, as far as the attributes have been
- /// introduced in the destination graph.
- ///
- public Node CopyToOtherRoot(RootGraph destination)
- {
- Node result = destination.GetOrAddNode(GetName());
- _ = CopyAttributesTo(result);
- return result;
- }
+ public int OutDegree(Graph graph = null)
+ {
+ IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr;
+ return Agdegree(graph_ptr, _ptr, 0, 1);
+ }
- public int OutDegree(Graph graph = null)
- {
- IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr;
- return Agdegree(graph_ptr, _ptr, 0, 1);
- }
+ public int InDegree(Graph graph = null)
+ {
+ IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr;
+ return Agdegree(graph_ptr, _ptr, 1, 0);
+ }
- public int InDegree(Graph graph = null)
- {
- IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr;
- return Agdegree(graph_ptr, _ptr, 1, 0);
- }
+ public int TotalDegree(Graph graph = null)
+ {
+ IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr;
+ return Agdegree(graph_ptr, _ptr, 1, 1);
+ }
- public int TotalDegree(Graph graph = null)
- {
- IntPtr graph_ptr = graph?._ptr ?? MyRootGraph._ptr;
- return Agdegree(graph_ptr, _ptr, 1, 1);
- }
+ public bool IsAdjacentTo(Node node)
+ {
+ return EdgesOut().Any(e => e.Head().Equals(node)) || EdgesIn().Any(e => e.Tail().Equals(node));
+ }
+
+ public void MakeInvisibleAndSmall()
+ {
+ SafeSetAttribute("style", "invis", "");
+ SafeSetAttribute("margin", "0", "");
+ SafeSetAttribute("width", "0", "");
+ SafeSetAttribute("height", "0", "");
+ SafeSetAttribute("shape", "point", "");
+ }
+
+ #region layout attributes
- public bool IsAdjacentTo(Node node)
+ ///
+ /// The position of the center of the node.
+ ///
+ public PointF GetPosition()
+ {
+ // The "pos" attribute is available as part of xdot output
+ if (HasAttribute("pos"))
{
- return EdgesOut().Any(e => e.Head().Equals(node)) || EdgesIn().Any(e => e.Tail().Equals(node));
+ var posString = GetAttribute("pos");
+ var coords = posString.Split(',');
+ float x = float.Parse(coords[0], NumberStyles.Any, CultureInfo.InvariantCulture);
+ float y = float.Parse(coords[1], NumberStyles.Any, CultureInfo.InvariantCulture);
+ return new PointF(x, y);
}
+ // If the "pos" attribute is not available, try the following FFI functions,
+ // which are available after a ComputeLayout
+ return new PointF(Convert.ToSingle(NodeX(_ptr)), Convert.ToSingle(NodeY(_ptr)));
+ }
- public void MakeInvisibleAndSmall()
+ ///
+ /// The size of bounding box of the node.
+ ///
+ public SizeF GetSize()
+ {
+ // The "width" and "height" attributes are available as part of xdot output
+ float w, h;
+ if (HasAttribute("width") && HasAttribute("height"))
{
- SafeSetAttribute("style", "invis", "");
- SafeSetAttribute("margin", "0", "");
- SafeSetAttribute("width", "0", "");
- SafeSetAttribute("height", "0", "");
- SafeSetAttribute("shape", "point", "");
+ w = float.Parse(GetAttribute("width"), NumberStyles.Any, CultureInfo.InvariantCulture);
+ h = float.Parse(GetAttribute("height"), NumberStyles.Any, CultureInfo.InvariantCulture);
}
-
- #region layout attributes
-
- ///
- /// The position of the center of the node.
- ///
- public PointF GetPosition()
+ else
{
- // The "pos" attribute is available as part of xdot output
- if (HasAttribute("pos"))
- {
- var posString = GetAttribute("pos");
- var coords = posString.Split(',');
- float x = float.Parse(coords[0], NumberStyles.Any, CultureInfo.InvariantCulture);
- float y = float.Parse(coords[1], NumberStyles.Any, CultureInfo.InvariantCulture);
- return new PointF(x, y);
- }
- // If the "pos" attribute is not available, try the following FFI functions,
+ // If they are not available, try the following FFI functions,
// which are available after a ComputeLayout
- return new PointF(Convert.ToSingle(NodeX(_ptr)), Convert.ToSingle(NodeY(_ptr)));
+ w = Convert.ToSingle(NodeWidth(_ptr));
+ h = Convert.ToSingle(NodeHeight(_ptr));
}
+ // Coords are in points, sizes in inches. 72 points = 1 inch
+ // We return everything in terms of points.
+ return new SizeF(w * 72, h * 72);
+ }
- ///
- /// The size of bounding box of the node.
- ///
- public SizeF GetSize()
- {
- // The "width" and "height" attributes are available as part of xdot output
- float w, h;
- if (HasAttribute("width") && HasAttribute("height"))
- {
- w = float.Parse(GetAttribute("width"), NumberStyles.Any, CultureInfo.InvariantCulture);
- h = float.Parse(GetAttribute("height"), NumberStyles.Any, CultureInfo.InvariantCulture);
- }
- else
- {
- // If they are not available, try the following FFI functions,
- // which are available after a ComputeLayout
- w = Convert.ToSingle(NodeWidth(_ptr));
- h = Convert.ToSingle(NodeHeight(_ptr));
- }
- // Coords are in points, sizes in inches. 72 points = 1 inch
- // We return everything in terms of points.
- return new SizeF(w * 72, h * 72);
- }
+ public RectangleF GetBoundingBox()
+ {
+ var size = GetSize();
+ var center = GetPosition();
+ var bottomleft = new PointF(center.X - size.Width / 2, center.Y - size.Height / 2);
+ return new RectangleF(bottomleft, size);
+ }
- public RectangleF GetBoundingBox()
- {
- var size = GetSize();
- var center = GetPosition();
- var bottomleft = new PointF(center.X - size.Width / 2, center.Y - size.Height / 2);
- return new RectangleF(bottomleft, size);
- }
+ ///
+ /// If the shape of this node was set to 'record', this method allows you to retrieve the
+ /// resulting rectangles.
+ ///
+ public IEnumerable GetRecordRectangles()
+ {
+ if (!HasAttribute("rects"))
+ yield break;
- ///
- /// If the shape of this node was set to 'record', this method allows you to retrieve the
- /// resulting rectangles.
- ///
- public IEnumerable GetRecordRectangles()
- {
- if (!HasAttribute("rects"))
- yield break;
-
- // There is a lingering issue in Graphviz where the x coordinates of the record rectangles may be off.
- // As a workaround we consult the x coordinates, and attempt to snap onto those.
- // https://github.com/Rubjerg/Graphviz.NetWrapper/issues/30
- var validXCoords = GetDrawing().OfType()
- .SelectMany(p => p.Value.Points).Select(p => p.X).ToList();
-
- foreach (string rectStr in GetAttribute("rects").Split(' '))
- {
- var rect = ParseRect(rectStr);
-
- var x1 = rect.X;
- var x2 = rect.X + rect.Width;
- var fixedX1 = (float)FindClosest(validXCoords, x1);
- var fixedX2 = (float)FindClosest(validXCoords, x2);
- var fixedRect = new RectangleF(
- new PointF(fixedX1, rect.Y),
- new SizeF(fixedX2 - rect.X, rect.Height));
- yield return fixedRect;
- }
- }
+ // There is a lingering issue in Graphviz where the x coordinates of the record rectangles may be off.
+ // As a workaround we consult the x coordinates, and attempt to snap onto those.
+ // https://github.com/Rubjerg/Graphviz.NetWrapper/issues/30
+ var validXCoords = GetDrawing().OfType()
+ .SelectMany(p => p.Value.Points).Select(p => p.X).ToList();
- ///
- /// Return the value that is closest to the given target value.
- /// Return target if the sequence if empty.
- ///
- private static double FindClosest(IEnumerable self, double target)
+ foreach (string rectStr in GetAttribute("rects").Split(' '))
{
- if (self.Any())
- return self.OrderBy(x => Math.Abs(x - target)).First();
- return target;
+ var rect = ParseRect(rectStr);
+
+ var x1 = rect.X;
+ var x2 = rect.X + rect.Width;
+ var fixedX1 = (float)FindClosest(validXCoords, x1);
+ var fixedX2 = (float)FindClosest(validXCoords, x2);
+ var fixedRect = new RectangleF(
+ new PointF(fixedX1, rect.Y),
+ new SizeF(fixedX2 - rect.X, rect.Height));
+ yield return fixedRect;
}
+ }
- private RectangleF ParseRect(string rect)
- {
- string[] points = rect.Split(',');
- float leftX = float.Parse(points[0], NumberStyles.Any, CultureInfo.InvariantCulture);
- float upperY = float.Parse(points[1], NumberStyles.Any, CultureInfo.InvariantCulture);
- float rightX = float.Parse(points[2], NumberStyles.Any, CultureInfo.InvariantCulture);
- float lowerY = float.Parse(points[3], NumberStyles.Any, CultureInfo.InvariantCulture);
- return new RectangleF(leftX, upperY, rightX - leftX, lowerY - upperY);
- }
+ ///
+ /// Return the value that is closest to the given target value.
+ /// Return target if the sequence if empty.
+ ///
+ private static double FindClosest(IEnumerable self, double target)
+ {
+ if (self.Any())
+ return self.OrderBy(x => Math.Abs(x - target)).First();
+ return target;
+ }
- public IReadOnlyList GetDrawing() => GetXDotValue(this, "_draw_");
- public IReadOnlyList GetLabelDrawing() => GetXDotValue(this, "_ldraw_");
+ private RectangleF ParseRect(string rect)
+ {
+ string[] points = rect.Split(',');
+ float leftX = float.Parse(points[0], NumberStyles.Any, CultureInfo.InvariantCulture);
+ float upperY = float.Parse(points[1], NumberStyles.Any, CultureInfo.InvariantCulture);
+ float rightX = float.Parse(points[2], NumberStyles.Any, CultureInfo.InvariantCulture);
+ float lowerY = float.Parse(points[3], NumberStyles.Any, CultureInfo.InvariantCulture);
+ return new RectangleF(leftX, upperY, rightX - leftX, lowerY - upperY);
+ }
- #endregion
+ public IReadOnlyList GetDrawing() => GetXDotValue(this, "_draw_");
+ public IReadOnlyList GetLabelDrawing() => GetXDotValue(this, "_ldraw_");
- [Obsolete("This method is only available after ComputeLayout(), and may crash otherwise. It is obsoleted by GetLabelDrawing(). Refer to tutorial.")]
- public GraphvizLabel GetLabel()
- {
- IntPtr labelptr = NodeLabel(_ptr);
- if (labelptr == IntPtr.Zero)
- return null;
- return new GraphvizLabel(labelptr, BoundingBoxCoords.Centered, new PointF(0, 0));
- }
+ #endregion
+
+ [Obsolete("This method is only available after ComputeLayout(), and may crash otherwise. It is obsoleted by GetLabelDrawing(). Refer to tutorial.")]
+ public GraphvizLabel GetLabel()
+ {
+ IntPtr labelptr = NodeLabel(_ptr);
+ if (labelptr == IntPtr.Zero)
+ return null;
+ return new GraphvizLabel(labelptr, BoundingBoxCoords.Centered, new PointF(0, 0));
}
}
diff --git a/Rubjerg.Graphviz/RootGraph.cs b/Rubjerg.Graphviz/RootGraph.cs
index 55f570a..807db73 100644
--- a/Rubjerg.Graphviz/RootGraph.cs
+++ b/Rubjerg.Graphviz/RootGraph.cs
@@ -3,97 +3,96 @@
using System.Linq;
using static Rubjerg.Graphviz.ForeignFunctionInterface;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+public enum GraphType
+{
+ Directed = 0,
+ StrictDirected = 1,
+ Undirected = 2,
+ StrictUndirected = 3
+}
+
+///
+/// Wraps a cgraph root graph.
+/// NB: If there is no .net wrapper left that points to any part of a root graph, the root graph is destroyed.
+///
+public class RootGraph : Graph
{
- public enum GraphType
+ private long _added_pressure = 0;
+ protected RootGraph(IntPtr ptr) : base(ptr, null) { }
+ ~RootGraph()
{
- Directed = 0,
- StrictDirected = 1,
- Undirected = 2,
- StrictUndirected = 3
+ if (_added_pressure > 0)
+ GC.RemoveMemoryPressure(_added_pressure);
+ _ = Agclose(_ptr);
}
///
- /// Wraps a cgraph root graph.
- /// NB: If there is no .net wrapper left that points to any part of a root graph, the root graph is destroyed.
+ /// Notify the garbage collector of the approximate allocated unmanaged memory used by this graph.
+ /// Because it is too much of a hassle to track the exact amount of unmanaged bytes allocated,
+ /// we use a rough estimate that is hopefully large enough in most cases to prevent OutOfMemory exceptions,
+ /// but hopefully not too large to completely kill GC performance.
+ /// This method ignores memory used by attributes.
///
- public class RootGraph : Graph
+ public void UpdateMemoryPressure()
{
- private long _added_pressure = 0;
- protected RootGraph(IntPtr ptr) : base(ptr, null) { }
- ~RootGraph()
- {
- if (_added_pressure > 0)
- GC.RemoveMemoryPressure(_added_pressure);
- _ = Agclose(_ptr);
- }
-
- ///
- /// Notify the garbage collector of the approximate allocated unmanaged memory used by this graph.
- /// Because it is too much of a hassle to track the exact amount of unmanaged bytes allocated,
- /// we use a rough estimate that is hopefully large enough in most cases to prevent OutOfMemory exceptions,
- /// but hopefully not too large to completely kill GC performance.
- /// This method ignores memory used by attributes.
- ///
- public void UpdateMemoryPressure()
- {
- if (_added_pressure > 0)
- GC.RemoveMemoryPressure(_added_pressure);
+ if (_added_pressure > 0)
+ GC.RemoveMemoryPressure(_added_pressure);
- // Up memory pressure proportional to the amount of unmanaged memory in use.
- long unmanaged_bytes_estimate = Nodes().Count() * 104 + Edges().Count() * 64;
- if (unmanaged_bytes_estimate > 0)
- GC.AddMemoryPressure(unmanaged_bytes_estimate);
- _added_pressure = unmanaged_bytes_estimate;
- }
+ // Up memory pressure proportional to the amount of unmanaged memory in use.
+ long unmanaged_bytes_estimate = Nodes().Count() * 104 + Edges().Count() * 64;
+ if (unmanaged_bytes_estimate > 0)
+ GC.AddMemoryPressure(unmanaged_bytes_estimate);
+ _added_pressure = unmanaged_bytes_estimate;
+ }
- ///
- /// Create a new graph.
- ///
- ///
- /// The name is not interpreted by Graphviz,
- /// except it is recorded and preserved when the graph is written as a file
- ///
- public static RootGraph CreateNew(GraphType graphtype, string name = null)
- {
- name = NameString(name);
- var ptr = Rjagopen(name, (int)graphtype);
- return new RootGraph(ptr);
- }
+ ///
+ /// Create a new graph.
+ ///
+ ///
+ /// The name is not interpreted by Graphviz,
+ /// except it is recorded and preserved when the graph is written as a file
+ ///
+ public static RootGraph CreateNew(GraphType graphtype, string name = null)
+ {
+ name = NameString(name);
+ var ptr = Rjagopen(name, (int)graphtype);
+ return new RootGraph(ptr);
+ }
- public static RootGraph FromDotFile(string filename)
- {
- string input;
- using (var sr = new StreamReader(filename))
- input = sr.ReadToEnd();
+ public static RootGraph FromDotFile(string filename)
+ {
+ string input;
+ using (var sr = new StreamReader(filename))
+ input = sr.ReadToEnd();
- return FromDotString(input);
- }
+ return FromDotString(input);
+ }
- protected static T FromDotString(string graph, Func constructor)
- where T : RootGraph
+ protected static T FromDotString(string graph, Func constructor)
+ where T : RootGraph
+ {
+ // Just to be safe, make sure the input has unix line endings. Graphviz does not properly support
+ // windows line endings passed to stdin when it comes to attribute line continuations.
+ var normalizedDotString = graph.Replace("\r\n", "\n");
+ IntPtr ptr = Rjagmemread(normalizedDotString);
+ if (ptr == IntPtr.Zero)
{
- // Just to be safe, make sure the input has unix line endings. Graphviz does not properly support
- // windows line endings passed to stdin when it comes to attribute line continuations.
- var normalizedDotString = graph.Replace("\r\n", "\n");
- IntPtr ptr = Rjagmemread(normalizedDotString);
- if (ptr == IntPtr.Zero)
- {
- throw new InvalidOperationException("Could not create graph");
- }
- var result = constructor(ptr);
- result.UpdateMemoryPressure();
- return result;
+ throw new InvalidOperationException("Could not create graph");
}
+ var result = constructor(ptr);
+ result.UpdateMemoryPressure();
+ return result;
+ }
- public static RootGraph FromDotString(string graph)
- {
- return FromDotString(graph, ptr => new RootGraph(ptr));
- }
+ public static RootGraph FromDotString(string graph)
+ {
+ return FromDotString(graph, ptr => new RootGraph(ptr));
+ }
- public void ConvertToUndirectedGraph()
- {
- ConvertToUndirected(_ptr);
- }
+ public void ConvertToUndirectedGraph()
+ {
+ ConvertToUndirected(_ptr);
}
}
diff --git a/Rubjerg.Graphviz/SubGraph.cs b/Rubjerg.Graphviz/SubGraph.cs
index 0fa05f7..b7af1c4 100644
--- a/Rubjerg.Graphviz/SubGraph.cs
+++ b/Rubjerg.Graphviz/SubGraph.cs
@@ -2,67 +2,66 @@
using System.Collections.Generic;
using static Rubjerg.Graphviz.ForeignFunctionInterface;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+public class SubGraph : Graph
{
- public class SubGraph : Graph
- {
- ///
- /// rootgraph must not be null
- ///
- internal SubGraph(IntPtr ptr, RootGraph rootgraph) : base(ptr, rootgraph) { }
+ ///
+ /// rootgraph must not be null
+ ///
+ internal SubGraph(IntPtr ptr, RootGraph rootgraph) : base(ptr, rootgraph) { }
- internal static SubGraph Get(Graph parent, string name = null)
- {
- name = NameString(name);
- IntPtr ptr = Agsubg(parent._ptr, name, 0);
- if (ptr == IntPtr.Zero)
- return null;
- return new SubGraph(ptr, parent.MyRootGraph);
+ internal static SubGraph Get(Graph parent, string name = null)
+ {
+ name = NameString(name);
+ IntPtr ptr = Agsubg(parent._ptr, name, 0);
+ if (ptr == IntPtr.Zero)
+ return null;
+ return new SubGraph(ptr, parent.MyRootGraph);
- }
+ }
- internal static SubGraph GetOrCreate(Graph parent, string name = null)
- {
- name = NameString(name);
- IntPtr ptr = Agsubg(parent._ptr, name, 1);
- return new SubGraph(ptr, parent.MyRootGraph);
- }
+ internal static SubGraph GetOrCreate(Graph parent, string name = null)
+ {
+ name = NameString(name);
+ IntPtr ptr = Agsubg(parent._ptr, name, 1);
+ return new SubGraph(ptr, parent.MyRootGraph);
+ }
- public void AddExisting(Node node)
- {
- _ = Agsubnode(_ptr, node._ptr, 1);
- }
+ public void AddExisting(Node node)
+ {
+ _ = Agsubnode(_ptr, node._ptr, 1);
+ }
- public void AddExisting(Edge edge)
- {
- _ = Agsubedge(_ptr, edge._ptr, 1);
- }
+ public void AddExisting(Edge edge)
+ {
+ _ = Agsubedge(_ptr, edge._ptr, 1);
+ }
- ///
- /// FIXME: use an actual subg equivalent to agsubedge and agsubnode
- /// https://github.com/ellson/graphviz/issues/1206
- /// This might cause a new subgraph creation.
- ///
- public void AddExisting(SubGraph subgraph)
- {
- _ = Agsubg(_ptr, subgraph.GetName(), 1);
- }
+ ///
+ /// FIXME: use an actual subg equivalent to agsubedge and agsubnode
+ /// https://github.com/ellson/graphviz/issues/1206
+ /// This might cause a new subgraph creation.
+ ///
+ public void AddExisting(SubGraph subgraph)
+ {
+ _ = Agsubg(_ptr, subgraph.GetName(), 1);
+ }
- public void AddExisting(IEnumerable nodes)
- {
- foreach (var node in nodes)
- AddExisting(node);
- }
+ public void AddExisting(IEnumerable nodes)
+ {
+ foreach (var node in nodes)
+ AddExisting(node);
+ }
- public void AddExisting(IEnumerable edges)
- {
- foreach (var edge in edges)
- AddExisting(edge);
- }
+ public void AddExisting(IEnumerable edges)
+ {
+ foreach (var edge in edges)
+ AddExisting(edge);
+ }
- public void Delete()
- {
- _ = Agclose(_ptr);
- }
+ public void Delete()
+ {
+ _ = Agclose(_ptr);
}
}
diff --git a/Rubjerg.Graphviz/XDot.cs b/Rubjerg.Graphviz/XDot.cs
index fb29500..7a878bc 100644
--- a/Rubjerg.Graphviz/XDot.cs
+++ b/Rubjerg.Graphviz/XDot.cs
@@ -1,242 +1,241 @@
using System;
using System.Drawing;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+// See https://graphviz.org/docs/outputs/canon/#xdot
+
+public record struct XDotColorStop
+{
+ public float Frac { get; init; }
+ public string Color { get; init; }
+}
+
+public record struct XDotLinearGrad
+{
+ public double X0 { get; init; }
+ public double Y0 { get; init; }
+ public double X1 { get; init; }
+ public double Y1 { get; init; }
+ public int NStops { get; init; }
+ public XDotColorStop[] Stops { get; init; }
+}
+
+public record struct XDotRadialGrad
{
- // See https://graphviz.org/docs/outputs/canon/#xdot
+ public double X0 { get; init; }
+ public double Y0 { get; init; }
+ public double R0 { get; init; }
+ public double X1 { get; init; }
+ public double Y1 { get; init; }
+ public double R1 { get; init; }
+ public int NStops { get; init; }
+ public XDotColorStop[] Stops { get; init; }
+}
- public record struct XDotColorStop
+public abstract record class XDotGradColor
+{
+ private XDotGradColor() { }
+ public sealed record class Uniform : XDotGradColor
{
- public float Frac { get; init; }
public string Color { get; init; }
}
-
- public record struct XDotLinearGrad
+ public sealed record class LinearGradient : XDotGradColor
{
- public double X0 { get; init; }
- public double Y0 { get; init; }
- public double X1 { get; init; }
- public double Y1 { get; init; }
- public int NStops { get; init; }
- public XDotColorStop[] Stops { get; init; }
+ public XDotLinearGrad LinearGrad { get; init; }
}
-
- public record struct XDotRadialGrad
+ public sealed record class RadialGradient : XDotGradColor
{
- public double X0 { get; init; }
- public double Y0 { get; init; }
- public double R0 { get; init; }
- public double X1 { get; init; }
- public double Y1 { get; init; }
- public double R1 { get; init; }
- public int NStops { get; init; }
- public XDotColorStop[] Stops { get; init; }
+ public XDotRadialGrad RadialGrad { get; init; }
}
+}
- public abstract record class XDotGradColor
- {
- private XDotGradColor() { }
- public sealed record class Uniform : XDotGradColor
- {
- public string Color { get; init; }
- }
- public sealed record class LinearGradient : XDotGradColor
- {
- public XDotLinearGrad LinearGrad { get; init; }
- }
- public sealed record class RadialGradient : XDotGradColor
- {
- public XDotRadialGrad RadialGrad { get; init; }
- }
- }
+public record struct XDotPoint
+{
+ public double X { get; init; }
+ public double Y { get; init; }
+ public double Z { get; init; }
- public record struct XDotPoint
- {
- public double X { get; init; }
- public double Y { get; init; }
- public double Z { get; init; }
+ public PointF ToPointF() => new PointF((float)X, (float)Y);
+}
- public PointF ToPointF() => new PointF((float)X, (float)Y);
- }
+public record struct XDotRect
+{
+ public double X { get; init; }
+ public double Y { get; init; }
+ public double Width { get; init; }
+ public double Height { get; init; }
- public record struct XDotRect
- {
- public double X { get; init; }
- public double Y { get; init; }
- public double Width { get; init; }
- public double Height { get; init; }
+ public RectangleF ToRectangleF() => new RectangleF((float)X, (float)Y, (float)Width, (float)Height);
+}
- public RectangleF ToRectangleF() => new RectangleF((float)X, (float)Y, (float)Width, (float)Height);
- }
+public record struct XDotPolyline
+{
+ public int Count { get; init; }
+ public XDotPoint[] Points { get; init; }
+}
- public record struct XDotPolyline
- {
- public int Count { get; init; }
- public XDotPoint[] Points { get; init; }
- }
+public enum XDotAlign
+{
+ Left,
+ Center,
+ Right
+}
+
+///
+/// Represents a line of text to be drawn.
+/// Labels with multiple lines will be represented by multiple instances.
+///
+public record struct XDotText
+{
+ ///
+ /// The X coordinate of the anchor point of the text.
+ ///
+ public double X { get; init; }
+ ///
+ /// The Y coordinate of the baseline of the text.
+ ///
+ public double Y { get; init; }
+ ///
+ /// How the text should be aligned, relative to the given anchor point.
+ ///
+ public XDotAlign Align { get; init; }
+ public double Width { get; init; }
+ public string Text { get; init; }
- public enum XDotAlign
+ ///
+ /// Compute the bounding box of this text element given the necessary font information.
+ ///
+ /// Font used to draw the text
+ /// Optional property of the font, to more accurately predict the bounding box.
+ public RectangleF TextBoundingBox(XDotFont font, float? distanceBetweenBaselineAndDescender = null)
{
- Left,
- Center,
- Right
+ var size = Size(font);
+ var descenderY = Y - (distanceBetweenBaselineAndDescender ?? font.Size / 5);
+ var leftX = Align switch
+ {
+ XDotAlign.Left => X,
+ XDotAlign.Center => X + size.Width / 2,
+ XDotAlign.Right => X + size.Width,
+ _ => throw new InvalidOperationException()
+ };
+ var bottomLeft = new PointF((float)leftX, (float)descenderY);
+ return new RectangleF(bottomLeft, size);
}
///
- /// Represents a line of text to be drawn.
- /// Labels with multiple lines will be represented by multiple instances.
+ /// The anchor point of the text.
+ /// The Y coordinate points to the baseline of the text.
+ /// The X coordinate points to the horizontal anchor of the text.
///
- public record struct XDotText
- {
- ///
- /// The X coordinate of the anchor point of the text.
- ///
- public double X { get; init; }
- ///
- /// The Y coordinate of the baseline of the text.
- ///
- public double Y { get; init; }
- ///
- /// How the text should be aligned, relative to the given anchor point.
- ///
- public XDotAlign Align { get; init; }
- public double Width { get; init; }
- public string Text { get; init; }
-
- ///
- /// Compute the bounding box of this text element given the necessary font information.
- ///
- /// Font used to draw the text
- /// Optional property of the font, to more accurately predict the bounding box.
- public RectangleF TextBoundingBox(XDotFont font, float? distanceBetweenBaselineAndDescender = null)
- {
- var size = Size(font);
- var descenderY = Y - (distanceBetweenBaselineAndDescender ?? font.Size / 5);
- var leftX = Align switch
- {
- XDotAlign.Left => X,
- XDotAlign.Center => X + size.Width / 2,
- XDotAlign.Right => X + size.Width,
- _ => throw new InvalidOperationException()
- };
- var bottomLeft = new PointF((float)leftX, (float)descenderY);
- return new RectangleF(bottomLeft, size);
- }
-
- ///
- /// The anchor point of the text.
- /// The Y coordinate points to the baseline of the text.
- /// The X coordinate points to the horizontal anchor of the text.
- ///
- public PointF Anchor() => new PointF((float)X, (float)Y);
-
- ///
- /// The width represents the estimated width of the text by GraphViz.
- /// The height represents the font size, which is usually the distance between the ascender and the descender
- /// of the font.
- ///
- public SizeF Size(XDotFont font) => new SizeF((float)Width, (float)font.Size);
- }
-
- public record struct XDotImage
- {
- public XDotRect Pos { get; init; }
- public string Name { get; init; }
- }
-
- public record struct XDotFont
- {
- ///
- /// Size in points
- ///
- public double Size { get; init; }
- public string Name { get; init; }
- public static XDotFont Default => new() { Size = 14, Name = "Times-Roman" };
- }
-
- [Flags]
- public enum XDotFontChar
- {
- None = 0,
- Bold = 1,
- Italic = 2,
- Underline = 4,
- Superscript = 8,
- Subscript = 16,
- StrikeThrough = 32,
- Overline = 64,
- }
+ public PointF Anchor() => new PointF((float)X, (float)Y);
///
- /// See https://graphviz.org/docs/outputs/canon/#xdot for semantics
+ /// The width represents the estimated width of the text by GraphViz.
+ /// The height represents the font size, which is usually the distance between the ascender and the descender
+ /// of the font.
///
- public abstract record class XDotOp
- {
- private XDotOp() { }
+ public SizeF Size(XDotFont font) => new SizeF((float)Width, (float)font.Size);
+}
- public sealed record class FilledEllipse : XDotOp
- {
- public XDotRect Value { get; init; }
- }
- public sealed record class UnfilledEllipse : XDotOp
- {
- public XDotRect Value { get; init; }
- }
- public sealed record class FilledPolygon : XDotOp
- {
- public XDotPolyline Value { get; init; }
- }
- public sealed record class UnfilledPolygon : XDotOp
- {
- public XDotPolyline Value { get; init; }
- }
- public sealed record class PolyLine : XDotOp
- {
- public XDotPolyline Value { get; init; }
- }
- public sealed record class FilledBezier : XDotOp
- {
- public XDotPolyline Value { get; init; }
- }
- public sealed record class UnfilledBezier : XDotOp
- {
- public XDotPolyline Value { get; init; }
- }
- public sealed record class Text : XDotOp
- {
- public XDotText Value { get; init; }
- }
- public sealed record class Image : XDotOp
- {
- public XDotImage Value { get; init; }
- }
- public sealed record class FillColor : XDotOp
- {
- public string Value { get; init; }
- }
- public sealed record class PenColor : XDotOp
- {
- public string Value { get; init; }
- }
- public sealed record class GradFillColor : XDotOp
- {
- public XDotGradColor Value { get; init; }
- }
- public sealed record class GradPenColor : XDotOp
- {
- public XDotGradColor Value { get; init; }
- }
- public sealed record class Font : XDotOp
- {
- public XDotFont Value { get; init; }
- }
- public sealed record class Style : XDotOp
- {
- public string Value { get; init; }
- }
- public sealed record class FontChar : XDotOp
- {
- public XDotFontChar Value { get; init; }
- }
+public record struct XDotImage
+{
+ public XDotRect Pos { get; init; }
+ public string Name { get; init; }
+}
+
+public record struct XDotFont
+{
+ ///
+ /// Size in points
+ ///
+ public double Size { get; init; }
+ public string Name { get; init; }
+ public static XDotFont Default => new() { Size = 14, Name = "Times-Roman" };
+}
+
+[Flags]
+public enum XDotFontChar
+{
+ None = 0,
+ Bold = 1,
+ Italic = 2,
+ Underline = 4,
+ Superscript = 8,
+ Subscript = 16,
+ StrikeThrough = 32,
+ Overline = 64,
+}
+
+///
+/// See https://graphviz.org/docs/outputs/canon/#xdot for semantics
+///
+public abstract record class XDotOp
+{
+ private XDotOp() { }
+
+ public sealed record class FilledEllipse : XDotOp
+ {
+ public XDotRect Value { get; init; }
+ }
+ public sealed record class UnfilledEllipse : XDotOp
+ {
+ public XDotRect Value { get; init; }
+ }
+ public sealed record class FilledPolygon : XDotOp
+ {
+ public XDotPolyline Value { get; init; }
+ }
+ public sealed record class UnfilledPolygon : XDotOp
+ {
+ public XDotPolyline Value { get; init; }
+ }
+ public sealed record class PolyLine : XDotOp
+ {
+ public XDotPolyline Value { get; init; }
+ }
+ public sealed record class FilledBezier : XDotOp
+ {
+ public XDotPolyline Value { get; init; }
+ }
+ public sealed record class UnfilledBezier : XDotOp
+ {
+ public XDotPolyline Value { get; init; }
+ }
+ public sealed record class Text : XDotOp
+ {
+ public XDotText Value { get; init; }
+ }
+ public sealed record class Image : XDotOp
+ {
+ public XDotImage Value { get; init; }
+ }
+ public sealed record class FillColor : XDotOp
+ {
+ public string Value { get; init; }
+ }
+ public sealed record class PenColor : XDotOp
+ {
+ public string Value { get; init; }
+ }
+ public sealed record class GradFillColor : XDotOp
+ {
+ public XDotGradColor Value { get; init; }
+ }
+ public sealed record class GradPenColor : XDotOp
+ {
+ public XDotGradColor Value { get; init; }
+ }
+ public sealed record class Font : XDotOp
+ {
+ public XDotFont Value { get; init; }
+ }
+ public sealed record class Style : XDotOp
+ {
+ public string Value { get; init; }
+ }
+ public sealed record class FontChar : XDotOp
+ {
+ public XDotFontChar Value { get; init; }
}
}
diff --git a/Rubjerg.Graphviz/XDotFFI.cs b/Rubjerg.Graphviz/XDotFFI.cs
index 074e69f..65cf459 100644
--- a/Rubjerg.Graphviz/XDotFFI.cs
+++ b/Rubjerg.Graphviz/XDotFFI.cs
@@ -1,205 +1,203 @@
using System;
using System.Runtime.InteropServices;
-namespace Rubjerg.Graphviz
-{
- ///
- /// See https://graphviz.org/docs/outputs/canon/#xdot
- ///
- internal static class XDotFFI
- {
- [DllImport("xdot.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr parseXDot([MarshalAs(UnmanagedType.LPStr)] string xdotString);
- [DllImport("xdot.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern void freeXDot(IntPtr xdotptr);
+namespace Rubjerg.Graphviz;
- // Accessors for xdot
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern UIntPtr get_cnt(IntPtr xdot);
+///
+/// See https://graphviz.org/docs/outputs/canon/#xdot
+///
+internal static class XDotFFI
+{
+ [DllImport("xdot.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr parseXDot([MarshalAs(UnmanagedType.LPStr)] string xdotString);
+ [DllImport("xdot.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern void freeXDot(IntPtr xdotptr);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_ops(IntPtr xdot);
+ // Accessors for xdot
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern UIntPtr get_cnt(IntPtr xdot);
- // Accessors for xdot_image
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr get_name_image(IntPtr img);
- public static string GetNameImage(IntPtr img) => Marshal.PtrToStringAnsi(get_name_image(img));
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_ops(IntPtr xdot);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_pos(IntPtr img);
+ // Accessors for xdot_image
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr get_name_image(IntPtr img);
+ public static string GetNameImage(IntPtr img) => Marshal.PtrToStringAnsi(get_name_image(img));
- // Accessors for xdot_font
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_size(IntPtr font);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_pos(IntPtr img);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr get_name_font(IntPtr font);
- public static string GetNameFont(IntPtr img) => Marshal.PtrToStringAnsi(get_name_font(img));
+ // Accessors for xdot_font
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_size(IntPtr font);
- // Accessors for xdot_op
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern XDotKind get_kind(IntPtr op);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr get_name_font(IntPtr font);
+ public static string GetNameFont(IntPtr img) => Marshal.PtrToStringAnsi(get_name_font(img));
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_ellipse(IntPtr op);
+ // Accessors for xdot_op
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern XDotKind get_kind(IntPtr op);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_polygon(IntPtr op);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_ellipse(IntPtr op);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_polyline(IntPtr op);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_polygon(IntPtr op);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_bezier(IntPtr op);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_polyline(IntPtr op);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_text(IntPtr op);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_bezier(IntPtr op);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_image(IntPtr op);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_text(IntPtr op);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr get_color(IntPtr op);
- public static string GetColor(IntPtr op) => Marshal.PtrToStringAnsi(get_color(op));
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_image(IntPtr op);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_grad_color(IntPtr op);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr get_color(IntPtr op);
+ public static string GetColor(IntPtr op) => Marshal.PtrToStringAnsi(get_color(op));
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_font(IntPtr op);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_grad_color(IntPtr op);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr get_style(IntPtr op);
- public static string GetStyle(IntPtr op) => Marshal.PtrToStringAnsi(get_style(op));
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_font(IntPtr op);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern uint get_fontchar(IntPtr op);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr get_style(IntPtr op);
+ public static string GetStyle(IntPtr op) => Marshal.PtrToStringAnsi(get_style(op));
- // Accessors for xdot_color
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern XDotGradType get_type(IntPtr clr);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern uint get_fontchar(IntPtr op);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr get_clr(IntPtr clr);
- public static string GetClr(IntPtr clr) => Marshal.PtrToStringAnsi(get_clr(clr));
+ // Accessors for xdot_color
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern XDotGradType get_type(IntPtr clr);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_ling(IntPtr clr);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr get_clr(IntPtr clr);
+ public static string GetClr(IntPtr clr) => Marshal.PtrToStringAnsi(get_clr(clr));
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_ring(IntPtr clr);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_ling(IntPtr clr);
- // Accessors for xdot_text
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_x_text(IntPtr txt);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_ring(IntPtr clr);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_y_text(IntPtr txt);
+ // Accessors for xdot_text
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_x_text(IntPtr txt);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern XDotAlign get_align(IntPtr txt);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_y_text(IntPtr txt);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_width(IntPtr txt);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern XDotAlign get_align(IntPtr txt);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr get_text_str(IntPtr txt);
- public static string GetTextStr(IntPtr txt) => Marshal.PtrToStringAnsi(get_text_str(txt));
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_width(IntPtr txt);
- // Accessors for xdot_linear_grad
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_x0_ling(IntPtr ling);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr get_text_str(IntPtr txt);
+ public static string GetTextStr(IntPtr txt) => Marshal.PtrToStringAnsi(get_text_str(txt));
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_y0_ling(IntPtr ling);
+ // Accessors for xdot_linear_grad
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_x0_ling(IntPtr ling);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_x1_ling(IntPtr ling);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_y0_ling(IntPtr ling);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_y1_ling(IntPtr ling);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_x1_ling(IntPtr ling);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern int get_n_stops_ling(IntPtr ling);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_y1_ling(IntPtr ling);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_stops_ling(IntPtr ling);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int get_n_stops_ling(IntPtr ling);
- // Accessors for xdot_radial_grad
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_x0_ring(IntPtr ring);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_stops_ling(IntPtr ling);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_y0_ring(IntPtr ring);
+ // Accessors for xdot_radial_grad
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_x0_ring(IntPtr ring);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_r0_ring(IntPtr ring);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_y0_ring(IntPtr ring);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_x1_ring(IntPtr ring);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_r0_ring(IntPtr ring);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_y1_ring(IntPtr ring);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_x1_ring(IntPtr ring);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_r1_ring(IntPtr ring);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_y1_ring(IntPtr ring);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern int get_n_stops_ring(IntPtr ring);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_r1_ring(IntPtr ring);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_stops_ring(IntPtr ring);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern int get_n_stops_ring(IntPtr ring);
- // Accessors for xdot_color_stop
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern float get_frac(IntPtr stop);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_stops_ring(IntPtr ring);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- private static extern IntPtr get_color_stop(IntPtr stop);
- public static string GetColorStop(IntPtr stop) => Marshal.PtrToStringAnsi(get_color_stop(stop));
+ // Accessors for xdot_color_stop
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern float get_frac(IntPtr stop);
- // Accessors for xdot_polyline
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern UIntPtr get_cnt_polyline(IntPtr polyline);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ private static extern IntPtr get_color_stop(IntPtr stop);
+ public static string GetColorStop(IntPtr stop) => Marshal.PtrToStringAnsi(get_color_stop(stop));
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_pts_polyline(IntPtr polyline);
+ // Accessors for xdot_polyline
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern UIntPtr get_cnt_polyline(IntPtr polyline);
- // Accessors for xdot_point
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_x_point(IntPtr point);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_pts_polyline(IntPtr polyline);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_y_point(IntPtr point);
+ // Accessors for xdot_point
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_x_point(IntPtr point);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_z_point(IntPtr point);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_y_point(IntPtr point);
- // Accessors for xdot_rect
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_x_rect(IntPtr rect);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_z_point(IntPtr point);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_y_rect(IntPtr rect);
+ // Accessors for xdot_rect
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_x_rect(IntPtr rect);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_w_rect(IntPtr rect);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_y_rect(IntPtr rect);
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern double get_h_rect(IntPtr rect);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_w_rect(IntPtr rect);
- // Index function for xdot_color_stop array
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_color_stop_at_index(IntPtr stops, int index);
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern double get_h_rect(IntPtr rect);
- // Index function for xdot_op array
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_op_at_index(IntPtr ops, int index);
+ // Index function for xdot_color_stop array
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_color_stop_at_index(IntPtr stops, int index);
- // Index function for xdot_pt array
- [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
- public static extern IntPtr get_pt_at_index(IntPtr pts, int index);
+ // Index function for xdot_op array
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_op_at_index(IntPtr ops, int index);
- }
+ // Index function for xdot_pt array
+ [DllImport("GraphvizWrapper.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
+ public static extern IntPtr get_pt_at_index(IntPtr pts, int index);
}
diff --git a/Rubjerg.Graphviz/XDotParser.cs b/Rubjerg.Graphviz/XDotParser.cs
index 77dc4bf..67cf39c 100644
--- a/Rubjerg.Graphviz/XDotParser.cs
+++ b/Rubjerg.Graphviz/XDotParser.cs
@@ -2,350 +2,349 @@
using System.Collections.Generic;
using System.Linq;
-namespace Rubjerg.Graphviz
+namespace Rubjerg.Graphviz;
+
+// These internal types are only used for marshaling
+// We replace them with more idiomatic types
+internal enum XDotGradType
{
- // These internal types are only used for marshaling
- // We replace them with more idiomatic types
- internal enum XDotGradType
- {
- None,
- Linear,
- Radial
- }
+ None,
+ Linear,
+ Radial
+}
- internal enum XDotKind
- {
- FilledEllipse, UnfilledEllipse,
- FilledPolygon, UnfilledPolygon,
- FilledBezier, UnfilledBezier,
- Polyline, Text,
- FillColor, PenColor, Font, Style, Image,
- GradFillColor, GradPenColor,
- FontChar
- }
+internal enum XDotKind
+{
+ FilledEllipse, UnfilledEllipse,
+ FilledPolygon, UnfilledPolygon,
+ FilledBezier, UnfilledBezier,
+ Polyline, Text,
+ FillColor, PenColor, Font, Style, Image,
+ GradFillColor, GradPenColor,
+ FontChar
+}
- internal struct XDot
- {
- public int Count { get; set; } // Number of xdot ops
- public XDotOp[] Ops { get; set; } // xdot operations
- }
+internal struct XDot
+{
+ public int Count { get; set; } // Number of xdot ops
+ public XDotOp[] Ops { get; set; } // xdot operations
+}
- internal static class XDotParser
+internal static class XDotParser
+{
+ public static List ParseXDot(string xdotString)
{
- public static List ParseXDot(string xdotString)
+ IntPtr xdot = XDotFFI.parseXDot(xdotString);
+ try
{
- IntPtr xdot = XDotFFI.parseXDot(xdotString);
- try
- {
- return TranslateXDot(xdot);
- }
- finally
- {
- if (xdot != IntPtr.Zero)
- {
- XDotFFI.freeXDot(xdot);
- }
- }
+ return TranslateXDot(xdot);
}
-
- internal static List TranslateXDot(IntPtr xdotPtr)
+ finally
{
- if (xdotPtr == IntPtr.Zero)
- throw new ArgumentNullException(nameof(xdotPtr));
-
- XDot xdot = new XDot
- {
- Count = (int)XDotFFI.get_cnt(xdotPtr)
- };
-
- // Translate the array of XDotOps
- int count = xdot.Count;
- xdot.Ops = new XDotOp[count];
- var opsPtr = XDotFFI.get_ops(xdotPtr);
- for (int i = 0; i < count; ++i)
+ if (xdot != IntPtr.Zero)
{
- IntPtr xdotOpPtr = XDotFFI.get_op_at_index(opsPtr, i);
- xdot.Ops[i] = TranslateXDotOp(xdotOpPtr);
+ XDotFFI.freeXDot(xdot);
}
-
- return xdot.Ops.ToList();
}
+ }
- private static XDotOp TranslateXDotOp(IntPtr xdotOpPtr)
- {
- if (xdotOpPtr == IntPtr.Zero)
- throw new ArgumentNullException(nameof(xdotOpPtr));
+ internal static List TranslateXDot(IntPtr xdotPtr)
+ {
+ if (xdotPtr == IntPtr.Zero)
+ throw new ArgumentNullException(nameof(xdotPtr));
- var kind = XDotFFI.get_kind(xdotOpPtr);
- switch (kind)
- {
- case XDotKind.FilledEllipse:
- return new XDotOp.FilledEllipse()
- {
- Value = TranslateEllipse(XDotFFI.get_ellipse(xdotOpPtr))
- };
- case XDotKind.UnfilledEllipse:
- return new XDotOp.UnfilledEllipse()
- {
- Value = TranslateEllipse(XDotFFI.get_ellipse(xdotOpPtr))
- };
- case XDotKind.FilledPolygon:
- return new XDotOp.FilledPolygon()
- {
- Value = TranslatePolyline(XDotFFI.get_polyline(xdotOpPtr))
- };
- case XDotKind.UnfilledPolygon:
- return new XDotOp.FilledPolygon()
- {
- Value = TranslatePolyline(XDotFFI.get_polyline(xdotOpPtr))
- };
- case XDotKind.FilledBezier:
- return new XDotOp.FilledBezier()
- {
- Value = TranslatePolyline(XDotFFI.get_polyline(xdotOpPtr))
- };
- case XDotKind.UnfilledBezier:
- return new XDotOp.UnfilledBezier()
- {
- Value = TranslatePolyline(XDotFFI.get_polyline(xdotOpPtr))
- };
- case XDotKind.Polyline:
- return new XDotOp.PolyLine()
- {
- Value = TranslatePolyline(XDotFFI.get_polyline(xdotOpPtr))
- };
- case XDotKind.Text:
- return new XDotOp.Text()
- {
- Value = TranslateText(XDotFFI.get_text(xdotOpPtr))
- };
- case XDotKind.FillColor:
- return new XDotOp.FillColor()
- {
- Value = XDotFFI.GetColor(xdotOpPtr)
- };
- case XDotKind.PenColor:
- return new XDotOp.PenColor()
- {
- Value = XDotFFI.GetColor(xdotOpPtr)
- };
- case XDotKind.GradFillColor:
- return new XDotOp.GradFillColor()
- {
- Value = TranslateGradColor(XDotFFI.get_grad_color(xdotOpPtr))
- };
- case XDotKind.GradPenColor:
- return new XDotOp.GradPenColor()
- {
- Value = TranslateGradColor(XDotFFI.get_grad_color(xdotOpPtr))
- };
- case XDotKind.Font:
- return new XDotOp.Font()
- {
- Value = TranslateFont(XDotFFI.get_font(xdotOpPtr))
- };
- case XDotKind.Style:
- return new XDotOp.Style()
- {
- Value = XDotFFI.GetStyle(xdotOpPtr)
- };
- case XDotKind.Image:
- return new XDotOp.Image()
- {
- Value = TranslateImage(XDotFFI.get_image(xdotOpPtr))
- };
- case XDotKind.FontChar:
- return new XDotOp.FontChar()
- {
- Value = TranslateFontChar(XDotFFI.get_fontchar(xdotOpPtr))
- };
- default:
- throw new ArgumentException($"Unexpected XDotOp.Kind: {kind}");
- }
+ XDot xdot = new XDot
+ {
+ Count = (int)XDotFFI.get_cnt(xdotPtr)
+ };
+
+ // Translate the array of XDotOps
+ int count = xdot.Count;
+ xdot.Ops = new XDotOp[count];
+ var opsPtr = XDotFFI.get_ops(xdotPtr);
+ for (int i = 0; i < count; ++i)
+ {
+ IntPtr xdotOpPtr = XDotFFI.get_op_at_index(opsPtr, i);
+ xdot.Ops[i] = TranslateXDotOp(xdotOpPtr);
}
- private static XDotFontChar TranslateFontChar(uint value)
+ return xdot.Ops.ToList();
+ }
+
+ private static XDotOp TranslateXDotOp(IntPtr xdotOpPtr)
+ {
+ if (xdotOpPtr == IntPtr.Zero)
+ throw new ArgumentNullException(nameof(xdotOpPtr));
+
+ var kind = XDotFFI.get_kind(xdotOpPtr);
+ switch (kind)
{
- return (XDotFontChar)(int)value;
+ case XDotKind.FilledEllipse:
+ return new XDotOp.FilledEllipse()
+ {
+ Value = TranslateEllipse(XDotFFI.get_ellipse(xdotOpPtr))
+ };
+ case XDotKind.UnfilledEllipse:
+ return new XDotOp.UnfilledEllipse()
+ {
+ Value = TranslateEllipse(XDotFFI.get_ellipse(xdotOpPtr))
+ };
+ case XDotKind.FilledPolygon:
+ return new XDotOp.FilledPolygon()
+ {
+ Value = TranslatePolyline(XDotFFI.get_polyline(xdotOpPtr))
+ };
+ case XDotKind.UnfilledPolygon:
+ return new XDotOp.FilledPolygon()
+ {
+ Value = TranslatePolyline(XDotFFI.get_polyline(xdotOpPtr))
+ };
+ case XDotKind.FilledBezier:
+ return new XDotOp.FilledBezier()
+ {
+ Value = TranslatePolyline(XDotFFI.get_polyline(xdotOpPtr))
+ };
+ case XDotKind.UnfilledBezier:
+ return new XDotOp.UnfilledBezier()
+ {
+ Value = TranslatePolyline(XDotFFI.get_polyline(xdotOpPtr))
+ };
+ case XDotKind.Polyline:
+ return new XDotOp.PolyLine()
+ {
+ Value = TranslatePolyline(XDotFFI.get_polyline(xdotOpPtr))
+ };
+ case XDotKind.Text:
+ return new XDotOp.Text()
+ {
+ Value = TranslateText(XDotFFI.get_text(xdotOpPtr))
+ };
+ case XDotKind.FillColor:
+ return new XDotOp.FillColor()
+ {
+ Value = XDotFFI.GetColor(xdotOpPtr)
+ };
+ case XDotKind.PenColor:
+ return new XDotOp.PenColor()
+ {
+ Value = XDotFFI.GetColor(xdotOpPtr)
+ };
+ case XDotKind.GradFillColor:
+ return new XDotOp.GradFillColor()
+ {
+ Value = TranslateGradColor(XDotFFI.get_grad_color(xdotOpPtr))
+ };
+ case XDotKind.GradPenColor:
+ return new XDotOp.GradPenColor()
+ {
+ Value = TranslateGradColor(XDotFFI.get_grad_color(xdotOpPtr))
+ };
+ case XDotKind.Font:
+ return new XDotOp.Font()
+ {
+ Value = TranslateFont(XDotFFI.get_font(xdotOpPtr))
+ };
+ case XDotKind.Style:
+ return new XDotOp.Style()
+ {
+ Value = XDotFFI.GetStyle(xdotOpPtr)
+ };
+ case XDotKind.Image:
+ return new XDotOp.Image()
+ {
+ Value = TranslateImage(XDotFFI.get_image(xdotOpPtr))
+ };
+ case XDotKind.FontChar:
+ return new XDotOp.FontChar()
+ {
+ Value = TranslateFontChar(XDotFFI.get_fontchar(xdotOpPtr))
+ };
+ default:
+ throw new ArgumentException($"Unexpected XDotOp.Kind: {kind}");
}
- private static XDotImage TranslateImage(IntPtr imagePtr)
+ }
+
+ private static XDotFontChar TranslateFontChar(uint value)
+ {
+ return (XDotFontChar)(int)value;
+ }
+ private static XDotImage TranslateImage(IntPtr imagePtr)
+ {
+ XDotImage image = new XDotImage
{
- XDotImage image = new XDotImage
- {
- Pos = TranslateRect(XDotFFI.get_pos(imagePtr)),
- Name = XDotFFI.GetNameImage(imagePtr)
- };
+ Pos = TranslateRect(XDotFFI.get_pos(imagePtr)),
+ Name = XDotFFI.GetNameImage(imagePtr)
+ };
- return image;
- }
+ return image;
+ }
- private static XDotFont TranslateFont(IntPtr fontPtr)
+ private static XDotFont TranslateFont(IntPtr fontPtr)
+ {
+ XDotFont font = new XDotFont
{
- XDotFont font = new XDotFont
- {
- Size = XDotFFI.get_size(fontPtr),
- Name = XDotFFI.GetNameFont(fontPtr)
- };
+ Size = XDotFFI.get_size(fontPtr),
+ Name = XDotFFI.GetNameFont(fontPtr)
+ };
- return font;
- }
+ return font;
+ }
- private static XDotRect TranslateEllipse(IntPtr ellipsePtr)
+ private static XDotRect TranslateEllipse(IntPtr ellipsePtr)
+ {
+ XDotRect ellipse = new XDotRect
{
- XDotRect ellipse = new XDotRect
- {
- X = XDotFFI.get_x_rect(ellipsePtr),
- Y = XDotFFI.get_y_rect(ellipsePtr),
- Width = XDotFFI.get_w_rect(ellipsePtr),
- Height = XDotFFI.get_h_rect(ellipsePtr)
- };
+ X = XDotFFI.get_x_rect(ellipsePtr),
+ Y = XDotFFI.get_y_rect(ellipsePtr),
+ Width = XDotFFI.get_w_rect(ellipsePtr),
+ Height = XDotFFI.get_h_rect(ellipsePtr)
+ };
- return ellipse;
- }
+ return ellipse;
+ }
- private static XDotGradColor TranslateGradColor(IntPtr colorPtr)
+ private static XDotGradColor TranslateGradColor(IntPtr colorPtr)
+ {
+ var type = XDotFFI.get_type(colorPtr);
+ switch (type)
{
- var type = XDotFFI.get_type(colorPtr);
- switch (type)
- {
- case XDotGradType.None:
- return new XDotGradColor.Uniform()
- {
- Color = XDotFFI.GetClr(colorPtr)
- };
- case XDotGradType.Linear:
- return new XDotGradColor.LinearGradient()
- {
- LinearGrad = TranslateLinearGrad(XDotFFI.get_ling(colorPtr))
- };
- case XDotGradType.Radial:
- return new XDotGradColor.RadialGradient()
- {
- RadialGrad = TranslateRadialGrad(XDotFFI.get_ring(colorPtr))
- };
- default:
- throw new ArgumentException($"Unexpected XDotColor.Type: {type}");
- }
+ case XDotGradType.None:
+ return new XDotGradColor.Uniform()
+ {
+ Color = XDotFFI.GetClr(colorPtr)
+ };
+ case XDotGradType.Linear:
+ return new XDotGradColor.LinearGradient()
+ {
+ LinearGrad = TranslateLinearGrad(XDotFFI.get_ling(colorPtr))
+ };
+ case XDotGradType.Radial:
+ return new XDotGradColor.RadialGradient()
+ {
+ RadialGrad = TranslateRadialGrad(XDotFFI.get_ring(colorPtr))
+ };
+ default:
+ throw new ArgumentException($"Unexpected XDotColor.Type: {type}");
}
+ }
- private static XDotLinearGrad TranslateLinearGrad(IntPtr lingPtr)
+ private static XDotLinearGrad TranslateLinearGrad(IntPtr lingPtr)
+ {
+ int count = XDotFFI.get_n_stops_ling(lingPtr);
+ XDotLinearGrad linearGrad = new XDotLinearGrad
{
- int count = XDotFFI.get_n_stops_ling(lingPtr);
- XDotLinearGrad linearGrad = new XDotLinearGrad
- {
- X0 = XDotFFI.get_x0_ling(lingPtr),
- Y0 = XDotFFI.get_y0_ling(lingPtr),
- X1 = XDotFFI.get_x1_ling(lingPtr),
- Y1 = XDotFFI.get_y1_ling(lingPtr),
- NStops = count,
- Stops = new XDotColorStop[count]
- };
-
- // Translate the array of ColorStops
- var stopsPtr = XDotFFI.get_stops_ling(lingPtr);
- for (int i = 0; i < count; ++i)
- {
- IntPtr colorStopPtr = XDotFFI.get_color_stop_at_index(stopsPtr, i);
- linearGrad.Stops[i] = TranslateColorStop(colorStopPtr);
- }
-
- return linearGrad;
+ X0 = XDotFFI.get_x0_ling(lingPtr),
+ Y0 = XDotFFI.get_y0_ling(lingPtr),
+ X1 = XDotFFI.get_x1_ling(lingPtr),
+ Y1 = XDotFFI.get_y1_ling(lingPtr),
+ NStops = count,
+ Stops = new XDotColorStop[count]
+ };
+
+ // Translate the array of ColorStops
+ var stopsPtr = XDotFFI.get_stops_ling(lingPtr);
+ for (int i = 0; i < count; ++i)
+ {
+ IntPtr colorStopPtr = XDotFFI.get_color_stop_at_index(stopsPtr, i);
+ linearGrad.Stops[i] = TranslateColorStop(colorStopPtr);
}
- private static XDotRadialGrad TranslateRadialGrad(IntPtr ringPtr)
- {
- int count = XDotFFI.get_n_stops_ring(ringPtr);
- XDotRadialGrad radialGrad = new XDotRadialGrad
- {
- X0 = XDotFFI.get_x0_ring(ringPtr),
- Y0 = XDotFFI.get_y0_ring(ringPtr),
- R0 = XDotFFI.get_r0_ring(ringPtr),
- X1 = XDotFFI.get_x1_ring(ringPtr),
- Y1 = XDotFFI.get_y1_ring(ringPtr),
- R1 = XDotFFI.get_r1_ring(ringPtr),
- NStops = count,
- Stops = new XDotColorStop[count]
- };
-
- // Translate the array of ColorStops
- var stopsPtr = XDotFFI.get_stops_ring(ringPtr);
- for (int i = 0; i < count; ++i)
- {
- IntPtr colorStopPtr = XDotFFI.get_color_stop_at_index(stopsPtr, i);
- radialGrad.Stops[i] = TranslateColorStop(colorStopPtr);
- }
+ return linearGrad;
+ }
- return radialGrad;
+ private static XDotRadialGrad TranslateRadialGrad(IntPtr ringPtr)
+ {
+ int count = XDotFFI.get_n_stops_ring(ringPtr);
+ XDotRadialGrad radialGrad = new XDotRadialGrad
+ {
+ X0 = XDotFFI.get_x0_ring(ringPtr),
+ Y0 = XDotFFI.get_y0_ring(ringPtr),
+ R0 = XDotFFI.get_r0_ring(ringPtr),
+ X1 = XDotFFI.get_x1_ring(ringPtr),
+ Y1 = XDotFFI.get_y1_ring(ringPtr),
+ R1 = XDotFFI.get_r1_ring(ringPtr),
+ NStops = count,
+ Stops = new XDotColorStop[count]
+ };
+
+ // Translate the array of ColorStops
+ var stopsPtr = XDotFFI.get_stops_ring(ringPtr);
+ for (int i = 0; i < count; ++i)
+ {
+ IntPtr colorStopPtr = XDotFFI.get_color_stop_at_index(stopsPtr, i);
+ radialGrad.Stops[i] = TranslateColorStop(colorStopPtr);
}
- private static XDotColorStop TranslateColorStop(IntPtr stopPtr)
+ return radialGrad;
+ }
+
+ private static XDotColorStop TranslateColorStop(IntPtr stopPtr)
+ {
+ XDotColorStop colorStop = new XDotColorStop
{
- XDotColorStop colorStop = new XDotColorStop
- {
- Frac = XDotFFI.get_frac(stopPtr),
- Color = XDotFFI.GetColorStop(stopPtr)
- };
+ Frac = XDotFFI.get_frac(stopPtr),
+ Color = XDotFFI.GetColorStop(stopPtr)
+ };
- return colorStop;
- }
+ return colorStop;
+ }
- private static XDotPolyline TranslatePolyline(IntPtr polylinePtr)
+ private static XDotPolyline TranslatePolyline(IntPtr polylinePtr)
+ {
+ int count = (int)XDotFFI.get_cnt_polyline(polylinePtr);
+ XDotPolyline polyline = new XDotPolyline
{
- int count = (int)XDotFFI.get_cnt_polyline(polylinePtr);
- XDotPolyline polyline = new XDotPolyline
- {
- Count = count,
- Points = new XDotPoint[count]
- };
+ Count = count,
+ Points = new XDotPoint[count]
+ };
- // Translate the array of Points
- var pointsPtr = XDotFFI.get_pts_polyline(polylinePtr);
- for (int i = 0; i < count; ++i)
- {
- IntPtr pointPtr = XDotFFI.get_pt_at_index(pointsPtr, i);
- polyline.Points[i] = TranslatePoint(pointPtr);
- }
-
- return polyline;
+ // Translate the array of Points
+ var pointsPtr = XDotFFI.get_pts_polyline(polylinePtr);
+ for (int i = 0; i < count; ++i)
+ {
+ IntPtr pointPtr = XDotFFI.get_pt_at_index(pointsPtr, i);
+ polyline.Points[i] = TranslatePoint(pointPtr);
}
- private static XDotPoint TranslatePoint(IntPtr pointPtr)
+ return polyline;
+ }
+
+ private static XDotPoint TranslatePoint(IntPtr pointPtr)
+ {
+ XDotPoint point = new XDotPoint
{
- XDotPoint point = new XDotPoint
- {
- X = XDotFFI.get_x_point(pointPtr),
- Y = XDotFFI.get_y_point(pointPtr),
- Z = XDotFFI.get_z_point(pointPtr)
- };
+ X = XDotFFI.get_x_point(pointPtr),
+ Y = XDotFFI.get_y_point(pointPtr),
+ Z = XDotFFI.get_z_point(pointPtr)
+ };
- return point;
- }
+ return point;
+ }
- private static XDotRect TranslateRect(IntPtr rectPtr)
+ private static XDotRect TranslateRect(IntPtr rectPtr)
+ {
+ XDotRect rect = new XDotRect
{
- XDotRect rect = new XDotRect
- {
- X = XDotFFI.get_x_rect(rectPtr),
- Y = XDotFFI.get_y_rect(rectPtr),
- Width = XDotFFI.get_w_rect(rectPtr),
- Height = XDotFFI.get_h_rect(rectPtr)
- };
+ X = XDotFFI.get_x_rect(rectPtr),
+ Y = XDotFFI.get_y_rect(rectPtr),
+ Width = XDotFFI.get_w_rect(rectPtr),
+ Height = XDotFFI.get_h_rect(rectPtr)
+ };
- return rect;
- }
+ return rect;
+ }
- private static XDotText TranslateText(IntPtr txtPtr)
+ private static XDotText TranslateText(IntPtr txtPtr)
+ {
+ XDotText text = new XDotText
{
- XDotText text = new XDotText
- {
- X = XDotFFI.get_x_text(txtPtr),
- Y = XDotFFI.get_y_text(txtPtr),
- Align = XDotFFI.get_align(txtPtr),
- Width = XDotFFI.get_width(txtPtr),
- Text = XDotFFI.GetTextStr(txtPtr)
- };
-
- return text;
- }
+ X = XDotFFI.get_x_text(txtPtr),
+ Y = XDotFFI.get_y_text(txtPtr),
+ Align = XDotFFI.get_align(txtPtr),
+ Width = XDotFFI.get_width(txtPtr),
+ Text = XDotFFI.GetTextStr(txtPtr)
+ };
+
+ return text;
}
}