loon.ggplot
vignettes/grammarOfInteractiveGraphics.Rmd
grammarOfInteractiveGraphics.Rmd
Just as ggplot2
provides a layered implementation of a
grammar of graphics, loon.ggplot
provides
a layered implementation of a grammar of interactive
graphics.
With loon.ggplot
, data analysts can easily switch
between the elegant and beautiful static graphics of
ggplot2
and the powerful direct manipulation interactive
graphics of loon
, using each where it is most
natural.
airquality
To provide a working example, consider the airquality
dataset:
data("airquality")
summary(airquality)
#> Ozone Solar.R Wind Temp
#> Min. : 1.00 Min. : 7.0 Min. : 1.700 Min. :56.00
#> 1st Qu.: 18.00 1st Qu.:115.8 1st Qu.: 7.400 1st Qu.:72.00
#> Median : 31.50 Median :205.0 Median : 9.700 Median :79.00
#> Mean : 42.13 Mean :185.9 Mean : 9.958 Mean :77.88
#> 3rd Qu.: 63.25 3rd Qu.:258.8 3rd Qu.:11.500 3rd Qu.:85.00
#> Max. :168.00 Max. :334.0 Max. :20.700 Max. :97.00
#> NA's :37 NA's :7
#> Month Day
#> Min. :5.000 Min. : 1.0
#> 1st Qu.:6.000 1st Qu.: 8.0
#> Median :7.000 Median :16.0
#> Mean :6.993 Mean :15.8
#> 3rd Qu.:8.000 3rd Qu.:23.0
#> Max. :9.000 Max. :31.0
#>
It has missing data (in Ozone
and Solar.R
)
and the variable Month
appears as numerical. The date
information can easily be made more meaningful:
airquality$Date <- with(airquality,
as.Date(paste("1973", Month, Day, sep = "-")))
# Could also look up the day of the week for each date and add it
airquality$Weekday <- factor(weekdays(airquality$Date),
levels = c("Monday", "Tuesday", "Wednesday",
"Thursday", "Friday",
"Saturday", "Sunday"))
Month
can also be turned it into a factor so that its
levels are three character month abbreviations (arranged to match
calendar order):
airquality$Month <- factor(month.abb[airquality$Month],
levels = month.abb[unique(airquality$Month)])
The data now look like
head(airquality, n = 3)
#> Ozone Solar.R Wind Temp Month Day Date Weekday
#> 1 41 190 7.4 67 May 1 1973-05-01 Tuesday
#> 2 36 118 8.0 72 May 2 1973-05-02 Wednesday
#> 3 12 149 12.6 74 May 3 1973-05-03 Thursday
With its mix of continuous and categorical variables (some with
missing data) this transformed data will be used to illustrate
loon.ggplot
’s grammar of interactive graphics.
ggplot()
becomes
l_ggplot()
The interactive grammar begins by simply replacing
ggplot()
by l_ggplot()
, wherever it appears in
the layered grammar. The same arguments (e.g., data
,
mapping
, etc.) and clauses (e.g. geoms, scales,
coordinates, etc.) are used, but now to create an interactive plot.
For example,
lgp <- l_ggplot(airquality,
mapping = aes(x = Wind, y = Ozone)) +
ggtitle("Air quality in New York (1973)") +
geom_point(size = 3)
Like ggplot()
, l_ggplot()
produces a data
structure containing the information needed to create a plot. No plot is
actually yet displayed; rather lgp
has the
potential to produce a plot on demand.
A look at its class
class(lgp)
#> [1] "l_ggplot" "gg" "ggplot"
suggests that it is both an l_ggplot
and a
ggplot
(and gg
).
In ggplot2
identical methods have been written for
both the plot()
and the
print()
function to show the ggplot
on the
current device.
In loon.ggplot
these functions are different for an
l_ggplot
:
plot(lgp)
displays the lgp
structure as a
static ggplot
on the current deviceprint(lgp)
displays it as an interactive
loon
plot in the R
session.For example, to see the ggplot
:
plot(lgp)
And to see the interactive loon
plot, simply print
it:
lgp # or print(lgp)
which will look something like
(as rendered in grid
graphics).
Each plot (the static ggplot
and the interactive
loon
plot) presents the same information, but in slightly
different form (e.g., different choices on title placement, white space
padding, etc.)
Though the data information is identical in both plots, the
loon
plot appears a little more spartan. This is because an
interactive plot is dynamic and can change in real time by direct
interaction; it is enough that the analyst appreciates the data content
of the plot without too much concern over display details. In contrast,
the ggplot
is more often meant to be shared in print and so
demands more flexibility to lay out its plot elements in an elegant
display.
Note: In ggplot2
, every time a
ggplot
is printed, a new plot is produced on the current
device. Similarly, in loon.ggplot
, every time an
l_ggplot
is printed, a new (interactive)
loon
plot is produced; every time it is plotted, a new
(static) ggplot
is produced.
At times, as when creating this document, it will be handy to have
programmatic access to the loon
plot. This can be done by
assigning the loon
plot to a variable, say lp
,
in any one of several different ways:
When it was first printed it, the interactive plot would have returned a string. For example, it was
#> [1] ".l0.ggplot.plot"
which is of the form ".lXX.ggplot.plot"
where
XX
is a non-negative integer.
This is the tcltk
“path” to the loon
plot
and uniquely identifies the loon
structure. The data
structure is now accessed through l_getFromPath(pathname)
as below.
lp <- l_getFromPath(".lXX.ggplot.plot") # replace XX by whatever number appeared
Of course, this requires you to have noticed and recovered the string
pathname for that plot when it first appeared. Fortunately, that is not
necessary. In the title bar of the window containing the
loon
plot, the string following "path: "
can
also be used. In the present case, this will be of the form
".lXX.ggplot"
(as before but without the additional
".plot"
suffix). The call
lp <- l_getFromPath(".lXX.ggplot") # replace XX by whatever number appeared
will return the interactive plot as before.
Finally, if you have the foresight to know that you would like to
have programmatic access to the interactive plot from the start, you
could assign it to a variable when it was first created.
There are two ways to do this.
One is
lp <- print(lgp)
The other uses a powerful function called loon.ggplot()
(more on this below):
lp <- loon.ggplot(lgp)
Note: that unlike l_getFromPath()
,
either of the above calls will produce a new
interactive plot from lgp
and assign it to
lp
.
Using l_ggplot()
in place of ggplot()
extends the graphics grammar of ggplot2
to produce
interactive loon
plots.
use l_ggplot()
in place of ggplot()
plotting an l_ggplot
produces a static
ggplot
display
printing an l_ggplot
produces an
interactive loon
display.
the l_ggplot
is not the interactive
loon
plot; it is an enhanced ggplot
and can be augmented just as any other
ggplot
.
Changes to the ggplot
have no effect on the
interactive plot
For example,
plot(lgp + geom_smooth())
behaves like any other ggplot
with no effect on the
interactive plot.
Changes to the interactive loon
plot have no
effect on the static l_ggplot
Make whatever changes you like interactively to the loon
plot lp
, or programmatically as below:
# Change glyph aesthetics of ALL points
lp["color"] <- "lightgrey"
lp["glyph"] <- "ctriangle" # closed triangle
lp["size"] <- 10 # proportional to area in loon
# Dynamically change the scaling (magnify or zoom in and out)
for (mag in rep(c(0.8, 1, 1.2), times = 5)){
lp["zoomX"] <- mag
lp["zoomY"] <- mag
Sys.sleep(0.1) # slow down to see effect
}
# Settle on
lp["zoomX"] <- 1.2
lp["zoomY"] <- 1.2
#
# Or, similarly, change the location/origin of the plot
xlocs <- seq(min(lp["x"]),
median(lp["x"]),
length.out = 10)
ylocs <- seq(min(lp["y"]),
median(lp["y"]),
length.out = 10)
# Dynamically change the origin
for (i in 1:length(xlocs)){
lp["panX"] <- xlocs[i]
lp["panY"] <- ylocs[i]
Sys.sleep(0.1) # slow down to see effect
}
# and back
xlocs <- rev(xlocs)
ylocs <- rev(ylocs)
# dynamically
for (i in 1:length(xlocs)){
lp["panX"] <- xlocs[i]
lp["panY"] <- ylocs[i]
Sys.sleep(0.1) # slow down to see effect
}
# Perhaps settle on
lp["panX"] <- 7
lp["panY"] <- 0
And now observe the effect on each of the plots:
# First the l_ggplot
plot(lgp)
No change. It is unaffected by any change to the interactive plot.
# Now the l_plot
plot(lp)
Reflects the changes made on the interactive plot.
loon.ggplot()
Earlier, loon.ggplot()
was recommended as a means to
produce an interactive plot from an l_ggplot
and to assign
it to a variable, as in
lp <- loon.ggplot(lgp)
This makes it seem essentially equivalent to print(lgp)
,
but it is not.
Instead, loon.ggplot()
is a powerful two way
bridge between ggplot
s and loon
plots.
When called on
an l_ggplot
(an extended ggplot
)
loon.ggplot(lgp)
produces an interactive loon
plot.
an interactive loon
plot, it produces a static
ggplot
loon.ggplot(lp)
This is a ggplot
with parameters to make it look like
the loon
plot it was built from (e.g. the title is centred
in this ggplot
).
loon.ggplot()
gives us a second way to produce a
static version of an interactive loon
plot.
plot(lp)
produces a grid
graphic object
(or grob
)
loon.ggplot(lp)
produces a ggplot
graphic object.
Either reproduces the current loon
plot as a static
snapshot of its present appearence (ideally wysiwyg).
Which is preferred depends on the use intended for the static plot .
The ggplot
version is easier to work with and trivial to
adapt using the grammar.
gp <- loon.ggplot(lp) # loon to ggplot
class(gp)
#> [1] "gg" "ggplot"
gp + geom_smooth()
Alternatively, a new interactive plot can be generated from
an ordinary ggplot
as
new_lp <- loon.ggplot(gp + geom_smooth())
and this interactive plot is turned into a new static
ggplot
with the same function, as shown below
loon.ggplot(new_lp)
Note that the grey polygon emphasizing the confidence region does not
appear. It is there, but as a hidden layer
of the
loon
plot that can be revealed at any time (via the
loon
inspector or programmatically). The polygon is not
shown by default because colours do not (yet) have an alpha channel (for
transparency) in tcltk
.
The bridge: loon.ggplot()
turns
ggplot
s to interactive loon
plots and
loon
plots to ggplot
s.
loon.ggplot
extends the grammar of graphics by adding
several new clauses.
+ linking()
+ linking(linkingGroup = NULL, linkingKey = NULL, linkedStates = NULL, sync = NULL)
loon
implements a group-key-state linking
model.
Interactive plots having the same linkingGroup
are
linked, in that each plot changes its display in
response to display changes of the linkedStates
of any plot
in the same linkingGroup
. Only those
linkedStates
in common are changed and display elements are
matched by values of the linkingKey
.
linkingGroup
A string naming the group or NULL
for no
linking.
linkedStates
A character vector of the states to be linked.
By default these include selected
, active
,
and others peculiar to each type of plot such as color
and
size
.
linkingKey
A length n
character vector of unique strings, one for
each observation; default is "0"
, "1"
, …,
"n-1"
.
sync
Specifies the direction of synchronizing the linked states
at the time the plot is created.
The value "pull"
pulls the values of the linked states from
plots in the linking group and assigns them to the new plot; value
"push"
pushes the newly created plot’s linked states values
out to the others in the linking group.
Default is "pull"
unless some aesthetics matching the
linked states are specified in the plot creation; then the default will
be "push"
.
Each plot will propogate, and respond to, only changes in those
state
s named in its linkedStates
. Display
elements associated with the unique set of keys in each plot’s
linkingKey
will change together.
+ hover()
+ hover(itemLabel = NULL, showItemLabels = NULL)
Provides a pop up display as the mouse hovers over a plot element in the interactive plot.
itemLabel
A character vector of length n
with a string to be used
to pop up when the mouse hovers above that element.
showItemLabels
A single logical value: TRUE
if pop up labels are to
appear on hover, FALSE
(the default) if they are
not.
+ selection()
+ selection(selected = NULL, selectBy = NULL, selectionLogic = NULL)
Set which elements (i.e., observations) are "selected"
.
These are shown highlighted in the plot.
selected
a logical or a logical vector of length n that determines which observations are selected (TRUE and hence appear highlighted in the plot) and which are not. Default is FALSE and no points are highlighted.
selectBy
A string determining how selection will occur in the interactive
plot. Default is "sweeping"
where a rectangular region is
reshaped or “swept” out to select observations.; alternately
"brushing"
will indicate that a fixed rectangular region is
moved about the display to select observations.
selectionLogic
One of "select"
(the default), "deselect"
,
and "invert"
. The first highlights observations as
selected, the second downlights them, and the third inverts them
(downlighting highlighted observations and highlighting downlighted
ones).
+ active()
+ active(active = NULL, activeGeomLayers = NULL)
Set active
and/or activeGeomLayers
active
a logical, or a logical vector of length n
, determining
which observations are active (hence appear in the plot) and which are
inactive (FALSE
and hence do not appear). Default is
TRUE
.
activeGeomLayers
determine which geom layer is interactive by its
geom_...
position in the grammar of the expression.
Currently, only geom_point()
and
geom_histogram()
can be set as the active geom layer(s) so
far. (N.B. more than one geom_point()
layer can be set as
an active layer, but only one geom_histogram()
can be set
as an active geom layer and it can be the only active layer)
+ zoom()
+ zoom(layerId = NULL, scaleToFun = NULL)
Change the visible plot region by scaling to different elements of the display.
layerId
numerical; which layer to scale the plot by. If the layerId is set as
NULL
(default), the region of the interactive graphics loon
will be determined by the ggplot object
(i.e. coord_cartesian
, xlim
, etc); else one
can use scaleToFun
to modify the region of the
layer.
scaleToFun
scale function to be used. If NULL
(default), based on
different layers, different scale functions will be applied. For
example, if the layer is the main graphic model,
i.e. l_plot
l_hist
, then the default
scaleToFun
is l_scaleto_plot
; else if the
layer is a general l_layer
widget, the default
scaleToFun
would be loon::l_scaleto_layer
.
If it is not NULL
, any of the following
scaleToFun
functions could be used
scale to | Subfunction |
---|---|
plot | l_scaleto_plot |
world | l_scaleto_world |
active | l_scaleto_active |
selected | l_scaleto_selected |
layer | l_scaleto_layer |
Alternatively, scaleToFun
can be any function whose
arguments match those of the functions above.
+ interactivity()
+ interactivity(linkingGroup, linkingKey, linkedStates, sync, # linking
active, activeGeomLayers, # active
selected, selectBy, selectionLogic, # selection
layerId, scaleToFun, # zoom
itemLabel, showItemLabels, # hover
... )
Set interactive components (e.g. linking, selection, etc) in one clause. All named arguments are as described in the other clauses.
...
other named arguments to modify loon plot states.
See l_info_states()
.l_ggplot()
becomes
ggplot()
We began with the recommendation that to have interactive
ggplot
s, all we need do is replace ggplot()
in
the grammar by l_ggplot()
with all the usual arguments. All
clauses of the ggplot
grammar can be used as before
plus the new interactive clauses. The
result is an l_ggplot
that prints as an
interactive loon
plot and plots
as a ggplot2
plot. The advantage is that using
l_ggplot()
makes it clear that an ordinary
ggplot
is not being produced and emphasizes the
loon-ggplot duality.
The final trick is that, with loon.ggplot
, one does not
actually need to use l_ggplot()
, simply use
ggplot()
. If no clauses are interactive, then an
ordinary ggplot
will be produced; if an interactive clause
is added, an l_ggplot
will be produced.
For example, the following produces an ordinary
ggplot
.
ggp <- ggplot(airquality, mapping = aes(Solar.R, Temp)) +
geom_point(size = 3) +
ggtitle("Air quality in New York (1973)")
# which is an ordinary ggplot and prints as one
ggp
Adding an interactive clause, turns the result into an
l_ggplot
.
lggp <- ggp +
linking(linkingGroup = "airquwqality") +
selection(selected = airquality$Solar.R < 100) +
zoom(layerId = 1, scaleToFun = l_scaleto_selected) +
geom_smooth()
# which is an interactive loon and prints as one
lggp
#> [1] ".l3.ggplot.plot"
#> attr(,"class")
#> [1] "l_plot" "loon"
# but plots as a ggplot
plot(lggp)
Note that the interactive effects do not appear in
plot(l_ggp)
; this is because this is still an
l_ggplot
. To print the interactive plot, a handle to it
must be found, for example using
l_getFromPath(".l3.ggplot")
l_ggp <- l_getFromPath(".l3.ggplot")
# Alternatively, the loon plot could have been captured when first
# created by using loon.ggplot(lggp) in stead of print(lggp) as follows
#
# l_ggp <- loon.ggplot(lggp)
#
# Either way, it will look like the following as a grid graphics plot
plot(l_ggp)
# and as below when presented as a ggplot
loon.ggplot(l_ggp)
The final trick is that either
l_ggplot()
or ggplot()
cam be produce
interactive plots using the ggplot
grammar.
It’s largely a matter of taste.