async function drawMap() {
const countryShapes = await d3.json("https://gist.githubusercontent.com/chekos/d8fc5487eea29a631a2de2b6c405d28f/raw/58018703a3926597b39765722993d8fc9cfc5266/world-geojson.json")
const countryNameAccessor = d => d.properties["NAME"]
const countryIdAccessor = d => d.properties["ADM0_A3_IS"]
const dataset = await d3.csv("https://gist.githubusercontent.com/chekos/d8fc5487eea29a631a2de2b6c405d28f/raw/3f0a910765be727d4dd5cbd668a6b88461ee208a/data_bank_data.csv")
const metric = "Population growth (annual %)"
let metricDataByCountry = {}
dataset.forEach(d => {
if (d["Series Name"] != metric) return
metricDataByCountry[d["Country Code"]] = +d["2017 [YR2017]"]
})
let dimensions = {
width: width,
margin: {
top: 10,
right: 10,
bottom: 10,
left: 10,
},
}
dimensions.boundedWidth = dimensions.width - dimensions.margin.left - dimensions.margin.right
const sphere = ({type: "Sphere"})
const projection = d3.geoEqualEarth()
.fitWidth(dimensions.boundedWidth, sphere)
const pathGenerator = d3.geoPath(projection)
const [[x0, y0], [x1, y1]] = pathGenerator.bounds(sphere)
dimensions.boundedHeight = y1
dimensions.height = dimensions.boundedHeight + dimensions.margin.top + dimensions.margin.bottom
// Wrapper and bounds
const wrapper = d3.select("#wrapper")
.append("svg")
.attr("width", dimensions.width)
.attr("height", dimensions.height)
const bounds = wrapper.append("g")
.style("transform", `translate(${dimensions.margin.left}px, ${dimensions.margin.top}px)`)
// Scales
const metricValues = Object.values(metricDataByCountry)
const metricValueExtent = d3.extent(metricValues)
const maxChange = d3.max([-metricValueExtent[0], metricValueExtent[1]])
const colorScale = d3.scaleLinear()
.domain([-maxChange, 0, maxChange])
.range(["indigo", "white", "darkgreen"])
// Draw data
const earth = bounds.append("path")
.attr("class", "earth")
.attr("d", pathGenerator(sphere))
const graticuleJson = d3.geoGraticule10()
const graticule = bounds.append("path")
.attr("class", "graticule")
.attr("d", pathGenerator(graticuleJson))
const countries = bounds.selectAll(".country")
.data(countryShapes.features)
.enter().append("path")
.attr("class", "country")
.attr("d", pathGenerator)
.attr("fill", d => {
const metricValue = metricDataByCountry[countryIdAccessor(d)]
if (typeof metricValue == "undefined") return "#e2e6e9"
return colorScale(metricValue)
})
// Peripherals
/// legend
const legendGroup = wrapper.append("g")
.attr("transform", `translate(${120}, ${
dimensions.width < 800
? dimensions.boundedHeight - 30
: dimensions.boundedHeight * 0.50
})`)
const legendTitle = legendGroup.append("text")
.attr("y", -23)
.attr("class", "legend-title")
.text("Population growth")
const legendByLine = legendGroup.append("text")
.attr("y", -9)
.attr("class", "legend-byline")
.text("Percent change in 2017")
/// gradient
const defs = wrapper.append("defs")
const legendGradientId = "legend-gradient"
const gradient = defs.append("linearGradient")
.attr("id", legendGradientId)
.selectAll("stop")
.data(colorScale.range())
.enter().append("stop")
.attr("stop-color", d => d)
.attr("offset", (d, i) => `${i * 100 / 2}%`) // it's ÷2 because we have 3 elements
const legendWidth = 120
const legendHeight = 16
const legendGradient = legendGroup.append("rect")
.attr("x", -legendWidth / 2)
.attr("height", legendHeight)
.attr("width", legendWidth)
.attr("fill", `url(#${legendGradientId})`)
const legendValueRight = legendGroup.append("text")
.attr("class", "legend-value")
.attr("x", legendWidth / 2 + 10)
.attr("y", legendHeight / 2)
.text(`${d3.format(".1f")(maxChange)}%`)
const legendValueLeft = legendGroup.append("text")
.attr("class", "legend-value")
.attr("x", -legendWidth / 2 - 10)
.attr("y", legendHeight / 2)
.text(`${d3.format(".1f")(-maxChange)}%`)
.style("text-anchor", "end")
/// current location
navigator.geolocation.getCurrentPosition(myPosition => {
const [x, y] = projection([
myPosition.coords.longitude,
myPosition.coords.latitude
])
const myLocation = bounds.append("circle")
.attr("class", "my-location")
.attr("cx", x)
.attr("cy", y)
.attr("r", 0)
.transition().duration(300)
.attr("r", 20)
.transition().duration(300)
.attr("r", 5)
})
// interactions
countries.on("mouseenter", onMouseEnter)
.on("mouseleave", onMouseLeave)
const tooltip = d3.select("#tooltip")
function onMouseEnter(datum) {
tooltip.style("opacity", 1)
const metricValue = metricDataByCountry[countryIdAccessor(datum)]
const [centerX, centerY] = pathGenerator.centroid(datum)
const x = centerX + dimensions.margin.left
const y = centerY + dimensions.margin.top
tooltip.select("#country")
.text(countryNameAccessor(datum))
tooltip.select("#value")
.text(`${d3.format(",.2f")(metricValue || 0)}%`)
tooltip.style("transform", `translate( calc(-50% + ${x}px), calc(-100% + ${y}px))`)
}
function onMouseLeave() {
tooltip.style("opacity", 0)
}
}