Prevent data overlap between two sliders - use a single slider for two quantities

北城以北 提交于 2019-12-24 19:24:50

问题


I am using 2 sliders to adjust the colorbar of a 2D heat map; one for the bottom (minimum) and one for the top (maximum). I want to make sure that the two cannot overlap i.e. if the full range is 0 till 5 and I set the maximum on 2 then the minimum cannot surpass 2. This should happen interactively. How can I achieve this? Additionally is there a maybe a way to integrate the two sliders into one? Thank you.

An example of my GUI. And the relevant part of the code:

def update(val, s=None):
    """Retreives the value from the sliders and updates the graph accordingly"""
    _cmin = s_cmin.val
    _cmax = s_cmax.val
    pcm.set_clim([_cmin, _cmax])
    plt.draw()

def reset(event):
    """Resets the sliders when the reset button is pressed"""
    s_cmin.reset()
    s_cmax.reset()

fig, ax = plt.subplots(figsize=(13,8))
plt.subplots_adjust(left=0.25,bottom=0.25)

# define axis minima and maxima:
x_min = Xi.min()
x_max = Xi.max()
y_min = Yi.min()
y_max = Yi.max()
c_min = Zi.min()
c_max = Zi.max()

pcm = ax.pcolormesh(Xi,Yi,Zi)
cb = plt.colorbar(pcm)
axcolor = 'lightgoldenrodyellow'
axx = plt.xlim([x_min, x_max])
ayy = plt.ylim([y_min, y_max])

# create a space in the figure to place the two sliders:
ax_cmin = plt.axes([0.15, 0.10, 0.65, 0.02], facecolor=axcolor)
ax_cmax = plt.axes([0.15, 0.15, 0.65, 0.02], facecolor=axcolor)
# the first argument is the rectangle, with values in percentage of the figure
# size: [left, bottom, width, height]

# create each slider on its corresponding place:
s_cmax = Slider(ax_cmax, 'max', c_min, c_max, valinit=c_max, valfmt='%1.4f')
s_cmin = Slider(ax_cmin, 'min', c_min, c_max, valinit=c_min, valfmt='%1.4f')

# set both sliders to call update when their value is changed:
s_cmin.on_changed(update)
s_cmax.on_changed(update)

# create a space in the figure to place the reset button
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
# create the reset button
button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
button.on_clicked(reset)

# create a space in the figure to place the textboxes:
axbox_xmin = plt.axes([0.07, 0.55, 0.04, 0.04])
axbox_xmax = plt.axes([0.12, 0.55, 0.04, 0.04])
axbox_ymin = plt.axes([0.07, 0.49, 0.04, 0.04])
axbox_ymax = plt.axes([0.12, 0.49, 0.04, 0.04])

# create the textboxes
tb_xmin = TextBox(axbox_xmin,'x', color=axcolor, hovercolor='0.975', label_pad=0.01)
tb_xmax = TextBox(axbox_xmax,'', color=axcolor, hovercolor='0.975')
tb_ymin = TextBox(axbox_ymin,'y', color=axcolor, hovercolor='0.975', label_pad=0.01)
tb_ymax = TextBox(axbox_ymax,'', color=axcolor, hovercolor='0.975')

# create the submit action
tb_xmin.on_submit(submit)
tb_xmax.on_submit(submit)
tb_ymin.on_submit(submit)
tb_ymax.on_submit(submit)

plt.show()

回答1:


In some cases it may indeed be desired to have one single Slider which can set some minimum and maximum simultaneously. So instead of having one single value, the slider could have two values and the rectangle inside the slider would be bounded by the two values, instead of starting at the minimum value of the slider.

The following would be solution to such a case. It uses a MinMaxSlider, i.e. a subclass of the Slider which is adapted to host two values.
Instead of a single input value, it would expect two values,

MinMaxSlider(... , valinit=0.5,valinit2=0.8)

such that the Sliderbar ranges from 0.5 to 0.8. Clicking on the slider would change the value which is closer to the click, making dragging rather easy.

In order to use this slider be aware that the function that the callback via on_changed now naturally has two arguments.

import six
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider

class MinMaxSlider(Slider):
    def __init__(self,ax, label, valmin, valmax, **kwargs):
        self.valinit2 = kwargs.pop("valinit2", valmax)
        self.val2 = self.valinit2
        Slider.__init__(self,ax, label, valmin, valmax, **kwargs)
        self.poly.xy = np.array([[self.valinit,0],[self.valinit,1],
                        [self.valinit2,1],[self.valinit2,0]])
        self.vline.set_visible(False)

    def set_val(self, val):
        if np.abs(val-self.val) < np.abs(val-self.val2):
            self.val = val
        else:
            self.val2 = val
        self.poly.xy = np.array([[self.val,0],[self.val,1],
                                 [self.val2,1],[self.val2,0]])
        self.valtext.set_text(self.valfmt % self.val +"\n"+self.valfmt % self.val2)
        if self.drawon:
            self.ax.figure.canvas.draw_idle()
        if not self.eventson:
            return
        for cid, func in six.iteritems(self.observers):
            func(self.val,self.val2)


import numpy as np

x = np.linspace(0,16,1001)
f = lambda x: np.sin(x)*np.sin(1.7*x+2)*np.sin(0.7*x+0.05)*x

fig,(ax, sliderax) = plt.subplots(nrows=2,gridspec_kw={"height_ratios":[1,0.05]})
fig.subplots_adjust(hspace=0.3)

ax.plot(x,f(x))

slider = MinMaxSlider(sliderax,"slider",x.min(),x.max(),
                      valinit=x.min(),valinit2=x.max())

def update(mini,maxi):
    ax.set_xlim(mini,maxi)

slider.on_changed(update)
update(x.min(),x.max())

plt.show()



来源:https://stackoverflow.com/questions/47353014/prevent-data-overlap-between-two-sliders-use-a-single-slider-for-two-quantitie

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!