jgd is a lightweight (C-based, zero dependency) R graphics device. It works by serializing R plotting operations into JSON and then streaming to an external renderer. A Deno-based reference server provides the primary rendering environment, serving a browser frontend over HTTP/WebSocket. A VS Code extension is also available as a demo client. Here's a screenshot of jgd running in VS Code:
The jgd protocol is designed to be frontend-agnostic. While VS Code is the current development focus, in principle any client able to read (newline-delimited) JSON could use it to render R plots.
Caveats: The package is experimental and may have some rough edges despite our best efforts at thorough local testing. The communication protocol between R and the renderer is still in development and not yet stable. Finally, we want to be transparent that this project has made heavy use of AI-assisted pair programming (Claude). It is highly doubtful that we would have been able to put this together without AI help.
install.packages('jgd', repos = 'https://grantmcdermott.r-universe.dev')The reference server provides a browser-based renderer over HTTP/WebSocket. Run it directly with Deno (no install needed):
deno run https://raw.githubusercontent.com/grantmcdermott/jgd/refs/heads/main/server/main.tsOr clone the repo and run locally:
cd server && deno task startA VS Code extension is available as a demo client. Download the .vsix from
the
nightly release,
then install it:
code --install-extension jgd-vscode-nightly.vsixAlternatively, build from source for local development:
cd vscode-ext
npm install && npm run compile
code --extensionDevelopmentPath="$(pwd)"Start the reference server, then open http://127.0.0.1:<port>/ in your
browser (the URL is printed on startup). In a separate terminal, start R and
run the following script. If you're using the VS Code extension instead, just
run the script from the VS Code R terminal.
library(jgd)
jgd()
# Base graphics
plot(1:10)
lines(1:10, col = "red", lwd = 3)
hist(rnorm(1000), col = "steelblue")
plot(cars)
abline(lm(dist ~ speed, data = cars), col = "red", lwd = 2)
# tinyplot
library(tinyplot)
plt(bill_dep ~ bill_len | body_mass, facet = ~island,
data = penguins, theme = "clean")
# ggplot2
library(ggplot2)
ggplot(penguins, aes(bill_len, bill_dep, col = species)) +
geom_point() +
facet_wrap(~island) +
theme_bw()Use ◀ ▶ in the plot pane (or Alt+Left / Alt+Right) to navigate plot
history.
The primary motivation for this package is supporting a nicer R graphics
experience in VS Code. At present, the VS Code R
extension provides
fairly crude "native" graphics support, since plots are displayed at PNGs. As a
result, users have for some time relied on the nice
httpgd package for a better graphics
experience; indeed, the official R extension docs even recommend using it.
However, the httpgd alternative has become increasingly tricky to work with
due to repeated CRAN removals and lack of maintenance bandwidth. In brief, this
is because it embeds a full C++ SVG rendering stack and HTTP server inside the R
process, which is powerful but fragile. At the time of writing, both httpgd
and its core unigd dependency are unavailable
on CRAN due to a variety of C++ toolchain issues: non-API entry points, compiler
compatibility failures, etc. (See
here
and
here).
jgd takes a different approach. First, it doesn't render anything; it just records. All rendering happens in the client (a VS Code webview, a browser tab, or any future frontend). Second, it is very lightweight. The core of the R package is written in pure C with zero external dependencies. The only system dependencies are the POSIX socket API (macOS/Linux) and Winsock (Windows), both of which R itself already uses.
Our idea (hope) is that we can support the main features of httpgd, but with a
more stable and lightweight footprint. Ultimately, if the community agrees, we
might even be able to integrate this simple package into the main R extension
logic, so that we get nice graphics support in VS Code out of the box.
Positron is a "batteries-included" fork of VS Code by Posit PBC. It comes with many great features, including first-class support for R (and Python) graphics. In our opinion, Positron is likely the best IDE choice for a plurality of R users and we can happily recommend it. However, that still leaves a non-trivial share of R users and use-cases, where a good "base" VS Code R experience is still needed. jgd is aimed at supporting these latter cases.
┌─────────────────────────────────────────────────┐
│ R Process │
│ │
│ jgd R package (pure C) │
│ ┌───────────────────────────────────────────┐ │
│ │ DevDesc callbacks → JSON serializer │ │
│ │ → socket client │──┼──┐
│ └───────────────────────────────────────────┘ │ │
└─────────────────────────────────────────────────┘ │
Unix domain sockets (macOS/Linux), │
named pipes (Windows), or TCP — NDJSON │
┌─────────────────────────────────────────────────┐ │
│ Server (Deno reference server or VS Code ext.) │◄─┘
│ │
│ Listener → Plot history → Canvas2D renderer │
│ (browser / webview) │
└─────────────────────────────────────────────────┘
The R package hooks into R's graphics engine via the standard DevDesc
callback interface. Every primitive — lines, rectangles, circles, polygons,
text, paths, raster images, clipping regions — is captured as a JSON object
and streamed over the socket. The renderer replays these operations faithfully
using the browser's Canvas2D API.
- Pure C, no C++ dependencies. The R package compiles with
R CMD INSTALLon any platform R supports. No Boost, no fmt, no Asio, no system graphics libraries. - Frontend-agnostic protocol. The JSON ops format is a simple, versioned schema. The Deno reference server and VS Code extension are the current clients, but the same stream could drive a Neovim plugin or any other renderer.
- Incremental updates. Adding a line to an existing plot sends only the new operations, not the entire plot. The renderer appends to the current frame.
- Client-side scaling. The renderer can replay the same operations at any resolution without round-tripping to R, enabling instant resize feedback.
- Base graphics:
plot(),hist(),lines(),points(),text(),abline(),polygon(),polyline(),rect(),image(),path() - ggplot2: Full support via no-op stubs for R 4.1+ pattern/mask/group callbacks
- Plot history: Back/forward navigation with ◀ ▶ buttons
- Incremental updates:
plot()+lines()= one history entry - Text rotation, transparent colors, clip regions, line types, raster images (base64-encoded PNG)
- Auto-discovery:
JGD_SOCKETenvironment variable orjgd-discovery.jsonfile for automatic connection - Export: PNG and SVG from the toolbar dropdown, with custom dimensions (inches + DPI)
- Cross-platform: Unix domain sockets on macOS/Linux, named pipes on Windows (default), TCP on all platforms
- Reference server: Deno-based server with browser frontend over HTTP/WebSocket
- Windows support: Named pipes (default) and TCP transport
- Browser frontend: Deno reference server with HTTP/WebSocket renderer
- Protocol stabilization: Stabilize and document the NDJSON protocol
- CRAN submission: Package the R side for CRAN distribution
- R extension integration: Incorporate the code from this package into the main VS Code R extension (if the upstream maintainers agree).
- No PDF export: PNG and SVG export are supported. For PDF, convert the exported SVG using any standard tool (e.g. Inkscape, Chrome print-to-PDF).
r-pkg/
├── DESCRIPTION
├── NAMESPACE
├── R/
│ ├── device.R # R wrapper: jgd()
│ └── zzz.R # .onLoad
├── src/
│ ├── device.c # DevDesc setup and registration
│ ├── callbacks.c # All graphics callbacks (line, rect, text, ...)
│ ├── display_list.c # Page state and JSON frame serialization
│ ├── json_writer.c # Streaming JSON builder (no dependencies)
│ ├── transport.c # Socket/pipe/TCP client + discovery
│ ├── metrics.c # Approximation-based font metrics
│ ├── color.c # R color → CSS rgba() conversion
│ ├── png_encoder.c # Minimal uncompressed PNG encoder + base64
│ └── init.c # .Call registration
└── tests/testthat/ # Unit tests (transport, frame output, etc.)
server/ # Deno reference server (HTTP/WebSocket renderer)
tests/ # End-to-end tests and benchmarks
vscode-ext/
├── package.json
└── src/
├── extension.ts # Activation, commands, env var injection
├── socket-server.ts # Socket server (Unix + TCP) + NDJSON framing
├── webview-provider.ts # Webview panel + Canvas2D renderer
└── plot-history.ts # Per-session plot history management
MIT