How to drag two 3D axes at once
Asked Answered
P

2

12

Basically I have two 3d axes in one figure, one animated built through matplotlib.aninmation and one 3d line plot, side by side. I'd like to add functionality so that when you rotate one axis, the other follows it's rotation; so for example a function that sends the current viewing angle to the other axis like;

angle1 = getviewingangle(ax1) 
ax2.view_init(angle1)
angle2 = getviewngangle(ax2)
ax1.view_init(angle2) 

etc. This is for comparing the animated path of a particle with it's pre-plotted trajectory.

Purism answered 15/12, 2016 at 14:58 Comment(0)
P
19

In order to synchronize the rotation between two subplots in mplot3d you can connect the motion_notify_event to a function that reads the angles from rotated plot and applies it to the respective other plot.

Here is an example from the gallery with the described functionality added.

from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np

n_angles = 36
n_radii = 8

radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)

x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
z = np.sin(-x*y)

fig = plt.figure( figsize=(13,6))
fig.subplots_adjust(left=0, right=1, top=1, bottom=0, wspace=0)
ax = fig.add_subplot(1, 2, 1, projection='3d')
ax2 = fig.add_subplot(1, 2, 2, projection='3d')

ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.2)
ax2.plot_trisurf(x, y, z, cmap=cm.viridis, linewidth=0.5)

def on_move(event):
    if event.inaxes == ax:
        ax2.view_init(elev=ax.elev, azim=ax.azim)
    elif event.inaxes == ax2:
        ax.view_init(elev=ax2.elev, azim=ax2.azim)
    else:
        return
    fig.canvas.draw_idle()

c1 = fig.canvas.mpl_connect('motion_notify_event', on_move)


plt.show()

It may make sense to additionally synchronize the zooming utility as well. In this case one may use the following function

def on_move(event):
    if event.inaxes == ax:
        if ax.button_pressed in ax._rotate_btn:
            ax2.view_init(elev=ax.elev, azim=ax.azim)
        elif ax.button_pressed in ax._zoom_btn:
            ax2.set_xlim3d(ax.get_xlim3d())
            ax2.set_ylim3d(ax.get_ylim3d())
            ax2.set_zlim3d(ax.get_zlim3d())
    elif event.inaxes == ax2:
        if ax2.button_pressed in ax2._rotate_btn:
            ax.view_init(elev=ax2.elev, azim=ax2.azim)
        elif ax2.button_pressed in ax2._zoom_btn:
            ax.set_xlim3d(ax2.get_xlim3d())
            ax.set_ylim3d(ax2.get_ylim3d())
            ax.set_zlim3d(ax2.get_zlim3d())
    else:
        return
    fig.canvas.draw_idle()
Pushy answered 16/12, 2016 at 18:35 Comment(1)
have you thought of adding a shareaxes kind of functionality as a PR to matplotllib for 3d plots (sort of like sharex for 2d)? THis would be really cool.Kantian
G
3

This has been added as a feature and will be released in matplotlib v3.8.0: https://github.com/matplotlib/matplotlib/pull/25821

You can now link view angles for 3D plots with ax1.shareview(ax2)

Update 2023-09-14: Matplotlib v3.8.0 was released today and this feature is now available.

Gettings answered 9/5, 2023 at 12:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.