[Tex/LaTex] How to fix jumping TikZ pictures in beamer

beamertikz-pgf

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

Best Answer

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.