hhprogram
hhprogram

Reputation: 3137

Matplotlib Checkbuttons in a row

Couldn't find anything on how to format the checkbuttons 'box' in matplotlib into a row vs in a column format. I found out I could move the actual check boxes and the labels (using get_position on the Text objects and set_xy on the rectangle objects) but that doesn't seem like the best way as I'm moving them separately and they don't seem to be using the same coordinate system.

Is there a simpler way to turn my checkbuttons from a column into a row?

Upvotes: 2

Views: 2175

Answers (1)

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339250

There is no simple way to make the checkboxes appear in a row instead of a column. The reason is that they are hardcoded in terms of axes coordinates. Of course you can move the bits and pieces around, but that sounds rather cumbersome.

A different option would be to subclass the CheckButtons class and implement your own layout. Now when doing so, a lot needs to be taken into account, like how much space there is between labels and checkboxes etc.

On the other hand, there is actually a nice matplotlib item, that would have all the knobs to turn concerning labelspacing, padding etc available and that works with rows and columns: the legend.

Now I was thinking why not produce the checkboxes as a clickable legend. This is what the code below does. It subclasses CheckButtons and creates a legend into its axes. This legend has the boxes as handles and the labels as legend labels. The good thing is now that you are able to layout the checkbuttons to your liking with all the tools the usual legend allows for: fontsize, markerfirst, frameon, fancybox, shadow, framealpha, facecolor, edgecolor, mode, bbox_transform, title, borderpad, labelspacing, handlelength, handletextpad, borderaxespad, columnspacing.

The most important one here is sure n_col, which you can set to the number of columns you would like.

I took the CheckButtons example from the matplotlib page and used this custom Checkbuttons class on it.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import CheckButtons,AxesWidget

class PremiumCheckButtons(CheckButtons,AxesWidget):
    def __init__(self, ax, labels, actives, linecolor="k", showedge=True, **kw):
        AxesWidget.__init__(self, ax)

        ax.set_xticks([])
        ax.set_yticks([])
        ax.set_navigate(False)
        if not showedge:
            ax.axis("off")
        linekw = {'solid_capstyle': 'butt', "color" : linecolor}
        class Handler(object):
            def legend_artist(self, legend, orig_handle, fontsize, handlebox):
                x0, y0 = handlebox.xdescent, handlebox.ydescent
                height = handlebox.height
                self.line1 = plt.Line2D([x0,x0+height],[y0,y0+height], **linekw)
                self.line2 = plt.Line2D([x0,x0+height],[y0+height,y0], **linekw)
                self.rect = plt.Rectangle((x0,y0),height, height, 
                                          edgecolor="k", fill=False)
                handlebox.add_artist(self.rect)
                handlebox.add_artist(self.line1)
                handlebox.add_artist(self.line2)
                return [self.line1, self.line2, self.rect]

        self.box = ax.legend(handles = [object() for i in labels ], 
                             labels = labels,
                             handler_map={object: Handler()}, **kw)

        self.lines = [(h[0],h[1]) for h in self.box.legendHandles]
        self.rectangles = [h[2] for h in self.box.legendHandles]
        self.labels = self.box.texts

        for i,(l1,l2) in enumerate(self.lines):
            l1.set_visible(actives[i])
            l2.set_visible(actives[i])

        self.connect_event('button_press_event', self._clicked)

        self.cnt = 0
        self.observers = {}


t = np.arange(0.0, 2.0, 0.01)
s0 = np.sin(2*np.pi*t)
s1 = np.sin(4*np.pi*t)
s2 = np.sin(6*np.pi*t)

fig, (rax,ax) = plt.subplots(nrows=2, gridspec_kw=dict(height_ratios = [0.1,1]) )
l0, = ax.plot(t, s0, visible=False, lw=2)
l1, = ax.plot(t, s1, lw=2)
l2, = ax.plot(t, s2, lw=2)
plt.subplots_adjust(left=0.2)

check = PremiumCheckButtons(rax, ('2 Hz', '4 Hz', '6 Hz'), (False, True, True),
                           showedge = False, ncol=3)


def func(label):
    if label == '2 Hz':
        l0.set_visible(not l0.get_visible())
    elif label == '4 Hz':
        l1.set_visible(not l1.get_visible())
    elif label == '6 Hz':
        l2.set_visible(not l2.get_visible())
    fig.canvas.draw_idle()
check.on_clicked(func)

plt.show()

enter image description here

In addition to all the legend arguments, the above PremiumCheckButtons class takes the arguments linecolor to set the color of the x of the checkbox (defaults to black) and showedge. showedge can be used to show the frame of the axes in which the "legend" resides and can be turned on to see this axes for debugging purposes. E.g. you need to make sure the complete legend actually resides inside the axes for the buttons to be clickable.

Upvotes: 5

Related Questions