Creating Choropleth Map Data Visualization Using JavaScript, on COVID-19 Stats May 6th, 2020 by Anastasia Zoumpliou

Creating a choropleth map data visualization using JavaScript (JS HTML5)These days you see choropleth maps in a variety of webpages and utilized for various subjects. Have you wondered how such data visualizations are built? Are you interested in making them by yourself? If so, stay with me through this JS charting tutorial, where I will show you how to create an interactive JavaScript choropleth map from scratch but with ease.

Basic knowledge of HTML5 and JS is always helpful, but even if you are a beginner in this field, understand these four simple steps and you will be able to quickly get a good-looking cross-platform interactive choropleth map data visualization for your app or website!

What exactly are choropleth maps? When you break down the word, you see that choro- (“choros”) means “area” and pleth- (“plethos”) means “multitude.” Exactly, these maps are used to visualize statistical data related to multiple geographic areas. Each area is colored or shaded differently according to the value of the given data, making it easy to grasp how a measurement varies across a territory.

For this JS mapping tutorial, I will use open COVID-19 data to show the distribution of confirmed cases and deaths around the world by country. I will start with a basic choropleth map that will graphically represent the confirmed cases. Then I will add a legend, customize the tooltip, and add bubbles to visualize the number of deaths.

Let’s get started!

How to Create a JavaScript Choropleth Map

Generally, building a JavaScript-based chart or map requires four basic things:

  1. Create the HTML page for your chart.
  2. Add the necessary JavaScript files.
  3. Prepare the data you want to visualize.
  4. Write the JS code for the chart.

Now, let’s look at each step in detail. Here is a choropleth map that will be the final output of the first part of this tutorial:

Step 1: Create the HTML page for your map

The first thing you need to do is create a basic HTML page. Give it a title and create an HTML block element (for example, <div>) to place your map chart. To identify this <div> later in the code, you should also give it an id attribute of your choice. Here, I have used the value “container”.

So your page can look like this:

<!DOCTYPE html>
<html>

<head>
  <title>Choropleth Map</title>
  <style>
    html,
    body,
    #container {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
  </style>
</head>

<body>
  <div id="container"></div>
</body>

</html>

Note that you can use CSS rules in the <style> block to define how you want your upcoming JavaScript choropleth map to be placed. In this case, using “100%” in both width and height parameters will make the visualization occupy the entire page.

Step 2. Add the necessary JavaScript files

Second, you should go to the <head> section and reference all the appropriate JS scripts you are going to use.

To create a choropleth map along this tutorial, I will use AnyChart’s AnyMap JS library which, as its name suggests, is specifically designed to produce maps. JavaScript charting libraries are very popular nowadays; they provide commonly needed functions out of the box to make the developing process simpler and faster. This specific library is lightweight, flexible, and easy to get started, particularly thanks to a greatly written documentation and a lot of samples in the map gallery.

In this tutorial, I will use the CDN, but keep in mind that you can download the scripts if you want.

For the choropleth map that I have in mind, I will need the following modules:

  • Core, which is the the basic module needed in order to use any other module,
  • Geo Maps, which contains the necessary information to create geo map charts, and
  • GeoData, which contains information about the geographic locations.

Additionally, I will use Proj4js, a free open-source JavaScript library that transforms point coordinates to geographic coordinates.

Here is the HTML code for this section:

<!DOCTYPE html>
<html>

<head>
  <title>Choropleth Map</title>
  <script src="https://cdn.anychart.com/releases/8.7.1/js/anychart-core.min.js" type="text/javascript"></script>
  <script src="https://cdn.anychart.com/releases/8.7.1/js/anychart-map.min.js" type="text/javascript"></script>
  <script src="https://cdn.anychart.com/releases/8.7.1/geodata/custom/world/world.js" type="text/javascript"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.3.15/proj4.js" type="text/javascript"></script>
  <style>
    html,
    body,
    #container {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
  </style>
</head>

<body>
  <div id="container"></div>
  <script>
    // The choropleth map code will be written here
  </script>
</body>

</html>

Step 3: Prepare the data you want to visualize

Load the data

In the third step, add data. In light of the COVID-19 pandemic, I will create a JavaScript choropleth map that will visualize the current situation around the world using data from the European Center for Disease Prevention and Control (ECDC). I have downloaded the JSON version and you can find it here (data as of April 14).

It is easy to load data from a JSON file using the Data Adapter helper module of AnyChart, which should be added to the <head> section:

<!DOCTYPE html>
<html>

<head>
  <title>Choropleth Map</title>
  <script src="https://cdn.anychart.com/releases/8.7.1/js/anychart-core.min.js" type="text/javascript"></script>
  <script src="https://cdn.anychart.com/releases/8.7.1/js/anychart-map.min.js" type="text/javascript"></script>
  <script src="https://cdn.anychart.com/releases/8.7.1/geodata/custom/world/world.js" type="text/javascript"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.3.15/proj4.js" type="text/javascript"></script>
  <script src="https://cdn.anychart.com/releases/8.7.1/js/anychart-data-adapter.min.js"></script>
  <style>
    html,
    body,
    #container {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
  </style>
</head>

<body>
  <div id="container"></div>
  <script>
    // The choropleth map code will be written here
  </script>
</body>

</html>

Now you can use the loadJsonFile method provided by the Data Adapter to load JSON.

anychart.data.loadJsonFile ("https://static.anychart.com/git-storage/word-press/data/choropleth-map-tutorial/data.json", function (data) {

});

Structure the data

The JSON data, which you have just loaded, contains data per day for each country.

There are two things you need to do here:

  • Calculate the sum of all confirmed cases for each country.
  • Structure the data in a format that will be recognizable by AnyChart. Specifically, I want to create an array that will have a region id and a value.

To implement the above, you need to write some code. Here is the approach I will use to calculate and structure the data:

I will read the JSON data and calculate the total confirmed cases for each country. As soon as I am done with one country, I will pass this info into an array that I have named “data” and then I will continue with the next country.

The array will use:

  • id: the geographic identifier for the country
  • value: the number of total confirmed cases for the country
  • title: the name of the country

Here is the code for this section:

anychart.data.loadJsonFile ("https://static.anychart.com/git-storage/word-press/data/choropleth-map-tutorial/data.json", function (data) {

  // Variables
  // go into the records section of the data
  var geoData = data.records

  // sum of all cases per country
  var sumCases = 0;

  // convert cases to numbers
  var numC;

  // create a new array with the resulting data
  var data = [];

  // Go through the initial data
  for (var i = 0; i < geoData.length; i++) {
    // convert strings to numbers and save them to new variables
    numC = parseInt(geoData[i].cases);

    // check if we are in the same country by comparing the geoId. 
    // if the country is the same add the cases to the appropriate variables
    if ((geoData[i + 1]) != null && (geoData[i].geoId == geoData[i + 1].geoId)) {
      sumCases = sumCases + numC;
    }
    else {
      // add last day cases of the same country
      sumCases = sumCases + numC;

      // insert the resulting data in the array using the AnyChart keywords 
      data.push({ id: geoData[i].geoId, value: sumCases, title: geoData[i].countriesAndTerritories })

      // reset the variables to start over
      sumCases = 0;
                        
    }
  };
});

Map the data

The final part of this step consists in connecting the resulting data with the geographic map. Just add the following two lines of the code and it will be completed:

var chart = anychart.map(data);
chart.geoData(anychart.maps.world);

Step 4: Write the JS code for the chart

Finally, write some JavaScript code to develop the JS choropleth map itself!

Remember that everything should be placed inside the <script> tag. You need to enclose the code inside the anychart.onDocumentReady function that is executed when the page is ready.

<script>
  anychart.onDocumentReady(function () {
    // The choropleth map code will be written here
  });
</script>

Data needs to be placed inside of this function, so let’s take the code from step 3 and put it here, to start with:

<script>
anychart.onDocumentReady(function () {
  // The choropleth map code will be written here
  anychart.data.loadJsonFile ("https://static.anychart.com/git-storage/word-press/data/choropleth-map-tutorial/data.json", function (data) {

  // Variables
  // go into the records section of the data
  var geoData = data.records

  // sum of all cases per country
  var sumCases = 0;

  // convert cases to numbers
  var numC;

  // create a new array with the resulting data
  var data = [];

  // Go through the initial data
  for (var i = 0; i < geoData.length; i++) {
    // convert strings to numbers and save them to new variables
    numC = parseInt(geoData[i].cases);

    // check if we are in the same country by comparing the geoId.
    // if the country is the same add the cases to the appropriate variables
    if ((geoData[i + 1]) != null && (geoData[i].geoId == geoData[i + 1].geoId)) {
      sumCases = sumCases + numC;
    }
    else {

      // add last day cases of the same country
      sumCases = sumCases + numC;

      // insert the resulting data in the array using the AnyChart keywords 
      data.push({ id: geoData[i].geoId, value: sumCases, title: geoData[i].countriesAndTerritories })

      // reset the variables to start over
      sumCases = 0;

    }
  };
  // connect the data with the map
  var chart = anychart.map(data); chart.geoData(anychart.maps.world);

});
</script>

Finally, let’s set everything else, coloring each country according to the value of the total number of confirmed cases. First, I will specify the chart type, create the series, and add an appropriate title. Second, I will create a color scale and apply it to the chart. Look:

// specify the chart type and set the series 
var series = chart.choropleth(data);
        
// set the chart title
chart.title("COVID-19 Global Cases");

// color scale ranges
ocs = anychart.scales.ordinalColor([
  { less: 99 },
  { from: 100, to: 999 },
  { from: 1000, to: 9999 },
  { from: 10000, to: 29999 },
  { from: 30000, to: 39000 },
  { from: 40000, to: 59000 },
  { from: 60000, to: 99999 },
  { greater: 100000 }
]);
        
// set scale colors
ocs.colors(["rgb(252,245,245)", "rgb(241,219,216)","rgb(229,190,185)", "rgb(211,152,145)", "rgb(192,117,109)", "rgb(178,93,86)", "rgb(152,50,48)", "rgb(150,33,31)"]);

// tell the series what to use as a colorRange (colorScale)
series.colorScale(ocs);

Then, command to draw the map chart:

// set the container id
chart.container('container');

// draw the chart
chart.draw();

Here is the result:


The resulting JS choropleth map, embedded from AnyChart Playground, gives a concise visualization of how COVID-19 has affected each country. You can hover over areas to see the name and the number of confirmed cases, for each country. Everyone can read such a geographic data visualization with ease.

The full code of the choropleth map just built using JavaScript/HTML5 is below:

<!DOCTYPE html>
<html>

<head>
  <title>Choropleth Map</title>
  <script src="https://cdn.anychart.com/releases/8.7.1/js/anychart-core.min.js" type="text/javascript"></script>
  <script src="https://cdn.anychart.com/releases/8.7.1/js/anychart-map.min.js" type="text/javascript"></script>
  <script src="https://cdn.anychart.com/releases/8.7.1/geodata/custom/world/world.js" type="text/javascript"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.3.15/proj4.js" type="text/javascript"></script>
  <script src="https://cdn.anychart.com/releases/8.7.1/js/anychart-data-adapter.min.js"></script>

  <style>>
    html,
    body,
    #container {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
  </style>
</head>

<body>
  <div id="container"></div>
  <script>

    anychart.onDocumentReady(function () {

      // load the data
      anychart.data.loadJsonFile("https://static.anychart.com/git-storage/word-press/data/choropleth-map-tutorial/data.json", function (data) {

        // Variables
        // go into the records section of the data
        var geoData = data.records

        // sum of all cases per country
        var sumCases = 0;

        // convert cases to numbers
        var numC;

        // create a new array with the resulting data
        var data = [];

        // Go through the initial data
        for (var i = 0; i < geoData.length; i++) {
          // convert strings to numbers and save them to new variables
          numC = parseInt(geoData[i].cases);

          // check if we are in the same country by comparing the geoId. 
          // if the country is the same add the cases to the appropriate variables
          if ((geoData[i + 1]) != null && (geoData[i].geoId == geoData[i + 1].geoId)) {
            sumCases = sumCases + numC;
          }
          else {

            // add last day cases of the same country
            sumCases = sumCases + numC;

            // insert the resulting data in the array using the AnyChart keywords 
            data.push({ id: geoData[i].geoId, value: sumCases, title: geoData[i].countriesAndTerritories })

            // reset the variables to start over
            sumCases = 0;

          }
        };

        // connect the data with the map
        var chart = anychart.map(data);
        chart.geoData(anychart.maps.world);

        // specify the chart type and set the series 
        var series = chart.choropleth(data);

        // set the chart title
        chart.title("COVID-19 Global Cases");

        // color scale ranges
        ocs = anychart.scales.ordinalColor([
          { less: 99 },
          { from: 100, to: 999 },
          { from: 1000, to: 9999 },
          { from: 10000, to: 29999 },
          { from: 30000, to: 39000 },
          { from: 40000, to: 59000 },
          { from: 60000, to: 99999 },
          { greater: 100000 }
        ]);

        // set scale colors
        ocs.colors(["rgb(252,245,245)", "rgb(241,219,216)", "rgb(229,190,185)", "rgb(211,152,145)", "rgb(192,117,109)", "rgb(178,93,86)", "rgb(152,50,48)", "rgb(150,33,31)"]);

        // tell the series what to use as a colorRange (colorScale)
        series.colorScale(ocs);

        // set the container id
        chart.container('container');
        
        // draw the chart
        chart.draw();
      });

    });
  </script>
</body>

</html>

JS Choropleth Map Customization

You already have a wonderful and fully functional JavaScript choropleth map. But what if you want to change some things or add some further functionality?

AnyChart is a very flexible JS charting library. So you can easily include modifications that are specific for your needs.

Right now I will show you how to implement the following changes:

  • Add a legend to the chart
  • Add bubbles for graphical representation of the number of deaths
  • Configure the tooltip

In the end, I will get the following picture:

Add a legend to the chart

As a general rule for data visualization, every time you utilize a color scale, you should include an explanation of what each color represents somewhere on the page

Using AnyChart, you can create a legend just by adding chart.legend(true);.

In this case, because the choropleth map has only one series, you will need to have the involved categories represented in the legend.

Here’s the code for adding a legend to the JS choropleth map created above:

// enable the legend
chart.legend(true);

// set the source mode of the legend
chart.legend().itemsSourceMode("categories");

That’s functional, but for aesthetic reasons I want the legend to appear on the right-hand side of the chart and to be aligned vertically. To do that, I will just add some CSS rules:

// enable the legend
chart.legend(true);

// set the source mode of the legend and add styles
chart.legend()
  .itemsSourceMode("categories") 
  .position('right')
  .align('top')
  .itemsLayout('vertical')
  .padding(50, 0, 0, 20)
  .paginator(false);

Here is the result (available on AnyChart Playground with the full code):


Add bubbles that will represent the number of deaths

Inspired by the visualization created by the Center for Systems Science and Engineering (CSSE) at Johns Hopkins University, I decided to depict the number of deaths using bubbles.

First, the total number of COVID-19 deaths for each country needs to be stored in the data alongside the total number of COVID-19 cases:

// sum of all cases per country
var sumCases = 0;

// sum of all deaths per country
var sumDeaths = 0;

// convert cases and deaths to numbers
var numC;
var numD;

// create a new array with the resulting data
var data = [];

// Go through the initial data
for (var i = 0; i < geoData.length; i++) {
  // convert strings to numbers and save them to new variables
  numC = parseInt(geoData[i].cases);
  numD = parseInt(geoData[i].deaths);

  // check if we are in the same country by comparing the geoId
  // if the country is the same, add cases and deaths to the appropriate variables
  if ((geoData[i + 1]) != null && (geoData[i].geoId == geoData[i + 1].geoId)) {
    sumCases = sumCases + numC;
    sumDeaths = sumDeaths + numD;
  }
  else {

    // add last day cases and deaths of the same country
    sumCases = sumCases + numC;
    sumDeaths = sumDeaths + numD;

    // insert the resulting data in the array using the AnyChart keywords 
    data.push({ id: geoData[i].geoId, value: sumCases, size: sumDeaths, title: geoData[i].countriesAndTerritories })

    // reset the variables to start over
    sumCases = 0;
    sumDeaths = 0;

  }
};

Second, store only the countries that have at least one death in a separate array:

// variable to store data that will be used for bubbles
var bubbleData=[];

// store only the countries that have at least 1 death
for (var i=0; i<data.length; i++) {
  if (data[i].size>0){
    bubbleData.push(data[i]);
  }
};

Then, add the following JS code to create the bubble series on top of the choropleth series on the map.

// create a series for bubbles
var series_1 = chart.bubble(bubbleData);

Take a look at the map below or on AnyChart Playground:


It is a good idea to customize the size of the bubbles so that they don’t occupy so much space on the plot. To do this, use AnyChart’s maxBubbleSize() and minBubbleSize() functions. For example, like this:

// set the maximum size of the bubble
chart.maxBubbleSize(25);

// set the minimum size of the bubble
chart.minBubbleSize(3);

Next, you can modify the color and stroke of the bubbles to make them more consistent with the rest of the chart:

// set colors and stroke of bubbles
series_1.normal().fill("black", 0.3);
series_1.hovered().fill("black", 0.1);
series_1.selected().fill("black", 0.5);
series_1.normal().stroke("rgb(150,33,31)");
series_1.hovered().stroke("rgb(150,33,31)", 2);
series_1.selected().stroke("rgb(150,33,31)", 4);

Check out the outcome after these modifications, below or on AnyChart Playground:


Configure the choropleth map tooltip

Finally, let’s configure the JS choropleth map’s tooltip so that it shows information about total cases if you hover over a country’s territory and total deaths if you hover over a bubble.

The code to achieve this can look as follows:

// tooltip formatting
series.tooltip().format("Total Confirmed Cases: {%value}");
series_1.tooltip().format("Total Deaths: {%size}");

Look at the result — you can also find it on AnyChart Playground with the full code:


Just in case, here’s the code of the final customized choropleth map coded in JavaScript, which can now be easily embedded into a web, mobile or standalone project:

<!DOCTYPE html>
<html>

<head>
  <title>Choropleth Map</title>
  <script src="https://cdn.anychart.com/releases/8.7.1/js/anychart-core.min.js" type="text/javascript"></script>
  <script src="https://cdn.anychart.com/releases/8.7.1/js/anychart-map.min.js" type="text/javascript"></script>
  <script src="https://cdn.anychart.com/releases/8.7.1/geodata/custom/world/world.js" type="text/javascript"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.3.15/proj4.js" type="text/javascript"></script>
  <script src="https://cdn.anychart.com/releases/8.7.1/js/anychart-data-adapter.min.js"></script>

  <style>
    html,
    body,
    #container {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
    }
  </style>
</head>

<body>
  <div id="container"></div>
  <script>

    anychart.onDocumentReady(function () {

      // load the data
      anychart.data.loadJsonFile("https://static.anychart.com/git-storage/word-press/data/choropleth-map-tutorial/data.json", function (data) {

        // Variables
        // go into the records section of the data
        var geoData = data.records

        // sum of all cases per country
        var sumCases = 0;

        // sum of all deaths per country
        var sumDeaths = 0;

        // convert cases and deaths to numbers
        var numC;
        var numD;

        // create a new array with the resulting data
        var data = [];

        // Go through the initial data
        for (var i = 0; i < geoData.length; i++) {
          // convert strings to numbers and save them to new variables
          numC = parseInt(geoData[i].cases);
          numD = parseInt(geoData[i].deaths);

          // check if we are in the same country by comparing the geoId. 
          // if the country is the same add the cases and deaths to the appropriate variables
          if ((geoData[i + 1]) != null && (geoData[i].geoId == geoData[i + 1].geoId)) {
            sumCases = sumCases + numC;
            sumDeaths = sumDeaths + numD;
          }
          else {
            // add last day cases and deaths of the same country
            sumCases = sumCases + numC;
            sumDeaths = sumDeaths + numD;

            // insert the resulting data in the array using the AnyChart keywords 
            data.push({ id: geoData[i].geoId, value: sumCases, size: sumDeaths, title: geoData[i].countriesAndTerritories })

            // reset the variables to start over
            sumCases = 0;
            sumDeaths = 0;

          }
        };

        // connect the data with the map
        var chart = anychart.map(data);
        chart.geoData(anychart.maps.world);

        // specify the chart type and set the series 
        var series = chart.choropleth(data);

        // variable to store data that will be used for bubbles
        var bubbleData = [];

        // store only the countries that have at least 1 death
        for (var i = 0; i < data.length; i++) {
          if (data[i].size > 0) {
            bubbleData.push(data[i]);
          }
        };

        // create a series for bubbles
        var series_1 = chart.bubble(bubbleData);

        // set the maximum size of the bubble
        chart.maxBubbleSize(25);

        // set the minimum size of the bubble
        chart.minBubbleSize(3);

        // set colors and stroke of bubbles
        series_1.normal().fill("black", 0.3);
        series_1.hovered().fill("black", 0.1);
        series_1.selected().fill("black", 0.5);
        series_1.normal().stroke("rgb(150,33,31)");
        series_1.hovered().stroke("rgb(150,33,31)", 2);
        series_1.selected().stroke("rgb(150,33,31)", 4);

        // set the chart title
        chart.title("COVID-19 Global Cases");

        // color scale ranges
        ocs = anychart.scales.ordinalColor([
          { less: 99 },
          { from: 100, to: 999 },
          { from: 1000, to: 9999 },
          { from: 10000, to: 29999 },
          { from: 30000, to: 39000 },
          { from: 40000, to: 59000 },
          { from: 60000, to: 99999 },
          { greater: 100000 }
        ]);

        // set scale colors
        ocs.colors(["rgb(252,245,245)", "rgb(241,219,216)", "rgb(229,190,185)", "rgb(211,152,145)", "rgb(192,117,109)", "rgb(178,93,86)", "rgb(152,50,48)", "rgb(150,33,31)"]);

        // tell the series what to use as a colorRange (colorScale)
        series.colorScale(ocs);

        // enable the legend
        chart.legend(true);

        // set the source mode of the legend and add styles
        chart.legend()
          .itemsSourceMode("categories")
          .position('right')
          .align('top')
          .itemsLayout('vertical')
          .padding(50, 0, 0, 20)
          .paginator(false);

        // tooltip formatting
        series.tooltip().format("Total Confirmed Cases: {%value}");
        series_1.tooltip().format("Total Deaths: {%size}");

        // set the container id
        chart.container('container');

        // draw the chart
        chart.draw();
      });


    });
  </script>
</body>

</html>

Conclusion

In this tutorial, I have shown you in detail how to create an interactive JavaScript choropleth map chart. Such data visualizations can be a really helpful tool for analyzing statistics at a glance.

Feel free to play with the code of the JS choropleth map samples built along the tutorial. For example, would you like to add zoom UI controls next? You can also visit the AnyChart gallery to see more demos of choropleth, bubble, and other JS maps.


No Comments Yet

*