Search code examples
pythonpython-3.xmatplotlibmatplotlib-widget

Creating a figure with plots, sliders and other widgets arranged with a gridspec layout


I want to create an animated graph which parameters can be controlled with sliders and other widgets. I have to create several similar figures so I want to pack this in some class to be reused with various parameters. But before that, I wanted to figure out how it even works.

This code will create a graph on upper part of the figure and will leave the rest blank. However, the x and y axes are drawn in range [-0.05,0.05], instead of in predefined ranges below.

How do I make sure the graph is drawn to scale I want?

Another thing I don't know is how do I add widgets to the layout? I want to insert them into gridspec without hardcoding the coordinates and sizes, to have them adjust to given space.

I tried something below, but that obviously didn't work. How do I go about this to make it work as I want?

import matplotlib.gridspec as gridspec
import numpy as np

from matplotlib import pyplot as plt

PI = np.pi

# Half width of the graph x-axis
x_axis = 4*PI
# x_axis offset
x_offset = 0
# Half height of the graph y-axis
y_axis = 8
# y_axis offset
y_offset = -1

fig = plt.figure()

mainGrid = gridspec.GridSpec(2, 1)
graphCell = plt.subplot(mainGrid[0, :])
graphCell.plot(xlim=(-x_axis-x_offset, x_axis-x_offset), ylim=(-y_axis-y_offset, y_axis-y_offset))
controlCell = mainGrid[1, :]
controlGrid = gridspec.GridSpecFromSubplotSpec(1, 7, controlCell)
sliderCell = controlGrid[0, 0]
sliderCount = 7
sliderGrid = gridspec.GridSpecFromSubplotSpec(sliderCount, 1, sliderCell)
sliders = []
for i in range(0, sliderCount):
    pass
    #sliders[i] = Slider(sliderGrid[0, i], "Test {}".format(i), 0.1, 8.0, valinit=2, valstep=0.01)

x_data = np.linspace(-x_axis-x_offset, x_axis-x_offset, 512)
y_data = [x for x in x_data]

line = plt.plot([], [])[0]
line.set_data(x_data, y_data)

plt.show()

Solution

  • Some issues:

    • plot doesn't have any xlim argument.
    • There is one grid too much in the code
    • Widgets need to live inside axes
    • The first index of a grid is the rows, not the columns.

    In total,

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.gridspec as gridspec
    from matplotlib.widgets import Slider
    
    # Half width of the graph x-axis
    x_axis = 4*np.pi
    # x_axis offset
    x_offset = 0
    # Half height of the graph y-axis
    y_axis = 8
    # y_axis offset
    y_offset = -1
    
    fig = plt.figure()
    
    mainGrid = gridspec.GridSpec(2, 1)
    ax = plt.subplot(mainGrid[0, :])
    ax.set(xlim=(-x_axis-x_offset, x_axis-x_offset), ylim=(-y_axis-y_offset, y_axis-y_offset))
    controlCell = mainGrid[1, :]
    
    sliderCount = 7
    sliderGrid = gridspec.GridSpecFromSubplotSpec(sliderCount, 1, controlCell)
    sliders = []
    for i in range(0, sliderCount):
        sliderax = plt.subplot(sliderGrid[i, 0])
        slider = Slider(sliderax, "Test {}".format(i), 0.1, 8.0, valinit=2, valstep=0.01)
        sliders.append(slider)
    
    x_data = np.linspace(-x_axis-x_offset, x_axis-x_offset, 512)
    y_data = [x for x in x_data]
    
    line = ax.plot([], [])[0]
    line.set_data(x_data, y_data)
    
    plt.show()
    

    enter image description here