matplotlib show() doesn't work twice - python

I have a strange problem, with matplotlib. If I run this program, I'm able to open and close several time the same figure.
import numpy
from pylab import figure, show
X = numpy.random.rand(100, 1000)
xs = numpy.mean(X, axis=1)
ys = numpy.std(X, axis=1)
fig = figure()
ax = fig.add_subplot(111)
ax.set_title('click on point to plot time series')
line, = ax.plot(xs, ys, 'o', picker=5) # 5 points tolerance
def onpick(event):
figi = figure()
ax = figi.add_subplot(111)
ax.plot([1,2,3,4])
figi.show()
fig.canvas.mpl_connect('pick_event', onpick)
show()
On the contrary, if I use the same code of onpick function into my custom widget it opens the figure only the first time, into the other events it enters into the functions but doesn't display the figure:
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt4 import NavigationToolbar2QT as NavigationToolbar
import time
STEP = 0.000152
class MplCanvas(FigureCanvas):
def __init__(self):
# initialization of the canvas
FigureCanvas.__init__(self, Figure())
self.queue = []
self.I_data = np.array([])
self.T_data = np.array([])
self.LvsT = self.figure.add_subplot(111)
self.LvsT.set_xlabel('Time, s')
self.LvsT.set_ylabel('PMT Voltage, V')
self.LvsT.set_title("Light vs Time")
self.LvsT.grid(True)
self.old_size = self.LvsT.bbox.width, self.LvsT.bbox.height
self.LvsT_background = self.copy_from_bbox(self.LvsT.bbox)
self.LvsT_plot, = self.LvsT.plot(self.T_data,self.I_data)
#self.LvsT_plot2, = self.LvsT.plot(self.T_data2,self.I_data2)
self.mpl_connect('axes_enter_event', self.enter_axes)
self.mpl_connect('button_press_event', self.onpick)
self.count = 0
self.draw()
def enter_axes(self,event):
print "dentro"
def onpick(self,event):
print "click"
print 'you pressed', event.canvas
a = np.arange(10)
print a
print self.count
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(a)
fig.show()
def Start_Plot(self,q,Vmin,Vmax,ScanRate,Cycles):
self.queue = q
self.LvsT.clear()
self.LvsT.set_xlim(0,abs(Vmin-Vmax)/ScanRate*Cycles)
self.LvsT.set_ylim(-3, 3)
self.LvsT.set_autoscale_on(False)
self.LvsT.clear()
self.draw()
self.T_data = np.array([])
self.I_data = np.array([])
# call the update method (to speed-up visualization)
self.timerEvent(None)
# start timer, trigger event every 1000 millisecs (=1sec)
self.timerLvsT = self.startTimer(3)
def timerEvent(self, evt):
current_size = self.LvsT.bbox.width, self.LvsT.bbox.height
if self.old_size != current_size:
self.old_size = current_size
self.LvsT.clear()
self.LvsT.grid()
self.draw()
self.LvsT_background = self.copy_from_bbox(self.LvsT.bbox)
self.restore_region(self.LvsT_background, bbox=self.LvsT.bbox)
result = self.queue.get()
if result == 'STOP':
self.LvsT.draw_artist(self.LvsT_plot)
self.killTimer(self.timerLvsT)
print "Plot finito LvsT"
else:
# append new data to the datasets
self.T_data = np.append(self.T_data,result[0:len(result)/2])
self.I_data = np.append(self.I_data,result[len(result)/2:len(result)])
self.LvsT_plot.set_data(self.T_data,self.I_data)#L_data
#self.LvsT_plot2.set_data(self.T_data2,self.I_data2)#L_data
self.LvsT.draw_artist(self.LvsT_plot)
self.blit(self.LvsT.bbox)
class LvsT_MplWidget(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent)
self.canvas = MplCanvas()
self.vbl = QtGui.QVBoxLayout()
self.vbl.addWidget(self.canvas)
self.setLayout(self.vbl)
This widget is needed for an animation plot and when the experiment is finished if I click on the plot it should appear a figure, that appears only the first time.
Do you have any clue?
Thank you very much.

At the start of your code, enable interactive mode via
plt.ion()

I have new information about this that a google search turned up
This is from the writer of matplotlib. This came from http://old.nabble.com/calling-show%28%29-twice-in-a-row-td24276907.html
Hi Ondrej,
I'm not sure where to find a good
explanation of that, but let me give
you some hints. It is intended to use
show only once per program. Namely
'show' should be the last line in your
script. If you want interactive
plotting you may consider interactive
mode (pyplot.ion-ioff) like in the
example below.
Furthermore for dynamic plotting all
animation demos might be useful.
Maybe you want to have also a look at
http://matplotlib.sourceforge.net/users/shell.html
.
best regards Matthias
So it seems it is an undocumented "feature" (bug?).
Edit: here is his code block:
from pylab import *
t = linspace(0.0, pi, 100)
x = cos(t)
y = sin(t)
ion() # turn on interactive mode
figure(0)
subplot(111, autoscale_on=False, xlim=(-1.2, 1.2), ylim=(-.2, 1.2))
point = plot([x[0]], [y[0]], marker='o', mfc='r', ms=3)
for j in arange(len(t)):
# reset x/y-data of point
setp(point[0], data=(x[j], y[j]))
draw() # redraw current figure
ioff() # turn off interactive mode
show()
So maybe by using draw() you can get what you want. I haven't tested this code, I'd like to know its behavior.

I had the same issue with show() only working the first time. Are you still on version 0.99.3 or thereabouts? I was able to resolve my problem recently, if you're still interested in changing the behaviour of show(), try this:
I noticed this paragraph titled multiple calls to show supported on the what's new part of the matplotlib download site.
A long standing request is to support multiple calls to show(). This has been difficult because it is hard to get consistent behavior across operating systems, user interface toolkits and versions. Eric Firing has done a lot of work on rationalizing show across backends, with the desired behavior to make show raise all newly created figures and block execution until they are closed. Repeated calls to show should raise newly created figures since the last call. Eric has done a lot of testing on the user interface toolkits and versions and platforms he has access to, but it is not possible to test them all, so please report problems to the mailing list and bug tracker.
This was 'what's new' for version 1.0.1, at time of writing the version in synaptic was still on 0.99.3. I was able to download and build from source v1.0.1. The additional packages I also required to satisfy dependencies were libfreetype6-dev tk-dev tk8.5-dev tcl8.5-dev python-gtk2-dev; your mileage may vary.
Now that i have matplotlib.__version__ == 1.0.1 , the following code works how I would expect:
from matplotlib import pyplot as p
from scipy import eye
p.imshow(eye(3))
p.show()
print 'a'
p.imshow(eye(6))
p.show()
print 'b'
p.imshow(eye(9))
p.show()
print 'c'

def onpick(self,event):
print "click"
print 'you pressed', event.canvas
...
ax.plot(a)
fig.show() # <--- this blocks the entire loop
Try:
def onpick(self,event):
print "click"
print 'you pressed', event.canvas
...
ax.plot(a)
self.draw()
self.update()

My workaround to this problem is to never call close.
I'm pretty sure you can control the transparency of a widget in PyQt. You might try controlling the visibility using Qt instead of matplotlib. I'm sure someone else who knows more about matplotlib can give a better answer than that though :D

You can create a figure instance by:
fig = plt.figure(0)
And draw your stuff by manipulate this fig.
You can use fig.show() for anytime to show your figure.

Related

Close pyplot and reopen with updated data from another thread

I am trying to display a pyplot that updates every 3 seconds with data from another thread. I need to close the plot and reopen with the updated variables. I do not need an animation, I want to close the figure and open a new one.
This does not work unless I manually close the Figure window.
import numpy as np
from matplotlib import pyplot as plt
import threading as th
from time import sleep
Xplot = []
YPlot = []
def RandomXYGen():
x = np.random.normal(5, .75)
y = np.random.normal(10, 1.5)
return x, y
def LoopData():
while True:
xdata, ydata = RandomXYGen()
Xplot.append(xdata)
YPlot.append(ydata)
print(xdata, ydata)
sleep(0.1)
def Plotter(xToPlot, yToPlot):
plt.close('all')
plt.plot(xToPlot, yToPlot)
plt.show()
t = th.Thread(target=LoopData, daemon=True)
t.start()
while True:
Plotter(Xplot, YPlot)
sleep(3)
Any ideas why it is not closing?
[python 3.10.8 on Windows 10]
After many attempts on figuring it out I was finally able to get help from the Python Discord server. This is what solves the above problem:
plt.show(block=False)
plt.pause(0.01)
instead of:
plt.show()
Leaving this here in case someone runs into the same issue as me.

Not sure why my Pyplot subplot wont update over time

I am working on a project that requires me to log data over time, while also plotting the data on screen with a live line graph. I have gotten everything but the line graph to work this far and am unsure what I am doing incorrectly. This is the imports that I am currently using.
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import pyplot as plt
from matplotlib import style
from tkinter import *
from PIL import Image
import numpy as np
import serial
from serial import Serial
import sqlite3
import time
from datetime import datetime
from array import *
import cv2
from pathlib import Path
from itertools import count
The data that is meant to be used for the Y axis plotting is stored in an array of data. Each index in this array is to hold the last read value from the sensors, i=0 is sensor 1 and so on.
A=[0,0,0,0,0,0,0,0]
This is the definition of the subplot that I am trying to draw to. I think I am setting this up correctly, however I am not getting the expected result so likely not.
fig1 = plt.Figure(dpi=100, facecolor="#f0f0f0")
a = fig1.add_subplot(111)
a.patch.set_facecolor("#f0f0f0")
a.set_xlabel('time (Sec)')
a.set_ylabel('pressure(kPa)')
a.set_ylim(0,100)
a.set_xlim(0,30)
graph1 = FigureCanvasTkAgg(fig1, master=root)
graph1.get_tk_widget().place(x=10, y=220, width=210, height=280)
graph1.draw();
I am currently just trying to get one of the lines to draw first before handling the, seemingly, minor issue that is overlapping multiple lines. This is the function that I am trying to use in order to draw said line.
def graph_plotdata():
global A
global a
line1 = []
time = []
time.append(next(index))
line1.append(A[0])
a.cla()
a.plot(time, line1)
graph1.draw()
I have tried several iterations of this code in order attempt to solve this problem. The closest I have to getting it to work is in the current state in which something is happening however instead of keeping my min and max limits on the graph it completely reformats my plot and plots an "invisible" line.
Before starting:
After starting:
I am not overwhelmingly experienced when is comes to python libraries so bare with me.
I use a dictionary to store the various lines and line plots and then update the plots using set_data(xdata, ydata). I'm not sure how your datastream works, so mine just updates when I push the update button and generates a random reading. You'll obviously want to change those parts to match your data input.
fig, ax = plt.subplots(1, 1)
plt.subplots_adjust(bottom = 0.20)
num_sensors = 10
latest_reading = [0]*num_sensors
lines = {index: [0] for index in range(num_sensors)}
times = [0]
line_plots = {index: ax.plot(lines[index])[0] for index in range(num_sensors)}
btn_ax = plt.axes([0.475, 0.05, 0.10, 0.05])
def update(event):
latest_reading = np.random.randint(0, 10, num_sensors)
times.append(times[-1] + 1)
for index in range(num_sensors):
lines[index].append(latest_reading[index])
line_plots[index].set_data(times, lines[index])
# Adjust limits
max_time_window = 20
ax.set_xlim(max(0, max(times)-max_time_window), max(times))
ax.set_ylim(0, max(lines))
plt.draw()
btn = mpl.widgets.Button(btn_ax, 'Update')
btn.on_clicked(update)
Thank you for the response.
I figured out the issue, it had nothing to do with my matplotlib/tkinter implementation. I just totally missed that I had a scope inheritance issue. The lists of 'time' and 'line1' are not persistent in the entire scope and therefore being rewritten to empty lists every time the 'graph_plotdata()' function is called.
my solution is as follows:
timet = []
line1 = []
"""----------Graph Updater-----------"""
def graph_plotdata():
global B
global a
global graph1
global timet
global line1
timet.append(next(index))
line1.append(B[0])
a.clear()
a.plot(timet, line1)
a.patch.set_facecolor("#f0f0f0")
a.set_xlabel('time (Sec)')
a.set_ylabel('pressure(kPa)')
a.set_ylim(0,30)
a.set_xlim(0,30)
graph1.draw()
Hopefully this helps people in the future running into a similar issue!

Streamlit segmentation faults when multiple windows are open for real-time data display web app

