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)
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.
TRUE
open in a new window, otherwise replace the
current window (if one exists).
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.
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.
GtkActionEntry
s 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
stock_id
unlist(gtkStockListIds())
or
http://library.gnome.org/devel/gtk/unstable/gtk-Stock-Items.html
for a list.
label
accelerator
gtkAcceleratorParse
. See
gdkKeySyms.
tooltip
callback
is_active
update.action
, init.action
update.actions
and init.actions
lists.
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
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
.
playState
object.
These can then be accessed by tools. The default tools
will recognise the following extra arguments:
click.mode
"Zoom"
,
"Identify"
, "Brush"
, "Annotation"
, or
"Arrow"
.
time.mode
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
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
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
label.offset
arrow
panel.arrows
,
specifying the type of arrows to draw.
e.g. list(ends="both", type="closed")
.
show.tooltips
show.toolbars
, show.statusbar
,
page.annotation
, clip.annotations
,
keep
, stay.on.top
playwith.options
.
Cairo
device.
TRUE
, FALSE
, NA
(don't eval global vars)
or a regular expression matching symbols to evaluate.
Or a list. See below.
playState
object
will passed to the function. If the function returns TRUE
,
the window will not be closed.
TRUE
,
the session will freeze until the window is closed.
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
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.
call
object), if given
this is used instead of expr
.
xlim
or
ylab
. This will only be needed in unusual cases when the
default guess fails.
playwith
invisibly returns the playState
object representing
the plot, window and device. The result of the plot call is available
as component $result
.
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:
trellis
. This is the best-supported case.
ggplot
.
This case is rather poorly supported.
viewport
argument to enable interaction.
par()
.
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.
playwith.options
,
autoplay
,
playwith.API
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.
}
Run the code above in your browser using DataLab