R/symbolmap.R

Defines functions default.symbolmap print.summary.symbolmap summary.symbolmap as.colourmap.symbolmap plan.legend.layout plot.symbolmap print.symbolmap update.symbolmap symbolmapparnames symbolmapdomain symbolmaptype

Documented in as.colourmap.symbolmap default.symbolmap plan.legend.layout plot.symbolmap print.summary.symbolmap print.symbolmap summary.symbolmap symbolmapdomain symbolmapparnames symbolmaptype update.symbolmap

##
## symbolmap.R
##
##   $Revision: 1.58 $  $Date: 2024/07/01 05:57:44 $
##

symbolmap <- local({

  known.unknowns <- c("shape", "pch", "chars",
                      "size", "cex",
                      "direction", "arrowtype", "headlength", "headangle", 
                      "col", "cols", "fg", "bg",
                      "lty", "lwd", "border", "fill",
                      "etch")

  trycolourmap <- function(...) {
    try(colourmap(...), silent=TRUE)
  }

  symbolmap <- function(..., range=NULL, inputs=NULL) {
    if(!is.null(range) && !is.null(inputs))
      stop("Arguments range and inputs are incompatible")
    ## graphics parameters
    parlist <- list(...)
    ## remove unrecognised parameters and NULL values 
    if(length(parlist) > 0) {
      ok <- names(parlist) %in% known.unknowns
      ok <- ok & !unlist(lapply(parlist, is.null))
      parlist <- parlist[ok]
    }
    got.pars <- (length(parlist) > 0)
    parnames <- names(parlist)
    type <- if(is.null(inputs) && is.null(range)) "constant" else
            if(!is.null(inputs)) "discrete" else "continuous"
    if(got.pars) {
      ## validate parameters
      if(is.null(parnames) || !all(nzchar(parnames)))
        stop("All graphics parameters must have names")
          atomic <- unlist(lapply(parlist, is.atomic))
      functions <- unlist(lapply(parlist, is.function))
      lenfs <- lengths(parlist)
      constants <- atomic & (lenfs == 1)
      if(any(bad <- !(constants | functions))) {
        if(type == "discrete" && any(repairable <- atomic & bad)) {
          ## recycle data to desired length
          parlist[repairable] <- lapply(parlist[repairable],
                                        reptolength,
                                        n=length(inputs))
          bad[repairable] <- FALSE
        }
        if(type == "continuous") {
          ## look for vectors of colour values
          iscol <- bad & sapply(parlist, is.colour) &
            (names(parlist) %in% c("cols", "col", "fg", "bg"))
          ## convert colour values to colour map
          if(any(iscol)) {
            cmap <- lapply(parlist[iscol], trycolourmap, range=range)
            success <- sapply(cmap, inherits, what="colourmap")
            iscol[iscol] <- success
            if(any(iscol)) {
              parlist[iscol] <- cmap[success]
              bad[iscol] <- FALSE
              functions[iscol] <- TRUE
            }
          }
        }
        nbad <- sum(bad)
        if(nbad > 0) 
          stop(paste(ngettext(nbad, "Argument", "Arguments"),
                     commasep(sQuote(parnames[bad])),
                     ngettext(nbad, "is neither a function nor a constant",
                              "are neither functions nor constants")))
      }
      if(type == "constant" && any(functions))
        type <- "continuous"
    } 
    switch(type,
           constant ={
             ## set of constant graphics parameters defining a single symbol
             stuff <- list(type=type, parlist=parlist)
             ConstantValue <- as.data.frame(parlist, stringsAsFactors=FALSE)
             f <- function(x) ConstantValue
           },
           discrete = {
             ## finite set of inputs mapped to symbols
             stuff <- list(type=type, inputs=inputs, parlist=parlist)
             f <- function(x) ApplyDiscreteSymbolMap(x, stuff)
           },
           continuous = {
             got.shape <- "shape" %in% parnames
             got.size <- "size" %in% parnames
             got.cha <- any(c("pch", "chars") %in% parnames)
             ## interval of real line (etc) mapped to symbols or characters
             if(!got.cha) {
               ## mapped to symbols
               if(!got.shape)
                 parlist$shape <- "circles"
               if(!got.size)
                 stop("Parameter 'size' is missing")
             }
             rangetype <- if(is.null(range)) "numeric" else
                          if(inherits(range, "POSIXt")) "datetime" else
                          if(inherits(range, "Date")) "date" else
                          if(is.numeric(range)) "numeric" else "unknown"
             stuff <- list(type=type, range=range, rangetype=rangetype,
                           parlist=parlist)
             f <- function(x) ApplyContinuousSymbolMap(x, stuff)
           })
    attr(f, "stuff") <- stuff
    class(f) <- c("symbolmap", class(f))
    f
  }

  reptolength <- function(z, n) { rep.int(z, n)[1:n] }
  
  MapDiscrete <- function(f, x, i) {
    if(is.function(f)) f(x) else if(length(f) == 1) rep.int(f, length(x)) else f[i]
  }
  
  MapContinuous <- function(f, x) {
    if(is.function(f)) f(x) else rep.int(f, length(x))
  }

  ApplyContinuousSymbolMap <- function(x, stuff) {
    with(stuff, {
      y <- as.data.frame(lapply(parlist, MapContinuous, x=x),
                         stringsAsFactors=FALSE)
      return(y)
    })
  }
  
  ApplyDiscreteSymbolMap <- function(x, stuff) {
    with(stuff, {
      ii <- match(x, inputs)
      if(anyNA(ii))
        stop("Some values do not belong to the domain of the symbol map")
      y <- as.data.frame(lapply(parlist, MapDiscrete, x=x, i=ii),
                         stringsAsFactors=FALSE)
      return(y)
    })
  }
  symbolmap
})

