Published
Edited
Sep 7, 2021
11 forks
10 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
currentYear = 2010
Insert cell
height = width * 0.7
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
svg = d3
.create('svg')
.style('width', width)
.style('height', height);
Insert cell
selectedLayer = svg.append('g').attr('class', 'selected-countries');
Insert cell
layer = svg.append('g').attr('class', 'counties');
Insert cell
Insert cell
countries = topojson.feature(world, world.objects.countries).features
Insert cell
boundaries = topojson.mesh(world,world.objects.countries, (a, b) => a !== b);
Insert cell
Insert cell
paletteScale = d3
.scaleLinear()
.domain([minValue, maxValue])
.range(['#EFEFFF', '#CF4646']);
Insert cell
Insert cell
geoData = countries.map(country => {
const totalPopulationAllYears = data.get(country.id);
const totalPopulationCurrentYear = totalPopulationAllYears === undefined ? 0 : totalPopulationAllYears[currentYear];
return {
...country,
properties: {
totalPopulation: totalPopulationCurrentYear,
fillColor: paletteScale(totalPopulationCurrentYear),
name: totalPopulationAllYears === undefined ? "" : totalPopulationAllYears['Country Name'],
currentYear,
},
}
})
Insert cell
Insert cell
projection = d3
.geoMercator()
.center([4.8357, 45.764]) // this is centered on France
.scale(200)
.translate([width / 2, height / 2]); // The map will appear at the right spot
Insert cell
path = d3.geoPath().projection(projection);
Insert cell
zoom = {
const zoomed = ({ transform }) => {
layer.attr('transform', transform);
selectedLayer.attr('transform', transform);
};
return d3.zoom().scaleExtent([0.5, 40]).on('zoom', zoomed);
}
Insert cell
svg.call(zoom);
Insert cell
Insert cell
countriesSelection = {
layer
.selectAll('path')
.data(geoData, (d) => d.id)
.join(
enter => {
enter
.append('path')
.attr('class', (d) => `country ${d.id}`)
.attr('d', path)
.style('fill', (d) => d.properties.fillColor)
.style('stroke', 'none');
},
() => {},
exit => {
exit
.remove();
},
);
return layer.selectAll('.country');
}
Insert cell
Insert cell
boundariesLayer = {
countriesSelection
layer
.selectAll('.country-boundary')
.data([boundaries])
.join(
enter => {
enter
.append('path')
.attr('d', path)
.attr('class', 'country-boundary')
.style('stroke', 'black')
.style('stroke-width', 1)
.style('stroke-opacity', 0.3)
.style('fill', 'none');
},
() => {},
exit => {
exit.remove()
}
);
return layer.selectAll('.country-boundary');
}
Insert cell
Insert cell
tooltipSelection = d3.select('body')
.append('div')
.attr('class', 'hover-info')
.style('visibility', 'hidden');
Insert cell
Insert cell
tooltipEventListeners = countriesSelection
.on('mouseenter', ({ target }) => {
tooltipSelection.style('visibility', 'visible');

d3.select(target)
.transition()
.duration(150)
.ease(d3.easeCubic)
.style('fill', '#ffba08');
})
.on('mousemove', ({ pageX, pageY, target }) => {

tooltipSelection
.style('top', `${pageY + 20}px`)
.style('left', `${pageX - 10}px`)
.style('z-index', 100)
.html(
`<strong>${
target.__data__.properties.name
}</strong><br>Total population (${target.__data__.properties.currentYear}): <strong>${
target.__data__.properties.totalPopulation
}</strong>`,
)
.append('div')
.attr('class', 'triangle');

d3.selectAll('.triangle').style('top', `${-Math.sqrt(20) - 3}px`);
})
.on('mouseleave', (e) => {
tooltipSelection.style('visibility', 'hidden');

d3.select(e.target)
.transition()
.duration(150)
.ease(d3.easeCubic)
.style('fill', (d) => d.properties.fillColor);
});

Insert cell
Insert cell
buttn = d3.create('button').html('<h3>Reinitialize Selection</h3>');
Insert cell
Insert cell
selectedCountriesSet = {
countriesSelection
return Generators.observe(next => {
let selectedSet = new Set()
// Yield the initial value.
next(selectedSet);
// Define event listeners to yield the next values
svg.selectAll('.country')
.on('click', null)
.on('click', ({ target }) => {
selectedSet.add(target.__data__.id);
next(selectedSet)
});
// Define the event listener of the button
buttn.on('click', () => {
selectedSet = new Set();
next(selectedSet);
});
// When the generator is disposed, detach the event listener.
return () => svg.selectAll('.country').on('click', null);
});
}
Insert cell
Insert cell
selectedCountries = geoData.filter((country) => selectedCountriesSet.has(country.id));

Insert cell
Insert cell
totalPopulationSelection = selectedCountries.reduce((acc, country) => acc + country.properties.totalPopulation, 0);
Insert cell
Insert cell
geo = selectedCountries.map(country => {
const newCountry = {
...country,
properties: {
...country.properties,
totalPopulation: totalPopulationSelection,
name: `${country.properties.name} (Selected)`,
}
};
return newCountry;
});
Insert cell
Insert cell
t = function getTransition() {
return d3.transition().duration(250).ease(d3.easeCubic);
}

Insert cell
Insert cell
changeSelectedLayer = {
const currentTransition = t();
selectedLayer
.selectAll('path')
.data(geo, (d) => d.id)
.join(
(enter) => {
enter
.append('path')
.attr('class', (d) => `selected-country ${d.id}`)
.attr('d', path)
.style('fill', '#ffba08')
.style('fill', '#f4a261')
.style('stroke', 'black')
.style('stroke-width', 1)
.style('stroke-opacity', 0.3)
.call((en) =>
en
.transition(currentTransition)
.style('fill', '#f4a261')
.style('stroke-opacity', 0.1),
);
},
() => {},
(exit) => {
exit.style('fill', '#f4a261').call((ex) =>
ex
.transition(currentTransition)
.style('fill', (d) => d.properties.fillColor)
.remove(),
);
},
);
}
Insert cell
Insert cell
{
changeSelectedLayer
selectedLayer
.on('mouseenter', ({ target }) => {
tooltipSelection.style('visibility', 'visible');

d3.select(target).style('fill', '#ffba08');
})
.on('mousemove', ({ pageX, pageY, target }) => {
const x = pageX;
const y = pageY;

tooltipSelection
.html(
`<strong>${
target.__data__.properties.name
}</strong><br>Total population of the selection (${target.__data__.properties.currentYear}): <strong>${target.__data__.properties.totalPopulation}</strong>`,
)
.style('top', `${y + 20}px`)
.style('left', `${x - 10}px`)

d3.selectAll('.triangle').style('top', `${-Math.sqrt(20) - 3}px`);
})
.on('mouseleave', ({ target }) => {
tooltipSelection.style('visibility', 'hidden');

d3.select(target).style('fill', '#f4a261');
});
}
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require('d3@6')
Insert cell
topojson = require('topojson-client@3')
Insert cell
md`## Data fetching`
Insert cell
world = d3.json('https://gist.githubusercontent.com/olemi/d4fb825df71c2939405e0017e360cd73/raw/d6f9f0e9e8bd33183454250bd8b808953869edd2/world-110m2.json')
Insert cell
rawData = FileAttachment("API_SP.POP.TOTL_DS2_en_csv_v2_2106202@1.csv").csv()
Insert cell
countryCodes = d3.tsv('https://d3js.org/world-110m.v1.tsv')
Insert cell
md`## Data formatting`
Insert cell
letterToNum = {
const letToNum = new Map();
countryCodes.forEach(item => {
if (item.iso_a3 !== "-99" && item.iso_n3 !== "-99") {
letToNum.set(item.iso_a3, item.iso_n3);
}
});
return letToNum;
}
Insert cell
data = {
const arrayData = rawData.map(item => {
let newDatum = Object.assign({}, item);
for (let i = 1960; i < 2020; i++) {
newDatum[`${i}`] = +newDatum[`${i}`]
}
newDatum["Country Code"] = letterToNum.get(item["Country Code"]);
return newDatum;
}).filter(item => {
return item["Country Code"] !== undefined;
})
const data = new Map()
arrayData.forEach(item => {
data.set(+item["Country Code"], item);
})
return data;
}

Insert cell
defaultMinValue = {
let minVal = 100000000000;
data.forEach(value => {
if (value["1960"] === 0) return;
minVal = Math.min(minVal, value["1960"])
})
return minVal;
}
Insert cell
defaultMaxValue = {
let maxVal = 0;
data.forEach(value => {
if (value["2019"] === 0) return;
maxVal = Math.max(maxVal, value["2019"])
})
return maxVal;
}
Insert cell
Insert cell
styles = html`
<style>
.hover-info {
width: 150px;
z-index: 10001;
position: absolute;
background: aliceblue;
border: 2px solid black;
border-radius: 4px;
overflow: visible;
}

.triangle {
position: absolute;
z-index: -1;
width: 10px;
height: 10px;
left: 5px;
transform: rotate(45deg);
background: aliceblue;
border-left: 2px solid black;
border-top: 2px solid black;
}
</style>`
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more