How to animate a line chart

I would like to animate a fitness function graph to debug my algorithm behavior. However, as soon as the plot is added to the page, it becomes a static image and no longer refreshes.

Any ideas on how to animate a line chart?

I also couldn’t find if I can reuse a plot slot to manually set a new graph.

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
import streamlit as st

fig, ax = plt.subplots()

max_x = 5
max_rand = 10

x = np.arange(0, max_x)
ax.set_ylim(0, max_rand)
line, = ax.plot(x, np.random.randint(0, max_rand, max_x))


def init():  # give a clean slate to start
    line.set_ydata([np.nan] * len(x))
    return line,


def animate(i):  # update the y values (every 1000ms)
    line.set_ydata(np.random.randint(0, max_rand, max_x))
    return line,

ani = animation.FuncAnimation(
    fig, animate, init_func=init, interval=1000, blit=True, save_count=10)

st.pyplot(plt)

StrackOverflow question

Hi @cristian.cotoi! Welcome to the Streamlit community! :hugs:

It is absolutely possible to animate matplotlib charts in Streamlit. I’ve modified your code in this gist:

import matplotlib.pyplot as plt
import numpy as np
import streamlit as st
import time

fig, ax = plt.subplots()

max_x = 5
max_rand = 10

x = np.arange(0, max_x)
ax.set_ylim(0, max_rand)
line, = ax.plot(x, np.random.randint(0, max_rand, max_x))
the_plot = st.pyplot(plt)

def init():  # give a clean slate to start
    line.set_ydata([np.nan] * len(x))

def animate(i):  # update the y values (every 1000ms)
    line.set_ydata(np.random.randint(0, max_rand, max_x))
    the_plot.pyplot(plt)

init()
for i in range(100):
    animate(i)
    time.sleep(0.1)

You can run this code right now by running:

streamlit run https://gist.githubusercontent.com/treuille/4f70bff85ec5856cc7696f5c73f1ea11/raw

Please note that if you use Altair, Streamlit also gives you more powerful and efficient constructs to animate charts like add_rows.

To see an example of this more advanced usage (including code snippet), please run

streamlit hello

and choose Plotting Demo from the select-box in the sidebar.

1 Like

Thanks for the response, works beautifully. Would you consider adding some documentation on working with charts? I couldn’t figure how to update charts from the source code or code examples.

Hey @cristian.cotoi, thanks for the recommendation and glad it’s working well for you :blush:. I’ve written down a note about adding more documentation for working with charts and I’ll run it by the team tomorrow to get it into the pipeline. I hope you’re having a great weekend!

1 Like

I am interested in an animated streamlit.image example, something like:

def get_frame():
    return np.random.randint(0, 100, size=(10,10))

my_image = st.image(get_frame(), caption='Random image', width=600)

while True:
    my_image.update(get_frame())
    time.sleep(0.1)

I think this functionality is not implemented yet. Perhaps there is a way with matplotlib.image

Thanks!

1 Like

Actually, that’s also possible :smiley:

Basically, almost every* Streamlit element outputs an object that can be used to modify itself.

*(exceptions: widgets, st.write, st.spinner)

For example, when you do el = st.image(img) you can just call other st methods on el to replace it with another element:

# Draw an image element.
el = st.image(img)

time.sleep(0.5)

# Replace the image with some text.
el.text('hi')

time.sleep(0.5)

# Replace the text with a progress bar.
el.progress(50)

# and so on.

There are a few exceptions to the pattern above (most prominently, st.write()!), but in general this should work.

Another trick is to use el = st.empty() to insert a placeholder element that you’ll fill later by calling el.something_else()!


Ok, so going back to your script, here’s a modified version that works on my end:

import streamlit as st
import numpy as np
import time

def get_frame():
    return np.random.randint(0, 255, size=(10,10))

my_image = st.image(get_frame(), caption='Random image', width=600)

while True:
    time.sleep(0.1)
    my_image.image(get_frame(), caption='Random image', width=600)
2 Likes

OK fantastic, thanks again @thiago you rock!

Wow. That is both awesome and magic :slight_smile:

2 Likes

import matplotlib.pyplot as plt
import numpy as np
import streamlit as st
import time

fig, ax = plt.subplots()

max_x = 50
max_rand = 100
frame_count = 100

