Article: Animated plots of spectral data
‘ggspectra’ 0.3.13.9000
Pedro J. Aphalo
2024-09-28
Source:vignettes/articles/animated-plots.Rmd
animated-plots.Rmd
Introduction
Package ggspectra
extends ggplot2
with
stats, geoms, scales and annotations suitable for light- or
radiation-related spectra. It also defines ggplot()
and
autoplot()
methods specialized for the classes defined in
package photobiology
for storing different types of
spectral data. The autoplot()
methods are described
separately in vignette User Guide: 2 Autoplot Methods and the
ggplot()
methods, statistics, and scales in User Guide:
1 Grammar of Graphics.
The new elements can be freely combined with methods and functions
defined in packages ‘ggplot2’, scales
,
ggrepel
, gganimate
and other extensions to
‘ggplot2’.
This article, focuses on the animation of plots of time-series of spectra, which can be acquired easily using Ocean Optics spectrometers toether with R package ‘ooacquire’. Such data can also be obtained with radiation transfer models. Simlarly, kinetics of chemical reactions involving light absorbing reactants and/or products as well as any other temporal change in colour and or intensity can be characterised through spectral data. In addition to time, as exemplified below using time-series, other series of measurements in response to changes in other continuous explanatory variables are well suited to animations.
Currently, plots of spectra created with method
autoplot()
cannot be animated with ‘gganimate’.
Set up
## News at https://www.r4photobiology.info/
##
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
##
## date, intersect, setdiff, union
# if suggested packages are available
gganimate_installed <- requireNamespace("gganimate", quietly = TRUE)
eval_chunks <- gganimate_installed
if (eval_chunks) {
library(gganimate)
} else {
message("Please, install package 'gganimate'.")
}
We change the default theme.
A short time series of spectra
We change the labels in the factor so that the time of measurement can be shown.
my_sun.spct <- sun_evening.spct
time <- when_measured(my_sun.spct, as.df = TRUE)
my_sun.spct$spct.idx <- factor(my_sun.spct$spct.idx,
levels = time[["spct.idx"]],
labels = as.character(round_date(time[["when.measured"]], unit = "minute")))
my_sun.spct
## Object: source_spct [7,965 x 3]
## containing 5 spectra in long form
## Wavelength range 290-1000 nm, step 0.34-0.47 nm
## Label: cosine.hour.9
## time.01 measured on 2023-06-12 18:38:00.379657 UTC
## time.02 measured on 2023-06-12 18:39:00.797266 UTC
## time.03 measured on 2023-06-12 18:40:00.714554 UTC
## time.04 measured on 2023-06-12 18:41:00.768459 UTC
## time.05 measured on 2023-06-12 18:42:00.769065 UTC
## Measured at 60.227 N, 24.018 E; Viikki, Helsinki, FI
## Variables:
## w.length: Wavelength [nm]
## s.e.irrad: Spectral energy irradiance [W m-2 nm-1]
## --
## # A tibble: 7,965 × 3
## w.length s.e.irrad spct.idx
## <dbl> <dbl> <fct>
## 1 290 0 2023-06-12 18:38:00
## 2 290. 0 2023-06-12 18:38:00
## 3 291. 0 2023-06-12 18:38:00
## 4 291. 0 2023-06-12 18:38:00
## 5 292. 0 2023-06-12 18:38:00
## 6 292. 0 2023-06-12 18:38:00
## 7 293. 0 2023-06-12 18:38:00
## 8 293. 0 2023-06-12 18:38:00
## 9 294. 0 2023-06-12 18:38:00
## 10 294. 0 2023-06-12 18:38:00
## # ℹ 7,955 more rows
We create a base plot to play with:
ggplot(data = my_sun.spct) +
aes(linetype = spct.idx) +
geom_line() +
scale_x_wl_continuous() +
scale_y_s.e.irrad_continuous()
We can animate a plot in different ways. Here, using long transitions between spectra. During the transitions interpolated data values are used to create an artificial smooth transition.
ggplot(data = my_sun.spct) +
geom_line() +
scale_x_wl_continuous() +
scale_y_s.e.irrad_continuous() +
transition_states(spct.idx,
transition_length = 2,
state_length = 1)
We add an indication of which spectrum is being displayed. Here we define the title and subtitle of the plot to be dynamic, showing information about the currently displayed frame of the animation.
ggplot(data = my_sun.spct) +
geom_line() +
scale_x_wl_continuous() +
scale_y_s.e.irrad_continuous() +
transition_states(spct.idx,
transition_length = 2,
state_length = 1) +
ggtitle('Now showing {closest_state} UTC',
subtitle = 'Frame {frame} of {nframes}')
It is possible to add some annotations. Here energy irradiances for red and far-red light.
ggplot(data = sun_evening.mspct |> smooth_spct(method = "supsmu", strength = 0.5)) +
geom_line() +
stat_wb_e_irrad(aes(colour = after_stat(wb.color)),
w.band = list(Red("Sellaro"), Far_red("Sellaro")),
ypos.fixed = 0.078) +
stat_wb_hbar(aes(colour = after_stat(wb.color)),
w.band = list(Red("Sellaro"), Far_red("Sellaro")),
ypos.fixed = 0.082) +
scale_x_wl_continuous() +
scale_y_s.e.irrad_continuous() +
scale_colour_identity() +
transition_states(spct.idx,
transition_length = 2,
state_length = 1,
) +
ggtitle('Now showing {closest_state} UTC',
subtitle = 'Frame {frame} of {nframes}')
A longer time series of spectra
my_sun.spct <- sun_hourly_august.spct
my_sun.spct
## Object: source_spct [15,841 x 4]
## containing 31 spectra in long form
## Wavelength range 290-800 nm, step 1 nm
## Label: Radiation transfer model (LibRadtran) simulation of solar spectrum
## by Anders K. Lindfors
## spct.01 measured on 2014-08-21 03:30:00 UTC
## spct.02 measured on 2014-08-21 04:30:00 UTC
## spct.03 measured on 2014-08-21 05:30:00 UTC
## spct.04 measured on 2014-08-21 06:30:00 UTC
## spct.05 measured on 2014-08-21 07:30:00 UTC
## spct.06 measured on 2014-08-21 08:30:00 UTC
## spct.07 measured on 2014-08-21 09:30:00 UTC
## spct.08 measured on 2014-08-21 10:30:00 UTC
## spct.09 measured on 2014-08-21 11:30:00 UTC
## spct.10 measured on 2014-08-21 12:30:00 UTC
## spct.11 measured on 2014-08-21 13:30:00 UTC
## spct.12 measured on 2014-08-21 14:30:00 UTC
## spct.13 measured on 2014-08-21 15:30:00 UTC
## spct.14 measured on 2014-08-21 16:30:00 UTC
## spct.15 measured on 2014-08-21 17:30:00 UTC
## spct.16 measured on 2014-08-22 03:30:00 UTC
## spct.17 measured on 2014-08-22 04:30:00 UTC
## spct.18 measured on 2014-08-22 05:30:00 UTC
## spct.19 measured on 2014-08-22 06:30:00 UTC
## spct.20 measured on 2014-08-22 07:30:00 UTC
## spct.21 measured on 2014-08-22 08:30:00 UTC
## spct.22 measured on 2014-08-22 09:30:00 UTC
## spct.23 measured on 2014-08-22 10:30:00 UTC
## spct.24 measured on 2014-08-22 11:30:00 UTC
## spct.25 measured on 2014-08-22 12:30:00 UTC
## spct.26 measured on 2014-08-22 13:30:00 UTC
## spct.27 measured on 2014-08-22 14:30:00 UTC
## spct.28 measured on 2014-08-22 15:30:00 UTC
## spct.29 measured on 2014-08-22 16:30:00 UTC
## spct.30 measured on 2014-08-21 19:22:00 UTC
## spct.31 measured on 2014-08-22 01:22:00 UTC
## Measured at 60.20388 N, 24.96082 E; Kumpula, Helsinki, Finland
## Variables:
## w.length: Wavelength [nm]
## s.e.irrad: Spectral energy irradiance [W m-2 nm-1]
## --
## # A tibble: 15,841 × 4
## w.length s.e.irrad UTC spct.idx
## <dbl> <dbl> <dttm> <fct>
## 1 290 0 2014-08-21 03:30:00 spct.01
## 2 291 2.85e-11 2014-08-21 03:30:00 spct.01
## 3 292 6.49e-10 2014-08-21 03:30:00 spct.01
## 4 293 2.84e- 9 2014-08-21 03:30:00 spct.01
## 5 294 9.05e- 9 2014-08-21 03:30:00 spct.01
## 6 295 2.94e- 8 2014-08-21 03:30:00 spct.01
## 7 296 8.70e- 8 2014-08-21 03:30:00 spct.01
## 8 297 1.91e- 7 2014-08-21 03:30:00 spct.01
## 9 298 4.52e- 7 2014-08-21 03:30:00 spct.01
## 10 299 9.78e- 7 2014-08-21 03:30:00 spct.01
## # ℹ 15,831 more rows
Object my_sun.spct
contains multiple spectra in long
form. A variable identifies which rows belong to each spectrum, based on
date and time. The data cover most of the daytime of two successive
days. We select data for one day.
idfactor <- getIdFactor(my_sun.spct)
idfactor
## [1] "spct.idx"
class(my_sun.spct[[idfactor]])
## [1] "factor"
unique(my_sun.spct[[idfactor]])
## [1] spct.01 spct.02 spct.03 spct.04 spct.05 spct.06 spct.07 spct.08 spct.09
## [10] spct.10 spct.11 spct.12 spct.13 spct.14 spct.15 spct.30
## 16 Levels: spct.01 spct.02 spct.03 spct.04 spct.05 spct.06 spct.07 ... spct.30
For building an animation, we need the indexing variable to be a
factor
.
my_sun.spct[["time"]] <- my_sun.spct[[idfactor]]
my_sun.spct[[idfactor]] <- factor(my_sun.spct[[idfactor]])
length(levels(my_sun.spct[[idfactor]]))
## [1] 16
We can display all 16 spectra in a single static plot.
ggplot(data = my_sun.spct) +
aes(group = get(idfactor)) +
geom_line(alpha = 1/3) +
scale_x_wl_continuous() +
scale_y_s.e.irrad_continuous()
The same data as above, but displayed as an animated plot.
anim <- ggplot(data = my_sun.spct) +
geom_line() +
scale_x_wl_continuous() +
scale_y_s.e.irrad_continuous() +
transition_states(get(idfactor),
transition_length = 2,
state_length = 1) +
ggtitle('Now showing {closest_state} UTC',
subtitle = 'Frame {frame} of {nframes}')
animate(anim, duration = 20, fps = 10)
To more easily see the differences in the shape of the spectra we can
scale them to an equal total irradiance, i.e., equal area under the
curves. To emphasize this, we can use geom_spct()
instead
of geom_line()
that was used in the examples above
anim <- ggplot(data = fscale(my_sun.spct, f = e_irrad, target = 100)) +
geom_spct() +
scale_x_wl_continuous() +
scale_y_s.e.irrad_continuous(name = s.e.irrad_label(scaled = TRUE)) +
transition_states(get(idfactor),
transition_length = 2,
state_length = 1) +
ggtitle('Now showing {closest_state} UTC',
subtitle = 'Frame {frame} of {nframes}')
animate(anim, duration = 20, fps = 10)