Related
I need to arrange several plots for a figure. I am creating individual plots using base and grid graphics. In order to arrange them in a single figure I have been using grid.echo(), grid.grab() to convert to grobs and then arrangeGrob() and grid.arrange() to build the final figure. A few weeks ago my tentative figure was working fine but now when I rerun the code it produces a figure with whitespace in the margins of the plots.
I add a minimal example that shows the problem that I am facing...
##minimal example
library(grid)
library(gridExtra)
library(gridGraphics)
##test plot
plot_n1<-plot(1:10,1:10, asp=1)
##convert test plot to grob
grid.echo()
test_p<-grid.grab()
##simulate several plots arranged in a more complex layout
multi<-arrangeGrob(test_p, test_p, test_p, test_p, ncol=1, heights=c(1/4,1/4,1/4,1/4))
##create graph
png(filename="minimal_multiplot.png", res=300, width=20, height=20, units="cm")
grid.arrange(test_p, multi, ncol=2, widths=c(2/3,1/3))
dev.off()
What am I doing wrong?
There does indeed appear to be a problem when converting a graphics plot into a grid plot, then using grid.grab() to grab and then draw the plot into a smaller regions (i.e., using your method). For instance, using viewports to define a slightly smaller region (coloured grey in the image below), axis material is missing.
# Packages
library(grid)
library(gridGraphics)
plot(1:10,1:10)
grid.echo()
test_p = grid.grab()
grid.newpage()
pushViewport(viewport(x = 0, width = .85, just = "left"))
grid.rect(gp = gpar(col = NA, fill = "grey90"))
grid.draw(test_p)
upViewport()
grid.rect(gp = gpar(col = "grey90", size = .1, fill = NA))
But Paul Murrell (author of the gridGraphics package) offers an alternative (see the examples at ?gridGraphics::grid.echo, and pp. 156-157 of The gridGraphics package in The R Journal v7/1). One can define a function that draws the plot, then that function becomes the argument of grid.echo() at the time of drawing the plot within the viewport. newpage = FALSE stops grid from opening a new page. Note that none of the axis material is chopped off.
pf = function() {
plot(1:10,1:10)
}
grid.newpage()
pushViewport(viewport(x = 0, width = .85, just = "left"))
grid.rect(gp = gpar(col =NA, fill = "grey90"))
grid.echo(pf, newpage=FALSE)
upViewport()
grid.rect(gp = gpar(col = "grey90", size = .1, fill = NA))
So to get your desired plot, I would do something like this - but still using viewports.
pf = function() {
par(mar=c(7.2, 7.2, 1, 1), mex = .3, tcl = .15, mgp = c(3, .15, 0))
plot(1:10, 1:10, cex.axis = .75, cex.lab = .75)
}
grid.newpage()
pushViewport(viewport(layout = grid.layout(3, 2,
widths = unit(c(2, 1), "null"),
heights = unit(c(1, 1, 1), "null"))))
pushViewport(viewport(layout.pos.col = 1, layout.pos.row = 1:3))
grid.echo(pf, newpage = FALSE)
upViewport()
for(i in 1:3) {
pushViewport(viewport(layout.pos.col = 2, layout.pos.row = i))
grid.echo(pf, newpage = FALSE)
upViewport()
}
upViewport()
grid.rect(gp = gpar(col = "grey90", size = .1, fill = NA))
I have a legend in my plot, but I'm trying to increase the font size so it fit the legend-box. When I try to increase the cex as defined below. The box gets bigger, while the text is still small.
Code:
legend(0,16, c("Available vCPUs", "Added vCPUs (1 per iteration ) "),
col=c('red', 'black'), cex=0.39, lty=1:1, lwd=2)
Excerpt from plot:
First approach:
Try to set the font size before to plot the legend.
x <- y <- rnorm(100, 0, 1)
plot(x, y, type = "n")
## here you set the font size default to `x`, in this example 0.5
## save defaults in `op`
op <- par(cex = 0.5)
legend("topright", legend = "foo legend", pch = 1, bty = "n")
## here you set cexto 1.5
## save new defaults in `op`
op <- par(cex = 1.5)
legend("topright", legend = "foo legend", pch = 1, bty = "n")
Second approach:
Holding the pt.cex parameter to 1, while trying different values for cex inside the legend call. Remember to delete op.
x <- rnorm(100, 10, 4)
y <- rnorm(100, 10, 4)
plot(x, y, type = "n")
## I tried to feed cex with 1.5 and 0.5. The font size changes while the points remain unchanged.
legend("topleft", "Legend", cex=0.5, pch=1, pt.cex = 1)
You can use cex to determine font size, use bty='n' to indicate no lines around the legend, then draw a rectangle separately on the graph with rect(). For example:
with(data, legend(-10,7, legend=c("Name_of_Legend"), bty = 'n', col=c("red"), lty=0, pch=20, cex=0.75))
with(data, rect(-10,6.2,-3,7))
I think you can try using the
y.intersp in legend, when the intervals between different text lines are reduced, you could increase text size without changing the size of legend box.
legend(0,16, c("Available vCPUs","Added vCPUs (1 per iteration )
"),col=c('red','black'),cex=0.39,lty=1:1,lwd=2, y.intersp = 0.3)
I want to make a legend on my graph, which is generated by plot() function. The original legend() function will generate a list which has only 1 column. How can I make a legend which has 2 columns?
I could not find a way to do that within a single call to legend for standard plots.
Here's an option, drawing two separate legends: one with lines and points, one with labels. x.intersp can be used to tweak distance between labels and lines.
plot(cumsum(runif(n = 100)))
# draw legend with lines and point but without labels and box. x.intersp controls horizontal distance between lines
L = legend(x = 'bottom', legend = rep(NA,4), col=1:2, lty=c(1,1,2,2), ncol=2, bty='n', x.intersp=0.5, pch=c(1,2,1,2), inset=0.02)
# use position data of previous legend to draw legend with invisble lines and points but with labels and box. x.intersp controls distance between lines and labels
legend(x = L$rect$left, y = L$rect$top, legend = c('Group A', 'Group B'), col=rep(NA,2), lty=c(1,1), ncol=1, x.intersp = 3, bg = NA)
Check this:
library(lattice)
myPCH <- 15:17
Data <- rnorm(50)
Index <- seq(length(Data))
xyplot(Data ~ Index,
pch = myPCH, col=1:2,
key = list(space = "right", adj=1,
text = list(c("a", "b", "c"), cex=1.5),
points = list(pch = myPCH),
points = list(pch = myPCH,col=2)))
It looks like Victorp answered this in the comments of the original post. The ncol argument in the legend function works for me:
legend(locator(1), legend=c("name1","name2", "name3", "name4"), lty=2, col=c("black", "blue", "dark green", "orange"), ncol=2)
enter image description here
heights1=c(5,5,4.5,4,4,4,4.5,2,4,4)
opar <- par(lwd = 0.3)
barplot(heights1,xlim=c(0,3), ylim=c(0,5), width=0.1,
main="Langauges(Verbal & Non-verbal)",
names.arg=c("Spanish", "Speak" , "English","Speak", "Hindi",
"Speak", "Arabic", "Speak", "Body Lang", "Speak"), ylab="Skill level ",
xlab="Language starting with mostly used", col=c("darkblue","red"),
cex.names=0.7,space=c(2,0,2,0,2,0,2,0,2,0))
legend("top", c("darkblue","red"), c("reading/Writing", "Speaking") );
Blue is for "reading/writing" and red is for "speaking". How do I make correction in legend? (I do not want to define legend inside barplot function)
You can use the fill argument for your colors. As with David Robinson's answer, I would also suggest placing the legend in the top right in this case.
legend("topright",
legend = c("reading/Writing", "Speaking"),
fill = c("darkblue", "red"))
Looking at some of your other questions, you might also want to spend some time getting your data into a more appropriate form before plotting.
Here's an example:
Here is your data:
heights1 = c(5, 5, 4.5, 4, 4, 4, 4.5, 2, 4, 4) # Your data
Here is your data as a matrix with appropriate dimnames
mydata <- matrix(heights1, ncol = 2, byrow = TRUE,
dimnames = list(c("Spanish", "English", "Hindi",
"Arabic", "Body Lang"),
c("Reading/Writing", "Speaking")))
mydata # Much more meaningful to look at than a simple vector
# Reading/Writing Speaking
# Spanish 5.0 5
# English 4.5 4
# Hindi 4.0 4
# Arabic 4.5 2
# Body Lang 4.0 4
Define your colors (optional, but useful if you are working with more than just a pair of bars per group)
colors <- c("darkblue", "red") # Define the colors you're using
Plot your data adding a little bit of extra space at the top and suppressing your axes. Not sure why you don't want to include the legend at this stage, but it could easily be done by adding the following arguments: legend.text = TRUE, args.legend = list(x = "topright", bty = "n")
barplot(t(mydata), beside = TRUE, col = colors,
ylim = c(0, 6), axes = FALSE,
xlab = "Language starting with mostly used",
main = "Languages (Verbal & Non-verbal)")
Reintroduce your y-axis and add your legend
axis(2, at = 0:5, labels = 0:5)
legend("topright", colnames(mydata), fill = colors, bty = "n")
Change your legend line to
legend("topright", c("reading/Writing", "Speaking"), col=c("darkblue","red"), lwd=10);
The lwd argument says the legend should have lines of 10 pixel thickness of each of the corresponding colors. It's a good idea to use "topright" rather than "top" in your case so that the legend doesn't appear under the bars.
Earlier I asked about creating a gradient of n values in base graphics (LINK). Now I'd like to create a gradient legend that goes with it. My ideal would be something like ggplot2's gradient legends:
Here's some code similar to what I'm working with:
colfunc <- colorRampPalette(c("red", "blue"))
plot(1:20, 1:20, pch = 19, cex=2, col = colfunc(20))
Here is an example of how to build a legend from first principles using rasterImage from grDevices and layout to split the screen
layout(matrix(1:2,ncol=2), width = c(2,1),height = c(1,1))
plot(1:20, 1:20, pch = 19, cex=2, col = colfunc(20))
legend_image <- as.raster(matrix(colfunc(20), ncol=1))
plot(c(0,2),c(0,1),type = 'n', axes = F,xlab = '', ylab = '', main = 'legend title')
text(x=1.5, y = seq(0,1,l=5), labels = seq(0,1,l=5))
rasterImage(legend_image, 0, 0, 1,1)
Late to the party, but here is a base version presenting a legend using discrete cutoffs. Thought it might be useful for future searchers.
layout(matrix(1:2,nrow=1),widths=c(0.8,0.2))
colfunc <- colorRampPalette(c("white","black"))
par(mar=c(5.1,4.1,4.1,2.1))
plot(1:10,ann=FALSE,type="n")
grid()
points(1:10,col=colfunc(10),pch=19,cex=1.5)
xl <- 1
yb <- 1
xr <- 1.5
yt <- 2
par(mar=c(5.1,0.5,4.1,0.5))
plot(NA,type="n",ann=FALSE,xlim=c(1,2),ylim=c(1,2),xaxt="n",yaxt="n",bty="n")
rect(
xl,
head(seq(yb,yt,(yt-yb)/10),-1),
xr,
tail(seq(yb,yt,(yt-yb)/10),-1),
col=colfunc(10)
)
mtext(1:10,side=2,at=tail(seq(yb,yt,(yt-yb)/10),-1)-0.05,las=2,cex=0.7)
And an example image:
The following creates a gradient color bar with three pinpoints without any plot beforehand and no alien package is needed. Hope it is useful:
plot.new()
lgd_ = rep(NA, 11)
lgd_[c(1,6,11)] = c(1,6,11)
legend(x = 0.5, y = 0.5,
legend = lgd_,
fill = colorRampPalette(colors = c('black','red3','grey96'))(11),
border = NA,
y.intersp = 0.5,
cex = 2, text.font = 2)
As a refinement of #mnel's great answer, inspired from another great answer of #Josh O'Brien, here comes a way to display the gradient legend inside the plot.
colfunc <- colorRampPalette(c("red", "blue"))
legend_image <- as.raster(matrix(colfunc(20), ncol=1))
## layer 1, base plot
plot(1:20, 1:20, pch=19, cex=2, col=colfunc(20), main='
Awesome gradient legend inside')
## layer 2, legend inside
op <- par( ## set and store par
fig=c(grconvertX(c(0, 10), from="user", to="ndc"), ## set figure region
grconvertY(c(4, 20.5), from="user", to="ndc")),
mar=c(1, 1, 1, 9.5), ## set margins
new=TRUE) ## set new for overplot w/ next plot
plot(c(0, 2), c(0, 1), type='n', axes=F, xlab='', ylab='') ## ini plot2
rasterImage(legend_image, 0, 0, 1, 1) ## the gradient
lbsq <- seq.int(0, 1, l=5) ## seq. for labels
axis(4, at=lbsq, pos=1, labels=F, col=0, col.ticks=1, tck=-.1) ## axis ticks
mtext(sq, 4, -.5, at=lbsq, las=2, cex=.6) ## tick labels
mtext('diff', 3, -.125, cex=.6, adj=.1, font=2) ## title
par(op) ## reset par