Enriching Data Visualizations with Annotations in Plotly using Python

“A picture is worth a thousand words.” Indeed it is, and an annotated picture is worth its contribution to data literacy.

Reshama Shaikh
Nerd For Tech
6 min readAug 15, 2021

--

With the advances in programming and computing, data scientists can now do state-of-the-art visualizations without requiring in-depth knowledge of D3 or Javascript. We can accomplish this using the python library plotly!

For these examples, I am using Python version 3.9 and plotly version 5.1.0.

A video version of annotations in plotly is available on YouTube.

Enriching Data Visualizations with Annotations in Plotly (10-minute presentation)

STEP 1: Read in sample data for visualization

import pandas as pdevent_dates = ['2020-01-01', '2020-02-01', '2020-03-01',
'2020-04-01', '2020-05-01', '2020-06-01',
'2020-07-01', '2020-08-01', '2020-09-01',
'2020-10-01', '2020-01-01', '2020-02-01',
'2020-03-01', '2020-04-01', '2020-05-01',
'2020-06-01', '2020-07-01', '2020-08-01',
'2020-09-01', '2020-10-01']
groupid = ['A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A',
'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B']
outcome = [92, 93, 107, 91, 113, 83, 87, 99, 101, 107,
103, 145, 131, 116, 131, 109, 108, 105, 96, 127]
data = {'event_date': event_dates,
'groupid': groupid,
'outcome': outcome}
# create the dataframe from the arrays
df = pd.DataFrame(data)

Look at data frame, by sampling a few rows: df.sample(5)

STEP 2: Create a basic, 2-group, line plot

import plotly.express as pxfig = px.line(df
, x='event_date'
, y='outcome'
, color='groupid'
, title = "Event Data Over Time for Two Groups")
fig.show()

STEP 3: Extend the x-axis range a bit

Let’s extend the x-axis on the left and right a little bit, so it is cleaner and easier to read and the graph doesn’t go to the edges.

This parameter gives options for the x-axis range: range_x=[..., ...]

# extend the x-axis rangefig = px.line(df
, x='event_date'
, y='outcome'
, color='groupid'
, title = "Event Data Over Time for Two Groups”
, range_x=['2019–12–01', '2020–11–01'],)
fig.show()

STEP 4: Add an author and data source footnote

This allows us to add a footnote annotation: fig.add_annotation(...).

fig = px.line(df
, x='event_date'
, y='outcome'
, color='groupid'
, title = "Event Data Over Time for Two Groups"
, range_x=['2019-12-01', '2020-11-01'],)
fig.update_layout(autosize=False, width=800, height=600,)# add today's date
from datetime import date
today = date.today()
# HOW TO ADD A FOOTNOTE TO BOTTOM LEFT OF PAGE
fig.add_annotation(
text = (f"@reshamas / {today}<br>Source: JHU CSSE")
, showarrow=False
, x = 0
, y = -0.15
, xref='paper'
, yref='paper'
, xanchor='left'
, yanchor='bottom'
, xshift=-1
, yshift=-5
, font=dict(size=10, color="grey")
, align="left"
,)
fig.show()

Tip: you may want to adjust the above values for x and y as well as for xshift and yshift, depending on the size of your graph.

Tip: the br in the footnote text represents a line break.

STEP 5: Add an annotation with a box & opacity

fig = px.line(
df
, x='event_date'
, y='outcome'
, title = "Event Data Over Time for Two Groups"
, color="groupid"
, range_x=['2019-12-01', '2020-11-01'],
)
fig.update_layout(
autosize=False,
width=800,
height=600,)
# add today's date
from datetime import date
today = date.today()
# HOW TO ADD A FOOTNOTE TO BOTTOM LEFT OF PAGE
fig.add_annotation(
text = (f"@reshamas / {today}<br>Source: JHU CSSE")
, showarrow=False
, x = 0
, y = -0.15
, xref='paper'
, yref='paper'
, xanchor='left'
, yanchor='bottom'
, xshift=-1
, yshift=-5
, font=dict(size=10, color="grey")
, align="left"
,
)
# add annotation text with an arrow
fig.add_annotation(
x='2020-02-01'
, y=145+1
, text=f'145 is the<br>the maximum value'
, yanchor='bottom'
, showarrow=True
, arrowhead=1
, arrowsize=1
, arrowwidth=2
, arrowcolor="#636363"
, ax=-20
, ay=-30
, font=dict(size=12, color="purple", family="Courier New, monospace")
, align="left"
,)
# add annotation with box, color-filled with opacity option
fig.add_annotation(
x='2020-05-01'
, y=135
, text=f'May<br>Day'
, yanchor='bottom'
, showarrow=True
, arrowhead=1
, arrowsize=1
, arrowwidth=2
, arrowcolor="#636363"
, ax=-20
, ay=-30
, font=dict(size=20, color="green", family="Courier New, monospace")
, align="left"
, bordercolor='green'
, borderwidth=2
, bgcolor="#CFECEC"
, opacity=0.8
,)
fig.show()

