R/Gviz.R

Defines functions .providerVersion .convert.summarizedJunctions.to.sashimi.junctions .create.summarizedJunctions.for.sashimi.junctions .ranges2ga .transcriptsAreCollapsed .getStringDims .computeGroupRange .resolveColMapping .defaultVarMap .findMismatches .moreInd .alpha .supportsAlpha .computeAlignments .covDefault availableDefaultMapping .fileExtension .registerImportFun .import.2bit .import.fasta .import.bam.alignments .import.bam .import.bw .import.gff3 .prepareDtData .getCovars .missingToNull .onLoad .box2Ellipse availableDisplayPars .makeParMapping devDims devRes vpLocation .updateObj .expBed exportTracks .getGenomeFromGRange plotTracks .recChromosome .legendInfo .getPlottingFeatures .defaultRange .genome2Dataset .getBiomart .ucsc2Ensembl .resize addScheme getScheme .chooseFace .collectSchemes .updatePars .panel.bwplot .panel.polygon .panel.mountain .ticks .getBiotypeColor .arrowBar .filledBoxes .filledArrow .handleComposite .pxResolution .strandName .setupTextSize .needsTitle .needsAxis .fontGp .dpOrDefaultFont .dpOrDefault .verticalSpace .whichStrand .getImageMap .needsStacking .deepCopyPars .chrName .checkClass

Documented in addScheme availableDefaultMapping availableDisplayPars exportTracks getScheme plotTracks

## package constants -----------------------------------------------------------
## A bunch of package constants

.DEFAULT_FILL_COL <- "lightgray"
.DEFAULT_OVERPLOT_COL <- "red"
.DEFAULT_LINE_COL <- "black"
.DEFAULT_SHADED_COL <- "#808080"
.DEFAULT_BRIGHT_SHADED_COL <- "#E0E0E0"
.DEFAULT_SYMBOL_COL <- "#0080FF"
.DEFAULT_LINE_COL <- "darkgray"
.PLOT_TYPES <- c(
    "p", "l", "b", "a", "s", "g", "r", "S",
    "smooth", "polygon", "horizon", "histogram",
    "mountain", "h", "boxplot", "gradient",
    "heatmap", "confint"
)
.ALIGNMENT_TYPES <- c("coverage", "sashimi", "pileup")
.THIN_BOX_FEATURES <- c(
    "utr", "ncRNA", "utr3", "utr5", "3UTR", "5UTR",
    "miRNA", "lincRNA", "three_prime_UTR", "five_prime_UTR"
)
.DEFAULT_HORIZON_COL <- c(
    "#B41414", "#E03231", "#F7A99C",
    "#9FC8DC", "#468CC8", "#0165B3"
)


## functions -------------------------------------------------------------------
## Check the class and structure of an object
.checkClass <- function(x, class, length = NULL, verbose = FALSE, mandatory = TRUE) {
    if (mandatory && missing(x)) {
        stop("Argument '", substitute(x), "' is missing with no default",
            call. = verbose
        )
    }
    msg <- paste("'", substitute(x), "' must be an object of class ",
        paste("'", class, "'", sep = "", collapse = " or "),
        sep = ""
    )
    fail <- !any(vapply(class, function(c, y) is(y, c), FUN.VALUE = logical(1L), y = x))
    if (!is.null(length) && length(x) != length) {
        if (!is.null(x)) {
            fail <- TRUE
            msg <- paste(msg, "of length", length)
        }
    }
    if (fail) {
        stop(msg, call. = verbose)
    } else {
        invisible(NULL)
    }
}



## We want to deal with chromosomes in a reasonable way. This coerces likely inputs to a unified
## chromosome name as understood by UCSC. Accepted inputs are:
##    - a single integer or a character coercable to one or integer-character combinations
##    - a character, starting with 'chr' (case insensitive)
## Arguments:
##    o x: a character string to be converted to a valid UCSC chromosome name
##    o force: a logical flag, force prepending of 'chr' if missing
## Value: the UCSC character name
.chrName <- function(x, force = FALSE) {
    if (!getOption("ucscChromosomeNames") || length(x) == 0) {
        return(as.character(x))
    }
    xu <- unique(x)
    xum <- vapply(xu, function(y) {
        xx <- suppressWarnings(as.integer(y))
        if (!is.na(xx)) {
            y <- xx
        }
        if (is.numeric(y)) {
            y <- paste("chr", y, sep = "")
        }
        if (y == "MT") { # ensembl  `MT` to `chrM` in UCSC
            y <- "chrM"
        }
        if (y %in% c("M", "X", "Y", "Z", "W")) { # mitochondrial genome and sex chromosomes
            y <- paste("chr", y, sep = "")
        }
        head <- tolower(substring(y, 1, 3)) == "chr"
        if (!head && force) {
            y <- paste("chr", y, sep = "")
            head <- TRUE
        }
        if (!head) {
            stop(sprintf(paste(
                "Invalid chromosome identifier '%s'\nPlease consider setting options(ucscChromosomeNames=FALSE)",
                "to allow for arbitrary chromosome identifiers."
            ), y))
        }
        substring(y, 1, 3) <- tolower(substring(y, 1, 3))
        y
    }, FUN.VALUE = character(1L))
    names(xum) <- xu
    return(as.vector(xum[as.character(x)]))
}


## Make a deep copy of the display parameter environment
.deepCopyPars <- function(GdObject) {
    oldPars <- displayPars(GdObject, hideInternal = FALSE)
    GdObject@dp <- DisplayPars()
    displayPars(GdObject) <- oldPars
    return(GdObject)
}



## One central place to check which display types result in stacking. This may change at some point for some
## unimplemented types...
## Arguments:
##    o GdObject: an object inheriting from class GdObject
## Value: a logical skalar indicating whether stacking is needed or not
.needsStacking <- function(GdObject) stacking(GdObject) %in% c("squish", "pack", "full")



## Get the coordinates for an HTML image map from the annotationTrack plot.
## Arguments:
##     o coordinates: a numeric matrix of annotation region coordinates (the bounding box if not rectangular)
## Value: valid HTML image map coordinates based on the current device dimensions
.getImageMap <- function(coordinates) {
    devSize <- devRes() * par("din")
    loc <- vpLocation()
    size <- loc$location[c(3, 4)] - loc$location[c(1, 2)]
    xscale <- current.viewport()$xscale
    yscale <- current.viewport()$yscale
    fw <- diff(xscale)
    fh <- diff(yscale)
    u2px <- function(x) ((x - xscale[1]) / fw * size[1]) + loc$location[1]
    u2py <- function(y) (devSize[2] - loc$location[2]) - ((y - yscale[1]) / fh * size[2])
    return(data.frame(
        x1 = u2px(coordinates[, 1]), y1 = u2py(coordinates[, 4]),
        x2 = u2px(coordinates[, 3]), y2 = u2py(coordinates[, 2]),
        stringsAsFactors = FALSE
    ))
}


.whichStrand <- function(trackList) {
    if (!is.list(trackList)) {
        trackList <- list(trackList)
    }
    str <- unlist(lapply(trackList, function(x) {
        if (is(x, "HighlightTrack") || is(x, "OverlayTrack")) {
            vapply(x@trackList, .dpOrDefault, par = "reverseStrand", FUN.VALUE = logical(1L))
        } else {
            .dpOrDefault(x, "reverseStrand")
        }
    }))
    return(ifelse(str, "reverse", "forward"))
}



## A function returning the amount of vertical space needed for a track
## Arguments:
##    o x: an object inheriting from class GdObject
## Value: the relative vertical space needed for the track
.verticalSpace <- function(x, totalSpace) {
    if (is(x, "AlignedReadTrack")) {
        size <- if (is.null(displayPars(x, "size"))) {
            type <- match.arg(.dpOrDefault(x, "detail", "coverage"), c("reads", "coverage"))
            if (type == "read") {
                  if (stacking(x) %in% c("sqish", "full")) 5 else 1
              } else {
                7
            }
        } else {
            displayPars(x, "size")
        }
        return(size)
    }
    if (is(x, "DataTrack") && is.null(displayPars(x, "size"))) {
        type <- match.arg(.dpOrDefault(x, "type", "p"), .PLOT_TYPES, several.ok = TRUE)
        size <- if (length(type) == 1L) {
            if (type == "gradient") 1 else if (type == "heatmap") nrow(values(x)) else 5
        } else {
            5
        }
        return(size)
    }
    if (is(x, "GenomeAxisTrack") || is(x, "IdeogramTrack") || is(x, "SequenceTrack")) {
        nv <- displayPars(x, "neededVerticalSpace")
        size <- displayPars(x, "size")
        if (is.null(size)) {
              if (!is.null(nv)) {
                  size <- nv
                  attr(size, "absolute") <- TRUE
              } else {
                  size <- 1
              }
          }
        return(size)
    }
    size <- .dpOrDefault(x, "size", 1)
    if (is(x, "StackedTrack")) {
          size <- max(size, min(floor(vpLocation()$size["height"] / 10), size * max(stacks(x))))
      }
    return(size)
}



## Return a particular displayPars value or a default
## Arguments:
##    o GdObject: an object inheriting from class GdObject
##    o par: the name of the displayPar, or a list of alternatives to go though before finally taking
##       the supplied default value
##    o default: a default value for the parameter if it can't be found in GdObject
## Value: the value of the displayPar
.dpOrDefault <- function(GdObject, par, default = NULL, fromPrototype = FALSE) {
    val <- getPar(x = GdObject, name = par, asIs = TRUE)
    val <- val[!vapply(val, is.null, logical(1))]
    if (length(val) == 0) {
        if (fromPrototype) {
            val <- .parMappings[[GdObject@name]][par]
            val <- val[!vapply(val, is.null, logical(1))]
            if (length(val) == 0) {
                  val <- NULL
              }
        } else {
            val <- default
        }
    } else {
        val <- val[[1]]
    }
    return(val)
}



## A special version of the above for font settings. This will try to extract the respective parent
## defaults before finally taking the provided default value.
## Arguments:
##    o GdObject: an object inheriting from class GdObject
##    o par: the name of the displayPar. Can also be a vector in which case a number of alternatives
##       is tested, then the parent default for the first element until finally moving to the supplied default
##    o type: the sub-type
##    o default: a default value for the parameter if it can't be found in GdObject
## Value: the value of the displayPar
.dpOrDefaultFont <- function(GdObject, par, type = NULL, default) {
    name <- if (is.null(type)) par else sprintf("%s.%s", par, type)
    val <- getPar(GdObject, name[1])
    name <- name[-1]
    while (is.null(val) && length(name)) {
        val <- getPar(GdObject, name[1])
        name <- name[-1]
    }
    if (is.null(val)) {
          val <- .dpOrDefault(GdObject, par[1], default)
      }
    return(val)
}


## Return the font settings for a GdObject
.fontGp <- function(GdObject, subtype = NULL, ...) {
    if (is(GdObject, "OverlayTrack")) {
          GdObject <- GdObject@trackList[[1]]
      }
    gp <- list(
        fontsize = as.vector(.dpOrDefaultFont(GdObject, "fontsize", subtype, 12))[1],
        fontface = as.vector(.dpOrDefaultFont(GdObject, "fontface", subtype, 1))[1],
        fontfamily = as.character(as.vector(.dpOrDefaultFont(GdObject, "fontfamily", subtype, 1)))[1],
        col = as.vector(.dpOrDefaultFont(GdObject, "fontcolor", subtype, "black"))[1],
        lineheight = as.vector(.dpOrDefaultFont(GdObject, "lineheight", subtype, 1))[1],
        alpha = as.vector(.dpOrDefaultFont(GdObject, "alpha", subtype, 1))[1],
        cex = as.vector(.dpOrDefaultFont(GdObject, "cex", subtype, 1))[1]
    )
    gp[names(list(...))] <- list(...)
    gp <- gp[!vapply(gp, is.null, logical(1))]
    return(do.call(gpar, gp))
}


## Check a list of GdObjects whether an axis needs to be drawn for each of them.
## Arguments:
##    o object: a list of GdObjects
## Value: a logical vector of the same length as 'objects'
.needsAxis <- function(objects) {
    if (!is.list(objects)) {
        objects <- list(objects)
    }
    atrack <- vapply(objects, function(x) {
        is(x, "NumericTrack") ||
            (is(x, "AlignmentsTrack") && "coverage" %in% match.arg(.dpOrDefault(x, "type", .ALIGNMENT_TYPES), .ALIGNMENT_TYPES, several.ok = TRUE)) ||
            (is(x, "AlignedReadTrack") && .dpOrDefault(x, "detail", "coverage") == "coverage")
    }, FUN.VALUE = logical(1L))
    isOnlyHoriz <- vapply(objects, function(x) {
        res <- FALSE
        if (is(x, "DataTrack")) {
            type <- match.arg(.dpOrDefault(x, "type", "p"), .PLOT_TYPES, several.ok = TRUE)
            res <- length(setdiff(type, "horizon")) == 0 && !.dpOrDefault(x, "showSampleNames", FALSE)
        }
        res
    }, FUN.VALUE = logical(1L))
    return(atrack & vapply(objects, .dpOrDefault, par = "showAxis", default = TRUE, FUN.VALUE = logical(1L)) & !isOnlyHoriz)
}

## Check a list of GdObjects whether a title needs to be drawn for each of them.
## Arguments:
##    o object: a list of GdObjects
## Value: a logical vector of the same length as 'objects'
.needsTitle <- function(objects) {
    if (!is.list(objects)) {
        objects <- list(objects)
    }
    vapply(objects, function(x) {
        if (is(x, "HighlightTrack") || is(x, "OverlayTrack")) {
            any(vapply(x@trackList, .dpOrDefault, par = "showTitle", default = TRUE, FUN.VALUE = logical(1L)))
        } else {
            .dpOrDefault(x, "showTitle", TRUE)
        }
    }, FUN.VALUE = logical(1L))
}


## Helper function to set up the text size based on the available space
## Arguments:
##    o trackList: a list of GdObjects
##    o sizes: a matching vector of relative vertical sizes
##    o title.width: the available width for the title
## Value: a list with items:
##    o spaceNeeded: the necessary vertical space
##    o cex: the character expansion factor
##    o title.width: the updated available title width
##    o spacing: the amount of spacing between tracks
##    o nwrap: the final (wrapped) title text
.setupTextSize <- function(trackList, sizes, title.width, panelOnly = FALSE, spacing = 5) {
    curVp <- vpLocation()
    trackList <- lapply(trackList, function(x) if (is(x, "OverlayTrack")) x@trackList[[1]] else x)
    spaceNeeded <- if (is.null(sizes)) {
        lapply(trackList, .verticalSpace, curVp$size["height"])
    } else {
        if (length(sizes) != length(trackList)) {
            stop("The 'sizes' vector has to match the size of the 'trackList'.")
        }
        rev(sizes)
    }
    whichAbs <- vapply(spaceNeeded, function(x) !is.null(attr(x, "absolute")) && attr(x, "absolute"), FUN.VALUE = logical(1L))
    spaceNeeded <- unlist(spaceNeeded)
    leftVetSpace <- curVp$size["height"] - sum(spaceNeeded[whichAbs])
    spaceNeeded[!whichAbs] <- spaceNeeded[!whichAbs] / sum(spaceNeeded[!whichAbs]) * leftVetSpace
    spaceNeeded <- spaceNeeded / sum(spaceNeeded)
    if (!panelOnly) {
        ## Figure out the fontsize for the titles based on available space. If the space is too small (<atLeast)
        ## we don't plot any text, and we also limit to 'maximum' to avoid overblown labels. If the displayPars
        ## 'cex.title' or 'cex.axis are not NULL, those override everything else.
        nn <- vapply(trackList, names, FUN.VALUE = character(1L))
        nwrap <- vapply(nn, function(x) paste(strwrap(x, 10), collapse = "\n"), character(1))
        needAxis <- .needsAxis(trackList)
        isOnlyHoriz <- vapply(trackList, function(x) {
            res <- FALSE
            if (is(x, "DataTrack")) {
                type <- match.arg(.dpOrDefault(x, "type", "p"), .PLOT_TYPES, several.ok = TRUE)
                res <- length(setdiff(type, "horizon")) == 0
            }
            res
        }, FUN.VALUE = logical(1L))
        nwrap[needAxis] <- nn[needAxis]
        lengths <- as.numeric(convertWidth(stringWidth(nwrap), "inches")) + 0.2
        heights <- curVp$isize["height"] * spaceNeeded
        atLeast <- 0.5
        maximum <- 1.2
        allCex <- heights / lengths
        parCex <- vapply(trackList, function(x) if (is.null(displayPars(x, "cex.title"))) NA else displayPars(x, "cex.title"), FUN.VALUE = numeric(1L))
        toSmall <- allCex < atLeast
        cex <- rep(max(c(atLeast, min(c(allCex[!toSmall], maximum), na.rm = TRUE))), length(allCex))
        cex[!is.na(parCex)] <- parCex[!is.na(parCex)]
        lengthsNoWrap <- as.numeric(convertWidth(stringWidth(nn), "inches")) * cex + 0.4
        needsWrapping <- lengthsNoWrap > heights
        nwrap[allCex < atLeast & is.na(parCex)] <- ""
        nwrap[!needsWrapping] <- nn[!needsWrapping]
        ## Figure out the title width based on the available tracks. If there is an axis for at least one of the tracks,
        ## we add 1.5 time the width of a single line of text to accomodate for it.
        wfac <- curVp$isize["width"]
        leaveSpace <- ifelse(any(needAxis), 0.15, 0.2)
        width <- (as.numeric(convertHeight(stringHeight(paste("g_T", nwrap, "g_T", sep = "")), "inches")) * cex + leaveSpace) / wfac
        showtitle <- vapply(trackList, .dpOrDefault, "showTitle", TRUE, FUN.VALUE = logical(1L))
        width[!showtitle & !needAxis] <- 0
        width[!showtitle] <- 0
        twfac <- if (missing(title.width) || is.null(title.width)) 1 else title.width
        title.width <- max(width, na.rm = TRUE)
        if (any(needAxis)) {
            cex.axis <- structure(vapply(trackList, .dpOrDefault, "cex.axis", 0.6, FUN.VALUE = numeric(1L)), names = nn)
            axTicks <- unlist(lapply(trackList, function(GdObject) {
                if (!is(GdObject, "NumericTrack") && !is(GdObject, "AlignedReadTrack") && !is(GdObject, "AlignmentsTrack")) {
                    return(NULL)
                }
                yvals <- if (is(GdObject, "AlignedReadTrack")) runValue(coverage(GdObject, strand = "*")) else values(GdObject)
                ylim <- .dpOrDefault(GdObject, "ylim", if (!is.null(yvals) && length(yvals)) {
                    range(yvals, na.rm = TRUE, finite = TRUE)
                } else {
                    c(-1, 1)
                })
                if (diff(ylim) == 0) {
                    ylim <- ylim + c(-1, 1)
                }
                yscale <- extendrange(r = ylim, f = 0.05)
                at <- pretty(yscale)
                at[at >= sort(ylim)[1] & at <= sort(ylim)[2]]
                atSpace <- max(as.numeric(convertWidth(stringWidth(at), "inches")) + 0.18) * cex.axis[names(GdObject)]
                if (is(GdObject, "DataTrack")) {
                    type <- match.arg(.dpOrDefault(GdObject, "type", "p"), .PLOT_TYPES, several.ok = TRUE)
                    if (any(c("heatmap", "gradient") %in% type)) {
                        nlevs <- max(1, nlevels(factor(getPar(GdObject, "groups")))) - 1
                        atSpace <- atSpace + 0.3 * atSpace + as.numeric(convertWidth(unit(3, "points"), "inches")) * nlevs
                    }
                    if (any(type %in% c("heatmap", "horizon")) && .dpOrDefault(GdObject, "showSampleNames", FALSE)) {
                        sn <- rownames(values(GdObject))
                        axSpace <- ifelse(isOnlyHoriz, 0, 10)
                        wd <- max(as.numeric(convertWidth(stringWidth(sn) + unit(axSpace, "points"), "inches")))
                        atSpace <- atSpace + (wd * .dpOrDefault(GdObject, "cex.sampleNames", 0.5))
                    }
                }
                atSpace
            }))
            hAxSpaceNeeded <- (max(axTicks)) / wfac
            title.width <- title.width + hAxSpaceNeeded
        }
    } else {
        title.width <- nwrap <- cex <- NA
    }
    spacing <- as.numeric(convertWidth(unit(spacing, "points"), "npc"))
    title.width <- title.width * twfac
    return(list(spaceNeeded = spaceNeeded, cex = cex, title.width = title.width, spacing = spacing, nwrap = nwrap))
}



## This coerces likely inputs for the genomic strand  to a unified
## strand name. Accepted inputs are:
##    o a single integer, where values <=0 indicate the plus strand, and values >=1 indicate the minus strand
##    o a character, either "+" or "-"
## If extended=TRUE, the additional values 2, "+-", "-+" and "*" are allowed, indicating to use both strands.
## Value: the validated strand name
.strandName <- function(x, extended = FALSE) {
    fun <- function(x, extended) {
        if (!extended) {
            if (is.numeric(x)) {
                x <- min(c(1, max(c(0, as.integer(x)))))
            } else if (is.character(x)) {
                x <- match(x, c("+", "-")) - 1
                if (any(is.na(x))) {
                    stop("The strand has to be specified either as a character ('+' or '-'), or as an integer value (0 or 1)")
                }
            }
        } else {
            if (is.numeric(x)) {
                x <- min(c(2, max(c(0, as.integer(x)))))
            } else if (is.character(x)) {
                x <- min(c(2, match(x, c("+", "-", "+-", "-+", "*")) - 1))
                if (any(is.na(x))) {
                    stop("The strand has to be specified either as a character ('+' or '-'), or as an integer value (0 or 1)")
                }
            }
        }
        x
    }
    return(vapply(x, fun, extended, FUN.VALUE = numeric(1L)))
}



## Compute native coordinate equivalent to 'min.width' pixel. This assumes that a graphics device is already
## open, otherwise a new window will pop up, which could be a little annoying.
## Arguments:
##    o min.width: the number of pixels
##    o coord: the axis for which to compute the coordinats, one in c("x","y")
## Value: the equivalent of 'min.width' in native coordinates.
.pxResolution <- function(min.width = 1, coord = c("x", "y")) {
    coord <- match.arg(coord, several.ok = TRUE)
    curVp <- vpLocation()
    co <- c(
        x = as.vector(abs(diff(current.viewport()$xscale)) / (curVp$size["width"]) * min.width),
        y = as.vector(abs(diff(current.viewport()$yscale)) / (curVp$size["height"]) * min.width)
    )
    return(co[coord])
}


## Deal with composite exons and provide polygon plotting coordinates for them. For all normal exons simply return
## the original bounding box data
## Arguments:
##    o box: the data frame with the bounding box information
##    o type: the type of coordinates for the composite exons, one in 'box', 'arrow' or 'fixedArrow'
## Value: a list with three elements: box, the non-composite exons, pols, the plotting coordinates for the merged composite exons,
##        and polpars, a data.frame of plotting parameters for the polygons
.handleComposite <- function(box, type = "box", W = 1 / 4, H = 1 / 3, min.width = 10, max.width = Inf) {
    box <- box[order(box$start), ]
    boxFinal <- data.frame(stringsAsFactors = FALSE)
    polFinal <- data.frame(stringsAsFactors = FALSE)
    polPars <- data.frame(stringsAsFactors = FALSE)
    bss <- split(box, box$transcript)
    for (b in bss) {
        ol <- names(which(table(b$exon) > 1))
        boxFinal <- rbind(boxFinal, b[!b$exon %in% ol, ])
        if (length(ol)) {
            b <- b[b$exon %in% ol, ]
            r <- IRanges(start = b$cx1, end = b$cx2)
            rr <- reduce(r)
            brs <- split(b, subjectHits(findOverlaps(r, rr)))
            for (j in seq_along(brs)) {
                if (nrow(brs[[j]]) == 1) {
                    boxFinal <- rbind(boxFinal, brs[[j]])
                } else {
                    xlocs <- as.vector(t(brs[[j]][, c("cx1", "cx2"), drop = FALSE]))
                    ylocs <- unlist(brs[[j]][, c("cy1", "cy2"), drop = FALSE])
                    fh <- seq_len(length(ylocs) / 2)
                    sh <- (length(ylocs) / 2 + 1):length(ylocs)
                    ylocs[sh] <- rev(ylocs[sh])
                    ylocs <- rep(ylocs, each = 2)
                    if (type == "box") {
                        polFinal <- rbind(polFinal, data.frame(
                            x = c(xlocs, rev(xlocs)), y = ylocs,
                            id = paste(brs[[j]][1, "transcript"], brs[[j]][1, "exon"], j),
                            stringsAsFactors = FALSE
                        ))
                        polPars <- rbind(polPars, data.frame(fill = brs[[j]][1, "fill"], col = brs[[j]][1, "col"], stringsAsFactors = FALSE))
                    } else {
                        polPars <- rbind(polPars, data.frame(fill = brs[[j]][1, "fill"], col = brs[[j]][1, "col"], stringsAsFactors = FALSE))
                        str <- brs[[j]]$strand[1]
                        offset <- (abs(brs[[j]]$cy1 - brs[[j]]$cy2)) * H / 2
                        if (str == "+" && abs(diff(xlocs[(length(xlocs) - 1):length(xlocs)])) > min.width) {
                            offset <- rep(offset, each = 2)
                            asel <- (length(xlocs) - 1):length(xlocs)
                            bsel <- seq_len(min(asel) - 1)
                            d <- abs(diff(xlocs[asel]))
                            afp <- if (type == "arrow") {
                                rep(xlocs[asel][1] + max(d * W, d - max.width), 2)
                            } else {
                                rep(max(xlocs[asel][1], xlocs[asel][2] - W), 2)
                            }
                            xlocs <- c(xlocs[bsel], xlocs[asel][1], afp, xlocs[asel][2], afp, xlocs[asel][1], rev(xlocs[bsel]))
                            mid <- length(ylocs) / 2
                            asel <- (mid - 1):(mid + 2)
                            bsel <- c(seq_len(mid - 2), (mid + 3):length(ylocs))
                            fh <- seq_len(length(bsel) / 2)
                            sh <- (length(bsel) / 2 + 1):length(bsel)
                            ylocs <- c(
                                ylocs[bsel[fh]] + offset[fh], ylocs[asel][c(1, 2)] + tail(offset, 1), ylocs[asel][1],
                                ylocs[asel][1] + abs(diff(ylocs[asel][c(1, 3)])) / 2, ylocs[asel][4],
                                ylocs[asel][3:4] - tail(offset, 1), ylocs[bsel[sh]] - offset[fh]
                            )
                            polFinal <- rbind(polFinal, data.frame(
                                x = c(xlocs, rev(xlocs)), y = ylocs,
                                id = paste(brs[[j]][1, "transcript"], brs[[j]][1, "exon"], j),
                                stringsAsFactors = FALSE
                            ))
                        } else if (str == "-" && abs(diff(xlocs[c(1, 2)])) > min.width) {
                            yoffset <- c(rep(offset[-1], each = 2), -rev(rep(offset[-1], each = 2)))
                            asel <- c(1, 2)
                            bsel <- seq(3, length(xlocs))
                            d <- abs(diff(xlocs[asel]))
                            afp <- if (type == "arrow") {
                                rep(xlocs[asel][2] - max(d * W, d - max.width), 2)
                            } else {
                                rep(min(xlocs[asel][2], xlocs[asel][1] + W), 2)
                            }
                            xlocs <- c(xlocs[asel][1], afp, xlocs[asel][2], xlocs[bsel], rev(xlocs[bsel]), xlocs[asel][2], afp, xlocs[asel][1])
                            asel <- c(1, 2, seq((length(ylocs) - 1), length(ylocs)))
                            bsel <- seq(3, (length(ylocs) - 2))
                            ylocs <- c(
                                ylocs[asel][1] + abs(diff(ylocs[asel][c(1, 3)])) / 2, ylocs[asel][1], rep(ylocs[asel][1] + offset[1], 2),
                                ylocs[bsel] + yoffset, rep(ylocs[asel][3] - offset[1], 2), ylocs[asel][3], ylocs[asel][1] + abs(diff(ylocs[asel][c(1, 3)])) / 2
                            )
                            polFinal <- rbind(polFinal, data.frame(
                                x = xlocs, y = ylocs,
                                id = paste(brs[[j]][1, "transcript"], brs[[j]][1, "exon"], j),
                                stringsAsFactors = FALSE
                            ))
                        } else {
                            offset <- c(rep(offset, each = 2), -rev(rep(offset, each = 2)))
                            polFinal <- rbind(polFinal, data.frame(
                                x = c(xlocs, rev(xlocs)), y = ylocs + offset,
                                id = paste(brs[[j]][1, "transcript"], brs[[j]][1, "exon"], j),
                                stringsAsFactors = FALSE
                            ))
                        }
                    }
                }
            }
        }
    }
    return(list(box = boxFinal, pols = polFinal, polpars = polPars))
}


## Take coordinates for the bounding boxes of annotation regions and plot filled arrows inside.
## Arguments:
##    o the data frame with the bounding box information
##    o W: the proportion of the total box width used for the arrow head
##    o H: the proportion of the total box height used for the arrow head
##    o lwd: the boundary line width
##    o lty: the boundary line type
##    o alpha: the transparency
##    o min.width: the minimum width of the arrow head. Below this size a simple box is drawn
## Note that the last arguments 4-9 all have to be of the same length as number of rows in box.
## Value: the function is called for its side-effects of drawing on the graphics device
.filledArrow <- function(box, W = 1 / 4, H = 1 / 3, lwd, lty, alpha, min.width = 10, max.width = Inf, absoluteWidth = FALSE) {
    boxC <- if ("transcript" %in% colnames(box)) {
        .handleComposite(box, ifelse(absoluteWidth, "fixedArrow", "arrow"), min.width = min.width, max.width = max.width, W = W, H = H)
    } else {
        list(box = box, pols = data.frame())
    }
    xx <- yy <- numeric()
    id <- character()
    pars <- data.frame()
    if (nrow(boxC$box)) {
        box <- boxC$box
        A <- box[, c(1, 2), drop = FALSE]
        B <- box[, c(3, 4), drop = FALSE]
        ## First everything that is still a box
        osel <- abs(B[, 1] - A[, 1]) < min.width | !box$strand %in% c("+", "-")
        xx <- c(A[osel, 1], B[osel, 1], B[osel, 1], A[osel, 1])
        offset <- (abs(B[osel, 2] - A[osel, 2]) * H / 2)
        yy <- c(rep(A[osel, 2] + offset, 2), rep(B[osel, 2] - offset, 2))
        id <- rep(seq_len(sum(osel)), 4)
        pars <- data.frame(fill = box$fill, col = box$col, lwd = lwd, lty = lty, alpha = alpha, stringsAsFactors = FALSE)[osel, ]
        ## Now the arrows facing right
        sel <- !osel & box$strand == "+"
        id <- c(id, rep(seq(from = if (!length(id)) 1 else max(id) + 1, by = 1, len = sum(sel)), 7))
        d <- abs(B[sel, 1] - A[sel, 1])
        alp <- if (!absoluteWidth) rep(A[sel, 1] + pmax(d * W, d - rep(max.width, sum(sel))), 2) else rep(pmax(B[sel, 1] - W, pmin(B[sel, 1], A[sel, 1])), 2)
        xx <- c(xx, A[sel, 1], alp, B[sel, 1], alp, A[sel, 1])
        yy <- c(
            yy, rep(A[sel, 2] + (abs(B[sel, 2] - A[sel, 2]) * H / 2), 2), A[sel, 2], A[sel, 2] + (abs(B[sel, 2] - A[sel, 2]) / 2), B[sel, 2],
            rep(B[sel, 2] - (abs(B[sel, 2] - A[sel, 2]) * H / 2), 2)
        )
        pars <- rbind(pars, data.frame(fill = box$fill, col = box$col, lwd = lwd, lty = lty, alpha = alpha, stringsAsFactors = FALSE)[sel, ])
        ## And finally those facing left
        sel <- !osel & box$strand == "-"
        id <- c(id, rep(seq(from = if (!length(id)) 1 else max(id) + 1, by = 1, len = sum(sel)), 7))
        d <- abs(B[sel, 1] - A[sel, 1])
        alp <- if (!absoluteWidth) rep(B[sel, 1] - pmax(d * W, d - rep(max.width, sum(sel))), 2) else rep(pmin(A[sel, 1] + W, pmax(B[sel, 1], A[sel, 1])), 2)
        xx <- c(xx, B[sel, 1], alp, A[sel, 1], alp, B[sel, 1])
        yy <- c(
            yy, rep(A[sel, 2] + (abs(B[sel, 2] - A[sel, 2]) * H / 2), 2), A[sel, 2], A[sel, 2] + (abs(B[sel, 2] - A[sel, 2]) / 2), B[sel, 2],
            rep(B[sel, 2] - (abs(B[sel, 2] - A[sel, 2]) * H / 2), 2)
        )
        pars <- rbind(pars, data.frame(fill = box$fill, col = box$col, lwd = lwd, lty = lty, alpha = alpha, stringsAsFactors = FALSE)[sel, ])
    }
    if (nrow(boxC$pols)) {
        xx <- c(xx, boxC$pols$x)
        yy <- c(yy, boxC$pols$y)
        id <- c(id, paste("pols", boxC$pols$id))
        pars <- rbind(pars, data.frame(fill = boxC$polpars$fill, col = boxC$polpars$col, lwd = lwd, lty = lty, alpha = alpha, stringsAsFactors = FALSE))
    }
    grid.polygon(
        x = xx, y = yy, gp = gpar(fill = pars$fill, col = pars$col, alpha = unique(pars$alpha), lwd = pars$lwd, lty = pars$lty), default.units = "native",
        id = factor(id)
    ) # fix for Sys.setenv(`_R_CHECK_LENGTH_1_CONDITION_`="true") Sys.setenv(`_R_CHECK_LENGTH_1_LOGIC2_`="true")
}


## Take coordinates for the bounding boxes of annotation regions and plot boxes, also making sure that
## composite exons in GeneRegionTracks (i.e., overlapping coordinates and same exon id) are merged
## appropriately
## Arguments:
##    o box: the data frame with the bounding box information
##    o lwd: the boundary line width
##    o lty: the boundary line type
##    o alpha: the transparency
## Value: the function is called for its side-effects of drawing on the graphics device
.filledBoxes <- function(box, lwd, lty, alpha) {
    if ("transcript" %in% colnames(box)) {
        box <- .handleComposite(box, "box")
        if (nrow(box$box)) {
              grid.rect(box$box$cx2, box$box$cy1,
                  width = box$box$cx2 - box$box$cx1, height = box$box$cy2 - box$box$cy1,
                  gp = gpar(col = as.character(box$box$col), fill = as.character(box$box$fill), lwd = lwd, lty = lty, alpha = alpha),
                  default.units = "native", just = c("right", "bottom")
              )
          }
        if (nrow(box$pols)) {
              grid.polygon(
                  x = box$pols$x, y = box$pols$y, id = factor(box$pols$id),
                  gp = gpar(col = as.character(box$polpars$col), fill = as.character(box$polpars$fill), lwd = lwd, lty = lty, alpha = alpha),
                  default.units = "native"
              )
          }
    } else {
        grid.rect(box$cx2, box$cy1,
            width = box$cx2 - box$cx1, height = box$cy2 - box$cy1,
            gp = gpar(col = as.character(box$col), fill = as.character(box$fill), lwd = lwd, lty = lty, alpha = alpha),
            default.units = "native", just = c("right", "bottom")
        )
    }
}


## Take start and end coordinates for genemodel-type annotations and draw a featherd line indicating
## the strand direction
## Arguments:
##    o xx1, xx2: integer vectors of equal length indicating the start and end of the gene models.
##    o strand: the strand information for each gene model. Needs to be of the same length as xx1 and xx2
##    o coords: the coordinates of the exon features, needed to avoid overlaps.
##    o y: the y value for the arrow bar, usually not set since it should always be 20
##    o W: the width of the arrow feathers in pixels
##    o D: the distance between arrow feathers in pixels
##    o H: the height of the arrow feathers in native coordinates (the total bounding box is usually 40)
##    o col: the boundary color
##    o lwd: the boundary line width
##    o lty: the boundary line type
##    o alpha: the transparency
##    o barOnly: only plot the bar, not the feathers
##    o diff: the current pixel resolution
##    o min.height: the minimum total height in pixels for the feathers (i.e., min.height/2 in each direction)
## Value: the function is called for its side-effects of drawing on the graphics device
.arrowBar <- function(xx1, xx2, strand, coords, y = 20, W = 3, D = 10, H, col, lwd, lty, alpha, barOnly = FALSE,
                      diff = .pxResolution(coord = "y"), min.height = 3) {
    exons <- IRanges(start = coords[, 1], end = coords[, 3])
    levels <- split(exons, coords[, 2] %/% 1)
    if (!barOnly) {
        onePx <- diff
        if (missing(H)) {
            onePy <- .pxResolution(coord = "y")
            H <- onePy * min.height / 2
        }
        fx1 <- fx2 <- scol <- fy1 <- fy2 <- NULL
        for (i in seq_along(xx1)) {
            x1 <- xx1[i]
            x2 <- xx2[i]
            len <- diff(c(x1, x2)) / onePx
            if (len > D + W * 2) {
                ax1 <- seq(from = x1 + (onePx * W), to = x1 + (len * onePx) - (onePx * W), by = onePx * D)
                ax2 <- ax1 + (onePx * W)
                feathers <- IRanges(start = ax1 - onePx, end = ax2 + onePx)
                cur.level <- y[i] %/% 1
                sel <- queryHits(findOverlaps(feathers, resize(levels[[cur.level]], width = width(levels[[cur.level]]) - 1)))
                if (length(sel)) {
                    ax1 <- ax1[-sel]
                    ax2 <- ax2[-sel]
                }
                fx1 <- c(fx1, rep(if (strand[i] == "-") ax1 else ax2, each = 2))
                fx2 <- c(fx2, rep(if (strand[i] == "-") ax2 else ax1, each = 2))
                scol <- c(scol, rep(col[i], length(ax1) * 2))
                fy1 <- c(fy1, rep(rep(y[i], length(ax1) * 2)))
                fy2 <- c(fy2, rep(c(y[i] - H, y[i] + H), length(ax1)))
            }
        }
        if (!is.null(fx1) && length(fx1)) {
              grid.segments(fx1, fy1, fx2, fy2, default.units = "native", gp = gpar(col = scol, lwd = lwd, lty = lty, alpha = alpha))
          }
    }
    bars <- data.frame(x1 = xx1, x2 = xx2, y = y, col = col, stringsAsFactors = FALSE)
    cutBars <- data.frame()
    for (i in seq_len(nrow(bars))) {
        b <- bars[i, ]
        cur.level <- b$y %/% 1
        ct <- if (cur.level != 0) {
            setdiff(
                IRanges(start = b$x1, end = b$x2),
                resize(levels[[cur.level]], width = width(levels[[cur.level]]) - 1)
            )
        } else {
            IRanges()
        }
        if (length(ct)) {
              cutBars <- rbind(cutBars, data.frame(x1 = start(ct), x2 = end(ct), y = b$y, col = b$col, stringsAsFactors = FALSE))
          }
    }
    ## fix bug when no introns are present
    if (nrow(cutBars)) {
          grid.segments(cutBars$x1, cutBars$y, cutBars$x2, cutBars$y,
              default.units = "native",
              gp = gpar(col = cutBars$col, lwd = lwd, lty = lty, alpha = alpha, lineend = "square")
          )
      }
    ## grid.segments(xx1, y, xx2, y, default.units="native", gp=gpar(col=col, lwd=lwd, lty=lty, alpha=alpha, lineend="square"))
}



## Extract track color for different subtypes within the track and use the default
## color value if no other is found, lightblue if no colors are set at all
## Arguments:
##    o GdObject: object inheriting from class GdObject
## Value: a color character
.getBiotypeColor <- function(GdObject) {
    defCol <- .dpOrDefault(GdObject, "fill", .DEFAULT_FILL_COL)
    col <- lapply(
        as.character(values(GdObject)[, "feature"]),
        function(x) .dpOrDefault(GdObject, x)[1]
    )
    needsDef <- vapply(col, is.null, FUN.VALUE = logical(1L))
    col[needsDef] <- rep(defCol, sum(needsDef))[seq_len(sum(needsDef))]
    return(unlist(col))
}


## Compute pretty tickmark location (code from tilingArray package)
## Arguments:
##    o x: a vector of data values
## Value: the tick mark coordinates
.ticks <- function(x) {
    rx <- range(x)
    lz <- log((rx[2] - rx[1]) / 3, 10)
    fl <- floor(lz)
    if (lz - fl > log(5, 10)) {
          fl <- fl + log(5, 10)
      }
    tw <- round(10^fl)
    i0 <- ceiling(rx[1] / tw)
    i1 <- floor(rx[2] / tw)
    seq(i0, i1) * tw
}



## A lattice-style panel function to draw smoothed 'mountain' plots
## Arguments:
##    o x, y: the x and y coordinates form the plot
##    o span, degree, family, evaluation: parameters that are passed on to loess
##    o lwd, lty, col: color, with and type of the plot lines
##    o fill: fill colors for areas above and under the baseline, a vector of length two
##    o col.line: color of the baseline
##    o baseline: the y value of the horizontal baseline
##    o alpha: the transparancy
## Value: the function is called for its side-effect of drawing on the graphics device
.panel.mountain <- function(x, y, span = 2 / 3, degree = 1, family = c("symmetric", "gaussian"), evaluation = 50,
                            lwd = plot.line$lwd, lty = plot.line$lty, col, col.line = plot.line$col,
                            baseline, fill, alpha = 1, ...) {
    x <- as.numeric(x)
    y <- as.numeric(y)
    fill <- rep(fill, 2)
    ok <- is.finite(x) & is.finite(y)
    if (sum(ok) < 1) {
          return()
      }
    if (!missing(col)) {
        if (missing(col.line)) {
              col.line <- col
          }
    }
    plot.line <- trellis.par.get("plot.line")
    smooth <- loess.smooth(x[ok], y[ok],
        span = span, family = family,
        degree = degree, evaluation = evaluation
    )
    tmp <- as.integer(smooth$y < baseline)
    changePoint <- NULL
    for (i in seq_along(tmp)) {
          if (i > 1 && tmp[i] != tmp[i - 1]) {
                changePoint <- c(changePoint, i)
            }
      }
    m <- (smooth$y[changePoint] - smooth$y[changePoint - 1]) / (smooth$x[changePoint] - smooth$x[changePoint - 1])
    xCross <- ((baseline - smooth$y[changePoint - 1]) / m) + smooth$x[changePoint - 1]
    newX <- newY <- NULL
    j <- 1
    xx <- smooth$x
    yy <- smooth$y
    smooth$x <- c(smooth$x, tail(smooth$x, 1))
    smooth$y <- c(smooth$y, baseline)
    xvals <- smooth$x[1]
    yvals <- baseline
    for (i in seq_along(smooth$x)) {
        if (i == length(smooth$x)) {
            xvals <- c(xvals, smooth$x[i])
            yvals <- c(yvals, baseline)
            fcol <- if (mean(yvals) < baseline) fill[1] else fill[2]
            panel.polygon(xvals, yvals, fill = fcol, col = fcol, border = fcol, alpha = alpha)
        } else if (i %in% changePoint) {
            xvals <- c(xvals, xCross[j])
            yvals <- c(yvals, baseline)
            fcol <- if (mean(yvals) < baseline) fill[1] else fill[2]
            panel.polygon(xvals, yvals, fill = fcol, col = fcol, border = fcol, alpha = alpha)
            xvals <- c(xCross[j], smooth$x[i])
            yvals <- c(baseline, smooth$y[i])
            j <- j + 1
        } else {
            xvals <- c(xvals, smooth$x[i])
            yvals <- c(yvals, smooth$y[i])
        }
    }
    grid.lines(
        x = xx, y = yy, gp = gpar(col = col.line, lty = lty, lwd = lwd, alpha = alpha),
        default.units = "native", ...
    )
}

## A lattice-style panel function to draw polygons (like coverage)
## Arguments:
##    o x, y: the x and y coordinates form the plot
##    o lwd, lty, col: color, with and type of the plot lines
##    o fill: fill colors for areas above and under the baseline, a vector of length two
##    o col.line: color of the baseline
##    o baseline: the y value of the horizontal baseline
##    o alpha: the transparancy
## Value: the function is called for its side-effect of drawing on the graphics device
.panel.polygon <- function(x, y, lwd = plot.line$lwd, lty = plot.line$lty, col,
                           col.line = plot.line$col, baseline, fill, alpha = 1, ...) {
    x <- as.numeric(x)
    y <- as.numeric(y)
    fill <- rep(fill, 2)
    ok <- is.finite(x) & is.finite(y)
    if (sum(ok) < 1) {
          return()
      }
    if (!missing(col)) {
        if (missing(col.line)) {
              col.line <- col
          }
    }
    x <- x[ok]
    y <- y[ok]
    plot.line <- trellis.par.get("plot.line")
    changePoint <- NULL
    tmp <- as.integer(y < baseline)
    for (i in seq_along(tmp)) {
          if (i > 1 && tmp[i] != tmp[i - 1]) {
                changePoint <- c(changePoint, i)
            }
      }
    m <- (y[changePoint] - y[changePoint - 1]) / (x[changePoint] - x[changePoint - 1])
    xCross <- ((baseline - y[changePoint - 1]) / m) + x[changePoint - 1]
    newX <- newY <- NULL
    j <- 1
    x <- c(x, tail(x, 1))
    y <- c(y, baseline)
    xvals <- x[1]
    yvals <- baseline
    for (i in seq_along(x)) {
        if (i == length(x)) {
            xvals <- c(xvals, x[i])
            yvals <- c(yvals, baseline)
            fcol <- if (mean(yvals) < baseline) fill[1] else fill[2]
            panel.polygon(xvals, yvals, fill = fcol, col = fcol, border = fcol, alpha = alpha)
        } else if (i %in% changePoint) {
            xvals <- c(xvals, xCross[j])
            yvals <- c(yvals, baseline)
            fcol <- if (mean(yvals) < baseline) fill[1] else fill[2]
            panel.polygon(xvals, yvals, fill = fcol, col = fcol, border = fcol, alpha = alpha)
            xvals <- c(xCross[j], x[i])
            yvals <- c(baseline, y[i])
            j <- j + 1
        } else {
            xvals <- c(xvals, x[i])
            yvals <- c(yvals, y[i])
        }
    }
    grid.lines(
        x = x, y = y, gp = gpar(col = col.line, lty = lty, lwd = lwd, alpha = alpha),
        default.units = "native", ...
    )
}


## A lattice-style panel function to draw box and whisker plots with groups
## Arguments: see ? panel.bwplot for details
## Value: the function is called for its side-effect of drawing on the graphics device
.panel.bwplot <- function(x, y, box.ratio = 1, box.width = box.ratio / (1 + box.ratio), lwd, lty, fontsize,
                          pch, col, alpha, cex, font, fontfamily, fontface, fill, varwidth = FALSE, notch = FALSE,
                          notch.frac = 0.5, ..., levels.fos = sort(unique(x)),
                          stats = boxplot.stats, coef = 1.5, do.out = TRUE) {
    if (all(is.na(x) | is.na(y))) {
        return()
    }
    x <- as.numeric(x)
    y <- as.numeric(y)
    cur.limits <- current.panel.limits()
    xscale <- cur.limits$xlim
    yscale <- cur.limits$ylim
    if (!notch) {
        notch.frac <- 0
    }
    blist <- tapply(y, factor(x, levels = levels.fos), stats,
        coef = coef, do.out = do.out
    )
    blist.stats <- do.call(rbind, lapply(blist, "[[", "stats"))
    blist.out <- lapply(blist, "[[", "out")
    blist.height <- box.width
    if (varwidth) {
        maxn <- max(table(x))
        blist.n <- vapply(blist, "[[", "n", FUN.VALUE = numeric(1L))
        blist.height <- sqrt(blist.n / maxn) * blist.height
    }
    blist.conf <- if (notch) {
        t(vapply(blist, "[[", "conf", FUN.VALUE = numeric(2L)))
    } else {
        blist.stats[, c(2, 4), drop = FALSE]
    }
    ybnd <- cbind(
        blist.stats[, 3], blist.conf[, 2], blist.stats[, 4], blist.stats[, 4], blist.conf[, 2],
        blist.stats[, 3], blist.conf[, 1], blist.stats[, 2], blist.stats[, 2], blist.conf[, 1], blist.stats[, 3]
    )
    xleft <- levels.fos - blist.height / 2
    xright <- levels.fos + blist.height / 2
    xbnd <- cbind(
        xleft + notch.frac * blist.height / 2, xleft,
        xleft, xright, xright, xright - notch.frac * blist.height / 2,
        xright, xright, xleft, xleft, xleft + notch.frac *
            blist.height / 2
    )
    xs <- matrix(NA_real_, nrow = nrow(xbnd) * 2, ncol = ncol(xbnd))
    ys <- matrix(NA_real_, nrow = nrow(xbnd) * 2, ncol = ncol(xbnd))
    xs[seq(along.with = levels.fos, by = 2), ] <- xbnd[seq(along.with = levels.fos), ]
    ys[seq(along.with = levels.fos, by = 2), ] <- ybnd[seq(along.with = levels.fos), ]
    panel.polygon(t(xs), t(ys), lwd = lwd, lty = lty, col = fill, alpha = alpha, border = col)
    panel.segments(rep(levels.fos, 2), c(blist.stats[, 2], blist.stats[, 4]), rep(levels.fos, 2),
        c(blist.stats[, 1], blist.stats[, 5]),
        col = col, alpha = alpha,
        lwd = lwd, lty = lty
    )
    panel.segments(levels.fos - blist.height / 2, c(blist.stats[, 1], blist.stats[, 5]), levels.fos + blist.height / 2,
        c(blist.stats[, 1], blist.stats[, 5]),
        col = col, alpha = alpha, lwd = lwd, lty = lty
    )
    if (all(pch == "|")) {
        mult <- if (notch) {
            1 - notch.frac
        } else {
            1
        }
        panel.segments(levels.fos - mult * blist.height / 2,
            blist.stats[, 3], levels.fos + mult * blist.height / 2,
            blist.stats[, 3],
            lwd = lwd, lty = lty,
            col = col, alpha = alpha
        )
    }
    else {
        panel.points(
            x = levels.fos, y = blist.stats[, 3],
            pch = pch, col = col, alpha = alpha, cex = cex,
            fontfamily = fontfamily, fontface = .chooseFace(
                fontface,
                font
            ), fontsize = fontsize
        )
    }
    panel.points(
        x = rep(levels.fos, vapply(blist.out, length, FUN.VALUE = numeric(1L))),
        y = unlist(blist.out), pch = pch, col = col,
        alpha = alpha, cex = cex,
        fontfamily = fontfamily, fontface = .chooseFace(
            fontface,
            font
        ), fontsize = fontsize
    )
}



## Check which parameters have already been set for a GdObject, and
## update all missing ones from the prototype of the current parent
## class.
## Arguments:
##    o x: an object inheriting from class GdObject
##    o class: the parent class from which to draw the missing parameters
## Value: The updated GdObject
.updatePars <- function(x, class) {
    current <- getPar(x, hideInternal = FALSE)
    defaults <- getPar(getClass(class)@prototype@dp, hideInternal = FALSE)
    ## Check whether we need to adjust any of those defaults based on the selected scheme
    sid <- getOption("Gviz.scheme")
    scheme <- if (is.null(sid)) list() else .schemes[[sid]]
    schemePars <- if (is.null(scheme) || is.null(scheme[[class]])) list() else scheme[[class]]
    if (!is.list(schemePars)) {
          stop(sprintf("Corrupted parameter definition for class '%s' in scheme '%s'", class, sid))
      }
    defaults[names(schemePars)] <- schemePars
    missing <- setdiff(names(defaults), names(current))
    if (is.null(getPar(x, ".__appliedScheme")) && length(schemePars)) {
        defaults[[".__appliedScheme"]] <- if (is.null(sid)) NA else sid
        defaults[names(schemePars)] <- schemePars
        missing <- c(union(missing, names(schemePars)), ".__appliedScheme")
    }
    x <- setPar(x, defaults[missing], interactive = FALSE)
    return(x)
}



## The scheme settings registry. This is essentially just a nested list of display parameters, and the values in this list
## will be used to initialize the objects. Please note that a display parameter still needs to be defined in the class
## definition for this to work, and that all parameters that are not set explicitely in the scheme will be taken from
## the defaults in that class definition. Scheme parameters still override everything else, and even parameters that are
## not defined for the class will be added from the scheme settings.
.schemes <- new.env()
.schemes[["default"]] <- list(
    GdObject = list(
        alpha = 1,
        background.panel = "transparent",
        background.title = "lightgray",
        background.legend = "transparent",
        cex.axis = NULL,
        cex.title = NULL,
        cex = 1,
        col.axis = "white",
        col.frame = "lightgray",
        col.grid = .DEFAULT_SHADED_COL,
        col.line = NULL,
        col.symbol = NULL,
        col.title = "white",
        col = .DEFAULT_SYMBOL_COL,
        collapse = TRUE,
        fill = .DEFAULT_FILL_COL,
        fontcolor.title = "white",
        fontcolor = "black",
        fontface.title = 2,
        fontface = 1,
        fontfamily.title = "sans",
        fontfamily = "sans",
        fontsize = 12,
        frame = FALSE,
        grid = FALSE,
        h = -1,
        lineheight = 1,
        lty.grid = "solid",
        lty = "solid",
        lwd.border = 1,
        lwd.grid = 1,
        lwd = 1,
        min.distance = 1,
        min.height = 3,
        min.width = 1,
        rotation.title = 90,
        rotation = 0,
        showAxis = TRUE,
        showTitle = TRUE,
        size = 1,
        v = -1
    ),

    StackedTrack = list(
        stackHeight = 0.75,
        reverseStacking = FALSE
    ),


    AnnotationTrack = list(
        arrowHeadWidth = 30,
        arrowHeadMaxWidth = 40,
        cex.group = 0.6,
        cex = 1,
        col.line = "darkgray",
        col = .DEFAULT_LINE_COL,
        featureAnnotation = NULL,
        fill = "lightblue",
        fontcolor.group = .DEFAULT_SHADED_COL,
        fontcolor.item = "white",
        fontface.group = 2,
        groupAnnotation = NULL,
        lex = 1,
        lineheight = 1,
        lty = "solid",
        lwd = 1,
        mergeGroups = FALSE,
        rotation = 0,
        shape = "arrow",
        showFeatureId = NULL,
        showId = NULL,
        showOverplotting = FALSE,
        size = 1
    ),

    DetailsAnnotationTrack = list(
        details.minWidth = 100,
        details.ratio = Inf,
        details.size = 0.5,
        detailsBorder.col = "darkgray",
        detailsBorder.fill = "transparent",
        detailsBorder.lty = "solid",
        detailsBorder.lwd = 1,
        detailsConnector.cex = 1,
        detailsConnector.col = "darkgray",
        detailsConnector.lty = "dashed",
        detailsConnector.lwd = 1,
        detailsConnector.pch = 20,
        detailsFunArgs = list(),
        groupDetails = FALSE
    ),

    GeneRegionTrack = list(
        arrowHeadWidth = 10,
        arrowHeadMaxWidth = 20,
        col = .DEFAULT_LINE_COL,
        collapseTranscripts = FALSE,
        exonAnnotation = NULL,
        fill = "#FFD58A",
        min.distance = 0,
        shape = c("smallArrow", "box"),
        showExonId = NULL,
        thinBoxFeature = .THIN_BOX_FEATURES,
        transcriptAnnotation = NULL
    ),

    BiomartGeneRegionTrack = list(
        C_segment = "burlywood4",
        D_segment = "lightblue",
        J_segment = "dodgerblue2",
        Mt_rRNA = "yellow",
        Mt_tRNA = "darkgoldenrod",
        Mt_tRNA_pseudogene = "darkgoldenrod1",
        V_segment = "aquamarine",
        miRNA = "cornflowerblue",
        miRNA_pseudogene = "cornsilk",
        misc_RNA = "cornsilk3",
        misc_RNA_pseudogene = "cornsilk4",
        protein_coding = "orange",
        pseudogene = "brown1",
        rRNA = "darkolivegreen1",
        rRNA_pseudogene = "darkolivegreen",
        retrotransposed = "blueviolet",
        scRNA = "gold4",
        scRNA_pseudogene = "darkorange2",
        snRNA = "coral",
        snRNA_pseudogene = "coral3",
        snoRNA = "cyan",
        snoRNA_pseudogene = "cyan2",
        tRNA_pseudogene = "antiquewhite3",
        utr3 = "orange",
        utr5 = "orange"
    ),

    GenomeAxisTrack = list(
        add35 = FALSE,
        add53 = FALSE,
        background.title = "transparent",
        cex.id = 0.7,
        cex = 0.8,
        col.id = "white",
        col.range = "cornsilk4",
        distFromAxis = 1,
        exponent = NULL,
        fill.range = "cornsilk3",
        fontcolor = "#808080",
        fontsize = 10,
        labelPos = "alternating",
        littleTicks = FALSE,
        lwd = 2,
        scale = NULL,
        showId = FALSE,
        showTitle = FALSE,
        size = NULL,
        col = "darkgray"
    ),

    DataTrack = list(
        aggregateGroups = FALSE,
        aggregation = "mean",
        missingAsZero = TRUE,
        amount = NULL,
        baseline = NULL,
        box.ratio = 1,
        box.width = NULL,
        cex.legend = 0.8,
        cex.sampleNames = NULL,
        cex = 0.7,
        coef = 1.5,
        col.baseline = NULL,
        col.boxplotFrame = .DEFAULT_SHADED_COL,
        col.histogram = .DEFAULT_SHADED_COL,
        col.horizon = NA,
        col.mountain = NULL,
        col.sampleNames = "white",
        col = trellis.par.get("superpose.line")[["col"]],
        collapse = FALSE,
        degree = 1,
        do.out = TRUE,
        evaluation = 50,
        factor = 0.5,
        family = "symmetric",
        fill.histogram = NULL,
        fill.horizon = c("#B41414", "#E03231", "#F7A99C", "#9FC8DC", "#468CC8", "#0165B3"),
        fill.mountain = c("#CCFFFF", "#FFCCFF"),
        fontcolor.legend = .DEFAULT_SHADED_COL,
        gradient = brewer.pal(9, "Blues"),
        groups = NULL,
        horizon.origin = 0,
        horizon.scale = NULL,
        jitter.x = FALSE,
        jitter.y = FALSE,
        levels.fos = NULL,
        lty.baseline = NULL,
        lty.mountain = NULL,
        lwd.baseline = NULL,
        lwd.mountain = NULL,
        min.distance = 0,
        na.rm = FALSE,
        ncolor = 100,
        notch.frac = 0.5,
        notch = FALSE,
        pch = 20,
        separator = 0,
        showColorBar = TRUE,
        showSampleNames = FALSE,
        size = NULL,
        span = 1 / 5,
        stackedBars = TRUE,
        stats = boxplot.stats,
        transformation = NULL,
        type = "p",
        varwidth = FALSE,
        window = NULL,
        windowSize = NULL,
        ylim = NULL,
        yTicksAt = NULL
    ),

    IdeogramTrack = list(
        background.title = "transparent",
        bevel = 0.45,
        cex.bands = 0.7,
        cex = 0.8,
        col = "red",
        fill = "#FFE3E6",
        fontcolor = .DEFAULT_SHADED_COL,
        fontsize = 10,
        showBandId = FALSE,
        showId = TRUE,
        showTitle = FALSE,
        size = NULL
    ),

    SequenceTrack = list(
        add53 = FALSE,
        background.title = "transparent",
        cex = 1,
        col = "darkgray",
        complement = FALSE,
        fontface = 2,
        fontsize = 10,
        lwd = 2,
        min.width = 2,
        noLetters = FALSE,
        rotation = 0,
        showTitle = FALSE,
        size = NULL
    ),

    HighlightTrack = list(
        col = "red",
        fill = "#FFE3E6"
    )
)

.schemes[["default.old"]] <- list(
    AnnotationTrack = list(col = "transparent")
)

## A helper function to be called upon package load that tries to find a stored Gviz scheme in the working directory
.collectSchemes <- function() {
    try(
        {
            if (".GvizSchemes" %in% base::ls(globalenv(), all.names = TRUE)) {
                schemes <- get(".GvizSchemes", globalenv())
                if (is.list(schemes) && !is.null(names(schemes))) {
                      lapply(names(schemes), function(x) addScheme(schemes[[x]], x))
                  }
            }
        },
        silent = TRUE
    )
}


## Helper function to select fontfaces
.chooseFace <- function(fontface = NULL, font = 1) {
    if (is.null(fontface)) {
          font
      } else {
        fontface
    }
}


## Get a scheme
## Arguments:
##    o name: the name of the scheme to get. Defaults to the current one.
## Value: A list containing the scheme
getScheme <- function(name = getOption("Gviz.scheme")) {
    s <- NULL
    if (!is.null(name)) {
          s <- .schemes[[name]]
      }
    if (is.null(s)) {
          s <- list()
      }
    return(s)
}

## Add a new scheme
## Arguments:
##    o scheme: the scheme to add
##    o name: the name of the scheme to add
addScheme <- function(scheme, name) {
    .schemes[[name]] <- scheme
}




## Helper function to compute native coordinate equivalent to 1 pixel and resize all ranges in a GRanges object accordingly
## Arguments:
##    o r: object inheriting from class GdObject
##    o min.width: the minimal pixel width of a region
##    o diff: the pixel resolution
## Value: the updated GdObject
.resize <- function(r, min.width = 2, diff = .pxResolution(coord = "x")) {
    if (min.width > 0) {
        minXDiff <- ceiling(min.width * diff)
        ## Extend all ranges to at least minXDiff
        xdiff <- width(r)
        xsel <- xdiff < minXDiff
        if (any(xsel)) {
            rr <- if (is(r, "GRanges")) ranges(r) else r
            start(rr)[xsel] <- pmax(1, start(rr)[xsel] - (minXDiff - xdiff[xsel]) / 2)
            end(rr)[xsel] <- end(rr)[xsel] + (minXDiff - xdiff[xsel]) / 2
            if (is(r, "GRanges")) r@ranges <- rr else r <- rr
        }
    }
    return(r)
}

## Tables containing the UCSC to ENSEMBL genome mapping
.biomartCurrentVersionTable <- read.delim(system.file(file.path("extdata", "biomartVersionsNow.txt"), package = "Gviz"), as.is = TRUE)
.biomartVersionTable <- read.delim(system.file(file.path("extdata", "biomartVersionsLatest.txt"), package = "Gviz"), as.is = TRUE)

## Helper function to map between UCSC and ENSEMBL genome information
## Arguments:
##    o id: character scalar, a UCSC genome identifier
## Value: a list with ENSEMBL the genome information
.ucsc2Ensembl <- function(id) {
    mt <- match(tolower(id), tolower(.biomartCurrentVersionTable$ucscId))
    val <- .biomartCurrentVersionTable[mt, ]
    if (is.na(mt)) {
        mt <- match(tolower(id), tolower(.biomartVersionTable$ucscId))
        val <- .biomartVersionTable[mt, c("species", "value", "dataset", "ucscId", "speciesShort", "speciesLong", "date", "version")]
    }
    return(as.list(val))
}

## Helper function to get the ENSEMBL biomart given a UCSC identifier
## Arguments:
##    o genome: character scalar, a UCSC genome identifier
## Value: a biomaRt object
.getBiomart <- function(genome) {
    map <- .ucsc2Ensembl(genome)
    if (map$date == "head") {
        bm <- useMart("ensembl", dataset = map$dataset)
        ds <- listDatasets(bm)
        mt <- ds[match(map$dataset, ds$dataset), "version"]
        if (is.na(mt)) {
            stop(sprintf(paste(
                "Gviz thinks that the UCSC genome identifier '%s' should map to the Biomart data set '%s' which is not correct.",
                "\nPlease manually provide biomaRt object"
            ), genome, map$dataset))
        }
        if (mt != map$value) {
            stop(sprintf(
                paste(
                    "Gviz thinks that the UCSC genome identifier '%s' should map to the current Biomart head as '%s',",
                    "but its current version is '%s'.\nPlease manually provide biomaRt object"
                ),
                genome, map$value, mt
            ))
        }
    } else {
        bm <- useMart(host = sprintf("%s.archive.ensembl.org", tolower(sub(".", "", map$date, fixed = TRUE))), biomart = "ENSEMBL_MART_ENSEMBL", dataset = map$dataset)
        ds <- listDatasets(bm)
        mt <- ds[match(map$dataset, ds$dataset), "version"]
        if (is.na(mt)) {
            stop(sprintf(paste(
                "Gviz thinks that the UCSC genome identifier '%s' should map to the Biomart data set '%s' which is not correct.",
                "\nPlease manually provide biomaRt object"
            ), genome, map$dataset))
        }
        if (mt != map$value) {
            stop(sprintf(
                paste(
                    "Gviz thinks that the UCSC genome identifier '%s' should map to Biomart archive %s (version %s) as '%s',",
                    "but its version is '%s'.\nPlease manually provide biomaRt object"
                ),
                genome, sub(".", " ", map$date, fixed = TRUE), map$version, map$value, mt
            ))
        }
    }
    return(bm)
}


## Helper function to translate from a UCSC genome name to a Biomart data set. This also caches the mart
## object in order to speed up subsequent calls
## Arguments:
##    o genome: character giving the UCSC genome
## Value: A BiomaRt connection object
.genome2Dataset <- function(genome) {
    map <- .ucsc2Ensembl(genome)
    if (is.na(map$date)) {
        stop(sprintf("Unable to automatically determine Biomart data set for UCSC genome identifier '%s'.\nPlease manually provide biomaRt object", genome))
    }
    cenv <- environment()
    bm <- .doCache(paste(map$dataset, genome, sep = "_"), expression(.getBiomart(genome)), .ensemblCache, cenv)
    return(bm)
}



## Return the plotting range for a GdObject, either from the contained ranges or from overrides.
## This function is vectorized and should also work for lists of GdObjects.
.defaultRange <- function(GdObject, from = NULL, to = NULL, extend.left = 0, extend.right = 0, factor = 0.01, annotation = FALSE) {
    if (!is.list(GdObject)) {
        GdObject <- list(GdObject)
    }
    GdObject <- c(GdObject, unlist(lapply(GdObject, function(x) if (is(x, "HighlightTrack") || is(x, "OverlayTrack")) x@trackList else NULL)))
    if (!length(GdObject) || !all(vapply(GdObject, is, "GdObject", FUN.VALUE = logical(1L)))) {
        stop("All items in the list must inherit from class 'GdObject'")
    }
    GdObject <- GdObject[!vapply(GdObject, is, "OverlayTrack", FUN.VALUE = logical(1L))]
    tfrom <- lapply(GdObject, function(x) {
        tmp <- start(x)
        if (is(x, "RangeTrack")) tmp <- tmp[seqnames(x) == chromosome(x)]
        tmp
    })
    tfrom <- if (is.null(unlist(tfrom))) Inf else min(vapply(tfrom[listLen(tfrom) > 0], min, FUN.VALUE = numeric(1L)))
    tto <- lapply(GdObject, function(x) {
        tmp <- end(x)
        if (is(x, "RangeTrack")) tmp <- tmp[seqnames(x) == chromosome(x)]
        tmp
    })
    tto <- if (is.null(unlist(tto))) Inf else max(vapply(tto[listLen(tto) > 0], max, FUN.VALUE = numeric(1L)))
    if ((is.null(from) || is.null(to)) && ((is.infinite(tfrom) || is.infinite(tto)) || is(GdObject, "GenomeAxisTrack"))) {
        stop(
            "Unable to automatically determine plotting ranges from the supplied track(s).\nPlease provide ",
            "range coordinates through the 'from' and 'to' arguments of the plotTracks function."
        )
    }
    ## FIX the cases with identical "tfrom" and "tto" (one base-pair plotting) by adding +1 to "tto"
    if (tto == tfrom) {
        tto <- tto + 1
    }
    range <- extendrange(r = c(tfrom, tto), f = factor)
    range[1] <- max(1, range[1])
    wasNull <- rep(FALSE, 2)
    if (is.null(from)) {
        wasNull[1] <- TRUE
        from <- range[1]
    }
    if (is.null(to)) {
        to <- range[2]
        wasNull[2] <- TRUE
    }
    ## We may need some extra space for annotations
    if (annotation) {
        rr <- unlist(lapply(GdObject, function(x) {
            gr <- .dpOrDefault(x, ".__groupRanges")
            gw <- .dpOrDefault(x, ".__groupLabelWidths", data.frame(before = 0, after = 0))
            if (is.null(gr) || length(gr) == 0) NULL else c(min(start(gr) + gw$before), max(end(gr) - gw$after))
        }))
        if (!is.null(rr)) {
            rr <- matrix(rr, ncol = 2, byrow = TRUE)
            if (wasNull[1]) {
                from <- min(from, rr[, 1])
            }
            if (wasNull[2]) {
                to <- max(to, rr[, 2])
            }
        }
    }
    from <- if (extend.left != 0 && extend.left > -1 && extend.left < 1) {
        from - (abs(diff(c(from, to))) * extend.left)
    } else {
        from - extend.left
    }
    to <- if (extend.right != 0 && extend.right > -1 && extend.right < 1) {
        to + (abs(diff(c(from, to))) * extend.right)
    } else {
        to + extend.right
    }
    if (from > to) {
        warning("'from' range can not be larger than 'to', reversing range coordinates")
        tto <- from
        from <- to
        to <- tto
    }
    return(c(from = as.integer(from), to = as.integer(to)))
}


## Figure out the colors to use for a DataTrack object from the supplied display parameters
.getPlottingFeatures <- function(GdObject) {
    pch <- .dpOrDefault(GdObject, "pch", 20)
    lty <- .dpOrDefault(GdObject, "lty", 1)
    lwd <- .dpOrDefault(GdObject, "lwd", 1)
    cex <- .dpOrDefault(GdObject, "cex", 0.7)
    groups <- .dpOrDefault(GdObject, "groups")
    col <- .dpOrDefault(GdObject, "col", "#0080ff")
    if (is.null(groups)) { ## When there are no groups we force a single color for all lines and points
        col <- col[1]
        col.line <- .dpOrDefault(GdObject, "col.line", col)[1]
        col.symbol <- .dpOrDefault(GdObject, "col.symbol", col)[1]
        pch <- pch[1]
        lwd <- lwd[1]
        lty <- lty[1]
        cex <- cex[1]
    } else { ## Otherwise colors are being mapped to group factors
        if (!is.factor(groups)) {
            groups <- factor(groups)
            nms <- unique(groups)
        } else {
            nms <- levels(groups)
        }
        col <- .dpOrDefault(GdObject, "col", trellis.par.get("superpose.line")$col)
        col <- rep(col, nlevels(groups))[seq_along(levels(groups))]
        col.line <- rep(.dpOrDefault(GdObject, "col.line", col), nlevels(groups))[seq_along(levels(groups))]
        col.symbol <- rep(.dpOrDefault(GdObject, "col.symbol", col), nlevels(groups))[seq_along(levels(groups))]
        lwd <- rep(lwd, nlevels(groups))[seq_along(levels(groups))]
        lty <- rep(lty, nlevels(groups))[seq_along(levels(groups))]
        pch <- rep(pch, nlevels(groups))[seq_along(levels(groups))]
        cex <- rep(cex, nlevels(groups))[seq_along(levels(groups))]
        names(col) <- names(col.line) <- names(col.symbol) <- names(lwd) <- names(lty) <- names(pch) <- names(cex) <- nms
    }
    col.baseline <- .dpOrDefault(GdObject, "col.baseline", col)[1]
    col.grid <- .dpOrDefault(GdObject, "col.grid", "#e6e6e6")[1]
    fill <- .dpOrDefault(GdObject, "fill", .DEFAULT_FILL_COL)[1]
    fill.histogram <- .dpOrDefault(GdObject, "fill.histogram", fill)[1]
    col.histogram <- .dpOrDefault(GdObject, "col.histogram", .dpOrDefault(GdObject, "col", .DEFAULT_SHADED_COL))[1]
    lty.grid <- .dpOrDefault(GdObject, "lty.grid", 1)
    lwd.grid <- .dpOrDefault(GdObject, "lwd.grid", 1)
    return(list(
        col = col, col.line = col.line, col.symbol = col.symbol, col.baseline = col.baseline,
        col.grid = col.grid, col.histogram = col.histogram, fill = fill, fill.histogram = fill.histogram,
        lwd = lwd, lty = lty, pch = pch, cex = cex, lwd.grid = lwd.grid, lty.grid = lty.grid
    ))
}


.legendInfo <- function() {
    legInfo <- matrix(FALSE, ncol = 7, nrow = 17, dimnames = list(
        c(
            "p", "b", "l", "a", "s", "S", "r", "h", "smooth",
            "histogram", "boxplot", "heatmap", "gradient", "mountain", "g", "horizon", "confint"
        ),
        c("lty", "lwd", "pch", "col", "cex", "col.lines", "col.symbol")
    ))
    legInfo[seq(2, 9), c("lty", "lwd", "col.lines")] <- TRUE
    legInfo[seq(1, 2), c("pch", "cex", "col.symbol")] <- TRUE
    legInfo[seq(1, 12), "col"] <- TRUE
    return(legInfo)
}

## A helper function to get the currently active chromosomes, also if the track is one of the collection
## track classes
.recChromosome <- function(GdObject) {
    chroms <- if (is(GdObject, "HighlightTrack") || is(GdObject, "OverlayTrack")) {
          unlist(lapply(GdObject@trackList, .recChromosome))
      } else {
        chromosome(GdObject)
    }
    return(unique(chroms))
}


## Plot a list of GdObjects as individual tracks similar to the display on the UCSC genome browser
## Arguments:
##    o trackList: a list of GdObjects
##    o from, to: the plotting range, will be figured out automatically from the tracks if missing
##    o sized: a vector of relative vertical sizes, or NULL to auto-detect
##    o panel.only: don't draw track titles, useful to embed in a lattice-like function, this also implies add=TRUE
##    o extend.right, extend.left: extend the coordinates in 'from' and 'too'
##    o title.width: the expansion factor for the width of the title track
## Value: the function is called for its side-effect of drawing on the graphics device
plotTracks <- function(trackList, from = NULL, to = NULL, ..., sizes = NULL, panel.only = FALSE, extend.right = 0,
                       extend.left = 0, title.width = NULL, add = FALSE, main, cex.main = 2, fontface.main = 2,
                       col.main = "black", margin = 6, chromosome = NULL, innerMargin = 3) {
    ## If we have to open a new device for this but do not run through the whole function because of errors we want to
    ## clean up in the end
    done <- FALSE
    cdev <- dev.cur()
    on.exit(if (cdev == 1 && !done) dev.off())
    ## We only need a new plot for regular calls to the function. Both add==TRUE and panel.only=TRUE will add to an existing grid plot
    if (!panel.only && !add) {
        grid.newpage()
    }
    if (!is.list(trackList)) {
        trackList <- list(trackList)
    }
    ## All arguments in ... are considered to be additional display parameters and need to be attached to each item in the track list
    dps <- list(...)
    trackList <- lapply(trackList, function(x) {
        displayPars(x, recursive = TRUE) <- dps
        return(x)
    })

    ## OverlayTracks and HighlightTracks can be discarded if they are empty
    trackList <- trackList[!vapply(trackList, function(x) (is(x, "HighlightTrack") || is(x, "OverlayTrack")) && length(x) < 1, FUN.VALUE = logical(1L))]
    isHt <- which(vapply(trackList, is, "HighlightTrack", FUN.VALUE = logical(1L)))
    isOt <- which(vapply(trackList, is, "OverlayTrack", FUN.VALUE = logical(1L)))
    ## A mix between forward and reverse strand tracks should trigger an alarm
    strds <- unique(.whichStrand(trackList))
    if (!is.null(strds) && length(strds) > 1) {
        warning("Plotting a mixture of forward strand and reverse strand tracks.\n Are you sure this is correct?")
    }
    ## We first run very general housekeeping tasks on the tracks for which we don't really need to know anything about device
    ## size, resolution or plotting ranges.
    ## Chromosomes should all be the same for all tracks, if not we will force them to be set to the first one that can be detected.
    ## If plotting ranges are supplied we can speed up a lot of the downstream operations by subsetting first.
    ## We may want to use alpha blending on those devices that support it, but also fall back to non-transparent colors without causing
    ## warnings.
    hasAlpha <- .supportsAlpha()
    chrms <- unique(unlist(lapply(trackList, .recChromosome)))
    if (is.null(chromosome)) {
        chrms <- if (!is.null(chrms)) chrms[gsub("^chr", "", chrms) != "NA"] else chrms
        chromosome <- head(chrms, 1)
        if (length(chromosome) == 0) {
            chromosome <- "chrNA"
        }
        if (!is.null(chrms) && length(unique(chrms)) != 1) {
            warning("The track chromosomes in 'trackList' differ. Setting all tracks to chromosome '", chromosome, "'", sep = "")
        }
    }
    if (!is.null(from) || !(is.null(to))) {
        trackList <- lapply(trackList, function(x) {
            chromosome(x) <- chromosome
            subset(x, from = from, to = to, chromosome = chromosome, sort = FALSE, stacks = FALSE, use.defaults = FALSE)
        })
    }
    trackList <- lapply(trackList, consolidateTrack,
        chromosome = chromosome, any(.needsAxis(trackList)), any(.needsTitle(trackList)),
        title.width, alpha = hasAlpha, ...
    )

    ## Now we figure out the plotting ranges. If no ranges are given as function arguments we take the absolute min/max of all tracks.
    ranges <- .defaultRange(trackList, from = from, to = to, extend.left = extend.left, extend.right = extend.right, annotation = TRUE)
    ## Now we can subset all the objects in the list to the current boundaries and compute the initial stacking
    trackList <- lapply(trackList, subset, from = ranges["from"], to = ranges["to"], chromosome = chromosome)
    trackList <- lapply(trackList, setStacks, recomputeRanges = FALSE)
    ## Highlight tracks are just a way to add a common highlighting region to several tracks, but other than that we can treat the containing
    ## tracks a normal track objects, and thus unlist them. We only want to record their indexes in the expanded list for later.
    htList <- list()
    expandedTrackList <- if (length(isHt)) {
        j <- 1
        tlTemp <- list()
        for (i in seq_along(trackList)) {
            if (!i %in% isHt) {
                tlTemp <- c(tlTemp, trackList[[i]])
                j <- j + 1
            } else {
                tlTemp <- c(tlTemp, trackList[[i]]@trackList)
                htList[[as.character(i)]] <- list(
                    indexes = j:(j + length(trackList[[i]]@trackList) - 1),
                    track = trackList[[i]]
                )
                j <- j + length(trackList[[i]]@trackList)
            }
        }
        tlTemp
    } else {
        trackList
    }
    ## If there is a AlignmentsTrack and also a SequenceTrack we can tell the former to use the latter, unless already provided
    isAt <- vapply(expandedTrackList, is, "AlignmentsTrack", FUN.VALUE = logical(1L))
    isSt <- vapply(expandedTrackList, is, "SequenceTrack", FUN.VALUE = logical(1L))
    for (ai in which(isAt)) {
        if (is.null(expandedTrackList[[ai]]@referenceSequence) && any(isSt)) {
            expandedTrackList[[ai]]@referenceSequence <- expandedTrackList[[min(which(isSt))]]
        }
    }
    ## We need to reverse the list to get a top to bottom plotting order
    expandedTrackList <- rev(expandedTrackList)
    map <- vector(mode = "list", length = length(expandedTrackList))
    titleCoords <- NULL
    names(map) <- rev(vapply(expandedTrackList, names, FUN.VALUE = character(1L)))
    ## Open a fresh page and set up the bounding box, unless add==TRUE
    if (!panel.only) {
        ## We want a margin pixel border
        ## for backward compatibility, if margin has length of 2,
        ## the first one will be used as a horizontal, second as a vertical margin
        if (length(margin) == 2) {
            margin <- rev(margin)
        }
        ## we switched to same settings as in par
        ## c(bottom, left, top, right)
        margin <- rep(as.numeric(margin), length.out = 4)
        vpWidth <- vpLocation()$size["width"]
        vpHeight <- vpLocation()$size["height"]
        vpBound <- viewport(
            x = margin[2L] / vpWidth, y = margin[1L] / vpHeight,
            width = (vpWidth - sum(margin[c(2, 4)])) / vpWidth,
            height = (vpHeight - sum(margin[c(1, 3)])) / vpHeight,
            just = c("left", "bottom")
        )
        pushViewport(vpBound)
        ## If there is a header we have to make some room for it here
        if (!missing(main) && main != "") {
            vpHeader <- viewport(width = 1, height = 0.1, y = 1, just = c("center", "top"))
            pushViewport(vpHeader)
            grid.text(main, gp = gpar(col = col.main, cex = cex.main, fontface = fontface.main))
            popViewport(1)
            vpMain <- viewport(width = 1, height = 0.9, y = 0.9, just = c("center", "top"))
        } else {
            vpMain <- viewport(width = 1, height = 1)
        }
        pushViewport(vpMain)
        ## A first guestimate of the vertical space that's needed
        spaceSetup <- .setupTextSize(expandedTrackList, sizes, title.width, spacing = innerMargin)
    } else {
        vpBound <- viewport()
        pushViewport(vpBound)
        spaceSetup <- .setupTextSize(expandedTrackList, sizes, spacing = innerMargin)
    }
    ## First iteration to set up all the dimensions by calling the drawGD methods in prepare mode, i.e.,
    ## argument prepare=TRUE. Nothing is drawn at this point, and this only exists to circumvent the
    ## chicken and egg problem of not knowing how much space we need until we draw, but also not knowing
    ## where to draw until we know the space needed.
    for (i in rev(seq_along(expandedTrackList)))
    {
        fontSettings <- .fontGp(expandedTrackList[[i]], cex = NULL)
        vpTrack <- viewport(
            x = 0, y = sum(spaceSetup$spaceNeeded[seq_len(i)]), just = c(0, 1), width = 1, height = spaceSetup$spaceNeeded[i],
            gp = fontSettings
        )
        pushViewport(vpTrack)
        vpContent <- if (!panel.only) {
            viewport(
                x = spaceSetup$title.width + spaceSetup$spacing,
                width = 1 - spaceSetup$title.width - spaceSetup$spacing * 2, just = 0
            )
        } else {
            viewport(width = 1)
        }
        pushViewport(vpContent)
        expandedTrackList[[i]] <- drawGD(expandedTrackList[[i]], minBase = ranges["from"], maxBase = ranges["to"], prepare = TRUE, subset = FALSE)
        popViewport(2)
    }
    ## Now lets recalculate the space and draw for real
    spaceSetup <- .setupTextSize(expandedTrackList, sizes, title.width, spacing = innerMargin)
    ## First the highlight box backgrounds
    htBoxes <- data.frame(stringsAsFactors = FALSE)
    for (hlite in htList) {
        if (length(ranges(hlite$track))) {
            inds <- setdiff(sort(length(expandedTrackList) - hlite$index + 1), which(vapply(expandedTrackList, is, "IdeogramTrack", FUN.VALUE = logical(1L))))
            y <- reduce(IRanges(start = inds, width = 1))
            yy <- ifelse(start(y) == 1, 0, sum(spaceSetup$spaceNeeded[seq_len(start(y)) - 1])) # check
            ht <- sum(spaceSetup$spaceNeeded[start(y):end(y)])
            htBoxes <- rbind(htBoxes, data.frame(
                y = yy, height = ht, x = start(hlite$track), width = width(hlite$track),
                col = .dpOrDefault(hlite$track, "col", "orange"),
                fill = .dpOrDefault(hlite$track, "fill", "red"),
                lwd = .dpOrDefault(hlite$track, "lwd", 1),
                lty = .dpOrDefault(hlite$track, "lty", 1),
                alpha = .dpOrDefault(hlite$track, "alpha", 1),
                inBackground = .dpOrDefault(hlite$track, "inBackground", TRUE),
                stringsAsFactors = FALSE
            ))
        }
    }
    .drawHtBoxes <- function(htBoxes, background = TRUE) {
        htBoxes <- htBoxes[htBoxes$inBackground == background, , drop = FALSE]
        rscales <- if (strds[1] == "reverse") c(from = ranges["to"], to = ranges["from"]) else ranges
        if (nrow(htBoxes)) {
            vpContent <- if (!panel.only) {
                viewport(
                    x = spaceSetup$title.width + spaceSetup$spacing, xscale = rscales,
                    width = 1 - spaceSetup$title.width - spaceSetup$spacing * 2, just = 0
                )
            } else {
                viewport(width = 1, xscale = rscales)
            }
            pushViewport(vpContent)
            grid.rect(
                x = htBoxes$x, just = c(0, 1), width = htBoxes$width, y = htBoxes$y + htBoxes$height, height = htBoxes$height,
                gp = gpar(col = htBoxes$col, fill = htBoxes$fill, lwd = htBoxes$lwd, lty = htBoxes$lty, alpha = unique(htBoxes$alpha)), default.units = "native"
            )
            popViewport(1)
        }
    }
    if (nrow(htBoxes)) {
        .drawHtBoxes(htBoxes)
    }
    ## Now the track content
    for (i in rev(seq_along(expandedTrackList)))
    {
        vpTrack <- viewport(x = 0, y = sum(spaceSetup$spaceNeeded[seq_len(i)]), just = c(0, 1), width = 1, height = spaceSetup$spaceNeeded[i])
        pushViewport(vpTrack)
        fill <- .dpOrDefault(expandedTrackList[[i]], "background.title", .DEFAULT_SHADED_COL)
        thisTrack <- if (is(expandedTrackList[[i]], "OverlayTrack")) expandedTrackList[[i]]@trackList[[1]] else expandedTrackList[[i]]
        if (!panel.only) {
            fontSettings <- .fontGp(expandedTrackList[[i]], subtype = "title", cex = NULL)
            vpTitle <- viewport(x = 0, width = spaceSetup$title.width, just = 0, gp = fontSettings)
            pushViewport(vpTitle)
            lwd.border.title <- .dpOrDefault(thisTrack, "lwd.title", 1)
            col.border.title <- .dpOrDefault(thisTrack, "col.border.title", "transparent")
            grid.rect(gp = gpar(fill = fill, col = col.border.title, lwd = lwd.border.title))
            needAxis <- .needsAxis(thisTrack)
            drawAxis(thisTrack, ranges["from"], ranges["to"], subset = FALSE)
            tit <- spaceSetup$nwrap[i]
            ## FIXME: Do we want something smarted for the image map coordinates?
            titleCoords <- rbind(titleCoords, cbind(.getImageMap(cbind(0, 0, 1, 1)),
                title = names(expandedTrackList[[i]])
            ))
            if (.dpOrDefault(thisTrack, "showTitle", TRUE) && !is.null(tit) && tit != "") {
                x <- if (needAxis) 0.075 else 0.4
                just <- if (needAxis) c("center", "top") else "center"
                ## FIXME: We need to deal with this when calculating the space for the title bar
                rot <- .dpOrDefault(thisTrack, "rotation.title", 90)
                gp <- .fontGp(thisTrack, "title", cex = spaceSetup$cex[i])
                suppressWarnings(grid.text(tit, unit(x, "npc"), rot = rot, gp = gp, just = just))
            }
            popViewport(1)
        }
        ## Draw the panel background, grid lines if necessary and the panel content
        vpBackground <- if (!panel.only) {
            viewport(
                x = spaceSetup$title.width,
                width = 1 - spaceSetup$title.width, just = 0
            )
        } else {
            viewport(width = 1)
        }
        pushViewport(vpBackground)
        grid.rect(gp = gpar(col = "transparent", fill = .dpOrDefault(thisTrack, "background.panel", "transparent")))
        drawGrid(thisTrack, ranges["from"], ranges["to"])
        popViewport(1)
        fontSettings <- .fontGp(expandedTrackList[[i]], cex = NULL)
        vpContentOuter <- if (!panel.only) {
            viewport(
                x = spaceSetup$title.width, width = 1 - spaceSetup$title.width,
                just = 0, gp = fontSettings, clip = TRUE
            )
        } else {
            viewport(width = 1, gp = fontSettings, clip = TRUE)
        }
        pushViewport(vpContentOuter)
        vpContent <- if (!panel.only) {
            viewport(x = spaceSetup$spacing, width = 1 - (spaceSetup$spacing * 2), just = 0, gp = fontSettings)
        } else {
            viewport(width = 1, gp = fontSettings)
        }
        pushViewport(vpContent)
        tmp <- drawGD(expandedTrackList[[i]], minBase = ranges["from"], maxBase = ranges["to"], subset = FALSE)
        if (!is.null(tmp)) {
            map[[(length(map) + 1) - i]] <- tmp
        }
        popViewport(2)
        if (.dpOrDefault(thisTrack, "frame", FALSE)) {
            grid.rect(gp = gpar(col = .dpOrDefault(thisTrack, "col.frame", .DEFAULT_SHADED_COL), fill = "transparent"))
        }
        popViewport(1)
    }
    if (nrow(htBoxes)) {
        .drawHtBoxes(htBoxes, FALSE)
    }
    popViewport(if (panel.only) 1 else 2)
    tc <- as.character(titleCoords[, 5])
    tc[which(tc == "" | is.na(tc) | is.null(tc))] <- "NA"
    names(tc) <- tc
    if (!is.null(titleCoords)) {
        tcoord <- as.matrix(titleCoords[, seq(1, 4)])
        rownames(tcoord) <- names(tc)
        map$titles <- ImageMap(coords = tcoord, tags = list(title = tc))
    }
    done <- TRUE
    return(invisible(map))
}



