uhoh
uhoh

Reputation: 3745

Matplotlib slider widgets (user-defined script-generated) aren't triggering recalculation and plot update

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))

Upvotes: 0

Views: 382

Answers (1)

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339062

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))

Upvotes: 1

Related Questions