Drawing a graph of your data or a function in an external plotting program and pasting it in as an image — you need not do that. LaTeX has a way to build a plot from data right inside the document. The lead actor is **pgfplots**, built on top of TikZ/PGF. From a function, from coordinates, or from a data file, it draws line and scatter plots, bar charts, even 3D surfaces — all in the same fonts and the same line weights as the surrounding text. This page centers on the pgfplots axis environment and \addplot, then covers calling external gnuplot as a calculator, and importing plots drawn in R or Python (matplotlib) as TikZ code.
Why typeset plots inside the document
Paste a PNG made in a spreadsheet or a drawing app and it usually clashes with the document: the axis labels are in a different typeface from the body, equations come in as fuzzy raster images, and the lines turn jagged when enlarged. Fix the data and you must remake and re-paste the figure. **pgfplots turns this around and lets LaTeX typeset the plot itself. The axis numbers and legend use the same font as your text, the curves are vector and stay smooth at any zoom, and a label can carry real math** such as $\sin x$.
A second benefit is separation from the data. Rather than hard-coding coordinates in the body, you point pgfplots at a .dat or .csv data file; then updating the data and recompiling is enough for the plot to follow. You can reuse the same numbers across a paper, slides, and an appendix without transcription mistakes. This is exactly the idea behind building tables from data (see related), and pgfplots ships with a sister package, pgfplotstable, for tables.
pgfplots is built on top of TikZ/PGF, so the whole figure lives inside a tikzpicture environment and you can freely mix in TikZ’s own coordinates, nodes, and decorations. The fundamentals of TikZ itself (\draw, the coordinate system, nodes) are left to the “TikZ basics” page; here we focus on plotting.
The skeleton of `pgfplots` — `axis` and `\addplot`
First load it in the preamble and declare a compatibility level. Writing \pgfplotsset{compat=1.18} pins the default behavior of that release (how axes look, how ticks are placed, and so on) so that a future pgfplots update will not silently change your figures. For a new document, the convention is to set as recent a number as your installed version allows.
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}In the body you place one **axis environment** inside a tikzpicture, and each call to **\addplot** inside it lays down one more curve. The axes, ticks, grid, and legend are set through options on axis; the color and markers of each curve through options on \addplot. Here is a minimal example.
\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}This samples the parabola y = x² at 100 points over −3 ≤ x ≤ 3 and draws it as a smooth blue curve. The axes carry x and f(x) labels, a title “A parabola” sits on top, grid=major lays a faint grid along the major ticks, and \addlegendentry attaches the legend entry “x².” Let us take the key points in turn.
- Wrap labels and titles in braces. Enclosing them as
xlabel={$x$}lets you safely put math or commas inside. ^is power; trig is in degrees by default.** The expression in\addplot {x^2}is evaluated by pgf’s internal parser. Functions like\sintake their argument in degrees, so to pass radians wrap them assin(deg(x))withdeg().domainandsamples.** A function plot samples the intervaldomain=a:batsamples=Npoints (the defaults are roughly −5:5 with 25 points). If a curve looks angular, raisesamples.- Two ways to do legends. Either place
\addlegendentry{…}after each curve, or set them together in theaxisoptions aslegend entries={A,B,...}. Position it withlegend pos=north west, etc.
The most-used axis options are gathered below. They go inside the [...] of axis, comma-separated.
| Option | What it does |
|---|---|
xlabel= / ylabel= | Axis labels for x and y; wrap in braces as xlabel={$x$}, math allowed |
title= | A title placed above the plot |
xmin= xmax= ymin= ymax= | Pin the visible range (the axis limits) |
grid= | Draw gridlines: grid=major along major ticks, grid=both along minor too |
legend pos= | Legend position: north west, etc.; outer north east places it outside the axes |
xtick= | State where ticks go (e.g. xtick={0,1,2}); xtick=data aligns them to the data points |
width= / height= | Set the figure’s dimensions |
ybar | Make it a vertical bar chart (below); xbar for horizontal |
Three ways to supply data — function, coordinates, file
\addplot accepts data in three broad ways. The first, seen above, is from a function: put an expression in {...} and pgfplots samples it over domain to make a curve.
\addplot[red, domain=0:2*pi, samples=200] {sin(deg(x))};This draws a sine curve from 0 to 2π. Since x is a radian value, the point is the deg() wrapper converting it to degrees before it reaches \sin, which works in degrees.
The second is from inline coordinates: list (x,y) pairs in coordinates {...} and pgfplots joins the points in order as a line (or, with markers only, a scatter plot). Handy when you have a few measured values.
\addplot[mark=*, blue] coordinates {
(0,0) (1,1) (2,4) (3,9) (4,16)
};Here five points are joined by a blue line with a filled circular marker (mark=*) at each. To drop the line and keep only the points, use only marks; change the line style with dashed and the like.
The third — most important in practice — is from a data file. table {filename} reads a whitespace-separated text file. Its first line names the columns (the header), and by default pgfplots plots column 1 as x and column 2 as y. Suppose you have this data.dat.
x y
0 0.0
1 0.8
2 0.9
3 0.1
4 -0.8
5 -1.0\begin{tikzpicture}
\begin{axis}[xlabel={$x$}, ylabel={$y$}, grid=major]
\addplot[mark=square, teal] table {data.dat};
% 列名で明示するなら:
% \addplot table[x=x, y=y] {data.dat};
\end{axis}
\end{tikzpicture}To be explicit, name the columns as in table[x=x, y=y] {data.dat} (names are case-sensitive). For a comma-separated CSV use table[col sep=comma]{...}; lines beginning with # or % are skipped as comments. When you need to reshape data or derive computed columns, the sister package **pgfplotstable** is there (see related, “Tables from data”).
Bar charts, log axes & 3D
The same axis/\addplot framework can change how data is shown. For a bar chart, just give axis the option ybar (vertical bars). Stack several \addplot calls and they automatically offset into clustered bars.
\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}Here the years (strings) are used as x-axis labels via symbolic x coords, with xtick=data aligning ticks to each data point. For horizontal bars, use xbar.
Logarithmic axes come simply from swapping axis for a different environment name: loglogaxis for log–log, semilogxaxis for a log x-axis only, and semilogyaxis for log y only. Nothing about the inner \addplot changes.
\begin{tikzpicture}
\begin{loglogaxis}[xlabel={$x$}, ylabel={$y$}]
\addplot[domain=1:1000, samples=50] {1/x};
\end{loglogaxis}
\end{tikzpicture}For 3D, use **\addplot3** and the axis becomes three-dimensional automatically. Specify surf for a surface or mesh for a wireframe, and write the function in the two variables x and y. 3D from coordinates or a data file works through \addplot3 in the same way.
\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}This draws the bell-shaped Gaussian surface z = e^(−x²−y²) on a 30×30 grid as a colored surface. Rotate the viewpoint with view={azimuth}{elevation}.
Using gnuplot as a calculator
pgfplots’s own parser is convenient but can be weak for complicated expressions or many samples. Writing \addplot gnuplot {...} hands the numerical work to the external program gnuplot, which computes the coordinates; pgfplots then reads them back and draws. In effect, gnuplot is used as a “desktop calculator.”
\begin{tikzpicture}
\begin{axis}[xlabel={$x$}, ylabel={$y$}]
\addplot[blue] gnuplot[domain=0:10] {sin(x)};
\end{axis}
\end{tikzpicture}Two cautions matter. First, **--shell-escape is required: because LaTeX launches an external command (gnuplot), you must compile with external-command execution enabled**, as in pdflatex --shell-escape document (also called -write18); otherwise it will not run. During processing, intermediate files for gnuplot are generated, the results are written there, and read back.
Second, the syntax differs: expressions handed to gnuplot are evaluated by gnuplot’s own math engine, so the power operator is gnuplot’s ****** rather than pgfplots’s ^, and trigonometric functions default to radians. So the “same” sine curve is written {sin(deg(x))} with the built-in parser but {sin(x)} through gnuplot. Because --shell-escape is disabled by default for security, add it only when you need it.
Importing plots from R and Python
You have already done your analysis in R or Python and want to reuse its plots — but without the blemishes of pasted images (mismatched fonts, fuzzy equations). The answer is to have each tool emit TikZ/pgfplots code, so the plot is typeset as part of the document, with fonts and math matching the body.
In R, the **tikzDevice package provides a “graphics device” that writes R’s standard graphics output (including base plots and ggplot2) as TikZ code**. Open the device with tikz(), run your usual plotting code, and close it with dev.off() to get a .tex file. Because it queries LaTeX for string widths and font metrics when placing text, the output matches the body typeface exactly, and you can put LaTeX math in axis labels. With standAlone=TRUE it emits a complete, separately compilable document.
library(tikzDevice)
tikz("plot.tex", width = 4, height = 3)
plot(cars$speed, cars$dist,
xlab = "Speed", ylab = "Distance")
dev.off()In Python (matplotlib), **tikzplotlib** (formerly matplotlib2tikz) converts a figure made with matplotlib.pyplot into pgfplots code. Write it out with tikzplotlib.save("figure.tex") and pull it in on the LaTeX side with \input{figure.tex}. Note that tikzplotlib is no longer maintained; a fork, **matplot2tikz**, is being developed as its successor — consider it for new work (the API is nearly the same).
import matplotlib.pyplot as plt
import tikzplotlib # or: import matplot2tikz
plt.plot([0, 1, 2, 3], [0, 1, 4, 9])
plt.xlabel("$x$")
plt.ylabel("$x^2$")
tikzplotlib.save("figure.tex")Either route produces a .tex that uses pgfplots internally, so the document also needs \usepackage{pgfplots} and \pgfplotsset{compat=...}. Being able to hand-tweak the generated code is another advantage that an image file does not offer.
Performance & externalization
pgfplots is beautiful but slow and memory-hungry on large data: a scatter plot of tens of thousands of points can make compilation long or hit TeX’s memory limits. There are several remedies.
- Thin the points. Draw every k-th point with
each nth point=k, or discard out-of-range data with filters likefilter discard if not. - Externalization. With
\usepgfplotslibrary{external}and\tikzexternalize, each figure is compiled once into its own PDF and thereafter merely included, making body recompiles light (needs--shell-escape). - A higher-memory engine.
LuaLaTeXhas looser memory limits and suits large figures. - Pre-render upstream. If a plot stays too heavy, importing the result drawn in R, Python, or gnuplot (previous section) can be the more practical route.
Features that need --shell-escape, such as externalization or gnuplot, must be enabled when you build in CI or a container. See the related page on Docker / CI setup.