I've been trying to make a toggle button to turn on and off the gridlines on my graph every time it's clicked. I understand how the toggle works as I was able to do it for some lines being animated onto my graph. I'm unsure if it's a problem with how MatplotLib sets axis and zorder etc. Or if it's to do with the fact i'm animating the graph and it updates the whole figure every frame. I didn't think that would be a problem though considering that everything is redrawn and not just the changing plots.
I've tried many different ways of doing this widget and none of it seemed to work. I've changed the visibility of the lines by using alpha, the colour since my background is black, switching ax.grid() to ax.grid(False). I'm not sure anymore, this is what I've got currently:
ax_grid = fig.add_axes([0.85, 0.2, 0.1, 0.04])
gbutton = Button(ax_grid, 'Grid', color = '0.3', hovercolor='0.7')
# Define the grid visibility state
grid_visible = False # Initialize the grid visibility state
def grid_lines(event):
global grid_visible
grid_visible = not grid_visible # Toggle the state
plt.sca(ax) # Ensure we are modifying the main plot's axes
ax.set_axisbelow(False) # trying to set it above
fig.canvas.draw()
if grid_visible:
ax.grid(color='white') # Show grid with properties
else:
ax.grid(color='black') # Simply turn off the grid without extra arguments
fig.canvas.draw_idle() # Redraw the canvas
Another version:
ax_grid = fig.add_axes([0.85, 0.2, 0.1, 0.04])
gbutton = Button(ax_grid, 'Grid', color = '0.3', hovercolor='0.7')
# Define the grid visibility state
grid_visible = False # Initialize the grid visibility state
def grid_lines(event):
global grid_visible
grid_visible = not grid_visible # Toggle state
# Access grid lines directly and toggle their visibility
for line in ax.get_xgridlines() + ax.get_ygridlines():
line.set_visible(grid_visible)
fig.canvas.draw_idle() # Redraw the canvas
My whole code incase it's needed:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.widgets import Slider, Button
import scipy
from math import sqrt
plt.rcParams["figure.autolayout"] = True
print("Default text color is: ", plt.rcParams['text.color'])
plt.rcParams.update({'text.color': "white"}) # changing default text colour to white
dt = 0.1
numsteps = 10000
pi = scipy.constants.pi
G = 4.30091e-3 # AU^3 * M_sun^-1 * yr^-2
wA = 3.0
wB = 3.0
thetaA = 0
thetaB = thetaA + pi # to put the other store on the opposite end of starA
# initialise variables
r = 5
mA = 10 # mass in solar mass
mB = 10
M = mA + mB
x_valA, y_valA = [], []
x_valB, y_valB = [], []
# Create the animation, fig represents the object/canvas and ax means it is the area being plotted on
fig, ax = plt.subplots(figsize=(8, 8), dpi=100)
ax.set_xlim(-10, 10)
ax.set_ylim(-10, 10)
ax.set_aspect('equal')
ax.set_facecolor("black")
fig.patch.set_facecolor("k")
# Create the stars and COM plot
starA, = ax.plot([], [], 'o', color='blue', markersize=10, label='Star A',zorder=9.5)
starB, = ax.plot([], [], 'o', color='red', markersize=10, label='Star B',zorder=9.5)
COM = ax.plot([0], [0], '+', color='white', markersize=5, label='COM',zorder=10)
orbitA, = ax.plot([], [], '-', color='cyan', alpha=0.5, label='Orbit A', zorder=5)
orbitB, = ax.plot([], [], '-', color='pink', alpha=0.5, label='Orbit B', zorder=5)
ax.grid(color='white', linestyle='--', linewidth=0.5, zorder=1)
leg = ax.legend(facecolor='k', labelcolor='w', fancybox=True, framealpha=0.6, loc='upper right', bbox_to_anchor=(1,1))
leg.set_zorder(20)
def orbit(r, mA, mB, M):
global x_valA, y_valA, x_valB, y_valB, thetaA, thetaB, G, dt
# Reset variables
x_valA, y_valA = [], []
x_valB, y_valB = [], []
M = mA + mB
rA = r * (mB / M)
rB = r * (mA / M)
# initial positions
positionA = np.array([rA * np.cos(thetaA), rA * np.sin(thetaA)]) # Star A initial position
positionB = np.array([rB * np.cos(thetaB), rB * np.sin(thetaB)]) # Star B initial position
# SIMULATION LOOP
for _ in range(numsteps):
# Store positions for both stars
x_valA.append(positionA[0])
y_valA.append(positionA[1])
x_valB.append(positionB[0])
y_valB.append(positionB[1])
# Update speed and angles for next positions
wA = sqrt(G * M / rA * rA)
wB = sqrt(G * M / rB * rB)
thetaA += wA * dt # update angle for starA
thetaB += wB * dt # update angle for starB
# Calculate new positions based on updated angles
positionA = np.array([rA * np.cos(thetaA), rA * np.sin(thetaA)])
positionB = np.array([rB * np.cos(thetaB), rB * np.sin(thetaB)])
# After simulation loop, update the orbit lines with the recorded paths
orbitA.set_data(x_valA, y_valA)
orbitB.set_data(x_valB, y_valB)
# initialising the data
def init():
starA.set_data([], [])
starB.set_data([], [])
orbitA.set_data([], []) # Clear the initial orbit paths
orbitB.set_data([], [])
return starA, starB, orbitA, orbitB
def update(frame):
starA.set_data([x_valA[frame]], [y_valA[frame]]) # Pass as lists
starB.set_data([x_valB[frame]], [y_valB[frame]])
if orbit_lines_visible:
orbitA.set_data(x_valA[:frame+1], y_valA[:frame+1])
orbitB.set_data(x_valB[:frame+1], y_valB[:frame+1])
return starA, starB, orbitA, orbitB
ani = FuncAnimation(fig, update, frames=numsteps, init_func=init, blit=False, interval=50)
plt.title("Binary Star System", fontsize=20, fontweight='bold')
def up(val):
global r, mA, mB, M
r = seperation_slider.val
mA = mA_slider.val
mB = mB_slider.val
M = mA + mB
orbit(r, mA, mB, M) # updated values into function
starA.set_markersize(mA_slider.val)
starB.set_markersize(mB_slider.val)
ani.event_source.stop() # Stop the current animation
ani.event_source.start() # Restart the animation with updated orbit
fig.canvas.draw_idle()
seperation_slider = Slider(ax=plt.axes([0.125, 0.02, 0.10, 0.04]), label='Seperation', valmin=1, valmax=15, valinit=r, valstep=1.11, facecolor='w')
mA_slider = Slider(ax=plt.axes([0.45, 0.02, 0.15, 0.04]), label="Mass A", valmin=0.1, valmax=100, valinit=mA, valstep=1.11, facecolor='b')
mB_slider = Slider(ax=plt.axes([0.80, 0.02, 0.15, 0.04]), label="Mass B", valmin=0.1, valmax=100, valinit=mB, valstep=1.11, facecolor='r')
seperation_slider.label.set_size(12)
mA_slider.label.set_size(12)
mB_slider.label.set_size(12)
mA_slider.vline.set_color('cyan')
mB_slider.vline.set_color('violet')
seperation_slider.vline.set_color('black')
seperation_slider.on_changed(up)
mA_slider.on_changed(up)
mB_slider.on_changed(up)
orbit(r, mA, mB, M)
ax_reset = fig.add_axes([0.85, 0.08, 0.1, 0.04])
rbutton = Button(ax_reset, 'Reset', color='0.3', hovercolor='0.7')
def reset(event):
seperation_slider.reset()
mA_slider.reset()
mB_slider.reset()
rbutton.on_clicked(reset)
# Toggle for orbit lines visibility
orbit_lines_visible = False
def lines(event):
global orbit_lines_visible
if orbit_lines_visible:
orbitA.set_alpha(0) # Hide orbit A
orbitB.set_alpha(0) # Hide orbit B
else:
orbitA.set_alpha(0.5) # Show orbit A
orbitB.set_alpha(0.5) # Show orbit B
orbit_lines_visible = not orbit_lines_visible
fig.canvas.draw_idle()
ax_lines = fig.add_axes([0.85, 0.14, 0.1, 0.04])
button = Button(ax_lines, 'Orbit Lines', color='0.3', hovercolor='0.7')
button.on_clicked(lines)
ax_grid = fig.add_axes([0.85, 0.2, 0.1, 0.04])
gbutton = Button(ax_grid, 'Grid', color = '0.3', hovercolor='0.7')
# Define the grid visibility state
grid_visible = False # Initialize the grid visibility state
def grid_lines(event):
global grid_visible
grid_visible = not grid_visible
if grid_visible:
ax.grid(True, color='white')
else:
ax.grid(False)
# Force immediate redraw
fig.canvas.draw()
plt.show()
The issue is that instead of turning the grid off, you're only changing its color to black. Even though your background is black, the grid is still active, it’s just invisible. To disable the grid, call:
ax.grid(False)
and to enable, explicitly call:
ax.grid(True, color='white', linestyle='--', linewidth=0.5)
Example working script:
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
fig, ax = plt.subplots()
fig.set_facecolor('black')
ax.set_facecolor('black')
for spine in ax.spines.values():
spine.set_color('white')
ax.tick_params(colors='white')
ax.grid(False)
grid_state = [False]
def toggle_grid(event):
grid_state[0] = not grid_state[0]
if grid_state[0]:
ax.grid(True, color='white', alpha=0.5, linestyle='-', linewidth=0.5)
else:
ax.grid(False)
fig.canvas.draw_idle()
button_ax = plt.axes([0.8, 0.05, 0.15, 0.075])
button = Button(button_ax, 'Grid', color='0.3', hovercolor='0.7')
button.on_clicked(toggle_grid)
plt.show()