Arash Howaida
Arash Howaida

Reputation: 2617

d3 unable to append full data after filtering

I have a simplified version of my project that I condensed into this snippet:

var margins = {top:20, bottom:300, left:100, right:100};

var height = 600;
var width = 1200;

var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;

var svg = d3.select('body')
    .append('svg')
    .attr('width', totalWidth)
    .attr('height', totalHeight);

var graphGroup = svg.append('g')
    .attr('transform', "translate("+margins.left+","+margins.top+")");

var data = [
  {'manager':'ABC-CA', 'aum':230561804112.86996, 'type':'JV'},
  {'manager':'AEGON-Industrial', 'aum':187676730861.82004, 'type':'JV'},
  {'manager':'AVIC', 'aum':677643221.8599999, 'type':'DM'},
  {'manager':'AXA-SPDB', 'aum':111220010833.66998, 'type':'JV'},
  {'manager':'Baoying', 'aum':26328526612.41, 'type':'DM'},
  {'manager':'Beixin Ruifeng', 'aum':10500065729.3, 'type':'JV'},
  {'manager':'BOB-Scotiabank', 'aum':69159188249.67, 'type':'JV'},
  {'manager':'BOC IM', 'aum':396466612963.73, 'type':'DM'},
  {'manager':'BOCI Securities', 'aum':57940275708.97, 'type':'JV'}
];

  var yExtents = d3.extent(data, function(d) {return d.aum; })

  var xScale = d3.scaleBand()
    .rangeRound([0, width])
    .domain(data.map(function(d) {return d.manager; }));

  var yScale = d3.scaleLinear()
      .range([height,0])
      .domain(yExtents);

  graphGroup.append("g")
      .attr("class", "axis axis--y")
      .attr("transform", "translate(0," + 0 + ")")
      .call(d3.axisRight(yScale))
      .selectAll("text")
      .attr('text-align','right')
      .attr("transform", "translate(-90,0)");

  graphGroup.append("g")
      .attr("class", "axis axis--x")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(xScale))
      .selectAll("text")
      .attr("transform", "translate(0,0)");


  var bars = graphGroup.selectAll('rect')
    .data(data)
    .enter()
    .append('rect')
    .attr('x', function(d) {return xScale(d.manager); })
    .attr('y', function(d) {return yScale(d.aum); })
    .attr('width', xScale.bandwidth())
    .attr('height', function(d) {return height-yScale(d.aum); })
    .style('fill','#003366');

  d3.select('#rd1').on('click', function() {

    var newData = data;

    bars.data(newData).enter()
        .append('rect');

    bars.transition()
        .duration(750)
        .attr('x', function(d) {return xScale(d.manager); })
        .attr('y', function(d) {return yScale(d.aum); })
        .attr('height', function(d) {return height-yScale(d.aum); })
        .style('fill','#003366');

  });

  d3.select('#rd2').on('click', function() {

    var newData = data.filter(function(d) {return d.type=="JV"});
    bars.data(newData).exit().remove();

    bars.enter()
        .attr('x', function(d) {return xScale(d.manager); })
        .attr('y',height);

    bars.transition()
        .duration(750)
        .attr('x', function(d) {return xScale(d.manager); })
        .attr('y', function(d) {return yScale(d.aum); })
        .attr('height', function(d) {return height-yScale(d.aum); });

  });
<form>
<label class='radio-label'>All <input type="radio" name="level" value="all" checked="checked" id='rd1'></input></label>
<label class='radio-label'>JV <input type="radio" name="level" value="jv" id='rd2'></input></label>
</form>
<script src="https://d3js.org/d3.v5.min.js"></script>

My intent is to filter the data by d.type=='JV' when the user clicks on the "JV" radio button, and that much works. However, my function for the "All" radio button doesn't work as I anticipated and troubleshooting it has proven problematic. The "All" radio button is supposed to display the whole data set again, but it only displays part of the data set, so it's missing values and I can't figure out how to make it right.

In the console log the console.log(newData.length) is the correct number: 9, but there are only 5 rects appended.

