tadon11Aaa
tadon11Aaa

Reputation: 420

Converting `tick()` from d3 v3 to v5

I have a force function that works in d3 V3 and I'd like to convert it to V5. I'll show the solution that works right now and then go into what's broken.

This works in v3

var force = d3.layout.force()
    .nodes(nodes)
    .size([width, height])
    .gravity(0)
    .charge(0)
    .friction(.9)
    .on("tick", tick)
    .start();

function tick(e) {

  var k = 0.03 * e.alpha;

  // Push nodes toward their designated focus.
  nodes.forEach(function(o, i) {
    var curr_act = o.act;

    var damper = .85;

    o.x += (x(+o.decade) - o.x) * k * damper;
    o.y += (y('met') - o.y) * k * damper;
    o.color = color('met');

 });

  circle
      .each(collide(.5))
      .style("fill", function(d) { return d.color; })
      .attr("cx", function(d) { return d.x; })
      .attr("cy", function(d) { return d.y; });
}

// Resolve collisions between nodes.
function collide(alpha) {

  var quadtree = d3.geom.quadtree(nodes);

  return function(d) {
    var r = d.radius + maxRadius + padding,
        nx1 = d.x - r,
        nx2 = d.x + r,
        ny1 = d.y - r,
        ny2 = d.y + r;
    quadtree.visit(function(quad, x1, y1, x2, y2) {

      if (quad.point && (quad.point !== d)) {
        var x = d.x - quad.point.x,
            y = d.y - quad.point.y,
            l = Math.sqrt(x * x + y * y),
            r = d.radius + quad.point.radius + (d.act !== quad.point.act) * padding;
        if (l < r) {
          l = (l - r) / l * alpha;
          d.x -= x *= l;
          d.y -= y *= l;
          quad.point.x += x;
          quad.point.y += y;
        }
      }
      return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
    });
  };
}

where the object circles is defined as.

var circle = svg.selectAll("circle")
    .data(nodes)
    .enter().append("circle")

And this is an example of a node.

This is my attempt to convert it to v5

var force = d3.forceSimulation(nodes)
.velocityDecay(.9)
.force("center", d3.forceCenter(width / 2,height / 2))
.force("charge", d3.forceManyBody().strength())
.on("tick", tick)

I kept everything else the same except that I replaced d3.geom.quadtree(nodes) with d3.quadtree(nodes).

I am having trouble with the tick function. In the old version, the e argument prints something like this.

enter image description here

In the new version, it prints undefined and the function breaks with Uncaught TypeError: Cannot read property 'alpha' of undefined.

Does tick() have a new format or a new way of passing arguments in v5?

Upvotes: 2

Views: 594

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38231

If you are trying to access simulation properties during the simulation's ticks, you no longer use an event passed as a parameter to the tick function. Instead you can access the simulation directly with this.

From the docs:

When a specified event is dispatched, each listener will be invoked with the this context as the simulation. (docs).

Which means that you can access alpha, for example, with this.alpha() (or simulation.alpha()), within the tick function in v4/v5:

d3.forceSimulation()
  .velocityDecay(.9)
  .force("charge", d3.forceManyBody().strength())
  .on("tick", tick)  
  .nodes([{},{}]);
  
function tick() {
  console.log(this.alpha());
}
.as-console-wrapper {
  min-height: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Upvotes: 3

Related Questions