Highlighted line chart with Plotly.Express | by Vaclav Dekanovsky | Towards Data Science

Creating interactive graphs with python’s Plotly.Express from a data frame works like a charm. With a single line of code, you can explore the basic characteristics of your dataset. Adding a few more code-lines you can conjure up a really fancy but very narrative chart.
In this exercise, I’ll walk you through the process of drawing a thick colored line on the top of the shaded progress of the concurrent events. It has two enormous benefits:
The trend you want to highlight is clearly visibleThe grey background suggests the distribution of other events
The trend you want to highlight is clearly visible
The grey background suggests the distribution of other events
You can create all the charts with me, using the notebook stored on the Github. In this article you will learn:
How to install Plotly
How to create a line plot using px.line(df, parameteres)
How to color only some lines using fig.update_traces or color_discrete_map
The order of the lines matter
How to position annotations on the Plotly’s chart
How to use the annotations to label, highlight, create a legend or describe chart’s area
How to add interactive buttons
How to use this kind of graph to show changes in ranking
I’ll use two datasets. The first about the progress of tourism around the world (tourist arrivals, 215 countries from 1995 to 2018), and the second showing ice-hockey national teams ranking in the last 6 years.
The dataset is preprocessed in preprocess.ipynb notebook on github and stored in python’s pickle.
Plotly.Express was introduced in the version 4.0.0 of the plotly library and you can easily install it using:
# pip pip install plotly# anacondaconda install -c anaconda plotly
Plotly Express also requires pandas to be installed, otherwise, you will get this error when you try to import it.
[In]: import plotly.express as px[Out]: ImportError: Plotly express requires pandas to be installed.
There are additional requirements if you want to use the plotly in Jupyter notebooks. For Jupyter Lab you need jupyterlab-plotly. In a regular notebook, I had to install nbformat (conda install -c anaconda nbformat)
towardsdatascience.com
Creating the line chart with plotly cannot be easier. The syntax goes as px.line(df, parameters). It looks simple, but the number of parameters is quite big. It’s also important to note that Plotly.Express loves long data structure (in contrast to wide structure), where each category and each value is a column.
I’ll draw and line chart for each country, showing yearly tourists in all the 215 countries. Plotly can handle that, but the chart won’t tell you much information. You can switch off/on the lines, using the menu-legend on the right, but it’s so long that you have to scroll through it to find your countries. When you hover over the lines a tooltip pops up providing details about the data.
# simple line chart with Plotly Expresspx.line(long_df,         x="years",         y="visitors",         color="Country Name",         title="Growth of tourism 1995-2018")
Let’s say for your research or marketing purposes you should present the evolution of tourism in Japan 🇯🇵 and Turkey 🇹🇷. So let’s blur the other lines, giving them a grey shade similar to the background, and highlight the Japanese and Turkish lines by coloring them. You have a few options to do so.
# first option is to use, .update_traces()fig = px.line(long_df,              x="years",              y="visitors",                color="Country Name")# set color of all traces to lightgreyfig.update_traces({"line":{"color":"lightgrey"}})# color Turkish line to bluefig.update_traces(patch={"line":{"color":"blue", "width":5}},                   selector={"legendgroup":"Turkey"})# color Japanese line to redfig.update_traces(patch={"line":{"color":"red", "width":5}},                   selector={"legendgroup":"Japan"})# remove the legend, y-axis and add a titlefig.update_layout(title="Tourism Growth in Turkey and Japan",                showlegend=False,                yaxis={"visible":False})# plot the chartfig.show()
Using fig.update_traces({"line":{"color":"lightgrey"}}) changes the color of all lines to light grey. Then we use patch and selector arguments of the .update_traces(). The patch sets the parameters of the line and selector defines to which line they are applied.
If you wonder how I know which values to supply to the dictionary, that {"line":{"color":"blue", "width":5} changes the properties of the line and that legendgroup is the right parameter to identify the line by Country name the easiest way is to read fig["data"]. Each Plotly chart is a dictionary and all parameters can be changed when you update this dictionary.
[In]: fig["data"][Out]: (Scattergl({     'hovertemplate': 'Country Name=Aruba<br>years=%{x}<br>visitors=%{y}<extra></extra>',     'legendgroup': 'Aruba',     'line': {'color': 'lightgrey', 'dash': 'solid'},     'mode': 'lines',...
Each plotly chart is a dictionary. Use fig.to_dict() or fig[“data”] to see the values.
But the result is not what we wanted. Our traces showing tourism evolution in Japan and Turkey are somewhere in the middle. Some grey lines lie over them and some remain below.
We can try Plotly’s parameter category_orders which is meant to influence the order of the lines, but adding category_orders={"Country Name":["Japan","Turkey"]}) makes it even worse. These traces are displayed first and all the grey lines are on top of them.
We can supply the full list of countries with Japan and Turkey at the end, but it’s easier to sort the dataframe itself. We .map() order 1 to Japan and 2 to Turkey and fillna(3) to all the other lines, then sort the data frame by this value.
# sort the dataframesorted_df = long_df.copy()# map the value ordersorted_df["order"] = sorted_df["Country Name"].map({"Japan": 1, "Turkey": 2}).fillna(3)# sort by this ordersorted_df.sort_values(by=["order","years"], ascending=False, inplace=True)
Another option of how to color the lines is to supply a dictionary to the plotly’s color_discrete_map parameter instead of applying fig.update_traces(). The dict structure is {trace_name: color}.
{'Aruba': 'lightgrey', 'Zimbabwe': 'lightgrey',... 'Turkey': 'red', 'Japan': 'blue'}
To change the width, you can manipulate the Plotly’s backend dictionary directly.
for i,d in enumerate(fig["data"]):    if d["legendgroup"] in ["Japan","Turkey"]:        fig["data"][i]["line"]["width"] = 5        fig.show()
Remember that all the code, including these various options on how to influence the look of the Plotly chart is available on Github.
The chart still looks quite ugly. We have removed the legend and the axis and we don’t even know how many visitors came to these countries. To show the viewer the important values, we must annotate the plot. Annotations are another dictionaries which are added into .fig.update_layout(..., annotations=[]). This annotation parameter contains a list of dictionaries and each dict is one annotation.
turkey_annotation = \[{"xref":"paper", "yref":"paper", "x":0, "y":0.15,  "xanchor":'right', "yanchor":"top",  "text":'7M',  "font":dict(family='Arial', size=12, color="red"),  "showarrow":False}, ... other annotations ...]
You can influence many parameters of the annotation. Its position, font, and whether there is an arrow pointing from the annotation text to some point on the chart. The coordinates x and y of the text can either refer to the plot or the paper-canvas. In the first case, you specify the position using values displayed on the axes, in the second (0,0) is the bottom left corner of the plot area and (1,1) is the top right corner.
The position also depends on the anchor (top-middle-bottom, left-center-right), offsets and adjustments. Each annotation can be modified by setting its font or HTML tags can be applied on the text like <b> or <i>.
Look at this gist and the resulting plot to understand the options you have when annotating.
If your annotations don’t fit the canvas, you can increase the space around the plot with margin={"l": pixels, "r": px, "t": px, "b": px} inside the .update_layout().
When we play a bit with our tourism chart, we can achieve quite good results. We set xref to paper and set it 0 for the beginning of the line. Adding xanchor="left” will align while text to the left of the plot area. You can set yref=”paper" and try to find the ideal position on the scale between 0 and 1, but it’s easier not to set it as paper and use the exact position e.g. 3 300 000 (note that since python 3.6 you can use underscores in numeric literals due to PEP515 and write the million as 3_300_000)
So the complete chart would look like this:
I think the most interesting about annotations outside of the plot area is that you can refer x to be on the canvas and y using the coordinates on the chart. You can set, "x":0 (on the canvas), "xanchor":"right and "y": 7_000_000 on the plot, "ynachor":"middle".
The chart above is pretty impressive, isn’t it, but what if you can add something more? What if you can give the users the option to highlight whatever data they want? That can be achieved with interactive buttons.
Buttons are added via fig.update_layout(updatemenus=[]). You are now probably used to the fact, that each element is described by a python dictionary and buttons are no exception.
Each button has several parameters:
args: what happens when the button is clicked
args2: what happens when it’s unclicked (to create toggle buttons)
label: what is written on the button
method: whether the button changes the plot, the layout or both
Each of args, args2 also accepts 3 parameters — changes to the traces, changes to the layout, and list of traces that are influenced. E.g.
args=[    # updates to the traces    {"line.color":["blue"],"line.width":5},         # changes to the layout    {"title":"Tourism growth in Japan",      "annotations": annotation_japan},         # which trace is affected      [1,2]     ]
The button set offers additional parameters influencing their position and style. Similarly to annotations, you can position the buttons around the plot. You can also choose between the single button, set of buttons, or a dropdown.
Let’s add buttons to switch on/off our two highlights about tourism in Japan and Turkey.
The buttons in Plotly Express are not almighty. If you click on Japan and Turkey buttons without clearing the plot by the second-toggle-click, you will see both colored lines. I didn’t find out how to ensure that one is dyed while all the others are grey. Such complex interaction can be achieved only with the Plotly’s dashboard library — Dash. Its callback actions allow almost every imaginable output.
You might also notice that so many lines, Plotly automatically picked WebGL format proven to improve the usability of the JavaScript plots with many data points.
When I have first encountered this kind of chart, it displayed the rank of most popular national parks and how it evolved over time. I can’t find this example anymore, so I’ve decided to create one rank evolution chart of my own. I’ll look at the Ice-Hockey national team rankings over the last 5 years.
Because I have switched to Linux recently, I gathered the data manually into .ods spreadsheet. You can use pd.read_excel(path, engine="odf) to collect the data, but I had to install pip install odfpy to achieve that.
Then you repeat the above code to display the overview of the ranks and highlight your favorite country. Here I had to deal with one new problem though. I wanted to show labels displaying the rank each year, only to this highlighted line (while it is supposed to be hidden for all the others).
Again this problem had two solutions with slightly different results. You can either use fig.update_traces() and set both colors and labels. In this case, you must specify both text and mode:
fig.update_traces(    patch={        "line":{"color":"yellow", "width":5},         "text":sweden["Ranking"].values,        "textposition":"top center",        "mode":"lines+text",        "textfont":{"size":15, "color":"blue"},        },     selector={"legendgroup":"Sweden"})
With this approach, the text is kind of ugly stick to the lines and I did not force it to lift. That can be done when you simply annotate with fig.update_layout(annotations=[]). You iterate over the data frame and prepare a list of dictionaries with each annotation:
annotations = []for index, row in sweden.iterrows():    annotations.append(        {"x": row["Year"],          "y": row["Ranking"],          "yshift": 6,         "text": row["Ranking"],         "xanchor": "center",          "yanchor": "bottom",          "showarrow":False,         "font":{"color":"blue", "size": 15}        }    )
I hope you liked this introduction to the tricks you can do with python’s Plotly Express. You have learned how to create the line chart containing a plethora of information and how to highlight important a part of this data to your audience. You have seen that there is often more than one approach to achieve the same. Finally, we have used the same approach of colored lines on the background of greyed ones to show changes in ranking.
Do you have other use cases for this type of chart?
If you want to create inspiring graphics like those in this post, use canva.com (affiliate link, when you click on it and purchase a product, you won't pay more, but I can receive a small reward; you can always write canva.com to your browser to avoid this). Canva offer some free templates and graphics too.Other articles:* Plotly Histogram - Complete Guide* Everything you wanted to know about Kfold train-test split* How to turn a list of addreses into a map
All exercises can be walked through in this notebook on Github — Highlighted_Line_Chart_on_Grey_Lines_Background