How can I fix jumping TikZ pictures in beamer?

The problem of stuff jumping in beamer is a common one, (see e.g. Avoiding jumping frames in beamer) and we've seen it here from time to time (I imagine the "related" list at the side will be quite long on this one!). With a TikZ picture, one method of fixing this is to manually specify the bounding box. I'd like to get rid of the word "manually" in that sentence!

This oughtn't to be too hard to implement, my imagined scheme would involve writing out the final bounding box to the .aux file so that it is available for use at the start of the tikzpicture environment.

First question: has anyone implemented this yet?

If (as I suspect) not, I'd like the second question to be "could someone do it for me?" but that's not in the spirit of the site. So here's my idea for how this would be done:

1. On each slide of a frame, the tikzpicture saves its bounding box.
2. At the end of the frame, the tikzpicture writes out the largest bounding box to the .aux file (note that the largest may not be the last).
3. On subsequent runs, the way that the bounding box was written out tells the tikzpicture what its bounding box ought to be and it draws a \useasboundingbox path accordingly (at the start of the picture).

So second question, which is a bit open-ended but hopefully I can get away with it: what are the pitfalls of the above.

And third question, (yup, I'm really stretching things here): if there is a part of the above that someone just happens to know, please put the code in an answer. I'm sufficiently confident of my TeX skills that I'll be able to take the pieces and put them together, and also to adapt pieces. So I'm not asking anyone to code this for me, just help me find the pieces needed.

(though if anyone does code it ...)

Here's a very simple example of what I mean (you have to compile it to see, though). As everyone knows, the earth goes around the sun. But not in this presentation. Cosmology according to this presentation is a ... little bit weird.

\documentclass{beamer}
\usepackage{tikz}

\begin{document}
\begin{frame}
\begin{tikzpicture}
\foreach \k in {1,...,8}
{
\fill<\k>[blue] (\k * 45:3) circle[radius=.2] coordinate (a);
}
\draw (0,0) -- (a);
\end{tikzpicture}
\end{frame}
\end{document}


Note: The above example is a very simple example, it is not a live example. I don't want an answer that fixes that particular example, I'm looking for a system of fixing every possible example.

-
Would you mind adding an example that's suitably jumpy? –  Matthew Leingang May 20 '11 at 18:37
You also have to check on subsequent runs whether the largest bounding box has changed. –  Caramdir May 20 '11 at 20:31
If you only have a TikZ picture on the slide, you could also do absolute positioning on the page (by using a coordinate system relative to (current page.center)). –  Caramdir May 20 '11 at 20:33

I finally (!) got round to implementing this. Here's my code:

\documentclass{beamer}
% \url{http://tex.stackexchange.com/q/18704/86}
\usepackage{tikz}

\newcounter{jumping}
\resetcounteronoverlays{jumping}

\makeatletter
\tikzset{
stop jumping/.style={
execute at end picture={%
\stepcounter{jumping}%
\immediate\write\pgfutil@auxout{%
\noexpand\jump@setbb{\the\value{jumping}}{\noexpand\pgfpoint{\the\pgf@picminx}{\the\pgf@picminy}}{\noexpand\pgfpoint{\the\pgf@picmaxx}{\the\pgf@picmaxy}}
},
\csname jump@\the\value{jumping}@maxbb\endcsname
\path (\the\pgf@x,\the\pgf@y);
\csname jump@\the\value{jumping}@minbb\endcsname
\path (\the\pgf@x,\the\pgf@y);
},
}
}
\def\jump@setbb#1#2#3{%
\@ifundefined{jump@#1@maxbb}{%
\expandafter\gdef\csname jump@#1@maxbb\endcsname{#3}%
}{%
\csname jump@#1@maxbb\endcsname
\pgf@xa=\pgf@x
\pgf@ya=\pgf@y
#3
\pgfmathsetlength\pgf@x{max(\pgf@x,\pgf@xa)}%
\pgfmathsetlength\pgf@y{max(\pgf@y,\pgf@ya)}%
\expandafter\xdef\csname jump@#1@maxbb\endcsname{\noexpand\pgfpoint{\the\pgf@x}{\the\pgf@y}}%
}
\@ifundefined{jump@#1@minbb}{%
\expandafter\gdef\csname jump@#1@minbb\endcsname{#2}%
}{%
\csname jump@#1@minbb\endcsname
\pgf@xa=\pgf@x
\pgf@ya=\pgf@y
#2
\pgfmathsetlength\pgf@x{min(\pgf@x,\pgf@xa)}%
\pgfmathsetlength\pgf@y{min(\pgf@y,\pgf@ya)}%
\expandafter\xdef\csname jump@#1@minbb\endcsname{\noexpand\pgfpoint{\the\pgf@x}{\the\pgf@y}}%
}
}
\makeatother

\begin{document}
\begin{frame}
\begin{tikzpicture}[stop jumping]
\foreach \k in {1,...,7}
{
\fill<\k>[blue] (\k * 45:\k) circle[radius=.2] coordinate (a);
}
\draw (0,0) -- (a);
\end{tikzpicture}
\end{frame}

\end{document}


Here's how it works. We have a global counter, jumping, which is stable under overlays. That is, beamer implements overlays by reprocessing the code several times. Normally, each time through would lead to a counter being incremented, but so long as beamer is told, it can take that into account and resets the counter for each run through. The upshot of this is that this counter can be used to label (in the non-TeXnical sense) tikzpictures in such a way that the different copies of the picture on different slides get the same label.

We use that label to save the bounding box of each version of the picture to the aux file. We do this at the end of the picture to ensure that we get the right bounding box. When the aux file is read in next time, it computes the maximum and minimum extends of the various bounding boxes for this picture and saves these as PGF points. Back in the picture, we use these computed points to adjust our bounding box to the maximum extent.

Some further remarks:

1. We save the bounding box of each picture before comparing it with the saved maximum. This means that the maximum is always computed on the actual sizes of the pictures and doesn't take into account any previous maximum. This means that it is sensitive to changes in the picture, which is how it should be.

2. The bounding box is adjusted by placing coordinates in the picture. This is to ensure that the picture doesn't jump around inside its box. Simply resetting the bounding box lengths would ensure that the picture took up the same amount of space on each slide, but not that the picture stayed in the same place in that box.

-
Just out of curiosity: Does this \begin{tikzpicture}[remember picture,overlay] \pgftransformshift{(current page.center)} \foreach \k in {1,...,8} { \fill<\k>[orange] (0,0) circle[radius=.5]; \fill<\k>[blue] (\k * 45:3) circle[radius=.2] coordinate (a); } \draw (0,0) -- (a); \end{tikzpicture} too much of a hack? I'm not even sure why it works. :) –  percusse Apr 12 '12 at 9:56
@percusse That ensures that your picture is positioned absolutely on the page, which is fine if the picture is all that there is. However, there may be occasions where the picture is only part of a slide and should be placed according to the normal flow, but should take up enough space that it doesn't jump around. What reminded me of it was this question: tex.stackexchange.com/q/51582/86 where the picture on the left jumps slightly due to the changing size of the nodes. –  Loop Space Apr 12 '12 at 10:03
Ah, sure. What I wanted to show that if you fix something in the recurring picture relative to page and draw everything to that anchor, you can get a fixed position of elements. Having said that, I see where you are going and indeed this hack might get messy after sometime. For the record :P, this is a little bit cleaner \begin{tikzpicture}[remember picture,overlay] \coordinate (c) at (current page.center); \foreach \k in {1,...,8} { \fill<\k>[orange] (c) circle[radius=.5]; \fill<\k>[blue] (c)++(\k * 45:3) circle[radius=.2] coordinate (a); } \draw (c) -- (a); –  percusse Apr 12 '12 at 10:09
I prefer the first version because it can be added to a picture without needing to change anything in the picture, so is easier if the picture is cut-and-pasted from elsewhere. You should post it as an answer. So long as it is clear under what conditions it could be used then it's a valid answer. –  Loop Space Apr 12 '12 at 10:16
This is really great! Can I include your code in a package (with attribution and a link to this question)? See github.com/rvf0068/graphiso.sty, where I was using a frame and a bounding box to avoid jumping. Your code works well there. –  rvf0068 Apr 12 '12 at 23:50

The \overprint, \onslide duo also seems to do the job:

\documentclass{beamer}
\usepackage{tikz}

\begin{document}
\begin{frame}
\begin{overprint}
\begin{tikzpicture}
\foreach \k in {1,...,8}
{
}
\end{tikzpicture}
\end{overprint}
\end{frame}

\end{document}


EDIT: in fact, in this particular example, there's no need to use overprint.

-
Thanks for the answer, but I'm not really interested in this particular example. I want a robust method that will work for just about any tikz picture. –  Loop Space May 20 '11 at 21:02
Actually, I take that back. I need to investigate \onslide a little more carefully. –  Loop Space May 20 '11 at 21:18
Investigated, and found an example that doesn't work with \onslide: namely, where the varying path also defines a coordinate. –  Loop Space May 21 '11 at 17:04

use it this way:

\begin{tikzpicture}
\foreach \k in {1,...,8}
{
}
\end{tikzpicture}


or shorter

\begin{tikzpicture}
\foreach \k in {1,...,8}
{
}
\end{tikzpicture}

-
Thanks for the answer, but I'm not really interested in this particular example. I want a robust method that will work for just about any tikz picture. –  Loop Space May 20 '11 at 21:02
Actually, I take that back. I need to investiage \uncover a little more carefully. –  Loop Space May 20 '11 at 21:17
uncover_is_ "robust" ... –  Herbert May 21 '11 at 5:10
Herbert, 'fraid not. See my modified example. Because I'm now specifying a coordinate on a path which changes with the frame, using \uncover will only remember the last definition regardless of the frame. Of course, I could adapt the example to not do this, but that's not what I'm trying to do here. –  Loop Space May 21 '11 at 17:03

One particularity of Andrew's MWE is that the position of the elements change on each slide, which makes it indeed difficult to calculate the resulting bounding box – which his own answer covers perfectly.

However, in many cases the goal is just some sort of piecewise uncovering of the elements of a beamer graphics. In such situations I have stopped using overlay specifications for the commands itself (\node<...>, \fill<...>), but instead always draw all elements, but hidden. To specify the visibility, I use a visible on=<...> TikZ style as follows:

\documentclass{beamer}
\usepackage{tikz}

% Keys to support piece-wise uncovering of elements in TikZ pictures:
% \node[visible on=<2->](foo){Foo}
%
% Internally works by setting opacity=0 when invisible, which has the
% adavantage (compared to \node<2->(foo){Foo} that the node is always there, hence
% always consumes space that (foo) is always available.
%
% The actual command that implements the invisibility can be overriden
% by altering the style invisible. For instance \tikzsset{invisible/.style={opacity=0.2}}
% would dim the "invisible" parts. Alternatively, the color might be set to white, if the
% output driver does not support transparencies (e.g., PS)
%
\tikzset{
invisible/.style={opacity=0},
alt/.code args={<#1>#2#3}{%
\alt<#1>{\pgfkeysalso{#2}}{\pgfkeysalso{#3}}
},
visible on/.style={alt=#1{}{invisible}},
}

\begin{document}

\begin{frame}{Uncovering TikZ elements piecewise (1)}
Invisble –– but already taking space:
\par
\bigskip
\fbox{  % to visualize bounding box
\begin{tikzpicture}[every node/.style={fill=red!30, draw=red}]
\node{Foo}
child[visible on=<2->]{node {Bar}}
child[visible on=<3->]{node {Baz}}
;
\end{tikzpicture}
}
\end{frame}

\begin{frame}{Uncovering TikZ elements piecewise (2)}
% Change "invsibility" style to dimmed
\tikzset{invisible/.style={opacity=0.3}}
Dimmed –– and obviously taking space:
\par
\bigskip

\fbox{  % to visualize bounding box
\begin{tikzpicture}[every node/.style={fill=red!30, draw=red}]
\node{Foo}
child[visible on=<2->]{node {Bar}}
child[visible on=<3->]{node {Baz}}
;
\end{tikzpicture}
}
\end{frame}

\end{document}


visible on=< ovspec > is implemented by applying the style invisible on all slides that are not contained in ovspec. The default implementation of invisible just sets opacity=0; however, as demonstrated in the example, this can easily be changed so other kinds of "invsibility" can easily be installed (dimming, gray filling, ...).

This approach (besides being, IMHO, much better readable) also has another advantage: All named elements (especially nodes) are always there, so you can use them for coordinate calcualtions even on slides they are not visible.

-

This barely counts as an answer, but here goes:

1. Not a problem. I think you can use the execute at end picture key here. The only pitfall I can think of is that the slide number is surprisingly hard to get at. See my answer here: LaTeX Beamer: How to get distinct page numbers when using overlays?.

2. Because of the non-standard way beamer expands the frame code, I think getting code executed at the end of the frame is hard. But you could take care of this in step 1 by writing a bounding-box line to the aux file at the first slide and then each time the bounding box gets bigger. Then when the aux file is read the last bounding-box line is the biggest.

3. This shouldn't be a problem either. I'd try the execute at begin picture key.

-
I think it counts. –  Loop Space May 20 '11 at 20:56