Public
Edited
May 3
Insert cell
Insert cell
data = d3.csvParse(await FileAttachment("data.csv").text(), d3.autoType)
Insert cell
Insert cell
function processPlat(data) {
const platformGroups = {};

data.forEach(d => {
const platform = d.Platform;
const metascore = +d.Metascore; // convert to int
if (platform in platformGroups) {
platformGroups[platform].count += 1;
platformGroups[platform].totalScore += metascore;
} else {
platformGroups[platform] = {
name: platform,
count: 1,
totalScore: metascore,
average: 0
};
}
});

const formatted = Object.values(platformGroups).map(group => {
group.average = group.totalScore/group.count;
return group;
});

formatted.sort((a, b) => b.average - a.average); // sort for graph
return formatted;
}

Insert cell
Insert cell
processedData = processPlat(data);
Insert cell
barChart = {
const margin = {top: 80, right: 20, bottom: 20, left: 150};
const width = 800;
const height = 600;

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);

const x = d3.scaleLinear() // for x-axis labels and scale
.domain([0, 100])
.range([margin.left, width - margin.right]);

const y = d3.scaleBand() // for y-axis labels and scale
.domain(processedData.map(d => d.name))
.range([margin.top, height - margin.bottom])
.padding(0.2);

svg.append("g") // adds x-axis to graph
.attr("transform", `translate(0,${margin.top})`)
.call(d3.axisTop(x))
.attr("font-family", "Verdana")
.attr("font-size", 14);

svg.append("g") // adds y-axis to graph
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.attr("font-family", "Verdana")
.attr("font-size", 14);

svg.selectAll("rect") // setting the bars
.data(processedData)
.join("rect")
.attr("x", margin.left)
.attr("y", d => y(d.name))
.attr("width", d => x(d.average) - margin.left)
.attr("height", y.bandwidth())
.attr("fill", "steelblue");

svg.append("text") // description of graph
.attr("x", width / 2)
.attr("y", margin.top / 2)
.attr("font-family", "Verdana")
.attr("text-anchor", "middle")
.attr("font-size", 16)
.text("Average Metascore by Platform");

return svg.node();
}
Insert cell
Insert cell
Insert cell
pieChart = {
const width = 700;
const height = 600;
const radius = Math.min(width, height) / 2;

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.style("position", "relative");

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

const color = d3.scaleOrdinal()
.domain(processedData.map(d => d.name))
.range(d3.schemeCategory10);

const pie = d3.pie().value(d => d.count);
const data_ready = pie(processedData);

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

const tooltip = d3.create("div") // setting up tooltip
.style("position", "absolute")
.style("pointer-events", "none")
.style("opacity", 0)
.style("background", "white")
.style("border", "1px solid #ccc")
.style("border-radius", "4px")
.style("padding", "6px 10px")
.style("font-size", "12px")
.style("font-family", "Verdana, sans-serif")
.style("box-shadow", "0 2px 6px rgba(0,0,0,0.2)");

const wrapper = html`<div style="position: relative;">${svg.node()}${tooltip.node()}</div>`;

chartGroup.selectAll("path")
.data(data_ready)
.join("path")
.attr("d", arc)
.attr("fill", d => color(d.data.name))
.attr("stroke", "white")
.attr("stroke-width", 1)
.on("mouseenter", function(event, d) {
tooltip.style("opacity", 1)
.html(`<strong>${d.data.name}</strong><br>${d.data.count} game(s)`);
})
.on("mousemove", function(event) {
tooltip.style("left", `${event.offsetX + 10}px`)
.style("top", `${event.offsetY - 30}px`);
})
.on("mouseleave", function() {
tooltip.style("opacity", 0);
});

chartGroup.selectAll("text.internal-label") // labels inside
.data(data_ready)
.join("text")
.attr("class", "internal-label")
.attr("transform", d => `translate(${arc.centroid(d)})`)
.style("text-anchor", "middle")
.attr("font-family", "Verdana")
.style("font-size", 10)
.text(d => d.data.name);

return wrapper;
}

Insert cell
Insert cell
function processByYear(data) {
const platformYearMap = {};

data.forEach(d => {
const match = d.Date.match(/\d{2,4}$/); // extracting year from date
if (!match) return;

let year = parseInt(match[0]);
year += (year < 25 ? 2000 : 1900); // convert 2 digit years

const platform = d.Platform;

const key = `${year}-${platform}`;
if (platformYearMap[key]) {
platformYearMap[key].count += 1;
} else {
platformYearMap[key] = {
year,
platform,
count: 1
};
}
});

return Object.values(platformYearMap);
}
Insert cell
Insert cell
yearData = processByYear(data)

Insert cell
Insert cell
function convertStreamData(streamData) {
const platforms = Array.from(new Set(streamData.map(d => d.platform)));
const years = Array.from(new Set(streamData.map(d => d.year))).sort((a, b) => a - b);

const yearPlatformMap = {};
years.forEach(year => {
yearPlatformMap[year] = { year };
platforms.forEach(p => yearPlatformMap[year][p] = 0);
});

streamData.forEach(({ year, platform, count }) => {
if (yearPlatformMap[year]) {
yearPlatformMap[year][platform] = count;
}
});

return {
data: Object.values(yearPlatformMap),
platforms
};
}

Insert cell
Insert cell
Insert cell
Insert cell
streamgraph = {
const margin = {top: 60, right: 30, bottom: 30, left: 50};
const width = 800;
const height = 400;

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height);

const x = d3.scaleLinear()
.domain(d3.extent(streamData.data, d => d.year))
.range([margin.left, width - margin.right]);

const y = d3.scaleLinear().range([height - margin.bottom, margin.top]);

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

const stack = d3.stack()
.keys(streamData.platforms)
.offset(d3.stackOffsetNone);

const stacked = stack(streamData.data);

y.domain([
d3.min(stacked, layer => d3.min(layer, d => d[0])),
d3.max(stacked, layer => d3.max(layer, d => d[1]))
]);

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

svg.append("g")
.selectAll("path")
.data(stacked)
.join("path")
.attr("fill", ({ key }) => color(key))
.attr("d", area)
.append("title")
.text(d => d.key);

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

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

const legend = svg.append("g") // horizontal legend at the top
.attr("font-family", "Verdana")
.attr("font-size", 11)
.attr("text-anchor", "start")
.attr("transform", `translate(${margin.left}, ${margin.top - 40})`);

let xOffset = 0;
let yOffset = 0;
const rowHeight = 18;
const maxRowWidth = width - margin.left - margin.right;
const padding = 12;

streamData.platforms.forEach((platform, i) => {
const labelWidth = platform.length * 7 + 32; // rough width estimate
if (xOffset + labelWidth > maxRowWidth) {
xOffset = 0;
yOffset += rowHeight;
}

const g = legend.append("g").attr("transform", `translate(${xOffset}, ${yOffset})`);

g.append("rect")
.attr("width", 12)
.attr("height", 12)
.attr("fill", color(platform));

g.append("text")
.attr("x", 16)
.attr("y", 10)
.text(platform);

xOffset += labelWidth + padding;
});
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