alexalex
alexalex

Reputation: 873

Dynamic filtering with D3

I'm quite new to D3 and coding in general. I'm trying to set up a bar chart which includes/excludes data depending on a checkbox. I have a set of product groups and countries which I want to toggle in/out of the total represented by the bar. The output should be one bar per product.

My full data set has many more products, product groups and countries so it is not viable to create a key-value pair for each potential combination of checkboxes. Instead I would like to create a function that re-evaluates the checkboxes and re-filters the data and updates the rollup when a checkbox is changed.

I'm not sure where this function should sit in my code or what it should look like... This is what I'm working with at the moment:

  var data = data.filter(function(d) {
    if (document.getElementById("nz_button").checked) {
      return d.country == 'NZ'
    }
    if (document.getElementById("au_button").checked) {
      return d.country == 'AU'
    }
    if (document.getElementById("us_button").checked) {
      return d.country == 'US'
    }
  })


  // to see how many distinct groups there are and sum volume
  var products = d3.nest()
    .key(function(d) {
      return d.product
    })
    .rollup(function(leaves) {
      var sum = 0;
      leaves.forEach(function(d) {
        sum += d.volume;
      })
      return sum
    })
    .entries(data);

Full code: http://plnkr.co/edit/qezdwMLt48RPc8KH17hS?p=preview

Maybe I should be working with selections and re-running the nest/rollup when required?

Any help appreciated. Thanks :)

Upvotes: 4

Views: 2501

Answers (1)

Cyril Cherian
Cyril Cherian

Reputation: 32327

You can move the full code which makes the graph in a new function like this:

function makeDataGraph(data) {//function to make the graph.
      //
      // FILTER
      //
      var data = data.filter(function(d) {
        if (document.getElementById("au_button").checked) {
          return d.country == 'AU'
        }
        if (document.getElementById("us_button").checked) {
          return d.country == 'US'
        }
        if (document.getElementById("nz_button").checked) {
          return d.country == 'NZ'
        }

      })


      // to see how many distinct groups there are and sum volume
      var products = d3.nest()
        .key(function(d) {
          return d.product
        })
        .rollup(function(leaves) {
          var sum = 0;
          leaves.forEach(function(d) {
            sum += d.volume;
          })
          return sum
        })
        .entries(data);

      // sorting on descending total
      console.log(products);
      products.sort(function(a, b) {
        return b.values - a.values
      })

      var max = d3.max(products, function(d) {
        return d.values;
      });
      var xscale = d3.scale.linear()
        .domain([0, max])
        .range([0, 600])

      var svg = d3.select("svg");



      //
      // Still needs to be cleaned up  \/  \/
      //

      var rects = svg.selectAll("rect.product")
        .data(products)
      rects.exit().remove();  
      rects.enter().append("rect").classed("product", true)
      rects.attr({
        x: 200,
        y: function(d, i) {
          return 100 + i * 50
        },
        width: function(d, i) {
          return xscale(d.values)
        },
        height: 50
      }).on("click", function(d, i) {
        console.log(i, d);
      })


      var labels = svg.selectAll("text.label")
        .data(products)
      labels.exit().remove();
      labels.enter().append("text").classed("label", true)
      labels.attr({
        x: 195,
        y: function(d, i) {
          return 128 + i * 50
        },
        "text-anchor": "end",
        "alignment-baseline": "middle"
      }).text(function(d) {
        return d.key || "N/A"
      })


      var volume = svg.selectAll("text.volume")
        .data(products);
      volume.exit().remove();  
      volume.enter().append("text").classed("volume", true)
      volume.attr({
        x: function(d, i) {
          return 205 + xscale(d.values)
        },
        y: function(d, i) {
          return 128 + i * 50
        },
        "text-anchor": "start",
        "alignment-baseline": "middle"
      }).text(function(d) {
        return d.values || "N/A"
      })
    }

Remember to do rects.exit().remove(); so that when the data is changed on click of the checkbox, rectangles related to old data is removed.

Now you can call this function from the click event and also afterloading the tsv like this:

d3.tsv("data.tsv", function(err, udata) {

  var udata = udata.map(process);
  console.log("udata", udata);

  var data = udata // making new var to preserve unfiltered data
  makeDataGraph(data);//call the function to make graph

  function handleClick() { // event handler...
    makeDataGraph(data)
  }
  //add listener to all check boxes.
  d3.selectAll(".filter_button").on("click", handleClick);
});

working code here

Upvotes: 2

Related Questions