Graphiques

Un graphique collé dans un article se repère en général du premier coup d’œil. Non parce que la courbe est laide, mais parce que les nombres des axes ne sont pas dans la bonne police. L’outil LaTeX qui règle cela à la racine s’appelle pgfplots : au lieu de recevoir un tracé sous forme d’image, il le construit à partir de nombres avec le même moteur de composition que le texte alentour. Cette page traite de l’environnement axis de pgfplots et de \addplot, de la lecture d’un fichier .dat, du passage du calcul à gnuplot, et de l’import de figures dessinées sous R ou Python en emportant les polices. Au passage, elle cite les messages d’erreur réellement produits par TeX Live 2024 — la plupart de ceux qui arrivent ici par une recherche en ont un sous les yeux.

Pourquoi une image collée ne s’accorde jamais tout à fait à la page

La raison n’est pas la résolution mais la police et la graisse du trait. Un tableur utilise par défaut une linéale alors que l’article est composé en romain à empattements ; un lecteur incapable de nommer l’une ou l’autre perçoit tout de même le décalage. De plus, réduire une image à la largeur du bloc de texte réduit aussi ses traits, si bien que les filets à l’intérieur de la figure deviennent plus fins que ceux du dehors. pgfplots ne reçoit que les coordonnées et place les nombres des graduations et la légende dans la police du document, via son propre traitement des formules, de sorte qu’aucun de ces écarts ne peut se produire. Écrire $\sin x$ dans une étiquette d’axe donne exactement les glyphes des formules du texte.

La seconde chose qui paie en pratique, c’est la séparation d’avec les données. Faites pointer le tracé vers un .dat ou un .csv au lieu de coder les coordonnées en dur : si la mesure est refaite, il suffit de remplacer le fichier et de recompiler, la figure suit. Article, diapositives et annexe lisent les mêmes nombres, si bien qu’aucune erreur de recopie n’est possible. Du côté des tableaux, pgfplotstable applique la même idée. pgfplots lui-même repose sur TikZ/PGF : toute la figure vit dans un tikzpicture et les nœuds et décorations TikZ s’y mêlent librement. TikZ proprement dit — \draw, le système de coordonnées, la lenteur de compilation — relève de la page TikZ.

Que se passe-t-il sans \pgfplotsset{compat=1.18}

On obtient un avertissement et les figures gardent les anciens réglages par défaut. Compilez sans compat avec le pgfplots 1.18.1 livré par TeX Live 2024 et le journal indique : "Package pgfplots Warning: running in backwards compatibility mode (unsuitable tick labels; missing features). Consider writing \pgfplotsset{compat=1.18} into your preamble." L’avertissement donne même le numéro à employer ; il suffit de s’y conformer.

latex
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}   % pin the release whose defaults you want

Ce mécanisme existe parce que pgfplots a modifié ses propres réglages par défaut au fil des versions. Changez le format des étiquettes de graduation ou la place réservée par un axe, et la même source produit une autre figure. compat est la clé qui déclare selon quelle version composer, et garantit que recompiler un manuscrit vieux de cinq ans ne déplacera pas les figures. TeX Live 2024 livre pgfplots 1.18.1, daté du 15 mai 2021 ; compat=1.18 est donc le niveau le plus élevé possible ici. Mettez la ligne dans tout nouveau document et, dans un document existant, ne l’augmentez pas à la légère : les figures risquent aussitôt de bouger.

axis et \addplot : le plus petit tracé qui marche

Placez un environnement axis dans un tikzpicture : chaque \addplot qu’il contient ajoute une courbe. La répartition est nette : tout ce qui concerne le cadre — étiquettes, graduations, grille, position de la légende — est une option de axis, et tout ce qui concerne une courbe donnée — couleur, marqueurs, pointillés — est une option de \addplot. Une fois cette structure à deux niveaux comprise, il ne reste qu’à chercher les noms d’options.

latex
\begin{tikzpicture}
  \begin{axis}[
    xlabel = {$x$},
    ylabel = {$f(x)$},
    title  = {A parabola},
    grid   = major,
  ]
    \addplot[blue, domain=-3:3, samples=100] {x^2};
    \addlegendentry{$x^2$}
  \end{axis}
\end{tikzpicture}

Cela échantillonne la parabole y = x² en 100 points sur −3 ≤ x ≤ 3 et la trace en courbe bleue lisse ; grid=major pose une grille légère sur les graduations principales et \addlegendentry ajoute une ligne de légende. Deux détails à retenir. Toujours mettre étiquettes et titres entre accolades : avec xlabel={$x$}, une virgule ou un ] à l’intérieur ne casse pas l’analyse des options. Et les légendes s’écrivent de deux façons : un \addlegendentry{…} après chaque courbe, ou un legend entries={A,B,...} groupé dans les options de axis ; la position se règle par legend pos=north west et consorts.

OptionRôle
xlabel= / ylabel=Étiquettes des axes x et y ; encadrées comme xlabel={$x$}, les formules passent sans risque
title=Un titre placé au-dessus du tracé
xmin= xmax= ymin= ymax=Fixer la plage visible pour que les axes ne bougent pas quand les données changent
grid=grid=major trace sur les graduations principales, grid=both aussi sur les secondaires
legend pos=Position de la légende : north west, etc. ; outer north east la sort du cadre
xtick=Indiquer les graduations (xtick={0,1,2}) ; xtick=data les aligne sur les données
width= / height=Taille finale de la figure ; width=\linewidth épouse la largeur du texte
ybar / xbarPasser en barres verticales / horizontales ; plusieurs \addplot se groupent automatiquement

Les valeurs par défaut de domain et samples, et pourquoi sin sort faux

Par défaut : 25 points sur l’intervalle −5:5 et trigonométrie en degrés. Le premier point explique aussitôt pourquoi une courbe paraît anguleuse ; le second est la cause la plus fréquente du « mon tracé n’a pas la bonne forme » avec pgfplots. Le plus savoureux est qu’aucune de ces deux valeurs n’appartient à pgfplots : son propre /pgfplots/samples/.initial est vide et la valeur redescend vers la couche PGF sous-jacente. Dans TeX Live 2024, tikz.code.tex porte les deux lignes \def\tikz@plot@samples{25} et \def\tikz@plot@domain{-5:5} : voilà les vrais réglages.

L’affaire des degrés se comprend le plus vite en chiffres. Sous TeX Live 2024, \pgfmathparse{sin(1)} vaut 0.01746 tandis que \pgfmathparse{sin(deg(1))} donne 0.84143. Le premier est le sinus d’un degré, le second le sinus d’un radian, et deg() n’est que le convertisseur radian-degré : deg(1) = 57.29578. Écrire \addplot {sin(x)} place donc sin 1° en x = 1 et produit une onde étrange de période 360. La bonne écriture est sin(deg(x)).

latex
% wrong: pgfmath reads the argument as degrees
\addplot[domain=0:2*pi, samples=200] {sin(x)};
% right: convert radians to degrees first
\addplot[red, domain=0:2*pi, samples=200] {sin(deg(x))};

Trois façons d’alimenter \addplot : fonction, coordonnées ou fichier .dat

Soit une expression dans {...}, soit des points dans coordinates {...}, soit un fichier via table {nom}. Les deux premières sont apparues plus haut. En pratique, c’est la troisième qui sert : pour un fichier texte séparé par des espaces, pgfplots prend la première ligne pour les noms de colonnes et trace par défaut la colonne 1 en x et la colonne 2 en y.

data.dat
x   y
0   0.0
1   0.8
2   0.9
3   0.1
4  -0.8
5  -1.0
latex
\begin{tikzpicture}
  \begin{axis}[xlabel={$x$}, ylabel={$y$}, grid=major]
    \addplot[mark=square, teal] table {data.dat};
    % naming the columns explicitly is the safer habit:
    % \addplot table[x=x, y=y] {data.dat};
  \end{axis}
\end{tikzpicture}

Les noms de colonnes sont sensibles à la casse. Si l’en-tête dit x mais que vous écrivez table[x=X, y=y], TeX Live 2024 s’arrête sur : "! Package pgfplots Error: Sorry, could not retrieve column 'X' from table '...'. Please check spelling (or introduce name aliases)." Comme échec, c’est plutôt aimable : le message conseille lui-même de vérifier l’orthographe ou d’ajouter un alias. Pour un CSV à virgules, écrivez table[col sep=comma, x=x, y=y] {data.csv} ; les lignes commençant par # ou % sont ignorées. Remodeler les données ou créer des colonnes calculées revient au package frère pgfplotstable.

Barres, axes logarithmiques et 3D avec \addplot3

Un diagramme en barres, c’est simplement ybar ajouté à axis ; un axe logarithmique, simplement un autre nom d’environnement à la place de axis ; et la 3D, simplement \addplot3. Ne pas avoir à réapprendre la syntaxe est une vertu de conception de pgfplots : la façon d’écrire \addplot reste identique. Dans un diagramme en barres, plusieurs \addplot se décalent d’eux-mêmes en barres groupées, et si l’axe x doit porter des chaînes comme des années, on associe symbolic x coords et xtick=data.

latex
\begin{tikzpicture}
  \begin{axis}[
    ybar,
    xlabel = {Year}, ylabel = {Count},
    symbolic x coords = {2023, 2024, 2025},
    xtick = data,
  ]
    \addplot coordinates {(2023,40) (2024,55) (2025,72)};
  \end{axis}
\end{tikzpicture}

Pour les axes logarithmiques, changez le nom d’environnement : loglogaxis pour log-log, semilogxaxis pour un x logarithmique seul, semilogyaxis pour y seul. En 3D, \addplot3 rend l’axis tridimensionnel de lui-même ; indiquez surf pour une surface ou mesh pour un maillage et écrivez la fonction avec les deux variables x et y. Le point de vue tourne avec view={azimut}{élévation}.

latex
\begin{tikzpicture}
  \begin{loglogaxis}[xlabel={$x$}, ylabel={$y$}]
    \addplot[domain=1:1000, samples=50] {1/x};
  \end{loglogaxis}
\end{tikzpicture}

\begin{tikzpicture}
  \begin{axis}[xlabel={$x$}, ylabel={$y$}, zlabel={$z$}]
    \addplot3[surf, samples=30, domain=-3:3]
      {exp(-x^2 - y^2)};
  \end{axis}
\end{tikzpicture}

gnuplot n’est pas livré avec TeX Live

Le point est assez important pour être dit d’emblée : utiliser \addplot gnuplot {...} suppose d’installer gnuplot soi-même, séparément. Dans le répertoire des binaires de TeX Live 2024 se trouvent asy (Asymptote) et mpost (MetaPost) — mais pas gnuplot. gnuplot est un programme de tracé indépendant, développé sans lien avec TeX, et doit venir de Homebrew ou du gestionnaire de paquets de la distribution.

À quoi sert alors cet appel ? L’analyseur intégré à pgfplots tourne sur TeX, ce qui le rend peu adapté aux expressions compliquées ou aux très nombreux échantillons. Écrire \addplot gnuplot {...} sous-traite le calcul à gnuplot et pgfplots ne trace que les coordonnées qui reviennent. Le mécanisme est d’une simplicité surprenante : TeX écrit un fichier de script pour gnuplot, gnuplot l’exécute et écrit une table de nombres dans un fichier, que TeX relit. En traitant \addplot[blue] gnuplot[domain=0:10] {sin(x)}; sous TeX Live 2024, le fichier .gnuplot engendré portait ces directives (le vrai nom de tâche est remplacé ici par job) : set table "job.pgf-plot.table"; set format "%.7e"; set samples 25; set dummy x; plot [x=0:10] sin(x);