symbolmaptype <- function(x) { attr(x, "stuff")$type }

symbolmapdomain <- function(x) {
  stuff <- attr(x, "stuff")
  d <- switch(stuff$type,
              constant = { integer(0) },
              discrete = { stuff$inputs },
              continuous = { stuff$range })
  return(d)
}

symbolmapparnames <- function(x) { names(attr(x, "stuff")[["parlist"]]) }

update.symbolmap <- function(object, ...) {
  y <- attr(object, "stuff")
  oldargs <- append(y[["parlist"]], y[c("inputs", "range")])
  do.call(symbolmap, resolve.defaults(list(...), oldargs))
}

print.symbolmap <- function(x, ...) {
  with(attr(x, "stuff"), {
    switch(type,
           constant = {
             if(length(parlist) == 0) {
               cat("Symbol map", "with no parameters", fill=TRUE)
             } else {
               cat("Symbol map", "with constant values", fill=TRUE)
             }
           },
           discrete = {
             cat("Symbol map", "for discrete inputs:", fill=TRUE)
             print(inputs)
           },
           continuous = {
             cat("Symbol map", "for",
                 switch(rangetype,
                        numeric="real numbers",
                        date = "dates",
                        datetime = "date/time values",
                        unknown = "unrecognised data"),
                 if(!is.null(range)) paste("in", prange(range)) else NULL,
                 fill=TRUE)
           })
    if(length(parlist) > 0) {
      for(i in seq_along(parlist)) {
        cat(paste0(names(parlist)[i], ": "))
        pari <- parlist[[i]]
        if(!is.function(pari) && length(pari) == 1)
          cat(pari, fill=TRUE) else print(pari)
      }
    }
    return(invisible(NULL))
  })
}

## Function which actually plots the symbols.
## Called by plot.ppp and plot.symbolmap
## Returns maximum size of symbols

