简体   繁体   English

D3:在力导向树中拖动父节点时,如何阻止子节点被吸引到中心?

[英]D3: How to stop child nodes from being attracted towards the center when parent is dragged in force-directed tree?

I have built a force-directed tree layout using d3.js v7.我使用 d3.js v7 构建了一个强制导向的树布局。 It works fine so far except for a behavior that I've seen even in the examples on Observable https://observablehq.com/collection/@d3/d3-force到目前为止它工作正常,除了我在 Observable https://observablehq.com/collection/@d3/d3-force的示例中看到的行为

This is what I have so far.这就是我到目前为止所拥有的。

在此处输入图像描述

The behavior is that when I drag a node towards the edge, the children seem to be attracted towards the center.行为是当我将节点拖向边缘时,孩子们似乎被吸引到中心。 Like so:像这样:

在此处输入图像描述

I'd like, instead, for the child nodes of "Load Balancing" to appear like this:相反,我希望“负载平衡”的子节点看起来像这样:

在此处输入图像描述

Here is how I've set up the simulation:这是我设置模拟的方式:

const simulation = d3
  .forceSimulation()
  .force(
    "link",
    d3
      .forceLink()
      .id((d) => d.id)
      .distance(100)
      .strength(1)
  )
  .force("charge", d3.forceManyBody().strength(-500))
  .force("x", d3.forceX())
  .force("y", d3.forceY())
  .alphaDecay(0.05);

  simulation.on("tick", () => {
    link
      .attr("x1", (d) => d.source.x)
      .attr("y1", (d) => d.source.y)
      .attr("x2", (d) => d.target.x)
      .attr("y2", (d) => d.target.y);
  
    node.attr("transform", function (d) {
      return "translate(" + d.x + "," + d.y + ")";
    });
  });

I've created a working Codepen for this here for you to play around with what I'm talking about.我在这里为此创建了一个可工作的 Codepen,供您使用我正在谈论的内容。

I've tried changing the node and link forces to different values but they didn't really help.我尝试将节点和链接力更改为不同的值,但它们并没有真正帮助。 I've also tried disabling the center force but then the simulation never appears on the screen if I do that.我也尝试过禁用center力,但如果我这样做,模拟永远不会出现在屏幕上。

.force("center").strength(0)

Is there a way to achieve what I'm looking for?有没有办法实现我正在寻找的东西? Any help is appreciated.任何帮助表示赞赏。

One way to control the other nodes behavior is to fix them in the dragged function.控制其他节点行为的一种方法是在拖动功能中修复它们。 I have added a function in your code, to fix other nodes and not allow them to move during drag.我在您的代码中添加了一个函数,用于修复其他节点并且不允许它们在拖动过程中移动。

 const data = { name: "AWS", resourceType: "aws", children: [{ name: "VPC", resourceType: "vpc", children: [{ name: "Load Balancing", resourceType: "load-balancing", children: [{ name: "Public subnet 1", resourceType: "subnet" }, { name: "Public subnet 2", resourceType: "subnet" }, { name: "Private subnet 1", resourceType: "subnet" }, { name: "Private subnet 2", resourceType: "subnet" } ] }] }] }; // Set up the canvas const chartContainer = d3.select("#svgcontainer"); const containerWidth = Math.max( 1200, chartContainer.node().getBoundingClientRect().width ); const containerHeight = Math.max( 600, chartContainer.node().getBoundingClientRect().height ); const margin = { top: 24, right: 124, bottom: 24, left: 24 }, width = containerWidth - margin.left - margin.right, height = containerHeight - margin.top - margin.bottom; const svg = chartContainer .append("svg") .attr("viewBox", [-width / 2, -height / 2, width, height]); const gLinks = svg .append("g") .attr("stroke", "#999") .attr("stroke-opacity", 0.6); // Prepare the data const root = d3.hierarchy(data); let node, link; // Deeper descendants are collapsed by default root.descendants().forEach((d, i) => { d.id = i; d._children = d.children; if (d.depth > 2) d.children = null; }); // Set up the force simulation const simulation = d3 .forceSimulation() .force( "link", d3 .forceLink() .id((d) => d.id) .distance(100) .strength(1) ) .force("charge", d3.forceManyBody().strength(-500)) .force("x", d3.forceX()) .force("y", d3.forceY()) .alphaDecay(0.05); // Update ========================================================= function update() { const links = root.links(); const nodes = root.descendants(); // Set up nodes node = svg.selectAll(".node").data(nodes, function(d) { return d.id; }); node.exit().remove(); const nodeEnter = node .enter() .append("g") .attr("class", "node") .call(drag(simulation)); nodeEnter .append("circle") .attr("fill", (d) => { return "#fff"; }) .attr("r", 16); nodeEnter .append("text") .attr("dx", 32) .attr("dy", ".35em") .text(function(d) { return d.data.name; }); nodeEnter.on("click", onNodeClicked); node = nodeEnter.merge(node); // Set up links link = gLinks.selectAll(".link").data(links, function(d) { return d.target.id; }); link.exit().remove(); const linkEnter = link.enter().append("line").attr("class", "link"); link = linkEnter.merge(link); simulation.nodes(nodes); simulation.force("link").links(links); } function drag(simulation) { function dragstarted(event) { if (!event.active) simulation.alphaTarget(0.3).restart(); event.subject.fx = event.subject.x; event.subject.fy = event.subject.y; event.fx = event.subject.x; event.fy = event.subject.y; } function dragged(event) { event.subject.fx = event.x; event.subject.fy = event.y; event.fx = event.x; event.fy = event.y; fix_other_nodes(event.subject); } function dragended(event) { if (!event.active) simulation.alphaTarget(0); event.subject.fx = event.x; event.subject.fy = event.y; } function fix_other_nodes(this_node) { node.each(function(d) { if (this_node != d) { d.fx = dx; d.fy = dy; } }); } return d3 .drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended); } function onNodeClicked(event, d) { // Show details drawer // setIsDrawerOpen(true); if (d._children) { d.children = d._children; d._children = null; } else { d._children = d.children; d.children = null; } if (d.children) { d.children.forEach((n) => { n.fx = dx; n.fy = dy; // Needed to make the children radiate from the parent upon expanding. n.needToUnfix = true; }); } update(); simulation.restart(); } update(); simulation.on("tick", () => { link .attr("x1", (d) => d.source.x) .attr("y1", (d) => d.source.y) .attr("x2", (d) => d.target.x) .attr("y2", (d) => d.target.y); node.attr("transform", function(d) { if (d.needToUnfix) { // This is used to make the expanding children radiate from the parent. dx = d.fx; dy = d.fy; d.fx = null; d.fy = null; d.needToUnfix = false; return "translate(" + dx + "," + dy + ")"; } return "translate(" + dx + "," + dy + ")"; }); });
 /************************/ html, body { height: 100%; background-color: #eee; } #root { height: 100%; } .App { height: 100%; } #svgcontainer { width: 100%; height: 100%; overflow: hidden; } .node { filter: drop-shadow(0 4px 4px rgb(0 0 0 / 0.4)); } .node text { font: 12px sans-serif; } .link { fill: none; stroke: #ccc; stroke-width: 2px; } .resource-icon { transition: all 200ms; } .box-details { opacity: 0; transition: all 200ms; } .box-details text { font: 8px sans-serif; } .reset-zoom-btn { opacity: 0; transition: all 200ms; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.6.1/d3.min.js" integrity="sha512-MefNfAGJ/pEy89xLOFs3V6pYPs6AmUhXJrRlydI/9wZuGrqxmrdQ80zKHUcyadAcpH67teDZcBeS6oMJLPtTqw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <div className="App"> <div id="svgcontainer"></div> </div>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM