src/Tools/Graphview/layout.scala
author wenzelm
Mon Mar 25 17:21:26 2019 +0100 (2 months ago)
changeset 69981 3dced198b9ec
parent 67601 b34be3010273
permissions -rw-r--r--
more strict AFP properties;
wenzelm@59232
     1
/*  Title:      Tools/Graphview/layout.scala
markus@49557
     2
    Author:     Markus Kaiser, TU Muenchen
wenzelm@59240
     3
    Author:     Makarius
markus@49557
     4
wenzelm@59307
     5
DAG layout according to:
wenzelm@59261
     6
wenzelm@59261
     7
  Georg Sander, "Graph Layout through the VCG Tool", in: Graph Drawing,
wenzelm@59261
     8
  DIMACS International Workshop (GD'94), Springer LNCS 894, 1995.
wenzelm@59261
     9
lars@67601
    10
  https://doi.org/10.1007/3-540-58950-3_371
wenzelm@59261
    11
  ftp://ftp.cs.uni-sb.de/pub/graphics/vcg/doc/tr-A03-94.ps.gz
markus@49557
    12
*/
markus@49557
    13
markus@49557
    14
package isabelle.graphview
markus@49557
    15
markus@49557
    16
markus@49557
    17
import isabelle._
markus@49557
    18
markus@49557
    19
wenzelm@59232
    20
