matplotlib detect object upon mouse event - python-2.7

Is there any way to detect the matplotlib object the mouse is focused on ?
this a piece of code that illustrates what i want
self.canvas.mpl_connect("motion_notify_event", self.on_focus)
def on_focus(self, event):
# get mouse position in figure
figPos = (event.x,event.y)
# get mouse position in axes if focusing on an axes
axesPos = event.xdata, event.ydata
# get axes instance if mouse is focusing on an axes
axes = event.inaxes
# get object (any matplotlib object, Text, Box, ...) mouse is focused on
obj = event.??????
thanks

Try with Axes.hitlist:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(range(10),range(10))
def on_focus(event):
print ax.hitlist(event)
fig.canvas.mpl_connect("motion_notify_event", on_focus)
plt.show()
But if you only want highlight you can use the built-in:
fig.canvas.mpl_connect("motion_notify_event",fig.canvas.onHilite)

Related

Why does Python crash when my PyQt window becomes inactive/is resized?

While playing around with different ways to show a webcam feed (obtained using imageio/ffmpeg) in a PyQt4 window, I stumbled upon this answer. After implementing this in Python 2.7 as an ImageDisplayWidget class (as summarized below), everything seems to work just fine: A window opens, showing my webcam feed without a glitch. If I close the window, everything is stopped and closed neatly.
But... Whenever I click anywhere outside this PyQt window (while it is showing the webcam feed), causing it to lose focus, Python.exe crashes with an unhandled win32 exception. The same happens when I try to resize the window.
I am probably making some kind of exceedingly silly beginner's mistake, but I just don't see it. Can anyone point me in the right direction? Am I breaking some basic rule of (Py)Qt or even Python?
Here's a minimal example:
import sys
import numpy
from PIL import Image, ImageQt # pillow
from PyQt4 import QtGui, QtCore
class DummyVideoGrabber(QtCore.QTimer):
signal_image_available = QtCore.pyqtSignal(QtGui.QImage)
def __init__(self):
super(DummyVideoGrabber, self).__init__()
self.timeout.connect(self.update_image)
self.counter = 0
def update_image(self):
# Dummy rgb image (in reality we get a numpy array from imageio's Reader)
self.counter += 1
numpy_image = numpy.zeros(shape=(480, 640, 3), dtype=numpy.int8)
numpy_image[:, :, self.counter%3] = 255
qt_image = ImageQt.ImageQt(Image.fromarray(numpy_image, mode='RGB'))
# Emit image
self.signal_image_available.emit(qt_image)
class ImageDisplayWidget(QtGui.QWidget):
"""
Custom widget that displays an image using QPainter.
Mostly copied from: https://stackoverflow.com/a/22355028/4720018
"""
def __init__(self, size_wxh=None, parent=None):
super(ImageDisplayWidget, self).__init__(parent)
self.image = QtGui.QImage()
def set_image(self, qimage, resize_window=False):
self.image = qimage
self.repaint()
def paintEvent(self, QPaintEvent):
if not self.image:
return
painter = QtGui.QPainter(self)
painter.drawImage(self.rect(), self.image, self.image.rect())
app = QtGui.QApplication(sys.argv)
# instantiate a display object
display = ImageDisplayWidget()
display.resize(640, 480)
display.show()
# instantiate a grabber object
grabber = DummyVideoGrabber()
grabber.signal_image_available.connect(display.set_image)
grabber.start(100) # timer interval in ms
# start the event loop
app.exec_()
I found that the crash can be prevented by adding a wasActiveWindow flag (initialized to True in the constructor) and encapsulating the drawImage() call in some logic like so:
if self.isActiveWindow():
if self.wasActiveWindow:
painter.drawImage(self.rect(), self.image, self.image.rect())
self.wasActiveWindow = True
else:
self.wasActiveWindow = False
However, resizing the window still crashes python.
Problem solved by keeping a reference to the qt_image as self.qt_image:
...
# Emit image
self.qt_image = ImageQt.ImageQt(Image.fromarray(numpy_image, mode='RGB'))
self.signal_image_available.emit(self.qt_image)
...
This way it works as it should. Don't need the self.wasActiveWindow workaround anymore.
Still not sure why not keeping a reference would lead to a low-level python crash though...

