d3 力网络中的水平链接标签

Dev*_*vin 5 javascript d3.js force-layout

我有一个带有弯曲链接的 d3 (v3) 力网络,如下所示:

在此处输入图片说明

我想要完成的是让链接的textPath元素是水平的,因为它们是数字,并且“81”需要看起来与“18”不同。我也希望有某种白色阴影/外发光/背景,因为我将它们直接放在链接上。我现在有一个白色的笔画,但它的效果不太好,因为有时一个数字的笔画会侵入它旁边的数字。

这里有一个可重复的例子,我承认它是从其他 SO 答案拼凑而成的:https : //jsfiddle.net/2gbekL7m/

代码的相关部分是:

var link_label = svg.selectAll(".link_label")
  .data(links)
  .enter()
  .append("text")
  .attr("class", "link_label")
  .attr("paint-order", "stroke")
  .attr("stroke", "white")
  .attr("stroke-width", 4)
  .attr("stroke-opacity", 1)
  .attr("stroke-linecap", "butt")
  .attr("stroke-linejoin", "miter")
  .style("fill", "black")
  .attr("dy", 5)
  .append("textPath")
  .attr("startOffset", "50%")
  .attr("xlink:href", function(d, i) {
    return "#link_" + i;
  })
  .text(function(d, i) {
    return d.n;
  });
Run Code Online (Sandbox Code Playgroud)

有谁知道如何通过固定方向和添加背景框来提高链接标签的可读性?

Ger*_*ado 5

首先,因为你没有张贴你的代码,我的答复解决了代码的jsfiddle您共享。

让我们分别解决您的两个问题:

定位标签

在我看来,您希望将标签放在链接的中间,并且始终是水平的,作为常规文本。在这种情况下,解决方案是删除textPath,这实际上会使您的选择更简单:

var link_label = svg.selectAll(".link_label")
    .data(links)
    .enter()
    .append("text")
    .text(function(d, i) {
        return d.n;
    });
Run Code Online (Sandbox Code Playgroud)

现在只需要获取这些路径的中间位置,我们可以在函数内部使用getTotalLength()和执行getPointAtLength()此操作tick

link_label.attr("x", function(d, i) {
        var pathLength = d3.select("#link_" + i).node().getTotalLength();
        d.point = d3.select("#link_" + i).node().getPointAtLength(pathLength / 2);
        return d.point.x
    })
    .attr("y", function(d) {
        return d.point.y
    })
Run Code Online (Sandbox Code Playgroud)

在这里,我将值存储在一个属性中,因为 x 和 y 位置的值都相同。这样我们就避免了不必要的重复计算。此外,有人可能会争辩说计算应该放在tick函数之外,以便只获得一次路径的长度:不幸的是,这里不是这种情况,因为在模拟过程中,路径的长度不断变化。

文字阴影

这里有几种不同的方法。一个简单的,可能不是最漂亮的,就是使用text-shadow。这是一个简单的上下左右的白色阴影:

.shadow {
    text-shadow: 2px 2px 0 #fff, 2px -2px 0 #fff, -2px 2px 0 #fff, -2px -2px 0 #fff
}
Run Code Online (Sandbox Code Playgroud)

该 CSS 适用于 Chrome 和 FireFox,但不适用于 Safari。

总之,这是演示:

var link_label = svg.selectAll(".link_label")
    .data(links)
    .enter()
    .append("text")
    .text(function(d, i) {
        return d.n;
    });
Run Code Online (Sandbox Code Playgroud)
link_label.attr("x", function(d, i) {
        var pathLength = d3.select("#link_" + i).node().getTotalLength();
        d.point = d3.select("#link_" + i).node().getPointAtLength(pathLength / 2);
        return d.point.x
    })
    .attr("y", function(d) {
        return d.point.y
    })
Run Code Online (Sandbox Code Playgroud)
.shadow {
    text-shadow: 2px 2px 0 #fff, 2px -2px 0 #fff, -2px 2px 0 #fff, -2px -2px 0 #fff
}
Run Code Online (Sandbox Code Playgroud)

编辑:看看Xavier 的回答,白色圆圈看起来比文字阴影更糟糕。


Xav*_*hot 5

重新使用赫拉尔的答案用的使用getPointAtLength(),这里是基于一个白色圆圈的问题的第二部分的替代,关于标签的阴影/背景:

var nodes = [{
      "ix": 0
    },
    {
      "ix": 1
    },
    {
      "ix": 2
    },
    {
      "ix": 3
    }
  ];

  var links = [{
      "source": 0,
      "target": 2,
      "n": 12
    },
    {
      "source": 0,
      "target": 1,
      "n": 34
    },
    {
      "source": 1,
      "target": 2,
      "n": 56
    },
    {
      "source": 1,
      "target": 0,
      "n": 78
    },
    {
      "source": 0,
      "target": 3,
      "n": 90
    }
  ];

  var w = 400,
    h = 400;

  var force = d3.layout.force()
    .size([w, h])
    .nodes(nodes)
    .links(links)
    .gravity(1)
    .linkDistance(30)
    .charge(-20000)
    .linkStrength(1);

  force.start();

  var svg = d3.select("body").append("svg")
    .attr("width", w)
    .attr("height", h)
    .attr("preserveAspectRatio", "xMinYMin meet")
    .attr("viewBox", "0 0 " + w + " " + h)
    .append("g");

  var marker = svg.selectAll("marker")
    .append("svg:defs")
    .data(["end-arrow"])
    .enter()
    .append("svg:marker")
    .attr("id", String)
    .attr("viewBox", "0 -3 6 6")
    .attr("refX", 11.3)
    .attr("refY", -0.2)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
    .append("svg:path")
    .attr("d", "M0,-3L6,0L0,3");

  var link = svg.selectAll("line.link")
    .data(links)
    .enter()
    .append("svg:path")
    .attr("id", function(d, i) {
      return "link_" + i;
    })
    .style("stroke", "black")
    .style("stroke-width", 2)
    .style("fill", "none")
    .attr("marker-end", "url(#end-arrow)");

  var link_label_shadow = svg.selectAll(".link_label_shadow")
    .data(links)
    .enter()
    .append("circle")
    .attr("r", 10)
    .style("fill", "white");

  var link_label = svg.selectAll(".link_label")
    .data(links)
    .enter()
    .append("text")
    .style("text-anchor", "middle")
    .style("dominant-baseline", "central")
    .attr("class", "shadow")
    .text(function(d, i) {
      return d.n;
    });

  var node = svg.selectAll(".node")
    .data(force.nodes())
    .enter()
    .append("svg:g")
    .attr("class", "node");

  node.append("svg:circle")
    .attr("r", 10)
    .style("fill", "black");

  node.call(force.drag);

  force.on("tick", function() {
    link.attr("d", function(d) {
      var dx = d.target.x - d.source.x;
      var dy = d.target.y - d.source.y;
      var dr = Math.sqrt(dx * dx + dy * dy);

      return "M" +
        d.source.x +
        "," +
        d.source.y +
        "A" +
        dr +
        "," +
        dr +
        " 0 0,1 " +
        d.target.x +
        "," +
        d.target.y;
    });

    link_label.attr("x", function(d, i) {
        var pathLength = d3.select("#link_" + i).node().getTotalLength();
        d.point = d3.select("#link_" + i).node().getPointAtLength(pathLength / 2);
        return d.point.x
      })
      .attr("y", function(d) {
        return d.point.y
      })
    node.attr("transform", function(d) {
      return ("translate(" + d.x + "," + d.y + ")");
    });

    link_label_shadow.attr("cx", function(d, i) {
        var pathLength = d3.select("#link_" + i).node().getTotalLength();
        d.point = d3.select("#link_" + i).node().getPointAtLength(pathLength / 2);
        return d.point.x
      })
      .attr("cy", function(d) {
        return d.point.y
      })
    node.attr("transform", function(d) {
      return ("translate(" + d.x + "," + d.y + ")");
    });
  });
  
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

与使用 设置标签的方式相同getPointAtLength(),我们可以在链接和文本标签之间的同一位置包含一个白色圆圈。

由于在其关联链接之后创建了白色圆圈,因此它们将位于链接上方,从而隐藏其中间部分。然后只插入标签,因此位于白色圆圈上方。