Creating a text grob that automatically adjusts to viewport size

01Aug12

Source: flickr, user saikofish, pic lou (http://www.flickr.com/photos/lou/2231734109/)

I recently wanted to construe a dashboard widget that contains some text and other elements using the grid graphics system. The size available for the widget will vary. When the sizes for the elements of the grobs in the widget are specified as Normalised Parent Coordinates the size adjustments happen automatically. Text does not automatically adjust though. The size of the text which is calculated as fontsize times the character expansion factor (cex) remains the same when the viewport size changes. For my widget this would require to adjust the fontsize or cex settings for each case seperately. While this is not really an obstacle, I asked myself how a grob that will adjust its text size automatically when being resized can be construed. Here I jot down my results in the hope that you may find this useful.

First I will create a new grob class called resizingTextGrob that is supposed to resize automatically.

library(grid)
library(scales)

resizingTextGrob <- function(...)
{
  grob(tg=textGrob(...), cl="resizingTextGrob")
}

The grob created by the function contains nothing more than a textGrob. In order for the grob class to print something we need to specify the drawDetails method for our class which will do the drawing. The drawDetails method is called automatically when drawing a grob using grob.draw.

drawDetails.resizingTextGrob <- function(x, recording=TRUE)
{
  grid.draw(x$tg)
}

Up to now this will produce the same results as a plain textGrob.

g <- resizingTextGrob(label="test 1")
grid.draw(g)
grid.text("test 2", y=.4)

Now, before doing the drawing we want to calculate the size of the viewport and adjust the fontsize accordingly. To do this we can take the approach to push a new viewport with an adjusted fontsize before the drawing occurs. To perfom the calculations and and push the viewport we specify a preDrawDetails method. This method is automatically called before any drawing occures. It gives us the chance to do some modifications, like e.g. pushing a viewport.

For this purpose first the available height is calculated. Than the fontsize is rescaled according to the available width. The rescaled fontsize is used for the new viewport. Now for a fully developed class we will want to include these parameters in the grob constructor of course. Or we might define a proportion factor argument by which to shrink the text instead. Anyway, to keep things simple this is not done here.

preDrawDetails.resizingTextGrob <- function(x)
{
  h <- convertHeight(unit(1, "snpc"), "mm", valueOnly=TRUE)
  fs <- rescale(h, to=c(18, 7), from=c(120, 20))
  pushViewport(viewport(gp = gpar(fontsize = fs)))
}

To clean up after the drawing the created viewport is popped. This is done in the postDrawDetails which is automatically called after the drawDetails method.

postDrawDetails.resizingTextGrob <- function(x)
  popViewport()

Now the output will depend on the size of the current viewport. When resizing the device the text size will adjust.

g <- resizingTextGrob(label="test 1")
grid.draw(g)
grid.text("test 2", y=.4)

Let’s compare the standard textGrob with the new class. For this purpose let’s draw a small clock and display it using different device sizes.

library(gridExtra)
a <- seq(2*pi, 2*pi/ 12, length=12) + pi/3
x <- cos(a) / 2
y <- sin(a) / 2
segs <- segmentsGrob(x*.2 + .5, y*.2+.5, x*.3 + .5, y*.3 + .5)
# the standard approach
tgs.1 <- textGrob(1:12, x*.4 + .5, y*.4 + .5)
# the new grob class
tgs.2 <- resizingTextGrob(1:12, x*.4 + .5, y*.4 + .5)
grid.arrange(grobTree(segs, tgs.1), grobTree(segs, tgs.2))

What it looks like at the beginning.

What it looks like when the device is resized.

Note how the size of the text of the lower clock adjusts to the device size. The text size in the upper graphs remains the same and becomes too big for the clock while it changes for the lower ones.

BTW: The definitive guide for  the grid graphics model is the book R graphics by Paul Murrell.

About these ads


Follow

Get every new post delivered to your Inbox.

Join 59 other followers

%d bloggers like this: