{"spec_id":"dashboard-metrics-tiles","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' dashboard-metrics-tiles: Real-Time Dashboard Tiles\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 92/100 | Created: 2026-05-21\n\nlibrary(ggplot2)\nlibrary(dplyr)\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\nCOL_GOOD     <- \"#009E73\"  # Okabe-Ito 1 — good / brand\nCOL_WARNING  <- \"#DDCC77\"  # imprint amber — warning\nCOL_CRITICAL <- \"#AE3030\"  # imprint red — critical / bad\n\n# --- Data -------------------------------------------------------------------\n# Server health metrics snapshot (6 tiles in 3x2 grid)\nmetric_names  <- c(\"CPU Usage\", \"Memory\", \"Response Time\", \"Disk I/O\", \"Throughput\", \"Error Rate\")\nvalue_nums    <- c(45.2,  72.1,  118,   38.6,  1247,  0.82)\nvalue_labels  <- c(\"45.2%\", \"72.1%\", \"118 ms\", \"38.6%\", \"1,247 req/s\", \"0.82%\")\nchanges       <- c(-5.2,   8.1,  -14.7,   3.4,  12.3, -22.5)\nstatuses      <- c(\"good\", \"warning\", \"good\", \"good\", \"good\", \"good\")\nup_is_good    <- c(FALSE,  FALSE,  FALSE,  FALSE,  TRUE,  FALSE)\n\nn_metrics <- length(metric_names)\nn_pts     <- 24\n\nstatus_colors <- ifelse(\n  statuses == \"critical\", COL_CRITICAL,\n  ifelse(statuses == \"warning\", COL_WARNING, COL_GOOD)\n)\n\nchange_colors <- ifelse(\n  (changes > 0 & !up_is_good) | (changes < 0 & up_is_good),\n  COL_CRITICAL, COL_GOOD\n)\n\narrows        <- ifelse(changes > 0, \"▲\", \"▼\")\nchange_labels <- paste0(arrows, \" \", sprintf(\"%.1f\", abs(changes)), \"%\")\n\nmetrics_df <- data.frame(\n  metric        = factor(metric_names, levels = metric_names),\n  value_label   = value_labels,\n  change_label  = change_labels,\n  status_color  = status_colors,\n  change_color  = change_colors,\n  stringsAsFactors = FALSE\n)\n\n# Generate sparkline histories (end pinned to current value, with slight trend)\nspark_list <- lapply(seq_len(n_metrics), function(i) {\n  base <- value_nums[i]\n  chg  <- changes[i] / 100 * base\n  steps <- rnorm(n_pts, mean = chg / n_pts, sd = base * 0.035)\n  vals  <- base - chg + cumsum(steps)\n  vals[n_pts] <- base\n  data.frame(\n    metric       = metric_names[i],\n    t            = seq_len(n_pts),\n    val          = vals,\n    status_color = status_colors[i],\n    stringsAsFactors = FALSE\n  )\n})\nspark_df <- do.call(rbind, spark_list)\nspark_df$metric <- factor(spark_df$metric, levels = metric_names)\n\n# Normalise each sparkline to [0.15, 0.65] within the panel's y space\nspark_df <- spark_df |>\n  group_by(metric) |>\n  mutate(val_norm = rescale(val, to = c(0.15, 0.65))) |>\n  ungroup()\n\nspark_end <- spark_df[spark_df$t == n_pts, ]\n\n# Annotation positions within the normalised [−0.18, 1.55] y range\nlabel_df <- data.frame(\n  metric       = metrics_df$metric,\n  x_mid        = (n_pts + 1) / 2,\n  y_value      = 1.35,\n  y_change     = 1.08,\n  y_name       = -0.06,\n  value_label  = metrics_df$value_label,\n  change_label = metrics_df$change_label,\n  status_color = metrics_df$status_color,\n  change_color = metrics_df$change_color,\n  name_color   = INK_SOFT,\n  stringsAsFactors = FALSE\n)\n\n# --- Plot -------------------------------------------------------------------\np <- ggplot() +\n  # Shaded area under sparkline\n  geom_area(\n    data = spark_df,\n    aes(x = t, y = val_norm, fill = status_color, group = metric),\n    alpha = 0.15,\n    show.legend = FALSE\n  ) +\n  # Sparkline\n  geom_line(\n    data = spark_df,\n    aes(x = t, y = val_norm, color = status_color, group = metric),\n    linewidth = 0.9,\n    show.legend = FALSE\n  ) +\n  # Terminal dot\n  geom_point(\n    data = spark_end,\n    aes(x = t, y = val_norm, color = status_color),\n    size = 2.0,\n    show.legend = FALSE\n  ) +\n  # KPI value — large, status-coloured\n  geom_text(\n    data = label_df,\n    aes(x = x_mid, y = y_value, label = value_label, color = status_color),\n    size = 7,\n    fontface = \"bold\",\n    show.legend = FALSE\n  ) +\n  # Change indicator with directional arrow\n  geom_text(\n    data = label_df,\n    aes(x = x_mid, y = y_change, label = change_label, color = change_color),\n    size = 3.2,\n    show.legend = FALSE\n  ) +\n  # Metric name label at bottom of tile\n  geom_text(\n    data = label_df,\n    aes(x = x_mid, y = y_name, label = metric, color = name_color),\n    size = 3.5,\n    fontface = \"bold\",\n    show.legend = FALSE\n  ) +\n  scale_color_identity() +\n  scale_fill_identity() +\n  facet_wrap(~metric, nrow = 2, ncol = 3) +\n  scale_y_continuous(limits = c(-0.18, 1.55), expand = c(0, 0)) +\n  scale_x_continuous(expand = expansion(mult = 0.05)) +\n  labs(\n    title = \"Server Health · dashboard-metrics-tiles · r · ggplot2 · anyplot.ai\"\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 = INK_SOFT, linewidth = 0.4),\n    panel.grid       = element_blank(),\n    axis.text        = element_blank(),\n    axis.title       = element_blank(),\n    axis.ticks       = element_blank(),\n    strip.text       = element_blank(),\n    plot.title       = element_text(color = INK, size = 11, hjust = 0.5),\n    plot.margin      = margin(t = 20, r = 20, b = 20, l = 20),\n    panel.spacing.x  = unit(1.5, \"lines\"),\n    panel.spacing.y  = unit(1.5, \"lines\")\n  )\n\n# --- Save -------------------------------------------------------------------\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"}