如何在D3 forceSimulation中动态更新节点的坐标?

时间:2019-06-20 02:05:29

标签: d3.js canvas

我正在尝试根据点击事件更新节点的位置。

我可以看到控制台中的数据已更新,但是x,y坐标未更新。我期望forceSimulation在数据更改后立即更新x,y坐标。

很明显,我误解了它的工作原理。但是,如果您能说出直觉上我到底错在哪里,那真的可以帮助我。我的想法是,力模拟将基于foci_dict更新forceX和forceY坐标。

在控制台中:{name:“ b”,年龄:27,索引:0,x:45.46420808466252,y:54.94672336907456,…}

对象已更新,但是坐标未更新。

<html>
      <body>
        <canvas id="bubbles" width="1500" height="500"></canvas>
        <button onClick="changeMe()"> clicck me </button>
      </body>
      <script src="https://d3js.org/d3.v4.min.js"></script>
      <script>
        var height = 500;
        var width = 1500;
        var canvas = d3.select("#bubbles");
        var ctx = canvas.node().getContext("2d");
        var r = 5;

        var data = [
          { name: "a", age: 27 },
          { name: "a", age: 21 },
          { name: "a", age: 37 },
          { name: "b", age: 23 },
          { name: "b", age: 33 },
          { name: "c", age: 23 },
          { name: "d", age: 33 }
        ];

        var foci_points = {
          'a': { x: 50, y: 50 },
          'b': { x: 150, y: 150 },
          'c': { x: 250, y: 250 },
          'd': { x: 350, y: 350 }
        };

        var simulation = d3.forceSimulation()
            .force("x", d3.forceX(function(d){
                return foci_points[d.name].x;
            }))
            .force("y", d3.forceY(function(d){
                return foci_points[d.name].y;
            })
            )
            .force("collide", d3.forceCollide(r+1))
            .force("charge", d3.forceManyBody().strength(10)) 
            .on("tick", update); 


        simulation.nodes(data); 

        function update() {
          ctx.clearRect(0, 0, width, height); 


          ctx.beginPath();
          data.forEach(drawNode);
          ctx.fill();
        } 

        function changeMe(){
            data[0].name="b";
            console.log(data);
        }
        function drawNode(d) {
          ctx.moveTo(d.x, d.y);
          ctx.arc(d.x, d.y, r, 0, 2 * Math.PI);
        } 
        update();


      </script>
    </html>

1 个答案:

答案 0 :(得分:1)

您必须解决两个问题:

  1. 仅由于基础数据已更改,作用力不会自动更新。您必须通过调用force.initialize(nodes)以编程方式重新初始化作用力。鉴于您的代码,这可能需要遵循以下几行:

    simulation.force("x").initialize(data);   // Get the x force and re-initialize.
    simulation.force("y").initialize(data);   // Get the y force and re-initialize.
    
  2. 当您单击按钮时,模拟会冷却甚至停止。要使其重新运行,必须通过将alpha设置为大于alphaMin的某个值并通过restarting进行模拟来重新加热它。

    simulation.alpha(1).restart();            // Reheat and restart the simulation.
    

最好在您的changeMe()函数中完成这些操作。

查看以下正在运行的演示代码段:

var height = 500;
var width = 1500;
var canvas = d3.select("#bubbles");
var ctx = canvas.node().getContext("2d");
var r = 5;

var data = [{
    name: "a",
    age: 27
  },
  {
    name: "a",
    age: 21
  },
  {
    name: "a",
    age: 37
  },
  {
    name: "b",
    age: 23
  },
  {
    name: "b",
    age: 33
  },
  {
    name: "c",
    age: 23
  },
  {
    name: "d",
    age: 33
  }
];

var foci_points = {
  'a': {
    x: 50,
    y: 50
  },
  'b': {
    x: 150,
    y: 150
  },
  'c': {
    x: 250,
    y: 250
  },
  'd': {
    x: 350,
    y: 350
  }
};

var simulation = d3.forceSimulation()
  .force("x", d3.forceX(function(d) {
    return foci_points[d.name].x;
  }))
  .force("y", d3.forceY(function(d) {
    return foci_points[d.name].y;
  }))
  .force("collide", d3.forceCollide(r + 1))
  .force("charge", d3.forceManyBody().strength(10))
  .on("tick", update);


simulation.nodes(data);

function update() {
  ctx.clearRect(0, 0, width, height);

  ctx.beginPath();
  data.forEach(drawNode);
  ctx.fill();
}

function changeMe() {
  data[0].name = "b";
  simulation.force("x").initialize(data);
  simulation.force("y").initialize(data);
  simulation.alpha(1).restart();
}

function drawNode(d) {
  ctx.moveTo(d.x, d.y);
  ctx.arc(d.x, d.y, r, 0, 2 * Math.PI);
}
update();
<script src="https://d3js.org/d3.v4.min.js"></script>

<body>
  <button onClick="changeMe()"> clicck me </button>
  <canvas id="bubbles" width="1500" height="500"></canvas>
</body>