I am building a web app in streamlit that
periodically loads a data file that a separate process updates
displays 9 matplotlib figures using st.pyplot
allows the user to select the ID of the data to display
It runs fine when one user is using the web app in one window. But if I open another tab, it segfaults shortly after.
I think the problem is especially bad when when both tabs are trying to load the data simultaneously. The reason I think this is because if I open another tab "staggered" from the first one, it can successfully run a bit before segfaulting. But if the tabs both start open, it segfaults instantly. Matplotlib might also be involved. I am not sure, but I think if I reduce the number of plots, there are likewise more successful runs before segfaulting.
I have implemented this periodic loading both with streamlit_autorefresh and an infinite while loop, but both have this problem.
I'll post what-I-think-are-relevant parts of the code below, using only one of the plots:
import streamlit as st
from streamlit_autorefresh import st_autorefresh
...
count = st_autorefresh(interval=streamlit_run_every_secs * 1000, key="autorefresh_counter")
...
col_1_1, _, _ = st.columns(3)
with col_1_1:
selected_product = st.selectbox(label='product',
options=tuple(products),
on_change=None)
...
col_4_1, col_4_2, col_4_3 = st.columns(3)
with col_4_1:
final_unhappiness_heatmap = st.empty()
final_desired_move_ticks_heatmap = st.empty()
...
def load_results(p):
print('load_results() begin')
results_fp = os.path.join(os.path.join(ff_path, 'results'), f'{p}.csv')
results_piv = pd.read_csv(results_fp, header=[0,1])
results_piv = rename_unnamed(results_piv)
results_piv.columns = [(col_lvl_0, float(col_lvl_1)) if col_lvl_1.replace('.', '').replace('-', '').isnumeric() else (col_lvl_0, col_lvl_1) for (col_lvl_0, col_lvl_1) in results_piv.columns]
results_piv.columns = pd.MultiIndex.from_tuples(results_piv.columns)
return results_piv
...
figs = []
def app_iteration():
print(f"{pd.to_datetime('now')}: Running app_iteration()")
global figs
results_piv = load_results(selected_product)
...
print(f'=== closing {len(figs)} figs ===')
for fig in figs:
print('closing fig')
plt.close(fig)
figs = []
...
with final_unhappiness_heatmap.container():
fig, ax = plt.subplots()
figs.append(fig)
sns.heatmap(results_piv.set_index('maturity').final_unhappiness, center=0, vmin=-1, vmax=1, cmap='coolwarm_r', annot=True, fmt=".2f", cbar=False, ax=ax)
ax.set_title(f'{selected_product} Final Unhappiness')
st.pyplot(fig)
...
app_iteration()
I have the same issue with matplotlib and steamlit. Here is the solution (Limitations and known issues section): https://docs.streamlit.io/streamlit-cloud/troubleshooting[https://docs.streamlit.io/streamlit-cloud/troubleshooting][1]
Matplotlib doesn't work well with threads. So if you're using Matplotlib you should wrap your code with locks as shown in the snippet below. This Matplotlib bug is more prominent when you share your app apps since you're more likely to get more concurrent users then.
from matplotlib.backends.backend_agg import RendererAgg
_lock = RendererAgg.lock
with _lock:
fig.title('This is a figure)')
fig.plot([1,20,3,40])
st.pyplot(fig)

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,

two output graphs in matplotlib scatterplot animation

I have some code to animate a scatterplot in matplotlib, everything runs smoothly, except that I end up with two output graphs at the end. One contains the desired animation, while the other is blank.
While it is probably a simple issue someone, I cannot see why, nor figure out why I am getting two graphs. Can anyone see what is giving me the "bogus" empty graph?
Cheers.
import math, numpy, time, matplotlib, pyvisa
from pyvisa import visa
from matplotlib import pyplot, animation
import os.path
class Realtime_measurement_and_scatter_plot:
def __init__(......):
self.animaiton = animation.FuncAnimation(self.figure, self.take_new_data_and_update_graph, init_func=self.initialise_graph, frames = self.number_of_measurements, interval=100, blit=False)
def initialise_graph(self):
self.figure = matplotlib.pyplot.figure()
self.axes = matplotlib.pyplot.axes()
self.axes.set_title("Realtime Plot")
self.x_data = []
self.y_data = []
self.scatterplot = matplotlib.pyplot.scatter(self.x_data, self.y_data)
return self.scatterplot,
def take_new_data_and_update_graph(self, i):
...
averaged_voltage = numpy.average(measured_voltages )
new_time = float(i) * float( self.time_between_measurements )
self.x_data = numpy.append( self.x_data, averaged_voltage )
self.y_data = numpy.append( self.y_data , new_time )
self.scatterplot = matplotlib.pyplot.scatter(self.x_data, self.y_data)
return self.scatterplot,
animation_instance = Realtime_measurement_and_scatter_plot(1000000, 1 , 1, 6, "Super_FUN_TEST.0",22)
matplotlib.pyplot.show()
tcaswell's comment about "init uses attributes you don't create until initialize_graph"
prompted me to fix that.
Changing this so that init creates the figure, axes ect instead of them being created in intialize_graph removes the two output graphs problem; solving my problem!

Categories

Resources