## Try to extract the (unique) genome information from a GRanges objects with the possibility to fall back to a default value
.getGenomeFromGRange <- function(range, default = NULL) {
    gn <- genome(range)
    if (length(unique(gn)) > 1) {
          warning("Only a single genome is supported for this object. Ignoring additional genome information")
      }
    if (length(gn) == 0 || all(is.na(gn))) {
        if (is.null(default)) {
              stop("A genome must be supplied when creating this object.")
          }
        return(default[1])
    }
    return(gn[1])
}










## Write all tracks in a list of tracks into
## a single BED file.
exportTracks <- function(tracks, range, chromosome, file) {
    if (missing(file)) {
          file <- "customTracks.bed"
      }
    con <- file(file, open = "wt")
    writeLines(
        sprintf("browser position %s:%i-%i", chromosome, range[1], range[2]),
        con
    )
    writeLines("browser hide all", con)
    for (t in seq_along(tracks))
    {
        track <- tracks[[t]]
        if (length(track) > 0 && (is(track, "AnnotationTrack") || is(track, "GeneRegion"))) {
            track <- as(track, "UCSCData")
            writeLines(as(track@trackLine, "character"), con)
            ## nextMet <- selectMethod("export.bed", c("RangedData", "characterORconnection"))
            ## nextMet(as(track, "GRanhes"), con)
            .expBed(as(track, "GRanges"), con)
        }
    }
    close(con)
}

## This funcion is broken in the rtracklayer package
.expBed <- function(object, con, variant = c("base", "bedGraph", "bed15"), color, append) {
    variant <- match.arg(variant)
    name <- strand <- thickStart <- thickEnd <- color <- NULL
    blockCount <- blockSizes <- blockStarts <- NULL
    df <- data.frame(as.character(seqnames(object)), start(object) - 1, end(object))
    score <- score(object)
    if (!is.null(score)) {
        if (!is.numeric(score) || any(is.na(score))) {
            stop("Scores must be non-NA numeric values")
        }
    }
    if (variant == "bedGraph") {
        if (is.null(score)) {
            score <- 0
        }
        df$score <- score
    }
    else {
        blockSizes <- object$blockSizes
        blockStarts <- object$blockStarts
        if (variant == "bed15" && is.null(blockSizes)) {
            blockStarts <- blockSizes <- ""
        }
        if (!is.null(blockSizes) || !is.null(blockStarts)) {
            if (is.null(blockSizes)) {
                stop("'blockStarts' specified without 'blockSizes'")
            }
            if (is.null(blockStarts)) {
                stop("'blockSizes' specified without 'blockStarts'")
            }
            lastBlock <- function(x) sub(".*,", "", x)
            lastSize <- lastBlock(blockSizes)
            lastStart <- lastBlock(blockStarts)
            if (any(df[[2]] + as.integer(lastSize) + as.integer(lastStart) != df[[3]]) ||
                any(sub(",.*", "", blockStarts) != 0)) {
                stop("blocks must span entire feature")
            }
            blockCount <- vapply(strsplit(blockSizes, ","), length, FUN.VALUE = numeric(1L))
        }
        if (is.null(color)) {
            color <- object$itemRgb
        }
        if (is.null(color) && !is.null(blockCount)) {
            color <- "0"
        } else if (!is.null(color)) {
            nacol <- is.na(color)
            colmat <- col2rgb(color)
            color <- paste(colmat[1, ], colmat[2, ], colmat[3, ], sep = ",")
            color[nacol] <- "0"
        }
        thickStart <- object$thickStart
        thickEnd <- object$thickEnd
        if (is.null(thickStart) && !is.null(color)) {
            thickStart <- start(object)
            thickEnd <- end(object)
        }
        strand <- object$strand
        if (!is.null(thickStart) && is.null(strand)) {
            strand <- rep(NA, length(object))
        }
        if (!is.null(strand) && is.null(score)) {
            score <- 0
        }
        name <- object$name
        if (is.null(name)) {
            name <- rownames(object)
        }
        if (!is.null(score) && is.null(name)) {
            name <- rep(NA, length(object))
        }
        df$name <- name
        df$score <- score
        df$strand <- strand
        df$thickStart <- thickStart
        df$thickEnd <- thickEnd
        df$itemRgb <- color
        df$blockCount <- blockCount
        df$blockSizes <- blockSizes
        df$blockStarts <- blockStarts
        if (variant == "bed15") {
            df$expCount <- object$expCount
            df$expIds <- object$expIds
            df$expScores <- object$expScores
        }
    }
    scipen <- getOption("scipen")
    options(scipen = 100)
    on.exit(options(scipen = scipen))
    write.table(df, con,
        sep = "\t", col.names = FALSE, row.names = FALSE,
        quote = FALSE, na = ".", append = append
    )
}





## ## Construct a URL to UCSC showing the custom tracks
## ucscUrl <- function(chr, range, spec, gen, open=TRUE)
## {
##     hgid <- system(sprintf("%s %s %s", system.file("lib/testUCSC.pl", package="Gviz"),
##                            "customTracks.bed", spec, gen), intern=TRUE, ignore.stderr=TRUE)
##
##     url <- sprintf(paste("http://genome.ucsc.edu/cgi-bin/hgTracks?hgsid=%s&Submit=go+to+genome+browser",
##                          "&position=%s%%3A%i-%i", sep=""), hgid, chr, range[1], range[2])
##     if(open)
##         browseURL(url)
##     return(url)
## }




.updateObj <- function(object) {
    availSlots <- getObjectSlots(object)
    availSlotNames <- names(availSlots)
    definedSlotNames <- slotNames(object)
    if (length(availSlotNames) == length(definedSlotNames) && all(sort(availSlotNames) == sort(definedSlotNames))) {
          return(object)
      }
    commonSlots <- intersect(definedSlotNames, availSlotNames)
    missingSlots <- setdiff(definedSlotNames, availSlotNames)
    newObject <- new(class(object))
    for (s in commonSlots) {
          slot(newObject, s) <- availSlots[[s]]
      }
    return(newObject)
}



vpLocation <- function() {
    xres <- devRes()[1]
    yres <- devRes()[2]
    ## find location and pixel-size of current viewport
    devloc1 <- c(
        convertX(unit(0, "npc"), "inches"),
        convertY(unit(0, "npc"), "inches"), 1
    ) %*% current.transform()
    devloc2 <- c(
        convertX(unit(1, "npc"), "inches"),
        convertY(unit(1, "npc"), "inches"), 1
    ) %*% current.transform()
    x1 <- (devloc1 / devloc1[3])[1] * xres
    y1 <- (devloc1 / devloc1[3])[2] * yres
    x2 <- (devloc2 / devloc2[3])[1] * xres
    y2 <- (devloc2 / devloc2[3])[2] * yres
    loc <- c(x1, y1, x2, y2)
    names(loc) <- c("x1", "y1", "x2", "y2")
    size <- c(x2 - x1, y2 - y1)
    names(size) <- c("width", "height")
    iloc <- c(x1 / xres, y1 / yres, x2 / yres, y2 / yres)
    names(iloc) <- c("x1", "y1", "x2", "y2")
    isize <- size / c(xres, yres)
    names(size) <- c("width", "height")
    return(list(
        location = loc, size = size, ilocation = iloc,
        isize = isize
    ))
}



devRes <- function() {
    ## find R's resolution for the current device
    if (current.viewport()$name != "ROOT") {
        vpt <- current.vpTree()
        depth <- upViewport(0)
        xres <- abs(as.numeric(convertWidth(unit(1, "inches"), "native")))
        yres <- abs(as.numeric(convertHeight(unit(1, "inches"), "native")))
        downViewport(depth)
    } else {
        xres <- abs(as.numeric(convertWidth(unit(1, "inches"), "native")))
        yres <- abs(as.numeric(convertHeight(unit(1, "inches"), "native")))
    }
    retval <- c(xres, yres)
    names(retval) <- c("xres", "yres")
    return(retval)
}



devDims <- function(width, height, ncol = 12, nrow = 8, res = 72) {
    f <- (((ncol + 1) * 0.1 + ncol + 1) / ((nrow + 1) * 0.1 + nrow + 1))
    if ((missing(width) & missing(height) || !missing(width) & !missing(height))) {
          stop("Need either argument 'width' or argument 'height'")
      }
    if (missing(height)) {
          return(list(width = width, height = width / f, pwidth = width * res, pheight = width / f * res))
      } else {
          return(list(width = height * f, height, pwidth = height * f * res, pheight = height * res))
      }
}

## Record the display parameters for each class once
.makeParMapping <- function() {
    classes <- c(
        "GdObject", "GenomeAxisTrack", "RangeTrack", "NumericTrack", "DataTrack", "IdeogramTrack", "StackedTrack",
        "AnnotationTrack", "DetailsAnnotationTrack", "GeneRegionTrack", "BiomartGeneRegionTrack", "AlignmentsTrack", "AlignedReadTrack"
    )
    defs <- try(lapply(classes, function(x) as(getClassDef(x)@prototype@dp, "list")), silent = TRUE)
    if (!is(defs, "try-error") && is.null(.parMappings)) {
        names(defs) <- classes
        assignInNamespace(x = ".parMappings", value = defs, ns = "Gviz")
    }
}
.parMappings <- NULL

## Show available display parameters for a class and their defaults
availableDisplayPars <- function(class) {
    if (!is.character(class)) {
          class <- class(class)
      }
    class <- match.arg(class, c(
        "GdObject", "GenomeAxisTrack", "RangeTrack", "NumericTrack", "DataTrack", "IdeogramTrack", "StackedTrack",
        "AnnotationTrack", "DetailsAnnotationTrack", "GeneRegionTrack", "BiomartGeneRegionTrack", "AlignedReadTrack",
        "AlignmentsTrack", "SequenceTrack", "SequenceBSgenomeTrack", "SequenceDNAStringSetTrack", "SequenceRNAStringSetTrack"
    ))
    parents <- names(getClassDef(class)@contains)
    .makeParMapping()
    pars <- .parMappings[c(parents, class)]
    finalPars <- inherited <- list()
    for (p in names(pars)) {
        finalPars[names(pars[[p]])] <- pars[[p]]
        inherited[names(pars[[p]])] <- p
    }
    finalPars <- finalPars[order(names(finalPars))]
    inherited <- inherited[order(names(inherited))]
    return(new("InferredDisplayPars", name = class, inheritance = unlist(inherited), finalPars))
}

## Compute ellipse outline coordinates for bounding boxes
.box2Ellipse <- function(box, np = 50) {
    t <- seq(0, 2 * pi, len = np)
    box$width <- box$cx2 - box$cx1
    box$height <- box$cy2 - box$cy1
    x <- rep(box$cx2 + -box$width + box$width / 2, each = np)
    y <- rep(box$cy1 + box$height / 2, each = np)
    a <- rep(box$width / 2, each = np)
    b <- rep(box$height / 2, each = np)
    tau <- 0
    xt <- x + (a * cos(t) * cos(tau) - b * sin(t) * sin(tau))
    yt <- y + (a * cos(t) * sin(tau) + b * sin(t) * cos(tau))
    return(data.frame(x1 = xt, y1 = yt, id = rep(seq_len(nrow(box)), each = np)))
}

