Skip to content

Commit 4933eec

Browse files
committed
Fix tree layout algorithm
1 parent 639ff22 commit 4933eec

File tree

2 files changed

+129
-51
lines changed

2 files changed

+129
-51
lines changed

PalCalc.UI/ViewModel/BreedingResultViewModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ public class BreedingResultViewModel : ObservableObject
1919
public BreedingResultViewModel()
2020
{
2121
var db = PalDB.LoadEmbedded();
22-
var latest = DirectSavesLocation.AllLocal.SelectMany(loc => loc.ValidSaveGames).MaxBy(game => game.LastModified);
23-
var saveGame = Storage.LoadSave(latest, db);
22+
var latest = DirectSavesLocation.AllLocal.SelectMany(loc => loc.ValidSaveGames).Where(s => Storage.LoadSaveFromCache(s, db) != null).MaxBy(game => game.LastModified);
23+
var saveGame = Storage.LoadSaveFromCache(latest, db);
2424

2525
var solver = new Solver.BreedingSolver(
2626
gameSettings: new GameSettings(),

PalCalc.UI/ViewModel/GraphSharp/BreedingTreeLayoutAlgorithm.cs

Lines changed: 127 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using PalCalc.Solver;
1111
using CommunityToolkit.Mvvm.ComponentModel;
1212
using System.Windows.Media.Animation;
13+
using QuickGraph.Serialization;
1314

1415
namespace PalCalc.UI.ViewModel.GraphSharp
1516
{
@@ -33,30 +34,96 @@ public object Clone()
3334

3435
// currently assuming left-to-right layout
3536
internal class BreedingTreeLayoutAlgorithm : DefaultParameterizedLayoutAlgorithmBase<BreedingTreeNodeViewModel, BreedingEdge, BreedingGraph, BreedingTreeLayoutAlgorithmParameters>
36-
3737
{
3838
private Dictionary<BreedingTreeNodeViewModel, Size> _sizes;
39+
private Dictionary<IBreedingTreeNode, BreedingTreeNodeViewModel> _viewmodels;
40+
private Dictionary<BreedingTreeNodeViewModel, Size> _fullSizes = new Dictionary<BreedingTreeNodeViewModel, Size>();
41+
private Dictionary<BreedingTreeNodeViewModel, Node> _nodesByVm;
42+
private Dictionary<IBreedingTreeNode, Node> _nodesByModel;
43+
44+
private class Node
45+
{
46+
private BreedingTreeLayoutAlgorithm algo;
47+
48+
public Node(BreedingTreeLayoutAlgorithm algo, BreedingTreeNodeViewModel asViewModel, IBreedingTreeNode asModel)
49+
{
50+
this.algo = algo;
51+
AsViewModel = asViewModel;
52+
AsModel = asModel;
53+
}
54+
55+
public BreedingTreeNodeViewModel AsViewModel { get; }
56+
public IBreedingTreeNode AsModel { get; }
57+
58+
public Point SelfCenter
59+
{
60+
get => algo.VertexPositions[AsViewModel];
61+
set => algo.VertexPositions[AsViewModel] = value;
62+
}
63+
public Size SelfSize => algo._sizes[AsViewModel];
64+
65+
public Size FullSize => algo.FullSizeWithChildren(AsViewModel);
66+
67+
public double SelfTopY
68+
{
69+
get => SelfCenter.Y - SelfSize.Height / 2;
70+
set
71+
{
72+
var p = SelfCenter;
73+
p.Y = value + SelfSize.Height / 2;
74+
SelfCenter = p;
75+
}
76+
}
77+
78+
public double SelfBottomY
79+
{
80+
get => SelfCenter.Y + SelfSize.Height / 2;
81+
set
82+
{
83+
var p = SelfCenter;
84+
p.Y = value - SelfSize.Height / 2;
85+
SelfCenter = p;
86+
}
87+
}
88+
89+
public double FullTopY => SelfCenter.Y - FullSize.Height / 2;
90+
public double FullBottomY => SelfCenter.Y + FullSize.Height / 2;
91+
}
3992

4093
public BreedingTreeLayoutAlgorithm(BreedingGraph visitedGraph, IDictionary<BreedingTreeNodeViewModel, Point> vertexPositions, IDictionary<BreedingTreeNodeViewModel, Size> vertexSizes, BreedingTreeLayoutAlgorithmParameters parameters)
4194
: base(visitedGraph, vertexPositions, parameters)
4295
{
4396
_sizes = new Dictionary<BreedingTreeNodeViewModel, Size>(vertexSizes);
97+
_viewmodels = visitedGraph.Edges.SelectMany(e => new[] { e.Source, e.Target }).Distinct().ToDictionary(vm => vm.Value);
98+
99+
_nodesByModel = new Dictionary<IBreedingTreeNode, Node>();
100+
_nodesByVm = new Dictionary<BreedingTreeNodeViewModel, Node>();
101+
foreach (var vm in _viewmodels.Values)
102+
{
103+
var node = new Node(this, vm, vm.Value);
104+
_nodesByModel.Add(vm.Value, node);
105+
_nodesByVm.Add(vm, node);
106+
}
44107
}
45108

46109
private Size FullSizeWithChildren(BreedingTreeNodeViewModel node)
47110
{
48-
var childrenByDepth = node.Value.TraversedTopDown(0).GroupBy(p => p.Item2).ToDictionary(g => g.Key, g => g.Select(p => p.Item1).ToList());
111+
if (_fullSizes.ContainsKey(node)) return _fullSizes[node];
49112

50-
var maxWidthByDepth = childrenByDepth.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Max(n => _sizes[VisitedGraph.NodeFor(n)].Width));
51-
var minHeightByDepth = childrenByDepth.ToDictionary(
52-
kvp => kvp.Key,
53-
kvp => Parameters.VertexGap * (kvp.Value.Count - 1) + kvp.Value.Sum(n => _sizes[VisitedGraph.NodeFor(n)].Height)
54-
);
113+
if (!node.Value.Children.Any())
114+
{
115+
var size = _sizes[node];
116+
_fullSizes[node] = size;
117+
return size;
118+
}
55119

56-
var fullWidth = (childrenByDepth.Count - 1) * Parameters.LayerGap + maxWidthByDepth.Values.Sum();
57-
var fullHeight = minHeightByDepth.Values.Max();
120+
Size fullSize = new Size(
121+
width: node.Value.Children.Max(n => FullSizeWithChildren(_viewmodels[n]).Width) + Parameters.LayerGap + _sizes[node].Width,
122+
height: Math.Max(node.Value.Children.Sum(c => FullSizeWithChildren(_viewmodels[c]).Height) + Parameters.VertexGap, _sizes[node].Height)
123+
);
58124

59-
return new Size(fullWidth, fullHeight);
125+
_fullSizes.Add(node, fullSize);
126+
return fullSize;
60127
}
61128

62129
protected override void InternalCompute()
@@ -66,72 +133,83 @@ protected override void InternalCompute()
66133
.ToDictionary(g => g.Key, g => g.Select(p => VisitedGraph.NodeFor(p.Item1)).ToList());
67134

68135
var fullSize = FullSizeWithChildren(VisitedGraph.NodeFor(VisitedGraph.Tree.Root));
69-
var layerX = fullSize.Width / 2;
136+
var layerCenterX = fullSize.Width / 2;
70137

138+
// node positions are centered within the node
71139
// start from root node, center-right
140+
double prevWidth = 0;
72141
foreach (var kvp in orderedNodesByDepth.OrderBy(kvp => kvp.Key))
73142
{
74-
layerX -= kvp.Value.Max(n => _sizes[n].Width);
75-
layerX -= Parameters.LayerGap;
76-
77143
var depth = kvp.Key;
78-
foreach (var node in kvp.Value)
144+
var width = kvp.Value.Max(n => _sizes[n].Width);
145+
146+
if (depth == 0)
147+
{
148+
layerCenterX -= width / 2; // properly align right-most (root) node with right side of tree
149+
}
150+
else
79151
{
152+
layerCenterX -= prevWidth / 2;
153+
layerCenterX -= Parameters.LayerGap;
154+
layerCenterX -= width / 2;
155+
}
156+
157+
foreach (var vmNode in kvp.Value)
158+
{
159+
var node = _nodesByVm[vmNode];
160+
80161
if (depth == 0)
81162
{
82-
VertexPositions[node] = new Point(layerX, 0);
163+
VertexPositions[vmNode] = new Point(layerCenterX, 0);
83164
continue;
84165
}
85166

86-
var parentNode = orderedNodesByDepth[depth - 1].Single(p => p.Value.Children.Contains(node.Value));
167+
// all of these nodes (depth > 0) will have a parent and a sibling
87168

88-
var parentHeight = _sizes[parentNode].Height;
89-
var parentPos = VertexPositions[parentNode];
90-
parentPos.Y += parentHeight / 2;
169+
// self Y will be the center of child Ys.
170+
// child Ys are based on their full height / 2
91171

92-
var selfTotalHeight = FullSizeWithChildren(node).Height;
93-
var parentTotalHeight = FullSizeWithChildren(parentNode).Height;
94-
172+
// ('Y' in this comment block refers to top of node)
173+
// parent Y will be avg(child1y [top], child2y [bottom])
174+
// child1y = parentFullTopY + child1FullHeight / 2
175+
// child2y = parentFullBottomY - child2FullHeight / 2
95176

96-
bool isFirstChild = parentNode.Value.Children.First() == node.Value;
177+
var parentVm = orderedNodesByDepth[depth - 1].Single(p => p.Value.Children.Contains(vmNode.Value));
178+
var parentNode = _nodesByVm[parentVm];
97179

98-
var selfY = isFirstChild
99-
? parentPos.Y - parentTotalHeight / 2
100-
: parentPos.Y + Parameters.VertexGap;
180+
bool isFirstChild = parentVm.Value.Children.First() == vmNode.Value;
181+
double nodeCenterY;
182+
if (isFirstChild)
183+
{
184+
nodeCenterY = parentNode.FullTopY + node.FullSize.Height / 2;
185+
}
186+
else
187+
{
188+
nodeCenterY = parentNode.FullBottomY - node.FullSize.Height / 2;
189+
}
101190

102-
VertexPositions[node] = new Point(layerX, selfY);
191+
node.SelfCenter = new Point(layerCenterX, nodeCenterY);
103192
}
193+
194+
prevWidth = width;
104195
}
105196

106197
// current positions are roughly accurate, but center each parent within their children
107198
foreach (var depth in orderedNodesByDepth.Keys.OrderByDescending(d => d))
108199
{
109-
foreach (var node in orderedNodesByDepth[depth])
200+
foreach (var nodeVm in orderedNodesByDepth[depth])
110201
{
111-
if (node.Value.Children.Any())
202+
if (nodeVm.Value.Children.Any())
112203
{
113-
var parent1 = VisitedGraph.NodeFor(node.Value.Children.First());
114-
var parent2 = VisitedGraph.NodeFor(node.Value.Children.Last());
115-
116-
var parent1Pos = VertexPositions[parent1];
117-
var parent2Pos = VertexPositions[parent2];
118-
119-
var parent1Center = new Point(
120-
parent1Pos.X + _sizes[parent1].Width / 2,
121-
parent1Pos.Y + _sizes[parent1].Height / 2
122-
);
204+
var node = _nodesByVm[nodeVm];
123205

124-
var parent2Center = new Point(
125-
parent2Pos.X + _sizes[parent2].Width / 2,
126-
parent2Pos.Y + _sizes[parent2].Height / 2
127-
);
206+
var parent1 = VisitedGraph.NodeFor(nodeVm.Value.Children.First());
207+
var parent2 = VisitedGraph.NodeFor(nodeVm.Value.Children.Last());
128208

129-
var newPos = new Point(
130-
VertexPositions[node].X,
131-
(parent1Center.Y + parent2Center.Y) / 2 - _sizes[node].Height / 2
132-
);
209+
var p1Node = _nodesByVm[parent1];
210+
var p2Node = _nodesByVm[parent2];
133211

134-
VertexPositions[node] = newPos;
212+
node.SelfCenter = new Point(node.SelfCenter.X, (p1Node.SelfBottomY + p2Node.SelfTopY) / 2);
135213
}
136214
}
137215
}

0 commit comments

Comments
 (0)