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#

  1. Import both the plotly.expressand the plotly.graph_objects modules as px and go, respectively

import plotly.express as px
import plotly.graph_objects as go
  1. Create a simple stacked bar chart using the bar function from the module px. 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()
  1. 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 method add_scatter on the Figure 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()
  1. Customise the gap between each bar by using the method update_layout and setting the argumen bargap. 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()
  1. 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()
  1. Use the method update_layout to change the overall visuals of the chart by setting a template

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()
  1. 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()
  1. 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()
  1. Change the location and format of the legend by calling the method update_layout and setting the argument legend

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()
  1. 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()