/* * Copyright 2007 Einar Egilsson ( http://tech.einaregilsson.com ) * For details about this project see http://tech.einaregilsson.com/2007/09/20/binary-tree-image */ using System; using System.Drawing; using System.Drawing.Imaging; using System.Drawing.Text; using System.IO; namespace EinarEgilsson { public interface INode { INode Left { get;} INode Right { get; } string Text { get;} object Value { get; } } /// /// Class for creating images of binary tree structures. Nodes in the binary tree /// must implement the INode interface. See http://tech.einaregilsson.com/2007/09/20/binary-tree-image /// for more details. /// public class BinaryTreeImage { #region Constants protected const int NodeWidth = 35; protected const int HalfNodeWidth = NodeWidth / 2; protected const int DX = 10; protected const int DY = 10; protected const int FontSize = 10; protected const string FontName = "Arial"; protected const int Padding = 20; protected readonly Brush Background = Brushes.White; protected readonly Pen MainPen = Pens.Black; protected readonly Brush ValueBrush = Brushes.Blue; protected readonly Brush TextBrush = Brushes.Black; protected readonly Font TextFont = new Font(FontName, FontSize); protected readonly Font ValueFont = new Font(FontName, FontSize, FontStyle.Bold); #endregion #region Members private int _treeDepth = 0; private Bitmap _bitmap; private Graphics _graphics; #endregion #region Public public BinaryTreeImage(INode root) { CreateImage(root); } public virtual Bitmap Bitmap { get { return _bitmap; } } public virtual void Save(string filename, ImageFormat format) { _bitmap.Save(filename, format); } public virtual void Save(string filename) { Save(filename, ImageFormat.Jpeg); } public virtual void WriteToStream(Stream outputStream, ImageFormat format) { _bitmap.Save(outputStream, format); } public virtual void WriteToStream(Stream outputStream) { WriteToStream(outputStream, ImageFormat.Jpeg); } public virtual void Show() { string filename = Path.GetTempFileName() + ".jpg"; Save(filename); System.Diagnostics.Process.Start(filename); } #endregion #region Image Creation protected virtual void CreateImage(INode root) { int canvasWidth, canvasHeight; ImageNode iroot = CreateImageNodeTree(root, 1); if (iroot == null) canvasHeight = canvasWidth = 30; else { canvasWidth = iroot.TotalWidth + Padding * 2; canvasHeight = _treeDepth * (NodeWidth + DY) + 1 * NodeWidth; } _bitmap = new Bitmap(canvasWidth, canvasHeight); _graphics = Graphics.FromImage(_bitmap); _graphics.FillRectangle(Background, 0, 0, _bitmap.Width, _bitmap.Height); DrawNode(iroot, true, 1, new Rectangle(0 - HalfNodeWidth, 0, NodeWidth, NodeWidth)); } protected virtual ImageNode CreateImageNodeTree(INode node, int depth) { if (node == null) return null; _treeDepth = Math.Max(_treeDepth, depth); ImageNode left = CreateImageNodeTree(node.Left, depth + 1); ImageNode right = CreateImageNodeTree(node.Right, depth + 1); ImageNode newNode = new ImageNode(); newNode.Node = node; newNode.Left = left; newNode.Right = right; newNode.RightTreeWidth = (right == null) ? 0 : right.TotalWidth; newNode.LeftTreeWidth = (left == null) ? 0 : left.TotalWidth; newNode.TotalWidth = newNode.RightTreeWidth + newNode.LeftTreeWidth + NodeWidth + DX; return newNode; } #endregion #region Draw Nodes protected virtual Rectangle DrawNode(ImageNode node, bool isRightChild, int depth, Rectangle parentBounds) { int x, y; if (node == null) return new Rectangle() ; int offset = (isRightChild) ? node.LeftTreeWidth + NodeWidth + DX : -node.RightTreeWidth - NodeWidth - DX; x = parentBounds.X + offset; y = depth * (DY + NodeWidth) - HalfNodeWidth; Rectangle bounds = new Rectangle(x, y, NodeWidth, NodeWidth); _graphics.DrawEllipse(MainPen, bounds); DrawNodeText(node, bounds); if (node.Left != null) { Rectangle leftBounds = DrawNode(node.Left, false, depth + 1, bounds); _graphics.DrawLine(MainPen, leftBounds.X + HalfNodeWidth, leftBounds.Y, bounds.X + HalfNodeWidth, bounds.Y + NodeWidth); } if (node.Right != null) { Rectangle rightBounds = DrawNode(node.Right, true, depth + 1, bounds); _graphics.DrawLine(MainPen, rightBounds.X + HalfNodeWidth, rightBounds.Y, bounds.X + HalfNodeWidth, bounds.Y + NodeWidth); } return bounds; } protected virtual void DrawNodeText(ImageNode node, Rectangle nodeBounds) { StringFormat strFormat = new StringFormat(); strFormat.Alignment = StringAlignment.Center; RectangleF typeBounds = new RectangleF(nodeBounds.X - 50, nodeBounds.Y + HalfNodeWidth - TextFont.GetHeight(), 100 + NodeWidth, TextFont.GetHeight()); _graphics.DrawString(node.Node.Text, TextFont, TextBrush, typeBounds, strFormat); if (node.Node.Value != null) { RectangleF valueBounds = new RectangleF(nodeBounds.X - 50, nodeBounds.Y + NodeWidth - ValueFont.GetHeight(), 100 + NodeWidth, ValueFont.GetHeight()); _graphics.DrawString(node.Node.Value.ToString(), ValueFont, ValueBrush, valueBounds, strFormat); } } #endregion #region Class VisualNode protected class ImageNode { public INode Node; public ImageNode Right; public ImageNode Left; public int LeftTreeWidth; public int RightTreeWidth; public int TotalWidth; } #endregion } }