Skip to content

Latest commit

History

73 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Graphviz.NetWrapper

codecov

Supported platforms

At the moment, Rubjerg.Graphviz ships with a bunch of precompiled Graphviz dlls built for 64 bit Windows. This library is compatible with .NET Standard 2.0. The unit tests run against .NET Framework 4.8 and .NET 8.0. In the future support may be extended to other platforms.

Contributing

This project aims to provide a thin .NET shell around the Graphviz C libraries, together with some convenience functionality that helps abstracting away some of the peculiarities of the Graphviz library and make it easier to integrate in an application. Pull request that fall within the scope of this project are welcome.

Installation

You can either add this library as a nuget package to project, or include the source and add a project reference.

To run the code from this library, you must have the Microsoft Visual C++ Redistributable (2015-2022) installed, which provides the required runtime libraries. You can download it from the official Microsoft website.

Adding as a Nuget package

Add the Rubjerg.Graphviz nuget package to your project.

Adding the Rubjerg.Graphviz code to your project or solution

  1. Make this code available to your own code, e.g. by adding this repository as a git submodule to your own repository.
  2. Add the projects Rubjerg.Graphviz and GraphvizWrapper to your solution.
  3. To use Rubjerg.Graphviz within a project of yours, simply add a project reference to it.

When building your project, you should now see all the Graphviz binaries show up in your output folder. If you don't, try setting the CopyLocalLockFileAssemblies property in your referencing project file to true. If that still fails, try reordering the projects in your solution, such that GraphvizWrapper and Rubjerg.Graphviz are at the top. There is an outstanding issue for this.

Documentation

For a reference of attributes to instruct Graphviz have a look at Node, Edge and Graph Attributes. For more information on the inner workings of the graphviz libraries, consult the various documents presented at the Graphviz documentation page.

Tutorial

