I wanted to make it possible to show values when pressing a dot in my scatterplot. The solution was found here: Possible to make labels appear when hovering over a point in matplotlib?
Solution:
from matplotlib.pyplot import figure, show
import numpy as npy
from numpy.random import rand
# picking on a scatter plot (matplotlib.collections.RegularPolyCollection)
x, y, c, s = rand(4, 100)
def onpick3(event):
ind = event.ind
print 'onpick3 scatter:', ind, npy.take(x, ind), npy.take(y, ind)
fig = figure()
ax1 = fig.add_subplot(111)
col = ax1.scatter(x, y, 100*s, c, picker=True)
#fig.savefig('pscoll.eps')
fig.canvas.mpl_connect('pick_event', onpick3)
show()
And it solved my problem. But I don't understand how, I've been googling around without any luck. I know how to plot with matplotlib, so that's not where my knowledge is lacking.
One thing I don't understand is the onpick3(event) function. What is this event parameter? Because the function itself is called upon further down without any given arguments: fig.canvas.mpl_connect('pick_event', onpick).
mpl_connect connects a signal to a slot. The slot is in this case onpick3.
Note that the slot is not called, i.e. the syntax is fig.canvas.mpl_connect('pick_event', onpick3) and not fig.canvas.mpl_connect('pick_event', onpick3())
It will only be called once the signal is triggered (mouse clicked on canvas). At this point the underlying event is provided as an argument in the function call.
You'll see that once you try to define the slot without argument. This would cause an error like onpick3 expects 0 arguments but got 1 or so.
You'll find details on the matplotlib event handling page.
The event itself is an instance of matplotlib.backend_bases.PickEvent. The .ind attribute is not well documented, but that is mainly because not all artists actually register this attribute to the event.
Related
Matplotlib offers these functions:
cla() # Clear axis
clf() # Clear figure
close() # Close a figure window
When should I use each function and what exactly does it do?
They all do different things, since matplotlib uses a hierarchical order in which a figure window contains a figure which may consist of many axes. Additionally, there are functions from the pyplot interface and there are methods on the Figure class. I will discuss both cases below.
pyplot interface
pyplot is a module that collects a couple of functions that allow matplotlib to be used in a functional manner. I here assume that pyplot has been imported as import matplotlib.pyplot as plt.
In this case, there are three different commands that remove stuff:
See matplotlib.pyplot Functions:
plt.cla() clears an axis, i.e. the currently active axis in the current figure. It leaves the other axes untouched.
plt.clf() clears the entire current figure with all its axes, but leaves the window opened, such that it may be reused for other plots.
plt.close() closes a window, which will be the current window, if not specified otherwise.
Which functions suits you best depends thus on your use-case.
The close() function furthermore allows one to specify which window should be closed. The argument can either be a number or name given to a window when it was created using figure(number_or_name) or it can be a figure instance fig obtained, i.e., usingfig = figure(). If no argument is given to close(), the currently active window will be closed. Furthermore, there is the syntax close('all'), which closes all figures.
methods of the Figure class
Additionally, the Figure class provides methods for clearing figures.
I'll assume in the following that fig is an instance of a Figure:
fig.clf() clears the entire figure. This call is equivalent to plt.clf() only if fig is the current figure.
fig.clear() is a synonym for fig.clf()
Note that even del fig will not close the associated figure window. As far as I know the only way to close a figure window is using plt.close(fig) as described above.
There is just a caveat that I discovered today.
If you have a function that is calling a plot a lot of times you better use plt.close(fig) instead of fig.clf() somehow the first does not accumulate in memory. In short if memory is a concern use plt.close(fig) (Although it seems that there are better ways, go to the end of this comment for relevant links).
So the the following script will produce an empty list:
for i in range(5):
fig = plot_figure()
plt.close(fig)
# This returns a list with all figure numbers available
print(plt.get_fignums())
Whereas this one will produce a list with five figures on it.
for i in range(5):
fig = plot_figure()
fig.clf()
# This returns a list with all figure numbers available
print(plt.get_fignums())
From the documentation above is not clear to me what is the difference between closing a figure and closing a window. Maybe that will clarify.
If you want to try a complete script there you have:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(1000)
y = np.sin(x)
for i in range(5):
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(x, y)
plt.close(fig)
print(plt.get_fignums())
for i in range(5):
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(x, y)
fig.clf()
print(plt.get_fignums())
If memory is a concern somebody already posted a work-around in SO see:
Create a figure that is reference counted
plt.cla() means clear current axis
plt.clf() means clear current figure
also, there's plt.gca() (get current axis) and plt.gcf() (get current figure)
Read more here: Matplotlib, Pyplot, Pylab etc: What's the difference between these and when to use each?
I register 2 callbacks - 1 for clicking and 1 for picking.
import matplotlib.pyplot as plt
fig = plt.figure()
fig.canvas.mpl_connect('button_release_event', onclick)
fig.canvas.mpl_connect('pick_event', onpick)
...
def onclick(event): ...
def onpick(event): ...
I wanted to know in my click event whether or not it occurs with a pick event, (knowing that clicking on a pickable object triggers both events). I noticed that the pick event always comes first, so I check / set a variable like clicked_point = True # or False in onpick and onclick.
This works fine so far but seems sketchy. I couldn't find an answer in the docs, but perhaps I have simply have not found the right section yet.
My alternative would be to only register 'button_release_event', and calculate Euclidean distances between click coordinates and data points on my scatter plot to determine if the 'pick_event' behavior should be evoked. I'd rather rely on a call order for these functions registered with mpl_connect if it does indeed exist.
I've come across code that checks if an event is in a set of artists (ax.collections.contains(event) or something similar). However, since I use a single call to scatter with the full array of data points (plt.scatter(X, Y, ..)), I am returned a single PathCollection object, which I understand represents a path for all the points in the order they came in. However I could never quite figure out to get indivual artists from this collection, so when I check for event containment in [PathCollection], it always returns false. Not sure if something like this was even doable or if it makes sense, but I figured I'd document it here in case it was relevant.
I am new to Python, and somewhat new to object oriented programming. Can anyone explain what is going on and how things are typically done with a matplotlib GUI callback? I've taken the "event_handling example code" from the Matplotlib website and stripped it down for clarity. When you run this code it makes a plot, and if you press a key on the keyboard the press function is called. The press function is passed only event, but somehow every other variable from main program level appears inside the call to press but as a global variable, is this normal for functions? I can print the value of x, but if I try to change it then it makes a local variable version, worse yet now I have seemingly no way to access the global version anymore?
#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
x=np.random.rand(3)
y=np.random.rand(3)
def press(event):
print(x)
print('Local Var:', locals().keys())
print('Global Var:', globals().keys())
fig, ax = plt.subplots()
fig.canvas.mpl_connect('key_press_event', press)
ax.plot(x,y)
plt.show()
I have searched and had quite a hard time finding any reference that explains how to access or properly pass useful data in and out of the callback function so that a GUI event can do something useful, like update some data or feature of a plot?
So lets say I wanted to have the callback function modify y and re-plot the data. How is that typically done?
you have global access to x inside your callback, but can't modify it unless you specify it global.
def press(event):
global x
...
locals().keys() and globals().keys() are printing namespaces; I am unsure why you need to do that.
Your callback receives an event that you can use and manipulate inside the function.
Here is an example:
#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
x=np.random.rand(3)
y=np.random.rand(3)
def press(event):
print(event, event.key)
fig, ax = plt.subplots()
fig.canvas.mpl_connect('key_press_event', press)
ax.plot(x,y)
plt.show()
click on the plot window to set the focus.
pressing f should print the event object and set the plot full screen
pressing f again, will print f and restore the size of the window
pressing s will print s and will offer to save your figure
etc...
To learn more about how you can manipulate events, look up backend_bases in the very rich matplotlib web site. For example, you can set mouse_clicks events that allow you to capture canvas coordinates to add points or modify figures...
I'm implementing an image viewer using matplotlib. The idea is that changes being made to the image (such as filter application) will update automatically.
I create a Figure to show the inital image and have added a button using pyQt to update the data. The data does change, I have checked, but the Figure does not. However, if after I've pressed the filter application button, I move the image using matplotlib's standard tool bar, the image is then updated.
I assume I'm doing something wrong when updating the image, but since the fact of moving it actually forces the update, it then shows the data change. I would like for this to happen when I press the button, though.
Below is some of the code. This is the initial figure initialization, which shows the original image:
self.observableFig = Figure((4.0, 4.0), dpi=100)
self.canvas = FigureCanvas(self.observableFig)
self.canvas.setParent(self.observableWindow)
self.canvas.setFocusPolicy(Qt.StrongFocus)
self.canvas.setFocus()
self.canvas.mpl_connect('button_press_event', self.on_click)
# Showing initial data on Window
self.observableFig.clear()
self.observableAxes = self.observableFig.add_subplot(1, 1, 1)
min, max = self.min, self.max
self.observableAxes.imshow(
self.data,
vmin=min,
vmax=max,
origin='lower'
)
And this is the event for when the button that changes the data is pressed:
self.observableAxes.imshow(self.data/2, origin='lower')
# plt.clf()
# plt.draw()
# plt.show()
I have tried draw(), show(), basically anything I've found on pyplot about this. I have also tried both with and without plt.ion() at the beginning, but it hasn't made a difference in this.
Thanks in advance.
The reason that nothing is updating is that you're trying to use pyplot methods for a figure that's not a part of the pyplot state machine. plt.draw() won't draw this figure, as plt doesn't know the figure exists.
Use fig.canvas.draw() instead.
Regardless, it's better to use fig.canvas.draw() that plt.draw(), as it's clear which figure you're drawing (the former draws one, the latter draws all, but only if they're tracked by pyplot).
Try something along these lines:
import numpy as np
import matplotlib.pyplot as plt
data = np.random.random((10,10))
# To make a standalone example, I'm skipping initializing the
# `Figure` and `FigureCanvas` and using `plt.figure()` instead...
# `plt.draw()` would work for this figure, but the rest is identical.
fig, ax = plt.subplots()
ax.set(title='Click to update the data')
im = ax.imshow(data)
def update(event):
im.set_data(np.random.random((10,10)))
fig.canvas.draw()
fig.canvas.mpl_connect('button_press_event', update)
plt.show()
Is there some way that I can get matplotlib to block code execution pending a matplotlib.backend_bases.Event?
I've been working on some classes for interactively drawing lines and polygons inside matplotlib figures, following these examples. What I'd really like to do is block execution until I'm done editing my polygon, then get the final positions of the vertices - if you're familiar with MATLAB, I'm basically trying to replicate the position = wait(roihandle) syntax, for example here.
I suppose I could set some class attribute of my interactive polygon object when a keypress occurs, then repeatedly poll the object in my script to see if the event has occurred yet, but I was hoping there would be a nicer way.
Well, that was easier than I thought it would be! For those who are interested I found a solution using figure.canvas.start_event_loop() and figure.canvas.stop_event_loop().
Here's a simple example:
from matplotlib import pyplot as plt
class FigEventLoopDemo(object):
def __init__(self):
self.fig, self.ax = plt.subplots(1, 1, num='Event loop demo')
self.clickme = self.ax.text(0.5, 0.5, 'click me',
ha='center', va='center',
color='r', fontsize=20, picker=10)
# add a callback that triggers when the text is clicked
self.cid = self.fig.canvas.mpl_connect('pick_event', self.on_pick)
# start a blocking event loop
print("entering a blocking loop")
self.fig.canvas.start_event_loop(timeout=-1)
def on_pick(self, event):
if event.artist is self.clickme:
# exit the blocking event loop
self.fig.canvas.stop_event_loop()
print("now we're unblocked")