2

The pgfplots axis environment has options width and height to specify the approximate width and height of the picture drawn, but the resulting box is not exactly as wide or high as specified—it may be less or more than requested, depending on what goes in the title and tick labels, as below.

How can I set the exact width or height of a tikzpicture drawn by pgfplots?

I understand that with scale only axis, the width or height options set the exact width or height of the axis box, but I'm looking to set the exact width or height of the TikZ picture drawn. The prevailing wisdom (e.g., from Specifying the width and height of a tikzpicture) is that there is no built-in general way in TikZ to scale a picture to exact dimensions because with TikZ you can just draw the picture exactly to the dimensions you want, and then there's no need to scale it afterward. But can this be done with pgfplots?

Documentation reference: https://tikz.dev/pgfplots/reference-scaling#sec-21.1

/pgfplots/width={⟨dimen⟩} (initially empty)

Sets the width of the final picture to {⟨dimen⟩}.

Any non-empty dimension like width=5cm sets the desired target width. Any TeX unit is accepted (like 200pt or 5in).

Unfortunately, this does not appear to be accurate, as the MWE below demonstrates and the caveat below explains:

Please note that pgfplots only estimates the size needed for axis- and tick labels. The estimate assumes a fixed amount of space for anything which is outside of the axis box. This has the effect that the final images may be slightly larger or slightly smaller than the prescribed dimensions. However, the fixed amount is always the same; it is set to 45pt. That means that multiple pictures with the same target dimensions will have the same size for their axis boxes – even if the size for descriptions varies.

Following the answers to Pgfplots height in subfigure not consistent to adjacent includegraphics, I tried using height=\mylength+\baselineskip, xlabel=\empty, title=\empty, but it changes the picture drawn (adding disproportionately more whitespace) and still doesn't end up with exactly the requested height (and if used for width with ylabel, it is further from the requested width).

Ideally, I would like to do this without having to iteratively search for inputs minimizing the actual distance from expected before committing the inputs to the document.

\documentclass{article}

\usepackage{amsmath}
\usepackage{pgfplots}
\usepackage{tikz}

\pgfplotsset{compat=1.18}

\newsavebox{\mybox}
\newlength{\mylength} \setlength{\mylength}{100pt}

\begin{document}

\section*{Width}

\begin{lrbox}{\mybox}
  \begin{tikzpicture}
    \begin{axis}[width=\mylength]
      \addplot[domain=0:2] {exp(-x)};
    \end{axis}
  \end{tikzpicture}
\end{lrbox}

\noindent expected=\the\mylength\smallskip\hrule width\mylength\relax
\vspace{\baselineskip}
\noindent actual=\the\wd\mybox\smallskip\hrule width\wd\mybox\relax
\vspace{\baselineskip}
\noindent\usebox{\mybox}

\section*{Height}

\begin{lrbox}{\mybox}
  \begin{tikzpicture}
    \begin{axis}[height=\mylength, xlabel={$\substack{X\\Y}$}, title={XXX}]
      \addplot[domain=0:2] {exp(-x)};
    \end{axis}
  \end{tikzpicture}
\end{lrbox}

expected=\the\mylength\ \vrule height\mylength\relax\quad
actual=\the\ht\mybox\ \vrule height\ht\mybox\relax\quad
\usebox{\mybox}

\end{document}

Minimum working example

6
  • 1
    Welcome to tex,sx. Commented Oct 24, 2023 at 1:46
  • 1
    I think there is no way to obtain the exact height including any possible label, unless you want to iterate things. Probably using scale only axis and doing a couple of iterations, you could obtain more or less the desired height.
    – Rmano
    Commented Oct 24, 2023 at 9:10
  • It is discussed here: tikz.dev/pgfplots/reference-scaling#sec-21.1
    – Rmano
    Commented Oct 24, 2023 at 9:17
  • I should mention \resizebox (graphicx package) and the tikzscale package. Commented Oct 24, 2023 at 23:35
  • @JohnKormylo I was aware I can use \resizebox to scale arbitrarily, but part of the idea of TikZ is that you can draw exactly the size you want, including appropriate text sizes, so you don't have to scale it—and if you scale as a post-processing step, that will ruin the text sizing. I was not aware of tikzscale, thanks; perhaps it will work but I'd have to look into it, seems to have some constraints like requiring the use of a separate .tikz file. Commented Oct 25, 2023 at 0:45

3 Answers 3

1

First, you get more consistent results using scale only axis, which totally ignores the labels. This is the part that matters.

Second, you can use \pgfresetboundingbox and \path to ignore the acutual size and substitute the desired size.

In the following example, the blue rectangle is the bounding box produced by pgfplots, and the red rectangle is the bounding box that will be used. This approach is an alternative to a groupplot.

\documentclass[border=10pt]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}

\newlength{\mylength} \setlength{\mylength}{100pt}

\begin{document}

  \begin{tikzpicture}
    \begin{axis}[scale only axis, width=0.8\mylength, height=0.8\mylength]
      \addplot[domain=0:2] {exp(-x)};
    \end{axis}
    \draw[blue] (current bounding box.south west) rectangle (current bounding box.north east);
    \pgfresetboundingbox
    \path (-0.18\mylength,-0.18\mylength) (0.82\mylength, 0.82\mylength);
    \draw[red] (current bounding box.south west) rectangle (current bounding box.north east);
  \end{tikzpicture}

