Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Filter Data with D3.js?

I've been searching all online but I can't really find what I'm looking for. I think maybe I'm not using the right terminology.

I have a simple scatterplot in D3.js. My csv file is like:

Group, X, Y

1, 4.5, 8
1, 9, 12
1, 2, 19
2, 9, 20
3, 2, 1
3, 8, 2

I want to filter by group. So the graph will display by default only the values of group 1, but you can also select to see values of group 2 or group 3.

Here is some code I have already...

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

var x = d3.scale.linear()
    .range([0, width]);

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

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("l2data.csv", function(error, data) {
  if (error) throw error;

  // Coerce the strings to numbers.
  data.forEach(function(d) {
    d.x = +d.x;
    d.y = +d.y;
  });

  // Compute the scales’ domains.
  x.domain(d3.extent(data, function(d) { return d.x; })).nice();
  y.domain(d3.extent(data, function(d) { return d.y; })).nice();

  // Add the x-axis.
  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.svg.axis().scale(x).orient("bottom"));

  // Add the y-axis.
  svg.append("g")
      .attr("class", "y axis")
      .call(d3.svg.axis().scale(y).orient("left"));

  // Add the points!
  svg.selectAll(".point")
      .data(data)
    .enter().append("path")
      .attr("class", "point")
      .attr("d", d3.svg.symbol().type("triangle-up"))
      .attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
});

like image 714
Alice K. Avatar asked Oct 10 '16 18:10

Alice K.


2 Answers

You have a good start here! To make this even better, you need three things. A UI component for selecting the group, an event listener for detecting a change in this component, and a function for handling the update to the visualization.

In the code I added, I've created three new functions for each part of the d3 lifecycle. Enter handles appending new elements to our graph. Update updates existing values based on a change in the bound data. Exit removes any elements that no longer have data bound to them. Read the classic article https://bost.ocks.org/mike/join/ by Mike Bostock at that link to get more info. With this, you have the flexibility to add cool transitions to your chart, and can also customize the way data should enter and exit.

var data = [{ group: 1, x: 5.5, y: 0 }, { group: 1, x: 0, y: 6 }, { group: 1, x: 7.5, y: 8 }, { group: 2, x: 4.5, y: 4 }, { group: 2, x: 4.5, y: 2 }, { group: 3, x: 4, y: 4 }];

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

var x = d3.scale.linear()
    .range([0, width]);

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

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 + ")");

    
// Coerce the strings to numbers.
data.forEach(function(d) {
  d.x = +d.x;
  d.y = +d.y;
});

// Compute the scales’ domains.
x.domain(d3.extent(data, function(d) { return d.x; })).nice();
y.domain(d3.extent(data, function(d) { return d.y; })).nice();


// Add the x-axis.
svg.append("g")
  .attr("class", "x axis")
  .attr("transform", "translate(0," + height + ")")
  .call(d3.svg.axis().scale(x).orient("bottom"));

// Add the y-axis.
svg.append("g")
  .attr("class", "y axis")
  .call(d3.svg.axis().scale(y).orient("left"));

// Get a subset of the data based on the group
function getFilteredData(data, group) {
	return data.filter(function(point) { return point.group === parseInt(group); });
}

// Helper function to add new points to our data
function enterPoints(data) {
  // Add the points!
  svg.selectAll(".point")
    .data(data)
    .enter().append("path")
    .attr("class", "point")
    .attr('fill', 'red')
    .attr("d", d3.svg.symbol().type("triangle-up"))
    .attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
}

function exitPoints(data) {
  svg.selectAll(".point")
      .data(data)
      .exit()
      .remove();
}

function updatePoints(data) {
  svg.selectAll(".point")
      .data(data)
      .transition()
	  .attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
}

// New select element for allowing the user to select a group!
var $groupSelector = document.querySelector('.group-select');
var groupData = getFilteredData(data, $groupSelector.value);

// Enter initial points filtered by default select value set in HTML
enterPoints(groupData);

$groupSelector.onchange = function(e) {
  var group = e.target.value;
  var groupData = getFilteredData(data, group);

  updatePoints(groupData);
  enterPoints(groupData);
  exitPoints(groupData);

};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<label>
  Group
  <select class="group-select">
    <option value=1 selected>1</option>
    <option value=2>2</option>
    <option value=3>3</option>
  </select>
</label>
like image 163
akosel Avatar answered Nov 14 '22 22:11

akosel


It depends on what exactly you need to do but you could do like in the following snippet I made based on your problem. I skipped data loading but the principle is the same.

You have to create a variable to hold your created points after you've created then use a select list, or something else to hide or show the points based on the selection.

// this variable will hold your created symbols
var points,
    margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = 400 - margin.left - margin.right,
    height = 300 - margin.top - margin.bottom;

var x = d3.scale.linear()
.range([0, width]);

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

var select = d3.select('body')
    .append('div')
    .append('select')
    .on('change', function(){
      // get selected group
      var group = select.property('value');
      // hide those points based on the selected value.
      points.style('opacity', function(d){
        return d.group == group ? 1:0;
      });
    });

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 + ")");

var data = [
      {group:1, x:4.5, y:8},
      {group:1, x:9, y:12},
      {group:1, x:2, y:19},
      {group:2, x:9, y:20},
      {group:3, x:2, y:1},
      {group:3, x:8, y:2}
    ];

// Coerce the strings to numbers.
data.forEach(function(d) {
  d.x = +d.x;
  d.y = +d.y;
});

var groups = d3.set(data.map(function(d){ return d.group; })).values();

// create group filtering select list
select.selectAll('option')
  .data(groups)
  .enter().append('option').text(function(d){ return d });

select.property('value', groups[0]); // default selected group


// Compute the scales’ domains.
x.domain(d3.extent(data, function(d) { return d.x; })).nice();
y.domain(d3.extent(data, function(d) { return d.y; })).nice();

// Add the x-axis.
svg.append("g")
  .attr("class", "x axis")
  .attr("transform", "translate(0," + height + ")")
  .call(d3.svg.axis().scale(x).orient("bottom"));

// Add the y-axis.
svg.append("g")
  .attr("class", "y axis")
  .call(d3.svg.axis().scale(y).orient("left"));

// Add the points!
points = svg.selectAll(".point")
  .data(data)
  .enter().append("path")
    .attr("class", "point")
    .attr("d", d3.svg.symbol().type("triangle-up"))
    .attr("transform", function(d){ 
      return "translate("+x(d.x)+","+y(d.y)+")";
    })
    // hide all points expect default group
    .style('opacity', function(d){ return d.group == groups[0]?1:0; });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.13/d3.min.js"></script>
like image 35
PierreB Avatar answered Nov 15 '22 00:11

PierreB