playwith: An interactive plot GUI

Description Usage Arguments Details Value Author(s) See Also Examples

View source: R/playwith.R

Description

A GTK+ graphical user interface for exploring and editing R plots.

Usage

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
playwith(expr,
         new = playwith.getOption("new"),
         title = NULL,
         labels = NULL,
         data.points = NULL,
         viewport = NULL,
         parameters = list(),
         tools = list(),
         init.actions = list(),
         preplot.actions = list(),
         update.actions = list(),
         ...,
         width = playwith.getOption("width"),
         height = playwith.getOption("height"),
         pointsize = playwith.getOption("pointsize"),
         eval.args = playwith.getOption("eval.args"),
         on.close = playwith.getOption("on.close"),
         modal = FALSE,
         link.to = NULL,
         playState = if (!new) playDevCur(),
         plot.call,
         main.function)

Arguments

expr

an expression to create a plot, like plot(mydata). Note, arguments and nested calls are allowed, just like a normal plot call (see examples). Could also be a chunk of code in {braces}. For quoted calls, use the plot.call argument.

new

if TRUE open in a new window, otherwise replace the current window (if one exists).

title

optional window title; otherwise derived from the plot call.

labels

a character vector of labels for data points. If missing, it will be guessed from the plot call arguments if possible.

data.points

a data frame (or other suitable plotting structure: see xy.coords) giving locations of data points, in case these can not be guessed from the plot call arguments. If a data frame, extra variables may be included; these can be used to label or locate points in the GUI. Note, if a suitable data argument is found in the plot call, that plays the same role.

viewport

name or vpPath of the viewport representing the data space. This allows interaction with grid graphics plots (but ignore this for Lattice plots). Experimental: can also be a named list.

parameters

defines simple tools for controlling values of any parameters appearing in the plot call. This must be a named list, where the value given for each name defines the possible or initial values of that parameter. The supported values are:

  • integer or AsIs (I()): creates a numeric spinbutton.

  • numeric scalar: creates a text entry box for numeric values.

  • numeric vector: creates a slider with given range.

  • character: creates a text entry box.

  • character vector: creates a combo box (including text entry).

  • logical: creates a checkbox.

  • function: creates a button, which calls the given function with a single argument playState.

These can also be lists, where the first item is the value as above. In this case an item named label can specify a label for the widget, and an item named handler can specify a function to run when the widget is changed. This function should be a function(playState, value); the parameter values are then accessed from playState$env. If the function returns FALSE the plot is not redrawn.

tools

a list of tool specifications. These are technically GtkActionEntrys but should be specified as lists with the following structure. Elements can be specified in this order, or named (as with a function call).

name

The name of the action (used internally to control the action state, or in a custom UI XML file). This item is required and must be the first element. All other elements are optional.

stock_id

The stock icon ID, or the name of an icon from the icon theme. See unlist(gtkStockListIds()) or http://library.gnome.org/devel/gtk/unstable/gtk-Stock-Items.html for a list.

label

The label for the action. If label is NULL, the default label for the given stock.id is used.

accelerator

The accelerator for the action, in the format understood by gtkAcceleratorParse. See gdkKeySyms.

tooltip

The tooltip for the action.

callback

The function to call when the action is activated.

is_active

Only for toggle actions: sets the initial state (TRUE / FALSE).

update.action, init.action

If present these items must be named. Their values are included in the update.actions and init.actions lists.

preplot.actions, update.actions

a list of actions to be run, respectively, before and after the plot is drawn (and each time it is redrawn). Note that preplot.actions can not assume that playState$is.lattice (or other state values) are set. They can, however, modify the plot call or its data before the plot is drawn.

These may be functions, or names of functions, or expressions. Functions are passed one argument, which is the playState. Note, these are in addition to any given in playwith.options("update.actions").

init.actions

init.actions are run whenever the plot type changes or its data changes. They are not run when only simple arguments to the call change, but they are run whenever the plot call is edited manually. Same format as update.actions.

...

extra arguments are stored in the playState object. These can then be accessed by tools. The default tools will recognise the following extra arguments:

click.mode

sets the initial action when clicking and dragging on the plot: one of "Zoom", "Identify", "Brush", "Annotation", or "Arrow".

time.mode

whether the plot is to start in "time mode", with navigation along the x-axis. If NA, it will guess whether to start in time.mode based on whether the current plot looks like a time series plot (but this can chew some extra memory). The default is taken from playwith.options("time.mode").

time.vector

a vector defining discrete times, as numeric, Date or POSIXt. It must be sorted, increasing. If given, then the "time mode" is used to navigate along these discrete times, rather than along the continuous x-axis. Special objects cur.index and cur.time will be provided in the plot environment, so the plot call can refer to these. cur.index is the current time step, between 1 and length(time.vector), and cur.time is time.vector[cur.index]. In this case time.mode will be on by default.

cur.index, cur.time, time.mode.page.incr

If time.vector is given, either of cur.index or cur.time will set the initial time step. time.mode.page.incr sets the number of steps to jump if the user clicks on the scroll bar.

page

In multi-page Lattice plots, this will set the initial page to display.

label.offset

the distance from a data point to its identifying label. Numeric, in units of character widths.

arrow

a list with arguments to panel.arrows, specifying the type of arrows to draw. e.g. list(ends="both", type="closed").

show.tooltips

show tooltips for toolbar items. This uses the GTK event loop internally, which might, occasionally, cause the R terminal to freeze.

show.toolbars, show.statusbar, page.annotation, clip.annotations, keep, stay.on.top

set the corresponding window options. All are logical. Defaults are taken from playwith.options.

width, height

initial size of the plot device in inches.

pointsize

default point size for text in the Cairo device.

eval.args

whether to evaluate the plot call arguments: can be TRUE, FALSE, NA (don't eval global vars) or a regular expression matching symbols to evaluate. Or a list. See below.

on.close

a function to be called when the user closes the plot window. The playState object will passed to the function. If the function returns TRUE, the window will not be closed.

modal

whether the window is modal: if TRUE, the session will freeze until the window is closed.

link.to

an existing playState (i.e. playwith plot) to link to. The set of brushed data points will then be synchronised between them. It is assumed that the data subscripts of the two plots correspond directly. Links can be broken with playUnlink.

playState

the playState object for an existing plot window. If given, the new plot will appear in that window, replacing the old plot. This over-rides the new argument.

plot.call

a plot call (call object), if given this is used instead of expr.

main.function

the function (or its name) appearing in the call which accepts typical plot arguments like xlim or ylab. This will only be needed in unusual cases when the default guess fails.

Details

This function opens a GTK+ window containing a plot device (from the cairoDevice package), a menubar and toolbars. There is a call toolbar (similar to the "address bar" of a web browser) at the top, showing the current plot call, which can be edited in-place. Then there are up to four toolbars, one on each side of the plot. The user interface is customisable: see playwith.options.

With the autoplay facility, playwith can function like a default graphics device (although it is not technically a graphics device itself, it is a wrapper around one).

See playwith.API for help on controlling the plot once open, as well as defining new tools. For the special case of tools to control parameter values, it is possible to create the tools automatically using the parameters argument.

Four types of plots are handled somewhat differently:

Some forms of interaction are based on evaluating and changing arguments to the plot call. This is designed to work in common cases, but could never work for all types of plots. To enable zooming, ensure that the main call accepts xlim and ylim arguments. Furthermore, you may need to specify main.function if the relevant high-level call is nested in a complex block of expressions.

To enable identification of data points, the locations of data points are required, along with appropriate labels. By default, these locations and labels will be guessed from the plot call, but this may fail. You can pass the correct values in as data.points and/or labels. Please also contact the maintainer to help improve the guesses. If identification of data points is not required, passing data.points = NA, labels = NA may speed things up.

Some lattice functions need to be called with subscripts = TRUE in order to correctly identify points in a multiple-panel layout. Otherwise the subscripts used will then refer to the data in each panel separately, rather than the original dataset. In this case a warning dialog box will be shown.

In order to interact with a plot, its supporting data needs to be stored: i.e. all variables appearing in the plot call must remain accessible. By default (eval.args = NA), objects that are not globally accessible will be copied into an attached environment and stored with the plot window. I.e. objects are stored unless they exist in the global environment (user workspace) or in an attached namespace. This method should work in most cases. However, it may end up copying more data than is really necessary, potentially using up memory. Note that if e.g. foo$bar appears in the call, the whole of foo will be copied.

If eval.args = TRUE then variables appearing in the plot call will be evaluated and stored even if they are defined in the global environment. Use this if the global variables might change (or be removed) before the plot is destroyed.

If eval.args = FALSE then the plot call will be left alone and no objects will be copied. This is OK if all the data are globally accessible, and will speed things up.

If a regular expression is given for eval.args then only variables whose names match it will be evaluated, and this includes global variables, as with eval.args=TRUE. In this case you can set invert.match=TRUE to store variables that are not matched. For example eval.args="^tmp" will store variables whose names begin with "tmp"; eval.args=list("^foo$", invert.match=TRUE) will store everything except foo.

Note: function calls appearing in the plot call will be evaluated each time the plot is updated – so random data as in plot(rnorm(100)) will keep changing, with confusing consequences! You should therefore generate random data prior to the plot call. Changes to variables in the workspace (if they are not stored locally) may also cause inconsistencies in previously generated plots.

Warning: the playwith device will tend to make itself the active device any time it is clicked on, so be careful if any other devices are left open.

Value

playwith invisibly returns the playState object representing the plot, window and device. The result of the plot call is available as component $result.

Author(s)

Felix Andrews [email protected]

See Also

playwith.options, autoplay, playwith.API

Examples

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
if (interactive()) {
options(device.ask.default = FALSE)

## Scatterplot (Lattice graphics).
## Labels are taken from rownames of data.
## Right-click on the plot to identify points.
playwith(xyplot(Income ~ log(Population / Area),
   data = data.frame(state.x77), groups = state.region,
   type = c("p", "smooth"), span = 1, auto.key = TRUE,
   xlab = "Population density, 1974 (log scale)",
   ylab = "Income per capita, 1974"))

## Scatterplot (base graphics); similar.
## Note that label style can be set from a menu item.
urbAss <- USArrests[,c("UrbanPop", "Assault")]
playwith(plot(urbAss, panel.first = lines(lowess(urbAss)),
   col = "blue", main = "Assault vs urbanisation",
   xlab = "Percent urban population, 1973",
   ylab = "Assault arrests per 100k, 1973"))

## Time series plot (Lattice).
## Date-time range can be entered directly in "time mode"
## (supports numeric, Date, POSIXct, yearmon and yearqtr).
## Click and drag to zoom in, holding Shift to constrain;
## or use the scrollbar to move along the x-axis.
library(zoo)
playwith(xyplot(sunspots ~ yearmon(time(sunspots)),
                xlim = c(1900, 1930), type = "l"),
         time.mode = TRUE)

## Time series plot (base graphics); similar.
## Custom labels are passed directly to playwith.
tt <- time(treering)
treeyears <- paste(abs(tt) + (tt <= 0),
                  ifelse(tt > 0, "CE", "BCE"))
playwith(plot(treering, xlim = c(1000, 1300)),
   labels = treeyears, time.mode = TRUE)

## Multi-panel Lattice plot.
## Need subscripts = TRUE to correctly identify points.
## Scales are "same" so zooming applies to all panels.
## Use the 'Panel' tool to expand a single panel, then use
## the vertical scrollbar to change pages.
Depth <- equal.count(quakes$depth, number = 3, overlap = 0.1)
playwith(xyplot(lat ~ long | Depth, data = quakes,
      subscripts = TRUE, aspect = "iso", pch = ".", cex = 2),
   labels = paste("mag", quakes$mag))

## Spin and brush for a 3D Lattice plot.
## Drag on the plot to rotate in 3D (can be confusing).
## Brushing is linked to the previous xyplot (if still open).
## Note, brushing 'cloud' requires a recent version of Lattice.
playwith(cloud(-depth ~ long * lat, quakes, zlab = "altitude"),
   new = TRUE, link.to = playDevCur(), click.mode = "Brush")

## Set brushed points according to a logical condition.
playSetIDs(value = which(quakes$mag >= 6))

## Interactive control of a parameter with a slider.
xx <- rnorm(50)
playwith(plot(density(xx, bw = bandwidth), panel.last = rug(xx)),
	parameters = list(bandwidth = seq(0.05, 1, by = 0.01)))

## The same with a spinbutton (use I() to force spinbutton).
## Initial value is set as the first in the vector of values.
## This also shows a combobox for selecting text options.
xx <- rnorm(50)
kernels <- c("gaussian", "epanechnikov", "rectangular",
   "triangular", "biweight", "cosine", "optcosine")
playwith(plot(density(xx, bw = bandwidth, kern = kernel), lty = lty),
	parameters = list(bandwidth = I(c(0.1, 1:50/50)),
            kernel = kernels, lty = 1:6))

## More parameters (logical, numeric, text).
playwith(stripplot(yield ~ site, data = barley,
    jitter = TRUE, type = c("p", "a"),
    aspect = aspect, groups = barley[[groups]],
    scales = list(abbreviate = abbrev),
    par.settings = list(plot.line = list(col = linecol))),
  parameters = list(abbrev = FALSE, aspect = 0.5,
                    groups = c("none", "year", "variety"),
                    linecol = "red"))

## Looking through 100 time series and comparing to a reference;
## Use buttons to save the current series number or its mean value.
dat <- ts(matrix(cumsum(rnorm(100*100)), ncol = 100), start = 1900)
colnames(dat) <- paste("Series", 1:100)
ref <- (dat[,3] + dat[,4]) / 2
playwith(xyplot(cbind(dat[,i], ref = ref)),
  parameters = list(i = 1:100,
     print_i = function(playState) print(playState$env$i),
     print_mean = function(p) print(mean(dat[,p$env$i])),
     save_to_ii = function(playState)
       .GlobalEnv$ii <- playState$env$i,
     append_to_ii = function(playState) {
       if (!exists("ii")) ii <- c()
       .GlobalEnv$ii <- c(ii, playState$env$i)
     })
)

## Composite plot (base graphics).
## Adapted from an example in help("legend").
## In this case, the initial plot() call is detected correctly;
## in more complex cases may need e.g. main.function="plot".
## Here we also construct data points and labels manually.
x <- seq(-4*pi, 4*pi, by = pi/24)
pts <- data.frame(x = x, y = c(sin(x), cos(x), tan(x)))
labs <- rep(c("sin", "cos", "tan"), each = length(x))
labs <- paste(labs, round(180 * x / pi) %% 360)
playwith( {
   plot(x, sin(x), type = "l", xlim = c(-pi, pi),
       ylim = c(-1.2, 1.8), col = 3, lty = 2)
   points(x, cos(x), pch = 3, col = 4)
   lines(x, tan(x), type = "b", lty = 1, pch = 4, col = 6)
   legend("topright", c("sin", "cos", "tan"), col = c(3,4,6),
       lty = c(2, -1, 1), pch = c(-1, 3, 4),
       merge = TRUE, bg = 'gray90')
}, data.points = pts, labels = labs)

## A ggplot example.
## NOTE: only qplot()-based calls will work.
## Labels are taken from rownames of the data.
if (require(ggplot2)) {
    playwith(qplot(qsec, wt, data = mtcars) + stat_smooth())
}

## A minimalist grid plot.
## This shows how to get playwith to work with custom plots:
## accept xlim/ylim and pass "viewport" to enable zooming.
myGridPlot <- function(x, y, xlim = NULL, ylim = NULL, ...)
{
   if (is.null(xlim)) xlim <- extendrange(x)
   if (is.null(ylim)) ylim <- extendrange(y)
   grid.newpage()
   pushViewport(plotViewport())
   grid.rect()
   pushViewport(viewport(xscale = xlim, yscale = ylim,
      name = "theData"))
   grid.points(x, y, ...)
   grid.xaxis()
   grid.yaxis()
   upViewport(0)
}
playwith(myGridPlot(1:10, 11:20, pch = 17), viewport = "theData")

## Presenting the window as a modal dialog box.
## When the window is closed, ask user to confirm.
confirmClose <- function(playState) {
	if (gconfirm("Close window and report IDs?",
                     parent = playState$win)) {
		cat("Indices of identified data points:\n")
		print(playGetIDs(playState))
		return(FALSE) ## close
	} else TRUE ## don't close
}
xy <- data.frame(x = 1:20, y = rnorm(20),
                 row.names = letters[1:20])
playwith(xyplot(y ~ x, xy, main = "Select points, then close"),
        width = 4, height = 3.5, show.toolbars = FALSE,
        on.close = confirmClose, modal = TRUE,
        click.mode = "Brush")

## Ask user to save plot to PNG when window is closed:
saveOnClose <- function(playState) {
    playDevSet(playState)
    if (!gconfirm("Save plot to PNG file? (Cancel = no)")) return(FALSE)
    fname <- gfile("Save PNG file as:", type = "save")
    if (is.na(fname)) return(TRUE) ## cancel
    dev.off(dev.copy(Cairo_png, file = fname,
        width = dev.size()[1], height = dev.size()[2]))
    FALSE 
}
#playwith.options(on.close = saveOnClose)


## Demonstrate cacheing of objects in local environment.
## By default, only local variables in the plot call are stored.
x_global <- rnorm(100)
doLocalStuff <- function(...) {
   y_local <- rnorm(100)
   angle <- (atan2(y_local, x_global) / (2*pi)) + 0.5
   color <- hsv(h = angle, v = 0.75)
   doRays <- function(x, y, col) {
      segments(0, 0, x, y, col = col)
   }
   playwith(plot(x_global, y_local, pch = 8, col = color,
      panel.first = doRays(x_global, y_local, color)),
   ...)
}
doLocalStuff(title = "locals only") ## eval.args = NA is default
## List objects that have been copied and stored:
## Note: if you rm(x_global) now, redraws will fail.
ls(playDevCur()$env)
## Next: store all data objects (in a new window):
doLocalStuff(title = "all stored", eval.args = TRUE, new = TRUE)
ls(playDevCur()$env)
## Now there are two devices open:
str(playDevList())
playDevCur()
playDevOff()
playDevCur()

## Not run: 
## Big data example, do not try to guess labels or time.mode.
gc()
bigobj <- rpois(5000000, 1)
print(object.size(bigobj), units = "Mb")
gc()
playwith(qqmath(~ bigobj, f.value = ppoints(500)),
   data.points = NA, labels = NA, time.mode = FALSE)
playDevOff()
gc()
## or generate the trellis object first:
trel <- qqmath(~ bigobj, f.value = ppoints(500))
playwith(trel)
rm(trel)
## in this case, it is much better to compute the sample first:
subobj <- quantile(bigobj, ppoints(500), na.rm = TRUE)
playwith(qqmath(~ subobj))
rm(subobj)
rm(bigobj)

## End(Not run)

## See demo(package = "playwith") for examples of new tools.
}

playwith documentation built on May 29, 2017, 12:28 p.m.

Related to playwith in playwith...