diff --git a/kjvstudy_org/templates/family_tree_interactive.html b/kjvstudy_org/templates/family_tree_interactive.html index 1db6d4e..f753cbc 100644 --- a/kjvstudy_org/templates/family_tree_interactive.html +++ b/kjvstudy_org/templates/family_tree_interactive.html @@ -806,6 +806,7 @@ function buildTreeData(rootId, maxDepth, direction = 'descendants') { spouse: person.spouse, gender: inferGender(person.name), hasChildren: relatedIds.length > 0, + hasParents: (person.parents || []).length > 0, kekule_number: person.kekule_number }, children: [] @@ -823,6 +824,119 @@ function buildTreeData(rootId, maxDepth, direction = 'descendants') { return buildNode(rootId, 0); } +// Build bidirectional tree - parents above, children below +function buildBidirectionalTreeData(rootId, descendantDepth = 3, ancestorDepth = 2) { + const person = familyTreeData[rootId]; + if (!person) return null; + + // Build ancestors (going up) + function buildAncestors(personId, depth) { + if (depth > ancestorDepth) return null; + + const p = familyTreeData[personId]; + if (!p) return null; + + const parentIds = p.parents || []; + + const node = { + id: personId, + data: { + name: p.name, + birth_year: p.birth_year, + death_year: p.death_year, + age_at_death: p.age_at_death, + generation: p.generation, + spouse: p.spouse, + gender: inferGender(p.name), + hasChildren: (p.children || []).length > 0, + hasParents: parentIds.length > 0, + kekule_number: p.kekule_number, + isAncestor: depth > 0 + }, + children: [] + }; + + // For ancestors, "children" in the tree are actually parents + for (const parentId of parentIds) { + const parentNode = buildAncestors(parentId, depth + 1); + if (parentNode) node.children.push(parentNode); + } + + return node; + } + + // Build descendants (going down) + function buildDescendants(personId, depth, visited = new Set()) { + if (depth > descendantDepth || visited.has(personId)) return null; + visited.add(personId); + + const p = familyTreeData[personId]; + if (!p) return null; + + const childIds = p.children || []; + + const node = { + id: personId, + data: { + name: p.name, + birth_year: p.birth_year, + death_year: p.death_year, + age_at_death: p.age_at_death, + generation: p.generation, + spouse: p.spouse, + gender: inferGender(p.name), + hasChildren: childIds.length > 0, + hasParents: (p.parents || []).length > 0, + kekule_number: p.kekule_number, + isDescendant: depth > 0 + }, + children: [] + }; + + for (const childId of childIds) { + const childNode = buildDescendants(childId, depth + 1, visited); + if (childNode) node.children.push(childNode); + } + + return node; + } + + // Get the main person's data + const rootNode = { + id: rootId, + data: { + name: person.name, + birth_year: person.birth_year, + death_year: person.death_year, + age_at_death: person.age_at_death, + generation: person.generation, + spouse: person.spouse, + gender: inferGender(person.name), + hasChildren: (person.children || []).length > 0, + hasParents: (person.parents || []).length > 0, + kekule_number: person.kekule_number, + isRoot: true + }, + children: [], + _ancestors: [] + }; + + // Build descendants + const visited = new Set([rootId]); + for (const childId of (person.children || [])) { + const childNode = buildDescendants(childId, 1, visited); + if (childNode) rootNode.children.push(childNode); + } + + // Build ancestors separately (we'll render them in a second tree above) + for (const parentId of (person.parents || [])) { + const ancestorNode = buildAncestors(parentId, 1); + if (ancestorNode) rootNode._ancestors.push(ancestorNode); + } + + return rootNode; +} + // Toggle node expand/collapse function toggleNode(d) { if (d.children) { @@ -1343,11 +1457,11 @@ function renderTree() { fitToView(); } -// Re-render tree from a specific person ID +// Re-render tree from a specific person ID (bidirectional - shows parents and children) function renderTreeFromId(personId) { container.innerHTML = ''; - const treeData = buildTreeData(personId, currentDepth, currentDirection); + const treeData = buildBidirectionalTreeData(personId, currentDepth, 2); if (!treeData) { container.innerHTML = '