我能使matplotlib滑块更加离散化吗?

14

我正在使用 matplotlib 滑块,类似于此演示。目前滑块使用 2 位小数并且“感觉”非常连续(尽管它们在某种程度上必须是离散的)。我能决定它们以哪种级别离散吗?整数步长?0.1 大小的步长?0.5?我的谷歌搜索无果。


1
我可以使用valfmt ='%1.0f'将显示更改为整数,但它不会改变输出。我目前正在寻找在某个地方引入一些舍入,但似乎这应该是滑块的属性。 - J Knight
2
我很快意识到我可以在滑块更新函数内部对输入进行round(),并且只显示正确的小数位数。但是如果我想四舍五入到1/2增量,则无法使显示匹配该值(仍将精确显示为0.1)。 - J Knight
每次只需要设置显示值即可。只要您不介意它不像离散值那样“感觉”(例如,进度条仍将平滑地进行),您仍然可以使显示反映出离散值。(稍后添加一个示例...) - Joe Kington
对于离散滑块,也可以参考这个答案 - ImportanceOfBeingErnest
2个回答

33

如果你只需要整数值,创建滑块时只需传入适当的valfmt(例如,valfmt ='%0.0f')即可。

但是,如果你想要非整数间隔,你需要每次手动设置文本值。即使你这样做了,滑块仍然会平稳地进展,而不会“感觉”像离散间隔。

以下是一个例子:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Slider

class ChangingPlot(object):
    def __init__(self):
        self.inc = 0.5

        self.fig, self.ax = plt.subplots()
        self.sliderax = self.fig.add_axes([0.2, 0.02, 0.6, 0.03],
                                          axisbg='yellow')

        self.slider = Slider(self.sliderax, 'Value', 0, 10, valinit=self.inc)
        self.slider.on_changed(self.update)
        self.slider.drawon = False

        x = np.arange(0, 10.5, self.inc)
        self.ax.plot(x, x, 'ro')
        self.dot, = self.ax.plot(self.inc, self.inc, 'bo', markersize=18)

    def update(self, value):
        value = int(value / self.inc) * self.inc
        self.dot.set_data([[value],[value]])
        self.slider.valtext.set_text('{}'.format(value))
        self.fig.canvas.draw()

    def show(self):
        plt.show()

p = ChangingPlot()
p.show()

如果您想让滑块完全感觉像离散值,您可以创建matplotlib.widgets.Slider的子类。关键效果由Slider.set_val控制。
在这种情况下,您可以像这样操作:
class DiscreteSlider(Slider):
    """A matplotlib slider widget with discrete steps."""
    def __init__(self, *args, **kwargs):
        """Identical to Slider.__init__, except for the "increment" kwarg.
        "increment" specifies the step size that the slider will be discritized
        to."""
        self.inc = kwargs.pop('increment', 0.5)
        Slider.__init__(self, *args, **kwargs)

    def set_val(self, val):
        discrete_val = int(val / self.inc) * self.inc
        # We can't just call Slider.set_val(self, discrete_val), because this 
        # will prevent the slider from updating properly (it will get stuck at
        # the first step and not "slide"). Instead, we'll keep track of the
        # the continuous value as self.val and pass in the discrete value to
        # everything else.
        xy = self.poly.xy
        xy[2] = discrete_val, 1
        xy[3] = discrete_val, 0
        self.poly.xy = xy
        self.valtext.set_text(self.valfmt % discrete_val)
        if self.drawon: 
            self.ax.figure.canvas.draw()
        self.val = val
        if not self.eventson: 
            return
        for cid, func in self.observers.iteritems():
            func(discrete_val)

以下是使用它的完整示例:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Slider

class ChangingPlot(object):
    def __init__(self):
        self.inc = 0.5

        self.fig, self.ax = plt.subplots()
        self.sliderax = self.fig.add_axes([0.2, 0.02, 0.6, 0.03],
                                          facecolor='yellow')

        self.slider = DiscreteSlider(self.sliderax, 'Value', 0, 10, 
                                     increment=self.inc, valinit=self.inc)
        self.slider.on_changed(self.update)

        x = np.arange(0, 10.5, self.inc)
        self.ax.plot(x, x, 'ro')
        self.dot, = self.ax.plot(self.inc, self.inc, 'bo', markersize=18)

    def update(self, value):
        self.dot.set_data([[value],[value]])
        self.fig.canvas.draw()

    def show(self):
        plt.show()

class DiscreteSlider(Slider):
    """A matplotlib slider widget with discrete steps."""
    def __init__(self, *args, **kwargs):
        """Identical to Slider.__init__, except for the "increment" kwarg.
        "increment" specifies the step size that the slider will be discritized
        to."""
        self.inc = kwargs.pop('increment', 0.5)
        Slider.__init__(self, *args, **kwargs)
        self.val = 1

    def set_val(self, val):
        discrete_val = int(val / self.inc) * self.inc
        # We can't just call Slider.set_val(self, discrete_val), because this 
        # will prevent the slider from updating properly (it will get stuck at
        # the first step and not "slide"). Instead, we'll keep track of the
        # the continuous value as self.val and pass in the discrete value to
        # everything else.
        xy = self.poly.xy
        xy[2] = discrete_val, 1
        xy[3] = discrete_val, 0
        self.poly.xy = xy
        self.valtext.set_text(self.valfmt % discrete_val)
        if self.drawon: 
            self.ax.figure.canvas.draw()
        self.val = val
        if not self.eventson: 
            return
        for cid, func in self.observers.items():
            func(discrete_val)


p = ChangingPlot()
p.show()

enter image description here


2

如果您不想子类化滑块,则可以使用@Joe Kington的答案中的几行代码来在回调函数中完成离散化:

sldr = Slider(ax,'name',0.,5.,valinit=0.,valfmt="%i")
sldr.on_changed(partial(set_slider,sldr))

然后:

def set_slider(s,val):
    s.val = round(val)
    s.poly.xy[2] = s.val,1
    s.poly.xy[3] = s.val,0
    s.valtext.set_text(s.valfmt % s.val)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接