Reputation: 384
I'm getting a mysterious error when I try to create a Matplotlib button.
I have a class which has a number of Matplotlib axes as instance attributes. I am instantiating the button with the following call:
Button(self.energy_diagram, 'Show Attractors')
And I get the following error:
Traceback (most recent call last):
File "ocr.py", line 20, in <module>
myNet.run_visualization(training_data, learning_data)
File "/home/daniel/Documents/coding/visuals.py", line 93, in run_visualization
self._plot_energy()
File "/home/daniel/Documents/coding/visuals.py", line 235, in _plot_energy
Button(self.energy_diagram, 'Show Attractors')
File "/usr/local/lib/python3.4/dist-packages/matplotlib/widgets.py", line 191, in __init__
transform=ax.transAxes)
TypeError: text() missing 1 required positional argument: 's'
Interestingly, the Button works if I add it to other axes within my figure, and the self.energy_diagram
axis is the only one that is 3d, so I'm wondering if that has something to do with it.
Any help would be much appreciated!
Upvotes: 0
Views: 162
Reputation: 35080
First, your error message. It's surprising, but fairly clear. Your note about the problematic axes being the only 3d one is the key. Two minimal examples with and without the error:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.widgets as widgets
hf1,ha1 = plt.subplots()
ha1.plot([1,2,3],[4,5,6])
butt1 = widgets.Button(ha1,'button 1') # <-- works great
hf2 = plt.figure()
ha2 = hf2.add_subplot(111,projection='3d')
ha2.plot([1,2,3],[4,5,6],[7,8,9])
butt2 = widgets.Button(ha2,'button 2') # <-- error
First, a look at /usr/local/lib/python3.4/dist-packages/matplotlibwidgets.py, around line 191:
class Button(AxesWidget):
def __init__(self, ax, label, image=None,
color='0.85', hovercolor='0.95'):
AxesWidget.__init__(self, ax)
if image is not None:
ax.imshow(image)
self.label = ax.text(0.5, 0.5, label,
verticalalignment='center',
horizontalalignment='center',
transform=ax.transAxes) # <-- line 191
The button attempts to call the text
method of the Axes
it's being put into, and this call is producing the error. With the help
of ha1.text
(bound version of matplotlib.pyplot.Axes.text
):
text(x, y, s, fontdict=None, withdash=False, **kwargs) method of matplotlib.axes._subplots.AxesSubplot instance
Add text to the axes.
Add text in string `s` to axis at location `x`, `y`, data
coordinates.
The same for ha2.text
(bound version of mpl_toolkits.mplot3d.Axes3D.text
):
text(x, y, z, s, zdir=None, **kwargs) method of matplotlib.axes._subplots.Axes3DSubplot instance
Add text to the plot. kwargs will be passed on to Axes.text,
except for the `zdir` keyword, which sets the direction to be
used as the z direction.
Spot the difference: the latter function has to receive a z
coordinate as well in order to place the text on the 3d axes. Makes sense. The Button
widget is simply not designed to work with 3d axes.
Now, you could try hacking yourself around the problem, although the fact that Button
is clearly lacking 3d axes support suggests that you will sooner or later shoot yourself in the foot. Anyway, you can actually get rid of the error by overwriting the text
method of ha2
with that of ha1
with some adjustments to put self
in the right place. Again, I'm not saying that this can't break anything anytime, nor that it's something horrible to do, but it's an option:
hf2 = plt.figure()
ha2 = hf2.add_subplot(111,projection='3d')
ha2.plot([1,2,3],[4,5,6],[7,8,9])
ha2.text = lambda x,y,s,self=ha2,**kwargs : plt.Axes.text(self,x,y,s,kwargs)
butt2 = widgets.Button(ha2,'button 2') # <-- no error
For what it's worth, it now looks as bad as the 2d version:
This leads me to my final point. As far as I can tell, widgets automatically occupy the entire axes they're put into. And it seems reasonable to not put a widget (an inherently 2d object) into a 3d axes: how exactly do you want it to be positioned? The obvious solution would be to store each widget in its own axes, on top of/next to the other GUI components and figures. This way you can naturally use 2d axes for each widget. I believe this is the standard approach for doing this, which might explain why there's no explicit mention of 3d axes not being supported. Why would they be?
Upvotes: 1