library(rtemis.draw)3 ECharts: high-level API
Start with a single series, then add grouping, fits, and statistical layers. Charts follow the page theme when theme is unset; explicit colors stay as specified. See Themes for theme overrides and palettes. Variable importance builds on the bar and boxplot examples here.
3.1 The draw_* functions
| Function | Chart |
|---|---|
draw_scatter() |
Scatter plot, optionally with fitted lines and confidence bands |
draw_line() |
Line and area charts |
draw_bar() |
Bar charts, vertical or horizontal, grouped or stacked |
draw_boxplot() |
Box plots with observation points, paired lines, and comparison labels |
draw_violin() |
Violin distributions with optional inset boxes |
draw_histogram() |
Histograms, optionally grouped |
draw_density() |
Kernel density plots, optionally grouped |
draw_pie() |
Pie and Nightingale/rose charts |
draw_heatmap() |
Matrix heatmaps with optional clustering and dendrograms |
draw_sankey() |
Sankey flow diagrams |
draw_gantt() |
Timeline / Gantt charts |
draw_spectrogram() |
Time-frequency heatmaps from a signal or a matrix |
These functions share common arguments — title, theme, width, height, element_id, filename — and, where the chart is categorical, palette; where it encodes a quantity in color, colormap.
3.2 Data
We’ll use the built-in penguins dataset. Let’s take a look at the variables:
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.3 Scatterplots
3.3.1 Simple
draw_scatter(penguins$bill_len, penguins$flipper_len)3.3.2 With Fitted Line
Pass fit = "gam" (or "glm") to overlay a fitted line with a 95% confidence band:
draw_scatter(
penguins$bill_len,
penguins$flipper_len,
fit = "gam"
)3.3.3 Grouped
draw_scatter(
penguins$bill_len,
penguins$flipper_len,
group = penguins$species
)3.3.4 Grouped with Fitted Lines
Fit lines and confidence bands are drawn per group and share the group’s color. Clicking a legend entry toggles the scatter points, fit line, and band together:
draw_scatter(
penguins$bill_len,
penguins$flipper_len,
group = penguins$species,
fit = "gam"
)3.3.5 Square Box and Equal Axes
Two arguments control the geometry of the plot rather than its content:
squaremakes the plotting box itself square — the box, excluding axis labels and margins.equal_axesgives one data unit the same size in pixels on both axes.
Set together they also say something about the limits, since the only square box with equal scaling is one whose axes span the same interval: both axes are put on one common interval derived from all the values, or from whichever limit you gave. This is the true-versus-predicted and ROC case, where the identity line has to run at 45 degrees to be read correctly.
set.seed(2026)
true <- rnorm(300, mean = 10, sd = 2)
predicted <- true + rnorm(300, sd = 0.8)
draw_scatter(
true,
predicted,
square = TRUE,
equal_axes = TRUE,
xlab = "True",
ylab = "Predicted",
title = "True vs. Predicted"
)The box is solved in the browser, which is the only side that knows the container width, and re-solved on every resize — so it stays square as the page reflows. draw_line() takes the same two arguments; an identity line drawn with it lands at exactly 45 degrees:
draw_line(
c(0, 1),
c(0, 1),
square = TRUE,
equal_axes = TRUE,
points = FALSE,
xlab = "False positive rate",
ylab = "True positive rate"
)Giving xlim and ylim as different intervals alongside both flags is an error rather than a silent override.
3.4 Line Plots
3.4.1 Single Series
Use the table’s year names as character labels to show 2007, 2008, and 2009, with one tick per year. Numeric values, including R integers, use a continuous axis that can choose fractional ticks and thousands separators. Character labels use equally spaced categories; use dates when spacing must reflect elapsed time.
year_counts <- table(penguins$year)
draw_line(
names(year_counts),
as.integer(year_counts),
title = "Penguins Observed per Year"
)3.4.2 Multiple Series
Pass a named list to y to draw one line per element:
year_species <- table(penguins$species, penguins$year)
years <- colnames(year_species)
draw_line(
years,
setNames(
lapply(rownames(year_species), function(sp) as.integer(year_species[sp, ])),
rownames(year_species)
)
)3.4.3 Smoothed Lines
draw_line(
names(year_counts),
as.integer(year_counts),
smooth = TRUE
)3.4.4 Area Chart
draw_line(
years,
setNames(
lapply(rownames(year_species), function(sp) as.integer(year_species[sp, ])),
rownames(year_species)
),
area = TRUE
)3.5 Barplots
draw_bar(
names(table(penguins$species)),
table(penguins$species)
)dat <- table(interaction(penguins$sex, penguins$species))
draw_bar(
names(dat),
dat,
horizontal = TRUE,
palette = rtemis_colors[c(2, 1)]
)3.5.1 Grouped and stacked series
A named list supplies one value vector per series. Each vector follows the category order in x; stack = TRUE combines the series within each category.
counts <- with(penguins, table(species, sex))
by_sex <- lapply(seq_len(ncol(counts)), function(i) as.numeric(counts[, i]))
names(by_sex) <- colnames(counts)
draw_bar(rownames(counts), by_sex, ylab = "Count")draw_bar(rownames(counts), by_sex, stack = TRUE, horizontal = TRUE,
xlab = "Count")3.6 Histograms
3.6.1 Single Numeric Vector
draw_histogram(penguins$body_mass)Use breaks for a binning rule (such as "FD"), a suggested number of bins, or explicit numeric edges. As in graphics::hist(), a requested count is a suggestion for conveniently spaced boundaries:
draw_histogram(penguins$body_mass, breaks = 20)3.6.2 Grouped
draw_histogram(
penguins$body_mass,
group = penguins$species
)3.6.3 Compare distributions across species
Species have different sample sizes. Normalize each histogram to unit area to compare shapes, and add a kernel density estimate on the same scale. A species’ legend entry controls both its bins and its curve.
draw_histogram(
penguins$body_mass,
group = penguins$species,
breaks = "FD",
normalization = "density",
density = TRUE,
xlab = "Body mass (g)",
ylab = "Density (per g)"
)Bins share boundaries across all samples and span their actual numeric intervals. "count" shows raw counts, "probability" shows sample fractions, and "percent" shows percentages. If bin_edges supplies unequal-width intervals, use "density" (total area one) or "count_density" (total area equal to sample size).
3.7 Density Plots
3.7.1 Single or Multiple Numeric Vectors
draw_density(
x = penguins$body_mass
)draw_density(
list(
`Flipper Length` = penguins$flipper_len,
`Bill Length` = penguins$bill_len
)
)3.7.2 Grouped Single or Multiple Numeric Vectors
draw_density(
x = penguins$body_mass,
group = penguins$species
)draw_density(
list(`Body Mass` = penguins$body_mass),
group = penguins$species
)draw_density(
list(
`Bill Length` = penguins$bill_len,
`Bill Depth` = penguins$bill_dep
),
group = penguins$sex
)3.7.3 Control smoothing
Use bandwidth to choose a smoothing scale in measurement units, or bw to choose an R bandwidth selector such as "SJ". adjust multiplies the selected bandwidth. The same settings apply to histogram density overlays and saved DensityConfig or HistogramConfig objects.
draw_density(
penguins$bill_len,
group = penguins$species,
bandwidth = 2,
kernel = "epanechnikov",
xlab = "Bill length (mm)",
ylab = "Density (per mm)"
)The default kernel is Gaussian. Estimated tails may extend beyond the observed range; they are a property of smoothing, not additional observations.
3.7.4 Ridgelines and grouped histograms
Ridgelines use separate rows with a shared numeric scale. Ordering by mean places the group with the largest mean first:
draw_density(penguins$bill_len, group = penguins$species,
mode = "ridge", order = "mean", xlab = "Bill length (mm)")For histograms, bar_mode = "group" places groups beside each other within each bin; "stack" adds their heights, separating positive and negative values.
draw_histogram(penguins$bill_len, group = penguins$species,
bar_mode = "stack", xlab = "Bill length (mm)")bin_stat can summarize the observations in each bin with "sum", "mean", "min", or "max". These statistics use their original units, require normalization = "count", and do not support a density overlay.
3.8 Boxplots
3.8.1 Single or Multiple Numeric Vectors
Input: Single numeric vector
draw_boxplot(
penguins$body_mass
)Note:
- The function automatically detects the presence of
NAs, prints a message, and excludes them from the plot - An unnamed vector is labeled
Variable 1; use a named list orlabelsfor a descriptive category
You can define a custom label either by passing a named list/data.frame or by using the labels argument:
draw_boxplot(
list(`Body Mass` = penguins$body_mass)
)draw_boxplot(
penguins$body_mass,
labels = "Body Mass"
)Input can be a list of any number of numeric vectors:
draw_boxplot(
list(
`Bill Length` = penguins$bill_len,
`Flipper Length` = penguins$flipper_len
)
)or a data.frame:
draw_boxplot(
penguins[, c("bill_len", "flipper_len")],
labels = c("Bill Length", "Flipper Length")
)draw_boxplot(
penguins[, c("bill_len", "flipper_len")],
labels = c("Bill Length", "Flipper Length"),
palette = rtemis_colors[2]
)3.8.2 Grouped Single or Multiple Numeric Vectors
draw_boxplot(
penguins$body_mass,
group = penguins$species
)draw_boxplot(
penguins[c("bill_len", "bill_dep")],
labels = c("Bill length (mm)", "Bill depth (mm)"),
group = penguins["species"]
)Categorical legends sit in a compact, centered row above the plotting area. Use legend_position = "top-right" to right-align that row, or choose "top-left", "bottom", "bottom-left", "bottom-right", "left", or "right". Outside placement reserves space for the complete legend; legend_placement = "inside" places it over the data instead. These controls also work with the other ECharts drawing functions and their configuration objects. A single grouped variable keeps its group labels on the axis and does not need an additional legend.
3.8.3 Violin distributions
Violin widths show the distribution within each sample. Each violin has the same maximum width; width does not compare sample size across species. show_box = TRUE adds a narrow box showing the median and quartiles.
draw_violin(
penguins["bill_len"],
group = penguins["species"],
show_box = TRUE,
boxpoints = "all",
ylab = "Bill length (mm)"
)The Gaussian density uses bw.nrd0() bandwidth selection. Increase adjust for a smoother shape, or supply a numeric bandwidth in measurement units. Densities stop at the observed range. Constant samples appear as crossbars.
3.8.4 Paired measurements and comparisons
The sleep dataset records extra hours of sleep for ten people receiving each of two drugs. Explicit subject IDs connect the same people across conditions, even if the rows are reordered. Missing measurements leave gaps.
Compute the paired test separately, then pass its result as an annotation. This keeps the statistical test and its assumptions visible in the analysis. The plotting function displays the supplied label without running a test.
sleep_data <- datasets::sleep
sleep_data$drug <- paste("Drug", sleep_data$group)
sleep_wide <- reshape(sleep_data[c("ID", "group", "extra")],
idvar = "ID", timevar = "group", direction = "wide")
paired_test <- t.test(sleep_wide$extra.1, sleep_wide$extra.2, paired = TRUE)
comparison <- data.frame(
from = "Drug 1", to = "Drug 2",
label = sprintf("Paired t-test: p = %.3f", paired_test$p.value)
)
draw_violin(
sleep_data$extra,
group = sleep_data$drug,
observation = sleep_data$ID,
paired = TRUE,
show_box = TRUE,
boxpoints = "all",
comparisons = comparison,
ylab = "Extra sleep (hours)"
)draw_boxplot() accepts the same pairing and comparison arguments. Comparisons use category names; for multiple grouped variables, also specify from_group and to_group. An optional position column sets bracket heights explicitly. For a saved BoxplotConfig, comparisons names a data-frame entry in the bound data list, just as observation names the ID column.
3.9 Pie Charts
3.9.1 Simple
species_counts <- table(penguins$species)
draw_pie(as.integer(species_counts), names(species_counts))3.9.2 Nightingale / Rose Chart
Set rose_type = "radius" to encode value as radius instead of arc angle:
draw_pie(
as.integer(species_counts),
names(species_counts),
rose_type = "radius"
)3.10 Heatmaps
3.10.1 Correlation Matrix
draw_heatmap() accepts any numeric matrix. For square matrices, square_cells is enabled automatically so every cell is perfectly square. This also applies to SVG exports and draw_panels() figures; the matrix fits within the allocated canvas while retaining its square cells.
Set zlim = c(-1, 1) to fix the color scale to the full correlation range:
num_vars <- c("bill_len", "bill_dep", "flipper_len", "body_mass")
m <- cor(na.omit(penguins[, num_vars]))
draw_heatmap(m, zlim = c(-1, 1), title = "Penguin Correlations")3.10.2 Lower Triangle
For symmetric matrices it is common to show only one triangle. Use triangle = "lower" to keep the lower triangle and diagonal, masking the upper triangle:
draw_heatmap(m, triangle = "lower", zlim = c(-1, 1))3.10.3 With Cell Values
Set show_values = TRUE to print each correlation coefficient inside its cell. value_digits controls the number of decimal places and defaults to two:
draw_heatmap(
m,
triangle = "lower",
zlim = c(-1, 1),
show_values = TRUE,
value_digits = 2
)3.10.4 Hierarchical Clustering
cluster_rows and cluster_cols reorder the matrix using hclust(), grouping similar rows and columns together:
draw_heatmap(
m,
cluster_rows = TRUE,
cluster_cols = TRUE,
zlim = c(-1, 1)
)3.10.5 General (Non-square) Heatmap
For rectangular matrices, set square_cells = FALSE. Here we compute the mean of each trait per species and z-score the columns so traits on different scales are directly comparable:
traits <- c("bill_len", "bill_dep", "flipper_len", "body_mass")
means <- sapply(
traits,
function(tr) tapply(penguins[[tr]], penguins$species, mean, na.rm = TRUE)
)
colnames(means) <- c("Bill Length", "Bill Depth", "Flipper Length", "Body Mass")
draw_heatmap(
scale(means),
square_cells = FALSE,
show_values = TRUE,
value_digits = 2,
title = "Mean Traits by Species (z-scored)"
)3.10.6 Trees and annotations
Supply a saved hclust or dendrogram to reuse a clustering result. Labeled leaves match the original matrix names; their order is applied to values, notes, and color tracks together. Trees without labels use input positions. row_cut and col_cut color branches within the requested clusters.
trait_values <- scale(means)
trait_tree <- hclust(dist(trait_values))
trait_notes <- matrix(
sprintf("%.2f SD", trait_values),
nrow = nrow(trait_values), dimnames = dimnames(trait_values)
)
species_colors <- c("#18A3AC", "#F48024", "#8C6BB1")
draw_heatmap(
trait_values, row_tree = trait_tree, row_cut = 2L,
cell_notes = trait_notes, show_notes = TRUE,
row_colors = species_colors, square_cells = FALSE,
title = "Standardized traits with saved clustering"
)Notes appear on hover; show_notes = TRUE also prints them in cells, including SVG exports. Long visible notes truncate to the cell width; hover retains the full text. Choose notes or numeric show_values labels. Color tracks accept R colors, with one row per matrix identity and one column per track. Named annotations align by identity; unnamed annotations use input positions.
For saved configurations, bind a data record containing values and any of row_tree, col_tree, cell_notes, row_colors, or col_colors. Presentation settings such as show_notes and row_cut belong in setup_HeatmapConfig(). Supplied trees take precedence over computed clustering and require a full matrix; triangle masking removes leaves and cannot be combined with them.
3.11 Sankey Diagrams
draw_sankey() takes a data frame of directed links with source, target, and value columns. Node names are derived from the unique values of source and target; no separate node list is needed.
links <- as.data.frame(
table(source = penguins$species, target = penguins$sex),
responseName = "value"
)
links source target value
1 Adelie female 73
2 Chinstrap female 34
3 Gentoo female 58
4 Adelie male 73
5 Chinstrap male 34
6 Gentoo male 61
draw_sankey(links, title = "Penguins by species and sex")Flows can chain through any number of columns — a node that is a target in one row and a source in another simply sits in the middle:
size_class <- ifelse(penguins$body_mass > 4200, "Large", "Small")
chain <- rbind(
as.data.frame(
table(source = penguins$species, target = penguins$sex),
responseName = "value"
),
as.data.frame(
table(source = penguins$sex, target = size_class),
responseName = "value"
)
)
chain <- chain[chain$value > 0, ]
draw_sankey(chain, node_align = "justify")orient = "vertical" flows top to bottom, and node_width, node_gap, and node_align tune the layout:
draw_sankey(links, orient = "vertical", node_width = 14, node_gap = 12)3.12 Timelines (Gantt)
draw_gantt() draws one horizontal bar per task, positioned by start and end and grouped into rows by label. ECharts has no native Gantt series; this is implemented as a custom series.
The input is a data frame with label, start, and end columns. Repeated label values put several bars on the same row:
tasks <- data.frame(
label = c("read", "preprocess", "tune", "tune", "train", "evaluate"),
start = c(0, 140, 420, 420, 1500, 2100),
end = c(120, 400, 1480, 1180, 2050, 2300),
stage = c("io", "prep", "model", "model", "model", "eval"),
failed = c(FALSE, FALSE, FALSE, TRUE, FALSE, FALSE)
)
draw_gantt(tasks, xlab = "ms")group names a column whose values color the bars and produce a legend, and border names a logical column whose TRUE rows get an outline without changing their fill — enough to flag failures while the fill still encodes the stage:
draw_gantt(
tasks,
group = "stage",
border = "failed",
xlab = "Elapsed (ms)",
title = "Run timeline"
)3.12.1 Absolute Time
Set axis_type = "time" for timestamps. POSIXct and Date columns are converted to epoch milliseconds automatically:
start <- as.POSIXct("2026-08-18 09:00:00", tz = "UTC")
schedule <- data.frame(
label = c("ingest", "features", "model A", "model B", "report"),
start = start + c(0, 900, 2700, 2700, 6300) ,
end = start + c(840, 2600, 6200, 5400, 7200),
team = c("data", "data", "ml", "ml", "ops")
)
draw_gantt(schedule, group = "team", axis_type = "time")zoom = TRUE (the default) enables mouse-wheel zoom, drag-to-pan, and a top-right toolbox with box-zoom, undo, and reset; guides = TRUE shows an axis pointer that follows the mouse. tooltip names a column to use as the bar’s tooltip text, and bar_height and bar_radius set thickness and corner radius:
tasks$detail <- paste0(tasks$label, " — ", tasks$end - tasks$start, " ms")
draw_gantt(
tasks,
group = "stage",
tooltip = "detail",
bar_height = 0.45,
bar_radius = 4,
xlab = "ms"
)rtemis models carry an execution timeline of the same shape, which is plotted with this chart type; see the rtemis documentation for that. ## Spectrograms
draw_spectrogram() renders an interactive time–frequency heatmap. Pass a raw numeric signal vector together with sample_rate; the function computes the STFT internally via signal::specgram(). Alternatively pass a pre-computed spectrogram matrix directly.
The first four examples use synthetic signals. The final example uses a recording bundled with the optional seewave package.
3.12.2 Chirp (Frequency Sweep)
A chirp sweeps linearly from a low to a high frequency. The spectrogram makes the sweep immediately visible as a diagonal ridge:
Fs <- 8000L # 8 kHz sample rate
t <- seq(0, 2, by = 1 / Fs)
sig <- signal::chirp(t, f0 = 100, t1 = 2, f1 = 3800)
draw_spectrogram(sig, sample_rate = Fs, title = "Linear Chirp (100 – 3800 Hz)")3.12.3 Multiple Pure Tones
Three simultaneous sine waves appear as three horizontal bands — one per frequency:
t <- seq(0, 2, by = 1 / Fs)
sig <- sin(2 * pi * 440 * t) +
sin(2 * pi * 1100 * t) +
sin(2 * pi * 2400 * t)
draw_spectrogram(sig, sample_rate = Fs, title = "440 Hz + 1100 Hz + 2400 Hz")3.12.4 Log Frequency Scale
Use freq_scale = "log" to expand the low-frequency region — useful when the signal of interest spans several octaves. freq_unit = "kHz" and time_unit = "ms" rescale the axis labels:
t <- seq(0, 2, by = 1 / Fs)
sig <- signal::chirp(t, f0 = 100, t1 = 2, f1 = 3800)
draw_spectrogram(
sig,
sample_rate = Fs,
freq_scale = "log",
freq_unit = "kHz",
time_unit = "ms",
title = "Chirp — log frequency scale"
)3.12.5 Diverging Palette (EEG / ERSP)
For signed data — such as an Event-Related Spectral Perturbation (ERSP) matrix from an EEG experiment — use colormap = "diverging". The midpoint color maps exactly to zero. Set db = FALSE because the values are already on a meaningful signed scale:
set.seed(1)
n_freq <- 60
n_time <- 120
freq <- seq(4, 80, length.out = n_freq) # 4 – 80 Hz
time <- seq(-0.5, 1.5, length.out = n_time) # −500 ms to +1500 ms
# Background: small random fluctuations
ersp <- matrix(rnorm(n_freq * n_time, sd = 0.4), nrow = n_freq)
# Alpha suppression (8–13 Hz, 200–800 ms post-stimulus)
alpha_f <- freq >= 8 & freq <= 13
alpha_t <- time >= 0.2 & time <= 0.8
ersp[alpha_f, alpha_t] <- ersp[alpha_f, alpha_t] - 2.5
# Gamma increase (40–60 Hz, 100–400 ms post-stimulus)
gamma_f <- freq >= 40 & freq <= 60
gamma_t <- time >= 0.1 & time <= 0.4
ersp[gamma_f, gamma_t] <- ersp[gamma_f, gamma_t] + 2
draw_spectrogram(
ersp,
frequency = freq,
time = time,
db = FALSE,
power = FALSE,
colormap = "diverging",
title = "Simulated ERSP"
)3.12.6 Existing time-frequency matrices
draw_spectrogram() expects frequency rows and time columns. With legacy Plotly coordinates x (time), y (frequency), and a z matrix whose rows correspond to y, pass the matrix directly. A rectangular example makes the orientation explicit:
x <- c(0, 0.5, 1, 1.5)
y <- c(10, 20, 30)
z <- rbind(c(1, 2, 3, 2), c(2, 4, 6, 4), c(1, 1, 2, 1))
draw_spectrogram(z, time = x, frequency = y, db = FALSE,
title = "Recorded spectral values")Transpose a matrix only if its rows represent time. Disable dB conversion for values that have already been transformed.
3.12.7 Real-world Data
The seewave package includes several bird song recordings as Wave objects ready to pass to draw_spectrogram():
library(seewave)
data(tico) # Tiaris olivaceus song, 22050 Hz
draw_spectrogram(
tico@left,
sample_rate = tico@samp.rate,
n_fft = 512L,
freq_range = c(0, 8000),
title = "Tiaris olivaceus (tico)"
)For EEG and physiological signals, PhysioNet (physionet.org) hosts thousands of freely downloadable recordings. The EDF/EDF+ format can be read into R with the edfReader package.
3.13 Supplied fits and annotations
Materialize predictions from any fitting procedure, then add them to a drawing. This example uses a quadratic model of stopping distance:
model <- lm(dist ~ poly(speed, 2), data = cars)
new <- data.frame(speed = seq(min(cars$speed), max(cars$speed), length.out = 100))
prediction <- predict(model, newdata = new, interval = "confidence")
figure <- draw_scatter(cars$speed, cars$dist, rug = TRUE,
xlab = "Speed (mph)", ylab = "Stopping distance (ft)")
figure <- draw_add_fit(figure, new$speed, prediction[, "fit"],
lower = prediction[, "lwr"], upper = prediction[, "upr"], name = "Quadratic fit")
draw_annotate(figure, hline = 50, x = 8, y = 55, text = "50 ft reference")Supplied fits contain coordinates and interval bounds, so rendering does not require the original model. A fit name matching an observation group links legend selection. draw_annotate() adds references, bands, and literal labels in the chart’s existing data coordinates. Add these layers before exporting or composing panels.