Public
Edited
Nov 24
Insert cell
Insert cell
// Built on from Vishal Devireddy - https://observablehq.com/@twotau/serpentine-bar-chart
// source for SIPRI https://www.sipri.org/media/press-release/2024/global-military-spending-surges-amid-war-rising-tensions-and-insecurity
{
const xOffset = 15;
const svgWidth = 400; //"100%"; //680;
const barHeight = 25;
const radius = 18;
const standardVerticalSpacePerBar = 0; //40
const extraSpaceForTopBar = 335; // Increased to add padding for title and subtitle
const extraSpaceForBottom = 120; // Added space for the caption and bottom padding
const svgHeight =
(dataSnake.length - 1) * standardVerticalSpacePerBar +
extraSpaceForTopBar +
extraSpaceForBottom;

const svg = d3
.create("svg")
.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`) // Adjusted svgHeight
.attr("width", "100%") //100
.attr("height", svgHeight) // Adjusted height
.style("display", "block")
.style("margin", "auto");

// Add title
svg
.append("text")
.attr("x", 40) // Centered
.attr("y", 30) // Position at the top
.attr("text-anchor", "middle")
.style("font-size", "24px")
.style("font-weight", "bold")
.text("Global military spending in 2023 was about 8x larger")
.append("tspan")
.attr("x", 33) // Align with the same X position
.attr("dy", "1.2em") // Offset to the next line
.text("than the newly agreed annual climate finance goal*");

// Add subtitle
svg
.append("text")
.attr("x", -105) // Centered
.attr("y", 90) // Position below the title
.attr("text-anchor", "middle")
.style("font-size", "18px")
.style("font-style", "italic")
.text("Outcome of COP29. Excludes inflation.");

const secondLargestValue = dataSnake[1].num;
const xScale = d3
.scaleLinear()
.domain([0, 2440])
.range([xOffset, 100]) // svgWidth
.nice();

// Adjust startY calculation to include extra space for title and subtitle
dataSnake.forEach((dataSnake, index) => {
const startY =
index === 0
? radius * 2 + 20 + 70 // Add 40 to move the first bar down
: radius * 2 +
extraSpaceForTopBar +
(index - 1) * standardVerticalSpacePerBar +
20;

const { d, x, y } = serpentineBar(
dataSnake.num * 1,
300,
radius,
xOffset,
startY,
0
);

svg
.append("path")
.attr("d", d)
.attr("fill", "none")
//.attr("stroke", "#2f5d9e")
.attr("stroke", index === 0 ? "#2f5d9e" : "#e55592")
.attr("stroke-width", barHeight)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "butt");

svg
.append("text")
.attr("x", xOffset - 10)
.attr("y", startY + 5)
.text(dataSnake.course)
.attr("alignment-baseline", "middle")
.style("text-anchor", "end")
.style("font-size", "18px");
// Format the labels for trillion and billion
const formattedLabel =
dataSnake.num >= 1000
? `$${(dataSnake.num / 1000).toFixed(2)} trillion`
: `$${dataSnake.num} billion`;

svg
.append("text")
.attr("x", x + 5)
.attr("y", y + 5)
.text(formattedLabel) // Use the formatted label
.attr("alignment-baseline", "middle")
.style("text-anchor", "start")
.style("font-size", "18px");

// ignore this code - was for something else

// Directly access the UK data using its known index
const ukData = dataSnake[15]; // Access the UK's data at index 15

// Calculate startY for the UK based on its position, which is index 15 in your case
const ukStartY =
radius * 2 +
extraSpaceForTopBar +
(15 - 1) * standardVerticalSpacePerBar +
20;

//Use the ukData to draw your elements
const ukResult = serpentineBar(
2534 / 107,
500,
radius,
xOffset,
ukStartY, // Correctly calculated startY based on the UK's position
0
);

//Add annotation
svg
.append("text")
.attr("x", ukResult.x + 60)
.attr("y", ukResult.y + 5)
.text(dataSnake.annotation)
.attr("alignment-baseline", "middle")
.style("text-anchor", "start")
.style("font-size", "14px");
});

//Add caption
svg
.append("text")
.attr("x", 80)
.attr("y", svgHeight - 20) // Position at the bottom
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text(
"Source: Leo Hickman; Stockholm International Peace Research Institute (SIPRI) • Graphic: Yusuf Imaad Khan"
);

return svg.node();
}
Insert cell
dataSnake = [
["Global Military Spending (2023)", 2440],
["New Climate Finance Goal", 300]
]
.map(([course, num, annotation]) => ({ course, num, annotation }))
.sort((a, b) => b.num - a.num)
Insert cell
Insert cell
Insert cell
Insert cell
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