25

I am trying to automatically update a scatter plot. The source of my X and Y values is external, and the data is pushed automatically into my code in a non-predicted time intervals (rounds).

I have only managed to plot all the data when the whole process ended, whereas I am trying to constantly add and plot data into my canvas.

What I DO get (at the end of the whole run) is this: enter image description here

Whereas, what I am after is this: enter image description here

A simplified version of my code:

import matplotlib.pyplot as plt def read_data(): #This function gets the values of xAxis and yAxis xAxis = [some values] #these valuers change in each run yAxis = [other values] #these valuers change in each run plt.scatter(xAxis,yAxis, label = 'myPlot', color = 'k', s=50) plt.xlabel('x') plt.ylabel('y') plt.show() 

4 Answers 4

50

There are several ways to animate a matplotlib plot. In the following let's look at two minimal examples using a scatter plot.

(a) use interactive mode plt.ion()

For an animation to take place we need an event loop. One way of getting the event loop is to use plt.ion() ("interactive on"). One then needs to first draw the figure and can then update the plot in a loop. Inside the loop, we need to draw the canvas and introduce a little pause for the window to process other events (like the mouse interactions etc.). Without this pause the window would freeze. Finally we call plt.waitforbuttonpress() to let the window stay open even after the animation has finished.

import matplotlib.pyplot as plt import numpy as np plt.ion() fig, ax = plt.subplots() x, y = [],[] sc = ax.scatter(x,y) plt.xlim(0,10) plt.ylim(0,10) plt.draw() for i in range(1000): x.append(np.random.rand(1)*10) y.append(np.random.rand(1)*10) sc.set_offsets(np.c_[x,y]) fig.canvas.draw_idle() plt.pause(0.1) plt.waitforbuttonpress() 

(b) using FuncAnimation

Much of the above can be automated using matplotlib.animation.FuncAnimation. The FuncAnimation will take care of the loop and the redrawing and will constantly call a function (in this case animate()) after a given time interval. The animation will only start once plt.show() is called, thereby automatically running in the plot window's event loop.

import matplotlib.pyplot as plt import matplotlib.animation import numpy as np fig, ax = plt.subplots() x, y = [],[] sc = ax.scatter(x,y) plt.xlim(0,10) plt.ylim(0,10) def animate(i): x.append(np.random.rand(1)*10) y.append(np.random.rand(1)*10) sc.set_offsets(np.c_[x,y]) ani = matplotlib.animation.FuncAnimation(fig, animate, frames=2, interval=100, repeat=True) plt.show() 
Sign up to request clarification or add additional context in comments.

4 Comments

I don't know how I could have gone this long and never known about ion. +1
Thanks for the detailed answer. By far the clearest of all dealing with this issue!
Oddly, the assignment to ani is required for (b) to work. Can you explain this magic?
@Alan The rough explanation is simply: If you have a class SomeClass and instantiate it via SomeClass(argument1, argument2, etc) it will be gone in the moment its __init__ method returns. If you need that instance for anything later on (and you will in case of an animation running at some point later in time), you need to store it in a variable, such that it is kept in memory, myinstance = SomeClass(...)
8

From what I understand, you want to update interactively your plot. If so, you can use plot instead of scatter plot and update the data of your plot like this.

import numpy import matplotlib.pyplot as plt fig = plt.figure() axe = fig.add_subplot(111) X,Y = [],[] sp, = axe.plot([],[],label='toto',ms=10,color='k',marker='o',ls='') fig.show() for iter in range(5): X.append(numpy.random.rand()) Y.append(numpy.random.rand()) sp.set_data(X,Y) axe.set_xlim(min(X),max(X)) axe.set_ylim(min(Y),max(Y)) raw_input('...') fig.canvas.draw() 

If this is the behaviour your are looking for, you just need to create a function appending the data of sp, and get in that function the new points you want to plot (either with I/O management or whatever the communication process you're using). I hope it helps.

5 Comments

Thanks, but it does not seem to work fully. Instead of plotting the data in each round of data collection - I get a blank canvas as long as the code runs, and once it finishes running - I get all the data at the same time (i.e., the top image in my question).
@aTben0 I wonder in which environment this solution would work as it's clearly missing an event loop.
@ImportanceOfBeingErnest As the posted issue is tagged as matplotlib and scatter-plot I was thinking about an I/O management which would wait for a file to be updated and then append X and Y as soon as the file is modified. If so, I don't see why the interactive mode is needed, or do I miss something? Anyway, thanks for your complete answer and comment.
You miss the event loop and the interactive mode is one way of obtaining it.
This won't work if you want to have a 3d plot. If you create plot like sp, = axe.plot([],[], []) and use sp.set_data([], [], []), you'll get an error since it is a 2DLine
1

Here is one way of creating an interactive plot in Jupyter notebook

# Import Libraries import numpy as np import matplotlib.pyplot as plt from IPython.display import display, clear_output # Create figure and subplot fig = plt.figure() ax = fig.add_subplot(1, 1, 1) # Define and update plot for i in range(20): x = np.linspace(0, i, 100); y = np.cos(x) ax.set_xlim(0, i) ax.cla() ax.plot(x, y) display(fig) clear_output(wait = True) plt.pause(0.1) 

This will update the same plot iteratively. A detailed description is given here https://pythonguides.com/matplotlib-update-plot-in-loop/

Comments

1

Using point.set_offsets(np.c_[xdata,ydata]) worked for me because when I updated to matplotlib 3.9, ax.set_data stopped working.

How it looks: 1

import matplotlib.pyplot as plt import numpy as np import matplotlib.animation as animation def lerp(p0: np.array, p1: np.array,t = None,res=10): # t = np.array(t) if t == None: t = np.linspace(0,1,res) outX = (1-t)*p0[0]+t*p1[0] outY = (1-t)*p0[1]+t*p1[1] return outX,outY def lerpAnimation(p0: np.array, p1: np.array,res = 10,speed=100): plt.ioff() xdata, ydata = [0],[0] fig, ax = plt.subplots() point = ax.scatter(xdata, ydata, lw=2) ax.grid() firstLerp = lerp(p0,p1) ax.plot(firstLerp[0],firstLerp[1]) def run(t): timedLerp = lerp(p0,p1,t) xdata[0], ydata[0] = timedLerp[0],timedLerp[1] # Here is where the values are changed point.set_offsets(np.c_[xdata,ydata]) return point, ani = animation.FuncAnimation(fig, run, (i for i in np.linspace(0,1,res)), interval=speed, save_count=100,repeat = True) plt.show() 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.