When writing a plotting function in R, I'd like to not modify the global environment, so I include something like
op <- par()
on.exit(par(op))
But this is less than satisfactory because it spits out warning messages (e.g., "In par(op) : graphical parameter "cin" cannot be set"), but more importantly, it is not compatible with multi-panel plots. For example, if I had a simple function like
pfun <- function(x) {
op <- par()
on.exit(par(op))
par(bg = "gray21", col = "deeppink", col.axis = "deeppink")
plot(x,
xaxt = "n",
yaxt = "n",
col = "deeppink",
cex = 2,
pch = 22,
bg = "deeppink",
col.lab = "deeppink")
axis(1, col = "deeppink")
axis(2, col = "deeppink")
}
it would work great for a single plot (apart from the warnings), but is incompatible with multi-panel plots, e.g.
par(mfrow = c(2, 2))
pfun(1:10)
pfun(10:1) # overwrites the first plot rather than plotting in the second panel
Is there a way to have the plot parameters reset on exit while also allowing for multi-panel plotting?
We can avoid interfering with multi-panel plots, by only saving /restoring the elements of par that we change in the function. In this case that means only storing bg, col, and axis.col. The important thing is to avoid interfering with the graphical parameters (particularly mfrow, mfcol and mfg) that control multiplot positions.
pfun <- function(x) {
op <- par('bg', 'col', 'col.axis')
on.exit(par(op))
par(bg = "gray21", col = "deeppink", col.axis = "deeppink")
plot(x,
xaxt = "n",
yaxt = "n",
col = "deeppink",
cex = 2,
pch = 22,
bg = "deeppink",
col.lab = "deeppink")
axis(1, col = "deeppink")
axis(2, col = "deeppink")
}
Or, even slightly neater is to make use of the fact that when we set parameters with par it invisibly returns a list of the old values of the parameters we changed. So just the following will work nicely:
op <- par(bg = "gray21", col = "deeppink", col.axis = "deeppink")
on.exit(par(op))
Related
I have the following function
C=15
S=seq(0,12,.1)
pA=1
pS=0.5
A <- function(S) (C/pA)-(pA/pS)*S
plot (S, A(S), type="l", col="red", ylim=c(0,15), xlim=c(0,15), xlab="S", ylab="A")
text(8, 11.5, "Function", col = "red", cex=.9)
text(11, 10.2, expression(A==frac(C,p[S])-frac(p[A],p[S])%.%S), cex=.9, col = "red")
grid()
How can I scale the axis so that I can "zoom out" a bit. I am looking for a function similar to ylim but where I could say: ylim=c(0,15, by=1).
Update
It seems your "zoom out" is meant for the grid lines, not for the axis or anything. I'll keep the previous answer below, but this should address it, I think.
# no change yet
plot (S, A(S), type="l", col="red", ylim=c(0,15), xlim=c(0,15), xlab="S", ylab="A")
text(8, 11.5, "Function", col = "red", cex=.9)
text(11, 10.2, expression(A==frac(C,p[S])-frac(p[A],p[S])%.%S), cex=.9, col = "red")
# no call to grid, since we want to control it a little more precisely
abline(h = seq(0, 15), col = "lightgray", lty = "dotted", lwd = par("lwd"))
abline(v = seq(0, 15, by = 5), col = "lightgray", lty = "dotted", lwd = par("lwd"))
You can do just abline(h=seq(...)) and not set col=, lty=, or lwd=, but realize that abline's default values are different from grid's defaults. I used those values to mimic grid's look and feel.
Old answer
I think you mean to change the y axis labels, not the y axis limits (which is all that ylim= can effect).
Using base R graphics:
plot (S, A(S), type="l", col="red", ylim=c(0,15), xlim=c(0,15), xlab="S", ylab="A",
yaxt = "n") # this is new
text(8, 11.5, "Function", col = "red", cex=.9)
text(11, 10.2, expression(A==frac(C,p[S])-frac(p[A],p[S])%.%S), cex=.9, col = "red")
grid()
axis(2, at = 1:15, las = 2)
Explanation:
plot(..., yaxt = "n") means to not plot the y-axis ticks and labels, see ?par.
axis(...) adds an axis to a side. By itself, it will just add a default axis.
at= sets where the ticks and labels are placed.
labels= sets what to put at each tick. If absent (like it is above), it just prints the at value.
las=2 rotates the number so that it is perpendicular to the axis line. I did this to show more of the numbers, otherwise they will mask a bit more.
While this shows every-other, that is affected by the size of the canvas; if you do the same plot in full-screen, it'll show every number.
I want to plot with a certain line width (about 1/5 of default lwd). All lines in the plot should have this lwd.
I do:
par(lwd = 0.2)
plot(1:10, type = "l")
The result is ok except for the thickness of the axes lines and ticks, which seems to be unaffected by the par() function.
The only option I know is to separately define the lwd for each axis:
par(lwd = 0.2)
plot(1:10, type = "l", axes = F)
axis(1, lwd = 0.2)
axis(2, lwd = 0.2)
box()
However, this is tedious and I can not imagine that there is no "global" lwd option. If you have an idea how this could be done efficiently for several plots please respond.
If we look at the formals of axis() function - default lwd is specified as 1:
> axis
function (side, at = NULL, labels = TRUE, tick = TRUE, line = NA,
pos = NA, outer = FALSE, font = NA, lty = "solid", lwd = 1,
lwd.ticks = lwd, col = NULL, col.ticks = NULL, hadj = NA,
padj = NA, ...)
{
And as you noticed they are not affected by the par() setting in this implementation.
One simple solution would be to make a wrapper for axis() function and make it use the par() setting by default. Here is how that might look like:
axislwd <- function(...) axis(lwd=par()$lwd, ...)
par(lwd = 0.2)
plot(1:10, type = "l", axes = F)
axislwd(1)
axislwd(2)
box()
Alternatively you can write a wrapper for the whole plot function instead:
plotlwd <- function(...) {
plot(axes = FALSE, ...)
axis(1, lwd=par()$lwd)
axis(2, lwd=par()$lwd)
box()
}
par(lwd = 0.2)
plotlwd(1:10, type="l")
I am having the following issue with the axis() function.
axis(1,
at=1:length(stringi::stri_rand_strings(21, 15)),
labels=stringi::stri_rand_strings(21, 15),
tick=1,
lwd=1,
mgp = c(0,1,0),
col = title_colour,
col.ticks = title_colour
,lty = "solid",
cex.axis = 1,las=2,cex=0.75)
But whatI really need are the tickmarks without the continuous x'x line connecting the ticks:
How do I accomplish this using axis()??
Set col to NA but col.ticks to a value:
plot(1, type = 'n', axes = FALSE)
axis(1, c(0.75, 1, 1.25), col = NA, col.ticks = 1)
(Note my reproducible and minimal example, try to include that in your question!)
I am using R for plotting. When my graph plots the legend appears where I want it to be but the colors are missing. mtcars 2 is a modified version of mtcars (one of the pre-loaded data sets) that adds a model and country of origin to the data set. mtcars.pca is what I named my redundance analysis (rda function under vegan), and mtcars.clust is titled for hierarchical clustering of the continuous factors of mtcars (hclust function of vegan) Below is the code I am using with mtcars2.
pca.fig = ordiplot(mtcars.pca, type = "none", las=1, xlim=c(-15,15), ylim = c(-20,10))
points(pca.fig, "sites", pch = 19, col = "green", select = mtcars2$origin =="domestic")
points(pca.fig, "sites", pch = 19, col = "blue", select = mtcars2$origin =="foreign")
ordiellipse(mtcars.pca, mtcars2$origin, conf = 0.95, label = FALSE)
ordicluster(mtcars.pca, mtcars.clust, col = "gray")
legend("bottomright", title="Car Origin", c("domestic", "foreign"), col = "origin")
You need to specify a vector of colours in legend and also a pch:
library("vegan")
data(dune, dune.env)
ord <- rda(dune)
plot(ord, type = "n")
cols <- c("red","blue","green")
points(ord, col = cols[dune.env$Use], pch = 19)
legend("bottomright", legend = levels(dune.env$Use), bty = "n",
col = cols, pch = 19)
If you don't add pch but just use col = cols legend() doesn't display any points. Because you used pch = 19 in your points() calls, use the same in the legend() call.
Also, note how to plot points of different colours in a single pass. I have some examples and explanation that go through the indexing trick I used in my code above to achieve this in a blog post of mine from a few years ago: http://www.fromthebottomoftheheap.net/2012/04/11/customising-vegans-ordination-plots/
I came to this question having the next problem in xts object:
I wanted to plot all time-series in xts object with legend. Moreover, there were around 20.
I used (wrong):
plot(returns_xts)
addLegend(...)
Correct version:
plot(returns_xts, legend.loc = "bottomright", col=1:20, lty = 1)
There is legend.loc parameter
col = 1:20 generates colors for you
Result:
My problem concerns the making of a graph for a publication in R. I have used the plot function like follows:
plot(x=data$SL, y=data$BD, xlab = "SL (mm)", ylab = "BD (mm)", pch=data$pch)
SL ranges from 51.7 to 73.7 and BD from 13.5 to 20.4. Unfortunately I am not allowed to post images yet.
However, wanting to get rid of the box I used "axes=F". Problem now is lack of control over the axis function. I used:
axis(side=1, lwd=3, xpd=TRUE, at=c(min(data$SL):max(data$SL)))
axis(side=2, lwd=3, xpd=TRUE, at=c(min(data$BD):max(data$BD)))
Problem is that I can't manage to get the y- and x-axis to come together on the same point as in the plot with the box. How to let the x- and y- axis to touch each other?
Most likely setting xaxs = "i" and yaxs = "i" will help you getting the desired behaviour.
plot(c(1,2,3),c(2,4,6),axes=F,xaxs = "i",yaxs="i",xlim=c(0,3),ylim=c(0,6))
axis(side=1, lwd=3, xpd=TRUE, at=0:3)
axis(side=2, lwd=3, xpd=TRUE, at=seq(0,6,2))
Try box(bty='L') to draw only the left and bottom parts of the box. You could also just draw the lines yourself using lines, segments, or abline and using grconvertX and grconvertY functions to find the locations where to draw the lines.
I suggest that you follow the procedure you outlined and then use:
box(which = "plot", bty = "l")
e.g.:
plot.new()
plot.window(xlim = c(1, 18), ylim = c(2, 20))
points(1:18, 2:19, pch = 1, col = "#FF7F24", cex = 1.2)
lines(1:18, 2:19, col = "#FF7F24", lwd = 2)
axis(side = 1,
lwd = 0,
lwd.ticks = 1,
at = 1:18,
cex.axis = 0.9)
title(main = "Plot",
ylab = "Y-Axis")
legend("top",
legend = c("Legend"),
col = c("#FF7F24"),
text.col = c("#FF7F24"),
pch = 1,
bty = "n",
cex = 1.2)
axis(side = 2,
lwd = 0,
lwd.ticks = 1)
box(which = "plot", bty = "l")
You should pass the options lwd = 0 and lwd.ticks = 1 to your seperate axis() calls in order to prevent some parts of your axes to appear fatter than other parts of your axis because some get overlayed by your call to box() and some do not.
The solution of using box() at the end is, I think, more general in that you can use it when e.g. you cannot or do not want to pass bty = "l" in your plot.default or plot.window call.