Skip to content
Permalink
2.x
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
package scalax.collection
package io.dot
import language.implicitConversions
import scala.collection.SortedMap
import OuterImplicits._, GraphEdge._, edge.LDiEdge, edge.Implicits._
import Indent._
import org.scalatest.matchers.should.Matchers
import org.scalatest.refspec.RefSpec
/** Tests [[Export]]. */
class TExportTest extends RefSpec with Matchers {
def `Example at http://en.wikipedia.org/wiki/DOT_language will be produced` {
implicit def toLDiEdge[N](diEdge: DiEdge[N]) = LDiEdge(diEdge._1, diEdge._2)("")
val g = Graph[String, LDiEdge](
"A1" ~+> "A2" ("f"),
"A2" ~+> "A3" ("g"),
"A1" ~> "B1",
"A1" ~> "B1",
"A2" ~+> "B2" ("(g o f)'"),
"A3" ~> "B3",
"B1" ~> "B3",
"B2" ~+> "B3" ("g'")
)
val root = DotRootGraph(directed = true, id = Some(Id("Wikipedia_Example")))
val subA = DotSubGraph(ancestor = root, subgraphId = Id("A"), attrList = List(DotAttr(Id("rank"), Id("same"))))
val subB = DotSubGraph(ancestor = root, subgraphId = Id("B"), attrList = List(DotAttr(Id("rank"), Id("same"))))
def edgeTransformer(innerEdge: Graph[String, LDiEdge]#EdgeT): Option[(DotGraph, DotEdgeStmt)] = {
val edge = innerEdge.edge
val label = edge.label.asInstanceOf[String]
Some(
root,
DotEdgeStmt(
NodeId(edge.from.toString),
NodeId(edge.to.toString),
if (label.nonEmpty) List(DotAttr(Id("label"), Id(label)))
else Nil
)
)
}
def nodeTransformer(innerNode: Graph[String, LDiEdge]#NodeT): Option[(DotGraph, DotNodeStmt)] =
Some(
(if (innerNode.value.head == 'A') subA else subB, DotNodeStmt(NodeId(innerNode.toString), Seq.empty[DotAttr]))
)
val dot = g.toDot(
dotRoot = root,
edgeTransformer = edgeTransformer,
cNodeTransformer = Some(nodeTransformer),
spacing = multilineCompatibleSpacing
)
val (expected_1, expected_2) = {
val expected_header_sorted =
"""digraph Wikipedia_Example {
| A1 -> A2 [label = f]
| A1 -> B1
| A2 -> A3 [label = g]
| A2 -> B2 [label = "(g o f)'"]
| A3 -> B3
| B1 -> B3
| B2 -> B3 [label = "g'"]""".stripMargin
val expected_footer = """
|}""".stripMargin
val expected_sub_A_sorted = """
| subgraph A {
| A1
| A2
| A3
| rank = same
| }""".stripMargin
val expected_sub_B_sorted = """
| subgraph B {
| B1
| B2
| B3
| rank = same
| }""".stripMargin
(
expected_header_sorted + expected_sub_A_sorted + expected_sub_B_sorted + expected_footer,
expected_header_sorted + expected_sub_B_sorted + expected_sub_A_sorted + expected_footer
)
}
val dot_sorted = {
var group = 1
val groups = {
val unsortedMap = dot.linesWithSeparators.toList.groupBy { line =>
group match {
case 1 | 2 => if (line.contains("subgraph")) group += 1
case 3 => if (line.head == '}') group += 1
case 4 => line should have length 0
}
group
}
SortedMap(unsortedMap.toList: _*)
}
import scala.math.Ordering.fromLessThan
val groups_sorted =
for (group <- groups.valuesIterator)
yield group.sorted(
fromLessThan { (a: String, b: String) =>
val (iA, iB) = (a.indexWhere(_ != ' '), b.indexWhere(_ != ' '))
if (iA == iB)
if (a.contains("rank")) true
else a < b
else if (a(iA) == '}') false
else iA < iB
}
)
groups_sorted.flatten
}.toList.foldLeft("")((result: String, elem: String) => result + elem)
dot_sorted should (be(expected_1) or
be(expected_2))
}
def `DOT headers are covered even in edge cases` {
val g = Graph.empty[String, UnDiEdge]
val dot = g.toDot(
dotRoot = DotRootGraph(
directed = false,
id = None,
attrList = List(DotAttr(Id("attr_1"), Id(""""one"""")), DotAttr(Id("attr_2"), Id("<two>")))
),
edgeTransformer = _ => None,
spacing = multilineCompatibleSpacing
)
val expected = """graph {
| attr_1 = "one"
| attr_2 = <two>
|}""".stripMargin
dot should be(expected)
}
def `Directed hyperedges may be mapped to multiple directed DOT edges` {
val hg = Graph(1 ~> 2 ~> 3)
val root = DotRootGraph(directed = true, id = None)
val dot = hg.toDot(
dotRoot = root,
edgeTransformer = e => None,
hEdgeTransformer = Some { h =>
val source = h.edge.source.toString
h.edge.targets map (target => (root, DotEdgeStmt(NodeId(source), NodeId(target.toString))))
},
spacing = multilineCompatibleSpacing
)
val expected = """digraph {
| 1 -> 2
| 1 -> 3
|}""".stripMargin
sortMid(dot) should be(expected)
}
def `Colons (':') in node_id's are handeled correctly` {
def struct(i: Int) = s"struct$i"
import implicits._, Record._
val (f0, f1, f2, here) = ("f0", "f1", "f2", "here")
val (n1, n2, n3): (Node, Node, Node) = (
Node(
struct(1),
Horizontal(Field("left", Some(f0)), Horizontal(Field("mid", Some(f1)), Field("right", Some(f2))))
),
Node(struct(2), Horizontal(Field("one", Some(f0)), Field("two", Some(f1)))),
Node(
struct(3),
Horizontal(
Field("hello&#92;nworld"),
Horizontal(
Vertical(
Horizontal(Field("b"), Vertical(Field("c"), Horizontal(Field("d", Some(here)), Field("e")))),
Field("f")
),
Horizontal(Field("g"), Field("h"))
)
)
)
)
val g = Graph(n1 ~+> n2(Ports(f1, f0)), n1 ~+> n3(Ports(f2, here)))
val root = DotRootGraph(
directed = true,
id = Some("structs"),
attrStmts = List(DotAttrStmt(Elem.node, List(DotAttr("shape", "record"))))
)
val dot = g.toDot(
dotRoot = root,
edgeTransformer = _.edge match {
case LDiEdge(source, target, label) =>
def withPort(n: Node, port: String): NodeId = n match {
case Node(id, _) => NodeId(id, port)
}
label match {
case Ports(sourcePort, targetPort) =>
Some(
(root, DotEdgeStmt(withPort(source.value, sourcePort), withPort(target.value, targetPort)))
): Option[(DotGraph, DotEdgeStmt)]
}
},
cNodeTransformer = Some(_.value match {
case Node(id, label) =>
Some((root, DotNodeStmt(id, List(DotAttr("label", label.toString)))))
}),
spacing = multilineCompatibleSpacing
)
val expected = """digraph structs {
| node [shape = record]
| struct1 [label = "<f0> left | <f1> mid | <f2> right"]
| struct1:f1 -> struct2:f0
| struct1:f2 -> struct3:here
| struct2 [label = "<f0> one | <f1> two"]
| struct3 [label = "hello&#92;nworld | {b | {c | <here> d | e} | f} | g | h"]
|}""".stripMargin
sortMid(dot) should be(expected)
}
def `doubly-nested subgraphs #69` {
import scalax.collection.Graph
import scalax.collection.edges.DiEdge
import scalax.collection.io.dot.implicits._
val g = Graph[Int, DiEdge](1)
val root = DotRootGraph(
directed = true,
id = Some("structs")
)
val branchDOT = DotSubGraph(root, "cluster_branch", attrList = List(DotAttr("label", "branch")))
val cSubGraph = DotSubGraph(branchDOT, "cluster_chained", attrList = List(DotAttr("label", "Chained")))
val iSubGraph = DotSubGraph(branchDOT, "cluster_unchained", attrList = List(DotAttr("label", "UnChained")))
val iNode = "inode"
val dot = g.toDot(
dotRoot = root,
edgeTransformer = _.edge match {
case _ =>
Some((root, DotEdgeStmt("hi", "guys")))
},
cNodeTransformer = Some { _ =>
Some((cSubGraph, DotNodeStmt("cnode")))
},
iNodeTransformer = Some { _ =>
Some((iSubGraph, DotNodeStmt(iNode)))
}
)
dot.contains(iNode) should be(true)
}
private val multilineCompatibleSpacing = Spacing(
indent = TwoSpaces,
graphAttrSeparator = new AttrSeparator("""
|""".stripMargin) {}
)
private def sortMid(dot: String): String = {
val lines = dot.linesWithSeparators.toIterable
val mid = lines.tail.init
s"${lines.head}${mid.sorted.mkString}${lines.last}"
}
}
case class Node(id: Id, label: Record.RLabel)