import { Injectable } from '@angular/core'; import { FamilyTreeNode } from './family-tree-builder'; import { LayoutNode } from '../models/layoutnode'; import { Family } from '../models/family'; @Injectable({ providedIn: 'root' }) export class FamilyTreeLayoutService { private readonly NODE_WIDTH = 140; private readonly NODE_HEIGHT = 70; private readonly HORIZONTAL_GAP = 200; private readonly VERTICAL_GAP = 200; private readonly SPOUSE_GAP = 180; private processedCouples = new Set(); private currentX = 0; buildLayout( roots: FamilyTreeNode[] ): LayoutNode[] { this.currentX = 0; this.processedCouples.clear(); const result: LayoutNode[] = []; const visited = new Set(); roots.forEach(root => { this.layoutNode( root, 0, result, visited ); this.currentX += this.HORIZONTAL_GAP; }); return result; } private layoutNode( node: FamilyTreeNode, depth: number, result: LayoutNode[], visited: Set ): number { if (visited.has(node.family.id)) { const existing = result.find( x => x.familyId === node.family.id ); return existing?.x ?? 0; } visited.add(node.family.id); const children = node.children; let x: number; if (children.length === 0) { x = this.currentX; this.currentX += this.HORIZONTAL_GAP; } else { const childXs = children.map(child => this.layoutNode( child, depth + 1, result, visited ) ); x = (Math.min(...childXs) + Math.max(...childXs)) / 2; } const y = depth * this.VERTICAL_GAP; let spouseX: number | undefined; let spouse: Family | undefined; if (node.spouses.length > 0) { const spouseNode = node.spouses[0]; const coupleKey = [ node.family.id, spouseNode.family.id ] .sort() .join('-'); if ( !this.processedCouples.has( coupleKey ) ) { this.processedCouples.add( coupleKey ); spouse = spouseNode.family; spouseX = x + this.SPOUSE_GAP; } } result.push({ familyId: node.family.id, family: node.family, spouse, depth, x, y, spouseX, width: this.NODE_WIDTH, height: this.NODE_HEIGHT }); return x; } }