using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using Netron.Diagramming.Core.Analysis;
namespace Netron.Diagramming.Core {
///
/// TreeLayout that computes a tidy layout of a node-link tree
/// diagram. This algorithm lays out a rooted tree such that each
/// depth level of the tree is on a shared line. The orientation of the
/// tree can be set such that the tree goes left-to-right (default),
/// right-to-left, top-to-bottom, or bottom-to-top.
///
/// The algorithm used is that of Christoph Buchheim, Michael Jünger,
/// and Sebastian Leipert from their research paper
///
/// Improving Walker's Algorithm to Run in Linear Time, Graph Drawing 2002.
/// This algorithm corrects performance issues in Walker's algorithm, which
/// generalizes Reingold and Tilford's method for tidy drawings of trees to
/// support trees with an arbitrary number of children at any given node.
///
class StandardTreeLayout : TreeLayoutBase {
#region Fields
BackgroundWorker worker;
private static TreeOrientation mOrientation = TreeOrientation.TopBottom; // the orientation of the tree
private static float mBreadth = 50F; // the spacing between sibling nodes
private static float mSubTreeSpacing = 50F; // the spacing between subtrees
private static float mDepth = 50F; // the spacing between depth levels
private float m_offset = 50; // pixel offset for root node position
private PointF m_anchor;
private PointF m_tmpa;
private float[] m_depths = new float[10];
private int mMaxDepth = 0;
private Dictionary Pars;
private float m_ax, m_ay; // for holding anchor co-ordinates
#endregion
#region Properties
///
/// Gets or sets the orientation of the tree.
///
/// The orientation.
public static TreeOrientation Orientation {
get {
return mOrientation;
}
set {
mOrientation = value;
}
}
///
/// Gets or sets the depth spacing.
///
/// The depth spacing.
public static float DepthSpacing {
get { return mDepth; }
set { mDepth = value; }
}
///
/// Gets or sets the breadth spacing.
///
/// The breadth spacing.
public static float BreadthSpacing {
get { return mBreadth; }
set { mBreadth = value; }
}
///
/// Gets or sets the spacing between subtrees.
///
/// The sub tree spacing.
public static float SubTreeSpacing {
get { return mSubTreeSpacing; }
set { mSubTreeSpacing = value; }
}
public float RootOffset {
get { return m_offset; }
set { m_offset = value; }
}
#endregion
#region Constructor
public StandardTreeLayout(IController controller)
: base("Standard TreeLayout", controller) {
Orientation = TreeOrientation.LeftRight;
}
#endregion
#region Methods
public override void Run() {
Run(2000);
}
public override void Run(int time) {
if (worker != null && worker.IsBusy)
worker.CancelAsync();
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerAsync(time);
}
public override void Stop() {
if (worker != null && worker.IsBusy)
worker.CancelAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e) {
this.Controller.View.Suspend();
Init();
Layout();
this.Controller.View.Resume();
}
///
/// Runs a single step.
///
private void RunStep() {
}
private bool Init() {
this.Graph = this.Model as IGraph;
if (Graph == null)
throw new InconsistencyException("The model has not been set and the Graph property is hence 'null'");
m_depths = new float[10];
this.LayoutRoot = this.Controller.Model.LayoutRoot;//could be null if not set in the GUI
Graph.ClearSpanningTree();
Graph.MakeSpanningTree(LayoutRoot as INode);
//Trace.WriteLine((LayoutRoot as INode).ChildCount);
if (Graph.SpanningTree == null)
throw new InconsistencyException("The spanning tree is not set (se the root of the tree layout first)");
Pars = new Dictionary();
if (Graph.Nodes.Count == 0)
return false;
if (Graph.Edges.Count == 0) //this layout is base on embedded springs in the connections
return false;
Params par;
foreach (INode node in Graph.Nodes) {
par = new Params();
par.init(node);
Pars.Add(node.Uid.ToString(), par);
}
return true;
}
public PointF LayoutAnchor {
get {
//if (m_anchor != null)
//{
// return m_anchor;
//}
m_tmpa = PointF.Empty;
if (Graph != null) {
Rectangle b = Bounds;
switch (mOrientation) {
case TreeOrientation.LeftRight:
m_tmpa = new PointF(m_offset, b.Height / 2F);
break;
case TreeOrientation.RightLeft:
m_tmpa = new PointF(b.Width - m_offset, b.Height / 2F);
break;
case TreeOrientation.TopBottom:
m_tmpa = new PointF(b.Width / 2F, m_offset);
break;
case TreeOrientation.BottomTop:
m_tmpa = new PointF(b.Width / 2F, b.Height - m_offset);
break;
case TreeOrientation.Center:
m_tmpa = new PointF(b.Width / 2F, b.Height / 2F);
break;
default:
break;
}
//TODO: set the center of the layout here on the view
//d.getInverseTransform().transform(m_tmpa, m_tmpa);
}
return m_tmpa;
}
set {
m_anchor = value;
}
}
///
/// Returns the location of halfway the two given nodes
///
///
///
///
///
private double spacing(INode l, INode r, bool siblings) {
bool w = (mOrientation == TreeOrientation.TopBottom || mOrientation == TreeOrientation.BottomTop);
return (siblings ? mBreadth : mSubTreeSpacing) + 0.5 * (w ? l.Rectangle.Width + r.Rectangle.Width : l.Rectangle.Height + r.Rectangle.Height);
}
private void updateDepths(int depth, INode item) {
bool v = (mOrientation == TreeOrientation.TopBottom || mOrientation == TreeOrientation.BottomTop);
double d = (v ? item.Rectangle.Height : item.Rectangle.Width);
if (m_depths.Length <= depth)
Array.Resize(ref m_depths, (int)(3 * depth / 2));
m_depths[depth] = (float)Math.Max(m_depths[depth], d);
mMaxDepth = (int)Math.Max(mMaxDepth, depth);
}
private void determineDepths() {
for (int i = 1; i < mMaxDepth; ++i)
m_depths[i] += m_depths[i - 1] + mDepth;
}
public void Layout() {
m_depths.Initialize();
mMaxDepth = 0;
PointF a = LayoutAnchor;
m_ax = a.X;
m_ay = a.Y;
INode root = LayoutRoot as INode;
Params rp = Pars[root.Uid.ToString()];
// do first pass - compute breadth information, collect depth info
firstWalk(root, 0, 1);
// sum up the depth info
determineDepths();
// do second pass - assign layout positions
secondWalk(root, null, -rp.prelim, 0);
}
private void firstWalk(INode n, int num, int depth) {
//Trace.WriteLine("depthj: " + depth);
Params np = Pars[n.Uid.ToString()];
np.number = num;
updateDepths(depth, n);
bool expanded = n.IsExpanded;
if (n.ChildCount == 0 || !expanded) // is leaf
{
INode l = (INode)n.PreviousSibling;
if (l == null) {
np.prelim = 0;
} else {
np.prelim = Pars[l.Uid.ToString()].prelim + spacing(l, n, true);
}
} else if (expanded) {
INode leftMost = n.FirstChild;
INode rightMost = n.LastChild;
INode defaultAncestor = leftMost;
INode c = leftMost;
for (int i = 0; c != null; ++i, c = c.NextSibling) {
firstWalk(c, i, depth + 1);
defaultAncestor = apportion(c, defaultAncestor);
}
executeShifts(n);
double midpoint = 0.5 *
(Pars[leftMost.Uid.ToString()].prelim + Pars[rightMost.Uid.ToString()].prelim);
INode left = (INode)n.PreviousSibling;
if (left != null) {
np.prelim = Pars[left.Uid.ToString()].prelim + spacing(left, n, true);
np.mod = np.prelim - midpoint;
} else {
np.prelim = midpoint;
}
}
}
private INode apportion(INode v, INode a) {
INode w = (INode)v.PreviousSibling;
if (w != null) {
INode vip, vim, vop, vom;
double sip, sim, sop, som;
vip = vop = v;
vim = w;
vom = (INode)vip.ParentNode.FirstChild;
sip = Pars[vip.Uid.ToString()].mod;
sop = Pars[vop.Uid.ToString()].mod;
sim = Pars[vim.Uid.ToString()].mod;
som = Pars[vom.Uid.ToString()].mod;
Params parms;
INode nr = nextRight(vim);
INode nl = nextLeft(vip);
while (nr != null && nl != null) {
vim = nr;
vip = nl;
vom = nextLeft(vom);
vop = nextRight(vop);
parms = Pars[vop.Uid.ToString()];
parms.ancestor = v;
double shift = (Pars[vim.Uid.ToString()].prelim + sim) -
(Pars[vip.Uid.ToString()].prelim + sip) + spacing(vim, vip, false);
if (shift > 0) {
moveSubtree(ancestor(vim, v, a), v, shift);
sip += shift;
sop += shift;
}
sim += Pars[vim.Uid.ToString()].mod;
sip += Pars[vip.Uid.ToString()].mod;
som += Pars[vom.Uid.ToString()].mod;
sop += Pars[vop.Uid.ToString()].mod;
nr = nextRight(vim);
nl = nextLeft(vip);
}
if (nr != null && nextRight(vop) == null) {
Params vopp = Pars[vop.Uid.ToString()];
vopp.thread = nr;
vopp.mod += sim - sop;
}
if (nl != null && nextLeft(vom) == null) {
Params vomp = Pars[vom.Uid.ToString()];
vomp.thread = nl;
vomp.mod += sip - som;
a = v;
}
}
return a;
}
private INode nextLeft(INode n) {
INode c = null;
if (n.IsExpanded) c = n.FirstChild;
return (c != null ? c : Pars[n.Uid.ToString()].thread);
}
private INode nextRight(INode n) {
INode c = null;
if (n.IsExpanded) c = n.LastChild;
return (c != null ? c : Pars[n.Uid.ToString()].thread);
}
private void moveSubtree(INode wm, INode wp, double shift) {
Params wmp = Pars[wm.Uid.ToString()];
Params wpp = Pars[wp.Uid.ToString()];
double subtrees = wpp.number - wmp.number;
wpp.change -= shift / subtrees;
wpp.shift += shift;
wmp.change += shift / subtrees;
wpp.prelim += shift;
wpp.mod += shift;
}
private void executeShifts(INode n) {
double shift = 0, change = 0;
for (INode c = n.LastChild; c != null; c = c.PreviousSibling) {
Params cp = Pars[c.Uid.ToString()];
cp.prelim += shift;
cp.mod += shift;
change += cp.change;
shift += cp.shift + change;
}
}
private INode ancestor(INode vim, INode v, INode a) {
INode p = (INode)v.ParentNode;
Params vimp = Pars[vim.Uid.ToString()];
if (vimp.ancestor != null && vimp.ancestor.ParentNode == p) {
return vimp.ancestor;
} else {
return a;
}
}
private void secondWalk(INode n, INode p, double m, int depth) {
Params np = Pars[n.Uid.ToString()];
setBreadth(n, p, np.prelim + m);
setDepth(n, p, m_depths[depth]);
if (n.IsExpanded) {
depth += 1;
for (INode c = n.FirstChild; c != null; c = c.NextSibling) {
if (worker.CancellationPending)
break;
secondWalk(c, n, m + np.mod, depth);
}
}
np.clear();
}
private void setBreadth(INode n, INode p, double b) {
switch (mOrientation) {
case TreeOrientation.LeftRight:
case TreeOrientation.RightLeft:
setY(n, p, m_ay + b);
break;
case TreeOrientation.TopBottom:
case TreeOrientation.BottomTop:
setX(n, p, m_ax + b);
break;
default:
throw new InconsistencyException();
}
}
private void setDepth(INode n, INode p, double d) {
switch (mOrientation) {
case TreeOrientation.LeftRight:
setX(n, p, m_ax + d);
break;
case TreeOrientation.RightLeft:
setX(n, p, m_ax - d);
break;
case TreeOrientation.TopBottom:
setY(n, p, m_ay + d);
break;
case TreeOrientation.BottomTop:
setY(n, p, m_ay - d);
break;
default:
throw new InconsistencyException();
}
}
#endregion
///
/// Paramter blob to temporarily keep working data of one node.
///
class Params {
public double prelim;
public double mod;
public double shift;
public double change;
public int number;
public INode ancestor;
public INode thread;
public void init(INode item) {
ancestor = item;
number = -1;
ancestor = thread = null;
}
public void clear() {
number = -2;
prelim = mod = shift = change = 0;
ancestor = thread = null;
}
}
}
public enum TreeOrientation {
LeftRight,
RightLeft,
TopBottom,
BottomTop,
Center
}
}