UNPKG

gedcom-d3

Version:

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

623 lines (560 loc) 14.2 kB
const d3ize = (tree) => { const notes = tree.filter(hasTag('NOTE')) let surnameList = [] const peopleNodes = tree .filter(hasTag('INDI')) .map((p) => toNode(p, notes, surnameList)) const families = tree.filter(hasTag('FAM')) const links = families.reduce((memo, family) => { return memo.concat(familyLinks(family, peopleNodes)) }, []) assignFy(peopleNodes, links) return { nodes: peopleNodes, links: links, families: families, surnameList: surnameList, } } // Tag search function const hasTag = (val) => { return (node) => { return node.tag === val } } // Data search function const hasData = (val) => { return (node) => { return node.data === val } } // ID search function const hasID = (val) => { return (node) => { return node.id === val } } const assignFy = (peopleNodes, links) => { // YOB known let yesyob = peopleNodes.filter((p) => { return p.yob !== '?' && !isNaN(+p.yob) }) yesyob.forEach((p) => (p.fy = +p.yob)) // YOB unknown let noyob = peopleNodes.filter((p) => { return p.yob === '?' }) let count = 10 // Cycle through list, adding fy until all complete while (noyob.length > 0 && count > 0) { let tempnoyob = noyob.slice() tempnoyob.forEach((p, index) => { // Build array of family let tpFamily = [] links.forEach((link) => { if (link.source == p.id) { tpFamily.push({ pRole: 'source', pType: link.sourceType, other: link.target, oType: link.targetType, }) } else if (link.target == p.id) { tpFamily.push({ pRole: 'target', pType: link.targetType, other: link.source, oType: link.sourceType, }) } }) // Check family for YOB tpFamily.forEach((member) => { // USE SOME() INSTEAD OF FOREACH!!! peopleNodes.forEach((person) => { // USE SOME() INSTEAD OF FOREACH!!! if (person.id == member.other && person.fy !== undefined) { // Person is source if (member.pRole === 'source') { // Person is husband if (member.pType === 'HUSB' && member.oType === 'WIFE') { p.fy = +person.fy - 3 // Person is father } else if (member.pType === 'HUSB' && member.oType === 'CHIL') { p.fy = +person.fy - 30 // Person is mother } else if (member.pType === 'WIFE') { p.fy = +person.fy - 27 } // Person is target } else if (member.pRole === 'target') { // Person is wife if (member.pType === 'WIFE' && member.oType === 'HUSB') { p.fy = +person.fy + 3 // Person is child of father } else if (member.pType === 'CHIL' && member.oType === 'HUSB') { p.fy = +person.fy + 30 // Person is child of mother } else if (member.pType === 'CHIL' && member.oType === 'WIFE') { p.fy = +person.fy + 27 } } } }) }) if (p.fy !== undefined) { noyob.splice(index, index + 1) } }) count -= 1 } const convertFy = (peopleNodes) => { const fyRatio = (peopleNodes) => { if (peopleNodes.length <= 50) { return 3 } else if (peopleNodes.length > 50 && peopleNodes.length <= 150) { return 4 } else if (peopleNodes.length > 150 && peopleNodes.length <= 250) { return 5 } else if (peopleNodes.length > 250) { return 6 } } let allFy = [] peopleNodes.forEach((p) => { if (p.fy !== undefined) { p.fy = p.fy * fyRatio(peopleNodes) allFy.push(p.fy) } }) let total = 0 allFy.forEach((fy) => (total += fy)) let average = total / allFy.length peopleNodes.forEach((p) => { if (p.fy !== undefined) { p.fy = -(p.fy - average) } }) } convertFy(peopleNodes) } // Get title const getTitle = (p) => { const title = p.tree.filter(hasTag('TITL')) || [] if (title.length > 0) { return title[title.length - 1].data } } // Get full name const getName = (p) => { let nameNode = (p.tree.filter(hasTag('NAME')) || [])[0] if (nameNode) { return nameNode.data.replace(/\//g, '') } else { return '?' } } // Get first name const getFirstName = (p) => { // Find 'NAME' tag const nameNode = (p.tree.filter(hasTag('NAME')) || [])[0] if (nameNode) { // Find 'GIVN' tag let firstNameNode = (nameNode.tree.filter(hasTag('GIVN')) || [])[0] if (firstNameNode) { // Remove middle name if (firstNameNode.data.search(' ') !== -1) { return firstNameNode.data.slice(0, firstNameNode.data.search(' ')) } else { return firstNameNode.data } } else { return '?' } } else { return '?' } } // Get surname const getSurname = (p) => { // Find 'NAME' tag const nameNode = (p.tree.filter(hasTag('NAME')) || [])[0] if (nameNode) { // Find 'SURN' tag const surnameNode = (nameNode.tree.filter(hasTag('SURN')) || [])[0] // If surname listed if (surnameNode) { // Remove alternate surnames if (surnameNode.data.search(',') !== -1) { return surnameNode.data.slice(0, surnameNode.data.search(',')) } else { return surnameNode.data } // Derive surname from name } else { nameArr = nameNode.data.split(' ') // Look for forward slashes let isSlashes = nameArr.some((str) => str[0] === '/') if (isSlashes) { return nameArr.find((str) => str[0] === '/').replace(/\//g, '') // no slashes, use final item in array } else { nameArr[nameArr.length - 1] = nameArr[nameArr.length - 1].replace( /\//g, '' ) return nameArr.length > 1 ? nameArr[nameArr.length - 1] : 'Hrm' } } } else { return '?' } } // Get gender const getGender = (p) => { // Find 'SEX' tag let genderNode = (p.tree.filter(hasTag('SEX')) || [])[0] if (genderNode) { return genderNode.data } else { return 'Unknown' } } // Get date of birth const getDOB = (p) => { // Find 'BIRT' tag let dobNode = (p.tree.filter(hasTag('BIRT')) || [])[0] if (dobNode) { // Find 'DATE' tag let dateNode = (dobNode.tree.filter(hasTag('DATE')) || [])[0] if (dateNode) { return dateNode.data } else { return '?' } } else { return '?' } } // Get year of birth const getYOB = (p) => { // Find 'BIRT' tag let dobNode = (p.tree.filter(hasTag('BIRT')) || [])[0] if (dobNode) { // Find 'DATE' tag let dateNode = (dobNode.tree.filter(hasTag('DATE')) || [])[0] if (dateNode) { return dateNode.data.slice(-4) } else { return '?' } } else { return '?' } } // Get place of birth const getPOB = (p) => { // Find 'BIRT' tag let pobNode = (p.tree.filter(hasTag('BIRT')) || [])[0] if (pobNode) { // Find 'DATE' tag let placeNode = (pobNode.tree.filter(hasTag('PLAC')) || [])[0] if (placeNode) { return placeNode.data } else { return '' } } else { return '' } } // Get date of death const getDOD = (p) => { // Find 'DEAT' tag let dobNode = (p.tree.filter(hasTag('BIRT')) || [])[0] let dodNode = (p.tree.filter(hasTag('DEAT')) || [])[0] if (dodNode) { // Find 'DATE' tag let dateNode = (dodNode.tree.filter(hasTag('DATE')) || [])[0] if (dateNode) { return dateNode.data } else { return '?' } } else if (dobNode) { let dateNode = (dobNode.tree.filter(hasTag('DATE')) || [])[0] if (dateNode) { return dateNode.data.slice(-4) + 100 } else { return '?' } } else { return 'Present' } } // Get year of death const getYOD = (p) => { let thisYear = new Date().getFullYear() // Find 'DEAT' tag let dobNode = (p.tree.filter(hasTag('BIRT')) || [])[0] let dodNode = (p.tree.filter(hasTag('DEAT')) || [])[0] // If DEATH tag if (dodNode) { // Find 'DATE' tag let dateNode = (dodNode.tree.filter(hasTag('DATE')) || [])[0] // If death date listed if (dateNode) { return dateNode.data.slice(-4) } else { return '?' } // BIRT tag, but no DEAT tag } else if (dobNode && !dodNode) { let dateNode = (dobNode.tree.filter(hasTag('DATE')) || [])[0] // If DOB listed if (dateNode) { // If born > 100 yrs ago, call dead if (dateNode.data.slice(-4) < thisYear - 100) { return '?' } else { return 'Present' } } else { return '?' } // no DEAT or BIRT tag } else { return '?' } } // Get place of birth const getPOD = (p) => { // Find 'BIRT' tag let podNode = (p.tree.filter(hasTag('DEAT')) || [])[0] if (podNode) { // Find 'DATE' tag let placeNode = (podNode.tree.filter(hasTag('PLAC')) || [])[0] if (placeNode) { return placeNode.data } else { return '' } } else { return '' } } // Get relatives const getFamilies = (p) => { let families = [] let pediInfo // If child let familyNode1 = p.tree.filter(hasTag('FAMC')) || [] if (familyNode1) { for (let i = 0; i < familyNode1.length; i++) { if (familyNode1[i].tree.length > 0) { // Get pedigree info if (familyNode1[i].tree[0].tag == 'PEDI') { pediInfo = { frel: familyNode1[i].tree[0].data, mrel: familyNode1[i].tree[0].data, } } else if (familyNode1[i].tree[0].tag == '_FREL') { pediInfo = { frel: familyNode1[i].tree[0].data, mrel: familyNode1[i].tree[1].data, } } } families.push({ id: familyNode1[i].data, pedi: pediInfo }) } } let familyNode2 = p.tree.filter(hasTag('FAMS')) || [] if (familyNode2) { for (let i = 0; i < familyNode2.length; i++) { families.push({ id: familyNode2[i].data }) } } return families } // Get color const getColor = (p, surnameList) => { const colorList = [ '#ef8a65', // coral '#00b4ff', // sky blue '#fac641', // mexican egg yolk '#c8d84c', // olive '#e1b386', // light brown '#a5c2cc', // light blue grey '#e87c76', // soft pink '#d0a8ec', // soft royal purple '#8ad5b2', // grass & sage '#f8a7d0', // dry wine '#6e90e6', // ligt purple blue '#a6e9e6', // sea foam '#df9ac1', // magenta '#4ae9bc', // forest '#e08e79', // blush '#80d152', // neon green '#e7c34e', // tangerine '#7ff0ca', // light sea foam '#ff835a', // burnt orange '#eebd6e', // chocolate '#a6b890', // olive sage '#c44d58', // rouge '#e8b28e', // peach '#d4ee5e', // lime '#f3f621', // light yellow '#e887aa', // newborn pink '#c4a8f6', // royal purple '#71cfde', // baby foam '#ccc', // light grey ] // If color description listed in GEDCOM const dscr = (p.tree.filter(hasTag('DSCR')) || [])[0] const foundName = surnameList.find((sName) => sName.surname === p.surname) // If surname already in list if (foundName) { foundName.count = foundName.count + 1 } else { surnameList.push({ surname: p.surname, count: 1, color: colorList[surnameList.length % colorList.length], }) } // surnameList.color = surnameList.length % colorList.length}); // If color listed assign that if (dscr) { return dscr.data // else assign color from colorList } else { return surnameList.find((sName) => sName.surname === p.surname).color } } // Get person notes const getNotes = (p) => { return p.tree.filter(hasTag('NOTE')) } // Get Bio const getBio = (p, notes) => { if (p.notes.length != 0) { let bio = '' // Notes for person p.notes.forEach((personNote) => { // personNote.data points to NOTE object if (notes.length > 0) { notes.forEach((note) => { if (personNote.data === note.pointer) { bio += note.data // Concat broken up note if (note.tree.length > 0) { note.tree.forEach((fragment) => (bio += fragment.data)) } } }) // personNote.data is actual note } else { bio += personNote.data } }) return bio } } const getFy = (p) => { if (p.yob === '?') { return 0 } else { return +(-p.yob * 3 + 6000) } } const toNode = (p, notes, surnameList) => { p.id = p.pointer p.title = getTitle(p) p.name = getName(p) p.firstName = getFirstName(p) p.surname = getSurname(p) p.gender = getGender(p) p.dob = getDOB(p) p.yob = getYOB(p) p.pob = getPOB(p) p.dod = getDOD(p) p.yod = getYOD(p) p.pod = getPOD(p) p.families = getFamilies(p) p.color = getColor(p, surnameList) p.notes = getNotes(p) p.bio = getBio(p, notes) return p } const familyLinks = (family, peopleNodes) => { let memberLinks = [] let maritalStatus = null let pedigree // Filter only individual objects from family tree let memberSet = family.tree.filter(function (member) { return ( member.tag && (member.tag === 'HUSB' || member.tag === 'WIFE' || member.tag === 'CHIL') ) }) // Filter marital status events family.tree.filter((event) => { if (event.tag === 'DIV' || event.tag === 'MARR') { if (maritalStatus !== 'DIV') { maritalStatus = event.tag } } }) // Iterate over each member of set to connect with other members while (memberSet.length > 1) { for (let i = 1; i < memberSet.length; i++) { // Exclude sibling relationships if (memberSet[0].tag != 'CHIL') { // If marital status listed if (memberSet[0].tag == 'HUSB' && memberSet[i].tag == 'WIFE') { memberLinks.push({ source: memberSet[0].data, target: memberSet[i].data, sourceType: memberSet[0].tag, targetType: memberSet[i].tag, type: maritalStatus, }) } else { // Filter pedigree info function getPedigree(personID, parentType, relInfo) { // GRAMPS let person = peopleNodes.filter(hasID(personID)) let personFamily = person[0].families.filter(hasID(family.pointer)) if (parentType == 'HUSB') { if (personFamily[0].pedi) { return personFamily[0].pedi.frel } else if (relInfo.some((parent) => parent.tag === '_FREL')) { return relInfo.find((parent) => parent.tag === '_FREL').data } } else { if (personFamily[0].pedi) { return personFamily[0].pedi.mrel } else if (relInfo.some((parent) => parent.tag === '_MREL')) { return relInfo.find((parent) => parent.tag === '_MREL').data } } } memberLinks.push({ source: memberSet[0].data, target: memberSet[i].data, sourceType: memberSet[0].tag, targetType: memberSet[i].tag, type: getPedigree( memberSet[i].data, memberSet[0].tag, memberSet[i].tree ), }) } } } memberSet.splice(0, 1) } return memberLinks } module.exports = d3ize