Matplotlib pause, hold last iteration - python

I have an animation loop in matplotlib, and I would like to freeze the last iteration of the animation. I am using the pause function with a conditional to check for the last iteration. However, in the last iteration, the previous frame is shown -- not the last frame.
Here is an example:
import numpy as np
import matplotlib.pyplot as plt
fig,ax = plt.subplots(1,1)
x = np.linspace(0, 6.28, 401)
freqs = np.arange(5)
for f in freqs:
print f
ax.plot(x, np.sin(f*x))
ax.set_title('$\sin(%d x)$'%f)
if f < freqs[-1]:
plt.pause(1)
ax.cla()
else:
print "hi"
plt.show() # Fails: shows frame with `f==3`.
This prints:
0
1
2
3
4
hi
However, the last frame (with f==4) is never shown. The animation freezes with title, "sin(3x)", and corresponding plot data for f==3, not 4.
Is there a "proper" way to hold the last frame? For example, plt.pause(10000) would work, but seems like a hack.

I always find it much more intuitive to set up the plot first, draw it and then start the animation.
import numpy as np
import matplotlib.pyplot as plt
fig,ax = plt.subplots(1,1)
x = np.linspace(0, 6.28, 401)
freqs = np.arange(5)
line, = ax.plot([],[])
ax.set_xlim([x[0], x[-1]])
ax.set_ylim([-1, 1])
ax.set_title('$\sin(x)$')
fig.canvas.draw()
for f in freqs:
print f
line.set_data(x, np.sin(f*x))
ax.set_title('$\sin(%d x)$'%f)
fig.canvas.draw()
if f < freqs[-1]:
plt.pause(1)
else:
print "hi"
plt.show()

Related

Updateing multi-plot matplotlib

I have a problem witch updateing matplotlib chart. The problem is that i have many curve's on it, and after update the number of them may change. In example code I have 2 sets of data, 1st with 90 curves, and 2nd with 80, and i wish I could plot 1st set, and then 2nd, in the same matplotlib window.
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox
import numpy as np
from numpy.lib.polynomial import RankWarning
import pandas as pd
import sys
fig, ax = plt.subplots()
fig.subplots_adjust(right=0.78)
_x = []
_y = []
_y1 = []
_x1 = []
for x in range(90):
_x.append(np.linspace(0, 10*np.pi, 100))
_y.append(np.sin(_x[x])+x)
for x in range(80):
_x1.append(np.linspace(0, 10*np.pi, 150))
_y1.append(np.tan(_x1[x]+x))
def narysuj(__x, __y):
p = [] # p-pomiar
f = [] # f-czestotliwosc
for x in range(len(__x)):
p.append([])
f.append([])
ax.set_prop_cycle(color=plt.cm.gist_rainbow(np.linspace(0, 1, len(__x))))
for x in range(len(__x)):
for line in range(len(__x[x])):
#print(len(_y[x]), line)
p[x].append(__y[x][line])
f[x].append(__x[x][line])
ax.plot(f[x], p[x], label=f"Label {x}")
plt.show()
narysuj(_x, _y)
narysuj(_x1, _y1)
PS I know the way I'm drawing those charts is highly ineffective.
I found what was the problem. I had to add plt.ion() at the start of program and ax.clear() before drawing.

Animating a scatter plot with a stationary gap in python

I'm animating a scatter plot with a code below. It reads data from a .csv file "sample.csv" and animates it by using np.roll in update function.
import numpy as np
import matplotlib.pyplot as plt
from numpy import genfromtxt
from matplotlib.animation import FuncAnimation
my_data = genfromtxt("sample.csv", delimiter=",", skip_header=1) # reading data
fig, ax = plt.subplots()
xdata, ydata = [], []
line, = ax.plot([], [], ".", markersize=14)
plt.grid()
def init():
ax.set_xlim(0, 50)
ax.set_ylim(0, 60)
return line,
def update(frame):
my_data[:,0] = np.roll(my_data[:,0],1) # moving graph
gap_loc = [19,20,21] # location of a gap
my_data[gap_loc, 1] = np.nan # creating a gap in graph
xdata.append(my_data[:,0])
ydata.append(my_data[:,1])
line.set_data(xdata, ydata)
return line,
ani = FuncAnimation(fig, update, frames=np.arange(0,50,1), init_func=init, blit=True)
plt.show()
The result looks like that:
As you see there is a gap which moves together with the remaining points. However, what I want to achieve is that the gap was stationary at the locations on the horizontal axis: 19,20,21.
How can I achieve this effect?
Below please find a dataset, I'm using for this animation.
Day,Var 1,Var 2
1,2,12
2,4,19
3,6,20
4,8,25
5,10,25
6,12,33
7,14,40
8,16,47
9,18,49
10,20,50
11,22,52
12,24,55
13,26,65
14,28,82
15,30,100
16,32,100
17,34,110
18,36,117
19,38,140
20,40,145
21,42,164
22,44,170
23,46,198
24,48,200
25,50,210
26,48,210
27,46,211
28,44,216
29,42,267
30,40,317
31,38,325
32,36,335
33,34,337
34,32,347
35,30,356
36,28,402
37,26,410
38,24,448
39,22,449
40,20,457
41,18,463
42,16,494
43,14,500
44,12,501
45,10,502
46,8,514
47,6,551
48,4,551
49,2,558
50,0,628
Define the gap when you load the data, and do so in the x column rather than the y:
# imports
my_data = genfromtxt("sample.csv", delimiter=",", skip_header=1) # reading data
gap_loc = [19,20,21] # location of a gap
my_data[gap_loc, 0] = np.nan # creating a gap in graph
# plotting code
So now when you roll the x column, there will always be np.nan at the x values [19, 20, 21], regardless of what the y coordinate is. You can use print(my_data) within the update function to make clear what was going on each iteration.
Here is the result:
Also, I think you are over-plotting because you continually expand xdata and ydata using append. I ended up just removing the xdata and ydata and doing:
def update(frame):
my_data[:,0] = np.roll(my_data[:,0],1) # moving graph
line.set_data(my_data[:,0], my_data[:,1])
return line,

Limit the Number of Open Matplotlib Figures in Python 3

I have a small python 3 script:
import matplotlib.pyplot as plt;
i = 0;
while(i < 40):
x = [1,2,3,4,5];
y = [1,2,3,4,5];
fig = plt.figure();
grid = plt.GridSpec(1, 1)
axis = fig.add_subplot(grid[0,0]);
axis.bar(x,y);
fig.canvas.flush_events()
while(len(plt.get_fignums()) > 10):
pass;
plt.show(block=False);
i += 1;
My goal is to plot 40 plots. I want the first 10 to plot immediately, and then the next figures will only plot if one of the open 10 plots are closed, one by one. This script seems to almost achieve what I want to do, but it crashes when I try to close one of the first 10 plots. Why does this happen? Thanks
while loop is not proper idea for this situation, your code should listens for close event of figures, then act as desired. these few lines of code may help you:
import matplotlib.pyplot as plt;
desiredNumberOfPlots_initialPopulation=1
desiredNumberOfPlots_total=3
def figOnce():
x = [1,2,3,4,5];
y = [1,2,3,4,5];
fig = plt.figure();
grid = plt.GridSpec(1, 1)
axis = fig.add_subplot(grid[0,0]);
axis.bar(x,y);
fig.canvas.flush_events()
fig.canvas.mpl_connect('close_event', handle_close)
plt.show(block=False);
global desiredNumberOfPlots_total
desiredNumberOfPlots_total-=1
def handle_close(evt):
global desiredNumberOfPlots_total
if desiredNumberOfPlots_total>0:
figOnce()
i=1
while(i<=desiredNumberOfPlots_initialPopulation):
print(i)
i+=1
figOnce()

Updating matplotlib figures in real time for data acquisition

I want to plot data in matplotlib in real time. I want to open a figure once at the start of the programme, then update the figure when new data is acquired. Despite there being a few similar questions out there, none quite answer my specific question.
I want each set of data points new_data1 and new_data2 to be plotted on the same figure at the end of each while loop i.e. one line after the first while loop, two lines on the same figure after the second while loop etc. Currently they are all plotted together, but only right at the end of the programme, which is no use for real time data acquisition.
import matplotlib.pyplot as plt
import numpy
hl, = plt.plot([], [])
def update_line(hl, new_datax, new_datay):
hl.set_xdata(numpy.append(hl.get_xdata(), new_datax))
hl.set_ydata(numpy.append(hl.get_ydata(), new_datay))
plt.xlim(0, 50)
plt.ylim(0,200)
plt.draw()
x = 1
while x < 5:
new_data1 = []
new_data2 = []
for i in range(500):
new_data1.append(i * x)
new_data2.append(i ** 2 * x)
update_line(hl, new_data1, new_data2)
x += 1
else:
print("DONE")
This programme plots all 5 lines, but at the end of the programme. I want each line to be plotted after one another, after the while loop is completed. I have tried putting in plt.pause(0.001) in the function, but it has not worked.
This programme is different from the one that has been put forward - that programme only plots one graph and does not update with time.
If I correctly understood your specifications, you can modify just a bit your MWE as follows:
import matplotlib.pyplot as plt
import numpy
fig = plt.figure(figsize=(11.69,8.27))
ax = fig.gca()
ax.set_xlim(0, 50)
ax.set_ylim(0,200)
hl, = plt.plot([], [])
def update_line(hl, new_datax, new_datay):
# re initialize line object each time if your real xdata is not contiguous else comment next line
hl, = plt.plot([], [])
hl.set_xdata(numpy.append(hl.get_xdata(), new_datax))
hl.set_ydata(numpy.append(hl.get_ydata(), new_datay))
fig.canvas.draw_idle()
fig.canvas.flush_events()
x = 1
while x < 10:
new_data1 = []
new_data2 = []
for i in range(500):
new_data1.append(i * x)
new_data2.append(i ** 2 * x)
update_line(hl, new_data1, new_data2)
# adjust pause duration here
plt.pause(0.5)
x += 1
else:
print("DONE")
which displays :
Not sure, if I am reading the requirements right but below is a blueprint. Please change it to suit your requirements. You may want to change the function Redraw_Function and edit the frames (keyword parameter, which is np.arange(1,5,1) ) in the FuncAnimation call. Also interval=1000 means 1000 milliseconds of delay.
If you are using Jupyter then comment out the second last line (where it says plt.show()) and uncomment the last line. This will defeat your purpose of real time update but I am sorry I had trouble making it work real time in Jupyter. However if you are using python console or official IDLE please run the code as it is. It should work nicely.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
plot, = plt.plot([],[])
def init_function():
ax.set_xlim(0,50)
ax.set_ylim(0,250)
return plot,
def Redraw_Function(UpdatedVal):
new_x = np.arange(500)*UpdatedVal
new_y = np.arange(500)**2*UpdatedVal
plot.set_data(new_x,new_y)
return plot,
Animated_Figure = FuncAnimation(fig,Redraw_Function,init_func=init_function,frames=np.arange(1,5,1),interval=1000)
plt.show()
# Animated_Figure.save('MyAnimated.gif',writer='imagemagick')
When you run the code, you obtain the below result. I tried to keep very little code but I am sorry, if your requirement was totally different.
Best Wishes,

Replacing part of a plot with a dotted line

I would like to replace part of my plot where the function dips down to '-1' with a dashed line carrying on from the previous point (see plots below).
Here's some code I've written, along with its output:
import numpy as np
import matplotlib.pyplot as plt
y = [5,6,8,3,5,7,3,6,-1,3,8,5]
plt.plot(np.linspace(1,12,12),y,'r-o')
plt.show()
for i in range(1,len(y)):
if y[i]!=-1:
plt.plot(np.linspace(i-1,i,2),y[i-1:i+1],'r-o')
else:
y[i]=y[i-1]
plt.plot(np.linspace(i-1,i,2),y[i-1:i+1],'r--o')
plt.ylim(-1,9)
plt.show()
Here's the original plot
Modified plot:
The code I've written works (it produces the desired output), but it's inefficient and takes a long time when I actually run it on my (much larger) dataset. Is there a smarter way to go about doing this?
You can achieve something similar without the loops:
import pandas as pd
import matplotlib.pyplot as plt
# Create a data frame from the list
a = pd.DataFrame([5,6,-1,-1, 8,3,5,7,3,6,-1,3,8,5])
# Prepare a boolean mask
mask = a > 0
# New data frame with missing values filled with the last element of
# the previous segment. Choose 'bfill' to use the first element of
# the next segment.
a_masked = a[mask].fillna(method = 'ffill')
# Prepare the plot
fig, ax = plt.subplots()
line, = ax.plot(a_masked, ls = '--', lw = 1)
ax.plot(a[mask], color=line.get_color(), lw=1.5, marker = 'o')
plt.show()
You can also highlight the negative regions by choosing a different colour for the lines:
My answer is based on a great post from July, 2017. The latter also tackles the case when the first element is NaN or in your case a negative number:
Dotted lines instead of a missing value in matplotlib
I would use numpy functionality to cut your line into segments and then plot all solid and dashed lines separately. In the example below I added two additional -1s to your data to see that this works universally.
import numpy as np
import matplotlib.pyplot as plt
Y = np.array([5,6,-1,-1, 8,3,5,7,3,6,-1,3,8,5])
X = np.arange(len(Y))
idxs = np.where(Y==-1)[0]
sub_y = np.split(Y,idxs)
sub_x = np.split(X,idxs)
fig, ax = plt.subplots()
##replacing -1 values and plotting dotted lines
for i in range(1,len(sub_y)):
val = sub_y[i-1][-1]
sub_y[i][0] = val
ax.plot([sub_x[i-1][-1], sub_x[i][0]], [val, val], 'r--')
##plotting rest
for x,y in zip(sub_x, sub_y):
ax.plot(x, y, 'r-o')
plt.show()
The result looks like this:
Note, however, that this will fail if the first value is -1, as then your problem is not well defined (no previous value to copy from). Hope this helps.
Not too elegant, but here's something that doesn't use loops which I came up with (based on the above answers) which works. #KRKirov and #Thomas Kühn , thank you for your answers, I really appreciate them
import pandas as pd
import matplotlib.pyplot as plt
# Create a data frame from the list
a = pd.DataFrame([5,6,-1,-1, 8,3,5,7,3,6,-1,3,8,5])
b=a.copy()
b[2]=b[0].shift(1,axis=0)
b[4]=(b[0]!=-1) & (b[2]==-1)
b[5]=b[4].shift(-1,axis=0)
b[0] = (b[5] | b[4])
c=b[0]
d=pd.DataFrame(c)
# Prepare a boolean mask
mask = a > 0
# New data frame with missing values filled with the last element of
# the previous segment. Choose 'bfill' to use the first element of
# the next segment.
a_masked = a[mask].fillna(method = 'ffill')
# Prepare the plot
fig, ax = plt.subplots()
line, = ax.plot(a_masked, 'b:o', lw = 1)
ax.plot(a[mask], color=line.get_color(), lw=1.5, marker = 'o')
ax.plot(a_masked[d], color=line.get_color(), lw=1.5, marker = 'o')
plt.show()

Categories

Resources