Building an Interactive Flow Map in JavaScript
A step-by-step tutorial showing how to create an interactive JS Flow Map. Illustrated by visualizing data on migration to the US.
It may seem to be complicated to create an interactive Flow Map for the Web using JavaScript. But it’s not! This easy-to-follow tutorial will show you how to build beautiful JS flow maps without too much effort.
In these difficult times of the pandemic, there is a lot of confusion and concern about the immigration status of people around the globe. I decided to take a look at the immigration data of the US which has more migrants than any other country in the world. Here, I explore where these immigrants come from and represent the top 15 countries that contributed the highest number of migrants to the US in the year 2019.
A flow map seems the perfect way to showcase the inflow of migrants into the United States from various countries. Before going further, let me give you a brief idea about a flow map and its uses.
What is a Flow Map?
Flow Maps geographically visualize the movement of objects — for example, people or goods from one location to another and their amount.
A flow map is a type of connector map that is drawn by connecting points placed on a map by straight or curved lines with an arrow or marker indicating the direction of the flow. Generally, the magnitude of the flow is represented by the thickness of the line.
Start and endpoints for the connectors in these maps are defined by the latitude and longitude parameters so it's necessary to set these for each connector. Note that the latitude of the point should be defined first and then the longitude for each point.
For example, here’s the flow map I will have created by the end of this tutorial.
Creating a Flow Map with JavaScript
There are a lot of good JavaScript charting libraries that can be used to create compelling data visualizations. Many of them provide the capabilities to build maps and have their strengths. So you can use whichever library best fits your project requirements.
In this tutorial, I am using AnyChart. It looks the most suitable here with the out-of-the-box flow map option and in-depth documentation to understand the process.
A flow map is slightly more complicated than a basic chart like a bar or a pie chart but I will walk you through the lines of code to make it easier to grasp. Some background knowledge about HTML and JavaScript will help you understand faster but nonetheless, it is not too difficult. Look how you can create a beautiful interactive JavaScript flow map in 4 simple steps.
Creating an HTML page
The first step is to create a blank HTML page that will hold the interactive flow map. Add a div
element with a unique id to this page which will be referenced later.
I set the width and height of the div to 100% so that the map is displayed over the entire screen. This can be modified based on the requirement and preference.
<html>
<head>
<title>JavaScript Flow Map</title>
<style type="text/css">
html, body, #container {
width: 100%; height: 100%; margin: 0; padding: 0;
}
</style>
</head>
<body>
<div id="container"></div>
</body>
</html>
Adding the necessary scripts
To use a charting library for building a data visualization, you will need to link the appropriate JS scripts of the library you are using. All these script files are included in the HTML page.
For creating a JS flow map, I will add AnyChart’s ‘core’ and ‘geo maps’ modules.
Since the map is of the whole world, I link the file containing the world geodata, from the library’s collection of maps also available on its CDN.
In addition, I will make use of another JavaScript library - Proj4js - which, in short, takes care of plotting the coordinates over the respective geographical areas.
<html>
<head>
<title>JavaScript Flow Map</title>
<script src="https://cdn.anychart.com/releases/8.10.0/js/anychart-core.min.js"></script>
<script src="https://cdn.anychart.com/releases/8.10.0/js/anychart-map.min.js"> </script>
<script src="https://cdn.anychart.com/geodata/latest/custom/world/world.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.3.15/proj4.js"></script>
<script src="https://cdn.anychart.com/releases/8.10.0/js/anychart-data-adapter.min.js"></script>
<style type="text/css">
html, body, #container {
width: 100%; height: 100%; margin: 0; padding: 0;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
// All the code for the JS flow map will come here
</script>
</body>
</html>
Connecting the data
For the map, the data needs to have the latitude and longitude coordinates along with the other information to be displayed. I created the dataset by collating immigration information from Wikipedia and adding the coordinates from a site called Latlong.
For the flow map, I need the latitude and longitude of the source country as well as the destination country. Here, the destination country is the US for all the data points. To check out how the dataset looks, you can find the file here.
To load the data file, I will include the Data Adapter module of AnyChart in the <head>
section of the HTML page.
Writing the JS code to draw the flow map
Before anything else, I will enclose all the code inside the anychart.onDocumentReady() function which will ensure that the page is fully loaded before anything is executed. Next, I will load the data using anychart.data.loadJsonFile() function.
Now, I create the flow map using the connector function since this is a type of connector map and then set the geodata along with settings to make sure that all the regions of the world are visible clearly.
anychart.onDocumentReady(function () {
anychart.data.loadJsonFile( 'https://gist.githubusercontent.com/shacheeswadia/a20ba5b62cef306ccc1a8e8857e5316a/raw/0337b16fa8dc4de97263bc0a4ededf935a529c35/migration-data.json',
function (data) {
// Creates map chart
var map = anychart.connector();
// Sets settings for map chart
map.geoData('anychart.maps.world');
// Darkens all the regions
map
.unboundRegions()
.enabled(true)
.fill('#E1E1E1')
.stroke('#D2D2D2');
})
});
I add a title to the chart and allow the overlap so that all the data points along with their labels are seen on the map even if they overlap.
// Sets title for map chart
map
.title('Migrations to the USA from the top 15 countries');
// Display all labels even if there is an overlap
map.
overlapMode("allow-overlap");
Now comes the main part of creating the connector series which will represent the various connections.
For this, I create a helper function with data as its parameter. In the function, I create the series that will form the connector lines and add the arrow markers at 100% position which is the destination since our flow is from the various source countries to the destination country - the US.
I then add labels that display the source country names.
// Helper function to create several series
var createSeries = function (data) {
// Creates connector series and customizes them
var connectorSeries = map
.connector(data);
connectorSeries
.markers()
.position('100%')
.size(10);
// Sets labels for the source countries
connectorSeries
.labels()
.enabled(true)
.format(function () {
return this.getData('from');
});
};
I now set the data and call the function that I created with that dataset as the argument. The final steps are setting the container to reference the previously added div and drawing the map.
// Creates Dataset from the data
var dataSet = anychart.data.set(data).mapAs();
createSeries(dataSet);
// Sets container id for the chart
map.container('container');
// Initiates chart drawing
map.draw();
Lo and behold! A nice, functional, JavaScript-based flow map is made! It wasn’t that tough to create such an interactive data visualization, was it?
Have a look at this initial version on CodePen.
Customizing the JS Flow Map
The existing flow map just built using JavaScript is a good representation showing where the majority of migrants come from. But the amount of immigrants from each country is not displayed. So, I will customize the map to show that and make the map more insightful, with some additional code. I will also improve the visual aesthetics and make some other minor changes.
A. Setting the colors and size of the connectors along with the legend
I decided to represent the number of migrants flowing to the US from each country by the thickness of the connector line as well as a color palette. It is not necessary to do both as either indicator can be used, but I like how the insights are more easily readable when there are both.
I modify the helper function to include name and color parameters along with the data. I will use the name to identify the connector series and manage the thickness of the lines whereas the color variable will indicate the color that I will specify while calling the function for each series.
I then add the name and color to the connector series and also add settings for hovering on the line and marker.
Next, I set the thickness of the lines based on the name of the series. This naming is based on the number of migrants which will be clearer once the function is called.
Since the connector series has different colors based on the data, I add a color legend.
Do not get overwhelmed if all this sounds complicated. Once you look at the code and the comments along with each snippet, it will make more sense.
// Helper function to create several series
var createSeries = function (name, data, color) {
// Creates connector series for destinations and customizes them
var connectorSeries = map
.connector(data)
.name(name)
.fill(color)
.stroke({
color: color,
thickness: 2
});
// Changes color to indicate the hovered line
connectorSeries
.hovered()
.stroke('1.5 #212121')
.fill('#212121');
// Settings for the arrow marker
connectorSeries
.markers()
.position('100%')
.fill(color)
.stroke({
color: color
})
.size(8);
// Settings for the hovered marker
connectorSeries
.hovered()
.markers()
.position('100%')
.size(10)
.fill('#212121')
.stroke('2 #455a64');
// Sets labels for the source countries
connectorSeries
.labels()
.enabled(true)
.format(function () {
return this.getData('from');
});
// Sets the thickness of the line based on the series
if (name === 'More than 50,000') {
connectorSeries.startSize(5).endSize(2);
} else if (name === '40,000 to 50,000') {
connectorSeries.startSize(3.5).endSize(1.5);
} else if (name === '20,000 to 40,000') {
connectorSeries.startSize(3).endSize(1);
} else if (name === '16,000 to 20,000') {
connectorSeries.startSize(2).endSize(0.5);
} else {
connectorSeries.startSize(1).endSize(0);
}
// Sets settings for legend items
connectorSeries
.legendItem()
.iconType('square')
.iconFill(color)
.iconStroke(false);
};
Before going further, I will create another helper function that is a filter function which is used to segregate data in each series. I will add this function right at the end of the code.
// Helper function to bind data field to the local var.
function filterFunction(val1, val2) {
if (val2) {
return function (fieldVal) {
return val1 <= fieldVal && fieldVal < val2;
};
}
return function (fieldVal) {
return val1 <= fieldVal;
};
}
Now, I create 6 different series based on the total migrant data, giving each of them a unique name, filtering data for each based on the total field in the dataset, and giving unique color values as the third argument. This creates 6 connector series groups with varying thickness and color based on the total migrant data.
// Creates 6 series, filtering the data by the amount of migration numbers
createSeries(
'Less than 16,000',
dataSet.filter('total', filterFunction(0, 16000)),
'#fed693'
);
createSeries(
'16,000 to 20,000',
dataSet.filter('total', filterFunction(16000, 20000)),
'#f5ad52'
);
createSeries(
'20,000 to 40,000',
dataSet.filter('total', filterFunction(20000, 40000)),
'#3fb8c5'
);
createSeries(
'40,000 to 50,000',
dataSet.filter('total', filterFunction(40000, 50000)),
'#1792c0'
);
createSeries(
'More than 50,000',
dataSet.filter('total', filterFunction(50000, 1000000)),
'#1c5eaa'
);
Since I added the legend, I enable it for the map and add a title to the legend.
// Turns on the legend for the sample
map
.legend()
.enabled(true)
.position('center')
.padding([20, 0, 20, 0])
.fontSize(10);
map
.legend()
.title()
.enabled(true)
.fontSize(13)
.padding([0, 0, 5, 0])
.text('Number of Migrants (in the year 2019)');
Note that the legend is interactive. So you can hover over each element of the legend and the corresponding series group will get highlighted. You can also click on the legend elements to add or remove that particular group of series. This is all inbuilt capability of the JS charting library. Pretty impressive, right?
Take a look at this intermediate customized version of the flow map here and check out the entire code on CodePen.
B. Improving the tooltip information
The JavaScript flow map’s default tooltip shows the latitude and longitude of the source and the destination. This information is not of any use to us. So, I customize the tooltip to display the name of the country and the total number of migrants from that country.
I use HTML for the tooltip that enables me to format the text. This makes the tooltip more informative and appealing.
// sets tooltip setting for the series
connectorSeries
.tooltip()
.useHtml(true)
.format(function () {
return (
'<h4 style="font-size:14px; font-weight:400; margin: 0.25rem 0;">From:<b> ' + this.getData('from') + '</b></h4>' + '<h4 style="font-size:14px; font-weight:400; margin: 0.25rem 0;">Total Migrants::<b>' + this.getData('total').toLocaleString() + '</b></h4>'
);
});
C. Enhancing the title and labels
In the end, I make some simple modifications to enhance the aesthetics of the map and add some insights to the title.
I format the title and add a subtitle using HTML again to make the styling of the text customizable.
// Sets title for map chart and customizes it
map
.title()
.enabled(true)
.useHtml(true)
.padding([0, 0, 40, 0])
.text(
'<span style="color:#212121;">Migrations to the USA from the top 15 countries</span><br/>' +
'<span style="font-size: 14px;">Majority of the migrants come from Mexico, Asia and South America</span>'
);
Lastly, I shift the map legend to the bottom and darken the label colors to make them more prominent.
map
.legend()
.position('bottom')
Done! A beautiful interactive JS flow map is ready to illustrate the data on immigration to the United States.
The entire code for the final finished version is on CodePen.
Conclusion
Building interactive maps with JavaScript can be difficult, but using JS charting libraries makes it so much simpler and quicker to create such visualizations. There are many chart types available in AnyChart that you can check out here or look into the other JavaScript charting libraries to find out more about them.
I hope this tutorial has demystified creating a flow map for you and made you excited to explore more charts with JavaScript libraries. Whether you are a native or a migrant, home is where everyone is happier and JS charting libraries are where chart creation is easier!
Please ask any questions or let me know of any suggestions that you have.