Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 15029 was 15029, checked in by bburlacu, 7 years ago

#2794: Improve centering and add padding. The padding is handled by the SymbolicExpressionTreeChart which asks the layout engine to layout the tree in the remaining area (minus the padding).

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