4

I have a plot with two logarithmic axes. I'd like to add a circle to a certain position of the plot. I tried to use plotrix, but this does not give options for "log-radius".

# data to plot
x = 10^(-1 * c(5:0))
y = x ^-1.5

#install.packages("plotrix", dependencies=T)
# use require() within functions
library("plotrix")

plot (x, y, log="xy", type="o")
draw.circle(x=1e-2, y=1e2, radius=1e1, col=2)

How can I add a circle to my log-log plot?

Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
R_User
  • 10,682
  • 25
  • 79
  • 120

3 Answers3

6

As krlmlr suggests, the easiest solution is to slightly modify plotrix::draw.circle(). The log-log coordinate system distorts coordinates of a circle given in the linear scale; to counteract that, you just need to exponentiate the calculated coordinates, as I've done in the lines marked with ## <- in the code below:

library("plotrix")

draw.circle.loglog <- 
function (x, y, radius, nv = 100, border = NULL, col = NA, lty = 1,
    lwd = 1)
{
    xylim <- par("usr")
    plotdim <- par("pin")
    ymult <- (xylim[4] - xylim[3])/(xylim[2] - xylim[1]) * plotdim[1]/plotdim[2]
    angle.inc <- 2 * pi/nv
    angles <- seq(0, 2 * pi - angle.inc, by = angle.inc)
    if (length(col) < length(radius))
        col <- rep(col, length.out = length(radius))
    for (circle in 1:length(radius)) {
        xv <- exp(cos(angles) * log(radius[circle])) * x[circle]         ## <-
        yv <- exp(sin(angles) * ymult * log(radius[circle])) * y[circle] ## <-
        polygon(xv, yv, border = border, col = col[circle], lty = lty,
            lwd = lwd)
    }
    invisible(list(x = xv, y = yv))
}

# Try it out 
x = 10^(-1 * c(5:0))
y = x ^-1.5

plot (x, y, log="xy", type="o")
draw.circle.loglog(x = c(1e-2, 1e-3, 1e-4), y = c(1e2, 1e6, 1e2),
                   radius = c(2,4,8), col = 1:3)

enter image description here

Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
  • [See here](http://stackoverflow.com/questions/9265588/r-plotting-a-point-on-the-y-axis-when-the-x-axis-is-using-a-log-scale/9265840#9265840) for a related discussion. Depending on what you want the units of radius to mean, you may want to replace `exp(...)` with `10^(...)` in both the coordinate-exponentiating lines. – Josh O'Brien Apr 13 '13 at 07:57
  • OK, I've fixed the way that the radius argument is handled. Now, a radius of 10 means that a circle centered at 1 on the x-axis will have its left edge at 0.1 and its right edge at 10. (Note that a circle of radius 1 will be plotted as a point (i.e. with its right and left edges at the x-coordinate).) – Josh O'Brien Apr 14 '13 at 14:40
  • nice,... your draw.circle.loglog() should definitley be added to the plotrix library. – R_User Apr 15 '13 at 06:47
5

A work around would be to apply log10 explicitly.

plot (log10(x), log10(y), type="o")
draw.circle(x=log10(1e-2), y=log10(1e2), radius=log10(1e1), col=2)

Edit (using symbols):

plot (x, y, log="xy", type="o",xlim=c(1e-5,1), ylim=c(1,1e8))
par(new=T)
symbols(x=1e-2, y=1e2, circles=1e1, xlim=c(1e-5,1), ylim=c(1,1e8), 
        xaxt='n', yaxt='n', ann=F, log="xy")
Nishanth
  • 6,932
  • 5
  • 26
  • 38
  • there was another answer here, which suggested to use `symbols`, but unfortunately I cant change the radius of the circle. I'd also don't want to rewrite all my plotting scripts (adding nice axis ticks,...), only to adda circle. Isn't there another way like "adding a second layer with liniear axis and transparanet and draw a circle there" oder "calculating coordinates of a circle function and plot it"? I was so sure, that there is an easy way,... – R_User Apr 10 '13 at 11:52
  • Doesn't this skew the circle badly? – krlmlr Apr 12 '13 at 09:29
  • I still can't change the size of the circle. It's also strange that the circle is not positioned correctly, if `log="xy"` is left out. leaving it in gives an error. Anyway, +1 for `par(new=T)`. – R_User Apr 15 '13 at 07:02
3

The function draw.circle from the plotrix package looks like that on my system:

> draw.circle
function (x, y, radius, nv = 100, border = NULL, col = NA, lty = 1, 
    lwd = 1) 
{
    xylim <- par("usr")
    plotdim <- par("pin")
    ymult <- (xylim[4] - xylim[3])/(xylim[2] - xylim[1]) * plotdim[1]/plotdim[2]
    angle.inc <- 2 * pi/nv
    angles <- seq(0, 2 * pi - angle.inc, by = angle.inc)
    if (length(col) < length(radius)) 
        col <- rep(col, length.out = length(radius))
    for (circle in 1:length(radius)) {
        xv <- cos(angles) * radius[circle] + x
        yv <- sin(angles) * radius[circle] * ymult + y
        polygon(xv, yv, border = border, col = col[circle], lty = lty, 
            lwd = lwd)
    }
    invisible(list(x = xv, y = yv))
}
<environment: namespace:plotrix>

What happens here is essentially that the circle is approximated by a polygon of 100 vertices (parameter nv). You can do either of the following:

  • Create your own version of draw.circle that does the necessary coordinate transformation to "undo" the log transform of the axes.

  • The function invisibly returns the list of coordinates that are used for plotting. (If you pass a vector as radius, then only the coordinates of the last circle are returned.) You might be able to apply a transform to those coordinates and call polygon on the result. Pass appropriate values for border, col, lty and/or lwd to hide the polygon drawn by the functions itself.

The first version sounds easier to me. Simply replace the + x by a * x, same for y, inside the for loop, and you're done. Equivalently, for the second version, you subtract x and then multiply by x each coordinate, same for y. EDIT: These transformations are slightly wrong, see Josh's answer for the correct ones.

krlmlr
  • 25,056
  • 14
  • 120
  • 217
  • I learned a lot from your way of approaching the problem. Although I accepted Josh's answer, You'll get the +50, since: (1) Your answer was the foundation of Josh's answer, and you were first (2) You acknowledged Josh's answer (3) you have less reputation. Hope thats fine for Josh. – R_User Apr 15 '13 at 06:58
  • @Sven -- That's totally fine with me. I don't generally answer questions here unless I learn something by doing so, and this question was great in that regard. Plus, krlmlr's answer has the better narrative description of what's going on in `draw.circle`, which is worth a lot. Cheers to you both. – Josh O'Brien Apr 15 '13 at 07:09