Skip to content

Instantly share code, notes, and snippets.

@robatwilliams
Last active December 27, 2018 12:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robatwilliams/1e2866c783618207545c8e9bea652d66 to your computer and use it in GitHub Desktop.
Save robatwilliams/1e2866c783618207545c8e9bea652d66 to your computer and use it in GitHub Desktop.
Currencies, then and now (2)

Companion gist for blog post: Mapping currency strength changes with D3

Displays currency strengthening or weakening against other world currencies, between a reference date in the past and today.

Try it out - click to change the base country, use the picker to change the reference date. Data is available from the start of 2000.

Uses ES6 features, so you need a modern browser.

Improvements over v1

  • Optionally use currencylayer for data, which has more countries (see "Data sources" below)
  • Improved colour scale

Data sources

World map: https://github.com/topojson/world-atlas

Country codes (ISO 3611-1): https://github.com/srcagency/iso-3166-1-codes

Country (ISO 3611) -> currency (ISO 4217) mapping: https://github.com/srcagency/country-currencies

Currency exchange rates:

  • http://fixer.io/ (default)
  • https://currencylayer.com (add ?useCurrencylayer) More countries, but 1000 monthly requests limit. It should fall back (silently) to using Fixer if the monthly request quota has been reached. The free tier doesn't support HTTPS so the requests will fail silently due to mixed content - you'll need to open it in its own window (rather than the bl.ocks.org iframe), then allow mixed content by clicking the error shield icon (or similar) in your browser address bar and choosing to allow loading unsafe scripts (or a similarly named option).
