Public
Edited
Apr 30
Insert cell
Insert cell
//loads the d3 library
d3 = require("d3@6")

Insert cell
//uses d3 to parse the Best Video Games of all time dataset
gamedata = d3.csvParse(await FileAttachment("data.csv").text(), d3.autoType)

Insert cell
Insert cell
platformData = {
// Count the number of games per console
const platformCounts = d3.rollup(gamedata, v => v.length, d => d.Platform);
// Convert to array of objects for easier use
const platformData = Array.from(platformCounts, ([Platform, Count]) => ({ Platform, Count }));
return platformData
}
Insert cell
margin = ({top: 40, right: 40, bottom: 40, left: 100})
Insert cell
height = 400

Insert cell
Insert cell
barchart = {
//creates svg with width x height
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);

//creates a scaleBand for categorical data
const x = d3.scaleBand()
.domain(platformData.map(d => d.Platform))
.range([margin.left, width-margin.right])
.padding(0.1);

//creates a scaleLinear for quantitative data
const y = d3.scaleLinear()
.domain([0, d3.max(platformData, d => d.Count)])
.range([height-margin.bottom, margin.top]);

//adds bars to svg
svg.selectAll("rect")
.data(platformData)
.enter()
.append("rect")
.attr("x", d => x(d.Platform))
.attr("y", d => y(d.Count))
.attr("width", x.bandwidth())
.attr("height", d => y(0) - y(d.Count))
.attr("fill", "steelblue");

//initializes xAxis and to have text rotated for easier readability
const xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x))
.call(g => g.selectAll("text")
.attr("transform", "rotate(-40)")
.attr("text-anchor", "end")
.attr("x", -5)
.attr("y", 10)
);

// initializes yAxis
const yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
// append each to the svg so they will be rendered
//adding the xAxis labels
svg.append("g")
.call(xAxis)
.call(g =>
g .select(".tick:last-of-type text")
.clone()
.attr("text-anchor", "middle")
.attr("x", -(width - margin.left - margin.right) / 2)
.attr("y", margin.bottom - 10)
.attr("font-weight", "bold")
.text("Console Type")
);
//adding the yAxis labels
svg.append("g")
.call(yAxis)
.call(g =>
g
.select(".tick:last-of-type text")
.clone()
.attr("transform", `rotate(-90)`)
.attr("text-anchor", "middle")
.attr("x", -(height - margin.top - margin.bottom) / 2)
.attr("y", -80)
.attr("font-weight", "bold")
.text("Appearances in top 100")
);
//output the graph
return svg.node()
}
Insert cell
Insert cell
lineareaChart = {

// Create the SVG for the chart
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);

// Define the scales for x and y
const xScale = d3.scaleBand()
.domain(platformData.map(d => d.Platform)) // x domain is based on Platform names
.range([margin.left, width - margin.right])
.padding(0.1);

const yScale = d3.scaleLinear()
.domain([0, d3.max(platformData, d => d.Count)]) // y domain is based on the maximum Count
.nice()
.range([height - margin.bottom, margin.top]);

// Create an area generator for the line area chart
const area = d3.area()
.x(d => xScale(d.Platform) + xScale.bandwidth() / 2) // x position of the area
.y0(height - margin.bottom) // y0 is the baseline at the bottom
.y1(d => yScale(d.Count)); // y1 is the count value

// Create a line generator for the line above the area
const line = d3.line()
.x(d => xScale(d.Platform) + xScale.bandwidth() / 2) // x position of the line
.y(d => yScale(d.Count)); // y position of the line

// Add the area path
svg.append("path")
.data([platformData])
.attr("class", "area")
.attr("d", area)
.style("fill", "lightblue")
.style("opacity", 0.5);

// Add the line path
svg.append("path")
.data([platformData])
.attr("class", "line")
.attr("d", line)
.style("fill", "none")
.style("stroke", "blue")
.style("stroke-width", 2);

// Add the x-axis at the bottom
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(xScale))
.selectAll(".tick text")
.style("text-anchor", "end")
.attr("transform", "rotate(-40)")
.style("font-size", "12px");

// Add the y-axis to the left
svg.append("g")
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(yScale));

return svg.node()
}
Insert cell
Insert cell
arcchart = {
const height = 600;
const ch = height-margin.top-50;

// Create nodes and assign platform or game type
const nodes = Array.from(
new Set(gamedata.flatMap(d => [d.Name, d.Platform]))
).map(id => ({
id,
type: gamedata.some(g => g.Platform === id) ? "platform" : "game"
}));

// Sort nodes to separate for the graph
nodes.sort((a, b) => {
if (a.type === b.type) return d3.ascending(a.id, b.id);
return a.type === "game" ? -1 : 1;
});

// Separate node groups
const gameNodes = nodes.filter(d => d.type === "game");
const platformNodes = nodes.filter(d => d.type === "platform");

// Independent x-scales for games and platforms
const gx = d3.scaleLinear()
.domain([0, gameNodes.length - 1])
.range([50, width * 0.7]);

const px = d3.scaleLinear()
.domain([0, platformNodes.length - 1])
.range([width * 0.75, width - 50]);

// Final x positions array
const xPositions = nodes.map(d =>
d.type === "game" ? gx(gameNodes.indexOf(d)) : px(platformNodes.indexOf(d))
);

// Index map and links
const nodeIndex = Object.fromEntries(nodes.map((d, i) => [d.id, i]));
const links = gamedata.map(d => ({
source: nodeIndex[d.Name],
target: nodeIndex[d.Platform]
}));

// Create responsive SVG
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("preserveAspectRatio", "xMidYMid meet")
.style("width", "100%")
.style("height", "auto")
.style("font", "10px sans-serif")
.style("overflow", "visible");

// Draw arcs
svg.append("g")
.selectAll("path")
.data(links)
.join("path")
.attr("fill", "none")
.attr("stroke", "#999")
.attr("stroke-width", 1)
.attr("d", d => {
const x1 = xPositions[d.source];
const x2 = xPositions[d.target];
const r = Math.abs(x2 - x1) / 2;
return `M${x1},${ch} A${r},${r} 0 0,1 ${x2},${ch}`;
});

// Add nodes as circles
svg.append("g")
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("cx", (d, i) => xPositions[i])
.attr("cy", ch)
.attr("r", d => d.type === "platform" ? 10 : 5) // Larger radius for platforms
.style("fill", d => d.type === "platform" ? "#007acc" : "#333")
.style("stroke", "#fff")
.style("stroke-width", 1);

// Add node labels
svg.append("g")
.selectAll("text")
.data(nodes)
.join("text")
.attr("transform", (d, i) => `translate(${xPositions[i]},${ch + 10})rotate(90)`)
.attr("text-anchor", "start")
.style("font-size", d => d.type === "platform" ? "12px" : "8px")
.style("font-weight", d => d.type === "platform" ? "bold" : "normal")
.style("fill", d => d.type === "platform" ? "#007acc" : "#333")
.text(d => d.id);

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