Make your st.pyplot interactive!

Learn how to make your pyplot charts interactive in a few simple steps

Posted in Tutorial, June 23 2022

Matplotlib is one of the most popular charting libraries in Python. Itā€™s also a popular way to add charts to your Streamlit apps. Just use st.pyplot!

But Matplotlib charts are static images. No zooming or moving the chart around.

In this post, I'll show you how to make them interactive:

1. Simple example:

  • Step 1. Create a basic Matplotlib chart
  • Step 2. Make the chart interactive

2. Advanced example:

  • Step 1. Render the graph statically
  • Step 2. Make the graph interactive with mpld3
  • Step 3. Add tooltips for even more interactivity

TLDR? Use mpld3 and render pyplots with Streamlitā€™s built-in st.pyplot command. With a few lines of code, you can add panning, zooming, resetting, and rendering!

1. Simple example

Step 1. Create a basic Matplotlib chart

First, create a basic Matplotlib chart and add it to your Streamlit app (youā€™ll add interactivity later).

Here is what the code will look like:

import streamlit as st
import matplotlib.pyplot as plt
#create your figure and get the figure object returned
fig = plt.figure() 
plt.plot([1, 2, 3, 4, 5]) 
st.pyplot(fig)

And hereā€™s what your app should look like now:

Step 2. Make the chart interactive

Making this chart interactive is super simple.

Use the fantastic mpld3 library. Itā€™ll convert the Matplotlib figure (fig) into an interactive Javascript representation and return it as HTML. Embed this HTML snippet in your app via Streamlitā€™s custom component API:

import matplotlib.pyplot as plt
import matplotlib.pyplot as mpld3
import streamlit.components.v1 as components
#create your figure and get the figure object returned
fig = plt.figure() 
plt.plot([1, 2, 3, 4, 5]) 
fig_html = mpld3.fig_to_html(fig)
components.html(fig_html, height=600)

Now your users can pan, zoom, reset, and explore the details of your chart! šŸ“Š

Want to explore it yourself? See the app deployed live here.

For more mpld3ā€™s plugins, check out mpld3ā€™s website and documentation.

2. Advanced example

Step 1. Render the graph statically

Start out by statically rendering the graph:

# Imports for all of the code
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import mpld3
import streamlit as st
from mpld3 import plugins
def f(t):
    return np.exp(-t) * np.cos(2*np.pi*t)
t1 = np.arange(0.0, 5.0, 0.1)
t2 = np.arange(0.0, 5.0, 0.02)
# How to set the graph size 
two_subplot_fig = plt.figure(figsize=(6,6))
plt.subplot(211)
plt.plot(t1, f(t1), color='tab:blue', marker=',')
plt.plot(t2, f(t2), color='black', marker='.')
plt.subplot(212)
plt.plot(t2, np.cos(2*np.pi*t2), color='tab:orange', linestyle='--', marker='.')
st.pyplot(two_subplot_fig)

This code will make something like this:

You might be thinking, ā€œWhy are we adding markers? It doesnā€™t look beautiful.ā€ Iā€™ll explain why below!

Step 2. Make the graph interactive with mpld3

Make the static graph interactive with mpld3:


# Replace st.pyplot(two_subplot_fig) with this code below! 
fig_html = mpld3.fig_to_html(two_subplot_fig)
components.html(fig_html, height=600)

Here is what itā€™ll look like with panning, zooming, and resetting:

Add tooltips to see X and Y coordinates for even more interactivity (itā€™s why weā€™ve added markers).

Here is what the code will look like:

# CODE TO ADD
# Define some CSS to control our custom labels
css = """
table
{
  border-collapse: collapse;
}
th
{
  color: #ffffff;
  background-color: #000000;
}
td
{
  background-color: #cccccc;
}
table, th, td
{
  font-family:Arial, Helvetica, sans-serif;
  border: 1px solid black;
  text-align: right;
}
"""
for axes in two_subplot_fig.axes:
    for line in axes.get_lines():
        # get the x and y coords
        xy_data = line.get_xydata()
        labels = []
        for x, y in xy_data:
            # Create a label for each point with the x and y coords
            html_label = f'<table border="1" class="dataframe"> <thead> <tr style="text-align: right;"> </thead> <tbody> <tr> <th>x</th> <td>{x}</td> </tr> <tr> <th>y</th> <td>{y}</td> </tr> </tbody> </table>'
            labels.append(html_label)
        # Create the tooltip with the labels (x and y coords) and attach it to each line with the css specified
        tooltip = plugins.PointHTMLTooltip(line, labels, css=css)
        # Since this is a separate plugin, you have to connect it
        plugins.connect(two_subplot_fig, tooltip)

