|
| 1 | +#' anyplot.ai |
| 2 | +#' dashboard-metrics-tiles: Real-Time Dashboard Tiles |
| 3 | +#' Library: ggplot2 3.5.1 | R 4.4.1 |
| 4 | +#' Quality: 92/100 | Created: 2026-05-21 |
| 5 | + |
| 6 | +library(ggplot2) |
| 7 | +library(dplyr) |
| 8 | +library(scales) |
| 9 | +library(ragg) |
| 10 | + |
| 11 | +set.seed(42) |
| 12 | + |
| 13 | +# --- Theme tokens ----------------------------------------------------------- |
| 14 | +THEME <- Sys.getenv("ANYPLOT_THEME", "light") |
| 15 | +PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17" |
| 16 | +ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420" |
| 17 | +INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8" |
| 18 | +INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0" |
| 19 | + |
| 20 | +COL_GOOD <- "#009E73" # Okabe-Ito 1 — good / brand |
| 21 | +COL_WARNING <- "#E69F00" # Okabe-Ito 5 — warning |
| 22 | +COL_CRITICAL <- "#D55E00" # Okabe-Ito 2 — critical / bad |
| 23 | + |
| 24 | +# --- Data ------------------------------------------------------------------- |
| 25 | +# Server health metrics snapshot (6 tiles in 3x2 grid) |
| 26 | +metric_names <- c("CPU Usage", "Memory", "Response Time", "Disk I/O", "Throughput", "Error Rate") |
| 27 | +value_nums <- c(45.2, 72.1, 118, 38.6, 1247, 0.82) |
| 28 | +value_labels <- c("45.2%", "72.1%", "118 ms", "38.6%", "1,247 req/s", "0.82%") |
| 29 | +changes <- c(-5.2, 8.1, -14.7, 3.4, 12.3, -22.5) |
| 30 | +statuses <- c("good", "warning", "good", "good", "good", "good") |
| 31 | +up_is_good <- c(FALSE, FALSE, FALSE, FALSE, TRUE, FALSE) |
| 32 | + |
| 33 | +n_metrics <- length(metric_names) |
| 34 | +n_pts <- 24 |
| 35 | + |
| 36 | +status_colors <- ifelse( |
| 37 | + statuses == "critical", COL_CRITICAL, |
| 38 | + ifelse(statuses == "warning", COL_WARNING, COL_GOOD) |
| 39 | +) |
| 40 | + |
| 41 | +change_colors <- ifelse( |
| 42 | + (changes > 0 & !up_is_good) | (changes < 0 & up_is_good), |
| 43 | + COL_CRITICAL, COL_GOOD |
| 44 | +) |
| 45 | + |
| 46 | +arrows <- ifelse(changes > 0, "▲", "▼") |
| 47 | +change_labels <- paste0(arrows, " ", sprintf("%.1f", abs(changes)), "%") |
| 48 | + |
| 49 | +metrics_df <- data.frame( |
| 50 | + metric = factor(metric_names, levels = metric_names), |
| 51 | + value_label = value_labels, |
| 52 | + change_label = change_labels, |
| 53 | + status_color = status_colors, |
| 54 | + change_color = change_colors, |
| 55 | + stringsAsFactors = FALSE |
| 56 | +) |
| 57 | + |
| 58 | +# Generate sparkline histories (end pinned to current value, with slight trend) |
| 59 | +spark_list <- lapply(seq_len(n_metrics), function(i) { |
| 60 | + base <- value_nums[i] |
| 61 | + chg <- changes[i] / 100 * base |
| 62 | + steps <- rnorm(n_pts, mean = chg / n_pts, sd = base * 0.035) |
| 63 | + vals <- base - chg + cumsum(steps) |
| 64 | + vals[n_pts] <- base |
| 65 | + data.frame( |
| 66 | + metric = metric_names[i], |
| 67 | + t = seq_len(n_pts), |
| 68 | + val = vals, |
| 69 | + status_color = status_colors[i], |
| 70 | + stringsAsFactors = FALSE |
| 71 | + ) |
| 72 | +}) |
| 73 | +spark_df <- do.call(rbind, spark_list) |
| 74 | +spark_df$metric <- factor(spark_df$metric, levels = metric_names) |
| 75 | + |
| 76 | +# Normalise each sparkline to [0.15, 0.65] within the panel's y space |
| 77 | +spark_df <- spark_df |> |
| 78 | + group_by(metric) |> |
| 79 | + mutate(val_norm = rescale(val, to = c(0.15, 0.65))) |> |
| 80 | + ungroup() |
| 81 | + |
| 82 | +spark_end <- spark_df[spark_df$t == n_pts, ] |
| 83 | + |
| 84 | +# Annotation positions within the normalised [−0.18, 1.55] y range |
| 85 | +label_df <- data.frame( |
| 86 | + metric = metrics_df$metric, |
| 87 | + x_mid = (n_pts + 1) / 2, |
| 88 | + y_value = 1.35, |
| 89 | + y_change = 1.08, |
| 90 | + y_name = -0.06, |
| 91 | + value_label = metrics_df$value_label, |
| 92 | + change_label = metrics_df$change_label, |
| 93 | + status_color = metrics_df$status_color, |
| 94 | + change_color = metrics_df$change_color, |
| 95 | + name_color = INK_SOFT, |
| 96 | + stringsAsFactors = FALSE |
| 97 | +) |
| 98 | + |
| 99 | +# --- Plot ------------------------------------------------------------------- |
| 100 | +p <- ggplot() + |
| 101 | + # Shaded area under sparkline |
| 102 | + geom_area( |
| 103 | + data = spark_df, |
| 104 | + aes(x = t, y = val_norm, fill = status_color, group = metric), |
| 105 | + alpha = 0.15, |
| 106 | + show.legend = FALSE |
| 107 | + ) + |
| 108 | + # Sparkline |
| 109 | + geom_line( |
| 110 | + data = spark_df, |
| 111 | + aes(x = t, y = val_norm, color = status_color, group = metric), |
| 112 | + linewidth = 0.9, |
| 113 | + show.legend = FALSE |
| 114 | + ) + |
| 115 | + # Terminal dot |
| 116 | + geom_point( |
| 117 | + data = spark_end, |
| 118 | + aes(x = t, y = val_norm, color = status_color), |
| 119 | + size = 2.0, |
| 120 | + show.legend = FALSE |
| 121 | + ) + |
| 122 | + # KPI value — large, status-coloured |
| 123 | + geom_text( |
| 124 | + data = label_df, |
| 125 | + aes(x = x_mid, y = y_value, label = value_label, color = status_color), |
| 126 | + size = 7, |
| 127 | + fontface = "bold", |
| 128 | + show.legend = FALSE |
| 129 | + ) + |
| 130 | + # Change indicator with directional arrow |
| 131 | + geom_text( |
| 132 | + data = label_df, |
| 133 | + aes(x = x_mid, y = y_change, label = change_label, color = change_color), |
| 134 | + size = 3.2, |
| 135 | + show.legend = FALSE |
| 136 | + ) + |
| 137 | + # Metric name label at bottom of tile |
| 138 | + geom_text( |
| 139 | + data = label_df, |
| 140 | + aes(x = x_mid, y = y_name, label = metric, color = name_color), |
| 141 | + size = 3.5, |
| 142 | + fontface = "bold", |
| 143 | + show.legend = FALSE |
| 144 | + ) + |
| 145 | + scale_color_identity() + |
| 146 | + scale_fill_identity() + |
| 147 | + facet_wrap(~metric, nrow = 2, ncol = 3) + |
| 148 | + scale_y_continuous(limits = c(-0.18, 1.55), expand = c(0, 0)) + |
| 149 | + scale_x_continuous(expand = expansion(mult = 0.05)) + |
| 150 | + labs( |
| 151 | + title = "Server Health · dashboard-metrics-tiles · r · ggplot2 · anyplot.ai" |
| 152 | + ) + |
| 153 | + theme_minimal(base_size = 8) + |
| 154 | + theme( |
| 155 | + plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG), |
| 156 | + panel.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT, linewidth = 0.4), |
| 157 | + panel.grid = element_blank(), |
| 158 | + axis.text = element_blank(), |
| 159 | + axis.title = element_blank(), |
| 160 | + axis.ticks = element_blank(), |
| 161 | + strip.text = element_blank(), |
| 162 | + plot.title = element_text(color = INK, size = 11, hjust = 0.5), |
| 163 | + plot.margin = margin(t = 20, r = 20, b = 20, l = 20), |
| 164 | + panel.spacing.x = unit(1.5, "lines"), |
| 165 | + panel.spacing.y = unit(1.5, "lines") |
| 166 | + ) |
| 167 | + |
| 168 | +# --- Save ------------------------------------------------------------------- |
| 169 | +ggsave( |
| 170 | + filename = sprintf("plot-%s.png", THEME), |
| 171 | + plot = p, |
| 172 | + device = ragg::agg_png, |
| 173 | + width = 8, |
| 174 | + height = 4.5, |
| 175 | + units = "in", |
| 176 | + dpi = 400 |
| 177 | +) |
0 commit comments