Published
Edited
May 31, 2021
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
//sheep = FileAttachment("apro_mt_lssheep.csv").csv()

sheep = Object.assign((await FileAttachment("apro_mt_lssheep.csv").csv()).map(d => {
d.date = parseDate(d.TIME)
d.Value = d.Value.replace(/,/g, "");
d.value = +d.Value >= 0 ? +d.Value * 1000 : 0; // value is per thousand heads
return d
})
)
Insert cell
Insert cell
Insert cell
Inputs.table(sheep)
Insert cell
Insert cell
countries
Insert cell
Insert cell
sheepNoEU = sheep.filter((d) => !d.GEO.includes("European Union"))
Insert cell
Insert cell
sheepNoEU[0].date
Insert cell
barYear = d3.max(sheepNoEU, d => d.date)
Insert cell
Insert cell
sheepNoEUYear = sheepNoEU.filter(d => d.date === barYear)
Insert cell
Insert cell
new Date(2020,0,1)
Insert cell
Insert cell
+new Date(2020,0,1)
Insert cell
Insert cell
sheepNoEUYearFixed = sheepNoEU.filter((d) => +d.date === +barYear)
Insert cell
Insert cell
Insert cell
Plot.plot({
height: 500,
// marks are where we add geometries to our chart
marks: [
// this is a bar chart on the x axis
Plot.barX(sheepNoEUYearFixed, {
x: 'value',
y: 'GEO',
})
]
})
Insert cell
Insert cell
countriesSorted = sheepNoEUYearFixed.sort((a,b) => b.value - a.value).map(d => d.GEO)
Insert cell
Insert cell
Plot.plot({
height: 500,
// sort with our list of countries
// affects the y axis
// this is similar to D3 scales
y: {
domain: countriesSorted,
},
marks: [
Plot.barX(sheepNoEUYearFixed, {
y: 'GEO',
x: 'value'
})
]
})
Insert cell
Insert cell
Plot.plot({
height: 500,
marginLeft: 120, // increases the margin on the left side
y: {
domain: countriesSorted,
},
marks: [
Plot.barX(sheepNoEUYearFixed, {
y: 'GEO',
x: 'value'
})
]
})
Insert cell
Insert cell
sheepNoEUYearFixed
Insert cell
Insert cell
fixNames = ({'Germany (until 1990 former territory of the FRG)': 'Germany', 'Kosovo (under United Nations Security Council Resolution 1244/99)': 'Kosovo'})
Insert cell
Insert cell
sheepFixedNames = sheepNoEUYearFixed.map(d => ({...d, GEO: fixNames[d.GEO] || d.GEO}))
Insert cell
Insert cell
Plot.plot({
height: 500,
marginLeft: 120,
y: {
domain: countriesSorted, // oops
},
marks: [
Plot.barX(sheepFixedNames, {
y: 'GEO',
x: 'value'
})
]
})
Insert cell
Insert cell
countriesSortedFixed = sheepFixedNames.sort((a,b) => b.value - a.value).map(d => d.GEO)
Insert cell
Insert cell
Plot.plot({
height: 500,
marginLeft: 120,
y: {
domain: countriesSortedFixed, // new array
},
marks: [
Plot.barX(sheepFixedNames, {
y: 'GEO',
x: 'value'
})
]
})
Insert cell
Insert cell
Insert cell
Plot.plot({
grid: true,
height: 600,
marginLeft: 120,
caption: 'Source: Eurostat', // let's add a source
x: {
ticks: 4, // less ticks
label: null, // hide the axis label
tickFormat: d => d3.format('.1s')(d).toLowerCase(), // better formatting
},
y: {
label: null,
domain: countriesSortedFixed,
},
marks: [
Plot.barX(sheepFixedNames, {
y: 'GEO',
x: 'value',
fill: '#E75A36' // colour the bars
})
]
})
Insert cell
Insert cell
Insert cell
dates = d3.groups(sheep, (d) => d.date).map((d) => d[0])
Insert cell
Insert cell
Insert cell
sheepData = sheep
.filter((d) => !d.GEO.includes("European Union"))
.map((d) => ({ ...d, GEO: fixNames[d.GEO] || d.GEO }))
.filter((d) => +d.date === +selectedYear)
Insert cell
sheepCountries = sheepData.sort((a,b) => b.value - a.value).map(d => d.GEO)
Insert cell
Insert cell
viewof selectedYear = Inputs.select(dates, {label: 'Select year:', format: d => d3.timeFormat('%Y')(d)})
Insert cell
Plot.plot({
grid: true,
height: 600,
marginLeft: 120,
caption: 'Source: Eurostat', // let's add a source
x: {
ticks: 4, // less ticks
label: null, // hide the axis label
tickFormat: d => d3.format('.1s')(d).toLowerCase(), // better formatting
},
y: {
label: null,
domain: sheepCountries,
},
marks: [
Plot.barX(sheepData, {
y: 'GEO',
x: 'value',
fill: '#E75A36' // colour the bars
})
]
})
Insert cell
Insert cell
md`We need to parse the dates first in order to create a chart that shows the number of sheep in a coun try over time. We can do this with a simple function:`
Insert cell
parseDate = d3.timeParse("%Y")
Insert cell
md`Let's process and structure the data the way we need it for our chart`
Insert cell
sheepProcessed = sheep
.map((d) => {
d.date = parseDate(d.TIME);
d.Value = d.Value.replace(/,/g, "");
d.value = +d.Value >= 0 ? +d.Value * 1000 : 0; // value is per thousand heads
return d;
}) ///comment this out and format data when loading
.filter((d) => d.GEO === selectedValue && d.date >= parseDate("1990"))
Insert cell
dataExtent = d3.extent(sheepProcessed, (d) => d.value)
Insert cell
md` To crerate a dropdown menu to filter the data by country we need an array with each individual country in the data`
Insert cell
countries = [...new Set(sheep.map((d) => d.GEO))]
Insert cell
Insert cell
chart = {
const height = 500;
const margin = { top: 30, bottom: 30, left: 60, right: 30 };
const svg = d3.select(DOM.svg(width, height));

const xScale = d3
.scaleTime()
.domain([parseDate("1990"), parseDate("2021")])
.range([0 + margin.left, width - margin.right]);

const yScale = d3
.scaleLinear()
.domain([0, dataExtent[1]])
.range([height - margin.top, margin.bottom]);

// const xAxis = d3.axisBottom().scale(xScale).ticks(10);

const xAxis = (g) =>
g
.call(d3.axisBottom(xScale).ticks(10))
.call((g) => g.select(".domain").remove())
.call((g) => g.selectAll(".tick line").remove())
.call((g) =>
g
.selectAll(".tick text")
.style("font", "14px sans-serif")
.attr("fill", "black")
);

const yAxis = (g) =>
g
.call(
d3
.axisLeft(yScale)
.ticks(5)
.tickFormat((d) => d3.format(".1s")(d).toLowerCase())
)
.call((g) => g.select(".domain").remove())
.call((g) =>
g
.selectAll(".tick line")
.attr("x2", width - margin.right - margin.left)
.attr("stroke", "lightgray")
)
.call((g) =>
g
.append("text")
.attr("fill", "black")
.attr("x", 2)
.attr("y", margin.top / 2)
.attr("dy", "0.0em")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.style("font", "20px sans-serif")
.text(`Sheep population over time in ${selectedValue}`)
)
.call((g) =>
g
.selectAll(".tick text")
.style("font", "14px sans-serif")
.attr("fill", "black")
);

svg
.append("g")
.attr("class", "x axis")
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(xAxis)
.selectAll("text") // rotate the axis labels
.style("text-anchor", "middle")
.attr("dx", width / sheepProcessed.length / 2);

svg
.append("g")
.attr("class", "y axis")
.attr("transform", `translate(${margin.left},0)`)
.call(yAxis)
.selectAll("text");

svg
.append("g")
.selectAll("rect")
.data(sheepProcessed)
.enter()
.append("rect")
.attr("fill", "#E75A36")
.attr("stroke", "#FED69E")
.attr("x", (d) => xScale(d.date))
.attr("y", (d) => yScale(d.value))
.attr("height", (d) => yScale(0) - yScale(d.value))
.attr("width", width / (sheepProcessed.length + 3))
.attr("opacity", 1);

return svg.node();
}
Insert cell
Insert cell
Insert cell
md`In this case, we are going to us population data from the Eurostat API`
Insert cell
Insert cell
nutsGeoPt = (
await fetch(
`https://raw.githubusercontent.com/eurostat/Nuts2json/master/${nutsYear}/4258/nutspt_${nutsLevel}.json`
)
).json()
Insert cell
nutsTopo = (await fetch(
`https://raw.githubusercontent.com/eurostat/Nuts2json/master/${nutsYear}/4258/10M/3.json`
)).json()
Insert cell
nutsGeo = topojson.feature(nutsTopo, nutsTopo.objects.nutsrg)
Insert cell
nutsGeo.features
Insert cell
Insert cell
Insert cell
Insert cell
popData = await d3.json(
`https://ec.europa.eu/eurostat/wdds/rest/data/v2.1/json/en/demo_r_pjangrp3?geoLevel=nuts${nutsLevel}&sex=T&age=TOTAL&unit=NR&time=${year}`
)
Insert cell
Insert cell
createArray = function (data) {
const arr = Object.entries(
data.dimension.geo.category.index
).map(([key, val]) => ({ id: key, val: data.value[val] || null }));
const ind = {};
for (var i = 0; i < arr.length; i++) ind[arr[i].id] = arr[i].val;
return ind;
}
Insert cell
Insert cell
Insert cell
Insert cell
ratio = 12 / 18
Insert cell
height = width * ratio
Insert cell
projection = d3
.geoAzimuthalEqualArea()
.rotate([-15, -58])
.scale(1000)
.fitSize([width, height], nutsGeo)
Insert cell
nutsYear = 2016
Insert cell
arr = Object.values(population)
Insert cell
//min = Math.min(...arr);
max = Math.max(...arr)
Insert cell
colorScale = d3
.scaleSequential(d3.interpolateOrRd)
.domain([0, 1000000])
.unknown("#eee")
Insert cell
Insert cell
map = {
const ratio = 11 / 16;
const height = width * ratio;
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

svg
.append("g")
.selectAll("path")
//.data(topojson.feature(nutsTopo, nutsTopo.objects.nutsbn).features)
.data(nutsGeo.features)
.enter()
.append("path")
.attr("d", d3.geoPath().projection(projection))
//.attr("fill", "none")
.attr("fill", (d) => colorScale(population[d.properties.id]))
//.attr("stroke", "#666")
.attr("stroke-width", 0.1);

return svg.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