Unlisted
Edited
Oct 3, 2024
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
data = await d3.json(
`https://ec.europa.eu/eurostat/api/dissemination/statistics/1.0/data/demo_r_mlifexp?geoLevel=nuts2&sex=${gender}&unit=YR&age=Y_LT1&time=2018`
)
Insert cell
Insert cell
indexStat = function(data) {
const arr = Object.entries(data.dimension.geo.category.index)
// define our object structure
.map(([key, val]) => ({
id: key,
value: data.value[val] || null,
name: data.dimension.geo.category.label[key]
}))
//sort the array in ascending order by 'value'
return arr.sort((a, b) =>
a.value > b.value ? -1 : b.value > a.value ? 1 : 0
);
}
Insert cell
Insert cell
lifeExpStats = indexStat(data)
// filter to only include EU member states
.filter((currentValue, i, arr) => {
if (currentValue.value !== null) {
let countryCode = currentValue.id.substring(0, 2);
if (acceptedCountries.includes(countryCode)) {
return currentValue;
}
}
})
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require("d3")
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
choroplethData = indexStat(
await d3.json(
`https://ec.europa.eu/eurostat/api/dissemination/statistics/1.0/data/demo_r_mlifexp?geoLevel=nuts2&sex=T&unit=YR&age=Y65&time=2018`
)
)
Insert cell
Insert cell
nutsTopojson = await d3.json(
"https://raw.githubusercontent.com/eurostat/Nuts2json/master/pub/v2/2016/3035/20M/2.json"
)
Insert cell
Insert cell
topojson = require("topojson")
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mapConfig = {
return {
width: Math.min(650, width),
height: Math.min(550, width),
background: "white" // use color sparingly when using data-driven color
};
}
Insert cell
Insert cell
Insert cell
projection = d3
.geoIdentity()
.reflectY(true)
// see https://github.com/d3/d3-geo#projection_fitExtent
.fitExtent([[10, 10], [mapConfig.width, mapConfig.height]], nutsRegions)
Insert cell
Insert cell
Insert cell
geoPath = d3.geoPath().projection(projection)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
createRootSVG = function() {
return d3
.select(DOM.svg(mapConfig.width, mapConfig.height))
.style("width", mapConfig.width + "px")
.style("height", "auto")
.style("background-color", mapConfig.background)
}
Insert cell
Insert cell
Insert cell
step2map = {
let map = createRootSVG();
addGraticule(map);
return map.node();
}
Insert cell
addGraticule = function(map) {
map
.append("g")
.selectAll("path")
.data(graticule.features)
.enter()
.append("path")
.attr("d", geoPath)
.attr("fill", "none")
.attr("stroke", "lightgrey");
}
Insert cell
Insert cell
Insert cell
step3map = {
let map = createRootSVG();
addGraticule(map);
addExternalRegions(map);
return map.node();
}
Insert cell
addExternalRegions = function (map) {
map
.append("g")
.selectAll("path")
.data(nonEuRegions.features)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", "none")
.attr("d", geoPath);
}
Insert cell
Insert cell
Insert cell
step4map = {
let map = createRootSVG();
addGraticule(map);
addExternalRegions(map);
addBlankNutsRegions(map);

return map.node();
}
Insert cell
addBlankNutsRegions = function(map) {
map
.append("g")
.selectAll("path")
.data(nutsRegions.features)
.enter()
.append("path")
.attr("fill", "white")
.attr("stroke", "black")
.attr("stroke-width", "0.1")
.attr("d", geoPath);
}
Insert cell
Insert cell
Insert cell
addNationalBorders = function(map) {
map
.append("g")
.selectAll("path")
.data(nutsBorders.features)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", d => {
// filter by nuts level (0 is national)
if (d.properties.lvl == 0) {
return "black";
}
})
.attr("stroke-width", "0.3")
.attr("d", geoPath);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
colorScale = {
if (colorScaleSelector == "threshold") {
return (
d3
.scaleThreshold()
.domain([17, 18.5, 19.5, 20.5, 22])
// try replacing this array of colours with 'd3.schemeBlues[6]' to use one of d3's colour palettes
.range([
"#FFE4B9",
"#FDCA79",
"#FBB33E",
"#F89D0E",
"#F67C14",
"#F45C19"
])
);
} else if (colorScaleSelector == "sequential") {
//colorScheme is chosen from the list of available schemes in d3-scale-chromatic using our dropdown
return d3.scaleSequential(d3[colorScheme]).domain(
// d3 extent scans our dataset and returns an array containing the max and min values
d3.extent(choroplethData, function(d) {
return d.value;
})
//.reverse() // to reverse the colour scale we can simple reverse the array that is specified in .domain()
);
}
}
Insert cell
Insert cell
Insert cell
addColouredNutsRegions = function(map) {
map
.append("g")
.selectAll("path")
.data(nutsRegions.features)
.enter()
.append("path")
.attr("fill", function(d) {
// here we match the nuts geometry id with its corresponding id in our stats dataset
// which gives us the statistical value for that region
let region = choroplethData.find(x => x.id == d.properties.id);
if (!region) {
return "grey"; //no data
} else {
// then we use our colour scale to give us a colour based on its value
return colorScale(region.value);
}
})
.attr("stroke", "black")
.attr("stroke-width", "0.3")
.attr("d", geoPath);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
addLegend = function(map) {
let legendWidth = 160;
map
.append("g")
.attr(
// position the legend on the right side of the map
"transform",
"translate(" + (mapConfig.width - legendWidth - 10) + " ,55)"
)
.append(() =>
// returns the d3 legend as a "g" element
legend({
color: colorScale,
title: "Life expectancy at 65",
width: legendWidth
})
);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
eurostatmap = require("eurostat-map@3.6.132")
Insert cell
Insert cell
html`<svg id="eurostat-map"></svg>`
Insert cell
eurostatmap
.map("ch")
.svgId("eurostat-map")
.width(mapConfig.width)
.title("Life expectancy at 65")
.subtitle("Years, by NUTS 2 regions, 2018")
.scale("20M")
.nutsLvl(2)
.stat({
eurostatDatasetCode: "demo_r_mlifexp",
filters: { time: "2018", sex: "T", age: "Y65", unit: "YR" },
unitText: "years"
})
.colorFun(d3.interpolateOranges)
.classifMethod("threshold")
.threshold([17, 18.5, 19.5, 20.5, 22])

.tooltipShowFlags(false)
.legend({
title: "Years",
x: mapConfig.width - 190,
y: 50
})
.build()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
filterScatterData = function(data, type) {
return data.filter((currentValue, i, arr) => {
if (currentValue.value !== null) {
let countryCode = currentValue.id.substring(0, 2);
if (countryCode == "DE") {
// removes 'until 1990 former territory of the FRG'
currentValue.name = "Germany";
}
if (memberStates.includes(countryCode)) {
return currentValue;
}
}
});
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
filterEuData = function(data) {
return [...data].filter(c => {
let countryCode = c.id.substring(0, 2);
//ignore the EU values that we dont want
if (countryCode == "EU" && c.id.substring(0, 4) !== "EU28") {
return;
} else if (c.id.substring(0, 4) == "EU28") {
c.name = "EU28";
return c;
}
});
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
efta = ["CH", "NO", "IS", "LI"]
Insert cell
filterEftaData = function(data) {
return data.filter(c => {
let countryCode = c.id.substring(0, 2);
if (efta.includes(countryCode)) {
return c;
}
});
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
filterUKData = function(data) {
return data.filter(c => {
let countryCode = c.id.substring(0, 2);
if (countryCode == "UK") {
return c;
}
});
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
others = ["RS", "MK"]
Insert cell
filterOthersData = function(data) {
return data.filter(c => {
let countryCode = c.id.substring(0, 2);
if (others.includes(countryCode)) {
return c;
}
});
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
getPlotMinMax = function(cities, towns, rural) {
let maxMinPerCountry = [];

cities.forEach(country => {
let citiesValue = country.value;

let townsValue;
towns.map(a => {
if (country.name == a.name) {
townsValue = a.value;
}
});

let ruralValue;
rural.map(a => {
if (a.name == country.name) {
ruralValue = a.value;
}
});
let max = Math.max(citiesValue, townsValue, ruralValue);
let min = Math.min(citiesValue, townsValue, ruralValue);

maxMinPerCountry.push({
country: country.name,
max,
min
});
});
return maxMinPerCountry;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
createScatterSvg = function() {
let svg = d3
.create("svg")
.attr("height", width / 1.255)
.attr("width", width);

//for smaller devices
if (width < 600) {
svg.attr("font-size", 11);
}
return svg;
}
Insert cell
Insert cell
Insert cell
scatterHeight =
+width / 1.255 - scatterMargins.top - scatterMargins.bottom;
Insert cell
scatterWidth = +width - scatterMargins.left - scatterMargins.right
Insert cell
Insert cell
domainX = {
let domain = [];
let plots = [
euCitiesPlotData,
membersCitiesPlotData,
ukCitiesPlotData,
eftaCitiesPlotData,
othersCitiesPlotData
];
plots.forEach(plot => {
plot.map(function(d) {
domain.push(d.name);
});
});

return domain;
}
Insert cell
scatterX = d3
.scaleBand()
.rangeRound([0, scatterWidth])
.padding(0.1)
.domain(domainX)
Insert cell
scatterY = d3
.scaleLinear()
.rangeRound([0, scatterHeight])
.domain([90, 0])
Insert cell
scatterYAxis = d3.axisLeft(scatterY).tickFormat(d => d + "%");
Insert cell
scatterXAxis = d3.axisBottom(scatterX).tickFormat((d, i) => {
// hide the labels of our separator objects
if (!d.includes("separator")) {
return d;
}
})
Insert cell
Insert cell
xGridlines = d3.axisTop(scatterX);
Insert cell
yGridlines = d3.axisLeft(scatterY).ticks(9);
Insert cell
addGridlines = function(g) {
g.append("g")
.call(yGridlines.tickSize(-scatterWidth).tickFormat(""))
.selectAll('line')
.attr("stroke", "lightgrey");
g.append("g")
.attr("transform", "translate(0," + scatterHeight + ")")
.call(xGridlines.tickSize(scatterHeight).tickFormat(""))
.selectAll('line')
.attr("stroke", "lightgrey");
}
Insert cell
Insert cell
addAxes = function(g) {
g.append("g")
.call(scatterYAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end");
g.append("g")
.attr("transform", "translate(0," + scatterHeight + ")")
.call(scatterXAxis)
.selectAll("text")
.attr("y", 0)
.attr("x", -10)
.attr("dy", "-10")
.attr("transform", "rotate(-90)")
.style("text-anchor", "end");
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
addDataToChart = function(g) {
// grey lines linking highest and lowest values for each country
minMaxData.forEach(minMax => {
g.selectAll(".euMinMax")
.data(minMax)
.enter()
.append("line")
.attr("stroke", "lightgrey")
.attr("stroke-width", 10)
.attr('x1', d => scatterX(d.country))
.attr('x2', d => scatterX(d.country))
.attr('y1', d => scatterY(d.max)) //lowest value of that country across all datasets
.attr('y2', d => scatterY(d.min)); //highest value across all datasets
});

// cities
citiesData.forEach(plot => {
g.selectAll(".cities")
.data(plot)
.enter()
.append("circle")
.filter(function(d) {
// dont show separators or null values
return d.value !== 0 && d.value !== null;
})
.attr("cx", function(d) {
return scatterX(d.name);
})
.attr("cy", function(d) {
return scatterY(d.value);
})
.attr('r', 5)
.attr('fill', "blue");
});

// towns/suburbs
townsData.forEach(plot => {
g.selectAll(".towns")
.data(plot)
.enter()
.append("circle")
.filter(function(d) {
return d.value !== 0 && d.value !== null;
})
.attr("cx", function(d) {
return scatterX(d.name);
})
.attr("cy", function(d) {
return scatterY(d.value);
})
.attr('r', 5)
.attr('fill', "orange");
});

// rural areas
ruralData.forEach(plot => {
g.selectAll(".rural")
.data(plot)
.enter()
.append("circle")
.filter(function(d) {
return d.value !== 0 && d.value !== null;
})
.attr("cx", function(d) {
return scatterX(d.name);
})
.attr("cy", function(d) {
return scatterY(d.value);
})
.attr('r', 5)
.attr('fill', "green");
});
}
Insert cell
addLegendToScatter = function(g) {
// symbols
g.append("circle")
.attr("r", 5)
.attr("fill", "blue")
.attr("transform", `translate(50, ${scatterHeight - 145})`);
g.append("circle")
.attr("r", 5)
.attr("fill", "orange")
.attr("transform", `translate(50, ${scatterHeight - 125})`);
g.append("circle")
.attr("r", 5)
.attr("fill", "green")
.attr("transform", `translate(50, ${scatterHeight - 105})`);

// labels
g.append("text")
.text("Cities")
.attr("transform", `translate(60, ${scatterHeight - 140})`);
g.append("text")
.text("Towns / suburbs")
.attr("transform", `translate(60, ${scatterHeight - 120})`);
g.append("text")
.text("Rural areas")
.attr("transform", `translate(60, ${scatterHeight - 100})`);
}
Insert cell
Insert cell
addTitleToScatterPlot = function (svg) {
svg.node().appendChild(
createSVGtext({
text: "People who perceive their own health as good or very good, 2018",
x: 35,
y: 30,
fontSize: 18,
fill: "black",
fontWeight: "bold",
maxCharsPerLine: 65
})
);
svg.node().appendChild(
createSVGtext({
text: "%, share of population aged ≥ 16 years, by degree of urbanisation",
x: 35,
y: 50,
fontSize: 15,
fontStyle: "italic",
maxCharsPerLine: 65
})
);
}
Insert cell
Insert cell
Insert cell
scatterPlot = {
let scatterSvg = createScatterSvg();
let chart = scatterSvg
.append("g")
.attr(
"transform",
"translate(" + scatterMargins.left + "," + scatterMargins.top + ")"
);
addGridlines(chart);
addAxes(chart);
addDataToChart(chart);
addLegendToScatter(chart);
addTitleToScatterPlot(scatterSvg);

return scatterSvg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
basicMap = {
let map = createRootSVG();
addGraticule(map);
addBlankNutsRegions(map);
addExternalRegions(map);
addNationalBorders(map);
return map.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
circulatoryDeathRate = indexStat(
await d3.json(
`https://ec.europa.eu/eurostat/api/dissemination/statistics/1.0/data/hlth_cd_asdr2?geoLevel=nuts1&sex=T&unit=RT&time=2016&age=TOTAL&icd10=I`
)
).map((d) => {
d.circulatory = d.value;
return d;
})
Insert cell
Insert cell
cancerDeathRate = indexStat(
await d3.json(
`https://ec.europa.eu/eurostat/api/dissemination/statistics/1.0/data/hlth_cd_asdr2/?geoLevel=nuts1&sex=T&unit=RT&time=2016&age=TOTAL&icd10=C`
)
).map((d) => {
d.cancer = d.value;
return d;
})
Insert cell
Insert cell
respiratoryDeathRate = indexStat(
await d3.json(
`https://ec.europa.eu/eurostat/api/dissemination/statistics/1.0/data/hlth_cd_asdr2?geoLevel=nuts1&sex=T&unit=RT&time=2016&age=TOTAL&icd10=J`
)
).map((d) => {
d.respiratory = d.value;
return d;
})
Insert cell
Insert cell
totalDeathRate = indexStat(
await d3.json(
`https://ec.europa.eu/eurostat/api/dissemination/statistics/1.0/data/hlth_cd_asdr2?geoLevel=nuts1&sex=T&unit=RT&time=2016&age=TOTAL&icd10=A-R_V-Y`
)
).map((d) => {
d.total = d.value;
return d;
})
Insert cell
Insert cell
centroidsData = (
await fetch(
`https://raw.githubusercontent.com/eurostat/Nuts2json/master/pub/v2/2016/3035/nutspt_1.json`
)
).json()
Insert cell
centroids = centroidsData.features.map((c) => {
c.id = c.properties.id;
return c;
})
Insert cell
Insert cell
Insert cell
pieChartMapData = {
//for each region, create object with each cause of death and its rate
let pieData = [];
let all = [
...cancerDeathRate,
...circulatoryDeathRate,
...respiratoryDeathRate,
...totalDeathRate,
...centroids
];
let merged = mergeObjectsInArray(all, "id").map(o => {
o.other = Math.round(o.total - o.cancer - o.circulatory - o.respiratory);
return o;
});
merged.filter(d => {
let countryCode = d.id.substring(0, 2);
if (!countryCode.includes("EU") && d.value !== null) {
pieData.push(
//pie chart data has to be an array of objects
{
id: d.id,
name: d.name,
geometry: d.geometry,
total: d.total,
data: [
{ name: "Circulatory", value: d.circulatory },
{ name: "Cancer", value: d.cancer },
{ name: "Respiratory", value: d.respiratory },
{ name: "Other", value: d.other }
]
}
);
return d;
}
});
return pieData;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
radiusFunction = d3
.scaleSqrt()
.domain(d3.extent(totalDeathRate.map(r => r.value)))
.range([5, 10]) // here we set minimum and maximum pie radius
Insert cell
Insert cell
sectorColor = function(value) {
if (value == "Cancer") {
return "#A4CDF8";
} else if (value == "Circulatory") {
return "#2E7AF9";
} else if (value == "Respiratory") {
return 'orange';
} else if (value == "Other") {
return '#FFCC80';
}
}
Insert cell
Insert cell
addPieChartsToMap = function (map) {
// Create segments for each region
pieChartMapData.forEach((region) => {
if (region.geometry) {
let x = projection(region.geometry.coordinates)[0];
let y = projection(region.geometry.coordinates)[1];
let arcs = pie(region.data);
map
.append("g")
.attr("transform", "translate(" + x + "," + y + ")")
.attr("stroke", "white")
.selectAll("path")
.data(arcs)
.join("path")
.attr("fill", (d) => sectorColor(d.data.name))
.attr(
"d",
d3.arc().innerRadius(0).outerRadius(radiusFunction(region.total))
)
.append("title")
// title visible when hovering the slices - here we could add a tooltip instead
.text((d) => `${d.data.name}: ${d.data.value}`);
}
});
}
Insert cell
Insert cell
Insert cell
Insert cell
pieChartMap = {
let map = createRootSVG();
addGraticule(map);
addBlankNutsRegions(map);
addExternalRegions(map);
addNationalBorders(map);
addPieChartsToMap(map);
addPieChartLegend(map);
return map.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more