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

布局分為水平布局和豎直布局兩個(gè)部分,我這里先進(jìn)行了水平布局再進(jìn)行了豎直布局。
一般來(lái)講緊湊的樹(shù)主要指同級(jí)節(jié)點(diǎn)方向上的緊湊性。由于這里樹(shù)是父節(jié)點(diǎn)在上,子結(jié)點(diǎn)在下,因此水平方向上的節(jié)點(diǎn)要盡量緊湊。那么便需要我將節(jié)點(diǎn)盡量往左布局。
如果自左往右布置節(jié)點(diǎn),那么很顯然,左邊的節(jié)點(diǎn)的位置一旦固定下來(lái)就不需要再行調(diào)整。
同時(shí),為了保持樹(shù)的美觀,父節(jié)點(diǎn)的水平中心應(yīng)當(dāng)是子結(jié)點(diǎn)的水平中心,這樣子結(jié)點(diǎn)就會(huì)對(duì)稱(chēng)分布在父節(jié)點(diǎn)的下方,有利于美觀。
然而父節(jié)點(diǎn)能正常布局的位置,子結(jié)點(diǎn)可能無(wú)法正常布局(子結(jié)點(diǎn)的寬度比父節(jié)點(diǎn)寬得多)。因此我們還需要調(diào)整子結(jié)點(diǎn)的布局。
由于是自左向右布局的,此時(shí)子結(jié)點(diǎn)的左邊的節(jié)點(diǎn)都已經(jīng)確定了,正確的布局很容易便可以通過(guò)右移得到。
為了保證正確性,還需要把父節(jié)點(diǎn)也右移。因?yàn)楦腹?jié)點(diǎn)的右邊沒(méi)有布局限制,因而可以放心的右移。這樣一直傳遞到根節(jié)點(diǎn)就可以了。
那么很明顯,整體來(lái)說(shuō),布局順序就是,自下而上,自左而右。
為了讓節(jié)點(diǎn)在布局的時(shí)候知道自己能被安排的最左位置,需要為每一層保存最左可布局位置的坐標(biāo)。
一旦有節(jié)點(diǎn)被安排妥當(dāng),便更新節(jié)點(diǎn)所在層次的最左可布局位置。后來(lái)的節(jié)點(diǎn)只要在這個(gè)位置的右方布局,就不會(huì)與已布置的節(jié)點(diǎn)沖突。
豎直布局并不復(fù)雜,算出每一層的Top就可以了。
代碼如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
//布局算法:
//采用平行節(jié)點(diǎn)最左布局,父子節(jié)點(diǎn)對(duì)中布局的原則。
/****
* void Layout(int lyr)
* {
*
* }
*
*****/
namespace CrowdTree
{
class LayoutTreeLayerInfo
{
//可安排布局的最左坐標(biāo)
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()
{
//獲取當(dāng)前節(jié)點(diǎn)能夠排布的最左位置,并預(yù)先安排當(dāng)前節(jié)點(diǎn)。
float originLeft = lyrInfo_.GetLayoutableLeft(lyrLevel_);
rect_.X = originLeft;
//根據(jù)當(dāng)前結(jié)點(diǎn)的坐標(biāo),安排子結(jié)點(diǎn),并需要根據(jù)子結(jié)點(diǎn)的安置情況重新調(diào)整父節(jié)點(diǎn)的安置
if (children_.Count > 0)
{
//計(jì)算子結(jié)點(diǎn)最左可以安放的位置
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);
//設(shè)置子結(jié)點(diǎn)安放的最左位
lyrInfo_.SetLayoutableLeft(lyrLevel_ + 1, childLeftest);
//安放子結(jié)點(diǎn)
for (int idxChild = 0; idxChild < children_.Count; ++idxChild)
{
children_[idxChild].LayoutHorizontal();
}
//利用子結(jié)點(diǎn)重新對(duì)中安置當(dāng)前節(jié)點(diǎn)
float center = (children_[0].Extent.Left + children_[children_.Count - 1].Extent.Right) / 2.0F;
rect_.X = center - rect_.Width / 2.0F;
}
//利用節(jié)點(diǎn)坐標(biāo)設(shè)置該層其他子結(jié)點(diǎn)所能安放的最左位置,并設(shè)置一下當(dāng)前層元素的最大高度
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)
{
//外輪廓,內(nèi)容,連線
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;
}
}
}