Alexander Moore
Alexander Moore

Reputation: 547

can't get .exit() and .enter() to work properly when updating data in d3.js

I'm currently trying to make a simple series of bar charts from a CSV file. The CSV file is divided into columns, each of which represents a question on a survey. Each row represents a survey respondent, and each cell is a response for a particular question from a particular.

The idea is to have a two arrow buttons at the top that allow me to change the chart so that it represents the next question in the survey.

I can get any given chart to load when I first load the page, but then when I try to use the buttons, I get very strange behaviors with some bars not disappearing, and some bars doubling up with other bars.

So for example, it might start off like this: enter image description here

And after a few presses of the button end up like this with both axes and bars doubled up:

enter image description here

I suspect that I'm assigning keys badly so that d3 doesn't know what it needs to remove, but I can't figure out much beyond that. Here is the code:

<!DOCTYPE html>
<html>
    <head>
      <meta charset="utf-8">
      <script src="http://d3js.org/d3.v3.min.js"></script>
      <style>
      </style>
      <script type="text/javascript">
        function draw(data) {
            "use strict";

            // Sets up initial question
            var current_question = [1];

            /*sets up the canvas for the visualization*/
            var margin = 75,
            width = 1400 - margin,
            height = 700 - margin;

            /*Adds title*/
            d3.select("body")
              .append("h2")
              .text("Environmental Attitudes");

            //Adds space for updated question text later
            var question_text = d3.select("body")
                                  .append('div')
                                  .attr('class','question_text');

            /*Adds buttons for question selection*/
            var buttons =d3.select("body")
                           .append('div')
                           .attr('class','question_selection_buttons');



            // y scale
            var y = d3.scale.linear()
                            .range([height,0]);
            // x scale
            var x = d3.scale.ordinal()
                      .rangeRoundBands([0, width], .1);
            //x axis
            var xAxis = d3.svg.axis()
                              .scale(x)
                              .orient("bottom");
            //y axis
            var yAxis = d3.svg.axis()
                              .scale(y)
                              .orient("left");

            /*Adds the SVG element that will house everything else*/
            var svg = d3.select("body")
                        .append("svg")
                        .attr("id", "svg_main")
                        .attr("width", width + margin)
                        .attr("height", height + margin);

            // list of variables
            var questions = d3.keys(data[0]).filter(function(d){
                                                        var x = "cluster";
                                                        if (d.indexOf(x) === -1 && d !== 'ids') {
                                                            return d    ;
                                                        }       
                                                    });

            var num_questions = questions.length;

            function update_chart(question) {

                /*
                d3.selectAll("svg > *")
                          .remove();
                */
                // filters out NaN observations from data from the question "question"
                var new_data = data.filter(function(d){
                    if(isNaN(+d[question])){
                        return false;
                    }
                    return true;
                });


                // rolls up data by answer in "question"
                var nested = d3.nest()
                               .key(function(d){return d[question];
                               })
                               .rollup(function(leaves){
                                    var total = data.length
                                    var responses = leaves.length;

                                    return {
                                        'responses' : responses,
                                        'percent' : responses/total
                                    };
                                })
                               .entries(new_data)

                var elem = svg.selectAll('rect')
                              .data(nested)
                              .exit()
                              .remove();

                //sets the domain of x by passing it the range of possible values
                x.domain(nested.map(function (d) {
                    return d.key;
                }));

                //sets the domain of y by passing it the range of possible values
                y.domain([0, d3.max(nested, function (d) {
                    return +d.values['percent'].toPrecision(3);
                })]);
                //draws x axis
                svg.append("g")
                   .attr('class','x axis')
                   .attr('transform', 'translate(0,' + height + ')')
                   .call(xAxis);
                //draws y axis
                svg.append('g')
                   .attr("class", 'y axis')
                   .call(yAxis);

                svg.selectAll('g')
                   .data(nested)
                   .enter().append('g')
                   .attr('class','response')
                   .attr('transform', function(d){
                        return "translate (" + x(d.key) + ",0)";
                    });


                svg.selectAll('rect')
                        .data(nested)
                        .enter().append("rect")
                        .attr("width", x.rangeBand())
                        .attr('height', function(d){

                            return height-y(+d.values['percent'].toPrecision(3));
                        })
                        .attr("y", function(d){return y(+d.values['percent'].toPrecision(3))})
                        .attr('transform', function(d){
                            return "translate (" + x(d.key) + ",0)";
                        });



            };


            var left = buttons.append('div')
                              .selectAll('div')
                              .data(current_question)
                              .enter()
                              .append('div')
                              .attr("class", "button")
                              .text("<")
                              .attr("id", "left-button");

            var right = buttons.append('div')
                               .selectAll('div')
                               .data(current_question)
                               .enter()
                               .insert('div')
                               .attr("class", "button")
                               .text(">")
                               .attr("id", "right-button");

            left.on("click", function(d) {
                    current_question--
                    if (current_question < 0) {
                        current_question = num_questions-1;
                    };
                    update_chart(questions[current_question])
                });
            right.on("click", function(d) {
                    current_question++
                    if (current_question >= num_questions) {
                        current_question = 0
                    }

                    update_chart(questions[current_question]);
                });

            update_chart(questions[current_question[0]]);


        }
      </script>
    </head>
    </body>
        <script type="text/javascript">
            d3.csv("clusterData.csv", draw)
        </script>
    </body>
</html>

Just one more note. I have gotten the chart to refresh properly by clearing all the contents of the svg element on every update. That code is commented out in the code above. I'd like instead to do this by using .exit().remove().

Here is a sample of the data

Here's a smaller snippet of data in CSV format:

