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

Last change on this file since 15040 was 15040, checked in by bburlacu, 4 years ago

#2794: Introduce properties for vertical and horizontal padding.

File size: 19.0 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2016 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;
29
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 int preferredNodeWidth = 70;
40    public int PreferredNodeWidth {
41      get { return preferredNodeWidth; }
42      set { preferredNodeWidth = value; }
43    }
44
45    private int preferredNodeHeight = 46;
46    public int PreferredNodeHeight {
47      get { return preferredNodeHeight; }
48      set { preferredNodeHeight = value; }
49    }
50
51    private int minHorizontalDistance = 30;
52    public int MinimumHorizontalDistance {
53      get { return minHorizontalDistance; }
54      set { minHorizontalDistance = value; }
55    }
56
57    private int minVerticalDistance = 30;
58    public int MinimumVerticalDistance {
59      get { return minVerticalDistance; }
60      set { minVerticalDistance = value; }
61    }
62
63    private int minHorizontalPadding = 20;
64    public int MinimumHorizontalPadding {
65      get { return minHorizontalPadding; }
66      set { minHorizontalPadding = value; }
67    }
68
69    private int minVerticalPadding = 20;
70    public int MinimumVerticalPadding {
71      get { return minVerticalPadding; }
72      set { minVerticalPadding = value; }
73    }
74
75    public SymbolicExpressionTreeChart() {
76      InitializeComponent();
77      this.image = new Bitmap(Width, Height);
78      this.stringFormat = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
79      this.spacing = 5;
80      this.lineColor = Color.Black;
81      this.backgroundColor = Color.White;
82      this.textFont = new Font(FontFamily.GenericSansSerif, 12);
83
84      visualTreeNodes = new Dictionary<ISymbolicExpressionTreeNode, VisualTreeNode<ISymbolicExpressionTreeNode>>();
85      visualLines = new Dictionary<Tuple<ISymbolicExpressionTreeNode, ISymbolicExpressionTreeNode>, VisualTreeNodeConnection>();
86
87      layoutEngine = new ReingoldTilfordLayoutEngine<ISymbolicExpressionTreeNode>(n => n.Subtrees) {
88        NodeWidth = preferredNodeWidth,
89        NodeHeight = preferredNodeHeight,
90        HorizontalSpacing = minHorizontalDistance,
91        VerticalSpacing = minVerticalDistance
92      };
93      reingoldTilfordToolStripMenuItem.Checked = true;
94    }
95
96    public SymbolicExpressionTreeChart(ISymbolicExpressionTree tree)
97      : this() {
98      this.Tree = tree;
99    }
100
101    #region Public properties
102    private int spacing;
103    public int Spacing {
104      get { return this.spacing; }
105      set {
106        this.spacing = value;
107        this.Repaint();
108      }
109    }
110
111    private Color lineColor;
112    public Color LineColor {
113      get { return this.lineColor; }
114      set {
115        this.lineColor = value;
116        this.Repaint();
117      }
118    }
119
120    private Color backgroundColor;
121    public Color BackgroundColor {
122      get { return this.backgroundColor; }
123      set {
124        this.backgroundColor = value;
125        this.Repaint();
126      }
127    }
128
129    private Font textFont;
130    public Font TextFont {
131      get { return this.textFont; }
132      set {
133        this.textFont = value;
134        this.Repaint();
135      }
136    }
137
138    private ISymbolicExpressionTree tree;
139    public ISymbolicExpressionTree Tree {
140      get { return this.tree; }
141      set {
142        tree = value;
143        Repaint();
144      }
145    }
146
147    private bool suspendRepaint;
148    public bool SuspendRepaint {
149      get { return suspendRepaint; }
150      set { suspendRepaint = value; }
151    }
152    #endregion
153
154    protected override void OnPaint(PaintEventArgs e) {
155      e.Graphics.DrawImage(image, 0, 0);
156      base.OnPaint(e);
157    }
158    protected override void OnResize(EventArgs e) {
159      base.OnResize(e);
160      if (this.Width <= 1 || this.Height <= 1)
161        this.image = new Bitmap(1, 1);
162      else {
163        this.image = new Bitmap(Width, Height);
164      }
165      this.Repaint();
166    }
167
168    public event EventHandler Repainted;//expose this event to notify the parent control that the tree was repainted
169    protected virtual void OnRepaint(object sender, EventArgs e) {
170      var repainted = Repainted;
171      if (repainted != null) {
172        repainted(sender, e);
173      }
174    }
175
176    public void Repaint() {
177      if (!suspendRepaint) {
178        this.GenerateImage();
179        this.Refresh();
180        OnRepaint(this, EventArgs.Empty);
181      }
182    }
183
184    public void RepaintNodes() {
185      if (!suspendRepaint) {
186        using (var graphics = Graphics.FromImage(image)) {
187          graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
188          graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
189          foreach (var visualNode in visualTreeNodes.Values) {
190            DrawTreeNode(graphics, visualNode);
191            if (visualNode.Content.SubtreeCount > 0) {
192              foreach (var visualSubtree in visualNode.Content.Subtrees.Select(s => visualTreeNodes[s])) {
193                DrawLine(graphics, visualNode, visualSubtree);
194              }
195            }
196          }
197        }
198        this.Refresh();
199      }
200    }
201
202    public void RepaintNode(VisualTreeNode<ISymbolicExpressionTreeNode> visualNode) {
203      if (!suspendRepaint) {
204        using (var graphics = Graphics.FromImage(image)) {
205          graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
206          graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
207          DrawTreeNode(graphics, visualNode);
208        }
209        this.Refresh();
210      }
211    }
212
213    private void GenerateImage() {
214      using (Graphics graphics = Graphics.FromImage(image)) {
215        graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
216        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
217        graphics.Clear(backgroundColor);
218        if (tree != null) {
219          DrawFunctionTree(graphics, preferredNodeWidth, preferredNodeHeight, minHorizontalDistance, minVerticalDistance);
220        }
221      }
222    }
223
224    public VisualTreeNode<ISymbolicExpressionTreeNode> GetVisualSymbolicExpressionTreeNode(ISymbolicExpressionTreeNode symbolicExpressionTreeNode) {
225      if (visualTreeNodes.ContainsKey(symbolicExpressionTreeNode))
226        return visualTreeNodes[symbolicExpressionTreeNode];
227      return null;
228    }
229
230    public VisualTreeNodeConnection GetVisualSymbolicExpressionTreeNodeConnection(ISymbolicExpressionTreeNode parent, ISymbolicExpressionTreeNode child) {
231      if (child.Parent != parent) throw new ArgumentException();
232      var key = Tuple.Create(parent, child);
233      VisualTreeNodeConnection connection = null;
234      visualLines.TryGetValue(key, out connection);
235      return connection;
236    }
237
238    #region events
239    public event MouseEventHandler SymbolicExpressionTreeNodeClicked;
240    protected virtual void OnSymbolicExpressionTreeNodeClicked(object sender, MouseEventArgs e) {
241      var clicked = SymbolicExpressionTreeNodeClicked;
242      if (clicked != null)
243        clicked(sender, e);
244    }
245
246    protected virtual void SymbolicExpressionTreeChart_MouseClick(object sender, MouseEventArgs e) {
247      var visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
248      if (visualTreeNode != null) {
249        OnSymbolicExpressionTreeNodeClicked(visualTreeNode, e);
250      }
251    }
252
253    public event MouseEventHandler SymbolicExpressionTreeNodeDoubleClicked;
254    protected virtual void OnSymbolicExpressionTreeNodeDoubleClicked(object sender, MouseEventArgs e) {
255      var doubleClicked = SymbolicExpressionTreeNodeDoubleClicked;
256      if (doubleClicked != null)
257        doubleClicked(sender, e);
258    }
259
260    protected virtual void SymbolicExpressionTreeChart_MouseDoubleClick(object sender, MouseEventArgs e) {
261      VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
262      if (visualTreeNode != null)
263        OnSymbolicExpressionTreeNodeDoubleClicked(visualTreeNode, e);
264    }
265
266    public event ItemDragEventHandler SymbolicExpressionTreeNodeDrag;
267    protected virtual void OnSymbolicExpressionTreeNodeDragDrag(object sender, ItemDragEventArgs e) {
268      var dragged = SymbolicExpressionTreeNodeDrag;
269      if (dragged != null)
270        dragged(sender, e);
271    }
272
273    private VisualTreeNode<ISymbolicExpressionTreeNode> draggedSymbolicExpressionTree;
274    private MouseButtons dragButtons;
275    private void SymbolicExpressionTreeChart_MouseDown(object sender, MouseEventArgs e) {
276      this.dragButtons = e.Button;
277      this.draggedSymbolicExpressionTree = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
278    }
279    private void SymbolicExpressionTreeChart_MouseUp(object sender, MouseEventArgs e) {
280      this.draggedSymbolicExpressionTree = null;
281      this.dragButtons = MouseButtons.None;
282    }
283
284    private void SymbolicExpressionTreeChart_MouseMove(object sender, MouseEventArgs e) {
285      VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
286      if (draggedSymbolicExpressionTree != null &&
287        draggedSymbolicExpressionTree != visualTreeNode) {
288        OnSymbolicExpressionTreeNodeDragDrag(draggedSymbolicExpressionTree, new ItemDragEventArgs(dragButtons, draggedSymbolicExpressionTree));
289        draggedSymbolicExpressionTree = null;
290      } else if (draggedSymbolicExpressionTree == null &&
291        visualTreeNode != null) {
292        string tooltipText = visualTreeNode.ToolTip;
293        if (this.toolTip.GetToolTip(this) != tooltipText)
294          this.toolTip.SetToolTip(this, tooltipText);
295
296      } else if (visualTreeNode == null)
297        this.toolTip.SetToolTip(this, "");
298    }
299
300    public VisualTreeNode<ISymbolicExpressionTreeNode> FindVisualSymbolicExpressionTreeNodeAt(int x, int y) {
301      foreach (var visualTreeNode in visualTreeNodes.Values) {
302        if (x >= visualTreeNode.X && x <= visualTreeNode.X + visualTreeNode.Width &&
303            y >= visualTreeNode.Y && y <= visualTreeNode.Y + visualTreeNode.Height)
304          return visualTreeNode;
305      }
306      return null;
307    }
308    #endregion
309
310    private void CalculateLayout(int preferredWidth, int preferredHeight, int minHDistance, int minVDistance) {
311      layoutEngine.NodeWidth = preferredWidth;
312      layoutEngine.NodeHeight = preferredHeight;
313      layoutEngine.HorizontalSpacing = minHDistance;
314      layoutEngine.VerticalSpacing = minVDistance;
315
316      var actualRoot = tree.Root;
317      if (actualRoot.Symbol is ProgramRootSymbol && actualRoot.SubtreeCount == 1) {
318        actualRoot = tree.Root.GetSubtree(0);
319      }
320      var visualNodes = layoutEngine.CalculateLayout(actualRoot, Width - minHorizontalPadding, Height - minVerticalPadding).ToList();
321      // add the padding
322      foreach (var vn in visualNodes) {
323        vn.X += minHorizontalPadding / 2;
324        vn.Y += minVerticalPadding / 2;
325      }
326
327      visualTreeNodes = visualNodes.ToDictionary(x => x.Content, x => x);
328      visualLines = new Dictionary<Tuple<ISymbolicExpressionTreeNode, ISymbolicExpressionTreeNode>, VisualTreeNodeConnection>();
329      foreach (var node in visualNodes.Select(n => n.Content)) {
330        foreach (var subtree in node.Subtrees) {
331          visualLines.Add(new Tuple<ISymbolicExpressionTreeNode, ISymbolicExpressionTreeNode>(node, subtree), new VisualTreeNodeConnection());
332        }
333      }
334    }
335
336    #region methods for painting the symbolic expression tree
337    private void DrawFunctionTree(Graphics graphics, int preferredWidth, int preferredHeight, int minHDistance, int minVDistance, bool recalculateLayout = true) {
338      if (recalculateLayout)
339        CalculateLayout(preferredWidth, preferredHeight, minHDistance, minVDistance);
340      var visualNodes = visualTreeNodes.Values;
341      //draw nodes and connections
342      foreach (var visualNode in visualNodes) {
343        DrawTreeNode(graphics, visualNode);
344        var node = visualNode.Content;
345        foreach (var subtree in node.Subtrees) {
346          var visualLine = GetVisualSymbolicExpressionTreeNodeConnection(node, subtree);
347          var visualSubtree = visualTreeNodes[subtree];
348          var origin = new Point(visualNode.X + visualNode.Width / 2, visualNode.Y + visualNode.Height);
349          var target = new Point(visualSubtree.X + visualSubtree.Width / 2, visualSubtree.Y);
350          graphics.Clip = new Region(new Rectangle(Math.Min(origin.X, target.X), origin.Y, Math.Max(origin.X, target.X), target.Y));
351          using (var linePen = new Pen(visualLine.LineColor)) {
352            linePen.DashStyle = visualLine.DashStyle;
353            graphics.DrawLine(linePen, origin, target);
354          }
355        }
356      }
357    }
358
359    private Action<Brush, int, int, int, int> fill;
360    private Action<Pen, int, int, int, int> draw;
361    protected void DrawTreeNode(Graphics graphics, VisualTreeNode<ISymbolicExpressionTreeNode> visualTreeNode) {
362      graphics.Clip = new Region(new Rectangle(visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width + 1, visualTreeNode.Height + 1));
363      graphics.Clear(backgroundColor);
364      var node = visualTreeNode.Content;
365      using (var textBrush = new SolidBrush(visualTreeNode.TextColor))
366      using (var nodeLinePen = new Pen(visualTreeNode.LineColor))
367      using (var nodeFillBrush = new SolidBrush(visualTreeNode.FillColor)) {
368        var x = visualTreeNode.X;
369        var y = visualTreeNode.Y;
370        var w = visualTreeNode.Width - 1;  // allow 1px for the drawing of the line
371        var h = visualTreeNode.Height - 1; // allow 1px for the drawing of the line
372        // draw leaf nodes as rectangles and internal nodes as ellipses
373        if (node.SubtreeCount > 0) {
374          fill = graphics.FillEllipse; draw = graphics.DrawEllipse;
375        } else {
376          fill = graphics.FillRectangle; draw = graphics.DrawRectangle;
377        }
378        fill(nodeFillBrush, x, y, w, h);
379        draw(nodeLinePen, x, y, w, h);
380        //draw name of symbol
381        graphics.DrawString(node.ToString(), textFont, textBrush, new RectangleF(x, y, w, h), stringFormat);
382      }
383    }
384
385    protected void DrawLine(Graphics graphics, VisualTreeNode<ISymbolicExpressionTreeNode> startNode, VisualTreeNode<ISymbolicExpressionTreeNode> endNode) {
386      var origin = new Point(startNode.X + startNode.Width / 2, startNode.Y + startNode.Height);
387      var target = new Point(endNode.X + endNode.Width / 2, endNode.Y);
388      graphics.Clip = new Region(new Rectangle(Math.Min(origin.X, target.X), origin.Y, Math.Max(origin.X, target.X), target.Y));
389      var visualLine = GetVisualSymbolicExpressionTreeNodeConnection(startNode.Content, endNode.Content);
390      using (var linePen = new Pen(visualLine.LineColor)) {
391        linePen.DashStyle = visualLine.DashStyle;
392        graphics.DrawLine(linePen, origin, target);
393      }
394    }
395    #endregion
396    #region save image
397    private void saveImageToolStripMenuItem_Click(object sender, EventArgs e) {
398      if (saveFileDialog.ShowDialog() == DialogResult.OK) {
399        string filename = saveFileDialog.FileName.ToLower();
400        if (filename.EndsWith("bmp")) SaveImageAsBitmap(filename);
401        else if (filename.EndsWith("emf")) SaveImageAsEmf(filename);
402        else SaveImageAsBitmap(filename);
403      }
404    }
405
406    public void SaveImageAsBitmap(string filename) {
407      if (tree == null) return;
408      Image image = new Bitmap(Width, Height);
409      using (Graphics g = Graphics.FromImage(image)) {
410        DrawFunctionTree(g, preferredNodeWidth, preferredNodeHeight, minHorizontalDistance, minVerticalDistance, false);
411      }
412      image.Save(filename);
413    }
414
415    public void SaveImageAsEmf(string filename) {
416      if (tree == null) return;
417      using (Graphics g = CreateGraphics()) {
418        using (Metafile file = new Metafile(filename, g.GetHdc())) {
419          using (Graphics emfFile = Graphics.FromImage(file)) {
420            DrawFunctionTree(emfFile, preferredNodeWidth, preferredNodeHeight, minHorizontalDistance, minVerticalDistance, false);
421          }
422        }
423        g.ReleaseHdc();
424      }
425    }
426    #endregion
427    #region export pgf/tikz
428    private void exportLatexToolStripMenuItem_Click(object sender, EventArgs e) {
429      var t = Tree;
430      if (t == null) return;
431      using (var dialog = new SaveFileDialog { Filter = "Tex (*.tex)|*.tex" }) {
432        if (dialog.ShowDialog() != DialogResult.OK) return;
433        string filename = dialog.FileName.ToLower();
434        var formatter = new SymbolicExpressionTreeLatexFormatter();
435        File.WriteAllText(filename, formatter.Format(t));
436      }
437    }
438    #endregion
439
440    private void reingoldTilfordToolStripMenuItem_Click(object sender, EventArgs e) {
441      minHorizontalDistance = 30;
442      minVerticalDistance = 30;
443      layoutEngine = new ReingoldTilfordLayoutEngine<ISymbolicExpressionTreeNode>(n => n.Subtrees) {
444        NodeWidth = preferredNodeWidth,
445        NodeHeight = preferredNodeHeight,
446        HorizontalSpacing = minHorizontalDistance,
447        VerticalSpacing = minVerticalDistance
448      };
449      reingoldTilfordToolStripMenuItem.Checked = true;
450      boxesToolStripMenuItem.Checked = false;
451      Repaint();
452    }
453
454    private void boxesToolStripMenuItem_Click(object sender, EventArgs e) {
455      minHorizontalDistance = 5;
456      minVerticalDistance = 5;
457      layoutEngine = new BoxesLayoutEngine<ISymbolicExpressionTreeNode>(n => n.Subtrees, n => n.GetLength(), n => n.GetDepth()) {
458        NodeWidth = preferredNodeWidth,
459        NodeHeight = preferredNodeHeight,
460        HorizontalSpacing = minHorizontalDistance,
461        VerticalSpacing = minVerticalDistance
462      };
463      reingoldTilfordToolStripMenuItem.Checked = false;
464      boxesToolStripMenuItem.Checked = true;
465      Repaint();
466    }
467  }
468}
Note: See TracBrowser for help on using the repository browser.