invoke.symbolmap <- local({

  ## plot points, handling various arguments
  do.points <- function(x, y, ...,
                        cex=size, size=NULL, 
                        col=cols, pch=chars, cols=NULL, chars=NULL,
                        lwd=1, etch=FALSE, 
                        do.plot=TRUE) {
    if(do.plot) {
      if(length(cex) == 0) cex <- 1
      if(length(col) == 0) col <- par("col")
      if(length(pch) == 0) pch <- 1
      if(length(lwd) == 0) lwd <- 1
      n <- length(x)
      if(length(cex) == 1) cex <- rep(cex, n)
      if(length(col) == 1) col <- rep(col, n)
      if(length(pch) == 1) pch <- rep(pch, 1)
      if(length(lwd) == 1) lwd <- rep(lwd, n)
      if(length(etch) == 1) etch <- rep(etch, n)
      ## infer which arguments are parallelised
      other <- append(list(...), list(cex=cex, pch=pch))
      isvec <- (lengths(other) == n)
      other.fixed <- other[!isvec]
      other.vec   <- other[isvec]
      ##
      if(any(i <- as.logical(etch))) {
        anti.col <- complementarycolour(col)
        anti.lwd <- if(is.numeric(etch)) etch else 2 * lwd
        do.call.matched(points.default,
                        resolve.defaults(list(x=x[i], y=y[i]),
                                         other.fixed,
                                         lapply(other.vec, "[", i=i),
                                         list(col=anti.col[i],
                                              lwd=anti.lwd[i])),
                        extrargs=c("col", "pch", "type", "bg",
                                   "cex", "lwd", "lty"))
      }
      do.call.matched(points.default,
                    resolve.defaults(list(x=x, y=y),
                                     other,
                                     list(col=col, lwd=lwd)),
                    extrargs=c("col", "pch", "type", "bg", "cex", "lwd", "lty"))
    }
    return(max(cex %orifnull% 1))
  }
  ## plot symbols likewise
  known.shapes <-  c("circles", "squares", "arrows", "crossticks")
  do.symbols <- function(x, y, ..., 
                         shape,
                         size=cex, cex=NULL,
                         fg=col, col=cols, cols=NULL,
                         lwd=1, etch=FALSE,
                         angleref=0,
                         do.plot=TRUE) {
    if(do.plot) {
      if(is.null(size))
        stop("parameter 'size' is required", call.=FALSE)
      ## zap tiny sizes
      tiny <- (size < (max(size)/1000))
      size[tiny] <- 0
      ## collect arguments
      n <- length(x)
      if(length(lwd) == 1) lwd <- rep(lwd, n)
      if(length(etch) == 1) etch <- rep(etch, n)
      if(length(fg) == 0) fg <- rep(par("col"), n) else if(length(fg) == 1) fg <- rep(fg, n)
      if(length(angleref) == 1) angleref <- rep(angleref, n)
      other <- resolve.defaults(list(...),
                                list(add=TRUE, inches=FALSE))
      ## infer which arguments are parallelised
      isvec <- (lengths(other) == n)
      other.fixed <- other[!isvec]
      other.vec   <- other[isvec]
      ##
      if(any(as.logical(etch))) {
        anti.fg <- complementarycolour(fg)
        anti.lwd <- if(is.numeric(etch)) etch else 2 * lwd
      }
      ## plot
      if(any(i <- (shape == "circles") & as.logical(etch))) 
        do.call.matched(symbols,
                        c(list(x=x[i], y=y[i], circles=size[i]/2),
                          other.fixed,
                          lapply(other.vec, "[", i=i),
                          list(lwd=anti.lwd[i], fg=anti.fg[i])),
                        extrargs=c("lwd", "lty"))
      if(any(i <- (shape == "circles")))
        do.call.matched(symbols,
                        c(list(x=x[i], y=y[i], circles=size[i]/2),
                          other.fixed,
                          lapply(other.vec, "[", i=i),
                          list(lwd=lwd[i], fg=fg[i])),
                        extrargs=c("lwd", "lty"))
      if(any(i <- (shape == "squares") & as.logical(etch)))
        do.call.matched(symbols,
                        c(list(x=x[i], y=y[i], squares=size[i]),
                          other.fixed,
                          lapply(other.vec, "[", i=i),
                          list(lwd=anti.lwd[i], fg=anti.fg[i])),
                        extrargs=c("lwd", "lty"))
      if(any(i <- (shape == "squares"))) 
        do.call.matched(symbols,
                        c(list(x=x[i], y=y[i], squares=size[i]),
                          other.fixed,
                          lapply(other.vec, "[", i=i),
                          list(lwd=lwd[i], fg=fg[i])),
                        extrargs=c("lwd", "lty"))
      if(any(i <- (shape == "arrows") & as.logical(etch)))
        do.call.matched(do.arrows,
                        c(list(x=x[i], y=y[i], len=size[i]),
                          other.fixed,
                          lapply(other.vec, "[", i=i),
                          list(lwd=anti.lwd[i], cols=anti.fg[i])),
                        extrargs=c("cols", "col", "lwd", "lty"))
      if(any(i <- (shape == "arrows"))) 
        do.call.matched(do.arrows,
                        c(list(x=x[i], y=y[i], len=size[i]),
                          other.fixed,
                          lapply(other.vec, "[", i=i),
                          list(lwd=lwd[i], cols=fg[i])),
                        extrargs=c("cols", "col", "lwd", "lty"))
      if(any(i <- (shape == "crossticks") & as.logical(etch)))
        do.call.matched(do.crossticks,
                        c(list(x=x[i], y=y[i], len=size[i], angleref=angleref[i]),
                          other.fixed,
                          lapply(other.vec, "[", i=i),
                          list(lwd=anti.lwd[i], cols=anti.fg[i])),
                        extrargs=c("cols", "col", "lwd", "lty"))
      if(any(i <- (shape == "crossticks"))) 
        do.call.matched(do.crossticks,
                        c(list(x=x[i], y=y[i], len=size[i], angleref=angleref[i]),
                          other.fixed,
                          lapply(other.vec, "[", i=i),
                          list(lwd=lwd[i], cols=fg[i])),
                        extrargs=c("cols", "col", "lwd", "lty"))
      if(any(nomatch <- is.na(match(shape, known.shapes)))) {
        unknown <- unique(shape[nomatch])
        nun <- length(unknown)
        warning(paste("Unrecognised",
                      ngettext(nun, "shape", "shapes"),
                      paste0(commasep(sQuote(unknown)), ";"),
                      "recognised values are",
                      commasep(sQuote(known.shapes))),
                call.=FALSE)
      }
    }
    return(max(size))
  }

  do.arrows <- function(x, y, len, direction=0, arrowtype=2, ...,
                        headlength=len * 0.4, 
                        headangle=40,
                        cols=col, col=par('fg'),
                        lwd=1, lty=1) {
    #' vectorise all arguments
    df <- data.frame(x=x, y=y, len=len, direction=direction,
                     arrowtype=arrowtype, headlength=headlength,
                     headangle=headangle, cols=cols, lwd=lwd, lty=lty)
    with(df, {
      alpha <- direction * pi/180
      dx <- len * cos(alpha)/2
      dy <- len * sin(alpha)/2
      x0 <- x - dx
      x1 <- x + dx
      y0 <- y - dy
      y1 <- y + dy
      segments(x0, y0, x1, y1, ..., col=cols, lty=lty, lwd=lwd)
      if(any(arrowtype != 0)) {
        halfangle <- (headangle/2) * pi/180
        beta1 <- alpha + halfangle
        beta2 <- alpha - halfangle
        hx1 <- headlength * cos(beta1)
        hy1 <- headlength * sin(beta1)
        hx2 <- headlength * cos(beta2)
        hy2 <- headlength * sin(beta2)
        if(any(left <- (arrowtype %in% c(1,3)))) {
          segments(x0[left], y0[left], (x0 + hx1)[left], (y0 + hy1)[left],
                   ..., col=cols[left], lwd=lwd[left], lty=lty[left])
          segments(x0[left], y0[left], (x0 + hx2)[left], (y0 + hy2)[left],
                   ..., col=cols[left], lwd=lwd[left], lty=lty[left])
        }
        if(any(right <- (arrowtype %in% c(2,3)))) {
          segments(x1[right], y1[right], (x1 - hx1)[right], (y1 - hy1)[right],
                   ..., col=cols[right], lwd=lwd[right], lty=lty[right])
          segments(x1[right], y1[right], (x1 - hx2)[right], (y1 - hy2)[right],
                   ..., col=cols[right], lwd=lwd[right], lty=lty[right])
        }
      }
    })
    return(invisible(NULL))
  }

  do.crossticks <- function(x, y, len, angleref=0, arrowtype=0, ...,
                            direction = angleref + 90) {
    do.arrows(x=x, y=y, len=len,
              direction=direction,
              arrowtype=arrowtype, ...)
  }

  sanitycheck <- function(df, forbidden, kind) {
    ## 'df' is the result of a symbol map
    ##  (applies to numeric, factor, character)
    bad <- sapply(lapply(df, forbidden), any)
    if(any(bad)) {
      stop(paste("Symbol map produced", kind, "values for",
                 ngettext(sum(bad), "parameter", "parameters"),
                 commasep(sQuote(colnames(df)[bad]))),
           call.=FALSE)
    }
    return(NULL)
  }
    

  ## main function

  invoke.symbolmap <- function(map, values, x=NULL, y=NULL, ...,
                               angleref=NULL, 
                               add=FALSE, do.plot=TRUE,
                               started = add && do.plot) {
    if(!inherits(map, "symbolmap"))
      stop("Argument 'map' should be an object of class 'symbolmap'")
    if(hasxy <- (!is.null(x) || !is.null(y))) {
      xy <- xy.coords(x, y)
      x <- xy$x
      y <- xy$y
    }
    if(is.null(angleref)) 
      angleref <- numeric(length(x)) ## zeroes, or numeric(0)
    ## function will return maximum size of symbols plotted.
    maxsize <- 0
    if(do.plot && !add) plot(x, y, type="n", ...)
    ## map numerical/factor values to graphical parameters
    g <- map(values)
    parnames <- colnames(g)
    ## trap user coding errors etc
    sanitycheck(g, is.na, "NA")
    sanitycheck(g, is.nan, "NaN")
    sanitycheck(g, is.infinite, "infinite")
    ##
    if(do.plot) {
      ## add spatial coordinates
      xydf <- data.frame(x=x, y=y, angleref=angleref)
      if(nrow(xydf) == 0)
        return(invisible(maxsize))
      g <- if(prod(dim(g)) == 0) xydf else 
           do.call(data.frame,
                   c(as.list(g), as.list(xydf), list(stringsAsFactors=FALSE)))
    }
    n <- nrow(g)
    ## figure out which function does the graphics job
    need.points <- any(c("pch", "chars") %in% parnames)
    need.symbols <- "shape" %in% parnames
    if(need.symbols && need.points) {
      worker <- with(g, ifelse(!is.na(shape), "symbols", "points"))
    } else if(need.symbols) {
      worker <- rep.int("symbols", n)
    } else {
      worker <- rep.int("points", n)
    } 
    ## split data according to graphics function involved
    z <- split(g, factor(worker))
    ## display using 'pch'
    zpoints <- z[["points"]]
    if(!is.null(zpoints) && nrow(zpoints) > 0) {
      ms <- do.call(do.points,
                    resolve.defaults(as.list(zpoints),
                                     list(...),
                                     list(do.plot=do.plot)))
      ## value is max(cex)
      ## guess size of one character
      charsize <- if(started) max(par('cxy')) else
                  if(hasxy) max(sidelengths(boundingbox(x,y))/40) else 1/40
      maxsize <- max(maxsize, charsize * ms)
    }
    ## display using 'symbols'
    zsymbols <- z[["symbols"]]
    if(!is.null(zsymbols) && nrow(zsymbols) > 0) {
      ms <- do.call(do.symbols,
                    resolve.defaults(as.list(zsymbols),
                                     list(...),
                                     list(do.plot=do.plot)))
      ## ms value is max physical size.
      maxsize <- max(maxsize, ms)
    }
    return(invisible(maxsize))
  }

  invoke.symbolmap
})