ids,ldcgrn,othssame,chemgen,natchld,natpark,sex,nukegen,harmgood,topprob1,drivless,grnmoney,topprob2,peopgrn,natsci,grngroup,redcehme,grntaxes,grncon,govdook,knowsol,chemfree,ihlpgrn,tempgen1,recycle,grndemo,impgrn,scigrn,h2oless,carsgen,indusgen,grnprice,grwthelp,nobuygrn,grneffme,helpharm,grnprog,toodifme,grnsign,harmsgrn,grnexagg,popgrwth,watergen,natenrgy,busgrn,natsoc,privent,grnsol,natroad,age,usdoenuf,grwtharm,econgrn,race,polgreed,polviews,grnintl,knwcause,grnecon,2_clusters,3_clusters,4_clusters,5_clusters,6_clusters,7_clusters,8_clusters,9_clusters,
1269,2,3,3,2,2,Female,1,4,Poverty,NaN,0,Health care,More information and education for people about the advantages of protecting the environment,2,0,1,4,3,4,3,1,4,3,NaN,0,3,3,1,3,2,3,3,1,NaN,4,4,4,0,4,3,4,3,2,More information and education for businesses about the advantages of protecting the environment,2,NaN,5,2,71,2,3,3,Black,4,6,3,3,3,1,0,1,0,5,6,2,0,
1403,4,2,3,1,1,Male,3,2,Health care,2,0,The environment,Heavy fines for people who damage the environment,2,0,2,3,5,3,4,2,4,3,2,0,4,4,2,1,1,2,4,4,2,3,4,5,0,4,4,4,1,2,NaN,2,4,3,2,18,2,2,4,Black,2,4,5,5,2,1,1,2,3,0,0,3,7,
1868,4,4,2,1,1,Male,1,2,The economy,NaN,0,Health care,More information and education for people about the advantages of protecting the environment,2,0,2,2,5,NaN,1,1,2,3,3,0,4,4,2,1,1,2,4,2,4,4,4,4,0,4,4,4,2,3,Heavy fines for businesses that damage the environment,2,5,4,2,51,1,2,4,White,4,4,4,1,3,1,1,1,3,0,0,3,7,
1296,2,2,2,1,1,Female,NaN,4,Health care,2,0,Immigration,More information and education for people about the advantages of protecting the environment,NaN,0,2,4,5,1,3,3,5,3,4,0,3,3,2,4,3,2,3,3,4,4,NaN,2,0,3,3,4,1,1,Heavy fines for businesses that damage the environment,1,3,2,NaN,41,1,3,NaN,White,4,4,4,3,2,0,1,2,2,3,2,7,4,
1256,2,4,2,1,2,Male,5,2,The economy,NaN,0,Poverty,More information and education for people about the advantages of protecting the environment,2,0,3,5,5,4,2,3,4,4,2,0,4,3,3,2,3,4,5,3,4,4,5,1,0,5,3,3,3,1,More information and education for businesses about the advantages of protecting the environment,1,3,5,1,51,2,2,4,Black,2,5,4,3,5,1,0,1,3,1,3,4,2,
1943,2,4,3,1,2,Female,2,1,Immigration,2,0,The economy,More information and education for people about the advantages of protecting the environment,1,0,2,5,5,2,2,2,4,3,3,0,3,2,3,1,1,2,4,2,5,4,2,4,0,4,2,4,1,1,More information and education for businesses about the advantages of protecting the environment,1,2,2,2,52,1,3,3,White,4,7,4,2,1,0,1,2,2,3,2,7,4,
1940,2,2,3,2,2,Male,3,2,Terrorism,3,0,Poverty,Use the tax system to reward people who protect the environment,1,0,3,3,4,2,2,3,4,1,4,0,2,2,2,2,2,3,4,2,2,4,4,4,0,4,2,4,1,1,Use the tax system to reward businesses that protect the environment,1,2,5,2,47,2,2,4,White,3,4,4,2,2,0,1,2,2,3,3,7,2,
1941,2,4,4,1,2,Male,4,2,Health care,1,0,Crime,More information and education for people about the advantages of protecting the environment,2,0,2,5,1,4,2,3,4,1,3,0,4,2,2,1,2,4,4,2,2,2,2,4,0,2,4,4,1,2,Heavy fines for businesses that damage the environment,2,2,5,2,75,1,2,4,White,2,3,4,2,4,1,0,1,0,5,6,2,2,

Upvotes: 0

Views: 1692

Answers (1)

Mark
Mark

Reputation: 108512

Couple problems:

1.) You are re-appending your axis on every update. Append a blank one then update it each time.

2.) Your rects aren't following enter, update, exit pattern at all. See below for how I handle the enter, update and exit all independently of each other.

// set x domain to new data
x.domain(nested.map(function (d) {
    return d.key;
}));

// set y domain to new data
y.domain([0, d3.max(nested, function (d) {
    return +d.values['percent'].toPrecision(3);
})]);

// select your rects and bind your data
// note how I'm giving it a "key" function to guarantee the join is computed properly (https://github.com/mbostock/d3/wiki/Selections#data)
var rects = svg.selectAll('rect')
  .data(nested, function(d){
    return d.key;
  });

// handle those elements entering
rects
  .enter().append("rect");

// handle the update
rects
  .attr("width", x.rangeBand())
  .attr('height', function(d){
      return height-y(+d.values['percent'].toPrecision(3));
  })
  .attr("y", function(d){return y(+d.values['percent'].toPrecision(3))})
  .attr('x', function(d){
      return x(d.key);
  });

// handle the exit
rects.exit().remove();

// just update an already existing axis
svg.select('g.x.axis')
  .call(xAxis);

svg.select('g.y.axis')
  .call(yAxis);

Here's a working example where I've cleaned up your code a bit.

Upvotes: 2

Related Questions