待测试
This commit is contained in:
@@ -301,42 +301,134 @@ const nodeRelationText = (member) => {
|
||||
return [member.relation, branch].filter(Boolean).join(" · ");
|
||||
};
|
||||
const layoutMembers = computed(() => {
|
||||
const generations = Array.from(
|
||||
new Set(members.value.map((member) => Number(member.generation))),
|
||||
).sort((left, right) => left - right);
|
||||
const groups = new Map(
|
||||
generations.map((generation) => [
|
||||
generation,
|
||||
members.value
|
||||
.filter((member) => Number(member.generation) === generation)
|
||||
.sort(
|
||||
(left, right) =>
|
||||
String(left.parentId || "").localeCompare(
|
||||
String(right.parentId || ""),
|
||||
) ||
|
||||
String(left.spouseOf || left.id).localeCompare(
|
||||
String(right.spouseOf || right.id),
|
||||
) ||
|
||||
Number(Boolean(left.spouseOf)) - Number(Boolean(right.spouseOf)) ||
|
||||
String(left.id).localeCompare(String(right.id)),
|
||||
),
|
||||
]),
|
||||
const memberById = new Map(
|
||||
members.value.map((member) => [String(member.id), member]),
|
||||
);
|
||||
const maxCount = Math.max(
|
||||
1,
|
||||
...Array.from(groups.values()).map((group) => group.length),
|
||||
);
|
||||
const canvasWidth = Math.max(660, maxCount * MEMBER_GAP + 80);
|
||||
return generations.flatMap((generation, generationIndex) => {
|
||||
const group = groups.get(generation) || [];
|
||||
const occupiedWidth = Math.max(0, (group.length - 1) * MEMBER_GAP);
|
||||
const startX = (canvasWidth - occupiedWidth) / 2;
|
||||
return group.map((member, memberIndex) => ({
|
||||
...member,
|
||||
x: snapToGrid(startX + memberIndex * MEMBER_GAP),
|
||||
y: snapToGrid(200 + generationIndex * GENERATION_GAP),
|
||||
}));
|
||||
const compareMembers = (left, right) =>
|
||||
Number(left.generation) - Number(right.generation) ||
|
||||
String(left.id).localeCompare(String(right.id));
|
||||
const spousesByPersonId = new Map();
|
||||
members.value.forEach((member) => {
|
||||
const partnerId = String(member.spouseOf || "");
|
||||
if (partnerId && memberById.has(partnerId)) {
|
||||
spousesByPersonId.set(partnerId, member);
|
||||
}
|
||||
});
|
||||
const primaryMembers = members.value
|
||||
.filter((member) => !member.spouseOf)
|
||||
.sort(compareMembers);
|
||||
const primaryById = new Map(
|
||||
primaryMembers.map((member) => [String(member.id), member]),
|
||||
);
|
||||
const childrenByParent = new Map();
|
||||
primaryMembers.forEach((member) => {
|
||||
const parentId = String(member.parentId || "");
|
||||
if (!parentId || !primaryById.has(parentId)) return;
|
||||
const children = childrenByParent.get(parentId) || [];
|
||||
children.push(member);
|
||||
childrenByParent.set(parentId, children);
|
||||
});
|
||||
childrenByParent.forEach((children) => children.sort(compareMembers));
|
||||
|
||||
// A family owns one contiguous horizontal span. Parents are centered in
|
||||
// that span, so a later generation cannot pull a branch beneath another one.
|
||||
const subtreeWidthById = new Map();
|
||||
const measureSubtree = (member, ancestors = new Set()) => {
|
||||
const id = String(member.id);
|
||||
if (subtreeWidthById.has(id)) return subtreeWidthById.get(id);
|
||||
if (ancestors.has(id)) return 1;
|
||||
const nextAncestors = new Set(ancestors);
|
||||
nextAncestors.add(id);
|
||||
const children = childrenByParent.get(id) || [];
|
||||
const descendantsWidth = children.reduce(
|
||||
(sum, child) => sum + measureSubtree(child, nextAncestors),
|
||||
0,
|
||||
);
|
||||
const ownWidth = spousesByPersonId.has(id) ? 2 : 1;
|
||||
const width = Math.max(ownWidth, descendantsWidth || 1);
|
||||
subtreeWidthById.set(id, width);
|
||||
return width;
|
||||
};
|
||||
const roots = primaryMembers.filter(
|
||||
(member) => !primaryById.has(String(member.parentId || "")),
|
||||
);
|
||||
const rootIds = new Set(roots.map((member) => String(member.id)));
|
||||
primaryMembers.forEach((member) => {
|
||||
if (!rootIds.has(String(member.id))) return;
|
||||
measureSubtree(member);
|
||||
});
|
||||
|
||||
const totalWidth = roots.reduce(
|
||||
(sum, root) => sum + (subtreeWidthById.get(String(root.id)) || 1),
|
||||
0,
|
||||
);
|
||||
const generations = Array.from(
|
||||
new Set(primaryMembers.map((member) => Number(member.generation))),
|
||||
).sort((left, right) => left - right);
|
||||
const generationIndexByNumber = new Map(
|
||||
generations.map((generation, index) => [generation, index]),
|
||||
);
|
||||
const positionedMembers = [];
|
||||
const positionedIds = new Set();
|
||||
const placeSubtree = (member, start, width, ancestors = new Set()) => {
|
||||
const id = String(member.id);
|
||||
if (ancestors.has(id)) return;
|
||||
const nextAncestors = new Set(ancestors);
|
||||
nextAncestors.add(id);
|
||||
const spouse = spousesByPersonId.get(id);
|
||||
const center = start + width / 2;
|
||||
const memberUnitX = spouse ? center - 0.5 : center;
|
||||
const y = snapToGrid(
|
||||
200 + (generationIndexByNumber.get(Number(member.generation)) || 0) * GENERATION_GAP,
|
||||
);
|
||||
positionedMembers.push({
|
||||
...member,
|
||||
x: snapToGrid(80 + memberUnitX * MEMBER_GAP),
|
||||
y,
|
||||
});
|
||||
positionedIds.add(id);
|
||||
if (spouse) {
|
||||
positionedMembers.push({
|
||||
...spouse,
|
||||
x: snapToGrid(80 + (memberUnitX + 1) * MEMBER_GAP),
|
||||
y,
|
||||
});
|
||||
positionedIds.add(String(spouse.id));
|
||||
}
|
||||
const children = childrenByParent.get(id) || [];
|
||||
const childrenWidth = children.reduce(
|
||||
(sum, child) => sum + subtreeWidthById.get(String(child.id)),
|
||||
0,
|
||||
);
|
||||
let childStart = start + (width - childrenWidth) / 2;
|
||||
children.forEach((child) => {
|
||||
const childWidth = subtreeWidthById.get(String(child.id));
|
||||
placeSubtree(child, childStart, childWidth, nextAncestors);
|
||||
childStart += childWidth;
|
||||
});
|
||||
};
|
||||
let rootStart = 0;
|
||||
roots.forEach((root) => {
|
||||
const width = subtreeWidthById.get(String(root.id)) || 1;
|
||||
placeSubtree(root, rootStart, width);
|
||||
rootStart += width;
|
||||
});
|
||||
return positionedMembers.length === members.value.length
|
||||
? positionedMembers
|
||||
: members.value
|
||||
.filter((member) => !positionedIds.has(String(member.id)))
|
||||
.reduce((result, member, index) => {
|
||||
result.push({
|
||||
...member,
|
||||
x: snapToGrid(80 + (totalWidth + index + 0.5) * MEMBER_GAP),
|
||||
y: snapToGrid(
|
||||
200 +
|
||||
(generationIndexByNumber.get(Number(member.generation)) || 0) *
|
||||
GENERATION_GAP,
|
||||
),
|
||||
});
|
||||
return result;
|
||||
}, positionedMembers);
|
||||
});
|
||||
const treeMetrics = computed(() => {
|
||||
const memberList = layoutMembers.value;
|
||||
@@ -395,21 +487,23 @@ const generationBandStyle = (row) => ({
|
||||
});
|
||||
const lineageConnectors = computed(() => {
|
||||
const memberById = new Map(
|
||||
layoutMembers.value.map((member) => [member.id, member]),
|
||||
layoutMembers.value.map((member) => [String(member.id), member]),
|
||||
);
|
||||
const spouseByPersonId = new Map();
|
||||
layoutMembers.value.forEach((member) => {
|
||||
if (member.spouseOf && memberById.has(member.spouseOf)) {
|
||||
spouseByPersonId.set(member.spouseOf, member);
|
||||
const partnerId = String(member.spouseOf || "");
|
||||
if (partnerId && memberById.has(partnerId)) {
|
||||
spouseByPersonId.set(partnerId, member);
|
||||
}
|
||||
});
|
||||
const childrenByParent = new Map();
|
||||
layoutMembers.value.forEach((member) => {
|
||||
if (member.spouseOf || !member.parentId || !memberById.has(member.parentId))
|
||||
const parentId = String(member.parentId || "");
|
||||
if (member.spouseOf || !parentId || !memberById.has(parentId))
|
||||
return;
|
||||
const children = childrenByParent.get(member.parentId) || [];
|
||||
const children = childrenByParent.get(parentId) || [];
|
||||
children.push(member);
|
||||
childrenByParent.set(member.parentId, children);
|
||||
childrenByParent.set(parentId, children);
|
||||
});
|
||||
|
||||
const connectors = [];
|
||||
|
||||
Reference in New Issue
Block a user