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

Last change on this file since 10520 was 10520, checked in by bburlacu, 6 years ago

#2076: Got rid of layout adapters. Extracted the previous drawing code and made it into another layout engine called the BoxesLayoutEngine (because it divides the areas necessary for each subtree into boxes and recursively applies the layout). Simplified usage of layout engine so that most of the things are handled internally, and the user just has to provide some lambdas telling the engine how to navigate the original tree. Added context option in the SymbolicExpressionTreeChart to choose which layout engine to use for tree drawing. Moved the SymbolicExpressionTreeLatexFormatter to the HeuristicLab.Encodings.SymbolicExpressionTreeEncoding.Views assembly because it depends on the layout engine.

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