Public
Edited
Apr 30
Insert cell
Insert cell
file = FileAttachment("data.csv").csv()
Insert cell
processed = file.filter(d => d.Metascore !== undefined && d.Metascore !== "")
.map(d=> ({
name : d.Name,
platform: d.Platform,
metascore : d.Metascore,
year : new Date(d.Date).getFullYear(), //extracting year
description: d.Title
}))
Insert cell
Insert cell
avgMetascoreByPlatform = Array.from(
d3.rollup(
processed,
v => d3.mean(v, d => d.metascore),
d => d.platform
),
([platform, avgMetascore]) => ({platform, avgMetascore})
)
Insert cell
Insert cell
Insert cell
chart = {
const width = 400;
const height = 200;
const marginTop = 30;
const marginRight = 30;
const marginBottom = 70;
const marginLeft = 80;

// set x scale for platform names
const x = d3.scaleBand()
.domain(avgMetascoreByPlatform.map(d => d.platform))
.range([marginLeft, width - marginRight])
.padding(0.1);

// set y sccale for scores
const y = d3.scaleLinear()
.domain([93, d3.max(avgMetascoreByPlatform, d => d.avgMetascore)]).nice()
.range([height - marginBottom, marginTop]);

// create svg canvas
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

// draw bars for each platform
svg.append("g")
.selectAll("rect")
.data(avgMetascoreByPlatform)
.join("rect")
.attr("x", d => x(d.platform))
.attr("y", d => y(d.avgMetascore))
.attr("height", d => y(93) - y(d.avgMetascore))
.attr("width", x.bandwidth())
.attr("fill", "steelblue");

// add x-axis with rotated platform names
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "rotate(-45)")
.style("text-anchor", "end")
.style("font-size", "6px");

// add x axis label
svg.append("text")
.attr("x", (width + marginLeft - marginRight)/2)
.attr("y", height - 10)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.text("Platform");

// add y axis
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y))
.selectAll("text")
.style("font-size", "8px");

// add y axis label
svg.append("text")
.attr("x", -(height/2) + 15)
.attr("y", 30)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.attr("transform", "rotate(-90)")
.text("Average Metascore");
return svg.node();
}
Insert cell
Insert cell
filteredProcessed = processed.filter(d =>
["PC", "PlayStation 3", "Xbox 360", "PlayStation 2", "Wii", "Switch"].includes(d.platform)
);

Insert cell
selectedPlatforms = ["PC", "PlayStation 3", "Xbox 360", "PlayStation 2", "Wii", "Switch"]

Insert cell

streamData = Array.from(
d3.rollup(
filteredProcessed,
v => v.length,
d => d.year,
d => d.platform
),
([year, platformMap]) => {
const row = { year };
for (const platform of selectedPlatforms) {
row[platform] = platformMap.get(platform) || 0; // fill missing with 0
}
return row;
}
).sort((a, b) => a.year - b.year);

Insert cell
Insert cell
Insert cell
legend = {
const svg = d3.create("svg")
.attr("width", 800)
.attr("height", 30);

const color = d3.scaleOrdinal()
.domain(selectedPlatforms)
.range(d3.schemeCategory10);

svg.selectAll("rect")
.data(selectedPlatforms)
.join("rect")
.attr("x", (d, i) => i * 130)
.attr("y", 5)
.attr("width", 12)
.attr("height", 12)
.attr("fill", d => color(d));

svg.selectAll("text")
.data(selectedPlatforms)
.join("text")
.attr("x", (d, i) => i * 130 + 16)
.attr("y", 15)
.text(d => d)
.attr("font-size", "10px")
.attr("alignment-baseline", "middle");

return svg.node();
}

Insert cell
Insert cell
streamChart = {
const width = 800;
const height = 400;
const marginTop = 20;
const marginRight = 20;
const marginBottom = 30;
const marginLeft = 50;

// get list of platform names
const platforms = Object.keys(streamData[0]).filter(k => k !== "year");

// stack data by platform using stream offset
const stack = d3.stack()
.keys(platforms)
.order(d3.stackOrderNone)
.offset(d3.stackOffsetWiggle); // creates stream-like flow

const series = stack(streamData);

// x scale map year to horizontal pos
const x = d3.scaleLinear()
.domain(d3.extent(streamData, d => d.year))
.range([marginLeft, width - marginRight]);

// y scale defines vertical extent of stacked series
const y = d3.scaleLinear()
.domain([
d3.min(series, s => d3.min(s, d => d[0])),
d3.max(series, s => d3.max(s, d => d[1]))
])
.range([height - marginBottom, marginTop]);

// color scale
const color = d3.scaleOrdinal()
.domain(platforms)
.range(d3.schemeCategory10);

// area generator
const area = d3.area()
.x(d => x(d.data.year))
.y0(d => y(d[0]))
.y1(d => y(d[1]));

const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

// draw each layer in stream graph
svg.selectAll("path")
.data(series)
.join("path")
.attr("fill", d => color(d.key))
.attr("d", area);

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x).ticks(10).tickFormat(d3.format("d")));

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


return svg.node();
}

Insert cell
Insert cell
heatmapData = Array.from(
d3.rollup(
processed,
v => v.length, // count of games
d => d.platform, // group by platform
d => d.year // then by year
),
([platform, yearMap]) =>
Array.from(yearMap, ([year, count]) => ({
platform,
year,
count
}))
).flat();

Insert cell
Insert cell
Insert cell
heatmapChart = {
const width = 800;
const height = 500;
const marginTop = 40;
const marginRight = 30;
const marginBottom = 80;
const marginLeft = 100;

// x scale: platforms
const x = d3.scaleBand()
.domain([...new Set(heatmapData.map(d => d.platform))])
.range([marginLeft, width - marginRight])
.padding(0.05);

// y Scale: years
const y = d3.scaleBand()
.domain([...new Set(heatmapData.map(d => d.year))].sort((a, b) => b - a)) // reversed order
.range([marginTop, height - marginBottom])
.padding(0.05);


// Color scale: number of games
const color = d3.scaleSequential()
.interpolator(d3.interpolateBlues)
.domain([0, d3.max(heatmapData, d => d.count)]);

//create svg canvas
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

// Draw rectangles
svg.append("g")
.selectAll("rect")
.data(heatmapData)
.join("rect")
.attr("x", d => x(d.platform))
.attr("y", d => y(d.year))
.attr("width", x.bandwidth())
.attr("height", y.bandwidth())
.attr("fill", d => color(d.count));

// x Axis
svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "rotate(-40)")
.style("text-anchor", "end");

// y Axis
svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y).tickFormat(d3.format("d")));

// x axis label
svg.append("text")
.attr("x", (width + marginLeft - marginRight)/2)
.attr("y", height - 10)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.text("Platform");

// y axis label
svg.append("text")
.attr("x", -(height/2) + 15)
.attr("y", 30)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.attr("transform", "rotate(-90)")
.text("Year");

return svg.node();
}


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