Cette seule ligne apprend deux choses. D’abord, la valeur par défaut samples de 25 est transmise telle quelle à gnuplot. Ensuite, l’expression part dans la syntaxe propre de gnuplot : d’où l’opérateur de puissance ** de gnuplot au lieu du ^ de pgfplots, et des fonctions trigonométriques en radians par défaut. La même sinusoïde s’écrit {sin(deg(x))} avec l’analyseur intégré, mais {sin(x)} via gnuplot. Et comme TeX doit lancer un programme externe, --shell-escape est indispensable (aussi noté -write18).

latex
% compile with:  pdflatex --shell-escape document
\begin{tikzpicture}
  \begin{axis}[xlabel={$x$}, ylabel={$y$}]
    % gnuplot syntax: radians, and ** for powers
    \addplot[blue] gnuplot[domain=0:10] {sin(x)};
  \end{axis}
\end{tikzpicture}

Exécutez-le là où gnuplot est absent et TeX Live 2024 signale : "! Package pgfplots Error: Sorry, the gnuplot-result file 'job.pgf-plot.table' could not be found. Maybe you need to enable the shell-escape feature? For pdflatex, this is '>> pdflatex -shell-escape'." Si gnuplot manque vraiment, le « sh: gnuplot: command not found » du shell apparaît juste au-dessus. Remarquez la suite du message : on peut exécuter à la main gnuplot <file>.gnuplot sur le fichier engendré. Tout ce dont pgfplots a besoin, c’est le fichier .table — de fait, en déposant une .table écrite à la main puis en recompilant, la figure est apparue sans le moindre gnuplot. Sur une intégration continue où gnuplot ne peut pas être installé, verser la .table engendrée dans le dépôt est une échappatoire praticable.

Importer les figures de R et Python avec leurs polices

L’analyse est déjà faite sous R ou Python, mais coller un PNG ne vous tente pas. La réponse : faire produire par chaque outil du code TikZ/PGF et l’inclure par \input. La figure est alors composée comme partie du document, sa police et ses formules s’accordant au texte. Précisons d’emblée qu’aucun de ces outils ne fait partie de TeX Live : chacun vient du gestionnaire de paquets de son langage.

Sous R, tikzDevice, sur le CRAN, fournit un périphérique graphique qui écrit la sortie graphique standard de R — tracés base comme ggplot2 — en code TikZ. Ouvrez le périphérique par tikz(), exécutez votre code de tracé habituel, fermez par dev.off() : vous avez un .tex. Sa particularité est d’interroger LaTeX sur les largeurs de chaînes et les métriques de police au moment de placer le texte, ce qui explique que la sortie s’accorde à la police du corps et qu’on puisse mettre des formules LaTeX dans une étiquette d’axe. Avec standAlone=TRUE, il produit un document complet compilable seul.

R
library(tikzDevice)
tikz("plot.tex", width = 4, height = 3)
plot(cars$speed, cars$dist,
     xlab = "Speed", ylab = "Distance")
dev.off()

Du côté Python, deux voies existent et on les confond volontiers. La première est le backend pgf livré dans matplotlib — aucun paquet supplémentaire. Un matplotlib.use("pgf") puis savefig("figure.pgf") produit un fichier .pgf. Point crucial : ce qu’il émet est du PGF brut, et non du code pgfplots. En examinant un fichier réellement produit par matplotlib 3.11.0, on ne trouve pas un seul \addplot ni un seul environnement axis ; le corps est une suite de commandes de bas niveau comme \pgfqpoint, \pgfpathlineto et \pgfsetstrokecolor. Les axes ne sont donc pas un axe composé par pgfplots, mais un axe dessiné au trait. Et le document n’a besoin que de \usepackage{pgf}, pas de \usepackage{pgfplots}.

