Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.EvolutionTracking/HeuristicLab.Encodings.SymbolicExpressionTreeEncoding.Views/3.4/SymbolicExpressionTreeChart.cs @ 10456

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

#1772: Merged trunk changes.

File size: 17.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 StringFormat stringFormat;
35    private Dictionary<ISymbolicExpressionTreeNode, VisualSymbolicExpressionTreeNode> visualTreeNodes;
36    private Dictionary<Tuple<ISymbolicExpressionTreeNode, ISymbolicExpressionTreeNode>, VisualSymbolicExpressionTreeNodeConnection> visualLines;
37    private readonly ReingoldTilfordLayoutEngine<ISymbolicExpressionTreeNode> layoutEngine;
38    private readonly SymbolicExpressionTreeLayoutAdapter layoutAdapter;
39
40    private const int preferredNodeWidth = 70;
41    private const int preferredNodeHeight = 46;
42    private const int minHorizontalDistance = 20;
43    private const int minVerticalDistance = 20;
44
45
46    public SymbolicExpressionTreeChart() {
47      InitializeComponent();
48      this.image = new Bitmap(Width, Height);
49      this.stringFormat = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
50      this.spacing = 5;
51      this.lineColor = Color.Black;
52      this.backgroundColor = Color.White;
53      this.textFont = new Font(FontFamily.GenericSerif, 14, GraphicsUnit.Pixel);
54      layoutEngine = new ReingoldTilfordLayoutEngine<ISymbolicExpressionTreeNode>();
55      layoutAdapter = new SymbolicExpressionTreeLayoutAdapter();
56    }
57
58    public SymbolicExpressionTreeChart(ISymbolicExpressionTree tree)
59      : this() {
60      layoutEngine = new ReingoldTilfordLayoutEngine<ISymbolicExpressionTreeNode>();
61      layoutAdapter = new SymbolicExpressionTreeLayoutAdapter();
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        visualTreeNodes = new Dictionary<ISymbolicExpressionTreeNode, VisualSymbolicExpressionTreeNode>();
108        visualLines = new Dictionary<Tuple<ISymbolicExpressionTreeNode, ISymbolicExpressionTreeNode>, VisualSymbolicExpressionTreeNodeConnection>();
109        if (tree != null) {
110          foreach (ISymbolicExpressionTreeNode node in tree.IterateNodesPrefix()) {
111            visualTreeNodes[node] = new VisualSymbolicExpressionTreeNode(node);
112            if (node.Parent != null) visualLines[Tuple.Create(node.Parent, node)] = new VisualSymbolicExpressionTreeNodeConnection();
113          }
114        }
115        Repaint();
116      }
117    }
118
119    private bool suspendRepaint;
120    public bool SuspendRepaint {
121      get { return suspendRepaint; }
122      set { suspendRepaint = value; }
123    }
124    #endregion
125
126    protected override void OnPaint(PaintEventArgs e) {
127      e.Graphics.DrawImage(image, 0, 0);
128      base.OnPaint(e);
129    }
130    protected override void OnResize(EventArgs e) {
131      base.OnResize(e);
132      if (this.Width <= 1 || this.Height <= 1)
133        this.image = new Bitmap(1, 1);
134      else
135        this.image = new Bitmap(Width, Height);
136      this.Repaint();
137    }
138
139    public void Repaint() {
140      if (!suspendRepaint) {
141        this.GenerateImage();
142        this.Refresh();
143      }
144    }
145
146    public void RepaintNodes() {
147      if (!suspendRepaint) {
148        using (var graphics = Graphics.FromImage(image)) {
149          graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
150          graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
151          foreach (var visualNode in visualTreeNodes.Values) {
152            DrawTreeNode(graphics, visualNode);
153          }
154        }
155        this.Refresh();
156      }
157    }
158
159    public void RepaintNode(VisualSymbolicExpressionTreeNode visualNode) {
160      if (!suspendRepaint) {
161        using (var graphics = Graphics.FromImage(image)) {
162          graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
163          graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
164          DrawTreeNode(graphics, visualNode);
165        }
166        this.Refresh();
167      }
168    }
169
170    public void HighlightNode(ISymbolicExpressionTreeNode node, Color color) {
171      var visualNode = GetVisualSymbolicExpressionTreeNode(node);
172      if (visualNode == null) return;
173      visualNode.LineColor = color;
174      RepaintNode(visualNode);
175    }
176
177    public void HighlightSubtree(ISymbolicExpressionTreeNode subtree, Color color) {
178      foreach (var node in subtree.IterateNodesBreadth())
179        HighlightNode(node, color);
180    }
181
182    private void GenerateImage() {
183      using (Graphics graphics = Graphics.FromImage(image)) {
184        graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
185        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
186        graphics.Clear(backgroundColor);
187        if (tree != null) {
188          DrawFunctionTree(tree, graphics, preferredNodeWidth, preferredNodeHeight, minHorizontalDistance, minVerticalDistance);
189        }
190      }
191    }
192
193    public VisualSymbolicExpressionTreeNode GetVisualSymbolicExpressionTreeNode(ISymbolicExpressionTreeNode symbolicExpressionTreeNode) {
194      if (visualTreeNodes.ContainsKey(symbolicExpressionTreeNode))
195        return visualTreeNodes[symbolicExpressionTreeNode];
196      return null;
197    }
198
199    public VisualSymbolicExpressionTreeNodeConnection GetVisualSymbolicExpressionTreeNodeConnection(ISymbolicExpressionTreeNode parent, ISymbolicExpressionTreeNode child) {
200      if (child.Parent != parent) throw new ArgumentException();
201      var key = Tuple.Create(parent, child);
202      VisualSymbolicExpressionTreeNodeConnection connection = null;
203      visualLines.TryGetValue(key, out connection);
204      return connection;
205    }
206
207    #region events
208    public event MouseEventHandler SymbolicExpressionTreeNodeClicked;
209    protected virtual void OnSymbolicExpressionTreeNodeClicked(object sender, MouseEventArgs e) {
210      var clicked = SymbolicExpressionTreeNodeClicked;
211      if (clicked != null)
212        clicked(sender, e);
213    }
214
215    protected virtual void SymbolicExpressionTreeChart_MouseClick(object sender, MouseEventArgs e) {
216      VisualSymbolicExpressionTreeNode visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
217      if (visualTreeNode != null) {
218        OnSymbolicExpressionTreeNodeClicked(visualTreeNode, e);
219      }
220    }
221
222    public event MouseEventHandler SymbolicExpressionTreeNodeDoubleClicked;
223    protected virtual void OnSymbolicExpressionTreeNodeDoubleClicked(object sender, MouseEventArgs e) {
224      var doubleClicked = SymbolicExpressionTreeNodeDoubleClicked;
225      if (doubleClicked != null)
226        doubleClicked(sender, e);
227    }
228
229    protected virtual void SymbolicExpressionTreeChart_MouseDoubleClick(object sender, MouseEventArgs e) {
230      VisualSymbolicExpressionTreeNode visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
231      if (visualTreeNode != null)
232        OnSymbolicExpressionTreeNodeDoubleClicked(visualTreeNode, e);
233    }
234
235    public event ItemDragEventHandler SymbolicExpressionTreeNodeDrag;
236    protected virtual void OnSymbolicExpressionTreeNodeDragDrag(object sender, ItemDragEventArgs e) {
237      var dragged = SymbolicExpressionTreeNodeDrag;
238      if (dragged != null)
239        dragged(sender, e);
240    }
241
242    private VisualSymbolicExpressionTreeNode draggedSymbolicExpressionTree;
243    private MouseButtons dragButtons;
244    private void SymbolicExpressionTreeChart_MouseDown(object sender, MouseEventArgs e) {
245      this.dragButtons = e.Button;
246      this.draggedSymbolicExpressionTree = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
247    }
248    private void SymbolicExpressionTreeChart_MouseUp(object sender, MouseEventArgs e) {
249      this.draggedSymbolicExpressionTree = null;
250      this.dragButtons = MouseButtons.None;
251    }
252
253    private void SymbolicExpressionTreeChart_MouseMove(object sender, MouseEventArgs e) {
254      VisualSymbolicExpressionTreeNode visualTreeNode = FindVisualSymbolicExpressionTreeNodeAt(e.X, e.Y);
255      if (draggedSymbolicExpressionTree != null &&
256        draggedSymbolicExpressionTree != visualTreeNode) {
257        OnSymbolicExpressionTreeNodeDragDrag(draggedSymbolicExpressionTree, new ItemDragEventArgs(dragButtons, draggedSymbolicExpressionTree));
258        draggedSymbolicExpressionTree = null;
259      } else if (draggedSymbolicExpressionTree == null &&
260        visualTreeNode != null) {
261        string tooltipText = visualTreeNode.ToolTip;
262        if (this.toolTip.GetToolTip(this) != tooltipText)
263          this.toolTip.SetToolTip(this, tooltipText);
264
265      } else if (visualTreeNode == null)
266        this.toolTip.SetToolTip(this, "");
267    }
268
269    public VisualSymbolicExpressionTreeNode FindVisualSymbolicExpressionTreeNodeAt(int x, int y) {
270      foreach (var visualTreeNode in visualTreeNodes.Values) {
271        if (x >= visualTreeNode.X && x <= visualTreeNode.X + visualTreeNode.Width &&
272            y >= visualTreeNode.Y && y <= visualTreeNode.Y + visualTreeNode.Height)
273          return visualTreeNode;
274      }
275      return null;
276    }
277    #endregion
278
279    #region methods for painting the symbolic expression tree
280    private void DrawFunctionTree(ISymbolicExpressionTree symbolicExpressionTree, Graphics graphics, int preferredWidth, int preferredHeight, int minHDistance, int minVDistance) {
281      var layoutNodes = layoutAdapter.Convert(symbolicExpressionTree).ToList();
282      layoutEngine.Reset();
283      layoutEngine.Root = layoutNodes[0];
284      foreach (var ln in layoutNodes)
285        layoutEngine.AddNode(ln.Content, ln);
286      layoutEngine.MinHorizontalSpacing = (preferredNodeWidth + minHDistance);
287      layoutEngine.MinVerticalSpacing = (preferredNodeHeight + minVDistance);
288      layoutEngine.CalculateLayout();
289      var bounds = layoutEngine.Bounds();
290      double sx = this.Width / (bounds.Width + preferredWidth);
291      if (sx > 1) sx = 1;
292      double sy = this.Height / bounds.Height;
293      if (sy > 1) sy = 1;
294      double dx = (this.Width - bounds.Width) / 2;
295      if (dx < 0) dx = 0;
296      double dy = (this.Height - bounds.Height) / 2;
297      if (dy < 0) dy = 0;
298
299      var levels = layoutNodes.GroupBy(n => n.Level, n => n);
300
301      foreach (var level in levels) {
302        var nodes = level.ToList();
303        double min = 0;
304        for (int i = 0; i < nodes.Count - 1; ++i) {
305          var w = (nodes[i + 1].X - nodes[i].X) * sx - preferredWidth;
306          if (w < min) min = w;
307        }
308        if (min > 0) min = 0;
309
310        foreach (var layoutNode in level) {
311          var visualNode = visualTreeNodes[layoutNode.Content];
312          visualNode.Width = (int)Math.Round(preferredWidth + min) - 2; // -2 to allow enough padding (1px on each side) for the node contour to be drawn
313          visualNode.Height = (int)Math.Round(preferredHeight * sy);
314          visualNode.X = (int)(Math.Round(layoutNode.X * sx + dx));
315          visualNode.Y = (int)(Math.Round(layoutNode.Y * sy + dy));
316          DrawTreeNode(graphics, visualNode);
317        }
318      }
319
320      graphics.ResetClip(); // reset clip region
321      // draw node connections
322      foreach (var visualNode in visualTreeNodes.Values) {
323        var node = visualNode.SymbolicExpressionTreeNode;
324        foreach (var subtree in node.Subtrees) {
325          var visualLine = GetVisualSymbolicExpressionTreeNodeConnection(node, subtree);
326          var visualSubtree = visualTreeNodes[subtree];
327          var origin = new Point(visualNode.X + visualNode.Width / 2, visualNode.Y + visualNode.Height);
328          var target = new Point(visualSubtree.X + visualSubtree.Width / 2, visualSubtree.Y);
329          using (var linePen = new Pen(visualLine.LineColor)) {
330            linePen.DashStyle = visualLine.DashStyle;
331            graphics.DrawLine(linePen, origin, target);
332          }
333        }
334      }
335    }
336
337    protected void DrawTreeNode(VisualSymbolicExpressionTreeNode visualTreeNode) {
338      using (var graphics = Graphics.FromImage(image)) {
339        graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
340        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
341        DrawTreeNode(graphics, visualTreeNode);
342      }
343    }
344
345    protected void DrawTreeNode(Graphics graphics, VisualSymbolicExpressionTreeNode visualTreeNode) {
346      graphics.Clip = new Region(new Rectangle(visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width + 1, visualTreeNode.Height + 1));
347      graphics.Clear(backgroundColor);
348      var node = visualTreeNode.SymbolicExpressionTreeNode;
349      using (var textBrush = new SolidBrush(visualTreeNode.TextColor))
350      using (var nodeLinePen = new Pen(visualTreeNode.LineColor))
351      using (var nodeFillBrush = new SolidBrush(visualTreeNode.FillColor)) {
352        //draw terminal node
353        if (node.SubtreeCount == 0) {
354          graphics.FillRectangle(nodeFillBrush, visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height);
355          graphics.DrawRectangle(nodeLinePen, visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height);
356        } else {
357          graphics.FillEllipse(nodeFillBrush, visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height);
358          graphics.DrawEllipse(nodeLinePen, visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height);
359        }
360        //draw name of symbol
361        var text = node.ToString();
362        graphics.DrawString(text, textFont, textBrush, new RectangleF(visualTreeNode.X, visualTreeNode.Y, visualTreeNode.Width, visualTreeNode.Height), stringFormat);
363      }
364    }
365    #endregion
366    #region save image
367    private void saveImageToolStripMenuItem_Click(object sender, EventArgs e) {
368      if (saveFileDialog.ShowDialog() == DialogResult.OK) {
369        string filename = saveFileDialog.FileName.ToLower();
370        if (filename.EndsWith("bmp")) SaveImageAsBitmap(filename);
371        else if (filename.EndsWith("emf")) SaveImageAsEmf(filename);
372        else SaveImageAsBitmap(filename);
373      }
374    }
375
376    public void SaveImageAsBitmap(string filename) {
377      if (tree == null) return;
378      Image image = new Bitmap(Width, Height);
379      using (Graphics g = Graphics.FromImage(image)) {
380        int height = this.Height / tree.Depth;
381        DrawFunctionTree(tree, g, 0, 0, Width, height);
382      }
383      image.Save(filename);
384    }
385
386    public void SaveImageAsEmf(string filename) {
387      if (tree == null) return;
388      using (var graphics = Graphics.FromImage(image)) {
389        var rectangle = new Rectangle(0, 0, image.Width, image.Height);
390        using (var metafile = new Metafile(filename, graphics.GetHdc(), rectangle, MetafileFrameUnit.Pixel, EmfType.EmfPlusDual)) {
391          graphics.ReleaseHdc();
392          using (var g = Graphics.FromImage(metafile)) {
393            DrawFunctionTree(tree, g, 0, 0, image.Width, image.Height);
394          }
395        }
396      }
397    }
398    #endregion
399    #region export pgf/tikz
400    private void exportLatexToolStripMenuItem_Click(object sender, EventArgs e) {
401      using (var dialog = new SaveFileDialog { Filter = "Tex (*.tex)|*.tex" }) {
402        if (dialog.ShowDialog() != DialogResult.OK) return;
403        string filename = dialog.FileName.ToLower();
404        var formatter = new SymbolicExpressionTreeLatexFormatter();
405        File.WriteAllText(filename, formatter.Format(Tree));
406      }
407    }
408    #endregion
409  }
410}
Note: See TracBrowser for help on using the repository browser.