hm
This commit is contained in:
parent
4d3fc2cd47
commit
c9dd7182aa
35 changed files with 478 additions and 288 deletions
|
|
@ -102,6 +102,24 @@ function lineagePartnerPathFromPositions(a, b, nodeW, nodeH, unionY, attachOffse
|
|||
if (a.rowIndex !== b.rowIndex) return lineageCrossGenerationPartnerPath(a, b, nodeW, nodeH, unionY, context);
|
||||
return lineageSameGenerationPartnerPath(a, b, nodeW, nodeH, unionY, attachOffsetA, attachOffsetB);
|
||||
}
|
||||
function lineageChildAnchorOnMarriageLine(partner, fallbackX, fallbackY) {
|
||||
const horizontals = (partner?.segments || [])
|
||||
.filter(seg => seg.kind === "h" && Math.abs(seg.x2 - seg.x1) > 10)
|
||||
.sort((a, b) => {
|
||||
const ty = Number.isFinite(partner?.unionY) ? partner.unionY : fallbackY;
|
||||
const dy = Math.abs(a.y1 - ty) - Math.abs(b.y1 - ty);
|
||||
if (Math.abs(dy) > 0.01) return dy;
|
||||
return Math.abs(b.x2 - b.x1) - Math.abs(a.x2 - a.x1);
|
||||
});
|
||||
const seg = horizontals[0] || null;
|
||||
if (!seg) return { x: fallbackX, y: fallbackY };
|
||||
const minX = Math.min(seg.x1, seg.x2) + 8;
|
||||
const maxX = Math.max(seg.x1, seg.x2) - 8;
|
||||
const midX = (seg.x1 + seg.x2) / 2;
|
||||
const x = minX <= maxX ? clamp(Number.isFinite(fallbackX) ? fallbackX : midX, minX, maxX) : midX;
|
||||
return { x, y: seg.y1 };
|
||||
}
|
||||
|
||||
|
||||
function lineageVerticalBridgePath(x, y1, y2, bridges = [], radius = 8.5) {
|
||||
if (!Number.isFinite(x) || !Number.isFinite(y1) || !Number.isFinite(y2)) return '';
|
||||
|
|
@ -170,6 +188,44 @@ function lineageBuildChildPathGroup(group, nodeW, nodeH, bridgeHorizontals) {
|
|||
return { d };
|
||||
}
|
||||
|
||||
function lineageChildBusSpan(group, nodeW) {
|
||||
const childCenters = (group.children || []).map(c => c.x + nodeW / 2);
|
||||
if (!childCenters.length) return null;
|
||||
return {
|
||||
left: Math.min(group.joinX, ...childCenters),
|
||||
right: Math.max(group.joinX, ...childCenters),
|
||||
};
|
||||
}
|
||||
|
||||
function lineageSpansOverlap(a, b, pad = 12) {
|
||||
if (!a || !b) return false;
|
||||
return Math.max(a.left, b.left) <= Math.min(a.right, b.right) + pad;
|
||||
}
|
||||
|
||||
function lineageSeparateChildBusLanes(groupsInGap, nodeW) {
|
||||
const lanes = [];
|
||||
const ordered = (groupsInGap || [])
|
||||
.filter(group => group && Number.isFinite(group.busY))
|
||||
.map(group => ({ group, span: lineageChildBusSpan(group, nodeW), baseY: group.busY }))
|
||||
.filter(entry => entry.span)
|
||||
.sort((a, b) => a.baseY - b.baseY || a.span.left - b.span.left || a.span.right - b.span.right);
|
||||
|
||||
for (const entry of ordered) {
|
||||
let laneIndex = 0;
|
||||
while (lanes[laneIndex]?.some(prev => Math.abs(prev.group.busY - entry.baseY) < 12 && lineageSpansOverlap(prev.span, entry.span))) {
|
||||
laneIndex += 1;
|
||||
}
|
||||
if (!lanes[laneIndex]) lanes[laneIndex] = [];
|
||||
const fromY = Number(entry.group.fromY);
|
||||
const childTop = Number(entry.group.childTop);
|
||||
const minY = Number.isFinite(fromY) ? fromY + 26 : -Infinity;
|
||||
const maxY = Number.isFinite(childTop) ? childTop - 18 : Infinity;
|
||||
const offset = laneIndex * 9;
|
||||
entry.group.busY = clamp(entry.baseY - offset, minY, maxY);
|
||||
lanes[laneIndex].push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
function lineageComponentSignature(component, family) {
|
||||
family = lineageEdgeFamily(family);
|
||||
const idSet = new Set(component.map(x => x?.id).filter(Boolean));
|
||||
|
|
@ -281,7 +337,7 @@ function lineageAssignPartnerAttachOffsets(linkGroups) {
|
|||
for (const [key, groups] of buckets.entries()) {
|
||||
groups.sort((a, b) => a.joinX - b.joinX || a.childTop - b.childTop);
|
||||
const n = groups.length;
|
||||
const step = 9;
|
||||
const step = 14;
|
||||
groups.forEach((group, i) => {
|
||||
const offset = n <= 1 ? 0 : (i - (n - 1) / 2) * step;
|
||||
const [nodeId] = key.split(':');
|
||||
|
|
@ -328,8 +384,8 @@ function lineageBuildLinks(nodes, idSet, layout) {
|
|||
|
||||
for (const groupsInGap of byGap.values()) {
|
||||
groupsInGap.sort((a, b) => a.joinX - b.joinX || a.children.length - b.children.length);
|
||||
const top = Math.min(...groupsInGap.map(g => g.parentBottom)) + 44;
|
||||
const bottom = Math.max(...groupsInGap.map(g => g.childTop)) - 42;
|
||||
const top = Math.min(...groupsInGap.map(g => g.parentBottom)) + 58;
|
||||
const bottom = Math.max(...groupsInGap.map(g => g.childTop)) - 54;
|
||||
const span = Math.max(16, bottom - top);
|
||||
groupsInGap.forEach((group, i) => {
|
||||
const laneY = top + span * ((i + 1) / (groupsInGap.length + 1));
|
||||
|
|
@ -343,22 +399,21 @@ function lineageBuildLinks(nodes, idSet, layout) {
|
|||
const attachB = 0;
|
||||
const partnerY = ((left.y + nodeH / 2) + (right.y + nodeH / 2)) / 2;
|
||||
const partner = lineagePartnerPathFromPositions(left, right, nodeW, nodeH, partnerY, attachA, attachB, layout);
|
||||
if (Number.isFinite(partner.unionX)) group.joinX = partner.unionX;
|
||||
const unionY = Number.isFinite(partner.unionY) ? partner.unionY : partnerY;
|
||||
const clearY = clamp(group.parentBottom + 22 + i * 2, unionY + 10, group.childTop - 36);
|
||||
group.fromY = clearY;
|
||||
group.busY = group.children.length === 1 ? Math.min(group.childTop - 18, clearY + 28) : Math.min(group.childTop - 24, Math.max(clearY + 24, laneY + 18 + i * 3));
|
||||
const anchor = lineageChildAnchorOnMarriageLine(partner, Number.isFinite(partner.unionX) ? partner.unionX : group.joinX, unionY);
|
||||
group.joinX = anchor.x;
|
||||
group.fromY = anchor.y;
|
||||
group.busY = group.children.length === 1 ? Math.min(group.childTop - 22, anchor.y + 38) : Math.min(group.childTop - 28, Math.max(anchor.y + 34, laneY + 24 + i * 5));
|
||||
if (partner.d) {
|
||||
partnerLinks.push(partner.d);
|
||||
horizontalSegments.push(...partner.segments.filter(seg => seg.kind === 'h'));
|
||||
}
|
||||
const trunk = lineageVerticalBridgePath(group.joinX, unionY, group.fromY, []);
|
||||
if (trunk) childLinks.push(trunk);
|
||||
} else {
|
||||
group.fromY = group.parentBottom;
|
||||
group.busY = group.children.length === 1 ? Math.min(group.childTop - 18, group.fromY + 30) : Math.min(group.childTop - 24, Math.max(group.fromY + 28, laneY + 18 + i * 3));
|
||||
group.busY = group.children.length === 1 ? Math.min(group.childTop - 22, group.fromY + 38) : Math.min(group.childTop - 28, Math.max(group.fromY + 34, laneY + 24 + i * 5));
|
||||
}
|
||||
});
|
||||
lineageSeparateChildBusLanes(groupsInGap, nodeW);
|
||||
}
|
||||
|
||||
for (const groupsInGap of byGap.values()) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue