{"spec_id":"streamline-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' streamline-basic: Basic Streamline Plot\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 92/100 | Created: 2026-09-09\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(ragg)\n\nset.seed(42)\n\n# --- Theme tokens -------------------------------------------------------\nTHEME       <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG     <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\nIMPRINT_SEQ_LOW  <- \"#009E73\"  # Imprint sequential cmap, low end\nIMPRINT_SEQ_HIGH <- \"#4467A3\"  # Imprint sequential cmap, high end\n\n# --- Vector field: electric dipole ---------------------------------------\n# Two opposite point charges. Field lines emanate from the positive charge\n# and curve toward the negative one, tracing the classic dipole pattern.\ncharges <- tibble::tibble(\n  x = c(-1.2, 1.2),\n  y = c(0, 0),\n  q = c(1, -1)\n)\nsoftening <- 0.05  # avoids the 1/r^2 singularity at each charge\n\nfield_vec <- function(x, y) {\n  dx <- x - charges$x\n  dy <- y - charges$y\n  r3 <- pmax(dx^2 + dy^2, softening^2)^1.5\n  c(u = sum(charges$q * dx / r3), v = sum(charges$q * dy / r3))\n}\n\nunit_field <- function(x, y) {\n  fv <- field_vec(x, y)\n  speed <- sqrt(sum(fv^2))\n  list(dir = fv / speed, speed = speed)\n}\n\n# --- Reference grid of the field (matches the spec's x/y/u/v layout) -----\naspect  <- 8 / 4.5\nbound_y <- c(-2, 2)\nbound_x <- bound_y * aspect\n\ngrid_x <- seq(bound_x[1], bound_x[2], length.out = 36)\ngrid_y <- seq(bound_y[1], bound_y[2], length.out = 20)\ngrid    <- expand.grid(x = grid_x, y = grid_y)\ngrid_uv <- t(mapply(field_vec, grid$x, grid$y))\ngrid$u  <- grid_uv[, 1]\ngrid$v  <- grid_uv[, 2]\ngrid$speed <- sqrt(grid$u^2 + grid$v^2)\ncap_val <- as.numeric(quantile(grid$speed, 0.9))\n\n# --- Integrate streamlines via RK4 (arc-length parameterized) -----------\nn_seeds   <- 20\nseed_r    <- 0.25\nstop_r    <- 0.15\nstep_size <- 0.025\nmax_steps <- 500\n\nangles <- seq(0, 2 * pi, length.out = n_seeds + 1)[1:n_seeds]\n# Seed half the lines outward from the positive charge (sign = 1, following\n# the field direction) and mirror the other half outward from the negative\n# charge (sign = -1, integrating against the local field). Physically both\n# halves are the same dipole field lines — the negative-charge lines are\n# just the ones arriving from far away, traced in reverse — and seeding\n# both charges keeps the pattern symmetric across the full canvas.\nseeds <- dplyr::bind_rows(\n  tibble::tibble(\n    x0 = charges$x[1] + seed_r * cos(angles),\n    y0 = charges$y[1] + seed_r * sin(angles),\n    sign = 1\n  ),\n  tibble::tibble(\n    x0 = charges$x[2] + seed_r * cos(angles),\n    y0 = charges$y[2] + seed_r * sin(angles),\n    sign = -1\n  )\n)\n\nrk4_step <- function(x, y, h, sign) {\n  k1 <- sign * unit_field(x, y)$dir\n  k2 <- sign * unit_field(x + h / 2 * k1[1], y + h / 2 * k1[2])$dir\n  k3 <- sign * unit_field(x + h / 2 * k2[1], y + h / 2 * k2[2])$dir\n  k4 <- sign * unit_field(x + h * k3[1], y + h * k3[2])$dir\n  c(x + h / 6 * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]),\n    y + h / 6 * (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2]))\n}\n\ntrace_streamline <- function(x0, y0, sid, sign) {\n  xs <- numeric(max_steps)\n  ys <- numeric(max_steps)\n  speeds <- numeric(max_steps)\n  x <- x0\n  y <- y0\n  n <- 0\n  for (i in seq_len(max_steps)) {\n    if (x < bound_x[1] || x > bound_x[2] || y < bound_y[1] || y > bound_y[2]) break\n    dmin <- min(sqrt((x - charges$x)^2 + (y - charges$y)^2))\n    if (dmin < stop_r && i > 3) break\n    n <- n + 1\n    xs[n] <- x\n    ys[n] <- y\n    speeds[n] <- unit_field(x, y)$speed\n    xy <- rk4_step(x, y, step_size, sign)\n    x <- xy[1]\n    y <- xy[2]\n  }\n  if (n == 0) return(NULL)\n  tibble::tibble(id = sid, x = xs[1:n], y = ys[1:n], speed = speeds[1:n])\n}\n\nstreamlines <- dplyr::bind_rows(\n  lapply(seq_len(nrow(seeds)), function(i) {\n    trace_streamline(seeds$x0[i], seeds$y0[i], i, seeds$sign[i])\n  })\n)\nstreamlines$speed_capped <- pmin(streamlines$speed, cap_val)\n\n# --- Title (fontsize scales down for titles longer than the 67-char baseline)\nplot_title <- \"Electric Field Around a Dipole · streamline-basic · r · ggplot2 · anyplot.ai\"\ntitle_fontsize <- max(8, round(12 * min(1, 67 / nchar(plot_title))))\n\n# --- Theme ----------------------------------------------------------------\nanyplot_theme <- theme_minimal(base_size = 7) +\n  theme(\n    plot.background   = element_rect(fill = PAGE_BG, color = PAGE_BG),\n    panel.background  = element_rect(fill = PAGE_BG, color = NA),\n    panel.grid.major  = element_line(color = grDevices::adjustcolor(INK, alpha.f = 0.12), linewidth = 0.15),\n    panel.grid.minor  = element_blank(),\n    axis.line         = element_line(color = INK_SOFT),\n    axis.title        = element_text(color = INK, size = 10),\n    axis.text         = element_text(color = INK_SOFT, size = 8),\n    plot.title        = element_text(color = INK, size = title_fontsize),\n    legend.background = element_rect(fill = PAGE_BG, color = NA),\n    legend.text       = element_text(color = INK_SOFT, size = 8),\n    legend.title      = element_text(color = INK, size = 10)\n  )\n\n# --- Plot -------------------------------------------------------------------\np <- ggplot(streamlines, aes(x = x, y = y, group = id, color = speed_capped, linewidth = speed_capped)) +\n  geom_path(lineend = \"round\", alpha = 0.9) +\n  geom_point(\n    data = charges, aes(x = x, y = y),\n    inherit.aes = FALSE, color = INK, size = 3, alpha = 0.9\n  ) +\n  scale_color_gradient(\n    name = \"Field strength\", low = IMPRINT_SEQ_LOW, high = IMPRINT_SEQ_HIGH,\n    limits = c(0, cap_val)\n  ) +\n  scale_linewidth(range = c(0.5, 1.3), guide = \"none\") +\n  coord_equal(xlim = bound_x, ylim = bound_y, expand = FALSE) +\n  labs(title = plot_title, x = \"X Position\", y = \"Y Position\") +\n  anyplot_theme\n\n# --- Save -------------------------------------------------------------------\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 8,\n  height   = 4.5,\n  units    = \"in\",\n  dpi      = 400\n)\n"}