## Display the symbol map itself (`legend' style)

plot.symbolmap <- function(x, ..., main,
                           xlim=NULL, ylim=NULL,
                           vertical=FALSE,
                           side=c("bottom", "left", "top", "right"),
                           annotate=TRUE, labelmap=NULL, add=FALSE,
                           nsymbols=NULL, warn=TRUE,
                           colour.only=FALSE, representatives=NULL) {
  if(missing(main))
    main <- short.deparse(substitute(x))
  miss.side <- missing(side)
  side <- match.arg(side)

  if(colour.only) {
    ## extract only the colour map and plot it
    cmap <- as.colourmap(x)
    if(inherits(cmap, "colourmap")) {
      if(miss.side)
        side <- if(vertical) "right" else "bottom"
      if(!is.numeric(side))
        side <- match(side, c("bottom", "left", "top", "right"))
      result <- plot.colourmap(cmap, ..., main=main,
                               xlim=xlim, ylim=ylim, vertical=vertical,
                               side=side, labelmap=labelmap, add=add)
      return(result)
    }
  }
    
  type <- symbolmaptype(x)
  map <- x
  stuff <- attr(map, "stuff")

  if(type == "constant" && length(stuff$parlist) == 0)
    return(invisible(NULL))

  if(is.null(labelmap)) {
    labelmap <- function(x) x
  } else if(type == "continuous" &&
            is.numeric(labelmap) && length(labelmap) == 1) {
    labscal <- labelmap
    labelmap <- function(x) { x * labscal }
  } else stopifnot(is.function(labelmap))

  ## determine the 'example' input values and their graphical representations
  switch(type,
         constant = {
           vv <- NULL
         },
         continuous = {
           ra <- stuff$range
           if(!is.null(representatives)) {
             vv <- representatives
             if(!all(ok <- inside.range(vv, ra))) {
               nbad <- sum(!ok)
               vv <- vv[ok]
               warning(paste(nbad, "out of", length(vv),
                             ngettext(nbad, "value", "values"),
                             "in the argument",
                             sQuote("representatives"),
                             ngettext(nbad, "was", "were"),
                             "outside the range of the symbol map, and",
                             ngettext(nbad, "was", "were"),
                             "removed"),
                       call.=FALSE)
             }
           } else {
             if(is.null(ra))
               stop("Cannot plot symbolmap with an infinite range")
             vv <- if(is.null(nsymbols)) prettyinside(ra) else
                 prettyinside(ra, n = nsymbols)
             if(is.numeric(vv))
               vv <- signif(vv, 4)
           }
         },
         discrete = {
           dd <- stuff$inputs
           if(!is.null(representatives)) {
             vv <- representatives
             if(!all(ok <- vv %in% dd)) {
               nbad <- sum(!ok)
               vv <- vv[ok]
               warning(paste(nbad, "out of", length(vv),
                             ngettext(nbad, "value", "values"),
                             "in the argument",
                             sQuote("representatives"),
                             ngettext(nbad, "was", "were"),
                             "outside the domain of the symbol map, and",
                             ngettext(nbad, "was", "were"),
                             "removed"),
                       call.=FALSE)
             }
           } else {
             vv <- if(is.null(nsymbols)) prettydiscrete(dd) else
                   prettydiscrete(dd, n = nsymbols)
           }
           if(warn && (length(vv) < length(dd))) {
             warning(paste("Only", length(vv), "out of", length(dd),
                           "symbols are shown in the symbol map"),
                     call.=FALSE)
           }
           if(vertical) vv <- rev(vv)
         })
  nn <- length(vv)
  ##    gg <- map(vv)
  ll <- paste(labelmap(vv))
    
  ## determine position of plot and symbols
  if(add) {
    ## x and y limits must respect existing plot space
    usr <- par('usr')
    if(is.null(xlim)) xlim <- usr[1:2]
    if(is.null(ylim)) ylim <- usr[3:4]
  } else {
    ## create new plot
    maxdiam <- invoke.symbolmap(map, vv, do.plot=FALSE, started=FALSE, ...)
    zz <- c(0, max(1, maxdiam))
    if(is.null(xlim) && is.null(ylim)) {
      if(vertical) {
        xlim <- zz
        ylim <- length(vv) * zz
      } else {
        xlim <- length(vv) * zz
        ylim <- zz
      }
    } else if(is.null(ylim)) {
      ylim <- zz
    } else if(is.null(xlim)) {
      xlim <- zz
    }
  }

  ## .......... initialise plot ...............................
  if(!add)
    do.call.matched(plot.default,
                    resolve.defaults(list(x=xlim, y=ylim,
                                          type="n", main=main,
                                          axes=FALSE, xlab="", ylab="",
                                          asp=1.0),
                                     list(...)))
  ## maximum symbol diameter
  maxdiam <- invoke.symbolmap(map, vv, do.plot=FALSE, started=TRUE, ...)

  ## .......... plot symbols ....................
  if(type == "constant") {
    xp <- mean(xlim)
    yp <- mean(ylim)
  } else if(vertical) {
    ## vertical arrangement
    xp <- rep(mean(xlim), nn)
    vskip <- 1.1 * max(maxdiam, 3 * max(strheight(labelmap(vv))))
    if(diff(ylim) > nn * vskip) {
      yp <- (1:nn) * vskip
      yp <- yp - mean(yp) + mean(ylim)
    } else {
      z <- seq(ylim[1], ylim[2], length=nn+1)
      yp <- z[-1] - diff(z)/2
    }
  } else {
    ## horizontal arrangement
    yp <- rep(mean(ylim), nn)
    hskip <- 1.1 * max(maxdiam, max(strwidth(labelmap(vv))))
    if(diff(xlim) > nn * hskip) {
      xp <- (1:nn) * hskip
      xp <- xp - mean(xp) + mean(xlim)
    } else {
      z <- seq(xlim[1], xlim[2], length=nn+1)
      xp <- z[-1] - diff(z)/2
    }
  }
  invoke.symbolmap(map, vv, xp, yp, ..., add=TRUE,
                   angleref=if(vertical) 90 else 0)

  ## ................. draw annotation ..................
  dotargs <- list(...)
  axiscol <- dotargs$col
  nac <- length(axiscol)
  if(nac > 0 && (!is.colour(axiscol) || nac > 1)) {
    ## only a single colour is permitted for 'axis'
    dotargs$col <- NULL
  }
  if(annotate && length(ll) > 0) {
    if(vertical) {
      ## default axis position is to the right 
      if(miss.side) side <- "right"
      sidecode <- match(side, c("bottom", "left", "top", "right"))
      if(!(sidecode %in% c(2,4)))
        warning(paste("side =", sQuote(side),
                      "is not consistent with vertical orientation"))
      pos <- c(ylim[1], xlim[1], ylim[2], xlim[2])[sidecode]
      ## draw axis
      do.call.matched(graphics::axis,
                      resolve.defaults(dotargs,
                                       list(side=sidecode, pos=pos, at=yp,
                                            labels=ll, tick=FALSE, las=1)),
                      extrargs=graphicsPars("axis"))
    } else {
      ## default axis position is below 
      if(miss.side) side <- "bottom"
      sidecode <- match(side, c("bottom", "left", "top", "right"))
      if(!(sidecode %in% c(1,3)))
        warning(paste("side =", sQuote(side),
                      "is not consistent with horizontal orientation"))
      pos <- c(ylim[1], xlim[1], ylim[2], xlim[2])[sidecode]
      ## draw axis
      do.call.matched(graphics::axis,
                      resolve.defaults(dotargs,
                                       list(side = sidecode, pos = pos,
                                            at = xp, labels=ll, tick=FALSE)),
                      extrargs=graphicsPars("axis"))
    } 
  }
  return(invisible(NULL))
}

plan.legend.layout <- function(B, 
                               ..., 
                               side=c("bottom", "left", "top", "right"),
                               sep=NULL,
                               leg.size=NULL,
                               sep.frac=0.05,
                               size.frac=0.05,
                               started=FALSE,
                               map=NULL) {
  ## Determine size and position of a box containing legend or symbolmap
  ## attached to a plot in region 'B'.
  ##   sep, leg.size are absolute distances;
  ##   sep.frac, size.frac are fractions of the maximum sidelength of B.
  side <- match.arg(side)
  B <- as.rectangle(B)
  Bsize <- max(sidelengths(B))
  if(is.null(leg.size)) {
    leg.size <- size.frac * Bsize
  } else {
    check.1.real(leg.size)
    stopifnot(leg.size > 0)
  }
  if(is.null(sep)) {
    sep <- sep.frac * Bsize
  } else {
    check.1.real(sep)
    stopifnot(sep > 0)
  }
  if(is.null(map) || !inherits(map, "symbolmap")) {
    vv <- NULL
    textlength <- 8
  } else {
    vv <- with(attr(map, "stuff"),
               if(type == "discrete") inputs else prettyinside(range))
    textlength <- max(nchar(paste(vv)))
  }
  if(started && !is.null(vv)) {
    textwidth <- max(strwidth(vv))
    textheight <- max(strheight(vv))
  } else {
    ## the plot has not been initialised: guess character size
    charsize <- diff(if(side %in% c("left", "right")) B$yrange else B$xrange)/40
    textwidth <- charsize * textlength
    textheight <- charsize
  }
  switch(side,
         right={
           ## symbols to right of image
           b <- owinInternalRect(B$xrange[2] + sep + c(0, leg.size),
                     B$yrange)
           ## text to right of symbols
           tt <- owinInternalRect(b$xrange[2] + sep + c(0, textwidth),
                      b$yrange)
           iside <- 4
         },
         left={
           ## symbols to left of image
           b <- owinInternalRect(B$xrange[1] - sep - c(leg.size, 0),
                     B$yrange)
           ## text to left of symbols
           tt <- owinInternalRect(b$xrange[1] - sep - c(textwidth, 0),
                      b$yrange)
           iside <- 2
         },
         top={
           ## symbols above image
           b <- owinInternalRect(B$xrange,
                     B$yrange[2] + sep + c(0, leg.size))
           ## text above symbols
           tt <- owinInternalRect(b$xrange,
                      b$yrange[2] + 3* charsize + c(0, textheight))
           iside <- 3
         },
         bottom={
           ## symbols below image
           b <- owinInternalRect(B$xrange,
                     B$yrange[1] - sep - c(leg.size, 0))
           ## text below symbols
           tt <- owinInternalRect(b$xrange,
                      b$yrange[1] - 3 * charsize - c(textheight, 0))
           iside <- 1
         })
  A <- boundingbox(B, b, tt)
  return(list(A=A, B=B, b=b, tt=tt,
              iside=iside, side=side, size=leg.size, charsize=charsize, sep=sep))
}


