Goal

In this tutorial, we are going to build a simple dashboard that shows some airplanes in the air in real-time thanks to the open API provided by ADS-B. We will use some of the modules and explain their usage and how to adjust them to our needs. So let's not beat around the bush and jump in to make our first dashboard!

Step 1: An empty map

index.html

We start by creating the index.html that will contain all code for our dashboard:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>flight stat</title>
</head>
<body>
<script></script>
</body>
</html>
We already added the script tag that will be populated with all the code.

loading libraries

First of all, we add all dependencies in the head of index.html:
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/topojson@3.0.2/dist/topojson.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.4/lodash.min.js"></script>
The libraries can be obtained through the links listed in the home of the documentation.

In order to add the map, we first include the corresponding module, along with the widget module that is required by all other widgets. So, again in the head, after the dependencies goes the following
<script src="build//widgets.min.js"></script>
<script src="build//adt/widgets.map.min.js"></script>
In this tutorial, the modules are available from the documentation, but you need to change the above to fit your directory structure. And that was all the HTML we had to write, from now on we will only write javascript code to add the all widgets, fetch the data from the flight status API and add interactions within the dashboard.

adding the map

Now we have everything to build our very first dashboard: a static map with no data shown at all. Just add a script tag in the body and create a map widget that fills the entire window:
<script>
var map = new adt.widgets.Map("flight")
    .x(0)                       // Top-left corner is adjusted to
    .y(0)                       // the window
    .width(window.innerWidth)   // Size is set to the window size
    .height(window.innerHeight)
    .render();                  // Must be called to draw the map
</script>
And that's it, congratulations, you just built your first map dashboard in no more than 22 lines! This is how it should look like. Although we don't have any data yet and it lacks some styling, our map can be zoomed with scroll or by clicking on any country! Even more awesome is that we are already through the hard part, adding libraries and setting up a first working dashboard. In the following steps we only add some designing and attach data to the map so that we have a fully functioning application.

chaining

All widgets have been designed with the chaining rule in mind that enables us setting multiple properties of the widget in a simple and clear manner. As you can see, once we created the map widget, we just set the parameters by adding consecutive function calls after the constructor. This is a general rule: all widget properties can be appended in the chain, as the (vast majority of) methods return a reference to the widget itself. When this is not the case, it is noted in the documentation.

Another important behavior is the method render() that must be called in order to draw the widget. Under the hood, adt.widget simply collects the various parameters of the widget while keeping it hidden and when we call render(), the widget actually becomes visible. Moreover, render() is implemented according to the get-or-create design, that is, once it is called, further calls simply update the widget with the new style and data. Therefore, you can simply modify the widget on-the-run and then render it in the end.

styling and basic interactions

This is all nice and good but we want to have some unique dashboard that we can play with! Let's start with the look and feel: adt.widgets.Map has some convenience functions we can call to set up some basic colors. For the sake of simplicity, we just append these right before rendering the map:
<script>
var map = new adt.widgets.Map("flight")
    .x(0)                       // Top-left corner is adjusted to
    .y(0)                       // the window
    .width(window.innerWidth)   // Size is set to the window size
    .height(window.innerHeight)
    .backgroundColor("#111")
    .borderColor("#888")
    .foregroundColor("#222")
    .render();                  // Must be called to draw the map
</script>
The functions backgroundColor(color), foregroundColor(color) and borderColor(color) are rather talkative, so no further explanation is needed. If you take a look at the map now (here is a live example), you can see that countries are highlighted automatically, due to the little brighter foreground color. Also, if you zoom in on a country, it remains highlighted.

Great, now that we have our map, let us fetch some data from the flight stats API and show it!

Step 2: Adding data

fetching data

Here we make use of the adt.rest module that implements the REST class, which is simply a wrapper for the existing request methods (more specifically d3 methods). First, add
<script src="build/widgets.min.js"></script>
to the head of index.html. To get the data, we create a new REST object that is directed to the endpoint of the API:
// We use the cors-anywhere app to access the API
// (even from localhost)
var proxy = "https://cors-anywhere.herokuapp.com/";

