Search code examples
rplotrangeaxis-labelsaxes

R: set axis ticks & range to include all points w/ pretty breaks


I have a function which builds line plots dynamically, hence need to solve my problem with a formula rather than manually setting ranges. (note to experts: I've written this in logical progression for myself & others later - apologies that this means more text; I find R help to be particularly fragmented and unhelpful for plotting, so hopefully this will help others).

The axes for the line plots I've generated have the right kinds of numbers of tick spacings (default, assumedly 'pretty breaks'?) but the axis range doesn't encompass the whole range of the data - some are outside. (you can ignore the rug plot deciles above the axis, though the first & last ones show the data range)

x axis length

So what I'd like is for the x axis width to encompass all of the data, but retain the pretty breaks, i.e. spread the min/max tick points out to nice values at or past the extreme values... ideally without lots of work! I don't mean to sound lazy, but it feels like something that would be a common desire. I was reading about at, including pretty and axTicks, also xaxt and xaxs and xpd.

In plot, xaxs controls the width the actual data line takes up within the plot, "i" extending it to the margins and "r" (or default) squeezing it in a little. Which is cool to know, but arguably isn't the 'axis' as one would expect. (setting here is "r"/default)

Help for xaxt lets one know that it's what you use to turn axis plotting off, but doesn't say what else it does / what they other types are.

In axis, at controls the axis line length, defaulting to axTicks, which defaults to xaxp. On the face of it, xaxp is what I want, allowing one to specify c(x1, x2, n), however the default x1 & x2 are inside the data range, rather than encompassing it.

axis help says:

"The axis line is drawn from the lowest to the highest value of at, but will be clipped at the plot region. By default, only ticks which are drawn from points within the plot region (up to a tolerance for rounding error) are plotted, but the ticks and their labels may well extend outside the plot region. Use xpd = TRUE or xpd = NA to allow axes to extend further"

Neither xpd = TRUE nor xpd = NA seem to change anything. xpd help says:

"If FALSE, all plotting is clipped to the plot region, if TRUE, all plotting is clipped to the figure region, and if NA, all plotting is clipped to the device region"

I.e. axis help says xpd controls axis extension, xpd help says xpd controls plotting clipping.

xaxp help says "See axTicks() for a pure R implementation of this"

axTicks "computes pretty tickmark locations, the same way as R does internally", and defaults to the at values for the relevant side, i.e. par("xaxp").

pretty seems scarily complex, and may well be what R uses internally to calculate the default tick spacing when applied to at; it would be cool to be able to edit the n argument of that and nothing else, i.e. specify the desired number of intervals given my plotting area size, but I'm happy to leave that if it means spending a day decompiling, understanding, then manually recompiling pretty! Also I wouldn't know whether the n of ticks in pretty does the same thing as n in xaxp, and which takes priority if so.

So: R help seems to use "axis" to mean both axis (xaxt, axis) and plotting (xaxs, xpd), there's no indication whether the parameter is controlled by par, plot, axis, or can be controlled by some combination of any of them (as usual). In trying to find the element responsible, axis says at which says axTicks which says xaxp which says axTicks which says at (= loop).

As a secondary, broader, meta question: am I doing something wrong here in terms of trying to find the answer to plotting problems? I read and read and the help is so regularly lacking, confusing, contradictory, and usually leads to an intuition black hole as to whether something should be controlled by par, plot or axis. Thanks in advance guys. Sorry this is long, I genuinely thought this would be a super simple option.

Minimal reproducible example:

png(filename="A.png", width=4*480, height=4*480, units="px", pointsize=80, bg="white", res=NA, family="", type="cairo-png")
par(mar=c(1.32,2,0.4,0.5), fig=c(0,1,0,1), las=1, lwd=8, bty="n", mgp=c(2,0.5,0), xpd=NA)
  plot(ChickWeight[,1:2],type="l",xaxs="i",yaxs="i")
dev.off()

Solution

  • The idea is to draw your own axes, not let plot use the default ones. Here's an example that seems to work,

    xx <- labeling::extended(min(ChickWeight[,1]), max(ChickWeight[,1]),
                             3, only.loose=TRUE) 
    yy <- labeling::extended(min(ChickWeight[,2]), max(ChickWeight[,2]),
                             3, only.loose=TRUE) 
    plot(range(xx),range(yy), t="n", xaxt="n", yaxt="n")
    points(ChickWeight[,1:2],type="p")
    axis(1, at = xx)
    axis(2, at = yy)