Public
Edited
Apr 18, 2023
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
resolutions_by_year = raw_resolutions
.groupby('year').count().rename({ count: 'resolutions' })
.orderby('year')
Insert cell
Insert cell
data = resolutions_by_year
// .join_left(vetos_by_year)
.join_left(vetos_by_member)
// For graphic order
.select(
"year",
"resolutions",
"vetos",
"russia_ussr",
"usa",
"uk",
"france",
"china"
)
.orderby("year")
Insert cell
Insert cell
Insert cell
Insert cell
meta = ({
title: "Résolutions adoptées et vetos apposés",
subtitle: "au Conseil de sécurité des Nations unies, de 1946 à mars 2023",
label: "Émetteurs des vetos",
source: "Source : Nations unies, www.un.org",
credit: "© Atelier de cartographie / Sciences Po, 2021",
note: "Par veto sont entendus les projets de résolutions ou paragraphes non adoptés",
note2: "en raison du vote négatif d'au moins un membre permanent",
note3: "lors des sessions publiques du Conseil de sécurité."
})
Insert cell
labels = new Map([
["resolutions", "Résolutions adoptées"],
["vetos", "Vetos apposés"],
["russia_ussr", "URSS / Russie"],
["usa", "États-Unis"],
["uk", "Royaume-Uni"],
["france", "France"],
["china", "Chine"]
])
Insert cell
dim = ({
width: 600,
height: 700,
barMaxHeight: barMaxHeight,
rowPadding: rowPadding,
margin: { top: 100, left: 20, right: 200, bottom: 20 }
})
Insert cell
x = d3.scaleLinear()
.domain(d3.extent(data.columnArray('year')))
.range([dim.margin.left, dim.width - dim.margin.right])
Insert cell
y = d3.scaleLinear()
.domain([0, d3.max(data.columnArray('resolutions'))])
.range([dim.barMaxHeight, 0])
Insert cell
Insert cell
rows = {
const rows = [],
max = (string) => d3.max(data.columnArray(string)),
yinvert = d3
.scaleLinear()
.domain([0, max("resolutions")])
.range([0, dim.barMaxHeight]), // !! range normal (non-inversé)
names = data.columnNames().slice(1); // toutes les colonnes sauf la première

let last = 0;

names.forEach((e) => {
let actual =
last +
(e == "resolutions"
? 0 // 1er graph = 0
: e == "russia_ussr"
? yinvert(max(e)) + rowPadding * 2 // espace supplémentaire
: yinvert(max(e)));
let row = { name: e, y: actual };
last = actual;
rows.push(row);
});
return rows;
}
Insert cell
Insert cell
Insert cell
Insert cell
graph = {
// Dimensions
const w = dim.width,
h = dim.height,
m = dim.margin
// OSSATURE SVG
let viz = html`
<svg width=${w} height=${h} viewBox="0,0,${w},${h}" class='bg' id='viz'>
<text id='title' y=10>
<tspan x=10>${meta.title}</tspan>
<tspan x=10 dy=20>${meta.subtitle}</tspan>
</text>

<text id="note" y=60>
<tspan x=10>${meta.note}</tspan>
<tspan x=10 dy=12>${meta.note2}</tspan>
<tspan x=10 dy=12>${meta.note3}</tspan>
</text>

<rect id="vetos-bg"
x=0 y=${m.top - rowPadding + y(0) + rows[2].y}
width=${w} height=${h - y(0) + rows[2].y}></rect>

<text class="label-row-extra"
x=${x(2024)}
y=${m.top + y(0) + rows[2].y}>${meta.label}</text>

<text id="source" x=${10} y=${h - 10}>${meta.source}</text>

<text id="copyright" transform="translate(${w - 10}, ${h - 10}) rotate(-90)">${meta.credit}</text>
</svg>
`
const svg = d3.select(viz)

for (const [i, r] of rows.entries()) {
const g = svg.append('g')
.attr("id", r.name)
.attr("transform", `translate(${[0, m.top + r.y + i*rowPadding]})`)

// Axe Y (une seule fois)
if (r.name == "resolutions")
g.append("g")
.attr("id", "axisY")
.attr("transform", `translate(${[m.left, 0]})`)
.call(
d3
.axisLeft()
.scale(y)
.tickSize(-(w - m.left - m.right))
)

// Barres
g.selectAll('rect')
.data(data)
.join('rect')
.attr('x', d => x(d.year))
.attr('y', d => y(d[r.name]))
.attr('height', d => y(0) - y(d[r.name]))
.attr('fill', r.name == 'resolutions' ? "#05c5f0" : r.name == 'vetos' ? "#f04405" : "#ff6c36")
.attr('width', barWidth)

// Labels
g.append('text')
.attr('class', 'label-row')
.attr('x', x(2024))
.attr('y', y(0))
.attr('font-weight', r.name == 'resolutions' || r.name == 'vetos' ? "bold" : "regular")
.text(labels.get(r.name))

// Axe X
g.append("g")
.attr("id", "axisX")
.attr("transform", `translate(${[barWidth/2, y(0)]})`)
.call(
d3
.axisBottom()
.scale(x)
.ticks(r.name == "resolutions" || r.name == "vetos" || r.name == "China" ? 16 : 0)
.tickFormat(d => d%2 == 0 ? d.toString() : null)
.tickSize(3)
)
}

// STYLE
svg.append('defs')
.html(`<style>
#viz.bg { background-color: white; }
#viz text { font-family: sans-serif; }
text#title {
font-size: 17px;
font-weight: bold;
dominant-baseline: hanging;
}
text#copyright, text#source, text#note {
font-size: 12px;
dominant-baseline: auto;
}
.label-row, .label-row-extra {
font-size: 14px;
dominant-baseline: auto;
}
.label-row-extra { font-weight: bold; }
#vetos-bg { fill: #ffebe3;}
#axisY .tick line { stroke-width: 0.2; }
#axisY path.domain { display: none; }
#axisX path.domain { color: grey; }
</style>`)

return viz
}
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