Replicating Anthropic’s charts

Anthropic’s communication relies on sharp data visualization. This vignette will show how to replicate the general look of their charts using claudeplot.

library(ggplot2)
library(claudeplot)

Agentic Coding benchmark

The first example comes from the Claude Fable 5 announcement post.

Anthropic’s model announcements lean on a recognisable chart: grouped, rounded-top bars comparing models on a coding benchmark, labelled directly, with a clean horizontal-grid theme. This article rebuilds that look from the ground up with claudeplot, gground, and patchwork.

The data

We can extract the data visually from the chart above.

dat <- data.frame(
  model = c("Claude Fable", "Claude Opus 4.8", "GPT 5.5"),
  benchmark = rep(c("SWE-Bench Pro", "FrontierCode"), each = 3),
  performance = c(80.3, 69.2, 58.6, 29.3, 13.4, 5.7)
)

dat
#>             model     benchmark performance
#> 1    Claude Fable SWE-Bench Pro        80.3
#> 2 Claude Opus 4.8 SWE-Bench Pro        69.2
#> 3         GPT 5.5 SWE-Bench Pro        58.6
#> 4    Claude Fable  FrontierCode        29.3
#> 5 Claude Opus 4.8  FrontierCode        13.4
#> 6         GPT 5.5  FrontierCode         5.7

Colors

The discrete scales (scale_fill_claude_d()) are the quickest way to colour a chart, but Anthropic’s slides usually pick a deliberate accent per model. Pull specific colours straight from the palettes with claude_palette():

pal_cols <- c(
  claude_palette("warm")[1],
  claude_palette("claude")[1],
  claude_palette("brand")[4]
)

pal_cols
#> [1] "#E8703A" "#1CA672" "#B0AEA5"

Left panel

Each panel is a geom_col_rounded() chart with direct value labels in Poppins. The theme_sub_*() helpers (from ggplot2 4.0) let us tweak one part of the theme at a time: here we keep the bottom strip text as the benchmark label, drop the x-axis ticks and title, and bold the model names.

library(gground)

p1 <- ggplot(
  subset(dat, benchmark == "SWE-Bench Pro"),
  aes(model, performance, fill = model)
) +
  geom_round_col(radius = 4) +
  geom_text(
    aes(label = performance),
    size = 4,
    family = "Poppins",
    nudge_y = 5
  ) +
  scale_x_discrete(labels = c("", "\nSWE-Bench Pro", "")) +
  scale_y_continuous(
    breaks = seq(0, 100, 20),
    limits = c(NA, 100),
    expand = expansion(c(0, 0.05))
  ) +
  scale_fill_manual(values = pal_cols) +
  guides(fill = "none") +
  labs(y = "Success Rate (%)") +
  theme_claude() +
  theme_sub_axis(ticks = element_blank()) +
  theme_sub_axis_x(
    title = element_blank(),
    text = element_text(face = "bold")
  )

p1

Right panel

The companion panel reuses the same recipe, but moves the legend inside the plotting area with theme_sub_legend() and swaps the legend glyph to points:

p2 <- ggplot(
  subset(dat, benchmark == "FrontierCode"),
  aes(model, performance, fill = model)
) +
  geom_round_col(radius = 4, key_glyph = "point") +
  geom_text(
    aes(label = performance),
    size = 4,
    family = "Poppins",
    nudge_y = 2
  ) +
  scale_x_discrete(labels = c("", "\nFrontierCode", "")) +
  scale_y_continuous(
    breaks = seq(0, 35, 5),
    limits = c(NA, 37),
    expand = expansion(c(0, 0.05))
  ) +
  scale_fill_manual(name = NULL, values = pal_cols) +
  guides(fill = guide_legend(override.aes = list(shape = 21, size = 3))) +
  labs(y = "Success Rate (%)") +
  theme_claude() +
  theme_sub_axis(ticks = element_blank()) +
  theme_sub_axis_x(
    title = element_blank(),
    text = element_text(face = "bold")
  ) +
  theme_sub_legend(
    position = "inside",
    position.inside = c(0.65, 0.9),
    direction = "vertical",
    background = element_rect(fill = "gray90", color = "gray80")
  )

