Tuesday, April 11, 2017

D3 updating a vertical stacked bar chart

Leave a Comment

I have a vertical stacked bar chart and want to dynamically remove rect elements. To do this I followed the basic enter/update/exit procedure. What happens though is, that the bars seem to be added even if their width = 0;

var drawBars = function(data) {   var margin = {top: 5, right: 20, bottom: 5, left: 170},       width = 1020 - margin.left - margin.right,       height = 1820 - margin.top - margin.bottom,       scope = this;    var x = d3.scale.linear()       .rangeRound([0, width-150]);    var y = d3.scale.ordinal()       .rangeBands([0,height], .2, 3);    var color = d3.scale.category20c();    var xAxis = d3.svg.axis()       .scale(x)       .orient("top")       .tickSize(0)       .tickFormat(d3.format("s"));    var yAxis = d3.svg.axis()       .scale(y)       .tickSize(0)       .orient("left");    var svg = d3.select('#barchart svg')       .attr("width", width + margin.left + margin.right)       .attr("height", height + margin.top + margin.bottom)       .append("g")       .attr("transform", "translate(" + margin.left + "," + margin.top + ")");    var createData = function(filter) {     var data = d3.nest()       .key(function(d) { return d.org; })       .key(function(d){ return d.taskforce_id })       .rollup(function(v){ return v.length })       .entries(scope.rawData['participants']);        _.each(data, d => {           _.extend(d, _.object(_.map(d.values, c => {             if (typeof filter !== 'undefined') {               if (c.key == filter) {                 return [c.key, c.values];               }               return [c.key, 0];             }             return [c.key, c.values];           })));           delete d.values;       });      data.forEach(function(d) {       var y0 = 0;       d.orgs = ["A", "B", "C"].map(function(org) {         if (d[org] === undefined) {           return {name: org, y0: 0, y1: 0};         } else {           return {name: org, y0: y0, y1: y0 += +d[org]};         }        });       d.total = d3.max(d.orgs, d => { return d.y1; });     });      data.sort(function(x, y){        return d3.descending(x.total, y.total);     });     return data;   };    var redrawChart = function(filter) {     var data = createData(filter);     console.log(data);      x.domain([0, d3.max(data, function(d) { return d.total; })]);     y.domain(data.map(function(d) { return d.key; }));     color.domain(d3.keys(data[0]).filter(function(key) { return key !== "key"; }));      svg.append("g")       .attr("class", "x axis")       .attr("transform", "translate(0," + 15 + ")")       .call(xAxis);      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");      /////////       //ENTER//       /////////     var chartRow = svg.selectAll("g.chartRow")       .data(data);      var newRow = chartRow       .enter()       .append("g")       .attr("class", "chartRow")       .attr("transform", function(d) { return "translate(0, " + y(d.key) + ")"; });      var rectRow = newRow.selectAll(".bar")       .data(function(d) { return d.orgs; });      rectRow       .enter()       .append("rect")       .attr("class", function(d) { return "bar t_"+ d.name; } )       .attr("height", y.rangeBand())       .on('click', function(d){         redrawChart(d.name);       })       .style("fill", function(d) { return color(d.name); });      //////////       //UPDATE//       //////////     chartRow.selectAll('rect').transition()       .duration(300)       .attr("width", function(d) { return x(d.y1) - x(d.y0); })       .attr("x", function(d) { return x(d.y0); })       .attr("opacity",1);        ////////       //EXIT//       ////////     chartRow.exit().selectAll("rect.bar").transition()       .style("opacity","0")       .attr("transform", "translate(0," + (height + margin.top + margin.bottom) + ")")       .remove();   };    redrawChart();    var legend = svg.selectAll(".legend")       .data(color.domain().slice().reverse());    var legends = legend.enter().append("g")       .attr("class", "legend")       .attr("transform", function(d, i) { return "translate(0," + i * 25 + ")"; });    legend.exit().remove();    legends.append("rect")       .attr("x", width - 28)       .attr("width", 18)       .attr("height", 18)       .style("fill", color);    legends.append("text")       .attr("x", width - 34)       .attr("y", 9)       .attr("dy", ".35em")       .style("text-anchor", "end")       .text(function(d) { return d; });    legends.append("text")       .attr("x", width + 14)       .attr("y", 9)       .attr("class", "numbers")       .attr("dy", ".35em")       .style("text-anchor", "end");  }; 

When I console.log the data everything seems ok, so the filtering works. However, the widths of the rect elements seem to use the OLD data set. Equally, the legends and axis are being duplicated

Fiddle here: https://jsfiddle.net/4nm44fgt/

Any insights?

1 Answers

Answers 1

Your second data bind should not use only the entering nodes, as after filtering this selection will be empty:

 var rectRow = chartRow.selectAll(".bar").data(function(d) {      return d.orgs;  }); 

instead of newRow.selectAll(".bar")...

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment