jgd
July 1, 2026 · View on GitHub
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. For example, the VS Code R Extension:
Users aren't limited to VS Code as a jgd frontend. For example, below we also provide a standalone Deno server for rendering your R plots in a web browser. More generally, the jgd protocol is designed to be frontend agnostic; any JSON-conversant client could use it to render R plots (e.g., Neovim, Emacs, Zed, or a custom web app). We encourage users to build alternatives and would welcome additional contributions.
Installation
To run jgd, you need to install the R package, as well as a frontend for displaying plots.
R package
The stable version of jgd is available on CRAN:
install.packages('jgd')
Or, grab the development version from R-universe:
install.packages('jgd', repos = 'https://grantmcdermott.r-universe.dev')
Display frontend
There are two officially supported frontends. Click to expand for your preferred method.
1) VS Code
Update (2026-06-30): Our jgd VS Code extension has been
absorbed into the main
VS Code R extension. We have adapted the instructions below accordingly.
The official VS Code R extension provides native support for jgd as part of its major v3.0.0 updates. At the time of writing, this requires installing the release candidate version from GitHub:
curl -fsSL \
https://github.com/REditorSupport/vscode-R/releases/download/latest/vscode-R.vsix \
-o vscode-R.vsix
code --install-extension vscode-R.vsix
Once the 3.0.0-rc version of the extension has been installed, you will also
be prompted to install the sess R package. Just say "yes", or install it
manually:
Rscript -e 'remotes::install_github("REditorSupport/vscode-R/sess")'
Provided that you have also installed jgd (above), everything should work
out of the gate. However, one thing to double check is that you don't have
any (conflicting) legacy R plot setting configurations. Open your VS Code
settings (Cmd + , / Ctrl + ,) and check the following entries:
r.plot.backend: "auto"
r.plot.useHttpgd: "false"
Test it out by executing some R plotting commands like the example script below.
2) Browser (Deno server)
If you're not using VS Code, our standalone Deno server provides a browser-based renderer. First install Deno, then run directly (dependencies are fetched automatically):
# macOS / Linux
deno run --allow-net --allow-read --allow-write --allow-env https://raw.githubusercontent.com/grantmcdermott/jgd/refs/heads/main/server/main.ts
# Windows
deno run -A https://raw.githubusercontent.com/grantmcdermott/jgd/refs/heads/main/server/main.ts
Or, clone the repo and run locally:
# git clone https://github.com/grantmcdermott/jgd.git ## clone first
cd server && deno task start && cd ..
Note
On Windows, R connections default to a named pipe, and Deno has no
permission combination narrower than -A (--allow-all) that can satisfy
binding one — that's why the Windows command above needs it (deno task start above already grants -A for the same reason).
To keep permissions scoped on Windows instead, add --tcp <port> (e.g.
--tcp 8888) to connect over localhost TCP rather than a named pipe:
deno run --allow-net --allow-read --allow-write --allow-env https://raw.githubusercontent.com/grantmcdermott/jgd/refs/heads/main/server/main.ts --tcp 8888
Once the Deno server is running, open http://127.0.0.1:<port>/
in your browser (the URL is printed on startup). Then you start executing
plotting commands from any R session.
Important: Unlike the VS Code extension, which does this automatically behind the scenes, you must activate the jgd device from your R session so that the Deno server can connect to it:
jgd::jgd()
Use
Test your installation by running some R plotting commands, like those provided by the script below.
# jgd::jgd() ## only needed if you are using the Deno server
# Base graphics
plot(cars)
abline(lm(dist ~ speed, data = cars), col = "red", lwd = 2)
hist(rnorm(1000), col = "steelblue")
# tinyplot
# install.packages("tinyplot")
library(tinyplot)
plt(bill_dep ~ bill_len | species, facet = ~island,
data = penguins, theme = "clean")
plt_add(type = "lm")
# ggplot2
# install.packages("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, and press ✕ to remove the current plot. Resizing the pane automatically
causes the plots to be resized too. Use the Export dropdown to save plots as
PNG or SVG at custom dimensions (inches × DPI).

Extension API (experimental)
jgd also includes an experimental API for styling effects beyond R's standard graphics parameters. These are passed as JSON strings and forwarded to the renderer, which can apply them using Canvas2D properties (blend modes, opacity, shadows, CSS filters, etc.). Three levels of extension are supported:
- Per-operation (
jgd_ext()/with_jgd_ext()): Styling applied to every drawing operation's graphics context. - Per-group (
jgd_begin_group()/with_jgd_group()): Bracket a set of drawing operations so the renderer can apply effects to the group as a whole. - Per-frame (
jgd_frame_ext()/with_jgd_frame_ext()): Properties attached to the entire frame, useful for post-processing effects.
Here's a simple example where we add shadows to the point elements of a plot.
plot(1:10, type = "n", main = "Shadowed points")
# Add shadow to points (group scoping)
with_jgd_group(
'{"shadow":{"blur":15,"color":"rgba(0,0,0,0.5)","offsetX":5,"offsetY":5}}',
points(1:10, pch = 19, cex = 3, col = "steelblue")
)

