UNPKG

11.8 kBPlain TextView Raw
1'use strict';
2/*
3* Copyright (C) 1998-2021 by Northwoods Software Corporation. All Rights Reserved.
4*/
5
6import * as go from '../release/go.js';
7
8// A custom layout that shows the two families related to a person's parents
9export class GenogramLayout extends go.LayeredDigraphLayout {
10 public spouseSpacing: number;
11
12 public constructor() {
13 super();
14 this.initializeOption = go.LayeredDigraphLayout.InitDepthFirstIn;
15 this.spouseSpacing = 30; // minimum space between spouses
16 }
17
18 public makeNetwork(coll: go.Diagram | go.Group | go.Iterable<go.Part>) {
19 // generate LayoutEdges for each parent-child Link
20 const net = this.createNetwork();
21 if (coll instanceof go.Diagram) {
22 this.add(net, coll.nodes, true);
23 this.add(net, coll.links, true);
24 } else if (coll instanceof go.Group) {
25 this.add(net, coll.memberParts, false);
26 } else if (coll.iterator) {
27 this.add(net, coll.iterator, false);
28 }
29 return net;
30 }
31
32 // internal method for creating LayeredDigraphNetwork where husband/wife pairs are represented
33 // by a single LayeredDigraphVertex corresponding to the label Node on the marriage Link
34 protected add(net: go.LayeredDigraphNetwork, coll: go.Iterable<go.Part>, nonmemberonly: boolean) {
35 const multiSpousePeople = new go.Set() as go.Set<go.Node>;
36 // consider all Nodes in the given collection
37 const it = coll.iterator;
38 while (it.next()) {
39 const node = it.value as go.Node;
40 if (!(node instanceof go.Node)) continue;
41 if (!node.isLayoutPositioned || !node.isVisible()) continue;
42 if (nonmemberonly && node.containingGroup !== null) continue;
43 // if it's an unmarried Node, or if it's a Link Label Node, create a LayoutVertex for it
44 if (node.isLinkLabel) {
45 // get marriage Link
46 const link = node.labeledLink;
47 if (link) {
48 const spouseA = link.fromNode;
49 const spouseB = link.toNode;
50 // create vertex representing both husband and wife
51 const vertex = net.addNode(node);
52 // now define the vertex size to be big enough to hold both spouses
53 if (spouseA && spouseB) {
54 vertex.width = spouseA.actualBounds.width + this.spouseSpacing + spouseB.actualBounds.width;
55 vertex.height = Math.max(spouseA.actualBounds.height, spouseB.actualBounds.height);
56 vertex.focus = new go.Point(spouseA.actualBounds.width + this.spouseSpacing / 2, vertex.height / 2);
57 }
58 }
59 } else {
60 // don't add a vertex for any married person!
61 // instead, code above adds label node for marriage link
62 // assume a marriage Link has a label Node
63 let marriages = 0;
64 node.linksConnected.each(l => { if (l.isLabeledLink) marriages++; });
65 if (marriages === 0) {
66 const vertex = net.addNode(node);
67 } else if (marriages > 1) {
68 multiSpousePeople.add(node);
69 }
70 }
71 }
72 // now do all Links
73 it.reset();
74 while (it.next()) {
75 const link = it.value as go.Link;
76 if (!(link instanceof go.Link)) continue;
77 if (!link.isLayoutPositioned || !link.isVisible()) continue;
78 if (nonmemberonly && link.containingGroup !== null) continue;
79 // if it's a parent-child link, add a LayoutEdge for it
80 if (!link.isLabeledLink) {
81 const fromNode = link.fromNode;
82 const toNode = link.toNode;
83 if (fromNode !== null && toNode !== null) {
84 const parent = net.findVertex(fromNode); // should be a label node
85 const child = net.findVertex(toNode);
86 if (parent !== null && child !== null) { // an unmarried child
87 net.linkVertexes(parent, child, link);
88 } else if (parent !== null) { // a married child
89 toNode.linksConnected.each(l => {
90 if (!l.isLabeledLink) return; // if it has no label node, it's a parent-child link
91 // found the Marriage Link, now get its label Node
92 const mlab = l.labelNodes.first();
93 // parent-child link should connect with the label node,
94 // so the LayoutEdge should connect with the LayoutVertex representing the label node
95 if (mlab !== null) {
96 const mlabvert = net.findVertex(mlab);
97 if (mlabvert !== null) {
98 net.linkVertexes(parent, mlabvert, link);
99 }
100 }
101 });
102 }
103 }
104 }
105 }
106
107 while (multiSpousePeople.count > 0) {
108 // find all collections of people that are indirectly married to each other
109 const node = multiSpousePeople.first() as go.Node;
110 const cohort = new go.Set() as go.Set<go.Node>;
111 this.extendCohort(cohort, node);
112 // then encourage them all to be the same generation by connecting them all with a common vertex
113 const dummyvert = net.createVertex();
114 net.addVertex(dummyvert);
115 const marriages = new go.Set() as go.Set<go.Link>;
116 cohort.each(n => {
117 n.linksConnected.each(l => {
118 marriages.add(l);
119 });
120 });
121 marriages.each(link => {
122 // find the vertex for the marriage link (i.e. for the label node)
123 const mlab = link.labelNodes.first();
124 if (mlab !== null) {
125 const v = net.findVertex(mlab);
126 if (v !== null) {
127 net.linkVertexes(dummyvert, v, null);
128 }
129 }
130 });
131 // done with these people, now see if there are any other multiple-married people
132 multiSpousePeople.removeAll(cohort);
133 }
134 }
135
136 // collect all of the people indirectly married with a person
137 protected extendCohort(coll: go.Set<go.Node>, node: go.Node) {
138 if (coll.contains(node)) return;
139 coll.add(node);
140 const lay = this;
141 node.linksConnected.each(l => {
142 if (l.isLabeledLink) { // if it's a marriage link, continue with both spouses
143 if (l.fromNode !== null) lay.extendCohort(coll, l.fromNode);
144 if (l.toNode !== null) lay.extendCohort(coll, l.toNode);
145 }
146 });
147 }
148
149 public assignLayers() {
150 super.assignLayers();
151 const horiz = this.direction === 0.0 || this.direction === 180.0;
152 // for every vertex, record the maximum vertex width or height for the vertex's layer
153 const maxsizes = [] as Array<number>;
154 const net = this.network;
155 if (net !== null) {
156 const vit = net.vertexes.iterator;
157 while (vit.next()) {
158 const v = vit.value as go.LayeredDigraphVertex;
159 const lay = v.layer;
160 let max = maxsizes[lay];
161 if (max === undefined) max = 0;
162 const sz = (horiz ? v.width : v.height);
163 if (sz > max) maxsizes[lay] = sz;
164 }
165 vit.reset();
166 // now make sure every vertex has the maximum width or height according to which layer it is in,
167 // and aligned on the left (if horizontal) or the top (if vertical)
168 while (vit.next()) {
169 const v = vit.value as go.LayeredDigraphVertex;
170 const lay = v.layer;
171 const max = maxsizes[lay];
172 if (horiz) {
173 v.focus = new go.Point(0, v.height / 2);
174 v.width = max;
175 } else {
176 v.focus = new go.Point(v.width / 2, 0);
177 v.height = max;
178 }
179 }
180 // from now on, the LayeredDigraphLayout will think that the Node is bigger than it really is
181 // (other than the ones that are the widest or tallest in their respective layer).
182 }
183 }
184
185 public commitNodes() {
186 super.commitNodes();
187 const net = this.network;
188 // position regular nodes
189 if (net !== null) {
190 const vit = net.vertexes.iterator;
191 while (vit.next()) {
192 const v = vit.value as go.LayeredDigraphVertex;
193 if (v.node !== null && !v.node.isLinkLabel) {
194 v.node.position = new go.Point(v.x, v.y);
195 }
196 }
197 vit.reset();
198 // position the spouses of each marriage vertex
199 const layout = this;
200 while (vit.next()) {
201 const v = vit.value as go.LayeredDigraphVertex;
202 if (v.node === null) continue;
203 if (!v.node.isLinkLabel) continue;
204 const labnode = v.node;
205 const lablink = labnode.labeledLink;
206 if (lablink !== null) {
207 // In case the spouses are not actually moved, we need to have the marriage link
208 // position the label node, because LayoutVertex.commit() was called above on these vertexes.
209 // Alternatively we could override LayoutVetex.commit to be a no-op for label node vertexes.
210 lablink.invalidateRoute();
211 let spouseA = lablink.fromNode;
212 let spouseB = lablink.toNode;
213 if (spouseA !== null && spouseB != null) {
214 // prefer fathers on the left, mothers on the right
215 if (spouseA.data.s === 'F') { // sex is female
216 const temp = spouseA;
217 spouseA = spouseB;
218 spouseB = temp;
219 }
220 // see if the parents are on the desired sides, to avoid a link crossing
221 const aParentsNode = layout.findParentsMarriageLabelNode(spouseA);
222 const bParentsNode = layout.findParentsMarriageLabelNode(spouseB);
223 if (aParentsNode !== null && bParentsNode !== null && aParentsNode.position.x > bParentsNode.position.x) {
224 // swap the spouses
225 const temp = spouseA;
226 spouseA = spouseB;
227 spouseB = temp;
228 }
229 spouseA.position = new go.Point(v.x, v.y);
230 spouseB.position = new go.Point(v.x + spouseA.actualBounds.width + layout.spouseSpacing, v.y);
231 if (spouseA.opacity === 0) {
232 const pos = new go.Point(v.centerX - spouseA.actualBounds.width / 2, v.y);
233 spouseA.position = pos;
234 spouseB.position = pos;
235 } else if (spouseB.opacity === 0) {
236 const pos = new go.Point(v.centerX - spouseB.actualBounds.width / 2, v.y);
237 spouseA.position = pos;
238 spouseB.position = pos;
239 }
240 }
241 }
242 }
243 vit.reset();
244 // position only-child nodes to be under the marriage label node
245 while (vit.next()) {
246 const v = vit.value as go.LayeredDigraphVertex;
247 if (v.node === null || v.node.linksConnected.count > 1) continue;
248 const mnode = layout.findParentsMarriageLabelNode(v.node);
249 if (mnode !== null && mnode.linksConnected.count === 1) { // if only one child
250 if (layout.network === null) continue;
251 const mvert = layout.network.findVertex(mnode);
252 if (mvert !== null) {
253 const newbnds = v.node.actualBounds.copy();
254 newbnds.x = mvert.centerX - v.node.actualBounds.width / 2;
255 // see if there's any empty space at the horizontal mid-point in that layer
256 if (layout.diagram !== null) {
257 const overlaps = layout.diagram.findObjectsIn(newbnds,
258 (x) => { const p = x.part; return (p instanceof go.Part) ? p : null; },
259 (p) => p !== v.node,
260 true
261 );
262 if (overlaps.count === 0) {
263 v.node.move(newbnds.position);
264 }
265 }
266 }
267 }
268 }
269 }
270 }
271
272 public findParentsMarriageLabelNode(node: go.Node) {
273 const it = node.findNodesInto();
274 while (it.next()) {
275 const n = it.value;
276 if (n.isLinkLabel) return n;
277 }
278 return null;
279 }
280}
281// end GenogramLayout class