157

I would like to scale my tikzpicture to exactly (or a proportion of) the \textwidth

Of course I could play with the [scale=0.5] option until I found the right value, but I assume there must be an easier way.

How to get a properly scaled (the fonts still must be correct) tikzpicture with an exact width.

lockstep
  • 250,273
Peter Smit
  • 14,035
  • 8
    I was surprised this wasn't possible out of the box, but then I found in the tikz manual: « Do not scale graphics. This means that when generating graphics using an external program, create them “at the right size.” » – Will Robertson Dec 02 '10 at 17:16
  • @Will: I don't see the point of that advice for graphics generated with TikZ as the scaling is quite intelligent (e.g. it keeps line widths and font sizes). – Caramdir Dec 02 '10 at 19:17
  • @Caramdir all lengths in tikz are absolute aren't they? I haven't used it much, but I didn't think you could set a figure size and then work in relative coordinates to that. – Will Robertson Dec 03 '10 at 00:40
  • Weirdly, scaling also keep arrow tip sizes and rounded corners, but does scale plot marker size. I believe in the end, "do not scale graphics" is sound advice. – A. Donda Jul 03 '20 at 04:10
  • @WillRobertson, thanks for pointing that out. To me that makes no sense. Please note that I'm not attacking you, I'm just confused by tikz documentation... I mean, I could also do this anywhere else "at the right size", for instance in inkscape... – Pouya Oct 29 '20 at 15:50

7 Answers7

91

This question was asked on comp.text.tex and received a good answer by Ulrike Fischer. It works by typesetting the {tikzpicture} once, measure its width and then retypeset it to the correct width by automatically computing the required scale.

Here's a more user-friendly interface for this solution using the environ package. It works by using a {scaletikzpicturetowidth} environment with the desired width as first argument in combination with specifying the [scale=\tikzscale] option to the tikzpicture. For example, to scale a tikzpicture to \textwidth, you would use:

\begin{center}
\begin{scaletikzpicturetowidth}{\textwidth}
\begin{tikzpicture}[scale=\tikzscale]
\draw (0,0) rectangle (1,1) node[below left] {$A$};
\draw (2,1) circle (1cm) node [below] {$B$};
\end{tikzpicture}
\end{scaletikzpicturetowidth}
\end{center}

Here's a complete compilable code which shows both the unscaled tikzpicture and the scaled one:

unscaled and scaled tikzpicture in comparison

