mardi 1 mars 2016

Multiple data filtering (dropdown + checkboxes) with d3.js

I'm working on a project where i put points on a map by using D3 and leaflet. I use this template because i have a lot of data to map.

First, i filter data with checkboxes by type and it works fine but now i want to add a dropdown list to filter by name of location. I was able to set the dropdown list with all names and when i click on a name, it filters the points i want by location. So no problem here

The problem is when i select a name and after change filter by the checkboxes, it returns filtered points for all the map and not just only the location selected. The same thing happens when i zoom on the map (i have a function which filter data by zooming to avoid the map to call useless data).

My csv looks like this :

date,dcomiris,latitude,longitude,type,iris,com,name
2014-01-01,600570101,49.4295880722704,2.08997269112341,7871F,Cathedrale-Universite,60057,Beauvais
2014-01-01,601750101,49.2585514437302,2.46903313061001,7871F,Rive Gauche,60175,Creil
2014-01-01,601750102,49.2658438351767,2.47995361882026,7871D,Voltaire,60175,Creil
2014-01-01,603820201,49.426958710346,2.8194006454886,7871H,Centre,60382,Margny-lès-Compiègne

And my js file :

infraMap = function(map, url, initialSelections) {
  var pointTypes = d3.map(),
      uniqueName = d3.map(),
      points = [],
      lastSelectedPoint;

  var dropDown = d3.select("#filter").append("select")
                      .attr("name", "communes-liste");

  // setup color
  var cValue = function(d) { return d.type;},
    color = d3.scale.category20();

  // checkboxes
  var drawPointTypeSelection = function() {
    showHide('#selections')
    labels = d3.select('#toggles').selectAll('input')
      .data(pointTypes.values())
      .enter().append("label");

    labels.append("input")
      .attr('type', 'checkbox')
      .property('checked', function(d) {
        return initialSelections === undefined || initialSelections.has(d.type)
      })
      .attr("value", function(d) { return d.type; })
      .on("change", drawWithLoading);

    labels.append("span")
      .attr('class', 'key')
      .style('background-color', function(d) { return color(cValue(d)); });

    labels.append("span")
      .text(function(d) { return d.type; });
  }

  //fonction filtre checkboxes
  var selectedTypes = function() {
    return d3.selectAll('#toggles input[type=checkbox]')[0].filter(function(elem) {
      return elem.checked;
    }).map(function(elem) {
      return elem.value;
    })
  }

  var pointsFilteredToSelectedTypes = function() {
    var currentSelectedTypes = d3.set(selectedTypes());
    return points.filter(function(item){
      return currentSelectedTypes.has(item.type);
    });
  }

  // 'loading...' 
  var drawWithLoading = function(e){
    d3.select('#loading').classed('visible', true);
    if (e && e.type == 'viewreset') {
      d3.select('#overlay').remove();
    }
    setTimeout(function(){
      draw();
      d3.select('#loading').classed('visible', false);
    }, 0);
  }

  // draw data
  var draw = function() {
    d3.select('#overlay').remove();

    var bounds = map.getBounds(),
        topLeft = map.latLngToLayerPoint(bounds.getNorthWest()),
        bottomRight = map.latLngToLayerPoint(bounds.getSouthEast()),
        existing = d3.set(),
        drawLimit = bounds.pad(0.4);

   //zoom filtering   
    filteredPoints = pointsFilteredToSelectedTypes().filter(function(d) {
      var latlng = new L.LatLng(d.latitude, d.longitude);

      if (!drawLimit.contains(latlng)) { return false };

      var point = map.latLngToLayerPoint(latlng);

      key = point.toString();
      if (existing.has(key)) { return false };
      existing.add(key);

      d.x = point.x;
      d.y = point.y;
      return true;
    });

    //map/svg
    var div = d3.select("body").append("div") 
    .attr("class", "tooltip")       
    .style("opacity", 0);

    var svg = d3.select(map.getPanes().overlayPane).append("svg")
      .attr('id', 'overlay')
      .attr("class", "leaflet-zoom-hide")
      .style("width", map.getSize().x + 'px')
      .style("height", map.getSize().y + 'px')
      .style("margin-left", topLeft.x + "px")
      .style("margin-top", topLeft.y + "px");

    var g = svg.append("g")
      .attr("transform", "translate(" + (-topLeft.x) + "," + (-topLeft.y) + ")");

    var svgPoints = g.attr("class", "points")
      .selectAll("g")
        .data(filteredPoints)
      .enter().append("g")
        .attr("class", "point")

    svgPoints.append("circle")
      .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
      .style('fill', function(d) { return color(cValue(d)); } )
      .style('stroke', 'black')
      .style('stroke-width', '1px')
      .style("cursor", "pointer")
      .attr("r", 4)
      .on("mouseover", function(d) {
        if(d.name == d.iris){    
          div.transition()    
              .duration(200)    
              .style("opacity", .9);    
          div.html(d.date + "<br/>"  + d.name + "<br />" + d.type )  
              .style("left", (d3.event.pageX + 10) + "px")   
              .style("top", (d3.event.pageY - 28) + "px");
        }
        else{
          div.transition()    
              .duration(200)    
              .style("opacity", .9);    
          div.html(d.date + "<br/>"  + d.name + "<br />" + d.iris + "<br />" + d.type )  
              .style("left", (d3.event.pageX + 10) + "px")   
              .style("top", (d3.event.pageY - 28) + "px");
        }  
      })          
      .on("mouseout", function(d) {   
        div.transition()    
            .duration(500)    
            .style("opacity", 0); 
      });

  // dropdown list
  var options = dropDown.selectAll("option")
       .data([{name:"*Toutes les communes"}].concat(uniqueName.values()))
       .enter()
       .append("option");
   options.text(function(d) { return d.name; })
       .attr("value", function(d) { return d.name; })
       .sort(function(a,b){return d3.ascending(a.name, b.name)});

   //function dropdown
  var dropListFilter = dropDown.on("change", function() {
      var selected = this.value;
      displayOthers = this.checked ? "inline" : "none";
      display = this.checked ? "none" : "inline";
       if(selected == '*Toutes les communes'){
        svg.selectAll(".point")
            .attr("display", display);
      }
      else{
        svg.selectAll(".point")
            .filter(function(d) {return selected != d.name;})
            .attr("display", displayOthers);

        svg.selectAll(".point")
            .filter(function(d) {return selected == d.name;})
            .attr("display", display);
      }
  });
  }

  var mapLayer = {
    onAdd: function(map) {
      map.on('viewreset moveend', draw);
      draw();
    }
  };

  d3.csv("my.csv", function(csv) {
      points = csv;
      points.forEach(function(point) {
        uniqueName.set(point.name, {name: point.name});
        pointTypes.set(point.type, {type: point.type, color: point.color});
      })    
      drawPointTypeSelection();
      map.addLayer(mapLayer);
    });
}

I think the problem is that the two filters can't communicate because my dropdown list is in the draw function and checkboxes are outside but when i put my dropdown list outside this function it can't find name of location.

Thanks !




Aucun commentaire:

Enregistrer un commentaire