python
import matplotlib
matplotlib.use("pgf")
import matplotlib.pyplot as plt

matplotlib.rcParams.update({
    "pgf.texsystem": "pdflatex",   # default here is xelatex
    "font.family": "serif",
    "text.usetex": True,
    "pgf.rcfonts": False,
})

fig, ax = plt.subplots(figsize=(4, 3))
ax.plot([0, 1, 2, 3], [0, 1, 4, 9])
ax.set_xlabel(r"$x$")
ax.set_ylabel(r"$x^2$")
fig.savefig("figure.pgf")

Un piège attend ici. Le .pgf engendré s’ouvre sur des commentaires invitant à mettre \usepackage{pgf} dans le préambule ; faites-le, appelez \input{figure.pgf}, et TeX Live 2024 s’arrête sur : "! Undefined control sequence. <recently read> \mathdefault". La cause : matplotlib entoure les étiquettes de graduation de \mathdefault{...} à l’écriture mais ne met pas la définition dans le .pgf. Son backend_pgf.py n’injecte cette définition dans un préambule interne que lorsque matplotlib produit lui-même le PDF. Le remède tient en une ligne : \providecommand{\mathdefault}[1]{#1} dans le préambule, et la compilation passe. Notez aussi que pgf.texsystem vaut xelatex par défaut ; si le document est composé avec pdfLaTeX, indiquez-le explicitement comme dans l’exemple ci-dessus.

latex
\documentclass{article}
\usepackage{pgf}
\usepackage{lmodern}
% matplotlib wraps tick labels in \mathdefault but never defines it
\providecommand{\mathdefault}[1]{#1}
\begin{document}
\input{figure.pgf}
\end{document}

L’autre voie Python est tikzplotlib (anciennement matplotlib2tikz), qui convertit une figure matplotlib en code pgfplots. Écrivez-la par tikzplotlib.save("figure.tex"), puis chargez \usepackage{pgfplots} et \pgfplotsset{compat=...} et faites \input{figure.tex}. Comme les axes forment un véritable environnement axis et non du PGF brut, vous pouvez retoucher graduations et légendes côté LaTeX après coup : c’est la différence décisive avec le backend pgf. Sachez toutefois que tikzplotlib n’est plus maintenu ; un fork, matplot2tikz, lui succède avec une API quasi identique. Les deux s’installent depuis PyPI et aucun ne fait partie de TeX Live.

Quand il y a trop de points et que la compilation n’en finit pas

D’abord, éclaircir les points : c’est ce qui aide le plus. Tous les calculs de pgfplots passent par l’expansion des macros TeX, si bien qu’un nuage de dizaines de milliers de points est franchement lent et peut même atteindre le plafond mémoire de TeX. Et sur le papier, si des points voisins sont plus proches que la résolution d’impression, ils ne sont de toute façon pas visibles. Les remèdes ci-dessous sont à peu près classés par efficacité.

  • Éclaircir les points. Tracer un point sur k avec each nth point=k et écarter les données hors plage par des filtres comme filter discard if not. Sur un nuage de points, le rendu est en général identique.
  • Externalisation. Avec \usepgfplotslibrary{external} et \tikzexternalize, chaque figure est compilée une fois dans son propre PDF puis simplement incluse. Recompiler le corps devient nettement plus léger, au prix de --shell-escape.
  • Un moteur doté de plus de mémoire. lualatex a des limites mémoire TeX plus souples et se bloque moins souvent sur une grande figure.
  • Rendre en amont, tout simplement. Si un tracé reste trop lourd, importer un résultat dessiné sous R, Python ou gnuplot (section précédente) est plus praticable.

L’externalisation et gnuplot exigent tous deux --shell-escape ; si la compilation a lieu en intégration continue ou en conteneur, il faut l’activer là aussi, sinon une construction qui passait en local échoue de l’autre côté. Cet échec est extrêmement fréquent : la page Docker / CI vaut le détour.