某些节点折叠的 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

如果有人能帮助我,我会很高兴。

非常感谢

我已经想出了如何实现这个:

  1. 不需要运行点击方法的每个条件中的update和restart方法,检查条件后一次就可以了:
function click(d) {
  if (d.children) {
    d._children = d.children;
    d.children = null;
  } else {
    d.children = d._children;
    d._children = null;
  }
    update();
}
  1. 调用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();
  1. 我已经从你的数据中删除了所有 "_children": null,它们是不必要的。

我在这里解决了这个问题: https://jsfiddle.net/orsisi/tnv37wxr/39/