Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Encodings.SymbolicExpressionTreeEncoding.Views/3.4/SymbolicExpressionTreeChart.cs @ 10616

Last change on this file since 10616 was 10565, checked in by mkommend, 11 years ago

#2076: Simplified the API of the tree layout engines.

File size: 18.2 KB
RevLine 
[3244]1#region License Information
2/* HeuristicLab
[9456]3 * Copyright (C) 2002-2013 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
[3244]4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Drawing;
[4651]25using System.Drawing.Imaging;
[10496]26using System.IO;
27using System.Linq;
[3244]28using System.Windows.Forms;
29
[10565]30
[3244]31namespace HeuristicLab.Encodings.SymbolicExpressionTreeEncoding.Views {
[8942]32  public partial class SymbolicExpressionTreeChart : UserControl {
[3470]33    private Image image;
[10496]34    private readonly StringFormat stringFormat;
[10520]35    private Dictionary<ISymbolicExpressionTreeNode, VisualTreeNode<ISymbolicExpressionTreeNode>> visualTreeNodes;
36    private Dictionary<Tuple<ISymbolicExpressionTreeNode, ISymbolicExpressionTreeNode>, VisualTreeNodeConnection> visualLines;
37    private ILayoutEngine<ISymbolicExpressionTreeNode> layoutEngine;
[3244]38
[10496]39    private const int preferredNodeWidth = 70;
40    private const int preferredNodeHeight = 46;
[10520]41    private const int minHorizontalDistance = 30;
42    private const int minVerticalDistance = 30;
[10496]43
[3244]44    public SymbolicExpressionTreeChart() {
45      InitializeComponent();
[3470]46      this.image = new Bitmap(Width, Height);
[8942]47      this.stringFormat = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
[3244]48      this.spacing = 5;
49      this.lineColor = Color.Black;
50      this.backgroundColor = Color.White;
[10496]51      this.textFont = new Font(FontFamily.GenericSansSerif, 12);
[10565]52
53      visualTreeNodes = new Dictionary<ISymbolicExpressionTreeNode, VisualTreeNode<ISymbolicExpressionTreeNode>>();
54      visualLines = new Dictionary<Tuple<ISymbolicExpressionTreeNode, ISymbolicExpressionTreeNode>, VisualTreeNodeConnection>();
55
56      layoutEngine = new ReingoldTilfordLayoutEngine<ISymbolicExpressionTreeNode>(n => n.Subtrees) {
[10520]57        NodeWidth = preferredNodeWidth,
58        NodeHeight = preferredNodeHeight,
59        HorizontalSpacing = minHorizontalDistance,
60        VerticalSpacing = minVerticalDistance
61      };
[10565]62      reingoldTilfordToolStripMenuItem.Checked = true;
[3244]63    }
64
[10565]65    //private ILayoutEngine<ISymbolicExpressionTreeNode> TreeLayoutEngine {
66    //  get { return layoutEngine; }
67    //  set {
68    //    layoutEngine = value;
69    //    InitializeLayout();
70    //    Repaint();
71    //  }
72    //}
[10561]73
[5513]74    public SymbolicExpressionTreeChart(ISymbolicExpressionTree tree)
[3244]75      : this() {
76      this.Tree = tree;
77    }
78
[10496]79    #region Public properties
[3244]80    private int spacing;
81    public int Spacing {
82      get { return this.spacing; }
83      set {
84        this.spacing = value;
85        this.Repaint();
86      }
87    }
88
89    private Color lineColor;
90    public Color LineColor {
91      get { return this.lineColor; }
92      set {
93        this.lineColor = value;
94        this.Repaint();
95      }
96    }
97
98    private Color backgroundColor;
99    public Color BackgroundColor {
100      get { return this.backgroundColor; }
101      set {
102        this.backgroundColor = value;
103        this.Repaint();
104      }
105    }
106
107    private Font textFont;
108    public Font TextFont {
109      get { return this.textFont; }
110      set {
111        this.textFont = value;
112        this.Repaint();
113      }
114    }
115
[5513]116    private ISymbolicExpressionTree tree;
117    public ISymbolicExpressionTree Tree {
[3244]118      get { return this.tree; }
119      set {
120        tree = value;
[10565]121        Repaint();
[3244]122      }
123    }
124
[6803]125    private bool suspendRepaint;
126    public bool SuspendRepaint {
127      get { return suspendRepaint; }
128      set { suspendRepaint = value; }
129    }
[10496]130    #endregion
[6803]131
[3470]132    protected override void OnPaint(PaintEventArgs e) {
[6803]133      e.Graphics.DrawImage(image, 0, 0);
[3470]134      base.OnPaint(e);
135    }
136    protected override void OnResize(EventArgs e) {
137      base.OnResize(e);
[5956]138      if (this.Width <= 1 || this.Height <= 1)
[3470]139        this.image = new Bitmap(1, 1);
[10561]140      else {
[3470]141        this.image = new Bitmap(Width, Height);
[10561]142      }
[3470]143      this.Repaint();
144    }
[3244]145
[10561]146    public event EventHandler Repainted;//expose this event to notify the parent control that the tree was repainted
147    protected virtual void OnRepaint(object sender, EventArgs e) {
148      var repainted = Repainted;
149      if (repainted != null) {
150        repainted(sender, e);
151      }
152    }
153
[3244]154    public void Repaint() {
[6803]155      if (!suspendRepaint) {
156        this.GenerateImage();
157        this.Refresh();
[10561]158        OnRepaint(this, EventArgs.Empty);
[6803]159      }
[3470]160    }
161
[8980]162    public void RepaintNodes() {
163      if (!suspendRepaint) {
[8986]164        using (var graphics = Graphics.FromImage(image)) {
165          graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
166          graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
167          foreach (var visualNode in visualTreeNodes.Values) {
168            DrawTreeNode(graphics, visualNode);
[10561]169            if (visualNode.Content.SubtreeCount > 0) {
170              foreach (var visualSubtree in visualNode.Content.Subtrees.Select(s => visualTreeNodes[s])) {
171                DrawLine(graphics, visualNode, visualSubtree);
172              }
173            }
[8986]174          }
[8980]175        }
176        this.Refresh();
177      }
178    }
179
[10520]180    public void RepaintNode(VisualTreeNode<ISymbolicExpressionTreeNode> visualNode) {
[8980]181      if (!suspendRepaint) {
[8986]182        using (var graphics = Graphics.FromImage(image)) {
183          graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
184          graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
185          DrawTreeNode(graphics, visualNode);
186        }
[8980]187        this.Refresh();
188      }
189    }
190
[3470]191    private void GenerateImage() {
192      using (Graphics graphics = Graphics.FromImage(image)) {
[3244]193        graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
194        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
195        graphics.Clear(backgroundColor);
196        if (tree != null) {
[10561]197          DrawFunctionTree(graphics, preferredNodeWidth, preferredNodeHeight, minHorizontalDistance, minVerticalDistance);
[3244]198        }
199      }
200    }
201
[10520]202    public VisualTreeNode<ISymbolicExpressionTreeNode> GetVisualSymbolicExpressionTreeNode(ISymbolicExpressionTreeNode symbolicExpressionTreeNode) {
[3915]203      if (visualTreeNodes.ContainsKey(symbolicExpressionTreeNode))
204        return visualTreeNodes[symbolicExpressionTreeNode];
205      return null;
206    }
207
[10520]208    public VisualTreeNodeConnection GetVisualSymbolicExpressionTreeNodeConnection(ISymbolicExpressionTreeNode parent, ISymbolicExpressionTreeNode child) {
[6803]209      if (child.Parent != parent) throw new ArgumentException();
210      var key = Tuple.Create(parent, child);
[10520]211      VisualTreeNodeConnection connection = null;
[6803]212      visualLines.TryGetValue(key, out connection);
213      return connection;
214    }
215
[3244]216    #region events
217    public event MouseEventHandler SymbolicExpressionTreeNodeClicked;
[8942]218    protected virtual void OnSymbolicExpressionTreeNodeClicked(object sender, MouseEventArgs e) {
[3244]219      var clicked = SymbolicExpressionTreeNodeClicked;
220      if (clicked != null)
221        clicked(sender, e);
222    }
223
[9043]224    protected virtual void SymbolicExpressionTreeChart_MouseClick(object sender, MouseEventArgs e) {
[10520]225      var visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
[8942]226      if (visualTreeNode != null) {
[3244]227        OnSymbolicExpressionTreeNodeClicked(visualTreeNode, e);
[8942]228      }
[3244]229    }
230
231    public event MouseEventHandler SymbolicExpressionTreeNodeDoubleClicked;
[8942]232    protected virtual void OnSymbolicExpressionTreeNodeDoubleClicked(object sender, MouseEventArgs e) {
[3244]233      var doubleClicked = SymbolicExpressionTreeNodeDoubleClicked;
234      if (doubleClicked != null)
235        doubleClicked(sender, e);
236    }
237
[9043]238    protected virtual void SymbolicExpressionTreeChart_MouseDoubleClick(object sender, MouseEventArgs e) {
[10520]239      VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
[3244]240      if (visualTreeNode != null)
241        OnSymbolicExpressionTreeNodeDoubleClicked(visualTreeNode, e);
242    }
243
244    public event ItemDragEventHandler SymbolicExpressionTreeNodeDrag;
[8942]245    protected virtual void OnSymbolicExpressionTreeNodeDragDrag(object sender, ItemDragEventArgs e) {
[3244]246      var dragged = SymbolicExpressionTreeNodeDrag;
247      if (dragged != null)
248        dragged(sender, e);
249    }
250
[10520]251    private VisualTreeNode<ISymbolicExpressionTreeNode> draggedSymbolicExpressionTree;
[3244]252    private MouseButtons dragButtons;
253    private void SymbolicExpressionTreeChart_MouseDown(object sender, MouseEventArgs e) {
254      this.dragButtons = e.Button;
255      this.draggedSymbolicExpressionTree = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
256    }
257    private void SymbolicExpressionTreeChart_MouseUp(object sender, MouseEventArgs e) {
258      this.draggedSymbolicExpressionTree = null;
259      this.dragButtons = MouseButtons.None;
260    }
261
262    private void SymbolicExpressionTreeChart_MouseMove(object sender, MouseEventArgs e) {
[10520]263      VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
[3244]264      if (draggedSymbolicExpressionTree != null &&
265        draggedSymbolicExpressionTree != visualTreeNode) {
266        OnSymbolicExpressionTreeNodeDragDrag(draggedSymbolicExpressionTree, new ItemDragEventArgs(dragButtons, draggedSymbolicExpressionTree));
267        draggedSymbolicExpressionTree = null;
268      } else if (draggedSymbolicExpressionTree == null &&
269        visualTreeNode != null) {
270        string tooltipText = visualTreeNode.ToolTip;
271        if (this.toolTip.GetToolTip(this) != tooltipText)
272          this.toolTip.SetToolTip(this, tooltipText);
273
274      } else if (visualTreeNode == null)
275        this.toolTip.SetToolTip(this, "");
276    }
277
[10520]278    public VisualTreeNode<ISymbolicExpressionTreeNode> FindVisualSymbolicExpressionTreeNodeAt(int x, int y) {
[3244]279      foreach (var visualTreeNode in visualTreeNodes.Values) {
280        if (x >= visualTreeNode.X && x <= visualTreeNode.X + visualTreeNode.Width &&
281            y >= visualTreeNode.Y && y <= visualTreeNode.Y + visualTreeNode.Height)
282          return visualTreeNode;
283      }
284      return null;
285    }
286    #endregion
287
[10565]288    private void CalculateLayout(int preferredWidth, int preferredHeight, int minHDistance, int minVDistance) {
289      layoutEngine.NodeWidth = preferredWidth;
290      layoutEngine.NodeHeight = preferredHeight;
291      layoutEngine.HorizontalSpacing = minHDistance;
292      layoutEngine.VerticalSpacing = minVDistance;
293
[10561]294      var actualRoot = tree.Root;
295      if (actualRoot.Symbol is ProgramRootSymbol && actualRoot.SubtreeCount == 1) {
296        actualRoot = tree.Root.GetSubtree(0);
297      }
298
[10565]299      var visualNodes = layoutEngine.CalculateLayout(actualRoot, Width, Height).ToList();
[10520]300      visualTreeNodes = visualNodes.ToDictionary(x => x.Content, x => x);
301      visualLines = new Dictionary<Tuple<ISymbolicExpressionTreeNode, ISymbolicExpressionTreeNode>, VisualTreeNodeConnection>();
302      foreach (var node in visualNodes.Select(n => n.Content)) {
303        foreach (var subtree in node.Subtrees) {
304          visualLines.Add(new Tuple<ISymbolicExpressionTreeNode, ISymbolicExpressionTreeNode>(node, subtree), new VisualTreeNodeConnection());
[8986]305        }
[10496]306      }
[10531]307    }
[10565]308
[10531]309    #region methods for painting the symbolic expression tree
[10561]310    private void DrawFunctionTree(Graphics graphics, int preferredWidth, int preferredHeight, int minHDistance, int minVDistance) {
[10565]311      CalculateLayout(preferredWidth, preferredHeight, minHDistance, minVDistance);
[10531]312      var visualNodes = visualTreeNodes.Values;
313      //draw nodes and connections
[10520]314      foreach (var visualNode in visualNodes) {
315        DrawTreeNode(visualNode);
316        var node = visualNode.Content;
[10496]317        foreach (var subtree in node.Subtrees) {
318          var visualLine = GetVisualSymbolicExpressionTreeNodeConnection(node, subtree);
319          var visualSubtree = visualTreeNodes[subtree];
320          var origin = new Point(visualNode.X + visualNode.Width / 2, visualNode.Y + visualNode.Height);
321          var target = new Point(visualSubtree.X + visualSubtree.Width / 2, visualSubtree.Y);
322          graphics.Clip = new Region(new Rectangle(Math.Min(origin.X, target.X), origin.Y, Math.Max(origin.X, target.X), target.Y));
323          using (var linePen = new Pen(visualLine.LineColor)) {
[8986]324            linePen.DashStyle = visualLine.DashStyle;
[10496]325            graphics.DrawLine(linePen, origin, target);
[8986]326          }
[6803]327        }
[3244]328      }
329    }
[8980]330
[10520]331    protected void DrawTreeNode(VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode) {
[8980]332      using (var graphics = Graphics.FromImage(image)) {
333        graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
334        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
335        DrawTreeNode(graphics, visualTreeNode);
336      }
337    }
338
[10520]339    protected void DrawTreeNode(Graphics graphics, VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode) {
[8980]340      graphics.Clip = new Region(new Rectangle(visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width + 1, visualTreeNode.Height + 1));
341      graphics.Clear(backgroundColor);
[10520]342      var node = visualTreeNode.Content;
[8986]343      using (var textBrush = new SolidBrush(visualTreeNode.TextColor))
344      using (var nodeLinePen = new Pen(visualTreeNode.LineColor))
345      using (var nodeFillBrush = new SolidBrush(visualTreeNode.FillColor)) {
346        //draw terminal node
347        if (node.SubtreeCount == 0) {
348          graphics.FillRectangle(nodeFillBrush, visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height);
349          graphics.DrawRectangle(nodeLinePen, visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height);
350        } else {
351          graphics.FillEllipse(nodeFillBrush, visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height);
352          graphics.DrawEllipse(nodeLinePen, visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height);
353        }
354        //draw name of symbol
355        var text = node.ToString();
356        graphics.DrawString(text, textFont, textBrush, new RectangleF(visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height), stringFormat);
[8980]357      }
358    }
[10561]359
360    protected void DrawLine(Graphics graphics, VisualTreeNode<ISymbolicExpressionTreeNode> startNode, VisualTreeNode<ISymbolicExpressionTreeNode> endNode) {
361      var origin = new Point(startNode.X + startNode.Width / 2, startNode.Y + startNode.Height);
362      var target = new Point(endNode.X + endNode.Width / 2, endNode.Y);
363      graphics.Clip = new Region(new Rectangle(Math.Min(origin.X, target.X), origin.Y, Math.Max(origin.X, target.X), target.Y));
364      var visualLine = GetVisualSymbolicExpressionTreeNodeConnection(startNode.Content, endNode.Content);
365      using (var linePen = new Pen(visualLine.LineColor)) {
366        linePen.DashStyle = visualLine.DashStyle;
367        graphics.DrawLine(linePen, origin, target);
368      }
369    }
[3244]370    #endregion
[4651]371    #region save image
372    private void saveImageToolStripMenuItem_Click(object sender, EventArgs e) {
373      if (saveFileDialog.ShowDialog() == DialogResult.OK) {
374        string filename = saveFileDialog.FileName.ToLower();
375        if (filename.EndsWith("bmp")) SaveImageAsBitmap(filename);
376        else if (filename.EndsWith("emf")) SaveImageAsEmf(filename);
377        else SaveImageAsBitmap(filename);
378      }
379    }
380
[9587]381    public void SaveImageAsBitmap(string filename) {
[4651]382      if (tree == null) return;
383      Image image = new Bitmap(Width, Height);
384      using (Graphics g = Graphics.FromImage(image)) {
[10561]385        DrawFunctionTree(g, preferredNodeWidth, preferredNodeHeight, minHorizontalDistance, minVerticalDistance);
[4651]386      }
387      image.Save(filename);
388    }
389
[9587]390    public void SaveImageAsEmf(string filename) {
[4651]391      if (tree == null) return;
392      using (Graphics g = CreateGraphics()) {
393        using (Metafile file = new Metafile(filename, g.GetHdc())) {
394          using (Graphics emfFile = Graphics.FromImage(file)) {
[10561]395            DrawFunctionTree(emfFile, preferredNodeWidth, preferredNodeHeight, minHorizontalDistance, minVerticalDistance);
[4651]396          }
397        }
398        g.ReleaseHdc();
399      }
400    }
401    #endregion
[10496]402    #region export pgf/tikz
403    private void exportLatexToolStripMenuItem_Click(object sender, EventArgs e) {
404      using (var dialog = new SaveFileDialog { Filter = "Tex (*.tex)|*.tex" }) {
405        if (dialog.ShowDialog() != DialogResult.OK) return;
406        string filename = dialog.FileName.ToLower();
407        var formatter = new SymbolicExpressionTreeLatexFormatter();
408        File.WriteAllText(filename, formatter.Format(Tree));
409      }
410    }
411    #endregion
[10520]412
413    private void reingoldTilfordToolStripMenuItem_Click(object sender, EventArgs e) {
[10565]414      layoutEngine = new ReingoldTilfordLayoutEngine<ISymbolicExpressionTreeNode>(n => n.Subtrees) {
[10520]415        NodeWidth = preferredNodeWidth,
416        NodeHeight = preferredNodeHeight,
417        HorizontalSpacing = minHorizontalDistance,
418        VerticalSpacing = minVerticalDistance
419      };
[10565]420      reingoldTilfordToolStripMenuItem.Checked = true;
421      boxesToolStripMenuItem.Checked = false;
422      Repaint();
[10520]423    }
424
425    private void boxesToolStripMenuItem_Click(object sender, EventArgs e) {
[10565]426      layoutEngine = new BoxesLayoutEngine<ISymbolicExpressionTreeNode>(n => n.Subtrees, n => n.GetLength(), n => n.GetDepth()) {
[10520]427        NodeWidth = preferredNodeWidth,
428        NodeHeight = preferredNodeHeight,
429        HorizontalSpacing = minHorizontalDistance,
430        VerticalSpacing = minVerticalDistance
431      };
[10565]432      reingoldTilfordToolStripMenuItem.Checked = false;
433      boxesToolStripMenuItem.Checked = true;
434      Repaint();
[10520]435    }
[3244]436  }
437}
Note: See TracBrowser for help on using the repository browser.