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

Last change on this file since 10499 was 10499, checked in by gkronber, 8 years ago

#2092: don't show the root node if it has only one sub-tree (the result producing branch). If ADFs are defined then it is necessary to show the root node as well.

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