Python pour le calcul scientifique/Graphiques

Rappel : les programmes commencent par :

#!/usr/bin/python3

import numpy as np
import matplotlib.pyplot as plt

Nous avons déjà vu un premier tracé élémentaire : Découverte de Python et de Jupyter > Premier tracé graphique ; nous en présentons un autre plus loin : Statistiques > Fréquence, histogramme.

x = np.arange(0, 2, 0.1)
y = x**2

plt.plot(x, y, label="y = x^2")
plt.xlabel("x")
plt.ylabel("y")
plt.title("Graphes de fonctions")
plt.legend()

plt.show()
M = np.random.randn(50)
plt.plot(M, np.ones_like(M), "|")
plt.hist(M, bins=10, density=1)

plt.show()

Nous allons approfondir le sujet.

Concepts de base

modifier

Exemple

modifier
 
Nom des différents objets constituant une figure de Python/Matplotlib.

Considérons un tracé un peu plus avancé : il consiste en deux graphiques représentant les graphes des fonctions cosinus et sinus. Voici deux manières de le réaliser :

x = np.linspace(0, 2*np.pi, 50)
ycos = np.cos(x)
ysin = np.sin(x)

fig, liste_axes = plt.subplots(2, 1, constrained_layout=True)

fig.suptitle("Fonctions trigonométriques")

liste_axes[0].plot(x, ycos)
liste_axes[0].set_title("cosinus")
liste_axes[0].set_xlabel("x")
liste_axes[0].set_ylabel("y")

liste_axes[1].plot(x, ysin)
liste_axes[1].set_title("sinus", pad = 5)
liste_axes[1].set_xlabel("x")
liste_axes[1].set_ylabel("y")

plt.savefig("fonctions_trigo_pyplot.svg", format="svg")

Ou bien

x = np.linspace(0, 2*np.pi, 50)
ycos = np.cos(x)
ysin = np.sin(x)

plt.subplots(constrained_layout=True)
plt.suptitle("Fonctions trigonométriques")

plt.subplot(2, 1, 1)
plt.plot(x, ycos)
plt.title("cosinus")
plt.xlabel("x")
plt.ylabel("y")

plt.subplot(2, 1, 2)
plt.plot(x, ysin)
plt.title("sinus", pad = 5)
plt.xlabel("x")
plt.ylabel("y")

Analyse

modifier

Première manière

modifier

La création de deux graphiques se fait avec la commande plt.subplot() à laquelle on indique le fait qu'il y a 2 lignes et 1 colonne ; le paramètre constrained_layout=True permet d'avoir une meilleure mise en page. Cette commande retourne deux identifiants : fig, l'identifiant de la figure, et liste_axes, les identifiants des deux sous-figures.

La méthode .suptitle() permet de créer un titre général. Pour chaque sous-figure (classe Axes, que l'on pourrait traduire par « système d'axes »), on utilise les méthodes .plot()pour tracer une courbe, .set_title() pour lui donner un nom et les méthodes .set_xlabel() et .set_ylabel() pour les titres des axes.

La fonction plt.savefig() permet d'enregistrer la figure dans un fichier, ici au format SVG.

Au lieu d'utiliser une matrice liste_axes, on peut aussi utiliser un n-uplet :

fig, (axes0, axes1) = plt.subplots(2, 1, constrained_layout=True)
[]
axes0.plot(x, ycos)
[]
axes1.plot(x, ysin)

L'image est constituée de plusieurs objets. Il y en a de deux types :

  • les contenants qui permettent la mise en page : Figure (la figure)[1] et Axes (les systèmes d'axes)[2] ;
  • les primitives : ce sont les objets qui sont tracés à l'écran, essentiellement les Line2D (les courbes)[3].

La Figure est un grand contenant, c'est lui qui contient tout le dessin ; on parle parfois de « toile » (canvas). Les Axes sont les différentes sous-figures ; il y en a ici 2. Les axis (« axe » en français) — à ne pas confondre avec le terme Axes précédent — sont au nombre de deux par Axes : l'axe de x (XAxis) et l'axe des y (YAxis).

Seconde manière

modifier

Chaque graphique est créé par la fonction plt.subplot(2, 1, 1). Les paramètres sont trois nombres : le nombre de lignes du tableau de figures, le nombre de colonnes de ce tableau, et le numéro de la cellule au sein du tableau en suivant le sens de lecture latin (de gauche à droite et de haut en bas). La création des graphiques est précédée de deux fonctions : plt.subplots(constrained_layout=True) qui impose une répartition harmonieuse des graphiques, et plt.suptitle() qui permet d'écrire un titre général.

Pour chaque graphique, on a les fonctions :

  • plt.plot() qui tracent le graphique ;
  • plt.title() qui donnent un titre à chaque graphique ;
  • plt.xlabel() et plt.ylabel() qui nomment les axes.

Styles de codage « pyplot » et « OO »

modifier

On voit qu'il y a aussi deux styles de codage, deux manières de rédiger. La première est la manière « pyplot » : la figure est décrite comme un automate fini (state machine), elle ne peut avoir qu'un nombre fini d'états et les fonctions font des transitions entre deux états. On utilise aussi l'expression « à la manière de Matlab ». On utilise des fonctions de l'espace de nom matplotlib.pylot (plt), ici : plt.subplots(), plt.savefig() et dans les exemples précédents, plt.plot(), plt.hist(), plt.xlabel(), plt.ylabel(), plt.title(), plt.legend() et plt.show().

La seconde manière est la manière « orientée objet » (OO) : comme nous avons défini des objets (la Figure, les Axes), nous pouvons utiliser des méthodes spécifiques à ces classes. C'est ainsi que nous avons les méthodes fig.suptitle(), liste_axes[:].plot(), .set_title(), .set_xlabel() et .set_ylabel().

Dans la manière OO, il faut donc connaître les identifiants des objets pour pouvoir utiliser leurs méthodes. La manière la plus simple est de créer explicitement les objets et d'assigner leur identifiant à des variables à cette occasion, par exemple :

fig, sysAxes = plt.subplots(1, 1)

courbe = sysAxes.plot(x, y)

ou bien

fig = plt.figure()
sysAxes = fig.add_subplot(1, 1, 1)

courbe = sysAxes.plot(x, y)

ou encore

fig = plt.figure()
sysAxes = plt.axes()

courbe = sysAxes.plot(x, y)

L'autre manière consiste à laisser pyplot tout créer tout seul, puis à récupérer les identifiants :

plt.plot(x, y)

fig = plt.gcf() # get current figure
sysAxes = plt.gca() # get current axes

courbe = sysAxes.lines[0]

ou bien

plt.plot(x, y)

fig = plt.gcf() # get current figure
sysAxes = fig.axes[0]

courbe = sysAxes.lines[0]

Mise en forme d'une courbe

modifier

Considérons une courbe unique. Nous pouvons changer l'apparence de la courbe en passant des paramètres lors de sa création. La première manière consiste à utiliser une chaîne de caractères comprenant :

  • une lettre pour la couleur : k pour le noir (black), y pour jaune (yellow)
  • un ou deux caractères pour le type de trait : - pour un trait continu, -- pour un trait discontinu…
  • un caractère pour la forme des marqueurs : o pour des ronds, un s pour un carré (square), un d pour un losange (diamond), un v ou un ^ pour un triangle pointe en bas ou en haut…

Par exemple :

plt.plot(x, y, "r--") # trait discontinu rouge

axes0 = plt.axes()
axes0.plot(x, y, "go") # cercles verts
Caractères de mise en forme de courbe
Couleur Marqueur
Caractère  Couleur Caractère Symbole
b bleu . point •
g vert , point d'un pixel ⋅
r rouge o cercle ○
c cyan v triangle pointe en bas ▽
m magenta ^ triangle pointe en haut △
y jaune < triangle pointe à gauche ◁
k noir > triangle pointe à droite ▷
w blanc s carré □
Style de ligne p pentagone
Caractère Style 1 à 4 étoile à trois branches de différentes orientations
- ligne continue * étoile ✭
-- ligne discontinue h, H hexagone sur la pointe ⬢ ou sur le plat ⬣
-. trait mixte + croix droite +
: trait pointillé x croix de saint André ×
D, d losange carré ◇ ou étroit ◊
| trait vertical |
_ trait horizontal —

Notez la différence entre ".-" qui affiche à la fois un marqueur point et un trait continu, et "-." qui crée un trait mixte.

Méthodes avancées

Il est possible de passer les paramètres de manière plus explicite :

plt.plot(x, y, color="b", linestyle="dashed", marker="o")

L'attribut color peut être spécifié de plusieurs manières :

  • par une lettre, comme ci-dessus ;
  • par un mot, le nom de la couleur ; il y a une cinquantaine de nom définis comme par exemple blue, lightblue, darkblue, grey, chocolate, fuschia[4]
  • avec le nom de la couleur dans la palette T10 ; les dix noms sont tab:blue, tab:orange, tab:green, tab:red, tab:purple, tab:brown, tab:pink, tab:gray, tab:olive et tab:cyan ; ce sont les couleurs utilisées par défaut ;
  • par un code RVB, les composante R, V et B étant indiquées en hexadécimal entre 00 et FF : color = "#00FF00" (bleu) ; on peut utiliser le canal alpha en quatrième composante (RVBA) : color = "#00FF0080" ; la composante A va de 00 pour du parfaitement transparent à FF pour du totalement opaque.

L'attribut linestyle peut être indiqué par des caractères comme précédemment ; on peut également utiliser les noms : dashed (discontinu), dashdot (mixte), dotted (pointillés), solid (continu) ou bien None ou une chaîne vide (pas de tracé).

On peut ajouter d'autres attributs, en particulier

  • linewidth qui définit l'épaisseur du trait ;
  • dashes : permet de définir un trait discontinu avec un doublet (2-uplet) indiquant le nombre de point affichés et le nombre de points non-affichés, par exemple dashes=(4, 2) ; on peut alterner plusieurs couple comme par exemple dashes=(8, 2, 4, 2)
  • markeredgecolor : couleur du trait du symbole marqueur ;
  • markeredgewidth : épaisseur du trait du symbole marqueur (en points) ;
  • markerfacecolor : couleur de remplissage du symbole marqueur ;
  • fillstyle : permet de ne remplir qu'une moitié du symbole marqueur ; prend les valeurs top, right, bottom et left pour indiquer la moitié remplie ;
  • markersize : taille du symbole marqueur (en points).


Avec le style de codage orienté objet, cela donne :

courbe = plt.plot(x, y)
courbe[0].set_linestyle("--")

# ou bien

courbe, = plt.plot(x, y)
courbe.set_linestyle("--")

# ou encore

plt.plot(x, y)
axes = plt.gca()
courbe = axes.lines[0]
courbe.set_linestyle("--")

Les principales méthodes des objets Line2D sont : .set_color(), .set_linestyle(), .set_linewidth(), .set_dashes(), .set_marker() et tous les .set_marker…() (edgecolor, edgewidthfacecolor, size).

Ressources

Annotations

modifier

Nous parlons ici le fait de placer des annotations sur une figure et non pas la notion d'annotation en programmation.

Il est possible de placer du texte avec la commande plt.text(x, y, chaine) où (x, y) sont les coordonnées, dans le repère du tracé, où l'on veut placer le texte, et chaine est une chaîne de caractères. On peut modifier l'orientation de ce texte avec le paramètre rotation exprimé en degrés, par exemple :

plt.text(0, 0, "origine", rotation=90)

Par ailleurs, la classe Axes dispose d'une méthode .annotate(). Dans sa forme la plus simple, on indique une chaîne correspondant à l'étiquette et un doublet (2-uplet) ou une liste indiquant les coordonnées où placer l'étiquette. Par exemple :

x = np.linspace(0, 2*np.pi, 50)
ycos = np.cos(x)

courbe = plt.plot(x, ycos)

fig = plt.gcf() # get current figure
sysAxes = plt.gca() # get current axes

ymin = ycos.min() # trouve la veleur minimale de ycos
imin = ycos.argmin() # trouve l'indice correspondant au minimum
xmin = x[imin]

sysAxes.annotate("min", (xmin, ymin))

Telle quelle, l'étiquette empiète sur la courbe. On peut placer l'étiquette à des coordonnées particulières avec l'attribut xytext cet attribut reçoit un doublet ou une liste de coordonnées (x, y).

Par ailleurs, on peut relier l'étiquette au point visé avec l'attribut arrowsprop (arrow's properties, propriétés de la flèche). Cet attribut reçoit un dictionnaire de propriétés, arrowsprop=dict(…) ou bien arrowsprop={…}. Par exemple, pour avoir simplement un trait, ce dictionnaire contient arrowstyle="-" et pour une flèche, arrowstyle="->".

Par exemple, avec l'exemple précédent :

sysAxes.annotate("min", (xmin, ymin), xytext=(xmin+0.5, ymin+0.5), arrowprops=dict(arrowstyle="->"))
# ou bien
sysAxes.annotate("min", (xmin, ymin), xytext=(xmin+0.5, ymin+0.5), arrowprops={"arrowstyle":"-"})
Ressource
(en) « matplotlib.axes.Axes.annotate », sur Matplotlib (consulté le 7 avril 2019)

Autres exemples de tracés 2D

modifier
 
Trois types de tracés de la fonction sinus avec Python/Matplotlib

Par exemple avec :

x = np.linspace(0, 2*np.pi, 20)
y = np.sin(x)

fig, listeAxes = plt.subplots(3, 1, constrained_layout=True)

# Tracé en escalier
listeAxes[0].step(x, y)

# tracé en bâtons
listeAxes[1].bar(x, y, width = 0.03)

# Tracé en flèches
listeAxes[2].quiver(x[:-1], y[:-1], np.diff(x), np.diff(y), scale_units="xy", scale=1, angles="xy", width=0.02)

Gestion des échelles

modifier

Pour fixer les valeurs extrêmes des axes, on utilise les commandes plt.xlim(min, max) et plt.ylim(min, max).

Pour avoir une échelle logarithmique, respectivement en x et en y, on utilise les commandes plt.xscale("log") et plt.yscale("log"). Ces fonctions peuvent recevoir les paramètres suivants :

  • linear : échelle linéaire (valeur par défaut) ;
  • log : échelle logarithmique ;
  • symlog : échelle logarithmique symétrique, c'est-à-dire avec la transformation   avec C = 1/ln(10) ; nous avons ainsi des valeurs positives et négative sans avoir la singularité en 0 ;
  • logit : l'échelle est transformée par la fonction logit.

On peut superposer une grille avec la commande plt.grid(True).

S'il y a trop de graduations sur un axe, on peut réduire le nombre de graduations avec :

from matplotlib.ticker import NullFormatter
# […]
plt.gca().yaxis.set_minor_formatter(NullFormatter())

On peut imposer les valeurs graduées :

plt.gca().set_xticks([0, 1, 2, 3])

On peut imposer le fait d'avoir les mêmes échelles en x ou en y avec les mots-clefs sharex et sharey. Par exemple :

fig = plt.figure()
ax1 = fig.add_subplot(2, 1, 1)
ax2 = fig.add_subplot(2, 1, 2, sharex=ax1)

ou bien

fig, axs = plt.subplots(2,1, sharex=True)

Si l'on veut que l'échelle en x soit la même que l'échelle en y, on peut utiliser la méthode set_aspect()[5] des objets axes, par exemple

plt.gca().set_aspect("equal", adjustable="box")

Si l'on veut que le graphique soit carré, on utilise le mot-clef square de l'objet axis :

plt.axis("square")

Si l'on veut que les échelles sur les axes x et y soient de pas identiques, par exemple pour qu'un carré soit un carré et un demi-cercle ne soit pas une demie ellipse, on utilise :

plt.gca().set_aspect("equal", adjustable="box")

Plusieurs courbes sur un même système d'axes

modifier

Pour mettre plusieurs courbes sur un même système d'axes, on peut soit les créer les unes après les autres :

plt.plot(x1, y1)
plt.plot(x2, y2)

Soit les mettre dans la même commande :

plt.plot(x1, y1, x2, y2)

Matplotlib définit lui-même des couleurs différentes. On peut bien évidemment forcer le style des courbes. On peut également ajouter une étiquette (label) qui sera ensuite affichée dans la légende :

plt.plot(x1, y1, "k", label="courbe 1")
plt.plot(x2, y2, "b", label="courbe 1")
plt.legend()

# ou bien
courbes = plt.plot(x1, y1, "k", x2, y2, "b")
courbes[0].set_label("courbe 1")
courbes[1].set_label("courbe 2")
plt.legend()

# ou bien
courbes = plt.plot(x1, y1, x2, y2)
courbes[0].set(color="k", label="courbe 1")
courbes[1].set(color="b", label="courbe 2")
plt.legend()

On peut choisir l'emplacement de la légende avec le paramètre loc qui peut prendre les valeurs "upper left", "upper center", "upper right", "center left"… la valeur est composée de deux adjectifs :

  • premier adjectif :
    • center signifie au centre, au milieu,
    • upper signifie en haut,
    • lower signifie en bas ;
  • second adjectif :
    • center signifie au centre, au milieu,
    • left signifie à gauche,
    • right signifie à droite.

Par exemple :

plt.legend(loc="upper right")

Si l'on veut des courbes avec des échelles en y différentes, il faut créer un axe x « jumeau » (twin) avec la méthode .twinx(). Par exemple :

fig, axe = plt.subplots(figsize=(5, 5))
plt.suptitle("Chargement")
axe2 = axe.twinx()

axe.plot(forces)
axe.set_xlabel("Étape")
axe.set_ylabel("F (N)")

axe2.plot(contraintes)
axe2.set_ylabel("σ (MPa)")

Tracé polaire

modifier
 
Exemple de courbe polaire avec Python/Matplotlib.
 
Rose des vents avec des données arbitraires, utilisé pour un exemple de création de graphique avec le langage Python/Matplotlib.

Un tracé polaire s'obtient avec la commande plt.polar(), par exemple :

theta = np.linspace(0, 2*np.pi, 20) # angle
r = 1 + 0.2*np.random.randn(20) # rayon

plt.polar(theta, r) # courbe polaire

Voici un exemple avec deux courbes superposées et une mise en forme des graduations et de la légende :

#!/usr/bin/python3

import numpy as np
import matplotlib.pyplot as plt

# **************
# * Constantes *
# **************

# positions angulaires
angles = 2*np.pi*np.linspace(0, 1, 9) # huitièmes de cercle
radDeg = 180/np.pi # conversion radians → degrés

etoile = np.array([1, 0.2, 1, 0.2, 1, 0.2, 1, 0.2, 1]) # Tracé de l'étoile
vents = np.array([2.85, 3.34, 5.2, 4.025, 4.9, 9.63, 7.5, 2.35, 2.85]) # Fréquence (unité arbitraire)
vents = vents/vents.max() # normalisation

# ***********
# * Traçage *
# ***********

courbes = plt.polar(angles, etoile, angles, vents) # tracé

# mise en forme
axes = plt.gca()
courbes[0].set(color="gray")
courbes[1].set(color="b", label="Fréquence des vents")
plt.legend(bbox_to_anchor=(1.45, 1.05), loc="upper right") # affichage à l'extérieur du tracé
axes.set_rticks([0.2, 0.4, 0.6, 0.8, 1]) # graduations radiales
axes.set_thetagrids(angles=radDeg*angles[0:-1], labels=("E", "NE", "N", "NO", "O", "SO", "S", "SE")) # graduations angulaires
plt.title("Rose des vents")

plt.savefig("rose_des_vents.svg", format="svg")

plt.show()

Cartes de valeurs et de vecteurs

modifier
 
Lignes de niveau et cartes couleur de z = sin(x) × sin(y) par Python/Matplotlib.

Lorsque l'on a une surface dans un espace à trois dimensions, de type z = ƒ(x&htinsp;, y ), on peut afficher les données sur la forme d'une carte :

  • lignes de niveau : on utilise la fonction np.contour() ;
  • surfaces de niveau : np.contourf() ;
  • carte de couleurs : np.pcolormesh().

La fonction colorbar() permet d'afficher l'échelle de couleurs.

Par exemple :

x = np.linspace(0, 2*np.pi, 20)
y = x.copy()
X, Y = np.meshgrid(x, y)

Z = np.sin(X)*np.sin(Y)

fig, liste_axes = plt.subplots(2, 2, constrained_layout=True)

im1 = liste_axes[0, 0].contour(X, Y, Z)
fig.colorbar(im1, ax=liste_axes[0, 0])

im2 = liste_axes[0, 1].contourf(X, Y, Z)
fig.colorbar(im2, ax=liste_axes[0, 1])

im3 = liste_axes[1, 0].pcolormesh(X, Y, Z)
fig.colorbar(im3, ax=liste_axes[1, 0])

On peut choisir d'autres échelles de couleur (color map) avec le paramètre cmpa :

im3 = liste_axes[1, 0].pcolormesh(X, Y, Z, cmap="Greens")

Les échelles de couleur disponibles sont données à la page :

(en) « Colors in matplotlib », sur matplotlib (consulté le 5 avril 2019).
 
Champ de vecteur tracé par Python/Matplotlib.

On peut aussi faire une carte d'un champ de vecteurs avec la commande plt.quiver() vue précédemment :

base = np.arange(10)
x, y = np.meshgrid(base, base)
ux = -0.1*y
uy = 0.1*x
plt.quiver(x, y, ux, uy, scale_units="xy", scale = 1, angles="xy")

Surfaces 3D

modifier
 
Surface z = sin(x) × sin(y) tracée avec Python/Matplotlib.

Il est également possible de tracer des surfaces 3D. Il faut pour cela charger Axes3D du module mpl_toolkits.mplot3d ; cela donne accès au paramètre projection="3d" lorsque l'on définit le système d'axes. Par exemple :

from mpl_toolkits.mplot3d import Axes3D # permet d'utiliser l'attribut projection="3d"

x = np.linspace(0, 2*np.pi, 20)
y = x.copy()
X, Y = np.meshgrid(x, y)

Z = np.sin(X)*np.sin(Y)

plt.figure()
ax = plt.axes(projection="3d")

ax.contour(X, Y, Z, zdir="z", offset=-1) # projection sur le plan z = -1
ax.plot_surface(X, Y, Z, alpha=0.8)

On peut changer l'angle de vue du système d'axes avec les paramètres elev (élévation) etazim (azimut). Par exemple

ax = plt.axes(projection="3d", elev = 10, azim = 90)

Mise en forme de la figure

modifier

La figure dispose de paramètres de mise en forme :

  • figsize : liste des dimensions (largeur, hauteur) en pouces (valeur par défaut [6.4, 4.8]) ;
  • dpi : résolution en points par pouce (valeur par défaut 100) ;
  • facecolor : couleur de fond (valeur par défaut white) ;
  • edgecolor : couleur du filet de bordure (valeur par défaut white) ;
  • layout : définit le placement des objets pour éviter les effets indésirables (par exemple le recouvrement, valeur par défaut none).

Par exemple :

M = np.random.randn(50)

fig = plt.figure(figsize = [10, 6])
plt.plot(M)

Mise en forme du texte

modifier

Si l'on veut utiliser le style LaTeX, on utilise :

plt.rcParams["text.usetex"] = True
[]
plt.xlabel("$x$")

Dessins

modifier

Les dessins portent le nom de « pièces » (patch). Les fonctions pour les créer se trouvent dans le module matplotlib.patches. Ces fonctions permettent de créer les pièces mais pour les dessiner (c'est-à-dire les mettre sur la « toile »), il faut utiliser la méthode .add_patch().

Par exemple :

from matplotlib.patches import Circle, Polygon

fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1]) # système d'axes occupant toute la figure
ax.set_xlim(0, 2) # axes gradués de 0 à 2
ax.set_ylim(0, 2)

# Création des pièces
cercle = Circle((0.25, 0.25), 0.1) # cercle de centre (0,25 ; 0,25) et de rayon 0,1
polygone = Polygon([[1.5, 1.5], [2, 1.5], [1.75, 2]]) # polygone à trois sommets (triangle)

# Dessin des pièces
ax.add_patch(cercle)
ax.add_patch(polygone)

Le dessin des pièces une par une est un processus assez long. Il est intéressant de créer une collection et de dessiner cette collection d'un seul tenant. On a pour cela recours au module matplotlib.collections. Le code devient donc :

from matplotlib.patches import Circle, Polygon
from matplotlib.collections import PatchCollection

fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1])

ax.set_xlim(0, 2)
ax.set_ylim(0, 2)

cercle = Circle((0.25, 0.25), 0.1)

polygone = Polygon([[1.5, 1.5], [2, 1.5], [1.75, 2]])

coll = PatchCollection((cercle, polygone))
ax.add_collection(coll)

Il est possible de dessiner des chemins (path).


Pour plus de détails voir : Découvrir le SVG/Chemins.

On a pour cela recours au module matplotlib.path. Par exemple :

from matplotlib.patches import PathPatch
from matplotlib.path import Path

fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1])

ax.set_xlim(0, 2)
ax.set_ylim(0, 2)

# Création du chemin
chemin = Path([[0.25, 0.5], [0.25, 1.5], [0.75, 1.5]],
  [Path.MOVETO, Path.CURVE3, Path.CURVE3])

# Création de la pièce à partir du chemin
pieceChemin = PathPatch(chemin)

# Dessin de la pièce
ax.add_patch(pieceChemin)

Ici, nous disons à la « plume » de se rendre (move to) au point de coordonnées (0,25 ; 0,25), puis de tracer une courbe de Bézier quadratique (curve 3) jusqu'au point (0,75 ; 1,5) avec comme point de contrôle intermédiaire (0,25 ; 1,5).

Transformation des objets graphiques

modifier

Il est possible de faire subir des transformations linéaires aux objets graphiques[6]. On utilise pour cela le module matplotlib.transforms (au pluriel) et l'attribut transform (au singulier) des commandes pyplot.

Par exemple, pour tourner un objet de 90° :

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms as transforms

donnees = np.random.randn(20) # points au hasard

base = plt.gca().transData # base de la transformation
rotation = transforms.Affine2D().rotate_deg(90) # rotation de 90°

plt.plot(donnees, transform = rotation + base)

La transformation transforms.Affine2D() a normalement pour argument une matrice 3 × 3 classique en géométrie. Lorsque l'on est en deux dimensions, cette matrice a la forme

 
Pour plus de détails voir : w:Matrice de transformation.

Les méthodes d'Affine2D() permettent de générer automatiquement cette matrice. Par exemple, transforms.Affine2D().rotate(θ) (angle θ en radians ; on utilise .rotate_deg() pour des angles en degrés) crée la matrice de rotation

 

On peut donner directement la matrice M (de type np.array() avec transforms.Affine2D().set_matrix(M). On peut indiquer les valeurs sous la forme d'une liste avec transforms.Affine2D().from_values(a, c, b, d, 0, 0) (notez l'ordre des paramètres).

Créer un GIF animé

modifier

Il existe plusieurs modules permettant de créer un GIF animé dans Python.

Avec le module PIL (Python imaging library)

modifier

La première étape consiste à créer un n-uplet contenant les noms de tous les fichiers image, dans l'ordre ; nous l'appelons imgs. Puis, nous créons un n-uplet contenant les pointeurs vers les images, ouverte avec PIL.Images.open() ; nous l'appelons trames. Nous créons le fichier GIF animé en utilisant la méthode .save() du n-uplet trames

from PIL import Image

imgs=[] # initialisation du n-uplet

for i in range(10): # nom des images : "fichieri.png", i étant un entier ("fichier0.png", "fichier1.png" etc.)
    imgs.append("fichier"+str(i)+".png")

trames = []
for i in imgs:
    nouv_trame = Image.open(i)
    trames.append(nouv_trame)

trames[0].save("animation.gif", format="GIF",
    append_images=trames[1:],
    save_all=True,
    duration=300, # durée en milliseconde
    loop=0) # pour boucler ;
Ressources

Les couches de matplotlib

modifier
(en) « Artist tutorial », sur Matplotlib (consulté le 29 mars 2019)
(en) « Artist API », sur Matplotlib (consulté le 29 mars 2019)

Pour bien comprendre la documentation du module matplotlib, il faut comprendre sa structure en couches. Cela n'est pas indispensable pour l'utilisation élémentaire du module, mais connaître ce vocabulaire devient important pour une utilisation avancée.

Le module matplotlib est organisé en trois couches :

  • la toile (FigureCanvas) : c'est l'espace sur lequel le tracé est fait ;
  • le restituteur (Renderer) : c'est l'objet chargé d'effectuer les tracés sur la toile ;
  • les artistes (Artist) : ce sont les classes des objets destinés à être tracés.

Il faut distinguer le tracé (plot), qui consiste à traduire les données en objets à tracer en utilisant les artistes, et la publication (show) qui consiste à afficher les tracés à l'écran ou à les écrire dans un fichier. La publication est le rôle de la « face cachée » (backend).

Cette structure en trois couches facilite la tâche de l'utilisateur : vous n'avez à vous concentrer qu'aux données et au type et style de tracé (en manipulant les artistes), la face cachée s'occupe de gérer la partie logicielle et matérielle (différence de système d'exploitation, de carte graphique, de format de fichier). La face cachée est donc une couche « abstraite », que l'utilisateur ne voit quasiment jamais.

Dans les cas les plus simples, l'utilisation d'un artiste provoque aussi sa publication : si l'on fait un .plot(), la courbe s'affiche à l'écran. Mais dans certains cas, il faut indiquer à Python de publier le graphique ; par exemple, s'il y a des opérations de tracé longues à faire, on a intérêt à tout afficher à la fin, une fois toutes les opérations faites. C'est la raison d'être des fonctions et méthodes .show() ou .draw().

On distingue ainsi deux types d'artistes : les contenants et les primitives. Nous avons ainsi l'organisation suivante :

  • face cachée (backend)
    • toile (FigureCanvas)
    • restituteur (Renderer)
  • artistes (Artist)
    • conteneurs (container)
      • figure (Figure)
        • systèmes d'axes (Axes)
          • axes (axis)
            • graduations (ticks)
          • légendes (legend)
    • primitives (primitive)
      • courbes (Line2D)
      • textes (Text)
      • bords (Spine)
      • pièces (Patch)

Notes et références

modifier
  1. « matplotlib.figure », sur Matplotlib.org (consulté le 3 mai 2022).
  2. « matplotlib.axes », sur Matplotlib.org (consulté le 3 mai 2022).
  3. « matplotlib.lines.Line2D », sur Matplotlib.org (consulté le 3 mai 2022).
  4. voir (en) « Specifying colors », sur Matplotlib (consulté le 30 mars 2019).
  5. « matplotlib.axes.Axes.set_aspect », sur Matplotlib.org (consulté le 3 mai 2022)
  6. « matplotlib.transforms », sur Matplotlib.org (consulté le 14 avril 2023).

Éléments de programmation < > Manipulation de matrices