Bubble map with D3
As a variation of a bubble chart, bubble maps display bubbles over geographical regions rather than the cartesian plane. The size or area of the bubble indicates the value of the particular variable, with the position on the map indicating location.
More about: Bubble map
Bubble map
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Include d3 library -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-geo@"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-geo-projection@4"></script>
<script src="https://unpkg.com/topojson@3"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.25.6/d3-legend.min.js"></script>
<!-- Create a container to host the chart -->
<div id="viz_container"></div>
//set svg parameters
const width = 450,
height = 350;
const svg = d3.select("#viz_container")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox","0 0 450 350")
.attr("preserveAspectRatio","xMinYMin");
//set map scale, location on screen and its projection
const projection = d3.geoRobinson()
.scale(85)
.center([0, 0])
.translate([width/2.2, height/2]);
//path generator
const generator = d3.geoPath()
.projection(projection);
//declare polygon, polyline and bubble
const poly = svg.append("g");
const line = svg.append("g");
const bubble = svg.append("g");
// declare URL
const dataURL = "https://raw.githubusercontent.com/GDS-ODSSS/unhcr-dataviz-platform/master/data/geospatial/bubble_map.csv";
const polygonsURL = "https://raw.githubusercontent.com/GDS-ODSSS/unhcr-dataviz-platform/master/data/geospatial/world_polygons_simplified.json";
const polylinesURL = "https://raw.githubusercontent.com/GDS-ODSSS/unhcr-dataviz-platform/master/data/geospatial/world_lines_simplified.json";
//load data
d3.csv(dataURL).then(function(population) {
//create a tooltip
const tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip");
//tooltip and mouse events
const mouseover = function(d) {
tooltip
.style("opacity", 1)
d3.select(this)
.style("fill", "#589BE5")
.style("stroke", "#EF4A60")
.style("opacity", 1)
};
const mousemove = function(event,d) {
f = d3.format(",")
tooltip
.html("<div style='color: #0072BC'><b>" + d.gis_name + "</b></div><div>Number of Refugee: " + `${f(d.ref)}`+"</div>")
.style("top", event.pageY - 10 + "px")
.style("left", event.pageX + 10 + "px")
};
const mouseleave = function(d) {
tooltip
.style("opacity", 0)
d3.select(this)
.style("stroke", "#0072BC")
.style("opacity", 1)
};
//set bubble scale
const valueScale = d3.extent(population, d => +d.ref)
const size = d3.scaleSqrt()
.domain(valueScale)
.range([1, 20]);
//draw bubble
bubble
.selectAll("circle")
.data(population)
.join("circle")
.attr("cx", d => projection([+d.lon, +d.lat])[0])
.attr("cy", d => projection([+d.lon, +d.lat])[1])
.attr("r", d => size(+d.ref))
.style("fill", "#589BE5")
.attr("stroke", "#0072BC")
.attr("stroke-width", 0.5)
.attr("fill-opacity", .6)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave);
//Add legend
const legendLabel = [100000,1000000,5000000];
const xCircle = 20;
const xLabel = 55;
svg
.selectAll("legend")
.data(legendLabel)
.join("circle")
.attr("cx", xCircle)
.attr("cy", d => height*0.9 - size(d))
.attr("r", d => size(d))
.style("fill", "none")
.attr("stroke", "#666666")
.attr("stroke-width", 0.75);
svg
.selectAll("legend")
.data(legendLabel)
.join("line")
.attr('x1', xCircle)
.attr('x2', xLabel)
.attr('y1', d => height*0.9 - size(d)*2)
.attr('y2', d => height*0.9 - size(d)*2)
.attr('stroke', '#666666')
.attr("stroke-width", 0.75);
svg
.selectAll("legend")
.data(legendLabel)
.join("text")
.attr('x', xLabel)
.attr('y', d => height*0.9 - size(d)*2)
.text(d => d3.format(",")(d))
.style("font-size", 9)
.style("fill", "#666666")
.attr('alignment-baseline', 'middle')
});
//load and draw polygons
d3.json(polygonsURL).then(function(topology) {
poly
.selectAll("path")
.data(topojson.feature(topology, topology.objects.world_polygons_simplified).features)
.join("path")
.attr("fill", "#CCCCCC")
.attr("d", generator);
});
//load and draw lines
d3.json(polylinesURL).then(function(topology) {
line
.selectAll("path")
.data(topojson.feature(topology, topology.objects.world_lines_simplified).features)
.join("path")
.style("fill","none")
.attr("d", generator)
.attr("class", d => d.properties.type)
});
//set note
svg
.append('text')
.attr('class', 'note')
.attr('x', width*0.01)
.attr('y', height*0.96)
.attr('text-anchor', 'start')
.style('font-size', 7)
.style("fill", "#666666")
.text('Source: UNHCR Refugee Data Finder');
svg
.append('text')
.attr('class', 'note')
.attr('x', width*0.01)
.attr('y', height*0.99)
.attr('text-anchor', 'start')
.style('font-size', 7)
.style("fill", "#666666")
.text('The boundaries and names shown and the designations used on this map do not imply official endorsement or acceptance by the United Nations.');