""" Extend the Node and Edge classes to control rendering. Add nodes by calling graph#nodes passing in the new node. Note that this function de-duplicates based upon the uid of the node; returning the existing node if possible. Add edges in the same style; calling graph#edges. Again, this de-duplicates based on the uid of the edge. Extension points for Node: uid: determines if a node is the same as another node in the graph. Default: random 16 word characters. id: graphviz node id; must return an identifier. label: display name for the node; any graphviz label is valid. Default: id. shape: graphviz shape. Default: 'oval'. colour: line colour for the node. Default: 'black' Extension points for Edge: uid: determines if an edge is the same as another edge. Useful for de-duplicating edges. Default: random 16 word characters. label: display name for the node; any graphviz label is valid. Default: '' [empty string]. colour: line colour for the node. Default: 'black' """ module YaGraph class Element def to_graphviz(out) "" end """ an id that is unique in the context of the element type. Subtypes should override this. """ def uid() @uid ||= random_id() end def random_id() (0...16).map { (65 + rand(26)).chr }.join end end class Graph < Element RANKDIR_LR = "LR" def initialize() @nodes = {} @edges = {} @subgraphs = {} end def edges(edge) @edges[edge.uid] ||= edge end def edge?(s, e) @edges.any? do |k, v| v.start == s and v.finish == e end end def nodes(node) @nodes[node.uid] ||= node end def subgraph(subgraph) @subgraphs[subgraph.uid] ||= subgraph end def rankdir(mode) @rankdir = mode end def initial_nodes() initial = {} @nodes.each {|k, n| initial[k] = n} @edges.each do |k, e| initial.delete(e.finish.uid) end initial.values end def terminal_nodes() terminal = {} @nodes.each {|k, n| terminal[k] = n} @edges.each do |k, e| terminal.delete(e.start.uid) end terminal.values end def to_graphviz(out) out.puts("digraph main {\n") out.puts(" rankdir=#{@rankdir}") if @rankdir @subgraphs.each {|k, s| s.to_graphviz(out) } @nodes.each {|k, n| n.to_graphviz(out) } @edges.each {|k, e| e.to_graphviz(out) } out.puts("}") end end class SubGraph < Graph def initialize() super() end def is_cluster() false end def to_graphviz(out) out.puts(" subgraph #{if is_cluster() then "cluster_" else "" end}#{uid()} {") if label() then out.puts(" label=\"#{label()}\"") end @nodes.each {|k, n| out.write(" "); n.to_graphviz(out) } @edges.each {|k, e| out.write(" "); e.to_graphviz(out) } out.puts(" }") end def label() nil end end class Node < Element def id() "" end def to_graphviz(out) out.puts(" #{id} [ color=\"#{colour}\" label=\"#{label}\" shape=\"#{shape}\" ]") end def label() id end def shape() "oval" end def colour() "black" end end class Edge < Element attr_reader :start, :finish def to_graphviz(out) out.puts(" #{start.id} -> #{finish.id} [ color=\"#{colour}\" label=\"#{label}\"]") end def label() "" end def colour() "black" end end end # vi: sw=2 ts=2 sts=2 et