Can't remove matplotlib's padding around imshow() figure

I'm embedding matplotlib into my PyQt4 GUI and I'm having a heck of a time. I can get the image to display but it adds a very thick padding around the content that I'd like to remove. Here's what I'm doing:
from PyQt4.QtCore import *
from PyQt.QtGui import *
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4Agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.image as mpImage
import matplotlib.pyplot as plt
class MatPlotLibImage(FigureCanvas):
def __init__(self):
super(MatPlotLibImage, self).__init__(self.fig)
self.axes = self.fig.add_subplot(111)
def LoadImage():
image = mpImage.imread("myImage.png")
imgplot = self.axes.imshow(image, interpolation="nearest")
# plt.axis("Off") -> Doesn't do anything as far as I can tell
imgplot.axes.set_axis_off() # Gets rid of frame
imgplot.axes.get_xaxis().set_visible(False) # Turn off x-axis
imgplot.axes.get_yaxis().set_visible(False) # Turn off y-axis
If I add this widget to a QDockWidget I get the following result:
As you can see it renders with a large white padding around the content. I cannot seem to remove this and everything I'm turning up online is focused on removing the padding when saving the image, not displaying. Does anyone know how to remove this padding at display time? Thanks in advance.
You may use subplots_adjust to get rid of the margins. I.e.
self.fig.subplots_adjust(bottom=0, top=1, left=0, right=1)
This will tell the figure not to use any margins around its child axes. You may then still get some white space to one direction, which is due to the canvas aspect ratio not being the same as the image aspect. However, I think that you don't want to change the image aspect and so this remaining margin would acutally be desired.

Graphing in Tkinter frame, updating info in Tkinter

So I am currently working on a basic stock program, and I have been able to get my graphs (of stock data from the last month) on my tkinter window any tips on how to actively update my tkinter window would be great! (FYI I am very new to programming, this is my first year, so please try to explain in basic terms!) Heres my code:
import numpy as np
import datetime as dt
import yahoo_finance as yf
import matplotlib.pyplot as plt
from Tkinter import *
import quandl
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
root=Tk()
root.geometry('1400x875')
root.title("Stock Information")
fmain=Frame(root, width=1400, height=900, bg='orange',bd=5)
fmain.place(x=100, y=0)
today=dt.date.today()
thirty_day_graph_frame=Frame(fmain, width=645, height=400,bg='green4',bd=5)
thirty_day_graph_frame.place(x=0, y=444)
thirty_days=dt.timedelta(days=43)
thirty_days_ago=today-thirty_days
five_yrs_graph_frame=Frame(fmain, width=645, height=400, bg='yellow2',bd=5)
five_yrs_graph_frame.place(x=655, y=444)
five_years=dt.timedelta(days=1825)
five_years_ago=today-five_years
def stock_info(stock_name):
stock=yf.Share(stock_name)
stock_price=stock.get_price()
name_price_label=Label(fmain, text=(stock_name,':', stock_price),font=("Times New Roman",23))
name_price_label.place(x=400, y=10)
day_high=quandl.get("WIKI/"+str(stock_name)+".2",start_date=str(today),end_date=str(today))
high_price_label=Label(fmain, text=(str(day_high)), font=("Times New Roman",20))
high_price_label.place(x=400, y=100)
thirty_day_data = quandl.get("WIKI/"+str(stock_name), start_date=str(thirty_days_ago), end_date=str(today),column_index=4) #So quandl.get gives a lot of info, so the column_index=4 is just getting closing prices
five_year_data = quandl.get("WIKI/"+str(stock_name),start_date=str(five_years_ago), end_date=str(today), column_index=4)
thirty_day_fig = plt.figure(figsize=(8,4))
plt.plot(thirty_day_data)
canvas = FigureCanvasTkAgg(thirty_day_fig, master=thirty_day_graph_frame)
plot_widget = canvas.get_tk_widget()
plot_widget.place(x=0,y=0)
five_year_fig=plt.figure(figsize=(8,4))
plt.plot(five_year_data)
canvas1=FigureCanvasTkAgg(five_year_fig, master=five_yrs_graph_frame)
plot_widget1=canvas1.get_tk_widget()
plot_widget1.place(x=1,y=0)
root.after(5000, stock_info, stock_name)
apple_button=Button(root,text='AAPL', command=lambda:stock_info('AAPL'))
tesla_button=Button(root,text='TSLA', command=lambda:stock_info('TSLA'))
google_button=Button(root,text='GOOG', command=lambda:stock_info('GOOG'))
apple_button.place(x=10, y=15)
tesla_button.place(x=10, y=45)
google_button.place(x=10,y=75)
root.mainloop()
The reason your graphs are plotted from the start is because of the way you assign commands to your buttons. One way to fix this is to assign the command as a lambda expression:
apple_button = Button(root, text='AAPL', command=lambda:stock_info('AAPL'))
To let your GUI update itself over time, you can create a loop using the root.after() method:
# Define the figure and canvas outside the loop
fig = plt.Figure()
a = fig.add_subplot(111)
canvas = FigureCanvasTkAgg(fig, master=f1)
canvas.get_tk_widget().grid()
def stock_info(stock_name):
# Get stock data and plot it on the GUI
...
a.cla()
a.plot(data)
canvas.draw()
# Schedule the function to call itself again after 5 seconds
root.after(5000, stock_info, stock_name)

