jgd

July 1, 2026 · View on GitHub

CRAN version R-universe version Dependencies R CMD check License

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).

Screenshot of jgd running in VS Code

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")
)

Example using jgd extension API for shadowed plots

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 INSTALL on 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:

AspectSummary
TransportUnix domain sockets (macOS/Linux), named pipes (Windows), or TCP
Wire formatNewline-delimited JSON (JSONL), UTF-8
Coordinate systemDevice pixels, top-left origin
HandshakeR sends {"type":"ping"}, server replies with {"type":"server_info",...}
DrawingR streams frame messages containing an array of drawing ops (line, rect, circle, text, polygon, polyline, path, raster, clip, beginGroup, endGroup)
ResizeServer sends {"type":"resize",...} to R; R replays the plot at new dimensions
Font metricsR requests string/glyph measurements; server responds with widths and ascent/descent
DiscoveryOptional discovery.json file for auto-connection (platform-specific cache dir)
ExtensionsFree-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_SOCKET environment variable or discovery.json file for automatic connection (see ?jgd_spec for 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

DirectoryDescription
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