{"spec_id":"windrose-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' windrose-basic: Wind Rose Chart\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 87/100 | Created: 2026-08-05\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(tidyr)\nlibrary(scales)\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\"\n\n# --- Data -----------------------------------------------------------------\n# Simulated hourly wind observations from a coastal weather station over one\n# year: a prevailing south-westerly flow plus a background of variable winds.\nn <- 4380\nfrom_prevailing <- runif(n) < 0.7\ndirection_raw <- ifelse(\n  from_prevailing,\n  rnorm(n, mean = 225, sd = 32),\n  runif(n, 0, 360)\n)\ndirection <- direction_raw %% 360\nspeed <- rweibull(n, shape = 2.1, scale = 9)\n\ncompass <- c(\n  \"N\", \"NNE\", \"NE\", \"ENE\", \"E\", \"ESE\", \"SE\", \"SSE\",\n  \"S\", \"SSW\", \"SW\", \"WSW\", \"W\", \"WNW\", \"NW\", \"NNW\"\n)\nsector_idx <- floor(((direction + 11.25) %% 360) / 22.5) + 1\nsector <- factor(compass[sector_idx], levels = compass)\n\nspeed_breaks <- c(0, 5, 10, 15, 20, Inf)\nspeed_labels <- c(\"0-5\", \"5-10\", \"10-15\", \"15-20\", \"20+\")\nspeed_bin <- cut(speed, breaks = speed_breaks, labels = speed_labels, right = FALSE)\n\nwind <- tibble::tibble(sector = sector, speed_bin = speed_bin)\n\nwind_freq <- wind %>%\n  count(sector, speed_bin, name = \"n\") %>%\n  complete(sector, speed_bin, fill = list(n = 0)) %>%\n  mutate(pct = 100 * n / sum(n))\n\n# --- Colors -----------------------------------------------------------------\n# imprint_seq (single-polarity, calm -> strong): brand green -> blue.\n# A straight RGB/Lab blend crosses the cyan hues on the \"short way\" between\n# green and blue, where the sRGB gamut is narrower - chroma dips in the\n# middle and the 5-10 / 10-15 stops become hard to tell apart. Interpolating\n# in polar LCh space (circular hue, linear L/C) keeps chroma decreasing\n# smoothly instead of dipping, while the two endpoints stay exact.\nimprint_seq_lch <- function(hex_from, hex_to, n) {\n  to_lab <- function(hex) convertColor(t(col2rgb(hex)) / 255, from = \"sRGB\", to = \"Lab\")\n  polar <- function(lab) c(L = lab[1], C = sqrt(lab[2]^2 + lab[3]^2), H = atan2(lab[3], lab[2]))\n  p1 <- polar(to_lab(hex_from))\n  p2 <- polar(to_lab(hex_to))\n  dH <- p2[\"H\"] - p1[\"H\"]\n  if (dH > pi) dH <- dH - 2 * pi\n  if (dH < -pi) dH <- dH + 2 * pi\n  t <- seq(0, 1, length.out = n)\n  L <- p1[\"L\"] + t * (p2[\"L\"] - p1[\"L\"])\n  C <- p1[\"C\"] + t * (p2[\"C\"] - p1[\"C\"])\n  H <- p1[\"H\"] + t * dH\n  lab_mat <- cbind(L, C * cos(H), C * sin(H))\n  rgb_mat <- pmin(pmax(convertColor(lab_mat, from = \"Lab\", to = \"sRGB\"), 0), 1)\n  grDevices::rgb(rgb_mat[, 1], rgb_mat[, 2], rgb_mat[, 3])\n}\nspeed_colors <- imprint_seq_lch(\"#009E73\", \"#4467A3\", length(speed_labels))\nnames(speed_colors) <- speed_labels\n\n# --- Plot ---------------------------------------------------------------\np <- ggplot(wind_freq, aes(x = sector, y = pct, fill = speed_bin)) +\n  geom_col(\n    width = 1,\n    color = PAGE_BG,\n    linewidth = 0.45,\n    position = position_stack(reverse = TRUE)\n  ) +\n  coord_polar(start = -pi / 16) +\n  scale_x_discrete(name = NULL) +\n  scale_y_continuous(\n    name = NULL,\n    breaks = scales::pretty_breaks(n = 4),\n    labels = function(x) paste0(x, \"%\"),\n    expand = expansion(mult = c(0, 0.06))\n  ) +\n  scale_fill_manual(values = speed_colors, name = \"Wind Speed (m/s)\") +\n  labs(title = \"windrose-basic · r · ggplot2 · anyplot.ai\") +\n  guides(fill = guide_legend(title.position = \"top\", nrow = 1)) +\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   = element_line(color = scales::alpha(INK, 0.18), linewidth = 0.3),\n    panel.grid.minor   = element_blank(),\n    axis.text.x        = element_text(color = INK_SOFT, size = 9),\n    axis.text.y        = element_text(color = INK_SOFT, size = 7),\n    axis.ticks         = element_blank(),\n    plot.title         = element_text(color = INK, size = 12, hjust = 0.5),\n    legend.position     = \"bottom\",\n    legend.direction    = \"horizontal\",\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, hjust = 0.5),\n    plot.margin         = margin(10, 10, 10, 10)\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"}