creating-an-svg-device-02.Rmd
This series of vignettes will walk through the process of creating a very basic SVG output device using devout
.
The only code to be written is R code. All the tricky C/C++ code is taken care of within devout
.
Vignettes in this series:
“Scalable Vector Graphics (SVG) is an XML-based vector image format for two-dimensional graphics with support for interactivity and animation” - wikipedia
An SVG file is just a text file with a specific format which contains instructions for drawing vector shapes e.g.
<?xml version="1.0" encoding="UTF-8" ?>
<svg width="391" height="391" viewBox="-70.5 -70.5 391 391" xmlns="http://www.w3.org/2000/svg">
<rect x="25" y="25" width="200" height="200" fill="lime" stroke-width="4" stroke="pink" />
<circle cx="125" cy="125" r="75" fill="black" />
<polyline points="50,150 50,200 200,200 200,100" stroke="red" stroke-width="4" fill="none" />
<line x1="50" y1="50" x2="200" y2="200" stroke="blue" stroke-width="4" />
</svg>
Since SVG is a text format, our canvas for drawing instructions is just a character string.
Out SVG callback from the prior vignette needs to be expanded so that:
device_call = 'open'
is called, a new string is created.device_call = 'close'
is called, the string is written to fileIn this (and future examples), the svg_callback
is a function which really just contains a giant switch statement which then calls particular functions for each device_call
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Call a different function to match each of the device calls we handle.
# Always return the state
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
svg_callback <- function(device_call, args, state) {
switch(
device_call,
"open" = svg_open (args, state),
"close" = svg_close(args, state),
state
)
}
In the following, svg_open()
is called when device_call = 'open'
and it creates a character string (called “svg”) which contains a minimal SVG document.
Note that the string is created as part of the state$rdata
. It needs to be part of the state
as this is the only data structure which is passed to-and-from the R internals and every device call.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# When opening a device
# - create a "canvas". For svg, the canvas is just a text string of SVG
# commands that we'll keep adding to with each device call
# - add the canvas to the 'state$rdata' list
# - always return the state so we keep the canvas across different device calls
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
svg_open <- function(args, state) {
state$rdata$svg <- '
<svg height="100" width="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>'
state
}
svg_close()
is called when device_call = 'close'
and this function writes the canvas (state$rdata$svg
) to a file - state$rdata$filename
.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# When the device is closed, output the SVG somehow
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
svg_close <- function(args, state) {
writeLines(state$rdata$svg, state$rdata$filename)
state
}
The filename to write to comes from the rdevice call. The first argument is always the name of the callback function. Any further named arguments become named elements of state$rdata
and made available to the callback function.
View the SVG text output
cat(paste(readLines("svg/test-canvas.svg"), collapse = "\n"))
#>
#> <svg height="100" width="100">
#> <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
#> </svg>
Open the output in an SVG viewer