Changes to R Base Graphics

Paul Murrell
Department of Statistics
The University of Auckland
paul@stat.auckland.ac.nz


This is a high-level description of the changes made to R base graphics between R version 1.3.1 and R version 1.4.0

The basic idea of these changes is to separate the graphics into three components: graphics devices, graphics engine, and graphics systems. R's main graphics are an example of a graphics system -- I refer to these as "base graphics".

This separation means that R's base graphics do not have any particularly special status (apart from the fact that they get loaded by default). Other graphics systems can be written and can get equal access to R's graphics engine and graphics devices (prior to these changes, other graphics systems had to go via R's base graphics, which was not convenient for some things. For example, controlling the clipping region on devices was very awkward.)

The interface to the graphics device component is given in GraphicsDevice.h. This describes the data and functions that every device must store -- the graphics engine assumes this information about every device. Graphics calls to the device must specify all locations and dimensions in device units.

The interface to the graphics engine component is given in GraphicsEngine.h. This describes the functions that the graphics engine can perform and is used by graphics systems to produce graphical output. Calls to the graphics engine must use device coordinates -- the graphics engine provides some helper functions for converting between device coordinates, normalised device coordinates, and inches.

The interface to R's base graphics is still given by Rgraphics.h until further changes are made (see below).

Both the graphics engine and the graphics devices must contain no information about a particular graphics system. Graphics systems must register with the graphics engine using GEregisterSystem. The system must provide a callback function when it registers -- the graphics engine will use this callback to signal the graphics system when important events occur (e.g., when a new device has been created). GEregisterSystem returns an integer that the graphics system must record -- the graphics engine records system-specific information with every device amd this integer is used within the callback function to get system-specific information back out of a device.

Calls to the graphics engine (and devices) must specify any relevant graphical parameters. For example, a call to GELine must specify a line colour, a line type, and a line width. (It used to be the case that the graphics engine and even graphics devices would look back up to the graphics system to see what the current settings were for graphcal parameters. This reflected the assumption that there would only ever be one graphics system to look back up to.) This reduces the allowable set of graphics parameters to those understood by the graphics engine; these are: col (for lines and borders), fill (for filling rects, polygons, ...), lwd (line width), lty (line type), font (font face), ps (point size of text), cex (character scaling factor). Other par() parameters are restricted to base graphics (as they should be).

The creation, destruction, copying, ... of devices is handled by the graphics engine.

There are three types of information stored about each R device: device-level information (including device-specific information) in the NewDevDesc structure; graphics engine information in the GEDevDesc structure; and graphics system-specific information in the GESystemDesc structure. The graphics engine is the master both in relation to the devices and in relation to graphics systems. The GEDevDesc structure (per device) contains information about the device in a NewDevDesc and information about every registered graphics system (e.g., current graphics system state) in an array of GESystemDesc's.

Registering a new graphics system

Special care needs to be taken in the code for the GE_InitState event. This code can rely on the system knowing its register index, but the dd->gesd[systemRegisterIndex] structure is empty. It is the callback code's responsibility to place a system state object within the dd->gesd[systemRegisterIndex] structure before trying to call code that will access that structure.

Graphics Structures

The R graphics engine is always loaded. (The R base graphics system is currently also always loaded, but this may not be the case forever.) During an R session, graphics devices may be created and deleted, and graphics systems may be loaded and unloaded. This section documents what happens during the creation/deletion of devices and the loading/unloading of systems, paying particular attention to the allocation and freeing of structures that hold the descriptions of the graphics devices and the graphics systems. First of all, a brief description of the structures involved:
  1. For each graphics device, there is a GEDevDesc. (stored in the global R_Devices array). This structure represent the graphics engine's record of the device. There is information about the device itself, in a NewDevDesc structure, and graphics-state information for the device for each registered graphics system in an array of GESystemDesc structures.
        GEDevDesc
            NewDevDesc *dev
    	GESystemDesc gesd[]
    
    1. The NewDevDesc structure contains basic generic information about the device, PLUS device-specific information in a device-specific structure, PLUS pointers to device-specific implementations of the required set of graphical functions.
              NewDevDesc
      	    < generic info >
      	    < graphics function pointers >
      	    void *deviceSpecific
        
    2. The GESystemDesc structure contains system-specific information in a system-specific structure, PLUS a callback function pointer.
              GESystemDesc
      	    void *systemSpecific
      	    GEcallback callback
        
  2. For each graphics system, there is a GESystemDesc structure (stored in the global registeredDevices array). This is just used to retain a record of which systems are registered and the respective callback hooks.
Now lets track the creation and free'ing of these structures, and which of the graphics engine, the graphics device, and the graphics system is responsible for these, for the cases of creating/deleting a device and loading/unloading a graphics system. The text is colour-coded by what sort of structure is being dealt with; there should be corresponding lines in the creation/deletion sections and in the loading/unloading sections (to ensure that all structures that are created are also freed). Also, ideally, the code responsible for the actions (engine, device, or system) should be the same in corresponding lines of the same colour. Finally, there should be a good reason if the order of creation is not reversed when structures are free'ed.

Creating a device

    device creates NewDevDesc
    device sets basic information and graphics function pointers
    device creates deviceSpecific
    device puts deviceSpecific in NewDevDesc
    engine creates GEDevDesc
    engine puts NewDevDesc in GEDevDesc 
    engine creates GESystemDesc for each registered system
    each system creates systemSpecific
    system puts systemSpecific in GESystemDesc
    engine puts GESystemDescs in GEDevDesc

Deleting a device

    device frees deviceSpecific
    each system frees systemSpecific
    engine frees GESystemDesc for each registered system    
    engine frees NewDevDesc
    engine frees GEDevDesc

Loading a graphics system

    engine creates GESystemDesc for each existing device
    system creates systemSpecific for each GESystemDesc
    system puts systemSpecific in GESystemDesc
    engine puts GESystemDescs in GEDevDesc
    engine creates GESystemDesc for global array

Unloading a graphics system

    system frees systemSpecific for each existing device
    engine frees GESystemDesc for each existing device    
    engine frees GESystemDesc in global array    

Writing a New Graphics System: Tricky bits/possible areas for redesign

These are some issues that have arisen in the implementation of grid, that would need to be considered when writing another graphics system.

Things that still need doing

The changes made so far have been "minimalist" in that there has not been a true separation of the base graphics from the graphics engine (because there is not enough time to do this at the moment). Some things that need getting to are: