{
const margin = {top: 40, right: 80, bottom: 60, left: 60};
const width = 800;
const height = 600;
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const platforms = [...new Set(games.map(d => d.platform))].sort();
const allPlatforms = "All Platforms";
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto;");
const controls = svg.append("g")
.attr("transform", `translate(${margin.left}, 20)`);
controls.append("foreignObject")
.attr("width", 120)
.attr("height", 30)
.append("xhtml:select")
.attr("id", "platform-filter")
.style("width", "100%")
.style("padding", "4px")
.style("border-radius", "4px")
.style("border", "1px solid #ccc")
.style("font-size", "12px")
.style("background", "#f8f8f8")
.on("change", function() {
updateChart(this.value);
})
.selectAll("option")
.data([allPlatforms, ...platforms])
.enter()
.append("option")
.attr("value", d => d)
.text(d => d);
// Create chart group
const chart = svg.append("g")
.attr("transform", `translate(${margin.left},${margin.top + 20})`);
// Create scales with focused y-axis (90-100)
const x = d3.scaleLinear()
.domain(d3.extent(games, d => d.year))
.range([0, innerWidth])
.nice();
const y = d3.scaleLinear()
.domain([90, 100])
.range([innerHeight, 0]);
// Vibrant color scale that makes points stand out
const color = d3.scaleOrdinal()
.domain(platforms)
.range([
"#FF6B6B", "#4ECDC4", "#45B7D1", "#FFA07A", "#98D8C8",
"#F06292", "#7986CB", "#9575CD", "#64B5F6", "#4DB6AC",
"#81C784", "#FFD54F", "#FF8A65", "#A1887F", "#90A4AE"
]);
// Add axes with better styling
chart.append("g")
.attr("transform", `translate(0,${innerHeight})`)
.call(d3.axisBottom(x).tickFormat(d3.format("d")))
.style("font-size", "10px")
.call(g => g.append("text")
.attr("x", innerWidth)
.attr("y", 30)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text("Year"));
chart.append("g")
.call(d3.axisLeft(y).ticks(5))
.style("font-size", "10px")
.call(g => g.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -40)
.attr("dy", "0.71em")
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text("Metascore (90-100)"));
// Add grid lines with better styling
chart.append("g")
.attr("class", "grid")
.call(d3.axisLeft(y)
.tickSize(-innerWidth)
.tickFormat(""))
.selectAll(".tick line")
.attr("stroke", "#f0f0f0")
.attr("stroke-width", 1);
// Add title with better styling
svg.append("text")
.attr("x", width / 2)
.attr("y", 20)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-weight", "bold")
.style("font-family", "sans-serif")
.text("Meta Score Distribution for Top Rated Games");
// Create enhanced tooltip
const tooltip = d3.select("body").append("div")
.attr("class", "scatter-tooltip")
.style("position", "absolute")
.style("background", "rgba(255, 255, 255, 0.96)")
.style("border-radius", "6px")
.style("padding", "12px")
.style("pointer-events", "none")
.style("opacity", 0)
.style("box-shadow", "0 3px 14px rgba(0,0,0,0.15)")
.style("border", "1px solid rgba(0,0,0,0.1)")
.style("font-family", "sans-serif")
.style("font-size", "12px")
.style("max-width", "300px")
.style("backdrop-filter", "blur(2px)")
.style("transition", "opacity 0.2s, transform 0.2s");
// Initial chart render with more visible points
let circles = chart.append("g")
.selectAll("circle")
.data(games)
.join("circle")
.attr("cx", d => x(d.year))
.attr("cy", d => y(d.metascore))
.attr("r", 5) // Slightly larger default size
.attr("fill", d => color(d.platform))
.attr("opacity", 0.9) // More opaque
.attr("stroke", "white")
.attr("stroke-width", 1)
.on("mouseover", function(event, d) {
d3.select(this)
.attr("r", 7) // More noticeable hover size
.attr("stroke-width", 2);
tooltip.html(`
<div class="tooltip-header" style="margin-bottom: 8px;">
<div style="font-size: 18px; font-weight: bold; color: ${color(d.platform)}; margin-bottom: 2px;">${d.metascore}</div>
<div style="font-size: 14px; font-weight: 600;">${d.name}</div>
</div>
<div class="tooltip-details" style="font-size: 12px; line-height: 1.5;">
<div><span style="font-weight: 500; color: #555;">Platform:</span> ${d.platform}</div>
<div><span style="font-weight: 500; color: #555;">Year:</span> ${d.year}</div>
<div><span style="font-weight: 500; color: #555;">Publisher:</span> ${d.publisher || 'N/A'}</div>
<div><span style="font-weight: 500; color: #555;">Genre:</span> ${d.genre || 'N/A'}</div>
</div>
`)
.style("left", `${event.pageX + 15}px`)
.style("top", `${event.pageY - 15}px`)
.style("opacity", 1)
.style("transform", "translateY(-5px)");
})
.on("mouseout", function() {
d3.select(this)
.attr("r", 5)
.attr("stroke-width", 1);
tooltip
.style("opacity", 0)
.style("transform", "translateY(0)");
})
.on("mousemove", (event) => {
tooltip
.style("left", `${event.pageX + 15}px`)
.style("top", `${event.pageY - 15}px`);
});
// Update chart with enhanced animations
function updateChart(selectedPlatform) {
const filteredData = selectedPlatform === allPlatforms
? games
: games.filter(d => d.platform === selectedPlatform);
// Update circles with smooth transitions
circles = chart.selectAll("circle")
.data(filteredData, d => d.name + d.platform + d.year);
// Exit animation - shrink and fade out
circles.exit()
.transition()
.duration(600)
.attr("r", 0)
.attr("opacity", 0)
.remove();
// Enter animation - grow and fade in
circles.enter()
.append("circle")
.attr("cx", d => x(d.year))
.attr("cy", d => y(d.metascore))
.attr("r", 0)
.attr("fill", d => color(d.platform))
.attr("opacity", 0)
.attr("stroke", "white")
.attr("stroke-width", 1)
.call(enter => {
enter.transition()
.duration(600)
.delay((d, i) => selectedPlatform === allPlatforms ? i * 3 : 0)
.attr("r", 5)
.attr("opacity", 0.9);
});
// Update existing points
circles
.transition()
.duration(600)
.attr("cx", d => x(d.year))
.attr("cy", d => y(d.metascore));
// Re-attach event handlers
chart.selectAll("circle")
.on("mouseover", function(event, d) {
d3.select(this)
.attr("r", 7)
.attr("stroke-width", 2);
tooltip.html(`
<div class="tooltip-header" style="margin-bottom: 8px;">
<div style="font-size: 18px; font-weight: bold; color: ${color(d.platform)}; margin-bottom: 2px;">${d.metascore}</div>
<div style="font-size: 14px; font-weight: 600;">${d.name}</div>
</div>
<div class="tooltip-details" style="font-size: 12px; line-height: 1.5;">
<div><span style="font-weight: 500; color: #555;">Platform:</span> ${d.platform}</div>
<div><span style="font-weight: 500; color: #555;">Year:</span> ${d.year}</div>
<div><span style="font-weight: 500; color: #555;">Publisher:</span> ${d.publisher || 'N/A'}</div>
<div><span style="font-weight: 500; color: #555;">Genre:</span> ${d.genre || 'N/A'}</div>
</div>
`)
.style("left", `${event.pageX + 15}px`)
.style("top", `${event.pageY - 15}px`)
.style("opacity", 1)
.style("transform", "translateY(-5px)");
})
.on("mouseout", function() {
d3.select(this)
.attr("r", 5)
.attr("stroke-width", 1);
tooltip
.style("opacity", 0)
.style("transform", "translateY(0)");
});
}
// Add CSS styles
svg.append("style").text(`
.scatter-tooltip {
transition: all 0.2s ease-out;
}
.tooltip-header {
border-bottom: 1px solid rgba(0,0,0,0.1);
padding-bottom: 6px;
margin-bottom: 6px;
}
#platform-filter {
cursor: pointer;
transition: border-color 0.2s;
}
#platform-filter:hover {
border-color: #999;
}
#platform-filter:focus {
outline: none;
border-color: #4a90e2;
box-shadow: 0 0 0 2px rgba(74,144,226,0.2);
}
`);
return svg.node();
}