呃,怎么說呢,這個和vczh的同名文章是互為補充的。這是最近老板的要求,所以就寫了這么個東西。
vczh的方法生成的樹是sparse的,而我這里樹則要緊湊一些,所使用的坐標系也與之不同。
效果(看起來挺菜,哇咔咔)

布局分為水平布局和豎直布局兩個部分,我這里先進行了水平布局再進行了豎直布局。
一般來講緊湊的樹主要指同級節點方向上的緊湊性。由于這里樹是父節點在上,子結點在下,因此水平方向上的節點要盡量緊湊。那么便需要我將節點盡量往左布局。
如果自左往右布置節點,那么很顯然,左邊的節點的位置一旦固定下來就不需要再行調整。
同時,為了保持樹的美觀,父節點的水平中心應當是子結點的水平中心,這樣子結點就會對稱分布在父節點的下方,有利于美觀。
然而父節點能正常布局的位置,子結點可能無法正常布局(子結點的寬度比父節點寬得多)。因此我們還需要調整子結點的布局。
由于是自左向右布局的,此時子結點的左邊的節點都已經確定了,正確的布局很容易便可以通過右移得到。
為了保證正確性,還需要把父節點也右移。因為父節點的右邊沒有布局限制,因而可以放心的右移。這樣一直傳遞到根節點就可以了。
那么很明顯,整體來說,布局順序就是,自下而上,自左而右。
為了讓節點在布局的時候知道自己能被安排的最左位置,需要為每一層保存最左可布局位置的坐標。
一旦有節點被安排妥當,便更新節點所在層次的最左可布局位置。后來的節點只要在這個位置的右方布局,就不會與已布置的節點沖突。
豎直布局并不復雜,算出每一層的Top就可以了。
代碼如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
//布局算法:
//采用平行節點最左布局,父子節點對中布局的原則。
/****
* void Layout(int lyr)
* {
*
* }
*
*****/
namespace CrowdTree
{
class LayoutTreeLayerInfo
{
//可安排布局的最左坐標
private Dictionary<int, float> lyrLefts = new Dictionary<int,float>();
private Dictionary<int, float> heights = new Dictionary<int,float>();
public void Reset()
{
lyrLefts = new Dictionary<int, float>();
heights = new Dictionary<int, float>();
}
public float GetLayoutableLeft(int level)
{
if (lyrLefts.ContainsKey(level))
{
return lyrLefts[level];
}
return 0;
}
public void SetLayoutableLeft(int level, float left)
{
if (lyrLefts.ContainsKey(level))
{
lyrLefts[level] = Math.Max(left, lyrLefts[level]);
}
else
{
lyrLefts[level] = Math.Max(0, left);
}
}
public void SetLayoutLevelMinHeight(int level, float height)
{
if (heights.ContainsKey(level))
{
heights[level] = Math.Max(heights[level], height);
}
else
{
heights[level] = height;
}
}
public float GetLayoutLevelMinHeight(int level)
{
if (heights.ContainsKey(level))
{
return heights[level];
}
return 0;
}
}
class LayoutTreeNode
{
protected LayoutTree owner_;
protected LayoutTreeLayerInfo lyrInfo_;
protected int lyrLevel_;
protected RectangleF rect_;
protected List<LayoutTreeNode> children_ = new List<LayoutTreeNode>();
public int Level
{
get { return lyrLevel_; }
set { lyrLevel_ = value; }
}
public LayoutTree Owner
{
get { return owner_; }
set { owner_ = value; }
}
public LayoutTreeLayerInfo LayerInfo
{
set { lyrInfo_ = value; }
}
public List<LayoutTreeNode> Children
{
get { return children_; }
}
public RectangleF Extent
{
get { return rect_; }
}
protected void LayoutHorizontal()
{
//獲取當前節點能夠排布的最左位置,并預先安排當前節點。
float originLeft = lyrInfo_.GetLayoutableLeft(lyrLevel_);
rect_.X = originLeft;
//根據當前結點的坐標,安排子結點,并需要根據子結點的安置情況重新調整父節點的安置
if (children_.Count > 0)
{
//計算子結點最左可以安放的位置
float childrenTotalWidth = 0.0F;
foreach (LayoutTreeNode child in children_)
{
childrenTotalWidth += child.Extent.Width;
}
childrenTotalWidth += ((children_.Count - 1) * owner_.HorizontalSpacer);
float childLeftest = originLeft + (rect_.Width / 2.0f) - (childrenTotalWidth / 2.0F);
//設置子結點安放的最左位
lyrInfo_.SetLayoutableLeft(lyrLevel_ + 1, childLeftest);
//安放子結點
for (int idxChild = 0; idxChild < children_.Count; ++idxChild)
{
children_[idxChild].LayoutHorizontal();
}
//利用子結點重新對中安置當前節點
float center = (children_[0].Extent.Left + children_[children_.Count - 1].Extent.Right) / 2.0F;
rect_.X = center - rect_.Width / 2.0F;
}
//利用節點坐標設置該層其他子結點所能安放的最左位置,并設置一下當前層元素的最大高度
lyrInfo_.SetLayoutableLeft(lyrLevel_, this.rect_.Right + owner_.HorizontalSpacer);
lyrInfo_.SetLayoutLevelMinHeight(lyrLevel_, this.rect_.Height);
}
protected void LayoutVertical(float top)
{
rect_.Y = top;
foreach (LayoutTreeNode child in children_)
{
child.LayoutVertical(top + lyrInfo_.GetLayoutLevelMinHeight(lyrLevel_) + owner_.VerticalSpacer);
}
}
public void Layout()
{
LayoutHorizontal();
LayoutVertical(0.0f);
}
public virtual void CalculateSize(float maxWidth, Font font, Graphics g)
{
}
public virtual void CalculateSizeRecursive(float maxWidth, Font font, Graphics g)
{
}
public virtual void Draw(Graphics g) { }
}
class TextLayoutTreeNode: LayoutTreeNode
{
private string text;
private StringFormat strFmt = new StringFormat();
private Font font;
public String Text
{
get { return text; }
set { text = value; }
}
public override void CalculateSize(float maxWidth, Font font, Graphics g)
{
strFmt.Alignment = StringAlignment.Center;
strFmt.LineAlignment = StringAlignment.Center;
rect_.Size = g.MeasureString(text, font, (int)maxWidth, strFmt);
this.font = font;
}
public override void CalculateSizeRecursive(float maxWidth, Font font, Graphics g)
{
CalculateSize(maxWidth, font, g);
foreach (LayoutTreeNode node in children_)
{
node.CalculateSizeRecursive(maxWidth, font, g);
}
}
public override void Draw(Graphics g)
{
//外輪廓,內容,連線
g.DrawRectangle(Pens.Black, Rectangle.Round(Extent));
g.DrawString(text, font, Brushes.Red, Extent);
foreach (LayoutTreeNode childNode in children_)
{
//繪制斜線
float childX = (childNode.Extent.Left + childNode.Extent.Right) / 2.0F;
float childY = childNode.Extent.Top;
float curX = (Extent.Left + Extent.Right) / 2.0f;
float curY = Extent.Bottom;
g.DrawLine(Pens.Black, new PointF(childX, childY), new PointF(curX, curY));
}
}
}
class LayoutTree
{
private float vertical_spacer;
private float horizontal_spacer;
private LayoutTreeNode root;
private LayoutTreeLayerInfo lyrInfo = new LayoutTreeLayerInfo();
public float VerticalSpacer
{
get { return vertical_spacer; }
set { vertical_spacer = value; }
}
public float HorizontalSpacer
{
get { return horizontal_spacer; }
set { horizontal_spacer = value; }
}
public LayoutTreeNode Root
{
get { return root; }
set { root = value; }
}
public void ResetLayout()
{
lyrInfo.Reset();
}
public T CreateNode<T> (LayoutTreeNode parent) where T:LayoutTreeNode, new()
{
T retNode = new T();
retNode.Owner = this;
retNode.LayerInfo = this.lyrInfo;
if (parent == null)
{
this.root = retNode;
retNode.Level = 0;
}
else
{
parent.Children.Add(retNode);
retNode.Level = parent.Level + 1;
}
return retNode;
}
}
}