使子节点靠近d3中的父节点 [英] bring child node close to the parent node in d3
问题描述
我正在尝试开发一个树形图,其中有一个中心节点,该节点将有4个子节点.那些 子节点将有7个不同的节点,但是应该像上图中那样在其父节点附近显示这7个不同的节点.如果我尝试减小该值以使其更接近,则树的一侧(左侧或右侧)会被弄乱.
I am trying to develop a tree graph where there is a center node which will have 4 child node. Those child node will have 7 different nodes but those 7 different nodes should be shown just near its parent node like in the attached diagram. If i try to decrease the value to bring them closer, one of the side(either left side or right side) of the tree gets messed up.
这是我所做的
line.link {
stroke: black;
}
line.hard--link {
stroke: black;
stroke-width: 2px;
}
<!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>
<script>
var data = {
"name": "root@gmail.com",
"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;
let groups = [];
data.children.forEach(d => {
let a = [];
if (d.children.length > 0) {
flagForChildren = true;
}
for (let 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 >= 6) {
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 = 45;
let leftData = {
name: data.name,
children: JSON.parse(JSON.stringify(data.children.slice(0, split_index)))
};
let leftDataArray = [];
leftDataArray.push(leftData);
// Right data
let rightData = {
name: data.name,
children: JSON.parse(JSON.stringify(data.children.slice(split_index)))
};
// Create d3 hierarchies
let right = d3.hierarchy(rightData);
let left = d3.hierarchy(leftData);
// Render both trees
drawTree(right, "right");
drawTree(left, "left");
// draw single tree
function drawTree(root, pos) {
let SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
const margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = window.innerWidth - margin.left - margin.right,
height = 500 - 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))
.style("margin-top", "20px")
.style("margin-left", "88px");
const div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// 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()
// Set the size
// Remember the tree is rotated
// so the height is used as the width
// and the width as the height
.size([height - 50, SWITCH_CONST * (width - deductWidthValue) / 2])
.separation((a, b) => 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", function(d) {
if (d.target.depth === 2) {
return 'link'
} else {
return 'hard--link'
}
})
.attr("x1", function(d) {
if (
d.target.depth === 3
) {
return 0;
}
return d.source.y + 100 / 2; //d.source.y + 100/2
})
.attr("x2", function(d) {
if (
d.target.depth === 3
) {
return 0;
} else if (d.target.depth === 2) {
return d.target.y;
}
return d.target.y + 100 / 2; //d.target.y + 100/2;
})
.attr("y1", function(d) {
if (
d.target.depth === 3
) {
return 0;
}
return d.source.x + 50 / 2;
})
.attr("y2", function(d) {
if (
d.target.depth === 3
) {
return 0;
} else if (d.target.depth === 2) {
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")
.on("mouseover", function(d) {
const dynamicLength = (d.data.topic_name && d.data.topic_name.length) ||
(d.data.name && d.data.name.length);
const rectWidth = dynamicLength <= 3 ? '60px' : `${dynamicLength * 8}px`;
div.transition()
.duration(200)
.style("opacity", 1);
div.html(d.data.topic_name || d.data.name)
.style("left", (d3.event.pageX) + "px")
.style("width", rectWidth)
.style("text-anchor", "middle")
.style("vertical-align", "baseline")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", d => {
div.transition()
.duration(500)
.style("opacity", 0);
})
.attr("class", function(d) {
return "node" + (d.children ? " node--internal" : " node--leaf");
})
.attr("transform", function(d) {
if (d.parent && d.parent.parent) { // this is the leaf node
if (d.parent.parent.parent) {
return (
"translate(" +
d.parent.y +
"," +
(d.x + LAST_CHILDREN_WIDTH + 15) +
")"
);
}
return "translate(" + d.y + "," + d.x + ")";
}
// Select the node with height 2
if (d.height === 2) {
//Lets line this up with its 2nd child (index = 1)
//If y of this child is <0, it means the parent and the child
//both are on the left
//side (with margin of 20 between parent and child)
if (d.children[1]['y'] < 0) {
return "translate(" + (d.children[1]['y'] + LAST_CHILDREN_WIDTH + 20) + "," + d.children[1]['x'] + ")"
// Else both parent and child are on the right.
//Now we also need to take into consideration the width
//of the rectangle (with margin of 20 between parent and child)
} else {
return "translate(" + (d.children[1]['y'] - rectangleWidth(d) - 20) + "," + (d['x']) + ")"
}
} else {
//This is the root of the tree.
//Subtract d.y by half of rectangleWidth because we need it to be in the center
//Same for d.x
return "translate(" + (d.y - (rectangleWidth(d) / 2)) + "," + (d.x - (rectangleHeight / 2)) + ")";
}
});
// topic rect
node
.append("rect")
.attr("height", (d, i) => d.parent && d.parent.parent ? 15 : rectangleHeight)
.attr("width", (d, i) => d.parent && d.parent.parent ? 15 : rectangleWidth(d))
.attr("rx", (d, i) => d.parent && d.parent.parent ? 5 : 5)
.attr("ry", (d, i) => d.parent && d.parent.parent ? 5 : 5)
// topic edges
node.append('line')
.attr('x1', d => {
if (d.depth === 2) {
return 10
}
})
.attr('x2', d => {
if (d.depth === 2) {
return 10
}
})
.attr('y1', d => {
if (d.depth === 2) {
if (d.children) {
return 0;
}
return 40;
}
})
.attr('y2', d => {
if (d.depth === 2) {
return 40
}
})
.attr('class', 'hard--link')
// topic names
node
.append("text")
.attr("dy", function(d, i) {
return d.parent && d.parent.parent ? 10 : rectangleHeight / 2;
})
.attr("dx", function(d, i) {
if (!(d.parent && d.parent.parent)) {
return 12;
} 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.length > 12 ? `${name.substring(0, 12)}...` : name;
})
.style("text-anchor", function(d) {
if (d.parent && d.parent.parent) {
return pos === "left" && "end"
}
})
.style("font-size", "12")
.attr("transform", function(d) {
if (d.parent && d.parent.parent) {
return pos === "left" ? "translate(-30,0)" : "translate(5,0)"
}
})
}
function rectangleWidth(d) {
const MIN_WIDTH = 50;
const MAX_WIDTH = 100;
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 < 3 ? MIN_WIDTH : MAX_WIDTH;
return dynamicLength;
}
</script>
</body>
</html>
预期的设计
最后一个节点的预期设计
推荐答案
节点的x
和y
是由d3
计算的,但是放置位置看起来不正确,可能是因为height
和rects
的width
没有被考虑.
The x
and y
for the nodes is being calculated by d3
but the placement doesn't look right maybe because the height
and the width
of the rects
weren't taken into account.
因此,我在根据d3计算出的x
和y
来翻译rects
的部分中的代码进行了一些更改,如下所示:
So I made a few changes to the code in the section where you are translating the rects
based on d3's calculated x
and y
like so:
.attr("transform", function (d) {
if (d.parent && d.parent.parent) { // this is the leaf node
if (d.parent.parent.parent) {
return (
"translate(" +
d.parent.y +
"," +
(d.x + LAST_CHILDREN_WIDTH + 15) +
")"
);
}
return "translate(" + d.y + "," + d.x + ")";
}
// Select the node with height 2
if (d.height == 2) {
//Lets line this up with its 2nd child (index = 1)
//If y of this child is <0, it means the parent and the child
//both are on the left
//side (with margin of 20 between parent and child)
if (d.children[1]['y'] < 0) {
return "translate(" + (d.children[1]['y'] + LAST_CHILDREN_WIDTH + 20) + "," + d.children[1]['x'] + ")"
// Else both parent and child are on the right.
//Now we also need to take into consideration the width
//of the rectangle (with margin of 20 between parent and child)
} else {
return "translate(" + (d.children[1]['y'] - rectangleWidth(d) - 20) + "," + (d['x']) + ")"
}
} else {
//This is the root of the tree.
//Subtract d.y by half of rectangleWidth because we need it to be in the center
//Same for d.x
return "translate(" + (d.y - (rectangleWidth(d) / 2)) + "," + (d.x - (rectangleHeight / 2)) + ")";
}
});
这是这些更改的小提琴.
Here's the fiddle with these changes.
body {
background: black
}
rect {
fill: darkgreen
}
line {
stroke: lightgreen;
stroke-width: 1
}
text {
font-family: 'Calibri';
}
.tooltip {
color: white
}
<!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>
<script>
var data = {
"name": "root@gmail.com",
"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;
let groups = [];
data.children.forEach(d => {
let a = [];
if (d.children.length > 0) {
flagForChildren = true;
}
for (let 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 >= 6) {
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 = 45;
let leftData = {
name: data.name,
children: JSON.parse(JSON.stringify(data.children.slice(0, split_index)))
};
let leftDataArray = [];
leftDataArray.push(leftData);
// Right data
let rightData = {
name: data.name,
children: JSON.parse(JSON.stringify(data.children.slice(split_index)))
};
// Create d3 hierarchies
let right = d3.hierarchy(rightData);
let left = d3.hierarchy(leftData);
console.log(right.descendants())
// Render both trees
drawTree(right, "right");
drawTree(left, "left");
// draw single tree
function drawTree(root, pos) {
let SWITCH_CONST = 1;
if (pos === "left") {
SWITCH_CONST = -1;
}
const margin = {
top: 10,
right: 10,
bottom: 10,
left: 10
},
width = window.innerWidth - margin.left - margin.right,
height = window.innerHeight - 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))
/* .style("margin-top", "20px")
.style("margin-left", "88px"); */
const div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// 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()
// Set the size
// Remember the tree is rotated
// so the height is used as the width
// and the width as the height
.size([height - 50, SWITCH_CONST * (width - deductWidthValue) / 2])
.separation((a, b) => 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", function(d) {
if (d.target.depth === 2) {
return 'link'
} else {
return 'hard--link'
}
})
.attr("x1", function(d) {
if (d.target.depth === 3) {
return 0;
} else if (d.target.depth === 2) {
if (d.source.y < 0) {
return (d.source.y + 100 / 2) - 100;
} else {
return (d.source.y + 100 / 2)
}
}
return 0; //d.source.y + 100/2
})
.attr("x2", function(d) {
if (d.target.depth === 3) {
return 0;
} else if (d.target.depth === 2) {
return d.target.y + 10;
} else if (d.target.depth === 1) {
if (d.target.y < 0) {
return d.target.y - 100 / 2
} else {
return d.target.y + 100 / 2;
}
}
return d.target.y + 100 / 2; //d.target.y + 100/2;
})
.attr("y1", function(d) {
if (d.target.depth === 3) {
return 0;
} else if (d.target.depth === 1) {
return (d.source.x + 50 / 2) - 20;
} else {
return d.source.x + 50 / 2;
}
})
.attr("y2", function(d) {
if (d.target.depth === 3) {
return 0;
} else if (d.target.depth === 2) {
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")
.on("mouseover", function(d) {
const dynamicLength = (d.data.topic_name && d.data.topic_name.length) ||
(d.data.name && d.data.name.length);
const rectWidth = dynamicLength <= 3 ? '60px' : `${dynamicLength * 8}px`;
div.transition()
.duration(200)
.style("opacity", 1);
div.html(d.data.topic_name || d.data.name)
.style("left", (d3.event.pageX) + "px")
.style("width", rectWidth)
.style("text-anchor", "middle")
.style("vertical-align", "baseline")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", d => {
div.transition()
.duration(500)
.style("opacity", 0);
})
.attr("class", function(d) {
return "node" + (d.children ? " node--internal" : " node--leaf");
})
.attr("transform", function(d) {
if (d.parent && d.parent.parent) { // this is the leaf node
if (d.parent.parent.parent) {
return (
"translate(" +
d.parent.y +
"," +
(d.x + LAST_CHILDREN_WIDTH + 15) +
")"
);
}
return "translate(" + d.y + "," + d.x + ")";
}
// Select the node with height 2
if (d.height == 2) {
//Lets line this up with its 2nd child (index = 1)
//If y of this child is <0, it means the parent and the child
//both are on the left
//side (with margin of 20 between parent and child)
if (d.children[1]['y'] < 0) {
return "translate(" + (d.children[1]['y'] + LAST_CHILDREN_WIDTH + 20) + "," + d.children[1]['x'] + ")"
// Else both parent and child are on the right.
//Now we also need to take into consideration the width
//of the rectangle (with margin of 20 between parent and child)
} else {
return "translate(" + (d.children[1]['y'] - rectangleWidth(d) - 20) + "," + (d['x']) + ")"
}
} else {
//This is the root of the tree.
//Subtract d.y by half of rectangleWidth because we need it to be in the center
//Same for d.x
return "translate(" + (d.y - (rectangleWidth(d) / 2)) + "," + (d.x - (rectangleHeight / 2)) + ")";
}
})
.on('click', function(d) {
console.log(d)
});
// topic rect
node
.append("rect")
.attr("height", (d, i) => d.parent && d.parent.parent ? 15 : rectangleHeight)
.attr("width", (d, i) => d.parent && d.parent.parent ? 15 : rectangleWidth(d))
.attr("rx", (d, i) => d.parent && d.parent.parent ? 5 : 5)
.attr("ry", (d, i) => d.parent && d.parent.parent ? 5 : 5)
// topic edges
node.append('line')
.attr('x1', d => {
if (d.depth === 2) {
return 10
}
})
.attr('x2', d => {
if (d.depth === 2) {
return 10
}
})
.attr('y1', d => {
if (d.depth === 2) {
if (d.children) {
return 0;
}
return 40;
}
})
.attr('y2', d => {
if (d.depth === 2) {
return 40
}
})
.attr('class', 'hard--link')
// topic names
node
.append("text")
.attr("dy", function(d, i) {
return d.parent && d.parent.parent ? 10 : rectangleHeight / 2;
})
.attr("dx", function(d, i) {
if (!(d.parent && d.parent.parent)) {
return 12;
} else {
return 20;
}
})
.style("fill", function(d, i) {
return d.parent && d.parent.parent ? "White" : "White";
})
.text(function(d) {
let name = d.data.topic_name || d.data.name;
return name.length > 12 ? `${name.substring(0, 12)}...` : name;
})
.style("text-anchor", function(d) {
if (d.parent && d.parent.parent) {
return pos === "left" && "end"
}
})
.style("font-size", "12")
.attr("transform", function(d) {
if (d.parent && d.parent.parent) {
return pos === "left" ? "translate(-30,0)" : "translate(5,0)"
}
})
}
function rectangleWidth(d) {
const MIN_WIDTH = 50;
const MAX_WIDTH = 100;
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 < 3 ? MIN_WIDTH : MAX_WIDTH;
return dynamicLength;
}
</script>
</body>
</html>
这篇关于使子节点靠近d3中的父节点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!