gedcom-d3 2.0.8 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +8 -0
- package/d3ize.js +560 -559
- package/package.json +3 -2
- package/publish.sh +50 -0
package/d3ize.js
CHANGED
|
@@ -1,622 +1,623 @@
|
|
|
1
1
|
const d3ize = (tree) => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
2
|
+
const notes = tree.filter(hasTag("NOTE"));
|
|
3
|
+
let surnameList = [];
|
|
4
|
+
const peopleNodes = tree
|
|
5
|
+
.filter(hasTag("INDI"))
|
|
6
|
+
.map((p) => toNode(p, notes, surnameList));
|
|
7
|
+
const families = tree.filter(hasTag("FAM"));
|
|
8
|
+
const links = families.reduce((memo, family) => {
|
|
9
|
+
return memo.concat(familyLinks(family, peopleNodes));
|
|
10
|
+
}, []);
|
|
11
|
+
assignFy(peopleNodes, links);
|
|
12
|
+
return {
|
|
13
|
+
nodes: peopleNodes,
|
|
14
|
+
links: links,
|
|
15
|
+
families: families,
|
|
16
|
+
surnameList: surnameList,
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
19
|
|
|
20
20
|
// Tag search function
|
|
21
21
|
const hasTag = (val) => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
22
|
+
return (node) => {
|
|
23
|
+
return node.tag === val;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
26
|
|
|
27
27
|
// Data search function
|
|
28
28
|
const hasData = (val) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
29
|
+
return (node) => {
|
|
30
|
+
return node.data === val;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
33
|
|
|
34
34
|
// ID search function
|
|
35
35
|
const hasID = (val) => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
36
|
+
return (node) => {
|
|
37
|
+
return node.id === val;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
40
|
|
|
41
41
|
const assignFy = (peopleNodes, links) => {
|
|
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
|
-
}
|
|
42
|
+
// YOB known
|
|
43
|
+
let yesyob = peopleNodes.filter((p) => {
|
|
44
|
+
return p.yob !== "?" && !isNaN(+p.yob);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
yesyob.forEach((p) => (p.fy = +p.yob));
|
|
48
|
+
|
|
49
|
+
// YOB unknown
|
|
50
|
+
let noyob = peopleNodes.filter((p) => {
|
|
51
|
+
return p.yob === "?";
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
let count = 10;
|
|
55
|
+
|
|
56
|
+
// Cycle through list, adding fy until all complete
|
|
57
|
+
while (noyob.length > 0 && count > 0) {
|
|
58
|
+
let tempnoyob = noyob.slice();
|
|
59
|
+
|
|
60
|
+
tempnoyob.forEach((p, index) => {
|
|
61
|
+
// Build array of family
|
|
62
|
+
let tpFamily = [];
|
|
63
|
+
|
|
64
|
+
links.forEach((link) => {
|
|
65
|
+
if (link.source == p.id) {
|
|
66
|
+
tpFamily.push({
|
|
67
|
+
pRole: "source",
|
|
68
|
+
pType: link.sourceType,
|
|
69
|
+
other: link.target,
|
|
70
|
+
oType: link.targetType,
|
|
71
|
+
});
|
|
72
|
+
} else if (link.target == p.id) {
|
|
73
|
+
tpFamily.push({
|
|
74
|
+
pRole: "target",
|
|
75
|
+
pType: link.targetType,
|
|
76
|
+
other: link.source,
|
|
77
|
+
oType: link.sourceType,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Check family for YOB
|
|
83
|
+
tpFamily.forEach((member) => {
|
|
84
|
+
// USE SOME() INSTEAD OF FOREACH!!!
|
|
85
|
+
peopleNodes.forEach((person) => {
|
|
86
|
+
// USE SOME() INSTEAD OF FOREACH!!!
|
|
87
|
+
if (person.id == member.other && person.fy !== undefined) {
|
|
88
|
+
// Person is source
|
|
89
|
+
if (member.pRole === "source") {
|
|
90
|
+
// Person is husband
|
|
91
|
+
if (member.pType === "HUSB" && member.oType === "WIFE") {
|
|
92
|
+
p.fy = +person.fy - 3;
|
|
93
|
+
|
|
94
|
+
// Person is father
|
|
95
|
+
} else if (member.pType === "HUSB" && member.oType === "CHIL") {
|
|
96
|
+
p.fy = +person.fy - 30;
|
|
97
|
+
|
|
98
|
+
// Person is mother
|
|
99
|
+
} else if (member.pType === "WIFE") {
|
|
100
|
+
p.fy = +person.fy - 27;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Person is target
|
|
104
|
+
} else if (member.pRole === "target") {
|
|
105
|
+
// Person is wife
|
|
106
|
+
if (member.pType === "WIFE" && member.oType === "HUSB") {
|
|
107
|
+
p.fy = +person.fy + 3;
|
|
108
|
+
|
|
109
|
+
// Person is child of father
|
|
110
|
+
} else if (member.pType === "CHIL" && member.oType === "HUSB") {
|
|
111
|
+
p.fy = +person.fy + 30;
|
|
112
|
+
|
|
113
|
+
// Person is child of mother
|
|
114
|
+
} else if (member.pType === "CHIL" && member.oType === "WIFE") {
|
|
115
|
+
p.fy = +person.fy + 27;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
if (p.fy !== undefined) {
|
|
122
|
+
noyob.splice(index, index + 1);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
count -= 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const convertFy = (peopleNodes) => {
|
|
129
|
+
const fyRatio = (peopleNodes) => {
|
|
130
|
+
if (peopleNodes.length <= 50) {
|
|
131
|
+
return 3;
|
|
132
|
+
} else if (peopleNodes.length > 50 && peopleNodes.length <= 150) {
|
|
133
|
+
return 4;
|
|
134
|
+
} else if (peopleNodes.length > 150 && peopleNodes.length <= 250) {
|
|
135
|
+
return 5;
|
|
136
|
+
} else if (peopleNodes.length > 250) {
|
|
137
|
+
return 6;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
let allFy = [];
|
|
141
|
+
peopleNodes.forEach((p) => {
|
|
142
|
+
if (p.fy !== undefined) {
|
|
143
|
+
p.fy = p.fy * fyRatio(peopleNodes);
|
|
144
|
+
allFy.push(p.fy);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
let total = 0;
|
|
149
|
+
allFy.forEach((fy) => (total += fy));
|
|
150
|
+
let average = total / allFy.length;
|
|
151
|
+
|
|
152
|
+
peopleNodes.forEach((p) => {
|
|
153
|
+
if (p.fy !== undefined) {
|
|
154
|
+
p.fy = -(p.fy - average);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
convertFy(peopleNodes);
|
|
159
|
+
};
|
|
160
160
|
|
|
161
161
|
// Get title
|
|
162
162
|
const getTitle = (p) => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
163
|
+
const title = p.tree.filter(hasTag("TITL")) || [];
|
|
164
|
+
if (title.length > 0) {
|
|
165
|
+
return title[title.length - 1].data;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
168
|
|
|
169
169
|
// Get full name
|
|
170
170
|
const getName = (p) => {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
171
|
+
let nameNode = (p.tree.filter(hasTag("NAME")) || [])[0];
|
|
172
|
+
if (nameNode) {
|
|
173
|
+
return nameNode.data.replace(/\//g, "");
|
|
174
|
+
} else {
|
|
175
|
+
return "?";
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
178
|
|
|
179
179
|
// Get first name
|
|
180
180
|
const getFirstName = (p) => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
181
|
+
// Find 'NAME' tag
|
|
182
|
+
const nameNode = (p.tree.filter(hasTag("NAME")) || [])[0];
|
|
183
|
+
if (nameNode) {
|
|
184
|
+
// Find 'GIVN' tag
|
|
185
|
+
let firstNameNode = (nameNode.tree.filter(hasTag("GIVN")) || [])[0];
|
|
186
|
+
if (firstNameNode) {
|
|
187
|
+
// Remove middle name
|
|
188
|
+
if (firstNameNode.data.search(" ") !== -1) {
|
|
189
|
+
return firstNameNode.data.slice(0, firstNameNode.data.search(" "));
|
|
190
|
+
} else {
|
|
191
|
+
return firstNameNode.data;
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
return "?";
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
return "?";
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
200
|
|
|
201
201
|
// Get surname
|
|
202
202
|
const getSurname = (p) => {
|
|
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
|
-
}
|
|
203
|
+
// Find 'NAME' tag
|
|
204
|
+
const nameNode = (p.tree.filter(hasTag("NAME")) || [])[0];
|
|
205
|
+
if (nameNode) {
|
|
206
|
+
// Find 'SURN' tag
|
|
207
|
+
const surnameNode = (nameNode.tree.filter(hasTag("SURN")) || [])[0];
|
|
208
|
+
|
|
209
|
+
// If surname listed
|
|
210
|
+
if (surnameNode) {
|
|
211
|
+
// Remove alternate surnames
|
|
212
|
+
if (surnameNode.data.search(",") !== -1) {
|
|
213
|
+
return surnameNode.data.slice(0, surnameNode.data.search(","));
|
|
214
|
+
} else {
|
|
215
|
+
return surnameNode.data;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Derive surname from name
|
|
219
|
+
} else {
|
|
220
|
+
nameArr = nameNode.data.split(" ");
|
|
221
|
+
|
|
222
|
+
// Look for forward slashes
|
|
223
|
+
let isSlashes = nameArr.some((str) => str[0] === "/");
|
|
224
|
+
if (isSlashes) {
|
|
225
|
+
return nameArr.find((str) => str[0] === "/").replace(/\//g, "");
|
|
226
|
+
|
|
227
|
+
// no slashes, use final item in array
|
|
228
|
+
} else {
|
|
229
|
+
nameArr[nameArr.length - 1] = nameArr[nameArr.length - 1].replace(
|
|
230
|
+
/\//g,
|
|
231
|
+
"",
|
|
232
|
+
);
|
|
233
|
+
return nameArr.length > 1 ? nameArr[nameArr.length - 1] : "Hrm";
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
return "?";
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
240
|
|
|
241
241
|
// Get gender
|
|
242
242
|
const getGender = (p) => {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
243
|
+
// Find 'SEX' tag
|
|
244
|
+
let genderNode = (p.tree.filter(hasTag("SEX")) || [])[0];
|
|
245
|
+
if (genderNode) {
|
|
246
|
+
return genderNode.data;
|
|
247
|
+
} else {
|
|
248
|
+
return "Unknown";
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
251
|
|
|
252
252
|
// Get date of birth
|
|
253
253
|
const getDOB = (p) => {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
}
|
|
254
|
+
// Find 'BIRT' tag
|
|
255
|
+
let dobNode = (p.tree.filter(hasTag("BIRT")) || [])[0];
|
|
256
|
+
if (dobNode) {
|
|
257
|
+
// Find 'DATE' tag
|
|
258
|
+
let dateNode = (dobNode.tree.filter(hasTag("DATE")) || [])[0];
|
|
259
|
+
if (dateNode) {
|
|
260
|
+
return dateNode.data;
|
|
261
|
+
} else {
|
|
262
|
+
return "?";
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
return "?";
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
268
|
|
|
269
269
|
// Get year of birth
|
|
270
270
|
const getYOB = (p) => {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
271
|
+
// Find 'BIRT' tag
|
|
272
|
+
let dobNode = (p.tree.filter(hasTag("BIRT")) || [])[0];
|
|
273
|
+
if (dobNode) {
|
|
274
|
+
// Find 'DATE' tag
|
|
275
|
+
let dateNode = (dobNode.tree.filter(hasTag("DATE")) || [])[0];
|
|
276
|
+
if (dateNode) {
|
|
277
|
+
return dateNode.data.slice(-4);
|
|
278
|
+
} else {
|
|
279
|
+
return "?";
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
return "?";
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
285
|
|
|
286
286
|
// Get place of birth
|
|
287
287
|
const getPOB = (p) => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
288
|
+
// Find 'BIRT' tag
|
|
289
|
+
let pobNode = (p.tree.filter(hasTag("BIRT")) || [])[0];
|
|
290
|
+
if (pobNode) {
|
|
291
|
+
// Find 'DATE' tag
|
|
292
|
+
let placeNode = (pobNode.tree.filter(hasTag("PLAC")) || [])[0];
|
|
293
|
+
if (placeNode) {
|
|
294
|
+
return placeNode.data;
|
|
295
|
+
} else {
|
|
296
|
+
return "";
|
|
297
|
+
}
|
|
298
|
+
} else {
|
|
299
|
+
return "";
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
302
|
|
|
303
303
|
// Get date of death
|
|
304
304
|
const getDOD = (p) => {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
305
|
+
// Find 'DEAT' tag
|
|
306
|
+
let dobNode = (p.tree.filter(hasTag("BIRT")) || [])[0];
|
|
307
|
+
let dodNode = (p.tree.filter(hasTag("DEAT")) || [])[0];
|
|
308
|
+
if (dodNode) {
|
|
309
|
+
// Find 'DATE' tag
|
|
310
|
+
let dateNode = (dodNode.tree.filter(hasTag("DATE")) || [])[0];
|
|
311
|
+
if (dateNode) {
|
|
312
|
+
return dateNode.data;
|
|
313
|
+
} else {
|
|
314
|
+
return "?";
|
|
315
|
+
}
|
|
316
|
+
} else if (dobNode) {
|
|
317
|
+
let dateNode = (dobNode.tree.filter(hasTag("DATE")) || [])[0];
|
|
318
|
+
if (dateNode) {
|
|
319
|
+
return dateNode.data.slice(-4) + 100;
|
|
320
|
+
} else {
|
|
321
|
+
return "?";
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
return "Present";
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
327
|
|
|
328
328
|
// Get year of death
|
|
329
329
|
const getYOD = (p) => {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
330
|
+
let thisYear = new Date().getFullYear();
|
|
331
|
+
|
|
332
|
+
// Find 'DEAT' tag
|
|
333
|
+
let dobNode = (p.tree.filter(hasTag("BIRT")) || [])[0];
|
|
334
|
+
let dodNode = (p.tree.filter(hasTag("DEAT")) || [])[0];
|
|
335
|
+
|
|
336
|
+
// If DEATH tag
|
|
337
|
+
if (dodNode) {
|
|
338
|
+
// Find 'DATE' tag
|
|
339
|
+
let dateNode = (dodNode.tree.filter(hasTag("DATE")) || [])[0];
|
|
340
|
+
|
|
341
|
+
// If death date listed
|
|
342
|
+
if (dateNode) {
|
|
343
|
+
return dateNode.data.slice(-4);
|
|
344
|
+
} else {
|
|
345
|
+
return "?";
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// BIRT tag, but no DEAT tag
|
|
349
|
+
} else if (dobNode && !dodNode) {
|
|
350
|
+
let dateNode = (dobNode.tree.filter(hasTag("DATE")) || [])[0];
|
|
351
|
+
|
|
352
|
+
// If DOB listed
|
|
353
|
+
if (dateNode) {
|
|
354
|
+
// If born > 100 yrs ago, call dead
|
|
355
|
+
if (dateNode.data.slice(-4) < thisYear - 100) {
|
|
356
|
+
return "?";
|
|
357
|
+
} else {
|
|
358
|
+
return "Present";
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
return "?";
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// no DEAT or BIRT tag
|
|
365
|
+
} else {
|
|
366
|
+
return "?";
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
369
|
|
|
370
370
|
// Get place of birth
|
|
371
371
|
const getPOD = (p) => {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
372
|
+
// Find 'BIRT' tag
|
|
373
|
+
let podNode = (p.tree.filter(hasTag("DEAT")) || [])[0];
|
|
374
|
+
if (podNode) {
|
|
375
|
+
// Find 'DATE' tag
|
|
376
|
+
let placeNode = (podNode.tree.filter(hasTag("PLAC")) || [])[0];
|
|
377
|
+
if (placeNode) {
|
|
378
|
+
return placeNode.data;
|
|
379
|
+
} else {
|
|
380
|
+
return "";
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
return "";
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
386
|
|
|
387
387
|
// Get relatives
|
|
388
388
|
const getFamilies = (p) => {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
389
|
+
let families = [];
|
|
390
|
+
let pediInfo;
|
|
391
|
+
// If child
|
|
392
|
+
let familyNode1 = p.tree.filter(hasTag("FAMC")) || [];
|
|
393
|
+
if (familyNode1) {
|
|
394
|
+
for (let i = 0; i < familyNode1.length; i++) {
|
|
395
|
+
if (familyNode1[i].tree.length > 0) {
|
|
396
|
+
// Get pedigree info
|
|
397
|
+
if (familyNode1[i].tree[0].tag == "PEDI") {
|
|
398
|
+
pediInfo = {
|
|
399
|
+
frel: familyNode1[i].tree[0].data,
|
|
400
|
+
mrel: familyNode1[i].tree[0].data,
|
|
401
|
+
};
|
|
402
|
+
} else if (familyNode1[i].tree[0].tag == "_FREL") {
|
|
403
|
+
pediInfo = {
|
|
404
|
+
frel: familyNode1[i].tree[0].data,
|
|
405
|
+
mrel: familyNode1[i].tree[1].data,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
families.push({ id: familyNode1[i].data, pedi: pediInfo });
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
let familyNode2 = p.tree.filter(hasTag("FAMS")) || [];
|
|
414
|
+
if (familyNode2) {
|
|
415
|
+
for (let i = 0; i < familyNode2.length; i++) {
|
|
416
|
+
families.push({ id: familyNode2[i].data });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return families;
|
|
420
|
+
};
|
|
421
421
|
|
|
422
422
|
// Get color
|
|
423
423
|
const getColor = (p, surnameList) => {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
424
|
+
const colorList = [
|
|
425
|
+
"#f6edd0", // cream
|
|
426
|
+
"#ef8a65", // coral
|
|
427
|
+
"#00b4ff", // sky blue
|
|
428
|
+
"#fac641", // mexican egg yolk
|
|
429
|
+
"#c8d84c", // olive
|
|
430
|
+
"#e1b386", // light brown
|
|
431
|
+
"#a5c2cc", // light blue grey
|
|
432
|
+
"#e87c76", // soft pink
|
|
433
|
+
"#d0a8ec", // soft royal purple
|
|
434
|
+
"#8ad5b2", // grass & sage
|
|
435
|
+
"#f8a7d0", // dry wine
|
|
436
|
+
"#6e90e6", // ligt purple blue
|
|
437
|
+
"#a6e9e6", // sea foam
|
|
438
|
+
"#df9ac1", // magenta
|
|
439
|
+
"#4ae9bc", // forest
|
|
440
|
+
"#e08e79", // blush
|
|
441
|
+
"#80d152", // neon green
|
|
442
|
+
"#e7c34e", // tangerine
|
|
443
|
+
"#7ff0ca", // light sea foam
|
|
444
|
+
"#ff835a", // burnt orange
|
|
445
|
+
"#eebd6e", // chocolate
|
|
446
|
+
"#a6b890", // olive sage
|
|
447
|
+
"#c44d58", // rouge
|
|
448
|
+
"#e8b28e", // peach
|
|
449
|
+
"#d4ee5e", // lime
|
|
450
|
+
"#f3f621", // light yellow
|
|
451
|
+
"#e887aa", // newborn pink
|
|
452
|
+
"#c4a8f6", // royal purple
|
|
453
|
+
"#71cfde", // baby foam
|
|
454
|
+
"#ccc", // light grey
|
|
455
|
+
];
|
|
456
|
+
|
|
457
|
+
// If color description listed in GEDCOM
|
|
458
|
+
const dscr = (p.tree.filter(hasTag("DSCR")) || [])[0];
|
|
459
|
+
|
|
460
|
+
const foundName = surnameList.find((sName) => sName.surname === p.surname);
|
|
461
|
+
|
|
462
|
+
// If surname already in list
|
|
463
|
+
if (foundName) {
|
|
464
|
+
foundName.count = foundName.count + 1;
|
|
465
|
+
} else {
|
|
466
|
+
surnameList.push({
|
|
467
|
+
surname: p.surname,
|
|
468
|
+
count: 1,
|
|
469
|
+
color: colorList[surnameList.length % colorList.length],
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// surnameList.color = surnameList.length % colorList.length});
|
|
474
|
+
|
|
475
|
+
// If color listed assign that
|
|
476
|
+
if (dscr) {
|
|
477
|
+
return dscr.data;
|
|
478
|
+
|
|
479
|
+
// else assign color from colorList
|
|
480
|
+
} else {
|
|
481
|
+
return surnameList.find((sName) => sName.surname === p.surname).color;
|
|
482
|
+
}
|
|
483
|
+
};
|
|
483
484
|
|
|
484
485
|
// Get person notes
|
|
485
486
|
const getNotes = (p) => {
|
|
486
|
-
|
|
487
|
-
}
|
|
487
|
+
return p.tree.filter(hasTag("NOTE"));
|
|
488
|
+
};
|
|
488
489
|
|
|
489
490
|
// Get Bio
|
|
490
491
|
const getBio = (p, notes) => {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
492
|
+
if (p.notes.length != 0) {
|
|
493
|
+
let bio = "";
|
|
494
|
+
|
|
495
|
+
// Notes for person
|
|
496
|
+
p.notes.forEach((personNote) => {
|
|
497
|
+
// personNote.data points to NOTE object
|
|
498
|
+
if (notes.length > 0) {
|
|
499
|
+
notes.forEach((note) => {
|
|
500
|
+
if (personNote.data === note.pointer) {
|
|
501
|
+
bio += note.data;
|
|
502
|
+
|
|
503
|
+
// Concat broken up note
|
|
504
|
+
if (note.tree.length > 0) {
|
|
505
|
+
note.tree.forEach((fragment) => (bio += fragment.data));
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// personNote.data is actual note
|
|
511
|
+
} else {
|
|
512
|
+
bio += personNote.data;
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
return bio;
|
|
516
|
+
}
|
|
517
|
+
};
|
|
517
518
|
|
|
518
519
|
const getFy = (p) => {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}
|
|
520
|
+
if (p.yob === "?") {
|
|
521
|
+
return 0;
|
|
522
|
+
} else {
|
|
523
|
+
return +(-p.yob * 3 + 6000);
|
|
524
|
+
}
|
|
525
|
+
};
|
|
525
526
|
|
|
526
527
|
const toNode = (p, notes, surnameList) => {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
528
|
+
p.id = p.pointer;
|
|
529
|
+
p.title = getTitle(p);
|
|
530
|
+
p.name = getName(p);
|
|
531
|
+
p.firstName = getFirstName(p);
|
|
532
|
+
p.surname = getSurname(p);
|
|
533
|
+
p.gender = getGender(p);
|
|
534
|
+
p.dob = getDOB(p);
|
|
535
|
+
p.yob = getYOB(p);
|
|
536
|
+
p.pob = getPOB(p);
|
|
537
|
+
p.dod = getDOD(p);
|
|
538
|
+
p.yod = getYOD(p);
|
|
539
|
+
p.pod = getPOD(p);
|
|
540
|
+
p.families = getFamilies(p);
|
|
541
|
+
p.color = getColor(p, surnameList);
|
|
542
|
+
p.notes = getNotes(p);
|
|
543
|
+
p.bio = getBio(p, notes);
|
|
544
|
+
return p;
|
|
545
|
+
};
|
|
545
546
|
|
|
546
547
|
const familyLinks = (family, peopleNodes) => {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
module.exports = d3ize
|
|
548
|
+
let memberLinks = [];
|
|
549
|
+
let maritalStatus = null;
|
|
550
|
+
let pedigree;
|
|
551
|
+
|
|
552
|
+
// Filter only individual objects from family tree
|
|
553
|
+
let memberSet = family.tree.filter(function (member) {
|
|
554
|
+
return (
|
|
555
|
+
member.tag &&
|
|
556
|
+
(member.tag === "HUSB" || member.tag === "WIFE" || member.tag === "CHIL")
|
|
557
|
+
);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// Filter marital status events
|
|
561
|
+
family.tree.filter((event) => {
|
|
562
|
+
if (event.tag === "DIV" || event.tag === "MARR") {
|
|
563
|
+
if (maritalStatus !== "DIV") {
|
|
564
|
+
maritalStatus = event.tag;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// Iterate over each member of set to connect with other members
|
|
570
|
+
while (memberSet.length > 1) {
|
|
571
|
+
for (let i = 1; i < memberSet.length; i++) {
|
|
572
|
+
// Exclude sibling relationships
|
|
573
|
+
if (memberSet[0].tag != "CHIL") {
|
|
574
|
+
// If marital status listed
|
|
575
|
+
if (memberSet[0].tag == "HUSB" && memberSet[i].tag == "WIFE") {
|
|
576
|
+
memberLinks.push({
|
|
577
|
+
source: memberSet[0].data,
|
|
578
|
+
target: memberSet[i].data,
|
|
579
|
+
sourceType: memberSet[0].tag,
|
|
580
|
+
targetType: memberSet[i].tag,
|
|
581
|
+
type: maritalStatus,
|
|
582
|
+
});
|
|
583
|
+
} else {
|
|
584
|
+
// Filter pedigree info
|
|
585
|
+
function getPedigree(personID, parentType, relInfo) {
|
|
586
|
+
// GRAMPS
|
|
587
|
+
let person = peopleNodes.filter(hasID(personID));
|
|
588
|
+
let personFamily = person[0].families.filter(hasID(family.pointer));
|
|
589
|
+
if (parentType == "HUSB") {
|
|
590
|
+
if (personFamily[0].pedi) {
|
|
591
|
+
return personFamily[0].pedi.frel;
|
|
592
|
+
} else if (relInfo.some((parent) => parent.tag === "_FREL")) {
|
|
593
|
+
return relInfo.find((parent) => parent.tag === "_FREL").data;
|
|
594
|
+
}
|
|
595
|
+
} else {
|
|
596
|
+
if (personFamily[0].pedi) {
|
|
597
|
+
return personFamily[0].pedi.mrel;
|
|
598
|
+
} else if (relInfo.some((parent) => parent.tag === "_MREL")) {
|
|
599
|
+
return relInfo.find((parent) => parent.tag === "_MREL").data;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
memberLinks.push({
|
|
605
|
+
source: memberSet[0].data,
|
|
606
|
+
target: memberSet[i].data,
|
|
607
|
+
sourceType: memberSet[0].tag,
|
|
608
|
+
targetType: memberSet[i].tag,
|
|
609
|
+
type: getPedigree(
|
|
610
|
+
memberSet[i].data,
|
|
611
|
+
memberSet[0].tag,
|
|
612
|
+
memberSet[i].tree,
|
|
613
|
+
),
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
memberSet.splice(0, 1);
|
|
619
|
+
}
|
|
620
|
+
return memberLinks;
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
module.exports = d3ize;
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gedcom-d3",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "A GEDCOM parser, which translates GEDCOM (.ged) files into D3 capable JSON. This package is specifically designed to prepare data for use in https://github.com/oh-kay-blanket/blood-lines",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"publish": "./publish.sh minor"
|
|
8
9
|
},
|
|
9
10
|
"keywords": [
|
|
10
11
|
"gedcom",
|
package/publish.sh
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Publish a new version of gedcom-d3 to NPM
|
|
5
|
+
# Usage: ./publish.sh [major|minor|patch]
|
|
6
|
+
# Default: patch
|
|
7
|
+
|
|
8
|
+
VERSION_TYPE="${1:-patch}"
|
|
9
|
+
|
|
10
|
+
if [[ "$VERSION_TYPE" != "major" && "$VERSION_TYPE" != "minor" && "$VERSION_TYPE" != "patch" ]]; then
|
|
11
|
+
echo "Usage: ./publish.sh [major|minor|patch]"
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# Ensure working directory is clean
|
|
16
|
+
if [[ -n "$(git status --porcelain)" ]]; then
|
|
17
|
+
echo "Error: Working directory is not clean. Commit or stash changes first."
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Ensure we're on master
|
|
22
|
+
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
|
23
|
+
if [[ "$BRANCH" != "master" ]]; then
|
|
24
|
+
echo "Error: Must be on master branch to publish. Currently on '$BRANCH'."
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Ensure we're up to date with remote
|
|
29
|
+
git fetch origin master
|
|
30
|
+
LOCAL="$(git rev-parse master)"
|
|
31
|
+
REMOTE="$(git rev-parse origin/master)"
|
|
32
|
+
if [[ "$LOCAL" != "$REMOTE" ]]; then
|
|
33
|
+
echo "Error: Local master is not up to date with origin. Pull or push first."
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Bump version (this updates package.json and creates a git tag)
|
|
38
|
+
echo "Bumping $VERSION_TYPE version..."
|
|
39
|
+
npm version "$VERSION_TYPE" -m "v%s"
|
|
40
|
+
|
|
41
|
+
# Push commit and tag
|
|
42
|
+
echo "Pushing to origin..."
|
|
43
|
+
git push origin master --tags
|
|
44
|
+
|
|
45
|
+
# Publish to NPM
|
|
46
|
+
echo "Publishing to NPM..."
|
|
47
|
+
npm publish
|
|
48
|
+
|
|
49
|
+
NEW_VERSION="$(node -p "require('./package.json').version")"
|
|
50
|
+
echo "Successfully published gedcom-d3@$NEW_VERSION"
|