Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 10531 was 10531, checked in by bburlacu, 10 years ago

#2076: SymbolicExpressionTreeChart: initialize layout when the tree node is changed, not when the tree draw method is called.

File size: 16.6 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;
[10496]29using Point = System.Drawing.Point;
[3244]30
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);
[10531]52      layoutEngine = new ReingoldTilfordLayoutEngine<ISymbolicExpressionTreeNode> {
[10520]53        NodeWidth = preferredNodeWidth,
54        NodeHeight = preferredNodeHeight,
55        HorizontalSpacing = minHorizontalDistance,
56        VerticalSpacing = minVerticalDistance
57      };
[3244]58    }
59
[5513]60    public SymbolicExpressionTreeChart(ISymbolicExpressionTree tree)
[3244]61      : this() {
62      this.Tree = tree;
63    }
64
[10496]65    #region Public properties
[3244]66    private int spacing;
67    public int Spacing {
68      get { return this.spacing; }
69      set {
70        this.spacing = value;
71        this.Repaint();
72      }
73    }
74
75    private Color lineColor;
76    public Color LineColor {
77      get { return this.lineColor; }
78      set {
79        this.lineColor = value;
80        this.Repaint();
81      }
82    }
83
84    private Color backgroundColor;
85    public Color BackgroundColor {
86      get { return this.backgroundColor; }
87      set {
88        this.backgroundColor = value;
89        this.Repaint();
90      }
91    }
92
93    private Font textFont;
94    public Font TextFont {
95      get { return this.textFont; }
96      set {
97        this.textFont = value;
98        this.Repaint();
99      }
100    }
101
[5513]102    private ISymbolicExpressionTree tree;
103    public ISymbolicExpressionTree Tree {
[3244]104      get { return this.tree; }
105      set {
106        tree = value;
107        if (tree != null) {
[10531]108          //the layout engine needs to be initialized here so that the visualNodes and the visualLines dictionaries are populated
109          InitializeLayout();
[10520]110          Repaint();
[3244]111        }
112      }
113    }
114
[6803]115    private bool suspendRepaint;
116    public bool SuspendRepaint {
117      get { return suspendRepaint; }
118      set { suspendRepaint = value; }
119    }
[10496]120    #endregion
[6803]121
[3470]122    protected override void OnPaint(PaintEventArgs e) {
[6803]123      e.Graphics.DrawImage(image, 0, 0);
[3470]124      base.OnPaint(e);
125    }
126    protected override void OnResize(EventArgs e) {
127      base.OnResize(e);
[5956]128      if (this.Width <= 1 || this.Height <= 1)
[3470]129        this.image = new Bitmap(1, 1);
130      else
131        this.image = new Bitmap(Width, Height);
132      this.Repaint();
133    }
[3244]134
135    public void Repaint() {
[6803]136      if (!suspendRepaint) {
137        this.GenerateImage();
138        this.Refresh();
139      }
[3470]140    }
141
[8980]142    public void RepaintNodes() {
143      if (!suspendRepaint) {
[8986]144        using (var graphics = Graphics.FromImage(image)) {
145          graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
146          graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
147          foreach (var visualNode in visualTreeNodes.Values) {
148            DrawTreeNode(graphics, visualNode);
149          }
[8980]150        }
151        this.Refresh();
152      }
153    }
154
[10520]155    public void RepaintNode(VisualTreeNode<ISymbolicExpressionTreeNode> visualNode) {
[8980]156      if (!suspendRepaint) {
[8986]157        using (var graphics = Graphics.FromImage(image)) {
158          graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
159          graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
160          DrawTreeNode(graphics, visualNode);
161        }
[8980]162        this.Refresh();
163      }
164    }
165
[3470]166    private void GenerateImage() {
167      using (Graphics graphics = Graphics.FromImage(image)) {
[3244]168        graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
169        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
170        graphics.Clear(backgroundColor);
171        if (tree != null) {
[10496]172          DrawFunctionTree(tree, graphics, preferredNodeWidth, preferredNodeHeight, minHorizontalDistance, minVerticalDistance);
[3244]173        }
174      }
175    }
176
[10520]177    public VisualTreeNode<ISymbolicExpressionTreeNode> GetVisualSymbolicExpressionTreeNode(ISymbolicExpressionTreeNode symbolicExpressionTreeNode) {
[3915]178      if (visualTreeNodes.ContainsKey(symbolicExpressionTreeNode))
179        return visualTreeNodes[symbolicExpressionTreeNode];
180      return null;
181    }
182
[10520]183    public VisualTreeNodeConnection GetVisualSymbolicExpressionTreeNodeConnection(ISymbolicExpressionTreeNode parent, ISymbolicExpressionTreeNode child) {
[6803]184      if (child.Parent != parent) throw new ArgumentException();
185      var key = Tuple.Create(parent, child);
[10520]186      VisualTreeNodeConnection connection = null;
[6803]187      visualLines.TryGetValue(key, out connection);
188      return connection;
189    }
190
[3244]191    #region events
192    public event MouseEventHandler SymbolicExpressionTreeNodeClicked;
[8942]193    protected virtual void OnSymbolicExpressionTreeNodeClicked(object sender, MouseEventArgs e) {
[3244]194      var clicked = SymbolicExpressionTreeNodeClicked;
195      if (clicked != null)
196        clicked(sender, e);
197    }
198
[9043]199    protected virtual void SymbolicExpressionTreeChart_MouseClick(object sender, MouseEventArgs e) {
[10520]200      var visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
[8942]201      if (visualTreeNode != null) {
[3244]202        OnSymbolicExpressionTreeNodeClicked(visualTreeNode, e);
[8942]203      }
[3244]204    }
205
206    public event MouseEventHandler SymbolicExpressionTreeNodeDoubleClicked;
[8942]207    protected virtual void OnSymbolicExpressionTreeNodeDoubleClicked(object sender, MouseEventArgs e) {
[3244]208      var doubleClicked = SymbolicExpressionTreeNodeDoubleClicked;
209      if (doubleClicked != null)
210        doubleClicked(sender, e);
211    }
212
[9043]213    protected virtual void SymbolicExpressionTreeChart_MouseDoubleClick(object sender, MouseEventArgs e) {
[10520]214      VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
[3244]215      if (visualTreeNode != null)
216        OnSymbolicExpressionTreeNodeDoubleClicked(visualTreeNode, e);
217    }
218
219    public event ItemDragEventHandler SymbolicExpressionTreeNodeDrag;
[8942]220    protected virtual void OnSymbolicExpressionTreeNodeDragDrag(object sender, ItemDragEventArgs e) {
[3244]221      var dragged = SymbolicExpressionTreeNodeDrag;
222      if (dragged != null)
223        dragged(sender, e);
224    }
225
[10520]226    private VisualTreeNode<ISymbolicExpressionTreeNode> draggedSymbolicExpressionTree;
[3244]227    private MouseButtons dragButtons;
228    private void SymbolicExpressionTreeChart_MouseDown(object sender, MouseEventArgs e) {
229      this.dragButtons = e.Button;
230      this.draggedSymbolicExpressionTree = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
231    }
232    private void SymbolicExpressionTreeChart_MouseUp(object sender, MouseEventArgs e) {
233      this.draggedSymbolicExpressionTree = null;
234      this.dragButtons = MouseButtons.None;
235    }
236
237    private void SymbolicExpressionTreeChart_MouseMove(object sender, MouseEventArgs e) {
[10531]238
[10520]239      VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
[3244]240      if (draggedSymbolicExpressionTree != null &&
241        draggedSymbolicExpressionTree != visualTreeNode) {
242        OnSymbolicExpressionTreeNodeDragDrag(draggedSymbolicExpressionTree, new ItemDragEventArgs(dragButtons, draggedSymbolicExpressionTree));
243        draggedSymbolicExpressionTree = null;
244      } else if (draggedSymbolicExpressionTree == null &&
245        visualTreeNode != null) {
246        string tooltipText = visualTreeNode.ToolTip;
247        if (this.toolTip.GetToolTip(this) != tooltipText)
248          this.toolTip.SetToolTip(this, tooltipText);
249
250      } else if (visualTreeNode == null)
251        this.toolTip.SetToolTip(this, "");
252    }
253
[10520]254    public VisualTreeNode<ISymbolicExpressionTreeNode> FindVisualSymbolicExpressionTreeNodeAt(int x, int y) {
[3244]255      foreach (var visualTreeNode in visualTreeNodes.Values) {
256        if (x >= visualTreeNode.X && x <= visualTreeNode.X + visualTreeNode.Width &&
257            y >= visualTreeNode.Y && y <= visualTreeNode.Y + visualTreeNode.Height)
258          return visualTreeNode;
259      }
260      return null;
261    }
262    #endregion
263
[10531]264    private void InitializeLayout() {
265      var actualRoot = tree.Root.SubtreeCount == 1 ? tree.Root.GetSubtree(0) : tree.Root;
[10520]266      layoutEngine.Initialize(actualRoot, n => n.Subtrees, n => n.GetLength(), n => n.GetDepth());
[10531]267      layoutEngine.CalculateLayout(this.Width, this.Height);
[10520]268      var visualNodes = layoutEngine.GetVisualNodes().ToList();
[10531]269      //populate the visual nodes and visual connections dictionaries
[10520]270      visualTreeNodes = visualNodes.ToDictionary(x => x.Content, x => x);
271      visualLines = new Dictionary<Tuple<ISymbolicExpressionTreeNode, ISymbolicExpressionTreeNode>, VisualTreeNodeConnection>();
272      foreach (var node in visualNodes.Select(n => n.Content)) {
273        foreach (var subtree in node.Subtrees) {
274          visualLines.Add(new Tuple<ISymbolicExpressionTreeNode, ISymbolicExpressionTreeNode>(node, subtree), new VisualTreeNodeConnection());
[8986]275        }
[10496]276      }
[10531]277    }
278
279    #region methods for painting the symbolic expression tree
280    private void DrawFunctionTree(ISymbolicExpressionTree symbExprTree, Graphics graphics, int preferredWidth, int preferredHeight, int minHDistance, int minVDistance) {
281      //we assume here that the layout has already been initialized when the symbolic expression tree was changed
282      //recalculate layout according to new node widths and spacing
283      layoutEngine.NodeWidth = preferredWidth;
284      layoutEngine.NodeHeight = preferredHeight;
285      layoutEngine.HorizontalSpacing = minHDistance;
286      layoutEngine.VerticalSpacing = minVDistance;
287      layoutEngine.CalculateLayout(Width, Height);
288      var visualNodes = visualTreeNodes.Values;
289      //draw nodes and connections
[10520]290      foreach (var visualNode in visualNodes) {
291        DrawTreeNode(visualNode);
292        var node = visualNode.Content;
[10496]293        foreach (var subtree in node.Subtrees) {
294          var visualLine = GetVisualSymbolicExpressionTreeNodeConnection(node, subtree);
295          var visualSubtree = visualTreeNodes[subtree];
296          var origin = new Point(visualNode.X + visualNode.Width / 2, visualNode.Y + visualNode.Height);
297          var target = new Point(visualSubtree.X + visualSubtree.Width / 2, visualSubtree.Y);
298          graphics.Clip = new Region(new Rectangle(Math.Min(origin.X, target.X), origin.Y, Math.Max(origin.X, target.X), target.Y));
299          using (var linePen = new Pen(visualLine.LineColor)) {
[8986]300            linePen.DashStyle = visualLine.DashStyle;
[10496]301            graphics.DrawLine(linePen, origin, target);
[8986]302          }
[6803]303        }
[3244]304      }
305    }
[8980]306
[10520]307    protected void DrawTreeNode(VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode) {
[8980]308      using (var graphics = Graphics.FromImage(image)) {
309        graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
310        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
311        DrawTreeNode(graphics, visualTreeNode);
312      }
313    }
314
[10520]315    protected void DrawTreeNode(Graphics graphics, VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode) {
[8980]316      graphics.Clip = new Region(new Rectangle(visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width + 1, visualTreeNode.Height + 1));
317      graphics.Clear(backgroundColor);
[10520]318      var node = visualTreeNode.Content;
[8986]319      using (var textBrush = new SolidBrush(visualTreeNode.TextColor))
320      using (var nodeLinePen = new Pen(visualTreeNode.LineColor))
321      using (var nodeFillBrush = new SolidBrush(visualTreeNode.FillColor)) {
322        //draw terminal node
323        if (node.SubtreeCount == 0) {
324          graphics.FillRectangle(nodeFillBrush, visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height);
325          graphics.DrawRectangle(nodeLinePen, visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height);
326        } else {
327          graphics.FillEllipse(nodeFillBrush, visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height);
328          graphics.DrawEllipse(nodeLinePen, visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height);
329        }
330        //draw name of symbol
331        var text = node.ToString();
332        graphics.DrawString(text, textFont, textBrush, new RectangleF(visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height), stringFormat);
[8980]333      }
334    }
[3244]335    #endregion
[4651]336    #region save image
337    private void saveImageToolStripMenuItem_Click(object sender, EventArgs e) {
338      if (saveFileDialog.ShowDialog() == DialogResult.OK) {
339        string filename = saveFileDialog.FileName.ToLower();
340        if (filename.EndsWith("bmp")) SaveImageAsBitmap(filename);
341        else if (filename.EndsWith("emf")) SaveImageAsEmf(filename);
342        else SaveImageAsBitmap(filename);
343      }
344    }
345
[9587]346    public void SaveImageAsBitmap(string filename) {
[4651]347      if (tree == null) return;
348      Image image = new Bitmap(Width, Height);
349      using (Graphics g = Graphics.FromImage(image)) {
[10496]350        DrawFunctionTree(tree, g, preferredNodeWidth, preferredNodeHeight, minHorizontalDistance, minVerticalDistance);
[4651]351      }
352      image.Save(filename);
353    }
354
[9587]355    public void SaveImageAsEmf(string filename) {
[4651]356      if (tree == null) return;
357      using (Graphics g = CreateGraphics()) {
358        using (Metafile file = new Metafile(filename, g.GetHdc())) {
359          using (Graphics emfFile = Graphics.FromImage(file)) {
[10496]360            DrawFunctionTree(tree, emfFile, preferredNodeWidth, preferredNodeHeight, minHorizontalDistance, minVerticalDistance);
[4651]361          }
362        }
363        g.ReleaseHdc();
364      }
365    }
366    #endregion
[10496]367    #region export pgf/tikz
368    private void exportLatexToolStripMenuItem_Click(object sender, EventArgs e) {
369      using (var dialog = new SaveFileDialog { Filter = "Tex (*.tex)|*.tex" }) {
370        if (dialog.ShowDialog() != DialogResult.OK) return;
371        string filename = dialog.FileName.ToLower();
372        var formatter = new SymbolicExpressionTreeLatexFormatter();
373        File.WriteAllText(filename, formatter.Format(Tree));
374      }
375    }
376    #endregion
[10520]377
378    private void reingoldTilfordToolStripMenuItem_Click(object sender, EventArgs e) {
379      layoutEngine = new ReingoldTilfordLayoutEngine<ISymbolicExpressionTreeNode> {
380        NodeWidth = preferredNodeWidth,
381        NodeHeight = preferredNodeHeight,
382        HorizontalSpacing = minHorizontalDistance,
383        VerticalSpacing = minVerticalDistance
384      };
[10531]385      InitializeLayout();
[10520]386      Repaint();
387    }
388
389    private void boxesToolStripMenuItem_Click(object sender, EventArgs e) {
390      layoutEngine = new BoxesLayoutEngine<ISymbolicExpressionTreeNode> {
391        NodeWidth = preferredNodeWidth,
392        NodeHeight = preferredNodeHeight,
393        HorizontalSpacing = minHorizontalDistance,
394        VerticalSpacing = minVerticalDistance
395      };
[10531]396      InitializeLayout();
[10520]397      Repaint();
398    }
[3244]399  }
400}
Note: See TracBrowser for help on using the repository browser.