How to update the legend from matplotlib toolbar

I am defining a matplotlib plot for a given data. once the plot is displayed, I am trying to change some line property using navigation tool bar edit option.
When I make change say example solid line to dashdotted, the update get reflected on the lines, but the legends are not updated.
How can I capture this event when the apply button is clicked, so i can use this to refresh the legend. At the moment I am capturing a pick_event as a signal to refresh the legends.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Qt4Agg')
x=np.linspace(0,100,100)
y=np.linspace(100,200,100)
plt.plot(x,y,label='test')
plt.legend()
ax.legend()
plt.show()
#optional code
def on_press(event):
lines, labels = ax.get_legend_handles_labels()
ax.legend(lines, labels, loc=0)
fig.canvas.draw()
cid = fig.canvas.mpl_connect('pick_event', on_press)
After a bit of struggle the only easy way to resolve this solution is to add legend refresh as part of the navigation tool bar call back function
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
def home_callback():
print "home called"
ax.legend()
x=np.linspace(0,100,100)
y=np.linspace(100,200,100)
fig=plt.figure()
ax=fig.add_subplot(111)
ax.plot(x,y,label='test')
ax.legend()
plt.show()
fm = plt.get_current_fig_manager()
fm.toolbar.actions()[0].triggered.connect(home_callback)

Save an animation at the final step as a figure

I wrote a script which animates the results I obtained, using matplotlib.
Besides the animation I got, I wanted to save the figure at the final step of the animation; just before the animation is repeated. I defined a save-flag, to avoid the figure being saved over and over. You can see the simplified version of my code below:
#!/usr/bin/env python
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.animation as animation
x = np.array(range(12))
y = np.array([i**2 for i in range(12)])
fig = plt.figure()
ax = plt.axes(xlim = (0,15), ylim = (0,150))
line, = ax.plot([],[], 'o-')
def init():
line.set_data([], [])
return line,
save_flag = False
def animate(i):
i = (i+1)%(len(x)+1)
line.set_data(x[0:i], y[0:i])
global save_flag
if (save_flag == False) and (i == (len(x)-1)):
print "\nThe figure is being saved!\n"
fig.savefig("foo" + ".png")
save_flag = True
return line,
ani = animation.FuncAnimation(fig, animate, repeat=True, blit=True, init_func=init)
plt.show()
If you run the script, you will probably see that, at the end of the first loop, the the animation becomes erroneous. This error is due to the blit, which is set True. However, if it is set to False, then the figure repeats itself as it should be.
Why is there such a problem; could it be a bug? (My Python version is 2.7.5+.)
Is there a better way to save a figure at the end of an animation?