Danny
Danny

Reputation: 384

TypeError from matplotlib Button instantiation

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

Answers (1)

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:

2d version bad bad ugly 3d 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

Related Questions