# [R] help simplifying complex graphic arguments to a function

Paul Murrell p.murrell at auckland.ac.nz
Wed Aug 19 23:22:19 CEST 2009

```Hi

The "standard" approach of having graphics parameter arguments separate
from the data arguments is a little bit creaky.  The relationship
between the nth data value and the nth graphics parameter only really
exists in your head.  It would feel much safer if you could specify the
graphics parameters as part of the data frame that contains the data, so
you had more confidence about the relationship between values (c.f.
ggplot2).  I think the standard approach really only works in practice
because it is simple enough to think about two one-dimensional vectors
matching up.
But as soon as you go to something like a matrix, it gets too hard to
keep straight in your head, all confidence drains away and you can see
the frailty of this approach.  If you have something hierarchical, like
a tree of nodes, god help you.

This is where the second approach mentioned by Baptiste may provide a
way out.  If you provide some sort of rational naming scheme for the
graphical elements that you are drawing, then you can address the
components of a more complex graphic by name.  Here's some code that
provides a trivial example ...

library(grid)

nrow <- 3
ncol <- 5

# Draw a table of cells
# grid.newpage()
pushViewport(viewport(width=.9, height=.9,
layout=grid.layout(nrow, ncol)))
for (i in 1:nrow) {
for (j in 1:ncol) {
pushViewport(viewport(layout.pos.row=i,
layout.pos.col=j))
grid.rect(name=paste("rect", i, j, sep="-"))
grid.text(paste("cell", i, j, sep="-"),
name=paste("text", i, j, sep="-"))
upViewport()
}
}
upViewport()

# Modify all cells in row 2
grid.gedit("rect-2", gp=gpar(fill="grey"))

# Modify all labels in col 4
grid.gedit("text-.-4", gp=gpar(col="light grey"))

# Modify just cell 3,2
grid.edit("rect-3-2", gp=gpar(fill="black"))
grid.edit("text-3-2", gp=gpar(col="white"))

If you don't want to impose the effort of calling grid.edit() on your
users, you can wrap this all up within your function.  The basic idea
would be to create a grob (or gTree) representing the table, with all
component grobs rationally labelled, then apply user-supplied edits to
the grob before returning the entire, modified object (or you can just
draw the modified grob).  Here's a trivial example of this idea ...

tableGrob <- function(nr, nc, edits=NULL) {
cells <- vector("list", nr*nc)
for (i in 1:nr) {
for (j in 1:nc) {
cellvp <- viewport(layout.pos.row=i,
layout.pos.col=j)
grobs <- gList(rectGrob(name=paste("rect", i, j, sep="-"),
vp=cellvp),
textGrob(paste("cell", i, j, sep="-"),
name=paste("text", i, j, sep="-"),
vp=cellvp))
cells[[(i - 1)*nc + j]] <- gTree(children=grobs)
}
}
table <- gTree(children=do.call("gList", cells),
vp=viewport(layout=grid.layout(nr, nc)))
if (!is.null(edits)) {
for (i in 1:length(edits))
table <- editGrob(table,
gPath=names(edits)[i],
grep=TRUE, global=TRUE,
gp=edits[[i]])
}
table
}

# Unmodified table
# grid.newpage()
grid.draw(tableGrob(3, 5))

# Modified table
grid.newpage()
grid.draw(tableGrob(3, 5,
edits=list("rect-2"=gpar(fill="grey"),
"text-.-4"=gpar(col="light grey"),
"rect-3-2"=gpar(fill="black"),
"text-3-2"=gpar(col="white"))))

For large tables this may draw slowly (c.f. ggplot2), but you'd need to
try it to find out.

Hope that is of some help.

Paul

p.s.  We might want to move any further discussion on to R-devel.

baptiste auguie wrote:
> I'm facing a similar challenge with a grid.table function (see example
> below). I envisaged two routes,
>
> 1- rather than assigning a list of properties for each cell, I would
> define a matrix for each property. For instance, the default could be,
>
> fill = matrix("grey90", nrow(values), ncol(values)) # or an array if
> more than 2D
> col = matrix("red", nrow(values), ncol(values))
> col.text = matrix("black", nrow(values), ncol(values))
>
> The drawing of each cell would look like,
>
> for( ii in 1:nrow){
>  for (jj in 1:ncol){
>   grid.rect(gp=gpar(fill = fill[ii, jj], col = col[ii, jj]))
>   grid.text(values[ii, jj], gp = gpar(col= col.text[ii, jj]))
>   # or whatever grid function you need
>    }
> }
>
> 2- Create the table with default values, but give a name to each grob
> and allow for subsequent editing of individual gpar() properties.
> (as suggested in sec. 7.3.10 "avoiding argument explosion" of Paul
> Murrell's R graphics book)
>
> Perhaps a structure like a gTree can help (in my case I wanted the
> table header to have different settings than the rest of the table).
>
> Best,
>
> baptiste
>
>
> source("http://gridextra.googlecode.com/svn/trunk/R/tableGrob.r")
>
>  tc  = textConnection("
>       carat   VeryLongWordIndeed color clarity depth
>  14513  1.35 Ideal     J     VS2  61.4
>  28685  0.30  Good     G    VVS1  64.0
>  50368  0.75 Ideal     F     SI2  59.2")
>  d = read.table(tc,head=T)
>  close(tc)
>
> grid.newpage()
> g = grid.table(d)
>
> grid.ls(g) # not much of a clue which is which... let's edit a random
> one (numbers probably session-dependent)
> grid.edit("GRID.tableGrob.556::table.header.left::GRID.cellGrob.718::GRID.rect.717",
> gp=gpar(fill="red"))
>
>
>
> 2009/8/17 Michael Friendly <friendly at yorku.ca>:
>> I'm working on a package to produce graphic displays of 2- and 3-way tables
>> and need some help/advice on how to simplify the specification of a complex
>> argument that gives the drawing details for each cell of the table.
>> Prototypes of two functions, 'tableplot' and 'cellgram' are given below.
>>
>> The essential idea is that for a given table ('values'), the cells can be
>> be of different 'types' (integers: 1, 2, ..., max(types)).  An argument
>> 'patterns' is a list of max(types) lists, where patterns[[k]] is presently
>> a list of 10 graphic parameters specifying shapes, colors, fill, background
>> color,
>> etc. that are passed as arguments to the cellgram function.
>>
>> The difficulty is that it's too hard to remember the order and meaning
>> of the cellgram arguments to be passed in the patterns argument to
>> tableplot,
>> and often quite a few of these will take their default values, but-- as
>> presently written-- all must be specified.
>>
>> The actual implementation of these functions use grid graphics, and I know
>> from other grid-based packages that use named specifications like
>>
>> gp = gpar(shape=1, shape.col="black", shape.lty=2, ...)
>>
>> are commonly used, but I'm unable to see how to re-write these functions to
>> take advantage of that form.  To be specific, I need to re-write cellgram
>> to take two arguments
>>
>> cellgram(cell.values, cell.pattern)
>>
>> and then be able to extract the current arguments from cell.pattern within
>> this function.
>>
>>
>> tableplot <- function(
>>
>>   values,               # Matrix of values to plot; can be a matrix, or an
>> array of 3 dimensions.
>>   types,          # Matrix of pattern designations (i.e., what pattern for
>> each cell).
>> #    patterns,             # List of lists; each list specifies one pattern.
>>   patterns = list(list(0, "black", 1, "white", "white", 0, 0.5, "grey80",
>> "no", 1)),
>>   ...){
>>
>> #
>>   #---Draw cellgrams.
>>
>>   for (i in 1:dim(values)[1]){
>>       for (j in 1:dim(values)[2]){
>>
>>           pattern = patterns[[types[i,j]]]
>>           cellgram(cell       = values[i,j,],
>>                  shape      = pattern[[1]],
>>                  shape.col = pattern[[2]],
>>                  shape.lty = pattern[[3]],
>>                  cell.fill = pattern[[4]],
>>                  back.fill = pattern[[5]],
>>                  label     = pattern[[6]],
>>                  label.size= pattern[[7]],
>>                  ref.col     = pattern[[8]],
>>                  ref.grid     = pattern[[9]],
>>                  scale.max = pattern[[10]]
>>                  )
>>           }
>>       }
>>
>> }
>>
>> cellgram = function(
>>
>>   #-- Arguments that may be vectorized:
>>
>>   cell,                   #-- cell value(s)
>>   shape=0,               #-- shape of cell value(s); 0=circle, 1=diamond,
>> 2=square, 3=cross
>>   shape.col="black", #-- color of shape(s), outline only
>>   shape.lty=1,         #-- line type used for shape(s)
>>
>>   #-- Arguments that can not be vectorized:
>>
>>   scale.max=1,     shape.lwd=1,       #-- line width of shape(s)
>>   cell.fill="white", #-- inside color of smallest shape in a cell
>>   back.fill="white", #-- background color of cell
>>   label=0,           #-- how many cell values will be printed; max is 4
>>   label.size=0.7,    #-- size of text labels
>>   ref.col="grey80",  #-- color for ref lines
>>   ref.grid=TRUE,     #-- to draw ref lines or not
>>   neg.col="white",   #-- fill color for negative cell value
>>   frame.col="black", #-- color of frame around cell
>>   frame.lwd="0.5"    #-- line width of frame around cell
>>   t.col="black",     #-- color of cell labels  [[ should be: label.col]]
>>   )
>> {
>> # ...
>> }
>>
>>
>> --
>> Michael Friendly     Email: friendly AT yorku DOT ca Professor, Psychology
>> Dept.
>> York University      Voice: 416 736-5115 x66249 Fax: 416 736-5814
>> 4700 Keele Street    http://www.math.yorku.ca/SCS/friendly.html
>> Toronto, ONT  M3J 1P3 CANADA
>>
>> ______________________________________________
>> R-help at r-project.org mailing list
>> https://stat.ethz.ch/mailman/listinfo/r-help
>> PLEASE do read the posting guide http://www.R-project.org/posting-guide.html
>> and provide commented, minimal, self-contained, reproducible code.
>>
>
>
>

--
Dr Paul Murrell
Department of Statistics
The University of Auckland
Private Bag 92019
Auckland
New Zealand
64 9 3737599 x85392
paul at stat.auckland.ac.nz
http://www.stat.auckland.ac.nz/~paul/

```

More information about the R-help mailing list