| author | wenzelm | 
| Sun, 29 Nov 2020 16:45:29 +0100 | |
| changeset 72780 | 6205c5d4fadf | 
| parent 71601 | 97ccf48c2f0c | 
| child 73340 | 0ffcad1f6130 | 
| permissions | -rw-r--r-- | 
| 59459 | 1 | /* Title: Tools/Graphview/graphview.scala | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 2 | Author: Markus Kaiser, TU Muenchen | 
| 59240 | 3 | Author: Makarius | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 4 | |
| 59459 | 5 | Graphview visualization parameters and GUI state. | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 6 | */ | 
| 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 7 | |
| 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 8 | package isabelle.graphview | 
| 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 9 | |
| 50464 | 10 | |
| 49565 
ea4308b7ef0f
ML support for generic graph display, with browser and graphview backends (via print modes);
 wenzelm parents: 
49557diff
changeset | 11 | import isabelle._ | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 12 | |
| 59290 | 13 | import java.awt.{Font, Color, Shape, Graphics2D}
 | 
| 59305 | 14 | import java.awt.geom.{Point2D, Rectangle2D}
 | 
| 49729 | 15 | import javax.swing.JComponent | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 16 | |
| 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 17 | |
| 59462 | 18 | abstract class Graphview(full_graph: Graph_Display.Graph) | 
| 50465 | 19 | {
 | 
| 59459 | 20 | graphview => | 
| 50472 
bad1a1ca61e1
separate instance of class Parameters for each Main_Panel -- avoid global program state;
 wenzelm parents: 
50471diff
changeset | 21 | |
| 59302 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 22 | |
| 59395 | 23 | def options: Options | 
| 59392 | 24 | |
| 59462 | 25 | val model = new Model(full_graph) | 
| 26 | ||
| 59392 | 27 | |
| 59302 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 28 | /* layout state */ | 
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 29 | |
| 59290 | 30 | // owned by GUI thread | 
| 59303 | 31 | private var _layout: Layout = Layout.empty | 
| 32 | def layout: Layout = _layout | |
| 59290 | 33 | |
| 34 | def metrics: Metrics = layout.metrics | |
| 59302 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 35 | def visible_graph: Graph_Display.Graph = layout.input_graph | 
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 36 | |
| 59303 | 37 | def translate_vertex(v: Layout.Vertex, dx: Double, dy: Double): Unit = | 
| 38 | _layout = _layout.translate_vertex(v, dx, dy) | |
| 59302 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 39 | |
| 59305 | 40 | def find_node(at: Point2D): Option[Graph_Display.Node] = | 
| 41 |     layout.output_graph.iterator.collectFirst({
 | |
| 59384 | 42 | case (Layout.Node(node), (info, _)) if Shapes.shape(info).contains(at) => node | 
| 59305 | 43 | }) | 
| 44 | ||
| 45 | def find_dummy(at: Point2D): Option[Layout.Dummy] = | |
| 46 |     layout.output_graph.iterator.collectFirst({
 | |
| 59384 | 47 | case (dummy: Layout.Dummy, (info, _)) if Shapes.shape(info).contains(at) => dummy | 
| 59305 | 48 | }) | 
| 49 | ||
| 59302 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 50 | def bounding_box(): Rectangle2D.Double = | 
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 51 |   {
 | 
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 52 | var x0 = 0.0 | 
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 53 | var y0 = 0.0 | 
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 54 | var x1 = 0.0 | 
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 55 | var y1 = 0.0 | 
| 59384 | 56 |     for ((_, (info, _)) <- layout.output_graph.iterator) {
 | 
| 57 | val rect = Shapes.shape(info) | |
| 58 | x0 = x0 min rect.getMinX | |
| 59 | y0 = y0 min rect.getMinY | |
| 60 | x1 = x1 max rect.getMaxX | |
| 61 | y1 = y1 max rect.getMaxY | |
| 62 | } | |
| 63 | x0 = (x0 - metrics.gap).floor | |
| 64 | y0 = (y0 - metrics.gap).floor | |
| 65 | x1 = (x1 + metrics.gap).ceil | |
| 66 | y1 = (y1 + metrics.gap).ceil | |
| 59302 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 67 | new Rectangle2D.Double(x0, y0, x1 - x0, y1 - y0) | 
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 68 | } | 
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 69 | |
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 70 | def update_layout() | 
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 71 |   {
 | 
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 72 | val metrics = Metrics(make_font()) | 
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 73 | val visible_graph = model.make_visible_graph() | 
| 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 74 | |
| 59384 | 75 | def node_text(node: Graph_Display.Node, content: XML.Body): String = | 
| 76 |       if (show_content) {
 | |
| 77 | val s = | |
| 67547 
aefe7a7b330a
clarified breakgain: keeping it constant avoids margin fluctuation in Pretty_Tooltip vs. Pretty_Text_Area;
 wenzelm parents: 
61590diff
changeset | 78 | XML.content(Pretty.formatted(content, | 
| 
aefe7a7b330a
clarified breakgain: keeping it constant avoids margin fluctuation in Pretty_Tooltip vs. Pretty_Text_Area;
 wenzelm parents: 
61590diff
changeset | 79 |             margin = options.int("graphview_content_margin").toDouble,
 | 
| 
aefe7a7b330a
clarified breakgain: keeping it constant avoids margin fluctuation in Pretty_Tooltip vs. Pretty_Text_Area;
 wenzelm parents: 
61590diff
changeset | 80 | metric = metrics.Pretty_Metric)) | 
| 59384 | 81 | if (s.nonEmpty) s else node.toString | 
| 82 | } | |
| 83 | else node.toString | |
| 84 | ||
| 71601 | 85 | _layout = Layout.make(options, metrics, node_text, visible_graph) | 
| 59302 
4d985afc0565
explict layout graph structure, with dummies and coordinates;
 wenzelm parents: 
59294diff
changeset | 86 | } | 
| 59290 | 87 | |
| 50476 
1cb983bccb5b
more official graphics context with font metrics;
 wenzelm parents: 
50475diff
changeset | 88 | |
| 59233 | 89 | /* tooltips */ | 
| 90 | ||
| 91 | def make_tooltip(parent: JComponent, x: Int, y: Int, body: XML.Body): String = null | |
| 92 | ||
| 93 | ||
| 59228 
56b34fc7a015
more dynamic visualizer -- re-use Isabelle/jEdit options;
 wenzelm parents: 
59220diff
changeset | 94 | /* main colors */ | 
| 
56b34fc7a015
more dynamic visualizer -- re-use Isabelle/jEdit options;
 wenzelm parents: 
59220diff
changeset | 95 | |
| 
56b34fc7a015
more dynamic visualizer -- re-use Isabelle/jEdit options;
 wenzelm parents: 
59220diff
changeset | 96 | def foreground_color: Color = Color.BLACK | 
| 
56b34fc7a015
more dynamic visualizer -- re-use Isabelle/jEdit options;
 wenzelm parents: 
59220diff
changeset | 97 | def background_color: Color = Color.WHITE | 
| 61176 | 98 | def selection_color: Color = new Color(204, 204, 255) | 
| 99 | def highlight_color: Color = new Color(255, 255, 224) | |
| 59228 
56b34fc7a015
more dynamic visualizer -- re-use Isabelle/jEdit options;
 wenzelm parents: 
59220diff
changeset | 100 | def error_color: Color = Color.RED | 
| 59251 | 101 | def dummy_color: Color = Color.GRAY | 
| 59228 
56b34fc7a015
more dynamic visualizer -- re-use Isabelle/jEdit options;
 wenzelm parents: 
59220diff
changeset | 102 | |
| 
56b34fc7a015
more dynamic visualizer -- re-use Isabelle/jEdit options;
 wenzelm parents: 
59220diff
changeset | 103 | |
| 50475 | 104 | /* font rendering information */ | 
| 105 | ||
| 59290 | 106 | def make_font(): Font = | 
| 59286 
ac74eedb910a
GUI.imitate_font: more explicit result size, e.g. relevant for caching;
 wenzelm parents: 
59265diff
changeset | 107 |   {
 | 
| 
ac74eedb910a
GUI.imitate_font: more explicit result size, e.g. relevant for caching;
 wenzelm parents: 
59265diff
changeset | 108 |     val family = options.string("graphview_font_family")
 | 
| 
ac74eedb910a
GUI.imitate_font: more explicit result size, e.g. relevant for caching;
 wenzelm parents: 
59265diff
changeset | 109 |     val size = options.int("graphview_font_size")
 | 
| 
ac74eedb910a
GUI.imitate_font: more explicit result size, e.g. relevant for caching;
 wenzelm parents: 
59265diff
changeset | 110 | new Font(family, Font.PLAIN, size) | 
| 
ac74eedb910a
GUI.imitate_font: more explicit result size, e.g. relevant for caching;
 wenzelm parents: 
59265diff
changeset | 111 | } | 
| 50475 | 112 | |
| 113 | ||
| 114 | /* rendering parameters */ | |
| 115 | ||
| 59294 | 116 | // owned by GUI thread | 
| 59384 | 117 | var show_content = false | 
| 118 | var show_arrow_heads = false | |
| 59294 | 119 | var show_dummies = false | 
| 61176 | 120 | var editor_style = false | 
| 50475 | 121 | |
| 122 | object Colors | |
| 123 |   {
 | |
| 124 | private val filter_colors = List( | |
| 59220 | 125 | new Color(0xD9, 0xF2, 0xE2), // blue | 
| 126 | new Color(0xFF, 0xE7, 0xD8), // orange | |
| 127 | new Color(0xFF, 0xFF, 0xE5), // yellow | |
| 128 | new Color(0xDE, 0xCE, 0xFF), // lilac | |
| 129 | new Color(0xCC, 0xEB, 0xFF), // turquoise | |
| 130 | new Color(0xFF, 0xE5, 0xE5), // red | |
| 131 | new Color(0xE5, 0xE5, 0xD9) // green | |
| 50475 | 132 | ) | 
| 133 | ||
| 134 | private var curr : Int = -1 | |
| 59220 | 135 | def next(): Color = | 
| 50475 | 136 |     {
 | 
| 137 | curr = (curr + 1) % filter_colors.length | |
| 138 | filter_colors(curr) | |
| 139 | } | |
| 140 | } | |
| 141 | ||
| 59460 | 142 | def paint(gfx: Graphics2D) | 
| 59294 | 143 |   {
 | 
| 144 | gfx.setRenderingHints(Metrics.rendering_hints) | |
| 59410 | 145 | |
| 59459 | 146 | for (node <- graphview.current_node) | 
| 147 | Shapes.highlight_node(gfx, graphview, node) | |
| 59410 | 148 | |
| 59294 | 149 | for (edge <- visible_graph.edges_iterator) | 
| 59459 | 150 | Shapes.Cardinal_Spline_Edge.paint(gfx, graphview, edge) | 
| 59410 | 151 | |
| 59294 | 152 | for (node <- visible_graph.keys_iterator) | 
| 59459 | 153 | Shapes.paint_node(gfx, graphview, node) | 
| 59294 | 154 | } | 
| 50472 
bad1a1ca61e1
separate instance of class Parameters for each Main_Panel -- avoid global program state;
 wenzelm parents: 
50471diff
changeset | 155 | |
| 59392 | 156 | var current_node: Option[Graph_Display.Node] = None | 
| 157 | ||
| 50465 | 158 | object Selection | 
| 159 |   {
 | |
| 61590 | 160 | private val state = Synchronized[List[Graph_Display.Node]](Nil) | 
| 50465 | 161 | |
| 59443 
5b552b4f63a5
support for off-line graph output, without GUI thread;
 wenzelm parents: 
59410diff
changeset | 162 | def get(): List[Graph_Display.Node] = state.value | 
| 59245 
be4180f3c236
more formal Graph_Display.Node (with ordering) and Graph_Display.Edge;
 wenzelm parents: 
59242diff
changeset | 163 | def contains(node: Graph_Display.Node): Boolean = get().contains(node) | 
| 50465 | 164 | |
| 59443 
5b552b4f63a5
support for off-line graph output, without GUI thread;
 wenzelm parents: 
59410diff
changeset | 165 | def add(node: Graph_Display.Node): Unit = state.change(node :: _) | 
| 
5b552b4f63a5
support for off-line graph output, without GUI thread;
 wenzelm parents: 
59410diff
changeset | 166 | def clear(): Unit = state.change(_ => Nil) | 
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 167 | } | 
| 50465 | 168 | |
| 59220 | 169 | sealed case class Node_Color(border: Color, background: Color, foreground: Color) | 
| 50464 | 170 | |
| 59245 
be4180f3c236
more formal Graph_Display.Node (with ordering) and Graph_Display.Edge;
 wenzelm parents: 
59242diff
changeset | 171 | def node_color(node: Graph_Display.Node): Node_Color = | 
| 59410 | 172 | if (Selection.contains(node)) | 
| 59245 
be4180f3c236
more formal Graph_Display.Node (with ordering) and Graph_Display.Edge;
 wenzelm parents: 
59242diff
changeset | 173 | Node_Color(foreground_color, selection_color, foreground_color) | 
| 59410 | 174 | else if (current_node == Some(node)) | 
| 175 | Node_Color(foreground_color, highlight_color, foreground_color) | |
| 59245 
be4180f3c236
more formal Graph_Display.Node (with ordering) and Graph_Display.Edge;
 wenzelm parents: 
59242diff
changeset | 176 | else | 
| 
be4180f3c236
more formal Graph_Display.Node (with ordering) and Graph_Display.Edge;
 wenzelm parents: 
59242diff
changeset | 177 | Node_Color(foreground_color, model.colors.getOrElse(node, background_color), foreground_color) | 
| 59220 | 178 | |
| 59407 | 179 | def edge_color(edge: Graph_Display.Edge, downwards: Boolean): Color = | 
| 180 | if (downwards || show_arrow_heads) foreground_color | |
| 181 | else error_color | |
| 49557 
61988f9df94d
added Graphview tool, based on Isabelle/Scala and Swing/Graphics2D;
 Markus Kaiser <markus.kaiser@in.tum.de> parents: diff
changeset | 182 | } |