Skip to content

Fix exponential ancestor/descendant traversal caused by pedigree collapse#110

Open
robekl wants to merge 1 commit into
PeWu:masterfrom
robekl:fix/pedigree-collapse-exponential-traversal
Open

Fix exponential ancestor/descendant traversal caused by pedigree collapse#110
robekl wants to merge 1 commit into
PeWu:masterfrom
robekl:fix/pedigree-collapse-exponential-traversal

Conversation

@robekl

@robekl robekl commented Jun 2, 2026

Copy link
Copy Markdown

Problem

With significant endogamy (consanguineous marriages — cousins marrying, common in large European or traditional family trees), AncestorChart.createHierarchy() and DescendantChart.createHierarchy() could visit the same family exponentially.

Each revisit re-queued that family's parents/children without checking whether they had already been added. The IdGenerator correctly produced unique node IDs for each occurrence, but the traversal kept growing.

Real-world impact: In a genealogy file with ~24K individuals and typical European endogamy, a single ancestor chart render triggered 445,895 traversal iterations, produced a 445K-node D3 tree, and called getComputedTextLength over 1 million times — freezing the browser for 4+ minutes. The same bug affected the descendant chart.

Fix

Add a Set<string> of visited real family IDs in both createHierarchy() methods. When a family ID is encountered a second time via a different path, mark it with ExpanderState.PLUS (the user can expand it manually) instead of re-traversing its parents or children.

This caps traversal at the number of unique families in the tree, reducing the pathological case from 445,895 iterations to 3,579.

The collapsed duplicate is still included as a node in the output (so the chart correctly shows the relationship exists), but its sub-tree is not expanded automatically.

Tests

Two regression tests added:

  • ancestor-chart.spec.ts — cousins who married: I1 and I2 share grandparents (F3) reachable via two separate ancestry paths. Verifies the chart renders in bounded time with F3 appearing once.

  • descendant-chart.spec.ts — siblings whose children intermarried: I5 and I6 (from different parents) both marry into F4. Verifies the descendant chart doesn't revisit F4 from the second path.

Compatibility

The change is backward-compatible. Families that are only reachable via a single path continue to render exactly as before. Only families reachable via multiple paths (pedigree collapse) now show a PLUS expander on their second occurrence rather than re-expanding infinitely.

Co-authored-by: Claude Sonnet 4.6 noreply@anthropic.com

…llapse

With significant endogamy (consanguineous marriages, common in large European
or traditional family trees), the ancestor and descendant charts could visit
the same ancestral family exponentially. Each revisit re-queued that family's
parents/children, producing O(2^n) iterations before the stratify() call.

In a real-world genealogy file, this caused 445,895 traversal iterations for
a single chart render — a 445K-node D3 tree and over 1 million
getComputedTextLength calls — freezing the browser for 4+ minutes.

Fix: add a Set of visited family IDs in AncestorChart.createHierarchy() and
DescendantChart.createHierarchy(). When a family ID is encountered a second
time via a different ancestral/descendant path, mark it with a PLUS expander
(indicating the user can expand it manually) instead of re-traversing its
parents or children. This caps traversal at the number of unique families in
the tree.

Add regression tests for both charts using a cousin-marriage (ancestor) and
a sibling-intermarriage (descendant) scenario that would previously trigger
the exponential growth.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@PeWu

PeWu commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Thank you for this PR. While I appreciate the effort to handle duplicated family members, I have some concerns regarding the current implementation:

Even if family members are duplicated in the tree, this is often expected behavior in genealogical visualizations to maintain context. Cutting them out results in a loss of information that is otherwise visible and useful.

Topola Viewer currently does not display or utilize expanders. If the goal is to manage duplication, it would be much more effective to implement functional expanders (where clicking a (+) button toggles the visibility of duplicated nodes) rather than removing them entirely.

Regardless of the approach, a change that alters the structure of the tree in this way should not be mandatory. If we were to implement this, it would need to be an optional setting in the API, allowing to toggle this behavior on or off. Then, it would be possible to have a config option in Topola Viewer to toggle the behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants