0

Here is a highly abstracted main program and module. Matplotlib slider widgets should cause the Data instances to recalculate and the plot should then update.

Each time a slider is updated, it should pass its new value to the appropriate method defined during the slider's instantiation. For example, moving the first slider should send it's value to d1.set_a() which triggers a recalculation of that data, and should then trigger P.offsets() (see MODULE) to update the plot.

Question: How can I get these user-defined, script-generated Sliders to trigger the data objects and the plot to update? Do the Slider Widgets instances offer more convenient methods than the way I'm doing it here?

MAIN Program:

import numpy as np
from MODULE import Data, Plot

x0 = np.linspace(0, 10., 11)
y1, y2 = [0.5 * (1.0 + f(x0)) for f in (np.cos, np.sin)]

d1, d2 = Data('hey', x0, y1), Data('wow', x0, y2) # data generating objects

p = Plot('hey')    # plot object

p.add_slider(d1, 'set_a', d1.a, (0.2, 1.0))
p.add_slider(d1, 'set_p', d1.p, (0.5, 2.0))
p.add_slider(d2, 'set_a', d2.a, (0.2, 1.0))
p.add_slider(d2, 'set_p', d2.p, (0.5, 2.0))

p.plotme((d1, d2))

Current result, sliders move but don't trigger recalculation/replotting:

slider test

MODULE:

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

class Plot(object):
    def __init__(self, name):
        self.name = name
        self.sliders = []
        self.axcolor = 'lightgoldenrodyellow'
        self.fig = plt.figure()

    def sliderfunc(self, value):
        print('sliderfunc')       # currently not getting here
        for (obj, P) in self.OPs:
            P.set_offsets(obj.xy.T)
        self.fig.canvas.draw_idle()

    def add_slider(self, obj, method, nominal, limits):
        ybot = 0.03 * (len(self.sliders) + 1)
        name = obj.name + '.' + method 
        ax_slider   = plt.axes([0.25, ybot, 0.50, 0.02], facecolor=self.axcolor)
        slider      = Slider(ax_slider, name, limits[0], limits[1],
                             valinit=nominal)
        slider.on_changed(getattr(obj, method)) # this may not be right
        self.sliders.append(slider)
        return slider

    def plotme(self, objs):
        ybot = 0.03 * (len(self.sliders) + 3)
        A = plt.axes([0.15, ybot, 0.65, 0.50])
        self.OPs = []
        for obj in objs:
            P = A.scatter(obj.x, obj.y)
            self.OPs.append((obj, P))
        plt.show()

class Data(object):
    def __init__(self, name, x0, y0):
        self.name = name
        self.a = 1.0
        self.p = 1.0
        self.x0, self.y0 = x0, y0
        self.setstuff(a=1.0, p=1.0)
    def setstuff(self, a=None, p=None):
        if a != None:
            self.set_a(a)
        if p != None:
            self.set_p(p)
    def set_a(self, a):
        self.a = a
        self.x = self.a * self.x0
        self.y = self.y0**self.p
        self.xy = np.vstack((self.x, self.y))
    def set_p(self, p):
        self.p = p
        self.x = self.a * self.x0
        self.y = self.y0**self.p
        self.xy = np.vstack((self.x, self.y))
12
  • 1
    Possibly you just forgot to assign the Slider to a variable. If you don't to that it will be garbage collected (as all objects without reference will in python) and removed from memory instantly after its creation. Commented Jul 9, 2019 at 1:48
  • @ImportanceOfBeingErnest nope that's not it. The contents of the p.sliders list still contains four matplotlib.widgets.Slider instances and I can still read their data. Commented Jul 9, 2019 at 1:52
  • Ok, so you will need to provide a runnable code, such that one can actually test this. (Currently there is some undefined variable, so I cannot run it) Commented Jul 9, 2019 at 1:56
  • 1
    I show O and fig undefined in sliderfunc. Strangely I get no traceback even when I adjust the sliders. Same result in ipython and terminal. Commented Jul 9, 2019 at 2:57
  • 1
    Spyder flags them as in error. Apparently sliderfunc is never called. It looked like both sliderfunc and add_slider have code which tries to update the plot. I'm out of my area of expertise here, but couldn't resist trying your code. I use Anaconda python also. Commented Jul 9, 2019 at 3:20

1 Answer 1

1

The individual sliders need to perform different actions, so each slider needs its own callback. Inside of it you may of course then call the same function (sliderfunc) to update the plot.

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


class Plot(object):
    def __init__(self, name):
        self.name = name
        self.sliders = []
        self.fig = plt.figure()

    def sliderfunc(self):
        for (obj, P) in self.OPs:
            P.set_offsets(obj.xy.T)
        self.fig.canvas.draw_idle()

    def add_slider(self, obj, method, nominal, limits):
        ybot = 0.03 * (len(self.sliders) + 1)
        name = obj.name + '.' + method 
        ax_slider   = plt.axes([0.25, ybot, 0.50, 0.02], facecolor="w")
        slider      = Slider(ax_slider, name, limits[0], limits[1],
                             valinit=nominal)

        def callback(val):
            getattr(obj, method)(val)
            self.sliderfunc()

        slider.on_changed(callback)
        self.sliders.append(slider)
        return slider

    def plotme(self, objs):
        ybot = 0.03 * (len(self.sliders) + 3)
        A = plt.axes([0.15, ybot, 0.65, 0.50])
        self.OPs = []
        for obj in objs:
            P = A.scatter(obj.x, obj.y)
            self.OPs.append((obj, P))
        plt.show()

class Data(object):
    def __init__(self, name, x0, y0):
        self.name = name
        self.a = 1.0
        self.p = 1.0
        self.x0, self.y0 = x0, y0
        self.update()

    def update(self):
        self.x = self.a * self.x0
        self.y = self.y0**self.p
        self.xy = np.vstack((self.x, self.y))

    def set_a(self, val):
        self.a = val
        self.update()

    def set_p(self, val):
        self.p = val
        self.update()


x0 = np.linspace(0, 10., 11)
y1, y2 = [0.5 * (1.0 + f(x0)) for f in (np.cos, np.sin)]

d1, d2 = Data('hey', x0, y1), Data('wow', x0, y2) # data generating objects

p = Plot('hey')    # plot object

p.add_slider(d1, 'set_a', d1.a, (0.2, 1.0))
p.add_slider(d1, 'set_p', d1.p, (0.5, 2.0))
p.add_slider(d2, 'set_a', d2.a, (0.2, 1.0))
p.add_slider(d2, 'set_p', d2.p, (0.5, 2.0))

p.plotme((d1, d2))
Sign up to request clarification or add additional context in comments.

1 Comment

Oh, this is really, really beautiful, wow! I think it would have taken quite a while for me to figure this out, but now that I see it it's clear as day. Thank you very much!

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.