某些节点折叠的 D3 强制布局
D3 force layout with some nodes collapsed
我是 d3 的完全初学者,几个星期以来一直被一个问题困扰。
基于示例:https://jsfiddle.net/t4vzg650/6/ 我用 d3 开发了一个可折叠的力布局。我已经成功地添加了链接和音频(目前只能在本地工作)。
但默认情况下,我只希望展开最佳实践、工具和课程结构节点,并折叠其他节点。然后可以通过双击按需打开它们。
我这里也遇到过类似的问题,但是一直没有实现。
var data = {
"name": "Best Practices",
"size": 50,
"color": "#C37B89",
"font": 15,
"children": [{
"name": "Course Structure",
"size": 50,
"color": "#BCCC9A",
"font": 15,
"_children": null,
"children": [{
"name": "x",
"size": 30,
"color": "#BCCC9A",
"font": 8,
"_children": null,
"children" : [{
"name": "x",
"size": 30,
"color": "#BCCC9A",
"font": 8,
}, {
"name": "x",
"size": 30,
"color": "#BCCC9A",
"font": 8,
}
]
}, {
"name": "x",
"size": 30,
"color": "#BCCC9A",
"font": 8,
}]
}, {
"name": "Tools",
"size": 50,
"color": "#EAE7C6",
"font": 15,
"_children": null,
"children": [{
"name": "x",
"size": 30,
"color": "#EAE7C6",
"font": 8
}, {
"name": "x",
"size": 30,
"color": "#EAE7C6",
"font": 8
}]
}]
};
var i = 0;
var root = d3.hierarchy(data);
var nodeSvg, linkSvg, nodeEnter, linkEnter;
var width = 960
var height = 600
var centerx = width/2
var centery = height/2
var svg = d3.select("#network")
.attr("width", width)
.attr("height", height)
.append("g");
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(100))
.force("charge", d3.forceManyBody().strength(-300))
//center network
.force("center", d3.forceCenter(centerx, centery))
.on("tick", ticked);
update();
function update() {
var nodes = flatten(root);
var links = root.links()
linkSvg = svg.selectAll(".link")
.data(links, function(d) {
return d.target.id;
})
linkSvg.exit().remove();
var linkEnter = linkSvg.enter()
.append("line")
.attr("class", "link");
linkSvg = linkEnter.merge(linkSvg)
nodeSvg = svg.selectAll(".node")
.data(nodes, function(d) {
return d.id;
})
nodeSvg.exit().remove();
var nodeEnter = nodeSvg.enter()
.append("g")
.attr("class", "node")
.on("dblclick", click)
.call(d3.drag()
.on("end", dragended))
nodeEnter.append("circle")
.attr("r", function(d) {return d.data.size})
.attr("fill", function(d) {return d.data.color})
nodeEnter.append("text")
.text(function(d) {
return d.data.name;})
.attr("font-size", function(d) {return d.data.font})
.attr("class", "headline")
nodeSvg = nodeEnter.merge(nodeSvg);
simulation
.nodes(nodes)
simulation.force("link")
.links(links);
}
function ticked() {
linkSvg
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
nodeSvg
.attr("transform", function(d) {
return "translate(" + d.x + ", " + d.y + ")";
});
}
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
update();
simulation.restart();
} else {
d.children = d._children;
d._children = null;
update();
simulation.restart();
}
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0)
d.fx = null
d.fy = null
}
function flatten(root) {
var nodes = [];
function recurse(node) {
if (node.children) node.children.forEach(recurse);
if (!node.id) node.id = ++i;
else ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
我的代码在这里:https://codepen.io/LaraB1612/pen/bGreEWV
如果有人能帮助我,我会很高兴。
非常感谢
我已经想出了如何实现这个:
- 不需要运行点击方法的每个条件中的update和restart方法,检查条件后一次就可以了:
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
- 调用flatten方法后需要调用update方法。在这种情况下,您还需要展平根的两个子项,包括工具和课程结构。它可能不是最优雅的解决方案,但它确实有效:
var flattenedChildOne = flatten(root.children[0]);
var flattenedChildTwo = flatten(root.children[1]);
flattenedChildOne.forEach(function(d) {
d._children = d.children;
d.children = null;
});
flattenedChildTwo.forEach(function(d) {
d._children = d.children;
d.children = null;
});
update();
- 我已经从你的数据中删除了所有
"_children": null
,它们是不必要的。
我在这里解决了这个问题:
https://jsfiddle.net/orsisi/tnv37wxr/39/
我是 d3 的完全初学者,几个星期以来一直被一个问题困扰。
基于示例:https://jsfiddle.net/t4vzg650/6/ 我用 d3 开发了一个可折叠的力布局。我已经成功地添加了链接和音频(目前只能在本地工作)。 但默认情况下,我只希望展开最佳实践、工具和课程结构节点,并折叠其他节点。然后可以通过双击按需打开它们。 我这里也遇到过类似的问题,但是一直没有实现。
var data = {
"name": "Best Practices",
"size": 50,
"color": "#C37B89",
"font": 15,
"children": [{
"name": "Course Structure",
"size": 50,
"color": "#BCCC9A",
"font": 15,
"_children": null,
"children": [{
"name": "x",
"size": 30,
"color": "#BCCC9A",
"font": 8,
"_children": null,
"children" : [{
"name": "x",
"size": 30,
"color": "#BCCC9A",
"font": 8,
}, {
"name": "x",
"size": 30,
"color": "#BCCC9A",
"font": 8,
}
]
}, {
"name": "x",
"size": 30,
"color": "#BCCC9A",
"font": 8,
}]
}, {
"name": "Tools",
"size": 50,
"color": "#EAE7C6",
"font": 15,
"_children": null,
"children": [{
"name": "x",
"size": 30,
"color": "#EAE7C6",
"font": 8
}, {
"name": "x",
"size": 30,
"color": "#EAE7C6",
"font": 8
}]
}]
};
var i = 0;
var root = d3.hierarchy(data);
var nodeSvg, linkSvg, nodeEnter, linkEnter;
var width = 960
var height = 600
var centerx = width/2
var centery = height/2
var svg = d3.select("#network")
.attr("width", width)
.attr("height", height)
.append("g");
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(100))
.force("charge", d3.forceManyBody().strength(-300))
//center network
.force("center", d3.forceCenter(centerx, centery))
.on("tick", ticked);
update();
function update() {
var nodes = flatten(root);
var links = root.links()
linkSvg = svg.selectAll(".link")
.data(links, function(d) {
return d.target.id;
})
linkSvg.exit().remove();
var linkEnter = linkSvg.enter()
.append("line")
.attr("class", "link");
linkSvg = linkEnter.merge(linkSvg)
nodeSvg = svg.selectAll(".node")
.data(nodes, function(d) {
return d.id;
})
nodeSvg.exit().remove();
var nodeEnter = nodeSvg.enter()
.append("g")
.attr("class", "node")
.on("dblclick", click)
.call(d3.drag()
.on("end", dragended))
nodeEnter.append("circle")
.attr("r", function(d) {return d.data.size})
.attr("fill", function(d) {return d.data.color})
nodeEnter.append("text")
.text(function(d) {
return d.data.name;})
.attr("font-size", function(d) {return d.data.font})
.attr("class", "headline")
nodeSvg = nodeEnter.merge(nodeSvg);
simulation
.nodes(nodes)
simulation.force("link")
.links(links);
}
function ticked() {
linkSvg
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
nodeSvg
.attr("transform", function(d) {
return "translate(" + d.x + ", " + d.y + ")";
});
}
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
update();
simulation.restart();
} else {
d.children = d._children;
d._children = null;
update();
simulation.restart();
}
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0)
d.fx = null
d.fy = null
}
function flatten(root) {
var nodes = [];
function recurse(node) {
if (node.children) node.children.forEach(recurse);
if (!node.id) node.id = ++i;
else ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
我的代码在这里:https://codepen.io/LaraB1612/pen/bGreEWV
如果有人能帮助我,我会很高兴。
非常感谢
我已经想出了如何实现这个:
- 不需要运行点击方法的每个条件中的update和restart方法,检查条件后一次就可以了:
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
- 调用flatten方法后需要调用update方法。在这种情况下,您还需要展平根的两个子项,包括工具和课程结构。它可能不是最优雅的解决方案,但它确实有效:
var flattenedChildOne = flatten(root.children[0]);
var flattenedChildTwo = flatten(root.children[1]);
flattenedChildOne.forEach(function(d) {
d._children = d.children;
d.children = null;
});
flattenedChildTwo.forEach(function(d) {
d._children = d.children;
d.children = null;
});
update();
- 我已经从你的数据中删除了所有
"_children": null
,它们是不必要的。
我在这里解决了这个问题: https://jsfiddle.net/orsisi/tnv37wxr/39/