TimingManager: Animation Sequences in JavaScript

Simon Potter simon.potter@auckland.ac.nz

Department of Statistics, University of Auckland

January 9, 2013

Abstract: The TimingManager library is a tool written in JavaScript used to apply animations in a web browser. It is not concerned with creation or modification of animation timing information, and delegates those tasks to R via the animaker package. Primarily TimingManager is focused on assigning actions to existing animations, then playing animation sequences using either a declarative or frame-based approach.

Introduction

There are many R [1] packages available which generate animations in various formats. A key problem when creating animation sequences is the difficulty in coordinating multiple animation actions. Even a simple sequence which ensures that action A follows action B can become complicated to specify and difficult to maintain. This is particularly problematic if those actions are also part of a larger collection of animation actions that also need to be run either in sequence or in parallel.

The animaker [2] package can be used to describe an animation sequence, which allows us to focus on when animation actions should occur. It does not express any description of an animation action, only representing them by labels. The key result from describing an animation sequence is the ability to generate timing schemes from it. These timing schemes tell us when an animation is to occur, for how long, and what its context is. For example, the context can tell us whether an animation action is the first, second, or third action within a sequence of actions.

The timing information that is generated by animaker can be exported for use within a web browser and the TimingManager [3] library provides us with an easy way of using this timing information.

This does not allow us to modify exported animations but does allow us to be able to use the animations created by the animaker package. animaker tells us when an animation occurs, and its duration, but it does not assign actions to animations. The TimingManager library not only assigns actions to animation descriptions, it also allows us to play back animations in a simple manner.

We demonstrate how TimingManager is used for both declarative animation and iterative animation by frames. The declarative animation is demonstrated via D3 [4] and its use of SVG [5] and CSS transitions [6]. The framed animation is performed by the HTML <canvas> element [7], which uses a painter-model approach, rather than SVG's object-based approach to image construction.

Exporting an Animation Sequence

We can provide a simple demonstration of how animaker and TimingManager are to be used by creating a simple plot where we have 3 squares that move around and change colour. The aim is to perform the following animation sequence:

  1. Move a red square from left to right. This starts immediately and lasts for 1 second. The square will turn black once it has completed moving.
  2. Move a green square from right to left. This starts 1 second after the previous animation and lasts for 1 second. The square will turn black once it has completed moving.
  3. Move a blue square from the left to the middle of the plot. This starts 1 second after the previous animation and lasts for 1 second. The square will turn black once it has completed moving.
  4. All squares will then move into the centre of the plot, also transitioning to white. This starts 1 second after the previous animation and lasts for 3 seconds.

Now that we have described this animation sequence, we first need to describe the timings of the animation sequence using animaker within R. The description is created below:

R> library(animaker)
R> redSq <- atomic(label = "red", durn = 1)
R> greenSq <- atomic(label = "green", start = 1, durn = 1)
R> blueSq <- atomic(label = "blue", start = 1, durn = 1)
R> final <- atomic(label = "final", start = 1, durn = 3)
R> # Combine animations into a sequence
R> completeAnim <- vec(redSq, greenSq, blueSq, final,
R+                     label = "complete")

The description of the animation has now been created, we can create a timing scheme that tells us when each action is to be called, along with the context in which it is being applied.

R> timing(completeAnim)
  label start durn vec      vecNum trac tracNum
1 red   0     1    complete 1                  
2 green 2     1    complete 2                  
3 blue  4     1    complete 3                  
4 final 6     3    complete 4                  

We can see what this timing information looks like by calling plot() on our animation description.

R> plot(completeAnim)
plot of chunk plotTimings

Now that the description of the animation sequence has been created, we need to be able to export it into a form that a web browser can use. In order to export this timing information into a form that is easy to manipulate in the browser, animaker uses the RJSONIO [8] package. This package translates R data structures into JSON equivalents. This means we can access the data in JavaScript that has been exported from R in a natural manner. animaker can export animation timings to a JavaScript variable that is assigned timing information. We do this by calling the export() function, as shown below.

R> export(completeAnim,
R+        jsVar = "simpleAnim",
R+        file = "simple-anim-timing.js")

What is happening here is that the timing information is going to be represented as a JavaScript object that is assigned to a variable called simpleAnim. The resulting code is saved to a file called simple-anim-timing.js. The first 20 lines of the file simple-anim-timing.js are the following:

