Code/Core/Graph.GraphViz.cs
using System.IO;
using System.Threading.Tasks;
using Nodebox.Dependencies.DotNetGraph.Compilation;
using Nodebox.Dependencies.DotNetGraph.Core;
using Nodebox.Dependencies.DotNetGraph.Extensions;

namespace Nodebox.GraphViz;


public static partial class GraphExtensions {
    extension(Graph graph) {
        public DotGraph ToGraphViz(string identifier = null) {
            identifier ??= graph.GraphVizIdentifier;
            identifier ??= "G";
            var identifierDisjunctor = new IdentifierDisjunctor();
            var dot = new DotGraph().WithIdentifier(identifier).Directed();
            dot.RankDir = "LR";

            var pinDotNodeStore = new Dictionary<(Node, Flow, PinIndex), DotNode>();
            foreach (var node in graph.GetAll<Node>()) {
                if (node.IsValid()) {
                    var subgraph = node.ToGraphVizSubGraph(pinDotNodeStore, identifierDisjunctor);
                    dot.Add(subgraph);
                }
            }

            foreach (var wire in graph.GetAll<Wire>()) {
                if (!wire.IsValid()) { continue; }
                if (!pinDotNodeStore.ContainsKey((wire.Source, Flow.Output, wire.SourceIndex))) { continue; }
                if (!pinDotNodeStore.ContainsKey((wire.Destination, Flow.Input, wire.DestinationIndex))) { continue; }
                var source = pinDotNodeStore[(wire.Source, Flow.Output, wire.SourceIndex)];
                var destination = pinDotNodeStore[(wire.Destination, Flow.Input, wire.DestinationIndex)];

                var edge = new DotEdge()
                    .Source(source)
                    .Destination(destination);
                dot.Add(edge);
            }

            return dot;
        }

        public async Task<string> ToGraphVizStringAsync(string name = "G") {
            var dot = graph.ToGraphViz();

            using var writer = new StringWriter();
            var context = new CompilationContext(writer, new());
            await dot.CompileAsync(context);

            var result = writer.GetStringBuilder().ToString();
            return result;
        }

        public string ToGraphVizString(string name = "G") => graph.ToGraphVizStringAsync(name).GetAwaiter().GetResult();
    }
}

public static partial class GraphExtensions {
    extension(Graph graph) {
        public string GraphVizIdentifier {
            get => graph.GetMetaOrDefault<string>("GraphVizName", null);
            set => graph.SetMeta("GraphVizName", value);
        }
    }
}