This document inducts the user of Graph for Scala, who is interested in visualizing graphs, into how to translate Graph instances to the DOT Language. Thus it may be viewed as a supplement of the Core User Guide.
At present DOT import is not supported.
Graph for Scala DOT is supplied as an extra module
(jar). As a rule of thumb, Graph-dot_majorX.minorX.patchX
depends on
Graph-core_majorY.minorY.patchY
with equaling major and minor versions.
In absence of versions being equal in this sense please refer the actual releases
listed on Download.
The following examples are based on a labeled graph shown on wikipedia, more specifically at commutative diagram.
For the complete example please refer to TExport.scala.
To export a graph instance to a DOT graph simply call toDOT
:
import scalax.collection.io.dot._ val dot = g.toDot(dotRoot, edgeTransformer)
Clearly, dot
of type String will contain the resulting DOT graph,
but what about the dotRoot
and edgeTransformer
arguments?
Fine-grained control over the resulting DOT graph is achieved by means
of translators. Let’s examine the parameters of the toDot
method of class Export in detail:
dotRoot
dotRoot: DotRootGraph
This parameter serves to define the DOT graph header and an arbitrary list of root level attributes but no edges or nodes. Let's supply
DotRootGraph(directed = true, id = Some("MyDot"), attrStmts = List(DotAttrStmt(Elem.node, List(DotAttr("shape", "record")))), attrList = List(DotAttr("attr_1", """"one""""), DotAttr("attr_2", "<two>")))
for dotRoot
. Then the DOT graph will start with
digraph MyDot { node [shape = record] attr_1 = "one" attr_2 = <two>
For completeness, a REPLable version of the above example looks like
import scalax.collection.Graph, scalax.collection.GraphEdge._ import scalax.collection.io.dot._ import implicits._ val root = DotRootGraph ( directed = true, id = Some("MyDot"), attrStmts = List(DotAttrStmt(Elem.node, List(DotAttr("shape", "record")))), attrList = List(DotAttr("attr_1", """"one""""), DotAttr("attr_2", "<two>"))) Graph.empty[Int,DiEdge].toDot(root, _ => None)
edgeTransformer
edgeTransformer: Graph[N,E]#EdgeT => Option[(DotGraph, DotEdgeStmt)]
edgeTransformer
, as the only obligatory transformer, will be called
for each edge of the graph except for working with hEdgeTransformer
.
The caller, toDot
, passes an inner edge to it and
edgeTransformer
returns either Some
tuple of
(DotGraph, DotEdgeStmt)
or None
.
The first member of the tuple of type DotGraph
, must be be either
DotRootGraph
instance passed as the dotRoot
argument to toDot
or
DotSubGraph
. This way you
may assign the edge to a DOT subgraph dynamically. Also, controlled by
the ancestor
member of DotSubGraph
, you are
free to define any tree structure of DOT subgraphs.
The edge attributes to be included in the DOT graph are determined by
means of the second member of the tuple of type DotEdgeStmt
.
It is also possible to return None
for specific edges
in order to exclude them from the DOT graph.
Given the following edge transformer function edgeTransformer
import scalax.collection.Graph import scalax.collection.edge.LDiEdge, scalax.collection.edge.Implicits._ import scalax.collection.io.dot._ import implicits._ val g = Graph[String, LDiEdge](("A1"~+>"A2")("f")) val root = DotRootGraph(directed = true, id = Some("Wikipedia_Example")) def edgeTransformer(innerEdge: Graph[String,LDiEdge]#EdgeT): Option[(DotGraph,DotEdgeStmt)] = innerEdge.edge match { case LDiEdge(source, target, label) => label match { case label: String => Some((root, DotEdgeStmt(source.toString, target.toString, if (label.nonEmpty) List(DotAttr("label", label.toString)) else Nil))) }} g.toDot(root, edgeTransformer)
the single edge ("A1"~+>"A2")("f")
will be transformed by
toDot
to
A1 -> A2 [label = f]
hEdgeTransformer
hEdgeTransformer: Graph[N,E]#EdgeT => Traversable[(DotGraph, DotEdgeStmt)]
hEdgeTransformer
comes in handy when transforming a hypergraph to the
DOT language. hEdgeTransformer
allows to break down each hyperedge to
a sequence of DOT edge statements and thus to overcome the lack of support for hyperedges
in the DOT language. See the diHyper
test in
TExport.scala for a simple example.
cNodeTransformer
cNodeTransformer: Option[ (Graph[N,E]#NodeT) => Option[(DotGraph, DotNodeStmt)]] = None
The optional transformer argument cNodeTransformer
,
with its prefix ‘c
’
denoting connected, is called for connected nodes.
This transformer may be used, for example, to assign a list of nodes to
a DOT subgraph like
val sub = DotSubGraph(ancestor = root, subgraphId = "S1", attrList = Seq(DotAttr("rank", "same"))) def nodeTransformer(innerNode: Graph[String,LDiEdge]#NodeT): Option[(DotGraph,DotNodeStmt)] = Some(sub, DotNodeStmt(innerNode.toString, Nil)) g.toDot(root, edgeTransformer, cNodeTransformer = Some(nodeTransformer))
Once the above nodeTransformer
is passed to toDot
for its cNodeTransformer
argument, all connected nodes will be
listed in the DOT subgraph S1. Given root
and g
from 0, nodeTransformer
will produce
subgraph S1 { rank = same A2 A1 }
Of course, a DOT subgraph that contains all nodes would be meaningless
in real life, so we’ll have to append some program logic to group
nodes in the desired subgraphs – as done in TExport.scala
.
iNodeTransformer
iNodeTransformer: Option[Graph[N,E]#NodeT => Option[(DotGraph, DotNodeStmt)]] = None
The prefix ‘i
’ of this second optional transformer
argument denotes isolated. If supplied, iNodeTransformer
is called for each isolated node. Unless your graph is guaranteed to be
connected or you want isolated nodes to be discarded in the DOT graph,
it is necessary to provide this transformer in addition to
edgeTransformer
.
spacing
spacing: Spacing = DefaultSpacing
Finally, the spacing
argument allows you to determine the layout
of the resulting DOT graph including indentation, separators and line breaks.
While testing the above code examples in REPL you might have noticed
that toDot
's resulting lines were indented by TABs.
The reason for this is that we did not supply an argument different from
DefaultSpacing
. For more details see the API Scaladoc.
When working with DOT records you may bank on the Record
object. For the usage of this little grammar see def `Colons (':') in node_id's are handled
correctly`
in TExport.scala.