Public
Edited
May 1
Insert cell
Insert cell
d3 = require("d3@6")
Insert cell
// load data.csv
data = d3.csvParse(
await FileAttachment("data.csv").text(),
d3.autoType
)
Insert cell
// count number of each platform
platformCounts = Array.from(
d3.rollup(data, v => v.length, d => d.Platform),
([platform, count]) => ({platform, count})
)

Insert cell
Insert cell
Insert cell
chartPie = {
const width = 1200;
const height = 800;
const radius = 350;

const container = DOM.element("div");
d3.select(container)
.style("position", "relative")
.style("width", `${width}px`)
.style("height", `${height}px`);

// svg
const svg = d3.select(container)
.append("svg")
.attr("width", width)
.attr("height", height);

// layout & data
const pieData = d3.pie()
.value(d => d.count)
.sort(null)(platformCounts);

const arcGen = d3.arc()
.innerRadius(0)
.outerRadius(radius);

const platforms = platformCounts.map(d => d.platform);
const color = d3.scaleOrdinal()
.domain(platforms)
.range(d3.quantize(t => d3.interpolateRainbow(t), platforms.length));

const pieGroup = svg.append("g")
.attr("transform", `translate(${width/2},${height/2})`);

// draw slices
pieGroup.selectAll("path")
.data(pieData)
.join("path")
.attr("d", arcGen)
.attr("fill", d => color(d.data.platform))
.attr("stroke", "#fff")
.attr("stroke-width", 1);

// add labels vertically along each slice's direction(save space for easy reading)
pieGroup.selectAll("text")
.data(pieData)
.join("text")
.attr("transform", d => {
const [x, y] = arcGen.centroid(d);
// compute slice mid-angle in degrees
const midAngle = (d.startAngle + d.endAngle) / 2 * 180 / Math.PI;
return `translate(${x},${y}) rotate(${midAngle})`;
})
.style("writing-mode", "vertical-rl") // vertical text flow
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("font-size", "12px")
.attr("fill", "#000")
.text(d => d.data.platform);

// legend
const legend = svg.append("g")
.attr("transform", `translate(${width - 220},20)`);

legend.selectAll("g")
.data(platformCounts)
.join("g")
.attr("transform", (_, i) => `translate(0, ${i * 20})`)
.call(g => {
g.append("rect")
.attr("width", 12)
.attr("height", 12)
.attr("fill", d => color(d.platform));
g.append("text")
.attr("x", 16)
.attr("y", 6)
.attr("dy", "0.35em")
.text(d => d.platform);
});

return container;
}

Insert cell
Insert cell
// Metascore >= 96
platformHigh96 = Array.from(
d3.rollup(
data.filter(d => d.Metascore >= 96),
v => v.length,
d => d.Platform
),
([platform, count]) => ({ platform, count })
).sort((a, b) => b.count - a.count);
Insert cell
Insert cell
Insert cell
chartHigh96 = {
const series = platformHigh96;
const width = 800;
const barHeight = 25;
const margin = { top: 40, right: 20, bottom: 40, left: 200 };
const height = margin.top + margin.bottom + series.length * barHeight;

const svg = d3.select(DOM.svg(width, height));

// x
const x = d3.scaleLinear()
.domain([0, d3.max(series, d => d.count)]).nice()
.range([margin.left, width - margin.right]);

// y
const y = d3.scaleBand()
.domain(series.map(d => d.platform))
.range([margin.top, height - margin.bottom])
.padding(0.1);

// horizon bar
svg.append("g")
.selectAll("rect")
.data(series)
.join("rect")
.attr("x", margin.left)
.attr("y", d => y(d.platform))
.attr("height", y.bandwidth())
.attr("width", d => x(d.count) - margin.left)
.attr("fill", "steelblue");

// label
svg.append("g")
.selectAll("text")
.data(series)
.join("text")
.attr("x", d => x(d.count) + 4)
.attr("y", d => y(d.platform) + y.bandwidth() / 2)
.attr("dy", "0.35em")
.text(d => d.count);

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

// x
svg.append("g")
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(5))
.append("text")
.attr("x", width - margin.right)
.attr("y", 30)
.attr("fill", "currentColor")
.attr("text-anchor", "end")

svg.append("text")
.attr("x", width / 2)
.attr("y", margin.top / 2)
.attr("text-anchor", "middle")
.attr("font-size", "16px")
.text("Metascore ≥ 96");

return svg.node();
}
Insert cell
Insert cell
Insert cell
chartParallelDateHigh96 = {
const width = 900;
const height = 500;
const margin = { top: 40, right: 40, bottom: 40, left: 150 };

// sort the platform by the order of increase 96+ game number, from part 2 bar chart
const platformOrder = [
"Xbox 360",
"PC",
"PlayStation 3",
"Switch",
"PlayStation 2",
"GameCube",
"Nintendo 64",
"PlayStation",
"Dreamcast",
"Wii",
"Xbox One",
"PlayStation 4",
"Xbox",
"Wii U",
"Xbox Series X",
"PlayStation 5"
];

// get release year
const parseDate = d3.timeParse("%d-%b-%y");
const cleanHigh = data
.map(d => ({
Platform: d.Platform,
Metascore: +d.Metascore,
Year: parseDate(d.Date).getFullYear()
}))
.filter(d => d.Metascore >= 96);

// 3 dimension
const dimensions = ["Platform", "Metascore", "Year"];

// vertical scales
const y = {
Platform: d3.scalePoint()
.domain(platformOrder.filter(p => cleanHigh.some(d => d.Platform === p)))
.range([height - margin.bottom, margin.top]),

Metascore: d3.scaleLinear()
.domain(d3.extent(cleanHigh, d => d.Metascore)).nice()
.range([height - margin.bottom, margin.top]),

Year: d3.scaleLinear()
.domain(d3.extent(cleanHigh, d => d.Year)).nice()
.range([height - margin.bottom, margin.top])
};

// horizontal scales
const x = d3.scalePoint()
.domain(dimensions)
.range([margin.left, width - margin.right]);

// lines
const line = d3.line()
.x(([dim, v]) => x(dim))
.y(([dim, v]) => y[dim](v));

// svg
const svg = d3.select(DOM.svg(width, height));

svg.append("g")
.selectAll("path")
.data(cleanHigh)
.join("path")
.attr("d", d => line(dimensions.map(dim => [dim, d[dim]])))
.attr("fill", "none")
.attr("stroke", "#eee");

svg.append("g")
.selectAll("path")
.data(cleanHigh)
.join("path")
.attr("d", d => line(dimensions.map(dim => [dim, d[dim]])))
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-opacity", 0.5)
.attr("stroke-width", 1);

// Dvertical axes and labels
dimensions.forEach(dim => {
const g = svg.append("g")
.attr("transform", `translate(${x(dim)},0)`);
if (dim === "Platform") {
g.call(d3.axisLeft(y.Platform));
} else {
g.call(
d3.axisLeft(y[dim])
.ticks(6)
.tickFormat(d3.format("d"))
);
}
g.append("text")
.attr("y", margin.top - 20)
.attr("text-anchor", "middle")
.attr("fill", "currentColor")
.text(dim);
});

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