9

Based on this solution, I wanted to make a slider where only specified values are allowed, the slider itself is also discrete the slider bar only moves if a new point was chosen (so basically a slider version of a radio button). For example, if I click near but not exactly on the current point, the slider shouldn't change and the plot should not redraw.

I got it pretty much working, but the result is lagging behind: if I click alternating between 1 and 10 in the example below, the slider updates correctly, but the moving point is always jumping to the previous value. How can I fix it:

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

class ChangingPlot(object):
    def __init__(self):
        self.draw_counter = 0
        x = np.logspace(0, 1, 10)

        self.fig, self.ax = plt.subplots()
        self.sliderax     = self.fig.add_axes([0.2, 0.02, 0.6, 0.03],axisbg='yellow')
        self.slider       = DiscreteSlider(self.sliderax,'Value', 0, 10,\
                                           allowed_vals=x, valinit=x[0])

        self.slider.on_changed(self.update)

        self.ax.plot(x, x, 'ro')
        self.dot, = self.ax.plot(x[0], x[0], 'bo', markersize=18)
        self.text = self.ax.text(2,8,str(self.draw_counter))

    def update(self, value):
        self.draw_counter += 1
        self.dot.set_data([[value],[value]])
        self.text.set_text(str(self.draw_counter)+' draws, value = '+str(value))

    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 new keyword 'allowed_vals'.
        This keyword specifies the allowed positions of the slider
        """
        self.allowed_vals = kwargs.pop('allowed_vals',None)
        self.previous_val = kwargs['valinit']
        Slider.__init__(self, *args, **kwargs)
        if self.allowed_vals==None:
            self.allowed_vals = [self.valmin,self.valmax]

    def set_val(self, val):
        discrete_val = self.allowed_vals[abs(val-self.allowed_vals).argmin()]
        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 self.previous_val!=discrete_val:
            self.previous_val = discrete_val
            if not self.eventson: 
                return
            for cid, func in self.observers.iteritems():
                func(discrete_val)

p = ChangingPlot()
p.show()
2
  • 1
    Thanks for sharing this. I just had to change the line self.val = val to self.val = discrete_val in the function set_val. Commented Oct 10, 2014 at 9:20
  • @JohnSmith This is nifty! Also exactly what I (and probably others) needed. You should apply the fix that HYRY suggested and submit this as a pull request to the matplotlib github repo. Commented Dec 16, 2014 at 5:03

1 Answer 1

5

Very nice example, You need to call Figure.canvas.draw() to reflush the screen:

def update(self, value):
    self.draw_counter += 1
    self.dot.set_data([[value],[value]])
    self.text.set_text(str(self.draw_counter)+' draws, value = '+str(value))
    self.fig.canvas.draw() # <--- Add this line
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.