10  Export & serialization

library(rtemis.draw)

Attaching package: 'rtemis.draw'
The following object is masked from 'package:graphics':

    Axis

Every drawing function returns an htmlwidget. This chapter covers where that widget can go: an IDE pane, a Quarto document, a Shiny app, a static file, or a JSON option you hand to something else entirely.

10.1 Sizing and identity

Four arguments are shared by every draw_*() function and by draw() itself:

  • width, height — CSS strings ("100%", "32rem") or numbers of pixels. A numeric size is respected exactly: the widget keeps its computed size instead of expanding to fill its container.
  • element_id — an explicit DOM id for the container. Leave it NULL and htmlwidgets generates one; set it when something on the page needs to find the chart.
  • filename — write a static file as well as returning the widget.
draw_scatter(
  penguins$bill_len,
  penguins$flipper_len,
  width = 500,
  height = 320
)

10.2 Static files

save_drawing() exports a widget to a static file. .svg is currently the supported format, rendered server-side by the same bundled ECharts build the browser uses, so the output matches what you see. It requires a node binary on PATH.

f <- file.path(tempdir(), "penguins.svg")
save_drawing(draw_scatter(penguins$bill_len, penguins$flipper_len), f)
file.exists(f)
[1] TRUE

width and height set the export size in pixels:

save_drawing(draw_pie(as.integer(table(penguins$species)), names(table(penguins$species))),
             file.path(tempdir(), "species.svg"), width = 600, height = 600)

Interactive features — tooltip formatters, event handlers — are stripped before rendering, since a static image cannot express them.

Passing filename to a drawing function does the same thing in one step:

draw_scatter(penguins$bill_len, penguins$flipper_len, filename = "penguins.svg")

The export uses the light theme by default. For a dark export, force the theme at draw time:

draw_scatter(x, y, theme = theme_dark(), filename = "penguins-dark.svg")

10.3 The option as data

Any option object converts to a plain list or to JSON. This is the ECharts option itself — nothing rtemis-specific — so it can be handed to any ECharts instance, in any language.

opt <- EChartsOption(
  title = Title(text = "Body mass"),
  x_axis = Axis(type = "category", data = names(table(penguins$species))),
  y_axis = Axis(type = "value"),
  series = BarSeries(data = as.integer(table(penguins$species)))
)

str(to_list(opt))
List of 4
 $ title :List of 1
  ..$ text: chr "Body mass"
 $ xAxis :List of 2
  ..$ type: chr "category"
  ..$ data: chr [1:3] "Adelie" "Chinstrap" "Gentoo"
 $ yAxis :List of 1
  ..$ type: chr "value"
 $ series:List of 1
  ..$ :List of 2
  .. ..$ data: int [1:3] 152 68 124
  .. ..$ type: chr "bar"
cat(to_json(opt, pretty = TRUE))
{
  "title": {
    "text": "Body mass"
  },
  "xAxis": {
    "type": "category",
    "data": ["Adelie", "Chinstrap", "Gentoo"]
  },
  "yAxis": {
    "type": "value"
  },
  "series": [
    {
      "data": [152, 68, 124],
      "type": "bar"
    }
  ]
}

to_list() works on every class in the package, not just the top-level option, and always drops unset properties — ECharts treats a missing key as its default, so a null would mean something different:

to_list(ItemStyle(color = "#6CA3A0", opacity = 0.5))
$color
[1] "#6CA3A0"

$opacity
[1] 0.5

Property names convert to ECharts’ camelCase on the way out:

to_list(Axis(type = "value", split_number = 4, boundary_gap = FALSE))
$type
[1] "value"

$splitNumber
[1] 4

$boundaryGap
[1] FALSE

To go the other way — a chart described as a document, in rtemis’ own schema rather than ECharts’ — see Chart configs.

10.4 Quarto

Nothing to do: print the widget in an R chunk and it renders inline, as every chart on this site does. Charts follow the page’s light/dark toggle when theme is left at NULL.

10.5 Shiny

drawOutput() and renderDraw() are the standard htmlwidgets pair:

library(shiny)
library(rtemis.draw)

ui <- fluidPage(
  selectInput("var", "Variable", c("bill_len", "bill_dep", "flipper_len", "body_mass")),
  drawOutput("plot", height = "460px")
)

server <- function(input, output) {
  output$plot <- renderDraw({
    draw_density(penguins[[input$var]], group = penguins$species, xlab = input$var)
  })
}

shinyApp(ui, server)

For charts that redraw on every interaction, animation = FALSE is worth passing to draw(): animating a re-render of many points is expensive and buys nothing when the update is driven by an input.

draw(compile(cfg, data = df), animation = FALSE)
© 2026 E.D. Gennatas