chart = {
const zoom = d3.zoom()
.scaleExtent([1, 4])
.on("zoom", zoomed);
const svg = d3.create("svg")
.attr("viewBox", [-80, -20, width + 50, height + 50]);
const clip = svg
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height)
.attr("x", -20)
.attr("y", -20);
const calculateGdpPerCapita = (d) => d.gdp / d.population_population_number_of_people;
const calculateWasteGenerationPerCapita = (d) => d.total_msw_total_msw_generated_tons_year / d.population_population_number_of_people / 365 * 1000;
const gx = svg.append("g");
const gy = svg.append("g");
const gBubbles = svg.append("g")
.attr('clip-path', 'url(#clip)')
.selectAll(".country")
.data(data.filter((d) => {
return d.gdp && d.population_population_number_of_people && d.total_msw_total_msw_generated_tons_year;
}))
.enter()
.append("circle")
.attr("class", (d) => `bubble bubble-${d.region_id}`)
.attr("cx", (d) => x(calculateGdpPerCapita(d)))
.attr("cy", (d) => y(calculateWasteGenerationPerCapita(d)))
.attr("r", (d) => z(d.population_population_number_of_people))
.attr("stroke", "white")
.style("fill", (d) => color(d.region_id))
.on("mouseover", showTooltip)
.on("mouseleave", hideTooltip)
const tooltip = svg.append("g")
.style("opacity", 1)
.attr("class", "tooltip")
// region legends
const gLegendBox = svg.append("g")
.append("rect")
.attr("x", width - 250)
.attr("y", 0)
.attr("width", 210)
.attr("height", 185)
.style("fill", "#F5F5F5")
.attr("stroke", "#D4D4D4")
const gLegendRect = svg.append("g")
.selectAll("regionLegendRect")
.data(regionIds)
.enter()
.append("rect")
.attr("x", width - 240)
.attr("y", (d, i) => 10 + i * (25))
.attr("width", 15)
.attr("height", 15)
.style("fill", (d, i) => colorCodes[i])
.on("mouseover", highlight)
.on("mouseleave", noHighlight)
const gLegendLabel = svg.append("g")
.selectAll("regionLegendLabel")
.data(regionIds)
.enter()
.append("text")
.attr("x", width - 220)
.attr("y", (d, i) => 18 + i * (25))
.attr("text-anchor", "left")
.style("cursor", "default")
.style("fill", (d, i) => colorCodes[i])
.style("alignment-baseline", "middle")
.style("font-size", 12)
.style("font-family", "sans-serif")
.text((d, i) => regions[i])
.on("mouseover", highlight)
.on("mouseleave", noHighlight)
// population legends
const populationLegendX = width - 150;
const populationLegendY = height - 60;
const populationLegendCircle = svg.append("g");
const populationLegendLabel = svg.append("g");
populationLegendCircle.selectAll("legend")
.data([10e7, 50e7, 10e8])
.enter()
.append("circle")
.attr("cx", populationLegendX)
.attr("cy", (d) => populationLegendY - z(d))
.attr("r", (d) => z(d))
.style("fill", "none")
.attr("stroke", "black");
populationLegendLabel.selectAll("legend")
.data([10e7, 50e7, 10e8])
.enter()
.append("text")
.attr("x", populationLegendX)
.attr("y", (d) => populationLegendY - 2 - 2 * z(d))
.text((d) => `${d/10e5}M`)
.style("font-size", 10)
.style("font-family", "sans-serif")
.style("text-anchor", "middle");
const populationLegendTitle = svg.append("text")
.attr("x", populationLegendX)
.attr("y", (d) => populationLegendY + 15)
.text("Population")
.style("font-size", 12)
.style("font-family", "sans-serif")
.style("text-anchor", "middle");
svg.call(xTitle).call(yTitle).call(zoom).call(zoom.transform, d3.zoomIdentity);
function zoomed({ transform }) {
const newX = transform.rescaleX(x);
const newY = transform.rescaleY(y);
gx.call(xAxis, newX);
gy.call(yAxis, newY);
gBubbles.attr("transform", transform).attr("stroke-width", 1.5 / transform.k);
tooltip.attr("transform", transform)
populationLegendCircle
.selectAll("circle")
.attr("cy", (d) => populationLegendY - z(d) * transform.k)
.attr("r", (d) => z(d) * transform.k);
populationLegendLabel
.selectAll("text")
.attr("y", (d) => populationLegendY - 2 - 2 * z(d) * transform.k);
}
// show country name
function showTooltip(e, d) {
tooltip
.selectAll("*")
.remove();
tooltip.append("text")
.attr("class", `tooltip-${d.iso3c}`)
.attr("x", x(calculateGdpPerCapita(d)))
.attr("y", y(calculateWasteGenerationPerCapita(d)))
.attr("pointer-events", "none")
.style("font-size", 12)
.style("font-family", "sans-serif")
.style("alignment-baseline", "middle")
.style("text-anchor", "middle")
.text(d.country_name)
.clone(true)
.lower()
.attr("class", `tooltip-${d.iso3c}`)
.attr("aria-hidden", "true")
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-width", 2)
.attr("stroke-linecap", "round")
.attr("stroke-linejoin", "round");
}
// hide country name
function hideTooltip(e, d) {
tooltip
.selectAll(`.tooltip-${d.iso3c}`)
.transition()
.duration(500)
.style("opacity", 0)
.remove();
}
// add highlight to a region
function highlight(e, d){
svg.selectAll(".bubble").style("opacity", 0.1).style("saturate", "20%")
svg.selectAll(`.bubble-${d}`).style("opacity", 1).style("saturate", "100%").attr("stroke", "#181818")
}
// remove highlight of a region
function noHighlight(){
svg.selectAll(".bubble").style("opacity", 1).style("saturate", "100%").attr("stroke", "white")
}
return Object.assign(svg.node(), {
reset() {
svg.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity);
}
});
}