\documentclass{article}
\usepackage{tikz}
\usepackage{environ}
\makeatletter
\newsavebox{\measure@tikzpicture}
\NewEnviron{scaletikzpicturetowidth}[1]{%
  \def\tikz@width{#1}%
  \def\tikzscale{1}\begin{lrbox}{\measure@tikzpicture}%
  \BODY
  \end{lrbox}%
  \pgfmathparse{#1/\wd\measure@tikzpicture}%
  \edef\tikzscale{\pgfmathresult}%
  \BODY
}
\makeatother
\begin{document}

Bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. \begin{center} \begin{tikzpicture} \draw (0,0) rectangle (1,1) node[below left] {$A$}; \draw (2,1) circle (1cm) node [below] {$B$}; \end{tikzpicture} \end{center} Bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. \begin{center} \begin{scaletikzpicturetowidth}{\textwidth} \begin{tikzpicture}[scale=\tikzscale] \draw (0,0) rectangle (1,1) node[below left] {$A$}; \draw (2,1) circle (1cm) node [below] {$B$}; \end{tikzpicture} \end{scaletikzpicturetowidth} \end{center} Bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla.

\end{document}

If you need to use the external tikzlibrary, here is a workaround:

\documentclass{article}
\usepackage{tikz}
\usepackage{environ}

\usetikzlibrary{external}\tikzexternalize%\tikzset{external/force remake}

\makeatletter \newcounter{tikz@scale@num} \newsavebox{\measure@tikzpicture} \NewEnviron{scaletikzpicturetowidth}[2][]{% % optional argument #1 is passed to \tikzsetnextfilename if not empty \stepcounter{tikz@scale@num}% \def\tikz@width{#2}% \def\tikzscale{1}\begin{lrbox}{\measure@tikzpicture}% \BODY \end{lrbox}% \pgfmathparse{#2/\wd\measure@tikzpicture}% \ifcsname tikzscale\number\value{tikz@scale@num}\endcsname\else \expandafter\xdef\csname tikzscale\number\value{tikz@scale@num}\endcsname{\pgfmathresult}% \fi \tikzset{external/system call={pdflatex \tikzexternalcheckshellescape -halt-on-error -interaction=batchmode -jobname "\image" "\string\expandafter\string\edef\string\csname\space tikzscale\number\value{tikz@scale@num}\string\endcsname{\csname tikzscale\number\value{tikz@scale@num}\endcsname}\texsource"}}% \edef\tikzscale{\csname tikzscale\number\value{tikz@scale@num}\endcsname}% \ifcat$#1$\else\tikzsetnextfilename{#1}\fi \BODY } \makeatother

\begin{document}

Bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. \begin{center} \begin{tikzpicture} \draw (0,0) rectangle (1,1) node[below left] {$A$}; \draw (2,1) circle (1cm) node [below] {$B$}; \end{tikzpicture} \end{center} Bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla. \begin{center} \begin{scaletikzpicturetowidth}{\textwidth} \begin{tikzpicture}[scale=\tikzscale] \draw (0,0) rectangle (1,1) node[below left] {$A$}; \draw (2,1) circle (1cm) node [below] {$B$}; \end{tikzpicture} \end{scaletikzpicturetowidth} \end{center} Bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla.

\end{document}

If you need to use \tikzsetnextfilename{name} for an automatically scaled picture, use instead the syntax \begin{scaletikzpicturetowidth}[name]{\textwidth}.

  • is there a ConTeXt equivalent? – flying sheep Sep 10 '11 at 10:48
  • @flying sheep: I don't see why you couldn't do that in ConTeXt, but as I don't know it at all, I can't help. Try asking a new question on the subject or go on a ConTeXt specific forum. – Philippe Goutet Sep 10 '11 at 20:03
  • 2
    This solution seemed good but just has no effect in my tex document. – Spenhouet Nov 28 '17 at 22:23
  • @Spen: There's no reason it shouldn't work. Does it work in the test document above but not in another of your documents? If so, you should try to add your packages one by one to the test document until the problem occurs. If that doesn't help, please ask a new question so you can provide more details. – Philippe Goutet Dec 02 '17 at 07:43
  • When I use this solution, the image does scale, but only to something like 75 % of the \textwidth. Why? When I use Martin Scharrer's adjustbox, the image successfully scales to the whole \textwidth, but on the other hand has the side effect that text and other details that shouldn't be scaled are scaled. – StrawberryFieldsForever Jul 04 '20 at 00:02
  • @StrawberryFieldsForever: does that happen in the test document above or in another document? does it happen with the figure above or with another one? Without more details, it's difficult to answer. Don't hesitate to ask a new question with all the details. – Philippe Goutet Jul 04 '20 at 06:06
  • @PhilippeGoutet your solution does not work if the \usetikzlibrary{external} is used. Do you know if there is a workaround for this case? – aaragon Apr 30 '21 at 12:28
  • @aaragon: putting \tikzexternaldisable just before the first \BODY and then \tikzexternalenable just after should do the trick. – Philippe Goutet May 01 '21 at 08:20
  • Yes that works, but it’s not an option when you have a book (or even a book chapter) with tons of figures. Is there a way to make this work without disabling external? – aaragon May 01 '21 at 08:22
  • @aaragon: see my edit, I've added a workaround. – Philippe Goutet May 01 '21 at 20:21
  • @PhilippeGoutet it works! One question. I had been using tikzsetnextfilename to avoid working on the figure if the name had been seen before. Your solution does not work with this macro, is there a way to fix this? – aaragon May 02 '21 at 08:23
  • @PhilippeGoutet also, while trying to find a solution to this problem, I put up a bounty here. I think your solution works perfectly. – aaragon May 02 '21 at 08:24
  • @PhilippeGoutet I see there are still problems with your solution. The font size is not the same as that of the underlying document. Also, for some reason the labels of pgfplots (xlabel and ylabel) are not placed in the right position (at least when using options \begin{groupplot}[group style={group name=myplot, group size=3 by 1, xlabels at=edge bottom, xticklabels at=edge bottom, horizontal sep=10pt}, unit vector ratio*=5 1 1, xtick={-1,1.}, ymajorticks=false, axis lines = middle, enlargelimits = true]). Any ideas? – aaragon May 02 '21 at 18:12
  • @aaragon: To get the right font size, you need to put the scale=\tikzscale inside the options for {groupplot} not inside those for {tikzpicture}. I don't see what you mean about the positions of xlabel and ylabel, could you be more precise ? Concerning the use of\tikzsetnextfilename{name}, what happens is that only the temporary picture gets this name, not the final one. What I can do is add an optional argument for that e.g. {scaletikzpicturetowidth}[name]{\textwidth}. If that syntax is acceptable, I'll edit my answer accordingly. – Philippe Goutet May 03 '21 at 19:38
  • That would be great! The syntax is acceptable. Thanks for the help. – aaragon May 03 '21 at 21:10
  • @PhilippeGoutet would this work? \newcommand{\includetikzatwidth}[2]{ \tikzsetnextfilename{#1} \begin{scaletikzpicturetowidth}{#2} \input{#1} \end{scaletikzpicturetowidth} } – aaragon May 04 '21 at 09:33
  • @PhilippeGoutet putting scale=\tikzscale inside the \begin{groupplot}[scale=\tikzscale, ... does not work. The figure now exceeds the limit as before... =/ – aaragon May 04 '21 at 16:22
  • @aaragaon: I've added the support for tikzsetnextfilename, see if it works as desired. For your problem with scale=\tikzscale it works perfectly for me when I put it inside the {groupplot} so there's probably something else you have in your figures that interferes. I'll need to see your code, so you should probably ask a new question about this. – Philippe Goutet May 04 '21 at 17:17
  • Is there a way to get this to work using the standalone package with mode=buildnew? I would like to avoid using tikzexternalize. – Nivek Jul 05 '21 at 21:38
  • Remark: Since the font size is fixed this will not always work (e.g. if there's some character on the border that is involved in bounding box computation, then scaling by X does not necessarily increase the width by X), but I don't think there's a completely general solution to fix it apart from binary searching the scale factor – user202729 Oct 26 '22 at 12:42
73

1)

For smaller tikzpictures you can simply use the \resizebox macro from the graphics (or graphicx) package:

\resizebox{\textwidth}{!}{%
\begin{tikzpicture}
  \draw (0,0) .... ;
\end{tikzpicture}
}%

However, this makes the picture part of a macro argument which e.g. doesn't allow verbatim text inside nodes. TikZ itself goes through some effort to process the node content as box not as macro argument to allow any form of code inside it, including verbatim.

The use of the environ package should be also avoided in the general case because it also makes the environment body a macro argument.


2)

In can simply define your own environment using lrbox like in Philippe's answer, but as normal environment and with \resizebox:

\newsavebox\mybox
\newenvironment{resizedtikzpicture}[1]{%
  \def\mywidth{#1}%
  \begin{lrbox}{\mybox}%
  \begin{tikzpicture}
}{%
  \end{tikzpicture}%
  \end{lrbox}%
  \resizebox{\mywidth}{!}{\usebox\mybox}%
}
%
% Usage example:
\begin{resizedtikzpicture}{\textwidth}[<tikz options>]
  \draw .... ;
\end{resizedtikzpicture}

3)

I recently created the package adjustbox to give users the power of \includegraphics options for text or other contents. This can be used here nicely:

% Preamble
\usepackage{adjustbox}

% Document
\begin{adjustbox}{width=\textwidth}% there is also 'max width' to only scale it down if it is larger
\begin{tikzpicture}[<options>]
  \draw .... ;
\end{tikzpicture}
\end{adjustbox}

It supports verbatim and other special content and will work for normal text as well as other picture environments.

Martin Scharrer
  • 262,582
  • 10
    the problem with all these methods is that they will also change the size of text inside the labels, which is not wanted. If you really need verbatim inside the {tikzpicture}, one way would be to write to an external file the environment's content instead of using the environ package. – Philippe Goutet Feb 20 '11 at 00:07
  • 7
    @Philippe: Ok, that's what he meant with "the fonts still must be correct". But then it could be done using x=\textwidth, i.e. setting the x-unit to the text width and drawing the picture using relative coordinates. The question isn't very clear. The OP doesn't mentions what kind of picture he has etc. – Martin Scharrer Feb 20 '11 at 00:13
  • method 1 here worked well for me – Will Townes Jan 26 '17 at 00:43
  • adjustbox worked in my case (I was using pgfplots with tikz) – Ilonpilaaja Jan 19 '20 at 01:06
50

Maybe the most simple way to use relative coordinates is using scale=\textwidth/1cm and then only use values between 0 and 1 for your coordinates (assuming your basic scale size is 1cm). You could also estimate the size of your figure and use the same trick. I had that problem since the figure was already done and I wanted to scale it afterwards.

\begin{tikzpicture}[scale=\textwidth/15.2cm,samples=200]
    %x axis
    \draw[->] (-0.1,0) -- (15.1,0) node[below] {$x$};

    %y axis
    \draw[->] (0,-0.1) -- (0,6.1) node[left] {$y$};

    %exponential function
    \draw[color=red, thick, domain=0.0:9.5] plot[id=efunc] function{1.5**(x-5) - 1.5**(-5) + 0.1} node[color=red, anchor=west] {$y = e^x$};

    %logistic function
    \draw[color=blue, thick, domain=0.0:15.0] plot[id=logfunc] function{5/(1 + 2**(-x+7.5)) - 5/(1 + 2**(7.5)) + 0.1} node[color=blue, anchor=north east] {$y = \cfrac{1}{1 + e^{-x}}$};
\end{tikzpicture}
Martin Scharrer
  • 262,582
Semmel
  • 501
  • 4
  • 2
  • 1
    I had a tikz picture where I used cm inside. I measured it's width in cm (open pdf in inkscape, select the image, change unit to cm) and then used \begin{tikzpicture}[scale=\linewidth/12cm, transform shape]... where 12cm was the width of my picture.

    It worked pretty nicely.

    – orestisf May 15 '17 at 22:34
  • Is there a way to combine tikzscale with standalone since I would like to scale standalone tikzpictures to linewidth keeping the fontsize as define within the standalone file. – Nivek Jul 05 '21 at 22:02
9

Here a solution based on Philippe Goutet's answer from an idea of Ulrike Fischer. I created a new environment tikzpicture*. The main idea now it's to avoid to use multiple environments. Only one is necessary tikzpicture*. Normally it's possible to use options like with tikzpicture. I made some tests but It would be interesting to make more tests.

Firstly, I suppose that the user wants to adjust all the figures to the same width. I chose \linewidth by default. The user needs to set up the parameters with \setupscalewithtikz. This macro requieres one argument (a length) to apply to newwidth . It's possible to change the value of \newwidth with \setlength{\newwidth}{length}.

Here an example :

\documentclass[landscape]{article}
\usepackage{fullpage,tikz}
\usepackage{environ,amsmath,multicol} 

\makeatletter
\newcommand*\setupscalewithtikz[1][\linewidth]{%
\newsavebox{\box@tikzpicture}
\newlength{\newwidth}
\setlength{\newwidth}{#1} 
}
\NewEnviron{tikzpicture*}[1][]{%
  \begin{lrbox}{\box@tikzpicture}%
  \begin{tikzpicture}[#1]
     \BODY
  \end{tikzpicture}% 
  \end{lrbox}%
  \pgfmathsetmacro\width@scale@picture{\newwidth/\wd\box@tikzpicture}%
  \begin{tikzpicture}[#1,scale=\width@scale@picture]
    \BODY
  \end{tikzpicture}% 
}%
\makeatother

\begin{document}
\parindent=0pt 

\def\somecode{%
  \draw[fill=red!20] (0,0) rectangle (1.2,1.2) coordinate (A);
  \draw[fill=blue!20][rotate around ={-atan(4/3):(A)}](A) rectangle ++(1.5,1.5);
  \draw[fill=green!20] (1.2,0) rectangle +(0.9,-0.9);
}  

\setlength\columnsep{1ex} 
\setlength\columnseprule{0.5 pt}   
\begin{multicols}{2}[Some squares with the environment \emph{tikzpicture*}]
\setupscalewithtikz % adapt automatically to \linewidth

    \begin{tikzpicture}
      \somecode  
    \end{tikzpicture} 

    \begin{tikzpicture*}
      \somecode 
    \end{tikzpicture*}  

    \setlength{\newwidth}{1cm} 
    \begin{tikzpicture*}
      \somecode 
    \end{tikzpicture*}

    \setlength{\newwidth}{6cm}
    \begin{tikzpicture*}
      \somecode 
    \end{tikzpicture*}  
\end{multicols}     
\end{document}

enter image description here

Alain Matthes
  • 95,075
  • I tried to use this approach with the tikz matrix environment, but it did not work. Any ideas? – cacamailg Jul 18 '14 at 12:28
  • 1
    @cacamailg Many of these solutions work by grabbing the whole TikZ picture as an argument. This means you can't use an active & in a matrix but you need to use the ampersand replacement option (or use \pgfmatrixnextcell). – Qrrbrbirlbel Oct 26 '22 at 12:28
5

A new package tikzscale was created in 2012, originated from this answer. It states that it scales plots most accurately compared to standard pgfplots methods.

In its documentation there is a good overview of means to scale pictures with and without scaling fonts.

4

You may simply set \pgfplotsset{compat=x.xx, width=\textwidth} as the following example if you use pgfplots.

\documentclass[twocolumn]{article}

\usepackage{pgfplots} \usepackage{lipsum}

\begin{document}

\lipsum[1]

\begin{figure}[h] \pgfplotsset{compat=1.16, width=\columnwidth} \begin{tikzpicture} \begin{axis} \addplot[ red, domain=-3e-3:3e-3, samples=201, ] {exp(-x^2 / (2e-3^2)) / (1e-3 * sqrt(2*pi))}; \end{axis} \end{tikzpicture} \end{figure}

\lipsum[2-5]

\end{document}

0

The solutions so far work fine if the picture scales linarly with its scale factor. However, if there are text nodes that extend the bounding box or excessive line widths, this assumption doesn't hold.

Instead, I'll assume that the width of a picure follows an affine linear equation w = a * x + b, where x is the scaling factor and a and b are constants determined by the picture.

My idea is to measure the width of the picture at two different scaling factors and then compute the correct factor. The macro \affinescale gets four parameters: The desired width, the two scaling factors to sample at and the code for the picture. Inside the code, \affinex contains the scaling factor. The user can decide how the y axis should be scaled and this construct is not limited to tikzpictures.

The two sample points should be chosen such that they're reasonably close to the correct value because there can be nonlinearities in the scaling behavior of a picture, see my examples 3 and 4 below.

Code

\documentclass{article}
\usepackage{tikz}

\newdimen\affinex
\newcommand{\affinescale}[4]{{%
    % #1 = desired width w
    % #2 = \affinex for first measurement x1
    % #3 = \affinex for second measurement x2
    % #4 = code to draw picture
    %
    % take first measurement
    \affinex=#2
    \setbox0=\hbox{{\ignorespaces #4\unskip}}%
    %\copy0
    %\hbox{x1 = \the\affinex, w1 = \the\wd0}%
    %
    % take second measurement
    \affinex=#3
    \setbox1=\hbox{{\ignorespaces #4\unskip}}%
    %\copy1
    %\hbox{x2 = \the\affinex, w2 = \the\wd1}%
    %
    % calculate x from 2, w1, w2, x1, x2; system of equations to solve:
    % w  = a * x  + b ; desired dimensions
    % w1 = a * x1 + b ; measurement 1
    % w2 = a * x2 + b ; measurement 2
    \pgfmathparse{(\wd1 - \wd0) / ((#3) - (#2))}%
    \let\a=\pgfmathresult
    \pgfmathparse{(\wd0 - \a * (#2))}%
    \let\b=\pgfmathresult
    \pgfmathsetlength{\affinex}{((#1) - \b) / \a}%
    %\hbox{a = \a, b = \b, x = \the\affinex}%
    %
    % finally, draw the picture
    \ignorespaces #4\unskip%
}}

\setlength{\parindent}{0pt}

\begin{document}
    Example 1: scale x and y by the same factor

    \vrule width 5cm height 1pt depth 0pt

    \affinescale{5cm}{2cm}{3cm}{
        \begin{tikzpicture}[x=\affinex,y=\affinex]
            \draw (2,0) node[below right] {(2,0)}
               -- (1,2) node[above]       {(1,2)}
               -- (0,0) node[below left]  {(0,0)}
               -- cycle;
        \end{tikzpicture}
    }

    Example 2: only scale x

    \vrule width 10cm height 1pt depth 0pt

    \affinescale{10cm}{0.9cm}{1.1cm}{
        \begin{tikzpicture}[x=\affinex]
            \draw[line width=10pt,black!20!white] (0,0) rectangle (10,1);
            \draw (0,0) node[above right] {(0,0)} rectangle (10,1) node[below left] {(10,1)};
            \node[right=5pt] at (10,.5) {node text};
        \end{tikzpicture}
    }

    Example 3: node sticks out to the right

    \vrule width 5cm height 1pt depth 0pt

    \affinescale{5cm}{2cm}{3cm}{
        \begin{tikzpicture}[x=\affinex]
            \draw[dashed] (0,0) -- (1,0) node[midway,above right] {piecewise linear width};
        \end{tikzpicture}
    }

    Example 4: due to scaling, node doesn't stick out anymore

    \vrule width 10cm height 1pt depth 0pt

    \affinescale{10cm}{8cm}{9cm}{
        \begin{tikzpicture}[x=\affinex]
            \draw[dashed] (0,0) -- (1,0) node[midway,above right] {piecewise linear width};
        \end{tikzpicture}
    }
\end{document}

Result

output

Maybe this could be extended to some kind of iteration scheme to always figure out the correct scaling factor, but I'm scared of the TeX wizardry that would entail. Also, it would be nice to save the scaling factor in the aux file to avoid typesetting the figure three times for every run of LaTeX.

wrtlprnft
  • 3,859
  • Examples 3 and 4 don't show in the preview image. – corwin.amber Nov 17 '17 at 09:59
  • @corwin.amber I don't undestand. For me, the image contains all four examples (always “Example n:” followed by a rule showing the desired width and finally the scaled image). Can you see all examples by clicking/touching the image? – wrtlprnft Nov 18 '17 at 10:56
  • Sry, I did not understand that "piecewise linear width" over a dashed line was the image. I guess I was expecting some shape like in the first two... – corwin.amber Nov 18 '17 at 19:00