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