LaGranf
LaGranf

Reputation: 63

Problem selecting element by id using d3 js

I'm trying to make a choropleth map in d3 js.

In my code I'm using gejson to draw french departments (counties) and then I want to color them using data from a csv file.

First I populate every counties with their official ID which is a number of 5 digits (ex : 75001).

Then I want to color them using a colorScale. To do so, I do a for each loop where I select counties using their ID (in the csv file this time) and I use the color scale and the data form the csv to get the color of the countie on the map.

I think that the problem problem is d3.select("#d" + e.insee) doesn't work.

I have taken the problem in every way possible and I really can"t figure out what is wrong in the code.

Here is my entire code. The data are loaded from a github so every one can execute it. I apologized for the long code.

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
 <head>
 <meta charset="utf-8">

 <title>All in One</title>

 <script src="https://d3js.org/d3.v5.min.js"></script>

 <style type="text/css">
   #info {
     margin-top: 50px;
   }

   #deptinfo {
     margin-top: 30px;
   }

   .department {
     cursor: pointer;
     stroke: black;
     stroke-width: .5px;
   }

   .department:hover {
     stroke-width: 2px;
   }

  div.tooltip {
    position: absolute;
    opacity:0.8;
    z-index:1000;
    text-align:left;
    border-radius:4px;
    -moz-border-radius:4px;
    -webkit-border-radius:4px;
    padding:8px;
    color:#fff;
    background-color:#000;
    font: 12px sans-serif;
    max-width: 300px;
    height: 40px;
  }

  #svg {
    display: block;
    margin: auto;
  }
</style>
 </head>
   <body>
     <div id="map"></div>
   </body>
 </html>

 <script type="text/javascript"> 
   const width = 850, 
   const height = 800,
   colors = ['#d4eac7', '#c6e3b5', '#b7dda2', '#a9d68f', '#9bcf7d', '#8cc86a', '#7ec157', '#77be4e', '#70ba45', '#65a83e', '#599537', '#4e8230', '#437029', '#385d22', '#2d4a1c', '#223815'];

 const path = d3.geoPath();

 const projection = d3.geoMercator()
 .center([2.332978, 48.860117])
 .scale(40000)
 .translate([width / 2, height / 2]);

 path.projection(projection);

 const svg = d3.select('#map').append("svg")
 .attr("id", "svg")
 .attr("width", width)
 .attr("height", height)
 .attr("class", "Blues");

 // Append the group that will contain our paths
 const deps = svg.append("g");

 var promises = []; 


promises.push(d3.json('https://raw.githubusercontent.com/cerezamo/dataviz/master/Graphique_bokeh/pop_comgeo.geojson'))

promises.push(d3.csv("https://raw.githubusercontent.com/cerezamo/dataviz/master/variables.csv"))

Promise.all(promises).then(function(values){
 const geojson = values[0];
 const csv = values[1];


var features = deps
 .selectAll("path")
 .data(geojson.features)
 .enter()
 .append("path")
 .attr('id', function(d) {return "d" + d.properties.insee;})// Creation of the id as (ex :"d75005")
 // I add a d so the id is not a pure number as it could create error when selecting it
 .attr("d", path);

var quantile = d3.scaleQuantile()
 .domain([0, d3.max(csv, function(e) { return + e.densitehabkm2; })])
 .range(colors);

var legend = svg.append('g')
 .attr('transform', 'translate(725, 150)')
 .attr('id', 'legend');

  legend.selectAll()
   .data(d3.range(colors.length))
   .enter().append('svg:rect')
     .attr('height', '20px')
     .attr('width', '20px')
     .attr('x', 5)
     .attr('y', function(d) { return d * 20; })
     .style("fill", function(d) { return colors[d]; });

var legendScale = d3.scaleLinear()
 .domain([0, d3.max(csv, function(e) { return +e.densitehabkm2; })])
 .range([0, colors.length * 20]);

var legendAxis = svg.append("g")
 .attr('transform', 'translate(750, 150)')
 .call(d3.axisRight(legendScale).ticks(3));


csv.forEach(function(e,i) {
 d3.select(("#d" +  e.insee)) // Line where I think the problem is
                              // Here I'm trying to select Id's using the same code but reading it in the csv file. I have check and id's in geojson and csv do correspond
   .style("fill", function(d) { return quantile(+e.densitehabkm2); })
   .on("mouseover", function(d) {
    div.transition()        
      .duration(200)      
      .style("opacity", .9);
    div.html("IZI")
      .style("left", (d3.event.pageX + 30) + "px")     
      .style("top", (d3.event.pageY - 30) + "px");
  })
  .on("mouseout", function(d) {
    div.style("opacity", 0);
    div.html("")
      .style("left", "-500px")
      .style("top", "-500px");
    });
  });

 //console.log(csv.insee);

});



// Append a DIV for the tooltip
 var div = d3.select("body").append("div")   
  .attr("class", "tooltip")               
  .style("opacity", 0);

</script>

Thank you very much for your time.

Upvotes: 1

Views: 402

Answers (2)

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

SVG <path> elements have, indeed, a black fill by default. However, that's not a problem here: your style("fill", ...) should work, regardless.

