src/Tools/Graphview/src/visualizer.scala
author wenzelm
Tue, 25 Sep 2012 20:28:47 +0200
changeset 49565 ea4308b7ef0f
parent 49557 61988f9df94d
child 49569 7b6aaf446496
permissions -rw-r--r--
ML support for generic graph display, with browser and graphview backends (via print modes); reverse graph layout (again), according to the graph orientation provided by ML; simplified scala types -- eliminated unused type parameters; more explicit Model.Info, Model.Graph; renamed isabelle.graphview.Graphview_Frame to isabelle.graphview.Frame in accordance to file name; removed obsolete Graph_XML and Tooltips; tuned graphview command line; more generous JVM resources via GRAPHVIEW_JAVA_OPTIONS;

/*  Title:      Tools/Graphview/src/visualizer.scala
    Author:     Markus Kaiser, TU Muenchen

Graph visualization interface.
*/

package isabelle.graphview

import isabelle._

import java.awt.{RenderingHints, Graphics2D}


class Visualizer(val model: Model) {
  object Coordinates {
    private var nodes = Map[String, (Double, Double)]()
    private var dummies = Map[(String, String), List[(Double, Double)]]()
    
    def apply(k: String): (Double, Double) = nodes.get(k) match {
      case Some(c) => c
      case None => (0, 0)
    }
    
    def apply(e: (String, String)): List[(Double, Double)] = dummies.get(e) match {
      case Some(ds) => ds
      case None => Nil
    }
    
    def translate(k: String, by: (Double, Double)) {
      val ((x, y), (dx, dy)) = (Coordinates(k), by)
      reposition(k, (x + dx, y + dy))
    }
    
    def reposition(k: String, to: (Double, Double)) {
      nodes += (k -> to)
    }
    
    def translate(d: ((String, String), Int), by: (Double, Double)) {
      val ((e, i),(dx, dy)) = (d, by)
      val (x, y) = apply(e)(i)
      reposition(d, (x + dx, y + dy))
    }
    
    def reposition(d: ((String, String), Int), to: (Double, Double)) {
      val (e, index) = d
      dummies.get(e) match {
        case None =>
        case Some(ds) =>
          dummies += ( e ->
            (ds.zipWithIndex :\ List[(Double, Double)]()){
              case ((t, i), n) => if (index == i) to :: n
                                  else t :: n
            }
          )
      }
    }
    
    def layout() {
      val (l, d) = Layout_Pendulum(model.current)
      nodes = l
      dummies = d
    }
    
    def bounds(): (Double, Double, Double, Double) = 
      model.visible_nodes().toList match {
        case Nil => (0, 0, 0, 0)
        case nodes => {
          val X: (String => Double) = {n => apply(n)._1}
          val Y: (String => Double) = {n => apply(n)._2}

          (X(nodes.minBy(X)), Y(nodes.minBy(Y)),
            X(nodes.maxBy(X)), Y(nodes.maxBy(Y)))              
        }
      }
  }
  
  private val visualizer = this
  object Drawer {
    import Shapes._
    import java.awt.Shape
    
    def apply(g: Graphics2D, n: Option[String]) = n match {
      case None => 
      case Some(_) => Growing_Node.paint(g, visualizer, n)
    }
    
    def apply(g: Graphics2D, e: (String, String), head: Boolean, dummies: Boolean) = {
      Cardinal_Spline_Edge.paint(g, visualizer, e, head, dummies)
    }
    
    def paint_all_visible(g: Graphics2D, dummies: Boolean) = {
      g.setFont(Font())
      g.setRenderingHints(rendering_hints)

      model.visible_edges().foreach(e => {
          apply(g, e, Parameters.arrow_heads, dummies)
        })

      model.visible_nodes().foreach(l => {
          apply(g, Some(l))
        })
    }
    
    def shape(g: Graphics2D, n: Option[String]): Shape = n match {
      case None => Dummy.shape(g, visualizer, None)
      case Some(_) => Growing_Node.shape(g, visualizer, n)
    }
  }
  
  object Selection {
    private var selected: List[String] = Nil
    
    def apply() = selected
    def apply(s: String) = selected.contains(s)
    
    def add(s: String) { selected = s :: selected }
    def set(ss: List[String]) { selected = ss }
    def clear() { selected = Nil }
  }
  
  object Color {
    import java.awt.{Color => awtColor}
    
    def apply(l: Option[String]): (awtColor, awtColor, awtColor) = l match {
      case None => (awtColor.GRAY, awtColor.WHITE, awtColor.BLACK)
      case Some(c) => {
          if (Selection(c))
            (awtColor.BLUE, awtColor.GREEN, awtColor.BLACK)
          else
            (awtColor.BLACK,
             model.colors.getOrElse(c, awtColor.WHITE), awtColor.BLACK)
      }
    }
      
    def apply(e: (String, String)): awtColor = awtColor.BLACK
  }  
  
  object Caption {    
    def apply(k: String) =
      if (Parameters.long_names) k
      else  k.split('.').last
  }
  
  object Tooltip {
    import java.awt.FontMetrics

    def apply(l: String, fm: FontMetrics): String =
    {
      val info = model.complete.get_node(l)
      XML.string_of_body(
        Pretty.formatted(info.content, 76, Pretty.font_metric(fm))
          .map(t => XML.Elem(Markup(HTML.PRE, Nil), HTML.spans(t, false))))
    }
  }
  
  object Font {
    import java.awt.{Font => awtFont}
    
    def apply() = new awtFont(Parameters.font_family,
                              awtFont.BOLD, Parameters.font_size)
  }

  val rendering_hints =
    new RenderingHints(
      RenderingHints.KEY_ANTIALIASING,
      RenderingHints.VALUE_ANTIALIAS_ON)
}