I have a program simulating the movement of a ball inside a box, and tracking it using matplotlib:
def track_bouncing_charge():
fig, (ax, ax_dist) = plt.subplots(1, 2, figsize=(14, 6))
ax = fig.add_subplot(121, projection='3d') # 3D plot
sensor_labels = [f"Sensor {i + 1}" for i in range(len(sensors))]
time_steps = []
distance_history = [[] for _ in range(len(sensors))]
time_counter = 0
# Initial charge position and velocity
charge_pos = np.array([0.5, 0.5, 0.5])
velocity = np.array([0.02, 0.03, 0.01])
dt = 0.1 # Time step
while True:
# Update charge position (simulate bouncing off walls)
charge_pos += velocity * dt
for i in range(3):
if charge_pos[i] <= 0 or charge_pos[i] >= 1:
velocity[i] = -velocity[i] # Reverse direction on collision
charge_pos[i] = max(0, min(1, charge_pos[i])) # Keep within bounds
# Generate synthetic electric field data
sensor_readings = np.array([compute_electric_field(sensor, charge_pos) for sensor in sensors])
# Solve for charge position using least squares optimization
initial_guess = np.array([0.5, 0.5, 0.5])
result = least_squares(error_function, initial_guess, args=(sensor_readings,))
estimated_position = result.x
# Compute distances
distances = [np.linalg.norm(sensor - estimated_position) for sensor in sensors]
time_steps.append(time_counter)
time_counter += dt
for i in range(len(sensors)):
distance_history[i].append(distances[i])
# Clear and redraw 3D plot
ax.clear()
ax.scatter(sensors[:, 0], sensors[:, 1], sensors[:, 2], c='b', marker='o', label='Sensors')
ax.scatter(charge_pos[0], charge_pos[1], charge_pos[2], c='g', s=200, label='True Charge Position')
# Draw lines connecting sensors to the estimated charge position with varying color intensity based on distance
for sensor in sensors:
distance = np.linalg.norm(sensor - estimated_position)
ax.plot([sensor[0], estimated_position[0]],
[sensor[1], estimated_position[1]],
[sensor[2], estimated_position[2]],
'r.-.', lw=min(20, 2 / distance), alpha=min(0.5, 0.5 / distance))
ax.set_xlabel('X Position (m)', fontsize=24, fontname='Arial', labelpad=20)
ax.set_ylabel('Y Position (m)', fontsize=24, fontname='Arial', labelpad=20)
ax.set_zlabel('Z Position (m)', fontsize=24, fontname='Arial', labelpad=20)
ax.tick_params(axis='both', which='major', labelsize=24)
# Remove bounding box lines while keeping the grid
for axis in [ax.xaxis, ax.yaxis, ax.zaxis]:
axis._axinfo["grid"].update(linewidth=0.5) # Keep grid lines but make them subtle
axis._axinfo["grid"].update(color=(0.7, 0.7, 0.7, 0.7)) # Light grey grid
# Hide the extra bounding box lines
ax.xaxis._axinfo["grid"].update(linewidth=0)
ax.yaxis._axinfo["grid"].update(linewidth=0)
ax.zaxis._axinfo["grid"].update(linewidth=0)
ax.xaxis._axinfo['tick']['outward_factor'] = 0 # Remove extra tick lines
ax.yaxis._axinfo['tick']['outward_factor'] = 0
ax.zaxis._axinfo['tick']['outward_factor'] = 0
ax.grid(True)
# Clear and redraw distance vs. time plot
ax_dist.clear()
for i in range(len(sensors)):
ax_dist.plot(time_steps, distance_history[i], label=f'Sensor {i + 1}')
ax_dist.set_xlabel('Time (s)', fontsize=24, fontname='Arial', labelpad=15)
ax_dist.set_ylabel('Distance to Charge (m)', fontsize=24, fontname='Arial', labelpad=15)
ax_dist.tick_params(axis='both', which='major', labelsize=24)
plt.pause(0.01) # Pause to allow real-time update
plt.tight_layout()
# Run the real-time tracking simulation
track_bouncing_charge()
The output looks like:
The left image shows how the ball moves, and the right shows the distance of the ball to the corners.
My question is : How to remove the extra 2D coordinate axes around the 3D plot? This is a simulation of a ball moving inside a box.
I have a program simulating the movement of a ball inside a box, and tracking it using matplotlib:
def track_bouncing_charge():
fig, (ax, ax_dist) = plt.subplots(1, 2, figsize=(14, 6))
ax = fig.add_subplot(121, projection='3d') # 3D plot
sensor_labels = [f"Sensor {i + 1}" for i in range(len(sensors))]
time_steps = []
distance_history = [[] for _ in range(len(sensors))]
time_counter = 0
# Initial charge position and velocity
charge_pos = np.array([0.5, 0.5, 0.5])
velocity = np.array([0.02, 0.03, 0.01])
dt = 0.1 # Time step
while True:
# Update charge position (simulate bouncing off walls)
charge_pos += velocity * dt
for i in range(3):
if charge_pos[i] <= 0 or charge_pos[i] >= 1:
velocity[i] = -velocity[i] # Reverse direction on collision
charge_pos[i] = max(0, min(1, charge_pos[i])) # Keep within bounds
# Generate synthetic electric field data
sensor_readings = np.array([compute_electric_field(sensor, charge_pos) for sensor in sensors])
# Solve for charge position using least squares optimization
initial_guess = np.array([0.5, 0.5, 0.5])
result = least_squares(error_function, initial_guess, args=(sensor_readings,))
estimated_position = result.x
# Compute distances
distances = [np.linalg.norm(sensor - estimated_position) for sensor in sensors]
time_steps.append(time_counter)
time_counter += dt
for i in range(len(sensors)):
distance_history[i].append(distances[i])
# Clear and redraw 3D plot
ax.clear()
ax.scatter(sensors[:, 0], sensors[:, 1], sensors[:, 2], c='b', marker='o', label='Sensors')
ax.scatter(charge_pos[0], charge_pos[1], charge_pos[2], c='g', s=200, label='True Charge Position')
# Draw lines connecting sensors to the estimated charge position with varying color intensity based on distance
for sensor in sensors:
distance = np.linalg.norm(sensor - estimated_position)
ax.plot([sensor[0], estimated_position[0]],
[sensor[1], estimated_position[1]],
[sensor[2], estimated_position[2]],
'r.-.', lw=min(20, 2 / distance), alpha=min(0.5, 0.5 / distance))
ax.set_xlabel('X Position (m)', fontsize=24, fontname='Arial', labelpad=20)
ax.set_ylabel('Y Position (m)', fontsize=24, fontname='Arial', labelpad=20)
ax.set_zlabel('Z Position (m)', fontsize=24, fontname='Arial', labelpad=20)
ax.tick_params(axis='both', which='major', labelsize=24)
# Remove bounding box lines while keeping the grid
for axis in [ax.xaxis, ax.yaxis, ax.zaxis]:
axis._axinfo["grid"].update(linewidth=0.5) # Keep grid lines but make them subtle
axis._axinfo["grid"].update(color=(0.7, 0.7, 0.7, 0.7)) # Light grey grid
# Hide the extra bounding box lines
ax.xaxis._axinfo["grid"].update(linewidth=0)
ax.yaxis._axinfo["grid"].update(linewidth=0)
ax.zaxis._axinfo["grid"].update(linewidth=0)
ax.xaxis._axinfo['tick']['outward_factor'] = 0 # Remove extra tick lines
ax.yaxis._axinfo['tick']['outward_factor'] = 0
ax.zaxis._axinfo['tick']['outward_factor'] = 0
ax.grid(True)
# Clear and redraw distance vs. time plot
ax_dist.clear()
for i in range(len(sensors)):
ax_dist.plot(time_steps, distance_history[i], label=f'Sensor {i + 1}')
ax_dist.set_xlabel('Time (s)', fontsize=24, fontname='Arial', labelpad=15)
ax_dist.set_ylabel('Distance to Charge (m)', fontsize=24, fontname='Arial', labelpad=15)
ax_dist.tick_params(axis='both', which='major', labelsize=24)
plt.pause(0.01) # Pause to allow real-time update
plt.tight_layout()
# Run the real-time tracking simulation
track_bouncing_charge()
The output looks like:
The left image shows how the ball moves, and the right shows the distance of the ball to the corners.
My question is : How to remove the extra 2D coordinate axes around the 3D plot? This is a simulation of a ball moving inside a box.
Share Improve this question edited Mar 16 at 10:33 Jenny 6833 silver badges16 bronze badges asked Mar 13 at 9:05 Renyun ZhangRenyun Zhang 273 bronze badges1 Answer
Reset to default 2The issue here is that you are first creating two 2D Axes with plt.subplots
, referenced respectively by ax
and ax_dist
. Then you create a new 3D Axes with fig.add_subplot
, which you place over the first set of 2D Axes (which still exists), and replace the ax
reference by this new Axes. The solution is to not create the first of the two 2D Axes.
Replacing:
fig, (ax, ax_dist) = plt.subplots(1, 2, figsize=(14, 6)) # Creates 2 2D Axes
ax = fig.add_subplot(121, projection='3d') # Creates a 3D Axes over the first 2D Axes
by:
fig = plt.figure(figsize=(14,6)) # Creates the figure
ax = fig.add_subplot(121, projection='3d') # Creates the 3D Axes
ax_dist = fig.add_subplot(122) # Creates the distance 2D Axes
does the trick.