block by shimizu d7b0c145e2b837a27d9aa2bf4b8c5465

force simulation - globe

Full Screen

Built with blockbuilder.org

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>force simulation - globe</title>
    <style>
      #graph {
        width: 940px;
        height: 460px;
      }
    </style>
  </head>
  <body>
    <div id="btn">    
      <button data-fn="addENode">Eノード追加</button>    
      <button data-fn="deleteDLinkNode">Dノード・リンク削除</button>    
      <button data-fn="addACLink">ACリンク追加</button>    
      <button data-fn="deleteBCLink">BCリンク削除</button>    
    </div>
    <div id="graph"></div>    


    <script src="//cdnjs.cloudflare.com/ajax/libs/d3/4.1.1/d3.min.js"></script>    
    <script>
      //描画エリアのサイズを取得する    
      var w = document.querySelector("#graph").clientWidth
      var h = document.querySelector("#graph").clientHeight

      //svgエレメントの初期化
      var svg = d3.select("#graph")
      .append("svg")
      .attr("width", w)
      .attr("height", h)
      ;

      var linkLayer = svg.append("g");    
      var nodeLayer = svg.append("g");   

      //ノード、リンク、初期データ    
      var data = {
        nodes:[
          {id: 'A', x:500, y:500},
          {id: 'B', x:0, y: 0},
          {id: 'C', x:500, y:0},
          {id: 'D', x:0, y:500},
        ],
        links:[
          {id:"AB", source: "A", target: "B"},
          {id:"BC",  source: "B", target: "C"},
          {id:"CD", source: "C", target: "D"},
          {id:"DB", source: "D", target: "B"},
        ]
      };

      //フォースシュミレーターオブジェクトを用意
      var simulation = d3.forceSimulation()
      .force("link", d3.forceLink().id(d =>  d.id).distance(() => 100).strength((d,i) => (i * 0.1) ))
      .velocityDecay(0.9)
      .force("collide",d3.forceCollide(32))
      .force('x', d3.forceX().strength(0.3).x(w/2))
      .force('y', d3.forceY().strength(0.3).y(h/2))
      .force('charge', d3.forceManyBody().strength((d,i) => -(i * 100)))    

      //ドラッグ時の処理        
      var drag = d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended)        

      function dragstarted(d) {
        if (!d3.event.active) simulation.alphaTarget(0.3).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) simulation.alphaTarget(0);
        d.fx = null;
        d.fy = null;
      }     

      //svgエレメントを更新する
      function updateElement(){
        simulation.nodes(data.nodes);
        simulation.force("link").links(data.links);


        var link = linkLayer.selectAll(".link")
        .data(data.links, d => d.id )
        ;


        link.enter().append("line")
          .attr("class", "link")
          .attr("stroke", "black")
        ;

        link.exit().remove();


        var node = nodeLayer.selectAll(".node")
        .data(data.nodes, d=> d.id )
        ;


        var newNode = node.enter().append("g")
        .attr("class", "node")
        .call(drag)
        ;

        node.exit().remove();

        newNode.append("image")
          .attr("xlink:href", "globe.png")
          .attr("x", "-16px")
          .attr("y", "-16px")
          .attr("width", "32px")
          .attr("height", "32px")        

        newNode.append("text")
          .attr("x", "1.5em")
          .attr("text-anchor", "middle")
          .attr("dominant-baseline", "middle")
          .text(d => d.id)
        ;

        simulation.alpha(1).restart();


      }

      //フォースシュミレーターの計算結果を使って、ノードとリンクの位置を更新する
      function ticked() {
        svg.selectAll(".link")
          .attr("x1", d => d.source.x)
          .attr("y1", d => d.source.y)
          .attr("x2", d => d.target.x)
          .attr("y2", d => d.target.y)
        ;

        svg.selectAll(".node")
          .attr("transform", d => "translate("+[d.x, d.y]+")")
        ;

      }

      //ボタンにデータを変更する処理をセットする。
      function setBtnEventListener() {
        //ボタンクリック時のイベントハンドラ
        var btnEventHandler = {
          addENode:function(){
            data.nodes.push({id:'E'});
            data.links.push({id:"EA", source: "E", target: "C"});
            updateElement();
          },
          addACLink:function(){
            data.links.push({id:"AC", source: "A", target: "C"});
            updateElement();
          },
          deleteDLinkNode:function(){
            data.nodes = data.nodes.filter(function(d){
              return d.id != "D";
            });
            data.links = data.links.filter(function(d){
              return d.source.id != "D" && d.target.id != "D";
            });

            updateElement();    
          },
          deleteBCLink:function(){
            data.links = data.links.filter(function(d){
              return !(d.source.id == "B" && d.target.id == "C");
            });
            updateElement();
          }
        };


        d3.select("#btn").selectAll("button").on("click", function(){
          btnEventHandler[this.dataset.fn]();
        });
      }

      function main() {
        updateElement();
        simulation.on("tick", ticked);    
        setBtnEventListener();
      }


      main();






    </script>    
  </body>
</html>