p2

Combining the panels

Finally, stitch the two panels together with patchwork and add a centred title themed with theme_claude():

library(patchwork)

(p1 | p2) +
  plot_annotation(
    title = "Agentic coding",
    theme = theme_claude() + theme(plot.title = element_text(hjust = 0.5))
  )

That’s the full Anthropic-style benchmark chart: rounded bars, hand-picked accent colours, direct labels in Poppins, and a clean theme_claude() base.

Prediction

The following example replicates the Viral capsid experimental property prediction chart from the Claude Fable 5 announcement post.

The data

To make the example more realistic, we will simulate a data distribution with similar characteristics.

set.seed(987123)

dat <- data.frame(
  model_name = rep(
    c("Claude Opus 4.8", "Claude Mythos Preview", "Claude Mythos 5"),
    each = 200
  ),
  value = c(
    (0.75 - rexp(200, 9)),
    rnorm(200, mean = 0.825, sd = 0.02),
    rnorm(200, mean = 0.85, sd = 0.02)
  )
)

lvls <- c("Claude Opus 4.8", "Claude Mythos Preview", "Claude Mythos 5")
dat$model_name <- factor(dat$model_name, levels = lvls)

Again, gground pairs nicely with claudeplot with the geom_round_boxplot() geom.

ggplot(dat, aes(model_name, value, fill = model_name)) +
  geom_round_boxplot(
    linewidth = 0.25,
    width = 0.5,
    outliers = FALSE
  ) +
  geom_hline(
    yintercept = 0.5,
    linetype = "dashed",
    color = claude_colors[["graphite"]]
  ) +
  annotate(
    "text",
    x = 3,
    y = 0.48,
    label = "Protein language model baseline",
    family = "Poppins",
    size = 3,
    color = claude_colors[["graphite"]]
  ) +
  scale_fill_claude_d() +
  labs(
    title = "Viral capsid experimental property prediction",
    subtitle = "Unsupervised AAV packaging classification (reasoning alone)",
    x = NULL,
    y = "Score"
  ) +
  theme_claude() +
  theme_sub_legend(position = "none")

OBS: while ggplot2 (>= 4.0.0) offers finer control of boxplot aesthetics, geom_round_boxplot currently doesn’t support arguments like box.color or staplewidth.

Finally, to make a closer replication, use stat = "identity".

dat <- data.frame(
  model_name = c("Claude Opus 4.8", "Claude Mythos Preview", "Claude Mythos 5"),
  ymin = c(0.645, 0.82, 0.84),
  ymax = c(0.835, 0.855, 0.86),
  middle = c(0.79, 0.835, 0.855),
  upper = c(0.82, 0.845, 0.858),
  lower = c(0.735, 0.825, 0.845)
)

lvls <- c("Claude Opus 4.8", "Claude Mythos Preview", "Claude Mythos 5")
dat$model_name <- factor(dat$model_name, levels = lvls)
ggplot(dat, aes(model_name, fill = model_name)) +
  geom_round_boxplot(
    aes(
      ymin = ymin,
      middle = middle,
      ymax = ymax,
      lower = lower,
      upper = upper
    ),
    linewidth = 0.25,
    width = 0.5,
    outliers = FALSE,
    stat = "identity"
  ) +
  geom_hline(
    yintercept = 0.7,
    linetype = "dashed",
    color = claude_colors[["graphite"]]
  ) +
  annotate(
    "text",
    x = 3,
    y = 0.48,
    label = "Protein language model baseline",
    family = "Poppins",
    size = 3,
    color = claude_colors[["graphite"]]
  ) +
  scale_y_continuous(
    breaks = seq(0.6, 0.9, 0.05),
    limits = c(0.6, NA)
  ) +
  scale_fill_claude_d() +
  labs(
    title = "Viral capsid experimental property prediction",
    subtitle = "Unsupervised AAV packaging classification (reasoning alone)",
    x = NULL,
    y = "Score"
  ) +
  theme_claude() +
  theme_sub_legend(position = "none")