16.2 Building the Dashboard

A Quarto Dashboard is a .qmd file with format: dashboard in the YAML header. Sections of the document — defined by ## and ### headings — become rows, columns, and tiles in the rendered dashboard. The layout we wireframed above maps directly onto Quarto Dashboard’s row/column model.

Multiple ways: flexdashboard versus Quarto Dashboard

Quarto Dashboard (format: dashboard) is the modern, maintained path and the one this chapter follows. flexdashboard is an older R Markdown package (output: flexdashboard::flex_dashboard) that uses the same conceptual model — rows, columns, value boxes, gauges — but with slightly different YAML and chunk options. If your project already uses flexdashboard, the layout patterns in this chapter translate directly: replace the YAML header, swap valueBox() for the Quarto ::: {.valuebox} div, and the rest is the same.

16.2.1 YAML header

Save the dashboard as absenteeism-dashboard.qmd with the following header:

---
title: "Absenteeism Dashboard"
format:
  dashboard:
    orientation: rows
    theme: cosmo
---

The orientation: rows directive says that level-2 (##) headings create horizontal rows, and that nested level-3 (###) headings create columns within those rows. The theme: cosmo picks one of the built-in Bootswatch themes for a clean, professional look.

16.2.2 Data setup chunk

The first code chunk in the dashboard loads and prepares the data. We use the same read_delim() pattern with backticked column names that we adopted in Chapter 6.

library(tidyverse)
library(plotly)

absenteeism <- read_delim(
  "https://ljkelly3141.github.io/datasets/bi-book/Absenteeism_at_work.csv",
  delim = ";"
) |>
  mutate(
    Chronic    = `Absenteeism time in hours` > 40,
    Month_name = month.abb[`Month of absence`]
  )

hourly_wage  <- 35
total_hours  <- sum(absenteeism$`Absenteeism time in hours`, na.rm = TRUE)
avg_hours    <- round(mean(absenteeism$`Absenteeism time in hours`, na.rm = TRUE), 1)
chronic_rate <- round(mean(absenteeism$Chronic, na.rm = TRUE) * 100, 1)
total_cost   <- total_hours * hourly_wage

These computed scalars feed the value boxes below. Defining them once at the top of the dashboard keeps the per-tile code minimal.

16.2.3 Row 1: KPI value boxes

Each tile is a ### section under a ## row heading. A value box uses a Quarto Dashboard div with ::: {.valuebox} to render a styled card. The dashboard structure for the first row looks like this:

## Row

### Average absence hours

::: {.valuebox icon="clock" color="primary"}
Avg absence hours

<avg_hours>
:::

### Chronic absence rate

::: {.valuebox icon="exclamation-triangle" color="warning"}
Chronic absence rate

<chronic_rate_pct>
:::

The icon field accepts any Bootstrap icon name. The color field sets the tile’s background using one of the theme’s palette names (primary, success, warning, danger, info). The <avg_hours> and <chronic_rate_pct> placeholders in the listing above stand in for inline R expressions that pull values from the data setup chunk into the rendered card at render time. In R Markdown and Quarto, an inline R expression is written by wrapping the R code in single backticks immediately after the letter r and a space — students who have used parameterized reports in earlier chapters will recognize the pattern.

16.2.4 Row 2: Cost and gauge

The second row pairs an estimated-cost value box with a gauge showing attendance rate. Gauges are useful for metrics that have a clear target range; the gauge plots a colored arc with thresholds for success, warning, and danger.

## Row

### Estimated absence cost

::: {.valuebox icon="cash-coin" color="primary"}
Estimated cost (annual)

<total_cost_dollars>
:::

### Attendance rate

The <total_cost_dollars> placeholder again stands for an inline R expression: in the source .qmd, the placeholder is replaced with backtick-wrapped inline R that formats total_cost as a dollar string with thousands separators.

The attendance gauge itself is an R code chunk under the ### Attendance rate heading. In Quarto Dashboard, plotly’s indicator mode produces a gauge with custom colored bands.

attendance_pct <- round((1 - mean(absenteeism$Chronic, na.rm = TRUE)) * 100, 1)

plotly::plot_ly(
  type  = "indicator",
  mode  = "gauge+number",
  value = attendance_pct,
  gauge = list(
    axis  = list(range = c(0, 100)),
    bar   = list(color = "steelblue"),
    steps = list(
      list(range = c(0, 75),   color = "#f8d7da"),
      list(range = c(75, 90),  color = "#fff3cd"),
      list(range = c(90, 100), color = "#d1e7dd")
    )
  )
)

The three colored bands cue the manager to whether attendance is in critical, warning, or healthy territory. flexdashboard offers an equivalent gauge() function that takes the same conceptual arguments.

16.2.5 Rows 3–4: Charts

The bottom of the dashboard hosts two charts — a bar chart of top absence reasons and a line chart of the monthly absence trend. Each lives in its own ## row. The bar chart uses coord_flip() so the long ICD-code labels have room to breathe.

reason_summary <- absenteeism |>
  filter(`Reason for absence` != 0) |>
  group_by(`Reason for absence`) |>
  summarise(`Total hours` = sum(`Absenteeism time in hours`, na.rm = TRUE)) |>
  slice_max(`Total hours`, n = 10)

p <- ggplot(reason_summary,
            aes(x = reorder(factor(`Reason for absence`), `Total hours`),
                y = `Total hours`)) +
  geom_col(fill = "steelblue") +
  coord_flip() +
  labs(x = "Reason code (ICD)", y = "Total hours") +
  theme_minimal()

ggplotly(p)

The line chart adds a dashed reference line at the overall mean, giving managers an at-a-glance read on whether a given month is above or below average.

monthly <- absenteeism |>
  filter(`Month of absence` > 0) |>
  group_by(`Month of absence`) |>
  summarise(`Avg hours` = mean(`Absenteeism time in hours`, na.rm = TRUE))

p2 <- ggplot(monthly,
             aes(x = `Month of absence`, y = `Avg hours`)) +
  geom_line(color = "steelblue", linewidth = 1) +
  geom_point(color = "steelblue", size = 2) +
  geom_hline(yintercept = mean(monthly$`Avg hours`),
             linetype = "dashed", color = "gray40") +
  scale_x_continuous(breaks = 1:12, labels = month.abb) +
  labs(x = NULL, y = "Average hours") +
  theme_minimal()

ggplotly(p2, tooltip = c("x", "y"))

Both charts are wrapped in ggplotly() to add hover tooltips with one function call. Building the static ggplot first means the chart can be reused in a static report without modification — a useful property when the same data must feed both a dashboard and a printed deliverable.