Search code examples
csvd3.jsdata-bindinggeojson

d3.js maps: using a geojson file AND CSV data


I'm having trouble getting a map in d3.js to respond to data held in a csv (or tsv). I'm working with a geojson file and have been successful in showing the county boundaries in Illinois. The map, however, does not respond to data in the tsv file.

I suspect that the problem may be where I bind the data -- data() -- to the path. Mark Bostock uses .data(topojson.feature(us, us.objects.counties).features), but that's based on a function structure I don't fully understand: function ready([us]) I suspect that the line of my code that's problematic is .data(values[0].features, values[population])

Here is the entirety of my code, which produces a map of Illinois counties but nothing else. I've stripped down the data to just be fill colors for troubleshooting purposes.

My json file is available here: https://raw.githubusercontent.com/cyrusobrien/cyrusobrien/master/documents/illinois_ods.geojson and the data is here: https://raw.githubusercontent.com/cyrusobrien/cyrusobrien/master/html/countytest.tsv

Apologies for not posting on jfiddle, but (I think) I'm having domain conflict issues there.

<html lang="en">
<head>
    <meta charset="utf-8">
    <title>My map</title>
    <script type="text/javascript" src="https://d3js.org/d3.v5.min.js"></script>
    <style></style>
</head>
<body>
<div id="container" class="svg-container"></div>
<script type="text/javascript">
    var w = 900;
    var h = 500;
    var svg = d3.select("div#container").append("svg").attr("preserveAspectRatio", "xMinYMin meet").style("background-color","#c9e8fd")
        .attr("viewBox", "0 0 " + w + " " + h)
        .classed("svg-content", true);

    // load data

    var population = d3.map();


    var promises = [
        d3.json("/documents/illinois_ods.geojson"),
        d3.tsv("/html/countytest.tsv", function(d) { population.set(d.NAME, +d.color); })];



    var projection = d3.geoMercator().translate([w/2, h/2]).scale(2900).center([-89.3985,40.6331]);
    var path = d3.geoPath().projection(projection);

//draw map
    Promise.all(promises).then(function(values){
        svg.selectAll("path")
            .data(values[0].features, values[population])
            .enter()
            .append("path")
            .attr("d", path)
            .attr("fill",function (d) {d.color = population.get(d.NAME); })
    });

</script>
</body>
</html>

If it's relevant, I'm running this on my local machine using webstorm and jekyll.

Thank you for your help!


Solution

  • Loading data

    Promise.all() returns an array of fulfilled promises. To use them, you can access them directly from the values array.

    One convenient thing to do is array destructuring: this is what Bostock does with his function ready([us]). In this case, you could do something like:

    Promise.all(promises).then(function([illinois, data]){
        enter code here
    });
    

    Manipulating data

    In your code, it seems what you're trying to do is use data() for the two datasets at the same time, which doesn't work in D3. We can merge the two datasets with a loop that finds the associated "part" of Illinois by name in the GeoJSON, and appends the associated data.

    Afterwards, you only manipulate one data: the illinois data that has been merged.

    const parts = illinois.features
    parts.forEach(geoJsonObject => {
      const linkedData = data.find(d => d === geoJsonObject.name)
      geoJsonObject.properties.data = linkedData
    })
    // .. use illinois, access data with properties.data:
    svg.selectAll("path")
      .data(illinois.features)
      .enter()
      .append("path")
      .attr("d", path)
      .attr("fill", d => d.properties.data.color)
    

    Putting everything in the data property is a little heavy handed right now, but I assume you've got other data than color. If not, feel free to append color directly to the object.