You can see a working example of the end result of this post here and above is a screenshot.
The full and current source can be found here.
I am not going to go over how to set up the basic shapes in the animation, you can find out how that is done by referring to my last post.
The 3 path shapes that I found challenging to animate are the red sine wave that is progressively added and removed from the document as the unit circle rotates along the x axis and the two blue angled arcs that form the small angled arc at the centre of the centre of the larger circle and the blue arc that expands around the circumference of the larger unit circle.
The first step is to append the paths to an svg group element. Their initial position is not important at this stage.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
There is a
state object (line 1) that is used as a container to hold a reference to all the svg shapes that will be transformed on each tick of the animation. The shapes are added as properties of the
state object. The sine curve path shape is added to the
state object on line 10 and the two path shapes that will render the arcs are bound as properties of the
state object on lines 13 and 17 repectively.
Line 3 creates a d3.js line function that will be used to generate the rather cryptic svg path mini language instructions for the d attriute of the svg path shape that will plot the points of the sine curve.
sineData array is initialised on line 8 and the line function will be executed for every element in the array and the
y accessor functions on lines 5 and 6 will be executed exactly once for each element in the array. Obviously as the array is initially empty, these functions will not be invoked until there actually is some data.
Now that the the svg shapes are on the document, the next step is to kick off the animation function:
1 2 3 4 5
line 3 initialises a
state.time variable that will be incremented on each tick of the animation. This variable is central to all calculations that will be used to position.
The body of the
animate function that is called on line 5 of the above code snippit takes the following form:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
state.time counter is either incremented or decremented on each tick of the animation depending on whether the animation is moving forwards or backwards.
Lnes 11 to 18 make a simple check of whether the time is greater than 2π, in which case it is time to start animating backwards or if the animation is moving backwards and time is less than 0 then the shapes need to move forward. The current direction is passed into the
animate function with each call to
Line 20 calls requestAnimationFrame to progress the animation each time.
Animating the sine wave
My first attempt at incrementally drawing the sine wave was to add and remove the sinewave on each call to
animate but this led to a very jerky horrible visual effect as the browser struggled to recreate the curve each time from the origin.
The solution was incredibly simple and I simply make a call to a function called
progressSineGraph that is listed below:
1 2 3 4 5 6 7 8 9
state object that contains references to all the shapes and properties that are needed to perform the animation is passed into the function along with the direction argument that specifies whether the shapes are animating forwards or backwards.
The code between lines 2 and 6 either adds the current value of the
time variable to the
sineData array if the animation is progressing forward or removes the current head of the array if the animation is moving backwards. The elements of this array will be used to plot the sine graph.
On line 8 the
d attribute of the
path shape is set to the
state.sine function that is listed below. As mentioned earlier, the
d attribute contains the mini language instructions to plot the curve. The line function will generate the instructions by calling the function below for each element in the array:
1 2 3 4
The line function will be called for each element int the array and
y coordinates will be created by calling the
y accessor functions that accept the current element as an argument.
I was pretty amazed how easy this technique is to animate shapes progressing or regressing.
The d3.js line function is a great abstraction and you can see what the generated output of the line function looks like below. The
state.sine function has created the
d attribute of the path shape that contains the instructions to draw the sine curve. I know which I would rather work with:
It is also worth noting that montone interpolation is set to ensure a smooth curve is drawn.
Animating the Arcs
All of the shapes in the animation are interlinked in some certain way and all the calculations of where to place the shapes on each tick of the animation are based on the incrementing
The black line that rotates from the centre of the circle to the circumference can be thought of as the hypotenuse of a right angle triangle. The hypotenuse in this document is an svg line shape with
y2 properties that are set using cartesian coordinates to position the shape.
The two arcs that form the angle at the centre of the circle and at the circumference will also use the hypotenuse coordinates once they have been calculated.
Below is the code that positions the svg line shape on each tick of the animation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
y2 corrdinates have been calculated, they are bound as properties of a
hypotenuseCoords object that can be referrenced later. Lines 15 to 18 positions the shape.
We can now use basic trigonometry and the d3.js arc function to create the arcs. The arc function is also a to the d3.js svg line function as it also generates instructions for the path shape’s
d attribute but it also has some additional properties such as the
endAngle properties that are specific to generating arcs.
Below is the code that sets the properties pf the two arcs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
The main calculation takes place between lines 1 and 10 and I would love somebody to tell me there is a better way than this. I always find that if I have used
let to declare a variable and later reassign the variable after initialisation, this usually tells to me that I have got something wrong or I have taken the wrong path.
In order to set the angle each time, I need a to give the
d3.svg.arc function a
startAngle and an
endAngle each time the function is called.
startAngle properties of the 2 arcs are set on lines 15 and 20 and I am using a static value of
PI \ 2 which is 90 degrees in radians. This is because a
startAngle of 0 will position the
startAngle at 12 o’clock on the circle and I want it to be at 3 o’clock or 90 degrees.
On line 1 the atan2 function is called to find the angle that the hypotenuse makes with the
startAngle of the arc or 0 on the y axis property.
The atan2 function takes into the account the two signs of the arguments that are passed in and places the angle in the correct quadrant. In this example the angle of the
hypotenuse is found by subracting the
y1 properties and subtracting
x1 of the hypotenuse and passing them into
atan2 returns the angle in radians between π and -π. Thus,
atan2(1, 1) = π/4 and
atan2(−1, −1) = −3π/4.
My first attempt at drawing the arcs did not contain the readjustment of the
angle variable below;
1 2 3
Without this readjustment, the arcs where being rendered like this:
This is because
atan2 returns the angles between π and -π and the angle was being calculated correctly for rays in quadrant 1 and 2 but not what was required for quadrants 3 and 4. The readjustment of
(-2 * (Math.PI) + angle) gives same ray but in the correct direction when in quadrants 3 and 4 by adding the angle onto -2π.
I am very happy with the end result. If you can suggest a better way for any of the above then please a comment below.
I think I need to move on from sine waves.