Question

Is my enter, update, exit implementation flawed, and if so, how do I fix it such that my data can be displayed in full again after filtering?

Upvotes: 2

Views: 184

Answers (1)

Coderino Javarino
Coderino Javarino

Reputation: 2896

var margins = {
  top: 20,
  bottom: 300,
  left: 100,
  right: 100
};

var height = 600;
var width = 1200;

var totalWidth = width + margins.left + margins.right;
var totalHeight = height + margins.top + margins.bottom;

var svg = d3.select('body')
  .append('svg')
  .attr('width', totalWidth)
  .attr('height', totalHeight);

var graphGroup = svg.append('g')
  .attr('transform', "translate(" + margins.left + "," + margins.top + ")");

var data = [{
    'manager': 'ABC-CA',
    'aum': 230561804112.86996,
    'type': 'JV'
  },
  {
    'manager': 'AEGON-Industrial',
    'aum': 187676730861.82004,
    'type': 'JV'
  },
  {
    'manager': 'AVIC',
    'aum': 677643221.8599999,
    'type': 'DM'
  },
  {
    'manager': 'AXA-SPDB',
    'aum': 111220010833.66998,
    'type': 'JV'
  },
  {
    'manager': 'Baoying',
    'aum': 26328526612.41,
    'type': 'DM'
  },
  {
    'manager': 'Beixin Ruifeng',
    'aum': 10500065729.3,
    'type': 'JV'
  },
  {
    'manager': 'BOB-Scotiabank',
    'aum': 69159188249.67,
    'type': 'JV'
  },
  {
    'manager': 'BOC IM',
    'aum': 396466612963.73,
    'type': 'DM'
  },
  {
    'manager': 'BOCI Securities',
    'aum': 57940275708.97,
    'type': 'JV'
  }
];

var yExtents = d3.extent(data, function(d) {
  return d.aum;
})

var xScale = d3.scaleBand()
  .rangeRound([0, width])
  .domain(data.map(function(d) {
    return d.manager;
  }));

var yScale = d3.scaleLinear()
  .range([height, 0])
  .domain(yExtents);

graphGroup.append("g")
  .attr("class", "axis axis--y")
  .attr("transform", "translate(0," + 0 + ")")
  .call(d3.axisRight(yScale))
  .selectAll("text")
  .attr('text-align', 'right')
  .attr("transform", "translate(-90,0)");

graphGroup.append("g")
  .attr("class", "axis axis--x")
  .attr("transform", "translate(0," + height + ")")
  .call(d3.axisBottom(xScale))
  .selectAll("text")
  .attr("transform", "translate(0,0)");

function applyData(newData) {
  var bars = graphGroup
    .selectAll('rect')
    .data(newData);

  bars.exit().remove();

  bars.enter()
    .append('rect')
    .attr('x', function(d) {
      return xScale(d.manager);
    })
    .attr('y', height)    
    .attr('width', xScale.bandwidth())
    .style('fill', '#003366')
    .merge(bars)
    .transition()
    .duration(750)
    .attr('x', function(d) {
      return xScale(d.manager);
    })
    .attr('y', function(d) {
      return yScale(d.aum);
    })
    .attr('height', function(d) {
      return height - yScale(d.aum);
    });
}

d3.select('#rd1').on('click', function() {
  applyData(data);
});

d3.select('#rd2').on('click', function() {

  var newData = data.filter(function(d) {
    return d.type == "JV"
  });
  applyData(newData);

});

applyData(data);
<form>
  <label class='radio-label'>All <input type="radio" name="level" value="all" checked="checked" id='rd1'></input></label>
  <label class='radio-label'>JV <input type="radio" name="level" value="jv" id='rd2'></input></label>
</form>
<script src="https://d3js.org/d3.v5.min.js"></script>

Selections are immutable -- you cannot save and reuse bars selection in between different data invokes. Also, you're duplicating your code in 3 places -- initial data and in each of the click handlers. I refactored your code by extracting it into one method.

Upvotes: 5

Related Questions