1.Все сыновья должны быть выровнены по левому краю
2.Все дочери должны быть выровнены по правому краю
3.Вся семья каждого ребенка (ребенок → супруг → потомки) должна рассматриваться как один вертикальный блок
4.Когда семья первого сына заканчивается, семья второго сына начинается непосредственно под ним
5.Та же логика вертикального стекирования применяется к дочерним элементам
6.Родители подключаются к дочерним элементам через одну среднюю точку, а затем разветвляются к дочерним узлам
Примечание. Я прикрепил эталонное изображение, показывающее ожидаемый результат.
Что я пробовал
Я вычисляю позиции вручную, используя рекурсию и отслеживая смещения по оси Y.
Код ниже приведен минимальный воспроизводимый пример, демонстрирующий логику макета.
Минимально воспроизводимый пример
Код: Выделить всё
.link {
fill: none;
stroke: #000;
stroke-width: 2px;
}
.node rect {
fill: #fff;
stroke-width: 1.5px;
}
.male { stroke: #007bff; }
.female { stroke: #dc3545; }
text {
font-family: Arial, sans-serif;
font-size: 12px;
text-anchor: middle;
dominant-baseline: middle;
}
.dot {
fill: #333;
}
const data = {
name: "Shobhan",
spouse: "Jatabai",
children: [
{
name: "Mahesh",
side: "left",
spouse: "Sunita",
children: [
{ name: "Sogan", spouse: "Samarta", children: [
{ name: "Samaraj" },
{ name: "Satyam" }
]}
]
},
{
name: "Renjana",
side: "right",
spouse: "Ramchandra",
children: [
{ name: "Aakash", spouse: "Neka" },
{ name: "Vishal", spouse: "Priti" }
]
}
]
};
const svg = d3.select("#svg");
const centerX = 600;
const topY = 80;
const leftX = 300;
const rightX = 900;
const nodeW = 100;
const nodeH = 36;
const vGap = 30;
let nodes = [];
let links = [];
// calculate height of an entire family block
function familyHeight(person) {
let h = nodeH;
if (person.children) {
person.children.forEach(c => {
h += familyHeight(c) + vGap;
});
}
return h;
}
// recursive layout
function layoutFamily(person, x, y, parentMid) {
const node = { ...person, x, y };
nodes.push(node);
if (parentMid) {
links.push({
x1: parentMid.x,
y1: parentMid.y,
x2: x,
y2: y
});
}
if (!person.children) return y + nodeH + vGap;
let cy = y + nodeH + vGap;
person.children.forEach(child => {
cy = layoutFamily(child, x, cy, { x, y });
});
return cy;
}
// root connector
svg.append("line")
.attr("x1", centerX)
.attr("y1", 20)
.attr("x2", centerX)
.attr("y2", topY)
.attr("stroke", "#000")
.attr("stroke-width", 2);
let leftY = topY + 60;
let rightY = topY + 60;
// layout children
data.children.forEach(child => {
const h = familyHeight(child);
const x = child.side === "left" ? leftX : rightX;
const y = child.side === "left" ? leftY : rightY;
links.push({
x1: centerX,
y1: topY,
x2: x,
y2: y
});
layoutFamily(child, x, y, null);
if (child.side === "left") leftY += h + vGap;
else rightY += h + vGap;
});
// draw links
svg.selectAll(".link")
.data(links)
.enter()
.append("path")
.attr("class", "link")
.attr("d", d => `
M ${d.x1} ${d.y1}
V ${(d.y1 + d.y2) / 2}
H ${d.x2}
V ${d.y2}
`);
// draw nodes
const g = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", d => `translate(${d.x},${d.y})`);
g.append("rect")
.attr("class", "male")
.attr("x", -nodeW - 10)
.attr("y", -nodeH / 2)
.attr("width", nodeW)
.attr("height", nodeH);
g.append("text")
.attr("x", -nodeW / 2 - 10)
.text(d => d.name);
const couples = g.filter(d => d.spouse);
couples.append("rect")
.attr("class", "female")
.attr("x", 10)
.attr("y", -nodeH / 2)
.attr("width", nodeW)
.attr("height", nodeH);
couples.append("text")
.attr("x", nodeW / 2 + 10)
.text(d => d.spouse);
couples.append("circle")
.attr("r", 4);
Подробнее здесь: https://stackoverflow.com/questions/798 ... ut-d3-tree