3

I’d love to generate a small video by creating an interpolation between two tikz figures, for instance to animate between these frames:

enter image description here

I recently discovered the nice animate package that allows the generation of quite advanced animation, but I can’t find how to animate nodes that are not positioned directly with coordinate, but using for instance fit,right=of, name intersection etc…

Ideally, I’d love a solution where I just provide 2 tikz pictures, letting a library to automatically calculate the interpolation, for instance based on the name of the element… If this does not exists, I’d be interested by ideas on how to implement it myself. For instance, how could I hook into the fit/right=of/name intersection… to compute the width and position of a node? (a general protocol more or less independent of the positioning strategy would be great!)

What I don’t want:

  • a fully manual solution where I need to manually compute the absolute position of the node. fit, right=of …, name intersection=… are great, and I’d like to use them. Ideally, I’d prefer to avoid defining intermediate nodes (e.g. a phantom node to precompute the position of the node…) but if I’ve no choice I might live with it.

What is fine:

  • I don’t mind using intermediately another language, or even a non-tikz based approach, if the other language’s expressiveness is good enough (labels, …)

In my dream:

  • this would also work to morph node shapes (e.g. to turn a square into a circle) etc… but it will certainly stay a dream.
\documentclass[aspectratio=169]{beamer}
% \usepackage[]{animate}
\usepackage{tikz}
\usetikzlibrary{positioning,fit}
\pgfdeclarelayer{background}
\pgfdeclarelayer{foreground}
\pgfsetlayers{background,main,foreground}

\begin{document} \begin{frame} I would like a smooth interpolation (just as a list of pdf pages) between these images:\

\only<1>{ \begin{tikzpicture} \node(abc) at (0,0) {ABC}; \node(def) at (1cm,.5cm){DEF}; \node(ghi) at (-1.5cm,.5cm){GHI}; \node[fill=green] at (0cm,-2cm){I should disappear, ideally smoothly via opacity.}; \begin{pgfonlayer}{background} \node[fit=(abc)(ghi),fill=red,label={Hey},rounded corners] {}; \end{pgfonlayer} \end{tikzpicture}}% \only<2>{ \begin{tikzpicture} \node(abc) at (0,0) {ABC}; \node(def) at (1cm,.5cm){DEF}; \node(ghi) at (-1.5cm,.5cm){GHI}; \node[fill=green,opacity=0] at (0cm,-2cm){I should disappear, ideally smoothly via opacity.}; \begin{pgfonlayer}{background} \node[fit=(def)(abc),fill=red,label={Hey},rounded corners] {}; \end{pgfonlayer} \end{tikzpicture}}% \only<3>{ \begin{tikzpicture} \node(abc) at (0,0) {ABC}; \node(def) at (1cm,.5cm){DEF}; \node(ghi) at (-1.5cm,.5cm){GHI}; \node[fill=green,opacity=0] at (0cm,-2cm){I should disappear, ideally smoothly via opacity.}; \begin{pgfonlayer}{background} \node[right=of abc,minimum width=3cm,fill=red,label={Hey},rounded corners] {}; \end{pgfonlayer} \end{tikzpicture}} \end{frame}

\end{document}

% Local Variables: % TeX-command-extra-options: "-shell-escape -halt-on-error" % End:

-- EDIT --

I ended up coding my own custom code, this way I can type:

\begin{frame}
  \begin{createNewLoop}{nb frames=25}
    \only<\blenderpointCurrentframePlusOne>{
      \begin{tikzpicture}
        \node(abc) at (0,0) {ABC};
        \node(def) at (2cm,.5cm){DEF};
        \node(ghi) at (-1.5cm,.5cm){GHI};
        \myAnimatedNode[fill=green,at={(0cm,-2cm)},opacity=1-\blenderpointAnimateFraction](smoothly){I should disappear, ideally smoothly via opacity.};
        \begin{pgfonlayer}{background}
          \myAnimatedNode[fill=red!\blenderpointAnimatePcInvert!purple,rounded corners][fit=(abc)(ghi)][fit=(def)(abc)][label={Hey},](mynode){};
        \end{pgfonlayer}
      \end{tikzpicture}%
    } 
  \end{createNewLoop}
\end{frame}

to generate:

enter image description here

the idea is to draw automatically 2 phantom nodes (opacity=0), and add a third node between these two nodes. Since it’s annoying to create these two things, I created a new \node-like function that automatically create the 2 phantom nodes, and that accept the position of the first node and of the final node as an arbitrary style. It might be possible to avoid that by creating a style that automatically draws the nodes before the main one… but it’s a first try.

Advantage:

  • the phantom nodes are taken into account when computing the bounding box, so the image does not look jump
  • it is quite agnostic to how the nodes are placed

