I have created a World Bubble Map where the bubbles are formed according to geolocation of the countries and bubble radius (size) should change according to selected parameters from radio button.
For instance, if population is selected, the bubble size should be formed according to population size of every nation and by selecting the next button it should change as per selected parameter.
So far I have managed to form a Bubble Map which reads the data from JSON file to plot the first bubbles, but I am stuck on how to make it read my other parameters from my CSV file, as all the necessary data that needs to be visualized is within a separate CSV file to the JSON file that the geolocation data comes from.
Is there a way, that I can link my CSV file to create the bubbles as per the countries geolocation and parameters from CSV file. All my coding has been done in this
Your current tooltip content is not displaying the value for the selected property (in yellow), it is however displaying the label of the selected option (blue underline). lets dig a bit closer, this is the current content logic:
<div>
<h3>${hover.NAME}</h3>
<div style="margin: 10px">
<h4>CountryCode : ${hover.ISO_A2} </h4>
<h4> ContinentCode : ${hover.CONTINENT} </h>
<h4>${parameter } : ${d3.format(",")(hover[p])}</h4>
</div>
</div>
in this case hover
is an object that has properties ISO_A2
and CONTINENT
which are not properties from your
It is clear that the original intent was to have the data available as part of the data feed available at that point, it is also clear that nothing has been done to merge these two datasets, so lets do that now.
My preference in this case is to use forEach
to iterate over countries
and modify each entry to include the entire matching record from the csv file. You could use map
but in this case we are already loading heaps of data into memory, we don't need to retain the unchanged version of the countries JSON as well as the new output that map
would have produced.
The other reason that I recommend injecting the data from the CSV into the array of countries is that we can avoid the overhead of a lookup when we need to access the data. Arranging the csv data and the country data into arrays of the same length and sorted so that the same indexes correlate to the same country in each file incurs the same over head as injecting the data and makes it more deterministic. It is also easier to support scenarios where one array has more values that the other.
All we need to know is the property mapping to identity the unique record in each file. We can see that the csv file is using 2 letter country codes:
country_id | CountryName | CountryCode | ContinentCode | CenterLongitude | CenterLatitude | areasqkm | population | airports | gdpgrowthrate | inflationrate | unemploymentrate | popnbelowpoverty | medianage |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
12 | Australia | AU | OC | 135.0 | -25.0 | 7741220 | 26141369 | 418 | 1.84 | 1.60 | 5.16 | 0.00 | 37.50 |
13 | Austria | AT | EU | 13.33 | 47.33 | 83871 | 8913088 | 50 | 1.42 | 1.50 | 7.35 | 13.30 | 44.50 |
114 | India | IN | AS | 77.0 | 20.0 | 3287263 | 1389637446 | 346 | 4.86 | 3.70 | 8.50 | 21.90 | 28.70 |
115 | Indonesia | ID | AS | 120.0 | -5.0 | 1904569 | 277329163 | 673 | 5.03 | 2.80 | 5.31 | 9.40 | 31.10 |
I have picked two pairs Australia, Austria and India, Indonesia for comparison as they cover common matching issues, Most countries have a 2 letter postal code, and it is common for people to pick this first, however India's postal code is 3 letters, Indonesia's is 4 and Austria have 1. I find this dataset particularly interesting for how close the name terms are lexicographically yet how different their unique codes are, so I always check with these specific cases first before running a test or proof across an entire dataset.
The feature property metadata in the json file has a lot of information, importantly, it also has the ISO_A2
value, which is the ISO standard 2 letter country code.
featurecla | scalerank | LABELRANK | SOVEREIGNT | SOV_A3 | ADM0_DIF | LEVEL | TYPE | TLC | ADMIN | ADM0_A3 | GEOU_DIF | GEOUNIT | GU_A3 | SU_DIF | SUBUNIT | SU_A3 | BRK_DIFF | NAME | NAME_LONG | BRK_A3 | BRK_NAME | BRK_GROUP | ABBREV | POSTAL | FORMAL_EN | FORMAL_FR | NAME_CIAWF | NOTE_ADM0 | NOTE_BRK | NAME_SORT | NAME_ALT | MAPCOLOR7 | MAPCOLOR8 | MAPCOLOR9 | MAPCOLOR13 | POP_EST | POP_RANK | POP_YEAR | GDP_MD | GDP_YEAR | ECONOMY | INCOME_GRP | FIPS_10 | ISO_A2 | ISO_A2_EH | ISO_A3 | ISO_A3_EH | ISO_N3 | ISO_N3_EH | UN_A3 | WB_A2 | WB_A3 | WOE_ID | WOE_ID_EH | WOE_NOTE | ADM0_ISO | ADM0_DIFF | ADM0_TLC | ADM0_A3_US | ADM0_A3_FR | ADM0_A3_RU | ADM0_A3_ES | ADM0_A3_CN | ADM0_A3_TW | ADM0_A3_IN | ADM0_A3_NP | ADM0_A3_PK | ADM0_A3_DE | ADM0_A3_GB | ADM0_A3_BR | ADM0_A3_IL | ADM0_A3_PS | ADM0_A3_SA | ADM0_A3_EG | ADM0_A3_MA | ADM0_A3_PT | ADM0_A3_AR | ADM0_A3_JP | ADM0_A3_KO | ADM0_A3_VN | ADM0_A3_TR | ADM0_A3_ID | ADM0_A3_PL | ADM0_A3_GR | ADM0_A3_IT | ADM0_A3_NL | ADM0_A3_SE | ADM0_A3_BD | ADM0_A3_UA | ADM0_A3_UN | ADM0_A3_WB | CONTINENT | REGION_UN | SUBREGION | REGION_WB | NAME_LEN | LONG_LEN | ABBREV_LEN | TINY | HOMEPART | MIN_ZOOM | MIN_LABEL | MAX_LABEL | LABEL_X | LABEL_Y | NE_ID | WIKIDATAID | NAME_AR | NAME_BN | NAME_DE | NAME_EN | NAME_ES | NAME_FA | NAME_FR | NAME_EL | NAME_HE | NAME_HI | NAME_HU | NAME_ID | NAME_IT | NAME_JA | NAME_KO | NAME_NL | NAME_PL | NAME_PT | NAME_RU | NAME_SV | NAME_TR | NAME_UK | NAME_UR | NAME_VI | NAME_ZH | NAME_ZHT | FCLASS_ISO | TLC_DIFF | FCLASS_TLC | FCLASS_US | FCLASS_FR | FCLASS_RU | FCLASS_ES | FCLASS_CN | FCLASS_TW | FCLASS_IN | FCLASS_NP | FCLASS_PK | FCLASS_DE | FCLASS_GB | FCLASS_BR | FCLASS_IL | FCLASS_PS | FCLASS_SA | FCLASS_EG | FCLASS_MA | FCLASS_PT | FCLASS_AR | FCLASS_JP | FCLASS_KO | FCLASS_VN | FCLASS_TR | FCLASS_ID | FCLASS_PL | FCLASS_GR | FCLASS_IT | FCLASS_NL | FCLASS_SE | FCLASS_BD | FCLASS_UA |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Admin-0 country | 1 | 2 | Australia | AU1 | 1 | 2 | Country | 1 | Australia | AUS | 0 | Australia | AUS | 0 | Australia | AUS | 0 | Australia | Australia | AUS | Australia | Auz. | AU | Commonwealth of Australia | Australia | Australia | 1 | 2 | 2 | 7 | 25364307 | 15 | 2019 | 1396567 | 2019 | 2. Developed region: nonG7 | 1. High income: OECD | AS | AU | AU | AUS | AUS | 36 | 36 | 36 | AU | AUS | -90 | 23424748 | Includes Ashmore and Cartier Islands (23424749) and Coral Sea Islands (23424790). | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | AUS | -99 | -99 | Oceania | Oceania | Australia and New Zealand | East Asia & Pacific | 9 | 9 | 4 | -99 | 1 | 0 | 1.7 | 5.7 | 134.04972 | -24.129522 | 1159320355 | Q408 | أستراليا | অস্ট্রেলিয়া | Australien | Australia | Australia | استرالیا | Australie | Αυστραλία | אוסטרליה | ऑस्ट्रेलिया | Ausztrália | Australia | Australia | オーストラリア | 오스트레일리아 | Australië | Australia | Austrália | Австралия | Australien | Avustralya | Австралія | آسٹریلیا | Úc | 澳大利亚 | 澳大利亞 | Admin-0 country | Admin-0 country | ||||||||||||||||||||||||||||||||||||||
Admin-0 country | 1 | 4 | Austria | AUT | 0 | 2 | Sovereign country | 1 | Austria | AUT | 0 | Austria | AUT | 0 | Austria | AUT | 0 | Austria | Austria | AUT | Austria | Aust. | A | Republic of Austria | Austria | Austria | 3 | 1 | 3 | 4 | 8877067 | 13 | 2019 | 445075 | 2019 | 2. Developed region: nonG7 | 1. High income: OECD | AU | AT | AT | AUT | AUT | 40 | 40 | 40 | AT | AUT | 23424750 | 23424750 | Exact WOE match as country | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | AUT | -99 | -99 | Europe | Europe | Western Europe | Europe & Central Asia | 7 | 7 | 5 | -99 | 1 | 0 | 3 | 8 | 14.130515 | 47.518859 | 1159320379 | Q40 | النمسا | অস্ট্রিয়া | Österreich | Austria | Austria | اتریش | Autriche | Αυστρία | אוסטריה | ऑस्ट्रिया | Ausztria | Austria | Austria | オーストリア | 오스트리아 | Oostenrijk | Austria | Áustria | Австрия | Österrike | Avusturya | Австрія | آسٹریا | Áo | 奥地利 | 奧地利 | Admin-0 country | Admin-0 country | ||||||||||||||||||||||||||||||||||||||
Admin-0 country | 1 | 2 | India | IND | 0 | 2 | Sovereign country | 1 | India | IND | 0 | India | IND | 0 | India | IND | 0 | India | India | IND | India | India | IND | Republic of India | India | India | 1 | 3 | 2 | 2 | 1366417754 | 18 | 2019 | 2868929 | 2019 | 3. Emerging region: BRIC | 4. Lower middle income | IN | IN | IN | IND | IND | 356 | 356 | 356 | IN | IND | 23424848 | 23424848 | Exact WOE match as country | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | IND | -99 | -99 | Asia | Asia | Southern Asia | South Asia | 5 | 5 | 5 | -99 | 1 | 0 | 1.7 | 6.7 | 79.358105 | 22.686852 | 1159320847 | Q668 | الهند | ভারত | Indien | India | India | هند | Inde | Ινδία | הודו | भारत | India | India | India | インド | 인도 | India | Indie | Índia | Индия | Indien | Hindistan | Індія | بھارت | Ấn Độ | 印度 | 印度 | Admin-0 country | Admin-0 country | ||||||||||||||||||||||||||||||||||||||
Admin-0 country | 3 | 2 | Indonesia | IDN | 0 | 2 | Sovereign country | 1 | Indonesia | IDN | 0 | Indonesia | IDN | 0 | Indonesia | IDN | 0 | Indonesia | Indonesia | IDN | Indonesia | Indo. | INDO | Republic of Indonesia | Indonesia | Indonesia | 6 | 6 | 6 | 11 | 270625568 | 17 | 2019 | 1119190 | 2019 | 4. Emerging region: MIKT | 4. Lower middle income | ID | ID | ID | IDN | IDN | 360 | 360 | 360 | ID | IDN | 23424846 | 23424846 | Exact WOE match as country | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | IDN | -99 | -99 | Asia | Asia | South-Eastern Asia | East Asia & Pacific | 9 | 9 | 5 | -99 | 1 | 0 | 1.7 | 6.7 | 101.892949 | -0.954404 | 1159320845 | Q252 | إندونيسيا | ইন্দোনেশিয়া | Indonesien | Indonesia | Indonesia | اندونزی | Indonésie | Ινδονησία | אינדונזיה | इंडोनेशिया | Indonézia | Indonesia | Indonesia | インドネシア | 인도네시아 | Indonesië | Indonezja | Indonésia | Индонезия | Indonesien | Endonezya | Індонезія | انڈونیشیا | Indonesia | 印度尼西亚 | 印度尼西亞 | Admin-0 country | Admin-0 country |
there are lots of different ways to do this, I'm just going to find
the matching record and stuff it into a new property called csv
using a verbose method to make it easy to follow:
// Convert the TopoJSON to GeoJSON
countries = {
const geo = topojson.feature(map_layer, map_layer.objects.countries);
geo.features.forEach(feature => {
if(feature.geometry) {
feature.centroid = centroid(feature);
}
let code = feature.properties.ISO_A2;
let cData = country_data.find(x => x.CountryCode === code);
feature.properties.csv = cData ?? {
areasqkm: "",
population: "",
airports: "",
gdpgrowthrate: "",
inflationrate: "",
unemploymentrate: "",
popnbelowpoverty: "",
medianage: ""
};
return feature;
});
return geo;
}
Now the data is available, including a null record to simplify error handling later. Lets add this to the tooltip content first, to verify that it works, i'll dump all the extended data just so we can explore the different values:
<div>
<h3>${hover.NAME}</h3>
<div style="margin: 10px">
<h4>CountryCode : ${hover.ISO_A2} </h4>
<h4> ContinentCode : ${hover.CONTINENT} </h>
<h4>${parameter } : ${d3.format(",")(hover.csv[p])}</h4>
<hr style="padding:0px"/>
<smaller>
Area Sqkm: ${d3.format(",")(hover.csv['areasqkm'])}<br/>
Population: ${d3.format(",")(hover.csv['population'])}<br/>
Airports: ${hover.csv['airports']}<br/>
GDP Growth Rate: ${d3.format(",")(hover.csv['gdpgrowthrate'])}<br/>
Inflation Rate: ${d3.format(",")(hover.csv['inflationrate'])}<br/>
Unemployment Rate: ${d3.format(",")(hover.csv['unemploymentrate'])}<br/>
Pop. Below Poverty: ${d3.format(",")(hover.csv['popnbelowpoverty'])}<br/>
Median Age: ${hover.csv['medianage']}<br/>
<smaller>
</div>
</div>
Now you just need to apply those property mapping paths to your bubble logic, I'm actually going to leave that logic out here, the main issue was how to make the data available, there are practicality issues to using the direct value to render proportional shapes. Very quickly you end up with some items so small you can't click or see them, other shapes will have such large discrepancies that functions like forceCollide()
will just make a mess... have a look at zimbabwe's inflation rate in this bubble map:
It is often more practical to map values to specific size domains, a scale of 1-5 is usually enough but you can go to 10, where 1 is the smallest on the map, and 10 the largest, then trim the data range into standard deviations so that outliers below all get 1 and outliers above all get the same max.
The techniques and reasoning are well outside of this discussion space.