You can adjust HTML, CSS, or anything you want. And if you want to interact with the graph or look at the code, check it out here!

Note: mpld3 limitations

Before I wrap this up, I wanted to note the limitations to mpld3:

  1. Complex charts sometimes donā€™t render properly.
  2. Dark mode isnā€™t supported.
  3. 3D charts donā€™t render properly.
  4. You need markers for tooltips.
  5. Some markers donā€™t work (examples: none or ā€˜+ā€™).

Wrapping up

Thank you for reading this post! Iā€™d love to know if you found this useful. Your feedback means a LOT. If you have any questions, please leave them in the comments below and check out the forum to see what our vibrant community is creating.

Happy Streamlit-ing! šŸŽˆ


This is a companion discussion topic for the original entry at https://blog.streamlit.io/make-your-st-pyplot-interactive/
5 Likes

My work around to get a nice chart:

import streamlit.components.v1 as components
import matplotlib.pyplot as plt
import mpld3
import streamlit as st
import matplotlib.pylab as pylab

#create your figure and get the figure object returned

def st_pyplot(fig):
size = fig.get_size_inches()
width = int(size[0]) * 100+50
height = int(size[1]) * 100+50
params = {ā€˜legend.fontsizeā€™: 30,
ā€˜figure.figsizeā€™: (10, 10),
ā€˜axes.labelsizeā€™: 30,
ā€˜axes.titlesizeā€™: 30,
ā€˜xtick.labelsizeā€™: 30,
ā€˜ytick.labelsizeā€™: 30}
pylab.rcParams.update(params)
fig_html = mpld3.fig_to_html(fig)
components.html(fig_html, width=width, height=height)

if name == ā€œmainā€:
fig = plt.figure()
# fig = plt.figure(figsize=(15,10))
plt.plot([1, 2, 3, 5, 8, 13, 21, 34, 55, 89])
plt.xlabel(ā€œLength (nm)ā€)
plt.ylabel(ā€œForce (pN)ā€)
st_pyplot(fig)

1 Like

Hi,
thanks for the pointer to zoom in a plot interactively.
I am trying to do the same with an image file.

One question I have is, after I zoom in, can I get the coordinates of the region that is in display after zooming in?

Thanks,
Rudra

I like this and it is helping me for 2D plots. Is there a way to make this work for 3D plots for example for this plot below?
x = [0, 1, 2, 3, 4, 5]
y = [0, 1, 2, 3, 4, 5]
z = [0, 1, 2, 3, 4, 5]
fig = plt.figure()
ax = plt.axes(projection=ā€œ3dā€)
ax.scatter(x, y, z, c=ā€˜gā€™, s=20)
ax.plot(x, y, z)
ax.set_xlabel(ā€˜X Labelā€™), ax.set_ylabel(ā€˜Y Labelā€™), ax.set_zlabel(ā€˜Z Labelā€™)
plt.show()

Hi @ibaha , there is a known limitation where 3d plots do not work at this current moment :frowning:

@willhuang, are you serious? OMG, thatā€™s going to kill what I am trying to do. I wanted to have rotatable and zoomable simple 3D plot with some simple boxes in it. Is it not doable in streamlit now? STREAMLIT DEVELOPERS, if you hear me, is that not possible?

Here is a possible solution that you could do?

For 3D visualizations Iā€™d recommend using pyvista (and stpyvista for rendering that in streamlit), or even plotly for simpler plots. Matplotlib is great for anything in 2D, but it is not really designed for 3D or interactivity.

1 Like

@edsaac, thanks for your recommendation. pyvista doesnā€™t seem to have zoom/rotate readily available. stpyvista on the other hand has that, which is what I needed.

Love this! Is there some way to click the plot and get the data from those points?