Sinaplot variation using particles?

I'm experimenting with using the particles package to build a variation on sinaplot and beeswarm plots, with d3-style packing.
Why? Aesthetically, I'd like the points to be more closely packed than sinaplot, and down the road it might be interesting to animate between different categorizations of the same points.

I've made a basic example (fun!) but I'm having trouble getting the packing to settle the way I'd like, with the points packed and as close as feasible to their targets. Any suggestions?

I started with the "# Random overlapping circles" example in the particles readme at GitHub - thomasp85/particles: A particle simulation engine based on a port of d3-force

library(particles); 
library(tidyverse); 
library(tidygraph)

data <- tibble(x1 = rep(1:3, 30), y1 = runif(90) * x1, radius = runif(90, min=0.05, 0.5))

set.seed(0)

# Plotting function
graph_plot <- . %>% {
  gr <- as_tibble(.)
  p <- 
    ggplot(gr, aes(x,y, size = radius, color = abs(y+x-y1-x1))) + 
    scale_size_area() +  
    scale_color_viridis_c() +
    coord_cartesian(xlim = c(0.5, 3.5), ylim = c(-0.5,3.5)) +
    guides(color = FALSE, size = FALSE) +
    geom_point(alpha=1/2) + theme_minimal()
  plot(p)
}

tbl_graph(data) %>%
  simulate(velocity_decay = 0.7, setup = predefined_genesis(x1,y1)) %>%
  wield(collision_force, radius = radius*0.8, n_iter = 2, 
        name = 'collide', strength = 0.4) %>%
  evolve(4, on_generation = graph_plot) %>%
  unwield('collide') %>%
  clear_history() %>%
  # weaker, narrower collision force to help coalesce
  wield(collision_force, radius = radius*0.5, n_iter = 2,
        strength = 0.1) %>%
  wield(x_force, x = x1, strength = 0.8) %>%
  wield(y_force, y = y1, strength = 0.2) %>%
  evolve(100, on_generation = graph_plot) 

1 Like

I think I'm figuring it out. The main thing I was overlooking was that geom_point does not map to geom_point radius using the same units. So the collision_force wasn't working the way I expected, since the particles were being plotted only a fraction of their intended size. All was more predictable when I switched to ggforce::geom_circle.

This works out nicely, but would probably slow too much for large numbers of particles.

particle_packing

library(particles); library(tidyverse)
library(tidygraph); library(ggraph)
library(magick); library(animation)

n <- 50   
y_precision <- 0.1
set.seed(0)
data <- tibble(x1 = rep(1:3, n), y1 = runif(n*3) * x1, radius = runif(n*3, min=0.02, 0.05))
max_radius <- max(data$radius)

# Plotting function
graph_plot <- . %>% {
  gr <- as_tibble(.)
  p <- 
    ggplot(gr, aes(x0 = x, y0 = y, r = radius, color = as.factor(x1), fill = as.factor(x1))) + 
    ggforce::geom_circle(alpha=1/2) + 
    guides(color = FALSE, fill = FALSE) +
    coord_equal() + theme_minimal()
  plot(p)
}

# Run simulation
tbl_graph(data) %>%
  # Pre-populate at the positions in the table, but send the small particles moving to the right
  simulate(setup = predefined_genesis(x1, y1, x_vel = 0.1 * max_radius / radius)) %>%

  # Keep everything fairly close to the original y1 value
  impose(y_constraint, ymin = y1 - y_precision, ymax = y1 + y_precision) %>%
  
  # Run with these a few steps
  evolve(5, on_generation = graph_plot) %>% 

  # Reheat, maybe unnecessary
  reheat(alpha = 1) %>%

  # Add force towards original x1, strongest for larger particles
  wield(x_force, x = x1, strength = 2 * radius) %>%

  # Add collision force
  wield(collision_force, radius = radius, n_iter = 2, strength = 1) %>%

  # Run 100 steps and continue plotting each
  evolve(100, on_generation = graph_plot) 

For my next steps, I'd like to get the centering better, which might involve fiddling with the x_force strength (proportional to radius^2?), and outputting as a GIF. Any suggestions?

Now my next step is to output as a GIF. Any hints?