Code/Core/Node.GraphViz.cs
using Nodebox.Dependencies.DotNetGraph.Core;
using Nodebox.Dependencies.DotNetGraph.Attributes;
using Nodebox.Dependencies.DotNetGraph.Extensions;

namespace Nodebox.GraphViz;


public static partial class NodeExtensions {
    extension(Node node) {
        public DotSubgraph ToGraphVizSubGraph(Dictionary<(Node, Flow, PinIndex), DotNode> pinStore = null, IdentifierDisjunctor identifierDisjunctor = null) {
            identifierDisjunctor ??= IdentifierDisjunctor.Bypass;

            string DirectionToString(Flow flow) => flow == Flow.Input ? "In" : "Out";
            string PinSanitizeName(PinIndex index, Pin pin) => pin.Name ?? index.ToString();
            string PinToIdentifier(Flow flow, PinIndex index, Pin pin) => $"{DirectionToString(flow)} {PinSanitizeName(index, pin)}";

            var name = DisplayInfo.ForGenericType(node.GetType()).Name;
            var label = name;
            if (node.GraphVizIdentifier != null && node.GraphVizIdentifier.Length > 0) {
                label += $" ({node.GraphVizIdentifier})";
            }
            var subgraph = new DotSubgraph()
                .WithIdentifier(identifierDisjunctor[$"cluster_{name}"])
                .WithLabel(label)
                .WithStyle(DotSubgraphStyle.Filled)
                .WithColor(DotColor.LightGrey);
            subgraph.SetAttribute("rank", new DotAttribute("same"));

            foreach (var (flow, index, pin) in node.GetAllPins()) {
                string value = flow == Flow.Input ? $"{node.GetInputValue(index)}" : null;
                var pinIdentifier = PinToIdentifier(flow, index, pin);
                var dotNode = new DotNode()
                    .WithIdentifier(identifierDisjunctor[pinIdentifier] + (value != null ? $" :{value}" : null))
                    .WithShape(DotNodeShape.Box)
                    .WithStyle(DotNodeStyle.Filled)
                    .WithColor(DotColor.White);

                pinStore?[(node, flow, index)] = dotNode;
                subgraph.Add(dotNode);
            }

            return subgraph;
        }

        public string GraphVizIdentifier {
            get => node.GetMetaOrDefault<string>("GraphVizName", null);
            set => node.SetMeta("GraphVizName", value);
        }
    }

    extension<T>(T node) where T : Node {
        public T WithGraphVizIdentifier(string identifier) {
            node.GraphVizIdentifier = identifier;
            return node;
        }
    }
}