{"spec_id":"flamegraph-basic","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' flamegraph-basic: Flame Graph for Performance Profiling\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 86/100 | Created: 2026-06-08\n\nlibrary(ggplot2)\nlibrary(dplyr)\nlibrary(tidyr)\nlibrary(ragg)\n\nset.seed(42)\n\n# Theme tokens — Imprint, 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 warm anchors — semantic exception for the conventional\n# flame-graph aesthetic (matte red → ochre → amber).\nFLAME_BASE  <- \"#AE3030\"  # matte red (Imprint pos 5) — base of flame\nFLAME_MID   <- \"#BD8233\"  # ochre (Imprint pos 4)\nFLAME_TIP   <- \"#DDCC77\"  # amber (Imprint anchor) — tip of flame\n# Dual label inks — picked per bar via fill luminance so deep-flame rows\n# (matte-red base) read in light ink while warm tip rows keep dark ink.\nLABEL_DARK  <- \"#1A1A17\"\nLABEL_LIGHT <- \"#F0EFE8\"\n\n# Data — simulated CPU profile of a model-training script.\n# Each row is a leaf call stack and its sample count; parent widths\n# are derived by summing all descendant leaves through the prefix.\nstacks <- tibble::tribble(\n  ~stack, ~value,\n  \"main;parse_config;tokenize;regex_match\",                         24,\n  \"main;parse_config;tokenize;trim_ws\",                              8,\n  \"main;parse_config;validate_schema;type_check\",                   28,\n  \"main;parse_config;validate_schema;range_check\",                  14,\n  \"main;load_dataset;read_csv;buffered_read;syscall_read\",          56,\n  \"main;load_dataset;read_csv;buffered_read;prefetch\",              12,\n  \"main;load_dataset;read_csv;parse_row;cast_types\",                38,\n  \"main;load_dataset;read_csv;parse_row;split_columns\",             21,\n  \"main;load_dataset;decode_utf8\",                                  22,\n  \"main;train_model;preprocess;normalize\",                          31,\n  \"main;train_model;preprocess;impute_missing\",                     14,\n  \"main;train_model;forward_pass;matmul;simd_kernel\",              152,\n  \"main;train_model;forward_pass;matmul;dispatch_blas\",             38,\n  \"main;train_model;forward_pass;activation_relu\",                  28,\n  \"main;train_model;backward_pass;grad_matmul;simd_kernel\",        124,\n  \"main;train_model;backward_pass;grad_matmul;dispatch_blas\",       32,\n  \"main;train_model;backward_pass;update_weights;step_sgd\",         36,\n  \"main;train_model;backward_pass;update_weights;apply_momentum\",   18,\n  \"main;evaluate;forward_pass;matmul;simd_kernel\",                  22,\n  \"main;evaluate;forward_pass;matmul;dispatch_blas\",                 9,\n  \"main;evaluate;forward_pass;activation_relu\",                      6,\n  \"main;evaluate;compute_metrics;accuracy_top_k\",                    8,\n  \"main;evaluate;compute_metrics;confusion_matrix\",                  6,\n  \"main;render_report;format_summary\",                               9,\n  \"main;render_report;write_output\",                                 6,\n  \"main;render_report;compress_artifacts\",                           4\n)\n\n# Expand each leaf stack into per-depth prefix frames, then aggregate.\nexpand_stack <- function(stack_str, val) {\n  parts <- strsplit(stack_str, \";\", fixed = TRUE)[[1]]\n  tibble::tibble(\n    depth  = seq_along(parts),\n    func   = parts,\n    prefix = vapply(seq_along(parts),\n                    function(i) paste(parts[seq_len(i)], collapse = \";\"),\n                    character(1)),\n    value  = val\n  )\n}\n\nframes <- do.call(rbind, Map(expand_stack, stacks$stack, stacks$value)) %>%\n  group_by(depth, prefix) %>%\n  summarise(value = sum(value), .groups = \"drop\") %>%\n  mutate(\n    func   = vapply(strsplit(prefix, \";\", fixed = TRUE),\n                    function(p) p[length(p)], character(1)),\n    parent = vapply(strsplit(prefix, \";\", fixed = TRUE),\n                    function(p) if (length(p) == 1) NA_character_\n                                else paste(p[-length(p)], collapse = \";\"),\n                    character(1))\n  ) %>%\n  arrange(depth, prefix) %>%\n  as.data.frame()\n\n# Lay out x positions: each child sits within its parent's span,\n# siblings ordered alphabetically (standard flame-graph convention).\nframes$xmin <- NA_real_\nframes$xmax <- NA_real_\n\nfor (d in sort(unique(frames$depth))) {\n  if (d == 1) {\n    cum <- 0\n    for (i in which(frames$depth == 1)) {\n      frames$xmin[i] <- cum\n      frames$xmax[i] <- cum + frames$value[i]\n      cum <- cum + frames$value[i]\n    }\n  } else {\n    for (par in unique(frames$parent[frames$depth == d])) {\n      par_row <- which(frames$prefix == par)\n      cum     <- frames$xmin[par_row]\n      kids    <- which(frames$parent == par & frames$depth == d)\n      kids    <- kids[order(frames$prefix[kids])]\n      for (i in kids) {\n        frames$xmin[i] <- cum\n        frames$xmax[i] <- cum + frames$value[i]\n        cum <- cum + frames$value[i]\n      }\n    }\n  }\n}\n\n# Y geometry — one row per depth, narrow gap between rows.\nframes$ymin <- frames$depth - 1 + 0.05\nframes$ymax <- frames$depth - 0.05\n\n# Conditional labels — truncate function names that don't fit the box,\n# drop entirely when the box is too narrow for even three characters.\ntotal_width  <- max(frames$xmax)\nframes$frac  <- (frames$xmax - frames$xmin) / total_width\nchar_budget  <- 95\nframes$max_chars <- floor(frames$frac * char_budget)\nframes$label <- vapply(seq_len(nrow(frames)), function(i) {\n  name  <- frames$func[i]\n  max_n <- frames$max_chars[i]\n  if (max_n >= nchar(name)) return(name)\n  if (max_n >= 4)          return(paste0(substr(name, 1, max_n - 1L), \"…\"))\n  \"\"\n}, character(1))\n\nmax_depth <- max(frames$depth)\n\n# Pick a contrasting label ink per bar — WCAG relative luminance on the\n# interpolated gradient fill, threshold tuned so depth-1/2 dark-red rows\n# get light text (AA) and ochre/amber rows keep dark text.\nfill_ramp  <- grDevices::colorRamp(c(FLAME_BASE, FLAME_MID, FLAME_TIP))\ndepth_norm <- (frames$depth - 1) / (max_depth - 1)\nfill_rgb   <- fill_ramp(depth_norm) / 255\nfill_lin   <- ifelse(fill_rgb <= 0.03928,\n                     fill_rgb / 12.92,\n                     ((fill_rgb + 0.055) / 1.055) ^ 2.4)\nframes$lum <- 0.2126 * fill_lin[, 1] +\n              0.7152 * fill_lin[, 2] +\n              0.0722 * fill_lin[, 3]\nframes$label_color <- ifelse(frames$lum < 0.25, LABEL_LIGHT, LABEL_DARK)\n\n# Plot\np <- ggplot(frames,\n            aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax, fill = depth)) +\n  geom_rect(color = ELEVATED_BG, linewidth = 0.5) +\n  geom_text(\n    aes(x = (xmin + xmax) / 2, y = (ymin + ymax) / 2,\n        label = label, color = label_color),\n    size = 3.0, family = \"sans\"\n  ) +\n  scale_color_identity() +\n  scale_fill_gradientn(\n    colors = c(FLAME_BASE, FLAME_MID, FLAME_TIP),\n    name   = \"depth\"\n  ) +\n  scale_x_continuous(\n    expand = expansion(add = c(0, 0)),\n    labels = scales::label_comma()\n  ) +\n  scale_y_continuous(\n    expand = expansion(add = c(0.05, 0.25)),\n    breaks = seq(0.5, max_depth - 0.5, by = 1),\n    labels = as.character(seq_len(max_depth))\n  ) +\n  labs(\n    title = \"flamegraph-basic · r · ggplot2 · anyplot.ai\",\n    x     = \"Samples (CPU time)\",\n    y     = \"Call stack depth\"\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 = ELEVATED_BG, color = NA),\n    panel.grid.major  = element_blank(),\n    panel.grid.minor  = element_blank(),\n    axis.title        = element_text(color = INK,      size = 10),\n    axis.title.x      = element_text(margin = margin(t = 8)),\n    axis.title.y      = element_text(margin = margin(r = 8)),\n    axis.text         = element_text(color = INK_SOFT, size = 8),\n    axis.ticks.x      = element_line(color = INK_SOFT),\n    axis.ticks.length = unit(3, \"pt\"),\n    axis.line.x       = element_line(color = INK_SOFT),\n    axis.line.y       = element_line(color = INK_SOFT, linewidth = 0.3),\n    plot.title        = element_text(color = INK, size = 12,\n                                     margin = margin(b = 10)),\n    plot.margin       = margin(t = 12, r = 18, b = 10, l = 12),\n    legend.position   = \"none\"\n  )\n\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"}