Paul Johnson
2009-Mar-11 21:09 UTC
[R] Working up examples of barplots with customized marginal items
Hello, everybody: I'm back to share a long piece of example code. Because I had trouble understanding the role of par() and margins when customizing barplots, I have struggled with this example. In case you are a student wanting to place legends or textual markers in the outer regions of a barplot, you might run these examples. There are a few little things I don't quite understand. If you look below where I've below placed ???????, you see I've used text() to write inside the bars of a plot. To vertically align the strings, I use the text() function's option pos. However, the text is not centered horizontally inside the bars. I don't think I found a perfectly general solution with offset because it does not take into account the width of the bars. Second, can changes to par be forced on all devices? I learned the hard way that a change like par(mar=c(10,3,4,5)) will apply to the current working device, but when a new output device is created, the margins are re-set to default. The margins in the saved graph won't match the screen unless par is run again: postscript( ---whatever--- ) ###par must be run again to manipulate the postscript device: par ( --whatever ---) plot (--whatever---) dev.off() On the other hand, dev.copy(postscript, ---whatever--) will inherit the par values set on the screen device. Is there a single command that can adjust the margins of all devices that are created within a given session? Anyway, here's my big barplot script and I hope some students benefit from experimenting with it pj ### Paul Johnson ### Twisting the margins of a barplot ### I never thought too much about customizing barplots, but ### now I have learned some tricks to share. Step through these ### examples to see the possibilities. set.seed(424242) x <- rpois(10, lambda=10) mynames <- c(rep("Really Long Name",9),"Really Long Name Long Long") #RSiteSearch("barplot names margins") barplot(x) ### Note long names off edge: barplot(x, names=mynames, las=2) ### Abbreviate offers one solution barplot(x, names=abbreviate(mynames), las=2) ### Other option is to reset margins ###default mar is c(5.1, 4.1, 4.1, 2.1) ### Lets make the bottom margin larger par(mar=c(15,4.1,4.1, 2.1)) barplot(x, names=mynames, las=2) ### Now the bottom is finished. But I'd like a legend. legend("topleft", legend=c("A really long label","B","Cee What I can do?","D"), col=1:2) ### My bar hits the legend. How to fix? ### It is not sufficient to simply "move the legend" up ### because then it runs off the edge of the graph barplot(x, names=mynames, las=2) legend(2,22, legend=c("A really long label","B","Cee What I can do?","D"), col=1:2) ### By itself, changing the top margin does not help. ### It is also vital to set the xpd parameter to T so that ### R will draw outside the main plot region par(mar=c(10,4.1,8.1, 2.1)) par(xpd=T) barplot(x, names=mynames, las=2) legend(2,22, legend=c("A really long label","B","Cee What I can do?","D"), col=1:2) ### Now lets gain some control on the colors and bars ### The default colors are so dark. You can't write on top ### of them. You can grab shades of gray like "gray30" or such. ### I'll just specify 4 colors I know will work. I prefer ### the light gray colors because they can be written over. mycols <- c("lightgray","gray70","gray80","gray90","gray75") barplot(x, names=mynames, las=2, col=mycols) legend(2,20,legend=c("A","B","C","D"),fill=mycols) ### Still, I don't like the solid shades so much. ### fill up the bars with angled lines. ### density determines number of lines inside the bar. myden <- c(60,20,30,40,25) ### angles determines the direction of lines inside bars myangles <- c(20,-30,60,-40,10) barplot(x, names=mynames, las=2, col=mycols,density=myden,angle=c(20,60,-20)) legend(1,20,legend=c("A","B","C","D"),density=myden,angle=myangles) ### for my coupe de grace, lets do some writing in the bars. ### Recall from Verzani you can get the x coordinates from the barplot barPositions <- barplot(x, names=mynames, las=2, col=mycols,density=myden,angle=c(20,60,-20)) barPositions ### The text option srt=90 turns the text sideways ### I'm just guessing that bars 1 and 8 should be labeled ### at coordinates 5 and 5 text (barPositions[1], 5, "my first bar is great", srt=90) text (barPositions[8], 5, "but 8 is greater", srt=90) ### Recently I had the problem of drawing a "clustered" bar chart. ### Lets suppose our x variable really represents responses from ### 2 groups of respondents, Men and Women. ## Create the matrix xmatr <- matrix(x, nrow=5) ### Use beside=T to cause barplot to treat each column ### of values as a group barplot(xmatr, beside=T, names=c("Men","Women")) valueNames <- c("Very Strong","Somewhat Strong","Not Strong","Somewhat Weak","Very Weak") ### Use mtext to write in the margin so that labels on plot are nice. par(mar=c(10,4.1,5.1, 2.1)) bp <- barplot(xmatr, beside=T, names=NULL) ### bp contains the positions of the bars. mtext(valueNames, side=1, las=3, at=bp) mtext(c("Men","Women"), side=1, at=c(bp[3],bp[8]),line=8, cex=2) ### Adding numbers to the bars may clarify text ( bp, as.vector(xmatr), labels=as.vector(xmatr),pos=1) ### However, the default color is too dark. ### Retrieve the first 5 items from the default grey color scale gc <- grey.colors(5) ### Replace the first one with a lighter gray gc[1] <- "gray80" bp <- barplot(xmatr, beside=T, names=NULL, col=gc) ### bp contains the positions of the bars. mtext(valueNames, side=1, las=3, at=bp) mtext(c("Men","Women"), side=1, at=c(bp[3],bp[8]),line=8, cex=2) ### Adding numbers to the bars may clarify text ( bp, as.vector(xmatr), labels=as.vector(xmatr),pos=1) ### That's OK for me. I wonder if I might not just write inside the ### bars. Hmmm. bp <- barplot(xmatr, beside=T, names=c("Men","Women"), col=gc) text(bp, 0.5*as.vector(xmatr), valueNames, srt=90) ### Hm. That's OK, but maybe I'd pref uniform vertical ### placement of text. bp <- barplot(xmatr, beside=T, names=c("Men","Women"), col=gc) text(bp, 2, valueNames, srt=90, pos=4) ### Hell, now the text is aligned vertically, but not centered ### in the bar. I can't figure out all of the details concerning ### ### I'm not able to say for sure what the best fix might be. ### I can either manipulate the x placement like so bp <- barplot(xmatr, beside=T, names=c("Men","Women"), col=gc) text(bp-0.25, 2, valueNames, srt=90, pos=4) ### Or use the offset option in the text command. ### I do not understand why this particular value works. bp <- barplot(xmatr, beside=T, names=c("Men","Women"), col=gc) text(bp, 2, valueNames, srt=90, pos=4, offset=-0.15) ### Don't forget: When save this into a file, the graph ### may be re-sized and text might "overflow" the boxes. ### That's especially likely if you have a large graph ### on the screen and then try to save the file with ### dev.copy(postscript...) ### That will resize some things, but not all. ### Here is the way to protect yourself. Resize your "screen" device ### so that it is the same size--in inches--as the output file. ### On linux, I run x11( width=6, height=6 ) ### On windows, I think it is windows( width=6, height=6 ) ### After you do that, run the par command again. The par command ### has to be executed again each time a device is created. That applies to ### the most recently created device. p <- barplot(xmatr, beside=T, names=c("Men","Women"), col=gc) text(bp-0.25, 2, valueNames, srt=90, pos=4) ### If that looks OK, then do this postscript(file="mybar-1.eps",height=6, width=6, onefile=F, horizontal=F, paper="special",family="Times") p <- barplot(xmatr, beside=T, names=c("Men","Women"), col=gc) text(bp-0.25, 2, valueNames, srt=90, pos=4) dev.off() ### note that the par settings have shifted back to the defaults. ### Each device is separate. ### As a result, the barplot we created before with large margins would ### make a horrible plot if you forgot to insert the par() commands here: postscript(file="mybar-2.eps",height=6, width=6, onefile=F, horizontal=F, paper="special",family="Times") par(mar=c(10,4.1,5.1, 2.1)) bp <- barplot(xmatr, beside=T, names=NULL, col=gc) ### bp contains the positions of the bars. mtext(valueNames, side=1, las=3, at=bp) mtext(c("Men","Women"), side=1, at=c(bp[3],bp[8]),line=8, cex=2) ### Adding numbers to the bars may clarify text ( bp, as.vector(xmatr), labels=as.vector(xmatr),pos=1) dev.off() ### Finally, suppose you make a mistake of specifying your ### output device size too small. The graph I made before ### with the text in the bars looked good on the screen, ### but it looks bad in a smaller output device. The text ### does not match the bars in this example. postscript(file="mybar-4.eps",height=4, width=4, onefile=F, horizontal=F, paper="special",family="Times") bp <- barplot(xmatr, beside=T, names=c("Men","Women"), col=gc) text(bp-0.25, 2, valueNames, srt=90, pos=4) dev.off() -- Paul E. Johnson Professor, Political Science 1541 Lilac Lane, Room 504 University of Kansas