object Layout
wenzelm@49744
    21
{
wenzelm@59302
    22
  /* graph structure */
wenzelm@59302
    23
wenzelm@59302
    24
  object Vertex
wenzelm@59302
    25
  {
wenzelm@59302
    26
    object Ordering extends scala.math.Ordering[Vertex]
wenzelm@59302
    27
    {
wenzelm@59302
    28
      def compare(v1: Vertex, v2: Vertex): Int =
wenzelm@59302
    29
        (v1, v2) match {
wenzelm@59302
    30
          case (Node(a), Node(b)) => Graph_Display.Node.Ordering.compare(a, b)
wenzelm@59302
    31
          case (Dummy(a1, a2, i), Dummy(b1, b2, j)) =>
wenzelm@59302
    32
            Graph_Display.Node.Ordering.compare(a1, b1) match {
wenzelm@59302
    33
              case 0 =>
wenzelm@59302
    34
                Graph_Display.Node.Ordering.compare(a2, b2) match {
wenzelm@59302
    35
                  case 0 => i compare j
wenzelm@59302
    36
                  case ord => ord
wenzelm@59302
    37
                }
wenzelm@59302
    38
              case ord => ord
wenzelm@59302
    39
            }
wenzelm@59311
    40
          case (Node(a), Dummy(b, _, _)) =>
wenzelm@59311
    41
            Graph_Display.Node.Ordering.compare(a, b) match {
wenzelm@59311
    42
              case 0 => -1
wenzelm@59311
    43
              case ord => ord
wenzelm@59311
    44
            }
wenzelm@59311
    45
          case (Dummy(a, _, _), Node(b)) =>
wenzelm@59311
    46
            Graph_Display.Node.Ordering.compare(a, b) match {
wenzelm@59311
    47
              case 0 => 1
wenzelm@59311
    48
              case ord => ord
wenzelm@59311
    49
            }
wenzelm@59302
    50
        }
wenzelm@59302
    51
    }
wenzelm@59302
    52
  }
wenzelm@59302
    53
wenzelm@59302
    54
  sealed abstract class Vertex
wenzelm@59302
    55
  case class Node(node: Graph_Display.Node) extends Vertex
wenzelm@59302
    56
  case class Dummy(node1: Graph_Display.Node, node2: Graph_Display.Node, index: Int) extends Vertex
wenzelm@59302
    57
wenzelm@59384
    58
  sealed case class Info(
wenzelm@59384
    59
    x: Double, y: Double, width2: Double, height2: Double, lines: List[String])
wenzelm@59384
    60
  {
wenzelm@59384
    61
    def left: Double = x - width2
wenzelm@59384
    62
    def right: Double = x + width2
wenzelm@59384
    63
    def width: Double = 2 * width2
wenzelm@59384
    64
    def height: Double = 2 * height2
wenzelm@59384
    65
  }
wenzelm@50469
    66
wenzelm@59384
    67
  type Graph = isabelle.Graph[Vertex, Info]
wenzelm@59302
    68
wenzelm@59384
    69
  def make_graph(entries: List[((Vertex, Info), List[Vertex])]): Graph =
wenzelm@59302
    70
    isabelle.Graph.make(entries)(Vertex.Ordering)
wenzelm@59262
    71
wenzelm@59302
    72
  val empty_graph: Graph = make_graph(Nil)
wenzelm@59302
    73
wenzelm@59302
    74
wenzelm@59307
    75
  /* vertex x coordinate */
wenzelm@59307
    76
wenzelm@59384
    77
  private def vertex_left(graph: Graph, v: Vertex) = graph.get_node(v).left
wenzelm@59384
    78
  private def vertex_right(graph: Graph, v: Vertex) = graph.get_node(v).right
wenzelm@59307
    79
wenzelm@59307
    80
  private def move_vertex(graph: Graph, v: Vertex, dx: Double): Graph =
wenzelm@59384
    81
    if (dx == 0.0) graph else graph.map_node(v, info => info.copy(x = info.x + dx))
wenzelm@59307
    82
wenzelm@59307
    83
wenzelm@59302
    84
  /* layout */
wenzelm@59302
    85
wenzelm@59302
    86
  val empty: Layout = new Layout(Metrics.default, Graph_Display.empty_graph, empty_graph)
wenzelm@50470
    87
wenzelm@59302
    88
wenzelm@59302
    89
  private type Levels = Map[Vertex, Int]
wenzelm@59302
    90
  private val empty_levels: Levels = Map.empty
wenzelm@59302
    91
wenzelm@59384
    92
  def make(options: Options, metrics: Metrics,
wenzelm@59384
    93
    node_text: (Graph_Display.Node, XML.Body) => String,
wenzelm@59384
    94
    input_graph: Graph_Display.Graph): Layout =
wenzelm@49737
    95
  {
wenzelm@59302
    96
    if (input_graph.is_empty) empty
markus@49557
    97
    else {
wenzelm@59302
    98
      /* initial graph */
wenzelm@59302
    99
wenzelm@59302
   100
      val initial_graph =
wenzelm@59302
   101
        make_graph(
wenzelm@59302
   102
          input_graph.iterator.map(
wenzelm@59384
   103
            { case (a, (content, (_, bs))) =>
wenzelm@59384
   104
                val lines = split_lines(node_text(a, content))
wenzelm@59384
   105
                val w2 = metrics.node_width2(lines)
wenzelm@59384
   106
                val h2 = metrics.node_height2(lines.length)
wenzelm@59384
   107
                ((Node(a), Info(0.0, 0.0, w2, h2, lines)), bs.toList.map(Node(_)))
wenzelm@59384
   108
            }).toList)
markus@49557
   109
wenzelm@59302
   110
      val initial_levels: Levels =
wenzelm@59302
   111
        (empty_levels /: initial_graph.topological_order) {
wenzelm@59302
   112
          case (levels, vertex) =>
wenzelm@59302
   113
            val level =
wenzelm@59302
   114
              1 + (-1 /: initial_graph.imm_preds(vertex)) { case (m, v) => m max levels(v) }
wenzelm@59302
   115
            levels + (vertex -> level)
wenzelm@59302
   116
        }
wenzelm@59302
   117
wenzelm@59302
   118
wenzelm@59302
   119
      /* graph with dummies */
wenzelm@59302
   120
wenzelm@59384
   121
      val dummy_info = Info(0.0, 0.0, metrics.dummy_size2, metrics.dummy_size2, Nil)
wenzelm@59384
   122
wenzelm@59302
   123
      val (dummy_graph, dummy_levels) =
wenzelm@59302
   124
        ((initial_graph, initial_levels) /: input_graph.edges_iterator) {
wenzelm@59302
   125
            case ((graph, levels), (node1, node2)) =>
wenzelm@59302
   126
              val v1 = Node(node1); val l1 = levels(v1)
wenzelm@59302
   127
              val v2 = Node(node2); val l2 = levels(v2)
wenzelm@59302
   128
              if (l2 - l1 <= 1) (graph, levels)
wenzelm@59263
   129
              else {
wenzelm@59302
   130
                val dummies_levels =
wenzelm@59302
   131
                  (for { (l, i) <- ((l1 + 1) until l2).iterator.zipWithIndex }
wenzelm@59302
   132
                    yield (Dummy(node1, node2, i), l)).toList
wenzelm@59302
   133
                val dummies = dummies_levels.map(_._1)
wenzelm@59302
   134
wenzelm@59302
   135
                val levels1 = (levels /: dummies_levels)(_ + _)
wenzelm@59302
   136
                val graph1 =
wenzelm@59384
   137
                  ((graph /: dummies)(_.new_node(_, dummy_info)).del_edge(v1, v2) /:
wenzelm@59302
   138
                    (v1 :: dummies ::: List(v2)).sliding(2)) {
wenzelm@59302
   139
                      case (g, List(a, b)) => g.add_edge(a, b) }
wenzelm@59302
   140
                (graph1, levels1)
wenzelm@59263
   141
              }
wenzelm@59263
   142
          }
markus@49557
   143
wenzelm@59302
   144
wenzelm@59302
   145
      /* minimize edge crossings and initial coordinates */
wenzelm@59302
   146
wenzelm@59313
   147
      val levels = minimize_crossings(options, dummy_graph, level_list(dummy_levels))
wenzelm@50469
   148
wenzelm@59302
   149
      val levels_graph: Graph =
wenzelm@59302
   150
        (((dummy_graph, 0.0) /: levels) {
wenzelm@59302
   151
          case ((graph, y), level) =>
wenzelm@59384
   152
            val num_lines = (0 /: level) { case (n, v) => n max graph.get_node(v).lines.length }
wenzelm@59312
   153
            val num_edges = (0 /: level) { case (n, v) => n + graph.imm_succs(v).size }
wenzelm@59384
   154
wenzelm@59384
   155
            val y1 = y + metrics.node_height2(num_lines)
wenzelm@59384
   156
wenzelm@59384
   157
            val graph1 =
wenzelm@59384
   158
              (((graph, 0.0) /: level) { case ((g, x), v) =>
wenzelm@59384
   159
                val info = g.get_node(v)
wenzelm@59384
   160
                val g1 = g.map_node(v, _ => info.copy(x = x + info.width2, y = y1))
wenzelm@59384
   161
                val x1 = x + info.width + metrics.gap
wenzelm@59384
   162
                (g1, x1)
wenzelm@59384
   163
              })._1
wenzelm@59384
   164
wenzelm@59384
   165
            val y2 = y1 + metrics.level_height2(num_lines, num_edges)
wenzelm@59384
   166
wenzelm@59384
   167
            (graph1, y2)
wenzelm@50474
   168
        })._1
wenzelm@50474
   169
wenzelm@59265
   170
wenzelm@59307
   171
      /* pendulum/rubberband layout */
markus@49557
   172
wenzelm@59307
   173
      val output_graph =
wenzelm@59313
   174
        rubberband(options, metrics, levels,
wenzelm@59313
   175
          pendulum(options, metrics, levels, levels_graph))
markus@49557
   176
wenzelm@59302
   177
      new Layout(metrics, input_graph, output_graph)
markus@49557
   178
    }
markus@49557
   179
  }
wenzelm@49737
   180
wenzelm@49744
   181
wenzelm@49744
   182
wenzelm@59302
   183
  /** edge crossings **/
wenzelm@50469
   184
wenzelm@59302
   185
  private type Level = List[Vertex]
wenzelm@49737
   186
wenzelm@59313
   187
  private def minimize_crossings(
wenzelm@59313
   188
    options: Options, graph: Graph, levels: List[Level]): List[Level] =
wenzelm@49745
   189
  {
wenzelm@59306
   190
    def resort(parent: Level, child: Level, top_down: Boolean): Level =
wenzelm@59302
   191
      child.map(v => {
wenzelm@59302
   192
          val ps = if (top_down) graph.imm_preds(v) else graph.imm_succs(v)
wenzelm@50469
   193
          val weight =
wenzelm@50468
   194
            (0.0 /: ps) { (w, p) => w + (0 max parent.indexOf(p)) } / (ps.size max 1)
wenzelm@59302
   195
          (v, weight)
markus@49557
   196
      }).sortBy(_._2).map(_._1)
wenzelm@50469
   197
wenzelm@59419
   198
    ((levels, count_crossings(graph, levels)) /:
wenzelm@59419
   199
      (1 to (2 * options.int("graphview_iterations_minimize_crossings")))) {
wenzelm@59419
   200
      case ((old_levels, old_crossings), iteration) =>
wenzelm@59419
   201
        val top_down = (iteration % 2 == 1)
wenzelm@59306
   202
        val new_levels =
wenzelm@59306
   203
          if (top_down)
wenzelm@59306
   204
            (List(old_levels.head) /: old_levels.tail)((tops, bot) =>
wenzelm@59306
   205
              resort(tops.head, bot, top_down) :: tops).reverse
wenzelm@59306
   206
          else {
wenzelm@59306
   207
            val rev_old_levels = old_levels.reverse
wenzelm@59306
   208
            (List(rev_old_levels.head) /: rev_old_levels.tail)((bots, top) =>
wenzelm@59306
   209
              resort(bots.head, top, top_down) :: bots)
wenzelm@59306
   210
          }
wenzelm@59306
   211
        val new_crossings = count_crossings(graph, new_levels)
wenzelm@59306
   212
        if (new_crossings < old_crossings)
wenzelm@59419
   213
          (new_levels, new_crossings)
wenzelm@59306
   214
        else
wenzelm@59419
   215
          (old_levels, old_crossings)
markus@49557
   216
    }._1
markus@49557
   217
  }
wenzelm@50469
   218
wenzelm@59307
   219
  private def level_list(levels: Levels): List[Level] =
markus@49557
   220
  {
wenzelm@59302
   221
    val max_lev = (-1 /: levels) { case (m, (_, l)) => m max l }
wenzelm@59302
   222
    val buckets = new Array[Level](max_lev + 1)
wenzelm@59302
   223
    for (l <- 0 to max_lev) { buckets(l) = Nil }
wenzelm@59302
   224
    for ((v, l) <- levels) { buckets(l) = v :: buckets(l) }
wenzelm@59302
   225
    buckets.iterator.map(_.sorted(Vertex.Ordering)).toList
wenzelm@59302
   226
  }
wenzelm@50469
   227
wenzelm@59307
   228
  private def count_crossings(graph: Graph, levels: List[Level]): Int =
wenzelm@59310
   229
    levels.iterator.sliding(2).map {
wenzelm@59302
   230
      case List(top, bot) =>
wenzelm@59302
   231
        top.iterator.zipWithIndex.map {
wenzelm@59302
   232
          case (outer_parent, outer_parent_index) =>
wenzelm@59310
   233
            graph.imm_succs(outer_parent).iterator.map(bot.indexOf(_)).map(outer_child =>
wenzelm@59310
   234
              (0 until outer_parent_index).iterator.map(inner_parent =>
wenzelm@59310
   235
                graph.imm_succs(top(inner_parent)).iterator.map(bot.indexOf(_)).
wenzelm@59310
   236
                  filter(inner_child => outer_child < inner_child).size).sum).sum
wenzelm@59302
   237
        }.sum
wenzelm@59447
   238
      case _ => 0
wenzelm@59310
   239
    }.sum
wenzelm@59302
   240
wenzelm@59302
   241
wenzelm@59302
   242
wenzelm@59302
   243
  /** pendulum method **/
wenzelm@59302
   244
wenzelm@59302
   245
  /*This is an auxiliary class which is used by the layout algorithm when
wenzelm@59302
   246
    calculating coordinates with the "pendulum method". A "region" is a
wenzelm@59302
   247
    group of vertices which "stick together".*/
wenzelm@59315
   248
  private class Region(val content: List[Vertex])
wenzelm@59302
   249
  {
wenzelm@59302
   250
    def distance(metrics: Metrics, graph: Graph, that: Region): Double =
wenzelm@59384
   251
      vertex_left(graph, that.content.head) -
wenzelm@59384
   252
      vertex_right(graph, this.content.last) -
wenzelm@59384
   253
      metrics.gap
wenzelm@59302
   254
wenzelm@59302
   255
    def deflection(graph: Graph, top_down: Boolean): Double =
wenzelm@59302
   256
      ((for (a <- content.iterator) yield {
wenzelm@59302
   257
        val x = graph.get_node(a).x
wenzelm@59302
   258
        val bs = if (top_down) graph.imm_preds(a) else graph.imm_succs(a)
wenzelm@59302
   259
        bs.iterator.map(graph.get_node(_).x - x).sum / (bs.size max 1)
wenzelm@59302
   260
      }).sum / content.length).round.toDouble
wenzelm@59302
   261
wenzelm@59302
   262
    def move(graph: Graph, dx: Double): Graph =
wenzelm@59307
   263
      if (dx == 0.0) graph else (graph /: content)(move_vertex(_, _, dx))
wenzelm@59315
   264
wenzelm@59315
   265
    def combine(that: Region): Region = new Region(content ::: that.content)
wenzelm@59302
   266
  }
wenzelm@59302
   267
wenzelm@59313
   268
  private def pendulum(
wenzelm@59313
   269
    options: Options, metrics: Metrics, levels: List[Level], levels_graph: Graph): Graph =
wenzelm@59302
   270
  {
wenzelm@59315
   271
    def combine_regions(graph: Graph, top_down: Boolean, level: List[Region]): List[Region] =
wenzelm@59315
   272
      level match {
wenzelm@59315
   273
        case r1 :: rest =>
wenzelm@59315
   274
          val rest1 = combine_regions(graph, top_down, rest)
wenzelm@59315
   275
          rest1 match {
wenzelm@59315
   276
            case r2 :: rest2 =>
wenzelm@59315
   277
              val d1 = r1.deflection(graph, top_down)
wenzelm@59315
   278
              val d2 = r2.deflection(graph, top_down)
wenzelm@59315
   279
              if (// Do regions touch?
wenzelm@59315
   280
                  r1.distance(metrics, graph, r2) <= 0.0 &&
wenzelm@59315
   281
                  // Do they influence each other?
wenzelm@59315
   282
                  (d1 <= 0.0 && d2 < d1 || d2 > 0.0 && d1 > d2 || d1 > 0.0 && d2 < 0.0))
wenzelm@59315
   283
                r1.combine(r2) :: rest2
wenzelm@59315
   284
              else r1 :: rest1
wenzelm@59315
   285
            case _ => r1 :: rest1
wenzelm@59315
   286
          }
wenzelm@59315
   287
        case _ => level
wenzelm@59302
   288
      }
wenzelm@59302
   289
wenzelm@59315
   290
    def deflect(level: List[Region], top_down: Boolean, graph: Graph): (Graph, Boolean) =
wenzelm@50469
   291
    {
wenzelm@59302
   292
      ((graph, false) /: (0 until level.length)) {
wenzelm@59302
   293
        case ((graph, moved), i) =>
wenzelm@59263
   294
          val r = level(i)
wenzelm@59302
   295
          val d = r.deflection(graph, top_down)
wenzelm@59316
   296
          val dx =
wenzelm@59316
   297
            if (d < 0.0 && i > 0)
wenzelm@59302
   298
              - (level(i - 1).distance(metrics, graph, r) min (- d))
wenzelm@59316
   299
            else if (d >= 0.0 && i < level.length - 1)
wenzelm@59302
   300
              r.distance(metrics, graph, level(i + 1)) min d
wenzelm@59265
   301
            else d
wenzelm@59316
   302
          (r.move(graph, dx), moved || d != 0.0)
markus@49557
   303
      }
wenzelm@50469
   304
    }
wenzelm@50469
   305
wenzelm@59315
   306
    val initial_regions = levels.map(level => level.map(l => new Region(List(l))))
wenzelm@50469
   307
wenzelm@59419
   308
    ((levels_graph, initial_regions, true) /:
wenzelm@59419
   309
      (1 to (2 * options.int("graphview_iterations_pendulum")))) {
wenzelm@59419
   310
      case ((graph, regions, moved), iteration) =>
wenzelm@59419
   311
        val top_down = (iteration % 2 == 1)
wenzelm@59302
   312
        if (moved) {
wenzelm@59315
   313
          val (graph1, regions1, moved1) =
wenzelm@59315
   314
            ((graph, List.empty[List[Region]], false) /:
wenzelm@59315
   315
              (if (top_down) regions else regions.reverse)) { case ((graph, tops, moved), bot) =>
wenzelm@59315
   316
                val bot1 = combine_regions(graph, top_down, bot)
wenzelm@59315
   317
                val (graph1, moved1) = deflect(bot1, top_down, graph)
wenzelm@59315
   318
                (graph1, bot1 :: tops, moved || moved1)
wenzelm@59315
   319
            }
wenzelm@59419
   320
          (graph1, regions1.reverse, moved1)
wenzelm@59265
   321
        }
wenzelm@59419
   322
        else (graph, regions, moved)
wenzelm@59302
   323
    }._1
markus@49557
   324
  }
wenzelm@59307
   325
wenzelm@59307
   326
wenzelm@59307
   327
wenzelm@59307
   328
  /** rubberband method **/
wenzelm@59307
   329
wenzelm@59307
   330
  private def force_weight(graph: Graph, v: Vertex): Double =
wenzelm@59307
   331
  {
wenzelm@59307
   332
    val preds = graph.imm_preds(v)
wenzelm@59307
   333
    val succs = graph.imm_succs(v)
wenzelm@59307
   334
    val n = preds.size + succs.size
wenzelm@59307
   335
    if (n == 0) 0.0
wenzelm@59307
   336
    else {
wenzelm@59307
   337
      val x = graph.get_node(v).x
wenzelm@59307
   338
      ((preds.iterator ++ succs.iterator).map(w => graph.get_node(w).x - x)).sum / n
wenzelm@59307
   339
    }
wenzelm@59307
   340
  }
wenzelm@59307
   341
wenzelm@59313
   342
  private def rubberband(
wenzelm@59313
   343
    options: Options, metrics: Metrics, levels: List[Level], graph: Graph): Graph =
wenzelm@59307
   344
  {
wenzelm@59384
   345
    val gap = metrics.gap
wenzelm@59307
   346
wenzelm@59419
   347
    (graph /: (1 to (2 * options.int("graphview_iterations_rubberband")))) { case (graph, _) =>
wenzelm@59307
   348
      (graph /: levels) { case (graph, level) =>
wenzelm@59307
   349
        val m = level.length - 1
wenzelm@59307
   350
        (graph /: level.iterator.zipWithIndex) {
wenzelm@59307
   351
          case (g, (v, i)) =>
wenzelm@59307
   352
            val d = force_weight(g, v)
wenzelm@59384
   353
            if (d < 0.0 && (i == 0 || vertex_right(g, level(i - 1)) + gap < vertex_left(g, v) + d) ||
wenzelm@59384
   354
                d > 0.0 && (i == m || vertex_left(g, level(i + 1)) - gap > vertex_right(g, v) + d))
wenzelm@59307
   355
              move_vertex(g, v, d.round.toDouble)
wenzelm@59307
   356
            else g
wenzelm@59307
   357
        }
wenzelm@59307
   358
      }
wenzelm@59307
   359
    }
wenzelm@59307
   360
  }
markus@49557
   361
}
wenzelm@59262
   362