I still need to check how resilient it is to rotations, other node shapes, etc… and I’m curious to hear if other solution exists. So for now I will let this question open.

Full code:

\documentclass[aspectratio=169]{beamer}
% \usepackage[]{animate}
\usepackage{tikz}
\usetikzlibrary{positioning,fit,calc,math}
\pgfdeclarelayer{background}
\pgfdeclarelayer{foreground}
\pgfsetlayers{background,main,foreground}

\begin{document} % \myAnimatedNode[common code][code phantom 1 like fit=()][code phantom 2 like right=of ...][code in between, by default](myName){content} \NewDocumentCommand{\myAnimatedNode}{O{}O{}O{}O{}R(){}m}{% \message{TIKZTIKZ (XXXXXXXXXXXXXXXX \blenderpointAnimateFraction)} % start hidden node \node#1,#2,opacity=0{#6};% % First hidden node \node#1,#3,opacity=0{#6};% % Last node to be drawn \path let \p1=($(#5-first-hidden.west)-(#5-first-hidden.east)$), \p2=($(#5-second-hidden.west)-(#5-second-hidden.east)$), \n1 = {veclen(\p1)-\pgflinewidth}, \n2 = {veclen(\p2)-\pgflinewidth}, \n3 = {(1-\blenderpointAnimateFraction)\n1 + \blenderpointAnimateFraction\n2}, \p4=($(#5-first-hidden.south)-(#5-first-hidden.north)$), \p5=($(#5-second-hidden.south)-(#5-second-hidden.north)$), \n4 = {veclen(\p4)-\pgflinewidth}, \n5 = {veclen(\p5)-\pgflinewidth}, \n6 = {(1-\blenderpointAnimateFraction)\n4 + \blenderpointAnimateFraction\n5)} in node#1,at={($(#5-first-hidden.center)!\blenderpointAnimateFraction!(#5-second-hidden.center)$)}, minimum width=\n3, minimum height=\n6,#4{#6};% } % Instead of writing a loop, we use a (more flexible) recursive function that might be practical later to, % for instance, quit a loop before the end \NewDocumentCommand{\createNewLoopAux}{mO{}m}{% \pgfmathparse{int(\blenderpointCurrentframe < \blenderpointNbFrames)}% \ifnum\pgfmathresult=1 \pgfmathparse{\blenderpointCurrentframe/(\blenderpointNbFrames-1)}% \let\blenderpointAnimateFraction\pgfmathresult% % It might be easier to specify stuff in % (0--100) like colors \pgfmathparse{\blenderpointAnimateFraction*100}% \let\blenderpointAnimatePc\pgfmathresult% % It might be easier to invert it 100-… \pgfmathparse{100-\blenderpointAnimatePc}% \let\blenderpointAnimatePcInvert\pgfmathresult% #3% \pgfmathparse{int(\blenderpointCurrentframe + 1)}% \let\blenderpointCurrentframe\pgfmathresult% \pgfmathparse{int(\blenderpointCurrentframePlusOne + 1)}% \let\blenderpointCurrentframePlusOne\pgfmathresult% \createNewLoopAux{#1}[#2]{#3}% \fi } \NewDocumentEnvironment{createNewLoop}{mO{}+b}{% \pgfkeys{ /blenderpointAnimate/.cd, % Make sure to include at least 2 frames nb frames/.store in=\blenderpointNbFrames, % Internals current frame/.store in=\blenderpointCurrentframe, current frame=0, current frame plus 1/.store in=\blenderpointCurrentframePlusOne, current frame plus 1=1, #1 }% \createNewLoopAux{#1}[#2]{#3} }{}

\begin{frame} \begin{createNewLoop}{nb frames=25} \only<\blenderpointCurrentframePlusOne>{ \begin{tikzpicture} \node(abc) at (0,0) {ABC}; \node(def) at (3cm,.5cm){DEF}; \node(ghi) at (-1.5cm,.5cm){GHI}; \myAnimatedNodefill=green,at={(0cm,-2cm)},opacity=1-\blenderpointAnimateFraction{I should disappear, ideally smoothly via opacity.}; \begin{pgfonlayer}{background} \myAnimatedNode[fill=red!\blenderpointAnimatePcInvert!purple,rounded corners][fit=(abc)(ghi)][fit=(def)(abc)][label={Hey},](mynode){}; \end{pgfonlayer} \end{tikzpicture}% } \end{createNewLoop} \end{frame}

\end{document}

tobiasBora
  • 8,684
  • @anis I don’t mind using another tool… but I’d prefer a "programmatic" approach over generic tools, – tobiasBora May 02 '23 at 07:37
  • did you try this : https://tex.stackexchange.com/questions/136143/tikz-animated-figure-in-beamer – anis May 02 '23 at 07:38
  • @anis well this does not provide a smooth animation. I'd like the red node to litterally move pixel by pixel between the two shapes. – tobiasBora May 02 '23 at 07:57
  • @anis for more details, I managed to write a first proof of concept code. It’s not perfect, but certainly good enough for a first try. – tobiasBora May 02 '23 at 11:06
  • 1
    Hello tobias, sorry I mistakenly deleted my very first message. I was talking about Manim, for eg https://www.manim.community/ – anis May 02 '23 at 16:06
  • So, answering your question about how to hook into right of for eg, you may want to learn about the basic layer of tikz and pgf https://tikz.dev/base-design – anis May 02 '23 at 16:13
  • a step deeper would be learning about creating a package for e.g. here is Sam Carter's tikz-ducks in github https://github.com/samcarter/tikzducks – anis May 02 '23 at 16:14
  • Finally, there are hybrid solutions to do this. Blender with a python script does wonder. You can create the entire scene with a script or do half on blender and the rest with the script. you can export the animation into as many frames as you need. – anis May 02 '23 at 16:16
  • @anis thanks. Yeah, I’m already doing some blender stuff, but it’s not practical to do simple 2D animations on it.It particular, it takes ages to render, and has poor latex support. Manim is great and I was considering it at some point (still in my todo list) but I wanted first a tikz-based solution for multiple reasons: first, the size of the text in the frame would not be really consistent with manim if the figures have different sizes. It’s also a bit of a learning curve.Finally, it integrates not as nicely as tikz with existing tex code (e.g. with itemize’s style).But I’ll add stuff for it – tobiasBora May 02 '23 at 17:03
  • Good arguments. I will dig on this when I have time. – anis May 02 '23 at 19:45
  • So I continued to experiment with the above code and it’s surprisingly robust. Using \tikzset{declare function = {blInterpolate(\x,\y) = (1-\blenderpointAnimateFraction)*\x+\blenderpointAnimateFraction*\y;}} I can even change parameters using for instance xshift/.evaluated={blInterpolate(0,-2.2cm)}. I still want some more advanced features, notably to chain more than one element, but it’s good enough for quick simple animations. – tobiasBora May 02 '23 at 20:11

1 Answers1

3

A more compact and perhaps robust method, based on animation start and final BBox coordinates of the node to be morphed.

enter image description here

\documentclass{standalone}

\usepackage{animate}

\usepackage{tikz}

\usetikzlibrary{fit,calc} \pgfdeclarelayer{background} \pgfsetlayers{background,main}

\def\framerate{25} % frames per second \def\duration{2} % [s], animation duration

\begin{document}

\begin{animateinline}[autoplay,loop]{\framerate} \multiframe{\inteval{\duration\framerate+1}}{ % total number of frames = framerate duration + 1 rPos=0+\fpeval{1/(\duration\framerate)}, % intermediate partway modifier [0...1] rOpacity=1+-\fpeval{1/(\duration\framerate)}, % opacity [1...0] rColorBlend=100+-\fpeval{100/(\duration*\framerate)} % colour blending [100...0] }{ \begin{tikzpicture}

  \node (abc) at (0,0) {ABC};
  \node (def) at (5cm,-1cm){DEF};
  \node (ghi) at (-1.5cm,.5cm){GHI};
  \node [fill=green,at={(0cm,-2cm)},opacity=\rOpacity] at (0cm,-2cm) {I am fading.}; 

  \node [rounded corners,fit=(abc)(ghi)] (start box) {}; % invisible, defines `start box'
  \node [rounded corners,fit=(abc)(def)] (final box) {}; % invisible, defines `final box'
  \coordinate (sw) at ($(start box.south west)!\rPos!(final box.south west)$); % &quot;animated&quot; BBox coordinates
  \coordinate (ne) at ($(start box.north east)!\rPos!(final box.north east)$);

  \begin{pgfonlayer}{background} % &quot;morphed&quot; node, using `fit' and animated BBox coordinates
    \node [fill=red!\rColorBlend!purple,rounded corners,label={Hey},fit=(sw)(ne)] (mynode) {};
  \end{pgfonlayer}

  % invisible (thanks to \savebox), but extends the overall bounding box for all animation frames
  \savebox0{
    \node [fill, rounded corners,label={Hey},fit=(start box.south west)(start box.north east)] {}; %start position
    \node [fill, rounded corners,label={Hey},fit=(final box.south west)(final box.north east)] {}; %final position
  }

\end{tikzpicture}

} \end{animateinline}

\end{document}

AlexG
  • 54,894