Skip to content

Commit 7c9ea66

Browse files
committed
Fix connector line distribution with true mirrored fan-out pattern
Separates nodes into left (paternal) and right (maternal) groups and ranks within each side independently. Outer nodes on both sides now get lower connector lines, creating symmetric visual pattern.
1 parent a1fafce commit 7c9ea66

1 file changed

Lines changed: 20 additions & 10 deletions

File tree

‎client/src/components/ancestry-tree/views/VerticalFamilyView.tsx‎

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -568,16 +568,26 @@ export function VerticalFamilyView({
568568
// Get all nodes at this generation that have parent connectors
569569
const genNodes = nodesWithParentsByGen.get(node.generation) || [node];
570570

571-
// Sort nodes by distance from center to get unique rank for each
572-
// This ensures even distribution with fan-out pattern (outside = lower)
573-
const sortedByOutwardness = [...genNodes].sort((a, b) => Math.abs(a.x) - Math.abs(b.x));
574-
const outwardnessRank = sortedByOutwardness.indexOf(node);
575-
576-
// Distribute lines evenly across the full usable space
577-
// Rank 0 (closest to center) near parents, highest rank (most outward) near child
578-
const subTrackOffset = genNodes.length > 1
579-
? (outwardnessRank / (genNodes.length - 1)) * usableSpace
580-
: usableSpace / 2;
571+
// Separate nodes into left (paternal) and right (maternal) sides for mirrored fan-out
572+
// Each side fans out independently: outside = lower line, inside = higher line
573+
const leftNodes = genNodes.filter(n => n.x < 0).sort((a, b) => a.x - b.x); // most negative (outer) first
574+
const rightNodes = genNodes.filter(n => n.x >= 0).sort((a, b) => b.x - a.x); // most positive (outer) first
575+
576+
// Determine which side this node is on and its rank within that side
577+
const isLeft = node.x < 0;
578+
const sideNodes = isLeft ? leftNodes : rightNodes;
579+
const sideRank = sideNodes.indexOf(node);
580+
const sideCount = sideNodes.length;
581+
582+
// Use the max count from either side to ensure symmetric spacing
583+
const maxSideCount = Math.max(leftNodes.length, rightNodes.length, 1);
584+
585+
// Distribute lines: rank 0 (outermost) gets lowest line, highest rank (innermost) gets highest line
586+
// Invert the rank so outer (rank 0) maps to high offset (low line)
587+
const normalizedRank = sideCount > 1
588+
? (sideCount - 1 - sideRank) / (maxSideCount - 1)
589+
: 0.5; // Center if only one node on this side
590+
const subTrackOffset = normalizedRank * usableSpace;
581591
const midY = parentBottomY + trackPadding + subTrackOffset;
582592

583593
if (father && mother) {

0 commit comments

Comments
 (0)