diff --git a/script.js b/script.js index 190df10..b40d878 100644 --- a/script.js +++ b/script.js @@ -197,36 +197,149 @@ function renderTree() { const container = document.getElementById('family-cards-container'); container.innerHTML = ''; - // Simple grid layout for now (placeholder for complex tree algo) - familyData.forEach((member, index) => { - const card = document.createElement('div'); - card.className = 'family-card'; - // Simple positioning - card.style.top = `${100 + (Math.floor(index / 5) * 180)}px`; - card.style.left = `${50 + ((index % 5) * 260)}px`; + if (familyData.length === 0) return; - const genderIcon = member.gender === 'male' ? '👨' : (member.gender === 'female' ? '👩' : '👤'); + // 1. Calculate Generations + const memberMap = new Map(familyData.map(m => [m.id, m])); + const generations = {}; + const processed = new Set(); - card.innerHTML = ` -
${genderIcon}
-
${member.firstName} ${member.lastName}
-
- ${new Date(member.birthDate).getFullYear()} - ${member.deathDate ? new Date(member.deathDate).getFullYear() : ''} -
- `; + function getGeneration(id, depth = 0) { + if (depth > 20) return 0; // Prevent infinite recursion + const member = memberMap.get(id); + if (!member) return 0; - card.addEventListener('click', () => openEditModal(member)); - container.appendChild(card); + let parentGen = -1; + if (member.motherId) parentGen = Math.max(parentGen, getGeneration(member.motherId, depth + 1)); + if (member.fatherId) parentGen = Math.max(parentGen, getGeneration(member.fatherId, depth + 1)); + + return parentGen + 1; + } + + familyData.forEach(member => { + const gen = getGeneration(member.id); + if (!generations[gen]) generations[gen] = []; + generations[gen].push(member); }); - drawLines(); + // 2. Position Members + const cardWidth = 220; + const cardHeight = 100; // Approx + const gapX = 40; + const gapY = 150; + const startY = 50; + + // Sort generations to ensure we process 0, 1, 2... + const genKeys = Object.keys(generations).sort((a, b) => a - b); + + // Store positions for line drawing + const positions = {}; // id -> {x, y, width, height} + + genKeys.forEach(genKey => { + const members = generations[genKey]; + // Sort members to try and keep spouses together (simple heuristic) + members.sort((a, b) => { + if (a.spouseId === b.id) return -1; + return 0; + }); + + // Center the row relative to the container + // We'll use a fixed large width for the container to allow scrolling + // Let's assume center is 1000px + const rowWidth = members.length * (cardWidth + gapX) - gapX; + let startX = Math.max(50, 1000 - rowWidth / 2); + + members.forEach((member, index) => { + const x = startX + (index * (cardWidth + gapX)); + const y = startY + (genKey * gapY); + + positions[member.id] = { x, y, width: cardWidth, height: cardHeight }; + + const card = document.createElement('div'); + card.className = 'family-card'; + card.style.left = `${x}px`; + card.style.top = `${y}px`; + + const genderIcon = member.gender === 'male' ? '👨' : (member.gender === 'female' ? '👩' : '👤'); + + card.innerHTML = ` +
${genderIcon}
+
${member.firstName} ${member.lastName}
+
+ ${new Date(member.birthDate).getFullYear()} - ${member.deathDate ? new Date(member.deathDate).getFullYear() : ''} +
+ `; + + card.addEventListener('click', () => openEditModal(member)); + container.appendChild(card); + }); + }); + + // 3. Draw Lines + drawLines(positions); } -function drawLines() { - // Placeholder for SVG line drawing logic +function drawLines(positions) { const svg = document.getElementById('relationship-lines'); svg.innerHTML = ''; - // Logic to draw lines between parent/child/spouse would go here + + // Set SVG size to match content + const maxX = Math.max(...Object.values(positions).map(p => p.x + p.width)) + 100; + const maxY = Math.max(...Object.values(positions).map(p => p.y + p.height)) + 100; + + svg.setAttribute('width', Math.max(2000, maxX)); // Min width 2000 for scrolling + svg.setAttribute('height', Math.max(1000, maxY)); + + familyData.forEach(member => { + const pos = positions[member.id]; + if (!pos) return; + + // Parent Connections + [member.motherId, member.fatherId].forEach(parentId => { + if (parentId && positions[parentId]) { + const parentPos = positions[parentId]; + + // Draw line from bottom of parent to top of child + const x1 = parentPos.x + parentPos.width / 2; + const parentBottom = parentPos.y + 120; // Approx bottom of card + + const x2 = pos.x + pos.width / 2; + const y2 = pos.y; + + // Bezier curve + const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + const d = `M ${x1} ${parentBottom} C ${x1} ${parentBottom + 50}, ${x2} ${y2 - 50}, ${x2} ${y2}`; + + path.setAttribute("d", d); + path.setAttribute("stroke", "#88a090"); // Moss color + path.setAttribute("stroke-width", "2"); + path.setAttribute("fill", "none"); + svg.appendChild(path); + } + }); + + // Spouse Connection + if (member.spouseId && positions[member.spouseId]) { + const spousePos = positions[member.spouseId]; + // Only draw once (e.g., if id < spouseId) + if (member.id < member.spouseId) { + const x1 = pos.x + pos.width; + const y1 = pos.y + 60; // Middle of card approx + const x2 = spousePos.x; + const y2 = spousePos.y + 60; + + const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + const d = `M ${x1} ${y1} L ${x2} ${y2}`; + + path.setAttribute("d", d); + path.setAttribute("stroke", "#1a472a"); // Forest color + path.setAttribute("stroke-width", "2"); + path.setAttribute("stroke-dasharray", "5,5"); // Dashed for spouse + path.setAttribute("fill", "none"); + svg.appendChild(path); + } + } + }); } function openEditModal(member) {