Skip navigation

Category Archives: R

I was cranking out a blog post for work earlier this week that shows off just how many integrations our platform has. I won’t blather about that content here, but as I was working on it, I really wanted to show off all the integrations.

A table seemed far too boring.

Several categorized unordered lists seemed too unwieldy.

Then, it dawned on me that I could make a visual representation of all the integration partners we have by thinking of the entire integrations’ ecosystem as a “universe” with each category being a “solar system” of that universe.

I’ve been leaning more heavily on javsascript for datavis these days, but I will always be more comfortable in {ggplot2}, so I headed to R to design a way to:

  • generate concentric orbits for “n” solar systems
  • randomize the placement of the planets in each ring
  • make a decent plot!

I worked with one of the most amazing designers on the planet (heh) to come up with some stellar (heh) styling for it, and this was the result:

5 solar system panels

I took the styling guidance and wrapped the messy, individual functions I had into a new {ggsolar} package, you can find at https://github.com/hrbrmstr/ggsolar.

It’s pretty raw, and I need to “geomify” it at some point, but it has

  • a function to generate the concentric circle polygons
  • another one to identify a random point on each ring
  • a naive plotting function, and
  • a theme cleanup function for decent output.

The default is to generate uniformly distributed concentric circles, but you have the option of supplying a custom radii vector to make it more “real”/“solar-sysetm-y”.

Here’s the general flow:

# sol_planets is a built in vector of our system's planet names
sol_orbits <- generate_orbits(sol_planets)

set.seed(1323) # this produced a decent placements

# naive but it works! You can specify your own point picker, too.
placed_planets <- randomize_planet_positions(sol_orbits)

# do the thing!
plot_orbits(
  orbits = sol_orbits, 
  planet_positions = placed_planets,
  label_planets = TRUE,
  label_family = hrbrthemes::font_es_bold
) +
  hrbrthemes::theme_ipsum_es(grid="") +
  coord_equal() +
  labs(
    title = "Sol",
    caption = "Pluto is 100% a planet"
  ) +
  theme_enhance_solar()

Random Systems

I included a generate_random_planets() function that uses a hidden Markov model to create believable planetary names, so you can now make your own universe with {ggplot2}!

set.seed(42)
(rando_planets <- generate_random_planets(12))

rando_orbits <- generate_orbits(rando_planets)

set.seed(123) # this produced decent placements

placed_planets <- randomize_planet_positions(rando_orbits)

plot_orbits(
  orbits = rando_orbits, 
  planet_positions = placed_planets,
  label_planets = TRUE,
  label_family = hrbrthemes::font_es_bold
) +
  hrbrthemes::theme_ipsum_es(grid="") +
  coord_equal() +
  labs(
    title = "Rando System"
  ) +
  theme_enhance_solar()

random system

FIN

Kick the tyres, use {gganimate} to make some animations, and be the ruler of your own universe! (We’re going to try to generate team “org charts” with these later in the week, so be creative, too!).

The official example WebR REPL is definitely cool and useful to get the feel for WebR. But, it is far from an ideal way to deal with it interactively, even as just a REPL.

As y’all know, I’ve been conducing numerous experiments with WebR and various web technologies. I started doing this for numerous reasons, one was to get folks excited about WebR and try to show there are endless possibilities for it (and hopefully avoid lock-in to prescribed views on how you should work with it). Another was to brush up on rusty web skills and have something fun to do during the continuing long aftermath of my spike protein invasion.

I started poking under the WebR covers this past weekend, and until there’s a more pyodide-like JS bridge on the R side of WebR, I decided to forego said spelunking. Instead, I began a dive into {plot2}, a really neat {ggplot2}-esque enhancement to base R plotting. While I could use any R-compatible IDE (there are many, btw), I wanted to do all the experiments in WebR-proper, since base plots work out of the box and the {ggplot2} ecosystem takes a bit of time to install. The tinkering began just fine, but it became a bit tedious doing browser refreshes (they’re automatic with Vite in dev mode) for small tweaks. There was no way I was using the official REPL given the lack of real interactivity in the console. And, I wanted to avoid keeping re-rendering Quarto documents, since that would have been as tedious as the Vite refreshes.

So, I decided to make an “IDE REPL” for WebR, so I could work with it like I would R in Sublime Text, VS Code, or RStudio. I mean, wouldn’t everyone?

You can check it out here, and the source is on GitHub.

I’m not going to take up much more time here, since it comes with some explanations out of the box, but I will reproduce the GH README for it at the end. I will present the structure of the project, here, to make it easier to build upon it (clone/fork away!).

