0

I have a problem with my first attempt at an Exit event. The code below draws one of three circles based on three different JSON files. The default is the medium sized circle. The drop-down menu allows you to select either a Small, Medium, or Large circle to be drawn next.

My problem is that I have not written the Exit mode correctly. If I comment out the exit() then each selection appears on the screen without deleting any of the previous elements, as you would expect. If the exit mode is not commented out then only the default (medium) circle is displayed.
This is likely very obvious to someone with D3JS experience. Please help me to learn where I am going wrong. Thanks so much! - Tim

<body>
<select id = "opts">
  <option value="circle1">Small</option>
  <option value="circle2" selected="selected">Medium</option> 
  <option value="circle3">Large</option>
</select> 

<script>
    // data sources for small, med, large circles
  var circle1 = "/MenuSelections/circle1.JSON";
  var circle2 = "/MenuSelections/circle2.JSON";
  var circle3 = "/MenuSelections/circle3.JSON";

  function drawCirc(newData){   
      // Read in the JSON data
      console.log(newData);  // Confirms data source
      d3.json(newData, function(dataset) {

    var svgContainer = d3.select("body").append("svg")
                                       .attr("width", 200)
                                       .attr("height", 200);

    //ENTER
    var circles = svgContainer.selectAll("circle")
                            .data(dataset.circle)
                            .enter()
                            .append("circle");

    var circleAttributes = circles
                         .attr("cx", function (d) { 
                             console.log(d)                         
                               return d.x_axis; })
                         .attr("cy", function (d) { return d.y_axis; })
                         .attr("r", function (d) { return d.radius; })
                         .style("fill", function(d) { return d.color; });
   //EXIT - NOT WORKING. Original remains, no new graph.
   // if this section commented out: new circles draw in without remove of prev.
    d3.select("body").selectAll("circle")
       .data(dataset.circle)
       .exit() 
      .remove();
    });
}
drawCirc(circle2); // Initiate with default selection 

// handle on click event for the ID (opts) of the menu item
d3.select('#opts')
  .on('change', function() {
    var newData = eval(d3.select(this).property('value'));
    // console.log(newData); 
   drawCirc(newData); // Call with new selection.
});
Tim
  • 929
  • 12
  • 30
  • If you have only a single circle then there's no need to use D3's data matching. You could simply remove the existing circle before adding the new one. – Lars Kotthoff Jun 17 '15 at 18:41
  • True, Lars. However, I am trying to get this code work so I can apply the same approach to a series of complex graphs based on selecting different JSON files. Select one JSON data source, view that graph. Select a different JSON file and view the new graph, etc. – Tim Jun 17 '15 at 19:30
  • In this case see https://stackoverflow.com/questions/29359373/d3-correct-number-of-elements-on-update-but-wrong-values – Lars Kotthoff Jun 17 '15 at 20:28
  • The second time you call `drawCirc`, and in particular the `.data(...)` call, the new circle will get bound in the `update` selection. Your code is only operating on the `enter` selection. The `exit` selection will be empty in this case, since you have one data value and one `circle` element, there is no need to remove anything. You can fix this up by specifying a `key` function to the `.data(...)` call, as per my answer on the question Lars linked to. You could also fix it by updating the `update` selection in your `drawCirc` function, rarther than just the `enter` selection. – Ben Lyall Jun 18 '15 at 23:34

1 Answers1

0

The advice from both Lars and Ben got me on the right track, with two key parts to the solution. The first was understanding that exit() acts on the elements, not their data values, and I needed to add a key as suggested above. Once I fixed that I still had a problem because I was defining my svg container WITHIN the drawCirc function, so each new selection resulted in a new SVG 200x200 container.
Good information on Enter, Update, Exit can be found here http://bost.ocks.org/mike/circles/ and here http://bl.ocks.org/mbostock/3808218

The book "Data Visualization with D3 and AngularJS" Chapter 1, pages 13-16 is also very helpful. Here is my revised code.

<body>
<select id = "opts">
  <option value="circle1">Small</option>
  <option value="circle2" selected="selected">Medium</option> 
  <option value="circle3">Large</option>
</select> 

<script>
    // var names correspond to the value of each drop down item
  var circle1 = "/MenuSelections/circle1.JSON";
  var circle2 = "/MenuSelections/circle2.JSON";
  var circle3 = "/MenuSelections/circle3.JSON";

    var svg = d3.select("body").append("svg")
              .attr("width", 200)
              .attr("height", 200);

  function drawCirc(newData){   
      console.log(newData);  // Confirms data source
      // Read in the JSON data
      d3.json(newData, function(dataset) {

    //ENTER
    var circle = svg.selectAll("circle")
                    .data(dataset.circle, function(d) {return d;});

    circle.enter().append("circle");

    circle.attr("cx", function (d) { 
                             console.log(d)                         
                               return d.x_axis; })
          .attr("cy", function (d) { return d.y_axis; })
          .attr("r", function (d) { return d.radius; })
          .style("fill", function(d) { return d.color; });

     //EXIT                     
     circle.exit().remove();
    });
}
drawCirc(circle2); // Initiate with default selection 

// handle on click event for the ID (opts) of the menu item
d3.select('#opts')
  .on('change', function() {
    var newData = eval(d3.select(this).property('value'));
   drawCirc(newData); // Call with new selection.
});
</script>
</body>
Tim
  • 929
  • 12
  • 30