D3.js - 固定某些节点坐标的力导向布局

在绘制节点链接图时,有时会存在这样的需求:我们需要固定某些节点的坐标,而其它节点遵循力导向布局,坐标位置不断变换,直到得到稳定的布局结果

效果图如下图所示,红色节点是固定了坐标的节点,灰色节点没有固定坐标,在力导向布局过程中红色节点的坐标始终不会发生变化。

在线演示:https://codepen.io/yangkui/pen/YzPGjgP

在 D3.js 中,如果想要某个节点固定在一个位置,可以指定以下两个额外的属性:

  • fx - 节点的固定 x-位置
  • fy - 节点的固定 y-位置

每次 tick 结束后,节点的 node.x 会被重新设置为 node.fx 并且 node.vx 会被设置为 0;同理 node.y 会被重新替换为 node.fy 并且 node.vy 被设置为 0;如果想要某个节点解除固定,则将 node.fx 和 node.fy 设置为 null 或者删除这两个属性。

下面是使用D3.js V5版本的实现代码:

<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Force layouttitle>
  <style>
    .link {
      stroke: #000;
      stroke-width: 1.5px;
    }
    .unfixedNode {
      cursor: move;
      fill: #ccc;
      stroke: #000;
      stroke-width: 1.5px;
    }
    .fixedNode {
      cursor: move;
      fill: red;
      stroke: #000;
      stroke-width: 1.5px;
    }
  style>
head>
<body>
  <script src="https://d3js.org/d3.v5.min.js">script>
  <script>
  	// fixed是固定坐标的节点,unfixed是没有固定坐标的节点。
    var graph ={
      "nodes": [
        {"id": 0, "category": "unfixed"},
        {"id": 1, "category": "ufixed"},
        {"id": 2, "category": "unfixed"},
        {"id": 3, "category": "fixed","x": 467, "y": 314},
        {"id": 4, "category": "unfixed"},
        {"id": 5, "category": "fixed","x": 425, "y": 207},
        {"id": 6, "category": "unfixed"},
        {"id": 7, "category": "unfixed"},
        {"id": 8, "category": "unfixed"},
        {"id": 9, "category": "fixed","x": 539, "y": 222},
        {"id": 10, "category": "unfixed"},
        {"id": 11, "category": "unfixed"},
        {"id": 12, "category": "unfixed"}
      ],
      "links": [
        {"source":  0, "target":  3},
        {"source":  1, "target":  3},
        {"source":  2, "target":  3},
        {"source":  3, "target":  4},
        {"source":  4, "target":  5},
        {"source":  5, "target":  6},
        {"source":  5, "target":  7},
        {"source":  5, "target":  8},
        {"source":  9, "target":  4},
        {"source":  9, "target":  11},
        {"source":  9, "target":  10},
        {"source":  9, "target":  12},
        {"source":  8, "target":  3},
      ]
    }

    var nodesCopy = JSON.parse(JSON.stringify(graph.nodes));

    var width = 960,
      height = 500;

    var force = d3.forceSimulation()
      .force("charge", d3.forceManyBody())
      .force("center", d3.forceCenter(width / 2, height / 2))
      .on("tick", tick);

    force
      .nodes(graph.nodes)
      .force("link", d3.forceLink(graph.links).id(function(d) {
      	return d.id;
      }));

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

    var link = svg.selectAll(".link"),
      node = svg.selectAll(".node");

    link = link.data(graph.links)
      .enter().append("line")
      .attr("class", "link");


    node = node.data(graph.nodes)
      .enter().append("circle")
      .attr("class", "node")
      .attr("class", d => {
        if (d.category == "fixed") {
          return "fixedNode";
        } else {
          return "unfixedNode";
        }
      })
      .attr("r", 12)
      .call(d3.drag()
        .on("start", dragstart)
        .on("drag", dragged)
        .on("end", dragended));

    node.append("title")
      .text(d => d.id);


    function tick() {
      node.attr("cx", function (d) {
        if (d.category == 'fixed') {
          d.fx = nodesCopy[d.id].x;
        }
        return d.x;
      })
        .attr("cy", function (d) {
          if (d.category == 'fixed') {
            d.fy = nodesCopy[d.id].y;
          };
          return d.y;
        });
      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;
        });
    }

    function dragstart(d) {
      if (!d3.event.active) {
        force.alphaTarget(.1).restart();
      }
      d.fx = d.x;
      d.fy = d.y;
    }

    function dragged(d) {
      d.fx = d3.event.x;
      d.fy = d3.event.y;
    }

    function dragended(d) {
      if (!d3.event.active) {
        force.alphaTarget(0);
      }
      d.fx = null;
      d.fy = null;
    }

    force.on("end", function () {
      console.log(graph.nodes)
    })
  script>
body>
html>

你可能感兴趣的:(可视化)