by Danielle Navarro, 17 Oct 2019


Any time you do something novel, there’s an element of exploration and surprise to the process. This happens during scientific research, in artistic work, and in so many other places besides. When I decided on a whim to use the jasmines package to encapsulate my generative art functions I wasn’t intending anything systematic. It was supposed to be a simple repository of independent projects with no cross-linking, no systematic interface, etc. Maybe it says something about me, but it hasn’t turned out that way so far. There are too many similarities between things I do across different projects not to spot the opportunity for systematicity, not to want to unify and organise into something resembling a coherent framework.

Two weeks ago I wrote about the tempest project a couple of weeks ago. The jasmines::tempest() function is the main focus of that project; it iteratively applies a curl noise operation to a seed object, and produces a variety of different images. To emphasise the role that different seeds play, I wrote a few functions like make_sticks(), make_bubbles() that would output a “seed” object, a tibble with variables x, y to specify co-ordinates and an id variable used to group points into distinct objects. The make_ prefix wasn’t chosen with any real thought, and as I have started to realise that the idea of a seed object to which a generative process is applied is a recurring theme, I later renamed all these functions using a seed_ prefix.

For the last week or so, I’ve been playing around with the jasmines::time_meander() function, something I’ve added to the package as a way of returning to the “brownian bridge” animations I wrote last year when teaching myself the basics of the gganimate package. I hadn’t planned any crossover with the tempest project but… no plan survives contact with the enemy, so to speak. My expectation was that every call to the meander() function would look like something like this…

time_meander() %>% 
  style_walk(background = pagecolour, palette = gray.colors) %>%
  animate(nframes = 200, detail = 5, type = "cairo")

In this snippet, the jasmines::meander() function creates the gganimate object that describes the paths each point follows, the length of the tails, the colours of each point and so on, and this is then passed to gganimate::animate() to construct the gif shown above. The output is quite similar to the very first brownian bridge animation I wrote over a year ago. A little smoother, a little cleaner perhaps, and with different colours, but clearly the same thing.

Despite the similarity between the output of meander() and the animations I’d been making earlier, the underlying code is structured a little differently. For one thing, this time around I realised that it makes a lot of sense to separate the data structure that I want to animate from the visual style of the animation itself. The meander() function handles the latter, and I wrote the make_bridges() function to handle the former…

bridges <- time_meander(
  length = 100,   # number of time points in the series
  seed = 2,       # number of time series to create 
  smoothing = 10  # how much smoothing to do
## Observations: 200
## Variables: 4
## $ id   <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
## $ time <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, …
## $ x    <dbl> 0.00000000, 0.06382904, 0.12835509, 0.19338244, 0.25744160,…
## $ y    <dbl> 0.00000000, 0.01269156, 0.03072611, 0.05755693, 0.09364531,…

The influence of the tempest() function is starting to appear. In my original brownian bridges code the data structure was a tibble with the same variables, but named differently. To be consistent with the way I’d been organising the “seeds” for tempest() the brownian bridge is defined in terms of co-ordinate variables x and y, and identifier id, and a new variable time that specifies the temporal evolution of the image. Even the fact that the number of time series is specified with an argument called seed is a hint that I’ve been compulsively trying to make the two systems compatible. In any case, here is what the output looks like:

ggplot(bridges) + 
  geom_line(aes(time, x), colour = "black", size = 2) + 
  geom_line(aes(time, y), colour = "white", size = 2) + 
  ylab("co-ordinate value") + 
  facet_wrap(~id) + 

All four time series here are pinned on both ends; they start and end at zero. Internally this is achieved by calling the e1071::rbridge() function to draw samples from a brownian bridge, and the smoothed appearance is achieved by taking a moving average (I admit this could have been done more elegantly).

Of course, there is nothing special about the value of zero, and no reason why each time series needs to be pinned to the same location either. Instead of “seeding” the bridge animation by specifying the number of time series, why not pass a complete data structure specifying the x and y value that each of the paths must return to? Conveniently, when I built the tempest() system that’s what I wrote the seed_ functions for, so I might as well reuse them here, right?

bubbles <- seed_bubbles(n = 2, grain = 30)
ggplot(bubbles, aes(x, y)) +
  geom_point(size = 4, colour = "white") + 
  coord_equal() + 

# make a smoothed brownian bridge with one series for
# each dot in the bubble plot: i.e., each row in the seed 
# is a distinct time series, and the corresponding bridge
# starts and ends at the x, y values in bubbles:
bridges <- bubbles %>% 
    length = 100,
    smoothing = 10,
    endpause = 5

# make a nice animation
bridges %>%
  style_walk(palette = gray.colors, background = pagecolour) %>%
  animate(nframes = 150, detail = 5, type = "cairo")

"Hey gurl" %>%
  seed_text() %>%
  time_meander(endpause = 10) %>%
  style_walk(palette = rainbow, background = pagecolour) %>% 
  animate(nframes = 150, detail = 5, type = "cairo")

seed_heart(100) %>%
  time_meander(endpause = 10) %>%
  style_walk(palette = rainbow, background = pagecolour) %>% 
  animate(nframes = 150, detail = 5, type = "cairo")

seed_heart(1000) %>%
  time_tempest(iterations = 1, scale = .15) %>% 
  style_ribbon(background = pagecolour, line_width = 1)