2

I've got an update button working that loads new .csv data into a D3.s stacked bar chart. My problem is that on update it seems to be using both data sets instead of just the new one. I think this is related to some confusion I have about update and selectAll, but I haven't been able to figure out how to replace instead of append. Here's my current code:

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
  font: 10px sans-serif;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.bar {
  fill: steelblue;
}

.x.axis path {
  display: none;
}

</style>

<body>
<!-- <label><input type="checkbox"> Sort values</label> -->
<div id="option">
    <input name="updateButton" 
           type="button" 
           value="Update" 
           onclick="updateData()" />
</div>

<script src="http://d3js.org/d3.v3.min.js"></script>
<script>

var margin = {top: 20, right: 20, bottom: 200, left: 40},
    width = 960 - margin.left - margin.right,
    height = 650 - margin.top - margin.bottom;

var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1);

var y = d3.scale.linear()
    .rangeRound([height, 0]);

var color = d3.scale.ordinal()
    .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .tickFormat(d3.format(".2s"));

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

d3.csv("FinalData4.csv", function(error, data) {
      color.domain(d3.keys(data[0]).filter(function(key) { return key !== "State"; }));

      data.forEach(function(d) {
        var y0 = 0;
        d.ages = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
        d.total = d.ages[d.ages.length - 1].y1;
      });

      //data.sort(function(a, b) { return b.total - a.total; });

      x.domain(data.map(function(d) { return d.State; }));
      y.domain([0, d3.max(data, function(d) { return d.total; })]);

    // x-axis label
    svg.append("g")
            .attr("class", "x axis")
            .attr("transform", "translate(0," + height + ")")
            .call(xAxis)
            .selectAll("text")  
                .style("text-anchor", "end")
                .attr("dx", "-.8em")
                .attr("dy", ".15em")
                .attr("transform", function(d) {
                    return "rotate(-65)" 
                    });

    // y-axis label      
      svg.append("g")
          .attr("class", "y axis")
          .call(yAxis)
        .append("text")
          .attr("transform", "rotate(-90)")
          .attr("y", 6)
          .attr("dy", ".71em")
          .style("text-anchor", "end")
          .text("USD in Millions");

    // adds bars
      var state = svg.selectAll(".state")
          .data(data)
        .enter().append("g")
          .attr("class", "g")
          .attr("transform", function(d) { return "translate(" + x(d.State) + ",0)"; });

      state.selectAll("rect")
          .data(function(d) { return d.ages; })
        .enter().append("rect")
          .attr("width", x.rangeBand())
          .attr("y", function(d) { return y(d.y1); })
          .attr("height", function(d) { return y(d.y0) - y(d.y1); })
          .style("fill", function(d) { return color(d.name); });

    // set up the legend
      var legend = svg.selectAll(".legend")
          .data(color.domain().slice().reverse())
        .enter().append("g")
          .attr("class", "legend")
          .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

      legend.append("rect")
          .attr("x", width - 18)
          .attr("width", 18)
          .attr("height", 18)
          .style("fill", color);

      legend.append("text")
          .attr("x", width - 24)
          .attr("y", 9)
          .attr("dy", ".35em")
          .style("text-anchor", "end")
          .text(function(d) { return d; });

});

