Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.Encodings.SymbolicExpressionTreeEncoding.Views/3.4/SymbolicExpressionTreeChart.cs @ 17625

Last change on this file since 17625 was 17181, checked in by swagner, 5 years ago

#2875: Merged r17180 from trunk to stable

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