/*
* 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; }
}
/// <summary>
/// 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.
/// </summary>
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
}
}