How can I fix jumping TikZ pictures in beamer?

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>[orange] (0,0) circle[radius=.5];
   \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.


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}
{
  \onslide<\k>{\fill[orange] (0,0) circle[radius=.5];
  \fill[blue] (\k * 45:3) circle[radius=.2];}
}
\end{tikzpicture}
\end{overprint}
\end{frame}

\end{document}

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


This solution was updated on another post.

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, ...).

Note: If ovspec itself contains a comma, either it or the complete argument has to be put inside curly braces (like in, visible on=<{1,3-4,8}> or `visible on={<1,3-4,8>}) in order to not confuse the pgfkeys parser.

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.