This document describes changes to the ‘base’ package and the ‘grDevices’ package for accessing and modifying the names and labels of graphics devices:
within ‘base’, the hidden-in-plain-sight internal information about the current graphics device and the list of open graphics devices, which is currently available via the read-writeable variables .Device
and .Devices
, will change to a read-only .Device()
and .Devices()
interface.
within ‘grDevices’, there will be new dev.label()
and dev.labels()
functions to assign labels to graphics devices and to list the current device labels. Furthermore, specifying a graphics device, e.g., in functions like dev.set()
, will be possible by specifying a label in addition to the existing approach of specifying a numeric index.
The variables .Devices
and .Device
in the ‘base’ namespace provide access to the list of open graphics devices and to the name of the current graphics device. For example, the following code opens a PDF graphics device, which means that the current graphics device is a "pdf"
device and the list of graphics devices contains the current "pdf"
device and the null graphics device (a default placeholder).
> pdf()
> .Device
[1] "pdf"
attr(,"filepath")
[1] "Rplots.pdf"
> .Devices
[[1]]
[1] "null device"
[[2]]
[1] "pdf"
attr(,"filepath")
[1] "Rplots.pdf"
Unfortunately, these variables are also writeable. For example, the following code modifies the name of the current device (and loses the "filepath"
attribute and leaves .Device
and .Devices
out of synch).
> .Device <- "mypdf"
> .Device
[1] "mypdf"
> .Devices
[[1]]
[1] "null device"
[[2]]
[1] "pdf"
attr(,"filepath")
[1] "Rplots.pdf"
The historical reason for this unfortunate state of affairs (which predates the introduction of package namespaces) is that the internal C code and R code that creates and maintains these .Device
and .Devices
variables is spread across the core R graphics engine and both the ‘base’ and ‘grDevices’ packages. For example, the variables are made visible from the ‘base’ namespace so that R code in ‘grDevices’ can access them.
The modern consequences of this unfortunate state of affairs include an inability to fully “lock” the ‘base’ namespace (because R itself modifies these variables during an R session) and the existence of add-on packages, like ‘R.devices’ and ‘dipsaus’, that access and modify these variables (because they can).
The first part of the solution is to move the .Device
and .Devices
variables into a (hidden) global environment within the ‘base’ namespace: .DeviceEnv
. This will allow the ‘base’ namespace to be locked (because .Device
and .Devices
can be accessed and modified within .DeviceEnv
without altering the binding of .DeviceEnv
itself).
The internal C code of the R graphics engine can be easily modified to access and modify .Device
and .Devices
within .DeviceEnv
rather than as global variables within the ‘base’ namespace.
The second part of the solution is to replace .Device
and .Devices
with functions .Device()
and .Devices()
that return exactly the same values as the original global variables. This is effectively a read-only version of the original global variables, which makes it easy to modify the R code within the ‘grDevices’ package that accesses .Device
and .Devices
.
The third part of the solution addresses the needs of the add-on packages that currently access and modify .Device
and .Devices
. The main requirement of these packages is the ability to assign a label to a graphics device and then refer to the graphics device by its label, rather than by a numeric index.
In order to satisfy this requirement, there are two new functions: dev.label()
and dev.labels()
. The latter is similar to dev.list()
, but it returns a character vector of labels, with numeric indices as the names
attribute. The former can be used both to query and to set the label for a single device (by default the current device). For example, the following code shows that, with the "pdf"
device open from before and selected as the current device, the current device has no label by default, but we can give it a label using dev.label()
.
> dev.cur()
pdf
2
> dev.label()
2
""
> dev.label("mylabel")
2
"mylabel"
> dev.label()
2
"mylabel"
It is also possible to provide a name for a graphics device when the device is created. For example, the following code opens another "pdf"
device, this time giving it a label immediately; dev.label()
shows the label on the current device and dev.labels()
shows the labels on all open devices.
> pdf(label="myPDFdevice")
> dev.label()
3
"myPDFdevice"
> dev.labels()
2 3
"mylabel" "myPDFdevice"
In addition, all functions that have an argument to specify a particular graphics device, such as dev.set()
, will now accept either a numeric index (as before) or a character label. For example, the current device is device 3, a "pdf"
device, with label "myPDFdevice"
. We can still switch to another device by giving dev.set()
a numeric index, in this case device number 2, but now we can also switch to another device by giving dev.set()
a character label. In this case we switch back to the device labelled "myPDFdevice"
(which is device number 3).
> dev.cur()
pdf
3
> dev.label()
3
"myPDFdevice"
> dev.set(2)
pdf
2
> dev.set("myPDFdevice")
pdf
3
Note that this new labelling facility has been added via the introduction of new functions, dev.label()
and dev.labels()
, because that makes it easy to maintain backward compatibility of existing functions, such as dev.list()
. Those functions still return numeric vectors (with device names as the names
attribute) as they always have. This was demonstrated above in the return value from dev.set()
; the code below repeats the demonstration for dev.cur()
and dev.list()
.
> dev.cur()
pdf
3
> dev.list()
pdf pdf
2 3
The changes are currently available in a fork of the R-SVN CI tool on GitHub. You can download and build that fork, or get a patch from the pull request and attempt to apply that to your local R sources, or try out the build that was generated by the pull request.
Suggested patches are also available for the following add-on packages:
These patches should build the package under R-release, R-devel, and the patched R-devel linked to above.