Skip to contents

unigd exposes a C API that allows other R packages to interact with the graphics device from compiled code. This enables building applications like plot viewers, web servers, or IDE integrations on top of unigd.

The API has extern "C" linkage, so it can be used from both C and C++ client code. It is a versioned vtable (struct of function pointers) obtained at runtime through R’s R_GetCCallable mechanism. All functions are thread-safe, so clients can call into unigd from background threads.

The examples in this document use C++ for brevity, but the API itself is plain C.

Architecture overview

R session
 |
 |  ugd()           opens a unigd graphics device
 v
unigd device        stores plot history, manages renderers
 |
 |  C API           function pointer table (unigd_api_v1)
 v
client package      attaches via device_attach(), receives callbacks,
                    renders plots on demand

The API follows a client model: a client package registers itself with an open unigd device and receives lifecycle callbacks. It can then query device state, browse plot history, and render plots to any supported format.

Package setup

DESCRIPTION

Your package needs to link against unigd:

Imports: unigd
LinkingTo: unigd

Imports ensures unigd is loaded (and its shared library available) before your package. LinkingTo gives your compiler access to the headers in unigd/inst/include/.

Include the API header

In your C/C++ source files:

#include <unigd_api.h>

This single header provides unigd_api_v1_create() and unigd_api_v1_destroy() (factory functions that obtain the API vtable via R_GetCCallable), as well as all type definitions from <unigd_api_v1.h> (structs, handle types, and typedefs).

Initialization

Obtain the API vtable once when your package is loaded. The recommended pattern uses a RAII guard and the [[cpp11::init]] attribute (which runs at R_init_<pkg> time):

#include <cpp11/R.hpp>
#include <unigd_api.h>

namespace mypackage {

// Global API pointer and client ID
unigd_api_v1 *api = nullptr;
UNIGD_CLIENT_ID client_id = 0;

namespace {
class api_guard {
public:
  ~api_guard() { destroy(); }

  void create() {
    if (api == nullptr) unigd_api_v1_create(&api);
  }
  void destroy() {
    if (api != nullptr) {
      unigd_api_v1_destroy(api);
      api = nullptr;
    }
  }
};

api_guard guard;
}  // namespace
}  // namespace mypackage

[[cpp11::init]] void import_unigd_api(DllInfo *dll) {
  mypackage::guard.create();
  mypackage::client_id = mypackage::api->register_client_id();
}

After this runs, mypackage::api points to the vtable and mypackage::client_id holds a unique identifier for your package. The guard ensures unigd_api_v1_destroy is called when the shared library unloads.

Types

Handle types

The API uses opaque void * handles for resource management. Each *_create / *_find function returns a handle that must be freed with the corresponding *_destroy function.

Type Returned by Freed by
UNIGD_HANDLE device_attach device_destroy
UNIGD_RENDER_HANDLE device_render_create device_render_destroy
UNIGD_FIND_HANDLE device_plots_find device_plots_find_destroy
UNIGD_RENDERERS_HANDLE renderers renderers_destroy
UNIGD_RENDERERS_ENTRY_HANDLE renderers_find renderers_find_destroy

ID types

Type Underlying Description
UNIGD_PLOT_ID uint32_t Stable identifier for a plot
UNIGD_PLOT_INDEX uint32_t Positional index (oldest = 1)
UNIGD_PLOT_RELATIVE int32_t Signed offset (0 = latest, -1 = previous)
UNIGD_CLIENT_ID uint32_t Unique client identifier
UNIGD_RENDERER_ID const char * Renderer string ID (e.g. "svg", "png")

Structs

unigd_graphics_client

Callback table provided by the client to device_attach. All callbacks receive the client_data pointer that was passed to device_attach.

struct unigd_graphics_client {
    void (*start)(void *client_data);        // Device activated
    void (*close)(void *client_data);        // Device closing
    void (*state_change)(void *client_data); // Plot state changed
    const char *(*info)(void *client_data);  // Return client info string
};
  • start: Called once after device_attach succeeds. Use this to launch background threads or initialize resources.
  • close: Called when the device is closed (e.g. dev.off() in R). Clean up resources and call device_destroy on the handle here. After this returns, the device handle is no longer valid.
  • state_change: Called whenever a plot is created, modified, or removed. Typically used to notify connected clients (e.g. push a WebSocket message).
  • info: Should return a static string identifying the client (e.g. "mypkg 1.0.0").

unigd_device_state

struct unigd_device_state {
    int upid;                // Update ID, increments on every state change
    UNIGD_PLOT_INDEX hsize;  // Number of plots in history
    bool active;             // Whether the device is active
};

The upid field is useful for change detection: if the upid hasn’t changed since the last check, no plots have been added, removed, or redrawn.

unigd_render_args

struct unigd_render_args {
    double width;   // Plot width in inches (or pixels for raster)
    double height;  // Plot height in inches (or pixels for raster)
    double scale;   // Zoom/scale factor (1.0 = 100%)
};

Pass width = -1 and height = -1 to use the device’s current dimensions.

unigd_render_access

struct unigd_render_access {
    const uint8_t *buffer;  // Pointer to rendered data
    uint64_t size;          // Size in bytes
};

The buffer is valid until the corresponding UNIGD_RENDER_HANDLE is destroyed.

unigd_find_results

struct unigd_find_results {
    unigd_device_state state;  // Device state at query time
    UNIGD_PLOT_INDEX size;     // Number of results
    UNIGD_PLOT_ID *ids;        // Array of plot IDs
};

unigd_renderer_info

struct unigd_renderer_info {
    UNIGD_RENDERER_ID id;       // e.g. "svg", "png"
    const char *mime;           // e.g. "image/svg+xml"
    const char *fileext;        // e.g. "svg"
    const char *name;           // Human-readable name
    const char *type;           // Renderer category
    const char *description;    // Short description
    bool text;                  // true if output is text, false if binary
};

Attaching to a device

A client attaches to an open unigd device by providing a callback struct, its client ID, and an arbitrary data pointer:

class MyClient {
  unigd_api_v1 *m_api = nullptr;
  UNIGD_HANDLE m_handle = nullptr;
  unigd_graphics_client m_client;

public:
  MyClient() {
    m_client.start = [](void *d) {
      static_cast<MyClient *>(d)->on_start();
    };
    m_client.close = [](void *d) {
      static_cast<MyClient *>(d)->on_close();
    };
    m_client.state_change = [](void *d) {
      static_cast<MyClient *>(d)->on_state_change();
    };
    m_client.info = [](void *) { return "mypkg 1.0.0"; };
  }

  bool attach(int devnum) {
    m_api = mypackage::api;
    m_handle = m_api->device_attach(
        devnum,              // R device number
        &m_client,           // callback table
        mypackage::client_id, // from register_client_id()
        this                 // client_data passed to callbacks
    );
    return m_handle != nullptr;
  }

  void on_start() {
    // Called after attach succeeds.
    // Launch background work here.
  }

  void on_state_change() {
    auto state = m_api->device_state(m_handle);
    // React to state.upid, state.hsize, etc.
  }

  void on_close() {
    // Device is closing. Clean up.
    if (m_api && m_handle) {
      m_api->device_destroy(m_handle);
    }
    delete this;
  }
};

The devnum parameter is the R graphics device number, typically obtained from dev.cur() after opening a ugd() device.

Querying device state

unigd_device_state state = api->device_state(handle);

// state.upid   incremented on every change
// state.hsize  total number of plots in history
// state.active false after dev.off()

Browsing plot history

Use device_plots_find to query plot IDs with pagination:

unigd_find_results results;
UNIGD_FIND_HANDLE fh = api->device_plots_find(
    handle,
    0,   // offset: 0 = start from latest, negative = relative
    10,  // limit: max number of results (0 = all)
    &results
);

for (UNIGD_PLOT_INDEX i = 0; i < results.size; ++i) {
  UNIGD_PLOT_ID id = results.ids[i];
  // ... use the plot ID ...
}

// Always free the results
api->device_plots_find_destroy(fh);

To remove a single plot or clear all history:

api->device_plots_remove(handle, plot_id);  // returns true on success
api->device_plots_clear(handle);            // returns true on success

Rendering plots

Rendering is a two-step process: create the render (which may trigger a redraw), then access the buffer.

// 1. Look up the renderer (optional, useful to get MIME type)
unigd_renderer_info rinfo;
auto rinfo_h = api->renderers_find("svg", &rinfo);
// rinfo.mime == "image/svg+xml"
// rinfo.text == true

