graph/encoding/dot: add multigraph serialization and deserialization support

This commit is contained in:
J. Holmes
2018-11-24 14:01:56 -07:00
committed by Dan Kortschak
parent dc5eba8a13
commit b3c4e40467
6 changed files with 791 additions and 37 deletions

View File

@@ -50,6 +50,19 @@ func Unmarshal(data []byte, dst encoding.Builder) error {
return copyGraph(dst, file.Graphs[0]) 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 // copyGraph copies the nodes and edges from the Graphviz AST source graph to
// the destination graph. Edge direction is maintained if present. // the destination graph. Edge direction is maintained if present.
func copyGraph(dst encoding.Builder, src *ast.Graph) (err error) { 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) panic(e)
} }
}() }()
gen := &generator{ gen := &simpleGraph{
directed: src.Directed, generator: generator{
ids: make(map[string]graph.Node), 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 { if dst, ok := dst.(DOTIDSetter); ok {
dst.SetDOTID(src.ID) dst.SetDOTID(src.ID)
@@ -97,7 +142,7 @@ type generator struct {
// node returns the gonum node corresponding to the given dot AST node ID, // node returns the gonum node corresponding to the given dot AST node ID,
// generating a new such node if none exist. // 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 { if n, ok := gen.ids[id]; ok {
return n return n
} }
@@ -117,8 +162,10 @@ func (gen *generator) node(dst encoding.Builder, id string) graph.Node {
return n return n
} }
type simpleGraph struct{ generator }
// addStmt adds the given statement to the graph. // 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) { switch stmt := stmt.(type) {
case *ast.NodeStmt: case *ast.NodeStmt:
n, ok := gen.node(dst, stmt.Node.ID).(encoding.AttributeSetter) 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. // 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) fs := gen.addVertex(dst, stmt.From)
ts := gen.addEdge(dst, stmt.To, stmt.Attrs) ts := gen.addEdge(dst, stmt.To, stmt.Attrs)
for _, f := range fs { 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. // 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) { switch v := v.(type) {
case *ast.Node: case *ast.Node:
n := gen.node(dst, v.ID) 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. // 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 { if !gen.directed && to.Directed {
panic(fmt.Errorf("directed edge to %v in undirected graph", to.Vertex)) 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) 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. // addEdgeAttrs adds the attributes to the given edge.
func addEdgeAttrs(edge graph.Edge, attrs []*ast.Attr) { func addEdgeAttrs(edge graph.Edge, attrs []*ast.Attr) {
e, ok := edge.(encoding.AttributeSetter) e, ok := edge.(encoding.AttributeSetter)

View File

@@ -10,6 +10,7 @@ import (
"gonum.org/v1/gonum/graph" "gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/encoding" "gonum.org/v1/gonum/graph/encoding"
"gonum.org/v1/gonum/graph/multi"
"gonum.org/v1/gonum/graph/simple" "gonum.org/v1/gonum/graph/simple"
) )
@@ -293,6 +294,107 @@ const undirectedNonchained = `strict graph {
B -- C [label="baz 2"]; 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 // Below follows a minimal implementation of a graph capable of validating the
// round-trip encoding and decoding of DOT graphs with nodes and edges // round-trip encoding and decoding of DOT graphs with nodes and edges
// containing DOT attributes. // containing DOT attributes.

View File

@@ -55,17 +55,33 @@ type Structurer interface {
Structure() []Graph Structure() []Graph
} }
// MultiStructurer represents a graph.Multigraph that can define subgraphs.
type MultiStructurer interface {
Structure() []Multigraph
}
// Graph wraps named graph.Graph values. // Graph wraps named graph.Graph values.
type Graph interface { type Graph interface {
graph.Graph graph.Graph
DOTID() string DOTID() string
} }
// Multigraph wraps named graph.Multigraph values.
type Multigraph interface {
graph.Multigraph
DOTID() string
}
// Subgrapher wraps graph.Node values that represent subgraphs. // Subgrapher wraps graph.Node values that represent subgraphs.
type Subgrapher interface { type Subgrapher interface {
Subgraph() graph.Graph 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 // 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 // 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 // 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, // implementation of the Node, Attributer, Porter, Attributers, Structurer,
// Subgrapher and Graph interfaces. // Subgrapher and Graph interfaces.
func Marshal(g graph.Graph, name, prefix, indent string) ([]byte, error) { func Marshal(g graph.Graph, name, prefix, indent string) ([]byte, error) {
var p printer var p simpleGraphPrinter
p.indent = indent p.indent = indent
p.prefix = prefix p.prefix = prefix
p.visited = make(map[edge]bool) 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 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 { type printer struct {
buf bytes.Buffer buf bytes.Buffer
@@ -94,8 +132,6 @@ type printer struct {
indent string indent string
depth int depth int
visited map[edge]bool
err error err error
} }
@@ -104,38 +140,17 @@ type edge struct {
from, to int64 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 name == "" {
if g, ok := g.(Graph); ok { if g, ok := g.(Graph); ok {
name = g.DOTID() 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 { if a, ok := g.(Attributers); ok {
p.writeAttributeComplex(a) 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 havePrintedNodeHeader := false
for _, n := range nodes { for _, n := range nodes {
if s, ok := n.(Subgrapher); ok { 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.buf.WriteByte(';')
} }
} }
p.closeBlock("}") p.closeBlock("}")
return nil 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) { func (p *printer) writeNode(n graph.Node) {
p.buf.WriteString(nodeID(n)) 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) { switch g := g.(type) {
case Node: case Node:
return g.DOTID() return g.DOTID()
@@ -372,3 +419,165 @@ func (p *printer) closeBlock(b string) {
p.newline() p.newline()
p.buf.WriteString(b) 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
}

View File

@@ -9,6 +9,7 @@ import (
"gonum.org/v1/gonum/graph" "gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/encoding" "gonum.org/v1/gonum/graph/encoding"
"gonum.org/v1/gonum/graph/multi"
"gonum.org/v1/gonum/graph/simple" "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)
}
}
}

View File

@@ -12,6 +12,12 @@ type Builder interface {
graph.Builder 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 // AttributeSetter is implemented by types that can set an encoded graph
// attribute. // attribute.
type AttributeSetter interface { type AttributeSetter interface {

View File

@@ -74,3 +74,20 @@ func Reverse(nodes []graph.Node) {
nodes[i], nodes[j] = nodes[j], nodes[i] 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] }