2

I've created a simple wordcloud:

require(wordcloud)    
words <- c('affectionate', 'ambitious', 'anxious', 'articulate', 'artistic', 'caring', 'contented', 'creative', 'cynical', 'daring', 'dependable', 'easygoing', 'energetic', 'funny', 'generous', 'genuine', 'goodlistener', 'goodtalker', 'happy', 'hardworking', 'humerous', 'impulsive', 'intelligent', 'kind', 'loyal', 'modest', 'optimistic', 'outgoing', 'outrageous', 'passionate', 'perceptive', 'physicallyfit', 'quiet', 'rational', 'respectful', 'romantic', 'shy', 'spiritual', 'spontaneous', 'sweet', 'thoughtful', 'warm')
freqs <- c(134, 53, 0, 5, 0, 247, 0, 78, 0, 0, 134, 178, 79, 344, 63, 65, 257, 0, 109, 113, 0, 0, 107, 51, 199, 24, 67, 232, 0, 109, 24, 28, 29, 2, 105, 70, 0, 35, 64, 156, 66, 45)
wordcloud(words, freqs)

I would like to put this into a "grob" so that I can arrange it with several other plots using grid.arrange() in the gridExtra package:

require(ggplot2)
p1 <- qplot(1:10, rnorm(10), colour = runif(10))
require(gridExtra)
grid.arrange(p1, my.wordcloud)

I understand that my wordcloud must be a "grob" to do this, but I don't understand how to make this so. I tried using the grob() function in the gridExtra package, but this didn't work. Suggestions?

Richie Cotton
  • 118,240
  • 47
  • 247
  • 360
rnorberg
  • 482
  • 7
  • 19
  • 2
    Maybe this is of use: http://stackoverflow.com/questions/14124373/combine-base-and-ggplot-graphics-in-r-figure-window/14125768#14125768 – Tyler Rinker Mar 23 '13 at 06:44
  • 1
    the plot method for wordcloud uses base graphics, not grid graphics (therefore no grob can encapsulate the drawing, or it would have to be written from scratch). Your best bet is to follow the examples of the gridBase package. – baptiste Mar 23 '13 at 07:51
  • 1
    Related: http://stackoverflow.com/questions/11253765/geom-wordcloud-is-this-a-pipe-dream – Spacedman Mar 23 '13 at 19:25

2 Answers2

8

It shouldn't be that difficult to adapt the code in wordcloud to construct the data need to fill in a text.grob in grid. The wordcloud code sends x, y, text and rot values to the base text function after a window with limits of 0,0 and 1, 1 is specified.

I needed to add this before the for-loop:

textmat <- data.frame(x1=rep(NA, length(words)), y1=NA, words=NA_character_, 
                       rotWord=NA, cexw=NA, stringsAsFactors=FALSE )

This at the end of the for-loop:

 textmat[i, c(1,2,4,5) ] <-  c(x1=x1, y1=y1, rotWord=rotWord*90, cexw = size[i] )
 textmat[i, 3] <- words[i]

And needed to amend the call to .overlap, because it is apparently not exported:

if (!use.r.layout) 
         return(wordcloud:::.overlap(x1, y1, sw1, sh1, boxes))

And I returned it invisibly after the loop was complete:

return(invisible(textmat[-1, ]))  # to get rid of the NA row at the beginning

After naming it wordcloud2:

> tmat <- wordcloud2(c(letters, LETTERS, 0:9), seq(1, 1000, len = 62))
> str(tmat)
'data.frame':   61 obs. of  5 variables:
 $ x1     : num  0.493 0.531 0.538 0.487 ...
 $ y1     : num  0.497 0.479 0.532 0.475 ...
 $ words  : chr  "b" "O" "M" ...
 $ rotWord: num  0 0 0 0 0 0 0 0 0 ...
 $ cexw   : num  0.561 2.796 2.682 1.421 ...

draw.text <- function(x,y,words,rotW,cexw) {
     grid.text(words, x=x,y=y, rot=rotW, gp=gpar( fontsize=9*cexw)) }

for(i in 1:nrow(tmat) ) { draw.text(x=tmat[i,"x1"], y=tmat[i,"y1"], 
                                    words=tmat[i,"words"], rot=tmat[i,"rotWord"], 
                                    cexw=tmat[i,"cexw"]) }

As suggested:

 with(tmat, grid.text(x=x1, y=y1, label=words, rot=rotWord, 
                      gp=gpar( fontsize=9*cexw)) }  # untested
baptiste
  • 75,767
  • 19
  • 198
  • 294
IRTFM
  • 258,963
  • 21
  • 364
  • 487
  • Where does the magic number '9' in fontsize=9*cexw come from? – Spacedman Mar 23 '13 at 09:26
  • I'm just converting from cex to font sizes. Choose a different size as your default if you desire. – IRTFM Mar 23 '13 at 09:29
  • arguments to textGrob are vectorised, so you could do without the last for loop and return a single textGrob. – baptiste Mar 23 '13 at 09:38
  • Excellent idea. I was mostly using it as a proof of concept. I wasn't sure I could get vectorized behavior out of the gpar call, but if you say so. – IRTFM Mar 23 '13 at 09:43
  • 1
    Many thanks! This does exactly what I need it to. Instead of plotting with grid.text at the end however, I used ggplot2 since all of the other things I am going to arrange with it are ggplot2 objects as well: `ggplot(tmat, aes(x=x1, y=y1, label=words, angle=rotWord, size=cexw))+ geom_text()+ labs(x='', y='')+ guides(size=F)+ scale_x_continuous(breaks=NULL)+ scale_y_continuous(breaks=NULL)+ theme_bw()+ theme(panel.border=element_blank())` – rnorberg Mar 23 '13 at 14:00
  • Internally ggplot uses grid graphics, and is probably creating those text labels as grobs from grid.text. Are we one step away from +geom_wordcloud(...)? – Spacedman Mar 23 '13 at 19:24
  • 1
    @Spacedman `stat_cloud` maybe? BTW @morberg , you can use grid.arrange with a textGrob: `grid.arrange(qplot(1,1), textGrob("hi"))` – baptiste Mar 23 '13 at 20:50
  • @morberg I am following your approach above ... what if `F` in the `guides(size=F)`? – SkyWalker Aug 24 '17 at 14:56
3

you can use gridBase package but with a clever viewport. here I am using vpStack to get the good dimensions.

par(mfrow=c(1, 2))
wordcloud(words, freqs)
plot.new()              
vps <- baseViewports()
p <- qplot(1:10, rnorm(10), colour = runif(10))
print(p,vp = vpStack(vps$figure,vps$plot))

enter image description here

EDIT Use knitr if you want just to generate a pdf.

Another option if you want just to create a pdf you can use Knitr. Mixing grid and base graphics is really simple. Latex will do the job for you. Fo example, the above result can be obtained by this chunk.

<<mixgridwithggplot, fig.show='hold',out.width='.5\\linewidth'>>=
wordcloud(words, freqs)
qplot(1:10, rnorm(10), colour = runif(10))
@

I am pretty sure that knitr can create the png of the 2 plots in a single png behind the code.

agstudy
  • 119,832
  • 17
  • 199
  • 261