mirror of
https://github.com/gonum/gonum.git
synced 2025-10-28 01:21:44 +08:00
graph/encoding/dot: add multigraph serialization and deserialization support
This commit is contained in:
@@ -50,6 +50,19 @@ func Unmarshal(data []byte, dst encoding.Builder) error {
|
||||
return copyGraph(dst, file.Graphs[0])
|
||||
}
|
||||
|
||||
// UnmarshalMulti parses the Graphviz DOT-encoded data as a multigraph and
|
||||
// stores the result in dst.
|
||||
func UnmarshalMulti(data []byte, dst encoding.MultiBuilder) error {
|
||||
file, err := dot.ParseBytes(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(file.Graphs) != 1 {
|
||||
return fmt.Errorf("invalid number of graphs; expected 1, got %d", len(file.Graphs))
|
||||
}
|
||||
return copyMultigraph(dst, file.Graphs[0])
|
||||
}
|
||||
|
||||
// copyGraph copies the nodes and edges from the Graphviz AST source graph to
|
||||
// the destination graph. Edge direction is maintained if present.
|
||||
func copyGraph(dst encoding.Builder, src *ast.Graph) (err error) {
|
||||
@@ -62,9 +75,41 @@ func copyGraph(dst encoding.Builder, src *ast.Graph) (err error) {
|
||||
panic(e)
|
||||
}
|
||||
}()
|
||||
gen := &generator{
|
||||
directed: src.Directed,
|
||||
ids: make(map[string]graph.Node),
|
||||
gen := &simpleGraph{
|
||||
generator: generator{
|
||||
directed: src.Directed,
|
||||
ids: make(map[string]graph.Node),
|
||||
},
|
||||
}
|
||||
if dst, ok := dst.(DOTIDSetter); ok {
|
||||
dst.SetDOTID(src.ID)
|
||||
}
|
||||
if a, ok := dst.(AttributeSetters); ok {
|
||||
gen.graphAttr, gen.nodeAttr, gen.edgeAttr = a.DOTAttributeSetters()
|
||||
}
|
||||
for _, stmt := range src.Stmts {
|
||||
gen.addStmt(dst, stmt)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// copyMultigraph copies the nodes and edges from the Graphviz AST source graph to
|
||||
// the destination graph. Edge direction is maintained if present.
|
||||
func copyMultigraph(dst encoding.MultiBuilder, src *ast.Graph) (err error) {
|
||||
defer func() {
|
||||
switch e := recover().(type) {
|
||||
case nil:
|
||||
case error:
|
||||
err = e
|
||||
default:
|
||||
panic(e)
|
||||
}
|
||||
}()
|
||||
gen := &multiGraph{
|
||||
generator: generator{
|
||||
directed: src.Directed,
|
||||
ids: make(map[string]graph.Node),
|
||||
},
|
||||
}
|
||||
if dst, ok := dst.(DOTIDSetter); ok {
|
||||
dst.SetDOTID(src.ID)
|
||||
@@ -97,7 +142,7 @@ type generator struct {
|
||||
|
||||
// node returns the gonum node corresponding to the given dot AST node ID,
|
||||
// generating a new such node if none exist.
|
||||
func (gen *generator) node(dst encoding.Builder, id string) graph.Node {
|
||||
func (gen *generator) node(dst graph.NodeAdder, id string) graph.Node {
|
||||
if n, ok := gen.ids[id]; ok {
|
||||
return n
|
||||
}
|
||||
@@ -117,8 +162,10 @@ func (gen *generator) node(dst encoding.Builder, id string) graph.Node {
|
||||
return n
|
||||
}
|
||||
|
||||
type simpleGraph struct{ generator }
|
||||
|
||||
// addStmt adds the given statement to the graph.
|
||||
func (gen *generator) addStmt(dst encoding.Builder, stmt ast.Stmt) {
|
||||
func (gen *simpleGraph) addStmt(dst encoding.Builder, stmt ast.Stmt) {
|
||||
switch stmt := stmt.(type) {
|
||||
case *ast.NodeStmt:
|
||||
n, ok := gen.node(dst, stmt.Node.ID).(encoding.AttributeSetter)
|
||||
@@ -206,7 +253,7 @@ func applyPortsToEdge(from ast.Vertex, to *ast.Edge, edge graph.Edge) {
|
||||
}
|
||||
|
||||
// addEdgeStmt adds the given edge statement to the graph.
|
||||
func (gen *generator) addEdgeStmt(dst encoding.Builder, stmt *ast.EdgeStmt) {
|
||||
func (gen *simpleGraph) addEdgeStmt(dst encoding.Builder, stmt *ast.EdgeStmt) {
|
||||
fs := gen.addVertex(dst, stmt.From)
|
||||
ts := gen.addEdge(dst, stmt.To, stmt.Attrs)
|
||||
for _, f := range fs {
|
||||
@@ -220,7 +267,7 @@ func (gen *generator) addEdgeStmt(dst encoding.Builder, stmt *ast.EdgeStmt) {
|
||||
}
|
||||
|
||||
// addVertex adds the given vertex to the graph, and returns its set of nodes.
|
||||
func (gen *generator) addVertex(dst encoding.Builder, v ast.Vertex) []graph.Node {
|
||||
func (gen *simpleGraph) addVertex(dst encoding.Builder, v ast.Vertex) []graph.Node {
|
||||
switch v := v.(type) {
|
||||
case *ast.Node:
|
||||
n := gen.node(dst, v.ID)
|
||||
@@ -237,7 +284,7 @@ func (gen *generator) addVertex(dst encoding.Builder, v ast.Vertex) []graph.Node
|
||||
}
|
||||
|
||||
// addEdge adds the given edge to the graph, and returns its set of nodes.
|
||||
func (gen *generator) addEdge(dst encoding.Builder, to *ast.Edge, attrs []*ast.Attr) []graph.Node {
|
||||
func (gen *simpleGraph) addEdge(dst encoding.Builder, to *ast.Edge, attrs []*ast.Attr) []graph.Node {
|
||||
if !gen.directed && to.Directed {
|
||||
panic(fmt.Errorf("directed edge to %v in undirected graph", to.Vertex))
|
||||
}
|
||||
@@ -307,6 +354,123 @@ func (gen *generator) appendSubgraphNode(n graph.Node) {
|
||||
gen.subNodes = append(gen.subNodes, n)
|
||||
}
|
||||
|
||||
type multiGraph struct{ generator }
|
||||
|
||||
// addStmt adds the given statement to the multigraph.
|
||||
func (gen *multiGraph) addStmt(dst encoding.MultiBuilder, stmt ast.Stmt) {
|
||||
switch stmt := stmt.(type) {
|
||||
case *ast.NodeStmt:
|
||||
n, ok := gen.node(dst, stmt.Node.ID).(encoding.AttributeSetter)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, attr := range stmt.Attrs {
|
||||
a := encoding.Attribute{
|
||||
Key: attr.Key,
|
||||
Value: attr.Val,
|
||||
}
|
||||
if err := n.SetAttribute(a); err != nil {
|
||||
panic(fmt.Errorf("unable to unmarshal node DOT attribute (%s=%s)", a.Key, a.Value))
|
||||
}
|
||||
}
|
||||
case *ast.EdgeStmt:
|
||||
gen.addEdgeStmt(dst, stmt)
|
||||
case *ast.AttrStmt:
|
||||
var n encoding.AttributeSetter
|
||||
var dst string
|
||||
switch stmt.Kind {
|
||||
case ast.GraphKind:
|
||||
if gen.graphAttr == nil {
|
||||
return
|
||||
}
|
||||
n = gen.graphAttr
|
||||
dst = "graph"
|
||||
case ast.NodeKind:
|
||||
if gen.nodeAttr == nil {
|
||||
return
|
||||
}
|
||||
n = gen.nodeAttr
|
||||
dst = "node"
|
||||
case ast.EdgeKind:
|
||||
if gen.edgeAttr == nil {
|
||||
return
|
||||
}
|
||||
n = gen.edgeAttr
|
||||
dst = "edge"
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
for _, attr := range stmt.Attrs {
|
||||
a := encoding.Attribute{
|
||||
Key: attr.Key,
|
||||
Value: attr.Val,
|
||||
}
|
||||
if err := n.SetAttribute(a); err != nil {
|
||||
panic(fmt.Errorf("unable to unmarshal global %s DOT attribute (%s=%s)", dst, a.Key, a.Value))
|
||||
}
|
||||
}
|
||||
case *ast.Attr:
|
||||
// ignore.
|
||||
case *ast.Subgraph:
|
||||
for _, stmt := range stmt.Stmts {
|
||||
gen.addStmt(dst, stmt)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown statement type %T", stmt))
|
||||
}
|
||||
}
|
||||
|
||||
// addEdgeStmt adds the given edge statement to the multigraph.
|
||||
func (gen *multiGraph) addEdgeStmt(dst encoding.MultiBuilder, stmt *ast.EdgeStmt) {
|
||||
fs := gen.addVertex(dst, stmt.From)
|
||||
ts := gen.addLine(dst, stmt.To, stmt.Attrs)
|
||||
for _, f := range fs {
|
||||
for _, t := range ts {
|
||||
edge := dst.NewLine(f, t)
|
||||
dst.SetLine(edge)
|
||||
applyPortsToEdge(stmt.From, stmt.To, edge)
|
||||
addEdgeAttrs(edge, stmt.Attrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addVertex adds the given vertex to the multigraph, and returns its set of nodes.
|
||||
func (gen *multiGraph) addVertex(dst encoding.MultiBuilder, v ast.Vertex) []graph.Node {
|
||||
switch v := v.(type) {
|
||||
case *ast.Node:
|
||||
n := gen.node(dst, v.ID)
|
||||
return []graph.Node{n}
|
||||
case *ast.Subgraph:
|
||||
gen.pushSubgraph()
|
||||
for _, stmt := range v.Stmts {
|
||||
gen.addStmt(dst, stmt)
|
||||
}
|
||||
return gen.popSubgraph()
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown vertex type %T", v))
|
||||
}
|
||||
}
|
||||
|
||||
// addLine adds the given edge to the multigraph, and returns its set of nodes.
|
||||
func (gen *multiGraph) addLine(dst encoding.MultiBuilder, to *ast.Edge, attrs []*ast.Attr) []graph.Node {
|
||||
if !gen.directed && to.Directed {
|
||||
panic(fmt.Errorf("directed edge to %v in undirected graph", to.Vertex))
|
||||
}
|
||||
fs := gen.addVertex(dst, to.Vertex)
|
||||
if to.To != nil {
|
||||
ts := gen.addLine(dst, to.To, attrs)
|
||||
for _, f := range fs {
|
||||
for _, t := range ts {
|
||||
edge := dst.NewLine(f, t)
|
||||
dst.SetLine(edge)
|
||||
applyPortsToEdge(to.Vertex, to.To, edge)
|
||||
addEdgeAttrs(edge, attrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
// addEdgeAttrs adds the attributes to the given edge.
|
||||
func addEdgeAttrs(edge graph.Edge, attrs []*ast.Attr) {
|
||||
e, ok := edge.(encoding.AttributeSetter)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"gonum.org/v1/gonum/graph"
|
||||
"gonum.org/v1/gonum/graph/encoding"
|
||||
"gonum.org/v1/gonum/graph/multi"
|
||||
"gonum.org/v1/gonum/graph/simple"
|
||||
)
|
||||
|
||||
@@ -293,6 +294,107 @@ const undirectedNonchained = `strict graph {
|
||||
B -- C [label="baz 2"];
|
||||
}`
|
||||
|
||||
func TestMultigraphDecoding(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
directed bool
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
directed: true,
|
||||
input: directedMultigraph,
|
||||
expected: directedMultigraph,
|
||||
},
|
||||
{
|
||||
directed: false,
|
||||
input: undirectedMultigraph,
|
||||
expected: undirectedMultigraph,
|
||||
},
|
||||
{
|
||||
directed: true,
|
||||
input: directedSelfLoopMultigraph,
|
||||
expected: directedSelfLoopMultigraph,
|
||||
},
|
||||
{
|
||||
directed: false,
|
||||
input: undirectedSelfLoopMultigraph,
|
||||
expected: undirectedSelfLoopMultigraph,
|
||||
},
|
||||
} {
|
||||
var dst encoding.MultiBuilder
|
||||
if test.directed {
|
||||
dst = multi.NewDirectedGraph()
|
||||
} else {
|
||||
dst = multi.NewUndirectedGraph()
|
||||
}
|
||||
|
||||
if err := UnmarshalMulti([]byte(test.input), dst); err != nil {
|
||||
t.Errorf("i=%d: unable to unmarshal DOT graph; %v", i, err)
|
||||
continue
|
||||
}
|
||||
buf, err := MarshalMulti(dst, "", "", "\t")
|
||||
if err != nil {
|
||||
t.Errorf("i=%d: unable to marshal graph; %v", i, dst)
|
||||
continue
|
||||
}
|
||||
actual := string(buf)
|
||||
if actual != test.expected {
|
||||
t.Errorf("i=%d: graph content mismatch; want:\n%s\n\nactual:\n%s", i, test.expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const directedMultigraph = `digraph {
|
||||
// Node definitions.
|
||||
0;
|
||||
1;
|
||||
2;
|
||||
|
||||
// Edge definitions.
|
||||
0 -> 1;
|
||||
0 -> 1;
|
||||
0 -> 2;
|
||||
2 -> 0;
|
||||
}`
|
||||
|
||||
const undirectedMultigraph = `graph {
|
||||
// Node definitions.
|
||||
0;
|
||||
1;
|
||||
2;
|
||||
|
||||
// Edge definitions.
|
||||
0 -- 1;
|
||||
0 -- 1;
|
||||
0 -- 2;
|
||||
0 -- 2;
|
||||
}`
|
||||
|
||||
const directedSelfLoopMultigraph = `digraph {
|
||||
// Node definitions.
|
||||
0;
|
||||
1;
|
||||
|
||||
// Edge definitions.
|
||||
0 -> 0;
|
||||
0 -> 0;
|
||||
1 -> 1;
|
||||
1 -> 1;
|
||||
}`
|
||||
|
||||
const undirectedSelfLoopMultigraph = `graph {
|
||||
// Node definitions.
|
||||
0;
|
||||
1;
|
||||
|
||||
// Edge definitions.
|
||||
0 -- 0;
|
||||
0 -- 0;
|
||||
1 -- 1;
|
||||
1 -- 1;
|
||||
}`
|
||||
|
||||
// Below follows a minimal implementation of a graph capable of validating the
|
||||
// round-trip encoding and decoding of DOT graphs with nodes and edges
|
||||
// containing DOT attributes.
|
||||
|
||||
@@ -55,17 +55,33 @@ type Structurer interface {
|
||||
Structure() []Graph
|
||||
}
|
||||
|
||||
// MultiStructurer represents a graph.Multigraph that can define subgraphs.
|
||||
type MultiStructurer interface {
|
||||
Structure() []Multigraph
|
||||
}
|
||||
|
||||
// Graph wraps named graph.Graph values.
|
||||
type Graph interface {
|
||||
graph.Graph
|
||||
DOTID() string
|
||||
}
|
||||
|
||||
// Multigraph wraps named graph.Multigraph values.
|
||||
type Multigraph interface {
|
||||
graph.Multigraph
|
||||
DOTID() string
|
||||
}
|
||||
|
||||
// Subgrapher wraps graph.Node values that represent subgraphs.
|
||||
type Subgrapher interface {
|
||||
Subgraph() graph.Graph
|
||||
}
|
||||
|
||||
// MultiSubgrapher wraps graph.Node values that represent subgraphs.
|
||||
type MultiSubgrapher interface {
|
||||
Subgraph() graph.Multigraph
|
||||
}
|
||||
|
||||
// Marshal returns the DOT encoding for the graph g, applying the prefix
|
||||
// and indent to the encoding. Name is used to specify the graph name. If
|
||||
// name is empty and g implements Graph, the returned string from DOTID
|
||||
@@ -76,7 +92,7 @@ type Subgrapher interface {
|
||||
// implementation of the Node, Attributer, Porter, Attributers, Structurer,
|
||||
// Subgrapher and Graph interfaces.
|
||||
func Marshal(g graph.Graph, name, prefix, indent string) ([]byte, error) {
|
||||
var p printer
|
||||
var p simpleGraphPrinter
|
||||
p.indent = indent
|
||||
p.prefix = prefix
|
||||
p.visited = make(map[edge]bool)
|
||||
@@ -87,6 +103,28 @@ func Marshal(g graph.Graph, name, prefix, indent string) ([]byte, error) {
|
||||
return p.buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// MarshalMulti returns the DOT encoding for the multigraph g, applying the
|
||||
// prefix and indent to the encoding. Name is used to specify the graph name. If
|
||||
// name is empty and g implements Graph, the returned string from DOTID
|
||||
// will be used. If strict is true the output bytes will be prefixed with
|
||||
// the DOT "strict" keyword.
|
||||
//
|
||||
// Graph serialization will work for a graph.Multigraph without modification,
|
||||
// however, advanced GraphViz DOT features provided by Marshal depend on
|
||||
// implementation of the Node, Attributer, Porter, Attributers, Structurer,
|
||||
// MultiSubgrapher and Multigraph interfaces.
|
||||
func MarshalMulti(g graph.Multigraph, name, prefix, indent string) ([]byte, error) {
|
||||
var p multiGraphPrinter
|
||||
p.indent = indent
|
||||
p.prefix = prefix
|
||||
p.visited = make(map[line]bool)
|
||||
err := p.print(g, name, false, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type printer struct {
|
||||
buf bytes.Buffer
|
||||
|
||||
@@ -94,8 +132,6 @@ type printer struct {
|
||||
indent string
|
||||
depth int
|
||||
|
||||
visited map[edge]bool
|
||||
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -104,38 +140,17 @@ type edge struct {
|
||||
from, to int64
|
||||
}
|
||||
|
||||
func (p *printer) print(g graph.Graph, name string, needsIndent, isSubgraph bool) error {
|
||||
nodes := graph.NodesOf(g.Nodes())
|
||||
sort.Sort(ordered.ByID(nodes))
|
||||
|
||||
p.buf.WriteString(p.prefix)
|
||||
if needsIndent {
|
||||
for i := 0; i < p.depth; i++ {
|
||||
p.buf.WriteString(p.indent)
|
||||
}
|
||||
}
|
||||
if !isSubgraph {
|
||||
p.buf.WriteString("strict ")
|
||||
}
|
||||
_, isDirected := g.(graph.Directed)
|
||||
if isSubgraph {
|
||||
p.buf.WriteString("sub")
|
||||
} else if isDirected {
|
||||
p.buf.WriteString("di")
|
||||
}
|
||||
p.buf.WriteString("graph")
|
||||
|
||||
func (p *simpleGraphPrinter) print(g graph.Graph, name string, needsIndent, isSubgraph bool) error {
|
||||
if name == "" {
|
||||
if g, ok := g.(Graph); ok {
|
||||
name = g.DOTID()
|
||||
}
|
||||
}
|
||||
if name != "" {
|
||||
p.buf.WriteByte(' ')
|
||||
p.buf.WriteString(name)
|
||||
}
|
||||
|
||||
p.openBlock(" {")
|
||||
_, isDirected := g.(graph.Directed)
|
||||
p.printFrontMatter(name, needsIndent, isSubgraph, isDirected, true)
|
||||
|
||||
if a, ok := g.(Attributers); ok {
|
||||
p.writeAttributeComplex(a)
|
||||
}
|
||||
@@ -150,6 +165,9 @@ func (p *printer) print(g graph.Graph, name string, needsIndent, isSubgraph bool
|
||||
}
|
||||
}
|
||||
|
||||
nodes := graph.NodesOf(g.Nodes())
|
||||
sort.Sort(ordered.ByID(nodes))
|
||||
|
||||
havePrintedNodeHeader := false
|
||||
for _, n := range nodes {
|
||||
if s, ok := n.(Subgrapher); ok {
|
||||
@@ -264,11 +282,40 @@ func (p *printer) print(g graph.Graph, name string, needsIndent, isSubgraph bool
|
||||
p.buf.WriteByte(';')
|
||||
}
|
||||
}
|
||||
|
||||
p.closeBlock("}")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *printer) printFrontMatter(name string, needsIndent, isSubgraph, isDirected, isStrict bool) error {
|
||||
p.buf.WriteString(p.prefix)
|
||||
if needsIndent {
|
||||
for i := 0; i < p.depth; i++ {
|
||||
p.buf.WriteString(p.indent)
|
||||
}
|
||||
}
|
||||
|
||||
if !isSubgraph && isStrict {
|
||||
p.buf.WriteString("strict ")
|
||||
}
|
||||
|
||||
if isSubgraph {
|
||||
p.buf.WriteString("sub")
|
||||
} else if isDirected {
|
||||
p.buf.WriteString("di")
|
||||
}
|
||||
p.buf.WriteString("graph")
|
||||
|
||||
if name != "" {
|
||||
p.buf.WriteByte(' ')
|
||||
p.buf.WriteString(name)
|
||||
}
|
||||
|
||||
p.openBlock(" {")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *printer) writeNode(n graph.Node) {
|
||||
p.buf.WriteString(nodeID(n))
|
||||
}
|
||||
@@ -293,7 +340,7 @@ func nodeID(n graph.Node) string {
|
||||
}
|
||||
}
|
||||
|
||||
func graphID(g graph.Graph, n graph.Node) string {
|
||||
func graphID(g interface{}, n graph.Node) string {
|
||||
switch g := g.(type) {
|
||||
case Node:
|
||||
return g.DOTID()
|
||||
@@ -372,3 +419,165 @@ func (p *printer) closeBlock(b string) {
|
||||
p.newline()
|
||||
p.buf.WriteString(b)
|
||||
}
|
||||
|
||||
type simpleGraphPrinter struct {
|
||||
printer
|
||||
visited map[edge]bool
|
||||
}
|
||||
|
||||
type multiGraphPrinter struct {
|
||||
printer
|
||||
visited map[line]bool
|
||||
}
|
||||
|
||||
type line struct {
|
||||
inGraph string
|
||||
id int64
|
||||
}
|
||||
|
||||
func (p *multiGraphPrinter) print(g graph.Multigraph, name string, needsIndent, isSubgraph bool) error {
|
||||
if name == "" {
|
||||
if g, ok := g.(Multigraph); ok {
|
||||
name = g.DOTID()
|
||||
}
|
||||
}
|
||||
|
||||
_, isDirected := g.(graph.Directed)
|
||||
p.printFrontMatter(name, needsIndent, isSubgraph, isDirected, false)
|
||||
|
||||
if a, ok := g.(Attributers); ok {
|
||||
p.writeAttributeComplex(a)
|
||||
}
|
||||
if s, ok := g.(MultiStructurer); ok {
|
||||
for _, g := range s.Structure() {
|
||||
_, subIsDirected := g.(graph.Directed)
|
||||
if subIsDirected != isDirected {
|
||||
return errors.New("dot: mismatched graph type")
|
||||
}
|
||||
p.buf.WriteByte('\n')
|
||||
p.print(g, g.DOTID(), true, true)
|
||||
}
|
||||
}
|
||||
|
||||
nodes := graph.NodesOf(g.Nodes())
|
||||
sort.Sort(ordered.ByID(nodes))
|
||||
|
||||
havePrintedNodeHeader := false
|
||||
for _, n := range nodes {
|
||||
if s, ok := n.(MultiSubgrapher); ok {
|
||||
// If the node is not linked to any other node
|
||||
// the graph needs to be written now.
|
||||
if g.From(n.ID()).Len() == 0 {
|
||||
g := s.Subgraph()
|
||||
_, subIsDirected := g.(graph.Directed)
|
||||
if subIsDirected != isDirected {
|
||||
return errors.New("dot: mismatched graph type")
|
||||
}
|
||||
if !havePrintedNodeHeader {
|
||||
p.newline()
|
||||
p.buf.WriteString("// Node definitions.")
|
||||
havePrintedNodeHeader = true
|
||||
}
|
||||
p.newline()
|
||||
p.print(g, graphID(g, n), false, true)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !havePrintedNodeHeader {
|
||||
p.newline()
|
||||
p.buf.WriteString("// Node definitions.")
|
||||
havePrintedNodeHeader = true
|
||||
}
|
||||
p.newline()
|
||||
p.writeNode(n)
|
||||
if a, ok := n.(encoding.Attributer); ok {
|
||||
p.writeAttributeList(a)
|
||||
}
|
||||
p.buf.WriteByte(';')
|
||||
}
|
||||
|
||||
havePrintedEdgeHeader := false
|
||||
for _, n := range nodes {
|
||||
nid := n.ID()
|
||||
to := graph.NodesOf(g.From(nid))
|
||||
sort.Sort(ordered.ByID(to))
|
||||
|
||||
for _, t := range to {
|
||||
tid := t.ID()
|
||||
|
||||
lines := graph.LinesOf(g.Lines(nid, tid))
|
||||
sort.Sort(ordered.LinesByIDs(lines))
|
||||
|
||||
for _, l := range lines {
|
||||
lid := l.ID()
|
||||
if p.visited[line{inGraph: name, id: lid}] {
|
||||
continue
|
||||
}
|
||||
p.visited[line{inGraph: name, id: lid}] = true
|
||||
|
||||
if !havePrintedEdgeHeader {
|
||||
p.buf.WriteByte('\n')
|
||||
p.buf.WriteString(strings.TrimRight(p.prefix, " \t\n")) // Trim whitespace suffix.
|
||||
p.newline()
|
||||
p.buf.WriteString("// Edge definitions.")
|
||||
havePrintedEdgeHeader = true
|
||||
}
|
||||
p.newline()
|
||||
|
||||
if s, ok := n.(MultiSubgrapher); ok {
|
||||
g := s.Subgraph()
|
||||
_, subIsDirected := g.(graph.Directed)
|
||||
if subIsDirected != isDirected {
|
||||
return errors.New("dot: mismatched graph type")
|
||||
}
|
||||
p.print(g, graphID(g, n), false, true)
|
||||
} else {
|
||||
p.writeNode(n)
|
||||
}
|
||||
|
||||
porter, edgeIsPorter := l.(Porter)
|
||||
if edgeIsPorter {
|
||||
if l.From().ID() == nid {
|
||||
p.writePorts(porter.FromPort())
|
||||
} else {
|
||||
p.writePorts(porter.ToPort())
|
||||
}
|
||||
}
|
||||
|
||||
if isDirected {
|
||||
p.buf.WriteString(" -> ")
|
||||
} else {
|
||||
p.buf.WriteString(" -- ")
|
||||
}
|
||||
|
||||
if s, ok := t.(MultiSubgrapher); ok {
|
||||
g := s.Subgraph()
|
||||
_, subIsDirected := g.(graph.Directed)
|
||||
if subIsDirected != isDirected {
|
||||
return errors.New("dot: mismatched graph type")
|
||||
}
|
||||
p.print(g, graphID(g, t), false, true)
|
||||
} else {
|
||||
p.writeNode(t)
|
||||
}
|
||||
if edgeIsPorter {
|
||||
if l.From().ID() == nid {
|
||||
p.writePorts(porter.ToPort())
|
||||
} else {
|
||||
p.writePorts(porter.FromPort())
|
||||
}
|
||||
}
|
||||
|
||||
if a, ok := l.(encoding.Attributer); ok {
|
||||
p.writeAttributeList(a)
|
||||
}
|
||||
|
||||
p.buf.WriteByte(';')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.closeBlock("}")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"gonum.org/v1/gonum/graph"
|
||||
"gonum.org/v1/gonum/graph/encoding"
|
||||
"gonum.org/v1/gonum/graph/multi"
|
||||
"gonum.org/v1/gonum/graph/simple"
|
||||
)
|
||||
|
||||
@@ -1417,3 +1418,258 @@ func TestEncode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type intlist []int64
|
||||
|
||||
func createMultigraph(g []intlist) graph.Multigraph {
|
||||
dg := multi.NewUndirectedGraph()
|
||||
for u, e := range g {
|
||||
u := int64(u)
|
||||
nu := multi.Node(u)
|
||||
for _, v := range e {
|
||||
nv := multi.Node(v)
|
||||
dg.SetLine(dg.NewLine(nu, nv))
|
||||
}
|
||||
}
|
||||
return dg
|
||||
}
|
||||
|
||||
func createNamedMultigraph(g []intlist) graph.Multigraph {
|
||||
dg := multi.NewUndirectedGraph()
|
||||
for u, e := range g {
|
||||
u := int64(u)
|
||||
nu := namedNode{id: u, name: alpha[u : u+1]}
|
||||
for _, v := range e {
|
||||
nv := namedNode{id: v, name: alpha[v : v+1]}
|
||||
dg.SetLine(dg.NewLine(nu, nv))
|
||||
}
|
||||
}
|
||||
return dg
|
||||
}
|
||||
|
||||
func createDirectedMultigraph(g []intlist) graph.Multigraph {
|
||||
dg := multi.NewDirectedGraph()
|
||||
for u, e := range g {
|
||||
u := int64(u)
|
||||
nu := multi.Node(u)
|
||||
for _, v := range e {
|
||||
nv := multi.Node(v)
|
||||
dg.SetLine(dg.NewLine(nu, nv))
|
||||
}
|
||||
}
|
||||
return dg
|
||||
}
|
||||
|
||||
func createNamedDirectedMultigraph(g []intlist) graph.Multigraph {
|
||||
dg := multi.NewDirectedGraph()
|
||||
for u, e := range g {
|
||||
u := int64(u)
|
||||
nu := namedNode{id: u, name: alpha[u : u+1]}
|
||||
for _, v := range e {
|
||||
nv := namedNode{id: v, name: alpha[v : v+1]}
|
||||
dg.SetLine(dg.NewLine(nu, nv))
|
||||
}
|
||||
}
|
||||
return dg
|
||||
}
|
||||
|
||||
var encodeMultiTests = []struct {
|
||||
name string
|
||||
g graph.Multigraph
|
||||
|
||||
prefix string
|
||||
|
||||
want string
|
||||
}{
|
||||
{
|
||||
g: createMultigraph([]intlist{}),
|
||||
want: `graph {
|
||||
}`,
|
||||
},
|
||||
{
|
||||
g: createMultigraph([]intlist{
|
||||
0: {1},
|
||||
1: {0, 2},
|
||||
2: {},
|
||||
}),
|
||||
want: `graph {
|
||||
// Node definitions.
|
||||
0;
|
||||
1;
|
||||
2;
|
||||
|
||||
// Edge definitions.
|
||||
0 -- 1;
|
||||
0 -- 1;
|
||||
1 -- 2;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
g: createMultigraph([]intlist{
|
||||
0: {1},
|
||||
1: {2, 2},
|
||||
2: {0, 0, 0},
|
||||
}),
|
||||
want: `graph {
|
||||
// Node definitions.
|
||||
0;
|
||||
1;
|
||||
2;
|
||||
|
||||
// Edge definitions.
|
||||
0 -- 1;
|
||||
0 -- 2;
|
||||
0 -- 2;
|
||||
0 -- 2;
|
||||
1 -- 2;
|
||||
1 -- 2;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
g: createNamedMultigraph([]intlist{
|
||||
0: {1},
|
||||
1: {2, 2},
|
||||
2: {0, 0, 0},
|
||||
}),
|
||||
want: `graph {
|
||||
// Node definitions.
|
||||
A;
|
||||
B;
|
||||
C;
|
||||
|
||||
// Edge definitions.
|
||||
A -- B;
|
||||
A -- C;
|
||||
A -- C;
|
||||
A -- C;
|
||||
B -- C;
|
||||
B -- C;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
g: createMultigraph([]intlist{
|
||||
0: {2, 1, 0},
|
||||
1: {2, 1, 0},
|
||||
2: {2, 1, 0},
|
||||
}),
|
||||
want: `graph {
|
||||
// Node definitions.
|
||||
0;
|
||||
1;
|
||||
2;
|
||||
|
||||
// Edge definitions.
|
||||
0 -- 0;
|
||||
0 -- 1;
|
||||
0 -- 1;
|
||||
0 -- 2;
|
||||
0 -- 2;
|
||||
1 -- 1;
|
||||
1 -- 2;
|
||||
1 -- 2;
|
||||
2 -- 2;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
g: createDirectedMultigraph([]intlist{}),
|
||||
want: `digraph {
|
||||
}`,
|
||||
},
|
||||
{
|
||||
g: createDirectedMultigraph([]intlist{
|
||||
0: {1},
|
||||
1: {0, 2},
|
||||
2: {},
|
||||
}),
|
||||
want: `digraph {
|
||||
// Node definitions.
|
||||
0;
|
||||
1;
|
||||
2;
|
||||
|
||||
// Edge definitions.
|
||||
0 -> 1;
|
||||
1 -> 0;
|
||||
1 -> 2;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
g: createDirectedMultigraph([]intlist{
|
||||
0: {1},
|
||||
1: {2, 2},
|
||||
2: {0, 0, 0},
|
||||
}),
|
||||
want: `digraph {
|
||||
// Node definitions.
|
||||
0;
|
||||
1;
|
||||
2;
|
||||
|
||||
// Edge definitions.
|
||||
0 -> 1;
|
||||
1 -> 2;
|
||||
1 -> 2;
|
||||
2 -> 0;
|
||||
2 -> 0;
|
||||
2 -> 0;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
g: createNamedDirectedMultigraph([]intlist{
|
||||
0: {1},
|
||||
1: {2, 2},
|
||||
2: {0, 0, 0},
|
||||
}),
|
||||
want: `digraph {
|
||||
// Node definitions.
|
||||
A;
|
||||
B;
|
||||
C;
|
||||
|
||||
// Edge definitions.
|
||||
A -> B;
|
||||
B -> C;
|
||||
B -> C;
|
||||
C -> A;
|
||||
C -> A;
|
||||
C -> A;
|
||||
}`,
|
||||
},
|
||||
{
|
||||
g: createDirectedMultigraph([]intlist{
|
||||
0: {2, 1, 0},
|
||||
1: {2, 1, 0},
|
||||
2: {2, 1, 0},
|
||||
}),
|
||||
want: `digraph {
|
||||
// Node definitions.
|
||||
0;
|
||||
1;
|
||||
2;
|
||||
|
||||
// Edge definitions.
|
||||
0 -> 0;
|
||||
0 -> 1;
|
||||
0 -> 2;
|
||||
1 -> 0;
|
||||
1 -> 1;
|
||||
1 -> 2;
|
||||
2 -> 0;
|
||||
2 -> 1;
|
||||
2 -> 2;
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestEncodeMulti(t *testing.T) {
|
||||
for i, test := range encodeMultiTests {
|
||||
got, err := MarshalMulti(test.g, test.name, test.prefix, "\t")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
if string(got) != test.want {
|
||||
t.Errorf("unexpected DOT result for test %d:\ngot: %s\nwant:%s", i, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@ type Builder interface {
|
||||
graph.Builder
|
||||
}
|
||||
|
||||
// MultiBuilder is a graph that can have user-defined nodes and edges added.
|
||||
type MultiBuilder interface {
|
||||
graph.Multigraph
|
||||
graph.MultigraphBuilder
|
||||
}
|
||||
|
||||
// AttributeSetter is implemented by types that can set an encoded graph
|
||||
// attribute.
|
||||
type AttributeSetter interface {
|
||||
|
||||
@@ -74,3 +74,20 @@ func Reverse(nodes []graph.Node) {
|
||||
nodes[i], nodes[j] = nodes[j], nodes[i]
|
||||
}
|
||||
}
|
||||
|
||||
// LinesByIDs implements the sort.Interface sorting a slice of graph.LinesByIDs
|
||||
// lexically by the From IDs, then by the To IDs, finally by the Line IDs.
|
||||
type LinesByIDs []graph.Line
|
||||
|
||||
func (n LinesByIDs) Len() int { return len(n) }
|
||||
func (n LinesByIDs) Less(i, j int) bool {
|
||||
a, b := n[i], n[j]
|
||||
if a.From().ID() != b.From().ID() {
|
||||
return a.From().ID() < b.From().ID()
|
||||
}
|
||||
if a.To().ID() != b.To().ID() {
|
||||
return a.To().ID() < b.To().ID()
|
||||
}
|
||||
return n[i].ID() < n[j].ID()
|
||||
}
|
||||
func (n LinesByIDs) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
|
||||
|
||||
Reference in New Issue
Block a user