简体   繁体   中英

Highlight country once selected with dropdown D3

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>

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>

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>

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.

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

Here you have ad 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM