Click on a country.
an iteration on the delightful Chatty Map from @martgnz
this is the world map I always wanted when I was a kid studying for the Geography Bee
the script tag that includes babel on the page is from the bl.ock ES2015 Sequences Sunburst
localized country names are sourced from the i18n-iso-countries project by @hellomichibye. want to add your language? just translate this list of country names and send michaelwittig a pull request!
the map itself is drawn with spam.js, a small library for to declaratively creating modern Canvas maps with D3. spam.js makes it easy to create static or zoomable maps with automatic projection and retina resolution.
oh and the voices? the voices you ask? they're from http://responsivevoice.org/ API docs source code
xxxxxxxxxx
<meta charset='utf-8' />
<style>
body {
max-width: 960px;
}
canvas {
cursor: crosshair
}
select {
display: table;
margin: 0 auto;
}
</style>
<body>
<script src='https://d3js.org/d3.v3.min.js' charset='utf-8'></script>
<script src='https://npmcdn.com/babel-core@5.8.34/browser.min.js'></script>
<script src='topojson.min.js'></script>
<script src='rbush.min.js'></script>
<script src='d3.geo.projection.min.js'></script>
<script src='console.js'></script>
<script src='spam.min.js'></script>
<script src='https://code.responsivevoice.org/responsivevoice.js'></script>
<script src='ar.js'></script>
<script src='de.js'></script>
<script src='es.js'></script>
<script src='et.js'></script>
<script src='fi.js'></script>
<script src='fr.js'></script>
<script src='nl.js'></script>
<script src='pt.js'></script>
<script src='sv.js'></script>
<script src='countriesAlpha2.js'></script>
<script lang='babel' type='text/babel'>
let hover = null
const width = 960
const height = 500
let graticule = d3.geo.graticule()
// Get the list of the voices
const voiceList = responsiveVoice.getVoices()
const voiceListArray = voiceList.map(d => d.name);
console.log('available voices from the ResponseVoice API', voiceListArray)
// Define the list of voices that we want to display
// each voice in this list has a list of country names
// in their native language
// this way the word spoken fits the accent
const displayVoiceList = [
'Arabic Male',
'Brazilian Portuguese Female',
'Portuguese Female',
'Deutsch Female',
'Dutch Female',
'Finnish Female',
'Finnish Male',
'French Female',
'Spanish Female',
'Spanish Latin American Female',
'Swedish Female',
'Swedish Male',
'Australian Female',
'US English Female',
'UK English Female',
'UK English Male',
// 'US English Male', // strangely the same as the UK English Male
// 'Fallback UK Female',
// 'Armenian Male',
// 'Chinese Female',
// 'Czech Female',
// 'Danish Female',
// 'Greek Female',
// 'Hatian Creole Female',
// 'Hindi Female',
// 'Hungarian Female',
// 'Indonesian Female',
// 'Italian Female',
// 'Japanese Female',
// 'Korean Female',
// 'Latin Female',
// 'Norwegian Female',
// 'Polish Female',
// 'Romanian Male',
// 'Russian Female',
// 'Slovak Female',
// 'Tamil Male',
// 'Thai Female',
// 'Turkish Female',
// 'Afrikaans Male',
// 'Albanian Male',
// 'Bosnian Male',
// 'Catalan Male',
// 'Croatian Male',
// 'Czech Male',
// 'Danish Male',
// 'Esperanto Male',
// 'Greek Male',
// 'Hungarian Male',
// 'Icelandic Male',
// 'Latin Male',
// 'Latvian Male',
// 'Macedonian Male',
// 'Moldavian Male',
// 'Montenegrin Male',
// 'Norwegian Male',
// 'Serbian Male',
// 'Serbo-Croatian Male',
// 'Slovak Male',
// 'Swahili Male',
// 'Vietnamese Male',
// 'Welsh Male'
]
console.log('voices whose languages we have country names for', displayVoiceList);
// hard code the relationship between voices and languages
const languageCodeHash = {
'Arabic Male': 'ar',
'Brazilian Portuguese Female': 'pt',
'Portuguese Female': 'pt',
'Deutsch Female': 'de',
'Dutch Female': 'nl',
'Finnish Female': 'fi',
'Finnish Male': 'fi',
'French Female': 'fr',
'Spanish Female': 'es',
'Spanish Latin American Female': 'es',
'Swedish Female': 'sv',
'Swedish Male': 'sv',
'Australian Female': 'en',
'US English Female': 'en',
'US English Male': 'en',
'UK English Female': 'en',
'UK English Male': 'en',
'Fallback UK Female': 'en',
};
let displayVoiceArrayOfObjects = [];
displayVoiceList.forEach((d, i) => displayVoiceArrayOfObjects.push({ 'name': d }));
// Create the selector
d3.select('body').append('select')
.selectAll('option')
.data(displayVoiceArrayOfObjects)
.enter()
.append('option')
.text(d => d.name)
.attr('value', d => d.name)
// store the current voice
const defaultVoice = 'Spanish Female';
let currentVoice = defaultVoice;
const index = 8; // 'Spanish Female'
// set the default voice
d3.select('select').property('selectedIndex', index);
responsiveVoice.setDefaultVoice(currentVoice)
// Fire the lang function according to the selector
d3.select('select').on('change', lang);
function lang() {
let selectedValue = d3.event.target.value;
currentVoice = selectedValue;
// Set the voice
return responsiveVoice.setDefaultVoice(selectedValue);
}
// create an object that allows us to use an English country name
// to lookup the ISO-3166-1 alpha-2 country code
var alpha2Lookup = {};
countriesAlpha2.forEach(d => {
alpha2Lookup[d.Name] = d.Code;
})
// draw the map
d3.json('world.json', (error, d) => {
topojson.presimplify(d)
let map = new StaticCanvasMap({
element: 'body',
width: width,
projection: d3.geo.robinson(),
data: [{
features: topojson.feature(d, d.objects['countries']),
static: {
prepaint: parameters => {
parameters.context.beginPath()
parameters.path(graticule())
parameters.context.lineWidth = 0.5
parameters.context.strokeStyle = 'rgb(200,200,200)'
parameters.context.stroke()
parameters.context.beginPath()
parameters.path({type: 'Sphere'})
parameters.context.lineWidth = 1
parameters.context.strokeStyle = 'rgb(30,30,30)'
parameters.context.stroke()
},
paintfeature: (parameters, d) => {
parameters.context.lineWidth = 0.5
parameters.context.strokeStyle = '#fff' // rgb(30,30,30)
parameters.context.stroke()
parameters.context.fillStyle = '#bbb'; // rgb(188, 223, 255)
parameters.context.fill()
}
},
dynamic: {
postpaint: parameters => {
if (!hover)
return
parameters.context.beginPath()
parameters.context.lineWidth = 1 / parameters.scale
parameters.path(hover)
parameters.context.stroke()
}
},
events: {
click: (parameters, d) => {
console.log('d from click event', d);
// speak silently to find out the language of the currentVoice
let language2 = languageCodeHash[currentVoice];
let countryName = d.id;
let countryCode;
switch(language2) {
case 'ar':
countryCode = alpha2Lookup[d.id];
countryName = ARi18n[countryCode];
break;
case 'de':
countryCode = alpha2Lookup[d.id];
countryName = DEi18n[countryCode];
break;
case 'es':
countryCode = alpha2Lookup[d.id];
countryName = ESi18n[countryCode];
break;
case 'et':
countryCode = alpha2Lookup[d.id];
countryName = ETi18n[countryCode];
break;
case 'fi':
countryCode = alpha2Lookup[d.id];
countryName = FIi18n[countryCode];
break;
case 'fr':
countryCode = alpha2Lookup[d.id];
countryName = FRi18n[countryCode];
break;
case 'nl':
countryCode = alpha2Lookup[d.id];
countryName = NLi18n[countryCode];
break;
case 'pt':
countryCode = alpha2Lookup[d.id];
countryName = PTi18n[countryCode];
break;
case 'sv':
countryCode = alpha2Lookup[d.id];
countryName = SVi18n[countryCode];
break;
case 'en':
break;
default:
break;
}
console.log('countryName', countryName, 'countryCode', countryCode);
// When clicking on a feature, say the name of that country
d && responsiveVoice.speak(countryName)
},
hover: (parameters, d) => {
hover = d
parameters.map.paint()
}
}
}]
})
map.init()
})
</script>
https://d3js.org/d3.v3.min.js
https://npmcdn.com/babel-core@5.8.34/browser.min.js
https://code.responsivevoice.org/responsivevoice.js