## We store some preset in the options on package load
.onLoad <- function(...) {
    .collectSchemes()
    options("ucscChromosomeNames" = TRUE, "Gviz.scheme" = "default", "Gviz.ucscUrl" = NULL)
}


## A helper function to replace missing function arguments in a list with NULL values. The function environment needs
## to be passed in as argument 'env' for this to work.
.missingToNull <- function(symbol, env = parent.frame()) {
    for (i in symbol) {
        mis <- try(do.call(missing, args = list(i), envir = env), silent = TRUE)
        if (!is(mis, "try-error") && mis) {
              assign(i, NULL, env)
          }
    }
}


## build a covariates data.frame from a variety of different inputs
.getCovars <- function(x) {
    if (is.data.frame(x)) {
        x
    } else {
        if (is(x, "GRanges")) {
            as.data.frame(mcols(x))
        } else {
            if (is(x, "GRangesList")) {
                as.data.frame(mcols(unlist(x)))
            } else {
                data.frame()
                ## stop(sprintf("Don't know how to extract covariates from a %s object", class(x)))
            }
        }
    }
}


## Prepare a data.frame or matrix containing the data for a DataTrack object. This involves trying to coerce
## and dropping non-numeric columns with a warning
.prepareDtData <- function(data, len = 0) {
    if (ncol(data) && nrow(data)) {
        for (i in seq_along(data)) {
            if (is.character(data[, i])) {
                data[, i] <- type.convert(data[, i], as.is = TRUE)
            }
        }
        isNum <- vapply(data, is.numeric, FUN.VALUE = logical(1L))
        if (any(!isNum)) {
            warning(sprintf(
                "The following non-numeric data column%s been dropped: %s", ifelse(sum(!isNum) > 1, "s have", " has"),
                paste(colnames(data)[!isNum], collapse = ", ")
            ))
        }
        if (sum(dim(data)) > 0) {
            data <- t(data[, isNum, drop = FALSE])
        }
    } else {
        data <- matrix(ncol = len, nrow = 0)
    }
    if (all(is.na(data))) {
        data <- matrix(ncol = len, nrow = 0)
    }
    if (ncol(data) != len) {
        stop("The columns in the 'data' matrix must match the genomic regions.")
    }
    return(data)
}



## An import function for gff3 files that tries to resolve the parent-child relationship
## between genes, transcripts and exons
.import.gff3 <- function(file) {
    dat <- import.gff3(file)
    res <- try({
        genes <- tolower(dat$type) == "gene"
        ginfo <- mcols(dat[genes, ])
        dat <- dat[!genes]
        transcripts <- tolower(dat$type) == "mrna"
        tinfo <- mcols(dat[transcripts, ])
        dat <- dat[!transcripts]
        mt <- match(as.character(dat$Parent), as.character(tinfo$ID))
        if (!all(is.na(mt))) {
            if (!"transcript_id" %in% colnames(mcols(dat))) {
                tid <- rep(NA, length(mt))
                tid[!is.na(mt)] <- tinfo[mt[!is.na(mt)], "ID"]
                mcols(dat)[["transcript_id"]] <- tid
            }
            if (!"transcript_name" %in% colnames(mcols(dat))) {
                tn <- rep(NA, length(mt))
                tn[!is.na(mt)] <- tinfo[mt[!is.na(mt)], "Name"]
                mcols(dat)[["transcript_name"]] <- tn
            }
            mt2 <- rep(NA, dim(mcols(dat))[1])
            mt2[!is.na(mt)] <- match(as.character(tinfo[mt[!is.na(mt)], "Parent"]), as.character(ginfo$ID))

            if (!all(is.na(mt2))) {
                if (!"gene_id" %in% colnames(mcols(dat))) {
                    gid <- rep(NA, length(mt2))
                    gid[!is.na(mt2)] <- ginfo[mt[!is.na(mt2)], "ID"]
                    mcols(dat)[["gene_id"]] <- gid
                }
                if (!"gene_name" %in% colnames(mcols(dat))) {
                    gn <- rep(NA, length(mt2))
                    gn[!is.na(mt2)] <- ginfo[mt[!is.na(mt2)], "Name"]
                    mcols(dat)[["gene_name"]] <- gn
                }
            }
        }
        if (all(is.na(mcols(dat)[["ID"]]))) {
              mcols(dat)[["ID"]] <- paste("item", seq_along(dat), sep = "_")
          }
        if (!"exon_id" %in% colnames(mcols(dat))) {
              mcols(dat)[["exon_id"]] <- mcols(dat)[["ID"]]
          }
        if (!is.null(mcols(dat)[["gene_name"]]) && all(is.na(mcols(dat)[["gene_name"]]))) {
              mcols(dat)[["gene_name"]] <- NULL
          }
        if (all(is.na(mcols(dat)[["transcript_name"]]))) {
              mcols(dat)[["transcript_name"]] <- NULL
          }
        dat
    })
    if (is(res, "try-error")) {
        warning(sprintf(paste(
            "File '%s' is not valid according to the GFF3 standard and can not be properly parsed.",
            "Results may not be what you expected!"
        ), file))
        res <- dat
    }
    return(res)
}

## An import function for bigWig files that knowns how to deal with missing seqnames
.import.bw <- function(file, selection) {
    bwf <- BigWigFile(path.expand(file))
    if (missing(selection)) {
        rr <- import.bw(con = bwf)
    } else {
        si <- seqinfo(bwf)
        rr <- if (!as.character(seqnames(selection)[1]) %in% seqnames(seqinfo(bwf))) {
            GRanges(seqnames(selection)[1], ranges = IRanges(1, 2), score = 1)[0]
        } else {
            import.bw(con = bwf, selection = selection)
        }
    }
    return(rr)
}

## An import function for bam files that distinguishes between DataTracks and AnnotationTracks
## FIXME: We probably want this to be able to deal with Gapped Alignments...
.import.bam <- function(file, selection) {
    if (!file.exists(paste(file, "bai", sep = ".")) &&
        !file.exists(paste(paste(head(strsplit("xxx.bam", ".", fixed = TRUE)[[1]], -1), collapse = "."), "bai", sep = "."))) {
          stop(
              "Unable to find index for BAM file '", file, "'. You can build an index using the following command:\n\t",
              "library(Rsamtools)\n\tindexBam(\"", file, "\")"
          )
      }
    sinfo <- scanBamHeader(file)[[1]]
    if (parent.env(environment())[["._trackType"]] == "DataTrack") {
        res <- if (!as.character(seqnames(selection)[1]) %in% names(sinfo$targets)) {
            mcols(selection) <- DataFrame(score = 0)
            selection
        } else {
            param <- ScanBamParam(what = c("pos", "qwidth"), which = selection, flag = scanBamFlag(isUnmappedQuery = FALSE))
            x <- scanBam(file, param = param)[[1]]
            cov <- coverage(IRanges(x[["pos"]], width = x[["qwidth"]]))
            if (length(cov) == 0) {
                mcols(selection) <- DataFrame(score = 0)
                selection
            } else {
                GRanges(seqnames = seqnames(selection), ranges = IRanges(start = start(cov), end = end(cov)), strand = "*", score = runValue(cov))
            }
        }
    } else {
        res <- if (!as.character(seqnames(selection)[1]) %in% names(sinfo$targets)) {
            mcols(selection) <- DataFrame(id = "NA", group = "NA")
            selection[0]
        } else {
            param <- ScanBamParam(what = c("pos", "qwidth", "strand", "qname"), which = selection, flag = scanBamFlag(isUnmappedQuery = FALSE))
            x <- scanBam(file, param = param)[[1]]
            GRanges(
                seqnames = seqnames(selection), ranges = IRanges(start = x[["pos"]], width = x[["qwidth"]]), strand = x[["strand"]],
                id = make.unique(x[["qname"]]), group = x[["qname"]]
            )
        }
    }
    return(res)
}

.import.bam.alignments <- function(file, selection) {
    indNames <- c(sub("\\.bam$", ".bai", file), paste(file, "bai", sep = "."))
    index <- NULL
    for (i in indNames) {
        if (file.exists(i)) {
            index <- i
            break
        }
    }
    if (is.null(index)) {
          stop(
              "Unable to find index for BAM file '", file, "'. You can build an index using the following command:\n\t",
              "library(Rsamtools)\n\tindexBam(\"", file, "\")"
          )
      }
    pairedEnd <- parent.env(environment())[["._isPaired"]]
    if (is.null(pairedEnd)) {
          pairedEnd <- TRUE
      }
    flag <- parent.env(environment())[["._flag"]]
    if (is.null(flag)) {
          flag <- scanBamFlag(isUnmappedQuery = FALSE)
      }
    bf <- BamFile(file, index = index, asMates = pairedEnd)
    param <- ScanBamParam(which = selection, what = scanBamWhat(), tag = "MD", flag = flag)
    reads <- if (as.character(seqnames(selection)[1]) %in% names(scanBamHeader(bf)$targets)) scanBam(bf, param = param)[[1]] else list()
    md <- if (is.null(reads$tag$MD)) rep(as.character(NA), length(reads$pos)) else reads$tag$MD
    if (length(reads$pos)) {
        layed_seq <- sequenceLayer(reads$seq, reads$cigar)
        region <- unlist(bamWhich(param), use.names = FALSE)
        ans <- stackStrings(layed_seq, start(region), end(region), shift = reads$pos - 1L, Lpadding.letter = "+", Rpadding.letter = "+")
        names(ans) <- seq_along(reads$qname)
    } else {
        ans <- DNAStringSet()
    }
    return(GRanges(
        seqnames = if (is.null(reads$rname)) character() else reads$rname,
        strand = if (is.null(reads$strand)) character() else reads$strand,
        ranges = IRanges(start = reads$pos, width = reads$qwidth),
        id = if (is.null(reads$qname)) character() else reads$qname,
        cigar = if (is.null(reads$cigar)) character() else reads$cigar,
        mapq = if (is.null(reads$mapq)) integer() else reads$mapq,
        flag = if (is.null(reads$flag)) integer() else reads$flag,
        md = md, seq = ans,
        isize = if (is.null(reads$isize)) integer() else reads$isize,
        groupid = if (pairedEnd) if (is.null(reads$groupid)) integer() else reads$groupid else seq_along(reads$pos),
        status = if (pairedEnd) {
            if (is.null(reads$mate_status)) factor(levels = c("mated", "ambiguous", "unmated")) else reads$mate_status
        } else {
            rep(
                factor("unmated", levels = c("mated", "ambiguous", "unmated")),
                length(reads$pos)
            )
        }
    ))
}


## An import function for fasta file that supports streaming if an index is present
.import.fasta <- function(file, selection, strict = TRUE) {
    ffile <- FastaFile(file)
    if (!file.exists(paste(file, "fai", sep = "."))) {
        if (strict) {
            stop(
                "Unable to find index for fasta file '", file, "'. You can build an index using the following command:\n\t",
                "library(Rsamtools)\n\tindexFa(\"", file, "\")"
            )
        } else {
            return(readDNAStringSet(file))
        }
    }
    idx <- scanFaIndex(file)
    if (!as.character(seqnames(selection)[1]) %in% as.character(seqnames(idx))) {
        return(DNAStringSet())
    } else {
        return(scanFa(file, selection))
    }
}


## An import function for the indexed 2bit format
.import.2bit <- function(file, selection) {
    tbf <- TwoBitFile(file)
    if (!as.character(seqnames(selection)[1]) %in% seqnames(seqinfo(tbf))) {
        return(DNAStringSet())
    } else {
        tmp <- import(tbf, which = selection)
        names(tmp) <- as.character(seqnames(selection)[1])
        return(tmp)
    }
}



## A mapping of (lower-cased) file extensions to import function calls. Most of those are already implemented in the rtracklayer package.
## If no mapping is found an error will be raised suggesting to provide a user-defined import function.
.registerImportFun <- function(file) {
    fileExt <- .fileExtension(file)
    file <- path.expand(file)
    return(switch(fileExt,
        "gff" = import.gff(file),
        "gff1" = import.gff1(file),
        "gff2" = import.gff2(file),
        "gff3" = .import.gff3(file),
        "gtf" = import.gff2(file),
        "bed" = import.bed(file),
        "bedgraph" = import.bedGraph(file),
        "wig" = import.wig(file),
        "bw" = .import.bw,
        "bigwig" = .import.bw,
        "bam" = .import.bam,
        stop(sprintf(
            "No predefined import function exists for files with extension '%s'. Please manually provide an import function.",
            fileExt
        ))
    ))
}


## Get the file extension for a file, taking into account potential gzipping
.fileExtension <- function(file) {
    if (!grepl("\\.", file)) {
          stop("Unable to identify extension for file '", file, "'")
      }
    ext <- sub(".*\\.", "", sub("\\.gz$|\\.gzip$", "", basename(file)))
    if (ext == "") {
          stop("Unable to identify extension for file '", file, "'")
      }
    return(tolower(ext))
}


availableDefaultMapping <- function(file, trackType) {
    .checkClass(file, "character", 1)
    .checkClass(trackType, "character", 1)
    ext <- tolower(if (grepl("\\.", file)) .fileExtension(file) else file)
    vm <- .defaultVarMap(ext, trackType, justMap = TRUE)
    vm[[".stream"]] <- NULL
    return(vm)
}


## Helper function to handle defaults function arguments
.covDefault <- function(x, cov, def) {
    res <- if (missing(x)) {
        if (is.null(cov)) {
            def
        } else {
            cov
        }
    } else {
        x
    }
    return(res)
}


## A helper function to process alignment information from a GRanges object
.computeAlignments <- function(range, drop.D.ranges = FALSE) {
    res <- list(range = range, stackRanges = GRanges(), stacks = numeric())
    if (length(range)) {
        alg <- extractAlignmentRangesOnReference(range$cigar, drop.D.ranges = drop.D.ranges)
        rp <- elementNROWS(alg)
        range <- sort(GRanges(
            seqnames = rep(seqnames(range), rp), strand = rep("*", sum(rp)), ranges = shift(unlist(alg), rep(start(range), rp) - 1),
            id = rep(range$id, rp), entityId = rep(seq_along(rp), rp), cigar = rep(range$cigar, rp), md = rep(range$md, rp),
            readStrand = rep(strand(range), rp), mapq = rep(range$mapq, rp), flag = rep(range$flag, rp), isize = rep(range$isize, rp),
            groupid = rep(range$groupid, rp), status = factor(rep(range$status, rp), levels = c("mated", "ambiguous", "unmated")),
            uid = seq_len(sum(rp))
        ))
        if (length(range)) {
            stTmp <- split(range, range$groupid)
            stackRanges <- unlist(range(stTmp))
            ss <- disjointBins(stackRanges)
            range$stack <- ss[match(range$groupid, names(stackRanges))]
            res <- list(range = range, stackRanges = stackRanges, stacks = range$stack)
        }
    }
    return(res)
}

## Check whether the current device supports alpha channel transparency
.supportsAlpha <- function() {
    d <- dev.cur()
    oldwarn <- getOption("warn")
    on.exit({
        options(warn = oldwarn)
        if (d == 1) {
              dev.off()
          }
    })
    options(warn = 2)
    ok <- !is(try(
        {
            grob <- grid.rect(x = unit(0, "npc"), y = unit(0, "npc"), width = 0, height = 0, gp = gpar(alpha = 0.5))
            ## grid.remove(grob$name)
        },
        silent = TRUE
    ), "try-error")
    return(ok)
}

## Return the alpha display parameter from a GdObject, respecting whether transparency is supported on the device
## or not. This is either drawn from the internal '.__hasAlphaSupport' display parameter, or, if not set, is
## determined dynamically.
.alpha <- function(GdObject, postfix = NULL) {
    support <- .dpOrDefault(GdObject, ".__hasAlphaSupport", .supportsAlpha())
    wh <- if (is.null(postfix)) "alpha" else c(paste("alpha", postfix, sep = "."), "alpha")
    alpha <- .dpOrDefault(GdObject, wh, 1)
    if (alpha != 1 && !support) {
          alpha <- 1
      }
    return(alpha)
}

## Draw horizontal arrows into a viewport indicating cropped read alignments
.moreInd <- function(n = 3, direction = "up", ...) {
    nn <- n * 2 + 1
    x <- rep(seq(1 / nn, 1 - (1 / nn), len = nn - 2)[seq(1, nn - 2, by = 2)], each = n) + c(-1 / nn / 2, 0, 1 / nn / 2)
    y <- rep(if (direction == "up") c(0, 1, 0) else c(1, 0, 1), n)
    grid.polyline(x, y, id = rep(seq_len(n), each = 3), gp = gpar(...))
}



