У меня есть график, разработанный в d3 js, где в центре лежит корневой узел, который представляет собой прямоугольник, и из этого прямоугольника отображаются 4 других узла с использованием линий, чтобы показать им связи с ним. У меня проблема с выравниванием прямоугольника по центру. Как мне его централизовать?
Вот мой код
var data = {
"name": "root",
"children": [{
"name": "Person Name 1",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}, {
"name": "Person name 2",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}, {
"name": "Person Name 3",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}, {
"name": "Person Name 4",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}
]
};
const LAST_CHILDREN_WIDTH = 13;
let flagForChildren = false;
var groups = [];
data.children.forEach(d => {
let a = [];
if (d.children.length > 0) {
flagForChildren = true;
}
for (var i = 0; i < d.children.length; i += 2) {
let b = d.children.slice(i, i + 2);
if (b[0] && b[1]) {
a.push(Object.assign(b[0], { children: [b[1]] }));
} else {
let child = b[0];
if (i === 7) {
child = Object.assign(child, { children: [{ name: "..." }] });
}
a.push(child);
}
}
d.children = a;
groups.push(d);
});
data.children = groups;
let split_index = Math.round(data.children.length / 2);
let rectangleHeight = 50;
// Left data
let leftData = {
name: data.name,
children: JSON.parse(JSON.stringify(data.children.slice(0, split_index)))
};
// Right data
let rightData = {
name: data.name,
children: JSON.parse(JSON.stringify(data.children.slice(split_index)))
};
// Create d3 hierarchies
let left = d3.hierarchy(leftData);
// Render both trees
drawTree(left, "left");
drawTree(left, "right");
// draw single tree
function drawTree(root, pos) {
let SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
var margin = { top: 20, right: 90, bottom: 30, left: 90 },
width = window.innerWidth - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
let svg = d3
.select("svg")
.attr("height", height + margin.top + margin.bottom)
.attr("width", width + margin.right + margin.left)
.attr('view-box', '0 0 ' + (width + margin.right) + ' ' + (height + margin.top + margin.bottom))
.attr('preserveAspectRatio', "xMidYMid meet")
.style("margin-left", "-30px")
.style("margin-top", "80px");
// Shift the entire tree by half it's width
let g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");
let deductWidthValue = flagForChildren ? 0 : width * 0.33;
// Create new default tree layout
let tree = d3
.tree()
.size([height - 50, SWITCH_CONST * (width - deductWidthValue) / 2])
.separation(function(a, b) {
return a.parent === b.parent ? 4 : 4.25;
});
tree(root);
let nodes = root.descendants();
let links = root.links();
// Set both root nodes to be dead center vertically
nodes[0].x = height / 2;
// Create links
let link = g
.selectAll(".link")
.data(links)
.enter();
link
.append("line")
.attr("class", "link")
.attr("x1", function(d) {
if (
d.target.parent &&
d.target.parent.parent &&
d.target.parent.parent.parent
) {
return 0;
}
return d.source.y + 100 / 2;
})
.attr("x2", function(d) {
if (
d.target.parent &&
d.target.parent.parent &&
d.target.parent.parent.parent
) {
return 0;
} else if (d.target.parent && d.target.parent.parent) {
return d.target.y;
}
return d.target.y + 100 / 2;
})
.attr("y1", function(d) {
if (
d.target.parent &&
d.target.parent.parent &&
d.target.parent.parent.parent
) {
return 0;
}
return d.source.x + 50 / 2;
})
.attr("y2", function(d) {
if (
d.target.parent &&
d.target.parent.parent &&
d.target.parent.parent.parent
) {
return 0;
} else if (d.target.parent && d.target.parent.parent) {
return d.target.x + LAST_CHILDREN_WIDTH / 2;
}
return d.target.x + 50 / 2;
});
//Rectangle width
let node = g
.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", function(d) {
return "node" + (d.children ? " node--internal" : " node--leaf");
})
.attr("transform", function(d) {
if (d.parent && d.parent.parent) {
if (d.parent.parent.parent) {
return (
"translate(" +
d.parent.y +
"," +
(d.x + LAST_CHILDREN_WIDTH + 3) +
")"
);
}
return "translate(" + d.y + "," + d.x + ")";
}
return "translate(" + d.y + "," + d.x + ")";
});
node
.append("rect")
.attr("height", function(d, i) {
return d.parent && d.parent.parent ? 20 : rectangleHeight;
})
.attr("width", function(d, i) {
return d.parent && d.parent.parent ? 15 : rectangleWidth(d);
})
.attr("rx", function(d, i) {
return d.parent && d.parent.parent ? 5 : 5;
})
.attr("ry", function(d, i) {
return d.parent && d.parent.parent ? 5 : 5;
});
node
.append("text")
.attr("dy", function(d, i) {
return d.parent && d.parent.parent ? 18 : 28;
})
.attr("dx", function(d, i) {
if (!(d.parent && d.parent.parent)) {
return 10;
} else {
return 20;
}
})
.style("fill", function(d, i) {
return d.parent && d.parent.parent ? "Black" : "White";
})
.text(function(d) {
let name = d.data.topic_name || d.data.name;
return name;
})
}
function rectangleWidth(d) {
let dynamicLength = 6;
if (d.data.topic_name) {
dynamicLength = d.data.topic_name.length;
} else if (d.data.name) {
dynamicLength = d.data.name.length;
}
dynamicLength = dynamicLength < 5 ? 5 : dynamicLength;
dynamicLength = dynamicLength > 30 ? 30 : dynamicLength;
return 10 + dynamicLength * 8;
}
rect {
fill: #149c10;
}
text {
font: 14px sans-serif;
}
text:hover {
cursor: pointer;
}
.breadcrumb-chip {
margin-top: 10px;
background: #5dca5d !important;
margin-left: -30px;
}
.breadcrumb-chip:hover {
cursor: pointer;
}
line {
fill: #149c10;
stroke-width: 1px;
stroke: #149c10;
}
.breadcrumb-path {
display: block;
width: 1px;
border: 0.5px solid #149c10;
position: absolute;
top: 90px;
margin-left: 0px;
bottom: 0;
height: 10px;
}
.search-topic {
margin: auto;
}
.btn-topic-search {
min-height: 40px !important;
}
.breadcrumb-chip-container {
width: 0px !important;
margin: 0 auto !important;
}<!DOCTYPE html>
<html>
<head>
<meta charset = "utf-8">
<meta name = "viewport" content = "width=device-width">
<title>JS Bin</title>
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script>
</head>
<body>
<svg className='spider-graph-svg'>
</svg>
</body>
</html>Размер прямоугольника зависит от длины текста, потому что иногда текст может быть длинным, и текст должен быть виден внутри прямоугольника, но корневой узел не выровнен по центру должным образом. Вертикальная линия тем должна быть посередине корневого узла. Как это сделать?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Быстрый ответ
node.filter(d=>d.depth==0)
.attr('transform',function(d){
var width = this.getBoundingClientRect().width;
return `translate(${d.y+width/2},${d.x})`
})
Проверить сниппет
var data = {
"name": "root",
"children": [{
"name": "Person Name 1",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}, {
"name": "Person name 2",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}, {
"name": "Person Name 3",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}, {
"name": "Person Name 4",
"children": [{
"name": "Branch 4.1"
}, {
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
},{
"name": "Branch 4.2"
},
{
"name": "Branch 4.2"
}
]
}
]
};
const LAST_CHILDREN_WIDTH = 13;
let flagForChildren = false;
var groups = [];
data.children.forEach(d => {
let a = [];
if (d.children.length > 0) {
flagForChildren = true;
}
for (var i = 0; i < d.children.length; i += 2) {
let b = d.children.slice(i, i + 2);
if (b[0] && b[1]) {
a.push(Object.assign(b[0], { children: [b[1]] }));
} else {
let child = b[0];
if (i === 7) {
child = Object.assign(child, { children: [{ name: "..." }] });
}
a.push(child);
}
}
d.children = a;
groups.push(d);
});
data.children = groups;
let split_index = Math.round(data.children.length / 2);
let rectangleHeight = 50;
// Left data
let leftData = {
name: data.name,
children: JSON.parse(JSON.stringify(data.children.slice(0, split_index)))
};
// Right data
let rightData = {
name: data.name,
children: JSON.parse(JSON.stringify(data.children.slice(split_index)))
};
// Create d3 hierarchies
let left = d3.hierarchy(leftData);
// Render both trees
drawTree(left, "left");
drawTree(left, "right");
// draw single tree
function drawTree(root, pos) {
let SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
var margin = { top: 20, right: 90, bottom: 30, left: 90 },
width = window.innerWidth - margin.left - margin.right,
height = 600 - margin.top - margin.bottom;
let svg = d3
.select("svg")
.attr("height", height + margin.top + margin.bottom)
.attr("width", width + margin.right + margin.left)
.attr('view-box', '0 0 ' + (width + margin.right) + ' ' + (height + margin.top + margin.bottom))
.attr('preserveAspectRatio', "xMidYMid meet")
.style("margin-left", "-30px")
.style("margin-top", "80px");
// Shift the entire tree by half it's width
let g = svg.append("g").attr("transform", "translate(" + width / 2 + ",0)");
let deductWidthValue = flagForChildren ? 0 : width * 0.33;
// Create new default tree layout
let tree = d3
.tree()
.size([height - 50, SWITCH_CONST * (width - deductWidthValue) / 2])
.separation(function(a, b) {
return a.parent === b.parent ? 4 : 4.25;
});
tree(root);
let nodes = root.descendants();
let links = root.links();
// Set both root nodes to be dead center vertically
nodes[0].x = height / 2;
// Create links
let link = g
.selectAll(".link")
.data(links)
.enter();
link
.append("line")
.attr("class", "link")
.attr("x1", function(d) {
if (
d.target.parent &&
d.target.parent.parent &&
d.target.parent.parent.parent
) {
return 0;
}
return d.source.y + 100 / 2;
})
.attr("x2", function(d) {
if (
d.target.parent &&
d.target.parent.parent &&
d.target.parent.parent.parent
) {
return 0;
} else if (d.target.parent && d.target.parent.parent) {
return d.target.y;
}
return d.target.y + 100 / 2;
})
.attr("y1", function(d) {
if (
d.target.parent &&
d.target.parent.parent &&
d.target.parent.parent.parent
) {
return 0;
}
return d.source.x + 50 / 2;
})
.attr("y2", function(d) {
if (
d.target.parent &&
d.target.parent.parent &&
d.target.parent.parent.parent
) {
return 0;
} else if (d.target.parent && d.target.parent.parent) {
return d.target.x + LAST_CHILDREN_WIDTH / 2;
}
return d.target.x + 50 / 2;
});
//Rectangle width
let node = g
.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", function(d) {
return "node" + (d.children ? " node--internal" : " node--leaf");
})
.attr("transform", function(d) {
if (d.parent && d.parent.parent) {
if (d.parent.parent.parent) {
return (
"translate(" +
d.parent.y +
"," +
(d.x + LAST_CHILDREN_WIDTH + 3) +
")"
);
}
return "translate(" + d.y + "," + d.x + ")";
}
return "translate(" + d.y + "," + d.x + ")";
});
node
.append("rect")
.attr("height", function(d, i) {
return d.parent && d.parent.parent ? 20 : rectangleHeight;
})
.attr("width", function(d, i) {
return d.parent && d.parent.parent ? 15 : rectangleWidth(d);
})
.attr("rx", function(d, i) {
return d.parent && d.parent.parent ? 5 : 5;
})
.attr("ry", function(d, i) {
return d.parent && d.parent.parent ? 5 : 5;
});
node
.append("text")
.attr("dy", function(d, i) {
return d.parent && d.parent.parent ? 18 : 28;
})
.attr("dx", function(d, i) {
if (!(d.parent && d.parent.parent)) {
return 10;
} else {
return 20;
}
})
.style("fill", function(d, i) {
return d.parent && d.parent.parent ? "Black" : "White";
})
.text(function(d) {
let name = d.data.topic_name || d.data.name;
return name;
})
node.filter(d=>d.depth==0)
.attr('transform',function(d){
var width = this.getBoundingClientRect().width;
return `translate(${d.y+width/2},${d.x})`
})
}
function rectangleWidth(d) {
let dynamicLength = 6;
if (d.data.topic_name) {
dynamicLength = d.data.topic_name.length;
} else if (d.data.name) {
dynamicLength = d.data.name.length;
}
dynamicLength = dynamicLength < 5 ? 5 : dynamicLength;
dynamicLength = dynamicLength > 30 ? 30 : dynamicLength;
return 10 + dynamicLength * 8;
}
rect {
fill: #149c10;
}
text {
font: 14px sans-serif;
}
text:hover {
cursor: pointer;
}
.breadcrumb-chip {
margin-top: 10px;
background: #5dca5d !important;
margin-left: -30px;
}
.breadcrumb-chip:hover {
cursor: pointer;
}
line {
fill: #149c10;
stroke-width: 1px;
stroke: #149c10;
}
.breadcrumb-path {
display: block;
width: 1px;
border: 0.5px solid #149c10;
position: absolute;
top: 90px;
margin-left: 0px;
bottom: 0;
height: 10px;
}
.search-topic {
margin: auto;
}
.btn-topic-search {
min-height: 40px !important;
}
.breadcrumb-chip-container {
width: 0px !important;
margin: 0 auto !important;
}<!DOCTYPE html>
<html>
<head>
<meta charset = "utf-8">
<meta name = "viewport" content = "width=device-width">
<title>JS Bin</title>
<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script>
</head>
<body>
<svg className='spider-graph-svg'>
</svg>
</body>
</html>
если текст очень маленький, он не будет централизован