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, 5 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
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2013 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
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;
25using System.Drawing.Imaging;
26using System.IO;
27using System.Linq;
28using System.Windows.Forms;
29using Point = System.Drawing.Point;
30
31namespace HeuristicLab.Encodings.SymbolicExpressionTreeEncoding.Views {
32  public partial class SymbolicExpressionTreeChart : UserControl {
33    private Image image;
34    private readonly StringFormat stringFormat;
35    private Dictionary<ISymbolicExpressionTreeNode, VisualTreeNode<ISymbolicExpressionTreeNode>> visualTreeNodes;
36    private Dictionary<Tuple<ISymbolicExpressionTreeNode, ISymbolicExpressionTreeNode>, VisualTreeNodeConnection> visualLines;
37    private ILayoutEngine<ISymbolicExpressionTreeNode> layoutEngine;
38
39    private const int preferredNodeWidth = 70;
40    private const int preferredNodeHeight = 46;
41    private const int minHorizontalDistance = 30;
42    private const int minVerticalDistance = 30;
43
44    public SymbolicExpressionTreeChart() {
45      InitializeComponent();
46      this.image = new Bitmap(Width, Height);
47      this.stringFormat = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
48      this.spacing = 5;
49      this.lineColor = Color.Black;
50      this.backgroundColor = Color.White;
51      this.textFont = new Font(FontFamily.GenericSansSerif, 12);
52      layoutEngine = new ReingoldTilfordLayoutEngine<ISymbolicExpressionTreeNode> {
53        NodeWidth = preferredNodeWidth,
54        NodeHeight = preferredNodeHeight,
55        HorizontalSpacing = minHorizontalDistance,
56        VerticalSpacing = minVerticalDistance
57      };
58    }
59
60    public SymbolicExpressionTreeChart(ISymbolicExpressionTree tree)
61      : this() {
62      this.Tree = tree;
63    }
64
65    #region Public properties
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
102    private ISymbolicExpressionTree tree;
103    public ISymbolicExpressionTree Tree {
104      get { return this.tree; }
105      set {
106        tree = value;
107        if (tree != null) {
108          //the layout engine needs to be initialized here so that the visualNodes and the visualLines dictionaries are populated
109          InitializeLayout();
110          Repaint();
111        }
112      }
113    }
114
115    private bool suspendRepaint;
116    public bool SuspendRepaint {
117      get { return suspendRepaint; }
118      set { suspendRepaint = value; }
119    }
120    #endregion
121
122    protected override void OnPaint(PaintEventArgs e) {
123      e.Graphics.DrawImage(image, 0, 0);
124      base.OnPaint(e);
125    }
126    protected override void OnResize(EventArgs e) {
127      base.OnResize(e);
128      if (this.Width <= 1 || this.Height <= 1)
129        this.image = new Bitmap(1, 1);
130      else
131        this.image = new Bitmap(Width, Height);
132      this.Repaint();
133    }
134
135    public void Repaint() {
136      if (!suspendRepaint) {
137        this.GenerateImage();
138        this.Refresh();
139      }
140    }
141
142    public void RepaintNodes() {
143      if (!suspendRepaint) {
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          }
150        }
151        this.Refresh();
152      }
153    }
154
155    public void RepaintNode(VisualTreeNode<ISymbolicExpressionTreeNode> visualNode) {
156      if (!suspendRepaint) {
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        }
162        this.Refresh();
163      }
164    }
165
166    private void GenerateImage() {
167      using (Graphics graphics = Graphics.FromImage(image)) {
168        graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
169        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
170        graphics.Clear(backgroundColor);
171        if (tree != null) {
172          DrawFunctionTree(tree, graphics, preferredNodeWidth, preferredNodeHeight, minHorizontalDistance, minVerticalDistance);
173        }
174      }
175    }
176
177    public VisualTreeNode<ISymbolicExpressionTreeNode> GetVisualSymbolicExpressionTreeNode(ISymbolicExpressionTreeNode symbolicExpressionTreeNode) {
178      if (visualTreeNodes.ContainsKey(symbolicExpressionTreeNode))
179        return visualTreeNodes[symbolicExpressionTreeNode];
180      return null;
181    }
182
183    public VisualTreeNodeConnection GetVisualSymbolicExpressionTreeNodeConnection(ISymbolicExpressionTreeNode parent, ISymbolicExpressionTreeNode child) {
184      if (child.Parent != parent) throw new ArgumentException();
185      var key = Tuple.Create(parent, child);
186      VisualTreeNodeConnection connection = null;
187      visualLines.TryGetValue(key, out connection);
188      return connection;
189    }
190
191    #region events
192    public event MouseEventHandler SymbolicExpressionTreeNodeClicked;
193    protected virtual void OnSymbolicExpressionTreeNodeClicked(object sender, MouseEventArgs e) {
194      var clicked = SymbolicExpressionTreeNodeClicked;
195      if (clicked != null)
196        clicked(sender, e);
197    }
198
199    protected virtual void SymbolicExpressionTreeChart_MouseClick(object sender, MouseEventArgs e) {
200      var visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
201      if (visualTreeNode != null) {
202        OnSymbolicExpressionTreeNodeClicked(visualTreeNode, e);
203      }
204    }
205
206    public event MouseEventHandler SymbolicExpressionTreeNodeDoubleClicked;
207    protected virtual void OnSymbolicExpressionTreeNodeDoubleClicked(object sender, MouseEventArgs e) {
208      var doubleClicked = SymbolicExpressionTreeNodeDoubleClicked;
209      if (doubleClicked != null)
210        doubleClicked(sender, e);
211    }
212
213    protected virtual void SymbolicExpressionTreeChart_MouseDoubleClick(object sender, MouseEventArgs e) {
214      VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
215      if (visualTreeNode != null)
216        OnSymbolicExpressionTreeNodeDoubleClicked(visualTreeNode, e);
217    }
218
219    public event ItemDragEventHandler SymbolicExpressionTreeNodeDrag;
220    protected virtual void OnSymbolicExpressionTreeNodeDragDrag(object sender, ItemDragEventArgs e) {
221      var dragged = SymbolicExpressionTreeNodeDrag;
222      if (dragged != null)
223        dragged(sender, e);
224    }
225
226    private VisualTreeNode<ISymbolicExpressionTreeNode> draggedSymbolicExpressionTree;
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) {
238
239      VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
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
254    public VisualTreeNode<ISymbolicExpressionTreeNode> FindVisualSymbolicExpressionTreeNodeAt(int x, int y) {
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
264    private void InitializeLayout() {
265      var actualRoot = tree.Root.SubtreeCount == 1 ? tree.Root.GetSubtree(0) : tree.Root;
266      layoutEngine.Initialize(actualRoot, n => n.Subtrees, n => n.GetLength(), n => n.GetDepth());
267      layoutEngine.CalculateLayout(this.Width, this.Height);
268      var visualNodes = layoutEngine.GetVisualNodes().ToList();
269      //populate the visual nodes and visual connections dictionaries
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());
275        }
276      }
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
290      foreach (var visualNode in visualNodes) {
291        DrawTreeNode(visualNode);
292        var node = visualNode.Content;
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)) {
300            linePen.DashStyle = visualLine.DashStyle;
301            graphics.DrawLine(linePen, origin, target);
302          }
303        }
304      }
305    }
306
307    protected void DrawTreeNode(VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode) {
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
315    protected void DrawTreeNode(Graphics graphics, VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode) {
316      graphics.Clip = new Region(new Rectangle(visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width + 1, visualTreeNode.Height + 1));
317      graphics.Clear(backgroundColor);
318      var node = visualTreeNode.Content;
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);
333      }
334    }
335    #endregion
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
346    public void SaveImageAsBitmap(string filename) {
347      if (tree == null) return;
348      Image image = new Bitmap(Width, Height);
349      using (Graphics g = Graphics.FromImage(image)) {
350        DrawFunctionTree(tree, g, preferredNodeWidth, preferredNodeHeight, minHorizontalDistance, minVerticalDistance);
351      }
352      image.Save(filename);
353    }
354
355    public void SaveImageAsEmf(string filename) {
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)) {
360            DrawFunctionTree(tree, emfFile, preferredNodeWidth, preferredNodeHeight, minHorizontalDistance, minVerticalDistance);
361          }
362        }
363        g.ReleaseHdc();
364      }
365    }
366    #endregion
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
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      };
385      InitializeLayout();
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      };
396      InitializeLayout();
397      Repaint();
398    }
399  }
400}
Note: See TracBrowser for help on using the repository browser.