An introduction to the sketch package

The R package sketch

Creates interactive illustrations embeddable in RMarkDown documents. The package compiles R code into JavaScript code by applying rewriting rules to the R AST. This allows users to develop JS-style visualisations using only the R syntax.

Figure 1. Illustration of the rewriting process.


1. Preliminaries

1.1 Installation

install.packages("sketch")

# Or for the latest development 
install.packages("remotes")
remotes::install_github("kcf-jackson/sketch")

1.2 Running a sketch R file

There are two ways to run a sketch R file.

  1. Save the sketch R file (with the usual .R extension), then call sketch::source_r with it.

  2. If you use RStudio, there is an add-in “source a sketch R file” listed under this package (consider binding that to the key combination alt-shift-s), and that sources the active tab in the editor.

1.3 Embedding a sketch R file in RMarkDown document

Simply use sketch::insert_sketch in a code chunk:

sketch::insert_sketch(
  file = "main.R", id = "sketch_1",
  width = 800, height = 600
)

where file is the path to the sketch file, id, width and height are the identifier, width and height of the sketch. (The identifier is needed to manage multiple sketches in the same Rmd document.)


2. Basic examples

2.1 p5.js

The functions setup, draw, createCanvas, background, frameCount, fill and circle are from the p5.js library, and one can use them as they are in the R code. This also works for all the other functions in the library.

setup <- function() {
    createCanvas(400, 300)
}

draw <- function() {
    background(0, 0, 33)    # RGB colors

    for (i in 1:3) {
        dia <- sin(frameCount * 0.025) * 30 * i
        fill(255, 70 * i, 0)       # RGB colors
        circle(100 * i, 150, dia)   # (x, y, diameter)    
    }
}

2.2 DOM manipulation

The web browser variable document and all of its methods are available, and DOM attributes also work as expected.

helper.R

# Create a new DOM element with an innerText
dom <- function(tag_name, inner_text) {
    declare (el)   # Declare variable before use
    el <- document$createElement(tag_name)   # document and its methods are available
    el$innerText <- inner_text   # attributes work as expected
    return (el)   # return must be explicit
}

# Insert a DOM element into another
insert_into <- function(x, y) {
    document$querySelector(y)$appendChild(x)
}

main.R

# Textbox
textbox <- dom("div", "Hello World!")

# Two buttons
button_1 <- dom("button", "1")
button_1$onclick <- function() { textbox$innerText <- "Hello again!" }

button_2 <- dom("button", "2")
button_2$onclick <- function() { textbox$innerText <- "Bye!" }

# HTML
insert_into(textbox, "body")
insert_into(button_1, "body")
insert_into(button_2, "body")


3 Special operators

#| load_script()

Use #| together with load_script to load any JavaScript library, CSV / JSON data file or an other sketch R file.

declare() / let()

declare() / let() are empty R functions that act as a placeholder to facilitate the conversion to JavaScript. If you want to assign a value to a variable, you may proceed as:

declare (x)
x <- 3
# alternatively
let (x = 3)

Note that declare and let are 100% interchangeable, e.g. declare (x = 3) is also valid. declare is preferred for top-level variable declaration while let is preferred for local use.

If one uses variables without declaring them, JavaScript will declare it for you and place them in the global namespace.

%=>% and %+%

`%+%` <- paste0
"a" %+% "b" %+% "c"  # gives "abc"

lambda

The lambda function offers a convenient way to define anonymous function, which works similar to pryr::f. For instance, function(x, y) { return(x + y) } can be rewritten as lambda(x, y, x + y).


4 Common pitfalls (of writing JS in R)

i. JavaScript uses 0-based indexing

// Web-browser console
x = [1,2,3]
x[0]  // 1

ii. JavaScript function reads argument by position / order

// Web-browser console
first = function(x, y) { console.log(x) }
first(y = 3, x = 1)  // 3

iii. return in JavaScript function must be explicit

add = function(x, y) { x + y }
add(3, 4) // undefined

add2 = function(x, y) { return(x + y) }
add(3, 4) // 7

iv. JavaScript passes objects by reference (think R6 in R)

// Web-browser console
x = [1,2,3]    // Array(3) [ 1, 2, 3 ]
y = x          // Array(3) [ 1, 2, 3 ]
y[0] = 999
y              // Array(3) [ 999, 2, 3 ]
x              // Array(3) [ 999, 2, 3 ]

v. A browser session has a pre-populated namespace

// Web-browser console
for (let b in window) {
    if (window.hasOwnProperty(b)) console.log(b);
}