See ?jgd_ext for the full list of supported fields and guidance on
building higher-level wrapper packages. The protocol details are documented
in ?jgd_spec.
Motivation
The original motivation for jgd was enabling a better R graphics experience
in VS Code. Historically, the VS Code R
extension
provided fairly crude native graphics support, since plots were displayed as
PNGs. As a result, users had for some time relied on the
httpgd package as a better alternative.
httpgd has numerous cool features, but it has also suffered from reliability
challenges. This is because the package embeds a full C++ SVG rendering stack
and HTTP server inside the R process, which is powerful but fragile. Both
httpgd and its core unigd dependency have
been removed from CRAN multiple times over the years due to various C++
toolchain issues (non-API entry points, compiler compatibility failures, etc.).
While both packages are currently available again, we were motivated to try a
different approach that is conceptually simpler and requires a much lighter
dependency stack.
The key idea behind jgd is that it doesn't render anything itself. Rather, it just records the underlying plotting operations and translates them into JSON, which it then passes on to a client that does the actual rendering (e.g., a VS Code webview, a browser tab, or any future frontend). While this might sound like extra work, it turns out that it is both highly efficient and generalizable (since JSON is so widely supported). Base R already provides most of the necessary scaffolding for an efficient translation layer, and we plug directly into that. The core of the jgd 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.
As a result, jgd supports many of the advanced features of a dedicated
rendering framework like httpgd. But it enjoys a much lighter footprint, which
reduces the maintenance burden and should facilitate long-run stability. Moreover,
we would emphasize the generalizability of the jgd approach. While VS Code
was our initial focus, in principle any JSON-conversant client can be supported.
We already offer a browser-based client (via a Deno server), but the same idea
could be extended to any modern IDE and even terminal clients. Please let us
know if you build out a jgd extension/plugin for your own preferred R
interface.
What about Positron?
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.
Architecture
┌─────────────────────────────────────────────────┐
│ R Process │
│ │
│ jgd R package (pure C) │
│ ┌───────────────────────────────────────────┐ │
│ │ DevDesc callbacks → JSON serializer │ │
│ │ → socket client │──┼──┐
│ └───────────────────────────────────────────┘ │ │
└─────────────────────────────────────────────────┘ │
Unix domain sockets (macOS/Linux), │
named pipes (Windows), or TCP — JSONL │
┌─────────────────────────────────────────────────┐ │
│ 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.
Design principles
- 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.
Protocol specification
The jgd protocol is a simple JSONL (JSON Lines) wire format designed so that
anyone can build a compatible renderer — you don't need to use our VS Code
extension or Deno server. The full specification is documented in the R package
at help("jgd_spec") (or equivalently ?jgd_spec). Here's a quick overview:
| Aspect | Summary |
|---|---|
| Transport | Unix domain sockets (macOS/Linux), named pipes (Windows), or TCP |
| Wire format | Newline-delimited JSON (JSONL), UTF-8 |
| Coordinate system | Device pixels, top-left origin |
| Handshake | R sends {"type":"ping"}, server replies with {"type":"server_info",...} |
| Drawing | R streams frame messages containing an array of drawing ops (line, rect, circle, text, polygon, polyline, path, raster, clip, beginGroup, endGroup) |
| Resize | Server sends {"type":"resize",...} to R; R replays the plot at new dimensions |
| Font metrics | R requests string/glyph measurements; server responds with widths and ascent/descent |
| Discovery | Optional discovery.json file for auto-connection (platform-specific cache dir) |
| Extensions | Free-form ext fields at frame, graphics-context, and group levels |
The protocol is versioned ("protocolVersion": 1) and forward-compatible:
receivers should ignore unknown fields and message types.
For the complete specification — including message schemas, the resize protocol,
multi-session routing, and implementation guidance — see ?jgd_spec in R or
browse the source at
r-pkg/R/spec.R.
What's supported
- Base graphics:
plot(),hist(),lines(),points(),text(),abline(),polygon(),polyline(),rect(),image(),path(), and packages built on base graphics (e.g., tinyplot) - Grid graphics: Full support for grid-based packages including ggplot2 and lattice, via no-op stubs for R 4.1+ pattern/mask/group callbacks
- Plot history: Back/forward navigation and removal 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 ordiscovery.jsonfile for automatic connection (see?jgd_specfor details) - 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
- Extended graphics context (experimental): Blend modes, opacity, shadows,
and CSS filters via
jgd_ext() - Drawing groups (experimental): Group drawing operations and apply
per-group effects via
jgd_begin_group()/jgd_end_group() - Frame-level extensions (experimental): Frame-wide properties such as
post-processing effects via
jgd_frame_ext()
Limitations
- 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).
Project structure
| Directory | Description |
|---|---|
r-pkg/ | R package (pure C, zero dependencies) |
server/ | Deno reference server (HTTP/WebSocket renderer) |
tests/ | End-to-end tests |
Acknowledgements
While we adopt a different implementation approach, jgd was inspired by Florian Rupprecht's httpgd and unigd projects, which demonstrate the value of an external web-based graphics device for R. We are also grateful to R Core for designing and maintaining R's flexible graphics engine, whose clean C callback interface made this project feasible.
This project has made heavy use of AI-assisted pair programming (both Claude and Copilot). It is highly doubtful that we would have been able to put this together without AI help.
Roadmap
- Windows support: Named pipes (default) and TCP transport
- Browser frontend: Deno reference server with HTTP/WebSocket renderer
- Protocol stabilization: Stabilize and document the JSON protocol
(see
?jgd_spec) - 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).
License
MIT