---
title: "Building an Interactive Dashboard"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Building an Interactive Dashboard}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
eval = FALSE
)
```
This article shows how to build a self-contained HTML dashboard from Brightspace
data using R Markdown and Chart.js. The result is a single HTML file you can
open in any browser, share with colleagues, or host on a web server -- no R or
Shiny required to view it.
## Strategy
R Markdown is ideal for this: R chunks compute and aggregate data, then knitr's
inline R expressions inject the results directly into Chart.js JavaScript
blocks. knitr knits the whole thing into a single self-contained HTML file.
## Step 1: Create the Rmd template
Create a file called `dashboard.Rmd` with this YAML front matter. The `params`
block lets you render the same template for different date ranges.
```yaml
---
title: "Brightspace LMS Dashboard"
output:
html_document:
self_contained: true
theme: null
params:
from_date: !r Sys.Date() - 365
to_date: !r Sys.Date()
---
```
## Step 2: Data preparation chunk
Add a hidden R chunk that loads and aggregates data. This chunk runs but
produces no visible output (`include=FALSE`).
```{r, eval=FALSE}
# This chunk uses include=FALSE in the actual Rmd
library(brightspaceR)
library(dplyr)
library(lubridate)
library(jsonlite)
# Helpers: convert R vectors to JS array literals
js_labels <- function(x) toJSON(as.character(x), auto_unbox = FALSE)
js_values <- function(x) toJSON(as.numeric(x), auto_unbox = FALSE)
# Fetch datasets
enrollments <- bs_get_dataset("User Enrollments")
roles <- bs_get_dataset("Role Details")
grades <- bs_get_dataset("Grade Results")
org_units <- bs_get_dataset("Org Units")
users <- bs_get_dataset("Users")
# Apply date filter from params
enrollments <- enrollments |>
filter(
as.Date(enrollment_date) >= params$from_date,
as.Date(enrollment_date) <= params$to_date
)
# KPIs
total_users <- format(nrow(users), big.mark = ",")
total_enrol <- format(nrow(enrollments), big.mark = ",")
n_courses <- format(
n_distinct(org_units$org_unit_id[org_units$type == "Course Offering"]),
big.mark = ","
)
avg_grade <- grades |>
filter(!is.na(points_numerator), points_numerator >= 0) |>
summarise(m = round(mean(points_numerator, na.rm = TRUE), 1)) |>
pull(m)
# Chart data
role_counts <- enrollments |>
bs_join_enrollments_roles(roles) |>
count(role_name, sort = TRUE) |>
head(8)
monthly_trend <- enrollments |>
mutate(month = floor_date(as.Date(enrollment_date), "month")) |>
count(month) |>
arrange(month)
grade_dist <- grades |>
filter(!is.na(points_numerator), points_numerator >= 0) |>
mutate(bracket = cut(points_numerator,
breaks = seq(0, 100, 10), include.lowest = TRUE, right = FALSE
)) |>
count(bracket) |>
filter(!is.na(bracket))
top_courses <- enrollments |>
bs_join_enrollments_orgunits(org_units) |>
filter(type == "Course Offering") |>
count(name, sort = TRUE) |>
head(10)
```
## Step 3: HTML layout with inline R
Below the data chunk, add raw HTML for the dashboard layout. knitr evaluates
inline R expressions everywhere in the document -- including inside HTML tags
and `
#
```
When knitr processes the Rmd, the inline R expressions are replaced with their
evaluated results:
```
Before knitr:
labels: INLINE_R: js_labels(role_counts$role_name)
After knitr:
labels: ["Student","Instructor","TA","Observer"]
```
The `js_labels()` and `js_values()` helpers use `jsonlite::toJSON()` to produce
valid JavaScript array literals. This is safer than manual `paste0()` because
`toJSON()` handles quoting, escaping, and edge cases automatically.
The same pattern applies to each chart: line charts for trends, bar charts for
distributions, horizontal bars for top courses. Each `new Chart()` call
references a `