R/utils.R

Defines functions normalize_size pack_circles jitter_ellipse with_seed_null

with_seed_null <- function(seed, code) {
  if (is.null(seed)) {
    code
  } else {
    withr::with_seed(seed, code)
  }
}

"%||%" <- function(a, b) {
  if (!is.null(a)) a else b
}

# The x and y shifts share one radius/angle draw. Drawing them twice would place
# the point at (rho1 cos theta1, rho2 sin theta2), which is not on the ellipse.
jitter_ellipse <- function(data, width, height) {
  rho <- sqrt(stats::runif(nrow(data)))
  theta <- stats::runif(nrow(data), 0, 2 * pi)

  ggplot2::transform_position(
    data,
    function(x) x + rho * cos(theta) * width,
    function(y) y + rho * sin(theta) * height
  )
}

# circleProgressiveLayout does not centre what it returns. A single circle lands
# at (-radius, 0), and packs of three or five sit off-centre by roughly a third
# of their radius; by ten the drift is gone. Centring puts the cluster on the
# data point it stands for, and leaves a lone point exactly where it was.
pack_circles <- function(areas) {
  layout <- packcircles::circleProgressiveLayout(areas)
  cbind(layout$x - mean(layout$x), layout$y - mean(layout$y))
}

# Circle areas are rescaled to [0.1, 1] so that the smallest point still packs a
# visible circle. A layer with no size aesthetic, or one constant size, packs
# equal circles.
normalize_size <- function(data) {
  if (!"size" %in% names(data) || max(data$size) == min(data$size)) {
    rep(1, nrow(data))
  } else {
    0.1 + (data$size - min(data$size)) * 0.9 / (max(data$size) - min(data$size))
  }
}

Try the vayr package in your browser

Any scripts or data that you put into this service are public.

vayr documentation built on Aug. 22, 2026, 1:08 a.m.