x = np.arange(0, max_x)
ax.set_ylim(0, max_rand)

data = np.random.randint(0, max_rand, frame_count+max_x)


line, = ax.plot(x, data[0:max_x])
the_plot = st.pyplot(plt)




def animate(i):  # update the y values (every 1000ms)
    line.set_ydata(data[i:max_x+i])
    the_plot.pyplot(plt)

for i in range(frame_count):
    animate(i)
    time.sleep(0.005)

I was playing around with your code and got a streaming graph working. if you need something like this.

streamlit run https://gist.githubusercontent.m/namitjuneja/be4cc3f376366eb89c980f22ae9ee633/raw/e568c37dbeb6ba9348dca40d8e35786810dc0833/main.py

to run the application

1 Like

@thiago

I kept running into exceptions like these while using streamlit. can confirm there was nothing wring with my code as a restart fixed the issue.

2019-10-13-023605_839x345_scrot

Dear @namitjuneja. Welcome to the community. I’m really sorry you’ve had this problem.

I suspect that the buy you’re seeing is due to a configuration we haven’t seen before.

To help us solve this for you, could you please submit a bug on Github including the exact code you’re running? An official bug report helps us because the form a bunch of useful questions about your configuration.

Thanks!!

Just a suggestion for streamlit.
I would like to try make a monitoring chart that scrolls infinitely from left to right.

I’ll use the matplotlib example as a starting point,
but would like to be able to do the same with plotly or altair.

Maybe having a remove/pop/replace_rows function in addition to add_rows could be helpful in this case?

Hey @devcon, welcome to the Streamlit community :star_struck:,

That’s definitely something we’ve been thinking about over here. We’d like to be able to support all sorts of data operations, like prepend, pop, update, etc. But first we need to come up with the proper “Streamlity” API for that :smiley:

Here is a thought on how such an API could work — I’d love to hear your thoughts on it:

(Note: This is just an idea! Not saying we’ll definitely implement it this way)

  1. First, we’d deprecate st.add_rows. That API is a bit weird anyway. Only some elements support it, but all elements have that method. Also, if we wanted to add more methods (like prepend_rows, remove_rows, etc) we’d be polluting the st namespace quite a bit.

  2. Second, we’d add a new st.Data object that wraps dataframes/NdArrays/lists/etc, and which you could mutate directly in order to update your app:

data = st.Data(my_dataframe)
st.line_chart(data)

# Append some rows
data += more_rows
# Or maybe data.append(more_rows)
# This would replace add_rows()

# Remove 10 rows from the head
del data[:10]
# Or maybe data.remove(0, 10)

# Remove 10 rows from the tail
del data[-10:]
# Or maybe data.remove(-10, 0)

# Replace row 50
data[50] = new_row
# Or maybe data.set(50, new_rows)
  1. All the data operations above would happen immediately in your app! So instead of calling .add_rows() on an element, you’d just operate on the Data object directly and things would magically happen behind the scenes. You could even have multiple charts depend on the same data!

  2. You could still pass dataframes directly into st.line_chart, by the way! That would work, but you just wouldn’t be able to later modify your chart by mutating the data.

Anyway, this is just an idea we’re toying with. It’s still in early design phase, so it’s not in Github yet. But I’m creating a feature request for additional data mutation methods here, which we’ll update when we have a better idea what to build.


In the meantime, you can always mutate your original dataset and rerun the chart command to update it on the screen. This works, but it’s a bit wasteful since you’d be sending the entire data to the app every time.

For example:

st.line_chart(df)

df = df.iloc[10:]
df = pd.concat([df, df2])

st.line_chart(df)
2 Likes

I personally like data.append(more_rows)

1 Like

Thank you @tc1.
That Data interface sounds like a pretty cool idea!

In the meantime.
I tried your sample code but I end up with 2 charts instead of updating the original chart.

Hey @devcon,
can you give a try to:

handle = st.line_chart(df)

df = df.iloc[10:]
df = pd.concat([df, df2])

handle.line_chart(df)

I haven’t tried myself, since I don’t have the whole script, but My guess is that the original snippet had a mistake. In order to redraw, we need to operate on the handle of an existing Streamlit object.

If you still have the problem, I would be happy to debug the whole script for you. Just paste here the final version you have.

Matteo

It works with the handle, thanks!
I thought I tried that before but maybe I messed something up along the way.