usingNUnit.Framework;usingSystem.Linq;namespaceRubjerg.Graphviz.Test;
#nullable enable
[TestFixture()]publicclassTutorial{publicconststringPointPattern=@"{X=[\d.]+, Y=[\d.]+}";publicconststringRectPattern=@"{X=[\d.]+, Y=[\d.]+, Width=[\d.]+, Height=[\d.]+}";publicconststringSplinePattern=@"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";[Test,Order(1)]publicvoidGraphConstruction(){// You can programmatically construct graphs as followsRootGraphroot=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 GraphvizNodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");// The edge name is only unique between two nodesEdgeedgeAB=root.GetOrAddEdge(nodeA,nodeB,"Some edge name");EdgeedgeBC=root.GetOrAddEdge(nodeB,nodeC,"Some edge name");EdgeanotherEdgeBC=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.EdgeedgeAB1=root.GetOrAddEdge(nodeA,nodeB);EdgeedgeAB2=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 wrappersedgeBC.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 methodsNode.IntroduceAttribute(root,"label","defaultlabel");nodeB.SetAttributeHtml("label","<b>Some HTML string</b>");// We can simply export this graph to a text file in dot formatroot.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(2)]publicvoidLayouting(){// If we have a given dot file (in this case the one we generated above), we can also read it back inRootGraphroot=RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory+"/out.dot");// We can ask Graphviz to compute a layout and render it to svgroot.ToSvgFile(TestContext.CurrentContext.TestDirectory+"/dot_out.svg");// We can use layout engines other than dot by explicitly passing the engine we wantroot.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. 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.RootGraphlayout=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.NodenodeA=layout.GetNode("A")!;PointDposition=nodeA.GetPosition();Utils.AssertPattern(PointPattern,position.ToString());RectangleDnodeboundingbox=nodeA.GetBoundingBox();Utils.AssertPattern(RectPattern,nodeboundingbox.ToString());// Or splines between nodesNodenodeB=layout.GetNode("B")!;Edgeedge=layout.GetEdge(nodeA,nodeB,"Some edge name")!;PointD[]spline=edge.GetFirstSpline();stringsplineString=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.varactiveFillColor=System.Drawing.Color.Black;foreach(varopinnodeA.GetDrawing()){if(opisXDotOp.FillColor{Value:Color.Uniform{HtmlColor:varhtmlColor}}){activeFillColor=System.Drawing.ColorTranslator.FromHtml(htmlColor);}elseif(opisXDotOp.FilledEllipse{Value:varboundingBox}){Utils.AssertPattern(RectPattern,boundingBox.ToString());}// Handle any xdot operation you require}foreach(varopinnodeA.GetLabelDrawing()){if(opisXDotOp.Text{Value:vartext}){Utils.AssertPattern(PointPattern,text.Anchor.ToString());varboundingBox=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}// 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(3)]publicvoidClusters(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with clusters");NodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");NodenodeD=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.SubGraphcluster1=root.GetOrAddSubgraph("cluster_1");cluster1.AddExisting(nodeB);cluster1.AddExisting(nodeC);SubGraphcluster2=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");varlayout=root.CreateLayout();SubGraphcluster=layout.GetSubgraph("cluster_1")!;RectangleDclusterbox=cluster.GetBoundingBox();RectangleDrootgraphbox=layout.GetBoundingBox();Utils.AssertPattern(RectPattern,clusterbox.ToString());Utils.AssertPattern(RectPattern,rootgraphbox.ToString());}[Test,Order(4)]publicvoidRecords(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with records");NodenodeA=root.GetOrAddNode("A");nodeA.SetAttribute("shape","record");// New line characters are not supported by record labels, and will be ignored by GraphviznodeA.SetAttribute("label","1|2|3|{4|5}|6|{7|8|9}");varlayout=root.CreateLayout();// The order of the list matches the order in which the labels occur in the label string above.varrects=layout.GetNode("A")!.GetRecordRectangles().ToList();varrectLabels=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)]publicvoidStringEscaping(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with escaped strings");Node.IntroduceAttribute(root,"label","\\N");NodenodeA=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.varsomePortId="port id with :| special characters";varvalidPortName=Edge.ConvertUidToPortName(somePortId);NodenodeB=root.GetOrAddNode("B");nodeB.SetAttribute("shape","record");nodeB.SetAttribute("label",$"<{validPortName}>1|2");// The conversion function makes sure different strings don't accidentally map onto the same portnameAssert.AreNotEqual(Edge.ConvertUidToPortName(":"),Edge.ConvertUidToPortName("|"));}}

About

Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - SimaTian/Graphviz.NetWrapper: Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes. · GitHub
Skip to content

Latest commit

History

73 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Graphviz.NetWrapper

codecov

Supported platforms

At the moment, Rubjerg.Graphviz ships with a bunch of precompiled Graphviz dlls built for 64 bit Windows. This library is compatible with .NET Standard 2.0. The unit tests run against .NET Framework 4.8 and .NET 8.0. In the future support may be extended to other platforms.

Contributing

This project aims to provide a thin .NET shell around the Graphviz C libraries, together with some convenience functionality that helps abstracting away some of the peculiarities of the Graphviz library and make it easier to integrate in an application. Pull request that fall within the scope of this project are welcome.

Installation

You can either add this library as a nuget package to project, or include the source and add a project reference.

To run the code from this library, you must have the Microsoft Visual C++ Redistributable (2015-2022) installed, which provides the required runtime libraries. You can download it from the official Microsoft website.

Adding as a Nuget package

Add the Rubjerg.Graphviz nuget package to your project.

Adding the Rubjerg.Graphviz code to your project or solution

  1. Make this code available to your own code, e.g. by adding this repository as a git submodule to your own repository.
  2. Add the projects Rubjerg.Graphviz and GraphvizWrapper to your solution.
  3. To use Rubjerg.Graphviz within a project of yours, simply add a project reference to it.

When building your project, you should now see all the Graphviz binaries show up in your output folder. If you don't, try setting the CopyLocalLockFileAssemblies property in your referencing project file to true. If that still fails, try reordering the projects in your solution, such that GraphvizWrapper and Rubjerg.Graphviz are at the top. There is an outstanding issue for this.

Documentation

For a reference of attributes to instruct Graphviz have a look at Node, Edge and Graph Attributes. For more information on the inner workings of the graphviz libraries, consult the various documents presented at the Graphviz documentation page.

Tutorial

usingNUnit.Framework;usingSystem.Linq;namespaceRubjerg.Graphviz.Test;
#nullable enable
[TestFixture()]publicclassTutorial{publicconststringPointPattern=@"{X=[\d.]+, Y=[\d.]+}";publicconststringRectPattern=@"{X=[\d.]+, Y=[\d.]+, Width=[\d.]+, Height=[\d.]+}";publicconststringSplinePattern=@"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";[Test,Order(1)]publicvoidGraphConstruction(){// You can programmatically construct graphs as followsRootGraphroot=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 GraphvizNodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");// The edge name is only unique between two nodesEdgeedgeAB=root.GetOrAddEdge(nodeA,nodeB,"Some edge name");EdgeedgeBC=root.GetOrAddEdge(nodeB,nodeC,"Some edge name");EdgeanotherEdgeBC=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.EdgeedgeAB1=root.GetOrAddEdge(nodeA,nodeB);EdgeedgeAB2=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 wrappersedgeBC.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 methodsNode.IntroduceAttribute(root,"label","defaultlabel");nodeB.SetAttributeHtml("label","<b>Some HTML string</b>");// We can simply export this graph to a text file in dot formatroot.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(2)]publicvoidLayouting(){// If we have a given dot file (in this case the one we generated above), we can also read it back inRootGraphroot=RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory+"/out.dot");// We can ask Graphviz to compute a layout and render it to svgroot.ToSvgFile(TestContext.CurrentContext.TestDirectory+"/dot_out.svg");// We can use layout engines other than dot by explicitly passing the engine we wantroot.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. 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.RootGraphlayout=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.NodenodeA=layout.GetNode("A")!;PointDposition=nodeA.GetPosition();Utils.AssertPattern(PointPattern,position.ToString());RectangleDnodeboundingbox=nodeA.GetBoundingBox();Utils.AssertPattern(RectPattern,nodeboundingbox.ToString());// Or splines between nodesNodenodeB=layout.GetNode("B")!;Edgeedge=layout.GetEdge(nodeA,nodeB,"Some edge name")!;PointD[]spline=edge.GetFirstSpline();stringsplineString=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.varactiveFillColor=System.Drawing.Color.Black;foreach(varopinnodeA.GetDrawing()){if(opisXDotOp.FillColor{Value:Color.Uniform{HtmlColor:varhtmlColor}}){activeFillColor=System.Drawing.ColorTranslator.FromHtml(htmlColor);}elseif(opisXDotOp.FilledEllipse{Value:varboundingBox}){Utils.AssertPattern(RectPattern,boundingBox.ToString());}// Handle any xdot operation you require}foreach(varopinnodeA.GetLabelDrawing()){if(opisXDotOp.Text{Value:vartext}){Utils.AssertPattern(PointPattern,text.Anchor.ToString());varboundingBox=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}// 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(3)]publicvoidClusters(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with clusters");NodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");NodenodeD=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.SubGraphcluster1=root.GetOrAddSubgraph("cluster_1");cluster1.AddExisting(nodeB);cluster1.AddExisting(nodeC);SubGraphcluster2=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");varlayout=root.CreateLayout();SubGraphcluster=layout.GetSubgraph("cluster_1")!;RectangleDclusterbox=cluster.GetBoundingBox();RectangleDrootgraphbox=layout.GetBoundingBox();Utils.AssertPattern(RectPattern,clusterbox.ToString());Utils.AssertPattern(RectPattern,rootgraphbox.ToString());}[Test,Order(4)]publicvoidRecords(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with records");NodenodeA=root.GetOrAddNode("A");nodeA.SetAttribute("shape","record");// New line characters are not supported by record labels, and will be ignored by GraphviznodeA.SetAttribute("label","1|2|3|{4|5}|6|{7|8|9}");varlayout=root.CreateLayout();// The order of the list matches the order in which the labels occur in the label string above.varrects=layout.GetNode("A")!.GetRecordRectangles().ToList();varrectLabels=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)]publicvoidStringEscaping(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with escaped strings");Node.IntroduceAttribute(root,"label","\\N");NodenodeA=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.varsomePortId="port id with :| special characters";varvalidPortName=Edge.ConvertUidToPortName(somePortId);NodenodeB=root.GetOrAddNode("B");nodeB.SetAttribute("shape","record");nodeB.SetAttribute("label",$"<{validPortName}>1|2");// The conversion function makes sure different strings don't accidentally map onto the same portnameAssert.AreNotEqual(Edge.ConvertUidToPortName(":"),Edge.ConvertUidToPortName("|"));}}

About

Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - SimaTian/Graphviz.NetWrapper: Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes. · GitHub
Skip to content

Latest commit

History

73 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Graphviz.NetWrapper

codecov

Supported platforms

At the moment, Rubjerg.Graphviz ships with a bunch of precompiled Graphviz dlls built for 64 bit Windows. This library is compatible with .NET Standard 2.0. The unit tests run against .NET Framework 4.8 and .NET 8.0. In the future support may be extended to other platforms.

Contributing

This project aims to provide a thin .NET shell around the Graphviz C libraries, together with some convenience functionality that helps abstracting away some of the peculiarities of the Graphviz library and make it easier to integrate in an application. Pull request that fall within the scope of this project are welcome.

Installation

You can either add this library as a nuget package to project, or include the source and add a project reference.

To run the code from this library, you must have the Microsoft Visual C++ Redistributable (2015-2022) installed, which provides the required runtime libraries. You can download it from the official Microsoft website.

Adding as a Nuget package

Add the Rubjerg.Graphviz nuget package to your project.

Adding the Rubjerg.Graphviz code to your project or solution

  1. Make this code available to your own code, e.g. by adding this repository as a git submodule to your own repository.
  2. Add the projects Rubjerg.Graphviz and GraphvizWrapper to your solution.
  3. To use Rubjerg.Graphviz within a project of yours, simply add a project reference to it.

When building your project, you should now see all the Graphviz binaries show up in your output folder. If you don't, try setting the CopyLocalLockFileAssemblies property in your referencing project file to true. If that still fails, try reordering the projects in your solution, such that GraphvizWrapper and Rubjerg.Graphviz are at the top. There is an outstanding issue for this.

Documentation

For a reference of attributes to instruct Graphviz have a look at Node, Edge and Graph Attributes. For more information on the inner workings of the graphviz libraries, consult the various documents presented at the Graphviz documentation page.

Tutorial

usingNUnit.Framework;usingSystem.Linq;namespaceRubjerg.Graphviz.Test;
#nullable enable
[TestFixture()]publicclassTutorial{publicconststringPointPattern=@"{X=[\d.]+, Y=[\d.]+}";publicconststringRectPattern=@"{X=[\d.]+, Y=[\d.]+, Width=[\d.]+, Height=[\d.]+}";publicconststringSplinePattern=@"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";[Test,Order(1)]publicvoidGraphConstruction(){// You can programmatically construct graphs as followsRootGraphroot=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 GraphvizNodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");// The edge name is only unique between two nodesEdgeedgeAB=root.GetOrAddEdge(nodeA,nodeB,"Some edge name");EdgeedgeBC=root.GetOrAddEdge(nodeB,nodeC,"Some edge name");EdgeanotherEdgeBC=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.EdgeedgeAB1=root.GetOrAddEdge(nodeA,nodeB);EdgeedgeAB2=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 wrappersedgeBC.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 methodsNode.IntroduceAttribute(root,"label","defaultlabel");nodeB.SetAttributeHtml("label","<b>Some HTML string</b>");// We can simply export this graph to a text file in dot formatroot.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(2)]publicvoidLayouting(){// If we have a given dot file (in this case the one we generated above), we can also read it back inRootGraphroot=RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory+"/out.dot");// We can ask Graphviz to compute a layout and render it to svgroot.ToSvgFile(TestContext.CurrentContext.TestDirectory+"/dot_out.svg");// We can use layout engines other than dot by explicitly passing the engine we wantroot.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. 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.RootGraphlayout=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.NodenodeA=layout.GetNode("A")!;PointDposition=nodeA.GetPosition();Utils.AssertPattern(PointPattern,position.ToString());RectangleDnodeboundingbox=nodeA.GetBoundingBox();Utils.AssertPattern(RectPattern,nodeboundingbox.ToString());// Or splines between nodesNodenodeB=layout.GetNode("B")!;Edgeedge=layout.GetEdge(nodeA,nodeB,"Some edge name")!;PointD[]spline=edge.GetFirstSpline();stringsplineString=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.varactiveFillColor=System.Drawing.Color.Black;foreach(varopinnodeA.GetDrawing()){if(opisXDotOp.FillColor{Value:Color.Uniform{HtmlColor:varhtmlColor}}){activeFillColor=System.Drawing.ColorTranslator.FromHtml(htmlColor);}elseif(opisXDotOp.FilledEllipse{Value:varboundingBox}){Utils.AssertPattern(RectPattern,boundingBox.ToString());}// Handle any xdot operation you require}foreach(varopinnodeA.GetLabelDrawing()){if(opisXDotOp.Text{Value:vartext}){Utils.AssertPattern(PointPattern,text.Anchor.ToString());varboundingBox=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}// 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(3)]publicvoidClusters(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with clusters");NodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");NodenodeD=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.SubGraphcluster1=root.GetOrAddSubgraph("cluster_1");cluster1.AddExisting(nodeB);cluster1.AddExisting(nodeC);SubGraphcluster2=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");varlayout=root.CreateLayout();SubGraphcluster=layout.GetSubgraph("cluster_1")!;RectangleDclusterbox=cluster.GetBoundingBox();RectangleDrootgraphbox=layout.GetBoundingBox();Utils.AssertPattern(RectPattern,clusterbox.ToString());Utils.AssertPattern(RectPattern,rootgraphbox.ToString());}[Test,Order(4)]publicvoidRecords(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with records");NodenodeA=root.GetOrAddNode("A");nodeA.SetAttribute("shape","record");// New line characters are not supported by record labels, and will be ignored by GraphviznodeA.SetAttribute("label","1|2|3|{4|5}|6|{7|8|9}");varlayout=root.CreateLayout();// The order of the list matches the order in which the labels occur in the label string above.varrects=layout.GetNode("A")!.GetRecordRectangles().ToList();varrectLabels=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)]publicvoidStringEscaping(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with escaped strings");Node.IntroduceAttribute(root,"label","\\N");NodenodeA=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.varsomePortId="port id with :| special characters";varvalidPortName=Edge.ConvertUidToPortName(somePortId);NodenodeB=root.GetOrAddNode("B");nodeB.SetAttribute("shape","record");nodeB.SetAttribute("label",$"<{validPortName}>1|2");// The conversion function makes sure different strings don't accidentally map onto the same portnameAssert.AreNotEqual(Edge.ConvertUidToPortName(":"),Edge.ConvertUidToPortName("|"));}}

About

Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - SimaTian/Graphviz.NetWrapper: Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes. · GitHub
Skip to content

Latest commit

History

73 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Graphviz.NetWrapper

codecov

Supported platforms

At the moment, Rubjerg.Graphviz ships with a bunch of precompiled Graphviz dlls built for 64 bit Windows. This library is compatible with .NET Standard 2.0. The unit tests run against .NET Framework 4.8 and .NET 8.0. In the future support may be extended to other platforms.

Contributing

This project aims to provide a thin .NET shell around the Graphviz C libraries, together with some convenience functionality that helps abstracting away some of the peculiarities of the Graphviz library and make it easier to integrate in an application. Pull request that fall within the scope of this project are welcome.

Installation

You can either add this library as a nuget package to project, or include the source and add a project reference.

To run the code from this library, you must have the Microsoft Visual C++ Redistributable (2015-2022) installed, which provides the required runtime libraries. You can download it from the official Microsoft website.

Adding as a Nuget package

Add the Rubjerg.Graphviz nuget package to your project.

Adding the Rubjerg.Graphviz code to your project or solution

  1. Make this code available to your own code, e.g. by adding this repository as a git submodule to your own repository.
  2. Add the projects Rubjerg.Graphviz and GraphvizWrapper to your solution.
  3. To use Rubjerg.Graphviz within a project of yours, simply add a project reference to it.

When building your project, you should now see all the Graphviz binaries show up in your output folder. If you don't, try setting the CopyLocalLockFileAssemblies property in your referencing project file to true. If that still fails, try reordering the projects in your solution, such that GraphvizWrapper and Rubjerg.Graphviz are at the top. There is an outstanding issue for this.

Documentation

For a reference of attributes to instruct Graphviz have a look at Node, Edge and Graph Attributes. For more information on the inner workings of the graphviz libraries, consult the various documents presented at the Graphviz documentation page.

Tutorial

usingNUnit.Framework;usingSystem.Linq;namespaceRubjerg.Graphviz.Test;
#nullable enable
[TestFixture()]publicclassTutorial{publicconststringPointPattern=@"{X=[\d.]+, Y=[\d.]+}";publicconststringRectPattern=@"{X=[\d.]+, Y=[\d.]+, Width=[\d.]+, Height=[\d.]+}";publicconststringSplinePattern=@"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";[Test,Order(1)]publicvoidGraphConstruction(){// You can programmatically construct graphs as followsRootGraphroot=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 GraphvizNodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");// The edge name is only unique between two nodesEdgeedgeAB=root.GetOrAddEdge(nodeA,nodeB,"Some edge name");EdgeedgeBC=root.GetOrAddEdge(nodeB,nodeC,"Some edge name");EdgeanotherEdgeBC=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.EdgeedgeAB1=root.GetOrAddEdge(nodeA,nodeB);EdgeedgeAB2=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 wrappersedgeBC.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 methodsNode.IntroduceAttribute(root,"label","defaultlabel");nodeB.SetAttributeHtml("label","<b>Some HTML string</b>");// We can simply export this graph to a text file in dot formatroot.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(2)]publicvoidLayouting(){// If we have a given dot file (in this case the one we generated above), we can also read it back inRootGraphroot=RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory+"/out.dot");// We can ask Graphviz to compute a layout and render it to svgroot.ToSvgFile(TestContext.CurrentContext.TestDirectory+"/dot_out.svg");// We can use layout engines other than dot by explicitly passing the engine we wantroot.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. 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.RootGraphlayout=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.NodenodeA=layout.GetNode("A")!;PointDposition=nodeA.GetPosition();Utils.AssertPattern(PointPattern,position.ToString());RectangleDnodeboundingbox=nodeA.GetBoundingBox();Utils.AssertPattern(RectPattern,nodeboundingbox.ToString());// Or splines between nodesNodenodeB=layout.GetNode("B")!;Edgeedge=layout.GetEdge(nodeA,nodeB,"Some edge name")!;PointD[]spline=edge.GetFirstSpline();stringsplineString=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.varactiveFillColor=System.Drawing.Color.Black;foreach(varopinnodeA.GetDrawing()){if(opisXDotOp.FillColor{Value:Color.Uniform{HtmlColor:varhtmlColor}}){activeFillColor=System.Drawing.ColorTranslator.FromHtml(htmlColor);}elseif(opisXDotOp.FilledEllipse{Value:varboundingBox}){Utils.AssertPattern(RectPattern,boundingBox.ToString());}// Handle any xdot operation you require}foreach(varopinnodeA.GetLabelDrawing()){if(opisXDotOp.Text{Value:vartext}){Utils.AssertPattern(PointPattern,text.Anchor.ToString());varboundingBox=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}// 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(3)]publicvoidClusters(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with clusters");NodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");NodenodeD=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.SubGraphcluster1=root.GetOrAddSubgraph("cluster_1");cluster1.AddExisting(nodeB);cluster1.AddExisting(nodeC);SubGraphcluster2=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");varlayout=root.CreateLayout();SubGraphcluster=layout.GetSubgraph("cluster_1")!;RectangleDclusterbox=cluster.GetBoundingBox();RectangleDrootgraphbox=layout.GetBoundingBox();Utils.AssertPattern(RectPattern,clusterbox.ToString());Utils.AssertPattern(RectPattern,rootgraphbox.ToString());}[Test,Order(4)]publicvoidRecords(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with records");NodenodeA=root.GetOrAddNode("A");nodeA.SetAttribute("shape","record");// New line characters are not supported by record labels, and will be ignored by GraphviznodeA.SetAttribute("label","1|2|3|{4|5}|6|{7|8|9}");varlayout=root.CreateLayout();// The order of the list matches the order in which the labels occur in the label string above.varrects=layout.GetNode("A")!.GetRecordRectangles().ToList();varrectLabels=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)]publicvoidStringEscaping(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with escaped strings");Node.IntroduceAttribute(root,"label","\\N");NodenodeA=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.varsomePortId="port id with :| special characters";varvalidPortName=Edge.ConvertUidToPortName(somePortId);NodenodeB=root.GetOrAddNode("B");nodeB.SetAttribute("shape","record");nodeB.SetAttribute("label",$"<{validPortName}>1|2");// The conversion function makes sure different strings don't accidentally map onto the same portnameAssert.AreNotEqual(Edge.ConvertUidToPortName(":"),Edge.ConvertUidToPortName("|"));}}

About

Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - SimaTian/Graphviz.NetWrapper: Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes. · GitHub
Skip to content

Latest commit

History

73 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Graphviz.NetWrapper

codecov

Supported platforms

At the moment, Rubjerg.Graphviz ships with a bunch of precompiled Graphviz dlls built for 64 bit Windows. This library is compatible with .NET Standard 2.0. The unit tests run against .NET Framework 4.8 and .NET 8.0. In the future support may be extended to other platforms.

Contributing

This project aims to provide a thin .NET shell around the Graphviz C libraries, together with some convenience functionality that helps abstracting away some of the peculiarities of the Graphviz library and make it easier to integrate in an application. Pull request that fall within the scope of this project are welcome.

Installation

You can either add this library as a nuget package to project, or include the source and add a project reference.

To run the code from this library, you must have the Microsoft Visual C++ Redistributable (2015-2022) installed, which provides the required runtime libraries. You can download it from the official Microsoft website.

Adding as a Nuget package

Add the Rubjerg.Graphviz nuget package to your project.

Adding the Rubjerg.Graphviz code to your project or solution

  1. Make this code available to your own code, e.g. by adding this repository as a git submodule to your own repository.
  2. Add the projects Rubjerg.Graphviz and GraphvizWrapper to your solution.
  3. To use Rubjerg.Graphviz within a project of yours, simply add a project reference to it.

When building your project, you should now see all the Graphviz binaries show up in your output folder. If you don't, try setting the CopyLocalLockFileAssemblies property in your referencing project file to true. If that still fails, try reordering the projects in your solution, such that GraphvizWrapper and Rubjerg.Graphviz are at the top. There is an outstanding issue for this.

Documentation

For a reference of attributes to instruct Graphviz have a look at Node, Edge and Graph Attributes. For more information on the inner workings of the graphviz libraries, consult the various documents presented at the Graphviz documentation page.

Tutorial

usingNUnit.Framework;usingSystem.Linq;namespaceRubjerg.Graphviz.Test;
#nullable enable
[TestFixture()]publicclassTutorial{publicconststringPointPattern=@"{X=[\d.]+, Y=[\d.]+}";publicconststringRectPattern=@"{X=[\d.]+, Y=[\d.]+, Width=[\d.]+, Height=[\d.]+}";publicconststringSplinePattern=@"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";[Test,Order(1)]publicvoidGraphConstruction(){// You can programmatically construct graphs as followsRootGraphroot=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 GraphvizNodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");// The edge name is only unique between two nodesEdgeedgeAB=root.GetOrAddEdge(nodeA,nodeB,"Some edge name");EdgeedgeBC=root.GetOrAddEdge(nodeB,nodeC,"Some edge name");EdgeanotherEdgeBC=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.EdgeedgeAB1=root.GetOrAddEdge(nodeA,nodeB);EdgeedgeAB2=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 wrappersedgeBC.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 methodsNode.IntroduceAttribute(root,"label","defaultlabel");nodeB.SetAttributeHtml("label","<b>Some HTML string</b>");// We can simply export this graph to a text file in dot formatroot.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(2)]publicvoidLayouting(){// If we have a given dot file (in this case the one we generated above), we can also read it back inRootGraphroot=RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory+"/out.dot");// We can ask Graphviz to compute a layout and render it to svgroot.ToSvgFile(TestContext.CurrentContext.TestDirectory+"/dot_out.svg");// We can use layout engines other than dot by explicitly passing the engine we wantroot.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. 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.RootGraphlayout=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.NodenodeA=layout.GetNode("A")!;PointDposition=nodeA.GetPosition();Utils.AssertPattern(PointPattern,position.ToString());RectangleDnodeboundingbox=nodeA.GetBoundingBox();Utils.AssertPattern(RectPattern,nodeboundingbox.ToString());// Or splines between nodesNodenodeB=layout.GetNode("B")!;Edgeedge=layout.GetEdge(nodeA,nodeB,"Some edge name")!;PointD[]spline=edge.GetFirstSpline();stringsplineString=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.varactiveFillColor=System.Drawing.Color.Black;foreach(varopinnodeA.GetDrawing()){if(opisXDotOp.FillColor{Value:Color.Uniform{HtmlColor:varhtmlColor}}){activeFillColor=System.Drawing.ColorTranslator.FromHtml(htmlColor);}elseif(opisXDotOp.FilledEllipse{Value:varboundingBox}){Utils.AssertPattern(RectPattern,boundingBox.ToString());}// Handle any xdot operation you require}foreach(varopinnodeA.GetLabelDrawing()){if(opisXDotOp.Text{Value:vartext}){Utils.AssertPattern(PointPattern,text.Anchor.ToString());varboundingBox=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}// 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(3)]publicvoidClusters(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with clusters");NodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");NodenodeD=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.SubGraphcluster1=root.GetOrAddSubgraph("cluster_1");cluster1.AddExisting(nodeB);cluster1.AddExisting(nodeC);SubGraphcluster2=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");varlayout=root.CreateLayout();SubGraphcluster=layout.GetSubgraph("cluster_1")!;RectangleDclusterbox=cluster.GetBoundingBox();RectangleDrootgraphbox=layout.GetBoundingBox();Utils.AssertPattern(RectPattern,clusterbox.ToString());Utils.AssertPattern(RectPattern,rootgraphbox.ToString());}[Test,Order(4)]publicvoidRecords(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with records");NodenodeA=root.GetOrAddNode("A");nodeA.SetAttribute("shape","record");// New line characters are not supported by record labels, and will be ignored by GraphviznodeA.SetAttribute("label","1|2|3|{4|5}|6|{7|8|9}");varlayout=root.CreateLayout();// The order of the list matches the order in which the labels occur in the label string above.varrects=layout.GetNode("A")!.GetRecordRectangles().ToList();varrectLabels=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)]publicvoidStringEscaping(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with escaped strings");Node.IntroduceAttribute(root,"label","\\N");NodenodeA=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.varsomePortId="port id with :| special characters";varvalidPortName=Edge.ConvertUidToPortName(somePortId);NodenodeB=root.GetOrAddNode("B");nodeB.SetAttribute("shape","record");nodeB.SetAttribute("label",$"<{validPortName}>1|2");// The conversion function makes sure different strings don't accidentally map onto the same portnameAssert.AreNotEqual(Edge.ConvertUidToPortName(":"),Edge.ConvertUidToPortName("|"));}}

About

Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - SimaTian/Graphviz.NetWrapper: Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes. · GitHub
Skip to content

Latest commit

History

73 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Graphviz.NetWrapper

codecov

Supported platforms

At the moment, Rubjerg.Graphviz ships with a bunch of precompiled Graphviz dlls built for 64 bit Windows. This library is compatible with .NET Standard 2.0. The unit tests run against .NET Framework 4.8 and .NET 8.0. In the future support may be extended to other platforms.

Contributing

This project aims to provide a thin .NET shell around the Graphviz C libraries, together with some convenience functionality that helps abstracting away some of the peculiarities of the Graphviz library and make it easier to integrate in an application. Pull request that fall within the scope of this project are welcome.

Installation

You can either add this library as a nuget package to project, or include the source and add a project reference.

To run the code from this library, you must have the Microsoft Visual C++ Redistributable (2015-2022) installed, which provides the required runtime libraries. You can download it from the official Microsoft website.

Adding as a Nuget package

Add the Rubjerg.Graphviz nuget package to your project.

Adding the Rubjerg.Graphviz code to your project or solution

  1. Make this code available to your own code, e.g. by adding this repository as a git submodule to your own repository.
  2. Add the projects Rubjerg.Graphviz and GraphvizWrapper to your solution.
  3. To use Rubjerg.Graphviz within a project of yours, simply add a project reference to it.

When building your project, you should now see all the Graphviz binaries show up in your output folder. If you don't, try setting the CopyLocalLockFileAssemblies property in your referencing project file to true. If that still fails, try reordering the projects in your solution, such that GraphvizWrapper and Rubjerg.Graphviz are at the top. There is an outstanding issue for this.

Documentation

For a reference of attributes to instruct Graphviz have a look at Node, Edge and Graph Attributes. For more information on the inner workings of the graphviz libraries, consult the various documents presented at the Graphviz documentation page.

Tutorial

usingNUnit.Framework;usingSystem.Linq;namespaceRubjerg.Graphviz.Test;
#nullable enable
[TestFixture()]publicclassTutorial{publicconststringPointPattern=@"{X=[\d.]+, Y=[\d.]+}";publicconststringRectPattern=@"{X=[\d.]+, Y=[\d.]+, Width=[\d.]+, Height=[\d.]+}";publicconststringSplinePattern=@"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";[Test,Order(1)]publicvoidGraphConstruction(){// You can programmatically construct graphs as followsRootGraphroot=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 GraphvizNodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");// The edge name is only unique between two nodesEdgeedgeAB=root.GetOrAddEdge(nodeA,nodeB,"Some edge name");EdgeedgeBC=root.GetOrAddEdge(nodeB,nodeC,"Some edge name");EdgeanotherEdgeBC=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.EdgeedgeAB1=root.GetOrAddEdge(nodeA,nodeB);EdgeedgeAB2=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 wrappersedgeBC.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 methodsNode.IntroduceAttribute(root,"label","defaultlabel");nodeB.SetAttributeHtml("label","<b>Some HTML string</b>");// We can simply export this graph to a text file in dot formatroot.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(2)]publicvoidLayouting(){// If we have a given dot file (in this case the one we generated above), we can also read it back inRootGraphroot=RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory+"/out.dot");// We can ask Graphviz to compute a layout and render it to svgroot.ToSvgFile(TestContext.CurrentContext.TestDirectory+"/dot_out.svg");// We can use layout engines other than dot by explicitly passing the engine we wantroot.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. 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.RootGraphlayout=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.NodenodeA=layout.GetNode("A")!;PointDposition=nodeA.GetPosition();Utils.AssertPattern(PointPattern,position.ToString());RectangleDnodeboundingbox=nodeA.GetBoundingBox();Utils.AssertPattern(RectPattern,nodeboundingbox.ToString());// Or splines between nodesNodenodeB=layout.GetNode("B")!;Edgeedge=layout.GetEdge(nodeA,nodeB,"Some edge name")!;PointD[]spline=edge.GetFirstSpline();stringsplineString=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.varactiveFillColor=System.Drawing.Color.Black;foreach(varopinnodeA.GetDrawing()){if(opisXDotOp.FillColor{Value:Color.Uniform{HtmlColor:varhtmlColor}}){activeFillColor=System.Drawing.ColorTranslator.FromHtml(htmlColor);}elseif(opisXDotOp.FilledEllipse{Value:varboundingBox}){Utils.AssertPattern(RectPattern,boundingBox.ToString());}// Handle any xdot operation you require}foreach(varopinnodeA.GetLabelDrawing()){if(opisXDotOp.Text{Value:vartext}){Utils.AssertPattern(PointPattern,text.Anchor.ToString());varboundingBox=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}// 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(3)]publicvoidClusters(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with clusters");NodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");NodenodeD=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.SubGraphcluster1=root.GetOrAddSubgraph("cluster_1");cluster1.AddExisting(nodeB);cluster1.AddExisting(nodeC);SubGraphcluster2=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");varlayout=root.CreateLayout();SubGraphcluster=layout.GetSubgraph("cluster_1")!;RectangleDclusterbox=cluster.GetBoundingBox();RectangleDrootgraphbox=layout.GetBoundingBox();Utils.AssertPattern(RectPattern,clusterbox.ToString());Utils.AssertPattern(RectPattern,rootgraphbox.ToString());}[Test,Order(4)]publicvoidRecords(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with records");NodenodeA=root.GetOrAddNode("A");nodeA.SetAttribute("shape","record");// New line characters are not supported by record labels, and will be ignored by GraphviznodeA.SetAttribute("label","1|2|3|{4|5}|6|{7|8|9}");varlayout=root.CreateLayout();// The order of the list matches the order in which the labels occur in the label string above.varrects=layout.GetNode("A")!.GetRecordRectangles().ToList();varrectLabels=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)]publicvoidStringEscaping(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with escaped strings");Node.IntroduceAttribute(root,"label","\\N");NodenodeA=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.varsomePortId="port id with :| special characters";varvalidPortName=Edge.ConvertUidToPortName(somePortId);NodenodeB=root.GetOrAddNode("B");nodeB.SetAttribute("shape","record");nodeB.SetAttribute("label",$"<{validPortName}>1|2");// The conversion function makes sure different strings don't accidentally map onto the same portnameAssert.AreNotEqual(Edge.ConvertUidToPortName(":"),Edge.ConvertUidToPortName("|"));}}

About

Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - SimaTian/Graphviz.NetWrapper: Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes. · GitHub
Skip to content

Latest commit

History

73 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Graphviz.NetWrapper

codecov

Supported platforms

At the moment, Rubjerg.Graphviz ships with a bunch of precompiled Graphviz dlls built for 64 bit Windows. This library is compatible with .NET Standard 2.0. The unit tests run against .NET Framework 4.8 and .NET 8.0. In the future support may be extended to other platforms.

Contributing

This project aims to provide a thin .NET shell around the Graphviz C libraries, together with some convenience functionality that helps abstracting away some of the peculiarities of the Graphviz library and make it easier to integrate in an application. Pull request that fall within the scope of this project are welcome.

Installation

You can either add this library as a nuget package to project, or include the source and add a project reference.

To run the code from this library, you must have the Microsoft Visual C++ Redistributable (2015-2022) installed, which provides the required runtime libraries. You can download it from the official Microsoft website.

Adding as a Nuget package

Add the Rubjerg.Graphviz nuget package to your project.

Adding the Rubjerg.Graphviz code to your project or solution

  1. Make this code available to your own code, e.g. by adding this repository as a git submodule to your own repository.
  2. Add the projects Rubjerg.Graphviz and GraphvizWrapper to your solution.
  3. To use Rubjerg.Graphviz within a project of yours, simply add a project reference to it.

When building your project, you should now see all the Graphviz binaries show up in your output folder. If you don't, try setting the CopyLocalLockFileAssemblies property in your referencing project file to true. If that still fails, try reordering the projects in your solution, such that GraphvizWrapper and Rubjerg.Graphviz are at the top. There is an outstanding issue for this.

Documentation

For a reference of attributes to instruct Graphviz have a look at Node, Edge and Graph Attributes. For more information on the inner workings of the graphviz libraries, consult the various documents presented at the Graphviz documentation page.

Tutorial

usingNUnit.Framework;usingSystem.Linq;namespaceRubjerg.Graphviz.Test;
#nullable enable
[TestFixture()]publicclassTutorial{publicconststringPointPattern=@"{X=[\d.]+, Y=[\d.]+}";publicconststringRectPattern=@"{X=[\d.]+, Y=[\d.]+, Width=[\d.]+, Height=[\d.]+}";publicconststringSplinePattern=@"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";[Test,Order(1)]publicvoidGraphConstruction(){// You can programmatically construct graphs as followsRootGraphroot=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 GraphvizNodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");// The edge name is only unique between two nodesEdgeedgeAB=root.GetOrAddEdge(nodeA,nodeB,"Some edge name");EdgeedgeBC=root.GetOrAddEdge(nodeB,nodeC,"Some edge name");EdgeanotherEdgeBC=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.EdgeedgeAB1=root.GetOrAddEdge(nodeA,nodeB);EdgeedgeAB2=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 wrappersedgeBC.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 methodsNode.IntroduceAttribute(root,"label","defaultlabel");nodeB.SetAttributeHtml("label","<b>Some HTML string</b>");// We can simply export this graph to a text file in dot formatroot.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(2)]publicvoidLayouting(){// If we have a given dot file (in this case the one we generated above), we can also read it back inRootGraphroot=RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory+"/out.dot");// We can ask Graphviz to compute a layout and render it to svgroot.ToSvgFile(TestContext.CurrentContext.TestDirectory+"/dot_out.svg");// We can use layout engines other than dot by explicitly passing the engine we wantroot.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. 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.RootGraphlayout=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.NodenodeA=layout.GetNode("A")!;PointDposition=nodeA.GetPosition();Utils.AssertPattern(PointPattern,position.ToString());RectangleDnodeboundingbox=nodeA.GetBoundingBox();Utils.AssertPattern(RectPattern,nodeboundingbox.ToString());// Or splines between nodesNodenodeB=layout.GetNode("B")!;Edgeedge=layout.GetEdge(nodeA,nodeB,"Some edge name")!;PointD[]spline=edge.GetFirstSpline();stringsplineString=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.varactiveFillColor=System.Drawing.Color.Black;foreach(varopinnodeA.GetDrawing()){if(opisXDotOp.FillColor{Value:Color.Uniform{HtmlColor:varhtmlColor}}){activeFillColor=System.Drawing.ColorTranslator.FromHtml(htmlColor);}elseif(opisXDotOp.FilledEllipse{Value:varboundingBox}){Utils.AssertPattern(RectPattern,boundingBox.ToString());}// Handle any xdot operation you require}foreach(varopinnodeA.GetLabelDrawing()){if(opisXDotOp.Text{Value:vartext}){Utils.AssertPattern(PointPattern,text.Anchor.ToString());varboundingBox=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}// 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(3)]publicvoidClusters(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with clusters");NodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");NodenodeD=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.SubGraphcluster1=root.GetOrAddSubgraph("cluster_1");cluster1.AddExisting(nodeB);cluster1.AddExisting(nodeC);SubGraphcluster2=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");varlayout=root.CreateLayout();SubGraphcluster=layout.GetSubgraph("cluster_1")!;RectangleDclusterbox=cluster.GetBoundingBox();RectangleDrootgraphbox=layout.GetBoundingBox();Utils.AssertPattern(RectPattern,clusterbox.ToString());Utils.AssertPattern(RectPattern,rootgraphbox.ToString());}[Test,Order(4)]publicvoidRecords(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with records");NodenodeA=root.GetOrAddNode("A");nodeA.SetAttribute("shape","record");// New line characters are not supported by record labels, and will be ignored by GraphviznodeA.SetAttribute("label","1|2|3|{4|5}|6|{7|8|9}");varlayout=root.CreateLayout();// The order of the list matches the order in which the labels occur in the label string above.varrects=layout.GetNode("A")!.GetRecordRectangles().ToList();varrectLabels=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)]publicvoidStringEscaping(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with escaped strings");Node.IntroduceAttribute(root,"label","\\N");NodenodeA=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.varsomePortId="port id with :| special characters";varvalidPortName=Edge.ConvertUidToPortName(somePortId);NodenodeB=root.GetOrAddNode("B");nodeB.SetAttribute("shape","record");nodeB.SetAttribute("label",$"<{validPortName}>1|2");// The conversion function makes sure different strings don't accidentally map onto the same portnameAssert.AreNotEqual(Edge.ConvertUidToPortName(":"),Edge.ConvertUidToPortName("|"));}}

About

Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - SimaTian/Graphviz.NetWrapper: Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes. · GitHub
Skip to content

Latest commit

History

73 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Graphviz.NetWrapper

codecov

Supported platforms

At the moment, Rubjerg.Graphviz ships with a bunch of precompiled Graphviz dlls built for 64 bit Windows. This library is compatible with .NET Standard 2.0. The unit tests run against .NET Framework 4.8 and .NET 8.0. In the future support may be extended to other platforms.

Contributing

This project aims to provide a thin .NET shell around the Graphviz C libraries, together with some convenience functionality that helps abstracting away some of the peculiarities of the Graphviz library and make it easier to integrate in an application. Pull request that fall within the scope of this project are welcome.

Installation

You can either add this library as a nuget package to project, or include the source and add a project reference.

To run the code from this library, you must have the Microsoft Visual C++ Redistributable (2015-2022) installed, which provides the required runtime libraries. You can download it from the official Microsoft website.

Adding as a Nuget package

Add the Rubjerg.Graphviz nuget package to your project.

Adding the Rubjerg.Graphviz code to your project or solution

  1. Make this code available to your own code, e.g. by adding this repository as a git submodule to your own repository.
  2. Add the projects Rubjerg.Graphviz and GraphvizWrapper to your solution.
  3. To use Rubjerg.Graphviz within a project of yours, simply add a project reference to it.

When building your project, you should now see all the Graphviz binaries show up in your output folder. If you don't, try setting the CopyLocalLockFileAssemblies property in your referencing project file to true. If that still fails, try reordering the projects in your solution, such that GraphvizWrapper and Rubjerg.Graphviz are at the top. There is an outstanding issue for this.

Documentation

For a reference of attributes to instruct Graphviz have a look at Node, Edge and Graph Attributes. For more information on the inner workings of the graphviz libraries, consult the various documents presented at the Graphviz documentation page.

Tutorial

usingNUnit.Framework;usingSystem.Linq;namespaceRubjerg.Graphviz.Test;
#nullable enable
[TestFixture()]publicclassTutorial{publicconststringPointPattern=@"{X=[\d.]+, Y=[\d.]+}";publicconststringRectPattern=@"{X=[\d.]+, Y=[\d.]+, Width=[\d.]+, Height=[\d.]+}";publicconststringSplinePattern=@"{X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}, {X=[\d.]+, Y=[\d.]+}";[Test,Order(1)]publicvoidGraphConstruction(){// You can programmatically construct graphs as followsRootGraphroot=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 GraphvizNodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");// The edge name is only unique between two nodesEdgeedgeAB=root.GetOrAddEdge(nodeA,nodeB,"Some edge name");EdgeedgeBC=root.GetOrAddEdge(nodeB,nodeC,"Some edge name");EdgeanotherEdgeBC=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.EdgeedgeAB1=root.GetOrAddEdge(nodeA,nodeB);EdgeedgeAB2=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 wrappersedgeBC.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 methodsNode.IntroduceAttribute(root,"label","defaultlabel");nodeB.SetAttributeHtml("label","<b>Some HTML string</b>");// We can simply export this graph to a text file in dot formatroot.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(2)]publicvoidLayouting(){// If we have a given dot file (in this case the one we generated above), we can also read it back inRootGraphroot=RootGraph.FromDotFile(TestContext.CurrentContext.TestDirectory+"/out.dot");// We can ask Graphviz to compute a layout and render it to svgroot.ToSvgFile(TestContext.CurrentContext.TestDirectory+"/dot_out.svg");// We can use layout engines other than dot by explicitly passing the engine we wantroot.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. 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.RootGraphlayout=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.NodenodeA=layout.GetNode("A")!;PointDposition=nodeA.GetPosition();Utils.AssertPattern(PointPattern,position.ToString());RectangleDnodeboundingbox=nodeA.GetBoundingBox();Utils.AssertPattern(RectPattern,nodeboundingbox.ToString());// Or splines between nodesNodenodeB=layout.GetNode("B")!;Edgeedge=layout.GetEdge(nodeA,nodeB,"Some edge name")!;PointD[]spline=edge.GetFirstSpline();stringsplineString=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.varactiveFillColor=System.Drawing.Color.Black;foreach(varopinnodeA.GetDrawing()){if(opisXDotOp.FillColor{Value:Color.Uniform{HtmlColor:varhtmlColor}}){activeFillColor=System.Drawing.ColorTranslator.FromHtml(htmlColor);}elseif(opisXDotOp.FilledEllipse{Value:varboundingBox}){Utils.AssertPattern(RectPattern,boundingBox.ToString());}// Handle any xdot operation you require}foreach(varopinnodeA.GetLabelDrawing()){if(opisXDotOp.Text{Value:vartext}){Utils.AssertPattern(PointPattern,text.Anchor.ToString());varboundingBox=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}// 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(3)]publicvoidClusters(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with clusters");NodenodeA=root.GetOrAddNode("A");NodenodeB=root.GetOrAddNode("B");NodenodeC=root.GetOrAddNode("C");NodenodeD=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.SubGraphcluster1=root.GetOrAddSubgraph("cluster_1");cluster1.AddExisting(nodeB);cluster1.AddExisting(nodeC);SubGraphcluster2=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");varlayout=root.CreateLayout();SubGraphcluster=layout.GetSubgraph("cluster_1")!;RectangleDclusterbox=cluster.GetBoundingBox();RectangleDrootgraphbox=layout.GetBoundingBox();Utils.AssertPattern(RectPattern,clusterbox.ToString());Utils.AssertPattern(RectPattern,rootgraphbox.ToString());}[Test,Order(4)]publicvoidRecords(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with records");NodenodeA=root.GetOrAddNode("A");nodeA.SetAttribute("shape","record");// New line characters are not supported by record labels, and will be ignored by GraphviznodeA.SetAttribute("label","1|2|3|{4|5}|6|{7|8|9}");varlayout=root.CreateLayout();// The order of the list matches the order in which the labels occur in the label string above.varrects=layout.GetNode("A")!.GetRecordRectangles().ToList();varrectLabels=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)]publicvoidStringEscaping(){RootGraphroot=RootGraph.CreateNew(GraphType.Directed,"Graph with escaped strings");Node.IntroduceAttribute(root,"label","\\N");NodenodeA=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.varsomePortId="port id with :| special characters";varvalidPortName=Edge.ConvertUidToPortName(somePortId);NodenodeB=root.GetOrAddNode("B");nodeB.SetAttribute("shape","record");nodeB.SetAttribute("label",$"<{validPortName}>1|2");// The conversion function makes sure different strings don't accidentally map onto the same portnameAssert.AreNotEqual(Edge.ConvertUidToPortName(":"),Edge.ConvertUidToPortName("|"));}}

About

Lean .NET wrapper around Graphviz for building graphs, reading/writing dot files, exporting images, or programmatically reading out the layout attributes.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages