Home > Software engineering >  Highlight country once selected with dropdown D3
Highlight country once selected with dropdown D3

Time:12-08

I have a map of Africa which I loaded using D3 which features several columns including a column named ADMIN which features each country's name. I'm trying to highlight the country on my map that is selected in my dropdown. For example if I select Algeria in my drop down, Algeria will highlight on my map. The map itself loads fine as well as the dropdown. I'm just having trouble coordinating the interaction between the dropdown function and the highlight function so that the selected country actually highlights.

//begin script when window loads
window.onload = setMap();

var attrArray = ["Algeria", "Angola", "Benin", "Botswana", "Burkina Faso", "Burundi", "Cabo Verde", "Cameroon", "Central African Republic", "Chad", "Comoros", "Congo", "Côte d'Ivoire", "Djibouti", "DR Congo", "Egypt", "Equatorial Guinea", "Eritrea", "Eswatini",
  "Ethiopia", "Gabon", "Gambia", "Ghana", "Guinea", "Guinea-Bissau", "Kenya", "Lesotho", "Liberia", "Libya", "Madagascar", "Malawi", "Mali", "Mauritania", "Mauritius", "Morocco", "Mozambique", "Namibia", "Niger", "Nigeria", "Rwanda", "Sao Tome & Principe", "Senegal",
  "Seychelles", "Sierra Leone", "Somalia", "South Africa", "South Sudan", "Sudan", "Tanzania", "Togo", "Tunisia", "Uganda", "Zambia", "Zimbabwe"
];

var expressed = attrArray[0];

//set up choropleth map
function setMap() {
  //map frame dimensions
  var width = 1200,
    height = 1000;

  //create new svg container for the map
  var map = d3.select("body")
    .append("svg")
    .attr("class", "map")
    .attr("width", width)
    .attr("height", height);

  //create Albers equal area conic projection centered on France
  var projection = d3.geoOrthographic()
    .scale(600)
    .translate([width / 2, height / 2.7])
    .clipAngle(85)
    .precision(.1);

  var path = d3.geoPath()
    .projection(projection)

  var graticule = d3.geoGraticule()
    .step([5, 5]); //place graticule lines every 5 degrees of longitude and latitude

  //create graticule background
  var gratBackground = map.append("path")
    .datum(graticule.outline()) //bind graticule background
    .attr("class", "gratBackground") //assign class for styling
    .attr("d", path) //project graticule

  //Example 2.6 line 5...create graticule lines
  var gratLines = map.selectAll(".gratLines")
    .data(graticule.lines()) //bind graticule lines to each element to be created
    .enter() //create an element for each datum
    .append("path") //append each element to the svg as a path element
    .attr("class", "gratLines") //assign class for styling
    .attr("d", path); //project graticule lines

  //use queue to parallelize asynchronous data loading
  d3.queue()
    .defer(d3.csv, "https://raw.githubusercontent.com/Sharitau/website/main/Portfolio/GovernanceFinal/data/AfricaCountries.csv") //load attributes from csv
    .defer(d3.json, "https://raw.githubusercontent.com/Sharitau/website/main/Portfolio/GovernanceFinal/data/world.topojson") //load background spatial data
    .defer(d3.json, "https://raw.githubusercontent.com/Sharitau/website/main/Portfolio/GovernanceFinal/data/Africa.TOPOJSON")
    .await(callback);

  function callback(error, csvData, world, africa) {
    //translate europe TopoJSON
    var basemapCountries = topojson.feature(world, world.objects.ne_10m_admin_0_countries),
      choroplethCountries = topojson.feature(africa, africa.objects.ne_10m_admin_0_countries).features;

    //select graticule elements that will be created
    //add Europe countries to map
    var countries = map.append("path")
      .datum(basemapCountries)
      .attr("class", "countries")
      .attr("d", path);

    //add France regions to map
    var regions = map.selectAll(".regions")
      .data(choroplethCountries)
      .enter()
      .append("path")
      .attr("class", function(d) {
        return "regions "   d.properties.ADMIN;
      })
      .attr("d", path)

    console.log(countries);
    console.log(regions)

    createDropdown(csvData);
  };

  function createDropdown(csvData) {
    //add select element
    var dropdown = d3.select("body")
      .append("select")
      .attr("class", "dropdown")
      .attr("d", path)
      .on("change", function() {
        highlight(d.properties);
      });

    //add initial option
    var titleOption = dropdown.append("option")
      .attr("class", "titleOption")
      .attr("disabled", "true")
      .text("Select Country");

    //add attribute name options
    var attrOptions = dropdown.selectAll("attrOptions")
      .data(attrArray)
      .enter()
      .append("option")
      .attr("value", function(d) {
        return d
      })
      .text(function(d) {
        return d
      });
  };

  //function to highlight enumeration units and bars
  function highlight(props) {
    //change stroke
    var selected = d3.selectAll("."   props.ADMIN)
      .style("stroke", "blue")
      .style("stroke-width", "2")
    console.log(props.ADMIN);
  };
};
.countries {
  fill: #0B1E38;
  stroke: #fff;
  stroke-width: 2px;
}

.map {
  position: absolute;
  right: 0;
}

.regions {
  fill: #316331;
}

.gratLines {
  fill: none;
  stroke: #999;
  stroke-width: 1px;
}

.gratBackground {
  fill: #0B1E38;
}

.dropdown {
  position: absolute;
  top: 30px;
  left: 500px;
  z-index: 10;
  font-family: sans-serif;
  font-size: 1em;
  font-weight: bold;
  padding: 2px;
  border: 1px solid #999;
  box-shadow: 2px 2px 4px #999;
}

option {
  font-weight: normal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
<script src="https://d3js.org/topojson.v3.min.js"></script>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

To answer your question using this answer, you need to get the value of the selected option using the attribute value on the select element - which is available as this in this function.

var attrArray = ["Algeria", "Angola", "Benin", "Botswana", "Burkina Faso", "Burundi", "Cabo Verde", "Cameroon", "Central African Republic", "Chad", "Comoros", "Congo", "Côte d'Ivoire", "Djibouti", "DR Congo", "Egypt", "Equatorial Guinea", "Eritrea", "Eswatini",
  "Ethiopia", "Gabon", "Gambia", "Ghana", "Guinea", "Guinea-Bissau", "Kenya", "Lesotho", "Liberia", "Libya", "Madagascar", "Malawi", "Mali", "Mauritania", "Mauritius", "Morocco", "Mozambique", "Namibia", "Niger", "Nigeria", "Rwanda", "Sao Tome & Principe", "Senegal",
  "Seychelles", "Sierra Leone", "Somalia", "South Africa", "South Sudan", "Sudan", "Tanzania", "Togo", "Tunisia", "Uganda", "Zambia", "Zimbabwe"
];

//map frame dimensions
var width = 1200,
  height = 1000;

//create new svg container for the map
var map = d3.select("body")
  .append("svg")
  .attr("class", "map")
  .attr("width", width)
  .attr("height", height);

//create Albers equal area conic projection centered on France
var projection = d3.geoOrthographic()
  .scale(600)
  .translate([width / 2, height / 2.7])
  .clipAngle(85)
  .precision(.1);

var path = d3.geoPath()
  .projection(projection)

var graticule = d3.geoGraticule()
  .step([5, 5]); //place graticule lines every 5 degrees of longitude and latitude

//create graticule background
var gratBackground = map.append("path")
  .datum(graticule.outline()) //bind graticule background
  .attr("class", "gratBackground") //assign class for styling
  .attr("d", path) //project graticule

//Example 2.6 line 5...create graticule lines
var gratLines = map.selectAll(".gratLines")
  .data(graticule.lines()) //bind graticule lines to each element to be created
  .enter() //create an element for each datum
  .append("path") //append each element to the svg as a path element
  .attr("class", "gratLines") //assign class for styling
  .attr("d", path); //project graticule lines

//use queue to parallelize asynchronous data loading
d3.queue()
  .defer(d3.json, "https://raw.githubusercontent.com/Sharitau/website/main/Portfolio/GovernanceFinal/data/world.topojson") //load background spatial data
  .defer(d3.json, "https://raw.githubusercontent.com/Sharitau/website/main/Portfolio/GovernanceFinal/data/Africa.TOPOJSON")
  .await(callback);

function callback(error, world, africa) {
  //translate europe TopoJSON
  var basemapCountries = topojson.feature(world, world.objects.ne_10m_admin_0_countries),
    choroplethCountries = topojson.feature(africa, africa.objects.ne_10m_admin_0_countries).features;

  //select graticule elements that will be created
  //add Europe countries to map
  var countries = map.append("path")
    .datum(basemapCountries)
    .attr("class", "countries")
    .attr("d", path);

  //add France regions to map
  var regions = map.selectAll(".regions")
    .data(choroplethCountries)
    .enter()
    .append("path")
    .attr("class", function(d) {
      return "regions "   d.properties.ADMIN;
    })
    .attr("d", path);

  createDropdown();
};

function createDropdown() {
  //add select element
  var dropdown = d3.select("body")
    .append("select")
    .attr("class", "dropdown")
    .on("change", function() {
      // The value is the name of the country
      var countryName = this.value;
      highlight(countryName);
    });

  //add initial option
  var titleOption = dropdown.append("option")
    .attr("class", "titleOption")
    .attr("disabled", "true")
    .text("Select Country");

  //add attribute name options
  var attrOptions = dropdown.selectAll("attrOptions")
    .data(attrArray)
    .enter()
    .append("option")
    .attr("value", function(d) {
      return d;
    })
    .text(function(d) {
      return d;
    });
};

//function to highlight enumeration units and bars
function highlight(countryName) {
  //change stroke
  var selected = d3.selectAll("."   countryName)
    .style("stroke", "blue")
    .style("stroke-width", "2")
  console.log(countryName);
};
.countries {
  fill: #0B1E38;
  stroke: #fff;
  stroke-width: 2px;
}

.map {
  position: absolute;
  right: 0;
}

.regions {
  fill: #316331;
}

.gratLines {
  fill: none;
  stroke: #999;
  stroke-width: 1px;
}

.gratBackground {
  fill: #0B1E38;
}

.dropdown {
  position: absolute;
  top: 30px;
  left: 500px;
  z-index: 10;
  font-family: sans-serif;
  font-size: 1em;
  font-weight: bold;
  padding: 2px;
  border: 1px solid #999;
  box-shadow: 2px 2px 4px #999;
}

option {
  font-weight: normal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
<script src="https://d3js.org/topojson.v3.min.js"></script>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Note that class is not the right place to store country names, even though it is an easy selector. Country names of multiple words are not registered correctly, like the selector for "Sierra Leone" is ".Sierra.Leone" and not ".Sierra Leone". Besides, try selecting "Congo"; every country that has the word "Congo" in it gets highlighted. You'd make your life much easier by creating a custom attribute "data-country" and selecting the correct country using d3.select("[data-country='" countryName "']"). See also the docs on attribute selectors.

In addition, the names in attrArray and the names in the countries map did not line up, so some highlighting did not work at all. I recommend just taking the names from the TopoJSON or creating a mapping from CSV name to TopoJSON name.

//map frame dimensions
var width = 1200,
  height = 1000;

//create new svg container for the map
var map = d3.select("body")
  .append("svg")
  .attr("class", "map")
  .attr("width", width)
  .attr("height", height);

//create Albers equal area conic projection centered on France
var projection = d3.geoOrthographic()
  .scale(600)
  .translate([width / 2, height / 2.7])
  .clipAngle(85)
  .precision(.1);

var path = d3.geoPath()
  .projection(projection)

var graticule = d3.geoGraticule()
  .step([5, 5]); //place graticule lines every 5 degrees of longitude and latitude

//create graticule background
var gratBackground = map.append("path")
  .datum(graticule.outline()) //bind graticule background
  .attr("class", "gratBackground") //assign class for styling
  .attr("d", path) //project graticule

//Example 2.6 line 5...create graticule lines
var gratLines = map.selectAll(".gratLines")
  .data(graticule.lines()) //bind graticule lines to each element to be created
  .enter() //create an element for each datum
  .append("path") //append each element to the svg as a path element
  .attr("class", "gratLines") //assign class for styling
  .attr("d", path); //project graticule lines

//use queue to parallelize asynchronous data loading
d3.queue()
  .defer(d3.json, "https://raw.githubusercontent.com/Sharitau/website/main/Portfolio/GovernanceFinal/data/world.topojson") //load background spatial data
  .defer(d3.json, "https://raw.githubusercontent.com/Sharitau/website/main/Portfolio/GovernanceFinal/data/Africa.TOPOJSON")
  .await(callback);

function callback(error, world, africa) {
  //translate europe TopoJSON
  var basemapCountries = topojson.feature(world, world.objects.ne_10m_admin_0_countries),
    choroplethCountries = topojson.feature(africa, africa.objects.ne_10m_admin_0_countries).features;

  //select graticule elements that will be created
  //add Europe countries to map
  var countries = map.append("path")
    .datum(basemapCountries)
    .attr("class", "countries")
    .attr("d", path);

  //add France regions to map
  var regions = map.selectAll(".regions")
    .data(choroplethCountries)
    .enter()
    .append("path")
    .attr("class", "regions")
    .attr("data-country", function(d) {
      return d.properties.ADMIN;
    })
    .attr("d", path);

  var countryNames = choroplethCountries.map(function(d) {
    return d.properties.ADMIN;
  }).sort();
  createDropdown(countryNames);
};

function createDropdown(countryNames) {
  //add select element
  var dropdown = d3.select("body")
    .append("select")
    .attr("class", "dropdown")
    .on("change", function() {
      // The value is the name of the country
      var countryName = this.value;
      highlight(countryName);
    });

  //add initial option
  var titleOption = dropdown.append("option")
    .attr("class", "titleOption")
    .attr("disabled", "true")
    .text("Select Country");

  //add attribute name options
  var attrOptions = dropdown.selectAll("attrOptions")
    .data(countryNames)
    .enter()
    .append("option")
    .attr("value", function(d) {
      return d;
    })
    .text(function(d) {
      return d;
    });
};

//function to highlight enumeration units and bars
function highlight(countryName) {
  //change stroke
  var selected = d3.selectAll("[data-country='"   countryName   "']")
    .style("stroke", "blue")
    .style("stroke-width", "2")
  console.log(countryName);
};
.countries {
  fill: #0B1E38;
  stroke: #fff;
  stroke-width: 2px;
}

.map {
  position: absolute;
  right: 0;
}

.regions {
  fill: #316331;
}

.gratLines {
  fill: none;
  stroke: #999;
  stroke-width: 1px;
}

.gratBackground {
  fill: #0B1E38;
}

.dropdown {
  position: absolute;
  top: 30px;
  left: 500px;
  z-index: 10;
  font-family: sans-serif;
  font-size: 1em;
  font-weight: bold;
  padding: 2px;
  border: 1px solid #999;
  box-shadow: 2px 2px 4px #999;
}

option {
  font-weight: normal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
<script src="https://d3js.org/topojson.v3.min.js"></script>
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Finally, there were some unnecessary complications in your code.

  • There was no need to use the c3 library;
  • You gave the select element .attr('d', path), which doesn't do anything. It only works for the path element in the SVG ecosystem.

CodePudding user response:

It look to me more a JS related issue than a d3 related issue:

Here you have a d variable that appear seemingly out of nowhere

function createDropdown(csvData) {
    //add select element
    var dropdown = d3.select("body")
      .append("select")
      .attr("class", "dropdown")
      .attr("d", path) // I don't think a select can use this
      .on("change", function() {
        highlight(d.properties); // where does this d come from a actually ? It is not provided by the function itself so what it is ?
      });
  // rest of code
}

And here :

    function highlight(props) {
        //change stroke
        var selected = d3.selectAll("."   props.ADMIN) // <= here
          .style("stroke", "blue")
          .style("stroke-width", "2")
        console.log(props.ADMIN);
      };

you select all elements with the class that match props.ADMIN but your code never actually use this class on any elements. The paths created from choroplethCountries data use the "regions " d.properties.ADMIN class, so if you want to highlight those, you should use the ".regions " d.properties.ADMIN selector.

Did you tried to run your code in debug mode to check how it behave exactly? Also, given the fact that your a beginner in JS, I would advise you to consider the possibility of not using d3. This is a very general purpose library, which give it a great flexibility but it also mean that you have to learn a lot of things before using it efficiently. If you are free to choose the tech, maybe it would be worthwhile to invest a bit of your time in investigating alternatives solution more specialized in the type of visualization you are going for.

  • Related