// ** Update data section (Called from the onclick)
function updateData() {


// Get the data again
d3.csv("FinalData2015.csv", function(error, data2) {
      color.domain(d3.keys(data2[0]).filter(function(key) { return key !== "State"; }));

      data2.forEach(function(d) {
        var y0 = 0;
        d.ages = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
        d.total = d.ages[d.ages.length - 1].y1;
      });

      x.domain(data2.map(function(d) { return d.State; }));
      y.domain([0, d3.max(data2, function(d) { return d.total; })]);


        var tran = d3.select("body").transition();

        // change x-axis label
        tran.select(".x.axis")
            .duration(1000)
            .call(xAxis)
            .selectAll("text")  
                .style("text-anchor", "end")
                .attr("dx", "-.8em")
                .attr("dy", ".15em")
                .attr("transform", function(d) {
                    return "rotate(-65)" 
                    });

        // change the y-axis label      
        tran.select(".y.axis")
            .duration(1000)
            .call(yAxis);

      // adds bars
      var state = svg.selectAll(".State")
          .data(data2)          
          .enter().append("g")
          //.transition()
          //.duration(1000)
          .attr("class", "g")
          .attr("transform", function(d) { return "translate(" + x(d.State) + ",0)"; });

      var test = state.selectAll("rect")
          .data(function(d) { return d.ages; })
          .enter().append("rect")
          .transition()
          .duration(1000)
          .attr("width", x.rangeBand())
          .attr("y", function(d) { return y(d.y1); })
          .attr("height", function(d) { return y(d.y0) - y(d.y1); })
          .style("fill", function(d) { return color(d.name); });

    });
}
</script>
</body>

UPDATE: I changed the functionUpdateData() code to what I have currently because I realized it had some junk in there. New code has working transitions for x and y axis labels, but still the same problem described above with the actual bars.

5
  • You need to handle the update and exit selections as well. See e.g. this tutorial. Commented Dec 7, 2014 at 20:16
  • I read the link you included and also ended up finding this explanation of update and exit helpful: [link] (bost.ocks.org/mike/join), but read it to say that dealing with the update selection means using .data, which I think I'm doing, and for exit it's .enter, which I'm also calling in the update function. Pretty sure I'm missing something obvious, but am really scratching my head, especially now that I've got my y and x axis updating correctly in the revised code above. Commented Dec 8, 2014 at 17:59
  • You may need to provide a key function in the second argument to .data() that tells D3 how to match existing and passed data. By default, this is done by index (i.e. the first datum matches the first element in the selection and so on), which is probably not what you want. Commented Dec 8, 2014 at 18:18
  • I actually made sure my two .csv files had the data in the exact same order to eliminate that potential problem. Before I figured out how to update my x axis labels I was getting two labels for each bar, and these also matched up exactly. So unless there is something about the key function that actually triggers the replacement of the first bar data with the second then I think the default indexing should be fine in this case. Commented Dec 8, 2014 at 19:16
  • Well it looks like in your update function you're still only handling the enter selection. Commented Dec 8, 2014 at 19:20

1 Answer 1

2

It is because you are not using update and exiting joins in the update function (read this article carefully http://bost.ocks.org/mike/join/). As far I understand, you want to update the existing csv data with the new one, for that you need to update the current set of data. I'll give you an example with a simpler code:

function updateData() {
    //this is the new data that you are binding to some circles on the canvas
    var circle = svg.selectAll("circle").data([200, 100, 50,10,5]);
    //if there is new data, you get those new circles with the enter() method
    var circleEnter = circle.enter().append("circle");
    circleEnter.attr("cy", 60);
    circleEnter.attr("cx", function(d, i) { return i * 100 + 30; });
    circleEnter.attr("r", function(d) { return Math.sqrt(d); });

    //------THIS IS THE PART YOU ARE MISSING------
    //but the circles that already exist need an update. (check there is no enter() method here)
    circle.attr("r", function(d) { return Math.sqrt(d); });

    //also you need to remove the circles that don´t have a data binding.
    circle.exit().remove();
}

so I think that in your code you have to write something like:

var state = svg.selectAll(".state").data(data);

//the update data
state.attr("transform", function(d) { return "translate(" + x(d.State) + ",0)"; });
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the link and suggestion. I've made some progress but am not there yet. I'm having the same problem as this person: [link] (stackoverflow.com/questions/18186011/…). My new data is updating but the old data is not getting removed. And if I delete the enter().append() lines the data is not updated. He set up a jsfiddle [link] (jsfiddle.net/9UfT2/2) if that helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.