I have a M x N 2D array: ith row represents that value of N points at time i.
I want to visualize the points [1 row of the array] in the form of a graph where the values get updated after a small interval. Thus the graph shows 1 row at a time, then update the values to next row, so on and so forth.
I want to do this in a jupyter notebook. Looking for reference codes.
I tried following things but no success:
http://community.plot.ly/t/updating-graph-with-new-data-every-100-ms-or-so/812
https://pythonprogramming.net/live-graphs-matplotlib-tutorial/
Create dynamic updated graph with Python
Update Lines in matplotlib
Here's an alternative, possibly simpler solution:
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
m = 100
n = 100
matrix = np.random.normal(0,1,m*n).reshape(m,n)
fig = plt.figure()
ax = fig.add_subplot(111)
plt.ion()
fig.show()
fig.canvas.draw()
for i in range(0,100):
ax.clear()
ax.plot(matrix[i,:])
fig.canvas.draw()
I had been particularly looking for a good answer for the scenario where one thread is pumping data and we want Jupyter notebook to keep updating graph without blocking anything. After looking through about dozen or so related answers, here are some of the findings:
Caution
Do not use below magic if you want a live graph. The graph update does not work if the notebook uses below:
%load_ext autoreload
%autoreload 2
You need below magic in your notebook before you import matplotlib:
%matplotlib notebook
Method 1: Using FuncAnimation
This has a disadvantage that graph update occurs even if your data hasn't been updated yet. Below example shows another thread updating data while Jupyter notebook updating graph through FuncAnimation.
%matplotlib notebook
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from random import randrange
from threading import Thread
import time
class LiveGraph:
def __init__(self):
self.x_data, self.y_data = [], []
self.figure = plt.figure()
self.line, = plt.plot(self.x_data, self.y_data)
self.animation = FuncAnimation(self.figure, self.update, interval=1000)
self.th = Thread(target=self.thread_f, daemon=True)
self.th.start()
def update(self, frame):
self.line.set_data(self.x_data, self.y_data)
self.figure.gca().relim()
self.figure.gca().autoscale_view()
return self.line,
def show(self):
plt.show()
def thread_f(self):
x = 0
while True:
self.x_data.append(x)
x += 1
self.y_data.append(randrange(0, 100))
time.sleep(1)
g = LiveGraph()
g.show()
Method 2: Direct Update
The second method is to update the graph as data arrives from another thread. This is risky because matplotlib is not thread safe but it does seem to work as long as there is only one thread doing updates.
%matplotlib notebook
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from random import randrange
from threading import Thread
import time
class LiveGraph:
def __init__(self):
self.x_data, self.y_data = [], []
self.figure = plt.figure()
self.line, = plt.plot(self.x_data, self.y_data)
self.th = Thread(target=self.thread_f, daemon=True)
self.th.start()
def update_graph(self):
self.line.set_data(self.x_data, self.y_data)
self.figure.gca().relim()
self.figure.gca().autoscale_view()
def show(self):
plt.show()
def thread_f(self):
x = 0
while True:
self.x_data.append(x)
x += 1
self.y_data.append(randrange(0, 100))
self.update_graph()
time.sleep(1)
from live_graph import LiveGraph
g = LiveGraph()
g.show()
I explored this and produced the following which is largely self-documenting:
import matplotlib.pyplot as plt
%matplotlib notebook
print('This text appears above the figures')
fig1 = plt.figure(num='DORMANT')
print('This text appears betweeen the figures')
fig2 = plt.figure()
print('This text appears below the figures')
fig1.canvas.set_window_title('Canvas active title')
fig1.suptitle('Figure title', fontsize=20)
# Create plots inside the figures
ax1 = fig1.add_subplot(111)
ax1.set_xlabel('x label')
ax2 = fig2.add_subplot(111)
# Loop to update figures
end = 40
for i in range(end):
ax2.cla() # Clear only 2nd figure's axes, figure 1 is ADDITIVE
ax1.set_title('Axes title') # Reset as removed by cla()
ax1.plot(range(i,end), (i,)*(end-i))
ax2.plot(range(i,end), range(i,end), 'rx')
fig1.canvas.draw()
fig2.canvas.draw()
Another simple solution, based on IPython.display functions display and clear_output. I found it here. Here is the code (based on #graham-s's answer):
from IPython.display import display, clear_output
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
m = 100
n = 100
matrix = np.random.normal(0, 1, size=(m, n))
fig = plt.figure()
ax = fig.add_subplot(111)
for i in range(m):
ax.clear()
ax.plot(matrix[i, :])
display(fig)
clear_output(wait=True)
plt.pause(0.2)
It uses %matplotlib inline instead of notebook, and does not produce small image as mentioned by #MasterScrat. Works both in Jupyter Notebook and in Jupyter Lab. Sometimes image blinks that's not very nice, but usable for quick investigations.
If you need to keep axes ranges between different frames, add ax.set_xlim/ax.set_ylim after ax.clear().
With a moderate modification of #Shital Shah's solution, I've created a more general framework which can simply apply to various scenario:
import matplotlib
from matplotlib import pyplot as plt
class LiveLine:
def __init__(self, graph, fmt=''):
# LiveGraph object
self.graph = graph
# instant line
self.line, = self.graph.ax.plot([], [], fmt)
# holder of new lines
self.lines = []
def update(self, x_data, y_data):
# update the instant line
self.line.set_data(x_data, y_data)
self.graph.update_graph()
def addtive_plot(self, x_data, y_data, fmt=''):
# add new line in the same figure
line, = self.graph.ax.plot(x_data, y_data, fmt)
# store line in lines holder
self.lines.append(line)
# update figure
self.graph.update_graph()
# return line index
return self.lines.index(line)
def update_indexed_line(self, index, x_data, y_data):
# use index to update that line
self.lines[index].set_data(x_data, y_data)
self.graph.update_graph()
class LiveGraph:
def __init__(self, backend='nbAgg', figure_arg={}, window_title=None,
suptitle_arg={'t':None}, ax_label={'x':'', 'y':''}, ax_title=None):
# save current backend for later restore
self.origin_backend = matplotlib.get_backend()
# check if current backend meets target backend
if self.origin_backend != backend:
print("original backend:", self.origin_backend)
# matplotlib.use('nbAgg',warn=False, force=True)
plt.switch_backend(backend)
print("switch to backend:", matplotlib.get_backend())
# set figure
self.figure = plt.figure(**figure_arg)
self.figure.canvas.set_window_title(window_title)
self.figure.suptitle(**suptitle_arg)
# set axis
self.ax = self.figure.add_subplot(111)
self.ax.set_xlabel(ax_label['x'])
self.ax.set_ylabel(ax_label['y'])
self.ax.set_title(ax_title)
# holder of lines
self.lines = []
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def close(self):
# check if current beckend meets original backend, if not, restore it
if matplotlib.get_backend() != self.origin_backend:
# matplotlib.use(self.origin_backend,warn=False, force=True)
plt.switch_backend(self.origin_backend)
print("restore to backend:", matplotlib.get_backend())
def add_line(self, fmt=''):
line = LiveLine(graph=self, fmt=fmt)
self.lines.append(line)
return line
def update_graph(self):
self.figure.gca().relim()
self.figure.gca().autoscale_view()
self.figure.canvas.draw()
With above 2 class, you can simply reproduce #Graham S's example:
import numpy as np
m = 100
n = 100
matrix = np.random.normal(0,1,m*n).reshape(m,n)
with LiveGraph(backend='nbAgg') as h:
line1 = h.add_line()
for i in range(0,100):
line1.update(range(len(matrix[i,:])), matrix[i,:])
Note that, the default backend is nbAgg, you can pass other backend like qt5Agg. When it is finished, it'll restore to your original backend.
and #Tom Hale's example:
with LiveGraph(figure_arg={'num':'DORMANT2'}, window_title='Canvas active title',
suptitle_arg={'t':'Figure title','fontsize':20},
ax_label={'x':'x label', 'y':''}, ax_title='Axes title') as g:
with LiveGraph() as h:
line1 = g.add_line()
line2 = h.add_line('rx')
end = 40
for i in range(end):
line1.addtive_plot(range(i,end), (i,)*(end-i))
line2.update(range(i,end), range(i,end))
Also, you can update particular line in the additive plot of #Tom Hale's example:
import numpy as np
with LiveGraph(figure_arg={'num':'DORMANT3'}, window_title='Canvas active title',
suptitle_arg={'t':'Figure title','fontsize':20},
ax_label={'x':'x label', 'y':''}, ax_title='Axes title') as g:
line1 = g.add_line()
end = 40
for i in range(end):
line_index = line1.addtive_plot(range(i,end), (i,)*(end-i))
for i in range(100):
j = int(20*(1+np.cos(i)))
# update line of index line_index
line1.update_indexed_line(line_index, range(j,end), (line_index,)*(end-j))
Note that, the second for loop is just for updating a particular line with index line_index. you can change that index to other line's index.
In my case, I use it in machine learning training loop to progressively update learning curve.
import numpy as np
import time
# create a LiveGraph object
g = LiveGraph()
# add 2 lines
line1 = g.add_line()
line2 = g.add_line()
# create 2 list to receive training result
list1 = []
list2 = []
# training loop
for i in range(100):
# just training
time.sleep(0.1)
# get training result
list1.append(np.random.normal())
list2.append(np.random.normal())
# update learning curve
line1.update(np.arange(len(list1)), list1)
line2.update(np.arange(len(list2)), list2)
# don't forget to close
g.close()
In addition to #0aslam0 I used code from here. I've just changed animate function to get next row every next time. It draws animated evolution (M steps) of all N points.
from IPython.display import HTML
import numpy as np
from matplotlib import animation
N = 5
M = 100
points_evo_array = np.random.rand(M,N)
# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0, M), ylim=(0, np.max(points_evo_array)))
lines = []
lines = [ax.plot([], [])[0] for _ in range(N)]
def init():
for line in lines:
line.set_data([], [])
return lines
def animate(i):
for j,line in enumerate(lines):
line.set_data(range(i), [points_evo_array[:i,j]])
return lines
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate,np.arange(1, M), init_func=init, interval=10, blit=True)
HTML(anim.to_html5_video())
Hope it will be useful
Here is a library that deals with real-time plotting/logging data (joystick), although I am not sure it is working with jupyter. You can install it using the usual pip install joystick.
Hard to make a working solution without more details on your data. Here is an option:
import joystick as jk
import numpy as np
class test(jk.Joystick):
# initialize the infinite loop decorator
_infinite_loop = jk.deco_infinite_loop()
def _init(self, *args, **kwargs):
"""
Function called at initialization, see the docs
"""
# INIT DATA HERE
self.shape = (10, 4) # M, N
self.data = np.random.random(self.shape)
self.xaxis = range(self.shape[1])
############
# create a graph frame
self.mygraph = self.add_frame(
jk.Graph(name="TheName", size=(500, 500), pos=(50, 50),
fmt="go-", xnpts=self.shape[1], freq_up=5, bgcol="w",
xylim=(0, self.shape[1]-1, None, None)))
#_infinite_loop(wait_time=0.5)
def _generate_fake_data(self): # function looped every 0.5 second
"""
Loop starting with the simulation start, getting data and
pushing it to the graph every 0.5 seconds
"""
# NEW (RANDOM) DATA
new_data = np.random.random(self.shape[1])
# concatenate data
self.data = np.vstack((self.data, new_data))
# push new data to the graph
self.mygraph.set_xydata(self.xaxis, self.data[-1])
t = test()
t.start()
t.stop()
t.exit()
This code will create a graph that is auto-updating 5 times a second (freq_up=5), while new data is (randomly) generated every 0.5 seconds (wait_time=0.5) and pushed to the graph for display.
If you don't want the Y-axis to wiggle around, type t.mygraph.xylim = (0, t.shape[1]-1, 0, 1).
I don't know much about matplotlib or jupyter. However, Graphs interest me. I just did some googling and came across this post. Seems like you have to render the graph as an HTML video to see a dynamic graph.
I tried that post. This is the notebook, if you wish to try. Note that the kernel (python 2) takes sometime to build the video. You can read more about it here.
Now you want to display a graph row to row. I tried this. In that notebook, I have a dump_data with 10 rows. I randomly take one and plot them and display as video.
It was interesting to learn about jupyter. Hope this helps.
Related
I'm trying to plot data that is generated in runtime. In order to do so I'm using matplotlib.animation.FuncAnimation.
While the data is displayed correctly, the axis values are not updating accordingly to the values that are being displayed:
The x axis displays values from 0 to 10 eventhough I update them in every iteration in the update_line function (see code below).
DataSource contains the data vector and appends values at runtime, and also returns the indexes of the values being returned:
import numpy as np
class DataSource:
data = []
display = 10
# Append one random number and return last 10 values
def getData(self):
self.data.append(np.random.rand(1)[0])
if(len(self.data) <= self.display):
return self.data
else:
return self.data[-self.display:]
# Return the index of the last 10 values
def getIndexVector(self):
if(len(self.data) <= self.display):
return list(range(len(self.data)))
else:
return list(range(len(self.data)))[-self.display:]
I've obtained the plot_animation function from the matplotlib docs.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from datasource import DataSource
def update_line(num, source, line):
data = source.getData()
indexs = source.getIndexVector()
if indexs[0] != 0:
plt.xlim(indexs[0], indexs[-1])
dim=np.arange(indexs[0],indexs[-1],1)
plt.xticks(dim)
line.set_data(indexs,data)
return line,
def plot_animation():
fig1 = plt.figure()
source = DataSource()
l, = plt.plot([], [], 'r-')
plt.xlim(0, 10)
plt.ylim(0, 1)
plt.xlabel('x')
plt.title('test')
line_ani = animation.FuncAnimation(fig1, update_line, fargs=(source, l),
interval=150, blit=True)
# To save the animation, use the command: line_ani.save('lines.mp4')
plt.show()
if __name__ == "__main__":
plot_animation()
How can I update the x axis values in every iteration of the animation?
(I appreciate suggestions to improve the code if you see any mistakes, eventhough they might not be related to the question).
Here is a simple case of how you can achieve this.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
%matplotlib notebook
#data generator
data = np.random.random((100,))
#setup figure
fig = plt.figure(figsize=(5,4))
ax = fig.add_subplot(1,1,1)
#rolling window size
repeat_length = 25
ax.set_xlim([0,repeat_length])
ax.set_ylim([-2,2])
#set figure to be modified
im, = ax.plot([], [])
def func(n):
im.set_xdata(np.arange(n))
im.set_ydata(data[0:n])
if n>repeat_length:
lim = ax.set_xlim(n-repeat_length, n)
else:
lim = ax.set_xlim(0,repeat_length)
return im
ani = animation.FuncAnimation(fig, func, frames=data.shape[0], interval=30, blit=False)
plt.show()
#ani.save('animation.gif',writer='pillow', fps=30)
Solution
My problem was in the following line:
line_ani = animation.FuncAnimation(fig1, update_line, fargs=(source, l),
interval=150, blit=True)
What I had to do is change the blit parameter to False and the x axis started to move as desired.
Unfortunately it is not possible to create live plots in a google colab notebook using %matplotlib notebook like it is in a offline jupyter notebook on my PC.
I found two similar questions answering how to achieve this for plotly plots (link_1, link_2).
However I cannot manage to adapt it to matplotlib or do not know if that is possible at all.
I am following code from this tutorial here: GitHub link.
In particular I would like to run this code, which creates a callback plotting the reward per step over the training steps:
import matplotlib.pyplot as plt
import numpy as np
%matplotlib notebook
class PlottingCallback(BaseCallback):
"""
Callback for plotting the performance in realtime.
:param verbose: (int)
"""
def __init__(self, verbose=1):
super(PlottingCallback, self).__init__(verbose)
self._plot = None
def _on_step(self) -> bool:
# get the monitor's data
x, y = ts2xy(load_results(log_dir), 'timesteps')
if self._plot is None: # make the plot
plt.ion()
fig = plt.figure(figsize=(6,3))
ax = fig.add_subplot(111)
line, = ax.plot(x, y)
self._plot = (line, ax, fig)
plt.show()
else: # update and rescale the plot
self._plot[0].set_data(x, y)
self._plot[-2].relim()
self._plot[-2].set_xlim([self.locals["total_timesteps"] * -0.02,
self.locals["total_timesteps"] * 1.02])
self._plot[-2].autoscale_view(True,True,True)
self._plot[-1].canvas.draw()
# Create log dir
log_dir = "/tmp/gym/"
os.makedirs(log_dir, exist_ok=True)
# Create and wrap the environment
env = make_vec_env('MountainCarContinuous-v0', n_envs=1, monitor_dir=log_dir)
plotting_callback = PlottingCallback()
model = PPO2('MlpPolicy', env, verbose=0)
model.learn(20000, callback=plotting_callback)
A hack that you can use, is use the same code that you would use on a jupyter notbook, create a button, and use JavaScript to click the button, fooling the frontend to request an update, so that it keeps updating the values.
Here it is an example that uses ipywidgets.
from IPython.display import display
import ipywidgets
progress = ipywidgets.FloatProgress(value=0.0, min=0.0, max=1.0)
import asyncio
async def work(progress):
total = 100
for i in range(total):
await asyncio.sleep(0.2)
progress.value = float(i+1)/total
display(progress)
asyncio.get_event_loop().create_task(work(progress))
button = ipywidgets.Button(description="This button does nothing... except send a\
socket request to google servers to receive updated information since the \
frontend wants to change..")
display(button,ipywidgets.HTML(
value="""<script>
var b=setInterval(a=>{
//Hopefully this is the first button
document.querySelector('#output-body button').click()},
1000);
setTimeout(c=>clearInterval(b),1000*60*1);
//Stops clicking the button after 1 minute
</script>"""
))
Dealing specifically with matplotlib is a bit more complicated, I thought I could simply call matplotlib plot on the asyncio function, but it really lags down the updates because it seems to do unnecessary rendering in the background where no one sees the plot. So another workaround is to update the plot on the code of the button update.
This code is also inspired by Add points to matlibplot scatter plot live and Matplotlib graphic image to base64
The reason being that it is unnecessary to create a plot figure for every plot, you can just modify the figure you already had. This of course means more code.
from IPython.display import display
import ipywidgets
import matplotlib.pyplot as plt
import numpy as np
import io
import base64
def pltToImg(plt):
s = io.BytesIO()
plt.savefig(s, format='png', bbox_inches="tight")
s = base64.b64encode(s.getvalue()).decode("utf-8").replace("\n", "")
#plt.close()
return '<img align="left" src="data:image/png;base64,%s">' % s
progress = ipywidgets.FloatProgress(value=0.0, min=0.0, max=1.0)
import asyncio
async def work(progress):
total = 100
for i in range(total):
await asyncio.sleep(0.5)
progress.value = float(i+1)/total
display(progress)
asyncio.get_event_loop().create_task(work(progress))
button = ipywidgets.Button(description="Update =D")
a=ipywidgets.HTML(
value="image here"
)
output = ipywidgets.Output()
plt.ion()
fig, ax = plt.subplots()
plot = ax.scatter([], [])
point = np.random.normal(0, 1, 2)
array = plot.get_offsets()
array = np.append(array, [point], axis=0)
plot.set_offsets(array)
plt.close()
ii=0
def on_button_clicked(b):
global ii
ii+=1
point=np.r_[ii,np.random.normal(0, 1, 1)]
array = plot.get_offsets()
array = np.append(array, [point], axis=0)
plot.set_offsets(array)
ax.set_xlim(array[:, 0].min() - 0.5, array[:,0].max() + 0.5)
ax.set_ylim(array[:, 1].min() - 0.5, array[:, 1].max() + 0.5)
a.value=(pltToImg(fig))
a.value+=str(progress.value)
a.value+=" </br>"
a.value+=str(ii)
button.on_click(on_button_clicked)
display(output,button,ipywidgets.HTML(
value="""<script>
var b=setInterval(a=>{
//Hopefully this is the first button
document.querySelector('#output-body button')?.click()},
500);
setTimeout(c=>clearInterval(b),1000*60*1);
//Stops clicking the button after 1 minute
</script>"""
),a)
I'm trying to create a plot that updates when given a set of points ([x,y]) but the figure gets stuck on the first plot points and won't plot the rest of the data. I looped a function call but it gets stuck on the first call. I need to be able to give the function multiple sets of single x and y values, and have them plot in a graph.
This is the code I have so far.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
from numpy import *
from time import sleep
import random as rd
class graphUpdater():
def __init__(self):
# Initialize arrays to be plotted
self.xs = []
self.ys = []
style.use('fivethirtyeight') # Figure Style
self.fig = plt.figure() # Initialize figure
self.ax1 = self.fig.add_subplot(111) # Create a subplot
# Ensure the figure auto-scales to fit all points. Might be overkill
self.ax1.set_autoscalex_on(True)
self.ax1.set_autoscaley_on(True)
self.ax1.set_autoscale_on(True)
self.ax1.autoscale(enable = True, axis = 'both', tight = False)
self.ax1.autoscale_view(False, True, True)
# Function that plots the arrays xs and ys. Also plots a linear regression of the data
def plotPoint(self):
self.ax1.clear() # Clears previous values to save memory
xp = linspace(min(self.xs), max(self.xs)) # x-range for regression
if(len(self.xs) > 1): # Conditional for regression, can't linearise 1 point
p1 = polyfit(self.xs, self.ys, 1) # Get the coefficients of the polynomial (slope of line)
self.ax1.plot(xp, polyval(p1, xp)) # Plot the line
self.ax1.plot(self.xs, self.ys, "+") # Plot the raw data points
self.ax1.set_xlabel('(L/A)*I') # Axis and title labels
self.ax1.set_ylabel('V')
self.ax1.set_title('DC Potential Drop')
def appendPlot(self, x, y):
self.xs.append(float(x)) # Append xs with x value
self.ys.append(float(y)) # Append ys with y value
self.plotPoint() # Call the plotPoint function to plot new array values
plt.show(block=False) # Plot and release so graphs can be over written
# Call the function
plsWork = graphUpdater() # I'm very hopeful
i = 0
while(i < 50):
plsWork.appendPlot(i, rd.randint(0, 20))
i += 1
sleep(0.1)
quit_case = input("Hit 'Enter' to Quit") # Conditional so the plot won't disappear
It doesn't work fully. If you put a breakpoint on the quit_case line and run it on debugger on pycharm it plots the graph "properly".
Don't use plt.show(block=False) and don't use time.sleep. Instead, matplotlib provides an animation module, which can be used to avoid such problems as here.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
from numpy import *
from time import sleep
import random as rd
#%matplotlib notebook use in case of running this in a Jupyter notebook
class graphUpdater():
def __init__(self):
# Initialize arrays to be plotted
self.xs = []
self.ys = []
style.use('fivethirtyeight') # Figure Style
self.fig = plt.figure() # Initialize figure
self.ax1 = self.fig.add_subplot(111) # Create a subplot
# Ensure the figure auto-scales to fit all points. Might be overkill
self.ax1.set_autoscalex_on(True)
self.ax1.set_autoscaley_on(True)
self.ax1.set_autoscale_on(True)
self.ax1.autoscale(enable = True, axis = 'both', tight = False)
self.ax1.autoscale_view(False, True, True)
# Function that plots the arrays xs and ys. Also plots a linear regression of the data
def plotPoint(self):
self.ax1.clear() # Clears previous values to save memory
xp = linspace(min(self.xs), max(self.xs)) # x-range for regression
if(len(self.xs) > 1): # Conditional for regression, can't linearise 1 point
p1 = polyfit(self.xs, self.ys, 1) # Get the coefficients of the polynomial (slope of line)
self.ax1.plot(xp, polyval(p1, xp)) # Plot the line
self.ax1.plot(self.xs, self.ys, "+") # Plot the raw data points
self.ax1.set_xlabel('(L/A)*I') # Axis and title labels
self.ax1.set_ylabel('V')
self.ax1.set_title('DC Potential Drop')
def appendPlot(self, x, y):
self.xs.append(float(x)) # Append xs with x value
self.ys.append(float(y)) # Append ys with y value
self.plotPoint() # Call the plotPoint function to plot new array values
# Call the function
plsWork = graphUpdater() # I'm very hopeful
f = lambda i: plsWork.appendPlot(i, rd.randint(0, 20))
ani = animation.FuncAnimation(plsWork.fig, f, frames=50, interval=100, repeat=False)
plt.show()
I wanted to update PatchCollections. I looked at the solution given at matplotlib change a Patch in PatchCollection and I tried to modified to my needs. It works fine without error but it just changes the position of the last rectangle. I want it to update with all the new rectangles positions.
import matplotlib.collections as mcollections
import matplotlib as mpl
import matplotlib.pyplot as plt
class UpdatablePatchCollection(mcollections.PatchCollection):
def __init__(self, patches, *args, **kwargs):
self.patches = patches
mcollections.PatchCollection.__init__(self, patches, *args, **kwargs)
def get_paths(self):
self.set_paths(self.patches)
return self._paths
m=[]
for i in range(5):
print(i)
rect = mpl.patches.Rectangle((i,2),1,1)
m.append(rect)
rect = mpl.patches.Rectangle((i,3),1,1)
m.append(rect)
# This is just for display purpose and will be commented out
collection = mcollections.PatchCollection(m)
ax = plt.figure(None).gca()
ax.set_xlim(0,10)
ax.set_ylim(0,10)
ax.add_artist(collection)
plt.show()
j = []
for i in range(10):
rect.set_xy((i,5))
rect.set_height(2)
rect.set_width(2)
j.append(rect)
print(i)
print(len(j))
m[:] = j[:]
collection = UpdatablePatchCollection(m)
ax = plt.figure(None).gca()
ax.set_xlim(0,10)
ax.set_ylim(0,10)
ax.add_artist(collection)
plt.show()
The result is for the first plt.show() :
Second plt.show() is :
The second loop replaces just the last value.
it is kind of similar to my previous question. If this works out then I'll accept the answer for that as well. Thanks
I'm trying to monitor real-time data with matplotlib.
I found that I can update plot dynamically with interactive mode in Pyplot.
And it worked well, but one problem is 'I cannot manipulate the figure window at all'. For example, move or re-size the figure window.
Here is my code.
This is cons of interactive mode? or I'm using it incorrectly?
import matplotlib.pyplot as plt
import time
import math
# generate data
x = [0.1*_a for _a in range(1000)]
y = map(lambda x : math.sin(x), x)
# interactive mode
plt.ion() # identical plt.interactive(True)
fig, ax = plt.subplots()
# ax = plt.gca()
lines, = ax.plot([], [])
# ax.set_ylim(-1, 1)
ax.grid()
MAX_N_DATA = 100
x_data = []
y_data = []
for i in range(len(x)):
# New data received
x_data.append(x[i])
y_data.append(y[i])
# limit data length
if x_data.__len__() > MAX_N_DATA:
x_data.pop(0)
y_data.pop(0)
# Set Data
lines.set_xdata(x_data)
lines.set_ydata(y_data)
# The data limits are not updated automatically.
ax.relim()
# with tight True, graph flows smoothly.
ax.autoscale_view(tight=True, scalex=True, scaley=True)
# draw
plt.draw()
time.sleep(0.01)
Thank you.
As shown in this answer to another question, replace plt.draw() with plt.pause(0.05). This solved the problem for me.
Although I still think you should use bokeh, I'll tell you how to do it with matplotlib.
The problem why it won't work ist that matplotlib's event loop is not active and therefore it cannot digest window events (like close or resize). Unfortunately it is not possible to trigger this digestion from the outside. What you have to do is to use matplotlib's animation system.
Your code is actually quite well prepared for it so you can use FuncAnimation.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import math
# generate data
x = [0.1*_a for _a in range(1000)]
y = map(lambda x : math.sin(x), x)
# don't need ion, we're using block=True (see end of code)
fig, ax = plt.subplots()
fig.show()
# ax = plt.gca()
lines, = ax.plot([], [])
# ax.set_ylim(-1, 1)
ax.grid()
MAX_N_DATA = 100
x_data = []
y_data = []
def showdata(i):
# New data received
x_data.append(x[i])
y_data.append(y[i])
# limit data length
if x_data.__len__() > MAX_N_DATA:
x_data.pop(0)
y_data.pop(0)
# Set Data
lines.set_xdata(x_data)
lines.set_ydata(y_data)
# The data limits are not updated automatically.
ax.relim()
# with tight True, graph flows smoothly.
ax.autoscale_view(tight=True, scalex=True, scaley=True)
# draw will be called by the animation system
# instead of time.sleep(0.01) we use an update interval of 10ms
# which has the same effect
anim = FuncAnimation(fig, showdata, range(len(x)), interval=10, repeat=False)
# start eventloop
plt.show(block=True)