Charts for data indexed by time. line_chart() is the
workhorse: it handles several series, ts objects, zooming,
annotations and forecast ribbons. The others cover totals and their
components, ranges, many categories at once, and daily patterns.
| Chart | Use it when |
|---|---|
line_chart() |
You track one or many series over time and want to zoom, compare and annotate |
area_chart() |
A single series where the volume under the line matters |
stacked_area_chart() |
You show a total and how each component contributes over time |
area_band() |
You show a range over time, such as a min/max or an interval |
streamgraph() |
Many categories change over time and the overall shape matters more than exact values |
calendar_heatmap() |
You look for daily, weekly or seasonal patterns in a daily series |
anim_line_chart() |
You present a single series and want it to draw itself on click |
line_chart()
line_chart() is built for time series. It draws one or
many series and comes with the interactions people expect from financial
and monitoring dashboards:
- a crosshair tooltip that shows every series at the hovered date, sorted by value, with the series nearest to the pointer highlighted;
- an interactive legend: click to hide or show a series, double-click to isolate it;
- direct labels at the end of each line;
- zooming: range buttons (1M, 6M, YTD, 1Y, 5Y, All, chosen from the data’s frequency), a navigator with a brush under the chart, and drag on the chart to zoom into a range (double-click to reset). The y axis rescales to the data in view.
A ts object can be passed directly. Monthly, quarterly
and yearly series get exact calendar dates; for other frequencies the
dates are interpolated from the time index, so use a data frame with a
real date column when exact dates matter:
line_chart(AirPassengers, y = "passengers", title = "Airline passengers, 1949-1960")The data frame interface needs a date column (Date,
POSIXct, “yyyy-mm-dd” strings, or numbers such as years)
and one or several value columns:
airpassengers <- data.frame(
passengers = as.matrix(AirPassengers),
date = zoo::as.Date(time(AirPassengers))
)
line_chart(
data = airpassengers,
x = "date",
y = "passengers",
curve = "curveMonotoneX",
area = TRUE
)Several series and rebasing
Pass several y columns (wide data), a group
column (long data), or an mts object. With
transform = "index", every series is rebased to 100 at the
first visible date. Zoom with the range buttons or the navigator and the
comparison is recomputed from the new starting point;
transform = "percent" shows the change since that point
instead.
line_chart(
EuStockMarkets,
transform = "index",
title = "European stock indices",
subtitle = "Daily closing prices, rebased to 100 at the start of the visible range"
)
library(gapminder)
countries <- gapminder[gapminder$country %in% c("China", "India", "United States",
"Brazil", "Nigeria", "Germany"), ]
line_chart(
countries,
x = "year",
y = "lifeExp",
group = "country",
points = TRUE,
curve = "curveMonotoneX",
title = "Life expectancy at birth",
ytitle = "Years"
)Annotations: periods, events and reference lines
bands shades periods (here the NBER recessions),
events marks dates with a vertical line, and
hlines draws reference values.
recessions <- data.frame(
start = as.Date(c("1969-12-01", "1973-11-01", "1980-01-01", "1981-07-01",
"1990-07-01", "2001-03-01", "2007-12-01")),
end = as.Date(c("1970-11-01", "1975-03-01", "1980-07-01", "1982-11-01",
"1991-03-01", "2001-11-01", "2009-06-01"))
)
line_chart(
economics,
x = "date",
y = "unemploy",
area = TRUE,
bands = recessions,
valueFormat = ",.0f",
valueSuffix = "k",
title = "US unemployment",
subtitle = "Thousands of persons, recessions shaded"
)Forecasts and moving averages
lower and upper draw a confidence or
forecast ribbon around a line. Here a Holt-Winters forecast from base
R’s stats package:
fit <- HoltWinters(log(AirPassengers))
fc <- predict(fit, n.ahead = 24, prediction.interval = TRUE)
passengers <- rbind(
data.frame(date = zoo::as.Date(time(AirPassengers)),
value = as.numeric(AirPassengers), lo = NA, hi = NA,
series = "Observed"),
data.frame(date = zoo::as.Date(time(fc)), value = exp(fc[, "fit"]),
lo = exp(fc[, "lwr"]), hi = exp(fc[, "upr"]),
series = "Forecast")
)
line_chart(
passengers,
x = "date", y = "value", group = "series",
lower = "lo", upper = "hi",
colors = c(Observed = "#4269d0", Forecast = "#ff725c"),
events = c("Forecast starts" = as.Date("1961-01-01")),
valueFormat = ",.0f",
title = "Airline passengers: Holt-Winters forecast"
)rollmean draws a trailing moving average over the raw
series, which is kept as a faint line (showRaw = FALSE
hides it). Missing values leave gaps in the lines:
set.seed(3)
samples <- data.frame(
time = seq(as.POSIXct("2026-03-01 09:00", tz = "UTC"), by = "5 min", length.out = 400),
cpu = 40 + cumsum(rnorm(400)),
mem = 60 + cumsum(rnorm(400, 0, 0.5))
)
samples$cpu[150:170] <- NA # a monitoring outage
line_chart(
samples,
x = "time",
y = c("cpu", "mem"),
rollmean = 12,
hlines = c(SLA = 70),
valueSuffix = "%",
title = "Server load",
subtitle = "5-minute samples with a 1-hour moving average"
)Other options include yScale = "log",
yZero, dateFormat, and turning off
navigator, rangeButtons, zoom,
legend, endLabels or animate.
area_chart()
area_chart() works similarly except that instead of a
line you get an area.
# 1. converting AirPassengers to a tidy data frame
airpassengers <- data.frame(
passengers = as.matrix(AirPassengers),
date= zoo::as.Date(time(AirPassengers))
)
# 2. plotting the area chart
area_chart(
data = airpassengers,
x = "date",
y = "passengers",
fill = "purple",
bgcol = "white"
)
stacked_area_chart()
This function allows you to create a stacked area chart. You need two components:
- A data frame in wide format (see an example below). If it’s in wide
format, you can still use
pivot_wider()from thetidyrpackage to make wider. - A date variable in
yyyy-mm-ddformat that will plotted in the x-axis.
Let’s work with the following data frame (shortened) provided by Mike Bostock in his stacked area chart example:
data <- data.frame(
date = c(
"2000-01-01", "2000-02-01", "2000-03-01", "2000-04-01",
"2000-05-01", "2000-06-01", "2000-07-01",
"2000-08-01", "2000-09-01", "2000-10-01"
),
Trade = c(
2000,1023, 983, 2793, 1821, 1837, 1792, 1853, 791, 739
),
Manufacturing = c(
734, 694, 739, 736, 685, 621, 708, 685, 667, 693
),
Leisure = c(
1782, 1779, 1789, 658, 675, 833, 786, 675, 636, 691
),
Agriculture = c(
655, 587,623, 517, 561, 2545, 636, 584, 559, 2504
)
)
data
#> date Trade Manufacturing Leisure Agriculture
#> 1 2000-01-01 2000 734 1782 655
#> 2 2000-02-01 1023 694 1779 587
#> 3 2000-03-01 983 739 1789 623
#> 4 2000-04-01 2793 736 658 517
#> 5 2000-05-01 1821 685 675 561
#> 6 2000-06-01 1837 621 833 2545
#> 7 2000-07-01 1792 708 786 636
#> 8 2000-08-01 1853 685 675 584
#> 9 2000-09-01 791 667 636 559
#> 10 2000-10-01 739 693 691 2504Note that when running stacked_area_chart() all the
variables available within the considered data frame will be plotted. If
you want to restrict the plotting to only specific variables, just drop
the unneeded columns:
stacked_area_chart(
data = data,
x = "date",
legendTextSize = 14
)You can modify the color scheme using the colorCategory
parameter:
stacked_area_chart(
data = data,
x = "date",
legendTextSize = 14,
curve = "curveCardinal",
colorCategory = "Accent",
bgcol = "white",
stroke = "black",
strokeWidth = 1
)
stacked_area_chart(
data = data,
x = "date",
legendTextSize = 14,
curve = "curveBasis",
colorCategory = "Set3",
bgcol = "black",
axisCol = "white",
xticks = 4,
stroke = "black"
)You can find list of D3 categorical color schemes here
Finally, if you hover over the chart you’ll notice a tooltip that identified the different area categories.
area_band()
area_band() lets you plot a filled area between two
y-values. For the sake of the example, let’s create an additional column
passengers_upper that has an additional 40 passengers for
each observation:
airpassengers <- data.frame(
passengers_lower = as.matrix(AirPassengers),
passengers_upper = as.matrix(AirPassengers) + 40,
date= zoo::as.Date(time(AirPassengers))
)
area_band(
data = airpassengers,
x = "date",
yLower = "passengers_lower",
yUpper = "passengers_upper",
fill = "yellow",
stroke = "black"
)
streamgraph()
streamgraph() is inspired by the Highcharts streamgraph: stacked
areas flowing around a centred (wiggle) baseline, with labels drawn
inside each layer, a crosshair tooltip and an interactive legend. Unlike
stacked_area_chart(), it takes long data. Hover over a
layer to highlight it, and click a legend item to hide or show it.
set.seed(2026)
years <- 1980:2020
genres <- c("Rock", "Pop", "Hip hop", "Electronic", "Country",
"R&B", "Jazz", "Metal", "Reggae", "Latin")
peaks <- c(1984, 1999, 2012, 2008, 1993, 2003, 1981, 1990, 1986, 2018)
sizes <- c(60, 55, 70, 40, 30, 35, 18, 25, 15, 45)
music <- expand.grid(year = years, genre = genres)
idx <- match(music$genre, genres)
music$listeners <- round(
sizes[idx] * exp(-((music$year - peaks[idx]) / 9)^2) + runif(nrow(music), 1, 6)
)
streamgraph(
data = music,
x = "year",
group = "genre",
value = "listeners",
valueFormat = ",.0f",
xtitle = "Year",
title = "Music genres, 1980-2020 (simulated listeners)"
)offset = "expand" turns the stream into a 100% stacked
view:
phones <- data.frame(
year = rep(as.numeric(rownames(WorldPhones)), ncol(WorldPhones)),
region = factor(rep(colnames(WorldPhones), each = nrow(WorldPhones)),
levels = colnames(WorldPhones)),
phones = as.vector(WorldPhones)
)
streamgraph(phones, x = "year", group = "region", value = "phones",
offset = "expand", order = "descending", curve = "monotoneX",
colorPalette = "Set2", title = "Share of telephones by region")
calendar_heatmap()
calendar_heatmap() is inspired by the signature heatmap
of Frappe Charts and by GitHub’s
contribution graph. It draws one rounded square per day on a
week-by-weekday grid, one panel per year, with a “Less … More” legend
and a staggered fade-in. Hover over a day to see its value, or over a
legend square to highlight every day in that color bucket.
set.seed(2026)
days <- seq(as.Date("2026-01-01"), as.Date("2026-12-31"), by = "day")
weekday <- !(format(days, "%u") %in% c("6", "7"))
activity <- data.frame(
day = days,
commits = rpois(length(days), ifelse(weekday, 6, 1.5)) * rbinom(length(days), 1, 0.8)
)
calendar_heatmap(activity, date = "day", value = "commits", title = "Commits in 2026")Data spanning several years is drawn as one panel per year:
set.seed(1)
days <- seq(as.Date("2021-01-01"), as.Date("2023-12-31"), by = "day")
season <- 10 + 8 * sin(2 * pi * as.numeric(format(days, "%j")) / 365)
rainfall <- data.frame(day = days, rain = pmax(0, rnorm(length(days), season, 6)))
calendar_heatmap(
rainfall, date = "day", value = "rain", weekStart = "monday",
colors = c("#9be9a8", "#40c463", "#30a14e", "#216e39"),
valueLabel = "mm of rain", valueFormat = ",.1f",
title = "Daily rainfall, 2021-2023"
)
anim_line_chart()
Heavily inspired by Jure
Stabuc’s example, anim_line_chart() draws an empty
frame, and each click on it plays an animation that draws the line. The
line stays in place once the animation ends. Click on the empty chart
below:
passengers_df <- data.frame(
passengers = as.matrix(AirPassengers),
date = zoo::as.Date(time(AirPassengers))
)
anim_line_chart(
data = passengers_df,
x = "date",
y = "passengers",
duration = 10000, # in milliseconds (10 seconds)
curve = "curveCardinal"
)