{"spec_id":"heatmap-rainflow","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' heatmap-rainflow: Rainflow Counting Matrix for Fatigue Analysis\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 87/100 | Created: 2026-06-02\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(scales)\nlibrary(ragg)\n\nset.seed(42)\n\n# Theme tokens (Imprint palette, theme-adaptive chrome)\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\"\n\n# Data: synthetic rainflow counting results for a structural steel component\n# Amplitude = half stress range (MPa), Mean = mean stress (MPa)\nn_bins       <- 20\namp_centers  <- seq(5, 100, length.out = n_bins)\nmean_centers <- seq(-40, 140, length.out = n_bins)\n\ndf <- expand.grid(amp_mpa = amp_centers, mean_mpa = mean_centers) %>%\n  mutate(\n    # Steeper amplitude decay + tighter Gaussian → concentrated hot-spot with\n    # large zero-count regions at high amplitude and extreme mean values\n    raw        = 3000 * exp(-amp_mpa / 15) * exp(-0.5 * ((mean_mpa - 60) / 25)^2),\n    count      = as.integer(pmax(0, round(raw + abs(rnorm(n(), 0, raw * 0.1 + 5))))),\n    count_plot = if_else(count <= 2L, NA_real_, as.double(count))\n  ) %>%\n  select(-raw)\n\n# Contour threshold at 25% of peak to outline the dominant fatigue cycle zone\ncontour_at <- max(df$count, na.rm = TRUE) * 0.25\n\n# Plot\ntitle_str <- \"heatmap-rainflow · r · ggplot2 · anyplot.ai\"\n\np <- ggplot(df, aes(x = mean_mpa, y = amp_mpa, fill = count_plot)) +\n  geom_tile() +\n  geom_contour(aes(z = count), color = INK_SOFT, linewidth = 0.4,\n               breaks = contour_at, alpha = 0.7) +\n  scale_fill_gradient(\n    low            = \"#009E73\",\n    high           = \"#4467A3\",\n    na.value       = PAGE_BG,\n    name           = \"Cycle Count\",\n    trans          = \"sqrt\",\n    labels         = scales::comma,\n    guide          = guide_colorbar(\n      barheight      = unit(10, \"lines\"),\n      title.position = \"top\"\n    )\n  ) +\n  scale_x_continuous(breaks = scales::pretty_breaks(n = 6)) +\n  scale_y_continuous(breaks = scales::pretty_breaks(n = 6)) +\n  labs(\n    title = title_str,\n    x     = \"Mean Stress (MPa)\",\n    y     = \"Stress Amplitude (MPa)\"\n  ) +\n  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        = element_blank(),\n    panel.border      = element_blank(),\n    axis.line         = element_line(color = INK_SOFT,   linewidth = 0.5),\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 = 12, hjust = 0.5),\n    legend.background = 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    plot.margin       = margin(20, 20, 20, 20)\n  )\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"}