Skip to contents

Scatter, line and step plots are the x-y family: each point or sample is one navigable term, Up and Down move between series in a multi-line chart, and sonification plays the y value as pitch as you move along x. A regression plot layers a fitted smooth over the points, so the fit is its own navigable layer. Scatter, line, step and smooth layers are all stable plot types (see “Supported plot types” in the README). The examples hub lists every other plot family.

Scatter Plot

A scatter plot displays the relationship between two continuous variables. Each point represents one observation, and color can encode a third categorical variable.

ggplot2

car_data <- data.frame(
  weight = mtcars$wt,
  mpg = mtcars$mpg,
  cylinders = factor(mtcars$cyl)
)

p <- ggplot(car_data, aes(x = weight, y = mpg, color = cylinders)) +
  geom_point(size = 3, alpha = 0.8) +
  labs(
    title = "Fuel Efficiency vs Vehicle Weight",
    x = "Weight (1000 lbs)",
    y = "Miles per Gallon",
    color = "Cylinders"
  ) +
  theme_minimal()

p

Base R

colors <- c("4" = "steelblue", "6" = "coral", "8" = "forestgreen")
plot(mtcars$wt, mtcars$mpg,
  pch = 19,
  col = colors[as.character(mtcars$cyl)],
  main = "Fuel Efficiency vs Vehicle Weight",
  xlab = "Weight (1000 lbs)",
  ylab = "Miles per Gallon"
)
legend("topright",
  legend = c("4 cyl", "6 cyl", "8 cyl"),
  col = colors, pch = 19
)

Line Plots

Single Line

A line plot connects ordered data points, making it ideal for showing trends over time or sequential categories.

ggplot2

temp_data <- data.frame(
  Month = factor(month.abb, levels = month.abb),
  Temperature = c(2, 4, 10, 15, 20, 25, 28, 27, 22, 15, 8, 3)
)

p <- ggplot(temp_data, aes(x = Month, y = Temperature, group = 1)) +
  geom_line(color = "tomato", linewidth = 1.2) +
  labs(
    title = "Average Monthly Temperature",
    y = "Temperature (C)"
  ) +
  theme_minimal()

p

Base R

months <- 1:12
temps <- c(2, 4, 10, 15, 20, 25, 28, 27, 22, 15, 8, 3)
plot(months, temps,
  type = "l", col = "tomato", lwd = 2,
  main = "Average Monthly Temperature",
  xlab = "Month", ylab = "Temperature (C)",
  xaxt = "n"
)
axis(1, at = 1:12, labels = month.abb)

Multiple Lines

A multi-line plot overlays several series on the same axes, enabling direct comparison of trends across groups.

ggplot2

multi_line <- data.frame(
  Year = rep(2015:2024, 3),
  Users = c(
    10, 15, 22, 35, 50, 72, 95, 120, 150, 180,
    8, 12, 18, 25, 38, 55, 70, 88, 110, 135,
    5, 8, 14, 20, 30, 42, 58, 75, 95, 118
  ),
  Platform = rep(c("Mobile", "Desktop", "Tablet"), each = 10)
)

p <- ggplot(multi_line, aes(x = Year, y = Users, color = Platform)) +
  geom_line(linewidth = 1.2) +
  labs(
    title = "Platform Users Over Time",
    y = "Users (millions)"
  ) +
  theme_minimal()

p

Base R

years <- 2015:2024
users <- cbind(
  Mobile  = c(10, 15, 22, 35, 50, 72, 95, 120, 150, 180),
  Desktop = c(8, 12, 18, 25, 38, 55, 70, 88, 110, 135),
  Tablet  = c(5, 8, 14, 20, 30, 42, 58, 75, 95, 118)
)

matplot(years, users,
  type = "l", lwd = 2,
  col = c("steelblue", "coral", "forestgreen"),
  lty = 1,
  main = "Platform Users Over Time",
  xlab = "Year", ylab = "Users (millions)"
)
legend("topleft",
  legend = colnames(users),
  col = c("steelblue", "coral", "forestgreen"),
  lwd = 2
)

Step Plot

A step plot suits a value that is piecewise constant: it is held across an interval and then jumps, rather than drifting between samples the way a line implies. The canonical case is a hypnogram — the sleep stage a sleep study scores for each epoch of the night.

MAIDR reports the layer’s step convention (hv, vh, or mid) so the description says where the value jumps, and offers a Transitions rotor mode that moves between the moments the level changes rather than sample by sample.

When the y aesthetic is an ordinal factor, the level name is announced (“REM”, “N2”, “Awake”) while the numeric level still drives sonification and braille — so the shape of the night is audible and the stage is speakable.

ggplot2

hypnogram <- data.frame(
  hour = seq(0, 7.5, by = 0.5),
  stage = factor(
    c(
      "Awake", "N1", "N2", "N3", "N3", "N2", "REM", "N2",
      "N3", "N3", "N2", "REM", "N2", "N1", "REM", "Awake"
    ),
    levels = c("N3", "N2", "N1", "REM", "Awake")
  )
)

p <- ggplot(hypnogram, aes(x = hour, y = stage, group = 1)) +
  geom_step(direction = "hv", color = "steelblue", linewidth = 1) +
  scale_x_continuous(breaks = 0:8) +
  labs(
    title = "Overnight Hypnogram",
    x = "Hours after lights out",
    y = "Sleep stage"
  ) +
  theme_minimal()

p

geom_step(direction = ) accepts "hv" (the default — hold, then jump at the next x), "vh" (jump at the current x, then hold), and "mid" (jump midway between x values). All three are passed through to MAIDR unchanged.

Base R

plot(type = "s") draws the horizontal segment first, matching "hv"; plot(type = "S") draws the vertical segment first, matching "vh".

hours <- seq(0, 7.5, by = 0.5)
stage_names <- c("N3", "N2", "N1", "REM", "Awake")
stage <- c(
  5, 3, 2, 1, 1, 2, 4, 2,
  1, 1, 2, 4, 2, 3, 4, 5
)

plot(hours, stage,
  type = "s", col = "steelblue", lwd = 2,
  main = "Overnight Hypnogram",
  xlab = "Hours after lights out", ylab = "Sleep stage",
  yaxt = "n"
)
axis(2, at = seq_along(stage_names), labels = stage_names, las = 1)

Regression Plots

A scatter plot with a fitted regression line shows the relationship between two variables along with the best-fit model.

ggplot2

p <- ggplot(mtcars, aes(x = wt, y = mpg)) +
  geom_point(color = "steelblue", size = 3) +
  geom_smooth(method = "lm", color = "red", se = TRUE) +
  labs(
    title = "Weight vs MPG with Linear Fit",
    x = "Weight (1000 lbs)",
    y = "Miles per Gallon"
  ) +
  theme_minimal()

p

Base R

plot(mtcars$wt, mtcars$mpg,
  pch = 19, col = "steelblue",
  main = "Weight vs MPG with Linear Fit",
  xlab = "Weight (1000 lbs)", ylab = "Miles per Gallon"
)
abline(lm(mpg ~ wt, data = mtcars), col = "red", lwd = 2)