Tip: there are many options for how text in annotations can be presented. There are options for font size, type and color. There are options for adding boxes around text with various formatting options. Arrows can be shown or hidden, using the showarrow=True option.

Tip: the location of the annotation “145 is the maximum value” can be placed using the coordinates, in this case, x='2020-02-01' and y=145. The location of the text can also be shifted using ax=-20 and ay=-30, as an example.

Tip: multiple annotations can be layered on the graph by adding fig.add_annotation object.

STEP 6: Add an annotation with vertical lines

fig = px.line(
df
, x='event_date'
, y='outcome'
, title = "Event Data Over Time for Two Groups"
, color="groupid"
, range_x=['2019-12-01', '2020-11-01'],
)
fig.update_layout(
autosize=False,
width=800,
height=600,)
fig.add_annotation(
x='2020-03-21'
, y=145+1
, text=f'Mar 21<br>First day of spring'
, yanchor='bottom'
, showarrow=False
, arrowhead=1
, arrowsize=1
, arrowwidth=2
, arrowcolor="#636363"
, ax=-20
, ay=-30
, font=dict(size=12, color="orange", family="Sans Serif")
, align="left"
,)
fig.add_annotation(
x='2020-06-21'
, y=145+1
, text=f'Jun 21<br>First day of summer'
, yanchor='bottom'
, showarrow=False
, arrowhead=1
, arrowsize=1
, arrowwidth=2
, arrowcolor="#636363"
, ax=-20
, ay=-30
, font=dict(size=12, color="purple", family="Sans Serif")
, align="left"
,)
# add vertical lines
fig.update_layout(shapes=
[dict(type= 'line',
yref= 'paper', y0= 0, y1= 1,
xref= 'x', x0='2020-03-21', x1='2020-03-21',
line=dict(color="MediumPurple",
width=3,
dash="dot")
),
dict(type= 'line',
yref= 'paper', y0= 0, y1= 1,
xref= 'x', x0='2020-06-21', x1='2020-06-21',
line=dict(color="MediumPurple",
width=3,
dash="solid")
)
])

fig.show()

Tip: the line type may be adjusted by using these options:

dash="dot" 
dash="solid"

Tip: multiple reference lines can be added using fig.update_layout(shapes=... and the lines can be added as a dictionary.

STEP 7: Add vertical & horizontal rectangle highlight sections

fig = px.line(
df
, x='event_date'
, y='outcome'
, title = "Event Data Over Time for Two Groups"
, color="groupid"
, range_x=['2019-12-01', '2020-11-01'],
)
fig.update_layout(
autosize=False,
width=800,
height=600,)
fig.add_annotation(
x='2020-03-21'
, y=145+1
, text=f'Mar 21<br>First day of spring'
, yanchor='bottom'
, showarrow=False
, arrowhead=1
, arrowsize=1
, arrowwidth=2
, arrowcolor="#636363"
, ax=-20
, ay=-30
, font=dict(size=12, color="orange", family="Sans Serif")
, align="left"
,)
fig.add_annotation(
x='2020-06-07'
, y=145+1
, text=f'Jun 21<br>First day<br>of summer'
, yanchor='bottom'
, showarrow=False
, arrowhead=1
, arrowsize=1
, arrowwidth=2
, arrowcolor="#636363"
, ax=-20
, ay=-30
, font=dict(size=12, color="purple", family="Sans Serif")
, align="left"
,)
# add vertical lines
fig.update_layout(shapes=
[dict(type= 'line',
yref= 'paper', y0= 0, y1= 1,
xref= 'x', x0='2020-03-21', x1='2020-03-21',
line=dict(color="MediumPurple",
width=3,
dash="dot")
),
dict(type= 'line',
yref= 'paper', y0= 0, y1= 1,
xref= 'x', x0='2020-06-21', x1='2020-06-21',
line=dict(color="MediumPurple",
width=3,
dash="solid")
)
])
# In United States: 'unofficial' summer is from Memorial Day to Labor Day# Make a vertical highlight section
fig.add_vrect(x0="2020-05-25", x1="2020-09-07",
annotation_text="Unofficial<br>Summertime<br>in USA<br>(Memorial Day to<br>Labor Day)", annotation_position="top right",
annotation_font_size=11,
annotation_font_color="Green",
fillcolor="yellow", opacity=0.25, line_width=0)
# Make a horizontal highlight section
fig.add_hrect(y0=90, y1=100,
annotation_text="Observed data<br>of interest", annotation_position="top right",
annotation_font_size=11,
annotation_font_color="Black",
fillcolor="red", opacity=0.25, line_width=0)
fig.show()

Tip: the horizontal and vertical highlight blocks can be created by adding an annotation section fig.add_hrect or fig.add_vrect. For the horizontal section, specify at which coordinates of y to place the blocks. The same can be done for the vertical highlight block, where x coordinates can be specified. The color of the highlight rectangle sections can be specified using the fillcolor option.

--

--