Interactive Time Series Charts in R with echarts4r
Put real dates on the x-axis: a dated line with a zoom slider, and a theme river of group shares over time
Data Visualization
Put real dates on the x-axis with echarts4r — a dated line with a draggable zoom slider, and a theme river that shows how a total splits among groups over time, all from a familiar pipe workflow. Each chart loads on demand so the page stays fast.
Published
July 9, 2026
Modified
July 9, 2026
TipKey takeaways
echarts4r wraps Apache ECharts, a free, open-source JavaScript charting library, in a tidy R pipe — e_charts() starts a chart, e_line() adds a line.
A time series puts real dates on the x-axis. Give e_charts() a Date column and echarts4r builds a proper time axis; add e_datazoom() for a draggable zoom slider to focus on any period.
A theme river (e_river()) shows how a total divides among groups over time — each coloured band is a group, and its thickness is that group’s share that period.
On this page every interactive chart sits behind a static poster you click to load — the heavy chart library only downloads on demand, so the page stays within the mobile performance budget.
The static poster is a plain ggplot2 chart, produced by code you can copy and run — the interactive version is the same data, made explorable.
Introduction
You already make time series charts with ggplot2: geom_line(), a Date on the x-axis with scale_x_date(), done. That static chart is perfect for a report or a paper. But a long dated series often begs to be explored — read the exact value on a given day, or zoom into one year of a decade. That is where echarts4r comes in: it brings Apache ECharts — a mature, free, open-source charting library — into R with a pipe workflow that feels familiar if you know the tidyverse.
This lesson builds two dated charts. First a dated line with a zoom slider: two stock indices over several years, with a real time axis you can drag to zoom. Then a theme river, a flowing stacked view that shows how a monthly total splits among a few groups over time. Each is built twice — once as a static ggplot2 poster (the reproducible baseline the page loads first), once as an interactive echarts4r widget you open with a click.
NoteWhy each chart loads on click
An interactive ECharts widget ships about 1 MB of JavaScript and draws to an HTML canvas (the browser’s pixel-drawing surface). Loading several at once would make the page slow on a phone — it would blow the Core Web Vitals budget (Google’s page-speed metrics, of which LCP, the time to paint the largest element, is the one a heavy widget hurts most). So on this page each interactive chart is represented by a static image first; clicking ▶ Load interactive chart swaps in the real, fully interactive echarts4r widget. You get the speed of a static page and the full interactivity on demand. In your own report or Shiny app you would simply print the widget directly — the code is identical.
The static baseline: a dated ggplot2 line
We will chart two European stock indices — the German DAX and the British FTSE — from the built-in EuStockMarkets series. It has no dates of its own, so we attach a sequential daily date to each observation, giving us a real time axis. First, build the data frame with base R and draw both lines with ggplot2. This static figure is the reproducible baseline — and, on this page, the main image the page loads first.
library(ggplot2)# Attach a daily date to each observation (base R — no dplyr needed)idx <-data.frame(day =as.Date("1991-01-01") +seq_len(nrow(EuStockMarkets)) -1,DAX =as.numeric(EuStockMarkets[, "DAX"]),FTSE =as.numeric(EuStockMarkets[, "FTSE"]))# Long form so ggplot draws one coloured line per indexidx_long <-data.frame(day =rep(idx$day, 2),level =c(idx$DAX, idx$FTSE),index =rep(c("DAX", "FTSE"), each =nrow(idx)))ggplot(idx_long, aes(day, level, colour = index)) +geom_line(linewidth =0.6) +scale_colour_manual(values =c(DAX ="#3a86d4", FTSE ="#f5a524")) +scale_x_date(date_labels ="%Y") +labs(x =NULL, y ="Index level", colour =NULL,title ="European stock indices over time") +theme_minimal(base_size =13) +theme(legend.position ="top")
The two lines use the brand azure #3a86d4 and amber #f5a524, scale_x_date() formats the axis as years, and theme_minimal() keeps it clean. This is a publication-ready chart. Now let’s make the same series explorable.
A dated line with a zoom slider
echarts4r reads a data frame, you name the x column in e_charts(), then add a line with e_line(). When that x column is a Date, echarts4r builds a time axis automatically — evenly spaced by real calendar distance, with sensible date ticks. Because this is a long series (over 1,800 days), we add a zoom slider with e_datazoom() so the reader can focus on any stretch. We assign the chart to line rather than printing it inline, so the page stays light — then load it behind the poster below.
invisible(Sys.setlocale("LC_TIME", "C")) # locale-neutral English date labels (axis + poster)library(echarts4r)idx <-data.frame(day =as.Date("1991-01-01") +seq_len(nrow(EuStockMarkets)) -1,DAX =as.numeric(EuStockMarkets[, "DAX"]),FTSE =as.numeric(EuStockMarkets[, "FTSE"]))line <- idx |>e_charts(day) |># a Date column -> a time axise_line(DAX) |># one line series per index columne_line(FTSE) |>e_color(c("#3a86d4", "#f5a524")) |># azure + amber, matching the static charte_tooltip(trigger ="axis") |># one tooltip showing both indices at a datee_datazoom(type ="slider") # a draggable zoom slider below the axis
Read the pipe top to bottom: start a chart on day, add a DAX line and a FTSE line, colour them azure and amber, show a shared tooltip on hover, and add the slider. Two e_line() calls give two series — the same base-R, one-call-per-column idiom the line chart lesson uses for grouped data, no dplyr needed. Now save the widget and show it behind a poster.
Two stock indices plotted over several years with dates on the x-axis. Hover a date to compare both indices; drag the slider to zoom into a period.
Hover any date and ECharts shows both index values together; drag the slider handles to zoom into a single year of trading — the interactions you cannot get from a static PNG. Everything else is the same data and the same azure-and-amber palette.
A theme river: shares of a total over time
A theme river (also called a stream graph) shows how a total divides among groups across time. Each coloured band is a group; the band’s thickness is that group’s value in that period, and the bands stack into the overall total. It reads like a flowing area chart and is a natural fit for “how did the mix change over time” questions. echarts4r draws one with e_river().
The base-R idiom mirrors the multi-line pattern: keep the data wide (a date column plus one value column per group) and add one e_river() call per group — no dplyrgroup_by() needed. Here we use a small fixed table of three groups over six months.
invisible(Sys.setlocale("LC_TIME", "C")) # locale-neutral English month labels (axis + poster)library(echarts4r)# A small wide table: one Date column + one value column per group (base R)mo <-seq(as.Date("2023-01-01"), by ="month", length.out =6)rv <-data.frame(month = mo,A =c(10, 12, 15, 14, 18, 20),B =c( 8, 9, 7, 11, 10, 12),C =c( 5, 6, 8, 7, 9, 11))river <- rv |>e_charts(month) |>e_river(A) |># one river band per group columne_river(B) |>e_river(C) |>e_color(c("#440154", "#21918c", "#fde725")) |># viridis, colourblind-safee_tooltip(trigger ="axis") # all three shares at the hovered date
Each group gets its own e_river() call on its own column and its own viridis colour. e_tooltip(trigger = "axis") reports all three shares at once when you hover a month. Save it and show the poster.
A theme river showing how a monthly total splits among three groups over time. Each band is a group; its thickness is that group’s share that month.
In the loaded chart, the bands flow and animate, and hovering a month lists every group’s share at once — the kind of exploration that turns a static stack into a story you can read across time.
NoteShow the underlying data
The dated line plots EuStockMarkets — the DAX and FTSE index levels — with a sequential daily date attached to each of its rows. The theme river uses this small fixed table of three groups over six months:
Monthly values for three groups (A, B, C) — the theme-river data.
month
A
B
C
2023-01-01
10
8
5
2023-02-01
12
9
6
2023-03-01
15
7
8
2023-04-01
14
11
7
2023-05-01
18
10
9
2023-06-01
20
12
11
Common issues
The chart doesn’t appear / the page just shows the poster. That’s by design on this site — click ▶ Load interactive chart and the real widget loads. In your own document, print(line) (or letting line be the last line of a chunk) renders the widget directly with no poster.
My x-axis shows numbers, not dates. echarts4r infers the axis type from the column. A numeric x gives a plain value axis; only a real Date (or POSIXct) column gives a proper time axis with date ticks. Build the x column with as.Date(...) before you pipe it into e_charts().
My theme river only shows one band, or errors.e_river() draws one band per call, from a wide table (a date column plus one value column per group). Add one e_river() per group column, as in the lesson — don’t reach for dplyr’s group_by(). If your data is long (a group column plus a value column), reshape it to wide first with reshape(long, idvar = "date", timevar = "group", direction = "wide").
Frequently asked questions
NoteIs echarts4r free to use, even on a commercial website?
Yes. echarts4r is released under the Apache 2.0 licence and wraps Apache ECharts, which is also Apache 2.0 — a permissive, free, open-source licence with no per-site or commercial fee. You can use it in reports, dashboards, and public sites without a paid licence.
NoteHow do I put real dates on the x-axis in echarts4r?
Make the x column a real Date (or POSIXct) and pass it to e_charts(). echarts4r then builds a time axis automatically — spaced by calendar distance with sensible date ticks. A numeric or character x gives a plain value or category axis instead, so convert with as.Date(...) first.
NoteWhat is a theme river and when should I use it?
A theme river (or stream graph) is a stacked, flowing area chart: each coloured band is a group, its thickness is that group’s value over time, and the bands stack to the total. Use it to show how a mix changes over time — market share by brand, traffic by source, budget by category. For an exact value-per-date read it is less precise than separate lines, so pair it with a tooltip.
NoteHow do I draw a theme river without dplyr’s group_by()?
Keep the data wide — a date column plus one value column per group — and add one e_river() call per column. That is the base-R idiom this lesson uses, and it mirrors adding one e_line() per column for a multi-series line chart. No group_by() required.
NoteHow is echarts4r different from ggiraph for time series?
ggiraph makes your existing ggplot2 time series interactive by rendering it as SVG, so the look is identical to ggplot2 and the widget is lightweight. echarts4r draws with a different engine (Apache ECharts on an HTML canvas) that offers richer built-in interactions — a draggable zoom slider, a real time axis, animated theme rivers — at the cost of a larger JavaScript payload. Use ggiraph for your publication ggplot look with light interactivity; reach for echarts4r when you need those richer built-in controls.
Test your understanding
ImportantYour turn: build a dated line with a zoom slider
Using the built-in AirPassengers series (144 monthly totals from January 1949), attach a real monthly Date to each value, then draw an interactive dated line with echarts4r: a single azure line, a hover tooltip, and a zoom slider. Assign the chart to a variable and print it to view.
TipHint
Build the date column with seq(as.Date("1949-01-01"), by = "month", length.out = 144) and pair it with as.numeric(AirPassengers) in a data frame. Then chain e_charts(date), e_line(passengers), e_color("#3a86d4"), e_tooltip(), and e_datazoom(type = "slider").
TipSolution
library(echarts4r)# Base-R data prep: a monthly Date column + the valuesap <-data.frame(date =seq(as.Date("1949-01-01"), by ="month", length.out =144),passengers =as.numeric(AirPassengers))ap |>e_charts(date) |>e_line(passengers, name ="Passengers") |>e_color("#3a86d4") |>e_tooltip() |>e_datazoom(type ="slider")
Because date is a real Date, echarts4r builds a time axis; the slider lets you zoom into any run of months.
Quick check. You have a wide table with a date column and three group columns, and you want a theme river with a band for each group. How many e_river() calls do you write?
TipShow answer
Three — one per group column.e_river() draws a single band per call, reading from a wide table, so three groups means e_river(A) |> e_river(B) |> e_river(C). It is the same one-call-per-column pattern as e_line() for multi-series lines.
Conclusion
echarts4r turns a familiar time series into an explorable one: give e_charts() a Date column for a real time axis, add e_line() and e_datazoom() for a zoomable dated line, or e_river() for a theme river of group shares over time. The static ggplot2 poster keeps the page fast; one click reveals the full interactive chart. Every result on this page was produced by the code shown — copy any block and run it to reproduce them. From here, the same pipe grammar extends to bar, scatter, and many other chart types.