3  S7 lower-level API

library(rtemis.draw)
.:rtemis.draw 0.0.3 🖌 aarch64-apple-darwin20

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:

  1. Build series objects (LineSeries, BarSeries, ScatterSeries, …)
  2. Build axis and component objects (Axis, Title, Legend, Tooltip, …)
  3. Combine into an EChartsOption
  4. Render with draw()
str(penguins)
'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 ...

3.1 Boxplots

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")
    )
))

3.1.1 Multiple Boxes

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)
))

3.1.2 Grouped Boxes

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
))

3.2 Histograms

Use graphics::hist() to compute bins, then pass counts to BarSeries:

h <- graphics::hist(na.omit(penguins$body_mass), plot = FALSE)

draw(EChartsOption(
  title  = Title(text = "Body Mass"),
  x_axis = Axis(type = "category", data = formatC(h$mids, format = "g")),
  y_axis = Axis(type = "value"),
  series = BarSeries(data = h$counts, bar_category_gap = "0%")
))

3.2.1 Grouped Histogram

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
))

3.3 Density Plots

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)
  )
))

3.3.1 Grouped Density

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
))

3.4 Bar Charts

species_counts <- table(penguins$species)

draw(EChartsOption(
  x_axis = Axis(type = "category", data = names(species_counts)),
  y_axis = Axis(type = "value"),
  series = BarSeries(data = as.integer(species_counts))
))

3.4.1 Stacked Bars

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
))

3.4.2 Horizontal Bars

Swap the axis types to produce horizontal bars:

draw(EChartsOption(
  x_axis = Axis(type = "value"),
  y_axis = Axis(type = "category", data = names(species_counts)),
  series = BarSeries(data = as.integer(species_counts))
))

3.5 Scatter Plots

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)
))

3.5.1 Grouped Scatter with Per-Series Colors

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
))

3.6 Line Plots

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
  )
))

3.6.1 Multiple Series

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
))

3.6.2 Area Chart

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
))

3.7 Pie Charts

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%")
))

3.7.1 Nightingale / Rose Chart

draw(EChartsOption(
  legend = Legend(orient = "vertical", left = "left"),
  series = PieSeries(
    data      = data_items,
    radius    = "75%",
    rose_type = "radius"
  )
))

3.8 Combining Options

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
))
© 2026 E.D. Gennatas