Overview
This vignette shows how to build a PowerPoint pipeline report using activecampaignr, officer, and mschart.
Why mschart? When you embed a ggplot into PowerPoint
via dml(ggobj = ...), the chart is a static image.
Recipients can’t edit the data, change colours, or resize without losing
quality. mschart creates native Office charts: fully
editable, with the data table embedded in the .pptx file.
Your audience can restyle the chart in PowerPoint without needing R.
Fetch Data
Use ac_deals_full() to get deals pre-joined with stage
names, pipeline names, and owner names:
deals <- ac_deals_full()Chart 1: Deals Won by Day of Week (mschart bar chart)
dow_data <- won |>
count(dow, name = "deals") |>
filter(!is.na(dow))
chart_dow <- ms_barchart(
data = dow_data,
x = "dow",
y = "deals"
) |>
chart_settings(dir = "vertical", grouping = "clustered") |>
chart_labels(
title = "Deals Won by Day of Week",
xlab = NULL,
ylab = "Deals"
) |>
chart_data_fill(values = rep("#356AE6", 7)) |>
chart_theme(
grid_major_line = fp_border(style = "none"),
legend_position = "n"
)Chart 2: Pipeline Velocity by Stage (mschart bar chart)
velocity_data <- won |>
filter(!is.na(stage_title)) |>
group_by(stage = stage_title) |>
summarise(
median_days = round(median(days_in_pipeline, na.rm = TRUE), 1),
.groups = "drop"
) |>
arrange(median_days)
chart_velocity <- ms_barchart(
data = velocity_data,
x = "stage",
y = "median_days"
) |>
chart_settings(dir = "horizontal", grouping = "clustered") |>
chart_labels(
title = "Pipeline Velocity (Median Days by Stage)",
xlab = NULL,
ylab = "Days"
) |>
chart_data_fill(values = rep("#1A3A7A", nrow(velocity_data))) |>
chart_theme(
grid_major_line = fp_border(style = "none"),
legend_position = "n"
)Chart 3: Win Rate by Owner (mschart bar chart)
owner_data <- won |>
filter(!is.na(owner_name)) |>
group_by(owner = owner_name) |>
summarise(
total = n(),
.groups = "drop"
) |>
# Join back to get all deals (not just won) for win rate
left_join(
deals |>
filter(!is.na(owner_name)) |>
count(owner = owner_name, name = "all_deals"),
by = "owner"
) |>
mutate(win_rate = round(total / all_deals * 100, 1)) |>
filter(all_deals >= 10) |>
arrange(desc(win_rate))
chart_owner <- ms_barchart(
data = owner_data,
x = "owner",
y = "win_rate"
) |>
chart_settings(dir = "horizontal", grouping = "clustered") |>
chart_labels(
title = "Win Rate by Owner (%)",
xlab = NULL,
ylab = "Win Rate (%)"
) |>
chart_data_fill(values = rep("#16a34a", nrow(owner_data))) |>
chart_theme(
grid_major_line = fp_border(style = "none"),
legend_position = "n"
)Chart 4: Monthly Deal Flow (mschart line chart)
monthly_flow <- deals |>
mutate(month = format(cdate, "%Y-%m")) |>
group_by(month) |>
summarise(
created = n(),
won = sum(status == "1", na.rm = TRUE),
lost = sum(status == "2", na.rm = TRUE),
.groups = "drop"
) |>
filter(!is.na(month)) |>
tidyr::pivot_longer(
cols = c(created, won, lost),
names_to = "metric",
values_to = "count"
)
chart_flow <- ms_linechart(
data = monthly_flow,
x = "month",
y = "count",
group = "metric"
) |>
chart_labels(
title = "Monthly Deal Flow",
xlab = NULL,
ylab = "Deals"
) |>
chart_data_stroke(
values = c(created = "#356AE6", won = "#16a34a", lost = "#dc2626")
) |>
chart_data_size(values = c(created = 2, won = 2, lost = 2)) |>
chart_theme(legend_position = "b")Assemble the PowerPoint
pptx <- read_pptx() |>
# Title slide
add_slide(layout = "Title Slide") |>
ph_with(
"Pipeline Analysis Report",
location = ph_location_type("ctrTitle")
) |>
ph_with(
format(Sys.Date(), "%d %b %Y"),
location = ph_location_type("subTitle")
) |>
# Slide 1: Day of week
add_slide(layout = "Title and Content") |>
ph_with(
"Deals Won by Day of Week",
location = ph_location_type("title")
) |>
ph_with(
chart_dow,
location = ph_location_type("body")
) |>
# Slide 2: Pipeline velocity
add_slide(layout = "Title and Content") |>
ph_with(
"Pipeline Velocity",
location = ph_location_type("title")
) |>
ph_with(
chart_velocity,
location = ph_location_type("body")
) |>
# Slide 3: Win rate by owner
add_slide(layout = "Title and Content") |>
ph_with(
"Win Rate by Owner",
location = ph_location_type("title")
) |>
ph_with(
chart_owner,
location = ph_location_type("body")
) |>
# Slide 4: Monthly deal flow
add_slide(layout = "Title and Content") |>
ph_with(
"Monthly Deal Flow",
location = ph_location_type("title")
) |>
ph_with(
chart_flow,
location = ph_location_type("body")
) |>
# Slide 5: Summary table
add_slide(layout = "Title and Content") |>
ph_with(
"Summary",
location = ph_location_type("title")
) |>
ph_with(
data.frame(
Metric = c("Total deals", "Won", "Lost", "Open",
"Win rate", "Median velocity"),
Value = c(
nrow(deals),
sum(deals$status == "1", na.rm = TRUE),
sum(deals$status == "2", na.rm = TRUE),
sum(deals$status == "0", na.rm = TRUE),
paste0(round(mean(deals$status == "1", na.rm = TRUE) * 100, 1), "%"),
paste(round(median(won$days_in_pipeline, na.rm = TRUE), 0), "days")
)
),
location = ph_location_type("body")
)
print(pptx, target = "pipeline-report.pptx")The resulting file is a standard .pptx that opens in
PowerPoint, Keynote, or Google Slides. All four charts are native Office
charts: double-click any chart in PowerPoint to edit the underlying
data, change colours, or adjust formatting.
Mixing mschart and ggplot
You can use both in the same report. Use mschart when
you want editable charts, and dml(ggobj = ...) when you
need ggplot-specific features (facets, custom geoms, annotations):
library(ggplot2)
p_custom <- won |>
ggplot(aes(days_in_pipeline)) +
geom_histogram(binwidth = 7, fill = "#356AE6", colour = "white") +
geom_vline(
xintercept = median(won$days_in_pipeline, na.rm = TRUE),
linetype = "dashed", colour = "red"
) +
annotate(
"text",
x = median(won$days_in_pipeline, na.rm = TRUE) + 5,
y = Inf, vjust = 2, hjust = 0,
label = paste("Median:", round(median(won$days_in_pipeline, na.rm = TRUE)), "days"),
colour = "red"
) +
labs(title = "Velocity Distribution", x = "Days to Close", y = "Deals") +
theme_minimal()
# Add as a ggplot image slide (not editable, but supports annotations)
pptx <- pptx |>
add_slide(layout = "Title and Content") |>
ph_with(
"Velocity Distribution (ggplot)",
location = ph_location_type("title")
) |>
ph_with(
dml(ggobj = p_custom),
location = ph_location_type("body")
)
print(pptx, target = "pipeline-report.pptx")When to Use Which
| Approach | Editable in PPT? | Best for |
|---|---|---|
mschart (ms_barchart, ms_linechart, etc.) |
Yes | Bar, line, pie, scatter charts that stakeholders may want to restyle |
dml(ggobj = ...) |
No (image) | Complex ggplot charts with facets, annotations, custom geoms |
ph_with(data.frame(...)) |
Yes (table) | Summary statistics, KPI tables |
