-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathFishboneLayout.cs
More file actions
301 lines (269 loc) · 10.7 KB
/
FishboneLayout.cs
File metadata and controls
301 lines (269 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
/*
* Copyright (c) Northwoods Software Corporation. All Rights Reserved.
*/
/*
* This is an extension and not part of the main Go library.
* Note that the API for this class may change with any version, even point releases.
* If you intend to use an extension in production, you should copy the code to your own source directory.
* Extensions can be found in the GoDiagram repository (https://github.com/NorthwoodsSoftware/GoDiagram/tree/main/Extensions).
* See the Extensions intro page (https://godiagram.com/intro/extensions.html) for more information.
*/
using System;
using System.Collections.Generic;
using System.Linq;
namespace Northwoods.Go.Layouts.Extensions {
/// <summary>
/// FishboneLayout is a custom <see cref="Layout"/> derived from <see cref="TreeLayout"/> for creating "fishbone" diagrams.
/// A fishbone diagram also requires a <see cref="Link"/> class that implements custom routing, <see cref="FishboneLink"/>.
/// </summary>
/// <remarks>
/// This only works for angle == 0 or angle == 180.
///
/// This layout assumes Links are automatically routed in the way needed by fishbone diagrams
/// by using the FishboneLink class instead of <see cref="Link"/>.
/// </remarks>
public class FishboneLayout : TreeLayout {
private readonly Dictionary<TreeVertex, int> _Direction = new();
/// <summary>
/// Create a Fishbone layout.
/// </summary>
public FishboneLayout() : base() {
Alignment = TreeAlignment.BusBranching;
SetsPortSpot = false;
SetsChildPortSpot = false;
}
/// <summary>
/// Create and initialize a <see cref="TreeNetwork"/> with the given nodes and links.
/// This override creates dummy vertexes, when necessary, to allow for proper positioning within the fishbone.
/// </summary>
/// <param name="coll"></param>
/// <returns><see cref="TreeNetwork"/></returns>
public override TreeNetwork MakeNetwork(IEnumerable<Part> coll = null) {
var net = base.MakeNetwork(coll);
if (net == null) return net;
// make a copy of the collection of TreeVertexes
// because we will be modifying the TreeNetwork.Vertexes collection in the loop
var verts = new List<TreeVertex>(net.Vertexes);
foreach (var v in verts) {
// ignore leaves of tree
if (v.DestinationEdges.Count == 0) continue;
if (v.DestinationEdges.Count % 2 == 1) {
// if there's an odd number of real children, add two dummies
var dummy = net.CreateVertex();
dummy.Bounds = new Rect();
dummy.Focus = new Point();
net.AddVertex(dummy);
net.LinkVertexes(v, dummy, null);
}
// make sure there's an odd number of children, including at least one dummy;
// CommitNodes will move the parent node to where this dummy child node is placed
var dummy2 = net.CreateVertex();
dummy2.Bounds = v.Bounds;
dummy2.Focus = v.Focus;
net.AddVertex(dummy2);
net.LinkVertexes(v, dummy2, null);
}
return net;
}
/// <summary>
/// Add a direction property to each vertex and modify <see cref="TreeVertex.LayerSpacing"/>.
/// </summary>
protected override void AssignTreeVertexValues(TreeVertex v) {
base.AssignTreeVertexValues(v);
_Direction[v] = 0; // assign direction to each TreeVertex
if (v.Parent != null) {
// The parent node will be moved to where the last dummy will be;
// reduce the space to account for the future hole.
if (v.Angle == 0 || v.Angle == 180) {
v.LayerSpacing -= v.Bounds.Width;
} else {
v.LayerSpacing -= v.Bounds.Height;
}
}
}
/// <inheritdoc/>
protected override void CommitNodes() {
if (Network == null) return;
// vertex Angle is set by BusBranching inheritance;
// assign spots assuming overall Angle == 0 or 180
// and links are always connecting horizontal with vertical
foreach (var e in Network.Edges) {
var link = e.Link;
if (link == null) continue;
link.FromSpot = Spot.None;
link.ToSpot = Spot.None;
var v = e.FromVertex as TreeVertex;
var w = e.ToVertex as TreeVertex;
if (v.Angle == 0) {
link.FromSpot = Spot.Left;
} else if (v.Angle == 180) {
link.FromSpot = Spot.Right;
}
if (w.Angle == 0) {
link.ToSpot = Spot.Left;
} else if (w.Angle == 180) {
link.ToSpot = Spot.Right;
}
}
// move the parent node to the location of the last dummy
foreach (var v in Network.Vertexes) {
var len = v.Children.Length;
if (len == 0) continue; // ignore leaf nodes
if (v.Parent == null) continue; // dont move root node
var dummy2 = v.Children[len - 1];
v.CenterX = dummy2.CenterX;
v.CenterY = dummy2.CenterY;
}
//perform a shift from the root
foreach (var v in Network.Vertexes) {
if (v.Parent == null) {
Shift(v);
}
}
// now actually change the Node.location of all nodes
base.CommitNodes();
}
/// <summary>
/// This override stops links from being committed since the work is handled by the <see cref="FishboneLink"/> class.
/// </summary>
protected override void CommitLinks() { }
/// <summary>
/// Shifts subtrees within the fishbone based on angle and node spacing.
/// </summary>
public void Shift(TreeVertex v) {
var p = v.Parent;
if (p != null && (v.Angle == 90 || v.Angle == 270)) {
var g = p.Parent;
if (g != null) {
var shift = v.NodeSpacing;
if (_Direction[g] > 0) {
if (g.Angle == 90) {
if (p.Angle == 0) {
_Direction[v] = 1;
if (v.Angle == 270) ShiftAll(2, -shift, p, v);
} else if (p.Angle == 180) {
_Direction[v] = -1;
if (v.Angle == 90) ShiftAll(-2, shift, p, v);
}
} else if (g.Angle == 270) {
if (p.Angle == 0) {
_Direction[v] = 1;
if (v.Angle == 90) ShiftAll(2, -shift, p, v);
} else if (p.Angle == 180) {
_Direction[v] = -1;
if (v.Angle == 270) ShiftAll(-2, shift, p, v);
}
}
} else if (_Direction[g] < 0) {
if (g.Angle == 90) {
if (p.Angle == 0) {
_Direction[v] = 1;
if (v.Angle == 90) ShiftAll(2, -shift, p, v);
} else if (p.Angle == 180) {
_Direction[v] = -1;
if (v.Angle == 270) ShiftAll(-2, shift, p, v);
}
} else if (g.Angle == 270) {
if (p.Angle == 0) {
_Direction[v] = 1;
if (v.Angle == 270) ShiftAll(2, -shift, p, v);
} else if (p.Angle == 180) {
_Direction[v] = -1;
if (v.Angle == 90) ShiftAll(-2, shift, p, v);
}
}
}
} else { // g == null: V is a child of the tree Root
var dir = p.Angle == 0 ? 1 : -1;
_Direction[v] = dir;
ShiftAll(dir, 0, p, v);
}
}
foreach (var c in v.Children) {
Shift(c);
}
}
/// <summary>
/// Shifts a subtree.
/// </summary>
public void ShiftAll(int direction, double absolute, TreeVertex root, TreeVertex v) {
var locx = v.CenterX;
locx += direction * Math.Abs(root.CenterY - v.CenterY) / 2;
locx += absolute;
v.CenterX = locx;
foreach (var c in v.Children) {
ShiftAll(direction, absolute, root, c);
}
}
// end Fishbone Layout
}
/// <summary>
/// Custom <see cref="Link"/> class for <see cref="FishboneLayout"/>.
/// </summary>
public class FishboneLink : Link {
/// <inheritdoc/>
public override LinkAdjusting ComputeAdjusting() {
return Adjusting;
}
/// <summary>
/// Determines the points for this link based on spots and maintains horizontal lines.
/// </summary>
public override bool ComputePoints() {
var result = base.ComputePoints();
if (result) {
// insert middle point to maintain horizontal lines
if (FromSpot == Spot.Right || FromSpot == Spot.Left) {
// deal with root node being on the wrong side
var fromnode = FromNode;
var fromport = FromPort;
Point p1;
if (fromnode != null && fromport != null && !fromnode.FindLinksInto().Any()) {
// pretend the link is coming from the opposite direction than the declared FromSpot
var fromctr = fromport.GetDocumentPoint(Spot.Center);
var fromfar = new Point(fromctr.X, fromctr.Y);
fromfar.X += (FromSpot == Spot.Left) ? 99999 : -99999;
p1 = GetLinkPointFromPoint(fromnode, fromport, fromctr, fromfar, true);
// update the route points
SetPoint(0, p1);
var endseg = FromEndSegmentLength;
if (double.IsNaN(endseg)) endseg = fromport.FromEndSegmentLength;
p1.X += (FromSpot == Spot.Left) ? endseg : -endseg;
SetPoint(1, p1);
} else {
p1 = GetPoint(1);
}
var tonode = ToNode;
var toport = ToPort;
if (tonode != null && toport != null) {
var toctr = toport.GetDocumentPoint(Spot.Center);
var far = new Point(toctr.X, toctr.Y);
far.X += (FromSpot == Spot.Left) ? -99999 / 2 : 99999 / 2;
far.Y += (toctr.Y < p1.Y) ? 99999 : -99999;
var p2 = GetLinkPointFromPoint(tonode, toport, toctr, far, false);
SetPoint(2, p2);
var dx = Math.Abs(p2.Y - p1.Y) / 2;
if (FromSpot == Spot.Left) dx = -dx;
InsertPoint(2, new Point(p2.X + dx, p1.Y));
}
} else if (ToSpot == Spot.Right || ToSpot == Spot.Left) {
var p1 = GetPoint(1);
var fromnode = FromNode;
var fromport = FromPort;
if (fromnode != null && FromPort != null) {
var parentlink = fromnode.FindLinksInto().First();
var fromctr = fromport.GetDocumentPoint(Spot.Center);
var far = new Point(fromctr.X, fromctr.Y);
far.X += (parentlink != null && parentlink.FromSpot == Spot.Left) ? -99999 / 2 : 99999 / 2;
far.Y += (fromctr.Y < p1.Y) ? 99999 : -99999;
var p0 = GetLinkPointFromPoint(fromnode, fromport, fromctr, far, true);
SetPoint(0, p0);
var dx = Math.Abs(p1.Y - p0.Y) / 2;
if (parentlink != null && parentlink.FromSpot == Spot.Left) dx = -dx;
InsertPoint(1, new Point(p0.X + dx, p1.Y));
}
}
}
return result;
}
}
}