private static void _shiftTree(TreeNode <AncestryTreeRendererNodeData> root, float deltaX, float deltaY) { TreeUtils.PostOrderTraverse(root, (node) => { node.Value.Bounds.X += deltaX; node.Value.Bounds.Y += deltaY; }); }
// Public members public static async Task <string> Save(Species species, AncestryTreeGenerationFlags flags) { // Generate the ancestry tree. TreeNode <AncestryTree.NodeData> ancestry_tree_root = await AncestryTree.GenerateTreeAsync(species, flags); TreeNode <AncestryTreeRendererNodeData> root = TreeUtils.CopyAs(ancestry_tree_root, x => { return(new AncestryTreeRendererNodeData { Species = x.Value.Species, IsAncestor = x.Value.IsAncestor, Bounds = new RectangleF() }); }); // Generate the evolution tree image. using (Font font = new Font("Calibri", 12)) { // Calculate the size of each node. float horizontal_padding = 5.0f; TreeUtils.PostOrderTraverse(root, (node) => { SizeF size = GraphicsUtils.MeasureString(node.Value.Species.ShortName, font); node.Value.Bounds.Width = size.Width + horizontal_padding; node.Value.Bounds.Height = size.Height; }); // Calculate node placements. _calculateNodePlacements(root); // Calculate the size of the tree. RectangleF bounds = _calculateTreeBounds(root); // Shift the tree so that the entire thing is visible. float min_x = 0.0f; TreeUtils.PostOrderTraverse(root, (node) => { if (node.Value.Bounds.X < min_x) { min_x = bounds.X; } }); _shiftTree(root, -min_x, 0.0f); // Create the bitmap. using (Bitmap bmp = new Bitmap((int)bounds.Width, (int)bounds.Height)) using (Graphics gfx = Graphics.FromImage(bmp)) { gfx.Clear(Color.FromArgb(54, 57, 63)); gfx.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; gfx.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; _drawSpeciesTreeNode(gfx, root, species, font); // Save the result. string out_dir = Global.TempDirectory + "anc"; if (!System.IO.Directory.Exists(out_dir)) { System.IO.Directory.CreateDirectory(out_dir); } string fpath = System.IO.Path.Combine(out_dir, species.ShortName + ".png"); bmp.Save(fpath); return(fpath); } } }
private static void _fixSubTreeOverlap(TreeNode <AncestryTreeRendererNodeData> node) { TreeUtils.PostOrderTraverse(node, (n) => { // Check if this node has overlapping subtrees. if (n.Children.Count() <= 1) { return; } bool has_overlapping = false; List <NodeBoundsPair <AncestryTreeRendererNodeData> > bounds = new List <NodeBoundsPair <AncestryTreeRendererNodeData> >(); foreach (TreeNode <AncestryTreeRendererNodeData> child in node.Children) { bounds.Add(new NodeBoundsPair <AncestryTreeRendererNodeData> { Node = child, Bounds = _calculateTreeBounds(child) }); } for (int i = 1; i < bounds.Count(); ++i) { if (bounds[i].Bounds.Left < bounds[i - 1].Bounds.Right) { has_overlapping = true; break; } } // If there are overlapping subtrees, move them so that they are no longer overlapping. if (has_overlapping) { int left = 0; int right = 0; if (bounds.Count() % 2 == 1) { int middle = (int)Math.Ceiling(bounds.Count() / 2.0f); left = middle - 1; right = middle + 1; } else { left = (bounds.Count() / 2) - 1; right = bounds.Count() / 2; } for (int i = left; i >= 0; --i) { float overlap = bounds[i].Bounds.Right - bounds[i + 1].Bounds.Left; if (overlap <= 0.0f) { continue; } if (i == left) { overlap /= 2.0f; } bounds[i].Bounds.X -= overlap; _shiftTree(bounds[i].Node, -overlap, 0.0f); } for (int i = right; i < bounds.Count(); ++i) { float overlap = bounds[i - 1].Bounds.Right - bounds[i].Bounds.Left; if (overlap <= 0.0f) { continue; } if (i == right) { overlap /= 2.0f; } bounds[i].Bounds.X += overlap; _shiftTree(bounds[i].Node, overlap, 0.0f); } } // Finally, center the parent node over its children. float child_min_x = node.Children.First().Value.Bounds.X; float child_max_x = node.Children.Last().Value.Bounds.X + node.Children.Last().Value.Bounds.Width; float child_width = (child_max_x - child_min_x); node.Value.Bounds.X = child_min_x + (child_width / 2.0f) - (node.Value.Bounds.Width / 2.0f); }); }
// Private members private string _treeToString() { // If no tree has been provided, there is nothing to render. if (Tree is null) { return(string.Empty); } // Generate timestamp strings for each entry ahead of time, so we can make sure that they're all the same length. int maxTimestampLength = 0; TreeUtils.PreOrderTraverse(Tree, x => { int length = _timestampToString(x.Value.Species.Timestamp).Length; if (length > maxTimestampLength) { maxTimestampLength = length; } }); // Render the tree. List <string> lines = new List <string>(); Stack <Tuple <int, int> > sibling_line_positions = new Stack <Tuple <int, int> >(); TreeUtils.PreOrderTraverse(Tree, x => { string line = ""; line += _timestampToString(x.Value.Species.Timestamp).PadRight(maxTimestampLength); line += " " + (x.Value.Species.IsExtinct ? "*" : "-"); if (DrawLines && x.Parent != null) { for (int i = 0; i < x.Depth * 2 - 1; ++i) { if (sibling_line_positions.Count() > 0 && sibling_line_positions.Any(y => y.Item2 == line.Length + 1)) { line += "│"; } else { line += " "; } } } else { line += " "; } if (x.Parent != null) { // If this node has a parent, draw a branch leading down it. if (x.Parent.Children.Count() > 1 && x.Parent.Children.Last() != x) { if (DrawLines) { line += "├─"; } // If this is the first sibling, take note of the index to draw connecting lines. if (sibling_line_positions.Count() == 0 || sibling_line_positions.First().Item1 != x.Depth) { sibling_line_positions.Push(new Tuple <int, int>(x.Depth, line.Length - 1)); } } else if (x.Parent.Children.Last() == x) { if (DrawLines) { line += "└─"; } // If this is the last sibling, remove the stored index. if (sibling_line_positions.Count() > 0 && sibling_line_positions.First().Item1 == x.Depth) { sibling_line_positions.Pop(); } } } line += x.Value.Species.ShortName; lines.Add(line); }); StringBuilder sb = new StringBuilder(); foreach (string line in lines) { if (sb.Length + line.Length > MaxLength) { sb.AppendLine("..."); break; } else { sb.AppendLine(line); } } return(sb.ToString()); }