J'apprends à utiliser mplot3d pour produire de beaux graphiques de données 3D et je suis assez satisfait jusqu'à présent. Ce que j'essaie de faire en ce moment est une petite animation d'une surface en rotation. Pour cela, j'ai besoin de définir une position de caméra pour la projection 3D. Je suppose que cela doit être possible puisqu'une surface peut être tournée à l'aide de la souris lorsque l'on utilise matplotlib de manière interactive. Mais comment puis-je faire cela à partir d'un script ? J'ai trouvé beaucoup de transformations dans mpl_toolkits.mplot3d.proj3d mais je n'ai pas pu trouver comment les utiliser pour mon but et je n'ai pas trouvé d'exemple pour ce que j'essaie de faire.
Réponses
Trop de publicités?Par "position de la caméra", il semble que vous souhaitiez régler l'angle d'élévation et l'angle d'azimut que vous utilisez pour visualiser le tracé 3D. Vous pouvez les définir avec ax.view_init
. J'ai utilisé le script ci-dessous pour créer d'abord le tracé, puis j'ai déterminé une bonne élévation, ou elev
d'où je peux voir ma parcelle. J'ai ensuite ajusté l'angle d'azimut, ou azim
Je me suis servi de mon ordinateur pour faire varier les 360 degrés autour de ma parcelle, en sauvegardant la figure à chaque fois (et en notant l'angle d'azimut lorsque j'ai sauvegardé la parcelle). Pour un panoramique de caméra plus compliqué, vous pouvez ajuster à la fois l'élévation et l'angle pour obtenir l'effet désiré.
from mpl_toolkits.mplot3d import Axes3D
ax = Axes3D(fig)
ax.scatter(xx,yy,zz, marker='o', s=20, c="goldenrod", alpha=0.6)
for ii in xrange(0,360,1):
ax.view_init(elev=10., azim=ii)
savefig("movie%d.png" % ii)
Ce qui serait pratique, c'est d'appliquer la position de la caméra à un nouveau tracé. Ainsi, je trace, puis je déplace le tracé avec la souris en modifiant la distance. J'essaie ensuite de reproduire la vue, y compris la distance, sur un autre graphique. Je trouve que axx.ax.get_axes() me donne un objet avec les anciens .azim et .elev.
EN PYTHON...
axx=ax1.get_axes()
azm=axx.azim
ele=axx.elev
dst=axx.dist # ALWAYS GIVES 10
#dst=ax1.axes.dist # ALWAYS GIVES 10
#dst=ax1.dist # ALWAYS GIVES 10
Plus tard, le graphique 3D...
ax2.view_init(elev=ele, azim=azm) #Works!
ax2.dist=dst # works but always 10 from axx
EDIT 1... OK, la position de la caméra est la mauvaise façon de penser concernant la valeur .dist. Elle se superpose à tout comme une sorte de multiplicateur scalaire pour l'ensemble du graphique.
Cela fonctionne pour le grossissement/zoom de la vue :
xlm=ax1.get_xlim3d() #These are two tupples
ylm=ax1.get_ylim3d() #we use them in the next
zlm=ax1.get_zlim3d() #graph to reproduce the magnification from mousing
axx=ax1.get_axes()
azm=axx.azim
ele=axx.elev
Plus tard Graph...
ax2.view_init(elev=ele, azim=azm) #Reproduce view
ax2.set_xlim3d(xlm[0],xlm[1]) #Reproduce magnification
ax2.set_ylim3d(ylm[0],ylm[1]) #...
ax2.set_zlim3d(zlm[0],zlm[1]) #...
Exemple minimal variant azim
, dist
y elev
Pour ajouter quelques exemples d'images simples à ce qui a été expliqué : https://stackoverflow.com/a/12905458/895245
Voici mon programme de test :
#!/usr/bin/env python3
import sys
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
if len(sys.argv) > 1:
azim = int(sys.argv[1])
else:
azim = None
if len(sys.argv) > 2:
dist = int(sys.argv[2])
else:
dist = None
if len(sys.argv) > 3:
elev = int(sys.argv[3])
else:
elev = None
# Make data.
X = np.arange(-5, 6, 1)
Y = np.arange(-5, 6, 1)
X, Y = np.meshgrid(X, Y)
Z = X**2
# Plot the surface.
surf = ax.plot_surface(X, Y, Z, linewidth=0, antialiased=False)
# Labels.
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
if azim is not None:
ax.azim = azim
if dist is not None:
ax.dist = dist
if elev is not None:
ax.elev = elev
print('ax.azim = {}'.format(ax.azim))
print('ax.dist = {}'.format(ax.dist))
print('ax.elev = {}'.format(ax.elev))
plt.savefig(
'main_{}_{}_{}.png'.format(ax.azim, ax.dist, ax.elev),
format='png',
bbox_inches='tight'
)
L'exécuter sans arguments donne les valeurs par défaut :
ax.azim = -60
ax.dist = 10
ax.elev = 30
principal_-60_10_30.png
Varier azim
L'azimut est la rotation autour de l'axe z, par exemple :
- 0 signifie "chercher à partir de +x"
- 90 signifie "regarder depuis +y"
principal_-60_10_30.png
main_0_10_30.png
principal_60_10_30.png
Varier dist
dist
semble être la distance du point central visible en coordonnées de données.
principal_-60_10_30.png
principal_-60_5_30.png
principal_-60_20_-30.png
Varier elev
A partir de là, nous comprenons que elev
est l'angle entre l'œil et le plan xy.
main_-60_10_60.png
principal_-60_10_30.png
principal_-60_10_0.png
principal_-60_10_-30.png
Testé sur matpotlib==3.2.2.
Essayez le code suivant pour trouver la position optimale de la caméra
Déplacez l'angle de vue du tracé en utilisant les touches du clavier comme indiqué dans la clause if.
Utilisez imprimer pour obtenir les positions des caméras
def move_view(event):
ax.autoscale(enable=False, axis='both')
koef = 8
zkoef = (ax.get_zbound()[0] - ax.get_zbound()[1]) / koef
xkoef = (ax.get_xbound()[0] - ax.get_xbound()[1]) / koef
ykoef = (ax.get_ybound()[0] - ax.get_ybound()[1]) / koef
## Map an motion to keyboard shortcuts
if event.key == "ctrl+down":
ax.set_ybound(ax.get_ybound()[0] + xkoef, ax.get_ybound()[1] + xkoef)
if event.key == "ctrl+up":
ax.set_ybound(ax.get_ybound()[0] - xkoef, ax.get_ybound()[1] - xkoef)
if event.key == "ctrl+right":
ax.set_xbound(ax.get_xbound()[0] + ykoef, ax.get_xbound()[1] + ykoef)
if event.key == "ctrl+left":
ax.set_xbound(ax.get_xbound()[0] - ykoef, ax.get_xbound()[1] - ykoef)
if event.key == "down":
ax.set_zbound(ax.get_zbound()[0] - zkoef, ax.get_zbound()[1] - zkoef)
if event.key == "up":
ax.set_zbound(ax.get_zbound()[0] + zkoef, ax.get_zbound()[1] + zkoef)
# zoom option
if event.key == "alt+up":
ax.set_xbound(ax.get_xbound()[0]*0.90, ax.get_xbound()[1]*0.90)
ax.set_ybound(ax.get_ybound()[0]*0.90, ax.get_ybound()[1]*0.90)
ax.set_zbound(ax.get_zbound()[0]*0.90, ax.get_zbound()[1]*0.90)
if event.key == "alt+down":
ax.set_xbound(ax.get_xbound()[0]*1.10, ax.get_xbound()[1]*1.10)
ax.set_ybound(ax.get_ybound()[0]*1.10, ax.get_ybound()[1]*1.10)
ax.set_zbound(ax.get_zbound()[0]*1.10, ax.get_zbound()[1]*1.10)
# Rotational movement
elev=ax.elev
azim=ax.azim
if event.key == "shift+up":
elev+=10
if event.key == "shift+down":
elev-=10
if event.key == "shift+right":
azim+=10
if event.key == "shift+left":
azim-=10
ax.view_init(elev= elev, azim = azim)
# print which ever variable you want
ax.figure.canvas.draw()
fig.canvas.mpl_connect("key_press_event", move_view)
plt.show()