I’m using Monaco, the editor that powers VS Code and the online GitHub editor. It has so many batteries included that it’s hard not to want to use it, even considering how much I despise Microsoft as a company. It is dead simple to use.

The entire project is in vanilla javascript, and there is no builder this time, since I wanted to make this as accessible to as many folks as possible.

This is the project structure:

├── boilerplate.js # text that appears in the source on first load or hard refresh
├── completions.js # a decent number of R completions (I'll add more)
├── index.css      # core CSS
├── index.html     # HTML shell
├── main.js        # Main "app"
├── resizers.js    # We need to keep the panes sized properly
├── r.js           # Some WebR bits
└── rlang.js       # Language stuff for Microsoft's Monaco editor

Rather than adorn the interface with silly buttons and baubles, I am putting functionality into the Monaco command palette.

Here’s what you’ve got with v0.1.0:

  • Auto-saves current source pane contents to local storage
  • R syntax highlighting
  • An oddly decent # of auto-completes
  • cmd-shift-p to bring up command palette
    • WebR: Clear Local Storage — nuke local storage and replace with the default document
    • WebR: Save Canvas as PNG — captain obvious
    • WebR: Save Source Pane — captain obvious
    • WebR: View WebR Environment Summary — captain obvious
    • WebR: View WebR History — captain obvious
  • cmd-shift-i inserts |>
  • option-shift-minus inserts <-
  • watches for ?… and will open up a new tab for web help on whatev u searched for (XSS protected)
  • watches for broweURL(…) and will open up the URL in a new tab (XSS protected)
  • baked-in install.runiverse(pkg) which will try to install a pkg from R Universe. It is ON YOU to load the deps and ensure all deps and the pkg itself will work in WebR. You can use this tool I made to help you out.

FIN

Apart from making the current functionality more robust/pretty, one big forthcoming advancement will be the ability to save/load the WebR workspace to local browser storage. What that will mean for you, is that you can go to an instance of the app, all source changes will automagicallly be saved/restored to the session between visits. Plus — if you’ve saved the workspace image — it will be auto-restored on the visit, leaving you to just have to re-install/load any necessary packages. This means you can get right back to “work”.

I’ll be adding the ability to load files from your local system and use {svglite} for graphics (Monaco has an amazing SVG viewer), and to actually work in the R Console area (either with some janky input box or janky xterm.js).

Kick the tyres, file bugs, feature enhancements, and PRs, and start playing more with WebR!

I won’t wax long and poetic here since I’ve already posted the experiment that has all the details.

TL;DR: there are still only ~90-ish 📦 in the WebR WASM “CRAN”, but more are absolutely on the way, including the capability to build your own CRAN and dev packages via Docker and host your own WebR WASM pkg repo.

@timelyportfolio created an experimental method to install built base R packages from R Universe, and I enhanced that method in another, recent experiment, but that’s a bit wonky, and you have to do some leg work to figure out if a package can be installed and then do a bunch of manual work (though that Observable notebook will save you time).

The aforelinked new experiment shows how to use Pyodide side-by-side with WebR. While this one doesn’t have them sharing data or emscripten filesystems yet, we’ll get there! This puts SCADS of Python packages at your fingertips to fill in the gap while we wait for more R 📦 to arrive.

Code is up on GitHub but hit the experiment first to see what’s going on.

R output, Python output, python plot

A small taste of the experiment.

See it live before reading!

The previous post brought lit-webr, to introduce Lit and basic reactivity.

Today, is more of the same, but we bring the OG Shiny demo plot into the modern age by using Observbable Plot to make the charts.

We’re still pulling data from R, but we’re letting Plot do all the heavy lifting.

Here’s what’s changed…

First, main.js no longer has an {svglite} dependency. This means slightly faster load times, and less code. After ensuring we have datasets available, this is remainder of what happens (please see the larger example for more extended “what’s goin’ on?” comments):

//  WE WILL TALK ABOUT THIS BELOW
import { webRDataFrameToJS } from './utils.js'

const regions = document.getElementById("regionsInput")
const plotOutput = document.getElementById("regionsOutput")

regions.options = await (await R.webR.evalR(`colnames(WorldPhones)`)).toArray()

//  WE WILL TALK ABOUT THIS BELOW
plotOutput.worldPhones = webRDataFrameToJS(
  await (await webR.evalR(
    `as.data.frame.table(WorldPhones, stringsAsFactors=FALSE) |> 
       setNames(c("year", "region", "phones"))`
  )).toJs())

plotOutput.region = regions.options[ 0 ]