// Create REST object that fetches the data
var api = new adt.rest.Rest(
    proxy + "https://public-api.adsbexchange.com/VirtualRadar/AircraftList.json"
);
The above snippet creates a REst object pointed at the flight stats base URL (with a proxy to avoid CORS errors). Sometimes the Heroku app used for the CORS proxy is down and the database is not accessible. If this happens, please try to refresh the page a few minutes later (or check https://cors-anywhere.herokuapp.com if the app is running again). To query the data from the database, we simply call the REST.json method that creates the HTTP request URL and fetches the data that is expected in a JSON format:
// Fetch the data
api.json({fCallS: "SAS"}, function(data) {
    // Data is fetched successfully
    updateMap(data);
}, function(error) {
    // Print error to the console
    console.log(error);
});
The first argument of REST.json is the parameters of the query, which in this case is a filter in the ADS-B database to have only flights with a flight call starting with 'SAS'. The second and third arguments are callbacks that will be triggered in case of success or failure.

draw on the map

What is left is to implement updateMap that will parse the data from the JSON response and add it to the map. But before that, we add a static and a dynamic drawing layer to the map:
map.staticLayer.add("SAS");     // Add static layer 'SAS'
map.dynamicLayer.add("SAS");    // Add dynamic layer 'SAS'
For the sake of simplicity, we add each flight as a white dot. The static dot will show the trail of the flight, the dynamic dot will tell if it is updated. After a little investigation of the structure of the data we receive from ADS-B, we can extract the coordinates of each aircraft:
// Update method
function updateMap(data) {
    // Loop through aircrafts
    data.acList.forEach(function(d) {
        // Get geo coordinates
        var latLon = [d.Lat, d.Long];

        // Add to static map as trail of the flight
        map.staticLayer.draw.dot("SAS", latLon, 1, "white");

        // Add to dynamic map to see the plane is updated
        map.dynamicLayer.draw.dot("SAS", latLon, 10, "white", 400);
    });
}
Now if you open the page, you should see some while dots around Scandinavia after the flashing animation of circles (this is how it should look). The animations are shown on the dynamic layer with initial size of 10 pixels, and a duration of 400ms, whereas the static dots are added to the static network. To speed up things a little, the map uses canvas for the static drawings and svg for the animated ones. Note that the map only draws elements with valid coordinates. If we really wanted, we could check if the dot was drawn by examining the return value of the methods draw.dot in each layer, but for now we skip that.

some improvements

We can make our map a little more active by wrapping the complete data fetching and draw in a setInterval:
// Query the database every 10 seconds
setInterval(function() {
    api.json({fCallS: "SAS"}, function (json) {
        // Data is fetched successfully
        updateMap(json);
    }, function (error) {
        // Print error to the console
        console.log(error);
    }, false);
}, 10000);
Since we don't clear the static page, we can actually see the trail of the flights, which become more revealing if you zoom in on the map around Scandinavia. To spice things up a little, let us track each flight individually by adding a variable that contains the flight info and the updateMap function, create a new flight whenever we see an unknown call. We also set a unique color for each new flight so that we can distinguish among them. When new data is available, we only update those flights that have a displacement more than 10 degrees in total along the latitude/longitude. All together our new updateMap method:
// Update method
var flights = {};
function updateMap(data) {
    // Loop through aircrafts
    data.acList.forEach(function(d) {
        var id = d.Call;
        if (!flights.hasOwnProperty(id))
            flights[id] = {
                color: "rgb(" + parseInt(255*Math.random())
                    + "," + parseInt(255*Math.random())
                    + "," + parseInt(255*Math.random()) + ")"
            };

        // Get geo coordinates
        var latLon = [d.Lat, d.Long];

        // Show airplane if it is new or latitude/longitude has an angle difference of 0.1
        if (!flights[id].hasOwnProperty(latLon) ||
            Math.abs(flights[id].latLon[0]-latLon[0])+Math.abs(flights[id].latLon[1]-latLon[1]) > 10) {
            // Add to static map as trail of the flight
            map.staticLayer.draw.dot("SAS", latLon, 1, flights[id].color);

            // Add to dynamic map to see the plane is updated
            map.dynamicLayer.draw.dot("SAS", latLon, 2, flights[id].color, 400);
        }

        // Update coordinates
        flights[id].latLon = latLon;
    });
}
For a reference, here you can see a working version of the automated and colored map.