1
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
2
// This code is distributed under the BSD license (for details please see \src\AddIns\Debugger\Debugger.AddIn\license.txt)
5
using System.Collections.Generic;
7
using Debugger.AddIn.Visualizers.Graph.Drawing;
10
namespace Debugger.AddIn.Visualizers.Graph.Layout
13
/// Calculates layout of <see cref="ObjectGraph" />, producing <see cref="PositionedGraph" />.
15
public class TreeLayout
17
static readonly double NodeMarginH = 30;
18
static readonly double NodeMarginV = 30;
19
static readonly double MarginTop = 0;
20
static readonly double MarginBottom = 0;
23
GraphEdgeRouter edgeRouter = new GraphEdgeRouter();
25
/// The produced layout is either a horizontal or vertical tree.
27
public LayoutDirection LayoutDirection { get; private set; }
29
public TreeLayout(LayoutDirection layoutDirection)
31
this.LayoutDirection = layoutDirection;
35
/// Calculates layout for given <see cref="ObjectGraph" />.
37
/// <param name="objectGraph"></param>
38
/// <returns></returns>
39
public PositionedGraph CalculateLayout(ObjectGraph objectGraph, Expanded expanded)
41
var positionedGraph = BuildPositionedGraph(objectGraph, expanded);
42
CalculateLayout(positionedGraph);
43
this.edgeRouter.RouteEdges(positionedGraph);
45
return positionedGraph;
48
// Expanded is passed so that the correct ContentNodes are expanded in the PositionedNode
49
PositionedGraph BuildPositionedGraph(ObjectGraph objectGraph, Expanded expanded)
51
var positionedNodeFor = new Dictionary<ObjectGraphNode, PositionedNode>();
52
var positionedGraph = new PositionedGraph();
54
// create empty PositionedNodes
55
foreach (ObjectGraphNode objectNode in objectGraph.ReachableNodes) {
56
var posNode = new PositionedNode(objectNode, expanded);
57
posNode.MeasureVisualControl();
58
positionedGraph.AddNode(posNode);
59
positionedNodeFor[objectNode] = posNode;
63
foreach (PositionedNode posNode in positionedGraph.Nodes)
65
foreach (PositionedNodeProperty property in posNode.Properties) {
66
if (property.ObjectGraphProperty.TargetNode != null) {
67
ObjectGraphNode targetObjectNode = property.ObjectGraphProperty.TargetNode;
68
PositionedNode edgeTarget = positionedNodeFor[targetObjectNode];
69
property.Edge = new PositionedEdge {
70
Name = property.Name, Source = property, Target = edgeTarget
75
positionedGraph.Root = positionedNodeFor[objectGraph.Root];
76
return positionedGraph;
79
void CalculateLayout(PositionedGraph positionedGraph)
81
// impose a tree structure on the graph
82
HashSet<PositionedEdge> treeEdges = DetermineTreeEdges(positionedGraph.Root);
84
CalculateSubtreeSizesRecursive(positionedGraph.Root, treeEdges);
86
CalculateNodePosRecursive(positionedGraph.Root, treeEdges, MarginTop, MarginBottom);
90
HashSet<PositionedEdge> DetermineTreeEdges(PositionedNode root)
92
var treeEdges = new HashSet<PositionedEdge>();
94
var seenNodes = new HashSet<PositionedNode>();
95
var q = new Queue<PositionedNode>();
100
var node = q.Dequeue();
101
foreach (var property in node.Properties) {
102
var edge = property.Edge;
103
if (edge != null && edge.Target != null) {
104
if (!seenNodes.Contains(edge.Target)) {
106
seenNodes.Add(edge.Target);
107
q.Enqueue(edge.Target);
115
void CalculateSubtreeSizesRecursive(PositionedNode root, HashSet<PositionedEdge> treeEdges)
117
double subtreeSize = 0;
118
foreach (var child in TreeChildNodes(root, treeEdges)) {
119
CalculateSubtreeSizesRecursive(child, treeEdges);
120
// just sum up the sizes of children
121
subtreeSize += child.SubtreeSize;
123
root.SubtreeSize = Math.Max(GetLateralSizeWithMargin(root), subtreeSize);
128
/// Given SubtreeSize for each node, positions the nodes, in a left-to-right or top-to-bottom layout.
130
/// <param name="node"></param>
131
/// <param name="lateralStart"></param>
132
/// <param name="mainStart"></param>
133
void CalculateNodePosRecursive(PositionedNode node, HashSet<PositionedEdge> treeEdges, double lateralBase, double mainBase)
135
double childsSubtreeSize = TreeChildNodes(node, treeEdges).Sum(child => child.SubtreeSize);
136
double center = TreeEdges(node, treeEdges).Count() == 0 ? 0 : 0.5 * (childsSubtreeSize - (GetLateralSizeWithMargin(node)));
138
// if root is larger than subtree, it would be shifted below lateralStart
139
// -> make whole layout start at lateralStart
140
lateralBase -= center;
143
SetLateral(node, GetLateral(node) + lateralBase + center);
144
SetMain(node, mainBase);
146
double childLateral = lateralBase;
147
double childsMainFixed = GetMain(node) + GetMainSizeWithMargin(node);
148
foreach (var child in TreeChildNodes(node, treeEdges)) {
149
CalculateNodePosRecursive(child, treeEdges, childLateral, childsMainFixed);
150
childLateral += child.SubtreeSize;
154
IEnumerable<PositionedEdge> TreeEdges(PositionedNode node, HashSet<PositionedEdge> treeEdges)
156
return node.Edges.Where(e => treeEdges.Contains(e));
159
IEnumerable<PositionedNode> TreeChildNodes(PositionedNode node, HashSet<PositionedEdge> treeEdges)
161
return TreeEdges(node, treeEdges).Select(e => e.Target);
164
#region Horizontal / vertical layout helpers
166
double GetMainSizeWithMargin(PositionedNode node)
168
return (this.LayoutDirection == LayoutDirection.LeftRight) ? node.Width + NodeMarginH : node.Height + NodeMarginV;
171
double GetLateralSizeWithMargin(PositionedNode node)
173
return (this.LayoutDirection == LayoutDirection.LeftRight) ? node.Height + NodeMarginV : node.Width + NodeMarginH;
176
double GetMain(PositionedNode node)
178
return (this.LayoutDirection == LayoutDirection.LeftRight) ? node.Left : node.Top;
181
double GetLateral(PositionedNode node)
183
return (this.LayoutDirection == LayoutDirection.LeftRight) ? node.Top : node.Left;
186
void SetMain(PositionedNode node, double value)
188
if (this.LayoutDirection == LayoutDirection.LeftRight) {
195
void SetLateral(PositionedNode node, double value)
197
if (this.LayoutDirection == LayoutDirection.LeftRight) {