草庐IT

javascript - 在节点边缘对齐标记 D3 强制布局

coder 2025-03-05 原文

我正在尝试实现 d3 力布局,但无法弄清楚如何以正确的方式放置我的链接标记。

这是我到目前为止得到的:

var links = force_data.force_directed_data.links;

        var nodes = {};

        // Compute the distinct nodes from the links.
        links.forEach(function (link) {
            link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
            link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
        });

        console.log(nodes);

        var width = 1000,
            height = 1000;

        var force = d3.layout.force()
            .nodes(d3.values(nodes))
            .links(links)
            .size([width, height])
            .linkDistance(300)
            .charge(-120)
            .friction(0.9)
            .on("tick", tick)
            .start();

        var svg = d3.select("#force-graph").append("svg")
            .attr("width", width)
            .attr("height", height);

        // Per-type markers, as they don't inherit styles.
        svg.append("defs").selectAll("marker")
            .data(["dominating"])
            .enter().append("marker")
            .attr("id", function (d) {
                return d;
            })
            .attr("viewBox", "0 -5 10 10")
            .attr("refX", 15)
            .attr("refY", -1.5)
            .attr("markerWidth", 12)
            .attr("markerHeight", 12)
            .attr("orient", "auto")
            .append("path")
            .attr("d", "M0,-5L10,0L0,5");

        svg.append("defs").selectAll("marker")
            .data(["concomidant"])
            .enter().append("marker")
            .attr("id", function (d) {
                return d;
            })
            .attr("viewBox", "0 -5 10 10")
            .attr("refX", 15)
            .attr("refY", -1.5)
            .attr("markerWidth", 12)
            .attr("markerHeight", 12)
            .attr("orient", "auto-start-reverse")
            .append("path")
            .attr("d", "M0,-5L10,0L0,5");

        var path = svg.append("g").selectAll("path")
            .data(force.links())
            .enter().append("path")
            .attr("class", function (d) {
                return "link " + d.type;
            })
            .attr("marker-end", function (d) {
                return "url(#" + d.type + ")";
            })
            .attr("marker-start", function (d) {
                if (d.type == "concomidant") {
                    return "url(#" + d.type + ")";
                }
            });


        var circle = svg.append("g").selectAll("circle")
            .data(force.nodes())
            .enter().append("circle")
            .attr("r", function (d) {
                return d.weight * 4;
            })
            .call(force.drag);

        var text = svg.append("g").selectAll("text")
            .data(force.nodes())
            .enter().append("text")
            .attr("x", 8)
            .attr("y", ".31em")
            .text(function (d) {
                return d.name;
            });

        // Use elliptical arc path segments to doubly-encode directionality.
        function tick() {
            path.attr("d", linkArc);
            circle.attr("transform", transform);
            text.attr("transform", transform);
        }

        function linkArc(d) {
            var dx = d.target.x - d.source.x,
                dy = d.target.y - d.source.y,
                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;
        }

        function transform(d) {
            return "translate(" + d.x + "," + d.y + ")";
        }

它可以为不同的链接添加不同类型的标记,但如果节点变大(根据它们的权重),标记会被节点覆盖。

这是现在的截图:

有没有办法将我的标记准确定位在节点的边缘?

最佳答案

有趣的问题。这通过正常设置链接路径,然后通过将长度退回圆的半径来重新计算结束位置来实现。

首先在标记 def 中,将 refXrefY 设置为 0(这是当前它保持在圆圈外的方式):

  .attr("refX", 0)
  .attr("refY", 0)

然后做:

function tick() {

  // fit path like you've been doing
  path.attr("d", function(d){
    var dx = d.target.x - d.source.x,
        dy = d.target.y - d.source.y,
        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;
  });

  // recalculate and back off the distance
  path.attr("d", function(d) {

    // length of current path
    var pl = this.getTotalLength(),
        // radius of circle plus marker head
        r = (d.target.weight) * 4 + 16.97, //16.97 is the "size" of the marker Math.sqrt(12**2 + 12 **2)
        // position close to where path intercepts circle
        m = this.getPointAtLength(pl - r);          

     var dx = m.x - d.source.x,
        dy = m.y - d.source.y,
        dr = Math.sqrt(dx * dx + dy * dy);

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

  circle.attr("transform", transform);
  text.attr("transform", transform);
}

运行代码:

<!DOCTYPE html>
<html>

<head>
  <script data-require="d3@3.5.17" data-semver="3.5.17" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
  <style>
    path.link {
      fill: none;
      stroke: #666;
      stroke-width: 1.5px;
    }
    
    circle {
      fill: #ccc;
      stroke: #fff;
      stroke-width: 1.5px;
    }
    
    text {
      fill: #000;
      font: 10px sans-serif;
      pointer-events: none;
    }
  </style>
</head>

<body>
  <script>
    var links = [{
      "source": "Harry",
      "target": "Sally",
      "value": "1.2"
    }, {
      "source": "Harry",
      "target": "Mario",
      "value": "1.3"
    }, {
      "source": "Sarah",
      "target": "Alice",
      "value": "0.2"
    }, {
      "source": "Eveie",
      "target": "Alice",
      "value": "0.5"
    }, {
      "source": "Peter",
      "target": "Alice",
      "value": "1.6"
    }, {
      "source": "Mario",
      "target": "Alice",
      "value": "0.4"
    }, {
      "source": "James",
      "target": "Alice",
      "value": "0.6"
    }, {
      "source": "Harry",
      "target": "Carol",
      "value": "0.7"
    }, {
      "source": "Harry",
      "target": "Nicky",
      "value": "0.8"
    }, {
      "source": "Bobby",
      "target": "Frank",
      "value": "0.8"
    }, {
      "source": "Alice",
      "target": "Mario",
      "value": "0.7"
    }, {
      "source": "Harry",
      "target": "Alice",
      "value": "0.5"
    }, {
      "source": "Sarah",
      "target": "Alice",
      "value": "1.9"
    }, {
      "source": "Roger",
      "target": "Alice",
      "value": "1.1"
    }];

    var nodes = {};

    // Compute the distinct nodes from the links.
    links.forEach(function(link) {
      link.type = "dominating";
      link.source = nodes[link.source] || (nodes[link.source] = {
        name: link.source
      });
      link.target = nodes[link.target] || (nodes[link.target] = {
        name: link.target
      });
    });

    var width = 500,
      height = 500;

    var force = d3.layout.force()
      .nodes(d3.values(nodes))
      .links(links)
      .size([width, height])
      .linkDistance(300)
      .charge(-120)
      .friction(0.9)
      .on("tick", tick)
      .start();

    var svg = d3.select("body").append("svg")
      .attr("width", width)
      .attr("height", height);

    // Per-type markers, as they don't inherit styles.
    svg.append("defs").selectAll("marker")
      .data(["dominating"])
      .enter().append("marker")
      .attr("id", function(d) {
        return d;
      })
      .attr("viewBox", "0 -5 10 10")
      .attr("refX", 0)
      .attr("refY", 0)
      .attr("markerWidth", 12)
      .attr("markerHeight", 12)
      .attr("orient", "auto")
      .append("path")
      .attr("d", "M0,-5L10,0L0,5");

    svg.append("defs").selectAll("marker")
      .data(["concomidant"])
      .enter().append("marker")
      .attr("id", function(d) {
        return d;
      })
      .attr("viewBox", "0 -5 10 10")
      .attr("refX", 0)
      .attr("refY", 0)
      .attr("markerWidth", 12)
      .attr("markerHeight", 12)
      .attr("orient", "auto-start-reverse")
      .append("path")
      .attr("d", "M0,-5L10,0L0,5");

    var path = svg.append("g").selectAll("path")
      .data(force.links())
      .enter().append("path")
      .attr("class", function(d) {
        return "link " + d.type;
      })
      .attr("marker-end", function(d) {
        return "url(#" + d.type + ")";
      })
      .attr("marker-start", function(d) {
        if (d.type == "concomidant") {
          return "url(#" + d.type + ")";
        }
      });


    var circle = svg.append("g").selectAll("circle")
      .data(force.nodes())
      .enter().append("circle")
      .attr("r", function(d) {
        return d.weight * 4;
      })
      .call(force.drag);

    var text = svg.append("g").selectAll("text")
      .data(force.nodes())
      .enter().append("text")
      .attr("x", 8)
      .attr("y", ".31em")
      .text(function(d) {
        return d.name;
      });

    function tick() {
      path.attr("d", function(d){
        var dx = d.target.x - d.source.x,
            dy = d.target.y - d.source.y,
            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;
      });
      path.attr("d", function(d) {
        var pl = this.getTotalLength(),
          r = (d.target.weight) * 4 + 16.97, //16.97 is the "size" of the marker Math.sqrt(12**2 + 12 **2)
          m = this.getPointAtLength(pl - r);

         var dx = m.x - d.source.x,
            dy = m.y - d.source.y,
            dr = Math.sqrt(dx * dx + dy * dy);

          return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + m.x + "," + m.y;
      });
      circle.attr("transform", transform);
      text.attr("transform", transform);
    }
    

    function transform(d) {
      return "translate(" + d.x + "," + d.y + ")";
    }
  </script>
</body>

</html>

关于javascript - 在节点边缘对齐标记 D3 强制布局,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41226734/

有关javascript - 在节点边缘对齐标记 D3 强制布局的更多相关文章

  1. ruby - nanoc 和多种布局 - 2

    是否可以为特定(或所有)项目使用多个布局?例如,我有几个项目,我想对其应用两种不同的布局。一个是绿色的,一个是蓝色的(但是)。我想将它们编译到我的输出目录中的两个不同文件夹中(例如v1和v2)。我一直在玩弄规则和编译block,但我不知道这是怎么回事。因为,每个项目在编译过程中只编译一次,我不能告诉nanoc第一次用layout1编译,第二次用layout2编译。我试过这样的东西,但它导致输出文件损坏。compile'*'doifitem.binary?#don’tfilterbinaryitemselsefilter:erblayout'layout1'layout'layout2'

  2. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

    我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

  3. ruby - 强制浏览器下载文件而不是打开文件 - 2

    我要下载http://foobar.com/song.mp3作为song.mp3,而不是让Chrome在其native中打开它浏览器中的播放器。我怎样才能做到这一点? 最佳答案 您只需要确保发送这些header:Content-Disposition:attachment;filename=song.mp3;Content-Type:application/octet-streamContent-Transfer-Encoding:binarysend_file方法为您完成:get'/:file'do|file|file=File.

  4. ruby-on-rails - 使用 gmaps4rails 动态加载谷歌地图标记 - 2

    如何只加载map边界内的标记gmaps4rails?当然,在平移和/或缩放后加载新的。与此直接相关的是,如何获取map的当前边界和缩放级别? 最佳答案 我是这样做的,我只在用户完成平移或缩放后替换标记,如果您需要不同的行为,请使用不同的事件监听器:在你看来(index.html.erb):{"zoom"=>15,"auto_adjust"=>false,"detect_location"=>true,"center_on_user"=>true}},false,true)%>在View的底部添加:functiongmaps4rail

  5. ruby - 在 Mechanize 中使用 JavaScript 单击链接 - 2

    我有这个:AccountSummary我想单击该链接,但在使用link_to时出现错误。我试过:bot.click(page.link_with(:href=>/menu_home/))bot.click(page.link_with(:class=>'top_level_active'))bot.click(page.link_with(:href=>/AccountSummary/))我得到的错误是:NoMethodError:nil:NilClass的未定义方法“[]” 最佳答案 那是一个javascript链接。Mechan

  6. ruby-on-rails - 将 Ruby 代码和文字标记与 Haml 混合 - 2

    如何用HAML编写这个ERB:#OR我可以:=some_ruby_code+":"#and=some_ruby_code%br但我不想在这里连接,我想将它写成内联:(=some_ruby_code):#and(=some_ruby_code)%br 最佳答案 =some_ruby_code+":"-#and=some_ruby_code+""编辑1:我不确定您在寻找什么。你想要其中之一吗?==#{some_ruby_code}:-#and==#{some_ruby_code}或==#{some_ruby_code}:-#and=so

  7. ruby - 强制 Ruby 不以标准形式/科学记数法/指数记数法输出 float - 2

    我遇到了同样的问题here对于python,但对于ruby​​。我需要输出这样一个小数字:0.00001,而不是1e-5。有关我的特定问题的更多信息,我正在使用f.write("Mynumber:"+small_number.to_s+"\n")输出到一个文件对于我的问题,准确性不是什么大问题,所以只做一个if语句来检查是否small_number那么更通用的方法是什么? 最佳答案 f.printf"Mynumber:%.5f\n",small_number您可以将.5(小数点右侧5位数字)替换为您喜欢的任何特定格式大小,例如,%8

  8. 区块链入门教程(6)--WeBASE-Front节点前置服务安装 - 2

    文章目录1.任务背景2.任务目标3.相关知识点4.任务实操4.1安装配置JDK4.2启动FISCOBCOS4.3下载解压WeBASE-Front4.4拷贝sdk证书文件4.5启动节点4.6访问节点4.7检查运行状态5.任务总结1.任务背景FISCOBCOS其实是有控制台管理工具,用来对区块链系统进行各种管理操作。但是对于初学者来说,还是可视化界面更友好,本节就来介绍WeBASE管理平台,这是一款微众银行开源的自研区块链中间件平台,可以降低区块链使用的门槛,大幅提高区块链应用的开发效率。微众银行是腾讯牵头设立的民营银行,在国内民营银行里还是比较出名的。微众银行参与FISCOBCOS生态建设,一定

  9. ruby - 选择包含子节点内文本的父节点 - 2

    基本上我想选择一个节点(div),其中它的子节点(h1,b,h3)包含指定的文本。Childtext1Childtext2...Childtext3我期待的是/html/div/而不是/html/div/h1我在下面有这个,但不幸的是返回了child,而不是div的xpath。expression="//div[contains(text(),'Childtext1')]"doc.xpath(expression)我期待的是/html/div/而不是/html/div/h1那么有没有一种方法可以简单地使用xpath语法来做到这一点? 最佳答案

  10. ruby-on-rails - 如何在 Rails 脚手架生成器上强制使用单数表名? - 2

    我正在使用遗留数据库并需要创建一些CRUD。我如何使用scaffold生成器并告诉他表的确切名称以避免复数化过程?表格也是西类牙语。 最佳答案 您可以只使用ActiveRecord::Base.table_name=方法手动设置表名。因此,在您的模型中您可以:classOrderDetail 关于ruby-on-rails-如何在Rails脚手架生成器上强制使用单数表名?,我们在StackOverflow上找到一个类似的问题: https://stackove

随机推荐