D3.js forceSimulation 노드에 여러 svg를 포함하는 방법

d3.js에서 노드에 여러 svg 요소를 포함하고 텍스트와 같은 요소를 동시에 드래그하는 방법을 설명한다.

예제 프로그램

D3.js forceSimulation의 forceRadial 데모이다. 요소를 원형으로 배치할 수 있다.

코드 확인

예제 코드

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>D3 v7 force simulation group element</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 nodesData = [];
    for (var i = 0; i < 50; i++) {
      nodesData.push({
        "x": width * Math.random(),
        "y": height * Math.random(),
        "r": 40 * Math.random() + 5
      });
    }

    // 2. svg 요소 추가
    var nodeGroup = d3.select("svg")
      .selectAll("g")
      .data(nodesData)
      .enter()
      .append("g")
      .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

    nodeGroup.append("circle")
      .attr("cx", function (d) { return d.x; })
      .attr("cy", function (d) { return d.y; })
      .attr("r", function (d) { return d.r })
      .attr("fill", "Gold")
      .attr("stroke", "black")
      .append("title")
      .text("This is title.");

    nodeGroup.append("text")
      .attr("x", function (d) { return d.x; })
      .attr("y", function (d) { return d.y; })
      .attr("text-anchor", "middle")
      .attr("dominant-baseline", "middle")
      .style("fill", "steelblue")
      .text("Ball")
      .append("title")
      .text("This is title.");

    // 3. forceSimulation 설정
    var simulation = d3.forceSimulation()
      .force("collide",
        d3.forceCollide()
          .radius(function (d) { return d.r + 1 }))
      .force("charge", d3.forceManyBody())
      .force("x", d3.forceX().strength(0.05).x(width / 2))
      .force("y", d3.forceY().strength(0.05).y(height / 2));

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

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

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

</html>

설명

이 예제 프로그램은 forceSimulation을 사용한다. forceSimulation에 대한 자세한 내용은 여기를 참조한다.

프로그램의 일부만 설명한다.

  var nodeGroup = d3.select("svg")
    .selectAll("g")
    .data(nodesData)
    .enter()
    .append("g")
    .call(d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended));

먼저 그룹 요소를 나타내는 <g>요소를 만든다. <g> 요소에 노드에 대한 데이터 배열을 할당하고 드래그 이벤트를 등록한다.

그런 다음에 <g> 태그의 자식 요소로 <circle><text>를 설정한다. <g> 요소에 노드의 데이터 배열이 할당되어 있으므로 참조하여 사용한다. 그리고, <circle><text>의 자식 요소로 title도 설정할 수 있다. 예제 프로그램 노드에 커서를 놓으면 제목 문자열이 표시된다. (단, 태블릿이나 스마트폰에서는 표시되지 않는다.)

  nodeGroup.append("circle")
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; })
    .attr("r", function(d) { return d.r })
    .attr("fill", "Gold")
    .attr("stroke", "black")
    .append("title")
    .text("This is title.");

  nodeGroup.append("text")
    .attr("x", function(d) { return d.x; })
    .attr("y", function(d) { return d.y; })
    .attr("text-anchor", "middle")
    .attr("dominant-baseline", "middle")
    .style("fill", "steelblue")
    .text("Ball")
    .append("title")
    .text("This is title.");

forceSimulation을 사용하지 않고, Drag으로만 동작의 경우에는 이벤트 함수를 아래와 같이 변경한다.

function dragged(event, d) {
  d3.select(this).select("circle")
    .attr("cx", d.x = event.x)
    .attr("cy", d.y = event.y);
  d3.select(this).select("text")
    .attr("x", d.x = event.x)
    .attr("y", d.y = event.y);
}



최종 수정 : 2024-01-18