The webRDataFrameToJS() function in utils.js was mentioned in a previous experiment. Its sole mission in life is to turn the highly structured object that is the result of calling WebR’s toJs() function on an R data.frame. Most JS data things like the structure webRDataFrameToJS() puts things into, and Observable Plot is a cool JS data thing.

The ugly await… await… sequence is to get the data from R to give to webRDataFrameToJS(). We got lucky thins time since as.data.frame.table does a niiice job taking the WorldPhones rownamed matrix and pivoting it longer.

We store the output of that into the region-plot component. I could/should have made it a private property, but no harm, no foul in this setting.

Lastly, in region-plot.js, our component is reduced to two properties: one to store the region name and one for the data you saw, above. We still use events to trigger updates between the popup and the plotter, and said plotter is doing this in render():

render() {
return html`
<div>
<slot></slot>
${
  Plot.plot({
    style: {
      background: "#001e38",
      color: "#c6cdd7",
      padding: "30px",
      fontSize: "10pt",
      fontFamily: '-apple-system, BlinkMacSystemFont, …'
    },
    inset: 10,
    marginLeft: 60,
    caption: "Data from AT&T (1961) The World's Telephones",
    x: {
      label: null,
      type: "band"
    },
    y: {
      label: "Number of ☎️ (K)",
      grid: true
    },
    marks: [
      Plot.barY(
        this.worldPhones.filter((d) => d.region === this.region),
        { x: "year", y: "phones", fill: "#4a6d88" }
      ),
      Plot.ruleY([0])
    ]
  })
}
</div>`;
}

When the region changes, it triggers a reactive update. When the refresh happens, this snippet:

js
this.worldPhones.filter((d) => d.region === this.region)

does the hard work of filtering out all but the region we selected from the tiny, in-memory phones “database”.

Plot may not be {ggplot2}, but it cleans up well, and we’ve even had it match the style we used in the previous experiment.

See it live before reading!

This is a Lit + WebR reproduction of the OG Shiny Demo App

Lit is a javascript library that makes it a bit easier to work with Web Components, and is especially well-suited in reactive environments.

My recent hack-y WebR experiments have been using Reef which is an even ligher-weight javascript web components-esque library, and it’s a bit more (initially) accessible than Lit. Lit’s focus on “Web Components-first” means that you are kind of forced into a structure, which is good, since reactive things can explode if not managed well.

I also think this might Shiny folks feel a bit more at home.

This is the structure of our Lit + WebR example (I keep rejiggering this layout, which likely frustrates alot of folks 🙃)_

lit-webr
├── css
│   └── style.css             # you know what this is
├── favicon.ico               # when developing locally I want my icon
├── index.html                # you know what this is
├── main.js                   # the core experiment runner
├── md
│   └── main.md               # the core experiment markdown file
├── r-code
│   └── region-plot.R         # we keep longer bits of R code in source files here
├── r.js                      # place for WebR work
├── renderers.js              # these experiment templates always use markdown
├── themes
│   └── ayu-dark.json         # my fav shiki theme
├── utils.js                  # handy utilities (still pretty bare)
├── wc
│   ├── region-plot.js        # 👉🏼 WEB COMPONENT for the plot
│   ├── select-list.js        # 👉🏼               for the regions popup menu
│   └── status-message.js     # 👉🏼               for the status message
├── webr-serviceworker.js.map # not rly necessary; just for clean DevTools console
└── webr-worker.js.map        # ☝🏽

A great deal has changed (due to using Lit) since the last time you saw one of these experiments. You should scan through the source before continuing.

The core changes to index.html are just us registering our web components:

<script type="module" src="./wc/select-list.js"></script>
<script type="module" src="./wc/region-plot.js"></script>
<script type="module" src="./wc/status-message.js"></script>

We could have rolled them up into one JS file and minified them, but we’re keeping things simple for these experiments.

Web Components (“components” from now on) become an “equal citizen” in terms of HTMLElements, and they’re registered right in the DOM.

The next big change is in this file (the rendered main.md), where we use these new components instead of our <div>s. The whittled down version of it is essentially:

<status-message id="status"></status-message>

<region-plot id="regionsOutput" svgId="lit-regions">
  <select-list label="Select a region:" id="regionsInput"></select-list>
</region-plot>

The intent of those elements is pretty clear (much clearer than the <div> versions), which is one aspect of components I like quite a bit.

You’ll also notice components are - (dash) crazy. That’s part of the Web Components spec and is mandatory.

We’re using pretty focused components. What I mean by that is that they’re not very reusable across other projects without copy/paste. Part of that is on me since I don’t do web stuff for a living. Part of it was also to make it easier to show how to use them with WebR.

With more modular code, plus separating out giant chunks of R source means that we can actually put the entirety of main.js right here (I’ve removed all the annotations; please look at main.js to see them; we will be explaining one thing in depth here, vs there, tho.):

import { renderMarkdownInBody } from "./renderers.js";
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";

await renderMarkdownInBody(
  `main`,
  "ayu-dark",
  [ 'javascript', 'r', 'json', 'md', 'xml', 'console' ],
  false
)

let message = document.getElementById("status");
message.text = "WebR Loading…"

import * as R from "./r.js";

message.text = "Web R Initialized!"

await R.webR.installPackages([ "svglite" ])

await R.library(`svglite`)
await R.library(`datasets`)

const regionRender = await globalThis.webR.evalR(await d3.text("r-code/region-plot.R"))

message.text = "{svglite} installed"

const regions = document.getElementById("regionsInput")
const plotOutput = document.getElementById("regionsOutput")

regions.options = await (await R.webR.evalR(`colnames(WorldPhones)`)).toArray()
plotOutput.region = regions.options[ 0 ]
plotOutput.renderFunction = regionRender
plotOutput.render()

message.text = "Ready"

I want to talk a bit about this line from main.js:

const regionRender = await globalThis.webR.evalR(
  await d3.text("r-code/region-plot.R")
)

That fetches the source of the single R file we have in this app, evaluates it, and returns the evaluated value (which is an R function object) to javascript. This is the script:

renderRegions <- function(region, id = "region-plot") {

  # our base plot theme

  list(
    panel.fill = "#001e38",
    bar.fill = "#4a6d88",
    axis.color = "#c6cdd7",
    label.color = "#c6cdd7",
    subtitle.color = "#c6cdd7",
    title.color = "#c6cdd7",
    ticks.color = "#c6cdd7",
    axis.color = "#c6cdd7"
  ) -> theme

  # get our svg graphics device amp'd
  s <- svgstring(width = 8, height = 4, pointsize = 8, id = id, standalone = FALSE)

  # setup theme stuff we can't do in barplot()
  par(
    bg = theme$panel.fill,
    fg = theme$label.color
  )

  # um, it's a barplot
  barplot(
    WorldPhones[, region],
    main = region,
    col = theme$bar.fill,
    sub = "Data from AT&T (1961) The World's Telephones",
    ylab = "Number of Telephones (K)",
    xlab = "Year",
    border = NA,
    col.axis = theme$axis.color,
    col.lab = theme$label.color,
    col.sub = theme$subtitle.color,
    col.main = theme$title.color
  )

  dev.off()

  # get the stringified SVG
  plot_svg <- s()

  # make it responsive
  plot_svg <- sub("width='\\d+(\\.\\d+)?pt'", "width='100%'", plot_svg)
  plot_svg <- sub("height='\\d+(\\.\\d+)?pt'", "", plot_svg)

  # return it
  plot_svg

}

That R function is callable right from javascript. Creating that ability was super brilliant of George (the Godfather of WebR). We actually end up giving it to the component that plots the barplot (see region-plot.js) right here:

plotOutput.renderFunction = regionRender

We’re getting a bit ahead of ourselves, since we haven’t talked about the components yet. We’ll do so, starting with the easiest one to grok, which is in status-message.js and is represented by the <status-message></status-message> tag.

These custom Lit components get everything HTMLElement has, plus whatever else you provide. I’m not going to show the entire source for status-message.js here as it is (lightly) annotated. We’ll just cover the fundamentals, as Lit components also have alot going on and we’re just using a fraction of what they can do. Here’s the outline of what’s in our status-message:

export class StatusMessage extends LitElement {
  static properties = { /* things you can assign to and read from */ }
  static styles = [ /* component-scoped CSS */ ]
  constructor() { /* initialization bits */
  render() { /* what gets called when things change */ }
}
// register it
customElements.define('status-message', StatusMessage);

Our status-message properties just has one property:

static properties = {
  text: {type: String}, // TypeScript annotations are requried by Lit
};

This means when we do:

let message = document.getElementById("status");
message.text = "WebR Loading…"

we are finding our component in the DOM, then updating the property we defined. That will trigger render() each time, and use any component-restricted CSS we’ve setup.

Things get a tad more complicated in select-list.js. We’ll just cover the highlights, starting with the properties:

static properties = {
  id: { type: String },    // gives us easy access to the id we set
  label: { type: String }, // lets us define the label up front
  options: { type: Array } // where the options for the popup will go
};

If you recall, this is how we used them in the source:

<region-plot id="regionsOutput" svgId="lit-regions">
  <select-list label="Select a region:" id="regionsInput"></select-list>
</region-plot>

The id and label properties will be available right away after the custom element creation.

We start option with an empty list:

constructor() {
  super()
  this.options = []
}

Our render() function places the <label> and <select> tags in the DOM and will eventually populate the menu once it has data:

render() {
  const selectId = `select-list-${this.id}`;
  return html`
  <label for="${selectId}">${this.label} 
    <select id="${selectId}" @change=${this._dispatch}>
      ${this.options.map(option => html`<option>${option}</option>`)}
    </select>
  </label>
  `;
}

Their clever use of JS template strings makes it much easier than ugly string concatenation.

That html in the return is doing alot of work, and not just returning text. You gotta read up on Lit to get more info b/c this is already too long.

The way we wired up reactivity in my Reef examples felt kludgy, and even the nicer way to do it in Reef feels kludgy to me. It’s really nice in Lit. This little addition to the <select> tag:

@change=${this._dispatch}

says to call a function named _dispatch whenever the value changes. That’s in the component as well:

_dispatch(e) {
  const options = {
    detail: e.target,
    bubbles: true,
    composed: true,
  };
  this.dispatchEvent(new CustomEvent(`regionChanged`, options));
}

We setup a data structure and then fire off a custom event that our plot component will listen for. We’ve just linked them together on one side. Now we just need to populate the options list, using some data from R:

const regions = document.getElementById("regionsInput")
regions.options = await (await R.webR.evalR(`colnames(WorldPhones)`)).toArray()

That’ll make the menu appear.

Hearkening back to the main.js plot setup:

const plotOutput = document.getElementById("regionsOutput")
plotOutput.region = regions.options[ 0 ]
plotOutput.renderFunction = regionRender
plotOutput.render()

we see that we:

  • find the element
  • set the default region to the first one in the popup
  • assign our R-created rendering function to it
  • and ask it nicely to render right now vs wait for someone to select something

The other side of that (region-plot.js) is a bit more complex. Let’s start with the properties:

static properties = {
  // we keep a local copy for fun
  region: { type: String },

  // this is where our S
  asyncSvg: { type: String },

  // a DOM-accessible id string (cld be handy)
  svgId: { type: String },

  // the function to be called to render
  renderFunction: { type: Function }
};

WebR === “async”, which is why you see that asyncSvg. Async is great and also a pain. There are way more functions in region-plot.js as a result.

We have to have something in renderFunction before WebR is powered up since the component will be alive before that. We’ll give it an anonymous async function that returns an empty SVG.

this.renderFunction = async () => `<svg></svg>`

Oddly enough, our render function does not call the plotting function. This is what it does:

render() {
  return html`
  <div>
  <slot></slot>
  ${unsafeSVG(this.asyncSvg)}
  </div>`;
}

This bit:

<slot></slot>

just tells render() to take whatever is wrapped in the tag and shove it there (it’s a bit more powerful than just that tho).

This bit:

${unsafeSVG(this.asyncSvg)}

is just taking our string with SVG in it and letting Lit know we really want to live dangerously. Lit does its best to help you avoid security issues and SVGs are dangerous.

So, how do we render the plot? With two new functions:

// this is a special async callback mechanism that 
// lets the component behave normally, but do things 
// asynchronously when necessary.
async connectedCallback() {

  super.connectedCallback();

  // THIS IS WHERE WE CALL THE PLOT FUNCTION
  this.asyncSvg = await this.renderFunction(this.region, this.svgId);

  // We'll catch this event when the SELECT list changes or when
  // WE fire it, like we do down below.
  this.addEventListener('regionChanged', async (e) => {
    this.region = e.detail.value;
    const res = await this.renderFunction(this.region, this.svgId);
    // if the result of the function call is from the R function and
    // not the anonymous one we initialized the oject with
    // we need to tap into the `values` slot that gets return
    // with any call to WebR's `toJs()`
    if (res.values) this.asyncSvg = res.values[ 0 ] ;
  });

}

// special function that will get called when we 
// programmatically ask for a forced update
performUpdate() {
  super.performUpdate();
  const options = {
    detail: { value: this.region },
    bubbles: true,
    composed: true,
  };

  // we fire the event so things happen async
  this.dispatchEvent(new CustomEvent(`regionChanged`, options));
}

That finished the wiring up on the plotting end.

Serving ‘Just’ Desserts (Locally)

I highly recommend using the tiny but awesome Rust-powered miniserve to serve things locally during development:

miniserve \
  --header "Cache-Control: no-cache; max-age=300" \
  --header "Cross-Origin-Embedder-Policy: require-corp" \
  --header "Cross-Origin-Opener-Policy: same-origin" \
  --header "Cross-Origin-Resource-Policy: cross-origin" \
  --index index.html \
  .

You can use that (once installed) from the local justfile, which (presently) has four semantically named actions:

  • install-miniserve
  • serve
  • rsync
  • github

You’ll need to make path changes if you decide to use it.

FIN

I realize this is quite a bit to take in, and — as I keep saying — most folks will be better off using WebR in Shiny (when available) or Quarto.

Lit gives us reactivity without the bloat that comes for the ride with Vue and React, so we get to stay in Vanilla JS land. You’ll notice there’s no “npm” or “bundling” or “rollup” here. You get to code in whatever environment you want, and serving WebR-powered pages is, then, as simple as an rsync.

Drop issues at the repo.

After writing the initial version of a tutorial on wrapping and binding R functions on the javascript side of WebR, I had a number of other WebR projects on the TODO list. But, I stuck around noodling on the whole “wrapping & binding” thing, and it dawned on me that there was a “pretty simple” way to make R functions available to javascript using javascript’s Function() constructor. I’ll eventually get this whole thing into the GH webr-experiments repo, but you can see below and just view-source (it’s as of the time of this post, in the 130’s re: line #’s) to check it out before that.

Dynamic JavaScript Function Creation

The Function() constructor has many modes, one of which involves providing the whole function source and letting it build a function for you. R folks likely know you can do that in R (ref: my {curlconverter} pkg), and it’s as straightforward on the javascript side. Open up DevTools Console and enter this:

const sayHello = new Function('return function (name) { return `Hello, ${name}` }')();

then call the function with some string value.

We only need one input to create a dynamic R function wrapper (for SUPER BASIC R functions ONLY). Yes, I truly mean that you can just do this:

let rbeta = await wrapRFunction("rbeta");
await rbeta(10, 1, 1, 0)

and get back the result in the way WebR’s toJs() function returns R objects:

{
  "type": "double",
  "names": null,
  "values": [
    0.9398577840605595,
    0.42045006265859153,
    0.26946718094298633,
    0.3913958406551122,
    0.8123499099597378,
    0.49116132695862963,
    0.754970193716774,
    0.2952198011408607,
    0.11734111483990002,
    0.6263863870230043
  ]
}

Formal Attire

R’s formals() function will let us get the argument list to a function, including any default values. We won’t be using them in this MVP version of the auto-wrapper, but I see no reason we couldn’t do that in a later iteration. It’s “easy” to make that a javascript function “the hard way”:

async function formals(rFunctionName) {
  let result = await globalThis.webR.evalR(`formals(${rFunctionName})`);
  let output = await result.toJs();
  return(Promise.resolve(output))
}

Now, we just need to use those names to construct the function. This is an ugly little function, but it’s really just doing some basic things:

async function wrapRFunction(rFunctionName) {
  let f = await formals(rFunctionName)
  let argNames = f.names.filter(argName => argName != '...');
  let params = argNames.join(', ')
  let env = argNames.map(name => `${name}: ${name}`).join(', ');
  let fbody =
  `return async function ${rFunctionName}(${params}) {
    let result = await globalThis.webR.evalR(
      '${rFunctionName}(${params})',
      { env: { ${env} }}
   );
    let output = await result.toJs();
    globalThis.webR.destroy(result);
    return Promise.resolve(output);
  }`;
  return new Function(fbody)()
}
  • first, we get the formals for the function name provided to us
  • then we remove ones we can’t handle (yet!)
  • then we take the R function parameter names and make two objects:
    • one that will make a comma-separated parameter declaration list (e.g. param1, param2, param3)
    • another that does the same, but as key: value pairs to pass as the environment (see previous WebR blog posts and experiments)
  • the function body is another template string that just makes the standard WebR set operations to evaluate an R object and get the value back.

We pass the built function string to the Function() constructor, and we have an automagic javascript wrapper for it.

FIN

This is a very naive wrapper. It’s all on the caller to specify all the values. R has some fun features, like the ability to not specify a value for a given parameter, and then check on the R side to see if it’s missing. It can also intermix named and positional parameters, and then there’s the dreaded ....

I’m going to noodle a bit more on how best to account and/or implement ^^, but I’ll 100% be using this idiom to wrap R functions as I keep experimenting.

One more thing: I had to abandon the use of the microlight syntax highlighter. It’s super tiny and fast, but it doesn’t handle the code I need, so I’ll be updating my nascent webr-template to incorporate what I am using now: Shiki. The next release will also include these basic wrapper functions.

It’s difficult to believe it has only been a couple of weeks since WebR has been around. But that might just be my perception. The spike protein invasion has significantly increased sedentary time, and that has enabled me to focus on this new toy to keep my attention focused on something positive. So, I’ve had “WebR on the mind” more than most others.

As folks likely know, I’m keeping a log of these “experiments” over at GH, and I’ve blogged, here, a bit on a few of them. (I promise I’ll get to Part 2 of “ggwebr”, soon.). Today we have an update on two of them.

WebR Filesystem Machinations

This WebR-powered page is pretty a pretty self-contained explanation of how to work with the Emscripten filesystem from both the javascript and R contexts of WebR. I set out to just do that, but I was also sick of writing HTML for everything, and spent some time coming up with a system for hacking on these experiments with Markdown.

Yes, I could just use the exciting new Quarto WebR extension and Quarto itself, but there are advantages to working in plain Markdown, HTML, and Vanilla JS blank canvas (Quarto-rendered HTML is a bit, er, crufty).

The code in this filesystem experiment is also a bit different in another way: it uses a Vanilla JS lightweight reactive framework called Reef.

It worked so well that I made a few more examples, which are covered in the remaining sections.

More Concise Example

The filesystem experiment is a tad big, especially since it introduces some fancy javascript things many R folks are not familiar with. That, combined with the cognitive load associated with learning WebR itself, meant I had to simplify things a bit if I wanted to convey the utility of this setup.

If you’re asking, “why not just use React?”, the answer is that React is overkill for the small projects I have in mind, and that I think many other folks who may want to use WebR also have in mind. I also don’t really like packers/bundlers. The early days of the web were fun and easy to work in, and I wanted to replicate that for WebR.

So, I made the sadly named webr-reef example. It’s a small app that:

  • loads a list of all datasets in the datasets package and shoves it into a <select> list.
  • when a dataset is selected, the str() output of it is displayed below the popup menu.

If you view index.md, you’ll see it’s just some light markdown and a couple bits of HTML for the:

  • reactive #message <div> that will show anything we assign to it
  • reactive #selected-dataset <select> list which will get automagically populated on load and, when changed, will cause the value of the next item in this list to change
  • reactive #dataset-output which will hold a str() of the selected dataset.

The index.html file is really just a shell for opengraph tags plus an element to put this app into. main.js does all the work.

The source is fairly well-annotated, but it still was not simple enough IMO.

ReefR Template

Reef + WebR == ReefR

I wanted to make this super easy to read and start using quickly, so it’s ready-made to deploy to GH Pages. Just tell GHP to use / of the main branch, change a couple settings — like SW_URL — and it should “just work”. Here’s the GHP Link for the template repo, and here it is running on my site.

There are just two reactive components: the #message thing I mentioned earlier, and a <pre> block that gets filled with the value of the WebR WASM R version string, which is taken from executing code in the WebR context.

FIN

I’ll be iterating on ReefR to add some JS and R helpers folks can use to make it easier to work with WebR, so keep an eye on the experiments and template repo if this piques your interest. Also drop issues with ideas or questions.

I have graphics working in Vanilla JS WebR, now, and I’ll cover the path to that in two parts.

The intent was to jump straight into ggplot2-land, but, as you saw in my previous post, WASM’d ggplot2 is a bear. And, I really didn’t grok what the WebR site docs were saying about how to deal with the special WebR canvas() device until I actually tried to work with it and failed miserably.

You will need to have gotten caught up on the previous WebR blog posts and experiments as I’m just covering some of the gnarly bits.

Not Your Parents’ evalR…()

If you’ve either been playing a bit with WebR or peeked under the covers of what others are doing, you’ve seen the evalR…() family of functions which evaluate supplied R code and optionally return a result. Despite reading the WebR docs on “canvas”, daft me tried to simply use one of those “eval” functions, to no avail.

The solution involves:

I’m going to block quote a key captureR since it explains “why” pretty well. Hit me up anywhere you like if you desire more info.

Unlike evalR() which only returns one R object, captureR() returns a variable number of objects when R conditions are captured. Since this makes memory management of individual objects unwieldy, captureR() requires the shelter approach to memory management, where all the sheltered objects are destroyed at once.

Let’s work through the “plottR” function I made to avoid repeating code to just get images out of R. It takes, as input:

  • an initialized WebR context
  • code that will produce something on a graphics device
  • dimensions of the image
  • the HTML <canvas> id to shove the image data to (we’ll explain this after the code block)
async function plottR(webR, plot_code = "plot(mtcars, col='blue')",
                        width = 400, height = 400, 
                            id = "base-canvas") {

  const webRCodeShelter = await new webR.Shelter();

  await webR.evalRVoid(`canvas(width=${width}, height=${height})`);

  const result = await webRCodeShelter.captureR(`${plot_code}`, {
    withAutoprint: true,
    captureStreams: true,
    captureConditions: false,
    env: webR.objs.emptyEnv,
  });

  await webR.evalRVoid("dev.off()");

  const msgs = await webR.flush();

  const canvas = document.getElementById(id)
  canvas.setAttribute("width", 2 * width);
  canvas.setAttribute("height", 2 * height);

  msgs.forEach(msg => {
    if (msg.type === "canvasExec") Function(`this.getContext("2d").${msg.data}`).bind(canvas)()
  });

}

You 100% need to read up on the HTML canvas element if you’re going to wield WebR yourself vs use Quarto, Shiny, Jupyter-lite, or anything else clever folks come up with. The output of your plots is going to be a series of HTML canvas instructions to do things like “move here”, “switch to this color”, “draw an ellipse”, etc. I will be linking to a full example of the canvas instructions output towards the end.

Now, let’s work through the function’s innards.

const webRCodeShelter = await new webR.Shelter();

gives us a temporary place to execute R code, knowing all the memory consumed will go away after we’re immediately done with it. Unlike the baked-in “global” shelter, this one is super ephemeral.

await webR.evalRVoid(`canvas(width=${width}, height=${height})`);

This is just like a call to png(…), svglite(…), pdf(…), etc. Check out coolbutuseless’ repo for tons of great examples of alternate graphics devices. I have a half-finished one for omnigraffle. They aren’t “hard” to write, but I think they are very tedious to crank through.

const result = await webRCodeShelter.captureR(`${plot_code}`, {
  withAutoprint: true,
  captureStreams: true,
  captureConditions: false,
  env: webR.objs.emptyEnv,
});

is different from what you’re used to. The captureR function will evaluate the given code, and takes some more options, described in the docs. TL;DR: we’re asking the evaluator to give us back pretty much what’d we see in the R console: all console messages and output streams, plus it does the familiar “R object autoprint” that you get for free when you fire up an R console.

So, we’ve sent our plot code into the abyss, and — since this is 100% like “normal” graphics devices — we also need to do the dev.off dance:

await webR.evalRVoid("dev.off()");

This will cause the rendering to happen.

Right now, where you can’t see it, is the digital manifestation of your wonderful plot. That’s great, but we’d like to see it!

const msgs = await webR.flush();

will tell it to get on with it and make sure everything that needs to be done is done. If you’re not familiar with async/await yet, you really need to dig into that to survive in DIY WebR land.

const canvas = document.getElementById(id)
canvas.setAttribute("width", 2 * width);   // i still need to read "why 2x"
canvas.setAttribute("height", 2 * height);

msgs.forEach(msg => {
  if (msg.type === "canvasExec") Function(`this.getContext("2d").${msg.data}`).bind(canvas)()
});

finds our HTML canvas element and then throws messages at it; alot of messages. To see the generated code for the 3D perspective plot example, head to this gist where I’ve pasted all ~10K instructions.

To make said persp plot, it’s just a simple call, now:

await plottR(webR, `basetheme("dark"); persp(x, y, z, theta=-45)`)

I used the default id for the canvas in the online example.

“How Did You Use The basetheme Package? It’s Not In The WASM R Repo?”

I yanked the four R source code files from the package and just source‘d them into the WebR environment:

const baseThemePackage = [ "basetheme.R", "coltools.R", "themes.R", "utils.R" ];

// load up the source from the basetheme pkg
for (const rSource of baseThemePackage) {
  console.log(`Sourcing: ${rSource}…`)
  await globalThis.webR.evalRVoid(`source("https://rud.is/w/ggwebr/r/${rSource}")`)
}

10K+ Lines Is Alot Canvas Code…

Yep! But, that’s how the HTML canvas element works and it’s shockingly fast, as you’ve seen via the demo.

FIN

We’ll cover a bit more in part 2 when we see how to get ggplot2 working, which will include a WebR version of {hrbrthemes}! I also want to thank James Balamuta for the Quarto WebR project which helped me out quite a bit in figuring this new tech out.

Before I let you go, I wanted to note that in those “messages” (the ones we pulled canvasExec call out of), there are message types that are not canvasExec (hence our need to filter them).

I thought you might want to know what they are, so I extracted the JSON, and ran it through some {dplyr}:

msgs |> 
  filter(
    type != "canvasExec"
  ) |> 
  pull(data) |> 
  writeLines()
R version 4.1.3 (2022-03-10) -- "One Push-Up"
Copyright (C) 2022 The R Foundation for Statistical Computing
Platform: wasm32-unknown-emscripten (32-bit)

R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.

R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.

Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.

>