wenzelm@59262
   363
final class Layout private(
wenzelm@59290
   364
  val metrics: Metrics,
wenzelm@59302
   365
  val input_graph: Graph_Display.Graph,
wenzelm@59302
   366
  val output_graph: Layout.Graph)
wenzelm@59262
   367
{
wenzelm@59302
   368
  /* vertex coordinates */
wenzelm@59302
   369
wenzelm@59302
   370
  def translate_vertex(v: Layout.Vertex, dx: Double, dy: Double): Layout =
wenzelm@59302
   371
  {
wenzelm@59302
   372
    if ((dx == 0.0 && dy == 0.0) || !output_graph.defined(v)) this
wenzelm@59302
   373
    else {
wenzelm@59384
   374
      val output_graph1 =
wenzelm@59384
   375
        output_graph.map_node(v, info => info.copy(x = info.x + dx, y = info.y + dy))
wenzelm@59302
   376
      new Layout(metrics, input_graph, output_graph1)
wenzelm@59262
   377
    }
wenzelm@59302
   378
  }
wenzelm@59262
   379
wenzelm@59262
   380
wenzelm@59384
   381
  /* regular nodes */
wenzelm@59384
   382
wenzelm@59384
   383
  def get_node(node: Graph_Display.Node): Layout.Info =
wenzelm@59384
   384
    output_graph.get_node(Layout.Node(node))
wenzelm@59384
   385
wenzelm@59384
   386
  def nodes_iterator: Iterator[Layout.Info] =
wenzelm@59384
   387
    for ((_: Layout.Node, (info, _)) <- output_graph.iterator) yield info
wenzelm@59384
   388
wenzelm@59384
   389
wenzelm@59290
   390
  /* dummies */
wenzelm@59290
   391
wenzelm@59384
   392
  def dummies_iterator: Iterator[Layout.Info] =
wenzelm@59384
   393
    for ((_: Layout.Dummy, (info, _)) <- output_graph.iterator) yield info
wenzelm@59302
   394
wenzelm@59384
   395
  def dummies_iterator(edge: Graph_Display.Edge): Iterator[Layout.Info] =
wenzelm@59384
   396
    new Iterator[Layout.Info] {
wenzelm@59302
   397
      private var index = 0
wenzelm@59302
   398
      def hasNext: Boolean = output_graph.defined(Layout.Dummy(edge._1, edge._2, index))
wenzelm@59384
   399
      def next: Layout.Info =
wenzelm@59302
   400
      {
wenzelm@59384
   401
        val info = output_graph.get_node(Layout.Dummy(edge._1, edge._2, index))
wenzelm@59302
   402
        index += 1
wenzelm@59384
   403
        info
wenzelm@59302
   404
      }
wenzelm@59302
   405
    }
wenzelm@59262
   406
}
wenzelm@59262
   407