Published
Edited
Dec 9, 2020
4 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function isTransitioning(selection) {
var transitioning = false
selection.each(function() {
if(d3.active(this, 'zoom')) { transitioning = true }
})
return transitioning
}
Insert cell
colors = Object.fromEntries(countries.map(country => {
let region
let regionName
let index
Object.entries(regions).forEach(r => {
const i = r[1].findIndex(d => d == country)
if (i >= 0) {
region = r[1]
regionName = r[0]
index = i
}
})
const fraction = region.length > 1 ? (index / (region.length-1)) : 0.5
const lookup = {
europe: d3.interpolateBlues(d3.scaleLinear([0, 1], [0.7, 0.5])(fraction)),
northAmerica: d3.interpolateYlOrBr(d3.scaleLinear([0, 1], [0.5, 0.7])(fraction)),
southAmerica: d3.interpolateYlOrBr(d3.scaleLinear([0, 1], [0.5, 0.7])(fraction)),
africaMidEast: d3.interpolateGreens(d3.scaleLinear([0, 1], [0.5, 0.6])(fraction)),
asia: d3.interpolateReds(d3.scaleLinear([0, 1], [0.7, 0.5])(fraction)),
australia: d3.interpolateRdPu(d3.scaleLinear([0, 1], [0.5, 0.7])(fraction))
}
return [country, lookup[regionName]]
}))
Insert cell
area = ({
world: d3.area()
.curve(d3.curveMonotoneX) //d3.curveCardinal.tension(0.6) d3.curveMonotoneX
.x(d => x(d.year))
.y1(d => y.world(d.stack1))
.y0(d => y.world(d.stack0)),
region: data => {
const regionY = y.region(data)
return d3.area()
.curve(d3.curveMonotoneX) //d3.curveCardinal.tension(0.6) d3.curveMonotoneX
.x(d => x(d.year))
.y1(d => regionY(d.regionStack1))
.y0(d => regionY(d.regionStack0))
},
country: data => {
const countryY = y.country(data)
return d3.area()
.curve(d3.curveMonotoneX) //d3.curveCardinal.tension(0.6) d3.curveMonotoneX
.x(d => x(d.year))
.y1(d => countryY(d.value))
.y0(d => countryY(0))
}
})
Insert cell
labelLine.region(current.countries[0].years)
Insert cell
current.countries[0].years
Insert cell
findStraight = (data, mode) => {
// for each segment of five years, calculate straightness and return the straigthest segment
const straightness = Object.fromEntries(d3.range(0,data.length - 4).map(d => [d, 0]))
for (let i = 0; i < data.length - 4; i++) {
let incline
if (mode == 'world') {
incline = (data[i+4].stack0 - data[i].stack0) / 5
d3.range(i, i+5).forEach((d,n) => straightness[i] += data[i].stack0 + incline * n - data[d].stack0)
} else if (mode == 'region') {
incline = (data[i+4].regionStack0 - data[i].regionStack0) / 5
d3.range(i, i+5)
.forEach((d,n) => straightness[i] += Math.abs(data[i].regionStack0 + incline * n - data[d].regionStack0) - n)
} else {
incline = (data[i+4].value) / 5
d3.range(i, i+5)
.forEach((d,n) => straightness[i] += data[i].value + incline * n - data[d].value)
}
}
const min = +Object.entries(straightness).find(e => e[1] == d3.min(Object.entries(straightness), d => d[1]))[0]
return data.slice(min, min+5)
}
Insert cell
labelLine = {
const yOffset = 4
const heightThreshold = 8
return {
world: data => {
const lineGen = d3.line()
.curve(d3.curveMonotoneX)
.x(d => x(d.year))
.y(d => y.world(d.stack0 + (d.stack1 - d.stack0) / 2) + yOffset)
data = data.filter(d => d.year >= 2011)
const midpoint = data.find(d => d.year == 2013)
const h = y.world(midpoint.stack0) - y.world(midpoint.stack1)
return {
path: lineGen(data),
display: h > heightThreshold ? true : false
}
},
region: data => {
const regionY = y.region(data[0])
const lineGen = d3.line()
.curve(d3.curveMonotoneX)
.x(d => x(d.year))
.y(d => regionY((d.regionStack1 + d.regionStack0) / 2) + yOffset)
// data = findStraight(data)
data = data.filter(d => d.year >= 2011)
const midpoint = data[2]
const h = regionY(midpoint.regionStack0) - regionY(midpoint.regionStack1)
return {
path: lineGen(data),
display: h > heightThreshold ? true : false
}
},
country: data => {
const countryY = y.country(data[0])
const lineGen = d3.line()
.curve(d3.curveMonotoneX)
.x(d => x(d.year))
.y(d => countryY(d.value / 2) + yOffset)
data = data.filter(d => d.year >= 2011)
const midpoint = data.find(d => d.year == 2013)
const h = countryY(0) - countryY(midpoint.value)
return {
path: lineGen(data),
display: h > heightThreshold ? true : false
}
}
}
}
Insert cell
drawCountries = countries.map(c => current.countries.find(d => d.country == c).years)
Insert cell
current = drawData.find(d => d.sector == sector).parameters
.find(d => d.parameter == parameter)
Insert cell
sector = selection.slice(0, selection.indexOf(':'))
Insert cell
x = d3.scaleLinear().domain([1995,2015]).range([margin.left, width - margin.right])
Insert cell
y = ({
world: d3.scaleLinear().domain([0, current.max]).range([height - margin.bottom, margin.top]),
region: d => d3.scaleLinear().domain([0, d.regionMax]).range([height - margin.bottom, margin.top]),
country: d => d3.scaleLinear().domain([0, d.countryMax]).range([height - margin.bottom, margin.top])
})
Insert cell
margin = ({ top: 10, bottom: 30, right: 100, left: 15 })
Insert cell
height = width/1.5
Insert cell
sectors = [
{ token: "Production", display: "Production" },
{ token: "Consumption", display: "Consumption" },
{ token: "Consumption_PC", display: "Consumption per capita" },
{ token: "Production_PGDP", display: "Production per GDP" },
{ token: "Consumption_PGDP", display: "Consumption per GDP" } ]
Insert cell
parameter = selection.slice(selection.indexOf(':') +1).replace(/_/g, ' ')
Insert cell
parameters = ["GHG emissions", "Energy Use", "Material Use", "Blue Water Consumption", "Land use" ]
Insert cell
html`<style>

@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400;1,600;1,700&display=swap');

#selection {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr 1fr 1fr;
}

.row-title {
text-align: left;
border-bottom: 1px solid gray;
}

.col-title {
align-self: end;
text-align: center;
border-bottom: 1px solid gray;
}

.cell {
text-align: center;
border-bottom: 1px solid gray;
padding-bottom: 4px;
}

body {
font-family: 'Open Sans', sans-serif;
}

text {
font-family: 'Open Sans', sans-serif;
}

</style>`
Insert cell
drawData = stacked.map(sector => {
return {
sector: sector.sector,
parameters: sector.parameters.map(parameter => {
const max = d3.max(parameter.years.map(d => d3.max(d.countries, c => c.stack1)))
return {
parameter: parameter.parameter,
unit: parameter.unit,
max: max,
world: parameter.years.map(d => ({
year: d.year,
stack0: 0,
stack1: d3.max(d.countries, v => v.stack1)
})),
regions: Object.fromEntries(Object.entries(regions).map(region => [
region[0], parameter.years.map(d => ({
year: d.year,
regionStack0: 0,
regionStack1: d3.max(d.countries.filter(c => region[1].includes(c.country)), v => v.regionStack1)
}))
])),
countries: countries.map(country => {
const region = reverseRegions[country]
const regionValues = parameter.years
.map(d => d3.max(d.countries.filter(c => reverseRegions[c.country] == region), v => v.regionStack1))
const regionMax = d3.max(regionValues)
const countryMax = d3.max(parameter.years.map(d => d.countries.find(c => c.country == country).value))
return {
country: country,
worldMax: max,
regionMax: regionMax,
countryMax: countryMax,
years: parameter.years.map(year => {
const values = year.countries.find(c => c.country == country)
return {
year: year.year,
stack0: values.stack0,
stack1: values.stack1,
regionStack0: values.regionStack0,
regionStack1: values.regionStack1,
regionMax: regionMax,
countryMax: countryMax,
value: values.value,
country: values.country
}
})
}
})
}
})
}
})
Insert cell
stacked = raw.filter(d => d.sector != 'Net-trade').map(d => {
const filtered = d.values.filter(d => d.parameter != 'No Data' && d.area == 'Country')
const parameters = [...d3.group(filtered, p => p.parameter)]

return {
sector: d.sector,
parameters: parameters.map(p => ({
parameter: p[0],
unit: p[1][0].unit,
years: d3.range(1995, 2016).map(y => {
const stacks = stack(p[1], y)
return {
year: y,
countries: p[1].map(c => {
const {stack0, stack1, regionStack0, regionStack1} = stacks[c.country_name]
return {
country: c.country_name,
value: c[y],
stack0: stack0,
stack1: stack1,
regionStack0: regionStack0,
regionStack1: regionStack1
}
})
}
})
}))
}
})
Insert cell
stack = (countries, year, region=null) => {
const stackable = countries
.map(d => ({country: d.country_name, value: d[year]}))
.sort((a, b) => a.value - b.value) // important to sort descending
const data = Object.fromEntries(countries.map(d => {
return [ d.country_name,
{
value: d[year],
// stack0: '',
// stack1: '',
// regionStack0: '',
// regionStack1: ''
}
]
}))
let heap = 0
let regionHeap = Object.fromEntries(Object.keys(regions).map(d => [d, 0]))
const stacked = stackable.forEach((d,i) => {
data[d.country].stack0 = heap
data[d.country].stack1 = heap + d.value
data[d.country].regionStack0 = regionHeap[reverseRegions[d.country]],
data[d.country].regionStack1 = regionHeap[reverseRegions[d.country]] + d.value,
heap = heap + d.value
regionHeap[reverseRegions[d.country]] = regionHeap[reverseRegions[d.country]] + d.value
})
return data
}
Insert cell
raw = FileAttachment("exiobase.json").json()
Insert cell
countries = raw[0].values.filter(d => d.parameter == 'GHG emissions' && d.area == 'Country').map(d => d.country_name)
Insert cell
regions = ({
europe: ["Austria", "Belgium", "Bulgaria", "Cyprus", "Czech Republic", "Germany", "Denmark", "Estonia", "Spain", "Finland", "France", "Greece", "Croatia", "Hungary", "Ireland", "Italy", "Lithuania", "Luxembourg", "Latvia", "Malta", "Netherlands", "Poland", "Portugal", "Romania", "Sweden", "Slovenia", "Slovakia", "United Kingdom", "Russia", "Switzerland", "Norway", "RoW Europe"],
northAmerica: ["United States", "Canada", "Mexico"],
southAmerica: ["Brazil", "RoW America"],
africaMidEast: ["South Africa", "RoW Africa", "RoW Middle East"],
asia: ["Japan", "China", "South Korea", "India", "Turkey", "Taiwan", "Indonesia", "RoW Asia and Pacific"],
australia: ["Australia"]
})
Insert cell
reverseRegions = {
const map = countries.map(country => {
const region = Object.entries(regions)
.map(region => [region[0], region[1].findIndex(c => c == country)])
.filter(r => r[1] >= 0)
return [country, region[0][0]]
})
return Object.fromEntries(map)
}
Insert cell
d3 = require('d3@6')
Insert cell
import {select} from "@jashkenas/inputs"
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