24

Does it exist some tikz register (let say \zoomfactor) that provides the curent scale factor. For example can I do something like :

\begin{tikzpicture}[scale=2]
    \begin{scope}[scale=1.5]
        \draw (0,0) circle (\zoomfactor);
    \end{scope}
\end{tikzpicture}

and to obtain circle of size 3=2*1.5 ?

2 Answers 2

19

You can get the current transformation entries to extract the scale but if there are different x and y scalings(or combinations e.g. full matrix) in action, this would fail and retrieve only the x scaling. Also it uses 1cm as the unit vector.

\documentclass{article}
\usepackage{tikz}

\newcommand{\getzoomfactor}{%
\pgfgettransformentries{\myxscale}{\@tempa}{\@tempa}{\myyscale}{\@tempa}{\@tempa}
\gdef\zoomfactor{\myxscale*1cm}
}

\begin{document}

\begin{tikzpicture}[scale=2]
    \begin{scope}[scale=1.5]
        \getzoomfactor
        \draw (0,0) circle (\zoomfactor);
                \node {\zoomfactor};
    \end{scope}
\end{tikzpicture}

\end{document}

enter image description here

3
  • Thanks, \myxscale is what I was searching for. In your example you can put \myxscale in place of \zoomfactor and it works withouyt assuming 1cm as the unit vector.
    – Kpym
    Dec 13, 2012 at 18:00
  • @Kpym If you mix numbers without units with numbers with units you'll always get trouble. So some care is needed.
    – percusse
    Dec 13, 2012 at 19:34
  • 1
    Fantastic answer! This just solved a problem I had for months! Feb 13, 2018 at 11:38
19
+200

A reliable way to obtain the overall scale factor that works for all angle-preserving transformations (i.e. combinations of scalings + rotations + reflections) is by taking the square root of the absolute value of the determinant of the transformation matrix. This number can be obtained with the following two lines:

\pgfgettransformentries{\myscaleA}{\myscaleB}{\myscaleC}{\myscaleD}{\mytmp}{\mytmp}
\pgfmathsetmacro{\scalefactor}{sqrt(abs(\myscaleA*\myscaleD-\myscaleB*\myscaleC))}

In the demonstration below I've wrapped these two lines in a command called \getscale that takes a command sequence as an argument and assigns it the currently active scale factor. So if you call \getscale{\scalefactor}, the macro \scalefactor will then expand to this factor. The other three lines in this definition aren't too important; they're just there to limit the scope of \myscaleA etc. so that these are not (re)defined by this command.

The number inside each circle below is the currently active scale factor, and I've scaled both this text and the line width by this factor.

\documentclass[tikz,margin=1mm]{standalone}

\newcommand*\getscale[1]{%
  \begingroup
    \pgfgettransformentries{\scaleA}{\scaleB}{\scaleC}{\scaleD}{\whatevs}{\whatevs}%
    \pgfmathsetmacro{#1}{sqrt(abs(\scaleA*\scaleD-\scaleB*\scaleC))}%
    \expandafter
  \endgroup
  \expandafter\def\expandafter#1\expandafter{#1}%
}

\begin{document}
\begin{tikzpicture}
\foreach \angle/\yshift in {0/0cm,60/1.6cm,120/3.2cm,180/4.8cm} {
  \foreach \scale/\xshift in {1/0cm,.7071/1.6cm,.5/2.73cm,.3536/3.53cm,.25/4.10cm} {
    \begin{scope}[xshift=\xshift,yshift=-\yshift,scale=\scale,rotate=\angle]
      \getscale{\scalefactor}
      \draw[->,line cap=round,line width=\scalefactor*1pt] (.75,0) arc (2:358:.75);
      \node[scale=\scalefactor] at (0,0) {\scalefactor};
    \end{scope}%
  }\par
}
\end{tikzpicture}
\end{document}

output

I didn't include reflections in this demonstration because I didn't want to make it too long, but you can test those by adding e.g. xscale=-1 to the scope. Don't worry about the rounding errors; they are inherent to TeX and quite inconsequential.

It may be worth noting that text can be transformed along with everything else by adding the transform shape key to its node instead of scale=…, but that this won't work for line widths. With this key, the text above would have been rotated as well as scaled.



Explanation

  • With \pgfgettransformentries{\myA}{\myB}{\myC}{\myD}{\myx}{\myy} the four entries of the active 2×2 transformation matrix “(A&B\\C&D)” and the two components of the translation vector by which points are translated are assigned to \myA, \myB, etc.
  • The determinant of this matrix is given by AD − BC, and the quantity we are interested in is the square root of the absolute value of this of this expression, i.e. √(| AD − BC |).
  • The determinant of a general 2×2 matrix is the factor by which all (signed) areas are scaled if the corresponding transformation is applied. It scales linearly with scalings in one direction and quadratically with uniform scalings (hence the sqrt(…)), and its sign is inverted by reflections through a line (hence the abs(…)). It is 1 for the identity transformation, as well as for all rotations and shearings, and if multiple transformation are applied these numbers are simply multiplied.
  • For transformations that involve non-uniform scalings and/or shearings, the scaling factor is direction dependent and the value assigned by \getscale will sort of be an average over all possible directions.
  • You could also recover the rotation angle in a similar fashion.

Why this is necessary

Percusse's method works great if the only transformations that have been applied is are scalings, but it fails if a rotation or reflection (or some other transformation) is also involved. When a scope is scaled by a factor λ, rotated by an angle θ and then optionally reflected through the vertical axis, the first parameter passed to \pgfgettransformentries is set to ± λ cos(θ), where ± can be either + or −, depending on whether the reflection as applied. It thus essentially only produces the correct answer if no rotations or reflections are applied at all (or they happen to cancel out).

For educational purposes/for fun, here's a demonstration of what can go wrong if just the first entry of the transformation matrix is used. The first circle is normal, the second is rotated by 60º and the third is reflected through the vertical axis. The last circle has a line width of -1pt and it looks like its appearance is viewer dependent (and I have no idea what it'll look like when printed).

\documentclass[tikz]{standalone}
\begin{document}\begin{tikzpicture}
    \begin{scope}[xshift=0cm,rotate=0,scale=1] %% <- does nothing
        \pgfgettransformentries{\scalefactor}{\tmp}{\tmp}{\tmp}{\tmp}{\tmp}
        \draw[->,line cap=round,line width=\scalefactor*1pt] (.75,0) arc (2:358:.75);
        \node at (0,0) {\scalefactor};
    \end{scope}
    \begin{scope}[xshift=1.6cm,rotate=60,scale=1] %% <- translation + rotation
        \pgfgettransformentries{\scalefactor}{\tmp}{\tmp}{\tmp}{\tmp}{\tmp}
        \draw[->,line cap=round,line width=\scalefactor*1pt] (.75,0) arc (2:358:.75);
        \node[scale=\scalefactor] at (0,0) {\scalefactor};
        \end{scope}%
    \begin{scope}[xshift=3.2cm,rotate=0,xscale=-1] %% <- translation + reflection
        \pgfgettransformentries{\scalefactor}{\tmp}{\tmp}{\tmp}{\tmp}{\tmp}
        \draw[->,line cap=round,line width=\scalefactor*1pt] (.75,0) arc (2:358:.75);
        \node[scale=\scalefactor] at (0,0) {\scalefactor};
    \end{scope}%
\end{tikzpicture}
\end{document}

output

You must log in to answer this question.

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