How to layer D3 Force Simulation nodes based on element and not node order?

家住魔仙堡 提交于 2020-01-15 06:05:10

问题


I have a a D3 v4 force simulation with nodes moving around the screen. Each node is a group consisting of a circle and some text below it. How do I order this so that the circles are on a bottom layer and the text on a top layer always. It's okay for a circle to overlap a circle, but it's never okay for a circle to overlap on top of text. Here is what I've got. Currently, the node's circle that is ahead of the other node will overlap that node's text.

  this.centerNode = this.d3Graph.selectAll(null)
          .data(this.nodes.slice(10,20))
          .enter()
          .append("g")

          this.centerNode.append("circle")
            .attr("class", "backCircle")
            .attr("r", 60)
            .attr("fill", "red")


            this.centerNode
            .append("text")
            .attr("fill", "black")
            .attr("font-size", "20px")
            .attr("y", -60)
            .text("test text" )

回答1:


You cannot achieve the desired outcome with your current approach. The reason is simple: each group has a text and a circle. However, the painting order depends on the order of the groups:

<g>
  <circle></circle>
  <text></text><!-- this text here... -->
</g>
<g>
  <circle></circle><!-- ...will be behind this circle here -->
  <text></text>
</g>
<!-- etc... -->

So, grouping the texts and the circles inside <g> elements, you will have the groups painted in a given order and, consequently, a circle over a text (the circle of a given group is painted over the texts of all groups before it).

Here is a demo (the Baz circle will be on top of all texts, and the Bar circle will be on top of Foo text):

var width = 300;
var height = 200;

var svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

var nodes = [{
  name: "Foo"
}, {
  name: "Bar"
}, {
  name: "Baz"
}];

var links = [{
  "source": 0,
  "target": 1
}, {
  "source": 0,
  "target": 2
}];

var simulation = d3.forceSimulation()
  .force("link", d3.forceLink())
  .force("charge", d3.forceManyBody())
  .force("center", d3.forceCenter(width / 2, height / 2));

var link = svg.selectAll(null)
  .data(links)
  .enter()
  .append("line")
  .style("stroke", "#ccc")
  .style("stroke-width", 1);

var color = d3.scaleOrdinal(d3.schemeCategory20);

var node = svg.selectAll(null)
  .data(nodes)
  .enter()
  .append("g")
  .call(d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended));

var nodeCircle = node.append("circle")
  .attr("r", 20)
  .attr("stroke", "gray")
  .attr("stroke-width", "2px")
  .attr("fill", function(d, i) {
    return color(i)
  });

var nodeTexts = node.append("text")
  .style("fill", "black")
  .attr("dx", 20)
  .attr("dy", 8)
  .text(function(d) {
    return d.name;
  });

simulation.nodes(nodes);
simulation.force("link")
  .links(links);

simulation.on("tick", function() {
  link.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;
    })

  node.attr("transform", (d) => "translate(" + d.x + "," + d.y + ")")

});

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}
<script src="https://d3js.org/d3.v4.min.js"></script>

Solution

A possible solution is creating two selections, one for the circles and one for the texts. Append the circles before, and the texts later. Remember to use the same nodes array for both:

var node = svg.selectAll(null)
  .data(nodes)
  .enter()
  .append("circle")
  //etc...

var nodeTexts = svg.selectAll(null)
  .data(nodes)
  .enter()
  .append("text")
  //etc...

That way, the texts will be always on top of the circles.

Check the demo:

var width = 300;
var height = 200;

var svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

var nodes = [{
  name: "Foo"
}, {
  name: "Bar"
}, {
  name: "Baz"
}];

var links = [{
  "source": 0,
  "target": 1
}, {
  "source": 0,
  "target": 2
}];

var simulation = d3.forceSimulation()
  .force("link", d3.forceLink())
  .force("charge", d3.forceManyBody())
  .force("center", d3.forceCenter(width / 2, height / 2));

var link = svg.selectAll(null)
  .data(links)
  .enter()
  .append("line")
  .style("stroke", "#ccc")
  .style("stroke-width", 1);

var color = d3.scaleOrdinal(d3.schemeCategory20);

var node = svg.selectAll(null)
  .data(nodes)
  .enter()
  .append("circle")
  .attr("r", 20)
  .attr("stroke", "gray")
  .attr("stroke-width", "2px")
  .attr("fill", function(d, i) {
    return color(i)
  })
  .call(d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended));

var nodeTexts = svg.selectAll(null)
  .data(nodes)
  .enter()
  .append("text")
  .style("fill", "black")
  .attr("dx", 20)
  .attr("dy", 8)
  .text(function(d) {
    return d.name;
  });

simulation.nodes(nodes);
simulation.force("link")
  .links(links);

simulation.on("tick", function() {
  link.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;
    })

  node.attr("transform", (d) => "translate(" + d.x + "," + d.y + ")")

  nodeTexts.attr("transform", (d) => "translate(" + d.x + "," + d.y + ")")
});

function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}
<script src="https://d3js.org/d3.v4.min.js"></script>


来源:https://stackoverflow.com/questions/47492323/how-to-layer-d3-force-simulation-nodes-based-on-element-and-not-node-order

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!