使用x1,y1和x2,y2时如何使用d3.line().curve?

How to use d3.line().curve when using x1, y1, and x2, y2?

我是 d3 的新手,想使用弯头连接节点。在线搜索后,我找到了一种与所需解决方案类似的解决方案 ,但该解决方案不适用于 d3 v4+。

此外,我从 d3 中找到了一个可行的方法,名为 d3.line().curve(d3.curveStepAfter) (我不确定这是否是正确的用法)可以看到一个例子here。但是我找不到一种方法来为我当前使用 x1、y1 和 x2、y2 的设置实现它。

数据

var data = {
    "nodes": [
    {
        "name": "Node 1",
        fx: 50,
        fy: 50
    },
    {
        "name": "Node 2",
        fx: 50,
        fy: 100
    },
    {
        "name": "Node 3",
        fx: 200,
        fy: 50
    },
    {
        "name": "Node 4",
        fx: 350,
        fy: 50
    },
    {
        "name": "Node 5",
        fx: 200,
        fy: 150
    }].map(function(d, i) { return (d.fixed = true, d) }),

    "links": [
    {
        "source": 0,
        "target": 2
    },
    {
        "source": 1,
        "target": 2
    },
    {
        "source": 2,
        "target": 3
    },
    {
        "source": 2,
        "target": 4
    }]
}

代码

var width = 560
var height = 500;

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

var force = self.force = d3.forceSimulation(data.nodes)
    .force("link", d3.forceLink(data.links))
    .force("collide", d3.forceCollide())
    .force("center", d3.forceCenter(width / 2, height / 2))
    .on("tick", ticked);

var link = svg.selectAll("line.link")
.data(data.links)
.enter()
.append("line")
    .style("stroke", "black")
    .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; })

var node = svg.selectAll("g.node")
    .data(data.nodes)
    .enter()
    .append("g")
        .attr("class", "node")
        .call(d3.drag()
            .on("start", dragstarted)
            .on("drag", dragged)
            .on("end", dragended));

d3.selectAll(".node")
    .append("circle")
        .style("fill", "red")
        .attr("r", 15);


function ticked() {
    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", function (d) {
        return "translate(" + d.x + "," + d.y + ")";
    });
};

function dragstarted(event) {
    if (!event.active) force.alphaTarget(0.3).restart();
    event.subject.fx = event.subject.x;
    event.subject.fy = event.subject.y;
}

function dragged(event) {
    event.subject.fx = Math.ceil(event.x/5)*5;
    event.subject.fy = Math.ceil(event.y/5)*5;
}

function dragended(event) {
    if (!event.active) force.alphaTarget(0);
}

代码呈现节点并用直线连接它们,目标是添加 curveStepAfter 以创建弯头连接,因为它看起来更适合我需要的图表类型。

感谢任何帮助。

这是一个完整的例子。

<html>
    <head>
        <script src="https://d3js.org/d3.v7.min.js"></script>
    </head>
    
    <body>
        <script>
            const data = {
              nodes: [
                { name: "Node 1", x: 50, y: 50 },
                { name: "Node 2", x: 50, y: 100 },
                { name: "Node 3", x: 200, y: 50 },
                { name: "Node 4", x: 350, y: 50 },
                { name: "Node 5", x: 200, y: 150 },
              ],
              links: [
                { source: 0, target: 2 },
                { source: 1, target: 2 },
                { source: 2, target: 3 },
                { source: 2, target: 4 },
              ],
            };
            
            const segments = data.links.map(({ source, target }) => [
              data.nodes[source],
              data.nodes[target],
            ]);

            const width = 560
            const height = 500;

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

            const line = d3.line()
                .x(d => d.x)
                .y(d => d.y)
                .curve(d3.curveStep);

            const link = svg.selectAll("path.link")
              .data(segments)
              .join("path")
                .attr("d", line)
                .attr("stroke", "black")
                .attr("fill", "none")

            const node = svg.selectAll("g.node")
              .data(data.nodes)
              .join("g")
                .attr("class", "node")
                .attr("transform", d => `translate(${d.x},${d.y})`)
                .call(d3.drag()
                    .on("drag", dragged));

            node.append("circle")
                .style("fill", "red")
                .attr("r", 15);

            function dragged(event, d) {
              d.x = Math.ceil(event.x / 5) * 5;
              d.y = Math.ceil(event.y / 5) * 5;

              link.attr("d", line);

              d3.select(this)
                  .attr("transform", `translate(${d.x},${d.y})`)
            }
        </script>
    </body>
</html>

主要思想是

const segments = data.links.map(({ source, target }) => [
  data.nodes[source],
  data.nodes[target],
]);

是网络中的线段数组。我们可以将每个段传递给一个 d3.line() 来为该段创建一个 <path>。我还删除了对 d3-force 的依赖,这不是必需的。