library(rtemis.draw)
Attaching package: 'rtemis.draw'
The following object is masked from 'package:graphics':
Axis
Attaching package: 'rtemis.draw'
The following object is masked from 'package:graphics':
Axis
The draw_* functions (Tier 1) cover the most common chart types with a clean, data-first interface. When you need control over individual ECharts options — custom axis breaks, stacked series, dual y-axes, per-point colors, or anything else not exposed through Tier 1 — you work directly with the S7 classes (Tier 2).
The pattern is always the same:
LineSeries, BarSeries, ScatterSeries, …)Axis, Title, Legend, Tooltip, …)EChartsOptiondraw()draw() is a single generic that dispatches on the option object: an EChartsOption renders with ECharts, a SigmaOption with Sigma.js, and a MapLibreOption with MapLibre. Each backend’s low-level chapter follows this same build-an-option-then-draw() pattern.
Class names and property semantics mirror the ECharts TypeScript API, so anything in the ECharts option reference maps across directly. Property names are snake_case in R and are converted to ECharts’ camelCase on serialization: border_width becomes borderWidth.
| Group | Classes |
|---|---|
| Top level | EChartsOption |
| Series | LineSeries, BarSeries, ScatterSeries, PieSeries, BoxplotSeries, HeatmapSeries, SankeySeries |
| Components | Title, Legend, Tooltip, Grid, Axis, VisualMap, DataZoom |
| Axis parts | AxisLine, AxisTick, AxisLabel, SplitLine, SplitArea, MinorTick, MinorSplitLine |
| Styles | ItemStyle, LineStyle, AreaStyle, TextStyle, LabelOption, LabelLine |
| Marks | MarkArea, MarkAreaDataPoint |
| Sankey parts | SankeyNodeItem, SankeyEdgeItem, SankeyLevelOption |
| Theme | Theme, theme_light(), theme_dark() |
EChartsOption itself carries title, legend, grid, x_axis, y_axis, tooltip, visual_map, toolbox, data_zoom, series, color, background_color, text_style, animation, animation_threshold, animation_duration, animation_easing, animation_delay, dark_mode, and use_utc.
Properties are type-checked at construction, so a mistake surfaces where it is made rather than as a blank chart in the browser:
Error:
! <rtemis.draw::Axis> object properties are invalid:
- @type must be one of 'value', 'category', 'time', 'log'.
Any option converts to a plain list or JSON with to_list() and to_json(); see Export & serialization.
'data.frame': 344 obs. of 8 variables:
$ species : Factor w/ 3 levels "Adelie","Chinstrap",..: 1 1 1 1 1 1 1 1 1 1 ...
$ island : Factor w/ 3 levels "Biscoe","Dream",..: 3 3 3 3 3 3 3 3 3 3 ...
$ bill_len : num 39.1 39.5 40.3 NA 36.7 39.3 38.9 39.2 34.1 42 ...
$ bill_dep : num 18.7 17.4 18 NA 19.3 20.6 17.8 19.6 18.1 20.2 ...
$ flipper_len: int 181 186 195 NA 193 190 181 195 193 190 ...
$ body_mass : int 3750 3800 3250 NA 3450 3650 3625 4675 3475 4250 ...
$ sex : Factor w/ 2 levels "female","male": 2 1 1 NA 1 2 1 2 NA NA ...
$ year : int 2007 2007 2007 2007 2007 2007 2007 2007 2007 2007 ...
BoxplotSeries expects data in [min, Q1, median, Q3, max] format. Use grDevices::boxplot.stats() to compute those values:
bs <- grDevices::boxplot.stats(na.omit(penguins$body_mass))$stats
draw(EChartsOption(
title = Title(text = "Body Mass"),
tooltip = Tooltip(trigger = "item"),
x_axis = Axis(type = "category", data = list("Body Mass")),
y_axis = Axis(type = "value", scale = TRUE),
series = BoxplotSeries(
data = list(bs),
item_style = ItemStyle(border_color = "red", color = "transparent")
)
))vars <- list(
`Bill Length` = penguins$bill_len,
`Bill Depth` = penguins$bill_dep,
`Flipper Length` = penguins$flipper_len
)
box_data <- lapply(vars, function(v) grDevices::boxplot.stats(na.omit(v))$stats)
draw(EChartsOption(
tooltip = Tooltip(trigger = "item"),
x_axis = Axis(type = "category", data = names(vars)),
y_axis = Axis(type = "value", scale = TRUE),
series = BoxplotSeries(data = box_data)
))One BoxplotSeries per group, each with its own ItemStyle:
groups <- levels(factor(penguins$species))
colors <- rtemis_colors[seq_along(groups)]
series <- lapply(seq_along(groups), function(i) {
vals <- na.omit(penguins$body_mass[penguins$species == groups[i]])
BoxplotSeries(
name = groups[i],
data = list(grDevices::boxplot.stats(vals)$stats),
item_style = ItemStyle(
color = adjustcolor(colors[i], alpha.f = 0.25),
border_color = colors[i]
)
)
})
draw(EChartsOption(
legend = Legend(),
tooltip = Tooltip(trigger = "item"),
x_axis = Axis(type = "category", data = list("Body Mass")),
y_axis = Axis(type = "value", scale = TRUE),
series = series
))Input to asJSON(keep_vec_names=TRUE) is a named vector. In a future version of jsonlite, this option will not be supported, and named vectors will be translated into arrays instead of objects. If you want JSON object output, please use a named list instead. See ?toJSON.
Input to asJSON(keep_vec_names=TRUE) is a named vector. In a future version of jsonlite, this option will not be supported, and named vectors will be translated into arrays instead of objects. If you want JSON object output, please use a named list instead. See ?toJSON.
Input to asJSON(keep_vec_names=TRUE) is a named vector. In a future version of jsonlite, this option will not be supported, and named vectors will be translated into arrays instead of objects. If you want JSON object output, please use a named list instead. See ?toJSON.
Use graphics::hist() to compute bins, then pass counts to BarSeries:
Consistent break points across groups; one BarSeries per group:
breaks <- graphics::hist(na.omit(penguins$body_mass), plot = FALSE)$breaks
series <- lapply(levels(factor(penguins$species)), function(sp) {
hg <- graphics::hist(
na.omit(penguins$body_mass[penguins$species == sp]),
breaks = breaks, plot = FALSE
)
BarSeries(name = sp, data = hg$counts)
})
draw(EChartsOption(
legend = Legend(),
x_axis = Axis(type = "category", data = formatC(
graphics::hist(na.omit(penguins$body_mass), breaks = breaks, plot = FALSE)$mids,
format = "g"
)),
y_axis = Axis(type = "value"),
series = series
))stats::density() returns $x and $y; pass them as [x, y] pairs to LineSeries:
d <- stats::density(na.omit(penguins$body_mass))
draw(EChartsOption(
title = Title(text = "Body Mass"),
x_axis = Axis(type = "value", scale = TRUE),
y_axis = Axis(type = "value"),
series = LineSeries(
data = mapply(c, d$x, d$y, SIMPLIFY = FALSE),
show_symbol = FALSE,
area_style = AreaStyle(opacity = 0.25)
)
))groups <- levels(factor(penguins$species))
series <- lapply(groups, function(sp) {
d <- stats::density(na.omit(penguins$body_mass[penguins$species == sp]))
LineSeries(
name = sp,
data = mapply(c, d$x, d$y, SIMPLIFY = FALSE),
show_symbol = FALSE,
area_style = AreaStyle(opacity = 0.25)
)
})
draw(EChartsOption(
legend = Legend(),
x_axis = Axis(type = "value", scale = TRUE),
y_axis = Axis(type = "value"),
series = series
))Set the same stack string on every series to stack them:
year_species <- table(penguins$year, penguins$species)
years <- rownames(year_species)
groups <- colnames(year_species)
series <- lapply(groups, function(sp) {
BarSeries(name = sp, data = as.integer(year_species[, sp]), stack = "total")
})
draw(EChartsOption(
legend = Legend(),
x_axis = Axis(type = "category", data = years),
y_axis = Axis(type = "value"),
series = series
))Swap the axis types to produce horizontal bars:
Scatter data is a list of [x, y] vectors, produced conveniently with mapply:
dat <- mapply(c, penguins$bill_len, penguins$flipper_len, SIMPLIFY = FALSE)
dat <- dat[!sapply(dat, function(p) any(is.na(p)))] # drop NA pairs
draw(EChartsOption(
tooltip = Tooltip(trigger = "item"),
x_axis = Axis(type = "value", scale = TRUE),
y_axis = Axis(type = "value", scale = TRUE),
series = ScatterSeries(data = dat)
))groups <- levels(factor(penguins$species))
colors <- rtemis_colors[seq_along(groups)]
series <- lapply(seq_along(groups), function(i) {
sp <- groups[i]
idx <- penguins$species == sp & !is.na(penguins$bill_len) & !is.na(penguins$flipper_len)
dat <- mapply(c, penguins$bill_len[idx], penguins$flipper_len[idx], SIMPLIFY = FALSE)
ScatterSeries(
name = sp,
data = dat,
item_style = ItemStyle(color = colors[i])
)
})
draw(EChartsOption(
legend = Legend(),
tooltip = Tooltip(trigger = "item"),
x_axis = Axis(type = "value", scale = TRUE),
y_axis = Axis(type = "value", scale = TRUE),
series = series
))Input to asJSON(keep_vec_names=TRUE) is a named vector. In a future version of jsonlite, this option will not be supported, and named vectors will be translated into arrays instead of objects. If you want JSON object output, please use a named list instead. See ?toJSON.
Input to asJSON(keep_vec_names=TRUE) is a named vector. In a future version of jsonlite, this option will not be supported, and named vectors will be translated into arrays instead of objects. If you want JSON object output, please use a named list instead. See ?toJSON.
Input to asJSON(keep_vec_names=TRUE) is a named vector. In a future version of jsonlite, this option will not be supported, and named vectors will be translated into arrays instead of objects. If you want JSON object output, please use a named list instead. See ?toJSON.
For numeric x, data is [x, y] pairs; for categorical x, data is y-values only with a category axis:
year_counts <- as.integer(table(penguins$year))
years <- sort(unique(penguins$year))
draw(EChartsOption(
title = Title(text = "Penguins Observed per Year"),
x_axis = Axis(type = "value", scale = TRUE),
y_axis = Axis(type = "value"),
series = LineSeries(
data = mapply(c, years, year_counts, SIMPLIFY = FALSE),
smooth = TRUE
)
))year_species <- table(penguins$species, penguins$year)
groups <- rownames(year_species)
years <- as.integer(colnames(year_species))
series <- lapply(groups, function(sp) {
LineSeries(
name = sp,
data = mapply(c, years, as.integer(year_species[sp, ]), SIMPLIFY = FALSE)
)
})
draw(EChartsOption(
legend = Legend(),
x_axis = Axis(type = "value", scale = TRUE),
y_axis = Axis(type = "value"),
series = series
))AreaStyle() on a LineSeries fills the area under the line:
series <- lapply(groups, function(sp) {
LineSeries(
name = sp,
data = mapply(c, years, as.integer(year_species[sp, ]), SIMPLIFY = FALSE),
area_style = AreaStyle(opacity = 0.25)
)
})
draw(EChartsOption(
legend = Legend(),
x_axis = Axis(type = "value", scale = TRUE),
y_axis = Axis(type = "value"),
series = series
))PieSeries data is a list of named lists with value and name:
species_counts <- table(penguins$species)
data_items <- mapply(
function(v, n) list(value = v, name = n),
as.integer(species_counts),
names(species_counts),
SIMPLIFY = FALSE, USE.NAMES = FALSE
)
draw(EChartsOption(
legend = Legend(orient = "vertical", left = "left"),
series = PieSeries(data = data_items, radius = "75%")
))A heatmap is a HeatmapSeries on two category axes, with a VisualMap supplying the color scale. Data is a list of [x_index, y_index, value] triples. Keep the numeric values for the color scale and format the cell labels separately. Here value_digits sets the decimal places to two; change it for more or less precision. The high-level draw_heatmap() uses the same value_digits argument, defaulting to two:
num_vars <- c("bill_len", "bill_dep", "flipper_len", "body_mass")
m <- cor(na.omit(penguins[, num_vars]))
value_digits <- 2L
cells <- list()
k <- 1L
for (i in seq_len(nrow(m))) {
for (j in seq_len(ncol(m))) {
cells[[k]] <- list(
value = c(j - 1, i - 1, unname(m[i, j])),
label = list(formatter = formatC(m[i, j], format = "f",
digits = value_digits, decimal.mark = "."))
)
k <- k + 1L
}
}
draw(EChartsOption(
tooltip = Tooltip(trigger = "item"),
grid = Grid(right = 110),
x_axis = Axis(type = "category", data = colnames(m)),
y_axis = Axis(type = "category", data = rownames(m)),
visual_map = VisualMap(
min = -1,
max = 1,
calculable = TRUE,
orient = "vertical",
right = 10,
top = "middle",
precision = value_digits,
in_range = list(color = c("#466D96", "#F5F5F5", "#F08904"))
),
series = HeatmapSeries(
data = cells,
label = LabelOption(show = TRUE)
)
))VisualMap maps a numeric range onto a visual channel: min/max set the domain, in_range the colors, calculable adds the draggable handle, and orient plus the corner anchors place the bar. For a vertical colorbar, top = "middle" centers it alongside the plotted data, including after resize.
SankeySeries takes nodes in data and links in links, built from SankeyNodeItem and SankeyEdgeItem. Unlike the other series, a Sankey carries its own layout arguments, since it has no axes to sit on:
counts <- as.data.frame(
table(source = penguins$species, target = penguins$sex),
responseName = "value"
)
nodes <- lapply(
unique(c(as.character(counts$source), as.character(counts$target))),
function(n) SankeyNodeItem(name = n)
)
edges <- mapply(
function(src, tgt, val) {
SankeyEdgeItem(source = src, target = tgt, value = val)
},
as.character(counts$source),
as.character(counts$target),
counts$value,
SIMPLIFY = FALSE, USE.NAMES = FALSE
)
draw(EChartsOption(
tooltip = Tooltip(trigger = "item"),
series = SankeySeries(
data = nodes,
links = edges,
node_width = 16,
node_gap = 10,
node_align = "justify",
label = LabelOption(show = TRUE),
line_style = LineStyle(color = "gradient", opacity = 0.35)
)
))SankeyLevelOption styles a whole column of the diagram at once — every node at a given depth, and the links leaving it:
draw(EChartsOption(
series = SankeySeries(
data = nodes,
links = edges,
levels = list(
SankeyLevelOption(
depth = 0L,
item_style = ItemStyle(color = "#6CA3A0"),
line_style = LineStyle(color = "source", opacity = 0.4)
),
SankeyLevelOption(
depth = 1L,
item_style = ItemStyle(color = "#F08904"),
line_style = LineStyle(color = "source", opacity = 0.3)
)
),
label = LabelOption(show = TRUE)
)
))Grid positions the plotting box inside the widget. contain_label = TRUE lets ECharts reserve room for axis labels automatically; set it to FALSE and give explicit offsets when you need the box itself at a fixed place — aligning several charts on a page, for instance:
d <- stats::density(na.omit(penguins$body_mass))
draw(EChartsOption(
grid = Grid(left = 72, right = 24, top = 40, bottom = 56, contain_label = FALSE),
title = Title(text = "Fixed plotting box"),
x_axis = Axis(type = "value", scale = TRUE, name = "Body mass", name_location = "middle", name_gap = 30),
y_axis = Axis(type = "value", name = "Density", name_location = "middle", name_gap = 55),
series = LineSeries(
data = mapply(c, d$x, d$y, SIMPLIFY = FALSE),
show_symbol = FALSE,
area_style = AreaStyle(opacity = 0.2)
)
))Offsets accept pixels (numeric) or CSS percentages ("8%").
DataZoom adds interactive range selection. type = "inside" is wheel-and-drag on the chart itself; type = "slider" adds a brush below it. The two are usually declared together:
set.seed(2026)
walk <- cumsum(rnorm(400))
draw(EChartsOption(
tooltip = Tooltip(trigger = "axis"),
x_axis = Axis(type = "value", scale = TRUE),
y_axis = Axis(type = "value", scale = TRUE),
data_zoom = list(
DataZoom(type = "inside", start = 0, end = 40),
DataZoom(type = "slider", start = 0, end = 40, bottom = 10)
),
series = LineSeries(
data = mapply(c, seq_along(walk), walk, SIMPLIFY = FALSE),
show_symbol = FALSE,
line_style = LineStyle(width = 1.5)
)
))start and end are percentages of the data range; start_value and end_value set it in data units instead. x_axis_index and y_axis_index attach the zoom to particular axes when a chart has more than one.
MarkArea shades a region of the plot. Each entry in data is a pair of MarkAreaDataPoint objects — the two opposite corners — and a point that names only x_axis spans the full height of the plot:
draw(EChartsOption(
tooltip = Tooltip(trigger = "axis"),
x_axis = Axis(type = "value", scale = TRUE),
y_axis = Axis(type = "value", scale = TRUE),
series = LineSeries(
data = mapply(c, seq_along(walk), walk, SIMPLIFY = FALSE),
show_symbol = FALSE,
mark_area = MarkArea(
data = list(
list(
MarkAreaDataPoint(x_axis = 120, name = "Window of interest"),
MarkAreaDataPoint(x_axis = 220)
)
),
item_style = ItemStyle(color = "rgba(108, 163, 160, 0.18)"),
label = LabelOption(show = TRUE, position = "top")
)
)
))The S7 API gives you full access to every ECharts option. Here is an example that stacks three variables side-by-side with a shared y-axis and a custom title:
vars <- list(
`Bill Length` = penguins$bill_len,
`Flipper Length` = penguins$flipper_len
)
series <- lapply(seq_along(vars), function(i) {
d <- stats::density(na.omit(vars[[i]]))
LineSeries(
name = names(vars)[i],
data = mapply(c, d$x, d$y, SIMPLIFY = FALSE),
show_symbol = FALSE,
area_style = AreaStyle(opacity = 0.2)
)
})
draw(EChartsOption(
title = Title(text = "Penguin Measurements", subtext = "Kernel density estimate"),
legend = Legend(),
tooltip = Tooltip(trigger = "axis"),
x_axis = Axis(type = "value", scale = TRUE),
y_axis = Axis(type = "value"),
series = series
))