pythonmatplotlibplotsliderparametric-equations

Tweaking the constant values of a parametric equation with sliders in matplotlib


I'm trying to plot this parametric equation and use Slider widget from matplotlib to make the plot interactive. I want to be able to tweak the value of the constants a and k and see how the plot changes. I have been able to produce this so far, but although sliders are interactive (i.e. I can change the values on them), changing the values on the sliders doesn't affect the plot at all. I couldn't figure out what I'm doing wrong. Here's my code. I would appreciate if you could point me in the right direction or provide any sort of help, really. Thanks.

NB: Initial values (a_init and k_init) are random and have no significance whatsoever. I don't think the problem is with them, though.

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

a_init = 15
k_init = 25

t = np.linspace(0, 2*np.pi, 100)

x = 2*k_init*np.cos(t)-a_init*np.cos(k_init*t)
y = 2*k_init*np.sin(t)-a_init*np.sin(k_init*t)

fig = plt.figure(figsize=(8,8))

parametric_ax = plt.axes([0.1, 0.2, 0.8, 0.65])
slider_ax = plt.axes([0.1, 0.03, 0.8, 0.05])
slider2_ax = plt.axes([0.1, 0.10, 0.8, 0.05])

plt.axes(parametric_ax)
parametric_plot, = plt.plot(x, y)

a_slider = Slider(slider_ax, 'a', 0, 1000, valinit=a_init)

k_slider = Slider(slider2_ax, 'k', 0, 1000, valinit=k_init)

def update(a, k):
    parametric_plot.set_ydata((2*k*np.cos(t)-a*np.cos(t*k)), (2*k*np.sin(t)-a*np.sin(t*k)))
    fig.canvas.draw_idle()          

a_slider.on_changed(update)
k_slider.on_changed(update)

plt.show()

Solution

  • The function update called on a slider update accepts only one argument (say val, which corresponds to the new value of the modified slider). Since this callback function is shared by two sliders, I suggest not to use this val parameter in the body of the function. Instead, the current values of the sliders can be directly retrieved.

    Method set_ydata only updates the data for y, and thus takes only a 1D array as input. Either you change the values of x and y simultaneously via set_data, or update it separately via set_xdata and set_ydata. The first solution requires a 2D array and is thus not the most suitable here.

    Finally that gives us something like

    def update(*val):
        a = a_slider.val
        k = k_slider.val
        parametric_plot.set_ydata(2 * k * np.sin(t) - a * np.sin(t * k))
        parametric_plot.set_xdata(2 * k * np.cos(t) - a * np.cos(t * k))
    

    Other slight improvements

    a_init, k_init = 15, 25
    t = np.linspace(0, 2 * np.pi, 10000)
    
    fig = plt.figure(figsize=(8, 8))
    
    ## Main axes
    ax = plt.axes([0.125, 0.15, 0.775, 0.80])
    plt.axis("equal")
    
    parametric_plot, = plt.plot(0, 0, c="royalblue") # do not define x and y
    plt.xlim(-200, 200)
    plt.ylim(-200, 200)
    
    ## Create sliders
    ax_slider_a = plt.axes([0.125, 0.03, 0.775, 0.03])
    ax_slider_k = plt.axes([0.125, 0.07, 0.775, 0.03])
    
    a_slider = Slider(ax_slider_a, r"$a$", 0, 100, valinit=a_init)
    k_slider = Slider(ax_slider_k, r"$k$", 0, 100, valinit=k_init, valstep=1)
    
    def update(*args):
        a, k = a_slider.val, k_slider.val 
        parametric_plot.set_ydata(2 * k * np.sin(t) - a * np.sin(t * k))
        parametric_plot.set_xdata(2 * k * np.cos(t) - a * np.cos(t * k))
    
    a_slider.on_changed(update)
    k_slider.on_changed(update)
    
    update()  # initialize the plot and thus avoid redundancies
    plt.show()
    

    enter image description here