Force(力导向布局)

508 阅读2分钟

简介

力导向图(Force-Directed Graph)是 d3.js 提供的一种十分经典的绘图算法。通过在二维空间里配置节点和连线,在各种各样力的作用下,节点间相互碰撞和运动并在这个过程中不断地降低能量,最终达到一种能量很低的安定状态,形成一种稳定的力导向图。

数据

直接上代码

  var nodes = [//节点集
    { name: "湖南邵阳" },
    { name: "山东莱州" },
    { name: "广东阳江" },
    { name: "山东枣庄" },
    { name: "泽" },
    { name: "恒" },
    { name: "鑫" },
    { name: "明山" },
    { name: "班长" }
  ];

  var edges = [//边集
    { source: 0, target: 4, relation: "籍贯", value: 1.3 },
    { source: 4, target: 5, relation: "舍友", value: 1 },
    { source: 4, target: 6, relation: "舍友", value: 1 },
    { source: 4, target: 7, relation: "舍友", value: 1 },
    { source: 1, target: 6, relation: "籍贯", value: 2 },
    { source: 2, target: 5, relation: "籍贯", value: 0.9 },
    { source: 3, target: 7, relation: "籍贯", value: 1 },
    { source: 5, target: 6, relation: "同学", value: 1.6 },
    { source: 6, target: 7, relation: "朋友", value: 0.7 },
    { source: 6, target: 8, relation: "职责", value: 2 }
  ];

主程序

完整代码

  var width = 700;
  var height = 700;
  var dome = document.getElementById('svgAll');
  var svg = d3.create("svg").attr("width", width).attr("height", height);

  var g = svg.append("g").attr("transform", "translate(10,10)");
  //指定缩放范围,缩放功能
  const zoom = d3
    .zoom()
    .scaleExtent([0.2, 4])
    .on("zoom", (e) => {
      g.attr("transform", `translate(${e.transform.x},${e.transform.y}) scale(${e.transform.k})`);
    });
  //动画持续时间
  var initialTransform = d3.zoomIdentity.translate(20, 20).scale(2);//初始缩放平移
  g.transition()
    .duration(1000)
    .call(zoom.transform, d3.zoomIdentity);
  svg.call(zoom);

  // 颜色
  var colorScale = d3.scaleOrdinal()
    .domain(d3.range(nodes.length))
    .range(d3.schemeCategory10);
  // 力导向图
  var forceSimulation = d3.forceSimulation()
    .force("link", d3.forceLink())
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter());
  // 节点数据
  forceSimulation.nodes(nodes)
    .on("tick", ticked);
  //边数据
  forceSimulation.force("link")
    .links(edges)
    .distance(function (d) {//每一边的长度
      return d.value * 100;
    })
  // 中心
  forceSimulation.force("center")
    .x(width / 2)
    .y(height / 2);

  //绘制边
  var links = g.append("g")
    .selectAll("line")
    .data(edges)
    .enter()
    .append("line")
    .attr("stroke", function (d, i) {
      return colorScale(i);
    })
    .attr("stroke-width", 1);
  // 边文字
  var linksText = g.append("g")
    .selectAll("text")
    .data(edges)
    .enter()
    .append("text")
    .text(function (d) {
      return d.relation;
    })

  // 节点分组
  var gs = g.selectAll(".circleText")
    .data(nodes)
    .enter()
    .append("g")
    .attr("transform", function (d, i) {
      var cirX = d.x;
      var cirY = d.y;
      return "translate(" + cirX + "," + cirY + ")";
    })
    .call(d3.drag()
      .on("start", started)
      .on("drag", dragged)
      .on("end", ended)
    );
  //节点
  gs.append("circle")
    .attr("r", 10)
    .attr("fill", function (d, i) {
      return colorScale(i);
    })
  //节点文字
  gs.append("text")
    .attr("x", -10)
    .attr("y", -20)
    .attr("dy", 10)
    .text(function (d) {
      return d.name;
    })

  // ticked函数
  function ticked() {
    links.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; });

    linksText.attr("x", function (d) {
      return (d.source.x + d.target.x) / 2;
    }).attr("y", function (d) {
      return (d.source.y + d.target.y) / 2;
    });

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

  // 拖拽事件
  function started(e, d) {
    forceSimulation.alphaTarget(0.8).restart();
    d.fx = d.x;
    d.fy = d.y;
  }
  function dragged(e, d) {
    d.fx = e.x;
    d.fy = e.y;
  }
  function ended(e, d) {
    forceSimulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
  }

  dome.appendChild(svg.node());