[9963] | 1 | using System;
|
---|
| 2 | using System.Collections.Generic;
|
---|
| 3 | using System.Drawing;
|
---|
| 4 | using System.Linq;
|
---|
| 5 | using HeuristicLab.Encodings.SymbolicExpressionTreeEncoding;
|
---|
| 6 | using HeuristicLab.EvolutionaryTracking.Views.Primitives;
|
---|
| 7 | using HeuristicLab.Visualization;
|
---|
| 8 |
|
---|
| 9 | namespace HeuristicLab.EvolutionaryTracking.Views {
|
---|
| 10 | public class ExtendedSymbolicExpressionTreeCanvas : ChartControl {
|
---|
| 11 | private const int HSize = 20;
|
---|
| 12 | private const int VSize = 15;
|
---|
| 13 | private const int HDistance = 5;
|
---|
| 14 | private const int VDistance = 5;
|
---|
| 15 | private const int MinGroupHorizontalSpacing = 200;
|
---|
| 16 | private const int MinGroupVerticalSpacing = 200;
|
---|
| 17 | private const int HorizontalGroupDistance = 20;
|
---|
| 18 | private const int VerticalGroupDistance = 20;
|
---|
| 19 |
|
---|
| 20 | public ReingoldTilfordLayoutEngine<ISymbolicExpressionTreeNode> TreeLayoutEngine { get; set; }
|
---|
| 21 | public ReingoldTilfordLayoutEngine<ISymbolicExpressionTree> GroupsLayoutEngine { get; set; }
|
---|
| 22 |
|
---|
| 23 | private SymbolicExpressionTreeLayoutAdapter layoutAdapter;
|
---|
| 24 |
|
---|
| 25 | private Dictionary<ISymbolicExpressionTreeNode, IPrimitive> nodesToPrimitives;
|
---|
| 26 | private Dictionary<ISymbolicExpressionTree, IPrimitive> treesToGroups;
|
---|
| 27 |
|
---|
| 28 | private Dictionary<Tuple<IPrimitive, IPrimitive>, Line> primitiveConnections;
|
---|
| 29 | private Dictionary<Tuple<IPrimitive, IPrimitive>, Line> groupsConnections;
|
---|
| 30 |
|
---|
| 31 | private ISymbolicExpressionTreeNode currentSelected;
|
---|
| 32 | private ISymbolicExpressionTreeNode SelectedSubtree {
|
---|
| 33 | get { return currentSelected; }
|
---|
| 34 | set {
|
---|
| 35 | if (Chart == null)
|
---|
| 36 | Chart = new Chart(0, 0, PreferredSize.Width, PreferredSize.Height);
|
---|
| 37 |
|
---|
| 38 | if (value == currentSelected) return;
|
---|
| 39 | var previousSelected = currentSelected;
|
---|
| 40 | currentSelected = value;
|
---|
| 41 |
|
---|
| 42 | DisableUpdate();
|
---|
| 43 | if (previousSelected != null) HighlightSubtree(previousSelected, Color.Black); // un-highlight the previously selected subtree
|
---|
| 44 | if (currentSelected != null) HighlightSubtree(currentSelected, Color.DeepSkyBlue); // highlight the new selected subtree
|
---|
| 45 | EnableUpdate();
|
---|
| 46 | EnforceUpdate();
|
---|
| 47 | }
|
---|
| 48 | }
|
---|
| 49 |
|
---|
| 50 | public ISymbolicExpressionTreeGenealogyGraph GenealogyGraph { get; set; }
|
---|
| 51 |
|
---|
| 52 | private void ClearDictionaries() {
|
---|
| 53 | if (nodesToPrimitives == null) nodesToPrimitives = new Dictionary<ISymbolicExpressionTreeNode, IPrimitive>();
|
---|
| 54 | nodesToPrimitives.Clear();
|
---|
| 55 |
|
---|
| 56 | if (treesToGroups == null) treesToGroups = new Dictionary<ISymbolicExpressionTree, IPrimitive>();
|
---|
| 57 | treesToGroups.Clear();
|
---|
| 58 |
|
---|
| 59 | if (groupsConnections == null) groupsConnections = new Dictionary<Tuple<IPrimitive, IPrimitive>, Line>();
|
---|
| 60 | groupsConnections.Clear();
|
---|
| 61 |
|
---|
| 62 | if (primitiveConnections == null) primitiveConnections = new Dictionary<Tuple<IPrimitive, IPrimitive>, Line>();
|
---|
| 63 | primitiveConnections.Clear();
|
---|
| 64 | }
|
---|
| 65 |
|
---|
| 66 | private ISymbolicExpressionTree root;
|
---|
| 67 | public ISymbolicExpressionTree Root {
|
---|
| 68 | get {
|
---|
| 69 | return root;
|
---|
| 70 | }
|
---|
| 71 | set {
|
---|
| 72 | if (value == null)
|
---|
| 73 | throw new ArgumentNullException();
|
---|
| 74 |
|
---|
| 75 | if (root == value) return;
|
---|
| 76 |
|
---|
| 77 | ClearDictionaries();
|
---|
| 78 | TreeLayoutEngine.Reset();
|
---|
| 79 | GroupsLayoutEngine.Reset();
|
---|
| 80 |
|
---|
| 81 | root = value;
|
---|
| 82 | currentSelected = null;
|
---|
| 83 |
|
---|
| 84 | if (Chart == null)
|
---|
| 85 | Chart = new Chart(0, 0, PreferredSize.Width, PreferredSize.Height);
|
---|
| 86 |
|
---|
| 87 | DisableUpdate();
|
---|
| 88 | Chart.Group.Clear();
|
---|
| 89 | var group = LayoutTree(root);
|
---|
| 90 | group.Ancestor = group;
|
---|
| 91 | group.Content = root;
|
---|
| 92 | Chart.Group.Add(group);
|
---|
| 93 | GroupsLayoutEngine.Root = group;
|
---|
| 94 | GroupsLayoutEngine.AddNode(group.Content, group);
|
---|
| 95 | treesToGroups.Add(group.Content, group);
|
---|
| 96 |
|
---|
| 97 | // highlight the received fragment
|
---|
| 98 | var graphNode = GenealogyGraph.GetGraphNodes(root).First();
|
---|
| 99 | if (graphNode.InEdges != null) {
|
---|
| 100 | var arc = graphNode.InEdges.Last(x => x.Source != x.Target);
|
---|
| 101 | var fragment = (IFragment)arc.Data;
|
---|
| 102 | if (fragment != null && fragment.Root != null) {
|
---|
| 103 | HighlightSubtree(fragment.Root, Color.Orange);
|
---|
| 104 | }
|
---|
| 105 | }
|
---|
| 106 |
|
---|
| 107 | EnableUpdate();
|
---|
| 108 | EnforceUpdate();
|
---|
| 109 | }
|
---|
| 110 | }
|
---|
| 111 |
|
---|
| 112 | public ExtendedSymbolicExpressionTreeCanvas() {
|
---|
| 113 | TreeLayoutEngine = new ReingoldTilfordLayoutEngine<ISymbolicExpressionTreeNode> { MinHorizontalSpacing = HDistance + HSize, MinVerticalSpacing = VDistance + VSize };
|
---|
| 114 | layoutAdapter = new SymbolicExpressionTreeLayoutAdapter();
|
---|
| 115 | GroupsLayoutEngine = new ReingoldTilfordLayoutEngine<ISymbolicExpressionTree> { MinHorizontalSpacing = MinGroupHorizontalSpacing, MinVerticalSpacing = MinGroupVerticalSpacing };
|
---|
| 116 | if (Chart == null)
|
---|
| 117 | Chart = new Chart(0, 0, PreferredSize.Width, PreferredSize.Height);
|
---|
| 118 | nodesToPrimitives = new Dictionary<ISymbolicExpressionTreeNode, IPrimitive>();
|
---|
| 119 | treesToGroups = new Dictionary<ISymbolicExpressionTree, IPrimitive>();
|
---|
| 120 | groupsConnections = new Dictionary<Tuple<IPrimitive, IPrimitive>, Line>();
|
---|
| 121 | primitiveConnections = new Dictionary<Tuple<IPrimitive, IPrimitive>, Line>();
|
---|
| 122 | }
|
---|
| 123 |
|
---|
| 124 | /// <summary>
|
---|
| 125 | /// Draws a symbolic expression tree using HeuristicLab.Visualization primitives and the ReingoldTilford layout algorithm.
|
---|
| 126 | /// </summary>
|
---|
| 127 | /// <param name="tree">The symbolic expression tree</param>
|
---|
| 128 | /// <returns>An IChart containing a group of primitives representing the tree.</returns>
|
---|
| 129 | private PrimitiveGroup LayoutTree(ISymbolicExpressionTree tree) {
|
---|
| 130 | var group = LayoutTree(tree.Root.GetSubtree(0).GetSubtree(0));
|
---|
| 131 | group.Content = tree;
|
---|
| 132 | return group;
|
---|
| 133 | }
|
---|
| 134 |
|
---|
| 135 | // this function is used by the SymbolicExpressionTreeLayoutAdapter
|
---|
| 136 | private ILayoutNode<ISymbolicExpressionTreeNode> ConvertFunction(ISymbolicExpressionTreeNode node) {
|
---|
| 137 | ILayoutNode<ISymbolicExpressionTreeNode> layoutNode;
|
---|
| 138 | if (node.SubtreeCount == 0) {
|
---|
| 139 | layoutNode = new LabeledRectangle(Chart, 0, 0, HSize, VSize) { Text = node.ToString(), Font = new Font(FontFamily.GenericSansSerif, 12) };
|
---|
| 140 | } else {
|
---|
| 141 | layoutNode = new LabeledEllipse(Chart, 0, 0, HSize, VSize) { Text = node.ToString(), Font = new Font(FontFamily.GenericSansSerif, 12) };
|
---|
| 142 | }
|
---|
| 143 | // layoutNode.Children are not initialized here, but when the adapter expands the tree
|
---|
| 144 | layoutNode.Ancestor = layoutNode;
|
---|
| 145 | layoutNode.Content = node;
|
---|
| 146 | return layoutNode;
|
---|
| 147 | }
|
---|
| 148 |
|
---|
| 149 | private PrimitiveGroup LayoutTree(ISymbolicExpressionTreeNode treeRoot) {
|
---|
| 150 | if (treeRoot == null) throw new ArgumentNullException();
|
---|
| 151 |
|
---|
| 152 | var layoutNodes = layoutAdapter.Convert(treeRoot, ConvertFunction).ToList(); // returns a list of layoutNodes in breadth order (relative to the tree traversal)
|
---|
| 153 | TreeLayoutEngine.Reset();
|
---|
| 154 | var group = new PrimitiveGroup(Chart);
|
---|
| 155 | // add primitives (rectangles and ellipses corresponding to terminal/function nodes)
|
---|
| 156 | foreach (var layoutNode in layoutNodes) {
|
---|
| 157 | var brush = new SolidBrush(Color.Transparent);
|
---|
| 158 | var pen = new Pen(Color.Black);
|
---|
| 159 | var fontBrush = new SolidBrush(pen.Color);
|
---|
| 160 | var treeNode = layoutNode.Content;
|
---|
| 161 | layoutNode.Number = layoutNode.Parent == null ? 0 : layoutNode.Parent.Content.IndexOfSubtree(layoutNode.Content);
|
---|
| 162 | var primitive = (IPrimitive)layoutNode;
|
---|
| 163 | primitive.Brush = brush;
|
---|
| 164 | primitive.Pen = pen;
|
---|
| 165 | if (treeNode.SubtreeCount == 0) {
|
---|
| 166 | var rectangle = (LabeledRectangle)primitive;
|
---|
| 167 | rectangle.FontBrush = fontBrush;
|
---|
| 168 | } else {
|
---|
| 169 | var ellipse = (LabeledEllipse)primitive;
|
---|
| 170 | ellipse.FontBrush = fontBrush;
|
---|
| 171 | }
|
---|
| 172 | TreeLayoutEngine.AddNode(treeNode, layoutNode);
|
---|
| 173 | group.Add(primitive);
|
---|
| 174 | nodesToPrimitives.Add(treeNode, primitive);
|
---|
| 175 | }
|
---|
| 176 | TreeLayoutEngine.Root = (ILayoutNode<ISymbolicExpressionTreeNode>)nodesToPrimitives[treeRoot];
|
---|
| 177 | TreeLayoutEngine.CalculateLayout(); // calculate X,Y coordinates for each layout node
|
---|
| 178 |
|
---|
| 179 | foreach (var layoutNode in layoutNodes) {
|
---|
| 180 | var primitive = (RectangularPrimitiveBase)layoutNode;
|
---|
| 181 | var lowerLeft = new PointD(layoutNode.X, layoutNode.Y);
|
---|
| 182 | primitive.SetPosition(lowerLeft, lowerLeft + new Offset(primitive.Size));
|
---|
| 183 | }
|
---|
| 184 | // connect nodes with lines
|
---|
| 185 | foreach (var layoutNode in layoutNodes) {
|
---|
| 186 | var primitive = (RectangularPrimitiveBase)layoutNode;
|
---|
| 187 | var pen = new Pen(Color.Black);
|
---|
| 188 | var node = layoutNode.Content;
|
---|
| 189 | if (node.SubtreeCount == 0) continue;
|
---|
| 190 | foreach (var subtree in node.Subtrees) {
|
---|
| 191 | var subtreePrimitive = (RectangularPrimitiveBase)nodesToPrimitives[subtree];
|
---|
| 192 | var start = new PointD(primitive.UpperRight.X - primitive.Size.Width / 2f, primitive.UpperRight.Y);
|
---|
| 193 | var end = new PointD(subtreePrimitive.LowerLeft.X + subtreePrimitive.Size.Width / 2f, subtreePrimitive.LowerLeft.Y);
|
---|
| 194 | var line = new Line(Chart, start, end, pen);
|
---|
| 195 | group.Add(line);
|
---|
| 196 | primitiveConnections.Add(new Tuple<IPrimitive, IPrimitive>(primitive, subtreePrimitive), line);
|
---|
| 197 | }
|
---|
| 198 | }
|
---|
| 199 | // once the coordonates for each primitive are updated according to the calculated layout, we update the bounds of the group (LowerLeft and UpperRight)
|
---|
| 200 | group.UpdateBounds();
|
---|
| 201 | return group;
|
---|
| 202 | }
|
---|
| 203 | /// <summary>
|
---|
| 204 | /// Handler for the user click event inside the ExtendedSymbolicExpressionTreeCanvas. When the user clicks, the following things should happen:
|
---|
| 205 | /// 1) The group of primitives containing the clicked screen point is identified
|
---|
| 206 | /// 2) The corresponding symbolic expression tree is retrieved from the groupsToTreesDictionary
|
---|
| 207 | /// 3) The corresponding genealogy graph node is retrieved from the GenealogyGraph
|
---|
| 208 | /// 4) The parents and the fragment are retrieved from the graph
|
---|
| 209 | /// 5) Both parents are laid out using the symbolic expression tree layout engine and the resulting groups of primitives are added to the canvas
|
---|
| 210 | /// 6) The fragment is highlighted in the root parent and in the other parent
|
---|
| 211 | /// </summary>
|
---|
| 212 | /// <param name="sender"></param>
|
---|
| 213 | /// <param name="e"></param>
|
---|
| 214 | protected override void pictureBox_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) {
|
---|
| 215 | // STEP 1: Get the primitives and the group
|
---|
| 216 | var primitives = Chart.GetAllPrimitives(e.Location).Where(p => p is PrimitiveGroup).ToList();
|
---|
| 217 | if (primitives.Count == 0) return;
|
---|
| 218 | if (primitives.Count > 1) throw new Exception("Groups/trees should not overlap."); // in the extended canvas, the PrimitiveGroups (each representing a symbolic expression tree) should not overlap
|
---|
| 219 | var selectedGroup = (PrimitiveGroup)primitives[0];
|
---|
| 220 | var selectedPrimitive = selectedGroup.GetAllPrimitives(Chart.TransformPixelToWorld(e.Location)).First(p => p is ILayoutNode<ISymbolicExpressionTreeNode>); // similarly with the above condition, there should only be one primitive in that location
|
---|
| 221 | // STEP 2: Get the selected tree and the selected subtree
|
---|
| 222 | SelectedSubtree = ((ILayoutNode<ISymbolicExpressionTreeNode>)selectedPrimitive).Content;
|
---|
| 223 | var selectedTree = ((ILayoutNode<ISymbolicExpressionTree>)selectedGroup).Content;
|
---|
| 224 | // STEP 3: Get the genealogy graph node, the parents and the fragment
|
---|
| 225 | var graphNode = GenealogyGraph.GetGraphNodes(selectedTree).First(); // get the GenealogyGraphNode corresponding to the tree
|
---|
| 226 | var parentVertices = graphNode.InEdges.Select(edge => (SymbolicExpressionTreeGenealogyGraphNode)edge.Source).ToList();
|
---|
| 227 |
|
---|
| 228 | DisableUpdate();
|
---|
| 229 |
|
---|
| 230 | foreach (var edge in graphNode.InEdges) {
|
---|
| 231 | var parentVertex = (SymbolicExpressionTreeGenealogyGraphNode)edge.Source;
|
---|
| 232 | var parent = parentVertex.SymbolicExpressionTree;
|
---|
| 233 | if (treesToGroups.ContainsKey(parent)) continue;
|
---|
| 234 | var childGroup = LayoutTree(parent); // from the layout perspective, roles are reversed and genealogical parents become layout children
|
---|
| 235 | treesToGroups.Add(parent, childGroup);
|
---|
| 236 | Chart.Group.Add(childGroup);
|
---|
| 237 | if (selectedGroup.Children == null) selectedGroup.Children = new List<ILayoutNode<ISymbolicExpressionTree>>();
|
---|
| 238 | selectedGroup.Children.Add(childGroup);
|
---|
| 239 | childGroup.Parent = selectedGroup;
|
---|
| 240 | childGroup.Level = childGroup.Parent.Level + 1;
|
---|
| 241 | childGroup.Number = childGroup.Parent.Children.Count - 1;
|
---|
| 242 | childGroup.Ancestor = childGroup;
|
---|
| 243 | childGroup.Content = parent;
|
---|
| 244 | GroupsLayoutEngine.AddNode(childGroup.Content, childGroup);
|
---|
| 245 | }
|
---|
| 246 | var parents = parentVertices.Select(x => x.SymbolicExpressionTree).ToList();
|
---|
| 247 | // highlight the fragment
|
---|
| 248 | if (graphNode.InEdges.Count == 2) {
|
---|
| 249 | // highlight the fragment
|
---|
| 250 | var fragment = (IFragment)graphNode.InEdges.Last().Data;
|
---|
| 251 | if (!(fragment == null || fragment.Root == null)) {
|
---|
| 252 |
|
---|
| 253 | var subtree0 = parents[0].IterateNodesPrefix().ElementAt(fragment.Index);
|
---|
| 254 | HighlightSubtree(subtree0, Color.Red);
|
---|
| 255 |
|
---|
| 256 | var subtree1 = parents[1].IterateNodesPrefix().ElementAt(fragment.OldIndex);
|
---|
| 257 | HighlightSubtree(subtree1, Color.LimeGreen);
|
---|
| 258 | }
|
---|
| 259 | }
|
---|
| 260 | // layout the groups
|
---|
| 261 | var groups = Chart.Group.Primitives.Where(p => p is PrimitiveGroup).Cast<PrimitiveGroup>().ToList();
|
---|
| 262 | var maxSize = new SizeF(groups.Max(g => g.Size.Width), groups.Max(g => g.Size.Height));
|
---|
| 263 | GroupsLayoutEngine.MinHorizontalSpacing = maxSize.Width + HorizontalGroupDistance;
|
---|
| 264 | GroupsLayoutEngine.MinVerticalSpacing = maxSize.Height + VerticalGroupDistance;
|
---|
| 265 |
|
---|
| 266 | GroupsLayoutEngine.CalculateLayout();
|
---|
| 267 | foreach (var group in Chart.Group.Primitives.Where(p => p is PrimitiveGroup).Cast<PrimitiveGroup>()) {
|
---|
| 268 | group.Move(new PointD(group.X, group.Y) - group.LowerLeft);
|
---|
| 269 | }
|
---|
| 270 | // draw or update (start,end) coordinates of group connections
|
---|
| 271 | foreach (var group in Chart.Group.Primitives.Where(p => p is PrimitiveGroup).Cast<PrimitiveGroup>()) {
|
---|
| 272 | if (group.Children == null) continue;
|
---|
| 273 | var start = new PointD(group.UpperRight.X - group.Size.Width / 2, group.UpperRight.Y);
|
---|
| 274 | foreach (var childGroup in group.Children.Cast<PrimitiveGroup>()) {
|
---|
| 275 | var tuple = new Tuple<IPrimitive, IPrimitive>(group, childGroup);
|
---|
| 276 | var childRoot = (RectangularPrimitiveBase)nodesToPrimitives[childGroup.Content.Root.GetSubtree(0).GetSubtree(0)];
|
---|
| 277 | var end = new PointD(childRoot.LowerLeft.X + childRoot.Size.Width / 2, childRoot.LowerLeft.Y);
|
---|
| 278 | if (groupsConnections.ContainsKey(tuple)) {
|
---|
| 279 | var line = groupsConnections[tuple];
|
---|
| 280 | line.SetPosition(start, end);
|
---|
| 281 | } else {
|
---|
| 282 | var line = new Line(Chart, start, end) { Pen = new Pen(Color.Black) };
|
---|
| 283 | Chart.Group.Add(line);
|
---|
| 284 | groupsConnections.Add(tuple, line);
|
---|
| 285 | }
|
---|
| 286 | }
|
---|
| 287 | }
|
---|
| 288 |
|
---|
| 289 | EnableUpdate();
|
---|
| 290 | EnforceUpdate();
|
---|
| 291 |
|
---|
| 292 | base.pictureBox_MouseUp(sender, e);
|
---|
| 293 | }
|
---|
| 294 | // highlight a given subtree with the specified color
|
---|
| 295 | public void HighlightSubtree(ISymbolicExpressionTreeNode subtree, Color color) {
|
---|
| 296 | foreach (var node in subtree.IterateNodesBreadth()) {
|
---|
| 297 | var primitive = nodesToPrimitives[node];
|
---|
| 298 | primitive.Pen.Brush = new SolidBrush(color);
|
---|
| 299 | }
|
---|
| 300 | }
|
---|
| 301 | // the (0,0) point of the HeuristicLab.Visualization.Chart is at the bottom left corner of the screen, while the (0,0) point of the layout engine
|
---|
| 302 | // and of the actual user control is at the top left corner. so we have to vertically flip everything to maintain correct orientation.
|
---|
| 303 | private void FlipVertical() {
|
---|
| 304 | foreach (var primitive in Chart.Group.Primitives) {
|
---|
| 305 | var group = primitive as PrimitiveGroup;
|
---|
| 306 | if (group != null) {
|
---|
| 307 | foreach (var groupPrimitive in group.Primitives) {
|
---|
| 308 | var rp = groupPrimitive as RectangularPrimitiveBase;
|
---|
| 309 | if (rp != null) {
|
---|
| 310 | rp.SetPosition(rp.LowerLeft.X, Height - rp.UpperRight.Y, rp.UpperRight.X, Height - rp.LowerLeft.Y);
|
---|
| 311 | } else {
|
---|
| 312 | var lp = groupPrimitive as LinearPrimitiveBase;
|
---|
| 313 | if (lp != null) {
|
---|
| 314 | lp.SetPosition(lp.Start.X, Height - lp.Start.Y, lp.End.X, Height - lp.End.Y);
|
---|
| 315 | }
|
---|
| 316 | }
|
---|
| 317 | }
|
---|
| 318 | } else {
|
---|
| 319 | var lp = primitive as LinearPrimitiveBase;
|
---|
| 320 | if (lp != null) {
|
---|
| 321 | lp.SetPosition(lp.Start.X, Height - lp.Start.Y, lp.End.X, Height - lp.End.Y);
|
---|
| 322 | }
|
---|
| 323 | }
|
---|
| 324 | }
|
---|
| 325 | }
|
---|
| 326 | // some convenience methods for the lazy (enabling, disabling updates for the Chart)
|
---|
| 327 | private void EnableUpdate() {
|
---|
| 328 | Chart.UpdateEnabled = true;
|
---|
| 329 | }
|
---|
| 330 | private void DisableUpdate() {
|
---|
| 331 | Chart.UpdateEnabled = false;
|
---|
| 332 | }
|
---|
| 333 | private void EnforceUpdate() {
|
---|
| 334 | DisableUpdate();
|
---|
| 335 | FlipVertical();
|
---|
| 336 | EnableUpdate();
|
---|
| 337 | Chart.EnforceUpdate();
|
---|
| 338 | }
|
---|
| 339 | }
|
---|
| 340 | }
|
---|