multiple interactive matplotlib behave differently in python 2.7 vs 3.6 - python

I have a python script that loops continuously with user input directing it to get data from a file then plots it using matplotlib. My hope is to have the plots show up and stick around (and continue to be interactive) while the script continues on in the console asking for the next round of user input and plotting. So the user might end up with a few plot windows open in the end that are all functional.
I actually have it working in my python 2.7 anaconda build with the following example code that just plots a random dataset each loop.
import sys
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
print('matplotlib version: '+matplotlib.__version__)
version = sys.version_info[0]
def plot_data(data):
fig = plt.figure(figsize=(6,6))
plt.plot(data)
plt.ion()
plt.show()
plt.pause(0.001)
while True:
if version < 3:
option = raw_input('type (1) to plot next data, (q) to quit\n')
else:
option = input('type (1) to plot next data, (q) to quit\n')
if option == '1':
data = np.random.choice(1000,100, replace = False)
plot_data(data)
elif option == 'q':
break
Running the code with my python 3.6 build, the plot shows up but is frozen in a non-responding state until I go back to the console and type in q to quit my user input loop. In python 2.7 the plot windows are fully functional through multiple input loops and plot calls. So I'm hoping someone knows what difference here. I'm printing the matplotlib version and it seems like both my 2.7 and 3.6 environments are using the same matplotlib 2.0.2 so maybe it's in the GUI handler?

I'm not sure if the setup from the question using a while loop and plt.ion() is actually supposed to work. At least for me using python 2.7 and matplotlib 2.1 it doesn't.
I would have though that this is to be expected, because the application freezes and becomes unresponsive as long as it waits for user input.
In any case, since it is always a bit delicate to mix the console and a GUI application, I would choose to work completely inside the event loop of the GUI.
You may connect "key_press_event"s to the "1" and "q" key and show new data inside the open matplotlib window.
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(6,6))
ax = plt.subplot(111)
def plot_data(ax, data):
ax.clear()
ax.plot(data)
ax.figure.canvas.draw_idle()
def press(event=None):
if event:
if event.key == "1":
data = np.random.choice(1000,100, replace = False)
plot_data(ax, data)
elif event.key == "q":
plt.close()
cid= fig.canvas.mpl_connect("key_press_event", press)
plt.show()
In case you want to create several figures, this would look like
import matplotlib.pyplot as plt
import numpy as np
def create_figure(data):
fig, ax = plt.subplots(figsize=(6,6))
ax.plot(data)
cid = fig.canvas.mpl_connect("key_press_event", press)
fig.show()
def plot_data():
data = np.random.choice(1000,100, replace = False)
create_figure(data)
def press(event=None):
if event:
if event.key == "1":
plot_data()
elif event.key == "q":
plt.close("all")
else:
plot_data()
press()
plt.show()
For more complicated input, you may create a small GUI, which collects the input and calls the function for plotting. As an example, one may use Tkinter for that purpose.
from Tkinter import * # use from tkinter import * if using python3
import matplotlib.pyplot as plt
import numpy as np
def create_figure(data):
fig, ax = plt.subplots(figsize=(6,6))
ax.plot(data)
fig.show()
def plot_data(n, amp):
try:
n = int(n)
amp = float(amp)
data = amp*np.random.rand(n)
create_figure(data)
except:
pass
def closeall():
for i in plt.get_fignums():
plt.close(i)
def create_input():
root = Tk()
Label(text="Number of datapoints").grid(row=1,column=0, sticky=W)
Label(text="Amplitude").grid(row=2,column=0, sticky=W)
tb1=Entry(root,textvariable=StringVar(root, value='24'))
tb1.grid(row=1,column=1, sticky=W+E)
tb2=Entry(root,textvariable=StringVar(root, value='6'))
tb2.grid(row=2,column=1, sticky=W+E)
b1=Button(root,text=" Plot ",command= lambda : plot_data(tb1.get(),tb2.get()))
b1.grid(row=4,column=1, sticky=W+E)
b2=Button(root,text="Close All",command=closeall)
b2.grid(row=5,column=1, sticky=W+E)
root.mainloop()
create_input()

Related

Dynamically updating Matplotlib

I want to update a single Matplotlib figure with each timestep in my Notebook. I have referenced numerous API resources and examples forum answers here on StackOverflow, however, all of the proposed code either did not graph or displayed a new figure with each timestep, like this answer,
import matplotlib.pyplot as plt
import time
import random
from collections import deque
import numpy as np
# simulates input from serial port
def random_gen():
while True:
val = random.randint(1,10)
yield val
time.sleep(0.1)
a1 = deque([0]*100)
ax = plt.axes(xlim=(0, 20), ylim=(0, 10))
d = random_gen()
line, = plt.plot(a1)
plt.ion()
plt.ylim([0,10])
plt.show()
for i in range(0,20):
a1.appendleft(next(d))
datatoplot = a1.pop()
line.set_ydata(a1)
plt.draw()
print a1[0]
i += 1
time.sleep(0.1)
plt.pause(0.0001) #add this it will be OK.
and this answer.
import numpy as np
import matplotlib.pyplot as plt
plt.axis([0, 10, 0, 1])
for i in range(10):
y = np.random.random()
plt.scatter(i, y)
plt.pause(0.1)
How can I update a figure with each timestep in Python, via Matplotlib or possibly other means? I appreciate your perspectives.
Thank you :)
Real-time drawing by entering the interactive mode of matplotlib.
If you only use plt.show() to draw, the program will stop executing the subsequent program, so open the drawing window through plt.ion() to enter the interactive mode, use the program plt.plot() to draw in real time, after the drawing is completed, use plt .ioff() exits the interactive mode and uses plt.show() to display the final image data. If plt.show() is not added at the end, it will flash back.
import matplotlib.pyplot as plt
import numpy as np
ax=[]
ay=[]
bx=[]
by=[]
num=0
plt.ion()
# plt.rcParams['savefig.dpi'] = 200
# plt.rcParams['figure.dpi'] = 200
plt.rcParams['figure.figsize'] = (10, 10)
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['lines.linewidth'] = 0.5
while num<100:
plt.clf()
plt.suptitle("TITLE",fontsize=30)
g1=np.random.random()
ax.append(num)
ay.append(g1)
agraphic=plt.subplot(2,1,1)
agraphic.set_title('TABLE1')
agraphic.set_xlabel('x',fontsize=10)
agraphic.set_ylabel('y', fontsize=20)
plt.plot(ax,ay,'g-')
#table2
bx.append(num)
by.append(g1)
bgraghic=plt.subplot(2, 1, 2)
bgraghic.set_title('TABLE2')
bgraghic.plot(bx,by,'r^')
plt.pause(0.4)
if num == 15:
plt.savefig('picture.png', dpi=300)
#break
num=num+1
plt.ioff()
plt.show()

How to store click position without global variables? Matplotlib/Python

I'm trying to stop a random plotting with just clicking the screen, but the examples I've seen use global variables and I don't want to use them. So what can I do?
This is my code so far. The function onclick is an idea I took from Matplotlib documentation, but they used it to print information on the console and I want to store it in a variable and then return it (for example in one variable called "get_mouse" and then break the while-cicle).
Pd: I know there're some crimes, sorry I'm a beginner...
import matplotlib.pyplot as plt
import numpy as np
def onclick(event):
if event.dblclick :
print('help me')
plt.ion()
B=np.random.random((1,5))
fig = plt.figure()
ax = plt.axes(xlim=(-2,2),ylim=(-2,2))
line, = ax.plot([],[],'bo', ms=4)
cid = fig.canvas.mpl_connect('button_press_event', onclick)
while True:
S=np.random.random((1,5))
line.set_data(B+S,B-S)
fig.canvas.draw()
fig.canvas.flush_events()
plt.pause(0.5)
if get_mouse == True:
break

Matplotpib figure in a loop not responding

Python 2.7.11, Win 7, x64, Numpy 1.10.4, matplotlib 1.5.1
I ran the following script from an iPython console after entering %matplotlib qt at the command line
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import numpy as np
number = input("Number: ")
coords = np.array(np.random.randint(0, number, (number, 3)))
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(coords[:,0], coords[:,1], coords[:,2])
plt.show()
It plots a random scatter in 3D. So I thought it would be a trivial matter to just pop it into a while loop & get a new figure on each iteration.
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import numpy as np
s = True
while s:
number = input("Number: ")
coords = np.array(np.random.randint(0, number, (number, 3)))
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(coords[:,0], coords[:,1], coords[:,2])
plt.show()
cont = input("Continue? (y/n)")
if cont == 'n':
s = False
...but the figures are just blank & unresponsive until I enter an input for cont then I get,
NameError: name 'y' is not defined
...and the whole thing crashes.
So what am I missing here?
EDIT: Taking into account Aquatically challenged's answer below. The figures still hang until the loop is exited, then they are all plotted at the same time. Anybody know why the plots are not done within the loop?
input tries to eval the string you type in, treating it as though it is Python code, then returns the result of the evaluation. For example, if result = input() and I type in 2 + abs(-3) then result will be equal to 5.
When you enter the string y this is treated as a variable name. Since you haven't defined any variable named y you will get a NameError. Instead of input you want to use raw_input, which just returns the input string without trying to evaluate it.
In order to get your figures to display within the while loop you need to insert a short pause to allow the contents of the figure to be drawn before you continue executing your while loop. You could use plt.pause, which also takes care of updating the active figure.
s = True
while s:
number = input("Number: ")
coords = np.array(np.random.randint(0, number, (number, 3)))
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(coords[:,0], coords[:,1], coords[:,2])
plt.pause(0.1)
cont = raw_input("Continue? (y/n)")
if cont == 'n':
s = False
I haven't replicated but when you input the 'y' or 'n'. try to put the single (or double quotes) are the y or n
to input strings without quotes. Use raw_input instead of input
as described here Python 2.7 getting user input and manipulating as string without quotations

Can I generate and show a different image during each loop with Matplotlib?

I am new to Matplotlib and Python. I mostly use Matlab. Currently, I am working with a Python code where I want to run a loop. In each loop, I will do some data processing and then show an image based on the processed data. When I go to the next loop, I want the previously stored image to be closed and generate a new image based on the latest data.
In other words, I want a python code equivalent to the following Matlab code:
x = [1 2 3];
for loop = 1:3
close all;
y = loop * x;
figure(1);
plot(x,y)
pause(2)
end
I tried the following python code to achieve my goal:
import numpy as np
import matplotlib
import matplotlib.lib as plt
from array import array
from time import sleep
if __name__ == '__main__':
x = [1, 2, 3]
for loop in range(0,3):
y = numpy.dot(x,loop)
plt.plot(x,y)
plt.waitforbuttonpress
plt.show()
This code puts all plots superimposed in the same figure. If I put the plt.show() command inside the for loop, only the first image is shown. Therefore, I could not replicate my Matlab code in Python.
try this:
import numpy
from matplotlib import pyplot as plt
if __name__ == '__main__':
x = [1, 2, 3]
plt.ion() # turn on interactive mode
for loop in range(0,3):
y = numpy.dot(x, loop)
plt.figure()
plt.plot(x,y)
plt.show()
_ = input("Press [enter] to continue.")
if you want to close the previous plot, before showing the next one:
import numpy
from matplotlib import pyplot as plt
if __name__ == '__main__':
x = [1, 2, 3]
plt.ion() # turn on interactive mode, non-blocking `show`
for loop in range(0,3):
y = numpy.dot(x, loop)
plt.figure() # create a new figure
plt.plot(x,y) # plot the figure
plt.show() # show the figure, non-blocking
_ = input("Press [enter] to continue.") # wait for input from the user
plt.close() # close the figure to show the next one.
plt.ion() turns on interactive mode making plt.show non-blocking.
and heres is a duplicate of your matlab code:
import numpy
import time
from matplotlib import pyplot as plt
if __name__ == '__main__':
x = [1, 2, 3]
plt.ion()
for loop in xrange(1, 4):
y = numpy.dot(loop, x)
plt.close()
plt.figure()
plt.plot(x,y)
plt.draw()
time.sleep(2)

matplotlib weirdness, it's not drawing my graph

What happened is I followed this demo, I modified it to suit my needs had it working, changed it to use a function to draw two graphs but now it doesn't work at all using plt.show() or plt.savefig()
here's my code
import csv
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
# I converted excel to a csv file
data = [x for x in csv.reader(open('ASS1_Q1.csv'))]
question1 = {}
question1['males'] = []
question1['females'] = []
for x in data:
if x[0].lower() == "male":
question1["males"].append(float(x[1]))
elif x[0].lower() == "female":
question1['females'].append(float(x[1]))
else:
print "Not a valid dataline", x
def plot_graph(data, filename):
fig = plt.figure()
ax = fig.add_subplot(111)
n, bins, patches = ax.hist(np.array(data), bins=13, align='mid', facecolor='#888888')
ax.set_xlabel('Speed in kph')
ax.set_ylabel('Amount of Females')
ax.set_xlim(min(data, max(data)))
# plt.savefig(filename)
plt.show()
plot_graph(question1['males'], "ASS1Q1-males.eps")
#plot_graph(question1['females'], "ASSQ2-females.eps")
print summary(question1['males'])
print summary(question1['females'])
Can someone explain why this is happening? what am I doing wrong?
Try removing
import matplotlib
matplotlib.use('Agg')
The command
python -c 'import matplotlib; matplotlib.use("")'
will show you the valid string arguments that can be sent to matplotlib.use.
On my machine, 'Agg' is listed as valid, though I get no output when this is set. If you are curious, you could just keep trying various options until you find one that works.
When you find the one that your prefer, you may also find it more convenient to set something like
backend : GtkAgg
in your ~/.matplotlib/matplotlibrc instead of using matplotlib.use(...).

Categories