var simpleAnim = [
 {
 "start":      0,
"durn":      1,
"label": "red",
"vec": "complete",
"vecNum": 1,
"trac": null,
"tracNum": null 
},
{
 "start":      2,
"durn":      1,
"label": "green",
"vec": "complete",
"vecNum": 2,
"trac": null,
"tracNum": null 
},
{ 
...

What we can see here is a close mapping between the timing scheme we printed out earlier and the JSON that was exported from it. The headers in our timing scheme have now become keys in JavaScript objects, each object representing a row in our timing scheme.

We can import this information into the browser using the HTML <script> tag. This allows us to make use of TimingManager because we exposed a JavaScript variable, simpleAnim, which contains all of the necessary timing information.

Using Timing Information in the Browser

In order to use the exported timing information, we will first begin by instantiating a TimingManager object in JavaScript. There are two parameters of interest when constructing this object; the first of which is simply the timing information that we have previously stored in a JavaScript variable. The second parameter is simply noting that we are using seconds as the base unit of time.

JS> var tm = new TimingManager(simpleAnim, "s");

Next we create JavaScript functions that we will assign as actions to our atomic animations. For clarity, the code used to generate the initial scene has been omitted. Note that each of the following functions takes a single parameter which contains the information about the animation as it is being called. In this case, the timing information is used to set the duration of each animation.

JS> var redAction = function(info) {
JS+     d3.select("#redsq")
JS+         .transition()
JS+         .duration(info.durn * 1000)
JS+         .attr("x", 400)
JS+         .transition()
JS+         .attr("fill", "black");
JS+ };
JS> var greenAction = function(info) {
JS+     d3.select("#greensq")
JS+         .transition()
JS+         .duration(info.durn * 1000)
JS+         .attr("x", 100)
JS+         .transition()
JS+         .attr("fill", "black");
JS+ };
JS> var blueAction = function(info) {
JS+     d3.select("#bluesq")
JS+         .transition()
JS+         .duration(info.durn * 1000)
JS+         .attr("x", 250)
JS+         .transition()
JS+         .attr("fill", "black");
JS+ };
JS> var finalAction = function(info) {
JS+     d3.selectAll("#redsq, #greensq, #bluesq")
JS+         .transition()
JS+         .duration(info.durn * 1000)
JS+         .attr({
JS+             x: 250,
JS+             y: 250,
JS+             fill: "white"
JS+         });
JS+ };

The only timing calculation we needed to make is to convert seconds to milliseconds. This is because D3, like almost all time related JavaScript code, uses milliseconds as its primary unit of time. While the D3 code shown above is what we're going to be using to perform animation, it is not strictly necessary to understand it, only the resulting actions it performs.

In order to bind actions to animations, we need to register the actions within TimingManager. To do this, we simply build a JavaScript object that has animation labels as its keys, and the associated actions as its values. This is shown below:

JS> tm.register({
JS+     red: redAction,
JS+     green: greenAction,
JS+     blue: blueAction,
JS+     final: finalAction
JS+ });

Now that the actions have been registered to atomic animations, we can play them.

JS> tm.play();

When play() is called, TimingManager begins to call the animation actions at the correct times. The final implementation of the animation sequence is shown below. In order to show the animation that we described earlier, simply click the “Play” button.

Complex Examples

More complex examples will be shown that use R to describe animation sequences with animaker, but do not require R to produce any graphics. The first of these examples is an equivalent of animaker's dynPlot() function. dynPlot() produces an animated plot showing visually when each atomic animation is due to be called. The second is a quick demonstration of frame-based animation in the browser.

Animated Timing Plot

To begin, we will first create an animation and export it from R. We first need to load the animaker package.

R> library(animaker)

By taking some of the code from the animaker technical report we can reconstruct an animation.

R> a <- atomic(label = "Alpha", durn = 1)
R> b <- atomic(label = "Bravo", durn = 2)
R> c <- atomic(label = "Charlie", durn = 3)
R> f <- atomic(label = "Delta", durn = NA)
R> navec <- vec(a, f, b, f, c, durn = 10)
R> # We'll have this repeated 3 times
R> navec <- rep(navec, 3)

We can then export this timing information into a form that can be used in the browser.

R> # Store timing information
R> timingData <- timing(navec)
R> # Turn it into a JS variable and save
R> export(timingData,
R+        jsVar = "timingData",
R+        file = "timing-data.js")

The file timing-data.js can now be used within an HTML document so that we can use this timing information within a browser. Now that we have the data exported from R, we can begin to use the timing information in the browser.

The implementation details have been omitted for brevity but we are able to make use of D3 to create the plot. Note that the actions associated with each atomic animation are going to be appending text below the timing plot. By clicking the “Play” button we can visualise how the playback is going to occur, along with triggering associated actions.

We can see that actions for each animation are simply to print out their labels, along with the starting times and their durations. This is a simple diagram demonstrating how this can be used, but instead of simply appending some text, we could be performing any task we like within JavaScript.

Framed Animation

In the case of framed animation, the intention is to provide the illusion of animation by producing several frames close together. This effect is employed in films where 24 individual frames are shown per second, but because they are shown close together, the illusion of a moving picture is produced. In the browser, a natural way of performing this task is by using the <canvas> element. This is mostly useful if we do not have the ability to employ declarative animation, as is the case with R graphics devices.

A frame-based animation has been created, using the same animation sequence description from the previous section. The key difference is that we need to overwrite the definitions of actions associated animations with new action functions. In order to do this, we simply register the actions again, but with an additional parameter, which determines whether we can overwrite definitions. The following code demonstrates this:

JS> tm.register({
JS+     Alpha: alphaFrameAction,
JS+     Bravo: bravoFrameAction,
JS+     Charlie: charlieFrameAction,
JS+     Delta: deltaFrameAction
JS+ }, true);

For the sake of brevity, we will not show the definitions of each of the action functions. However, their definitions are such that they aim to simply draw a filled rectangle when they are called. Additionally, they append text to a field below the plot, so we can see when the “frames” are being drawn.

Now that we have our timing information and our actions registered we can play our frame-based animation. To do this, we call the frameApply() method. It requires a single parameter, which represents the number of frames to draw per second.

JS> tm.frameApply(10);

The result of this function call is that animation actions are repeatedly called during the duration of their actions, rather than just once at the starting point of the animation. The resulting animation that has been constructed is shown below.

Instead of each action describing how to perform an animation, the action simply draws to a canvas. We can also see that a frame counter is being shown to make it clear that we really are drawing multiple times, even if it doesn't appear to be the case.

An example beyond this simple demonstration for frame-based animations has not been created. The main reason for this is because in a web browser, we usually have the capacity to apply declarative animations. Declarative animations should be preferred to frame-based animations primarily because they are much simpler to write. Another reason is because web browsers are able to hardware accelerate declarative animations, whereas frame-based animations are not able to receive this benefit. As a result, declarative animations are almost always going to look nicer than an equivalent frame-based approach.

Conclusion

This has been a quick demonstration to show how we can use both framed animation and declarative animation within a web browser. The use of R’s animaker package and TimingManager provide a convenient interface to describe and apply animations respectively. It allows us to separate the task of describing when things are happening from the task of describing what the animations are aiming to show.

Downloads

This document is licensed under a Creative Commons Attribution 3.0 New Zealand License. The code is freely available under the GPL. The described functionality of TimingManager is present in the latest verion on GitHub.

References

  1. R Development Core Team (2012). R: A Language and Environment for Statistical Computing. R Foundation for Statistical Computing, Vienna, Austria. ISBN 3-900051-07-0.
  2. Murrell, P. and Potter, S. (2012). animaker: Generating Animation Timelines. https://github.com/pmur002/animaker.
  3. Potter, S. (2012). TimingManager: A Library for Managing Animation Timing Information. https://github.com/sjp/TimingManager.
  4. Bostock, M. (2012). Data Driven Documents. https://d3js.org/.
  5. W3C (2011). Scalable Vector Graphics (SVG) 1.1 (Second Edition). https://www.w3.org/TR/SVG/.
  6. W3C (2012). CSS Transitions (Working Draft). https://www.w3.org/TR/css3-transitions/.
  7. W3C (2012). HTML5: The canvas element. https://www.w3.org/TR/html5/embedded-content-0.html#the-canvas-element.
  8. Lang, D. T. (2012). RJSONIO: Serialize R objects to JSON, JavaScript Object Notation. https://CRAN.R-project.org/package=RJSONIO.