🏠 back to Observable

linking data to map elements (toward a choropleth)

I’ve been banging my head over this for awhile now and would love some help.

I’ve managed to isolate a cluster of countries from a .json file derived from Natural Earth data. I’ve also distilled down some data about these countries. Now I’d like to link these two elements to create a choropleth map.

Looking around at the many examples, it seems there are numerous approaches that will work. Currently, i’m trying to define a color function using scaleQuantize, as follows:

color = d3.scaleQuantize()
    .domain([0, d3.max( Object.values(sard_regional_distribution_data), d=> d.value)])

… seems to work (no errors), but when I try to tie this back into my map with the following 'fill` function, nothing happens:

      .style("fill", function(d) {
            return color(sard_regional_distribution_data[d.name]);

Am I correct in reading that the color function simply establishes a 4 groupings of blue color according to the values present in my data, and that the fill function should match those values against the names of each country? If so, any pointers on what I am missing to actually get the choropleth to render?

Here’s my current progress:

Thanks in advance for any help!!

No: I was not correct about ‘quantize’ imposing 4 groupings of data within a range. I was confusing it with ‘quartile’.

According to d3-scale, a quantize scale may aid differentiation by rounding continuous data to a fixed set of discrete values.

… So, while the color function I was working with can help me define a fixed set of discrete values, that set is not necessarily limited to 4.

This doesn’t take me too far yet - as I am not too concerned about how many grouping there are, but instead about linking the data to the countries… but it’s always good to understand what one is doing :wink:

… Trouble is I still don’t understand :cry:

I’ll keep digging, but I would be overjoyed for some additional guidance :pray:

By the way - I’ve watched a lot of YouTube videos and read several tutorials on this concept, and most of these assume some sort of familiarity with how JS stitches bits of data together. I suppose I need to find an in-person teacher who can explain this all to me, as so far I haven’t found a clear resource breaking down the basic mechanics of JavaScrpit. I am still learning by trial and error and coming at everything sideways. With enough time, learning this way is possible… but oh so painful some days! :confounded:

I’ve just sent you a suggestion using a JavaScript Map to index the values.

Note that in theory these values should not be used on a choropleth since they are absolute numbers (a choropleth should represent values “relative to surface”, or “relative to population”).

1 Like

Thank you, @Fil. I am eager to check it out! (Currently limited in so doing)

Regarding your comment, however, I don’t know that I follow and welcome further clarification. I thought choropleths were for visualizing relative quantities, or in my case, in this country there are so many active projects, in that country so many, etc. In another notebook, I did this with a simple donut chart (which was good), but I like the idea of learning how to share in countries relative to one another. Yet in all cases I can only imagine doing this with absolute values: number if active projects, number of projects affecting indigenous people, etc.

If you have a moment, can you please elaborate a bit in why such values aren’t good for such a map?

In a way it’s because the color is “multiplied” by the surface area of the plot. If instead of representing the area by a polygon, you think of it as a collection of dots, then the “intensity” that gives the color happens as many times as there are dots, ie proportionally to its surface.

The way I like to think about this, is to ask if the colors would change if I divided the polygons into subpolygons (random halves, or regions…).

If all you know (in the data) is, for example, the median height (or GDP per capita) of people in the country, then you can suppose it is about the same in the Western and in the Eastern parts of the country: subdivide the country, the numbers won’t change much, and the colors won’t change much either: the map is “stable” with respect to this operation. :white_check_mark:

On the other hand, for an absolute number (# of projects, total GPD…), if you take two halves of the country, then you have to divide that total value to distribute it to each half of the country. The map’s colors are going to be very different. The map is not stable wrt subdivision. :x:


Thanks for the clear explanation! I managed to catch a glance at your suggestion. I’ll re read it in my morning. Still a bit confused by some aspects of the linkages, but hope to better understand with time and headspace for more concentration. I really appreciate your time and generous guidance!!

Thanks for this explanation! I have never heard the issue described in this way, and it makes more sense than all the other explanations I have heard.

1 Like

Thanks again for all the help, @fil!

Just to make sure I understand:

         style("fill", function(d) {
-        return color(sard_regional_distribution_data[d.name]);
+        return color(sard_regional_distribution_data_index.get(d.properties.ADMIN));

Here (above) you removed my attempt to run the color function on d.name. In my attempt, it seems, I hadn’t actually properly mapped the data to d, so the color function wasn’t really running on anything (hence the lack of effect). In your correction, you run the color function on the index value you created (below), which maps both d.name and d.value back to d.

Where I am a bit confused still is the part where you do this: .get(d.properties.ADMIN). I had seen this pattern in several other Observable choropleth maps, but my initial attempts to use it weren’t returning an error due to my version of sard_regional_distribution_data not being a function (and yet your index mapping is also not a function?). Further - I see that ADMIN is from my .JSON file and correlates to country name, so what appears to be happening is that you’re matching the data from the index to the country shape. To confirm: your choice to use ADMIN was arbitrary and selected for this apparent alignment? That is, I had used SUBUNIT to draw the map, and the code also works with SUBUNIT rather than ADMIN, so all that matters here is that the data line up?

+        sard_regional_distribution_data_index = new Map(sard_regional_distribution_data.map(d => [d.name, d.value]))

And as for this line above - why not just use sard_regional_distribution_data? That is, I created this array specifically to bring together the data from the filters I had established above. Your index is not an array, but a map - does this matter? I’ve actually had trouble before trying to get maps like this to play well with other visualizations (if I recall, I was trying to use a mapping to supply data to a donut chart, and it failed where an array worked).

And finally: a question on my color function:

My attempt was this:

color = d3.scaleQuantize()
    .domain([0, d3.max( Object.values(sard_regional_distribution_data), d=> d.value)])

I was trying set the domain max from the actual values reflected in sard_regional_distribution_data. The result in your working model is this:

… but there are nearly twice as many projects in India as Bangladesh, which is not at all apparent!

If I manually set the max extent of the domain, like this:

color = d3.scaleQuantize()
    .domain([0, 130])

I get something closer to my expectations:

… Which leads me to believe that my attempt to programatically pull the max extent is incorrect. Any insights?

I really appreciate your help and guidance on all of this! :love_you_gesture: Thank you so much!

I haven’t looked at the color scale or the data at all I must say, I was just trying to answer your question (“linking data to map elements”).

On this first topic, there were two issues:

  1. your initial code was trying to read the value for India by checking the dataset (sard_regional_distribution_data) as if it were indexed by countries names (sard_regional_distribution_data[d.name]). It wasn’t, and that’s why I introduced a data structure that was: a JavaScript Map. You can inspect it in its own cell to see that it’s an “key/value” object; it has a specific method map.get(index) that retrieves the value from the key.

  2. second, d.name was empty, because d, in this context, is an element of the original array ne_10m_admin_0_sovereignty_south_asia that you passed to selectAll(…).data(). However this element has a .properties which itself contains many properties — it looked like ADMIN corresponded to the values in sard_regional_distribution_data, so I chose that, but it was a bit of hasty choice.

Re: the color scale, you probably don’t need something as complex as a quantile scale for such a low number of values. Besides, I think you should consider something like this instead of a choropleth:

1 Like

Thanks @fil! Helpful as always.

I saw this the other day, and it also looks like a great alternative:

… Might take me a year to figure out how to learn it though :joy: