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:
num_vars <- c("bill_len", "bill_dep", "flipper_len", "body_mass")
m <- cor(na.omit(penguins[, num_vars]))
cells <- list()
k <- 1L
for (i in seq_len(nrow(m))) {
for (j in seq_len(ncol(m))) {
cells[[k]] <- c(j - 1, i - 1, unname(m[i, j]))
k <- k + 1L
}
}
draw(EChartsOption(
tooltip = Tooltip(trigger = "item"),
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,
in_range = list(color = c("#466D96", "#F5F5F5", "#F08904"))
),
series = HeatmapSeries(
data = cells,
label = LabelOption(show = TRUE, formatter = "{@[2]}")
)
))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.
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
))