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.

hyssop
  • 183
  • 1
  • 2
  • 12
  • You need to handle the update and exit selections as well. See e.g. [this tutorial](http://bost.ocks.org/mike/circles/). – Lars Kotthoff Dec 07 '14 at 20:16
  • I read the link you included and also ended up finding this explanation of update and exit helpful: [link] (http://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. – hyssop Dec 08 '14 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. – Lars Kotthoff Dec 08 '14 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. – hyssop Dec 08 '14 at 19:16
  • Well it looks like in your update function you're still only handling the enter selection. – Lars Kotthoff Dec 08 '14 at 19:20

1 Answers1

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)"; });
  • 1
    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] (http://stackoverflow.com/questions/18186011/how-to-update-data-in-stack-bar-chart-in-d3). 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] (http://jsfiddle.net/9UfT2/2/) if that helps. – hyssop Dec 08 '14 at 17:41