[
{"numeric":"004","alpha2":"AF","alpha3":"AFG","name":"Afghanistan"},
{"numeric":"248","alpha2":"AX","alpha3":"ALA","name":"Åland Islands"},
{"numeric":"008","alpha2":"AL","alpha3":"ALB","name":"Albania"},
{"numeric":"012","alpha2":"DZ","alpha3":"DZA","name":"Algeria"},
{"numeric":"016","alpha2":"AS","alpha3":"ASM","name":"American Samoa"},
{"numeric":"020","alpha2":"AD","alpha3":"AND","name":"Andorra"},
{"numeric":"024","alpha2":"AO","alpha3":"AGO","name":"Angola"},
{"numeric":"660","alpha2":"AI","alpha3":"AIA","name":"Anguilla"},
{"numeric":"010","alpha2":"AQ","alpha3":"ATA","name":"Antarctica"},
{"numeric":"028","alpha2":"AG","alpha3":"ATG","name":"Antigua and Barbuda"},
{"numeric":"032","alpha2":"AR","alpha3":"ARG","name":"Argentina"},
{"numeric":"051","alpha2":"AM","alpha3":"ARM","name":"Armenia"},
{"numeric":"533","alpha2":"AW","alpha3":"ABW","name":"Aruba"},
{"numeric":"036","alpha2":"AU","alpha3":"AUS","name":"Australia"},
{"numeric":"040","alpha2":"AT","alpha3":"AUT","name":"Austria"},
{"numeric":"031","alpha2":"AZ","alpha3":"AZE","name":"Azerbaijan"},
{"numeric":"044","alpha2":"BS","alpha3":"BHS","name":"Bahamas"},
{"numeric":"048","alpha2":"BH","alpha3":"BHR","name":"Bahrain"},
{"numeric":"050","alpha2":"BD","alpha3":"BGD","name":"Bangladesh"},
{"numeric":"052","alpha2":"BB","alpha3":"BRB","name":"Barbados"},
{"numeric":"112","alpha2":"BY","alpha3":"BLR","name":"Belarus"},
{"numeric":"056","alpha2":"BE","alpha3":"BEL","name":"Belgium"},
{"numeric":"084","alpha2":"BZ","alpha3":"BLZ","name":"Belize"},
{"numeric":"204","alpha2":"BJ","alpha3":"BEN","name":"Benin"},
{"numeric":"060","alpha2":"BM","alpha3":"BMU","name":"Bermuda"},
{"numeric":"064","alpha2":"BT","alpha3":"BTN","name":"Bhutan"},
{"numeric":"068","alpha2":"BO","alpha3":"BOL","name":"Bolivia (Plurinational State of)"},
{"numeric":"535","alpha2":"BQ","alpha3":"BES","name":"Bonaire, Sint Eustatius and Saba"},
{"numeric":"070","alpha2":"BA","alpha3":"BIH","name":"Bosnia and Herzegovina"},
{"numeric":"072","alpha2":"BW","alpha3":"BWA","name":"Botswana"},
{"numeric":"074","alpha2":"BV","alpha3":"BVT","name":"Bouvet Island"},
{"numeric":"076","alpha2":"BR","alpha3":"BRA","name":"Brazil"},
{"numeric":"086","alpha2":"IO","alpha3":"IOT","name":"British Indian Ocean Territory"},
{"numeric":"096","alpha2":"BN","alpha3":"BRN","name":"Brunei Darussalam"},
{"numeric":"100","alpha2":"BG","alpha3":"BGR","name":"Bulgaria"},
{"numeric":"854","alpha2":"BF","alpha3":"BFA","name":"Burkina Faso"},
{"numeric":"108","alpha2":"BI","alpha3":"BDI","name":"Burundi"},
{"numeric":"132","alpha2":"CV","alpha3":"CPV","name":"Cabo Verde"},
{"numeric":"116","alpha2":"KH","alpha3":"KHM","name":"Cambodia"},
{"numeric":"120","alpha2":"CM","alpha3":"CMR","name":"Cameroon"},
{"numeric":"124","alpha2":"CA","alpha3":"CAN","name":"Canada"},
{"numeric":"136","alpha2":"KY","alpha3":"CYM","name":"Cayman Islands"},
{"numeric":"140","alpha2":"CF","alpha3":"CAF","name":"Central African Republic"},
{"numeric":"148","alpha2":"TD","alpha3":"TCD","name":"Chad"},
{"numeric":"152","alpha2":"CL","alpha3":"CHL","name":"Chile"},
{"numeric":"156","alpha2":"CN","alpha3":"CHN","name":"China"},
{"numeric":"162","alpha2":"CX","alpha3":"CXR","name":"Christmas Island"},
{"numeric":"166","alpha2":"CC","alpha3":"CCK","name":"Cocos (Keeling) Islands"},
{"numeric":"170","alpha2":"CO","alpha3":"COL","name":"Colombia"},
{"numeric":"174","alpha2":"KM","alpha3":"COM","name":"Comoros"},
{"numeric":"178","alpha2":"CG","alpha3":"COG","name":"Congo"},
{"numeric":"180","alpha2":"CD","alpha3":"COD","name":"Congo (Democratic Republic of the)"},
{"numeric":"184","alpha2":"CK","alpha3":"COK","name":"Cook Islands"},
{"numeric":"188","alpha2":"CR","alpha3":"CRI","name":"Costa Rica"},
{"numeric":"384","alpha2":"CI","alpha3":"CIV","name":"Côte d'Ivoire"},
{"numeric":"191","alpha2":"HR","alpha3":"HRV","name":"Croatia"},
{"numeric":"192","alpha2":"CU","alpha3":"CUB","name":"Cuba"},
{"numeric":"531","alpha2":"CW","alpha3":"CUW","name":"Curaçao"},
{"numeric":"196","alpha2":"CY","alpha3":"CYP","name":"Cyprus"},
{"numeric":"203","alpha2":"CZ","alpha3":"CZE","name":"Czechia"},
{"numeric":"208","alpha2":"DK","alpha3":"DNK","name":"Denmark"},
{"numeric":"262","alpha2":"DJ","alpha3":"DJI","name":"Djibouti"},
{"numeric":"212","alpha2":"DM","alpha3":"DMA","name":"Dominica"},
{"numeric":"214","alpha2":"DO","alpha3":"DOM","name":"Dominican Republic"},
{"numeric":"218","alpha2":"EC","alpha3":"ECU","name":"Ecuador"},
{"numeric":"818","alpha2":"EG","alpha3":"EGY","name":"Egypt"},
{"numeric":"222","alpha2":"SV","alpha3":"SLV","name":"El Salvador"},
{"numeric":"226","alpha2":"GQ","alpha3":"GNQ","name":"Equatorial Guinea"},
{"numeric":"232","alpha2":"ER","alpha3":"ERI","name":"Eritrea"},
{"numeric":"233","alpha2":"EE","alpha3":"EST","name":"Estonia"},
{"numeric":"231","alpha2":"ET","alpha3":"ETH","name":"Ethiopia"},
{"numeric":"238","alpha2":"FK","alpha3":"FLK","name":"Falkland Islands (Malvinas)"},
{"numeric":"234","alpha2":"FO","alpha3":"FRO","name":"Faroe Islands"},
{"numeric":"242","alpha2":"FJ","alpha3":"FJI","name":"Fiji"},
{"numeric":"246","alpha2":"FI","alpha3":"FIN","name":"Finland"},
{"numeric":"250","alpha2":"FR","alpha3":"FRA","name":"France"},
{"numeric":"254","alpha2":"GF","alpha3":"GUF","name":"French Guiana"},
{"numeric":"258","alpha2":"PF","alpha3":"PYF","name":"French Polynesia"},
{"numeric":"260","alpha2":"TF","alpha3":"ATF","name":"French Southern Territories"},
{"numeric":"266","alpha2":"GA","alpha3":"GAB","name":"Gabon"},
{"numeric":"270","alpha2":"GM","alpha3":"GMB","name":"Gambia"},
{"numeric":"268","alpha2":"GE","alpha3":"GEO","name":"Georgia"},
{"numeric":"276","alpha2":"DE","alpha3":"DEU","name":"Germany"},
{"numeric":"288","alpha2":"GH","alpha3":"GHA","name":"Ghana"},
{"numeric":"292","alpha2":"GI","alpha3":"GIB","name":"Gibraltar"},
{"numeric":"300","alpha2":"GR","alpha3":"GRC","name":"Greece"},
{"numeric":"304","alpha2":"GL","alpha3":"GRL","name":"Greenland"},
{"numeric":"308","alpha2":"GD","alpha3":"GRD","name":"Grenada"},
{"numeric":"312","alpha2":"GP","alpha3":"GLP","name":"Guadeloupe"},
{"numeric":"316","alpha2":"GU","alpha3":"GUM","name":"Guam"},
{"numeric":"320","alpha2":"GT","alpha3":"GTM","name":"Guatemala"},
{"numeric":"831","alpha2":"GG","alpha3":"GGY","name":"Guernsey"},
{"numeric":"324","alpha2":"GN","alpha3":"GIN","name":"Guinea"},
{"numeric":"624","alpha2":"GW","alpha3":"GNB","name":"Guinea-Bissau"},
{"numeric":"328","alpha2":"GY","alpha3":"GUY","name":"Guyana"},
{"numeric":"332","alpha2":"HT","alpha3":"HTI","name":"Haiti"},
{"numeric":"334","alpha2":"HM","alpha3":"HMD","name":"Heard Island and McDonald Islands"},
{"numeric":"336","alpha2":"VA","alpha3":"VAT","name":"Holy See"},
{"numeric":"340","alpha2":"HN","alpha3":"HND","name":"Honduras"},
{"numeric":"344","alpha2":"HK","alpha3":"HKG","name":"Hong Kong"},
{"numeric":"348","alpha2":"HU","alpha3":"HUN","name":"Hungary"},
{"numeric":"352","alpha2":"IS","alpha3":"ISL","name":"Iceland"},
{"numeric":"356","alpha2":"IN","alpha3":"IND","name":"India"},
{"numeric":"360","alpha2":"ID","alpha3":"IDN","name":"Indonesia"},
{"numeric":"364","alpha2":"IR","alpha3":"IRN","name":"Iran (Islamic Republic of)"},
{"numeric":"368","alpha2":"IQ","alpha3":"IRQ","name":"Iraq"},
{"numeric":"372","alpha2":"IE","alpha3":"IRL","name":"Ireland"},
{"numeric":"833","alpha2":"IM","alpha3":"IMN","name":"Isle of Man"},
{"numeric":"376","alpha2":"IL","alpha3":"ISR","name":"Israel"},
{"numeric":"380","alpha2":"IT","alpha3":"ITA","name":"Italy"},
{"numeric":"388","alpha2":"JM","alpha3":"JAM","name":"Jamaica"},
{"numeric":"392","alpha2":"JP","alpha3":"JPN","name":"Japan"},
{"numeric":"832","alpha2":"JE","alpha3":"JEY","name":"Jersey"},
{"numeric":"400","alpha2":"JO","alpha3":"JOR","name":"Jordan"},
{"numeric":"398","alpha2":"KZ","alpha3":"KAZ","name":"Kazakhstan"},
{"numeric":"404","alpha2":"KE","alpha3":"KEN","name":"Kenya"},
{"numeric":"296","alpha2":"KI","alpha3":"KIR","name":"Kiribati"},
{"numeric":"408","alpha2":"KP","alpha3":"PRK","name":"Korea (Democratic People's Republic of)"},
{"numeric":"410","alpha2":"KR","alpha3":"KOR","name":"Korea (Republic of)"},
{"numeric":"414","alpha2":"KW","alpha3":"KWT","name":"Kuwait"},
{"numeric":"417","alpha2":"KG","alpha3":"KGZ","name":"Kyrgyzstan"},
{"numeric":"418","alpha2":"LA","alpha3":"LAO","name":"Lao People's Democratic Republic"},
{"numeric":"428","alpha2":"LV","alpha3":"LVA","name":"Latvia"},
{"numeric":"422","alpha2":"LB","alpha3":"LBN","name":"Lebanon"},
{"numeric":"426","alpha2":"LS","alpha3":"LSO","name":"Lesotho"},
{"numeric":"430","alpha2":"LR","alpha3":"LBR","name":"Liberia"},
{"numeric":"434","alpha2":"LY","alpha3":"LBY","name":"Libya"},
{"numeric":"438","alpha2":"LI","alpha3":"LIE","name":"Liechtenstein"},
{"numeric":"440","alpha2":"LT","alpha3":"LTU","name":"Lithuania"},
{"numeric":"442","alpha2":"LU","alpha3":"LUX","name":"Luxembourg"},
{"numeric":"446","alpha2":"MO","alpha3":"MAC","name":"Macao"},
{"numeric":"807","alpha2":"MK","alpha3":"MKD","name":"Macedonia (the former Yugoslav Republic of)"},
{"numeric":"450","alpha2":"MG","alpha3":"MDG","name":"Madagascar"},
{"numeric":"454","alpha2":"MW","alpha3":"MWI","name":"Malawi"},
{"numeric":"458","alpha2":"MY","alpha3":"MYS","name":"Malaysia"},
{"numeric":"462","alpha2":"MV","alpha3":"MDV","name":"Maldives"},
{"numeric":"466","alpha2":"ML","alpha3":"MLI","name":"Mali"},
{"numeric":"470","alpha2":"MT","alpha3":"MLT","name":"Malta"},
{"numeric":"584","alpha2":"MH","alpha3":"MHL","name":"Marshall Islands"},
{"numeric":"474","alpha2":"MQ","alpha3":"MTQ","name":"Martinique"},
{"numeric":"478","alpha2":"MR","alpha3":"MRT","name":"Mauritania"},
{"numeric":"480","alpha2":"MU","alpha3":"MUS","name":"Mauritius"},
{"numeric":"175","alpha2":"YT","alpha3":"MYT","name":"Mayotte"},
{"numeric":"484","alpha2":"MX","alpha3":"MEX","name":"Mexico"},
{"numeric":"583","alpha2":"FM","alpha3":"FSM","name":"Micronesia (Federated States of)"},
{"numeric":"498","alpha2":"MD","alpha3":"MDA","name":"Moldova (Republic of)"},
{"numeric":"492","alpha2":"MC","alpha3":"MCO","name":"Monaco"},
{"numeric":"496","alpha2":"MN","alpha3":"MNG","name":"Mongolia"},
{"numeric":"499","alpha2":"ME","alpha3":"MNE","name":"Montenegro"},
{"numeric":"500","alpha2":"MS","alpha3":"MSR","name":"Montserrat"},
{"numeric":"504","alpha2":"MA","alpha3":"MAR","name":"Morocco"},
{"numeric":"508","alpha2":"MZ","alpha3":"MOZ","name":"Mozambique"},
{"numeric":"104","alpha2":"MM","alpha3":"MMR","name":"Myanmar"},
{"numeric":"516","alpha2":"NA","alpha3":"NAM","name":"Namibia"},
{"numeric":"520","alpha2":"NR","alpha3":"NRU","name":"Nauru"},
{"numeric":"524","alpha2":"NP","alpha3":"NPL","name":"Nepal"},
{"numeric":"528","alpha2":"NL","alpha3":"NLD","name":"Netherlands"},
{"numeric":"540","alpha2":"NC","alpha3":"NCL","name":"New Caledonia"},
{"numeric":"554","alpha2":"NZ","alpha3":"NZL","name":"New Zealand"},
{"numeric":"558","alpha2":"NI","alpha3":"NIC","name":"Nicaragua"},
{"numeric":"562","alpha2":"NE","alpha3":"NER","name":"Niger"},
{"numeric":"566","alpha2":"NG","alpha3":"NGA","name":"Nigeria"},
{"numeric":"570","alpha2":"NU","alpha3":"NIU","name":"Niue"},
{"numeric":"574","alpha2":"NF","alpha3":"NFK","name":"Norfolk Island"},
{"numeric":"580","alpha2":"MP","alpha3":"MNP","name":"Northern Mariana Islands"},
{"numeric":"578","alpha2":"NO","alpha3":"NOR","name":"Norway"},
{"numeric":"512","alpha2":"OM","alpha3":"OMN","name":"Oman"},
{"numeric":"586","alpha2":"PK","alpha3":"PAK","name":"Pakistan"},
{"numeric":"585","alpha2":"PW","alpha3":"PLW","name":"Palau"},
{"numeric":"275","alpha2":"PS","alpha3":"PSE","name":"Palestine, State of"},
{"numeric":"591","alpha2":"PA","alpha3":"PAN","name":"Panama"},
{"numeric":"598","alpha2":"PG","alpha3":"PNG","name":"Papua New Guinea"},
{"numeric":"600","alpha2":"PY","alpha3":"PRY","name":"Paraguay"},
{"numeric":"604","alpha2":"PE","alpha3":"PER","name":"Peru"},
{"numeric":"608","alpha2":"PH","alpha3":"PHL","name":"Philippines"},
{"numeric":"612","alpha2":"PN","alpha3":"PCN","name":"Pitcairn"},
{"numeric":"616","alpha2":"PL","alpha3":"POL","name":"Poland"},
{"numeric":"620","alpha2":"PT","alpha3":"PRT","name":"Portugal"},
{"numeric":"630","alpha2":"PR","alpha3":"PRI","name":"Puerto Rico"},
{"numeric":"634","alpha2":"QA","alpha3":"QAT","name":"Qatar"},
{"numeric":"638","alpha2":"RE","alpha3":"REU","name":"Réunion"},
{"numeric":"642","alpha2":"RO","alpha3":"ROU","name":"Romania"},
{"numeric":"643","alpha2":"RU","alpha3":"RUS","name":"Russian Federation"},
{"numeric":"646","alpha2":"RW","alpha3":"RWA","name":"Rwanda"},
{"numeric":"652","alpha2":"BL","alpha3":"BLM","name":"Saint Barthélemy"},
{"numeric":"654","alpha2":"SH","alpha3":"SHN","name":"Saint Helena, Ascension and Tristan da Cunha"},
{"numeric":"659","alpha2":"KN","alpha3":"KNA","name":"Saint Kitts and Nevis"},
{"numeric":"662","alpha2":"LC","alpha3":"LCA","name":"Saint Lucia"},
{"numeric":"663","alpha2":"MF","alpha3":"MAF","name":"Saint Martin (French part)"},
{"numeric":"666","alpha2":"PM","alpha3":"SPM","name":"Saint Pierre and Miquelon"},
{"numeric":"670","alpha2":"VC","alpha3":"VCT","name":"Saint Vincent and the Grenadines"},
{"numeric":"882","alpha2":"WS","alpha3":"WSM","name":"Samoa"},
{"numeric":"674","alpha2":"SM","alpha3":"SMR","name":"San Marino"},
{"numeric":"678","alpha2":"ST","alpha3":"STP","name":"Sao Tome and Principe"},
{"numeric":"682","alpha2":"SA","alpha3":"SAU","name":"Saudi Arabia"},
{"numeric":"686","alpha2":"SN","alpha3":"SEN","name":"Senegal"},
{"numeric":"688","alpha2":"RS","alpha3":"SRB","name":"Serbia"},
{"numeric":"690","alpha2":"SC","alpha3":"SYC","name":"Seychelles"},
{"numeric":"694","alpha2":"SL","alpha3":"SLE","name":"Sierra Leone"},
{"numeric":"702","alpha2":"SG","alpha3":"SGP","name":"Singapore"},
{"numeric":"534","alpha2":"SX","alpha3":"SXM","name":"Sint Maarten (Dutch part)"},
{"numeric":"703","alpha2":"SK","alpha3":"SVK","name":"Slovakia"},
{"numeric":"705","alpha2":"SI","alpha3":"SVN","name":"Slovenia"},
{"numeric":"090","alpha2":"SB","alpha3":"SLB","name":"Solomon Islands"},
{"numeric":"706","alpha2":"SO","alpha3":"SOM","name":"Somalia"},
{"numeric":"710","alpha2":"ZA","alpha3":"ZAF","name":"South Africa"},
{"numeric":"239","alpha2":"GS","alpha3":"SGS","name":"South Georgia and the South Sandwich Islands"},
{"numeric":"728","alpha2":"SS","alpha3":"SSD","name":"South Sudan"},
{"numeric":"724","alpha2":"ES","alpha3":"ESP","name":"Spain"},
{"numeric":"144","alpha2":"LK","alpha3":"LKA","name":"Sri Lanka"},
{"numeric":"729","alpha2":"SD","alpha3":"SDN","name":"Sudan"},
{"numeric":"740","alpha2":"SR","alpha3":"SUR","name":"Suriname"},
{"numeric":"744","alpha2":"SJ","alpha3":"SJM","name":"Svalbard and Jan Mayen"},
{"numeric":"748","alpha2":"SZ","alpha3":"SWZ","name":"Swaziland"},
{"numeric":"752","alpha2":"SE","alpha3":"SWE","name":"Sweden"},
{"numeric":"756","alpha2":"CH","alpha3":"CHE","name":"Switzerland"},
{"numeric":"760","alpha2":"SY","alpha3":"SYR","name":"Syrian Arab Republic"},
{"numeric":"158","alpha2":"TW","alpha3":"TWN","name":"Taiwan, Province of China[a]"},
{"numeric":"762","alpha2":"TJ","alpha3":"TJK","name":"Tajikistan"},
{"numeric":"834","alpha2":"TZ","alpha3":"TZA","name":"Tanzania, United Republic of"},
{"numeric":"764","alpha2":"TH","alpha3":"THA","name":"Thailand"},
{"numeric":"626","alpha2":"TL","alpha3":"TLS","name":"Timor-Leste"},
{"numeric":"768","alpha2":"TG","alpha3":"TGO","name":"Togo"},
{"numeric":"772","alpha2":"TK","alpha3":"TKL","name":"Tokelau"},
{"numeric":"776","alpha2":"TO","alpha3":"TON","name":"Tonga"},
{"numeric":"780","alpha2":"TT","alpha3":"TTO","name":"Trinidad and Tobago"},
{"numeric":"788","alpha2":"TN","alpha3":"TUN","name":"Tunisia"},
{"numeric":"792","alpha2":"TR","alpha3":"TUR","name":"Turkey"},
{"numeric":"795","alpha2":"TM","alpha3":"TKM","name":"Turkmenistan"},
{"numeric":"796","alpha2":"TC","alpha3":"TCA","name":"Turks and Caicos Islands"},
{"numeric":"798","alpha2":"TV","alpha3":"TUV","name":"Tuvalu"},
{"numeric":"800","alpha2":"UG","alpha3":"UGA","name":"Uganda"},
{"numeric":"804","alpha2":"UA","alpha3":"UKR","name":"Ukraine"},
{"numeric":"784","alpha2":"AE","alpha3":"ARE","name":"United Arab Emirates"},
{"numeric":"826","alpha2":"GB","alpha3":"GBR","name":"United Kingdom of Great Britain and Northern Ireland"},
{"numeric":"840","alpha2":"US","alpha3":"USA","name":"United States of America"},
{"numeric":"581","alpha2":"UM","alpha3":"UMI","name":"United States Minor Outlying Islands"},
{"numeric":"858","alpha2":"UY","alpha3":"URY","name":"Uruguay"},
{"numeric":"860","alpha2":"UZ","alpha3":"UZB","name":"Uzbekistan"},
{"numeric":"548","alpha2":"VU","alpha3":"VUT","name":"Vanuatu"},
{"numeric":"862","alpha2":"VE","alpha3":"VEN","name":"Venezuela (Bolivarian Republic of)"},
{"numeric":"704","alpha2":"VN","alpha3":"VNM","name":"Viet Nam"},
{"numeric":"092","alpha2":"VG","alpha3":"VGB","name":"Virgin Islands (British)"},
{"numeric":"850","alpha2":"VI","alpha3":"VIR","name":"Virgin Islands (U.S.)"},
{"numeric":"876","alpha2":"WF","alpha3":"WLF","name":"Wallis and Futuna"},
{"numeric":"732","alpha2":"EH","alpha3":"ESH","name":"Western Sahara"},
{"numeric":"887","alpha2":"YE","alpha3":"YEM","name":"Yemen"},
{"numeric":"894","alpha2":"ZM","alpha3":"ZMB","name":"Zambia"},
{"numeric":"716","alpha2":"ZW","alpha3":"ZWE","name":"Zimbabwe"}
]
[
{ "country": "AD", "currency": "EUR" },
{ "country": "AE", "currency": "AED" },
{ "country": "AF", "currency": "AFN" },
{ "country": "AG", "currency": "XCD" },
{ "country": "AI", "currency": "XCD" },
{ "country": "AL", "currency": "ALL" },
{ "country": "AM", "currency": "AMD" },
{ "country": "AO", "currency": "AOA" },
{ "country": "AQ", "currency": null },
{ "country": "AR", "currency": "ARS" },
{ "country": "AS", "currency": "USD" },
{ "country": "AT", "currency": "EUR" },
{ "country": "AU", "currency": "AUD" },
{ "country": "AW", "currency": "AWG" },
{ "country": "AX", "currency": "EUR" },
{ "country": "AZ", "currency": "AZN" },
{ "country": "BA", "currency": "BAM" },
{ "country": "BB", "currency": "BBD" },
{ "country": "BD", "currency": "BDT" },
{ "country": "BE", "currency": "EUR" },
{ "country": "BF", "currency": "XOF" },
{ "country": "BG", "currency": "BGN" },
{ "country": "BH", "currency": "BHD" },
{ "country": "BI", "currency": "BIF" },
{ "country": "BJ", "currency": "XOF" },
{ "country": "BL", "currency": "EUR" },
{ "country": "BM", "currency": "BMD" },
{ "country": "BN", "currency": "BND" },
{ "country": "BO", "currency": "BOB" },
{ "country": "BQ", "currency": "USD" },
{ "country": "BR", "currency": "BRL" },
{ "country": "BS", "currency": "BSD" },
{ "country": "BT", "currency": "INR" },
{ "country": "BV", "currency": "NOK" },
{ "country": "BW", "currency": "BWP" },
{ "country": "BY", "currency": "BYR" },
{ "country": "BZ", "currency": "BZD" },
{ "country": "CA", "currency": "CAD" },
{ "country": "CC", "currency": "AUD" },
{ "country": "CD", "currency": "CDF" },
{ "country": "CF", "currency": "XAF" },
{ "country": "CG", "currency": "XAF" },
{ "country": "CH", "currency": "CHF" },
{ "country": "CI", "currency": "XOF" },
{ "country": "CK", "currency": "NZD" },
{ "country": "CL", "currency": "CLP" },
{ "country": "CM", "currency": "XAF" },
{ "country": "CN", "currency": "CNY" },
{ "country": "CO", "currency": "COP" },
{ "country": "CR", "currency": "CRC" },
{ "country": "CU", "currency": "CUP" },
{ "country": "CV", "currency": "CVE" },
{ "country": "CW", "currency": "ANG" },
{ "country": "CX", "currency": "AUD" },
{ "country": "CY", "currency": "EUR" },
{ "country": "CZ", "currency": "CZK" },
{ "country": "DE", "currency": "EUR" },
{ "country": "DJ", "currency": "DJF" },
{ "country": "DK", "currency": "DKK" },
{ "country": "DM", "currency": "XCD" },
{ "country": "DO", "currency": "DOP" },
{ "country": "DZ", "currency": "DZD" },
{ "country": "EC", "currency": "USD" },
{ "country": "EE", "currency": "EUR" },
{ "country": "EG", "currency": "EGP" },
{ "country": "EH", "currency": "MAD" },
{ "country": "ER", "currency": "ERN" },
{ "country": "ES", "currency": "EUR" },
{ "country": "ET", "currency": "ETB" },
{ "country": "FI", "currency": "EUR" },
{ "country": "FJ", "currency": "FJD" },
{ "country": "FK", "currency": "FKP" },
{ "country": "FM", "currency": "USD" },
{ "country": "FO", "currency": "DKK" },
{ "country": "FR", "currency": "EUR" },
{ "country": "GA", "currency": "XAF" },
{ "country": "GB", "currency": "GBP" },
{ "country": "GD", "currency": "XCD" },
{ "country": "GE", "currency": "GEL" },
{ "country": "GF", "currency": "EUR" },
{ "country": "GG", "currency": "GGP" },
{ "country": "GH", "currency": "GHS" },
{ "country": "GI", "currency": "GIP" },
{ "country": "GL", "currency": "DKK" },
{ "country": "GM", "currency": "GMD" },
{ "country": "GN", "currency": "GNF" },
{ "country": "GP", "currency": "EUR" },
{ "country": "GQ", "currency": "XAF" },
{ "country": "GR", "currency": "EUR" },
{ "country": "GS", "currency": "GBP" },
{ "country": "GT", "currency": "GTQ" },
{ "country": "GU", "currency": "USD" },
{ "country": "GW", "currency": "XOF" },
{ "country": "GY", "currency": "GYD" },
{ "country": "HK", "currency": "HKD" },
{ "country": "HM", "currency": "AUD" },
{ "country": "HN", "currency": "HNL" },
{ "country": "HR", "currency": "HRK" },
{ "country": "HT", "currency": "HTG" },
{ "country": "HU", "currency": "HUF" },
{ "country": "ID", "currency": "IDR" },
{ "country": "IE", "currency": "EUR" },
{ "country": "IL", "currency": "ILS" },
{ "country": "IM", "currency": "GBP" },
{ "country": "IN", "currency": "INR" },
{ "country": "IO", "currency": "USD" },
{ "country": "IQ", "currency": "IQD" },
{ "country": "IR", "currency": "IRR" },
{ "country": "IS", "currency": "ISK" },
{ "country": "IT", "currency": "EUR" },
{ "country": "JE", "currency": "GBP" },
{ "country": "JM", "currency": "JMD" },
{ "country": "JO", "currency": "JOD" },
{ "country": "JP", "currency": "JPY" },
{ "country": "KE", "currency": "KES" },
{ "country": "KG", "currency": "KGS" },
{ "country": "KH", "currency": "KHR" },
{ "country": "KI", "currency": "AUD" },
{ "country": "KM", "currency": "KMF" },
{ "country": "KN", "currency": "XCD" },
{ "country": "KP", "currency": "KPW" },
{ "country": "KR", "currency": "KRW" },
{ "country": "KW", "currency": "KWD" },
{ "country": "KY", "currency": "KYD" },
{ "country": "KZ", "currency": "KZT" },
{ "country": "LA", "currency": "LAK" },
{ "country": "LB", "currency": "LBP" },
{ "country": "LC", "currency": "XCD" },
{ "country": "LI", "currency": "CHF" },
{ "country": "LK", "currency": "LKR" },
{ "country": "LR", "currency": "LRD" },
{ "country": "LS", "currency": "LSL" },
{ "country": "LT", "currency": "EUR" },
{ "country": "LU", "currency": "EUR" },
{ "country": "LV", "currency": "EUR" },
{ "country": "LY", "currency": "LYD" },
{ "country": "MA", "currency": "MAD" },
{ "country": "MC", "currency": "EUR" },
{ "country": "MD", "currency": "MDL" },
{ "country": "ME", "currency": "EUR" },
{ "country": "MF", "currency": "EUR" },
{ "country": "MG", "currency": "MGA" },
{ "country": "MH", "currency": "USD" },
{ "country": "MK", "currency": "MKD" },
{ "country": "ML", "currency": "XOF" },
{ "country": "MM", "currency": "MMK" },
{ "country": "MN", "currency": "MNT" },
{ "country": "MO", "currency": "MOP" },
{ "country": "MP", "currency": "USD" },
{ "country": "MQ", "currency": "EUR" },
{ "country": "MR", "currency": "MRO" },
{ "country": "MS", "currency": "XCD" },
{ "country": "MT", "currency": "EUR" },
{ "country": "MU", "currency": "MUR" },
{ "country": "MV", "currency": "MVR" },
{ "country": "MW", "currency": "MWK" },
{ "country": "MX", "currency": "MXN" },
{ "country": "MY", "currency": "MYR" },
{ "country": "MZ", "currency": "MZN" },
{ "country": "NA", "currency": "NAD" },
{ "country": "NC", "currency": "XPF" },
{ "country": "NE", "currency": "XOF" },
{ "country": "NF", "currency": "AUD" },
{ "country": "NG", "currency": "NGN" },
{ "country": "NI", "currency": "NIO" },
{ "country": "NL", "currency": "EUR" },
{ "country": "NO", "currency": "NOK" },
{ "country": "NP", "currency": "NPR" },
{ "country": "NR", "currency": "AUD" },
{ "country": "NU", "currency": "NZD" },
{ "country": "NZ", "currency": "NZD" },
{ "country": "OM", "currency": "OMR" },
{ "country": "PA", "currency": "PAB" },
{ "country": "PE", "currency": "PEN" },
{ "country": "PF", "currency": "XPF" },
{ "country": "PG", "currency": "PGK" },
{ "country": "PH", "currency": "PHP" },
{ "country": "PK", "currency": "PKR" },
{ "country": "PL", "currency": "PLN" },
{ "country": "PM", "currency": "EUR" },
{ "country": "PN", "currency": "NZD" },
{ "country": "PR", "currency": "USD" },
{ "country": "PS", "currency": "JOD" },
{ "country": "PT", "currency": "EUR" },
{ "country": "PW", "currency": "USD" },
{ "country": "PY", "currency": "PYG" },
{ "country": "QA", "currency": "QAR" },
{ "country": "RE", "currency": "EUR" },
{ "country": "RO", "currency": "RON" },
{ "country": "RS", "currency": "RSD" },
{ "country": "RU", "currency": "RUB" },
{ "country": "RW", "currency": "RWF" },
{ "country": "SA", "currency": "SAR" },
{ "country": "SB", "currency": "SBD" },
{ "country": "SC", "currency": "SCR" },
{ "country": "SD", "currency": "SDG" },
{ "country": "SE", "currency": "SEK" },
{ "country": "SG", "currency": "SGD" },
{ "country": "SH", "currency": "SHP" },
{ "country": "SI", "currency": "EUR" },
{ "country": "SJ", "currency": "NOK" },
{ "country": "SK", "currency": "EUR" },
{ "country": "SL", "currency": "SLL" },
{ "country": "SM", "currency": "EUR" },
{ "country": "SN", "currency": "XOF" },
{ "country": "SO", "currency": "SOS" },
{ "country": "SR", "currency": "SRD" },
{ "country": "SS", "currency": "SSP" },
{ "country": "ST", "currency": "STD" },
{ "country": "SV", "currency": "USD" },
{ "country": "SX", "currency": "ANG" },
{ "country": "SY", "currency": "SYP" },
{ "country": "SZ", "currency": "SZL" },
{ "country": "TC", "currency": "USD" },
{ "country": "TD", "currency": "XAF" },
{ "country": "TF", "currency": "EUR" },
{ "country": "TG", "currency": "XOF" },
{ "country": "TH", "currency": "THB" },
{ "country": "TJ", "currency": "TJS" },
{ "country": "TK", "currency": "NZD" },
{ "country": "TL", "currency": "USD" },
{ "country": "TM", "currency": "TMT" },
{ "country": "TN", "currency": "TND" },
{ "country": "TO", "currency": "TOP" },
{ "country": "TR", "currency": "TRY" },
{ "country": "TT", "currency": "TTD" },
{ "country": "TV", "currency": "AUD" },
{ "country": "TW", "currency": "TWD" },
{ "country": "TZ", "currency": "TZS" },
{ "country": "UA", "currency": "UAH" },
{ "country": "UG", "currency": "UGX" },
{ "country": "UM", "currency": "USD" },
{ "country": "US", "currency": "USD" },
{ "country": "UY", "currency": "UYU" },
{ "country": "UZ", "currency": "UZS" },
{ "country": "VA", "currency": "EUR" },
{ "country": "VC", "currency": "XCD" },
{ "country": "VE", "currency": "VEF" },
{ "country": "VG", "currency": "USD" },
{ "country": "VI", "currency": "USD" },
{ "country": "VN", "currency": "VND" },
{ "country": "VU", "currency": "VUV" },
{ "country": "WF", "currency": "XPF" },
{ "country": "WS", "currency": "EUR" },
{ "country": "YE", "currency": "YER" },
{ "country": "YT", "currency": "EUR" },
{ "country": "ZA", "currency": "ZAR" },
{ "country": "ZM", "currency": "ZMW" },
{ "country": "ZW", "currency": "USD" }
]
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Currencies, then and now</title>
<link rel="stylesheet" href="style.css" />
<script defer src="https://unpkg.com/d3@4.12.2/build/d3.js"></script>
<script defer src="https://unpkg.com/d3fc@13.1.1/build/d3fc.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.25.4/d3-legend.js"></script>
<script defer src="https://unpkg.com/topojson-client@3.0.0"></script>
</head>
<body>
<d3fc-group id="container" class="container" auto-resize>
<d3fc-svg id="map" class="map"></d3fc-svg>
<label class="datepicker-group">
Reference date
<input id="datepicker" class="datepicker" type="date" required />
</label>
</d3fc-group>
<script defer src="index.js"></script>
</body>
</html>
(function () {
const apiMinDate = '2000-01-01'; // currencylayer does go back another year
const scaleThresholds = {
constrain: 0.3,
ignore: 1
};
const useCurrencylayer = window.top.location.search.includes('useCurrencylayer');
const getFxRates = useCurrencylayer ? getFxRatesCurrencylayer : getFxRatesFixer;
const containerFc = d3.select('#container');
const svgFc = containerFc.select('#map');
const svg = svgFc.select('svg');
let countries;
let baseCountryCode = getQueryParam('baseCountry') || 'GB';
let referenceDate = getQueryParam('referenceDate') || oneYearAgo();
const projection = d3.geoMercator();
const pathGenerator = d3.geoPath(projection);
const percentFormat = d3.format('+.1%');
const rateFormat = d3.format('.2f');
const colorScale = d3.scaleLinear()
.range(['red', 'white', 'green']);
const legend = d3.legendColor()
.scale(colorScale)
.cells(7) // odd, so the 0 pivot is included
.labelFormat(percentFormat)
.title('Change')
.titleWidth(100);
svgFc.on('measure', () => {
const { width, height } = d3.event.detail;
projection
// scale to fit width (Mercator-specific equation)
.scale((width - 5) / (2 * Math.PI))
// clip to fit height
.clipExtent([[0, 0], [width, height]])
// center the projection center in the element
.translate([width / 2, height / 2])
// shift projection center down to match Mercator's original map which was truncated at 80deg N and 66deg S
.center([0, 10]);
});
// called on requestRedraw, auto-resize of d3fc-group will make subsequent calls
svgFc.on('draw', draw);
d3.select('#datepicker')
.attr('min', apiMinDate)
.on('change', onDatepickerChange)
.node().value = referenceDate;
Promise.all([
loadJson('https://unpkg.com/world-atlas@1/world/110m.json'),
loadJson('country-codes.json'),
loadJson('country-currency.json'),
]).then(([world, countryCodes, countryCurrency]) => {
const data = prepareGeoData(world);
countries = data.countries;
countries.forEach(country => addCountryData(country, countryCodes, countryCurrency));
svg.datum(data);
update();
});
function update() {
const baseCountry = countries.find(country => country.properties.code === baseCountryCode);
const baseCurrency = baseCountry.properties.currency;
svg.datum().baseCountry = baseCountry;
return Promise.all([
getFxRates(baseCurrency, 'latest'),
getFxRates(baseCurrency, referenceDate)
]).then(rates => {
countries.forEach(country => updateCountryFx(country, rates[0], rates[1]));
// symmetric, with pivot at zero to match the range
const extremum = determineScaleExtremum(countries);
colorScale.domain([-extremum, 0, extremum]).nice();
containerFc.node().requestRedraw();
});
}
/**
* Attempt to keep the scale useful, in the face of wild currency fluctuations.
* A fixed scale would be better for comparisons, but extents vary too wildly.
*
* This implementation makes the scale reasonably useful for both short and long time periods.
* Ignored & constrained values will be extrapolated by d3, and will tend towards black.
*/
function determineScaleExtremum(countries) {
console.group('determineScaleExtent');
const withRates = countries.filter(country => country.properties.rates);
const extremum = d3.max(withRates, country => {
const { change } = country.properties.rates;
const absChange = Math.abs(change);
if (absChange > scaleThresholds.ignore) {
console.log(`ignoring extreme change ${percentFormat(change)} from ${country.properties.name}`);
} else if (absChange > scaleThresholds.constrain) {
console.log(`constraining large change ${percentFormat(change)} from ${country.properties.name}`);
return scaleThresholds.constrain;
} else {
return absChange;
}
});
console.groupEnd();
return extremum;
}
function draw() {
// fc.dataJoin simplifies pattern of enter().append() & all.attr()
// Since it's projected, sphere will be rendered as a rectangle.
fc.dataJoin('path', 'sphere-background')(svg, d => [d.land])
.datum({ type: 'Sphere' }) // shape the path generator should render
.attr('d', pathGenerator);
// The selectAll() underneath pushes data down from context (svg in this case) to the selectees
fc.dataJoin('path', 'land')(svg, d => [d.land])
.attr('d', pathGenerator);
const countries = fc.dataJoin('path', 'country')(svg, d => d.countries)
.attr('d', pathGenerator)
.attr('fill', countryColor);
countries.enter()
.filter(d => d.properties.currency)
.classed('interactive', true)
.on('click', d => selectBaseCountry(d.properties.code));
fc.dataJoin('title')(countries, d => [d])
.text(countryTitle);
fc.dataJoin('path', 'country-borders')(svg, d => [d.borders])
.attr('d', pathGenerator);
fc.dataJoin('g', 'legend')(svg, d => [d])
.attr('transform', `translate(${projection([-150, 0])})`) // put it out of the way, in the Pacific Ocean
.call(legend);
}
function countryColor(country) {
return countryCharacteristic(country, {
base: () => 'hsl(240, 100%, 50%)',
sameCurrencyAsBase: () => 'hsl(240, 100%, 70%)',
ratesPresent: rates => colorScale(rates.change),
ratesMissing: () => 'gray'
});
}
function countryTitle(country) {
const { properties: props } = country;
const { name, currency } = props;
return countryCharacteristic(country, {
base: () => `${name} (base country) (${currency})`,
sameCurrencyAsBase: () => `${name} (same currency as base) (${currency})`,
ratesPresent: rates =>
`${name} (${currency}, then ${rateFormat(rates.reference)}, now ${rateFormat(rates.current)}, change ${percentFormat(rates.change)})`,
ratesMissing: () => `${name} (${currency ? currency + ', no data for current context' : 'no currency'})`
});
}
function countryCharacteristic(country, characteristics) {
const baseCountry = svg.datum().baseCountry;
if (country === baseCountry) {
return characteristics.base();
} else if (country.properties.currency === baseCountry.properties.currency) {
return characteristics.sameCurrencyAsBase();
} else if (country.properties.rates) {
return characteristics.ratesPresent(country.properties.rates);
} else {
return characteristics.ratesMissing();
}
}
function selectBaseCountry(code) {
baseCountryCode = code;
update();
}
function selectReferenceDate(date) {
referenceDate = date;
update();
}
function onDatepickerChange() {
const picker = d3.event.target;
const date = picker.value;
if (picker.validity.valid && date && /^\d{4}-\d{2}-\d{2}/.test(date)) {
if (date >= apiMinDate) {
selectReferenceDate(date);
}
}
}
function prepareGeoData(world) {
const land = topojson.feature(world, world.objects.land);
const countries = topojson.feature(world, world.objects.countries).features;
// Mesh excludes sea borders. Filter further to include shared borders only once
const borders = topojson.mesh(world, world.objects.countries, (a, b) => a !== b);
return { land, countries, borders };
}
function addCountryData(country, countryCodes, countryCurrency) {
const codes = countryCodes.find(candidate => candidate.numeric === country.id);
if (codes) {
Object.assign(country.properties, {
code: codes.alpha2,
name: codes.name,
currency: countryCurrency.find(candidate => candidate.country === codes.alpha2).currency
});
}
}
function updateCountryFx(country, currentRates, referenceRates) {
const currency = country.properties.currency;
let rates;
if (currency) {
const reference = referenceRates[currency];
const current = currentRates[currency];
if (reference != null && current != null) {
const change = (current - reference) / reference;
rates = { current, reference, change };
}
}
country.properties.rates = rates;
}
function getFxRatesFixer(base, date) {
const apiBase = 'https://api.fixer.io';
// API returns previous weekday for weekend dates
return loadJson(`${apiBase}/${date}?base=${base}`)
.catch(xhr => {
alert(`Error loading FX data (fixer.io): ${xhr.status} ${xhr.responseText}`);
throw xhr;
})
.then(data => data.rates);
}
function getFxRatesCurrencylayer(base, date) {
const apiBase = 'http://apilayer.net/api';
const accessKey = '661432fe87cd37cfb0eaad81d8af9cb6';
if (date === 'latest') {
date = new Date().toISOString().slice(0, 10);
}
return loadJson(`${apiBase}/historical?access_key=${accessKey}&date=${date}`)
.then(responseData => {
if (responseData.success) {
return calculateRatesFromUSD(responseData.quotes, base);
} else if (responseData.code === 104) {
console.warn('currencylayer API limit exceeded. Falling back to fixer.io');
return getFxRatesFixer(base, date);
} else {
const { code, info } = responseData.error;
console.error(`Error loading FX data (currencylayer): ${code} ${info}`);
throw responseData.error;
}
});
}
function loadJson(url) {
return new Promise((resolve, reject) => {
d3.json(url, (error, data) => error ? reject(error.target) : resolve(data));
});
}
function oneYearAgo() {
const date = new Date();
date.setFullYear(date.getFullYear() - 1);
return date.toISOString().slice(0, 10);
}
function calculateRatesFromUSD(usdRates, base) {
// currencylayer free doesn't support base switching, so calculate
const usdBase = usdRates['USD' + base];
const rates = {};
Object.entries(usdRates).forEach(([pair, usdQuote]) => {
const quote = pair.slice(3);
rates[quote] = usdQuote / usdBase;
});
return rates;
}
function getQueryParam(name) {
const pair = window.top.location.search.substr(1)
.split('&')
.map(param => param.split('='))
.find(pair => pair[0] === name);
return pair && pair[1];
}
})();
// Make the data function simply copy data down, by wrapping it to counter the unwrapping that data().does
// const dataKey = d => [d];
body {
margin: 0;
font-family: sans-serif;
}
d3fc-group.container {
display: block;
width: 100vw;
height: calc(100vw / 1.65); /* Mercator's original aspect ratio */
}
@media (min-aspect-ratio: 165/100) {
d3fc-group.container {
width: calc(100vh * 1.65);
height: 100vh;
}
}
d3fc-svg.map {
display: block;
height: 100%;
}
.sphere-background {
fill: rgb(170, 218, 255);
}
.country {
cursor: not-allowed;
}
.country.interactive {
cursor: pointer;
}
.country-borders {
fill: none;
stroke: black;
stroke-width: 0.5px;
}
.datepicker-group {
position: absolute;
left: 20px;
bottom: 20px;
}
.datepicker {
display: block;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment