Showing locations on a map can be pretty cool to provide some context for your story or to give your reader an overview of where the story takes place. A good way to build a simple, yet responsive and professional looking map is to use the JavaScript library Leaflet.

In an earlier post, „Your first choropleth map„, we used Leaflet as well, but coded the map using the Leaflet R package, which works like a wrapper to translate the more common Leaflet functions into R syntax. It’s very useful if you’re more used to R syntax and don’t want to learn JavaScript anytime soon. But using the original JS library and coding the map with JavaScript will give us way more freedom when customizing the map, which is why we’ll try it that way today.

As an example, let’s start with a map of the locations of data journalism newsrooms in the German speaking area. This is what the finished map will look like:

Before we start, we need to pick a code editor. As we’ve discussed in our Intro to HTML, CSS and JavaScript, we could write our code in any text editor if we wanted. But its way nicer to work with an editor that helps us organize our code, that highlights the Syntax and helps with indentation and code completion. Most data journalists I know use Sublime or Atom. Atom is a project by the great guys from GitHub and it’s open source. I prefer using it, while Kira, for example, prefers Sublime because it’s better with larger datasets. Once you’ve set up your editor of choice, you can start right away.

Create a new folder named my_map. This is where we’re going to put all the files and code we need to build our first leaflet marker map. Now, go to your code editor and open a new file. Let’s save it as index.html. This file is where all the code we’ll write in JavaScript and CSS later will be put in it’s right place so it can work together to create our map. If you’re confused about any of this, take a quick look at part two of our intro post, where we discuss JavaScript libraries and file management.

When we’re all done, a simple double click on index.html will open a browser window showing us our map. Now, let’s build our map, file by file.

 

index.html

This file will provide a structure that our map can bind to so it has somewhere to display. We’re going to go over its content a little quicker in this post, since we’ve discussed the structure of HTML documents in our previous post.

We’ll start with a <html> tag that contains the entire content of our document. Between <head> tags we can include scripts, styles and meta information. In the meta tag we set the charset to utf-8 to include German umlauts (ä,ö,ü and ß) for our map like in Düsseldorf or Tröger.

With <link> we can source Leaflets CSS stylesheets from an URL. With <script> we include the JavaScript code of the library. If you copy and paste the URLs to your browser, you can have a look at all the code that’s in the library! Instead of including them via an URL, we could also save the entire content to a file and keep it locally in our my_map folder.

<!DOCTYPE html>
<html>
<head>

  <meta charset="utf-8">

  <link rel="stylesheet" href="https://leaflet.github.io/Leaflet.draw/lib/leaflet/leaflet.css">
  <script type="text/javascript" src="https://leaflet.github.io/Leaflet.draw/lib/leaflet/leaflet.js"></script> 

</head>
</html>

leaflet.css and leaflet.js are the default scripts and styles of a leaflet map. We can use the functions provided by those files to write our personal maps. We’ll save our code in additional files and source them between the <head> tags of the index.html file. When opening the index file in a browser, the default libraries and the files with our changes are loaded. This is why we’ll add a style.css and an app.js to the html document. We’ll create those in a minute.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="https://leaflet.github.io/Leaflet.draw/lib/leaflet/leaflet.css">
  <script type="text/javascript" src="https://leaflet.github.io/Leaflet.draw/lib/leaflet/leaflet.js"></script>
  
<!-- add our additional css and js -->
<link rel="stylesheet" href="style.css">
  <script type="text/javascript" src="app.js"></script>

</head>

</html>

Last but not least, we’ll add a div containing the map between the <body> tags. We’ll give it the ID map, which is the variable name we will pass to the JavaScript code in app.js so it knows where on the webpage to put the map. We add an onLoad event to the body, so the JavaScript is executed as soon as the page is loaded.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="https://leaflet.github.io/Leaflet.draw/lib/leaflet/leaflet.css">
  <script type="text/javascript" src="https://leaflet.github.io/Leaflet.draw/lib/leaflet/leaflet.js"></script>
  
<!-- add our additional scripts -->
<link rel="stylesheet" href="style.css">
  <script type="text/javascript" src="app.js"></script>

</head>

<!-- add map to body -->
<body onload="myFunction()">

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

</html>

When you build more complex webpages, your index.html will probably contain a lot more elements. But for now, we want to create a page containing only the map, so our html file is ready. Save it in your my_map folder. If you doubleclick it, an empty browser window should open. There’s nothing there yet, of course, because we haven’t coded the actual map.

 

Custom marker icons

Before we get into starting the map, let’s create a folder named icons in our main folder. Because we’ll start with two location markers, we’ll add two custom markers I made with the design program sketch as png-files:

RPData.png | SRF.png

Save them in my_maps>icons.

 

add.js

Finally! Hands on the map! Open a new file in your text editor.

Everything we’ll code here has to be within the function myFunction() that we added as an onLoad event in the index.html. If you named it differently, use the name you chose here as well.

function myFunction() {

}

Next, we add the variable map. Here we set the default viewpoint and the background tiles. You can choose background tiles and get the code to change the settings here. The function setView() sets the default viewpoint and zoom level when the page is loaded. We’ve set it to 52.201675, 10.507759. If you look for that location in Google Maps, you’ll find yourself in Braunschweig, Germany.

function myFunction() {

// create variable named map, set viewpoint and default zoom level  
var map = L.map('map').setView([52.201675, 10.507759], 7);

// add tile layer 
  L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}', {
	attribution: 'Tiles &copy; Esri &mdash; Esri, DeLorme, NAVTEQ',
	maxZoom: 16
}).addTo(map);

// add a minimal zoom to prevent users from zooming out too far
  map._layersMinZoom=5;

}

Next, we’ll add the custom icons after map._layersMinZoom=5; and before }, which ends the function.

  var srf = L.icon({
// path to the icon
      iconUrl: 'icons/SRF.png',
// size of the icon 
     iconSize:     [60, 52],
// point of the icon which will correspond to marker's location 
      iconAnchor:   [0, 26],
// point of the icon where the popup window will open
      popupAnchor: [35, -26]
  });

  var rp = L.icon({
      iconUrl: 'icons/RPData.png',
      iconSize:     [52, 60], 
      iconAnchor:   [26, 60], 
      popupAnchor: [0, -60]
  });

Now we add the location markers to the map…

  var p1 = L.marker([47.417563, 8.560453], {icon: srf}).addTo(map);
  var p2 = L.marker([51.233059, 6.698716], {icon: rp}).addTo(map);

…and bind a popup text to them:

// this is basic html! Visit http://www.w3schools.com/tags to learn about html tags!
p1.bindPopup("<strong style='color: #84b819'>SRF Data</strong><br>Schweizer Rundfunk und Fernsehen | Zürich<br>Head: Sandra Manca");

// the .openPopup() opens this popup when the page is loaded
p2.bindPopup("<strong style='color: #84b819'>RP Data</strong><br>Rheinsiche Post | Düsseldorf<br>Head: Phil Ninh").openPopup();

var popup = L.popup();

Remember that the code has to be within the function myFunction(). Now save the file as app.js in the my_map folder.

Doubleclicking on the index.html still results in an empty browser window. Let’s change that!

 

style.css

Open up another empty file in your code editor. This will be our style sheet, which further tells the browser how our map should look. The most important style we’ll add is the height of our map:

#map {
    height: 550px;
}

This was the reason we didn’t see the map before. If you now save the file as style.css in the my_map-folder and doubleclick the index.html, magic happens! Try it ; )

Awesome! But let’s add some more style:

.leaflet-popup-content {
/* change size of margin */
    margin: 14px 14px;
/* make the line height smaller */
	line-height: 1.4;
	}

/* change color when the cursor hovers over the popup close button */
  .leaflet-container a.leaflet-popup-close-button:hover {
  	color: #9d132a;
  	}

/* change color of an unvisited link and the zoom symbols */
    a:link {
        color: #9d132a;
    }

/* change color of a visited link */
    a:visited {
        color: #84b819;
    }

/* change color when the cursor hovers over a link */
    a:hover {
        color: #e11b3c;
    }

Now save the changes and doubleclick index.html! Here you have your first customized marker map with leaflet.js. Congratulations! If that’s enough for you right now, that’s fine. In the last section, we’re going to take a look at how to automatically add multiple markers at once to your map.

 

Adding information to app.js with a loop

If you want to add more locations with custom markers and popup texts to a map, you might not want to code every icon and marker individually. There is a faster way to get this, even if it looks more complicated at first glance. But let’s give it a try!

We will now add some more of the data journalism newsrooms in Germany and Switzerland. First of all, add these additional custom markers to your icons folder:

BR.png | MOPO.png | NZZ.png | Spiegel.png | Zeit.png

Writing a loop isn’t that time saving when you only have seven locations to mark, like in this example. But imagine if you had a lot more points to add to the map: The manual way I showed above would be pretty exhausting.

Now for the data. Let’s say you have a .csv file with all the information on the different newsrooms we want to add to the map like this:

Bildschirmfoto 2016-07-05 um 21.45.59

We have the longitude and latitude, the newsrooms name, the media company, the city, the newsroom’s head/leader and the path to the customized icon.

Now, JavaScript really likes its data saved as a JSON (JavaScript Object Notation) file. You can convert .csv files into .json files quite easily with free online tools, for example here. For now, simply download the data directly as a JSON file here and add it to the my_map folder. Open it and take a look around to get to know this data format if it’s new to you.

Next, we need to add the library jquery to our index.html to deal with the JSON data:

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

Save the file and open app.js. Delete everything from // create custom marker to var popup = L.popup();. Instead, we are going to load the JSON file and loop through it. Journocoder Sakander Zirai showed me this trick to load the JSON data as soon as the rest of the page is loaded:

// load json-file  
$(document).ready(function() {
      $.ajax({
          type: "GET",
          url: "ddj.json",
          dataType: "json",
          mimeType: "application/json",
          success: function(data) {processData(data);}
       });
  });

When the data from the JSON file is loaded, we want a function called processData() to be executed, like we specified in the success attribute above. It doesn’t exist yet, so let’s write it!

We can loop through the information in the JSON file with a for loop. You may remember for loops from our tutorials on R. They may look a little different in JavaScript, but they’re quite similar to the R for loops in how they work. So let’s start our loop:

  function processData(allText) {

    for (var i in allText){
             data = allText[i];

We’ll begin with the custom markers:

            var customicon = L.icon({
              // the iconUrl is now the ith element in data.icon 
              iconUrl: data.icon,
              iconSize:     [52, 60], // size of the icon
              iconAnchor:   [26, 60], // point of the icon which will correspond to marker's location
              popupAnchor: [0, -60] // point of the icon where the popup window will open
          });

Now, because we have two icons with different orientations (SRF.png and Zeit.png) in our data, we add an if-statement to the loop to customize the icon settings if the company names match these two:

if (data.company === "Zeit Online") {
            customicon = L.icon({
              iconUrl: data.icon,
              iconSize:     [60, 52], 
              iconAnchor:   [60, 26], 
              popupAnchor: [-35, -26]
              });
          };
          if (data.company === "Schweizer Rundfunk und Fernsehen") {
            customicon = L.icon({
              iconUrl: data.icon,
              iconSize:     [60, 52], 
              iconAnchor:   [0, 26], 
              popupAnchor: [35, -26]
              });
          };

// add the marker to the map
           L.marker([data.long, data.lat], {icon: customicon})
          .addTo(map)

Great! Next, we’ll „write“ the popup texts by concatenating elements from the data with some HTML code to style them:

          .bindPopup("<strong style='color: #84b819'>" + data.newsroom + "</strong><br>" + data.company + " | " + data.city + "<br>Head: " + data.head)

// close the loop, the function processData(allText) and myFunction()
          }
  }

}

Now doubleclick on your index.html again and you’ll see your map! You can now add as many markers as you like to your data!

Feel free to change the settings or scroll through the default leaflet css and js to see what functions you can customize. In order to show off your map, just upload my_map to your server and set the url path to the index.html.

Both maps are hosted on our GitHub page. If you have any problems, questions or feedback send us a message or leave a comment! It’s also useful to have a look at Leaflets documentation or to look for help on StackOverflow.