using System; using System.Collections.Generic; using System.ComponentModel; using Netron.Diagramming.Core.Analysis; namespace Netron.Diagramming.Core { /// /// Layout that computes a circular "balloon-tree" layout of a tree. /// This layout places children nodes radially around their parents, and is ///equivalent to a top-down flattened view of a ConeTree. /// /// The algorithm used is that of G. Melançon and I. Herman from their /// research paper Circular Drawings of Rooted Trees, Reports of the Centre for /// Mathematics and Computer Sciences, Report Number INS–9817, 1998. /// class BalloonTreeLayout : TreeLayoutBase { #region Fields private int m_minRadius = 2; private Dictionary Pars; BackgroundWorker worker; #endregion #region Properties /// /// Gets or sets the MinRadius /// public int MinRadius { get { return m_minRadius; } set { m_minRadius = value; } } #endregion #region Constructor /// ///Default constructor /// public BalloonTreeLayout(IController controller) : base("Balloon TreeLayout", controller) { } #endregion #region Methods /// /// Layouts this instance. /// public void Layout() { FirstWalk(LayoutRoot as INode); SecondWalk(LayoutRoot as INode, null, LayoutRoot.Rectangle.X, LayoutRoot.Rectangle.Y, 1, 0); } /// /// Inits this instance. /// /// 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'"); this.LayoutRoot = this.Controller.Model.LayoutRoot;//could be null if not set in the GUI Graph.ClearSpanningTree(); Graph.MakeSpanningTree(LayoutRoot as INode); 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(); Pars.Add(node.Uid.ToString(), par); } return true; } /// /// First traversal. /// /// The n. private void FirstWalk(INode n) { Params np = Pars[n.Uid.ToString()]; np.d = 0; double s = 0; if (n.Children != null) { foreach (INode c in n.Children) { //if (!c.isVisible()) continue; FirstWalk(c); Params cp = Pars[c.Uid.ToString()]; np.d = Math.Max(np.d, cp.r); cp.a = Math.Atan(((double)cp.r) / (np.d + cp.r)); s += cp.a; } } AdjustChildren(np, s); SetRadius(np); } /// /// Second traversal. /// /// The n. /// The r. /// The x. /// The y. /// The l. /// The t. private void SecondWalk(INode n, INode r, double x, double y, double l, double t) { setX(n, r, x); setY(n, r, y); Params np = Pars[n.Uid.ToString()]; double dd = l * np.d; double p = t + Math.PI; double fs = (n.ChildCount == 0 ? 0 : np.f / n.ChildCount); double pr = 0; if (n.Children != null) { foreach (INode c in n.Children) { //if (!c.isVisible()) continue; Params cp = Pars[c.Uid.ToString()]; double aa = np.c * cp.a; double rr = np.d * Math.Tan(aa) / (1 - Math.Tan(aa)); p += pr + aa + fs; double xx = (l * rr + dd) * Math.Cos(p); double yy = (l * rr + dd) * Math.Sin(p); pr = aa; SecondWalk(c, n, x + xx, y + yy, l * np.c/*l*rr/cp.r*/, p); } } } /// /// Sets the radius. /// /// The np. private void SetRadius(Params np) { np.r = Convert.ToInt32((Math.Max(np.d, m_minRadius) + 2 * np.d) / 2.1D); } /// /// Adjusts the children. /// /// The np. /// The s. private void AdjustChildren(Params np, double s) { if (s > Math.PI) { np.c = Math.PI / s; np.f = 0; } else { np.c = 1; np.f = Math.PI - s; } } /// /// Runs this instance. /// public override void Run() { Run(2000); } /// /// Runs the specified time. /// /// The time. public override void Run(int time) { worker = new BackgroundWorker(); worker.DoWork += new DoWorkEventHandler(worker_DoWork); worker.RunWorkerAsync(time); } /// /// Stops this instance. /// public override void Stop() { if (worker != null && worker.IsBusy) worker.CancelAsync(); } /// /// Handles the DoWork event of the worker control. /// /// The source of the event. /// The instance containing the event data. private void worker_DoWork(object sender, DoWorkEventArgs e) { Init(); Layout(); } #endregion #region Classes /// /// Paramter blob to temporarily keep working data of one node. /// class Params { public int d; public int r; //public double rx; //public double ry; public double a; public double c; public double f; } #endregion } }