Files
provenance/frontend/patches/family-chart+0.9.0.patch
T
justin e0573e6be2 Move cardToMiddle vertical-centering fix into the family-chart patch
Fold the fly-to vertical-centering fix into our patch-package patch (alongside
the existing spouse-layout fix) instead of compensating in app code, and revert
the in-app workaround so the two don't double-correct.

- patches/family-chart+0.9.0.patch: cardToMiddle now scales datum.y by the zoom
  k in both dist builds (.js + .esm.js), matching datum.x. Verified the patch
  applies cleanly (patch-package --error-on-fail).
- tree/page.tsx: the cardToMiddle caller passes raw y again (the patched library
  does the scaling now); pre-scaling here too would double-correct. Behavior is
  identical to the previous in-app fix — both center the node exactly.
- CLAUDE.md: documents the two family-chart patches, how to regenerate them, and
  that both should be upstreamed. The cardToMiddle fix is submitted upstream
  (donatso/family-chart#103, issue #102); the spouse-layout fix is a TODO.

The frontend Dockerfile already COPYs patches/ before npm ci, so the fix is in
the production build.

Signed-off-by: Justin Paul <justin@jpaul.me>
2026-06-11 09:21:30 -04:00

137 lines
7.8 KiB
Diff

diff --git a/node_modules/family-chart/dist/family-chart.esm.js b/node_modules/family-chart/dist/family-chart.esm.js
index 3867be0..656fafa 100644
--- a/node_modules/family-chart/dist/family-chart.esm.js
+++ b/node_modules/family-chart/dist/family-chart.esm.js
@@ -10,10 +10,10 @@ function sortChildrenWithSpouses(children, datum, data) {
const b_p2 = otherParent(b, datum, data);
const a_i = a_p2 ? spouses.indexOf(a_p2.id) : -1;
const b_i = b_p2 ? spouses.indexOf(b_p2.id) : -1;
- if (datum.data.gender === "M")
- return a_i - b_i;
- else
- return b_i - a_i;
+ // Provenance patch: order children by spouse order (ascending), gender-
+ // independent, so each couple's children sit next to the matching spouse
+ // in the centered-spouse layout (see setupSpouses).
+ return a_i - b_i;
});
}
function sortAddNewChildren(children) {
@@ -701,20 +701,38 @@ function calculateTree(data, { main_id = null, node_separation = 250, level_sepa
if (spouses.length > 0) {
if (one_level_rels && d.depth > 0)
continue;
- const side = d.data.data.gender === "M" ? -1 : 1; // female on right
- d.x += spouses.length / 2 * node_separation * side;
+ // Provenance patch: keep the person in the MIDDLE and split
+ // their spouses to alternating sides, then recenter the whole
+ // cluster on the person's slot, so each couple's children
+ // descend from between the correct pair instead of all
+ // spouses stacking on one side. Ordered by relationship order
+ // (spouse 1 on top, spouse 2 below, further spouses farther
+ // out) — NOT gender; children are sorted to match in
+ // sortChildrenWithSpouses.
+ const offsets = spouses.map((_sp, i) => {
+ const dir = i % 2 === 0 ? 1 : -1;
+ const dist = Math.floor(i / 2) + 1; // 1,1,2,2,3,3...
+ return -(node_separation * dist) * dir;
+ });
+ // Mean of the person (offset 0) + every spouse, so the
+ // cluster stays centered on the person's original slot.
+ const center = offsets.reduce((a, b) => a + b, 0) / (offsets.length + 1);
+ const base_x = d.x;
+ d.x = base_x - center;
spouses.forEach((sp_id, i) => {
const spouse = {
data: data_stash.find(d0 => d0.id === sp_id),
added: true,
depth: d.depth,
spouse: d,
- x: d.x - (node_separation * (i + 1)) * side,
+ x: base_x + offsets[i] - center,
y: d.y,
tid: `${d.data.id}-spouse-${i}`,
};
- spouse.sx = i > 0 ? spouse.x : spouse.x + (node_separation / 2) * side;
- spouse.sy = i > 0 ? spouse.y : spouse.y + (node_separation / 2) * side;
+ // Anchor links at the midpoint toward the person.
+ const toward = spouse.x < d.x ? 1 : -1;
+ spouse.sx = spouse.x + toward * (node_separation / 2);
+ spouse.sy = spouse.y;
if (!d.spouses)
d.spouses = [];
d.spouses.push(spouse);
@@ -1073,7 +1091,7 @@ function calculateTreeFit(svg_dim, tree_dim) {
return { k, x, y };
}
function cardToMiddle({ datum, svg, svg_dim, scale, transition_time }) {
- const k = scale || 1, x = svg_dim.width / 2 - datum.x * k, y = svg_dim.height / 2 - datum.y, t = { k, x: x / k, y: y / k };
+ const k = scale || 1, x = svg_dim.width / 2 - datum.x * k, y = svg_dim.height / 2 - datum.y * k, t = { k, x: x / k, y: y / k };
positionTree({ t, svg, transition_time });
}
function manualZoom({ amount, svg, transition_time = 500 }) {
diff --git a/node_modules/family-chart/dist/family-chart.js b/node_modules/family-chart/dist/family-chart.js
index 1c750d4..edeb804 100644
--- a/node_modules/family-chart/dist/family-chart.js
+++ b/node_modules/family-chart/dist/family-chart.js
@@ -33,10 +33,9 @@
const b_p2 = otherParent(b, datum, data);
const a_i = a_p2 ? spouses.indexOf(a_p2.id) : -1;
const b_i = b_p2 ? spouses.indexOf(b_p2.id) : -1;
- if (datum.data.gender === "M")
- return a_i - b_i;
- else
- return b_i - a_i;
+ // Provenance patch: order children by spouse order (ascending),
+ // gender-independent, to match the centered-spouse layout.
+ return a_i - b_i;
});
}
function sortAddNewChildren(children) {
@@ -724,20 +723,31 @@
if (spouses.length > 0) {
if (one_level_rels && d.depth > 0)
continue;
- const side = d.data.data.gender === "M" ? -1 : 1; // female on right
- d.x += spouses.length / 2 * node_separation * side;
+ // Provenance patch: keep the person in the MIDDLE and
+ // split spouses to alternating sides, recentered on the
+ // person's slot, so each couple's children descend from
+ // between the correct pair (see esm build for details).
+ const offsets = spouses.map((_sp, i) => {
+ const dir = i % 2 === 0 ? 1 : -1;
+ const dist = Math.floor(i / 2) + 1;
+ return -(node_separation * dist) * dir;
+ });
+ const center = offsets.reduce((a, b) => a + b, 0) / (offsets.length + 1);
+ const base_x = d.x;
+ d.x = base_x - center;
spouses.forEach((sp_id, i) => {
const spouse = {
data: data_stash.find(d0 => d0.id === sp_id),
added: true,
depth: d.depth,
spouse: d,
- x: d.x - (node_separation * (i + 1)) * side,
+ x: base_x + offsets[i] - center,
y: d.y,
tid: `${d.data.id}-spouse-${i}`,
};
- spouse.sx = i > 0 ? spouse.x : spouse.x + (node_separation / 2) * side;
- spouse.sy = i > 0 ? spouse.y : spouse.y + (node_separation / 2) * side;
+ const toward = spouse.x < d.x ? 1 : -1;
+ spouse.sx = spouse.x + toward * (node_separation / 2);
+ spouse.sy = spouse.y;
if (!d.spouses)
d.spouses = [];
d.spouses.push(spouse);
@@ -1096,7 +1106,7 @@
return { k, x, y };
}
function cardToMiddle({ datum, svg, svg_dim, scale, transition_time }) {
- const k = scale || 1, x = svg_dim.width / 2 - datum.x * k, y = svg_dim.height / 2 - datum.y, t = { k, x: x / k, y: y / k };
+ const k = scale || 1, x = svg_dim.width / 2 - datum.x * k, y = svg_dim.height / 2 - datum.y * k, t = { k, x: x / k, y: y / k };
positionTree({ t, svg, transition_time });
}
function manualZoom({ amount, svg, transition_time = 500 }) {