ロゴ メインコンテンツへ
RSSフィード
「Web 開発」に関連する記事一覧

d3.js ドラッグ可能なノードグラフのサンプル

2023/10/22
(この記事の文字数: 117)

d3.js でドラッグで動かせるノードグラフを作りたかったので、とりあえずシンプルな2つのノードを線で繋ぎつつ、ドラッグで動かせるようにするサンプルを作ったので掲載しておきます。

ソースコードです。画像URL以外はコピペで動くと思います。

  <svg width="400" height="400"></svg>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"
    integrity="sha512-M7nHCiNUOwFt6Us3r8alutZLm9qMt4s9951uo8jqO4UwJ1hziseL6O3ndFyigx6+LREfZqnhHxYjKRJ8ZQ69DQ=="
    crossorigin="anonymous" referrerpolicy="no-referrer"></script>
  <script>
    const width = document.querySelector("svg").clientWidth;
    const height = document.querySelector("svg").clientHeight;
    const nodeCount = 21;
    const nodesData = [];
    const imageWidth = 100;
    const imageHeight = 100;

    const fontSize = 13;
    class Node {
      constructor(
        index,
        label,
        x, y, radius, width, height, href,
        imageOffsetX, imageOffsetY,
        forceRateY = 1.0
      ) {
        this.index = index;
        this.label = label;
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.width = width;
        this.height = height;
        this.href = href;
        this.imageOffsetX = imageOffsetX;
        this.imageOffsetY = imageOffsetY;
        this.forceRateY = forceRateY;
      }
    }

    nodesData.push(new Node(0,
      "アルフレッド",
      width * 0.5 - 50, height * 0.5,
      1,
      100, 100,
      "/blog/images/FehCylPortraits/CYL_Alfred_Engage.png",
      100, 100 / 2,
      0
    ));
    nodesData.push(new Node(1,
      "セリーヌ",
      width * 0.5 + 50, height * 0.5,
      1,
      100, 100,
      "/blog/images/FehCylPortraits/CYL_Celine_Engage.png",
      0, 100 / 2,
      0
    ));

    class NodeEdge {
      constructor(label, source, target, distance) {
        this.label = label;
        this.labelOffset = 6;
        this.source = source;
        this.target = target;
        this.distance = distance;
      }
    }

    const linksData = [];
    linksData.push(new NodeEdge(
      "兄妹",
      0, 1,
      300)
    );

    const svg = d3.select("svg");

    const link = svg.append("g")
      .selectAll("line")
      .data(linksData)
      .enter()
      .append("line")
      .attr("stroke-width", 2)
      .attr("stroke", "gray");

    const edgeLabels = svg.append("g")
      .selectAll("text")
      .data(linksData)
      .enter()
      .append("text")
      .attr("font-size", fontSize)
      .style("text-anchor", "middle")
      .attr("stroke", "black")
      .text(x => x.label);

    const colorScale = d3.scaleOrdinal(d3.schemeSet3);
    const node = svg.append("g")
      .selectAll("circle")
      .data(nodesData)
      .enter()
      .append("circle")
      .attr("r", function (d) { return d.radius })
      .attr("fill", function (d, i) {
        return colorScale(i);
      })
      .attr("stroke", "black")
      .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

    const nodeLabels = svg.append("g")
      .selectAll("text")
      .data(nodesData)
      .enter()
      .append("text")
      .attr("font-size", fontSize)
      .style("text-anchor", "middle")
      .attr("stroke", "black")
      .text(x => x.label);

    const images = svg.append("g")
      .selectAll("image")
      .data(nodesData)
      .enter()
      .append("image")
      .attr("href", d => d.href)
      .attr("height", d => d.height)
      .attr("width", d => d.width)
      .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));



    svg.append("svg:defs")
      .selectAll("marker")
      .data(["arrow"])
      .enter()
      .append("svg:marker")
      .attr("id", String)
      .attr("viewBox", "0 -5 10 10")
      .attr("refX", 10)
      .attr("refY", 0)
      .attr("markerWidth", 6)
      .attr("markerHeight", 6)
      .attr("orient", "auto-start-reverse")
      .append("svg:path")
      .attr("d", "M0,-5L10,0L0,5");

    const simulation = d3.forceSimulation(nodesData)
      // .force('link', d3.forceLink().links(linksData))
      .on("tick", ticked);

    const fontHeight = 20;
    function ticked() {
      link
        .attr("x1", d => nodesData[d.source].x)
        .attr("y1", d => nodesData[d.source].y)
        .attr("x2", d => nodesData[d.target].x)
        .attr("y2", d => nodesData[d.target].y)
        .attr("marker-start", "url(#arrow)")
        .attr("marker-end", "url(#arrow)");

      edgeLabels
        .attr("x", d => (nodesData[d.source].x + nodesData[d.target].x) / 2)
        .attr("y", d => (nodesData[d.source].y + nodesData[d.target].y) / 2 - d.labelOffset);

      node
        .attr("cx", d => d.x)
        .attr("cy", d => d.y);

      images
        .attr("x", d => d.x - d.imageOffsetX)
        .attr("y", d => d.y - d.imageOffsetY);

      nodeLabels
        .attr("x", d => d.x - d.imageOffsetX + d.width / 2)
        .attr("y", d => d.y + d.height / 2 + fontHeight);

    }

    function dragstarted(e, d) {
      if (!e.active) simulation.alphaTarget(0.3).restart();
      d.fx = d.x;
      d.fy = d.y;
    }

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

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

  このエントリーをはてなブックマークに追加  

<<「Web 開発」の記事一覧に戻る

コメント(0 件)



コンテンツロード: 0.0073 sec
Copyright(C)2006-2024 puarts All Rights Reserved