Public
Edited
Sep 30, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
data = FileAttachment("players_20.csv").csv()
Insert cell
stackedBarChart = {
// dimensions and margins of the graph
const margin = { top: 60, right: 30, bottom: 60, left: 70 };
const width = 800 - margin.left - margin.right;
const height = 600 - margin.top - margin.bottom;

// SVG element
const svg = d3
.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);

// Append a group element
const g = svg
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);

// Title
svg
.append("text")
.attr("x", (width + margin.left + margin.right) / 2)
.attr("y", margin.top / 2)
.attr("text-anchor", "middle")
.attr("font-size", "20px")
.attr("font-weight", "bold")
.text("Top 10 Clubs: Player Positions and Values");

// Data for the top 10 clubs
const data = [
{
club: "Atlético Madrid",
Defender: 164850000,
Forward: 131100000,
Goalkeeper: 88975000,
Midfielder: 205450000
},
{
club: "Borussia Dortmund",
Defender: 117325000,
Forward: 140710000,
Goalkeeper: 39320000,
Midfielder: 234970000
},
{
club: "FC Barcelona",
Defender: 206250000,
Forward: 298800000,
Goalkeeper: 99350000,
Midfielder: 264900000
},
{
club: "FC Bayern München",
Defender: 162500000,
Forward: 236100000,
Goalkeeper: 41175000,
Midfielder: 249000000
},
{
club: "Juventus",
Defender: 160875000,
Forward: 252600000,
Goalkeeper: 63300000,
Midfielder: 258700000
},
{
club: "Liverpool",
Defender: 184100000,
Forward: 236700000,
Goalkeeper: 72650000,
Midfielder: 214200000
},
{
club: "Manchester City",
Defender: 193200000,
Forward: 264500000,
Goalkeeper: 83350000,
Midfielder: 258000000
},
{
club: "Manchester United",
Defender: 147375000,
Forward: 220350000,
Goalkeeper: 78025000,
Midfielder: 218400000
},
{
club: "Paris Saint-Germain",
Defender: 182950000,
Forward: 338000000,
Goalkeeper: 63300000,
Midfielder: 196950000
},
{
club: "Real Madrid",
Defender: 184350000,
Forward: 266400000,
Goalkeeper: 94500000,
Midfielder: 247500000
}
];

// Positions
const positions = ["Defender", "Forward", "Goalkeeper", "Midfielder"];

// Stack the data by positions
const stack = d3.stack().keys(positions)(data);

// X axis: Clubs
const x = d3
.scaleBand()
.domain(data.map((d) => d.club))
.range([0, width])
.padding([0.2]);

// Y axis: Total value in EUR
const y = d3
.scaleLinear()
.domain([
0,
d3.max(data, (d) => d.Defender + d.Forward + d.Goalkeeper + d.Midfielder)
])
.range([height, 0]);

// Colors for each position
const color = d3
.scaleOrdinal()
.domain(positions)
.range(["#ff7f0e", "#1f77b4", "#2ca02c", "#d62728"]);

// Add X axis
g.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(x));

// Add X axis label
g.append("text")
.attr("text-anchor", "middle")
.attr("x", width / 2)
.attr("y", height + margin.bottom - 15)
.attr("class", "axis-label")
.text("Club");

// Add Y axis
g.append("g").call(d3.axisLeft(y));

// Add Y axis label
g.append("text")
.attr("text-anchor", "middle")
.attr("x", -height / 2)
.attr("y", -margin.left + 20)
.attr("transform", "rotate(-90)")
.attr("class", "axis-label")
.text("Total Value (EUR)");

// Create stacked bars
g.selectAll("g.stack")
.data(stack)
.enter()
.append("g")
.attr("fill", (d) => color(d.key))
.selectAll("rect")
.data((d) => d)
.enter()
.append("rect")
.attr("x", (d) => x(d.data.club))
.attr("y", (d) => y(d[1]))
.attr("height", (d) => y(d[0]) - y(d[1]))
.attr("width", x.bandwidth());

// Tooltip for better interaction
const tooltip = d3
.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0)
.style("position", "absolute")
.style("background", "lightsteelblue")
.style("padding", "5px")
.style("border-radius", "5px")
.style("pointer-events", "none");

g.selectAll("rect")
.on("mouseover", (event, d) => {
tooltip.transition().duration(200).style("opacity", 0.9);
tooltip
.html(
`Club: ${d.data.club}<br>Position: ${
positions[stack.indexOf(d)]
}<br>Value: €${d[1] - d[0]}`
)
.style("left", event.pageX + 5 + "px")
.style("top", event.pageY - 28 + "px");
})
.on("mouseout", () => {
tooltip.transition().duration(500).style("opacity", 0);
});

// Legend for player positions
const legend = svg
.append("g")
.attr("transform", `translate(${width - 150}, ${margin.top})`);

positions.forEach((pos, i) => {
legend
.append("rect")
.attr("x", 0)
.attr("y", i * 20)
.attr("width", 10)
.attr("height", 10)
.style("fill", color(pos));

legend
.append("text")
.attr("x", 20)
.attr("y", i * 20 + 9)
.text(pos)
.attr("alignment-baseline", "middle")
.style("font-size", "12px");
});

return svg.node();
}

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