Search code examples
rplotpch

Automatically resizing a custom plotting character?


Hey everybody I am hoping there is someone out there who can help me with plotting a custom made pch in R. In my case I am trying to create a graph relating to water quality over time. One way to monitor water quality is to use a secchi disc, which is basically a metal disc with quarters of the disc painted black and white (see the plot below).

So far I've been able to successfully create the disc and put it on a plot, but my problem comes with a stretching effect depending on the dimensions of the plot.

Here are the function I've used to create the disc so far:

# A circle function to draw a circle (pulled form another stackoverflow page)

circleFun <- function(center=c(0,0), diameter=1, npoints=100, start=0, end=2, filled=TRUE){
  tt <- seq(start*pi, end*pi, length.out=npoints)
  df <- data.frame(
    x = center[1] + diameter / 2 * cos(tt),
    y = center[2] + diameter / 2 * sin(tt)
  )
  if(filled==TRUE) { #add a point at the center so the whole 'pie slice' is filled
    df <- rbind(df, center)
  }
  return(df)
}

# and my function to create a secchi disc
secchiDisc = function(x,y,diameter = 1){
  quarterCircle1 = circleFun(c(x,y),diameter = diameter, start=0, end=0.5)
  quarterCircle3 = circleFun(c(x,y),diameter = diameter, start=1, end=1.5)
  fullCircle = circleFun(c(x, y), diameter, start=0, end=2)
  polygon(quarterCircle1$x,quarterCircle1$y,col="black")
  polygon(quarterCircle3$x,quarterCircle3$y,col="black")
  polygon(fullCircle$x,fullCircle$y)
}

# make a plot to show what it looks like
par(mar = c(5, 4, 4, 2) + 0.1)
plot(0,0,pch = "")
secchiDisc(0,0)

enter image description here

# create data frame 
data = as.data.frame(list(Year = 1970:2015,
                          Depth = rnorm(46,3,1)))

# and create a time series plot showing changes in depth over time
plot(data$Year,data$Depth,pch="",ylim = c(7,0))
for(i in 1:nrow(data)){
  secchiDisc(data$Year[i],data$Depth[i])
}



So here is how it looks when plotting with a time series: enter image description here

And obviously I can just stretch the plot horizontally until it cleans up, but I was wondering if anyone out there has any suggestions on how to automatically get the disc to re-size based on the plot? enter image description here

I've tried to customize the circle functions to allow for a stretch effect, but to no avail. I think one of the problems with the 'stretch effect' approach is that I still want the disc to appear circular, but I couldn't get the diameter of the circle to change independently of the x or y dimension.

Another thought I had was to save a blank plot of the secchi disc as a png file and try to plot the imported file. Thoughts on this approach?

Thanks for any help.


Solution

  • I think I have a solution. If not, I must be very close. The problem is that in your function, the diameter is the same for the x-axis and the y-axis, independently of range of data. Said differently, the range of the Years axis is 45 and the range of the Depth axis is ~5. When you try to apply the same diameter in units to both axis, the shape of the circle is deformed. My solution is to calculate a ratio of the ranges of the x-axis and y-axis. The ratio is then applied to the sin line in circleFun. I'm not sure if the ratio should be divided by sqrt(2) but it works. Also, you should not resize your plotting window manually after plotting the chart. use win.graph in windows or quartz() in mac before the plot.

        # A circle function to draw a circle (pulled form another stackoverflow page)
    
    circleFun <- function(center=c(0,0), diameter=1, npoints=100, start=0, end=1, filled=TRUE,ratio=1){
      tt <- seq(start*pi, end*pi, length.out=npoints)
      df <- data.frame(
        x = center[1] + diameter / 2 * cos(tt),
        y = center[2] + diameter/ratio / 2 * sin(tt)
      )
      if(filled==TRUE) { #add a point at the center so the whole 'pie slice' is filled
        df <- rbind(df, center)
      }
      return(df)
    }
    
    # and my function to create a secchi disc
    secchiDisc = function(x,y,diameter = 1,ratio=1){
      quarterCircle1 = circleFun(c(x,y),diameter = diameter, start=0, end=0.5,ratio=ratio)
      quarterCircle3 = circleFun(c(x,y),diameter = diameter, start=1, end=1.5,ratio=ratio)
      fullCircle = circleFun(c(x, y), diameter, start=0, end=2,ratio=ratio)
      polygon(quarterCircle1$x,quarterCircle1$y,col="black")
      polygon(quarterCircle3$x,quarterCircle3$y,col="black")
      polygon(fullCircle$x,fullCircle$y)
    }
    
    data = as.data.frame(list(Year = 1970:2015,
                              Depth = rnorm(46,3,1)))
    
    xx <-diff(range(data$Year))
    yy <-diff(range(data$Depth))
    ratio=(xx/yy)/sqrt(2)
    plot(data$Year,data$Depth,pch="")
    for(i in 1:nrow(data)){
      secchiDisc(data$Year[i],data$Depth[i],diameter = 1.5,ratio=ratio)
    }
    

    enter image description here