D3.js forceSimulation 링크(link) 작용력

D3.js forceSimulation의 프로그램 링크에 의한 상호 작용을 설명한다.

예제 프로그램

노드 (둥근 사람)를 드래그 해본다.

코드 확인

예제 코드

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>D3 v7 force simulation node detail</title>
</head>

<body>
  <svg width="800" height="600"></svg>
  <script src="https://d3js.org/d3.v7.min.js"></script>
  <script>
    // 1. 그리려는 데이터 준비
    var width = document.querySelector("svg").clientWidth;
    var height = document.querySelector("svg").clientHeight;
    var nodeNumber = 30;
    var nodesData = [];
    for (var i = 0; i < nodeNumber; i++) {
      nodesData.push({
        "index": i,
        "x": width * Math.random(),
        "y": height * Math.random(),
        "r": 10
      });
    }
    var linksData = [];
    var i = 0;
    for (var j = i + 1; j < nodeNumber; j++) {
      linksData.push({
        "source": i,
        "target": j,
        "l": Math.random() * 150 + 5 + nodesData[i].r + nodesData[j].r
      });
    }

    // 2. svg 요소 추가
    var link = d3.select("svg")
      .selectAll("line")
      .data(linksData)
      .enter()
      .append("line")
      .attr("stroke-width", 1)
      .attr("stroke", "black");

    var node = d3.select("svg")
      .selectAll("circle")
      .data(nodesData)
      .enter()
      .append("circle")
      .attr("r", function (d) { return d.r })
      .attr("fill", "LightSalmon")
      .attr("stroke", "black")
      .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

    // 3. forceSimulation 설정
    var simulation = d3.forceSimulation()
      .force("link",
        d3.forceLink()
          .distance(function (d) { return d.l; })
          .strength(0.03)
          .iterations(16))
      .force("collide",
        d3.forceCollide()
          .radius(function (d) { return d.r; })
          .strength(0.7)
          .iterations(16))
      .force("charge", d3.forceManyBody().strength(-200))
      .force("x", d3.forceX().strength(0.02).x(width / 2))
      .force("y", d3.forceY().strength(0.02).y(height / 2));

    simulation
      .nodes(nodesData)
      .on("tick", ticked);

    simulation.force("link")
      .links(linksData)
      .id(function (d) { return d.index; });

    // 4. forceSimulation 그림 업데이트 함수
    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("cx", function (d) { return d.x; })
        .attr("cy", function (d) { return d.y; });
    }

    // 5. 드래그 이벤트 함수
    function dragstarted(event, d) {
      if (!event.active) simulation.alphaTarget(0.3).restart();
      d.fx = d.x;
      d.fy = d.y;
    }

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

    function dragended(event, d) {
      if (!event.active) simulation.alphaTarget(0);
      d.fx = null;
      d.fy = null;
    }
  </script>
</body>

</html>

설명

1. 그리려는 데이터 준비

var width = document.querySelector("svg").clientWidth;
var height = document.querySelector("svg").clientHeight;
var nodeNumber = 30;
var nodesData = [];
for (var i = 0; i < nodeNumber; i++) {
  nodesData.push({
    "index": i,
    "x": width * Math.random(),
    "y": height * Math.random(),
    "r": 10
  });
}

document.querySelector(...)svg의 폭과 높이를 가져온다.

링크를 연결하는 노드의 ID는 임의의 키 이름을 지정할 수 있다. 위 코드에서는 index를 노드용 데이터 배열에 저장하고 나중에 지정한다. 숫자가 아닌 문자열로도 ID를 지정할 수 있다.

var linksData = [];
var i = 0;
for (var j = i + 1; j < nodeNumber; j++) {
  linksData.push({
    "source": i,
    "target": j,
    "l": Math.random() * 150 + 5 + nodesData[i].r + nodesData[j].r
  });
}

링크용 배열 데이터 linksData을 작성한다. 위 코드에서는 링크의 길이를 링크마다 바꾸기 위해 l을 정의해 둔다.

2. svg 요소 추가

링크와 노드의 svg 요소를 추가한다. call(,,,)로 드래그가 발생하는 이벤트 함수를 등록하고 있다.

3. forceSimulation 설정

var simulation = d3.forceSimulation()
  .force("link",
    d3.forceLink()
    .distance(function(d) { return d.l; })
    .strength(0.03)
    .iterations(16)) // <<< 링크에 의한 상호작용
  .force("collide",
    d3.forceCollide()
    .radius(function(d) { return d.r; })
    .strength(0.7)
    .iterations(16))
  .force("charge", d3.forceManyBody().strength(-200))
  .force("x", d3.forceX().strength(0.02).x(width / 2))
  .force("y", d3.forceY().strength(0.02).y(height / 2));

simulation
  .nodes(nodesData)
  .on("tick", ticked);

simulation.force("link")
  .links(linksData)
  .id(function(d) { return d.index; }); // <<< 링크에 의한 상호작용

forceSimulation에서 설정할 수 있는 링크에 의한 상호작용을 설명한다.

.force("link",
  d3.forceLink()
  .distance(function(d) { return d.l; })
  .strength(0.03)
  .iterations(16))
함수 설명
distance 링크의 길이를 설정한다.
링크는 항상 설정된 길이로 돌아가려고 한다.
기본값은 30이다.
strength 링크의 강도를 설정한다.
기본값은 1/Math.min(count(link.source), count(link.target))이다.
하나씩 노드와 연결되어 있으면 1이다.
같은 노드 사이에 2개의 링크가 있어도 반동력이 같아진다.
iterations simulation의 반복 횟수를 설정한다.
반복 횟수를 늘리면 계산이 안정되지만 계산 시간이 늘어난다.
기본값은 1이다.
  simulation.force("link")
    .links(linksData)
    .id(function(d) { return d.index; });

id(..)로 색인화된 키 이름을 지정한다. 기본값은 node.index이다.

4. forceSimulation 드로잉 업데이트 기능

시뮬레이션 단계별로 호출되는 함수이다. svg 요소를 이동하기 위해 계산 결과를 svg 요소의 위치에 반영한다.

5. 드래그시 이벤트 함수

드래그시의 이벤트 함수이다.




최종 수정 : 2024-01-18