如何根据元素而不是节点顺序对D3 Force Simulation节点进行分层?

时间:2017-11-26 01:59:07

标签: d3.js svg

我有一个D3 v4力模拟,节点在屏幕上移动。每个节点都是一个由圆圈和下面的一些文本组成的组。如何订购,以便圆圈位于底层,顶层的文字始终。圆圈与圆圈重叠是可以的,但是圆圈在文本顶部重叠是不可能的。这就是我所拥有的。目前,位于另一个节点之前的节点圈将与该节点的文本重叠。

  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 个答案:

答案 0 :(得分:4)

使用您当前的方法无法达到预期的效果。原因很简单:每个组都有一个文本和一个圆圈。但是,绘画顺序取决于组的顺序:

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

因此,将文本和圆圈分组到<g>元素中,您将按给定顺序绘制组,因此,在文本上绘制一个圆圈(给定组的圆圈绘制在文本上在它之前的所有小组)。

以下是演示文稿(Baz圈子将位于所有文字的顶部,Bar圈子将位于Foo文字的顶部):

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>

解决方案

一种可能的解决方案是创建两个选项,一个用于圆圈,另一个用于文本。在之前添加圈子,在之后添加文本。请记住对两者使用相同的nodes数组:

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

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

这样,文本将在圈子之上始终

查看演示:

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>