如何根据元素而不是节点顺序对D3 Force Simulation节点进行分层? [英] How to layer D3 Force Simulation nodes based on element and not node order?

查看:44
本文介绍了如何根据元素而不是节点顺序对D3 Force Simulation节点进行分层?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个D3 v4力仿真,其中节点在屏幕上移动.每个节点都是一个小组,由一个圆圈和下面的一些文字组成.我该如何排序,以使圆圈始终位于最底层,而文本始终位于最顶层.圆重叠一个圆是可以的,但是圆重叠在文本的顶部是不可行的.这就是我所拥有的.当前,该节点在另一个节点之前的圆将与该节点的文本重叠.

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" )

推荐答案

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

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... -->

因此,将文本和<g>元素内的圆圈分组,您将按给定的顺序绘制组,因此,在文本上方绘制一个圆圈(给定组的圆圈绘制在所有文本的上方组).

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).

这是一个演示(Baz圆圈将在所有文本的顶部,而Bar圆圈将在Foo文本的顶部):

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>

一个可能的解决方案是创建两个选择,一个用于圆圈,一个用于文本.在之前之前添加圆圈,并在之后之前添加文字.记住对两个都使用相同的nodes数组:

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.

查看演示

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>

这篇关于如何根据元素而不是节点顺序对D3 Force Simulation节点进行分层?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