在 matplotlib 中水平显示单选按钮

Posted

技术标签:

【中文标题】在 matplotlib 中水平显示单选按钮【英文标题】:Displaying Radio buttons horizontally in matplotlib 【发布时间】:2019-08-01 08:51:14 【问题描述】:

我正在使用matplotlib.widgets 在我的小部件中创建单选按钮,即将出现的按钮垂直堆叠,我希望它们水平堆叠。

MVCE:

import matplotlib.pyplot as plt
from matplotlib.widgets import RadioButtons
plt.subplots_adjust(left=0.2)
rax = plt.axes([0.5,0.05,0.1,0.1])
radio =  RadioButtons(rax ,['1','2','3'], active=0, activecolor='blue' )
plt.show()

正如您在此示例中看到的那样,您可以获得这样的单选按钮 ,

我想知道有没有办法水平堆叠这些单选按钮。

【问题讨论】:

我找不到使用matplotlib.widgets RadioButtons 的方法,但是,直接使用tkinter 很容易,例如。见here 或here @Jack 谢谢,实际上 matplotlib 中有很多工作很容易,这就是我坚持使用它的原因,现在我已经切换到滑块而不是单选按钮,但仍然是单选按钮会是更好的选择。 是的,matplotlib-only 解决这个问题会很好。希望比我更有知识的人能来为你解决这个问题! 【参考方案1】:

目前正在尝试在PR #13374 中向RadioButtons 引入orientation 参数;这还没有最终确定。

正如我在此 PR 中所评论的,另一种选择是对按钮使用散点图。下面展示了我如何想象这个实现。与通常的按钮相比,有两个主要增强功能:

单选按钮始终为圆形,与轴的大小无关。 它们可以任意对齐,尤其是水平对齐。

这是通过在内部创建一个图例来实现的,该图例具有所有必需的选项。 Legend 的任何有效参数也可用于按钮。

import matplotlib.pyplot as plt
from matplotlib.widgets import AxesWidget, RadioButtons

class MyRadioButtons(RadioButtons):

    def __init__(self, ax, labels, active=0, activecolor='blue', size=49,
                 orientation="vertical", **kwargs):
        """
        Add radio buttons to an `~.axes.Axes`.
        Parameters
        ----------
        ax : `~matplotlib.axes.Axes`
            The axes to add the buttons to.
        labels : list of str
            The button labels.
        active : int
            The index of the initially selected button.
        activecolor : color
            The color of the selected button.
        size : float
            Size of the radio buttons
        orientation : str
            The orientation of the buttons: 'vertical' (default), or 'horizontal'.
        Further parameters are passed on to `Legend`.
        """
        AxesWidget.__init__(self, ax)
        self.activecolor = activecolor
        axcolor = ax.get_facecolor()
        self.value_selected = None

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

        circles = []
        for i, label in enumerate(labels):
            if i == active:
                self.value_selected = label
                facecolor = activecolor
            else:
                facecolor = axcolor
            p = ax.scatter([],[], s=size, marker="o", edgecolor='black',
                           facecolor=facecolor)
            circles.append(p)
        if orientation == "horizontal":
            kwargs.update(ncol=len(labels), mode="expand")
        kwargs.setdefault("frameon", False)    
        self.box = ax.legend(circles, labels, loc="center", **kwargs)
        self.labels = self.box.texts
        self.circles = self.box.legendHandles
        for c in self.circles:
            c.set_picker(5)
        self.cnt = 0
        self.observers = 

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


    def _clicked(self, event):
        if (self.ignore(event) or event.mouseevent.button != 1 or
            event.mouseevent.inaxes != self.ax):
            return
        if event.artist in self.circles:
            self.set_active(self.circles.index(event.artist))

把它当作

plt.subplots_adjust(left=0.2)
rax = plt.axes([0.5,0.05,0.4,0.07])
radio =  MyRadioButtons(rax ,['1','2','3'], active=0, activecolor='crimson',
                        orientation="horizontal")

plt.show()

或者

rax = plt.axes([0.2,0.5,0.25,0.1])
radio =  MyRadioButtons(rax ,["AA", "BB", "CC", "DD"], ncol=2)

【讨论】:

知道如何将图例放在圆圈下方吗?一直在 ax.legend 玩 bbox_to_anchor,但到目前为止没有成功。将圆圈彼此靠近如何? @Trmotta 圆圈的接近程度首先取决于此框使用的plt.axes 的范围,如果将其变小,则它们之间的距离更近。也可以使用ax.legend() 的任何其他参数,请参阅legend 文档。由于图例不允许在手柄下方放置标签,因此此解决方案对此不是很有用;在这种情况下,不使用图例会更好。实际上,您可以参考链接的 PR 以获取可能的替代选项,其中带有圆圈下方的标签。 @ImportanceOfBeingErnest 我不断收到lib/python3.9/site-packages/matplotlib/_api/deprecation.py", line 218, in __set__ return super().__set__(instance, value) AttributeError: can't set attribute 很棒的答案!我将它移植到 matplotlib >3.3(修复了上面的错误)【参考方案2】:

由于 stackexchange 不希望将其作为编辑, 这是来自@ImportanceOfBeingErnest 的上述答案的更新版本,适用于最近的 matplotlib 版本(例如 >= v3.3)

import matplotlib.pyplot as plt
from matplotlib.widgets import AxesWidget, RadioButtons
from matplotlib import cbook

class MyRadioButtons(RadioButtons):

    def __init__(self, ax, labels, active=0, activecolor='blue', size=49,
                 orientation="vertical", **kwargs):
        """
        Add radio buttons to an `~.axes.Axes`.
        Parameters
        ----------
        ax : `~matplotlib.axes.Axes`
            The axes to add the buttons to.
        labels : list of str
            The button labels.
        active : int
            The index of the initially selected button.
        activecolor : color
            The color of the selected button.
        size : float
            Size of the radio buttons
        orientation : str
            The orientation of the buttons: 'vertical' (default), or 'horizontal'.
        Further parameters are passed on to `Legend`.
        """
        AxesWidget.__init__(self, ax)
        self.activecolor = activecolor
        axcolor = ax.get_facecolor()
        self.value_selected = None

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

        circles = []
        for i, label in enumerate(labels):
            if i == active:
                self.value_selected = label
                facecolor = activecolor
            else:
                facecolor = axcolor
            p = ax.scatter([],[], s=size, marker="o", edgecolor='black',
                           facecolor=facecolor)
            circles.append(p)
        if orientation == "horizontal":
            kwargs.update(ncol=len(labels), mode="expand")
        kwargs.setdefault("frameon", False)    
        self.box = ax.legend(circles, labels, loc="center", **kwargs)
        self.labels = self.box.texts
        self.circles = self.box.legendHandles
        for c in self.circles:
            c.set_picker(5)
        
        self._observers = cbook.CallbackRegistry()
        
        self.connect_event('pick_event', self._clicked)


    def _clicked(self, event):
        if (self.ignore(event) or event.mouseevent.button != 1 or
            event.mouseevent.inaxes != self.ax):
            return
        if event.artist in self.circles:
            self.set_active(self.circles.index(event.artist))

把它当做

plt.subplots_adjust(left=0.2)
rax = plt.axes([0.5,0.05,0.4,0.07])
radio =  MyRadioButtons(rax ,['1','2','3'], active=0, activecolor='crimson',
                        orientation="horizontal")

plt.show()

【讨论】:

以上是关于在 matplotlib 中水平显示单选按钮的主要内容,如果未能解决你的问题,请参考以下文章

如何在角度材料中水平对齐单选按钮?

如何让输入单选元素在反应 [material-ui] 中水平对齐?

Axure:切换单选按扭,控制界面显示不同内容

C#单选按钮测验[重复]

Python Tkinter 中的单选按钮值

matplotlib中水平条形图的注释[重复]