// 2. Render a specific plot
unigd_render_access render;
auto render_h = api->device_render_create(
    handle,
    "svg",                  // renderer ID
    plot_id,                // from device_plots_find
    {720.0, 576.0, 1.0},   // {width, height, scale}
    &render
);

if (render_h) {
  // render.buffer contains the SVG data
  // render.size is the byte length
  std::string svg(render.buffer, render.buffer + render.size);
}

// 3. Free resources
api->device_render_destroy(render_h);
api->renderers_find_destroy(rinfo_h);

To enumerate all available renderers:

unigd_renderers_list list;
auto rh = api->renderers(&list);

for (uint64_t i = 0; i < list.size; ++i) {
  const auto &r = list.entries[i];
  // r.id, r.mime, r.fileext, r.name, r.text, ...
}

api->renderers_destroy(rh);

Retrieving a client from a device

If your R code needs to look up a previously attached client (e.g. to expose its state to R), use device_get:

void *data = api->device_get(devnum, mypackage::client_id);
if (data) {
  auto *client = static_cast<MyClient *>(data);
  // ... use client ...
}

This returns the client_data pointer that was passed to device_attach.

Logging

The API provides a thread-safe logging function that prints to the R console:

api->log("Something happened");
// Output: "unigd client: Something happened"

Safe to call from any thread.

Memory management

Every function that returns a handle allocates memory that must be freed by the corresponding destroy function. Failing to do so will leak memory.

Allocator Deallocator
device_attach device_destroy
device_render_create device_render_destroy
device_plots_find device_plots_find_destroy
renderers renderers_destroy
renderers_find renderers_find_destroy

The unigd_render_access buffer is owned by its UNIGD_RENDER_HANDLE. Copy the data out if you need it after calling device_render_destroy.

Similarly, the unigd_find_results.ids array is owned by its UNIGD_FIND_HANDLE and becomes invalid after device_plots_find_destroy.

Thread safety

All API functions are safe to call from any thread. Internally, unigd uses a shared mutex to synchronize access to the plot store.

The state_change callback is invoked from R’s main thread (during graphics engine operations). If your callback needs to communicate with background threads, use appropriate synchronization.

API reference

General

Function Signature Description
log void (const char *) Print a message to the R console (thread-safe)
info const char *() Returns "unigd <version>"

Client registration

Function Signature Description
register_client_id UNIGD_CLIENT_ID () Obtain a unique client ID

Device operations

Function Signature Description
device_attach UNIGD_HANDLE (int devnum, unigd_graphics_client *, UNIGD_CLIENT_ID, void *) Attach client to device
device_get void *(int devnum, UNIGD_CLIENT_ID) Retrieve client data
device_destroy void (UNIGD_HANDLE) Free device handle
device_state unigd_device_state (UNIGD_HANDLE) Query device state

Plot history

Function Signature Description
device_plots_find UNIGD_FIND_HANDLE (UNIGD_HANDLE, UNIGD_PLOT_RELATIVE, UNIGD_PLOT_INDEX, unigd_find_results *) Query plot IDs
device_plots_find_destroy void (UNIGD_FIND_HANDLE) Free find results
device_plots_remove bool (UNIGD_HANDLE, UNIGD_PLOT_ID) Remove a plot
device_plots_clear bool (UNIGD_HANDLE) Clear all plots

Rendering

Function Signature Description
device_render_create UNIGD_RENDER_HANDLE (UNIGD_HANDLE, UNIGD_RENDERER_ID, UNIGD_PLOT_ID, unigd_render_args, unigd_render_access *) Render a plot
device_render_destroy void (UNIGD_RENDER_HANDLE) Free render data

Renderers

Function Signature Description
renderers UNIGD_RENDERERS_HANDLE (unigd_renderers_list *) List all renderers
renderers_destroy void (UNIGD_RENDERERS_HANDLE) Free renderer list
renderers_find UNIGD_RENDERERS_ENTRY_HANDLE (UNIGD_RENDERER_ID, unigd_renderer_info *) Look up a renderer
renderers_find_destroy void (UNIGD_RENDERERS_ENTRY_HANDLE) Free renderer lookup