{"spec_id":"climograph-walter-lieth","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' climograph-walter-lieth: Walter-Lieth Climate Diagram\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 92/100 | Created: 2026-06-15\n\nlibrary(ggplot2)\nlibrary(ragg)\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\"\nINK_MUTED   <- if (THEME == \"light\") \"#6B6A63\" else \"#A8A79F\"\n\n# Imprint categorical palette (hybrid-v3 sort order)\nIMPRINT_PALETTE <- c(\n  \"#009E73\",  # 1 — brand green (first categorical series)\n  \"#C475FD\",  # 2 — lavender\n  \"#4467A3\",  # 3 — blue\n  \"#BD8233\",  # 4 — ochre\n  \"#AE3030\",  # 5 — matte red\n  \"#2ABCCD\",  # 6 — cyan\n  \"#954477\",  # 7 — rose\n  \"#99B314\"   # 8 — lime\n)\n\n# Domain-convention colors (semantic exception: temperature→red, precipitation→blue/water)\nCOLOR_TEMP   <- IMPRINT_PALETTE[5]           # #AE3030 — temperature (hot/warm semantic)\nCOLOR_PRECIP <- IMPRINT_PALETTE[3]           # #4467A3 — precipitation (water semantic)\nCOLOR_FROST  <- IMPRINT_PALETTE[6]           # #2ABCCD — frost period (cold/ice semantic)\nGRID_COLOR   <- adjustcolor(INK, alpha.f = 0.12)\n\n# Station metadata — Ankara, Turkey (continental climate with summer arid period)\nstation_elev        <- 891\nannual_mean_temp    <- 11.8\nannual_precip_total <- 352\n\n# Monthly climate normals (1991-2020 reference period)\nclimate_df <- data.frame(\n  month_idx = 1:12,\n  month_lbl = c(\"J\", \"F\", \"M\", \"A\", \"M\", \"J\", \"J\", \"A\", \"S\", \"O\", \"N\", \"D\"),\n  temp      = c(-0.5,  1.0,  6.0, 11.5, 16.5, 20.0, 23.5, 23.5, 18.5, 12.5,  6.0,  2.0),\n  precip    = c( 38.0, 31.0, 29.0, 33.0, 43.0, 34.0, 16.0, 12.0, 17.0, 27.0, 31.0, 41.0)\n)\n\n# Walter-Lieth 1:2 scaling convention: 10°C aligns with 20 mm on the shared axis\nclimate_df$prec_scaled <- climate_df$precip / 2\n\n# Smooth interpolation between months for continuous fill regions\nn_interp <- 600\nx_smooth <- seq(1, 12, length.out = n_interp)\nt_smooth <- approx(climate_df$month_idx, climate_df$temp,        x_smooth)$y\np_smooth <- approx(climate_df$month_idx, climate_df$prec_scaled, x_smooth)$y\np_smooth <- pmin(p_smooth, 50)  # cap at 100 mm / 2 = 50 (standard WL scale maximum)\n\n# Ribbon endpoints for thin background tint under pattern fills\nfill_df <- data.frame(\n  x          = x_smooth,\n  temp       = t_smooth,\n  prec_s     = p_smooth,\n  humid_ymax = pmax(t_smooth, p_smooth),\n  arid_ymin  = pmin(t_smooth, p_smooth)\n)\n\n# --- Diagonal hatching for humid region (precipitation > temperature curve) ---\n# Slope chosen so hatch lines appear at ~45 degrees on 8×4.5-inch canvas\n# (x range 11 units at 8/11 in/unit, y range 35 units at ~4.1/35 in/unit → slope ≈ 5.5)\nhatch_slope     <- 5.5   # dy/dx in data coordinates\nhatch_spacing_y <- 2.5   # y-intercept spacing between parallel lines\n\n# Intercepts: must cover y ∈ [-0.5, 21] at x ∈ [1,12]; at x=12: y = 60.5 + b\nhatch_intercepts <- seq(-65, 25, by = hatch_spacing_y)\n\nhatch_seg_list <- lapply(hatch_intercepts, function(b) {\n  y_line  <- hatch_slope * (x_smooth - 1) + b\n  in_humid <- p_smooth > t_smooth & y_line >= t_smooth & y_line <= p_smooth\n  if (!any(in_humid)) return(NULL)\n  rle_res <- rle(in_humid)\n  pos <- 1L\n  k   <- 0L\n  out_list <- vector(\"list\", sum(rle_res$values))\n  for (i in seq_along(rle_res$lengths)) {\n    len <- rle_res$lengths[i]\n    if (rle_res$values[i]) {\n      k <- k + 1L\n      end_pos <- pos + len - 1L\n      out_list[[k]] <- data.frame(\n        x    = x_smooth[pos],    xend = x_smooth[end_pos],\n        y    = y_line[pos],      yend = y_line[end_pos]\n      )\n    }\n    pos <- pos + len\n  }\n  if (k > 0L) do.call(rbind, out_list[seq_len(k)]) else NULL\n})\nhatch_seg_df <- do.call(rbind, Filter(Negate(is.null), hatch_seg_list))\n\n# --- Stipple dots for arid region (temperature > precipitation curve) ---\nx_dot_seq <- seq(1, 12, by = 0.6)\ny_dot_seq <- seq(-5, 30, by = 1.6)\ndot_grid  <- expand.grid(x = x_dot_seq, y = y_dot_seq)\ndot_grid$t_at_x <- approx(x_smooth, t_smooth, dot_grid$x, rule = 2)$y\ndot_grid$p_at_x <- approx(x_smooth, p_smooth, dot_grid$x, rule = 2)$y\narid_dots <- dot_grid[\n  dot_grid$t_at_x > dot_grid$p_at_x &\n  dot_grid$y      >= dot_grid$p_at_x &\n  dot_grid$y      <= dot_grid$t_at_x, ]\n\n# Frost-month rectangles (mean temperature below 0°C)\nfrost_months <- climate_df$month_idx[climate_df$temp < 0]\nfrost_df <- if (length(frost_months) > 0) {\n  data.frame(\n    xmin = frost_months - 0.45,\n    xmax = frost_months + 0.45\n  )\n} else {\n  NULL\n}\n\n# Plot title — mandatory anyplot format with descriptive station prefix\nplot_title <- paste0(\n  \"Ankara, Turkey · climograph-walter-lieth · r · ggplot2 · anyplot.ai\"\n)\ntitle_fontsize <- max(8L, round(12L * min(1.0, 67 / nchar(plot_title))))\n\n# Subtitle carries the station header (Walter-Lieth convention)\nplot_subtitle <- sprintf(\n  \"%d m a.s.l.  ·  T = %.1f°C  ·  ΣP = %d mm\",\n  station_elev, annual_mean_temp, as.integer(annual_precip_total)\n)\n\n# Build plot\np <- ggplot() +\n  # Humid background tint (blue, very light) under the hatching\n  geom_ribbon(\n    data = fill_df,\n    aes(x = x, ymin = temp, ymax = humid_ymax),\n    fill = COLOR_PRECIP, alpha = 0.10, inherit.aes = FALSE\n  ) +\n  # Diagonal hatching lines for humid period (blue/hatched per Walter-Lieth convention)\n  (if (!is.null(hatch_seg_df) && nrow(hatch_seg_df) > 0)\n    geom_segment(\n      data = hatch_seg_df,\n      aes(x = x, xend = xend, y = y, yend = yend),\n      color = COLOR_PRECIP, linewidth = 0.35, alpha = 0.65, inherit.aes = FALSE\n    )\n  else NULL) +\n  # Arid background tint (red, very light) under the stipple dots\n  geom_ribbon(\n    data = fill_df,\n    aes(x = x, ymin = arid_ymin, ymax = temp),\n    fill = COLOR_TEMP, alpha = 0.10, inherit.aes = FALSE\n  ) +\n  # Stipple dots for arid period (red/dotted per Walter-Lieth convention)\n  (if (nrow(arid_dots) > 0)\n    geom_point(\n      data = arid_dots,\n      aes(x = x, y = y),\n      color = COLOR_TEMP, size = 0.55, alpha = 0.70, inherit.aes = FALSE\n    )\n  else NULL) +\n  # Frost-month indicator bands below 0°C\n  (if (!is.null(frost_df))\n    geom_rect(\n      data = frost_df,\n      aes(xmin = xmin, xmax = xmax),\n      ymin = -5, ymax = 0,\n      fill = COLOR_FROST, alpha = 0.40, inherit.aes = FALSE\n    )\n  else NULL) +\n  # Frost threshold reference line\n  geom_hline(yintercept = 0, color = INK_SOFT, linewidth = 0.4, linetype = \"dashed\") +\n  # Precipitation curve (plotted at precip/2 on temp axis; right axis shows actual mm)\n  geom_line(\n    data = climate_df,\n    aes(x = month_idx, y = prec_scaled),\n    color = COLOR_PRECIP, linewidth = 1.3, lineend = \"round\"\n  ) +\n  # Temperature curve\n  geom_line(\n    data = climate_df,\n    aes(x = month_idx, y = temp),\n    color = COLOR_TEMP, linewidth = 1.3, lineend = \"round\"\n  ) +\n  # X axis: month abbreviations\n  scale_x_continuous(\n    breaks = 1:12,\n    labels = c(\"J\", \"F\", \"M\", \"A\", \"M\", \"J\", \"J\", \"A\", \"S\", \"O\", \"N\", \"D\"),\n    expand = c(0.025, 0)\n  ) +\n  # Dual y-axis: left = temperature (°C), right = precipitation (mm) at 2× scale\n  scale_y_continuous(\n    name   = \"Temperature (°C)\",\n    limits = c(-5, 30),\n    breaks = c(0, 10, 20, 30),\n    sec.axis = sec_axis(\n      ~ . * 2,\n      name   = \"Precipitation (mm)\",\n      breaks = c(0, 20, 40, 60),\n      labels = c(\"0\", \"20\", \"40\", \"60\")\n    )\n  ) +\n  labs(\n    title    = plot_title,\n    subtitle = plot_subtitle,\n    x        = NULL\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.major.y = element_line(color = GRID_COLOR, linewidth = 0.3),\n    panel.grid.major.x = element_blank(),\n    panel.grid.minor   = element_blank(),\n    panel.border       = element_rect(color = INK_SOFT, fill = NA, linewidth = 0.5),\n    axis.title.y.left  = element_text(color = COLOR_TEMP,   size = 10),\n    axis.title.y.right = element_text(color = COLOR_PRECIP, size = 10),\n    axis.text.y.left   = element_text(color = INK_SOFT, size = 8),\n    axis.text.y.right  = element_text(color = INK_SOFT, size = 8),\n    axis.text.x        = element_text(color = INK_SOFT, size = 8),\n    axis.line          = element_blank(),\n    plot.title         = element_text(color = INK, size = title_fontsize, face = \"bold\"),\n    plot.subtitle      = element_text(color = INK_SOFT, size = 9,\n                                      margin = margin(t = 2, b = 6)),\n    plot.margin        = margin(12, 16, 10, 12, \"pt\")\n  )\n\n# Save (landscape: 8 × 4.5 in @ 400 dpi → 3200 × 1800 px)\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"}