【问题标题】:Horizontal link labels in d3 force networkd3 force网络中的水平链接标签
【发布时间】:2018-10-25 20:21:59
【问题描述】:

我有一个带有弯曲链接的 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;
  });

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

【问题讨论】:

  • 谢谢,我已经遇到过这个答案,但由于某种原因,它不适用于我的情况。链接标签根本不出现。

标签: javascript d3.js force-layout


【解决方案1】:

首先,由于您没有发布您的代码,我的回答将针对您共享的JSFiddle 中的代码。

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

定位标签

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

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

现在只需获取这些路径的中间位置,我们可以在tick 函数中使用getTotalLength()getPointAtLength()

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

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

文字阴影

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

.shadow {
    text-shadow: 2px 2px 0 #fff, 2px -2px 0 #fff, -2px 2px 0 #fff, -2px -2px 0 #fff
}

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

大家一起来看看演示吧:

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 = 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 + ")");
    });
  });
.shadow {
  text-shadow: 2px 2px 0 #fff, 2px -2px 0 #fff, -2px 2px 0 #fff, -2px -2px 0 #fff
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

编辑:看看Xavier's 的答案,白色圆圈看起来比文字阴影要严重。

【讨论】:

    【解决方案2】:

    Gerardo's answergetPointAtLength() 一起使用,这是问题第二部分的一个基于白色圆圈的替代方案,涉及标签的阴影/背景:

    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 + ")");
        });
      });
      
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

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

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

    【讨论】:

    • 白色圆圈看起来比text-shadow好得多。
    • 谢谢,学分应该主要归你所有,提出getPointAtLength 解决方案;p
    猜你喜欢
    • 1970-01-01
    • 2019-04-02
    • 1970-01-01
    • 2017-11-09
    • 2023-03-03
    • 2013-04-27
    • 2017-03-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多