Repository: matplotlib/matplotlib
File: lib/matplotlib/widgets.py (Python)

Code before:
    props = cbook.normalize_kwargs(props, mtext.Text)
    return cycler(**props)() if props else itertools.repeat({})


class CheckButtons(AxesWidget):
    r"""
    A GUI neutral set of check buttons.

    For the check buttons to remain responsive you must keep a
    reference to this object.

    Connect to the CheckButtons with the `.on_clicked` method.

    Attributes
    ----------
    ax : `~matplotlib.axes.Axes`
        The parent Axes for the widget.
    labels : list of `~matplotlib.text.Text`
        The text label objects of the check buttons.
    """

    def __init__(self, ax, labels, actives=None, *, useblit=True,
                 label_props=None, frame_props=None, check_props=None):
        """
        Add check buttons to `~.axes.Axes` instance *ax*.

        Parameters
        ----------
        ax : `~matplotlib.axes.Axes`
            The parent Axes for the widget.
        labels : list of str
            The labels of the check buttons.
        actives : list of bool, optional
            The initial check states of the buttons. The list must have the
            same length as *labels*. If not given, all buttons are unchecked.
        useblit : bool, default: True
            Use blitting for faster drawing if supported by the backend.
            See the tutorial :ref:`blitting` for details.

            .. versionadded:: 3.7

        label_props : dict of lists, optional
            Dictionary of `.Text` properties to be used for the labels. Each
            dictionary value should be a list of at least a single element. If
            the list is of length M, its values are cycled such that the Nth
            label gets the (N mod M) property.

            .. versionadded:: 3.7
        frame_props : dict, optional
            Dictionary of scatter `.Collection` properties to be used for the
            check button frame. Defaults (label font size / 2)**2 size, black

Review comment (suggestion):
(Answering in a code review in order to explain better)

> I assume that this is just the move things phase and code changes will happen in the follow up? Also thanks for doing this!

Correct. In the next PR the `_Buttons` class will also gain a `self._buttons_xs` attribute, which will be used when creating the `_buttons` in `_init_props`. As for `self.labels`, they will be created with the algorithm written in the previous PR, but with `mtext.Text` instanced once etc.

Code after:
    props = cbook.normalize_kwargs(props, mtext.Text)
    return cycler(**props)() if props else itertools.repeat({})


class _Buttons(AxesWidget):
    """
    The base class for `CheckButtons` and `RadioButtons`.

    This class provides common functionality for button widgets,
    such as handling click events, managing button labels, and connecting callbacks.

    The class itself is private and may be changed or removed without prior warning.
    However, the public API it provides to subclasses is stable and considered
    public on the subclasses.
    """

    def __init__(self, ax, labels, *, useblit=True, label_props=None, **kwargs):
        super().__init__(ax)

        ax.set_xticks([])
        ax.set_yticks([])
        ax.set_navigate(False)

        self._useblit = useblit

        self._buttons_ys = np.linspace(1, 0, len(labels)+2)[1:-1]

        label_props = _expand_text_props(label_props)

        self.labels = [
            ax.text(0.25, y, label, transform=ax.transAxes,
                    horizontalalignment="left", verticalalignment="center",
                    **props)
            for y, label, props in zip(self._buttons_ys, labels, label_props)]
        text_size = np.array([text.get_fontsize() for text in self.labels]) / 2

        self._init_props(text_size, **kwargs)

        self.connect_event('button_press_event', self._clicked)
        if self._useblit:
            self.connect_event('draw_event', self._clear)

        self._observers = cbook.CallbackRegistry(signals=["clicked"])

    def _init_props(self, text_size, **kwargs):
        raise NotImplementedError("This method should be defined in subclasses")

    def _clear(self, event):
        """Internal event handler to clear the buttons."""
        if self.ignore(event) or self.canvas.is_saving():
            return