Plotting
Plotting with a HTML canvas
WebR’s supporting R package includes a built in graphics device, webr::canvas()
. When R uses this device, messages are sent to the main thread containing bitmap image data. The image data can then be displayed using a HTML Canvas element on the page.
A 2x scaling is used to improve the visual quality of the bitmap output. For the best results the width and height of the HTML canvas element displaying the final plot should be twice that of the graphics device. For example, the default arguments for webr::canvas()
create a device with a width and height of 504
, and so a correctly sized HTML canvas will have width
and height
attributes set to 1008
.
The background colour for the plot can be set with the bg
argument, and the text size may be changed by setting the pointsize
argument.
The webr::canvas()
graphics device relies on OffscreenCanvas
support in the web browser or JavaScript engine running webR. A modern and up-to-date browser will be required for plotting with this device. Older browsers without OffscreenCanvas
support should still be able to plot using the Cairo-based graphics devices, such as png()
.
Output Messages
The webr::canvas()
graphics device emits webR output messages when triggered by certain events. The resulting output messages are of type Message
with the type
property set as 'canvas'
and the data
property populated with further details about the event that caused the message.
New plot page
When the graphics device creates a new page for plotting, a message is emitted of the form,
type: 'canvas', data: { event: 'canvasNewPage' } } {
This message can be used as a signal to clear any existing plots, or create a new empty HTML canvas element.
Bitmap image data
When the graphics device is ready to send image data to the main thread for display, a message is emitted with the the image additionally included in the form of a JavaScript ImageBitmap
object. The message emitted is of the form,
type: 'canvas', data: { event: 'canvasImage', image: ImageBitmap } } {
Drawing the bitmap image data
Once the ImageBitmap
data has been received by the main thread, it can be displayed on the containing web page. A HTML canvas element can be used to display the image data using its 2D rendering context. The HTML canvas element can be created dynamically by the running JavaScript environment, or it may already exist in the page.
Once a 2D rendering context has been obtained, the image data can be displayed using the drawImage()
method.
Setting the default device
R’s default graphics device can be set so that webr::canvas()
is always used for new plots. Before running any plotting code, evaluate the following R code to set the default device,
await webR.evalRVoid('options(device=webr::canvas)');
Text rendering and font support
When the webr::canvas()
graphics device is used it is the web browser that handles the specifics of text rendering, and so any fonts installed on the host system can be used with the family
argument when plotting. Modern features provided by the browser such as RTL text, ligatures, colour emoji, or the use of Arabic, Japanese or Cyrillic script should also be handled automatically.
The editable R code in this example demonstrates font and text features, feel free to experiment.
Example: Handling multiple plots
In the following fully worked example, multiple plots are handled by listening for 'canvasNewPage'
events from the graphics device and dynamically adding new HTML canvas elements to the page.
<html>
<head>
<title>WebR Multiple Plots Example</title>
</head>
<body>
<h1>WebR Multiple Plots Example</h1>
<p><div id="loading">Please wait, webR is loading...</div></p>
<button id="plot-button" disabled="true">Run graphics demo</button>
<p>See the JavaScript console for additional output messages.</p>
<div id="plot-container"></div>
<script type="module">
import { WebR } from 'https://webr.r-wasm.org/latest/webr.mjs';
const webR = new WebR();
let canvas = null;
let loading = document.getElementById('loading');
let container = document.getElementById('plot-container');
let button = document.getElementById('plot-button');
.onclick = () => {
button.replaceChildren();
container.evalRVoid(`
webR webr::canvas()
demo(graphics)
demo(persp)
dev.off()
`);
}
async () => {
(// Remove the loading message once webR is ready
await webR.init();
.remove();
loading.removeAttribute('disabled');
button
// Handle webR output messages in an async loop
for (;;) {
const output = await webR.read();
switch (output.type) {
case 'canvas':
if (output.data.event === 'canvasImage') {
// Add plot image data to the current canvas element
.getContext('2d').drawImage(output.data.image, 0, 0);
canvaselse if (output.data.event === 'canvasNewPage') {
} // Create a new canvas element
= document.createElement('canvas');
canvas .setAttribute('width', '1008');
canvas.setAttribute('height', '1008');
canvas.style.width = "450px";
canvas.style.height = "450px";
canvas.style.display = "inline-block";
canvas.appendChild(canvas);
container
}break;
default:
console.log(output);
}
};
})()</script>
</body>
</html>
Click the button below to see the output of this demo,
Capturing plots
Plots may be captured by the webr::canvas()
graphics device when using captureR()
to evaluate R code. Captured plots are in the form of JavaScript ImageBitmap
objects and may be drawn to the page in the same way as described above.
In the following example, a set of demo plots are captured and then displayed on the page.
<html>
<head>
<title>WebR Test Console</title>
</head>
<body>
<div id="plot-output"></div>
<div>
<pre><code id="out">Loading webR, please wait...</code></pre>
</div>
<script type="module">
import { WebR } from 'https://webr.r-wasm.org/latest/webr.mjs';
const webR = new WebR();
await webR.init();
const shelter = await new webR.Shelter();
const capture = await shelter.captureR("demo(graphics)");
.images.forEach((img) => {
captureconst canvas = document.createElement("canvas");
.width = img.width;
canvas.height = img.height;
canvasconst ctx = canvas.getContext("2d");
.drawImage(img, 0, 0, img.width, img.height);
ctxdocument.getElementById("plot-output").appendChild(canvas);
;
})
.purge();
shelter</script>
</body>
</html>
Arguments for the capturing webr::canvas()
graphics device that’s used during evaluation, such as setting a custom width or height, can be included as part of the optional EvalROptions
argument to captureR()
:
const shelter = await new webR.Shelter();
const capture = await shelter.captureR("hist(rnorm(1000))", {
captureGraphics: {
width: 504,
height: 252,
bg: "cornsilk",
}; })
Plotting from the console
The Console
class includes callbacks that are used for handling image rendering. This example builds off the interactive webR REPL Console. In addition to the console, there is a <canvas>
element to which plots will be drawn. The callbacks canvasImage
and canvasNewPage
are used to draw plots.
When a new plot is created, the canvasNewPage
callback is used clearing the bitmap from the canvas using reset()
. Subsequently, the canvasImage
callback is used to draw the ImageBitMap
object onto the canvas.
<html>
<head>
<title>WebR Test Console</title>
<style>
body {display: flex;
}</style>
</head>
<body>
<div id="plot-output">
<canvas width="500" height="500" id="plot-canvas"></canvas>
</div>
<div>
<pre><code id="out">Loading webR, please wait...</code></pre>
<input spellcheck="false" autocomplete="off" id="input" type="text">
<button onclick="globalThis.sendInput()" id="run">Run</button>
</div>
<script type="module">
/* Create a webR console using the Console helper class */
import { Console } from 'https://webr.r-wasm.org/latest/webr.mjs';
var canvas = document.getElementById("plot-canvas")
var ctx = canvas.getContext('2d');
const webRConsole = new Console({
stdout: line => document.getElementById('out').append(line + '\n'),
stderr: line => document.getElementById('out').append(line + '\n'),
prompt: p => document.getElementById('out').append(p),
canvasImage: ci => ctx.drawImage(ci, 0, 0),
canvasNewPage: () => ctx.reset(),
;
}).run();
webRConsole
/* Set the default graphics device to be half the canvas element size */
await webRConsole.stdin("options(device=webr::canvas(250, 250))");
/* Write to the webR console using the ``stdin()`` method */
let input = document.getElementById('input');
.sendInput = () => {
globalThis.stdin(input.value);
webRConsoledocument.getElementById('out').append(input.value + '\n');
.value = "";
input
}
/* Send input on Enter key */
.addEventListener(
input"keydown",
=> {if(evt.keyCode === 13) globalThis.sendInput()}
(evt) ;
)</script>
</body>
</html>
Plotting with other graphics devices
In older browsers or JavaScript engines without OffscreenCanvas
support, alternative graphics devices may still be used to produce plots. The following methods do not rely on direct rendering in the web browser, but instead the resulting image data is created entirely within the WebAssembly environment and written to the Emscripten virtual filesystem.
Bitmap graphics using Cairo for Wasm, e.g. png()
WebR may be built with bitmap graphics support though the use of a WebAssembly version of the Cairo graphics library and its prerequisites. This support is not enabled by default when building webR from source, as it significantly increases the output WebAssembly binary size and build time, but Cairo graphics support is explicitly enabled for the publicly available distributions of webR via CDN.
When webR is built with Cairo support the following graphics devices are available for use with R in the usual way:
Text rendering and font support
Unlike the HTML canvas device described in the previous section, rendering graphics entirely within the WebAssembly environment presents a challenge in that the sandbox does not have access to the font data installed in the host system. As such, fonts must be made available for use on the Emscripten virtual filesystem before plotting occurs.
When built with Cairo support, webR bundles a minimal selection of fonts. The Noto series of fonts was chosen for this purpose for its open licence and notably high support for internationalisation,
Access to font data is managed through a WebAssembly build of Fontconfig. The fonts bundled by webR support Latin, Cyrillic and Greek scripts, and additional fonts can be uploaded to the Emscripten virtual filesystem in the directory /home/web_user/fonts
to allow for different typefaces or additional script support.
Vector graphics using pdf()
and svglite()
Vector graphics can be produced with webR through use of the built-in pdf()
graphics device, or through the svglite
package, which can be installed in webR using the command webr::install("svglite")
.
Obtaining the plot data from the VFS
The contents of graphics output that has been written to the Emscripten virtual filesystem can be obtained as a JavaScript UInt8Array
using the Filesystem API. The data can then be offered for display or download by working with the resulting ArrayBuffer
.
In this example, a vector graphics plot is created using the pdf()
graphics device, the contents of the file is read from the Emscripten virtual filesystem, and finally the PDF file is offered to the user via a download link.
<html>
<head>
<title>WebR PDF Plot Download Example</title>
</head>
<body>
<h1>WebR PDF Plot Download Example</h1>
<p id="loading">Please wait, webR is working on producing a plot...</div>
<p id="link-container"></p>
<script type="module">
import { WebR } from 'https://webr.r-wasm.org/latest/webr.mjs';
const webR = new WebR();
await webR.init();
// Create a PDF file containing a plot
await webR.evalRVoid(`
pdf()
hist(rnorm(10000))
dev.off()
`);
// Obtain the contents of the file from the VFS
const plotData = await webR.FS.readFile('/home/web_user/Rplots.pdf');
// Create a link for the user to download the file contents
const blob = new Blob([plotData], { type: 'application/octet-stream' });
const link = document.createElement('a');
.download = 'Rplots.pdf';
link.href = URL.createObjectURL(blob);
link.textContent = 'Click to download PDF';
linkdocument.getElementById('link-container').appendChild(link);
// Everything is ready, remove the loading message
document.getElementById('loading').remove();
</script>
</body>
</html>