18 changed files with 344 additions and 492 deletions
@ -1,21 +0,0 @@
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt)
|
||||
|
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
// TODO this class is almost not necessary, is used only for TreeLayouter purposes.
|
||||
// TreeLayouter could remember the additional information in a Dictionary PositionedEdge -> bool
|
||||
|
||||
/// <summary>
|
||||
/// Edge in the tree-layouted <see cref="PositionedGraph"/>.
|
||||
/// </summary>
|
||||
public class TreeGraphEdge : PositionedEdge |
||||
{ |
||||
/// <summary>
|
||||
/// Is this an edges making up the main tree?
|
||||
/// </summary>
|
||||
public bool IsTreeEdge { get; set; } |
||||
} |
||||
} |
@ -1,75 +0,0 @@
@@ -1,75 +0,0 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt)
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using Debugger.AddIn.Visualizers.Graph.Drawing; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Node in tree-layouted <see cref="PositionedGraph"/>.
|
||||
/// This is the abstract ancestor of TreeNodeLR and TreeNodeTB.
|
||||
/// There are 2 dimensions - "main" and "lateral".
|
||||
/// Main dimension is the dimension in which the graph depth grows (vertical when TB, horizontal when LR).
|
||||
/// Lateral dimension is the other dimension. Siblings are placed next to each other in Lateral dimension.
|
||||
/// </summary>
|
||||
public abstract class TreeGraphNode : PositionedGraphNode |
||||
{ |
||||
public static TreeGraphNode Create(LayoutDirection direction, ObjectGraphNode objectNode) |
||||
{ |
||||
switch (direction) { |
||||
case LayoutDirection.TopBottom: return new TreeNodeTB(objectNode); |
||||
case LayoutDirection.LeftRight: return new TreeNodeLR(objectNode); |
||||
default: throw new DebuggerVisualizerException("Unsupported layout direction: " + direction.ToString()); |
||||
} |
||||
} |
||||
|
||||
public double HorizontalMargin { get; set; } |
||||
public double VerticalMargin { get; set; } |
||||
|
||||
protected TreeGraphNode(ObjectGraphNode objectNode) : base(objectNode) |
||||
{ |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Width or height of the subtree.
|
||||
/// </summary>
|
||||
public double SubtreeSize { get; set; } |
||||
|
||||
public abstract double MainCoord { get; set; } |
||||
public abstract double LateralCoord { get; set; } |
||||
|
||||
public abstract double MainSize { get; } |
||||
public abstract double LateralSize { get; } |
||||
|
||||
public double MainSizeWithMargin { get { return MainSize + MainMargin; } } |
||||
public double LateralSizeWithMargin { get { return LateralSize + LateralMargin; } } |
||||
|
||||
public abstract double MainMargin { get; } |
||||
public abstract double LateralMargin { get; } |
||||
|
||||
public IEnumerable<PositionedEdge> ChildEdges |
||||
{ |
||||
get |
||||
{ |
||||
foreach (TreeGraphEdge childEdge in Edges) |
||||
{ |
||||
if (childEdge.IsTreeEdge) |
||||
{ |
||||
yield return childEdge; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
public IEnumerable<TreeGraphNode> Childs |
||||
{ |
||||
get |
||||
{ |
||||
foreach (PositionedEdge outEdge in this.ChildEdges) |
||||
yield return (TreeGraphNode)outEdge.Target; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,27 +0,0 @@
@@ -1,27 +0,0 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt)
|
||||
|
||||
using System; |
||||
using Debugger.AddIn.Visualizers.Graph.Drawing; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// TreeNode used in LR layout mode.
|
||||
/// </summary>
|
||||
public class TreeNodeLR: TreeGraphNode |
||||
{ |
||||
public TreeNodeLR(ObjectGraphNode objectNode) : base(objectNode) |
||||
{ |
||||
} |
||||
|
||||
public override double MainSize { get { return this.Width; } } |
||||
public override double LateralSize { get { return this.Height ; } } |
||||
|
||||
public override double MainCoord { get { return this.Left; } set { this.Left = value; } } |
||||
public override double LateralCoord { get { return this.Top; } set { this.Top = value; } } |
||||
|
||||
public override double MainMargin { get { return this.HorizontalMargin; } } |
||||
public override double LateralMargin { get { return this.VerticalMargin; } } |
||||
} |
||||
} |
@ -1,27 +0,0 @@
@@ -1,27 +0,0 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt)
|
||||
|
||||
using System; |
||||
using Debugger.AddIn.Visualizers.Graph.Drawing; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// TreeNode used in TB layout mode.
|
||||
/// </summary>
|
||||
public class TreeNodeTB : TreeGraphNode |
||||
{ |
||||
public TreeNodeTB(ObjectGraphNode objectNode) : base(objectNode) |
||||
{ |
||||
} |
||||
|
||||
public override double MainSize { get { return this.Height; } } |
||||
public override double LateralSize { get { return this.Width; } } |
||||
|
||||
public override double MainCoord { get { return this.Top; } set { this.Top = value; } } |
||||
public override double LateralCoord { get { return this.Left; } set { this.Left = value; } } |
||||
|
||||
public override double MainMargin { get { return this.VerticalMargin; } } |
||||
public override double LateralMargin { get { return this.HorizontalMargin; } } |
||||
} |
||||
} |
@ -0,0 +1,187 @@
@@ -0,0 +1,187 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt)
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Windows; |
||||
using Debugger.AddIn.Visualizers.Graph.Drawing; |
||||
using System.Linq; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Calculates layout of <see cref="ObjectGraph" />, producing <see cref="PositionedGraph" />.
|
||||
/// </summary>
|
||||
public class TreeLayout |
||||
{ |
||||
private static readonly double NodeMarginH = 30; |
||||
private static readonly double NodeMarginV = 30; |
||||
|
||||
GraphEdgeRouter edgeRouter = new GraphEdgeRouter(); |
||||
/// <summary>
|
||||
/// The produced layout is either a horizontal or vertical tree.
|
||||
/// </summary>
|
||||
public LayoutDirection LayoutDirection { get; private set; } |
||||
|
||||
public TreeLayout(LayoutDirection layoutDirection) |
||||
{ |
||||
this.LayoutDirection = layoutDirection; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Calculates layout for given <see cref="ObjectGraph" />.
|
||||
/// </summary>
|
||||
/// <param name="objectGraph"></param>
|
||||
/// <returns></returns>
|
||||
public PositionedGraph CalculateLayout(ObjectGraph objectGraph, Expanded expanded) |
||||
{ |
||||
var positionedGraph = BuildPositionedGraph(objectGraph, expanded); |
||||
CalculateLayout(positionedGraph); |
||||
this.edgeRouter.RouteEdges(positionedGraph); |
||||
|
||||
return positionedGraph; |
||||
} |
||||
|
||||
// Expanded is passed so that the correct ContentNodes are expanded in the PositionedNode
|
||||
PositionedGraph BuildPositionedGraph(ObjectGraph objectGraph, Expanded expanded) |
||||
{ |
||||
var positionedNodeFor = new Dictionary<ObjectGraphNode, PositionedNode>(); |
||||
var positionedGraph = new PositionedGraph(); |
||||
|
||||
// create empty PositionedNodes
|
||||
foreach (ObjectGraphNode objectNode in objectGraph.ReachableNodes) { |
||||
var posNode = new PositionedNode(objectNode); |
||||
posNode.InitContentFromObjectNode(expanded); |
||||
positionedGraph.AddNode(posNode); |
||||
positionedNodeFor[objectNode] = posNode; |
||||
} |
||||
|
||||
// create edges
|
||||
foreach (PositionedNode posNode in positionedGraph.Nodes) |
||||
{ |
||||
foreach (PositionedNodeProperty property in posNode.Properties) { |
||||
if (property.ObjectGraphProperty.TargetNode != null) { |
||||
ObjectGraphNode targetObjectNode = property.ObjectGraphProperty.TargetNode; |
||||
PositionedNode edgeTarget = positionedNodeFor[targetObjectNode]; |
||||
property.Edge = new PositionedEdge { |
||||
Name = property.Name, Source = property, Target = edgeTarget |
||||
}; |
||||
} |
||||
} |
||||
} |
||||
positionedGraph.Root = positionedNodeFor[objectGraph.Root]; |
||||
return positionedGraph; |
||||
} |
||||
|
||||
void CalculateLayout(PositionedGraph positionedGraph) |
||||
{ |
||||
HashSet<PositionedNode> seenNodes = new HashSet<PositionedNode>(); |
||||
HashSet<PositionedEdge> treeEdges = new HashSet<PositionedEdge>(); |
||||
// first layout pass
|
||||
CalculateSubtreeSizes(positionedGraph.Root, seenNodes, treeEdges); |
||||
// second layout pass
|
||||
CalculateNodePosRecursive(positionedGraph.Root, treeEdges, 0, 0); |
||||
} |
||||
|
||||
// determines which edges are tree edges, and calculates subtree size for each node
|
||||
private void CalculateSubtreeSizes(PositionedNode root, HashSet<PositionedNode> seenNodes, HashSet<PositionedEdge> treeEdges) |
||||
{ |
||||
seenNodes.Add(root); |
||||
double subtreeSize = 0; |
||||
foreach (var property in root.Properties) { |
||||
var edge = property.Edge; |
||||
if (edge != null) { |
||||
var targetNode = edge.Target; |
||||
if (!seenNodes.Contains(targetNode)) { |
||||
// when we come to a node for the first time, we declare the incoming edge a tree edge
|
||||
treeEdges.Add(edge); |
||||
CalculateSubtreeSizes(targetNode, seenNodes, treeEdges); |
||||
subtreeSize += targetNode.SubtreeSize; |
||||
} |
||||
} |
||||
} |
||||
root.Measure(); |
||||
root.SubtreeSize = Math.Max(GetLateralSizeWithMargin(root), subtreeSize); |
||||
} |
||||
|
||||
|
||||
/// <summary>
|
||||
/// Given SubtreeSize for each node, positions the nodes, in a left-to-right or top-to-bottom layout.
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <param name="lateralStart"></param>
|
||||
/// <param name="mainStart"></param>
|
||||
void CalculateNodePosRecursive(PositionedNode node, HashSet<PositionedEdge> treeEdges, double lateralBase, double mainBase) |
||||
{ |
||||
double childsSubtreeSize = TreeChildNodes(node, treeEdges).Sum(child => child.SubtreeSize); |
||||
double center = TreeEdges(node, treeEdges).Count() == 0 ? 0 : 0.5 * (childsSubtreeSize - (GetLateralSizeWithMargin(node))); |
||||
if (center < 0) { |
||||
// if root is larger than subtree, it would be shifted below lateralStart
|
||||
// -> make whole layout start at lateralStart
|
||||
lateralBase -= center; |
||||
} |
||||
|
||||
SetLateral(node, GetLateral(node) + lateralBase + center); |
||||
SetMain(node, mainBase); |
||||
|
||||
double childLateral = lateralBase; |
||||
double childsMainFixed = GetMain(node) + GetMainSizeWithMargin(node); |
||||
foreach (var child in TreeChildNodes(node, treeEdges)) { |
||||
CalculateNodePosRecursive(child, treeEdges, childLateral, childsMainFixed); |
||||
childLateral += child.SubtreeSize; |
||||
} |
||||
} |
||||
|
||||
IEnumerable<PositionedEdge> TreeEdges(PositionedNode node, HashSet<PositionedEdge> treeEdges) |
||||
{ |
||||
return node.Edges.Where(e => treeEdges.Contains(e)); |
||||
} |
||||
|
||||
IEnumerable<PositionedNode> TreeChildNodes(PositionedNode node, HashSet<PositionedEdge> treeEdges) |
||||
{ |
||||
return TreeEdges(node, treeEdges).Select(e => e.Target); |
||||
} |
||||
|
||||
#region Horizontal / vertical layout helpers
|
||||
|
||||
double GetMainSizeWithMargin(PositionedNode node) |
||||
{ |
||||
return (this.LayoutDirection == LayoutDirection.LeftRight) ? node.Width + NodeMarginH : node.Height + NodeMarginV; |
||||
} |
||||
|
||||
double GetLateralSizeWithMargin(PositionedNode node) |
||||
{ |
||||
return (this.LayoutDirection == LayoutDirection.LeftRight) ? node.Height + NodeMarginV : node.Width + NodeMarginH; |
||||
} |
||||
|
||||
double GetMain(PositionedNode node) |
||||
{ |
||||
return (this.LayoutDirection == LayoutDirection.LeftRight) ? node.Left : node.Top; |
||||
} |
||||
|
||||
double GetLateral(PositionedNode node) |
||||
{ |
||||
return (this.LayoutDirection == LayoutDirection.LeftRight) ? node.Top : node.Left; |
||||
} |
||||
|
||||
void SetMain(PositionedNode node, double value) |
||||
{ |
||||
if (this.LayoutDirection == LayoutDirection.LeftRight) { |
||||
node.Left = value; |
||||
} else { |
||||
node.Top = value; |
||||
} |
||||
} |
||||
|
||||
void SetLateral(PositionedNode node, double value) |
||||
{ |
||||
if (this.LayoutDirection == LayoutDirection.LeftRight) { |
||||
node.Top = value; |
||||
} else { |
||||
node.Left = value; |
||||
} |
||||
} |
||||
|
||||
#endregion
|
||||
} |
||||
} |
@ -1,137 +0,0 @@
@@ -1,137 +0,0 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt)
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Windows; |
||||
using Debugger.AddIn.Visualizers.Graph.Drawing; |
||||
using System.Linq; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Calculates layout of <see cref="ObjectGraph" />, producing <see cref="PositionedGraph" />.
|
||||
/// </summary>
|
||||
public class TreeLayouter |
||||
{ |
||||
private static readonly double horizNodeMargin = 30; |
||||
private static readonly double vertNodeMargin = 30; |
||||
|
||||
GraphEdgeRouter edgeRouter = new GraphEdgeRouter(); |
||||
|
||||
public TreeLayouter() |
||||
{ |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Calculates layout for given <see cref="ObjectGraph" />.
|
||||
/// </summary>
|
||||
/// <param name="objectGraph"></param>
|
||||
/// <returns></returns>
|
||||
public PositionedGraph CalculateLayout(ObjectGraph objectGraph, LayoutDirection direction, Expanded expanded) |
||||
{ |
||||
var positionedGraph = BuildPositionedGraph(objectGraph, direction, expanded); |
||||
CalculateLayout(positionedGraph); |
||||
this.edgeRouter.RouteEdges(positionedGraph); |
||||
|
||||
return positionedGraph; |
||||
} |
||||
|
||||
PositionedGraph BuildPositionedGraph(ObjectGraph objectGraph, LayoutDirection direction, Expanded expanded)// Expanded is passed so that the correct ContentNodes are expanded in the PositionedNode
|
||||
{ |
||||
var treeNodeFor = new Dictionary<ObjectGraphNode, PositionedGraphNode>(); |
||||
var resultGraph = new PositionedGraph(); |
||||
|
||||
// create empty PositionedNodes
|
||||
foreach (ObjectGraphNode objectGraphNode in objectGraph.ReachableNodes) { |
||||
TreeGraphNode posNode = TreeGraphNode.Create(direction, objectGraphNode); |
||||
posNode.InitContentFromObjectNode(expanded); |
||||
posNode.HorizontalMargin = horizNodeMargin; |
||||
posNode.VerticalMargin = vertNodeMargin; |
||||
resultGraph.AddNode(posNode); |
||||
treeNodeFor[objectGraphNode] = posNode; |
||||
} |
||||
|
||||
// create edges
|
||||
foreach (PositionedGraphNode posNode in resultGraph.Nodes) |
||||
{ |
||||
foreach (PositionedNodeProperty property in posNode.Properties) { |
||||
if (property.ObjectGraphProperty.TargetNode != null) { |
||||
ObjectGraphNode targetObjectNode = property.ObjectGraphProperty.TargetNode; |
||||
PositionedGraphNode edgeTarget = treeNodeFor[targetObjectNode]; |
||||
property.Edge = new TreeGraphEdge |
||||
{ IsTreeEdge = false, Name = property.Name, Source = property, Target = edgeTarget }; |
||||
} |
||||
} |
||||
} |
||||
resultGraph.Root = treeNodeFor[objectGraph.Root]; |
||||
return resultGraph; |
||||
} |
||||
|
||||
void CalculateLayout(PositionedGraph resultGraph) |
||||
{ |
||||
HashSet<PositionedGraphNode> seenNodes = new HashSet<PositionedGraphNode>(); |
||||
// first layout pass
|
||||
CalculateSubtreeSizes((TreeGraphNode)resultGraph.Root, seenNodes); |
||||
// second layout pass
|
||||
CalculateNodePosRecursive((TreeGraphNode)resultGraph.Root, 0, 0); |
||||
} |
||||
|
||||
// determines which edges are tree edges, and calculates subtree size for each node
|
||||
private void CalculateSubtreeSizes(TreeGraphNode root, HashSet<PositionedGraphNode> seenNodes) |
||||
{ |
||||
seenNodes.Add(root); |
||||
double subtreeSize = 0; |
||||
|
||||
foreach (PositionedNodeProperty property in root.Properties) { |
||||
var edge = property.Edge as TreeGraphEdge; // we know that these egdes are TreeEdges
|
||||
if (edge != null) { |
||||
var neigborNode = (TreeGraphNode)edge.Target; |
||||
if (seenNodes.Contains(neigborNode)) { |
||||
edge.IsTreeEdge = false; |
||||
} else { |
||||
edge.IsTreeEdge = true; |
||||
CalculateSubtreeSizes(neigborNode, seenNodes); |
||||
subtreeSize += neigborNode.SubtreeSize; |
||||
} |
||||
} |
||||
} |
||||
root.Measure(); |
||||
root.SubtreeSize = Math.Max(root.LateralSizeWithMargin, subtreeSize); |
||||
} |
||||
|
||||
|
||||
/// <summary>
|
||||
/// Given SubtreeSize for each node, positions the nodes, in a left-to-right or top-to-bottom fashion.
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <param name="lateralStart"></param>
|
||||
/// <param name="mainStart"></param>
|
||||
private void CalculateNodePosRecursive(TreeGraphNode node, double lateralStart, double mainStart) |
||||
{ |
||||
double childsSubtreeSize = node.Childs.Sum(child => child.SubtreeSize); |
||||
// center this node
|
||||
double center = node.ChildEdges.Count() == 0 ? 0 : 0.5 * (childsSubtreeSize - (node.LateralSizeWithMargin)); |
||||
if (center < 0) { |
||||
// if root is larger than subtree, it would be shifted below lateralStart
|
||||
// -> make whole layout start at lateralStart
|
||||
lateralStart -= center; |
||||
} |
||||
|
||||
// design alternatives
|
||||
// node.MainPos += center; // used this
|
||||
// Adapt(node).PosLateral += center; // TreeNodeAdapterLR + TreeNodeAdapterTB
|
||||
// SetMainPos(node, GetMainPos(node) + 10) // TreeNodeAdapterLR + TreeNodeAdapterTB, no creation
|
||||
|
||||
node.LateralCoord += lateralStart + center; |
||||
node.MainCoord = mainStart; |
||||
|
||||
double childLateral = lateralStart; |
||||
double childsMainFixed = node.MainCoord + node.MainSizeWithMargin; |
||||
foreach (TreeGraphNode child in node.Childs) { |
||||
CalculateNodePosRecursive(child, childLateral, childsMainFixed); |
||||
childLateral += child.SubtreeSize; |
||||
} |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue