var num = 11; var numTop = 5; var margin = {top: 35, right: 70, bottom: 30, left: 70}; var width = 950, height = 500; var devicePixelRatio = window.devicePixelRatio || 1; var canvas = d3.select("canvas") .attr("width", width * devicePixelRatio) .attr("height", height * devicePixelRatio) .style("width", width + "px") .style("height", height + "px"); var svg = d3.select("svg") .style("width", width + "px") .style("height", height + "px"); var color = d3.scaleOrdinal() .range(["#DB7F85", "#50AB84", "#4C6C86", "#C47DCB", "#B59248", "#DD6CA7", "#E15E5A", "#5DA5B3", "#725D82", "#54AF52", "#954D56"]); var xscale = d3.scaleLinear() .domain([1959, 2021]) .range([margin.left, width-margin.right]); var xaxis = d3.axisBottom() .scale(xscale) .tickFormat(d3.format("")); var xaxis2 = d3.axisTop() .scale(xscale) .tickFormat(d3.format("")); var yscale = d3.scaleLinear() .domain([0-0.2, num-0.5]) .range([margin.top, height-margin.bottom]); var radius = d3.scaleSqrt() .domain([0,0.1]) .range([0,4]); d3.csv("medals.csv", function(error, data) { var hostCountries = {}; // Find host countries by year data.forEach(function(d) { d.gold = +d.gold; d.year = +d.year; if (d.host === "y") { hostCountries[d.year] = d.name; } }); var years = Object.keys(hostCountries).map(function(d) {return +d;}); xaxis.tickValues(years); xaxis2.tickValues(years); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + (height-margin.bottom) + ")") .call(xaxis); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + (margin.top-10) + ")") .call(xaxis2); // Vertical guide line var hiddenMargin = 100; var highlightedYear; var verticalGuide = svg.append("line") .attr("class", "guide") .attr("x1", -hiddenMargin) .attr("y1", margin.top - 10) .attr("x2", -hiddenMargin) .attr("y2", height - margin.bottom) .style("stroke-width", function() { return xscale(2) - xscale(0); }) //two year interval .style("opacity", 0); var mouseTrap = svg.append("rect") .attr("width", width) .attr("height", height) .style("opacity", 0) .on("mouseover", function() { verticalGuide.style("opacity", 0.1); }) .on("mouseout", function() { verticalGuide.style("opacity", 0); }) .on("mousemove", function() { var mousex = d3.mouse(this)[0] var x = xscale.invert(mousex); var found = false; for (var i = 0; i < years.length; i++) { if (Math.abs(years[i] - x) <= 1) { // game interval (2 years) in half highlightedYear = years[i]; found = true; break; } } if (!found) { highlightedYear = undefined; } mouseTrap.style("cursor", highlightedYear? "pointer":"auto"); verticalGuide.attr("transform", "translate(" + (xscale(highlightedYear)+hiddenMargin) + ", 0)"); }) .on("click", function() { if (highlightedYear) { var url = "https://en.wikipedia.org/wiki/" + highlightedYear + ((highlightedYear <= 1975) ? "_Southeast_Asian_Peninsular_Games":"_Southeast_Asian_Games"); window.open(url, "_blank"); } }); // nest by name and rank by total popularity var nested = d3.nest() .key(function(d) { return d.name; }) .rollup(function(leaves) { return { data: leaves, sum: d3.sum(leaves, function(d) { return d.gold; }) }; }) .entries(data) .sort(function(a, b) { return d3.descending(a.value.sum, b.value.sum); }) var topnames = nested.slice(0, num).map(function(d) { return d.key; }); data = data.filter(function(d) { return topnames.indexOf(d.name) > -1; }); // nest by name and rank by total popularity window.byYear = {} d3.nest() .key(function(d) { return d.year; }) .key(function(d) { return d.name; }) // .sortValues(function(a, b) { return a.gold - b.gold; }) .rollup(function(leaves,i) { return leaves[0].gold; }) .entries(data) .forEach(function(year) { byYear[year.key] = {}; year.values .sort(function(a, b) { return d3.descending(a.value, b.value); }) .forEach(function(name, i) { byYear[year.key][name.key] = i; }); }); // window.byName = {}; // d3.nest() // .key(function(d) { return d.name; }) // .key(function(d) { return d.year; }) // .rollup(function(leaves,i) { // return leaves[0].gold; // }) // .entries(data) // .forEach(function(name) { // byName[name.key] = []; // name.values // .forEach(function(year) { // byName[name.key].push(year.value); // }); // }); // console.log(byName["Thailand"].join(", ")); var ctx = canvas.node().getContext("2d"); ctx.scale(devicePixelRatio, devicePixelRatio); // Draw a circle for each host country var countrySumRank = nested.map(function(d) { return d.key; }); for (var year in hostCountries) { if (countrySumRank.indexOf(hostCountries[year]) < numTop) { ctx.fillStyle = color(hostCountries[year]); } else { ctx.fillStyle = "#888"; } ctx.beginPath(); ctx.arc(xscale(year), yscale(byYear[year][hostCountries[year]]), 5, 0, 2*Math.PI); ctx.fill(); ctx.closePath(); } nested.slice(0, num).reverse().forEach(function(name, i) { var yearspopular = name.value.data; if (i >= num-numTop) { ctx.globalAlpha = 0.85; ctx.strokeStyle = color(name.key); ctx.lineWidth = 2.5; } else { ctx.globalAlpha = 0.55; ctx.strokeStyle = "#888"; ctx.lineWidth = 1; } // bump line ctx.globalCompositeOperation = "darken"; ctx.lineCap = "round"; yearspopular.forEach(function(d, j) { if (j > 0) { var previousYear = yearspopular[j-1].year; ctx.beginPath(); if ((d.year - previousYear) > 4) { //skipping games ctx.setLineDash([5, 10]); } else { ctx.setLineDash([]); } ctx.moveTo(xscale(previousYear), yscale(byYear[previousYear][name.key])) // ctx.lineTo(xscale(d.year), yscale(byYear[d.year][name.key])); ctx.bezierCurveTo( xscale(previousYear)+15, yscale(byYear[previousYear][name.key]), xscale(d.year)-15, yscale(byYear[d.year][name.key]), xscale(d.year), yscale(byYear[d.year][name.key])); // ctx.closePath(); ctx.stroke(); } }); }); ctx.textAlign = "right"; ctx.textBaseline = "middle"; ctx.font = "10px sans-serif"; nested.slice(0, num).reverse().forEach(function(name, i) { var yearspopular = name.value.data; if (i >= num-numTop) { ctx.fillStyle = color(name.key); } else { ctx.fillStyle = "#555"; } ctx.globalCompositeOperation = "source-over"; ctx.globalAlpha = 0.9; // start names ctx.save(); ctx.textAlign = "end"; var start = yearspopular[0].year; var x = xscale(start)-10; var y = yscale(byYear[start][name.key]); switch (name.key) { case "Indonesia": x += 30; y -= 10; break; case "Philippines": x += 45; y -= 10; break; case "Cambodia": x += 10; y -= 10; break; default: break; } ctx.fillText(name.key, x, y); ctx.restore(); // end names ctx.textAlign = "start"; var end= yearspopular[yearspopular.length-1].year; ctx.fillText(name.key, xscale(end)+10, yscale(byYear[end][name.key])); }); // legend var legendPos = {x: width*0.12, y: height*0.78}; ctx.fillStyle = "#888"; ctx.beginPath(); ctx.arc(legendPos.x, legendPos.y, 5, 0, 2*Math.PI); ctx.fill(); ctx.closePath(); ctx.textAlign = "start"; ctx.fillText("marks the years that each country hosts the SEA Games.", legendPos.x + 10, legendPos.y - 1); });