3. Making Better Bar Charts#
In Chapter 3, we learned how to make simple bar charts as well as stacked bar charts. This recipe is all about how to make these charts more impactful and aesthetically better. We will start with a simple stacked bar chart and iteratively improve it
Getting Ready#
For this recipe, we are going to create a DataFrame
containing the Year-on-year changes in electricity demand by region from 2019 to 2025.
import pandas as pd
dta = pd.DataFrame(data=[
[269.1, 716.9, 211.8, 455.0, 432.2, 503.1],
[-120.6, 118.1, 114.5, 91.4, 88.6, 81.7],
[-3.5, 51.3, 59.6, 42.4, 68.3, 74.8] ,
[-77.3, 101.6, 109.5, -25.9, 50.5, 57.3],
[-105.8, 125.6, -94.9, 39.5, 37.0, 40],
[-151.4, 307.5, 97.6, 102.6, 182.5, 181.5]],
index = [ 'China', 'India', 'Southeast Asia', 'United States', 'European Union', 'Others'],
columns = [2020, 2021, 2022, 2023, 2024, 2025]
).T.rename_axis('Year')
dta['Total'] = dta.sum(axis=1).values
dta
China | India | Southeast Asia | United States | European Union | Others | Total | |
---|---|---|---|---|---|---|---|
Year | |||||||
2020 | 269.1 | -120.6 | -3.5 | -77.3 | -105.8 | -151.4 | -189.5 |
2021 | 716.9 | 118.1 | 51.3 | 101.6 | 125.6 | 307.5 | 1421.0 |
2022 | 211.8 | 114.5 | 59.6 | 109.5 | -94.9 | 97.6 | 498.1 |
2023 | 455.0 | 91.4 | 42.4 | -25.9 | 39.5 | 102.6 | 705.0 |
2024 | 432.2 | 88.6 | 68.3 | 50.5 | 37.0 | 182.5 | 859.1 |
2025 | 503.1 | 81.7 | 74.8 | 57.3 | 40.0 | 181.5 | 938.4 |
How to do it#
Import both the
plotly.express
and theplotly.graph_objects
modules aspx
andgo
, respectively
import plotly.express as px
import plotly.graph_objects as go
Create a simple stacked bar chart using the
bar
function from the modulepx
. This is going to be our starting point
fig = px.bar(dta, x=dta.index, y=[ 'China', 'India', 'Southeast Asia', 'United States', 'European Union', 'Others'],
hover_name=dta.index,
color_discrete_sequence= px.colors.qualitative.Pastel,
height=600, width=900,
title='Year-on-year change in electricity demand by <br>region, 2020-2025')
fig.show()
The next stept is to add the information in the
'Totals'
column. We will do this by overlying a scatter over the bar chart in order to highlight the overall trend. To do this, call the methodadd_scatter
on theFigure
object. Note that we are setting `mode=”markers”, so the scatter shows only the markers and not the lines connecting them
fig = px.bar(dta, x=dta.index, y=[ 'China', 'India', 'Southeast Asia', 'United States', 'European Union', 'Others'],
hover_name=dta.index,
color_discrete_sequence= px.colors.qualitative.Pastel,
height=600, width=900,
title='Year-on-year change in electricity demand by <br>region, 2020-2025')
fig.add_scatter(x=dta.index, y=dta.Total, mode="markers", name="Total")
fig.show()
Customise the gap between each bar by using the method
update_layout
and setting the argumenbargap
. In this case, we want to make the gap slightly wider in order to make the chart less crowded
fig = px.bar(dta, x=dta.index, y=[ 'China', 'India', 'Southeast Asia', 'United States', 'European Union', 'Others'],
hover_name=dta.index,
color_discrete_sequence= px.colors.qualitative.Pastel,
height=600, width=900,
title='Year-on-year change in electricity demand by <br>region, 2020-2025')
fig.add_scatter(x=dta.index, y=dta.Total, mode="markers", name="Total")
fig.update_layout(bargap=0.5) # gap between bars of adjacent location coordinates.
fig.show()
Customise the contents and format of the hover template. Note that we can use HTM elements such as
<br>
and</b>
fig = px.bar(dta, x=dta.index, y=[ 'China', 'India', 'Southeast Asia', 'United States', 'European Union', 'Others'],
hover_name=dta.index,
color_discrete_sequence= px.colors.qualitative.Pastel,
height=600, width=900,
title='Year-on-year change in electricity demand by <br>region, 2020-2025')
fig.add_scatter(x=dta.index, y=dta.Total, mode="markers", name="Total")
fig.update_layout(bargap=0.5) # gap between bars of adjacent location coordinates.
fig.update_traces(hovertemplate ='<br>%{x} <br>'+ '<b>%{y:.2f} Twh </b>')
fig.show()
Use the method
update_layout
to change the overall visuals of the chart by setting atemplate
fig = px.bar(dta, x=dta.index, y=[ 'China', 'India', 'Southeast Asia', 'United States', 'European Union', 'Others'],
hover_name=dta.index,
color_discrete_sequence= px.colors.qualitative.Pastel,
height=600, width=900,
title='Year-on-year change in electricity demand by <br>region, 2020-2025')
fig.add_scatter(x=dta.index, y=dta.Total, mode="markers", name="Total")
fig.update_layout(bargap=0.5) # gap between bars of adjacent location coordinates.
fig.update_traces(hovertemplate ='<br>%{x} <br>'+ '<b>%{y:.2f} Twh </b>')
fig.update_layout(template="plotly_white")
fig.show()
Customise the axes titles and its format by using the methods:
-
update_yaxes
-
update_xaxes
fig = px.bar(dta, x=dta.index, y=[ 'China', 'India', 'Southeast Asia', 'United States', 'European Union', 'Others'],
hover_name=dta.index,
color_discrete_sequence= px.colors.qualitative.Pastel,
height=600, width=900,
title='Year-on-year change in electricity demand by <br>region, 2020-2025')
fig.add_scatter(x=dta.index, y=dta.Total, mode="markers", name="Total")
fig.update_layout(bargap=0.5) # gap between bars of adjacent location coordinates.
fig.update_traces(hovertemplate ='<br>%{x} <br>'+ '<b>%{y:.2f} Twh </b>')
# Add a template
fig.update_layout(template="plotly_white")
# Axis title and format
fig.update_yaxes(title_text="TWh", title_font_size=10, title_font_family='PT Serif Caption')
fig.update_xaxes(title_text="Year", title_font_size=10, title_font_family='PT Serif Caption')
fig.show()
Use the method
updat_layout
to customise the format of the title to match the axes’ labels
fig = px.bar(dta, x=dta.index, y=[ 'China', 'India', 'Southeast Asia', 'United States', 'European Union', 'Others'],
hover_name=dta.index,
color_discrete_sequence= px.colors.qualitative.Pastel,
height=600, width=900,
title='Year-on-year change in electricity demand by <br>region, 2020-2025')
fig.add_scatter(x=dta.index, y=dta.Total, mode="markers", name="Total")
fig.update_layout(bargap=0.5) # gap between bars of adjacent location coordinates.
fig.update_traces(hovertemplate ='<br>%{x} <br>'+ '<b>%{y:.2f} Twh </b>')
fig.update_layout(template="plotly_white")
# Axis title and format
fig.update_yaxes(title_text="TWh", title_font_size=10, title_font_family='PT Serif Caption')
fig.update_xaxes(title_text="Year", title_font_size=10, title_font_family='PT Serif Caption')
# Figure title format
fig.update_layout(margin=dict(l=100, r=100, t=100, b=120, pad=5),
title_font_size=14,
title_font_family='PT Serif Caption')
fig.show()
Change the location and format of the legend by calling the method
update_layout
and setting the argumentlegend
fig = px.bar(dta, x=dta.index, y=[ 'China', 'India', 'Southeast Asia', 'United States', 'European Union', 'Others'],
hover_name=dta.index,
color_discrete_sequence= px.colors.qualitative.Pastel,
height=600, width=900,
title='Year-on-year change in electricity demand by <br>region, 2020-2025')
fig.add_scatter(x=dta.index, y=dta.Total, mode="markers", name="Total")
fig.update_layout(bargap=0.5) # gap between bars of adjacent location coordinates.
fig.update_traces(hovertemplate ='<br>%{x} <br>'+ '<b>%{y:.2f} Twh </b>')
fig.update_layout(template="plotly_white")
# Axis title and format
fig.update_yaxes(title_text="TWh", title_font_size=10, title_font_family='PT Serif Caption')
fig.update_xaxes(title_text="Year", title_font_size=10, title_font_family='PT Serif Caption')
# Figure title format
fig.update_layout(margin=dict(l=100, r=100, t=100, b=120, pad=5),
title_font_size=14,
title_font_family='PT Serif Caption')
# Legend location and format
fig.update_layout(legend={
"title": "",
"orientation":"h",
"font": dict(
family="Courier",
size=10,
color="black"
),
},)
fig.show()
To finish our improved bar chart, let’s add a footnote with information about the source for the underlying data
fig = px.bar(dta, x=dta.index, y=[ 'China', 'India', 'Southeast Asia', 'United States', 'European Union', 'Others'],
hover_name=dta.index,
color_discrete_sequence= px.colors.qualitative.Pastel,
title='Year-on-year change in electricity demand by <br>region, 2020-2025')
fig.add_scatter(x=dta.index, y=dta.Total, mode="markers", name="Total")
fig.update_layout(template="plotly_white")
fig.update_traces(hovertemplate ='<br>%{x} <br>'+ '<b>%{y:.2f} Twh </b>')
fig.update_layout(bargap=0.5) # gap between bars of adjacent location coordinates.
fig.update_layout(dict(yaxis={'anchor': 'x', 'range': [-750, 1750], }))
# Figure title and labels
fig.update_layout(height=600, width=900, margin=dict(l=100, r=100, t=100, b=120, pad=5),
title_font_size=14,
title_font_family='PT Serif Caption')
fig.update_yaxes(title_text="TWh", title_font_size=10, title_font_family='PT Serif Caption')
fig.update_xaxes(title_text="", title_font_size=10, title_font_family='PT Serif Caption')
# fig.update_layout(yaxis= dict(title='TWh', tickfont = dict(size=10)), xaxis=dict(title=''))
# Footnotes
fig.add_annotation(xref='paper', x=1.0, yref='paper', y=-0.25,
text="Source: International Energy Agency (2024)", showarrow=False,
font=dict(size=11,
# color='#a70684',
family='PT Serif Caption'))
fig.update_layout(
hoverlabel=dict(
bgcolor="white",
font_size=12,
font_family="Courier"
)
)
# Legend location and format
fig.update_layout(legend={
"title": "",
"orientation":"h",
# "yanchor":"top",
# "title_font_family": "Times New Roman",
"font": dict(
family="Courier",
size=10,
color="black"
),
# "bgcolor": "Gold",
},)
fig.show()