How to Create a Hub-and-Spoke Plot with Plotly
By: Ehsan Khodabandeh
Tags: Data Science, Programming, Data Visualization, Python

How to Create a Hub-and-Spoke Plot with Plotly

Plot lat/long data on the map with lines connecting the locations

A while ago, I needed to plot the lines connecting the latitude (lat) and longitude (long) coordinates of some locations on the map. What I needed to draw is known as the “hub-and-spoke” plot. Knowing that Plotly already has some samples such as the one here, I was able to do it pretty easily. But real-life data won’t always lend itself perfectly to Plotly’s examples, and there may be other requirements that out-of-the-box examples don’t meet.

In this blog, I’ll show you how to build one of these graphs on your own. In particular, we’ll assume that:

The data is not always from the same region (like the US). Our geospatial data can be for North America, South America, or Europe.

The plot needs to be saved as an HTML file.

The graph should be offline*.

*This was actually something that required more effort prior to Plotly version 4, especially since their documentation emphasizes online mode, as you can see here. But version 4 is offline only. You can read more about the migration of online features here.

Some Background on Plotly Figures

There are multiple ways to represent a figure in Plotly. Two of the most common ways are using (a) dictionaries or using (b) graph objects. Though each has its pros and cons, Plotly recommends the latter for its additional benefits, which you can check out here.

To create a figure, we need two main components: the data and a layout. From the documentation:

You can build a complete figure by passing trace and layout specifications to the plotly.graph_objects.Figure constructor. These trace and layout specifications can be either dictionaries or graph objects.

The layout dictionary specifies the properties of the figure’s layout, while data is a list of trace dictionaries with configuration options for individual traces (trace is a word Plotly uses to describe plots).

Now that we know how figures are constructed, we can prepare our data and plot them on the map. Although the code below is for Plotly version 4, you can find the implementation for both versions 3 and 4 in this Jupyter Notebook.

Data Preparation

The data (as you can see in this sample .csv file) consists of information about each origin and destination location (namely, city, state, country, lat, and long). To draw the plot, we only care about:

Lat/long of origins and destinations.

Location attributes on the map, such as size, color, and shape.

And any other information about a location, or the path between the locations that we’d like to show on the map. For example, you may want to show the name of each location, or the length (in miles) of each path when a user’s mouse hovers over it (We only do the former here.)

So first, let’s load the data:

import pandas as pd

import plotly.graph_objects as go data = pd.read_csv('sample_us.csv')

Next, create the location dataframe with all needed attributes:

After that, we can add each location and its attributes to the figure:

After that, we can add each path to the figure:

Finally, let’s specify the layout of our map:

Since we want to show the map as an HTML file, we need one more line:

fig.write_html(file=f'{title}.html', auto_open=True)

That’s it! This works as intended, but only if the lat/long data is confined to the US. To make it more responsive to different kinds of underlying data (like European locations, for example), we need to make one small change.

General Data

If you look closely at the codes above, you’ll see that in the layout dictionary, we specified scope='usa' . According to Plotly’s documentation, scope accepts one of the following values:

"world" | "usa" | "europe" | "asia" | "africa" | "north america" | "south america".

In order to decide what scope to use, we should look at the range of lats and longs in our data.

We can then compare our range against the approximate range of each scope to decide which one to use (I found these by searching for them online — they should serve our purposes just fine.)

All that’s left to do is to change scope=scope in layout and we’re all done.

And voila! We’re done. Let’s test out this method on two different datasets.

This is a map of US data:

And this is one from Europe:

Note that the sample data and the more-detailed code for this blog are available on GitHub.