d3.js - 如何根据元素而不是节点顺序对 D3 力模拟节点进行分层?

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

最佳答案

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

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

关于d3.js - 如何根据元素而不是节点顺序对 D3 力模拟节点进行分层?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47492323/

相关文章:

javascript - d3 v4 错误 this.setAttribute 不是函数

javascript - Linechart Zoom D3 V4 上的文本转换和日期时间格式问题

javascript - SVG:转换转义的 SVG 字符串并附加到正文不执行任何操作

html - 以纯色为背景的 SVG 形状透明度

css - 如何仅使用 CSS 按比例缩放调整 SVG 内容的大小?

javascript - 如何使用 d3.js selection.text() 方法添加

javascript - 指令采用同一实例的范围

javascript - '[string, string] 类型的参数错误? (在 Angular 和 d3 中)

javascript - 如何使用 Javascript 在 SVG 中绘制不可缩放的文本?

reactjs - 使用 Jest 测试/模拟 ReactComponent SVG 导入