as.colourmap.symbolmap <- function(x, ..., warn=TRUE) {
  ## extract only the colour map and plot it
  parlist <- attr(x, "stuff")$parlist
  iscol <- sapply(parlist, inherits, what="colourmap")
  nc <- sum(iscol)
  if(nc == 0) {
    if(warn) warning("No colour map information was detected", call.=FALSE)
    return(NULL)
  }
  used <- which(iscol)[[1L]]
  cmap <- parlist[[used]]
  if(nc > 1 && warn && length(unique(parlist[iscol])) > 1)
    warning(paste("More than one colour map was detected;",
                  "using the colour map for",
                  sQuote(names(parlist)[used])),
            call.=FALSE)
    return(cmap)
}

summary.symbolmap <- function(object, ...) {
  st <- attr(object, "stuff")
  typ <- st[["type"]]
  dom <- switch(typ,
                constant = { integer(0) },
                discrete = { st$inputs },
                continuous = { st$range })
  parlist <- st[["parlist"]]
  parnames <- names(parlist)
  iscolmap <- sapply(parlist, inherits, what="colourmap")
  isatom <- sapply(parlist, is.atomic)
  lenfs <- lengths(parlist)
  isconstant <- isatom & (lenfs == 1)
  if(any(iscolmap)) 
    isconstant[iscolmap] <- (lengths(lapply(parlist[iscolmap], colouroutputs)) == 1)
  colournames <- c("col", "cols", "fg", "bg", "border", "fill")
  shapenames <- c("shape", "pch", "chars", "direction", "arrowtype", "headlength", "headangle", "etch")
  sizenames <- c("size", "cex", "headlength")
  physicalsizenames <- c("size", "headlength")
  iscolour <- iscolmap | (parnames %in% colournames)
  isshape <- parnames %in% shapenames
  issize <- parnames %in% sizenames
  isphysical <- parnames %in% physicalsizenames
  fixedcolour <- all(isconstant[iscolour])
  fixedshape <- all(isconstant[isshape])
  fixedsize <- all(isconstant[issize])
  z <- list(type        = typ,
            domain      = dom, 
            pars        = parnames,
            colmaps     = parnames[iscolmap],
            rangetype   = if(typ == "continuous") st[["rangetype"]] else NULL,
            range       = if(typ == "continuous") st[["range"]] else NULL,
            isconstant  = isconstant, # vector
            iscolour    = iscolour, # vector
            issize      = issize, # vector
            isshape     = isshape, # vector
            isphysical  = isphysical, # vector
            fixedshape  = fixedshape, # logical(1)
            fixedsize   = fixedsize,  # logical(1)
            fixedcolour = fixedcolour # logical(1)
            )
  class(z) <- c("summary.symbolmap", class(z))
  return(z)
}

print.summary.symbolmap <- function(x, ...) {
  with(x, {
    switch(type,
           constant = {
             if(length(pars) == 0) {
               cat("Symbol map", "with no parameters", fill=TRUE)
             } else {
               cat("Symbol map", "with constant values", fill=TRUE)
             }
           },
           discrete = {
             cat("Symbol map", "for discrete inputs:", fill=TRUE)
             print(domain)
           },
           continuous = {
             cat("Symbol map", "for",
                 switch(rangetype,
                        numeric="real numbers",
                        date = "dates",
                        datetime = "date/time values",
                        unknown = "unrecognised data"),
                 if(!is.null(range)) paste("in", prange(range)) else NULL,
                 fill=TRUE)
           })
    if(length(pars) > 0) {
      splat("Graphics parameters defined:")
      splat(paste("\t",
                  sQuote(pars),
                  "\t",
                  paren(ifelse(isconstant, "constant", "variable")),
                  "\t",
                  ifelse(pars %in% colmaps, "(colour map)", ""),
                  "\n"))
      if(all(isconstant)) {
        splat("All graphics parameters are constant")
      } else {
        att <- c("size", "shape", "colour")
        fux <- c(fixedsize, fixedshape, fixedcolour)
        attfux <- att[fux]
        attnon <- att[!fux]
        blurb <- "Symbols have"
        if(length(attfux))
          blurb <- paste(blurb, "fixed", commasep(attfux, " and "))
        if(length(attnon)) {
          if(length(attfux)) blurb <- paste0(blurb, ", but")
          blurb <- paste(blurb, "variable", commasep(attnon, " and "))
        }
        splat(blurb)
      }
    }
    return(invisible(NULL))
  })
}

default.symbolmap <- function(x, ...) {
  UseMethod("default.symbolmap")
}

Try the spatstat.geom package in your browser

Any scripts or data that you put into this service are public.

spatstat.geom documentation built on Sept. 18, 2024, 9:08 a.m.