I'd like to add text to the right outer margin of multiple plots that is parallel to the axis but oriented towards the center of the plot (the orientation of the words "red" and "blue" in the below plot:
par(mfcol=2:1)
curve(sin,-2*pi,2*pi,col=2)
limits <- par("usr")
text(limits[2]+.25, mean(limits[3:4]),
"red", srt=270, xpd=T)
curve(sin,-2*pi,2*pi,col=4)
text(limits[2]+.25, mean(limits[3:4]),
"blue", srt=270, xpd=T)
mtext("Color of line",side=4,outer=T)
If the mtext function used the srt parameter rather than las (which was apparently the case for S plus), this would be trivial and the above workaround using usr would be unnecessary. But I'd like to be able to orient text in the outer margin ("Color of line" above) this same way, which I appear to be unable to do even manually with text (using xpd=T still constrains the text to the most recent figure region rather than the device region).
Is there a way to do this that doesn't require using layout as in the answer of #mrflick here? This seems like it should be trivial but I don't see how it can be done.
To find the y coordinates of the center of the device, you can use grconvertY to convert from "normalized device coordinates" ("ndc"; ranges from 0 to 1) to user coordinates.
The x value is here simply adjusted with an appropriate factor (e.g. limits[2] * 1.2).
windows()
par(mfrow = c(2, 1), oma = c(0, 0, 0, 2))
curve(sin, -2*pi, 2*pi, col = 2)
limits <- par("usr")
text(limits[2] + 0.25, mean(limits[3:4]),
"red", srt = 270, xpd = TRUE)
curve(sin, -2*pi, 2*pi, col = 4)
text(limits[2] + 0.25, mean(limits[3:4]),
"blue", srt = 270, xpd = TRUE)
text(x = limits[2] * 1.2, y = grconvertY(0.5, from = "ndc"),
labels = "color of line", xpd = NA, srt = 270)
Please see previous revisions if you rather want to calculate y position from user coordinates ("usr") and plot margins ("mai").
Related
I'm trying to use base R to plot an image with points overlaid on top, but the image is not taking up the whole plot area. How can I make sure the image is utilizing the whole plot area (essentially making it larger) while still maintaining the spatial boundaries provided by points()? For example:
library(EBImage)
library(magrittr)
url <- "https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png"
img <- EBImage::readImage(url)
if (Sys.info()[["sysname"]] == "Linux") {
img <- EBImage::rotate(img, 180) %>%
EBImage::flop(.)
}
res = dim(img)[1:2]
par(bg = 'grey')
plot(1,1,xlim=c(0,res[1]),ylim=c(res[2],0),asp=1,type='n',xaxs='i',yaxs='i',
xaxt='n',yaxt='n',xlab='',ylab='',bty='n')
graphics::rasterImage(img,1,1,res[1],res[2])
points(266,266, pch = 20, cex = 0.75, col="red")
points(329, 266, pch = 20, cex = 0.75, col="red")
points(315, 318, pch = 20, cex = 0.75, col="red")
Results in an image like this:
Ideally, I'd like to have the image fill the entire area of the plot (in grey).
By default, R allows a certain margin around the figure region to allow for labels, axes, and annotations. The default is c(5, 4, 4, 2) + 0.1.
To change these margins, do this:
par(bg = 'grey', mar=c(0,0,0,0))
I have drawn the following plot using the circlizepackage. The red circle is the unit circle drawn afterwards, using plotrix. I want to plot the first track outside the red unit circle. For this reason I changed canvas.xlim and canvas.ylim to c(-1.2, 1.2). However, this does not work. Any ideas how to increase the circle radius for the circlize plot?
NOTE: Alternatively, it would be sufficient for me, if the tracks would be outside of the unit circle instead of inside.
library(circlize)
set.seed(2)
n = 10
a = data.frame(factor = "dummy",
x = rnorm(n, 100, sd=10))
circos.par(track.height = 0.2,
canvas.xlim=c(-1.2, 1.2), # bigger canvas?
canvas.ylim=c(-1.2, 1.2)) # bigger canvas?
circos.initialize(factors = a$factor,
x = a$x, xlim = c(0, 360))
lim <- c(-1.2, 1.2)
plot(NULL, asp=1, xlim=lim, ylim=lim)
circos.trackHist(a$factor, a$x, col = "blue", bg.col = grey(.95))
plotrix::draw.circle(0,0,1, border="red", lwd=2) # unit circle
I don't know how to adjust xlim and ylim to make two plots fit. But if you just want to put the red circle inside the track, you can use draw.sector() function directly:
circos.initialize(factors = a$factor,
x = a$x, xlim = c(0, 360))
circos.trackHist(a$factor, a$x, col = "blue", bg.col = grey(.95))
draw.sector(0, 360, rou1 = circlize:::get_most_inside_radius(),
border = "red")
Here circlize:::get_most_inside_radius() returns the distance between the bottom border of the last track to the center of the circle.
Is there a way to draw the lines in such a way that they would start on the side of the points, or allow the symbols to be in foreground?
My solution was to make the symbols bigger and more visible.
Edit 1: it's for plot {graphics} of the R program.
Edit 2: the code per popular request.
legend(2,.4,bty='n', c('sugar','citrus','none'), pch=c('s','c','u'), pt.bg='white',lty= c(1,2,3), lwd=1.5, title="Condition",pt.cex=c(1.5),cex=1.5)
Edit 3: This is solved for plot(type='b') but somehow not for legend.
Thanks for reading!
The only thing I can come up with is to manually finagle the dash lengths until they end up looking the way you want them. For instance, this:
> plot(1,1)
> legend(c("A", "B"), col = 1:2, x = 1, y = .8, lty="99", pch=1:2)
produces the image below.
The lty parameter allows you to specify the lengths of lines and dashes as hex characters. In this case, it's saying to create a line of length 9 then create a space of length 9 then repeat. It looks like 9 is about the best fit to space around a normal pch symbol.
Note that you'd probably need to adjust this depending on the size of the image, symbol, etc. My advice ultimately would be to export the image from R and touch up the image to meet your needs in graphic editing software.
Going with the suggestion by #JeffAllen, here is a way to get what I think you might want. It requires modifying the legend() function to return the position of the points (these are given by x1 and y1 in body(legend)[[46]]).
legend2 <- legend
body(legend2)[[49]] <- quote(
invisible(list(rect = list(w = w, h = h, left = left, top = top),
text = list(x = xt, y = yt), points = list(x = x1, y = y1)))
)
Make a plot:
plot(-100:100, -100:100, type = "b")
While drawing the legend, draw white circles (pch = 21 with pt.bg = 'white') over the lines, and assign the values invisibly returned by legend2() to an object. Note also the changes to pt.lwd and pt.cex.
myLegend <- legend2(1, .8, bty = 'n', c('sugar','citrus','none'), pch = 21,
pt.bg = 'white', pt.lwd = 0, lty = c(1, 2, 3), lwd = 1.5, title = "Condition",
pt.cex = c(1.8), cex = 1.5)
Finally, draw the characters you'd like to use in the legend using points(), supplying the x and y values from the object myLegend.
points(myLegend$points$x, myLegend$points$y, pch = c('s','c','u'), cex = 1.5)
And this should get you something like:
You could also use the filled points offered by R (pch=21:25) and specify the fill color using pc.bg which gets passed to the points call when creating a legend.
plot(1,1)
legend(c("A", "B"), col = 1:2, x = 1, y = .8, lty=1, pt.bg=1:2, pch=21:22)
generates the following:
I am trying to remove all margins and the "figure region" of a plot in R, so that the plot region comprises the entire graphic device. I thought the code below would do it, but there is still a border around my plot (wider on left/bottom, thinner on top/right). Thanks
par(oma=c(0, 0, 0, 0))
par(mar=c(0, 0, 0, 0))
par(plt=c(0, 1, 0, 1))
Thought I would add a picture to show my progress. The xaxs and yaxs removed nearly all border from the top and right- there is still a border on the left and bottom.
The relevant portion of my script is below.
png("Test.png",
width = 256, height = 256,
units = "px", pointsize = 6.4,
bg = "black", res = NA)
par(mar=c(0, 0, 0, 0), xaxs='i', yaxs='i')
smoothScatter(lhb$px, lhb$pz, nrpoints=0, xlim=c(-3,3), ylim=c(0,5),
main="", xlab="", ylab="", axes=FALSE,
colramp=colorRampPalette(c("black", "#202020", "#736AFF", "cyan", "yellow", "#F87431", "#FF7F00", "red", "#7E2217"))
)
segments(.83, 1.597, .83, 3.436, col = par("fg"), lty = par("lty"), lwd = par("lwd"))
segments(-.83, 1.597, -.83, 3.436, col = par("fg"), lty = par("lty"), lwd = par("lwd"))
segments(-.83, 3.436, .83, 3.436, col = par("fg"), lty = par("lty"), lwd = par("lwd"))
segments(-.83, 1.597, .83, 1.597, col = par("fg"), lty = par("lty"), lwd = par("lwd"))
dev.off()
One issue is fundamentally not getting what plt does. From ?par we have:
‘plt’ A vector of the form ‘c(x1, x2, y1, y2)’ giving the
coordinates of the plot region as fractions of the current
figure region.
So your plot region is of zero size if you do par(plt=c(1, 1, 1, 1)), so that doesn't seem to be the way to go. This is because the figure region contains the plot region.
This plot seems to cover the entire region, without any margins:
op <- par(mar = rep(0, 4))
plot(1:10)
par(op)
it covers it so well you can't see the axes or the box:
This assumes the default for 0 outer margin (oma). Is this what you were looking for?
We can see that just adjusting the plot margins, as above, we also change the plt parameter as a side effect:
> par("plt")
[1] 0.1173174 0.9399106 0.1457273 0.8828467
> op <- par(mar = rep(0, 4))
> par("plt")
[1] 0 1 0 1
> par(op)
> par("plt")
[1] 0.1173174 0.9399106 0.1457273 0.8828467
indicating that simply setting the plot margins is sufficient to get a plot/figure region encompassing the entire device.
Of course, there is still a bit of internal padding that insures the ranges of the axes are slightly large than the range of the data in both the x and y coordinates. But you can control this with xaxs and yaxs --- see ?par
Update: As the OP has shown the sort of figure they are trying to produce without margins, I can provide a reproducible example:
set.seed(1)
dat <- matrix(rnorm(100*100), ncol = 100, nrow = 100)
layout(matrix(1:2, ncol = 2))
image(dat)
op <- par(mar = rep(0, 4))
image(dat)
par(op)
layout(1)
which gives for comparison:
and showing just the full plotting region:
Try setting the clip region parameter 'xpd' to NA ( clipped to device).
par(xpd = NA)
I am creating a boxplot in R with the following code:
boxplot(perc.OM.y ~ Depth, axes = F, ylim = c(-0.6, 0.2), xlim = c(3.5, 5.5),
lwd = 0.1, col = 8,
ylab = "Loss of Percent Organic Matter per Year", cex.lab = 1.5)
axis(1, at = c(3.5, 4, 5, 5.5), labels = c(" ", "Shallow", "Deep", " "),
cex.axis = 1.5)
axis(2, cex.axis = 1.5)
The problem is that the number labels on the y-axis currently overlap the axis title. Is there a way to put more space between the axis title and the axis number labels?
Thanks
## dummy data
dat <- data.frame(Depth = sample(c(3:6), 20, replace = TRUE), OM = 5 * runif(20))
Add some room for the y-axis labels and annotations, by making the margin bigger on the left hand side of the plot (side = 2):
## margin for side 2 is 7 lines in size
op <- par(mar = c(5,7,4,2) + 0.1) ## default is c(5,4,4,2) + 0.1
Now plot:
## draw the plot but without annotation
boxplot(OM ~ Depth, data = dat, axes = FALSE, ann = FALSE)
## add axes
axis(1, at = 1:4, labels = c(" ", "Shallow", "Deep", " "), cex.axis = 1.5)
axis(2, cex.axis = 2)
## now draw the y-axis annotation on a different line out from the plot
## using the extra margin space:
title(ylab = "Loss of Percent Organic Matter per Year", cex.lab = 1.5,
line = 4.5)
## draw the box to finish off
box()
Then reset the plotting margins:
par(op)
This gives:
So we've created more space for the margin of the plot on side 2, and then drawn the axes and the annotation (ylab) separately to control how the plot is spaced out.
So the key to this is this line:
op <- par(mar = c(5,7,4,2) + 0.1) ## default is c(5,4,4,2) + 0.1
What we do is save the original graphical parameters in object op, and change the margin sizes (in numbers of lines) to be 5, 7, 4, 2 + 0.1 lines each for the bottom , left, top, right margins respectively. The line above shows the defaults, so the code gives 2 more lines on the left margin than usually provided by default.
Then when we draw the y-axis label using title(), we specify which line (of the 7) to draw the label at:
title(ylab = "Loss of Percent Organic Matter per Year", cex.lab = 1.5,
line = 4.5)
Here I used line 4.5, but 5 would work also. The greater the values of 'line' the farther from the plot the label is drawn.
The trick is to find the value for the left margin and the value of 'line' in the title() call that allows the axis tick marks and the axis label to not overlap. Trial and error is likely the best solution to find the values you need with base graphics.
Try setting the first value of mgp larger. You'll want to make the margins bigger too, with mar.
par(mgp=c(5,1,0))
par(mar=c(5,6,4,2)+0.1)
I just found this solution very straightforward and useful when I wanted to shrink the white space around the diagram (consider size limits in the conference papers!) while I wanted to avoid overlapping Y-axes title and big numbers as the ticks.
I use to set the titles as text and put them wherever I want, after setting the margins manually:
First, set the margins to the arbitrary values:
par( mar=c(m1, m2, m3, m4) )
where m1 to m4 are margins for four sides (1=bottom, 2=left, 3=top and 4=right).
For example:
par( mar=c(3.1, 4.7, 2.3, 0))
Then, when plotting, set main="", xlab="" and ylab="" (otherwise their text will overlap with this new text)
Finally, using mtext(), set the axis titles and diagram title manually:
mtext(side=1, text="X axes title", line=0.5)
mtext(side=2, text="Y axes title", line=3)
mtext(side=3, text="Diagram title", line=1.5)
The line parameter is the distance from the diagram (the smaller values puts it closer to the diagram).