1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | import * as go from '../release/go.js';
|
7 | import { GenogramLayout } from './GenogramLayout.js';
|
8 |
|
9 | export function init() {
|
10 | if ((window as any).goSamples) (window as any).goSamples();
|
11 | const $ = go.GraphObject.make;
|
12 | const myDiagram =
|
13 | $(go.Diagram, 'myDiagramDiv',
|
14 | {
|
15 | initialAutoScale: go.Diagram.Uniform,
|
16 | 'undoManager.isEnabled': true,
|
17 |
|
18 | nodeSelectionAdornmentTemplate:
|
19 | $(go.Adornment, 'Auto',
|
20 | { layerName: 'Grid' },
|
21 | $(go.Shape, 'Circle', { fill: 'yellow', stroke: null }),
|
22 | $(go.Placeholder)
|
23 | ),
|
24 | layout:
|
25 | $(GenogramLayout, { direction: 90, layerSpacing: 30, columnSpacing: 10 })
|
26 | });
|
27 |
|
28 |
|
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 |
|
49 |
|
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 |
|
75 |
|
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 |
|
101 |
|
102 | myDiagram.nodeTemplateMap.add('M',
|
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 | {
|
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',
|
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 | {
|
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 |
|
155 | myDiagram.nodeTemplateMap.add('LinkLabel',
|
156 | $(go.Node, { selectable: false, width: 1, height: 1, fromEndSegmentLength: 20 }));
|
157 |
|
158 |
|
159 | myDiagram.linkTemplate =
|
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',
|
170 | $(go.Link,
|
171 | { selectable: false },
|
172 | $(go.Shape, { strokeWidth: 2, stroke: 'blue' })
|
173 | ));
|
174 |
|
175 |
|
176 |
|
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 |
|
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 |
|
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 );
|
225 | }
|
226 |
|
227 | interface Data {
|
228 | key: number;
|
229 |
|
230 | n: string;
|
231 | s: string;
|
232 | m: number;
|
233 | f: number;
|
234 | ux: number;
|
235 | vir: number;
|
236 | a: string;
|
237 | }
|
238 |
|
239 |
|
240 | export function setupDiagram(diagram: go.Diagram, array: Array<Object>, focusId: number) {
|
241 | diagram.model =
|
242 | go.GraphObject.make(go.GraphLinksModel,
|
243 | {
|
244 | linkLabelKeysProperty: 'labelKeys',
|
245 |
|
246 | nodeCategoryProperty: 's',
|
247 |
|
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 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 | }
|
265 | }
|
266 |
|
267 | export function findMarriage(diagram: go.Diagram, a: number, b: number) {
|
268 | const nodeA = diagram.findNodeForKey(a);
|
269 | const nodeB = diagram.findNodeForKey(b);
|
270 | if (nodeA !== null && nodeB !== null) {
|
271 | const it = nodeA.findLinksBetween(nodeB);
|
272 | while (it.next()) {
|
273 | const link = it.value;
|
274 |
|
275 | if (link.data !== null && link.data.category === 'Marriage') return link;
|
276 | }
|
277 | }
|
278 | return null;
|
279 | }
|
280 |
|
281 |
|
282 | export 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 |
|
295 | continue;
|
296 | }
|
297 | const link = findMarriage(diagram, key, wife);
|
298 | if (link === null) {
|
299 |
|
300 | const mlab = { s: 'LinkLabel' } as Data;
|
301 | model.addNodeData(mlab);
|
302 |
|
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 |
|
314 | continue;
|
315 | }
|
316 | const link = findMarriage(diagram, key, husband);
|
317 | if (link === null) {
|
318 |
|
319 | const mlab = { s: 'LinkLabel' } as Data;
|
320 | model.addNodeData(mlab);
|
321 |
|
322 | const mdata = { from: key, to: husband, labelKeys: [mlab.key], category: 'Marriage' };
|
323 | model.addLinkData(mdata);
|
324 | }
|
325 | }
|
326 | }
|
327 | }
|
328 | }
|
329 |
|
330 |
|
331 | export 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 |
|
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 | }
|