x and y attributes of nodes lost after enter-update-exit pattern

Viewed 46

I am trying to transition the nodes of a force simulation in D3v4. The original force is drawn correctly with the nodes aggregated by shark family, and I would like to click on the "Greenland shark" button so only the node with the name 'greenland shark' would be visible (this is working), and I would like that if the user clicks on the "sharks by family" button that the force would go back to it's original positions. However, if I click on "sharks by family" after clicking on "Greenland shark", only two nodes are visible since apart from the node that is updating directly from the greenland shark node, the rest don't have a cx and cy so they are all stacked on top of one another in the default position at 0,0. I am not sure why the cx and cy attributes are lost, could it be a bug in my enter, update, exit pattern?

Below is the relevant code and here is the Plunker link to the visualization + code https://plnkr.co/edit/rvbOJT2fIgxBlsqOtXR4 and visualization only https://run.plnkr.co/plunks/rvbOJT2fIgxBlsqOtXR4/

      // Original force simulation by family

      var simulation = d3.forceSimulation();

      simulation.force('x', d3.forceX(function(d) {
              var i = mapIndex(d.family);
              return xposition(i)
          }).strength(0.03))
          .force('y', d3.forceY(function(d) {
              var i = mapIndex(d.family);
              return yposition(i)
          }).strength((0.03)))
          .force('collide', d3.forceCollide(function(d) {
              return radiusScale(+d.size)
          })).velocityDecay(0.1).alphaDecay(0.001);


      var circles = g.selectAll(".sharks")
          .data(nodes)
          .enter().append("circle")
          .attr("class", "sharks")
          .attr("r", function(d) {
              return radiusScale(+d.size)
          })
          .attr("fill", function(d) {
              return colorScale(d.family)
          })
          .attr('stroke', '')


      simulation.nodes(nodes)
          .on('tick', ticked);


      nodes.forEach(function(d) {
          d.x = familyXScale(d.family)
          d.y = yPositionScale(sharks.indexOf(d.name))
      })


      function ticked() {
          circles
              .attr("cx", function(d) {
                  return d.x
              })
              .attr("cy", function(d) {
                  return d.y
              })
      }

      function charge(d) {
          return -Math.pow(d.radius, 2.0) * forceStrength;
      }



       // function for only showing one node (greenland shark)
      function greenlandShark() {
          console.log('greenlandShark');
          console.log(nodes);

          var newNodes = filterNodes('common_name', 'Greenland shark');

          circles = g.selectAll(".sharks").data(newNodes);

          circles.exit()
              .transition()
              .duration(1000)
              .attr("r", 0)
              .remove();

          circles
              .attr('r', function(d) {
                  return radiusScale(+d.size)
              })
              .attr('fill', function(d) {
                  return colorScale(d.family)
              });

          simulation.nodes(newNodes)
              .on('tick', ticked);

          simulation.force('x', d3.forceX().strength(0.03).x(center.x))
              .force('y', d3.forceY(function(d) {
                  return height / 2
              }).strength((0.03)));

          simulation.alpha(1).restart();

      }

      function filterNodes(key, group) {
          var newnodes = nodes.filter(function(d) {
              return d[key] == group;
          });
          return newnodes;;
      }

      // function for visualizing all nodes again organized by family     
      function sharksByFamily() {

          circles = g.selectAll(".sharks").data(nodes);

          circles.exit().transition().duration(750)
              .attr("r", 0)
              .remove();

          circles.transition().duration(750)
              .attr("fill", function(d) {
                  return colorScale(d.family)
              }).attr("r", function(d) {
                  return radiusScale(+d.size);
              })

          circles.enter().append("circle").attr("class", "sharks")
              .attr("fill", function(d) {
                  return colorScale(d.family)
              }).attr("r", function(d) {
                  return radiusScale(+d.size);
              })
              .attr('stroke', '')

          simulation.force('x', d3.forceX(function(d) {
                  var i = mapIndex(d.family);
                  return xposition(i)
              }).strength(0.03))
              .force('y', d3.forceY(function(d) {
                  var i = mapIndex(d.family);
                  return yposition(i)
              }).strength((0.03)))
              .force('collide', d3.forceCollide(function(d) {
                  return radiusScale(+d.size)
              })).velocityDecay(0.1).alphaDecay(0.001);


          // cx cy not showing up for nodes
          simulation.nodes(nodes)
              .on('tick', ticked);

          simulation.alpha(1).restart();

      }
1 Answers

The attributes are there, that's not the problem. The problem is simply the definition of circles inside the ticked function.

For instance, if you do this:

function ticked() {
    g.selectAll("circle").attr("cx", function(d) {
            return d.x
        })
        .attr("cy", function(d) {
            return d.y
        })
};

It will work. Here is your forked plunker: https://plnkr.co/edit/szhg8eUYQUMxHcLmBF8N?p=preview

However, the idiomatic solution here is merging the selections inside the sharksByFamilyRev function:

circles = circles.enter().append("circle").attr("class", "sharks")
    .attr("fill", function(d) {
        return colorScale(d.family)
    }).attr("r", function(d) {
        return radiusScale(+d.size);
    })
    .attr('stroke', '')
    .merge(circles);

Here is the plunker with that change: https://plnkr.co/edit/pBPJUhEKQIPnB0ammGDd?p=preview

PS: you have other issues in that code, which are not related to the present question (like mixing jQuery and D3, repetition of code etc...).

Related