The problem here is that you have several paths with the same ID. If you have a look at the original GeoJson, you'll see that you have several insee properties, for different years. So, your code is painting several black paths, one on top of the other. When you select by ID you select only one of them (by the way, it goes without saying that IDs should be unique in the document), and the other black paths avoid you seeing the painted path. Also, your solution simply makes all paths transparent, so when you paint any one of them it will be visible, but all other transparent paths are still there, over the path you selected.

All that being said, the simplest solution is filtering the original data, for instance:

geojson.features = geojson.features.filter(function(d) {
    return d.properties.year === 1962;
});

Basically, by selecting just one year, you avoid all those paths stacked one on top of the other (and also the browser will render the page way faster).

With that change alone, your style method will work. Here is the running demo:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">

<head>
  <meta charset="utf-8">

  <title>All in One</title>

  <script src="https://d3js.org/d3.v5.min.js"></script>

  <style type="text/css">
    #info {
      margin-top: 50px;
    }
    
    #deptinfo {
      margin-top: 30px;
    }
    
    .department {
      cursor: pointer;
      stroke: black;
      stroke-width: .5px;
    }
    
    .department:hover {
      stroke-width: 2px;
    }
    
    div.tooltip {
      position: absolute;
      opacity: 0.8;
      z-index: 1000;
      text-align: left;
      border-radius: 4px;
      -moz-border-radius: 4px;
      -webkit-border-radius: 4px;
      padding: 8px;
      color: #fff;
      background-color: #000;
      font: 12px sans-serif;
      max-width: 300px;
      height: 40px;
    }
    
    #svg {
      display: block;
      margin: auto;
    }
  </style>
</head>

<body>
  <div id="map"></div>
</body>

</html>

<script type="text/javascript">
  const width = 850,
    height = 800,
    colors = ['#d4eac7', '#c6e3b5', '#b7dda2', '#a9d68f', '#9bcf7d', '#8cc86a', '#7ec157', '#77be4e', '#70ba45', '#65a83e', '#599537', '#4e8230', '#437029', '#385d22', '#2d4a1c', '#223815'];

  const path = d3.geoPath();

  const projection = d3.geoMercator()
    .center([2.332978, 48.860117])
    .scale(40000)
    .translate([width / 2, height / 2]);

  path.projection(projection);

  const svg = d3.select('#map').append("svg")
    .attr("id", "svg")
    .attr("width", width)
    .attr("height", height)
    .attr("class", "Blues");

  // Append the group that will contain our paths
  const deps = svg.append("g");

  var promises = [];


  promises.push(d3.json('https://raw.githubusercontent.com/cerezamo/dataviz/master/Graphique_bokeh/pop_comgeo.geojson'))

  promises.push(d3.csv("https://raw.githubusercontent.com/cerezamo/dataviz/master/variables.csv"))

  Promise.all(promises).then(function(values) {
    const geojson = values[0];
    const csv = values[1];

    geojson.features = geojson.features.filter(function(d) {
      return d.properties.year === 1962;
    })

    var features = deps
      .selectAll("path")
      .data(geojson.features)
      .enter()
      .append("path")
      .attr('id', function(d) {
        return "d" + d.properties.insee;
      }) // Creation of the id as (ex :"d75005")
      // I add a d so the id is not a pure number as it could create error when selecting it
      .attr("d", path);

    var quantile = d3.scaleQuantile()
      .domain([0, d3.max(csv, function(e) {
        return +e.densitehabkm2;
      })])
      .range(colors);

    var legend = svg.append('g')
      .attr('transform', 'translate(725, 150)')
      .attr('id', 'legend');

    legend.selectAll()
      .data(d3.range(colors.length))
      .enter().append('svg:rect')
      .attr('height', '20px')
      .attr('width', '20px')
      .attr('x', 5)
      .attr('y', function(d) {
        return d * 20;
      })
      .style("fill", function(d) {
        return colors[d];
      });

    var legendScale = d3.scaleLinear()
      .domain([0, d3.max(csv, function(e) {
        return +e.densitehabkm2;
      })])
      .range([0, colors.length * 20]);

    var legendAxis = svg.append("g")
      .attr('transform', 'translate(750, 150)')
      .call(d3.axisRight(legendScale).ticks(3));


    csv.forEach(function(e, i) {
      d3.select(("path#d" + e.insee)) // Line where I think the problem is
        // Here I'm trying to select Id's using the same code but reading it in the csv file. I have check and id's in geojson and csv do correspond
        .style("fill", function() {
          return quantile(+e.densitehabkm2);
        })
        .on("mouseover", function(d) {
          console.log(d);
          div.transition()
            .duration(200)
            .style("opacity", .9);
          div.html("IZI")
            .style("left", (d3.event.pageX + 30) + "px")
            .style("top", (d3.event.pageY - 30) + "px");
        })
        .on("mouseout", function(d) {
          div.style("opacity", 0);
          div.html("")
            .style("left", "-500px")
            .style("top", "-500px");
        });
    });

    //console.log(csv.insee);

  });



  // Append a DIV for the tooltip
  var div = d3.select("body").append("div")
    .attr("class", "tooltip")
    .style("opacity", 0);
</script>

Upvotes: 2

LaGranf
LaGranf

Reputation: 63

Well the problem was in fact in the css.

Thanks to Ryan Morton for the answer.

Add .attr('fill', 'none') to when you first create the map objects. It's autofilling with black and somehow preventing your colors later: https://jsfiddle.net/bz3o5yah/

Upvotes: -1

Related Questions