UNHCR Logo
  • Guidance
  • Chart types
  • Resources
  • Tutorials
  • Product gallery
Tutorials
  • R
    • Change over time
    • Comparison
    • Correlation
    • Distribution
    • Geospatial
    • Part-to-a-whole
    • Ranking
  • Matplotlib
    • Change over time
    • Comparison
    • Correlation
    • Distribution
    • Part-to-a-whole
    • Ranking
  • Plotly Python
    • Comparison
  • D3
    • Change over time
    • Comparison
    • Correlation
    • Distribution
    • Geospatial
    • Part-to-a-whole
On this page
  • Bubble map
  • Choropleth map
  1. Home
  2. Tutorials
  3. D3
  4. Geospatial

Bubble map

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 - Other tutorials: R

            
              <!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="bubble_map_container"></div>
            
          
            
                             
            
          
            

              //set svg parameters
              const width = 450,
                    height = 350;
              const svg = d3.select("#bubble_map_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", "#8FC1E1")
                      .style("stroke", "#D25A45")
                      .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", "#8FC1E1")
                    .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.');

            
          
Global Refugee displacement by country of origin | 2021

Choropleth map

A choropleth map is a type of thematic map in which areas are shaded or patterned according to a data variable. The variable is categorized into intervals, with each interval represented by a colour, and the map filled accordingly. Colour progression is used to represent the differences, often blending from dark to light or one colour to another.

More about: Choropleth map - Other tutorials: R

            
              <!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="choropleth_map_container"></div>
            
          
            
                             
            
          
            

              //set svg parameters
              const width = 450,
                    height = 350;
              const svg = d3.select("#choropleth_map_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 path = d3.geoPath()
                      .projection(projection);
              
              // set color scale
              const color = d3.scaleThreshold()
                      .domain([10000,100000,1000000,1000000])
                      .range(["#CDE3F1", "#8FC1E1", "#4F9ED0", "#0072BC"])
                      .unknown("#BFBFBF");

              //declare polygon and polyline
              const poly = svg.append("g");
              const line = svg.append("g");

              // declare URL
              const dataURL = "https://raw.githubusercontent.com/GDS-ODSSS/unhcr-dataviz-platform/master/data/geospatial/choropleth_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
              const promises = [
                d3.json(polygonsURL),
                d3.csv(dataURL)
              ];
                 
              Promise.all(promises).then(ready)
              function ready([topology, population]) {
              
                // prepare pop data to join shapefile
                const data = {};
                population.forEach(function(d){
                  data[d.iso3] = +d.refugees
                });

                // set mouse events
                const mouseover = function(d) {
                  d3.selectAll(".countries")
                    .transition()
                    .duration(100)
                    .style("opacity", .3)
                  d3.select(this)
                    .transition()
                    .duration(100)
                    .style("opacity", 1)
                };
                const mouseleave = function(d) {
                  d3.selectAll(".countries")
                    .transition()
                    .duration(100)
                    .style("opacity", 1)
                  d3.select(this)
                    .transition()
                    .duration(100)
                    .style("opacity", 1)
                };
                
                // load and draw polygons
                poly
                  .selectAll("path")
                  .data(topojson.feature(topology, topology.objects.world_polygons_simplified).features)
                  .join("path")
                    .attr("fill", function(d) { return color(d.refugees = data[d.properties.color_code])})
                    .attr("d", path)
                    .attr("class", function(d){ return "countries" })
                  .on("mouseover", mouseover)
                  .on("mouseleave", mouseleave)
                  .append("title")
                    .text(function(d) { return `${d.properties.gis_name} \nRefugee Population: ${d3.format(",")(d.refugees)}`
                }
              )};

              //load and draw lines
              d3.json(polylinesURL).then(function(topology) {
              line
                .selectAll("path")
                   .data(topojson.feature(topology, topology.objects.world_lines_simplified).features)
                   .enter()
                   .append("path")
                   .attr("d", path)
                   .style("fill","none")
                   .attr("class", function(d) {return d.properties.type;})
              });

              //zoom function
              const zoom = true
              if (zoom){
                var zoomFunction = d3.zoom()
                    .scaleExtent([1, 8])
                    .on('zoom', function(event) {
                      poly.selectAll('path')
                       .attr('transform', event.transform);
                      line.selectAll('path')
                       .attr('transform', event.transform);
                });
                svg.call(zoomFunction);
              };

              // set legend
              svg.append("g")
                .attr("class", "legendThreshold")
                .attr("transform", "translate(5,255)");

              const legend = d3.legendColor()
              .labelFormat(d3.format(",.0f"))
              .labels(d3.legendHelpers.thresholdLabels)
              .labelOffset(3)
              .shapePadding(0)
              .scale(color);
              
              svg.select(".legendThreshold")
                  .call(legend);
              
              // 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)
                .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)
                .text('The boundaries and names shown and the designations used on this map do not imply official endorsement or acceptance by the United Nations.');

            
          
Global Refugee displacement by country of asylum | 2021
Contact us
  • Guidance
  • Chart types
  • Resources
  • Tutorials
  • Product gallery

© UNHCR