// Package draw provides functions for visualizing graph structures. At this // time, draw supports the DOT language which can be interpreted by Graphviz, // Grappa, and others.
package draw import ( ) // ToDo: This template should be simplified and split into multiple templates. const dotTemplate = `strict {{.GraphType}} { {{range $k, $v := .Attributes}} {{$k}}="{{$v}}"; {{end}} {{range $s := .Statements}} "{{.Source}}" {{if .Target}}{{$.EdgeOperator}} "{{.Target}}" [ {{range $k, $v := .EdgeAttributes}}{{$k}}="{{$v}}", {{end}} weight={{.EdgeWeight}} ]{{else}}[ {{range $k, $v := .SourceAttributes}}{{$k}}="{{$v}}", {{end}} weight={{.SourceWeight}} ]{{end}}; {{end}} } ` type description struct { GraphType string Attributes map[string]string EdgeOperator string Statements []statement } type statement struct { Source interface{} Target interface{} SourceWeight int SourceAttributes map[string]string EdgeWeight int EdgeAttributes map[string]string } // DOT renders the given graph structure in DOT language into an io.Writer, for // example a file. The generated output can be passed to Graphviz or other // visualization tools supporting DOT. // // The following example renders a directed graph into a file my-graph.gv: // // g := graph.New(graph.IntHash, graph.Directed()) // // _ = g.AddVertex(1) // _ = g.AddVertex(2) // _ = g.AddVertex(3, graph.VertexAttribute("style", "filled"), graph.VertexAttribute("fillcolor", "red")) // // _ = g.AddEdge(1, 2, graph.EdgeWeight(10), graph.EdgeAttribute("color", "red")) // _ = g.AddEdge(1, 3) // // file, _ := os.Create("./my-graph.gv") // _ = draw.DOT(g, file) // // To generate an SVG from the created file using Graphviz, use a command such // as the following: // // dot -Tsvg -O my-graph.gv // // Another possibility is to use os.Stdout as an io.Writer, print the DOT output // to stdout, and pipe it as follows: // // go run main.go | dot -Tsvg > output.svg // // DOT also accepts the [GraphAttribute] functional option, which can be used to // add global attributes when rendering the graph: // // _ = draw.DOT(g, file, draw.GraphAttribute("label", "my-graph")) func [ comparable, any]( graph.Graph[, ], io.Writer, ...func(*description)) error { , := generateDOT(, ...) if != nil { return fmt.Errorf("failed to generate DOT description: %w", ) } return renderDOT(, ) } // GraphAttribute is a functional option for the [DOT] method. func (, string) func(*description) { return func( *description) { .Attributes[] = } } func generateDOT[ comparable, any]( graph.Graph[, ], ...func(*description)) (description, error) { := description{ GraphType: "graph", Attributes: make(map[string]string), EdgeOperator: "--", Statements: make([]statement, 0), } for , := range { (&) } if .Traits().IsDirected { .GraphType = "digraph" .EdgeOperator = "->" } , := .AdjacencyMap() if != nil { return , } for , := range { , , := .VertexWithProperties() if != nil { return , } := statement{ Source: , SourceWeight: .Weight, SourceAttributes: .Attributes, } .Statements = append(.Statements, ) for , := range { := statement{ Source: , Target: , EdgeWeight: .Properties.Weight, EdgeAttributes: .Properties.Attributes, } .Statements = append(.Statements, ) } } return , nil } func renderDOT( io.Writer, description) error { , := template.New("dotTemplate").Parse(dotTemplate) if != nil { return fmt.Errorf("failed to parse template: %w", ) } return .Execute(, ) }