3  ECharts: high-level API

library(rtemis.draw)

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:

  • square makes the plotting box itself square — the box, excluding axis labels and margins.
  • equal_axes gives 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)
2026-10-02 10:32:13 Removed 2 NA values from x [distribution_samples]

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)
2026-10-02 10:32:13 Removed 2 NA values from x [distribution_samples]

3.6.2 Grouped

draw_histogram(
  penguins$body_mass,
  group = penguins$species
)
2026-10-02 10:32:13 Removed 2 NA values from x [distribution_samples]

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)"
)
2026-10-02 10:32:14 Removed 2 NA values from x [distribution_samples]

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
)
2026-10-02 10:32:14 Removed 2 NA values from x [distribution_samples]
draw_density(
  list(
    `Flipper Length` = penguins$flipper_len,
    `Bill Length` = penguins$bill_len
  )
)
2026-10-02 10:32:14 Removed 2 NA values from Flipper Length [distribution_samples]
2026-10-02 10:32:14 Removed 2 NA values from Bill Length [distribution_samples]

3.7.2 Grouped Single or Multiple Numeric Vectors

draw_density(
  x = penguins$body_mass,
  group = penguins$species
)
2026-10-02 10:32:14 Removed 2 NA values from x [distribution_samples]
draw_density(
  list(`Body Mass` = penguins$body_mass),
  group = penguins$species
)
2026-10-02 10:32:14 Removed 2 NA values from Body Mass [distribution_samples]
draw_density(
  list(
    `Bill Length` = penguins$bill_len,
    `Bill Depth` = penguins$bill_dep
  ),
  group = penguins$sex
)
2026-10-02 10:32:14 Removed 2 NA values from Bill Length [distribution_samples]
2026-10-02 10:32:14 Removed 2 NA values from Bill Depth [distribution_samples]

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)"
)
2026-10-02 10:32:14 Removed 2 NA values from x [distribution_samples]

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)")
2026-10-02 10:32:14 Removed 2 NA values from x [distribution_samples]

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)")
2026-10-02 10:32:14 Removed 2 NA values from x [distribution_samples]

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
)
2026-10-02 10:32:15 Removed 2 NA values from data [boxplot_option]

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 or labels for 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)
)
2026-10-02 10:32:15 Removed 2 NA values from data [boxplot_option]
draw_boxplot(
  penguins$body_mass,
  labels = "Body Mass"
)
2026-10-02 10:32:15 Removed 2 NA values from data [boxplot_option]

Input can be a list of any number of numeric vectors:

draw_boxplot(
  list(
    `Bill Length` = penguins$bill_len,
    `Flipper Length` = penguins$flipper_len
  )
)
2026-10-02 10:32:15 Removed 4 NA values from data [boxplot_option]

or a data.frame:

draw_boxplot(
  penguins[, c("bill_len", "flipper_len")],
  labels = c("Bill Length", "Flipper Length")
)
2026-10-02 10:32:15 Removed 4 NA values from data [boxplot_option]
draw_boxplot(
  penguins[, c("bill_len", "flipper_len")],
  labels = c("Bill Length", "Flipper Length"),
  palette = rtemis_colors[2]
)
2026-10-02 10:32:15 Removed 4 NA values from data [boxplot_option]

3.8.2 Grouped Single or Multiple Numeric Vectors

draw_boxplot(
  penguins$body_mass,
  group = penguins$species
)
2026-10-02 10:32:15 Removed 2 NA values from data [boxplot_option]
draw_boxplot(
  penguins[c("bill_len", "bill_dep")],
  labels = c("Bill length (mm)", "Bill depth (mm)"),
  group = penguins["species"]
)
2026-10-02 10:32:15 Removed 4 NA values from data [boxplot_option]

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)"
)
2026-10-02 10:32:15 Removed 2 NA values from data [boxplot_option]

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.

© 2026 E.D. Gennatas