UNPKG

14 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';
7import { GenogramLayout } from './GenogramLayout.js';
8
9export function init() {
10 if ((window as any).goSamples) (window as any).goSamples(); // init for these samples -- you don't need to call this
11 const $ = go.GraphObject.make;
12 const myDiagram =
13 $(go.Diagram, 'myDiagramDiv',
14 {
15 initialAutoScale: go.Diagram.Uniform,
16 'undoManager.isEnabled': true,
17 // when a node is selected, draw a big yellow circle behind it
18 nodeSelectionAdornmentTemplate:
19 $(go.Adornment, 'Auto',
20 { layerName: 'Grid' }, // the predefined layer that is behind everything else
21 $(go.Shape, 'Circle', { fill: 'yellow', stroke: null }),
22 $(go.Placeholder)
23 ),
24 layout: // use a custom layout, defined below
25 $(GenogramLayout, { direction: 90, layerSpacing: 30, columnSpacing: 10 })
26 });
27
28 // determine the color for each attribute shape
29 function attrFill(a: string): string {
30 switch (a) {
31 case 'A': return 'green';
32 case 'B': return 'orange';
33 case 'C': return 'red';
34 case 'D': return 'cyan';
35 case 'E': return 'gold';
36 case 'F': return 'pink';
37 case 'G': return 'blue';
38 case 'H': return 'brown';
39 case 'I': return 'purple';
40 case 'J': return 'chartreuse';
41 case 'K': return 'lightgray';
42 case 'L': return 'magenta';
43 case 'S': return 'red';
44 default: return 'transparent';
45 }
46 }
47
48 // determine the geometry for each attribute shape in a male;
49 // except for the slash these are all squares at each of the four corners of the overall square
50 const tlsq = go.Geometry.parse('F M1 1 l19 0 0 19 -19 0z');
51 const trsq = go.Geometry.parse('F M20 1 l19 0 0 19 -19 0z');
52 const brsq = go.Geometry.parse('F M20 20 l19 0 0 19 -19 0z');
53 const blsq = go.Geometry.parse('F M1 20 l19 0 0 19 -19 0z');
54 const slash = go.Geometry.parse('F M38 0 L40 0 40 2 2 40 0 40 0 38z');
55 function maleGeometry(a: string): go.Geometry {
56 switch (a) {
57 case 'A': return tlsq;
58 case 'B': return tlsq;
59 case 'C': return tlsq;
60 case 'D': return trsq;
61 case 'E': return trsq;
62 case 'F': return trsq;
63 case 'G': return brsq;
64 case 'H': return brsq;
65 case 'I': return brsq;
66 case 'J': return blsq;
67 case 'K': return blsq;
68 case 'L': return blsq;
69 case 'S': return slash;
70 default: return tlsq;
71 }
72 }
73
74 // determine the geometry for each attribute shape in a female;
75 // except for the slash these are all pie shapes at each of the four quadrants of the overall circle
76 const tlarc = go.Geometry.parse('F M20 20 B 180 90 20 20 19 19 z');
77 const trarc = go.Geometry.parse('F M20 20 B 270 90 20 20 19 19 z');
78 const brarc = go.Geometry.parse('F M20 20 B 0 90 20 20 19 19 z');
79 const blarc = go.Geometry.parse('F M20 20 B 90 90 20 20 19 19 z');
80 function femaleGeometry(a: string): go.Geometry {
81 switch (a) {
82 case 'A': return tlarc;
83 case 'B': return tlarc;
84 case 'C': return tlarc;
85 case 'D': return trarc;
86 case 'E': return trarc;
87 case 'F': return trarc;
88 case 'G': return brarc;
89 case 'H': return brarc;
90 case 'I': return brarc;
91 case 'J': return blarc;
92 case 'K': return blarc;
93 case 'L': return blarc;
94 case 'S': return slash;
95 default: return tlarc;
96 }
97 }
98
99
100 // two different node templates, one for each sex,
101 // named by the category value in the node data object
102 myDiagram.nodeTemplateMap.add('M', // male
103 $(go.Node, 'Vertical',
104 { locationSpot: go.Spot.Center, locationObjectName: 'ICON' },
105 $(go.Panel,
106 { name: 'ICON' },
107 $(go.Shape, 'Square',
108 { width: 40, height: 40, strokeWidth: 2, fill: 'white', portId: '' }),
109 $(go.Panel,
110 { // for each attribute show a Shape at a particular place in the overall square
111 itemTemplate:
112 $(go.Panel,
113 $(go.Shape,
114 { stroke: null, strokeWidth: 0 },
115 new go.Binding('fill', '', attrFill),
116 new go.Binding('geometry', '', maleGeometry))
117 ),
118 margin: 1
119 },
120 new go.Binding('itemArray', 'a')
121 )
122 ),
123 $(go.TextBlock,
124 { textAlign: 'center', maxSize: new go.Size(80, NaN) },
125 new go.Binding('text', 'n'))
126 ));
127
128 myDiagram.nodeTemplateMap.add('F', // female
129 $(go.Node, 'Vertical',
130 { locationSpot: go.Spot.Center, locationObjectName: 'ICON' },
131 $(go.Panel,
132 { name: 'ICON' },
133 $(go.Shape, 'Circle',
134 { width: 40, height: 40, strokeWidth: 2, fill: 'white', portId: '' }),
135 $(go.Panel,
136 { // for each attribute show a Shape at a particular place in the overall circle
137 itemTemplate:
138 $(go.Panel,
139 $(go.Shape,
140 { stroke: null, strokeWidth: 0 },
141 new go.Binding('fill', '', attrFill),
142 new go.Binding('geometry', '', femaleGeometry))
143 ),
144 margin: 1
145 },
146 new go.Binding('itemArray', 'a')
147 )
148 ),
149 $(go.TextBlock,
150 { textAlign: 'center', maxSize: new go.Size(80, NaN) },
151 new go.Binding('text', 'n'))
152 ));
153
154 // the representation of each label node -- nothing shows on a Marriage Link
155 myDiagram.nodeTemplateMap.add('LinkLabel',
156 $(go.Node, { selectable: false, width: 1, height: 1, fromEndSegmentLength: 20 }));
157
158
159 myDiagram.linkTemplate = // for parent-child relationships
160 $(go.Link,
161 {
162 routing: go.Link.Orthogonal, curviness: 15,
163 layerName: 'Background', selectable: false,
164 fromSpot: go.Spot.Bottom, toSpot: go.Spot.Top
165 },
166 $(go.Shape, { strokeWidth: 2 })
167 );
168
169 myDiagram.linkTemplateMap.add('Marriage', // for marriage relationships
170 $(go.Link,
171 { selectable: false },
172 $(go.Shape, { strokeWidth: 2, stroke: 'blue' })
173 ));
174
175
176 // n: name, s: sex, m: mother, f: father, ux: wife, vir: husband, a: attributes/markers
177 setupDiagram(myDiagram, [
178 { key: 0, n: 'Aaron', s: 'M', m: -10, f: -11, ux: 1, a: ['C', 'F', 'K'] },
179 { key: 1, n: 'Alice', s: 'F', m: -12, f: -13, a: ['B', 'H', 'K'] },
180 { key: 2, n: 'Bob', s: 'M', m: 1, f: 0, ux: 3, a: ['C', 'H', 'L'] },
181 { key: 3, n: 'Barbara', s: 'F', a: ['C'] },
182 { key: 4, n: 'Bill', s: 'M', m: 1, f: 0, ux: 5, a: ['E', 'H'] },
183 { key: 5, n: 'Brooke', s: 'F', a: ['B', 'H', 'L'] },
184 { key: 6, n: 'Claire', s: 'F', m: 1, f: 0, a: ['C'] },
185 { key: 7, n: 'Carol', s: 'F', m: 1, f: 0, a: ['C', 'I'] },
186 { key: 8, n: 'Chloe', s: 'F', m: 1, f: 0, vir: 9, a: ['E'] },
187 { key: 9, n: 'Chris', s: 'M', a: ['B', 'H'] },
188 { key: 10, n: 'Ellie', s: 'F', m: 3, f: 2, a: ['E', 'G'] },
189 { key: 11, n: 'Dan', s: 'M', m: 3, f: 2, a: ['B', 'J'] },
190 { key: 12, n: 'Elizabeth', s: 'F', vir: 13, a: ['J'] },
191 { key: 13, n: 'David', s: 'M', m: 5, f: 4, a: ['B', 'H'] },
192 { key: 14, n: 'Emma', s: 'F', m: 5, f: 4, a: ['E', 'G'] },
193 { key: 15, n: 'Evan', s: 'M', m: 8, f: 9, a: ['F', 'H'] },
194 { key: 16, n: 'Ethan', s: 'M', m: 8, f: 9, a: ['D', 'K'] },
195 { key: 17, n: 'Eve', s: 'F', vir: 16, a: ['B', 'F', 'L'] },
196 { key: 18, n: 'Emily', s: 'F', m: 8, f: 9 },
197 { key: 19, n: 'Fred', s: 'M', m: 17, f: 16, a: ['B'] },
198 { key: 20, n: 'Faith', s: 'F', m: 17, f: 16, a: ['L'] },
199 { key: 21, n: 'Felicia', s: 'F', m: 12, f: 13, a: ['H'] },
200 { key: 22, n: 'Frank', s: 'M', m: 12, f: 13, a: ['B', 'H'] },
201
202 // "Aaron"'s ancestors
203 { key: -10, n: 'Paternal Grandfather', s: 'M', m: -33, f: -32, ux: -11, a: ['A', 'S'] },
204 { key: -11, n: 'Paternal Grandmother', s: 'F', a: ['E', 'S'] },
205 { key: -32, n: 'Paternal Great', s: 'M', ux: -33, a: ['F', 'H', 'S'] },
206 { key: -33, n: 'Paternal Great', s: 'F', a: ['S'] },
207 { key: -40, n: 'Great Uncle', s: 'M', m: -33, f: -32, a: ['F', 'H', 'S'] },
208 { key: -41, n: 'Great Aunt', s: 'F', m: -33, f: -32, a: ['B', 'I', 'S'] },
209 { key: -20, n: 'Uncle', s: 'M', m: -11, f: -10, a: ['A', 'S'] },
210
211 // "Alice"'s ancestors
212 { key: -12, n: 'Maternal Grandfather', s: 'M', ux: -13, a: ['D', 'L', 'S'] },
213 { key: -13, n: 'Maternal Grandmother', s: 'F', m: -31, f: -30, a: ['H', 'S'] },
214 { key: -21, n: 'Aunt', s: 'F', m: -13, f: -12, a: ['C', 'I'] },
215 { key: -22, n: 'Uncle', s: 'M', ux: -21 },
216 { key: -23, n: 'Cousin', s: 'M', m: -21, f: -22 },
217 { key: -30, n: 'Maternal Great', s: 'M', ux: -31, a: ['D', 'J', 'S'] },
218 { key: -31, n: 'Maternal Great', s: 'F', m: -50, f: -51, a: ['B', 'H', 'L', 'S'] },
219 { key: -42, n: 'Great Uncle', s: 'M', m: -30, f: -31, a: ['C', 'J', 'S'] },
220 { key: -43, n: 'Great Aunt', s: 'F', m: -30, f: -31, a: ['E', 'G', 'S'] },
221 { key: -50, n: 'Maternal Great Great', s: 'F', ux: -51, a: ['D', 'I', 'S'] },
222 { key: -51, n: 'Maternal Great Great', s: 'M', a: ['B', 'H', 'S'] }
223 ],
224 4 /* focus on this person */);
225}
226
227interface Data {
228 key: number;
229 // n: name, s: sex, m: mother, f: father, ux: wife, vir: husband, a: attributes/markers
230 n: string;
231 s: string;
232 m: number;
233 f: number;
234 ux: number;
235 vir: number;
236 a: string;
237}
238
239// create and initialize the Diagram.model given an array of node data representing people
240export function setupDiagram(diagram: go.Diagram, array: Array<Object>, focusId: number) {
241 diagram.model =
242 go.GraphObject.make(go.GraphLinksModel,
243 { // declare support for link label nodes
244 linkLabelKeysProperty: 'labelKeys',
245 // this property determines which template is used
246 nodeCategoryProperty: 's',
247 // create all of the nodes for people
248 nodeDataArray: array
249 });
250 setupMarriages(diagram);
251 setupParents(diagram);
252
253 const node = diagram.findNodeForKey(focusId);
254 if (node !== null) {
255 diagram.select(node);
256 // remove any spouse for the person under focus:
257 // node.linksConnected.each(l => {
258 // if (!l.isLabeledLink) return;
259 // l.opacity = 0;
260 // var spouse = l.getOtherNode(node);
261 // spouse.opacity = 0;
262 // spouse.pickable = false;
263 // });
264 }
265}
266
267export function findMarriage(diagram: go.Diagram, a: number, b: number) { // A and B are node keys
268 const nodeA = diagram.findNodeForKey(a);
269 const nodeB = diagram.findNodeForKey(b);
270 if (nodeA !== null && nodeB !== null) {
271 const it = nodeA.findLinksBetween(nodeB); // in either direction
272 while (it.next()) {
273 const link = it.value;
274 // Link.data.category === "Marriage" means it's a marriage relationship
275 if (link.data !== null && link.data.category === 'Marriage') return link;
276 }
277 }
278 return null;
279}
280
281// now process the node data to determine marriages
282export function setupMarriages(diagram: go.Diagram) {
283 const model = diagram.model as go.GraphLinksModel;
284 const nodeDataArray = model.nodeDataArray;
285 for (let i = 0; i < nodeDataArray.length; i++) {
286 const data = nodeDataArray[i] as Data;
287 const key = data.key;
288 if (data.ux !== undefined) {
289 let uxs: Array<number> = [];
290 if (typeof data.ux === 'number') uxs = [data.ux] as Array<number>;
291 for (let j = 0; j < uxs.length; j++) {
292 const wife = uxs[j];
293 if (key === wife) {
294 // or warn no reflexive marriages
295 continue;
296 }
297 const link = findMarriage(diagram, key, wife);
298 if (link === null) {
299 // add a label node for the marriage link
300 const mlab = { s: 'LinkLabel' } as Data;
301 model.addNodeData(mlab);
302 // add the marriage link itself, also referring to the label node
303 const mdata = { from: key, to: wife, labelKeys: [mlab.key], category: 'Marriage' };
304 model.addLinkData(mdata);
305 }
306 }
307 }
308 if (data.vir !== undefined) {
309 const virs: Array<number> = (typeof data.vir === 'number') ? [data.vir] : data.vir as Array<number>;
310 for (let j = 0; j < virs.length; j++) {
311 const husband = virs[j];
312 if (key === husband) {
313 // or warn no reflexive marriages
314 continue;
315 }
316 const link = findMarriage(diagram, key, husband);
317 if (link === null) {
318 // add a label node for the marriage link
319 const mlab = { s: 'LinkLabel' } as Data;
320 model.addNodeData(mlab);
321 // add the marriage link itself, also referring to the label node
322 const mdata = { from: key, to: husband, labelKeys: [mlab.key], category: 'Marriage' };
323 model.addLinkData(mdata);
324 }
325 }
326 }
327 }
328}
329
330// process parent-child relationships once all marriages are known
331export function setupParents(diagram: go.Diagram) {
332 const model = diagram.model as go.GraphLinksModel;
333 const nodeDataArray = model.nodeDataArray;
334 for (let i = 0; i < nodeDataArray.length; i++) {
335 const data = nodeDataArray[i] as Data;
336 const key = data.key;
337 const mother = data.m;
338 const father = data.f;
339 if (mother !== undefined && father !== undefined) {
340 const link = findMarriage(diagram, mother, father);
341 if (link === null) {
342 // or warn no known mother or no known father or no known marriage between them
343 if (window.console) window.console.log('unknown marriage: ' + mother + ' & ' + father);
344 continue;
345 }
346 const mdata = link.data;
347 const mlabkey = mdata.labelKeys[0];
348 const cdata = { from: mlabkey, to: key };
349 model.addLinkData(cdata);
350 }
351 }
352}