A Lissajous curve is a graph of a two-dimensional set of parametric equations of the form:
\[\begin{eqnarray} x &=& A sin \left( w_1t + \delta \right) \\ y &=& B sin \left( w_2t \right) \end{eqnarray}\]
These curves were first investigated in 1815 by Nathaniel Bowditch. Jules Antoine Lissajous studied them in more detail by 1857 and they were named after him since then.
Lissajous curves represent complex harmonic mation. \(A\) and \(B\) determine the width to height ratio, but the curve is more sensitive to the frequency ratio \(w_1/w_2\) ratio. Lissajous curves are closed if the frequency ratio is rational, and \(w_1\) and \(w_2\) determine the number of vertical and horizontal lobes, respectively.
Here I will be using Lissajous curves to introduce some features of data handling and visualization with the tidyverse family packages:
- tabular data handling with
dplyr
, - functional programming with the
pmap_df
function ofpurrr
, - plotting Lissajous curves with
geom_path()
andfacet_grid()
fromggplot2
- and creating aminated GIFs with
gganimate
andtransformr
.
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(purrr)
library(ggplot2)
library(gganimate)
library(transformr)
Plotting a Lissajous curve
We can determine the points of a Lissajous curve with the lissajous
function. The function returns a data frame with values of x and y, and ratio
and phase
variables are added for later plots
lissajous <- function(w1, w2, diff_phase, ratio, phase){
t <- seq(0, 2*pi, length.out = 100)
x <- sin(w1*t + diff_phase)
y <- sin(w2*t)
df <- data.frame(x = x, y = y, ratio = ratio, phase = phase)
return(df)
}
Once we have the coordinates of the plots, we can represent the curve with geom_path()
. This geom connects observations in the order they appear in the data. If we use geom_line()
points are connected by order of the x
axis. I use theme_void()
because I don’t need coordinate axis in this kind of plots.
ggplot(lissajous(w1 = 1, w2 = 3, diff_phase = pi/4, ratio = "1/3", phase = "pi/4"), aes(x,y)) +
geom_path() +
theme_void()
The above plot is a Lissajous curve with \(w_1=1\) and \(w_2=3\). Note that the figure has one vertical lobe and three horizontal lobes.
Plotting several Lissajous curves at once
Let’s try to plot several Lissajous curves together. I will be plotting eight ratios of frequencies for five different phase differences. I am using expand.grid for generating all combinations and mutate to obtain function parameters from ratio
and phase
labels.
lissajous_values <- expand.grid(ratio = c("1/1", "1/2", "1/3", "2/3", "3/4", "3/5", "4/5", "5/6"), phase = c("0", "pi/4", "pi/2", "3pi/4", "pi"))
lissajous_values <- lissajous_values %>%
mutate(w1 = as.numeric(substr(ratio, 1, 1)),
w2 = as.numeric(substr(ratio, 3, 3)),
phase_num = case_when(phase == "0" ~ 0,
phase == "pi/4" ~ pi/4,
phase == "pi/2" ~ pi/2,
phase == "3pi/4" ~ 3*pi/4,
phase == "pi" ~ pi))
Let’s glimpse at the table:
lissajous_values %>% glimpse()
## Rows: 40
## Columns: 5
## $ ratio <fct> 1/1, 1/2, 1/3, 2/3, 3/4, 3/5, 4/5, 5/6, 1/1, 1/2, 1/3, 2/3, …
## $ phase <fct> 0, 0, 0, 0, 0, 0, 0, 0, pi/4, pi/4, pi/4, pi/4, pi/4, pi/4, …
## $ w1 <dbl> 1, 1, 1, 2, 3, 3, 4, 5, 1, 1, 1, 2, 3, 3, 4, 5, 1, 1, 1, 2, …
## $ w2 <dbl> 1, 2, 3, 3, 4, 5, 5, 6, 1, 2, 3, 3, 4, 5, 5, 6, 1, 2, 3, 3, …
## $ phase_num <dbl> 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.000…
Now we need to apply the lissajous function to each row of the table, and integrate the resulting 40 data frames of 100 rows into a single data frame. We can do that using the pmap_df
function of the purrr
package:
lissajous_table <- pmap_df(lissajous_values, function(ratio, phase, w1, w2, phase_num) lissajous(w1 = w1, w2 = w2, diff_phase = phase_num, ratio = ratio, phase = phase))
The resulting data frame lissajous_table has 40 x 100 = 4,000 rows:
lissajous_table %>% glimpse()
## Rows: 4,000
## Columns: 4
## $ x <dbl> 0.00000000, 0.06342392, 0.12659245, 0.18925124, 0.25114799, 0.31…
## $ y <dbl> 0.00000000, 0.06342392, 0.12659245, 0.18925124, 0.25114799, 0.31…
## $ ratio <fct> 1/1, 1/1, 1/1, 1/1, 1/1, 1/1, 1/1, 1/1, 1/1, 1/1, 1/1, 1/1, 1/1,…
## $ phase <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
To plot the forty Lissajous curves we use a single plot:
x
andy
as aesthetics of each plot, created withgeom_path()
andtheme_void()
.- we use
ratio
andphase
as arguments offacet_grid()
(that’s why they are added to thelissajous
function). Theswitch = "y"
argument sets the labels of frequency ratio to the left instead of to the right.
ggplot(lissajous_table, aes(x,y)) +
geom_path() +
theme_void() +
facet_grid(ratio ~ phase, switch = "y")
Examining the effect of phase with an animated GIF
To finish this gallery of Lissajous plots I have shown the effect of phase difference for a Lissajous curve. To run this on your computer you need:
- the
gganimate
package to create the plots to be wrapped into the animation, - the
transformr
package to generate smooth transitions between contiguous plots of the animation - and the
gifski
package to create the GIF.
The code to build the GIF is wrapped into the lissajous_gif
function:
- The
values
data frame haspoints
values ofdiff_phase
between 0 and \(2\pi\). We will be using 100 images to build the GIF, one for each value of phase difference. - The
l_gif
function returns the points of a Lissajous curve for eachdiff_phase
using a number ofpoints
. These values are stored in thetable
data frame. - I use the
transition_states
function ofgganimate
to generate the plots to build the GIF. The function will generate one plot for each value ofdiff_phase
. - The function returns the set of plots of the
anim
variable.
lissajous_gif <- function(w1, w2, points = 100){
values <- data.frame(w1, w2, diff_phase = seq(0, 2*pi, length.out = 100))
l_gif <- function(w1, w2, diff_phase){
t <- seq(0, 2*pi, length.out = points)
x <- sin(w1*t + diff_phase)
y <- sin(w2*t)
df <- data.frame(x = x, y = y, diff_phase = diff_phase)
return(df)
}
table <- pmap_df(values, function(w1, w2, diff_phase) l_gif(w1, w2, diff_phase))
anim <- ggplot(table, aes(x, y)) +
geom_path() +
theme_void() +
transition_states(diff_phase,
transition_length = 2,
state_length = 1)
return(anim)
}
Let’s see an animation for a Lissajous curve of \(w_1 = 2\) and “w_2 = 3”
lissajous_gif(w1=2, w2=3, points=100)
And an animation for \(w_1 = 4\) and \(w_2 = 5\):
lissajous_gif(w1=4, w2=5, points=500)
Built with R 4.1.0, dplyr 1.0.7, gganimate 1.0.7, ggplot2 3.3.4, purrr 0.3.4, and transformr 0.1.3