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

2023-12-10

我正在尝试实现 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 + ")";
        }

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

这是现在的屏幕截图:

D3 Force Graph

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


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

首先在标记定义中,设置refX and refY到 0(这是当前保持在圆外的方式):

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

Then do:

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="[email protected]" 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>
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在节点边缘对齐标记 D3 强制布局 的相关文章

  • 如何使用 lodash、underscore 或 bluebird 同步迭代数组 [关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 我有一个数组 其中每个索引处包含文件名 我想下载这些文件一次一个 同步 我知道关于 Async 模块 但我想知道是否有任何功能Lodash
  • 如何查询和过滤 Firebase 实时数据库 [重复]

    这个问题在这里已经有答案了 我想从数据库中获取所有人员 其中名字和姓氏由用户输入给出 到目前为止 这是我的代码 admin database ref persons orderByChild Firstname equalTo firstN
  • 从多层嵌套数组 JavaScript 中获取所有键值

    我有一个这样的对象 var data id 36e1e015d703120058c92cf65e6103eb title Alex McGibbon id 60beb5e7d7600200e5982cf65e6103ad title Ale
  • 从 JavaScript 重新启动动画 GIF,无需重新加载图像

    我正在使用动画 GIF 创建动画幻灯片 我正在从一个动画淡入淡出到下一个动画 问题是 我发现确保 GIF 从第一帧开始动画的唯一方法是每次显示时重新加载它 每个 GIF 大约 200KB 这对于连续幻灯片播放来说带宽太大了 这是我当前的代码
  • 在 JavaScript 中计算不包括周末和节假日的天数

    我正在尝试编写一个代码 其中将计算总天数 不包括周末和自定义假期 我通过 stackoverflow 和 adobe 论坛进行搜索以找到解决方案 并提供了以下代码 如果公共假期恰逢工作日 周六至周三 则不计算在内 我的问题是 如果公共假期落
  • Sequelize 关联 - 请改用 Promise 风格

    我正在尝试将 3 张桌子连接在一起Products Suppliers and Categories然后排SupplierID 13 我读过了如何在sequelize中实现多对多关联 https stackoverflow com a 25
  • codePointAt 和 charCodeAt 之间的区别

    有什么区别String prototype codePointAt and String prototype charCodeAt 在 JavaScript 中 A codePointAt 65 A charCodeAt 65 从 MDN
  • 为什么人们将自己的自定义/用户函数添加到 jQuery 对象中? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我见过人们将自己的自定义 用户功能添加到jQuery目的 例如 myUserFunc function regular JS code 你为什么
  • jQuery 在附加元素后立即返回 div 元素的高度 0

    我有一个浮动 div 最初没有内容 我使用 jQuery 将一组元素附加到 div 然后立即调用原始 div 的 height 方法 我添加的元素在样式表中具有定义的最小高度 而浮动 div 则没有 问题是 当我在原始 div 上调用 he
  • 如何使用javascript将视频文件转换为字符串?

    我在 signalR 工作 我想通过将视频文件拆分为不同部分来将视频文件从一个客户端发送到另一个客户端 我已经通过分割图像源数据发送图像并在另一个客户端上接收该图像 document getElementById fileUpload ad
  • AngularJS 服务并承诺最佳实践

    我有一个 AngularJS 应用程序services 调用 http资源并返回promise我在控制器中解决了这个问题 这是我正在做的事情的示例 app service Blog function http q var deferred
  • 如何将值从孩子的孩子传递给父母?

    我有一个父组件 有一个子组件 它也有一个子组件 Parent Child One child of parent Child Two child of child 当在子二中定义一个值时 我使用回调将该值传递给子一 但我也想将相同的值传递回
  • 使标签充当输入按钮

    我怎样才能做一个 a href http test com tag test Test a 就像表单按钮一样 通过充当表单按钮 我的意思是 当单击链接执行操作时method get 或 post 以便能够通过 get 或 post 捕获它
  • 如何使用 ReactJS 使表中的列可以以两种方式排序

    我正在 ReactJS 中构建一个简单的应用程序 它通过调用某个 API 来使用 JSON 数组 然后我将数组的结果填充到表中 我现在想让表的列可排序 我理想的情况是同时进行升序和降序排序 一旦我单击标题 当它按升序排序时 它应该按降序排序
  • 检查是否安装了 Google Analytics 或 Universal Analytics?

    我正在尝试通过 JavaScript 来确定是否加载了 Google Analytics 或 Universal Analytics 一些客户仍在使用旧的 Google Analytics 我们希望推出一个收集数据的 JavaScript
  • 数据表 - 从 AJAX 源过滤数据

    我有一个数据表 正在从 api 获取数据 现在我的状态是活动的 非活动的 如果标志是活动的 那么我需要在数据表中显示 否则我不应该显示过期的 这是我的fiddle https jsfiddle net lakshmipriya001 qLp
  • JS - 如何将图像对象变成灰度并显示它

    基本上 当单击按钮时 它会告诉移动设备转到相机 一旦相机拍照 它就会给我图像数据 它被称为数据 URL 吗 这是我处理它的代码 var imagesrc data image jpeg base64 imageData var myimag
  • 如何在 JavaScript 中将日期时间微格式转换为本地时间?

    我有一个页面当前正在使用日期时间微格式 http microformats org wiki datetime design pattern显示时间戳 但我只显示我自己的时区的人类可读时间
  • 如何使用node.js获取屏幕分辨率

    我需要使用 node js 获取屏幕分辨率 但以下代码不起作用 var w screen width var h screen height 这也行不通 var w window screen width var h window scre
  • 如何向 SvelteKit/Vite 应用添加版本号?

    我正在尝试在我的 SvelteKit 应用程序中创建一个系统 它会在某个页面上向您显示有关当前应用程序版本的信息 最好是 Git 提交哈希和描述 我尝试使用Vite的定义功能 https vitejs dev config define在构

随机推荐