## fancyaxis: Draw axis which shows minimum, maximum, quartiles ## and mean ## ## Copyright (C) 2005 Steven J. Murdoch ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## This is very much a work in progress and still of alpha ## quality. See the example file for usage. It currently does not ## deal with logarithmic scales properly and needs manual tweaking ## of several values to suit different data and output device ## resolution. Comments and suggestions are appreciated. My ## contact details can be found here: ## http://www.cl.cam.ac.uk/users/sjm217/#contact ## ## The design of the graph is based on a scatterplot presented in ## "The Visual Display of Quantitative Information", Edward Tufte. ## Thanks to Paul Murrell for assistance with handling the log axes ## ## $Id: fancyaxis.R 6927 2009-03-08 12:49:17Z sjm217 $ # Add a small amount of noise to a numeric vector, preserving minimum # and minimum clippedjitter <- function(x, ...) { # x: numeric to which jitter should be added. # ...: parameters passed to jitter() mi <- min(x) ma <- max(x) len=length(x) # Find a position for the min and max mipos <- ((1:len)[x==mi])[1] mapos <- ((1:len)[x==ma])[1] # The standard jittered data xj <- jitter(x, ...) # Find the elements which are outside the limits under <- xjma # Find the distance away from the limit dunder <- mi-xj[under] dover <- xj[over]-ma # Reflect over the limit repunder <- dunder+mi repover <- ma-dover # Replace out of limit values with the reflected ones xj[under] <- repunder xj[over] <- repover # Replace a jittered min/max with the original min/max xj[mipos] <- mi xj[mapos] <- ma # Return updated array xj } # Draw a rug plot, but ommit the baseline (actually, draw over it minimalrug <- function(x, lwd=0.7, tcl=0.3, ...) { # x: a numeric vector # ...: parameters passed to rug() # Rounded ends don't work well with erasing one end oldlend <- par(lend = "butt") on.exit(par(oldlend)) # Used for overwriting the axis line to leave tickmarks bg <- par("bg") if (bg == "transparent") bg <- "white" # Draw the rug rug(x, ticksize=NA, lwd=lwd, ...) # Acrobat shows "shadows" around a line erased with a line # of similar width, so use a thicker line overlwd=1 # Remove the baseline (... is put first to allow other the other # parameters to override it) axis(..., at=x, col=bg, tcl=0, label=FALSE, lwd=overlwd) } fancyaxis <- function(side, summ, at=NULL, mingap=0.5, digits=2, shiftfac=0.003, gapfac=0.003) { # side: like axis() # summ: a summary object, for example returned by summary() # mingap: the smallest gap permitted between two tickmarks, # expressed as a fraction of the default tickmark gap # digits: the number of digits to round minimum and maximum to # shiftfac: proportion of plot width used to offset the broken axis # gapfac: proportion of plot width used to leave for median gap # TODO: # Deal with case where length(axTicks)<2 # Deal with logarithmic axis case properly, as axTicks difference # is not uniform. # Get summary information amin <- summ[1] aq1 <- summ[2] amed <- summ[3] amean <- summ[4] aq3 <- summ[5] amax <- summ[6] # Find out the properties of the side we are doing parside <- if (side==1){ # Does the outside of the plot have larger or smaller vales flip <- 1 # Are we on the xaxis xaxis <- TRUE # Is this axis logarithmic islog <- par("xlog") # Is the other axis logarithmic otherlog <- par("ylog") # Relevant index of par("usr") 3 } else if (side==2) { flip <- 1 xaxis <- FALSE islog <- par("ylog") otherlog <- par("xlog") 1 } else if (side==3) { flip <- -1 xaxis <- TRUE islog <- par("xlog") otherlog <- par("ylog") 4 } else if (side==4) { flip <- -1 xaxis <- FALSE islog <- par("ylog") otherlog <- par("xlog") 2 } # Calculate default positions of ticks if (is.null(at)) ticks <- axTicks(side) else ticks <- at # Remove any ticks outside the range ticks <- ticks[(ticks>=amin) & (ticks<=amax)] # Calculate the minimum desired gap between ticks numticks <- length(ticks) if (islog) axgap <- (log10(ticks[numticks])-log10(ticks[numticks-1]))*mingap else axgap <- (ticks[numticks]-ticks[numticks-1])*mingap # Get new range of tickmarks numticks <- length(ticks) firsttick <- ticks[1] lasttick <- ticks[numticks] # If max tick will be too close to the last tick, replace it, # otherwise append it if (islog && (log10(amax) - log10(lasttick) < axgap)) { ticks[numticks]<-amax } else if (amax - lasttick < axgap) { ticks[numticks]<-amax } else { ticks<-c(ticks,amax) } # Similarly for first tick if (islog && (abs(log10(amin)-log10(firsttick)) < axgap)) { ticks[1]<-amin } else if (firsttick - amin < axgap) { ticks[1]<-amin } else { ticks<-c(amin, ticks) } # Format the labels. min and max should have as many # trailing zeros they were rounded to, the others # should have the minimum needed to represent the tick marks numticks <- length(ticks) # Min and max lmin <- format(round(ticks[1], digits), nsmall=digits, trim=TRUE) lmax <- format(round(ticks[numticks], digits), nsmall=digits, trim=TRUE) # The others middle <- format(ticks[2:(numticks-1)], trim=TRUE) # Combine them labels <- c(lmin,middle,lmax) # Draw the axis oldlend <- par(lend = "butt") on.exit(par(oldlend)) # Used for overwriting the axis line to leave tickmarks bg <- par("bg") if (bg == "transparent") bg <- "white" lwd=0.7 # Draw the axis and tickmarks axis(side, ticks, labels=FALSE, col="gray50", lwd=lwd) # Erase the axis overlwd=1 axis(side, ticks, labels=FALSE, col=bg, tcl = 0, lwd=overlwd) # Draw the labels axis(side, ticks, labels=labels, tick=FALSE) # Axis position base<-par("usr")[parside] # Width and height in user units plotwidth <- diff(par("usr")[1:2]) plotheight <- diff(par("usr")[3:4]) # Shift for the q2 and q3 axis from the base (in inches) shift <- par("pin")[1]*shiftfac*flip # Gap for the median gap <- par("pin")[1]*gapfac # Shift for the mean pointer away from the axis meanshift <- par("cin")[1]*0.5*flip # Scale lengths so both axes are equal on output device if (!xaxis) { # Y axis # Convert inches into user units shift <- shift/par("pin")[1]*plotwidth meanshift <- meanshift/par("pin")[1]*plotwidth gap <- gap/par("pin")[2]*plotheight } else { # X axis # Convert inches into user units shift <- shift/par("pin")[2]*plotheight meanshift <- meanshift/par("pin")[2]*plotheight gap <- gap/par("pin")[1]*plotwidth } if (islog) { # Log case on this axis (affects gap) lmed <- log10(amed) gapt <- 10^(lmed + gap) gapb <- 10^(lmed - gap) } else { # Linear case on this axis gapt <- amed + gap gapb <- amed - gap } # Position of q2 and q3 axis segments offset <- base + shift # Which segment is the mean in? if((amean>aq3) || (amean