I'm using matplotlib sliders, similar to this demo. The sliders currently use 2 decimal places and 'feel' quite continuous (though they have to be discrete on some level). Can I decide on what level they are discrete? Integer steps? 0.1-sized steps? 0.5? My google-fu failed me.
-
1I can change the display to integers with: valfmt='%1.0f' but it doesn't change the output. I'm currently looking to introduce some rounding somewhere, but it seems like this should be a property of the slider. – J Knight Dec 01 '12 at 04:40
-
2I quickly realized I can round() the input inside the function the slider updates, as well as only displaying the correct number decimal places. But if I want to round to 1/2 increments, I can't make the display match the value (will still show accurate to 0.1) – J Knight Dec 01 '12 at 05:04
-
You just need to set the display value each time. As long as you're okay with it not "feeling" like discrete values (e.g. the bar will still progress smoothly), you can still make the display reflect discrete values. (Adding an example in just a bit...) – Joe Kington Dec 01 '12 at 15:32
-
For a discrete slider also see [this answer](http://stackoverflow.com/a/41152160/4124317) – ImportanceOfBeingErnest Dec 15 '16 at 23:27
2 Answers
If you just want integer values, just pass in an approriate valfmt
when you create the slider (e.g. valfmt='%0.0f'
)
However, if you want non-integer invervals, you'll need to manually set the text value each time. Even if you do this, though, the slider will still progress smoothly, and it won't "feel" like discrete intervals.
Here's an example:
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()
If you wanted to make the slider "feel" completely like discrete values, you could subclass matplotlib.widgets.Slider
. The key effect is controlled by Slider.set_val
In that case, you'd do something like this:
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)
And as a full example of using it:
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()

- 259
- 4
- 16

- 275,208
- 71
- 604
- 463
If you would rather not subclass the Slider, I picked a few lines off @Joe Kington's answer to accomplish the discretization within the callback function:
sldr = Slider(ax,'name',0.,5.,valinit=0.,valfmt="%i")
sldr.on_changed(partial(set_slider,sldr))
and then:
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)

- 343
- 3
- 9