## Compute mismatches for AlignmentsTracks based on the read sequences and a reference sequence
.findMismatches <- function(GdObject) {
    rgo <- .dpOrDefault(GdObject, ".__plottingRange")
    mmPos <- mmSamp <- mmSeq <- mmStack <- NULL
    if (!is.null(rgo)) {
        ref <- as.character(as(subseq(GdObject@referenceSequence, start = rgo["from"], end = rgo["to"]), "Rle"))
        cm <- consensusMatrix(GdObject@sequences, as.prob = FALSE, baseOnly = TRUE)[-5, ]
        cmm <- colMaxs(cm)
        css <- colSums(cm)
        cmp <- rbind(t(t(cm) / css), 0)
        rownames(cmp)[5] <- "N"
        sel <- is.na(cmp["A", ])
        cmp[, sel] <- 0
        cmp["N", sel] <- 1
        consStr <- strsplit(consensusString(cmp), "")[[1]]
        varRegs <- which(cmm != css | (consStr != "N" & consStr != ref))
        if (length(varRegs)) {
            rvg <- ref[varRegs]
            sel <- rvg != "-" & rvg != "N"
            if (any(sel)) {
                varRegs <- varRegs[sel]
                rvg <- rvg[sel]
                mmTab <- do.call(rbind, lapply(varRegs, function(x) as.character(subseq(GdObject@sequences, x, width = 1))))
                isMm <- t(rvg != "-" & mmTab != "+" & mmTab != "-" & mmTab != rvg)
                mmRelPos <- col(isMm)[which(isMm)]
                mmPos <- varRegs[mmRelPos] + rgo["from"] - 1
                mmSampInd <- row(isMm)[which(isMm)]
                mmSamp <- rownames(isMm)[mmSampInd]
                mmSeq <- mmTab[ncol(isMm) * (mmSampInd - 1) + mmRelPos]
                mmStack <- stacks(GdObject)[match(mmSamp, ranges(GdObject)$entityId)]
            }
        }
    }
    return(data.frame(position = mmPos, stack = mmStack, read = mmSamp, base = as.character(mmSeq), stringsAsFactors = TRUE))
}





## Return the default mappings between the metadata columns of an imported GRanges object and those
## of the track's GRanges object.
.defaultVarMap <- function(inputType, trackType, stream, fromUser = FALSE, justMap = FALSE) {
    vm <- list(
        gtf = list(GeneRegionTrack = list(
            feature = "type",
            gene = c("gene_id", "gene_name"),
            exon = c("exon_name", "exon_id"),
            transcript = c("transcript_name", "transcript_id"),
            symbol = c("gene_name", "gene_id")
        )),
        gff = list(
            AnnotationTrack = list(
                feature = "type",
                group = "group"
            ),
            GeneRegionTrack = list(
                feature = "type",
                transcript = "group"
            )
        ),
        gff1 = list(
            AnnotationTrack = list(
                feature = "type",
                group = group
            ),
            GeneRegionTrack = list(
                feature = "type",
                transcript = "group"
            )
        ),
        gff2 = list(
            AnnotationTrack = list(
                feature = "type",
                group = c("group", "Parent"),
                id = c("ID", "Name", "Alias")
            ),
            GeneRegionTrack = list(
                feature = "type",
                gene = c("gene_id", "gene_name"),
                exon = c("exon_name", "exon_id"),
                symbol = c("gene_name", "gene_id")
            )
        ),
        gff3 = list(
            AnnotationTrack = list(
                feature = "type",
                id = c("ID", "Name", "Alias"),
                group = "Parent"
            ),
            GeneRegionTrack = list(
                feature = "type",
                gene = c("gene_id", "gene_name"),
                exon = c("exon_name", "exon_id", "ID"),
                transcript = c("transcript_name", "transcript_id", "Parent"),
                symbol = c("gene_name", "gene_id", "Name", "Alias")
            )
        ),
        bedgraph = list(DataTrack = list(score = "score")),
        wig = list(DataTrack = list(score = "score")),
        bed = list(AnnotationTrack = list(feature = "itemRgb", id = "name")),
        bigwig = list(DataTrack = list(
            score = "score",
            .stream = TRUE
        )),
        bw = list(DataTrack = list(
            score = "score",
            .stream = TRUE
        )),
        bam = list(
            DataTrack = list(
                score = "score",
                .stream = TRUE
            ),
            AnnotationTrack = list(
                id = "id",
                group = "group",
                .stream = TRUE
            ),
            AlignmentsTrack = list(
                id = "id",
                cigar = "cigar",
                mapq = "mapq",
                flag = "flag",
                isize = "isize",
                groupid = "groupid",
                status = "status",
                md = "md",
                seq = "seq",
                .stream = TRUE
            )
        )
    )
    if (justMap) {
          return(vm[[inputType]][[trackType]])
      }
    if (fromUser) {
        vm[[inputType]] <- setNames(list(list(".stream" = stream)), trackType)
    } else {
        if (is.null(vm[[inputType]]) || is.null(vm[[inputType]][[trackType]])) {
            warning(sprintf(
                paste(
                    "There are no default mappings from %s files to %s. Please provide a manual mapping",
                    "in the track constructor if you haven't already done so."
                ),
                inputType, trackType
            ))
            vm[[inputType]] <- setNames(list(list(".stream" = stream)), trackType)
        }
    }
    return(vm[[inputType]][[trackType]])
}


## Helper function to go through the metadata columns of a DataFrame and match their colnames to a mapping if they
## are available
.resolveColMapping <- function(data, args, defMap) {
    colnames(mcols(data)) <- paste(colnames(mcols(data)), "orig", sep = "__.__")
    for (i in names(defMap)) {
        if (is.character(args[[i]]) && length(args[[i]]) == 1 && paste(args[[i]], "orig", sep = "__.__") %in% colnames(mcols(data))) {
            defMap[[i]] <- args[[i]]
            args[[i]] <- NULL
        }
        mt <- match(paste(defMap[[i]], "orig", sep = "__.__"), colnames(mcols(data)))
        mt <- mt[!is.na(mt)][1]
        if (!is.na(mt)) {
              mcols(data)[[i]] <- mcols(data)[, mt]
          }
    }
    mcols(data) <- mcols(data)[, !grepl("__.__", colnames(mcols(data))), drop = FALSE]
    return(list(data = data, args = args, defMap = defMap))
}


## For an AnnotationTrack or a GeneRegionTrack, compute the actual ranges for a complete range element group
## (i.e., a whole transcript or track group) and also add the necessary space for the text label if needed.
## We add this information to the internal display parameters '.__groupRanges', '.__groupLabels' and
## '.__groupLabelWidths'. This function has to be called before stacks are being computed because 'setStacks'
## will use the values in '.__groupRanges'.
## Note that the computed ranges are not quite right because we are only crudely guessing the size of the
## title panels at this stage.
.computeGroupRange <- function(GdObject, hasAxis = FALSE, hasTitle = .dpOrDefault(GdObject, "showTitle", TRUE), title.width = 1) {
    if (is(GdObject, "AnnotationTrack")) {
        finalRanges <- IRanges()
        GdObjectOrig <- GdObject
        GdObject <- GdObject[seqnames(GdObject) == chromosome(GdObject)]
        pr <- .dpOrDefault(GdObject, ".__plottingRange", data.frame(from = min(start(GdObject)), to = max(end(GdObject))))
        if (is.null(title.width)) {
            title.width <- 1
        }
        if (length(GdObject) > 0) {
            gp <- group(GdObject)
            needsGrp <- any(duplicated(gp))
            finalRanges <- if (needsGrp) {
                groups <- split(range(GdObject), gp)
                unlist(range(groups))
            } else {
                range(GdObject)
            }
            if (.dpOrDefault(GdObject, ".__hasAnno", FALSE)) {
                ## The label justification
                just <- .dpOrDefault(GdObject, "just.group", "left")
                rev <- .dpOrDefault(GdObject, "reverseStrand", FALSE)
                ## A crude guestimate of the space needed for a title
                twidth <- if (hasTitle) {
                    fact <- title.width + (hasAxis * 2)
                    .getStringDims(GdObject, "g_T", unit = "npc", subtype = "title")$height * fact
                } else {
                    0
                }
                tfact <- ifelse(twidth > 1, 1, 1 / (1 - twidth))
                ## The labels and spacers are plotted in a temporary viewport to figure out their size
                labels <- if (needsGrp) {
                    vapply(split(identifier(GdObject), gp), function(x) paste(sort(unique(x)), collapse = "/"), FUN.VALUE = character(1L))
                } else {
                    setNames(identifier(GdObject), gp)
                }
                xscale <- c(max(pr["from"], min(start(finalRanges))), min(pr["to"], max(end(finalRanges))))
                if (diff(xscale) == 0) {
                    xscale[2] <- xscale[2] + 1
                }
                pushViewport(dataViewport(xscale = xscale, extension = 0, yscale = c(0, 1), gp = .fontGp(GdObject, "group")))
                labelWidths <- setNames(as.numeric(convertWidth(stringWidth(labels), "native")) * tfact * 1.3, names(labels))
                spaceBefore <- as.numeric(convertWidth(unit(3, "points"), "native")) * tfact
                spaceAfter <- as.numeric(convertWidth(unit(7, "points"), "native")) * tfact
                popViewport(1)
                switch(as.character(just),
                    "left" = {
                        if (!rev) {
                            start(finalRanges) <- start(finalRanges) - (spaceBefore + labelWidths + spaceAfter)
                        } else {
                            end(finalRanges) <- end(finalRanges) + spaceAfter + labelWidths + spaceBefore
                        }
                        sb <- spaceBefore
                        sa <- spaceAfter
                    },
                    "right" = {
                        if (!rev) {
                            end(finalRanges) <- end(finalRanges) + spaceAfter + labelWidths + spaceBefore
                        } else {
                            start(finalRanges) <- start(finalRanges) - (spaceBefore + labelWidths + spaceAfter)
                        }
                        sb <- spaceBefore
                        sa <- spaceAfter
                    },
                    "above" = {
                        featureWidths <- end(finalRanges) - start(finalRanges)
                        additionalLabelSpace <- ceiling((labelWidths - featureWidths) / 2)
                        additionalLabelSpace[additionalLabelSpace < 0] <- 0
                        end(finalRanges) <- end(finalRanges) + additionalLabelSpace
                        start(finalRanges) <- start(finalRanges) - additionalLabelSpace
                        sa <- sb <- 0
                    },
                    "below" = {
                        featureWidths <- end(finalRanges) - start(finalRanges)
                        additionalLabelSpace <- ceiling((labelWidths - featureWidths) / 2)
                        additionalLabelSpace[additionalLabelSpace < 0] <- 0
                        end(finalRanges) <- end(finalRanges) + additionalLabelSpace
                        start(finalRanges) <- start(finalRanges) - additionalLabelSpace
                        sa <- sb <- 0
                    },
                    stop(sprintf("Unknown label justification '%s'", just))
                )
                displayPars(GdObjectOrig) <- list(
                    ".__groupLabelWidths" = data.frame(before = sb, label = labelWidths, after = sa),
                    ".__groupLabels" = labels
                )
            }
        }
        displayPars(GdObjectOrig) <- list(".__groupRanges" = finalRanges)
    }
    return(GdObjectOrig)
}

## Calculate the vectorized string dimensions and return the results in a data.frame with columns 'width' and 'height'
## The unit of the return value can be controlled, and additional parameters like font size and expansion factors can
## be passed in as additional arguments (all in ... is passed on to 'gpar'). If needed, the font defaults for a
## subtype can be extracted by providing the subtype argument.
.getStringDims <- function(GdObject, string, unit = "native", subtype = NULL, ...) {
    gp <- .fontGp(GdObject, subtype, ...)
    pushViewport(viewport(gp = gp, xscale = current.viewport()$xscale, yscale = current.viewport()$yscale))
    res <- data.frame(
        width = as.numeric(convertWidth(stringWidth(string), unit)),
        height = as.numeric(convertHeight(stringHeight(string), unit))
    )
    popViewport(1)
    return(res)
}


## Check whether transcripts are to be collapsed for a GeneRegionTrack
.transcriptsAreCollapsed <- function(GdObject) {
    res <- FALSE
    if (is(GdObject, "GeneRegionTrack")) {
        ctrans <- .dpOrDefault(GdObject, "collapseTranscripts", FALSE)
        res <- (is.logical(ctrans) && ctrans == TRUE) || ctrans %in% c("gene", "shortest", "longest", "meta")
    }
    return(res)
}

## Create list for drawing sashimi-like plots
## using summarizeJunctions on GAlignments
## plotting is done via grid.xspline (requires x, y, id, score)
.ranges2ga <- function(range) {
    range <- sort(range)
    range <- range[!duplicated(range$entityId)]
    ga <- GAlignments(
        seqnames = seqnames(range), pos = start(range), cigar = range$cigar,
        strand = if (is.null(range$readStrand)) strand(range) else range$readStrand,
        ## id=range$id,
        ## entityId=range$entityId,
        ## md=range$md,
        ## readStrand=range$readStrand,
        ## mapq=range$mapq,
        ## flag=range$flag,
        ## isize=range$isize,
        ## groupid=range$groupid,
        ## status=range$status,
        ## uid=range$uid,
        ## stack=range$stack,
        seqlengths = seqlengths(range)
    )
    ga
}

.create.summarizedJunctions.for.sashimi.junctions <- function(range) {
    ga <- .ranges2ga(range)
    juns <- summarizeJunctions(ga)
    juns
}

.convert.summarizedJunctions.to.sashimi.junctions <- function(juns, score = 1L, lwd.max = 10, strand = "*", filter = NULL, filterTolerance = 0L, trans = NULL) {
    ## filter junctions
    if (!is.null(filter)) {
        ## if filterTolerance is > 0 than pass it as maxgap parameter in findOverlaps
        ## make sure it is positive value
        if (filterTolerance < 0) {
            filterTolerance <- abs(filterTolerance)
            warning(sprintf(
                "\"sashimiFilterTolerance\" can't be negative, taking absolute value of it: %d",
                filterTolerance
            ))
        }
        ovs <- findOverlaps(juns, filter, type = "start", maxgap = filterTolerance)
        ove <- findOverlaps(juns, filter, type = "end", maxgap = filterTolerance)
        ## combine both Hits objects, select junctions present in both
        ovv <- rbind(as.matrix(ovs), as.matrix(ove))
        ovv <- ovv[duplicated(ovv), , drop = FALSE]
        ## create row/col index for selecting the correct strand
        ws <- strand(filter[ovv[, "subjectHits"]])
        levels(ws) <- c("plus_score", "minus_score", "score")
        ws <- cbind(row = ovv[, "queryHits"], col = as.character(ws))
        ## rol/col subseting will only works on matrix
        M <- as.matrix(values(juns))
        rownames(M) <- seq_len(nrow(M))
        ##
        filter$score <- 0L
        filter$score[sort(unique(ovv[, "subjectHits"]))] <- tapply(M[ws], ovv[, "subjectHits"], sum)
        juns <- filter
    } else {
        ## if no filter ranges were defined
        ## select strand (default both)
        juns$score <- if (strand == "+") juns$plus_score else if (strand == "-") juns$minus_score else juns$score
    }
    ## filter based on evidence (default no filtering, 1 read)
    juns <- juns[juns$score >= score]
    if (length(juns)) {
        ## count how many overlaps to determine the y
        ov <- findOverlaps(juns, reduce(juns, min.gapwidth = 0L))
        ov <- split(queryHits(ov), subjectHits(ov))
        juns$y <- as.integer(unlist(lapply(ov, order)))
        ## apply data transformation if one is set up
        if (is.list(trans)) {
          trans <- trans[[1]]
        }
        if (!is.null(trans)) {
          if (!is.function(trans) || length(formals(trans)) != 1L) {
            stop("Display parameter 'transformation' must be a function with a single argument")
          }
          test <- trans(juns$score)
          if (!is(test, "numeric") || length(test) != length(juns$score)) {
            stop(
              "The function in display parameter 'transformation' results in invalid output.\n",
              "It has to return a numeric matrix with the same dimensions as the input data."
            )
          }
          juns$score <- test
        }
        ## scale the score to lwd.max
        juns$scaled <- (lwd.max - 1) / pmax((max(juns$score) - min(c(1, juns$score))), 1) * (juns$score - max(juns$score)) + lwd.max
        ## create list
        juns <- list(
            x = as.numeric(rbind(
                start(juns),
                mid(ranges(juns)), end(juns)
            )),
            y = as.numeric(rbind(0, juns$y, 0)),
            id = rep(seq_len(length(juns)), each = 3),
            score = as.numeric(juns$score),
            scaled = juns$scaled
        )
    } else {
        juns <- list(
            x = numeric(),
            y = numeric(),
            id = integer(),
            score = numeric(),
            scaled = numeric()
        )
    }
    return(juns)
}


# temporary fix for providerVersion
.providerVersion <- function(sequence) {
  genome <- try(providerVersion(sequence), silent=TRUE)
  if (is(genome, "try-error")) {
    genome <- metadata(sequence)$genome
  }
  if (is.null(genome)) {
    genome <- NA
  }
  return(genome)
}

Try the Gviz package in your browser

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

Gviz documentation built on March 16, 2021, 6:01 p.m.