Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support top-left oriented coordinate system #65

Merged
merged 12 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ jobs:

- name: Run Unittests With Coverage Calculation (.NET 4.8)

run: packages\opencover\4.7.1221\tools\OpenCover.Console.exe -skipautoprops -register '-target:bash.exe' -targetargs:'nunit-console.sh Rubjerg.Graphviz.Test\bin\x64\Release\net48\Rubjerg.Graphviz.Test.dll' '-filter:+[Rubjerg*]* -[Rubjerg.Graphviz.Test*]*'
run: packages\opencover\4.7.1221\tools\OpenCover.Console.exe -skipautoprops -returntargetcode -register '-target:bash.exe' -targetargs:'nunit-console.sh Rubjerg.Graphviz.Test\bin\x64\Release\net48\Rubjerg.Graphviz.Test.dll' '-filter:+[Rubjerg*]* -[Rubjerg.Graphviz.Test*]*'

- name: Run Unittests With Coverage Calculation (.NET 6)

run: packages\opencover\4.7.1221\tools\OpenCover.Console.exe -skipautoprops -register '-target:bash.exe' -targetargs:'nunit-console-netcore.sh Rubjerg.Graphviz.Test\bin\x64\Release\net6.0\Rubjerg.Graphviz.Test.dll' '-filter:+[Rubjerg*]* -[Rubjerg.Graphviz.Test*]*'
run: packages\opencover\4.7.1221\tools\OpenCover.Console.exe -skipautoprops -returntargetcode -register '-target:bash.exe' -targetargs:'nunit-console-netcore.sh Rubjerg.Graphviz.Test\bin\x64\Release\net6.0\Rubjerg.Graphviz.Test.dll' '-filter:+[Rubjerg*]* -[Rubjerg.Graphviz.Test*]*'

- name: Upload Coverage data
run: |
Expand Down
55 changes: 26 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ documents presented at the [Graphviz documentation page](https://graphviz.org/do

```cs
using NUnit.Framework;
using System.Drawing;
using System.Linq;

namespace Rubjerg.Graphviz.Test;
Expand All @@ -59,7 +58,7 @@ namespace Rubjerg.Graphviz.Test;
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 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.]+}";

Expand Down Expand Up @@ -131,57 +130,52 @@ public class Tutorial

// 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();
// of attributes. Graphviz outputs coordinates in a bottom-left originated coordinate system.
// But since many applications require rendering in a top-left originated coordinate system,
// we provide a way to translate the coordinates.
RootGraph layout = root.CreateLayout(coordinateSystem: CoordinateSystem.TopLeft);

// 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();
PointD position = nodeA.GetPosition();
Utils.AssertPattern(PointPattern, position.ToString());

RectangleF nodeboundingbox = nodeA.GetBoundingBox();
RectangleD 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();
PointD[] 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;
var activeFillColor = System.Drawing.Color.Black;
foreach (var op in nodeA.GetDrawing())
{
if (op is XDotOp.FillColor { Value: string htmlColor })
if (op is XDotOp.FillColor { Value: Color.Uniform { HtmlColor: var htmlColor } })
{
activeColor = ColorTranslator.FromHtml(htmlColor);
activeFillColor = System.Drawing.ColorTranslator.FromHtml(htmlColor);
}
else if (op is XDotOp.FilledEllipse { Value: var filledEllipse })
else if (op is XDotOp.FilledEllipse { Value: var boundingBox })
{
var boundingBox = filledEllipse.ToRectangleF();
Utils.AssertPattern(RectPattern, boundingBox.ToString());
}
// Handle any xdot operation you require
}

var activeFont = XDotFont.Default;
foreach (var op in nodeA.GetDrawing())
foreach (var op in nodeA.GetLabelDrawing())
{
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 })
if (op is XDotOp.Text { Value: var text })
{
var anchor = text.Anchor();
Utils.AssertPattern(PointPattern, anchor.ToString());
var boundingBox = text.TextBoundingBox(activeFont);
Utils.AssertPattern(PointPattern, text.Anchor.ToString());
var boundingBox = text.TextBoundingBoxEstimate();
Utils.AssertPattern(RectPattern, boundingBox.ToString());
Assert.AreEqual(text.Text, "A");
Assert.AreEqual(text.Font.Name, "Times-Roman");
}
// Handle any xdot operation you require
}
Expand Down Expand Up @@ -223,8 +217,8 @@ public class Tutorial
var layout = root.CreateLayout();

SubGraph cluster = layout.GetSubgraph("cluster_1");
RectangleF clusterbox = cluster.GetBoundingBox();
RectangleF rootgraphbox = layout.GetBoundingBox();
RectangleD clusterbox = cluster.GetBoundingBox();
RectangleD rootgraphbox = layout.GetBoundingBox();
Utils.AssertPattern(RectPattern, clusterbox.ToString());
Utils.AssertPattern(RectPattern, rootgraphbox.ToString());
}
Expand All @@ -234,14 +228,17 @@ public class Tutorial
{
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");
nodeA.SetAttribute("shape", "record");
// New line characters are not supported by record labels, and will be ignored by Graphviz
nodeA.SetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}");

var layout = root.CreateLayout();

// 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();
var rectLabels = layout.GetNode("A").GetRecordRectangleLabels().Select(l => l.Text).ToList();
Assert.AreEqual(9, rects.Count);
Assert.AreEqual(new[] { "1", "2", "3", "4", "5", "6", "7", "8", "9" }, rectLabels);
}

[Test, Order(5)]
Expand All @@ -261,8 +258,8 @@ public class Tutorial
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");
nodeB.SetAttribute("shape", "record");
nodeB.SetAttribute("label", $"<{validPortName}>1|2");

// The conversion function makes sure different strings don't accidentally map onto the same portname
Assert.AreNotEqual(Edge.ConvertUidToPortName(":"), Edge.ConvertUidToPortName("|"));
Expand Down
4 changes: 2 additions & 2 deletions Rubjerg.Graphviz.Test/CGraphEdgeCases.cs
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,8 @@ 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.SetAttribute("shape", "record");
nodeA.SetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}");

root.ComputeLayout();
var dotstr = root.ToDotString();
Expand Down
22 changes: 8 additions & 14 deletions Rubjerg.Graphviz.Test/OldTutorial.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using NUnit.Framework;
using System.Drawing;
using System.Linq;

#pragma warning disable CS0618 // Type or member is obsolete
Expand Down Expand Up @@ -69,28 +68,23 @@ public void Layouting()

// Or programatically read out the layout attributes
Node nodeA = root.GetNode("A");
PointF position = nodeA.GetPosition();
PointD 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());
RectangleD 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();
PointD[] 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
Expand Down Expand Up @@ -137,10 +131,10 @@ public void 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());
RectangleD clusterbox = cluster.GetBoundingBox();
RectangleD 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)]
Expand Down
17 changes: 9 additions & 8 deletions Rubjerg.Graphviz.Test/Reproductions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,16 @@ public void TestRecordShapeAlignment(string fontname, double fontsize, double ma

Node nodeA = root.GetOrAddNode("A");

nodeA.SafeSetAttribute("shape", "record", "");
nodeA.SafeSetAttribute("label", "{20 VH|{1|2}}", "");
nodeA.SetAttribute("shape", "record");
nodeA.SetAttribute("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 is fixed by passing snapOntoDrawingCoordinates: true
var rects = nodeA.GetRecordRectangles(snapOntoDrawingCoordinates: true).ToList();
Assert.That(rects[0].FarPoint().X, Is.EqualTo(rects[2].FarPoint().X));
}

// This test only failed when running in isolation
Expand Down Expand Up @@ -74,8 +75,8 @@ 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");
nodeA.SetAttribute("shape", "record");
nodeA.SetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}");

var dotString = root.ToDotString();
Assert.IsFalse(dotString.Contains("\r"));
Expand All @@ -88,8 +89,8 @@ 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");
nodeA.SetAttribute("shape", "record");
nodeA.SetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}");

var xdotGraph = root.CreateLayout();
var xNodeA = xdotGraph.GetNode("A");
Expand Down
40 changes: 17 additions & 23 deletions Rubjerg.Graphviz.Test/TestDotLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,13 @@ 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.GetBoundingBox(), default(RectangleD));
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.GetPosition(), default(PointD));
Assert.AreEqual(nodeA.GetBoundingBox(), default(RectangleD));
Assert.AreEqual(nodeA.GetSize(), default(SizeD));
Assert.AreEqual(nodeA.GetRecordRectangles().Count(), 0);
Assert.AreEqual(nodeA.GetDrawing().Count, 0);
Assert.AreEqual(nodeA.GetLabelDrawing().Count, 0);
Expand All @@ -65,12 +64,10 @@ public void TestLayoutMethodsWithInProcessLayout()

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));
Expand Down Expand Up @@ -98,12 +95,10 @@ public void TestLayoutMethodsWithLayout()
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));
Expand Down Expand Up @@ -179,15 +174,14 @@ 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");
nodeA.SetAttribute("shape", "record");
nodeA.SetAttribute("label", "1|2|3|{4|5}|6|{7|8|9}");

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));
Utils.AssertOrder(rects, r => (r.Origin.X, -r.Origin.Y));
Assert.That(rects.Count, Is.EqualTo(9));
}

Expand All @@ -196,8 +190,8 @@ public void TestEmptyRecordShapes()
{
RootGraph root = CreateUniqueTestGraph();
Node nodeA = root.GetOrAddNode("A");
nodeA.SafeSetAttribute("shape", "record", "");
nodeA.SafeSetAttribute("label", "||||", "");
nodeA.SetAttribute("shape", "record");
nodeA.SetAttribute("label", "||||");

root.ComputeLayout();

Expand All @@ -223,11 +217,11 @@ public void TestPortNameConversion(bool escape)
{
RootGraph root = CreateUniqueTestGraph();
Node node = root.GetOrAddNode("N");
node.SafeSetAttribute("shape", "record", "");
node.SafeSetAttribute("label", label, "");
node.SetAttribute("shape", "record");
node.SetAttribute("label", label);
Edge edge = root.GetOrAddEdge(node, node, "");
edge.SafeSetAttribute("tailport", port1 + ":n", "");
edge.SafeSetAttribute("headport", port2 + ":s", "");
edge.SetAttribute("tailport", port1 + ":n");
edge.SetAttribute("headport", port2 + ":s");
root.ToDotFile(GetTestFilePath("out.gv"));
}

Expand Down Expand Up @@ -269,12 +263,12 @@ public void TestLabelEscaping(bool escape)
{
RootGraph root = CreateUniqueTestGraph();
Node node1 = root.GetOrAddNode("1");
node1.SafeSetAttribute("shape", "record", "");
node1.SafeSetAttribute("label", label1, "");
node1.SetAttribute("shape", "record");
node1.SetAttribute("label", label1);
Node node2 = root.GetOrAddNode("2");
node2.SafeSetAttribute("label", label2, "");
node2.SetAttribute("label", label2);
Node node3 = root.GetOrAddNode("3");
node3.SafeSetAttribute("label", label3, "");
node3.SetAttribute("label", label3);
root.ToDotFile(GetTestFilePath("out.gv"));
}

Expand Down
Loading
Loading