{"spec_id":"phase-diagram","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' phase-diagram: Phase Diagram (State Space Plot)\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 89/100 | Created: 2026-09-05\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(grid)\nlibrary(ragg)\n\nset.seed(42)\n\n# --- Theme tokens -----------------------------------------------------------\nTHEME       <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG     <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nELEVATED_BG <- if (THEME == \"light\") \"#FFFDF6\" else \"#242420\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\nIMPRINT_PALETTE <- c(\n  \"#009E73\", # 1 - first categorical series (brand green)\n  \"#C475FD\", # 2 - lavender\n  \"#4467A3\"  # 3 - blue\n)\n\n# Blend grid color toward the page background so it recedes behind the data\n# (more so in dark theme, where a flat INK grid reads too bright).\nmix_color <- function(c1, c2, weight) {\n  rgb_mix <- col2rgb(c1) * weight + col2rgb(c2) * (1 - weight)\n  rgb(rgb_mix[1], rgb_mix[2], rgb_mix[3], maxColorValue = 255)\n}\nGRID_COLOR <- mix_color(INK, PAGE_BG, if (THEME == \"light\") 0.35 else 0.22)\n\n# --- Data: damped harmonic oscillator state space ---------------------------\n# dx/dt = v ; dv/dt = -omega^2 * x - 2 * zeta * omega * v (underdamped spiral)\nomega    <- 1.5\nzeta     <- 0.12\ndt       <- 0.02\nn_steps  <- 800\n\nintegrate_trajectory <- function(x0, v0) {\n  x <- numeric(n_steps + 1)\n  v <- numeric(n_steps + 1)\n  x[1] <- x0\n  v[1] <- v0\n  for (i in seq_len(n_steps)) {\n    ax <- v[i]\n    av <- -omega^2 * x[i] - 2 * zeta * omega * v[i]\n    x_mid <- x[i] + 0.5 * dt * ax\n    v_mid <- v[i] + 0.5 * dt * av\n    ax_mid <- v_mid\n    av_mid <- -omega^2 * x_mid - 2 * zeta * omega * v_mid\n    x[i + 1] <- x[i] + dt * ax_mid\n    v[i + 1] <- v[i] + dt * av_mid\n  }\n  tibble::tibble(x = x, dx_dt = v)\n}\n\ninitial_conditions <- tibble::tibble(\n  x0    = c(2.5, -2.0, 1.0),\n  v0    = c(0.0, 1.5, -2.2),\n  label = c(\"x0 = 2.5, v0 = 0.0\", \"x0 = -2.0, v0 = 1.5\", \"x0 = 1.0, v0 = -2.2\")\n)\n\n# Direction arrows: a few short tangent segments sampled along the early/mid\n# part of each spiral (skipped near the fixed point, where segments would be\n# too short to read) so a viewer can tell which way the system evolves.\nmake_direction_arrows <- function(traj, label, n_arrows = 4, step_ahead = 7) {\n  n <- nrow(traj)\n  idx <- unique(round(seq(0.05, 0.55, length.out = n_arrows) * n))\n  idx <- idx[idx >= 1 & idx + step_ahead <= n]\n  tibble::tibble(\n    x     = traj$x[idx],\n    y     = traj$dx_dt[idx],\n    xend  = traj$x[idx + step_ahead],\n    yend  = traj$dx_dt[idx + step_ahead],\n    label = label\n  )\n}\n\ntrajectory_list <- lapply(seq_len(nrow(initial_conditions)), function(i) {\n  cond <- initial_conditions[i, ]\n  integrate_trajectory(cond$x0, cond$v0) |> mutate(label = cond$label)\n})\ntrajectories <- bind_rows(trajectory_list)\ntrajectories$label <- factor(trajectories$label, levels = initial_conditions$label)\n\narrows <- bind_rows(lapply(seq_len(nrow(initial_conditions)), function(i) {\n  make_direction_arrows(trajectory_list[[i]], initial_conditions$label[i])\n}))\narrows$label <- factor(arrows$label, levels = initial_conditions$label)\n\nstart_points <- initial_conditions |>\n  transmute(x = x0, dx_dt = v0, label = factor(label, levels = initial_conditions$label))\n\n# Square the data domain so coord_fixed's 1:1 aspect fills the square canvas\n# instead of letterboxing (the raw x/y ranges aren't symmetric).\nhalf_extent <- max(abs(trajectories$x), abs(trajectories$dx_dt)) * 1.08\n\n# --- Theme --------------------------------------------------------------\nanyplot_theme <- theme_minimal(base_size = 8) +\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 = GRID_COLOR, linewidth = 0.25),\n    panel.grid.minor  = element_blank(),\n    axis.title        = element_text(color = INK, size = 10),\n    axis.text         = element_text(color = INK_SOFT, size = 8),\n    axis.line         = element_line(color = INK_SOFT),\n    axis.ticks        = element_blank(),\n    plot.title        = element_text(color = INK, size = 12),\n    legend.background = element_rect(fill = ELEVATED_BG, color = NA),\n    legend.key        = element_rect(fill = ELEVATED_BG, color = NA),\n    legend.text       = element_text(color = INK_SOFT, size = 8),\n    legend.title      = element_text(color = INK, size = 10),\n    legend.position   = \"right\"\n  )\n\n# --- Plot ---------------------------------------------------------------\np <- ggplot() +\n  geom_hline(yintercept = 0, color = INK_SOFT, linewidth = 0.3, alpha = 0.4) +\n  geom_vline(xintercept = 0, color = INK_SOFT, linewidth = 0.3, alpha = 0.4) +\n  geom_path(\n    data = trajectories,\n    aes(x = x, y = dx_dt, color = label),\n    linewidth = 0.85, alpha = 0.85, lineend = \"round\"\n  ) +\n  geom_segment(\n    data = arrows,\n    aes(x = x, y = y, xend = xend, yend = yend, color = label),\n    linewidth = 0.85,\n    arrow = arrow(length = unit(0.2, \"cm\"), type = \"closed\", angle = 24),\n    show.legend = FALSE\n  ) +\n  geom_point(\n    data = start_points,\n    aes(x = x, y = dx_dt, color = label),\n    size = 3.2, shape = 21, fill = PAGE_BG, stroke = 1.2\n  ) +\n  geom_point(\n    aes(x = 0, y = 0),\n    size = 3.5, shape = 4, stroke = 1.3, color = INK\n  ) +\n  scale_color_manual(values = IMPRINT_PALETTE) +\n  scale_x_continuous(limits = c(-half_extent, half_extent), expand = c(0, 0)) +\n  scale_y_continuous(limits = c(-half_extent, half_extent), expand = c(0, 0)) +\n  coord_fixed(ratio = 1) +\n  labs(\n    title = \"phase-diagram · r · ggplot2 · anyplot.ai\",\n    x = \"Displacement x\",\n    y = expression(Velocity ~ dx/dt),\n    color = \"Initial condition\"\n  ) +\n  anyplot_theme\n\n# --- Save -----------------------------------------------------------------\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 6,\n  height   = 6,\n  units    = \"in\",\n  dpi      = 400\n)\n"}