\end{document}

demo

2
  • Where do the magic scalars in 0.8\mylength, -0.18\mylength, 0.82\mylength come from? If I save this in an lrbox as in the MWE, the width and height of that box come out to 100.4pt, not to 100pt. Commented Oct 24, 2023 at 17:27
  • The extra 0.4pt come from the red lines (half in, half out). The magic numbers were guesses, with some trial and error. All that matters are the totals (0.18+0;82=1). Commented Oct 24, 2023 at 23:40
1

Probably there is a better way, but if you need an exact size, you may interpolate (this could be done in an automatic way, I think... ). Not perfect, but...


\documentclass{article}
\usepackage{a4wide}
\usepackage{amsmath}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}

\newsavebox{\mybox}
\newlength{\mylength} \setlength{\mylength}{100pt}
\newlength{\resone}
\newlength{\restwo}
\newlength{\offset}
\newlength{\desired}

\newcommand{\mydo}{%

\begin{lrbox}{\mybox}
  \begin{tikzpicture}
    \begin{axis}[width=\mylength, ylabel={$\substack{X\\Y}$}, title={XXX}]
      \addplot[domain=0:2] {exp(-x)};
    \end{axis}
  \end{tikzpicture}
\end{lrbox}

\noindent expected=\the\mylength\smallskip\hrule width\mylength\relax
\vspace{\baselineskip}
\noindent actual=\the\wd\mybox\smallskip\hrule width\wd\mybox\relax
\vspace{\baselineskip}
\noindent\usebox{\mybox}
}

\begin{document}

\section*{Width}

\mydo

\setlength{\resone}{\wd\mybox}

\setlength{\mylength}{200pt}

\mydo

\setlength{\restwo}{\wd\mybox}

\begin{align*}
    w_1 &= \the\resone = a + b\cdot\mathrm{100pt} \\
    w_2 &= \the\resone = a + b\cdot\mathrm{200pt}
\end{align*}

\pgfmathsetlength{\offset}{-\restwo+2*\resone}
\pgfmathsetmacro{\coeff}{(\restwo-\resone)/100pt}
Coefficient $a$ is \pgfmathprintnumber[precision=4]{\coeff}, $a$  is \the\offset

\pgfmathsetlength{\desired}{(100pt-\offset)/\coeff}
To have 100pt, you need \the\desired

\bigskip

\setlength{\mylength}{\desired}
\mydo


\end{document}

enter image description here

0

With ticklabel style={draw=red}, we can see that the labels are nodes with inner sep and outer sep.

enter image description here

  • In pgfmanual's documentation

/pgf/inner sep=(dimension) (no default, initially .3333em)

/pgf/outer sep=(dimension) or “auto” (no default)

  • To get the height of a number

    \newlength{\hdigit} \settoheight{\hdigit}{9}

  • and the line width divided by 2 (observation with line width=10pt)

If we want the height of the graph with the labels to be 100pt, we must remove these dimensions from the graph.

\documentclass{article}
%https://tex.stackexchange.com/questions/699336/how-to-set-the-exact-width-and-height-of-tikzpicture-with-pgfplots
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}
\usepackage{showframe}
\newsavebox{\mybox}
\newlength{\mylength} \setlength{\mylength}{100pt}

\newlength{\hdigit}
\settoheight{\hdigit}{9}
\newlength{\wnumber}
\settowidth{\wnumber}{0.8}
%
\newlength{\decaly}
\setlength{\decaly}{0.6666em}% inner sep x 2
\addtolength{\decaly}{0pt}%  outer sep x 1
\addtolength{\decaly}{\hdigit}% text height
\addtolength{\decaly}{0.2pt}% line width/2
%
\newlength{\decalx}
\setlength{\decalx}{0.6666em}% inner sep x 2
\addtolength{\decalx}{0pt}%  outer sep x 1
\addtolength{\decalx}{\wnumber}% width number 0.8
\addtolength{\decalx}{0.2pt}% line width
\begin{document}

\section{height}
\begin{lrbox}{\mybox}
  \begin{tikzpicture}
    \begin{axis}[
      line width=0.4pt,
      scale only axis,
      height=\mylength-\decaly,
      ticklabel style={
        %draw=red,
        outer sep=0pt,
        },
      ]    
      \addplot[domain=0:2] {exp(-x)};
    \end{axis}
\end{tikzpicture}
\end{lrbox}
actual=\the\ht\mybox

\usebox{\mybox}
\hspace{1pt}\rule{1pt}{100pt}

\section{width}
\begin{lrbox}{\mybox}
  \begin{tikzpicture}
    \begin{axis}[
      line width=0.4pt,
      scale only axis,
      width=\mylength-\decalx,
      ticklabel style={
        %draw=red,
        outer sep=0pt,
        },
      ]    
      \addplot[domain=0:2] {exp(-x)};
    \end{axis}
  \end{tikzpicture}
\end{lrbox}
actual=\the\wd\mybox

\noindent
\usebox{\mybox}
\end{document}

enter image description here

1
  • Remark: A slight deviation with the xelatex or lualatex engine
    – pascal974
    Commented Oct 25, 2023 at 7:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .