R/low_level.R

Defines functions circos.stackedText circos.labels circos.connect get_bezier_points circos.violin circos.boxplot circos.barplot circos.dendrogram circos.trackHist circos.yaxis .default.major.by circos.xaxis circos.axis circos.trackText degree fontsize circos.text circos.segments circos.polygon .rotate circos.triangle str2unit circos.roundrect_pos circos.rect circos.trackLines circos.lines circos.trackPoints circos.points

Documented in circos.axis circos.barplot circos.boxplot circos.connect circos.dendrogram circos.labels circos.lines circos.points circos.polygon circos.rect circos.segments circos.stackedText circos.text circos.trackHist circos.trackLines circos.trackPoints circos.trackText circos.triangle circos.violin circos.xaxis circos.yaxis degree fontsize

# == title
# Add points to a plotting region
#
# == param
# -x            Data points on x-axis, measured in "current" data coordinate
# -y            Data points on y-axis, measured in "current" data coordinate
# -sector.index Index for the sector
# -track.index  Index for the track
# -pch          Point type
# -col          Point color
# -cex          Point size
# -bg           backgrond of points
#
# == details
# This function can only add points in one specified cell. Pretending a low-level plotting
# function, it can only be applied in plotting region which has been created.
#
# You can think the function similar as the normal `graphics::points`
# function, just adding points in the circular plotting region. The position of
# cell is identified by ``sector.index`` and ``track.index``, if they are not
# specified, they are in 'current' sector and 'current' track.
#
# Data points out of the plotting region will also be added, but with warning messages.
#
# Other graphics parameters which are available in the function are ``pch``, ``col``
# and ``cex`` which have same meaning as those in the `graphics::par`.
#
# It is recommended to use `circos.points` inside ``panel.fun`` in `circos.trackPlotRegion` so that
# it draws points directly on "curent" cell.
#
# == seealso
# https://jokergoo.github.io/circlize_book/book/graphics.html#points
#
# == example
# circos.initialize(letters[1:8], xlim = c(0, 1))
# circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
#     circos.points(runif(10), runif(10))
# })
# circos.points(runif(10), runif(10), sector.index = "c", pch = 16, col = "red")
# circos.clear()
circos.points = function(
	x, y,
	sector.index = get.current.sector.index(),
    track.index = get.current.track.index(),
    pch = par("pch"),
    col = par("col"),
    cex = par("cex"),
    bg = par("bg")) {

    if(!has.cell(sector.index, track.index)) {
        stop_wrap("'circos.points' can only be used after the plotting region has been created")
    }

    if(missing(y)) {
        if(ncol(x) == 2) {
            y = x[, 2]
            x = x[, 1]
        }
    }

    len_x = length(x)
    len_y = length(y)
    if(len_x == 1) x = rep(x, len_y)
    if(len_y == 1) y = rep(y, len_x)

	if(length(x) != length(y)) {
		stop_wrap("Length of x and y differ.")
	}

    # whether the points that are out of the plotting region.
    # If there is, throw warnings.
    check.points.position(x, y, sector.index, track.index)

    d = circlize(x, y, sector.index = sector.index, track.index = track.index)
    points(polar2Cartesian(d), pch = pch, col = col, cex = cex, bg = bg)
    return(invisible(NULL))
}

# == title
# Add points to the plotting regions in a same track
#
# == param
# -sectors      A `factor` or a character vector which represents the categories of data
# -factors      The same as ``sectors``. It will be removed in future versions. 
# -x            Data points on x-axis
# -y            Data points on y-axis
# -track.index  Index for the track
# -pch          Point type
# -col          Point color
# -cex          Point size
# -bg           backgrond color
#
# == details
# The function adds points in multiple cells by first splitting data into several parts in which
# each part corresponds to one factor (sector index) and then adding points in each cell by calling `circos.points`.
#
# Length of ``pch``, ``col`` and ``cex`` can be one, length of levels of the factors or length of
# factors.
#
# This function can be replaced by a ``for`` loop containing `circos.points`.
#
# == example
# circos.initialize(letters[1:8], xlim = c(0, 1))
# df = data.frame(sectors = sample(letters[1:8], 100, replace = TRUE),
#                 x = runif(100), y = runif(100))
# circos.track(ylim = c(0, 1))
# circos.trackPoints(df$sectors, x = df$x, y = df$y, pch = 16, col = as.numeric(factor(df$fa)))
# circos.clear()
circos.trackPoints = function(
    sectors,
	x, y,
	track.index = get.current.track.index(),
    pch = par("pch"),
    col = par("col"),
    cex = par("cex"),
    bg = par("bg"),
    factors = sectors
    ) {

    # basic check here
    if(length(x) != length(factors) || length(y) != length(factors)) {
        stop_wrap("Length of data and length of factors differ.\n")
    }

    if(!is.factor(factors)) {
        factors = factor(factors)
    }

	# check whether there are some categories that are not in the circle
	setdiff.factors = setdiff(levels(factors), get.all.sector.index())
    if(length(setdiff.factors)) {
        stop_wrap("Cannot find these categories in existed sectors:", paste(setdiff.factors, collapse = ", "), ".")
    }

    le = levels(factors)

    # set these graphic parameters with same length as the factors
    pch = recycle.with.factors(pch, factors)
    col = recycle.with.factors(col, factors)
    cex = recycle.with.factors(cex, factors)
    bg = recycle.with.factors(bg, factors)

    for(i in seq_along(le)) {
        l = factors == le[i]

        nx = x[l]
        ny = y[l]
        npch = pch[l]
        ncol = col[l]
        ncex = cex[l]
        nbg = bg[l]
        circos.points(nx, ny, sector.index = le[i],
                      track.index = track.index,
                      pch = npch, col = ncol, cex = ncex, bg = nbg)

    }
    return(invisible(NULL))
}

# == title
# Add lines to the plotting region
#
# == param
# -x            Data points on x-axis, measured in "current" data coordinate.
# -y            Data points on y-axis, measured in "current" data coordinate.
# -sector.index Index for the sector.
# -track.index  Index for the track.
# -col          Line color.
# -lwd          Line width.
# -lty          Line style.
# -type         Line type, similar as ``type`` argument in `graphics::lines`, but only in ``c("l", "o", "h", "s")``
# -straight     Whether draw straight lines between points.
# -area         Whether to fill the area below the lines. If it is set to ``TRUE``, ``col`` controls the filled color
#               in the area and ``border`` controls color of the line.
# -area.baseline deprecated, use ``baseline`` instead.
# -baseline     The base line to draw areas. By default it is the minimal of y-range (bottom). It can be a string or a number.
#               If a string, it should be one of ``bottom`` and ``top``. This argument also works if ``type`` is set to ``h``.
# -border       color for border of the area.
# -pt.col       If ``type`` is "o", point color.
# -cex          If ``type`` is "o", point size.
# -pch          If ``type`` is "o", point type.
#
# == details
# Normally, straight lines in the Cartesian coordinate have to be transformed into curves in the circular layout.
# But if you do not want to do such transformation you can use this function just drawing straight
# lines between points by setting ``straight`` to ``TRUE``.
#
# Drawing areas below lines can help to identify the direction of y-axis in cells (since it is a circle). This can be done by specifying
# ``area`` to ``TURE``.
#
# == example
# sectors = letters[1:9]
# circos.par(points.overflow.warning = FALSE)
# circos.initialize(sectors, xlim = c(0, 10))
# circos.trackPlotRegion(sectors, ylim = c(0, 10), track.height = 0.5)
#
# circos.lines(sort(runif(10)*10), runif(10)*8, sector.index = "a")
# circos.text(5, 9, "type = 'l'", sector.index = "a", facing = "outside")
#
# circos.lines(sort(runif(10)*10), runif(10)*8, sector.index = "b", type = "o")
# circos.text(5, 9, "type = 'o'", sector.index = "b", facing = "outside")
#
# circos.lines(sort(runif(10)*10), runif(10)*8, sector.index = "c", type = "h")
# circos.text(5, 9, "type = 'h'", sector.index = "c", facing = "outside")
#
# circos.lines(sort(runif(10)*10), runif(10)*8, sector.index = "d", type = "h", baseline = 5)
# circos.text(5, 9, "type = 'h', baseline = 5", sector.index = "d", facing = "outside")
#
# circos.lines(sort(runif(10)*10), runif(10)*8, sector.index = "e", type = "s")
# circos.text(5, 9, "type = 's'", sector.index = "e", facing = "outside")
#
# circos.lines(sort(runif(10)*10), runif(10)*8, sector.index = "f", area = TRUE)
# circos.text(5, 9, "type = 'l', area = TRUE", sector.index = "f")
#
# circos.lines(sort(runif(10)*10), runif(10)*8, sector.index = "g", type = "o", area = TRUE)
# circos.text(5, 9, "type = 'o', area = TRUE", sector.index = "g")
#
# circos.lines(sort(runif(10)*10), runif(10)*8, sector.index = "h", type = "s", area = TRUE)
# circos.text(5, 9, "type = 's', area = TRUE", sector.index = "h")
#
# circos.lines(sort(runif(10)*10), runif(10)*8, sector.index = "i", area = TRUE, baseline = "top")
# circos.text(5, 9, "type = 'l', area = TRUE, baseline = 'top'", sector.index = "i")
#
# circos.clear()
circos.lines = function(
	x, y,
	sector.index = get.current.sector.index(),
    track.index = get.current.track.index(),
    col = ifelse(area, "grey", par("col")),
    lwd = par("lwd"),
    lty = par("lty"),
    type = "l",
    straight = FALSE,
    area = FALSE,
    area.baseline = NULL,
    border = "black",
    baseline = "bottom",
    pt.col = par("col"),
    cex = par("cex"),
    pch = par("pch")) {

	if(!is.null(area.baseline)) {
		baseline = area.baseline
		warning_wrap("`area.baseline` is deprecated, please use `baseline` instead.")
	}

    if(missing(y)) {
        if(ncol(x) == 2) {
            y = x[, 2]
            x = x[, 1]
        }
    }

	if(length(x) != length(y)) {
		stop_wrap("Length of x and y differ.")
	}

    n = length(x)

    if(circos.par$ring) {
        l = c(x[seq(1, n-1)] - x[seq(2, n)] > 1e-10, FALSE)
        if(any(l)) {
            x[l] = x[l] - get.cell.meta.data("xrange", sector.index, track.index)
        }
    }

	if(baseline == "bottom") {
		baseline = get.cell.meta.data("ylim", sector.index, track.index)[1]
	} else if(baseline == "top") {
		baseline = get.cell.meta.data("ylim", sector.index, track.index)[2]
	}

    if(type == "l") {

    } else if(type == "o") {
        circos.points(x, y, sector.index = sector.index, track.index = track.index,
                      col = pt.col, cex = cex, pch = pch)
        circos.lines(x, y, sector.index = sector.index, track.index = track.index,
                     col = col, lwd = lwd, lty = lty, area = area, border = border)
        return(invisible(NULL))
    } else if(type == "h") {
    	if(length(col) == 1) col = rep(col, length(x))
    	if(length(lwd) == 1) lwd = rep(lwd, length(x))
    	if(length(lty) == 1) lty = rep(lty, length(x))
        for(i in seq_along(x)) {
            circos.lines(c(x[i], x[i]), c(baseline, y[i]),
                         sector.index = sector.index, track.index = track.index,
                         col = col[i], lwd = lwd[i], lty = lty[i], straight = TRUE)
        }
        return(invisible(NULL))
    } else if(type == "s") {
		d = matrix(nrow = 0, ncol = 2)
        for(i in seq_along(x)) {
            if(i == 1) {
                next
            }
			d = rbind(d, lines.expand(c(x[i-1], x[i]), c(y[i-1], y[i-1]), sector.index, track.index))
			d = rbind(d, cbind(c(x[i], x[i]), c(y[i-1], y[i])))
        }

		if(area) {
			ylim = get.cell.meta.data("ylim", sector.index, track.index)
			d = rbind(d, c(d[nrow(d), 1], baseline))
			d = rbind(d, c(d[1, 1], baseline))
			circos.polygon(d[, 1], d[, 2], sector.index = sector.index, track.index = track.index,
				   col = col, border = border, lwd = lwd, lty = lty)
		} else {
			circos.lines(d[, 1], d[, 2], sector.index = sector.index, track.index = track.index,
							 col = col, lwd = lwd, lty = lty)
        }
		return(invisible(NULL))
    }

    if(!has.cell(sector.index, track.index)) {
        stop_wrap("'circos.lines' can only be used after the plotting region been created.")
    }

    # whether the points that are out of the plotting region.
    check.points.position(x, y, sector.index, track.index)

    if(straight) {
        d = cbind(x, y)
    } else {
        d = lines.expand(x, y, sector.index, track.index)
    }

	if(area) {
		ylim = get.cell.meta.data("ylim", sector.index, track.index)
		d = rbind(d, c(d[nrow(d), 1], baseline))
		d = rbind(d, c(d[1, 1], baseline))
		circos.polygon(d[, 1], d[, 2], sector.index = sector.index, track.index = track.index,
		       col = col, border = border, lwd = lwd, lty = lty)
		return(invisible(NULL))
	}

    d2 = circlize(d[, 1], d[, 2], sector.index = sector.index, track.index = track.index)

    lines(polar2Cartesian(d2), col = col, lwd = lwd, lty = lty)
    return(invisible(NULL))
}

# == title
# Add lines to the plotting regions in a same track
#
# == param
# -sectors      A `factor` or a character vector which represents the categories of data.
# -factors      The same as ``sectors``. It will be removed in future versions. 
# -x            Data points on x-axis.
# -y            Data points on y-axis.
# -track.index  Index for the track.
# -col          Line color.
# -lwd          Line width.
# -lty          Line style.
# -type         Line type, similar as ``type`` argument in `graphics::lines`, but only in ``c("l", "o", "h", "s")``.
# -straight     Whether draw straight lines between points.
# -area         Whether to fill the area below the lines. If it is set to ``TRUE``, ``col`` controls the filled color
#               in the area and ``border`` controls the color of the line.
# -area.baseline Deprecated, use ``baseline`` instead.
# -baseline The base line to draw area, pass to `circos.lines`.
# -border       Color for border of the area.
# -pt.col       If ``type`` is "o", points color.
# -cex          If ``type`` is "o", points size.
# -pch          If ``type`` is "o", points type.
#
# == details
# The function adds lines in multiple cells by first splitting data into several parts in which
# each part corresponds to one factor (sector index) and then add lines in cells by calling `circos.lines`.
#
# This function can be replaced by a ``for`` loop containing `circos.lines`.
circos.trackLines = function(
    sectors,
	x, y,
	track.index = get.current.track.index(),
    col = par("col"),
    lwd = par("lwd"),
    lty = par("lty"),
    type = "l",
    straight = FALSE,
	area = FALSE,
	area.baseline = NULL,
	border = "black",
	baseline = "bottom",
    pt.col = par("col"),
    cex = par("cex"),
    pch = par("pch"),
    factors = sectors) {

	if(!is.null(area.baseline)) {
		baseline = area.baseline
		warning_wrap("`area.baseline` is deprecated, please use `baseline` instead.")
	}

    # basic check here
    if(length(x) != length(factors) || length(y) != length(factors)) {
        stop_wrap("Length of data and length of factors differ.")
    }

    if(!is.factor(factors)) {
        factors = factor(factors)
    }

    # check whether there are some categories that are not in the circle
	setdiff.factors = setdiff(levels(factors), get.all.sector.index())
    if(length(setdiff.factors)) {
        stop_wrap("Cannot find these categories in existed sectors:", paste(setdiff.factors, collapse = ", "), ".")
    }

    le = levels(factors)

    # set these graphic parameters with same length as the factors
    col = recycle.with.factors(col, factors)
    lwd = recycle.with.factors(lwd, factors)
    lty = recycle.with.factors(lty, factors)
    pt.col = recycle.with.factors(pt.col, factors)
    cex = recycle.with.factors(cex, factors)
    pch = recycle.with.factors(pch, factors)

	area = recycle.with.levels(area, le)
	baseline = recycle.with.levels(baseline, le)
	border = recycle.with.levels(border, le)

    for(i in seq_along(le)) {
        l = factors == le[i]
        nx = x[l]
        ny = y[l]
        ncol = col[l]
        nlwd = lwd[l]
        nlty = lty[l]
        npt.col = pt.col[l]
        ncex = cex[l]
        npch = pch[l]
        circos.lines(nx, ny, sector.index = le[i],
                      track.index = track.index,
                      col = ncol, lwd = nlwd, lty = nlty, area = area[i], border = border[i],
					  baseline = baseline[i],
                      pt.col = npt.col, cex = ncex, pch = npch, type = type, straight = straight)

    }
    return(invisible(NULL))
}


# == title
# Draw rectangle-like grid
#
# == param
# -xleft        x for the left bottom points
# -ybottom      y for the left bottom points
# -xright       x for the right top points
# -ytop         y for the right top points
# -sector.index Index for the sector
# -track.index  Index for the track
# -radius       Radius of the corners of rectangulars. Please set it in a form of "3mm".
# -rot          Rotation of the rectangles. The value is measured clockwise in degree.
#               Rotation is relative to the center of the rectangles.
# -... pass to `graphics::polygon`
#
# == details
# The name for this function is `circos.rect`
# because if you imagine the plotting region as Cartesian coordinate, then it is rectangle.
# in the polar coordinate, the up and bottom edge become two arcs.
#
# This function can be vectorized.
#
# == examples
# circos.initialize(c("a", "b", "c", "d"), xlim = c(0, 10))
# circos.track(ylim = c(0, 10), panel.fun = function(x, y) {
#     for(rot in seq(0, 360, by = 30)) {
#         circos.rect(2, 2, 6, 6, rot = rot)
#     }
# }, track.height = 0.5)
#
circos.rect = function(
	xleft, ybottom, xright, ytop,
	sector.index = get.current.sector.index(),
	track.index = get.current.track.index(),
    radius = NULL, rot = 0,
	...) {

    # if(! (length(xleft) == 1 &&
    #       length(ybottom) == 1 &&
    #       length(xright) == 1 &&
    #       length(ytop) == 1) ) {
    #     stop("There should only be one data points in 'xleft', 'ybottom', 'xright' or 'ytop'.\n")
    # }

    if(!has.cell(sector.index, track.index)) {
        stop_wrap("'circos.rect' can only be used after the plotting region been created.")
    }

    if(missing(xleft) && missing(ybottom) && missing(xright) && missing(ytop)) {
        cell.xlim = get.cell.meta.data("cell.xlim", sector.index = sector.index, track.index = track.index)
        cell.ylim = get.cell.meta.data("cell.ylim", sector.index = sector.index, track.index = track.index)
        xleft = cell.xlim[1]
        ybottom = cell.ylim[1]
        xright = cell.xlim[2]
        ytop = cell.ylim[2]
    }

    n1 = length(xleft)
    n2 = length(ybottom)
    n3 = length(xright)
    n4 = length(ytop)
    n = max(c(n1, n2, n3, n4))
    if(n1 == 1) xleft = rep(xleft, n)
    if(n2 == 1) ybottom = rep(ybottom, n)
    if(n3 == 1) xright = rep(xright, n)
    if(n4 == 1) ytop = rep(ytop, n)

    if(! (length(xleft) == length(ybottom) && length(ybottom) == length(xright) && length(xright) == length(ytop)) ) {
		stop_wrap("xleft, ybottom, xright, ytop should have same length.")
	}

    if(circos.par$ring) {
        l = xleft > xright
        if(any(l)) {
            xleft[l] = xleft[l] - get.cell.meta.data("xrange", sector.index, track.index)
        }
    }

    # # no filled colors, just four edges, here edges colors are controled by ``border``
    # if(is.na(col)) {
    #     # vertical lines in the original coordinate system are still straight lines
    #     # in the new coordinate system except they now pointing to the circle center.
    #     circos.lines(c(xleft, xleft), c(ybottom, ytop),
    #                  sector.index = sector.index, track.index = track.index,
    #                  col = border, lty = lty, lwd = lwd, straight = TRUE)
    #     # horizontal lines in the original coordinate system are now arcs and the arcs
    #     # share the same circle center as the polar coordinate system
    #     circos.lines(c(xleft, xright), c(ytop, ytop),
    #                sector.index = sector.index, track.index = track.index,
    #                col = border, lty = lty, lwd = lwd)
    #     circos.lines(c(xright, xright), c(ytop, ybottom),
    #                  sector.index = sector.index, track.index = track.index,
    #                  col = border, lty = lty, lwd = lwd, straight = TRUE)
    #     circos.lines(c(xleft, xright), c(ybottom, ybottom),
    #                sector.index = sector.index, track.index = track.index,
    #                col = border, lty = lty, lwd = lwd)
    # } else {
    #     circos.polygon(c(xleft, xleft, xright, xright, xleft),
    #                    c(ybottom, ytop, ytop, ybottom, ybottom),
    #                    sector.index = sector.index, track.index = track.index,
    #                    col = col, border = border, lty = lty, lwd = lwd)
    # }

    np = length(xleft)
    if(is.null(radius)) {
        if(rot == 0) {
            x = unlist(lapply(seq_len(np), function(i) c(xleft[i], xleft[i], xright[i], xright[i], xleft[i], NA)))
            y = unlist(lapply(seq_len(np), function(i) c(ybottom[i], ytop[i], ytop[i], ybottom[i], ybottom[i], NA)))
            x = x[-length(x)]
            y = y[-length(y)]
        } else {
            xcenter = (xleft + xright)/2
            ycenter = (ybottom + ytop)/2

            p1 = list(x = xleft, y = ybottom)
            p2 = list(x = xright, y = ybottom)
            p3 = list(x = xright, y = ytop)
            p4 = list(x = xleft, y = ytop)

            center = list(x = c(p1$x + p3$x)/2, y = (p1$y + p3$y)/2)

            q1 = .rotate(p1$x, p1$y, center$x, center$y, rot/180*pi)
            q2 = .rotate(p2$x, p2$y, center$x, center$y, rot/180*pi)
            q3 = .rotate(p3$x, p3$y, center$x, center$y, rot/180*pi)
            q4 = .rotate(p4$x, p4$y, center$x, center$y, rot/180*pi)

            x = unlist(lapply(seq_len(np), function(i) {
                    c(q1$x[i], q2$x[i], q3$x[i], q4$x[i], q1$x[i], NA)
                }))
            y = unlist(lapply(seq_len(np), function(i) {
                    c(q1$y[i], q2$y[i], q3$y[i], q4$y[i], q1$y[i], NA)
                }))
            x = x[-length(x)]
            y = y[-length(y)]
        }
        circos.polygon(x, y, sector.index = sector.index, track.index = track.index, ...)
    } else {
        pos = list(x = numeric(0), y = numeric(0))
        for(i in seq_len(np)) {
            pos2 = circos.roundrect_pos(xleft[i], ybottom[i], xright[i], ytop[i], radius = radius)
            pos$x = c(pos$x, pos2$x, NA)
            pos$y = c(pos$y, pos2$y, NA)
        }
        circos.polygon(pos$x, pos$y, sector.index = sector.index, track.index = track.index, ...)
    }

    return(invisible(NULL))
}

circos.roundrect_pos = function(xleft, ybottom, xright, ytop, radius = "4mm", 
    sector.index = get.current.sector.index(),
    track.index = get.current.track.index()) {

    if(is.character(radius)) {
        radius = str2unit(radius)
    }

    cell_width = abs(diff(get.cell.meta.data("xplot", sector.index = sector.index, track.index = track.index)))/360*pi*2*mean(get.cell.meta.data("yplot", sector.index = sector.index, track.index = track.index))
    cell_height = abs(diff(get.cell.meta.data("yplot", sector.index = sector.index, track.index = track.index)))

    cell.xlim = get.cell.meta.data("cell.xlim", sector.index = sector.index, track.index = track.index)
    cell.ylim = get.cell.meta.data("cell.ylim", sector.index = sector.index, track.index = track.index)
    cell.xrange = cell.xlim[2] - cell.xlim[1]
    cell.yrange = cell.ylim[2] - cell.ylim[1]

    w = (xright - xleft)/cell.xrange*cell_width
    h = (ytop - ybottom)/cell.yrange*cell_height
    radius = min(radius*2/w, radius*2/h, 0.5)

    xleft0 = (xleft - cell.xlim[1])/cell.xrange*cell_width
    xright0 = (xright - cell.xlim[1])/cell.xrange*cell_width
    ybottom0 = (ybottom - cell.ylim[1])/cell.yrange*cell_height
    ytop0 = (ytop - cell.ylim[1])/cell.yrange*cell_height
    pos = roundrect_pos(xleft0, ybottom0, xright0, ytop0, radius = radius, asp = 1)

    pos$x = pos$x/cell_width*cell.xrange + cell.xlim[1]
    pos$y = pos$y/cell_height*cell.yrange + cell.ylim[1]

    pos
}

str2unit = function(str) {
    d = gsub("[^\\d]+$", "", str, perl = TRUE)
    u = gsub("^.*[\\d.]", "", str, perl = TRUE)
    convert_length(as.numeric(d), u)
}

# == title
# Draw triangles
#
# == param
# -x1 x-coordinates for the first point.
# -y1 y-coordinates for the first point.
# -x2 x-coordinates for the second point.
# -y2 y-coordinates for the second point.
# -x3 x-coordinates for the third point.
# -y3 y-coordinates for the third point.
# -... Pass to `circos.polygon`.
#
# == example
# circos.initialize(c("a", "b", "c", "d"), xlim = c(0, 10))
# circos.track(ylim = c(0, 10), panel.fun = function(x, y) {
#     circos.triangle(c(2, 2), c(2, 8),
#                     c(8, 8), c(2, 8),
#                     c(5, 5), c(8, 2))
# }, track.height = 0.5)
#
circos.triangle = function(x1, y1, x2, y2, x3, y3, ...) {
    n1 = length(x1)
    n2 = length(y1)
    n3 = length(x2)
    n4 = length(y2)
    n5 = length(x3)
    n6 = length(y3)

    n = max(c(n1, n2, n3, n4, n5, n6))
    if(n1 == 1) x1 = rep(x1, n)
    if(n2 == 1) y1 = rep(y1, n)
    if(n3 == 1) x2 = rep(x2, n)
    if(n4 == 1) y2 = rep(y2, n)
    if(n5 == 1) x3 = rep(x3, n)
    if(n6 == 1) y3 = rep(y3, n)

    if(! (length(x1) == length(y1) && length(y1) == length(x2) && length(x2) == length(y2) && length(y2) == length(x3) && length(x3) == length(y3)) ) {
        stop_wrap("x1, y1, x2, y2, x3, y3 should have same length.")
    }

    np = length(x1)
    x = unlist(lapply(seq_len(np), function(i) {
            c(x1[i], x2[i], x3[i], x1[i], NA)
        }))
    y = unlist(lapply(seq_len(np), function(i) {
            c(y1[i], y2[i], y3[i], y1[i], NA)
        }))
    x = x[-length(x)]
    y = y[-length(y)]

    circos.polygon(x, y, ...)
}

.rotate = function(x, y, cx, cy, theta) {
    x2 = x - cx
    y2 = y - cy
    rho = sqrt(x2^2 + y2^2)
    alpha = atan(y2/x2)
    alpha = ifelse(x2 < 0, alpha + pi, ifelse(y2 < 0, alpha + 2*pi, alpha))
    beta = alpha + theta
    nx = rho*cos(beta) + cx
    ny = rho*sin(beta) + cy
    return(list(x = nx, y = ny))
}


# == title
# Draw polygon
#
# == param
# -x            Data points on x-axis
# -y            Data points on y-axis
# -sector.index Index for the sector
# -track.index  Index for the track
# -... pass to `graphics::polygon`
#
# == details
# similar as `graphics::polygon`.
#
# Note: start point should overlap with the end point.
#
# == example
# set.seed(123)
# sectors = letters[1:4]
# circos.initialize(sectors, xlim = c(0, 1))
# circos.trackPlotRegion(ylim = c(-3, 3), track.height = 0.4, panel.fun = function(x, y) {
#     x1 = runif(20)
#     y1 = x1 + rnorm(20)
#     or = order(x1)
#     x1 = x1[or]
#     y1 = y1[or]
#     loess.fit = loess(y1 ~ x1)
#     loess.predict = predict(loess.fit, x1, se = TRUE)
#     d1 = c(x1, rev(x1))
#     d2 = c(loess.predict$fit + loess.predict$se.fit,
#         rev(loess.predict$fit - loess.predict$se.fit))
#     circos.polygon(d1, d2, col = "#CCCCCC", border = NA)
#     circos.points(x1, y1, cex = 0.5)
#     circos.lines(x1, loess.predict$fit)
# })
# circos.clear()
circos.polygon = function(
	x, y,
	sector.index = get.current.sector.index(),
	track.index = get.current.track.index(),
	...) {

    if(!has.cell(sector.index, track.index)) {
        stop_wrap("'circos.polygon' can only be used after the plotting region been created.")
    }

    # whether the points that are out of the plotting region.
    check.points.position(x, y, sector.index, track.index)

    d = lines.expand(x, y, sector.index, track.index)
    d2 = circlize(d, sector.index = sector.index, track.index = track.index)
    polygon(polar2Cartesian(d2), ...)
    return(invisible(NULL))
}

# == title
# Draw segments through pairwise of points
#
# == param
# -x0 x coordinates for starting points.
# -y0 y coordinates for ending points.
# -x1 x coordinates for starting points.
# -y1 y coordinates for ending points.
# -sector.index Index for the sector.
# -track.index  Index for the track.
# -straight Whether the segment is a straight line.
# -col Color of the segments.
# -lwd Line width of the segments.
# -lty Line type of the segments.
# -... Pass to `graphics::lines`.
#
# == example
# circos.initialize(letters[1:8], xlim = c(0, 1))
# circos.track(ylim = c(0, 1), track.height = 0.3, panel.fun = function(x, y) {
#     x = seq(0.2, 0.8, by = 0.2)
#     y = seq(0.2, 0.8, by = 0.2)
#
#     circos.segments(x, 0.1, x, 0.9)
#     circos.segments(0.1, y, 0.9, y)
# })
# circos.clear()
circos.segments = function(
	x0, y0, x1, y1,
	sector.index = get.current.sector.index(),
	track.index = get.current.track.index(),
	straight = FALSE,
	col = par("col"),
	lwd = par("lwd"),
	lty = par("lty"),
	...) {

	n1 = length(x0)
    n2 = length(y0)
    n3 = length(x1)
    n4 = length(y1)
    n = max(c(n1, n2, n3, n4))
    if(n1 == 1) x0 = rep(x0, n)
    if(n2 == 1) y0 = rep(y0, n)
    if(n3 == 1) x1 = rep(x1, n)
    if(n4 == 1) y1 = rep(y1, n)

	if(! (length(x0) == length(y0) && length(y0) == length(x1) && length(x1) == length(y1)) ) {
		stop_wrap("x0, y0, x1, y1 should have same length.")
	}

    if(circos.par$ring) {
        l = x0 - x1 > 1e-10
        if(any(l)) {
            x0[l] = x0[l] - get.cell.meta.data("xrange", sector.index, track.index)
        }
    }

	if(length(col) == 1 && length(lwd) ==1 && length(lty) == 1) {

	} else {
		np = length(x0)
		if(length(straight) == 1) straight = rep(straight, np)
		if(length(col) == 1) col = rep(col, np)
		if(length(lwd) == 1) lwd = rep(lwd, np)
		if(length(lty) == 1) lty = rep(lty, np)

		for(i in seq_len(np)) {
			circos.lines(c(x0[i], x1[i]), c(y0[i], y1[i]), sector.index = sector.index,
				straight = straight[i], track.index = track.index, col = col[i],
				lwd = lwd[i], lty = lty[i], ...)
		}
		return(invisible(NULL))
	}

	if(!has.cell(sector.index, track.index)) {
        stop_wrap("'circos.segments' can only be used after the plotting region been created.")
    }

	np = length(x0)
	if(length(straight) == 1) straight = rep(straight, np)
	if(length(col) == 1) col = rep(col, np)
	if(length(lwd) == 1) lwd = rep(lwd, np)
	if(length(lty) == 1) lty = rep(lty, np)
	x = NULL
	y = NULL

	col2 = NULL
	lwd2 = NULL
	lty2 = NULL
	for(i in seq_along(x0)) {
		if(straight[i]) {
			x = c(x, c(x0[i], x1[i], NA))
			y = c(y, c(y0[i], y1[i], NA))
			col2 = c(col2, col[i])
			lwd2 = c(lwd2, lwd[i])
			lty2 = c(lty2, lty[i])
		} else {
			d = lines.expand(c(x0[i], x1[i]), c(y0[i], y1[i]), sector.index, track.index)
			x = c(x, c(d[, 1], NA))
			y = c(y, c(d[, 2], NA))
			nd = nrow(d)
			col2 = c(col2, rep(col[i], nd))
			lwd2 = c(lwd2, rep(lwd[i], nd))
			lty2 = c(lty2, rep(lty[i], nd))
		}
	}

	n2 = length(x)
	x = x[-n2]
	y = y[-n2]
	col2 = col2[-n2]
	lwd2 = lwd2[-n2]
	lty2 = lty2[-n2]
	d2 = circlize(x, y, sector.index = sector.index, track.index = track.index)
	d3 = polar2Cartesian(d2)
	lines(d3[,1], d3[,2], col = col2, lwd = lwd2, lty = lty2, ...)
}

# == title
# Draw text in a cell
#
# == param
# -x            Data points on x-axis
# -y            Data points on y-axis
# -labels       Labels for each points
# -sector.index Index for the sector
# -track.index  Index for the track
# -direction    deprecated, use ``facing`` instead.
# -facing       Facing of text. Please refer to vignette for different settings
# -niceFacing   Should the facing of text be adjusted to fit human eyes?
# -adj          offset for text. By default the text position adjustment is either horizontal or vertical
#           in the canvas coordinate system. The "circular horizontal" offset can be set as a value in degree
#           unit and the value should be wrapped by `degree`.
# -...       Pass to `graphics::text`
# -cex          Font size
# -col          Font color
# -font         Font style
#
# == details
# The function is similar to `graphics::text`. All you need to note is the ``facing`` settings.
#
# == seealso
# https://jokergoo.github.io/circlize_book/book/graphics.html#text
#
# == example
# sectors = letters[1:4]
# circos.par(points.overflow.warning = FALSE)
# circos.initialize(sectors, xlim = c(0, 10))
# circos.trackPlotRegion(sectors, ylim = c(0, 10),
#   track.height = 0.5, panel.fun = function(x, y) {
#     circos.text(3, 1, "inside", facing = "inside", cex = 0.8)
#     circos.text(7, 1, "outside", facing = "outside", cex = 0.8)
#     circos.text(0, 5, "reverse.clockwise", facing = "reverse.clockwise",
#         adj = c(0.5, 0), cex = 0.8)
#     circos.text(10, 5, "clockwise", facing = "clockwise", adj = c(0.5, 0),
#         cex = 0.8)
#     circos.text(5, 5, "downward", facing = "downward", cex = 0.8)
#     circos.text(3, 9, "====bending.inside====", facing = "bending.inside",
#         cex = 0.8)
#     circos.text(7, 9, "====bending.outside====", facing = "bending.outside",
#         cex = 0.8)
# })
# circos.clear()
circos.text = function(
	x, y,
	labels,
	sector.index = get.current.sector.index(),
    track.index = get.current.track.index(),
    direction = NULL,
    facing = c("inside", "outside", "reverse.clockwise", "clockwise",
	    "downward", "bending", "bending.inside", "bending.outside"),
    niceFacing = FALSE,
	adj = par("adj"),
	cex = 1,
	col = par("col"),
	font = par("font"),
	...) {


    if(missing(y)) {
        if(ncol(x) == 2) {
            y = x[, 2]
            x = x[, 1]
        }
    }
    
    len_x = length(x)
    len_y = length(y)
    if(len_x == 1) x = rep(x, len_y)
    if(len_y == 1) y = rep(y, len_x)

	if(length(x) != length(y)) {
		stop_wrap("Length of x and y differ.")
	}

    if(!has.cell(sector.index, track.index)) {
        stop_wrap("'circos.text' can only be used after the plotting region been created.")
    }

	if(length(cex) == 1) {
		cex = rep(cex, length(x))
	}
	if(length(col) == 1) {
		col = rep(col, length(x))
	}
	if(length(font) == 1) {
		font = rep(font, length(x))
	}
	if(length(adj) == 1) {
		adj = c(adj, adj)
	}

	labels = as.vector(labels)

    ## check direction or facing
    if(!is.null(direction)) {
        warning_wrap("`direction` is deprecated, please use `facing` instead.")
        facing = switch(direction[1],
                        default = "inside",
                        default2 = "outside",
                        vertical_left = "reverse.clockwise",
                        vertical_right = "clockwise",
                        horizontal = "downward",
                        arc = "bending.inside")
        if(is.null(facing)) {
            stop_wrap("Wrong `direction` value, please use `facing` instead.")
        }
    }

    facing = match.arg(facing)[1]
    if(facing == "bending") {
    	facing = "bending.inside"
    }

    d = circlize(x, y, sector.index = sector.index, track.index = track.index)

    # adjust positions by `adj`
    if(inherits(adj, "list")) {
	    labels_width = strwidth(labels, cex = cex, font = font)
	    labels_height = strheight(labels, cex = cex, font = font)
	    if(facing == "clockwise") {
	    	rou_offset = -(adj[[1]] - 0.5) * labels_width
	    	#theta_offset = as.degree(asin(-(adj[2] - 0.5)*labels_height/2/d[, "rou"]))
	    	theta_offset = adj[[2]]
	    	if(!inherits(theta_offset, "degree")) stop_wrap("The second item in `adj` should be wrapped by `degree()` if facing is clockwise.")
	    } else if(facing == "reverse.clockwise") {
	    	rou_offset = (adj[[1]] - 0.5) * labels_width
	    	#theta_offset = as.degree(asin((adj[2] - 0.5)*labels_height/2/d[, "rou"]))
	    	theta_offset = adj[[2]]
	    	if(!inherits(theta_offset, "degree")) stop_wrap("The second item in `adj` should be wrapped by `degree()` if facing is reverse clockwise.")
	    } else if(facing == "inside") {
	    	rou_offset = -(adj[[2]] - 0.5) * labels_height
	    	#theta_offset = as.degree(asin(-(adj[1] - 0.5)*labels_width/2/d[, "rou"]))
	    	theta_offset = adj[[1]]
	    	if(!inherits(theta_offset, "degree")) stop_wrap("The first item in `adj` should be wrapped by `degree()` if facing is inside.")
	    } else if(facing == "outside") {
	    	rou_offset = (adj[[2]] - 0.5) * labels_height
	    	#theta_offset = as.degree(asin((adj[1] - 0.5)*labels_width/2/d[, "rou"]))
	    	theta_offset = adj[[1]]
	    	if(!inherits(theta_offset, "degree")) stop_wrap("The first item in `adj` should be wrapped by `degree()` if facing is outside.")
	    }

	    if(facing %in% c("clockwise", "reverse.clockwise", "inside", "outside")) {
			d2 = d
			d2[, "rou"] = d[, "rou"] + rou_offset
			d2[, "theta"] = d[, "theta"] + theta_offset
			dd = reverse.circlize(d2, sector.index = sector.index, track.index = track.index)
			x = dd[, 1]
			y = dd[, 2]
			#circos.points(x, y)
			adj = c(0.5, 0.5)
			d = circlize(x, y, sector.index = sector.index, track.index = track.index)
	    }
	}

    # whether the points that are out of the plotting region.
    check.points.position(x, y, sector.index, track.index)

	if(niceFacing && facing %in% c("clockwise", "reverse.clockwise", "inside", "outside", "bending.inside", "bending.outside")) {
		if(facing %in% c("clockwise", "reverse.clockwise")) {
			degree = circlize(x, y, sector.index = sector.index, track.index = track.index)[, 1]
			degree = degree %% 360
			l1 = degree >= 90 & degree < 270  # should be reverse.clockwise
			l2 = !l1  # should be clockwise
			if(facing == "reverse.clockwise") {
				adj1 = adj
				adj2 = 1 - adj
				facing1 = "reverse.clockwise"
				facing2 = "clockwise"
			} else {
				adj1 = 1- adj
				adj2 = adj
				facing1 = "reverse.clockwise"
				facing2 = "clockwise"
			}
		} else if(facing %in% c("inside", "outside", "bending.inside", "bending.outside")) {
			degree = circlize(x, y, sector.index = sector.index, track.index = track.index)[, 1]
			degree = degree %% 360
			l1 = degree > 0 & degree < 180  # should be inside
			l2 = !l1   # should be outside
			if(facing == "inside") {
				adj1 = adj
				adj2 = 1 - adj
				facing1 = "inside"
				facing2 = "outside"
			} else if(facing == "outside") {
				adj1 = 1 - adj
				adj2 = adj
				facing1 = "inside"
				facing2 = "outside"
			} else if(facing == "bending.inside") {
				adj1 = adj
				adj2 = 1 - adj
				facing1 = "bending.inside"
				facing2 = "bending.outside"
			} else if(facing == "bending.outside") {
				adj1 = 1 - adj
				adj2 = adj
				facing1 = "bending.inside"
				facing2 = "bending.outside"
			}
		}
		if(sum(l1)) {
			circos.text(x[l1], y[l1], labels[l1], sector.index = sector.index,
				track.index = track.index, facing = facing1, niceFacing = FALSE, adj = adj1,
				cex = cex[l1], col = col[l1], font = font[l1], ...)
		}

		if(sum(l2)) {
			circos.text(x[l2], y[l2], labels[l2], sector.index = sector.index,
				track.index = track.index, facing = facing2, niceFacing = FALSE, adj = adj2,
				cex = cex[l2], col = col[l2], font = font[l2], ...)
		}
		return(invisible(NULL))
	}

	if(grepl("bending", facing)) {

		chars = strsplit(labels, "")
		if(facing == "bending.outside") {
     		chars = lapply(chars, rev)
		}

    	nlabel = length(labels)
		strw = lapply(chars, strwidth, cex = cex, font = font)
		strh = lapply(chars, strheight, cex = cex, font = font)

		if(facing == "bending.outside") {
			adj = 1 - adj
		}

		alpha.offset = sapply(strw, function(x) sum(x))*adj[1]/d[, 2] * 180/pi
		rou.offset = sapply(strh, function(x) -x[1]*adj[2])

		for(i in seq_along(labels)) {
			# degree of the bottom center of each char
			theta = numeric(length(strw[[i]]))
			alpha = d[i, 1] + alpha.offset[i]
			rou = d[i, 2] + rou.offset[i]

			for(j in  seq_along(strw[[i]])) {
				theta[j] = alpha - asin(strw[[i]][j]/2/d[i, 2])*180/pi
				alpha = alpha - asin(strw[[i]][j]/d[i, 2])*180/pi
			}
			dr = reverse.circlize(theta, rep(rou, length(theta)), sector.index = sector.index, track.index = track.index)

			if(facing == "bending.inside") {
				circos.text(dr[, 1], dr[, 2], labels = chars[[i]], sector.index = sector.index, track.index = track.index, cex = cex[i], col = col[i], font = font[i], facing = "inside", adj = c(0.5, 0), ...)
			} else if(facing == "bending.outside") {
				circos.text(dr[, 1], dr[, 2], labels = chars[[i]], sector.index = sector.index, track.index = track.index, cex = cex[i], col = col[i], font = font[i], facing = "outside", adj = c(0.5, 1), ...)
			}
			#circos.points(dr[, 1], dr[, 2], pch = 16, cex = 0.8)
		}

	} else {

        srt = d[,1]-90    #srt = ifelse(srt > 0, srt, 360 + srt)

        if(facing == "reverse.clockwise") {           # pointing to the circle center, but facing left at 90 degree
            srt = srt - 90
        } else if(facing == "clockwise") {   # pointing to the circle center, but facing right at 90 degree
            srt = srt + 90
        } else if(facing == "downward") {       # horizontal at the finnal graph
            srt = rep(0, length(srt))
        } else if(facing == "outside") {
			srt = srt + 180
		}

		m = polar2Cartesian(d)

		for(i in seq_along(x)) {
			text(m[i, 1], m[i, 2], labels = labels[i], srt = srt[i],
				 cex = cex[i], col = col[i], font = font[i], adj = adj, ...)
		}
    }

    return(invisible(NULL))
}

# == title
# Convert fontsize to cex
#
# == param
# -x value for fontsize
#
fontsize = function(x) {
	x/par("ps")
}

# == title
# Mark the value as a degree value
#
# == param
# -x degree value
#
# == value
# a ``degree`` object
#
degree = function(x) {
	class(x) = "degree"
	list(x)
}

# == title
# Draw text in cells among the whole track
#
# == param
# -sectors      A `factor` or a character vector which represents the categories of data
# -factors      The same as ``sectors``. It will be removed in future versions. 
# -x            Data points on x-axis
# -y            Data points on y-axis
# -labels       Labels
# -track.index  Index for the track
# -direction    deprecated, use ``facing`` instead.
# -facing       Facing of text
# -niceFacing   Should the facing of text be adjusted to fit human eyes?
# -adj          Adjustment for text
# -cex          Font size
# -col          Font color
# -font         Font style
#
# == details
# The function adds texts in multiple cells by first splitting data into several parts in which
# each part corresponds to one factor (sector index) and then add texts in cells by calling `circos.text`.
#
# This function can be replaced by a ``for`` loop containing `circos.text`.
circos.trackText = function(
	sectors, 
	x, y,
	labels,
	track.index = get.current.track.index(),
    direction = NULL,
    facing = c("inside", "outside", "reverse.clockwise", "clockwise",
	    "downward", "bending", "bending.inside", "bending.outside"),
    niceFacing = FALSE,
    adj = par("adj"),
    cex = 1,
    col = par("col"),
    font = par("font"),
    factors = sectors) {

    # basic check here
    if(length(x) != length(factors) || length(y) != length(factors)) {
        stop_wrap("Length of data and length of factors differ.\n")
    }

    if(!is.factor(factors)) {
        factors = factor(factors)
    }

    # check whether there are some categories that are not in the circle
	setdiff.factors = setdiff(levels(factors), get.all.sector.index())
    if(length(setdiff.factors)) {
        stop_wrap("Cannot find these categories in existed sectors:", paste(setdiff.factors, collapse = ", "), ".")
    }

    le = levels(factors)

    # set these graphic parameters with same length as the factors
    # ``direction`` and ``adj`` are not recycled
    cex = recycle.with.factors(cex, factors)
    col = recycle.with.factors(col, factors)
    font = recycle.with.factors(font, factors)

    for(i in seq_along(le)) {
        l = factors == le[i]
        nx = x[l]
        ny = y[l]
        nlabels = labels[l]
        ncex = cex[l]
        ncol = col[l]
        nfont = font[l]
        circos.text(nx, ny, sector.index = le[i],
                      track.index = track.index, labels = nlabels,
                      direction = direction, facing = facing, niceFacing = niceFacing,
					  adj = adj, cex = ncex, col = ncol, font = nfont)

    }
    return(invisible(NULL))
}

# == title
# Draw x-axis
#
# == param
# -h                Position of the x-axis, can be "top", "bottom" or a numeric value
# -major.at         If it is numeric vector, it identifies the positions
#                   of the major ticks. It can exceed ``xlim`` value and the exceeding part
#                   would be trimmed automatically. If it is ``NULL``, about every 10 degrees there is a major tick.
# -labels           labels of the major ticks. Also, the exceeding part would be trimmed automatically.
#                   The value can also be logical (either an atomic value or a vector) which represents
#                   which labels to show.
# -major.tick       Whether to draw major tick. If it is set to ``FALSE``, there will be
#                   no minor ticks neither.
# -sector.index     Index for the sector.
# -track.index      Index for the track.
# -labels.font      Font style for the axis labels.
# -labels.cex       Font size for the axis labels.
# -labels.direction Deprecated, use ``facing`` instead.
# -labels.facing    Facing of labels on axis, passing to `circos.text`
# -labels.niceFacing Should facing of axis labels be human-easy.
# -direction        Whether the axis ticks point to the outside or inside of the circle.
# -minor.ticks      Number of minor ticks between two close major ticks.
# -major.tick.length Length of the major ticks, measured in "current" data coordinate. `convert_y` can be
#                   used to convert an absolute unit to the data coordinate.
# -major.tick.percentage Not used any more, please directly use ``major.tick.length``.
# -lwd              Line width for ticks.
# -col              Color for the axes.
# -labels.col       Color for the labels.
# -labels.pos.adjust  Whether to adjust the positions of the first label and the last label so that the first label 
#                     align to its left and the last label align to its right if they exceed the range on axes. The value can be a vector
#                    of length two which correspond to the first label and the last label.
#
# == details
# It only draws axes on x-direction.
#
# == seealso
# `circos.yaxis` draws axes on y-direction.
#
# https://jokergoo.github.io/circlize_book/book/graphics.html#axes
#
# == example
# sectors = letters[1:8]
# circos.par(points.overflow.warning = FALSE)
# circos.initialize(sectors, xlim = c(0, 10))
# circos.trackPlotRegion(sectors, ylim = c(0, 10), track.height = 0.1,
#     bg.border = NA, panel.fun = function(x, y) {
#         circos.text(5, 10, get.cell.meta.data("sector.index"))
# })
#
# circos.trackPlotRegion(sectors, ylim = c(0, 10))
# circos.axis(sector.index = "a")
# circos.axis(sector.index = "b", direction = "inside", labels.facing = "outside")
# circos.axis(sector.index = "c", h = "bottom")
# circos.axis(sector.index = "d", h = "bottom", direction = "inside",
#     labels.facing = "reverse.clockwise")
# circos.axis(sector.index = "e", h = 5, major.at = c(1, 3, 5, 7, 9))
# circos.axis(sector.index = "f", h = 5, major.at = c(1, 3, 5, 7, 9),
#     labels = c("a", "c", "e", "g", "f"), minor.ticks = 0)
# circos.axis(sector.index = "g", h = 5, major.at = c(1, 3, 5, 7, 9),
#     labels = c("a1", "c1", "e1", "g1", "f1"), major.tick = FALSE,
#     labels.facing = "reverse.clockwise")
# circos.axis(sector.index = "h", h = 2, major.at = c(1, 3, 5, 7, 9),
#     labels = c("a1", "c1", "e1", "g1", "f1"), minor.ticks = 2, 
#     major.tick.length = mm_y(5), labels.facing = "clockwise")
# circos.clear()
#
# if(FALSE) {
#
# ############### real-time clock #################
# factors = letters[1]
#
# circos.par("gap.degree" = 0, "cell.padding" = c(0, 0, 0, 0), "start.degree" = 90)
# circos.initialize(sectors, xlim = c(0, 12))
# circos.trackPlotRegion(sectors, ylim = c(0, 1), bg.border = NA)
# circos.axis(sector.index = "a", major.at = 0:12, labels = "",
#     direction = "inside", major.tick.length = mm_y(3))
# circos.text(1:12, rep(0.5, 12), 1:12, facing = "downward")
#
# while(1) {
#     current.time = as.POSIXlt(Sys.time())
#     sec = ceiling(current.time$sec)
#     min = current.time$min
#     hour = current.time$hour
#
#     # erase the clock hands
#     draw.sector(rou1 = 0.8, border = "white", col = "white")
#
#     sec.degree = 90 - sec/60 * 360
#     arrows(0, 0, cos(sec.degree/180*pi)*0.8, sin(sec.degree/180*pi)*0.8)
#
#     min.degree = 90 - min/60 * 360
#     arrows(0, 0, cos(min.degree/180*pi)*0.7, sin(min.degree/180*pi)*0.7, lwd = 2)
#
#     hour.degree = 90 - hour/12 * 360 - min/60 * 360/12
#     arrows(0, 0, cos(hour.degree/180*pi)*0.4, sin(hour.degree/180*pi)*0.4, lwd = 2)
#
#     Sys.sleep(1)
# }
# circos.clear()
# }
circos.axis = function(
	h = "top",
	major.at = NULL,
	labels = TRUE,
	major.tick = TRUE,
	sector.index = get.current.sector.index(),
	track.index = get.current.track.index(),
	labels.font = par("font"),
	labels.cex = par("cex"),
	labels.facing = "inside",
	labels.direction = NULL,
	labels.niceFacing = TRUE,
	direction = c("outside", "inside"),
	minor.ticks = 4,
	major.tick.length = mm_y(1),
    major.tick.percentage = 0.5,
	lwd = par("lwd"),
	col = par("col"),
	labels.col = par("col"),
	labels.pos.adjust = TRUE) {

    os = get.current.sector.index()
    ot = get.current.track.index()
    set.current.cell(sector.index, track.index)
    on.exit(set.current.cell(os, ot))

    if(!is.null(labels.direction)) {
        labels.facing = switch(labels.direction[1],
                        default = "inside",
                        default2 = "outside",
                        vertical_left = "reverse.clockwise",
                        vertical_right = "clockwise",
                        horizontal = "downward",
                        arc = "bending")
        warning_wrap("`labels.direction` is deprecated, please use `labels.facing` instead.")
    }

	direction = direction[1]
	if(! direction %in% c("outside", "inside")) {
		stop_wrap("Direction should be in 'outside' and 'inside'.")
	}

	xlim = get.cell.meta.data("xlim", sector.index, track.index)

	sector.data = get.sector.data(sector.index)

	if(h == "top") {
		h = get.cell.meta.data("cell.ylim", sector.index, track.index)[2]
	} else if(h == "bottom") {
		h = get.cell.meta.data("cell.ylim", sector.index, track.index)[1]
	}

	if(is.null(major.at)) {
		major.by = .default.major.by(sector.index, track.index)
		major.at = seq(floor(xlim[1]/major.by)*major.by, xlim[2], by = major.by)
		major.at = c(major.at, major.at[length(major.at)] + major.by)
	}

	minor.at = NULL
	if(minor.ticks != 0) {
		for(i in seq_along(major.at)) {
			if(i == 1) next
			k = seq_len(minor.ticks) / (minor.ticks + 1)
			minor.at = c(minor.at, k * (major.at[i] - major.at[i - 1]) + major.at[i - 1])
		}
	}

	xlim2 = xlim
	circos.lines(c(ifelse(major.at[1] >= xlim2[1], major.at[1], xlim2[1]),
	               ifelse(major.at[length(major.at)] <= xlim2[2], major.at[length(major.at)], xlim2[2])),
				 c(h, h), sector.index = sector.index, track.index = track.index, lwd = lwd, col = col)

	# ticks
	yrange = get.cell.meta.data("yrange", sector.index, track.index)
    if(!missing(major.tick.percentage)) {
        message("`major.tick.percentage` is not used any more, please directly use argument `major.tick.length`.")
    }
	# major.tick.length = yrange * major.tick.percentage
	# major.tick.length = convert_y(2, "mm", sector.index, track.index)

	op = circos.par("points.overflow.warning")
	circos.par("points.overflow.warning" = FALSE)
	l = major.at >= xlim2[1] & major.at <= xlim2[2]
	if(major.tick) {
		circos.segments(major.at[l], rep(h, sum(l)), major.at[l], rep(h, sum(l)) + major.tick.length*ifelse(direction == "outside", 1, -1), straight = TRUE,
			             sector.index = sector.index, track.index = track.index, lwd = lwd, col = col)
	}
	#for(i in seq_along(major.at)) {

		# if(major.at[i] < xlim2[1] || major.at[i] > xlim2[2]) {
		# 	next
		# }

		# if(major.tick) {
		# 	circos.lines(c(major.at[i], major.at[i]), c(h, h + major.tick.length*ifelse(direction == "outside", 1, -1)), straight = TRUE,
		# 	             sector.index = sector.index, track.index = track.index, lwd = lwd)
		# }

	labels.adj = NULL
	if(direction == "outside") {
		if(labels.facing == "inside") {
			labels.adj = c(0.5, 0)
		} else if(labels.facing == "outside") {
			labels.adj = c(0.5, 1)
		} else if(labels.facing == "reverse.clockwise") {
			labels.adj = c(1, 0.5)
		} else if(labels.facing == "clockwise") {
			labels.adj = c(0, 0.5)
		} else if(labels.facing == "downward") {
			labels.adj = c(0.5, 0.5)
		} else {
			labels.adj = c(0.5, 0)
		}
	} else {
		if(labels.facing == "inside") {
			labels.adj = c(0.5, 1)
		} else if(labels.facing == "outside") {
			labels.adj = c(0.5, 0)
		} else if(labels.facing == "reverse.clockwise") {
			labels.adj = c(0, 0.5)
		} else if(labels.facing == "clockwise") {
			labels.adj = c(1, 0.5)
		} else if(labels.facing == "downward") {
			labels.adj = c(0.5, 0.5)
		} else {
			labels.adj = c(0.5, 1)
		}
	}

	add_axis_labels = function(x, y, labels, h, col, labels.pos.adjust, ...) {
		arg_list = list(...)

		n = length(x)
		first_label_width = convert_x(strwidth(labels[1], units = "inches", cex = arg_list$cex), "inches", arg_list$sector.index, arg_list$track.index, h = h)
        first_label_height = convert_x(strheight(labels[1], units = "inches", cex = arg_list$cex), "inches", arg_list$sector.index, arg_list$track.index, h = h)

        if(n >= 1) {
			last_label_width = convert_x(strwidth(labels[n], units = "inches", cex = arg_list$cex), "inches", arg_list$sector.index, arg_list$track.index, h = h)
			last_label_height = convert_x(strheight(labels[n], units = "inches", cex = arg_list$cex), "inches", arg_list$sector.index, arg_list$track.index, h = h)
		} else {
            last_label_width = 0
            last_label_height = 0
        }

		if(labels.facing == "inside") {
			offset.first = first_label_width/2 - (x[1] - get.cell.meta.data("cell.xlim", sector.index, track.index)[1])
			offset.last = last_label_width/2 - abs(x[n] - get.cell.meta.data("cell.xlim", sector.index, track.index)[2])
		} else if(labels.facing == "outside") {
			offset.first = first_label_width/2 - (x[1] - get.cell.meta.data("cell.xlim", sector.index, track.index)[1])
			offset.last = last_label_width/2 - abs(x[n] - get.cell.meta.data("cell.xlim", sector.index, track.index)[2])
		} else if(labels.facing == "reverse.clockwise") {
			offset.first = first_label_height/2 - (x[1] - get.cell.meta.data("cell.xlim", sector.index, track.index)[1])
			offset.last = last_label_height/2 - abs(x[n] - get.cell.meta.data("cell.xlim", sector.index, track.index)[2])
		} else if(labels.facing == "clockwise") {
			offset.first = first_label_height/2 - (x[1] - get.cell.meta.data("cell.xlim", sector.index, track.index)[1])
			offset.last = last_label_height/2 - abs(x[n] - get.cell.meta.data("cell.xlim", sector.index, track.index)[2])
		} else if(labels.facing == "downward") {
			offset.first = first_label_width/2 - (x[1] - get.cell.meta.data("cell.xlim", sector.index, track.index)[1])
			offset.last = last_label_width/2 - abs(x[n] - get.cell.meta.data("cell.xlim", sector.index, track.index)[2])
		} else {
			offset.first = first_label_width/2 - (x[1] - get.cell.meta.data("cell.xlim", sector.index, track.index)[1])
			offset.last = last_label_width/2 - abs(x[n] - get.cell.meta.data("cell.xlim", sector.index, track.index)[2])
		}

		if(length(labels.pos.adjust) == 1) labels.pos.adjust = rep(labels.pos.adjust, 2)
		if(!labels.pos.adjust[1]) {
			offset.first = 0
		}
		if(!labels.pos.adjust[2]) {
			offset.last = 0
		}

		if(n == 1) {
			circos.text(x + ifelse(offset.first > 0, offset.first, 0), y, labels, col = col, ...)
		} else if(n == 2) {
			circos.text(x[1] + ifelse(offset.first > 0, offset.first, 0), y[1], labels[1], col = col, ...)
			circos.text(x[2] - ifelse(offset.last > 0, offset.last, 0), y[2], labels[2], col = col, ...)
		} else if(n > 2) {
			circos.text(x[1] + ifelse(offset.first > 0, offset.first, 0), y[1], labels[1], col = col, ...)
			circos.text(x[2:(n-1)], y[2:(n-1)], labels[2:(n-1)], col = col, ...)
			circos.text(x[n] - ifelse(offset.last > 0, offset.last, 0), y[n], labels[n], col = col, ...)
		}

		# circos.text(x, y, labels, ...)
	}

	if(is.logical(labels)) {
        if(labels[1]) {
    		add_axis_labels(major.at[l], rep(h, sum(l)) + (major.tick.length + mm_y(0.5, sector.index, track.index))*ifelse(direction == "outside", 1, -1),
    		           labels = major.at[l], adj = labels.adj,
    		           font = labels.font, cex = labels.cex, sector.index = sector.index, track.index = track.index,
    		           facing = labels.facing, niceFacing = labels.niceFacing, h = h, col = labels.col,
    		           labels.pos.adjust = labels.pos.adjust)
    	}
    } else if(is.function(labels)) {
        add_axis_labels(major.at[l], rep(h, sum(l)) + (major.tick.length + mm_y(0.5, sector.index, track.index))*ifelse(direction == "outside", 1, -1),
                       labels = labels(major.at[l]), adj = labels.adj,
                       font = labels.font, cex = labels.cex, sector.index = sector.index, track.index = track.index,
                       facing = labels.facing, niceFacing = labels.niceFacing, h = h, col = labels.col,
                       labels.pos.adjust = labels.pos.adjust)

    } else if(length(labels)) {
		add_axis_labels(major.at[l], rep(h, sum(l)) + (major.tick.length + mm_y(0.5, sector.index, track.index))*ifelse(direction == "outside", 1, -1),
		            labels = labels[l], adj = labels.adj,
		            font = labels.font, cex = labels.cex, sector.index = sector.index, track.index = track.index,
			        facing = labels.facing, niceFacing = labels.niceFacing, h = h, col = labels.col,
			        labels.pos.adjust = labels.pos.adjust)
	}

	#}
	if(major.tick) {
		# for(i in seq_along(minor.at)) {
		# 	if(minor.at[i] < xlim2[1] || minor.at[i] > xlim2[2]) {
		# 		next
		# 	}

		# 	circos.lines(c(minor.at[i], minor.at[i]), c(h, h + major.tick.length/2*ifelse(direction == "outside", 1, -1)), straight = TRUE,
		# 	             sector.index = sector.index, track.index = track.index, lwd = lwd)
		# }

		l = minor.at >= xlim2[1] & minor.at <= xlim2[2]
		circos.segments(minor.at[l], rep(h, sum(l)), minor.at[l], rep(h, sum(l)) + major.tick.length/2*ifelse(direction == "outside", 1, -1), straight = TRUE,
			sector.index = sector.index, track.index = track.index, lwd = lwd, col = col)
	}

	circos.par("points.overflow.warning" = op)
	return(invisible(NULL))
}

# == title
# Draw x-axis
#
# == param
# -... All pass to `circos.axis`.
#
# == details
# This function is identical to `circos.axis`.
#
circos.xaxis = function(...) {
	circos.axis(...)
}

.default.major.by = function(
	sector.index = get.current.sector.index(),
	track.index = get.current.track.index()) {
	# start.degree - end.degree is always a positive value.
	d = circos.par("major.by.degree")
	cell.start.degre = get.cell.meta.data("cell.start.degree", sector.index, track.index)
	tm = reverse.circlize(c(cell.start.degre, cell.start.degre-d), rep(get.cell.meta.data("cell.bottom.radius", sector.index = sector.index, track.index = track.index), 2))
	major.by = abs(tm[1, 1] - tm[2, 1])
	digits = as.numeric(gsub("^.*e([+-]\\d+)$", "\\1", sprintf("%e", major.by)))
	major.by = round(major.by, digits = -1*digits)
	return(major.by)
}

# == title
# Draw y-axis
#
# == param
# -side add the y-axis on the left or right of the cell
# -at         If it is numeric vector, it identifies the positions
#                   of the ticks. It can exceed ``ylim`` value and the exceeding part
#                   would be trimmed automatically.
# -labels           labels of the ticks. The exceeding part would be trimmed automatically.
#                   The value can also be logical (either an atomic value or a vector) which represents
#                   which labels to show.
# -tick       Whether to draw ticks.
# -sector.index     Index for the sector
# -track.index      Index for the track
# -labels.font      font style for the axis labels
# -labels.cex       font size for the axis labels
# -labels.niceFacing Should facing of axis labels be human-easy
# -tick.length      length of the tick
# -lwd              line width for ticks
# -col              color for the axes
# -labels.col       color for the labels
#
# == details
# Note, you need to set the gap between sectors manually by `circos.par` to make sure there is enough space
# for y-axis.
#
# == example
# op = par(no.readonly = TRUE)
#
# sectors = letters[1:8]
# circos.par(points.overflow.warning = FALSE)
# circos.par(gap.degree = 8)
# circos.initialize(sectors, xlim = c(0, 10))
# circos.trackPlotRegion(sectors, ylim = c(0, 10), track.height = 0.5)
# par(cex = 0.8)
# for(a in letters[2:4]) {
#   circos.yaxis(side = "left", sector.index = a)
# }
# for(a in letters[5:7]) {
#   circos.yaxis(side = "right", sector.index = a)
# }
# circos.clear()
#
# par(op)
circos.yaxis = function(
	side = c("left", "right"),
	at = NULL,
	labels = TRUE,
	tick = TRUE,
	sector.index = get.current.sector.index(),
	track.index = get.current.track.index(),
	labels.font = par("font"),
	labels.cex = par("cex"),
	labels.niceFacing = TRUE,
	tick.length = convert_x(1, "mm", sector.index, track.index),
	lwd = par("lwd"),
	col = par("col"),
	labels.col = par("col")) {

	ylim = get.cell.meta.data("ylim", sector.index, track.index)

	side = match.arg(side)[1]
	if(side == "left") {
		v = get.cell.meta.data("cell.xlim", sector.index, track.index)[1]
	} else if(side == "right") {
		v = get.cell.meta.data("cell.xlim", sector.index, track.index)[2]
	}

	if(is.null(at)) {
		at = grid.pretty(ylim)
        if(is.function(labels)) {
            labels = labels(at)
        } else {
		  labels = at
        }
	}

	ylim2 = ylim

	circos.lines(rep(v, 2),
		get.cell.meta.data("cell.ylim", sector.index, track.index),
		sector.index = sector.index, track.index = track.index, lwd = lwd, col = col)

	# ticks
	yrange = get.cell.meta.data("yrange", sector.index, track.index)
	xrange = get.cell.meta.data("xrange", sector.index, track.index)
	# tick.length = tick.length/abs(get.cell.meta.data("cell.start.degree", sector.index, track.index) - get.cell.meta.data("cell.end.degree", sector.index, track.index)) * xrange
	# tick.length = convert_x(2, "mm", sector.index, track.index)

	op = circos.par("points.overflow.warning")
	circos.par("points.overflow.warning" = FALSE)
	l = at >= ylim2[1] & at <= ylim2[2]
	if(tick) {
		circos.segments(rep(v, sum(l)), at[l], rep(v, sum(l)) + tick.length*ifelse(side == "right", 1, -1), at[l], straight = TRUE,
			             sector.index = sector.index, track.index = track.index, lwd = lwd, col = col)
	}

	labels.adj = NULL
	if(side == "left") {
        if(!circos.par$xaxis.clock.wise) {
            labels.adj = c(0, 0.5)
        } else {
            labels.adj = c(1, 0.5)
        }
	} else {
        if(!circos.par$xaxis.clock.wise) {
            labels.adj = c(1, 0.5)
        } else {
            labels.adj = c(0, 0.5)
        }
	}

	if(is.logical(labels) && labels) {
		circos.text(rep(v, sum(l)) + (tick.length + convert_x(0.5, "mm", sector.index, track.index))*ifelse(side == "right", 1, -1), at[l],
		           labels = at[l], adj = labels.adj,
		           font = labels.font, cex = labels.cex, sector.index = sector.index, track.index = track.index,
		           facing = "inside", niceFacing = labels.niceFacing, col = labels.col)
	} else if(is.logical(labels) && !labels) {

    } else if(length(labels)) {
		circos.text(rep(v, sum(l)) + (tick.length + convert_x(0.5, "mm", sector.index, track.index))*ifelse(side == "right", 1, -1), at[l],
		            labels = labels[l], adj = labels.adj,
		            font = labels.font, cex = labels.cex, sector.index = sector.index, track.index = track.index,
			        facing = "inside", niceFacing = labels.niceFacing, col = labels.col)
	}

	circos.par("points.overflow.warning" = op)
	return(invisible(NULL))
}


#####################################################################
#
# simulate high-level graphic functions such as barplot, hist, boxplot ...
#
#####################################################################

# == title
# Draw histogram in cells among a whole track
#
# == param
# -sectors      A `factor` or a character vector which represents the categories of data
# -factors      The same as ``sectors``. It will be removed in future versions. 
# -x            Data on the x-axis
# -track.index  Index for the track which is going to be updated. Setting it to ``NULL`` means
#               creating the plotting regions in the next newest track.
# -track.height Height of the track. It is the percentage to the radius of the unit circle.
#               If to update a track, this argument is disabled.
# -ylim         Ranges on y-direction. By default, ``ylim`` is calculated automatically.
# -force.ylim   Whether to force all cells in the track to share the same ``ylim``.
# -col          Filled color for histogram
# -border       Border color for histogram
# -lty          Line style for histogram
# -lwd          Line width for histogram
# -bg.col       Background color for the plotting regions
# -bg.border    Color for the border of the plotting regions
# -bg.lty       Line style for the border of the plotting regions
# -bg.lwd       Line width for the border of the plotting regions
# -breaks       see `graphics::hist`
# -include.lowest see `graphics::hist`
# -right          see `graphics::hist`
# -draw.density   whether draw density lines instead of histogram bars.
# -area           whether to fill the area below the density lines. If it is set to ``TRUE``, ``col`` controls the filled color in the area and ``border`` controls color of the line.
# -bin.size size of the bins of the histogram
#
# == details
# It draw histogram in cells among a whole track. It is also an example to show how to add self-defined
# high-level graphics by this package.
#
# == seealso
# https://jokergoo.github.io/circlize_book/book/high-level-plots.html#histograms
#
# == example
# \donttest{
# x = rnorm(1600)
# sectors = sample(letters[1:16], 1600, replace = TRUE)
# circos.initialize(sectors, x = x)
# circos.trackHist(sectors, x = x, col = "#999999",
#   border = "#999999")
# circos.trackHist(sectors, x = x, bin.size = 0.1,
#   col = "#999999", border = "#999999")
# circos.trackHist(sectors, x = x, draw.density = TRUE,
#   col = "#999999", border = "#999999")
# circos.clear()
# }
circos.trackHist = function(
	sectors,
	x,
	track.height = circos.par("track.height"),
    track.index = NULL,
    ylim = NULL,
    force.ylim = TRUE,
    col = ifelse(draw.density, "black", NA),
	border = "black",
	lty = par("lty"),
	lwd = par("lwd"),
    bg.col = NA,
    bg.border = "black",
    bg.lty = par("lty"),
    bg.lwd = par("lwd"),
    breaks = "Sturges",
    include.lowest = TRUE,
    right = TRUE,
    draw.density = FALSE,
    bin.size = NULL,
    area = FALSE,
    factors = sectors) {

    # basic check here
    if(length(x) != length(factors)) {
        stop_wrap("Length of data and length of factors differ.")
    }

    if(!is.factor(factors)) {
        factors = factor(factors)
    }

	# check whether there are some categories that are not in the circle
	setdiff.factors = setdiff(levels(factors), get.all.sector.index())
    if(length(setdiff.factors)) {
        stop_wrap("Cannot find these categories in existed sectors:", paste(setdiff.factors, collapse = ", "), ".")
    }

    # calculate the distributions
    le = levels(factors)

    xx = NULL
    yy = NULL
    fa = NULL

    for(i in seq_along(le)) {
        l = factors == le[i]
        nx = x[l]

        if(!is.null(bin.size)) {
        	breaks = seq(min(nx), max(nx), by = bin.size)
        	if(breaks[length(breaks)] < max(nx)) {
	        	breaks = c(breaks, breaks[length(breaks)] + bin.size)
	        }
        }

        h = hist(nx, plot = FALSE, breaks = breaks, include.lowest = include.lowest, right = right)

        xx = c(xx, h$breaks)
        if(draw.density) {
            yy = c(yy, 0, h$density)
        } else {
            yy = c(yy, 0, h$counts)
        }

        fa = c(fa, rep(le[i], length(h$breaks)))
    }

    # create the plotting region
    circos.trackPlotRegion(sectors = fa, y=yy, track.height = track.height,
                      track.index = track.index, ylim = ylim, force.ylim = force.ylim,
                      bg.col = bg.col, bg.border = bg.border, bg.lty = bg.lty, bg.lwd = bg.lwd)

    track.index = get.current.track.index()

	l3 = logical(0)
	for(i in seq_along(le)) {
		xlim = get.cell.meta.data("xlim", sector.index = le[i], track.index = track.index)
		l = fa == le[i]
		l2 = xx[l] >= xlim[1] & xx[l] <= xlim[2]
		l3 = c(l3, l2)
	}

	xx = xx[l3]
	yy = yy[l3]
	fa = fa[l3]

    if(draw.density) {
        circos.trackLines(sectors = fa, xx, yy, track.index = track.index,
                          col = col, lty = lty, lwd = lwd, area = area, border = border)
    } else {
        # in each cell, draw rectangles
        col = recycle.with.levels(col, le)
        border = recycle.with.levels(border, le)
        lty = recycle.with.levels(lty, le)
        lwd = recycle.with.levels(lwd, le)
        for(i in seq_along(le)) {
            l = fa == le[i]

            nx = xx[l]
            ny = yy[l]

            cell.xlim = get.cell.meta.data("cell.xlim", le[i], track.index)
            nx[nx < cell.xlim[1]] = cell.xlim[1]
            nx[nx > cell.xlim[2]] = cell.xlim[2]

            for(j in seq_along(nx)) {
                if(j == 1) {
                    next
                }

                circos.rect(nx[j-1], 0, nx[j], ny[j],
                            sector.index = le[i], track.index = track.index,
                            col = col[i], border = border[i], lty = lty[i], lwd = lwd[i])
            }
        }
    }
    return(invisible(NULL))
}


# == title
# Add circular dendrograms
#
# == param
# -dend A `stats::dendrogram` object.
# -facing Is the dendromgrams facing inside to the circle or outside?
# -max_height Maximum height of the dendrogram. This is important if more than one dendrograms
#             are drawn in one track and making them comparable. The height of a dendrogram
#             can be obtained by ``attr(dend, "height")``.
# -use_x_attr Whether use the ``x`` attribute to determine node positions in the dendrogram, used internally.
# -sector.index Index of sector.
# -track.index Index of track.
#
# == details
# Assuming there are ``n`` nodes in the dendrogram, the positions for leaves on x-axis are always ``0.5, 1.5, ..., n - 0.5``.
# So you must be careful with ``xlim`` when you initialize the cirular layout.
#
# You can use the ``dendextend`` package to render the dendrograms.
#
# == seealso
# https://jokergoo.github.io/circlize_book/book/high-level-plots.html#phylogenetic-trees
#
# == example
# load(system.file(package = "circlize", "extdata", "bird.orders.RData"))
#
# labels = hc$labels  # name of birds
# ct = cutree(hc, 6)  # cut tree into 6 pieces
# n = length(labels)  # number of bird species
# dend = as.dendrogram(hc)
#
# circos.par(cell.padding = c(0, 0, 0, 0))
# circos.initialize(sectors = "a", xlim = c(0, n)) # only one sector
# max_height = attr(dend, "height")  # maximum height of the trees
# circos.trackPlotRegion(ylim = c(0, 1), bg.border = NA, track.height = 0.3,
#     panel.fun = function(x, y) {
#         for(i in seq_len(n)) {
#             circos.text(i-0.5, 0, labels[i], adj = c(0, 0.5),
#                 facing = "clockwise", niceFacing = TRUE,
#                 col = ct[labels[i]], cex = 0.7)
#         }
# })
#
# suppressPackageStartupMessages(require(dendextend))
# dend = color_branches(dend, k = 6, col = 1:6)
#
# circos.trackPlotRegion(ylim = c(0, max_height), bg.border = NA,
#     track.height = 0.4, panel.fun = function(x, y) {
#         circos.dendrogram(dend, max_height = max_height)
# })
# circos.clear()
circos.dendrogram = function(
    dend,
    facing = c("outside", "inside"),
    max_height = NULL,
    use_x_attr = FALSE,
    sector.index = get.current.sector.index(),
    track.index = get.current.track.index()) {

    os = get.current.sector.index()
    ot = get.current.track.index()
    set.current.cell(sector.index, track.index)
    on.exit(set.current.cell(os, ot))
    
    facing = match.arg(facing)[1]

    if(is.null(max_height)) {
        max_height = attr(dend, "height")
    }

    is.leaf = function(object) {
        leaf = attr(object, "leaf")
        if(is.null(leaf)) {
            FALSE
        } else {
            leaf
        }
    }

    use_x_attr = use_x_attr

    lines_par = function(col = par("col"), lty = par("lty"), lwd = par("lwd"), ...) {
        return(list(col = col, lty = lty, lwd = lwd))
    }

    points_par = function(col = par("col"), pch = par("pch"), cex = par("cex"), ...) {
        return(list(col = col, pch = pch, cex = cex))
    }

    draw.d = function(dend, max_height, facing = "outside", max_width = 0) {
        leaf = attr(dend, "leaf")
        height = attr(dend, "height")
        midpoint = attr(dend, "midpoint")
        n = length(dend)

        xl = numeric(n)
        yl = numeric(n)
        for(i in seq_len(n)) {
            if(use_x_attr) {
                xl[i] = attr(dend[[i]], "x")
            } else {
                if(is.leaf(dend[[i]])) {
                    xl[i] = x[as.character(attr(dend[[i]], "label"))]
                } else {
                    xl[i] = attr(dend[[i]], "midpoint") + x[as.character(labels(dend[[i]]))[1]]
                }
            }
            yl[i] = attr(dend[[i]], "height")
        }

        # graphic parameter for current branch
        # only for lines, there are lwd, col, lty
        edge_par_lt = vector("list", n)
        for(i in seq_len(n)) {
            edge_par_lt[[i]] = do.call("lines_par", as.list(attr(dend[[i]], "edgePar")))  # as.list to convert NULL to list()
        }
        node_par = attr(dend, "nodePar")
        if(!is.null(node_par)) node_par = do.call("points_par", as.list(attr(dend, "nodePar")))

        # plot the connection line
        if(facing == "outside") {
            if(n == 1) {
                circos.lines(c(xl[1], xl[1]), max_height - c(yl[1], height), 
                    col = edge_par_lt[[1]]$col, lty = edge_par_lt[[1]]$lty, lwd = edge_par_lt[[1]]$lwd, straight = TRUE)
            } else {
                circos.lines(c(xl[1], xl[1]), max_height - c(yl[1], height), 
                    col = edge_par_lt[[1]]$col, lty = edge_par_lt[[1]]$lty, lwd = edge_par_lt[[1]]$lwd, straight = TRUE)
                circos.lines(c(xl[1], (xl[1]+xl[2])/2), max_height - c(height, height), 
                    col = edge_par_lt[[1]]$col, lty = edge_par_lt[[1]]$lty, lwd = edge_par_lt[[1]]$lwd)
                if(n > 2) {
                    for(i in seq(2, n-1)) {
                        circos.lines(c(xl[i], xl[i]), max_height - c(yl[i], height), 
                            col = edge_par_lt[[i]]$col, lty = edge_par_lt[[i]]$lty, lwd = edge_par_lt[[i]]$lwd, straight = TRUE)
                        circos.lines(c((xl[i-1]+xl[i])/2, (xl[i]+xl[i+1])/2), max_height - c(height, height), 
                            col = edge_par_lt[[i]]$col, lty = edge_par_lt[[i]]$lty, lwd = edge_par_lt[[i]]$lwd)
                    }
                }
                circos.lines(c(xl[n], xl[n]), max_height - c(yl[n], height), 
                    col = edge_par_lt[[n]]$col, lty = edge_par_lt[[n]]$lty, lwd = edge_par_lt[[n]]$lwd, straight = TRUE)
                circos.lines(c(xl[n], (xl[n]+xl[n-1])/2), max_height - c(height, height), 
                    col = edge_par_lt[[n]]$col, lty = edge_par_lt[[n]]$lty, lwd = edge_par_lt[[n]]$lwd)
            }
            if(!is.null(node_par)) {
                circos.points(mean(xl)/2, max_height - height, col = node_par$col, pch = node_par$pch, cex = node_par$cex)
            }
        } else if(facing == "inside") {
            if(n == 1) {
                circos.lines(c(xl[1], xl[1]), c(yl[1], height), 
                    col = edge_par_lt[[1]]$col, lty = edge_par_lt[[1]]$lty, lwd = edge_par_lt[[1]]$lwd, straight = TRUE)
            } else {
                circos.lines(c(xl[1], xl[1]), c(yl[1], height), 
                    col = edge_par_lt[[1]]$col, lty = edge_par_lt[[1]]$lty, lwd = edge_par_lt[[1]]$lwd, straight = TRUE)
                circos.lines(c(xl[1], (xl[1]+xl[2])/2), c(height, height), 
                    col = edge_par_lt[[1]]$col, lty = edge_par_lt[[1]]$lty, lwd = edge_par_lt[[1]]$lwd)
                if(n > 2) {
                    for(i in seq(2, n-1)) {
                        circos.lines(c(xl[i], xl[i]), c(yl[i], height), 
                            col = edge_par_lt[[i]]$col, lty = edge_par_lt[[i]]$lty, lwd = edge_par_lt[[i]]$lwd, straight = TRUE)
                        circos.lines(c((xl[i-1]+xl[i])/2, (xl[i]+xl[i+1])/2), c(height, height), 
                            col = edge_par_lt[[i]]$col, lty = edge_par_lt[[i]]$lty, lwd = edge_par_lt[[i]]$lwd)
                    }
                }
                circos.lines(c(xl[n], xl[n]), c(yl[n], height), 
                    col = edge_par_lt[[n]]$col, lty = edge_par_lt[[n]]$lty, lwd = edge_par_lt[[n]]$lwd, straight = TRUE)
                circos.lines(c(xl[n], (xl[n]+xl[n-1])/2), c(height, height), 
                    col = edge_par_lt[[n]]$col, lty = edge_par_lt[[n]]$lty, lwd = edge_par_lt[[n]]$lwd)
            }
            if(!is.null(node_par)) {
                circos.points(mean(xl)/2, height, col = node_par$col, pch = node_par$pch, cex = node_par$cex)
            }
        }

        # do it recursively
        for(i in seq_len(n)) {
            if(is.leaf(dend[[i]])) {
                node_par = attr(dend[[i]], "nodePar")
                if(!is.null(node_par)) node_par = do.call("points_par", as.list(attr(dend[[i]], "nodePar")))
                if(facing == "outside") {
                    if(!is.null(node_par)) {
                        circos.points(xl[i], max_height, col = node_par$col, pch = node_par$pch, cex = node_par$cex)
                    }
                } else if(facing == "inside") {
                    if(!is.null(node_par)) {
                        circos.points(xl[i], 0, col = node_par$col, pch = node_par$pch, cex = node_par$cex)
                    }
                }
            } else {
                draw.d(dend[[i]], max_height, facing, max_width)
            }
        }
    }

    labels = as.character(labels(dend))
    x = seq_along(labels) - 0.5

    names(x) = labels
    n = length(labels)

    if(!is.leaf(dend)) draw.d(dend, max_height, facing, max_width = n)
}

# == title
# Draw barplots
#
# == param
# -value A numeric vector or a matrix. If it is a matrix, columns correspond to the height of bars.
# -pos Positions of the bars.
# -bar_width Width of bars. It assumes the bars locating at ``x = 1, 2, ...``.
# -col Filled color of bars.
# -border Color for the border.
# -lwd Line width.
# -lty Line style.
# -sector.index Index of sector.
# -track.index Index of track.
#
# == details
# If the input variable is a matrix, it draws a stacked barplot.
#
# Please note, the x-values of barplots are normally integer indices. Just be careful
# when initializing the circular layout.
#
# == example
# circos.initialize(letters[1:4], xlim = c(0, 10))
# circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
#     value = runif(10)
#     circos.barplot(value, 1:10 - 0.5, col = 1:10)
# })
# circos.track(ylim = c(-1, 1), panel.fun = function(x, y) {
#     value = runif(10, min = -1, max = 1)
#     circos.barplot(value, 1:10 - 0.5, col = ifelse(value > 0, 2, 3))
# })
# circos.clear()
#
# circos.initialize(letters[1:4], xlim = c(0, 10))
# circos.track(ylim = c(0, 4), panel.fun = function(x, y) {
#     value = matrix(runif(10*4), ncol = 4)
#     circos.barplot(value, 1:10 - 0.5, col = 2:5)
# })
# circos.clear()
circos.barplot = function(value, pos, bar_width = 0.6,
    col = NA, border = "black", lwd = par("lwd"), lty = par("lty"),
    sector.index = get.current.sector.index(),
    track.index = get.current.track.index()) {

    os = get.current.sector.index()
    ot = get.current.track.index()
    set.current.cell(sector.index, track.index)
    on.exit(set.current.cell(os, ot))

    if(is.matrix(value)) {
        if(nrow(value) != length(pos)) {
            stop("nrow of `value` should be the same as the length of `pos`.")
        }

        if(any(value < 0)) {
            stop("`value` should all be positive if it is a matrix.")
        }

        n = ncol(value)
        if(length(col) == 1) col = rep(col, n)
        if(length(border) == 1) border = rep(border, n)
        if(length(lwd) == 1) lwd = rep(lwd, n)
        if(length(lty) == 1) lty = rep(lty, n)
        if(length(bar_width) == 1) bar_width = rep(bar_width, nrow(value))

        for(i in 1:n) {
            if(i == 1) {
                circos.rect(pos - bar_width/2, 0, pos + bar_width/2, rowSums(value[, seq_len(i), drop = FALSE]), 
                    col = col[i], border = border[i], lwd = lwd[i], lty = lty[i])
            } else {
                circos.rect(pos - bar_width/2, rowSums(value[, seq_len(i-1), drop = FALSE]), pos + bar_width/2, rowSums(value[, seq_len(i), drop = FALSE]), 
                    col = col[i], border = border[i], lwd = lwd[i], lty = lty[i])
            }
        }
    } else if(is.atomic(value)) {
        if(length(value) != length(pos)) {
            stop("`value` and `pos` should have the same length.")
        }
        circos.rect(pos - bar_width/2, 0, pos + bar_width/2, value, col = col, border = border, lwd = lwd, lty = lty)
    } else {
        stop("`value` should be a vector or a matrix.")
    }
}


# == title
# Draw boxplots
#
# == param
# -value A numeric vector, a matrix or a list. If it is a matrix, boxplots are made by columns (each column is a box).
# -pos Positions of the boxes.
# -outline Whether to draw outliers.
# -box_width Width of boxes.
# -col Filled color of boxes.
# -border Color for the border as well as the quantile lines.
# -lwd Line width.
# -lty Line style
# -cex Point size.
# -pch Point type.
# -pt.col Point color.
# -sector.index Index of sector.
# -track.index Index of track.
#
# == detail
# Please note, the x-values of boxplots are normally integer indices. Just be careful
# when initializing the circular layout.
# 
# == example
# circos.initialize(letters[1:4], xlim = c(0, 10))
# circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
#     for(pos in seq(0.5, 9.5, by = 1)) {
#         value = runif(10)
#         circos.boxplot(value, pos)
#     }
# })
# circos.clear()
#
# circos.initialize(letters[1:4], xlim = c(0, 10))
# circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
#     value = replicate(runif(10), n = 10, simplify = FALSE)
#     circos.boxplot(value, 1:10 - 0.5, col = 1:10)
# })
# circos.clear()
circos.boxplot = function(value, pos, outline = TRUE, box_width = 0.6,
    col = NA, border = "black", lwd = par("lwd"), lty = par("lty"),
    cex = par("cex"), pch = 1, pt.col = par("col"),
    sector.index = get.current.sector.index(),
    track.index = get.current.track.index()) {

    os = get.current.sector.index()
    ot = get.current.track.index()
    set.current.cell(sector.index, track.index)
    on.exit(set.current.cell(os, ot))

    single_boxplot = function(value, pos, outline = TRUE, box_width = 0.6,
        col = NA, border = "black", lwd = par("lwd"), lty = par("lty"),
        cex = par("cex"), pch = 1, pt.col = par("col")) {

        boxplot_stats = boxplot(value, plot = FALSE)$stats
        box_height = boxplot_stats[4, 1] - boxplot_stats[2, 1]

        circos.rect(pos - 0.5* box_width, boxplot_stats[2, 1], pos + 0.5 * box_width, boxplot_stats[4, 1],
            col = col, border = border, lty = lty, lwd = lwd)
        circos.segments(pos - 0.5 * box_width, boxplot_stats[5, 1], pos + 0.5 * box_width, boxplot_stats[5, 1],
            col = border, lty = lty, lwd = lwd)
        circos.segments(pos, boxplot_stats[5, 1], pos, boxplot_stats[4, 1],
            col = border, lty = lty, lwd = lwd)
        circos.segments(pos, boxplot_stats[1, 1], pos, boxplot_stats[2, 1],
            col = border, lty = lty, lwd = lwd)
        circos.segments(pos - 0.5 * box_width, boxplot_stats[1, 1], pos + 0.5 * box_width, boxplot_stats[1, 1],
            col = border, lty = lty, lwd = lwd)
        circos.segments(pos - 0.5 * box_width, boxplot_stats[3, 1], pos + 0.5 * box_width, boxplot_stats[3, 1],
            col = border, lty = lty, lwd = lwd)
        if (outline) {
            l1 = value > boxplot_stats[5, 1]
            if (any(l1)) circos.points(x = rep(pos, sum(l1)), y = value[l1], cex = cex, col = pt.col, pch = pch)
            l2 = value < boxplot_stats[1, 1]
            if (any(l2)) circos.points(x = rep(pos, sum(l2)), y = value[l2], cex = cex, col = pt.col, pch = pch)
        }
    }

    if(is.matrix(value)) {
        value = as.data.frame(value)   
    } 

    if(is.list(value)) {
        n = length(value)
        if(length(pos) != n) {
            stop_wrap("Length of `pos` should be same as number of boxes.")
        }

        if(length(box_width) == 1) box_width = rep(box_width, length(value))

        if(length(col) == 1) col = rep(col, n)
        if(length(border) == 1) border = rep(border, n)
        if(length(lwd) == 1) lwd = rep(lwd, n)
        if(length(lty) == 1) lty = rep(lty, n)
        if(length(cex) == 1) cex = rep(cex, n)
        if(length(pch) == 1) pch = rep(pch, n)
        if(length(pt.col) == 1) pt.col = rep(pt.col, n)

        for(i in 1:n) {
            single_boxplot(value[[i]], pos = pos[i], outline = outline, box_width = box_width[i],
                col = col[i], border = border[i], lwd = lwd[i], lty = lty[i], cex = cex[i], 
                pch = pch[i], pt.col = pt.col[i])
        }
    } else if(is.atomic(value)) {
        single_boxplot(value, pos = pos, outline = outline, box_width = box_width,
            col = col, border = border, lwd = lwd, lty = lty, cex = cex, pch = pch, pt.col = pt.col)
    }
}

# == title
# Draw violin plots
#
# == param
# -value A numeric vector, a matrix or a list. If it is a matrix, boxplots are made by columns.
# -pos Positions of the boxes.
# -violin_width Width of violins.
# -col Filled color of boxes.
# -border Color for the border as well as the quantile lines.
# -lwd Line width.
# -lty Line style
# -show_quantile Whether to show the quantile lines.
# -cex Point size.
# -pch Point type.
# -pt.col Point color
# -max_density The maximal density value across several violins. It is used to compare between violins.
# -sector.index Index of sector.
# -track.index Index of track.
#
# == example
# \donttest{
# circos.initialize(letters[1:4], xlim = c(0, 10))
# circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
#     for(pos in seq(0.5, 9.5, by = 1)) {
#         value = runif(10)
#         circos.violin(value, pos)
#     }
# })
# circos.clear()
#
# circos.initialize(letters[1:4], xlim = c(0, 10))
# circos.track(ylim = c(0, 1), panel.fun = function(x, y) {
#     value = replicate(runif(10), n = 10, simplify = FALSE)
#     circos.violin(value, 1:10 - 0.5, col = 1:10)
# })
# circos.clear()
# }
circos.violin = function(value, pos, violin_width = 0.8, 
    col = NA, border = "black", lwd = par("lwd"), lty = par("lty"),
    show_quantile = TRUE, pt.col = par("col"), cex = par("cex"), pch = 16,
    max_density = NULL, sector.index = get.current.sector.index(),
    track.index = get.current.track.index()) {

    os = get.current.sector.index()
    ot = get.current.track.index()
    set.current.cell(sector.index, track.index)
    on.exit(set.current.cell(os, ot))

    single_violin = function(density, pos, violin_width = 0.8, 
        col = NA, border = "black", lwd = par("lwd"), lty = par("lty"),
        show_quantile = TRUE, pt.col = par("col"), cex = par("cex"), pch = 16,
        max_d = max(density$y), value = NULL) {

        y = density$x
        x = density$y

        x = x/max_d * (violin_width/2)
        y = c(y, rev(y))
        x = c(-x + pos, rev(x + pos))
        box_stat = boxplot(value, plot = FALSE)$stat

        circos.polygon(x, y, border = border, col = col, lwd = lwd, lty = lty)
        if(show_quantile) {
            circos.lines(c(pos, pos), box_stat[1:2, 1])
            circos.lines(x = c(pos, pos), y = box_stat[4:5, 1])
            circos.points(pos, box_stat[3, 1], cex = cex, col = pt.col, pch = pch)
        }
    }

    if(is.matrix(value)) {
        value = as.data.frame(value)   
    } 

    if(is.list(value)) {
        n = length(value)
        if(length(pos) != n) {
            stop_wrap("Length of `pos` should be same as number of violins.")
        }

        if(length(col) == 1) col = rep(col, n)
        if(length(border) == 1) border = rep(border, n)
        if(length(lwd) == 1) lwd = rep(lwd, n)
        if(length(lty) == 1) lty = rep(lty, n)
        if(length(cex) == 1) cex = rep(cex, n)
        if(length(pch) == 1) pch = rep(pch, n)
        if(length(pt.col) == 1) pt.col = rep(pt.col, n)
        if(length(violin_width) == 1) violin_width = rep(violin_width, length(value))

        density_list = lapply(value, density, na.rm = TRUE)
        
        for(i in seq_along(density_list)) {
            density = density_list[[i]]
            l = density$x >= min(value[[i]], na.rm = TRUE) & density$x <= max(value[[i]], na.rm = TRUE); l[is.na(l)] = FALSE
            density$x = density$x[l]
            density$y = density$y[l]
            density_list[[i]] = density
        }

        max_d = max(sapply(density_list, function(d) max(d$y)))
        if(!is.null(max_density)) max_d = max_density

        for(i in 1:n) {
            single_violin(density_list[[i]], pos = pos[i], violin_width = violin_width,
                col = col[i], border = border[i], lwd = lwd[i], lty = lty[i],
                show_quantile = show_quantile, pt.col = pt.col[i], cex = cex[i], pch = pch[i],
                max_d = max_d, value = value[[i]])
        }
    } else if(is.atomic(value)) {
        density = density(value, na.rm = TRUE)
        l = density$x >= min(value, na.rm = TRUE) & density$x <= max(value, na.rm = TRUE); l[is.na(l)] = FALSE
        density$x = density$x[l]
        density$y = density$y[l]
        max_d = max(density$y)
        if(!is.null(max_density)) max_d = max_density

        single_violin(density, pos = pos, violin_width = violin_width, 
            col = col, border = border, lwd = lwd, lty = lty,
            show_quantile = show_quantile, pt.col = pt.col, cex = cex, pch = pch,
            max_d = max(density$y), value = value)
    }
}




get_bezier_points = function(x1, y1, x2, y2, xlim, ylim) {

    x1 = (x1 - xlim[1])/(xlim[2] - xlim[1])
    x2 = (x2 - xlim[1])/(xlim[2] - xlim[1])
    y1 = (y1 - ylim[1])/(ylim[2] - ylim[1])
    y2 = (y2 - ylim[1])/(ylim[2] - ylim[1])

    r = 0.6 - abs(x2 - x1)/2
    p = cbind(c(0, 0, 1, 1), c(0, r, r, 1))
    pt = bezier::bezier(t = seq(0, 1, length = 50), p = p)

    wx = x2 - x1
    x = pt[, 1] * wx + x1

    wy = y2 - y1
    y = pt[, 2]*wy + y1

    x = x*(xlim[2] - xlim[1]) + xlim[1]
    y = y*(ylim[2] - ylim[1]) + ylim[1]

    data.frame(x = x, y = y)
}

# == title
# Draw connecting lines/ribons between two sets of points
#
# == param
# -x0 x coordinates for point set 1. The value can also be a two-column matrix.
# -y0 y coordinates for point set 1.
# -x1 x coordinates for point set 2. The value can also be a two-column matrix.
# -y1 y coordinates for point set 2.
# -sector.index Index for the sector.
# -track.index  Index for the track.
# -type Which type of connections. Values can be "normal", "segments" and "bezier".
# -segments.ratio When ``type`` is set to ``segments``, each connecting line is segmented into three parts.
#        This argument controls the length of the three parts of sub-segments.
# -col Color of the segments.
# -border Border color of the links.
# -lwd Line width of the segments.
# -lty Line type of the segments.
# -... Other arguments.
#
# == example
# \donttest{
# circos.initialize(c("a"), xlim = c(0, 1))
# circos.track(ylim = c(0, 1), track.height = 0.7, bg.border = NA, 
#     panel.fun = function(x, y) {
#     circos.lines(CELL_META$cell.xlim, rep(CELL_META$cell.ylim[1], 2), col = "#CCCCCC")
#     circos.lines(CELL_META$cell.xlim, rep(CELL_META$cell.ylim[2], 2), col = "#CCCCCC")
#     x0 = runif(100)
#     x1 = runif(100)
#    
#     circos.connect(x0, 0, x1, 1, 
#         type = "normal", border = NA,
#         col = rand_color(100, luminosity = "bright", transparency = 0.75))
# })
#
# circos.initialize(c("a"), xlim = c(0, 1))
# circos.track(ylim = c(0, 1), track.height = 0.7, bg.border = NA, 
#     panel.fun = function(x, y) {
#     circos.lines(CELL_META$cell.xlim, rep(CELL_META$cell.ylim[1], 2), col = "#CCCCCC")
#     circos.lines(CELL_META$cell.xlim, rep(CELL_META$cell.ylim[2], 2), col = "#CCCCCC")
#     x0 = runif(100)
#     x1 = runif(100)
#    
#     circos.connect(x0, 0, x1, 1, 
#         type = "bezier", border = NA,
#         col = rand_color(100, luminosity = "bright", transparency = 0.75))
# })
#
# circos.initialize(c("a"), xlim = c(0, 1))
# circos.track(ylim = c(0, 1), track.height = 0.7, bg.border = NA, 
#     panel.fun = function(x, y) {
#     circos.lines(CELL_META$cell.xlim, rep(CELL_META$cell.ylim[1], 2), col = "#CCCCCC")
#     circos.lines(CELL_META$cell.xlim, rep(CELL_META$cell.ylim[2], 2), col = "#CCCCCC")
#     x0 = sort(runif(200))
#     x0 = matrix(x0, ncol = 2, byrow = TRUE)
#     x1 = sort(runif(200))
#     x1 = matrix(x1, ncol = 2, byrow = TRUE)
#
#     circos.connect(x0, 0, x1, 1, 
#         type = "normal", border = NA,
#         col = rand_color(100, luminosity = "bright", transparency = 0.5))
# })
#
# circos.initialize(c("a"), xlim = c(0, 1))
# circos.track(ylim = c(0, 1), track.height = 0.7, bg.border = NA, 
#     panel.fun = function(x, y) {
#     circos.lines(CELL_META$cell.xlim, rep(CELL_META$cell.ylim[1], 2), col = "#CCCCCC")
#     circos.lines(CELL_META$cell.xlim, rep(CELL_META$cell.ylim[2], 2), col = "#CCCCCC")
#     x0 = sort(runif(500))
#     x0 = matrix(x0, ncol = 2, byrow = TRUE)
#     x0 = x0[sample(nrow(x0), nrow(x0)), ]
#     x1 = sort(runif(500))
#     x1 = matrix(x1, ncol = 2, byrow = TRUE)
#     x1 = x1[sample(nrow(x1), nrow(x1)), ]
#
#     l = abs(x0[, 1] - x1[, 1]) < 0.5
#
#     circos.connect(x0[l ,], 0, x1[l, ], 1, 
#         type = "bezier", border = NA,
#         col = rand_color(sum(l), luminosity = "bright", transparency = 0.5))
# })
# }
circos.connect = function(x0, y0, x1, y1,
    sector.index = get.current.sector.index(),
    track.index = get.current.track.index(),
    type = c("normal", "segments", "bezier"),
    segments.ratio = c(1, 1, 1),
    col = par("col"),
    border = "black",
    lwd = par("lwd"),
    lty = par("lty"),
    ...) {

    type = match.arg(type)[1]

    if(type == "bezier") {
        if(!requireNamespace("bezier")) {
            stop_wrap("You need to install 'bezier' package from CRAN.")
        }
    }

    if(is.null(nrow(x0))) {
        n1 = length(x0)
    } else {
        x0 = x0[, 1:2]
        n1 = nrow(x0)
    }
    n2 = length(y0)
    if(is.null(nrow(x1))) {
        n3 = length(x1)
    } else {
        x1 = x1[, 1:2]
        n3 = nrow(x1)
    }
    n4 = length(y1)
    n = max(c(n1, n2, n3, n4))
    if(n1 == 1) { 
        if(is.null(nrow(x0))) {
            x0 = rep(x0, n)
        } else {
            x0 = matrix(rep(x0, each = n), ncol = 2)
        }
        n1 = n
    }
    if(n2 == 1) { y0 = rep(y0, n); n2 = n }
    if(n3 == 1) { 
        if(is.null(nrow(x1))) {
            x1 = rep(x1, n)
        } else {
            x1 = matrix(rep(x1, each = n), ncol = 2)
        }
        n3 = n
    }
    if(n4 == 1) { y1 = rep(y1, n); n4 = n }

    if(! (n1 == n2 && n2 == n3 && n3 == n4) ) {
        stop_wrap("x0, y0, x1, y1 should have same length.")
    }

    if(!is.matrix(x0)) x0 = matrix(x0, ncol = 1)
    if(!is.matrix(x1)) x1 = matrix(x1, ncol = 1)

    if(length(col) == 1) col = rep(col, n)
    if(length(border) == 1) border = rep(border, n)
    if(length(lwd) == 1) lwd = rep(lwd, n)
    if(length(lty) == 1) lty = rep(lty, n)


    if(ncol(x0) == 1 && ncol(x1) == 1) {
        if(type == "normal") {
            circos.segments(x0, y0, x1, y1, sector.index = sector.index, track.index = track.index,
                col = col, lwd = lwd, lty = lty, ...)
        } else if(type == "segments") {
            if(length(segments.ratio) == 1) segments.ratio = rep(segments.ratio, 3)
            segments.ratio = segments.ratio[1:3]
            segments.ratio = segments.ratio/sum(segments.ratio)
            for(i in 1:n) {
                w = x1[i] - x0[i]
                h = y1[i] - y0[i]
                circos.segments(x0[i], y0[i], x0[i], y0[i] + h*segments.ratio[1], col = col[i], lwd = lwd[i], lty = lty[i], straight = TRUE, 
                    sector.index = sector.index, track.index = track.index)
                circos.segments(x0[i], y0[i]+h*segments.ratio[1], x1[i], y0[i] + h*sum(segments.ratio[1:2]), col = col[i], lwd = lwd[i], lty = lty[i],
                    sector.index = sector.index, track.index = track.index)
                circos.segments(x1[i], y0[i]+h*sum(segments.ratio[1:2]), x1[i], y1[i], col = col[i], lwd = lwd[i], lty = lty[i], straight = TRUE,
                    sector.index = sector.index, track.index = track.index)
            }
        } else if(type == "bezier") {
            for(i in 1:n) {
                pt = get_bezier_points(x0[i], y0[i], x1[i], y1[i], 
                    xlim = get.cell.meta.data("xlim", sector.index = sector.index, track.index = track.index),
                    ylim = get.cell.meta.data("ylim", sector.index = sector.index, track.index = track.index)
                )
                circos.lines(pt[, 1], pt[, 2], col = col[i], lwd = lwd[i], lty = lty[i],
                    sector.index = sector.index, track.index = track.index)
            }
        }
    } else {
        if(type == "segments") {
            stop_wrap("'segments' is only allowed for connections as lines where `x0` and `x1` are both set as vectors.")
        }

        if(type == "normal") {
            for(i in 1:n) {
                if(ncol(x0) == 2 && ncol(x1) == 2) {
                    circos.polygon(c(x0[i, 1], x0[i, 2], x1[i, 2], x1[i, 1], x0[i, 1]),
                                   c(y0[i], y0[i], y1[i], y1[i], y0[i]),
                                   col = col[i], border = border[i], lwd = lwd[i], lty = lty[i], 
                                   sector.index = sector.index, track.index = track.index)
                } else if(ncol(x0) == 2 && ncol(x1) == 1) {
                    circos.polygon(c(x0[i, 1], x0[i, 2], x1[i, 1], x0[i, 1]),
                                   c(y0[i], y0[i], y1[i], y0[i]),
                                   col = col[i], border = border[i], lwd = lwd[i], lty = lty[i], 
                                   sector.index = sector.index, track.index = track.index)
                } else if(ncol(x0) == 2 && ncol(x1) == 2) {
                    circos.polygon(c(x0[i, 1], x1[i, 2], x1[i, 1], x0[i, 1]),
                                   c(y0[i], y1[i], y1[i], y0[i]),
                                   col = col[i], border = border[i], lwd = lwd[i], lty = lty[i], 
                                   sector.index = sector.index, track.index = track.index)
                } 
            }
        } else if(type == "bezier") {
            for(i in 1:n) {
                if(ncol(x0) == 2 && ncol(x1) == 2) {
                    pt1 = get_bezier_points(x0[i, 2], y0[i], x1[i, 2], y1[i], 
                        xlim = get.cell.meta.data("xlim", sector.index = sector.index, track.index = track.index),
                        ylim = get.cell.meta.data("ylim", sector.index = sector.index, track.index = track.index)
                    )
                    pt2 = get_bezier_points(x1[i, 1], y1[i], x0[i, 1], y0[i], 
                        xlim = get.cell.meta.data("xlim", sector.index = sector.index, track.index = track.index),
                        ylim = get.cell.meta.data("ylim", sector.index = sector.index, track.index = track.index)
                    )
                    circos.polygon(c(pt1[, 1], pt2[, 1], pt1[1, 1]),
                                   c(pt1[, 2], pt2[, 2], pt1[1, 2]),
                                   col = col[i], border = border[i], lwd = lwd[i], lty = lty[i], 
                                   sector.index = sector.index, track.index = track.index)
                } else if(ncol(x0) == 2 && ncol(x1) == 1) {
                    pt1 = get_bezier_points(x0[i, 2], y0[i], x1[i, 1], y1[i], 
                        xlim = get.cell.meta.data("xlim", sector.index = sector.index, track.index = track.index),
                        ylim = get.cell.meta.data("ylim", sector.index = sector.index, track.index = track.index)
                    )
                    pt2 = get_bezier_points(x1[i, 1], y1[i], x0[i, 1], y0[i], 
                        xlim = get.cell.meta.data("xlim", sector.index = sector.index, track.index = track.index),
                        ylim = get.cell.meta.data("ylim", sector.index = sector.index, track.index = track.index)
                    )
                    circos.polygon(c(pt1[, 1], pt2[, 1], pt1[1, 1]),
                                   c(pt1[, 2], pt2[, 2], pt1[1, 2]),
                                   col = col[i], border = border[i], lwd = lwd[i], lty = lty[i], 
                                   sector.index = sector.index, track.index = track.index)
                } else if(ncol(x0) == 2 && ncol(x1) == 2) {
                    pt1 = get_bezier_points(x0[i, 1], y0[i], x1[i, 2], y1[i], 
                        xlim = get.cell.meta.data("xlim", sector.index = sector.index, track.index = track.index),
                        ylim = get.cell.meta.data("ylim", sector.index = sector.index, track.index = track.index)
                    )
                    pt2 = get_bezier_points(x1[i, 1], y1[i], x0[i, 1], y0[i], 
                        xlim = get.cell.meta.data("xlim", sector.index = sector.index, track.index = track.index),
                        ylim = get.cell.meta.data("ylim", sector.index = sector.index, track.index = track.index)
                    )
                    circos.polygon(c(pt1[, 1], pt2[, 1], pt1[1, 1]),
                                   c(pt1[, 2], pt2[, 2], pt1[1, 2]),
                                   col = col[i], border = border[i], lwd = lwd[i], lty = lty[i], 
                                   sector.index = sector.index, track.index = track.index)
                } 
            }
        }
    }
}


# == title
# Add a label track
#
# == param
# -sectors A vector of sector names.
# -x Positions of the labels.
# -labels A vector of labels.
# -facing Facing of the labels. The value can only be ``"clockwise"`` or ``"reverse.clockwise"``.
# -niceFacing Whether automatically adjust the facing of the labels.
# -col Color for the labels.
# -cex Size of the labels.
# -font Font of the labels.
# -padding Padding of the labels, the value is the ratio to the height of the label.
# -connection_height Height of the connection track.
# -line_col Color for the connection lines.
# -line_lwd Line width for the connection lines.
# -line_lty Line type for the connectioin lines.
# -labels_height Height of the labels track.
# -side Side of the labels track, is it in the inside of the track where the regions are marked?
# -labels.side Same as ``side``. It will replace ``side`` in the future versions.
# -track.margin Bottom and top margins.
#
# == details
# This function creates two tracks, one for the connection lines and one for the labels.
#
# If two labels are too close and overlap, this function automatically adjusts the positions of neighouring labels.
#
# == example
# circos.initialize(sectors = letters[1:8], xlim = c(0, 1))
# circos.track(ylim = c(0, 1))
# circos.labels(c("a", "a", "b", "b"), x = c(0.1, 0.12, 0.4, 0.6), labels = c(0.1, 0.12, 0.4, 0.6))

# circos.initialize(sectors = letters[1:8], xlim = c(0, 1))
# circos.labels(c("a", "a", "b", "b"), x = c(0.1, 0.12, 0.4, 0.6), labels = c(0.1, 0.12, 0.4, 0.6),
#     side = "outside")
# circos.track(ylim = c(0, 1))
# circos.clear()
circos.labels = function(
    sectors, x, labels, 
    facing = "clockwise", 
    niceFacing = TRUE,
    col = par("col"), 
    cex = 0.8, 
    font = par("font"), 
    padding = 0.4,
    connection_height = mm_h(5), 
    line_col = par("col"), 
    line_lwd = par("lwd"), 
    line_lty = par("lty"),
    labels_height = min(c(cm_h(1.5), max(strwidth(labels, cex = cex, font = font)))),
    side = c("inside", "outside"), 
    labels.side = side,
    track.margin = circos.par("track.margin")) {

    if(missing(sectors) && missing(x)) {
        env = circos.par("__tempenv__")
        if(identical(env$circos.heatmap.initialized, TRUE)) {
            subset = unlist(lapply(env$sector.meta.data, function(x) x$subset))
            x = unlist(lapply(env$sector.meta.data, function(x) x$cell_middle))
            labels = labels[subset]
            sectors = rep(names(env$sector.meta.data), times = sapply(env$sector.meta.data, function(x) length(x$subset)))
        }
    }
    bed = data.frame(sectors, x, x)

    circos.genomicLabels(bed, labels = labels, facing = facing, niceFacing = niceFacing, col = col, cex = cex,
        font = font, padding = padding, connection_height = connection_height, line_col = line_col,
        line_lwd = line_lwd, line_lty = line_lty, labels_height = labels_height, side = side, labels.side = labels.side,
        track.margin = track.margin)
}


# == title
# Add a stacked text track
#
# == param
# -sectors A vector of sector names.
# -x  A vector of x-coordinates.
# -text  A vector of texts.
# -bg.border  Background color.
# -bg.col  Colors for borders.
# -niceFacing Current not supported.
# -side Side of the track.
# -col Text colors.
# -font Text fontfaces.
# -cex Font sizes.
# -family Font families.
#
# == details
# The height of the track is not fixed, so you may need to manually adjust the track height.
#
# == example
# \dontrun{
# circos.par$circle.margin = 0.5
# circos.par$cell.padding = rep(0, 4)
# circos.par$track.margin = rep(0, 2)
#
# circos.initialize(sectors = letters[1:4], xlim = c(0, 1))
#
# sectors = sample(letters[1:4], 40, replace = TRUE)
# x = runif(40)
# text = sapply(letters[sample(26, 40, replace = TRUE)], function(x) strrep(x, sample(4:6, 1)))
# circos.stackedText(sectors, x, text, bg.col = "#EEEEEE")
#
# circos.track(ylim = c(0, 1))
# circos.clear()
#
# #### genome plot
# circos.par$track.margin = rep(0, 2)
# circos.par$cell.padding = rep(0, 4)
#
# circos.initializeWithIdeogram(plotType = NULL)
# bed = generateRandomBed(50)
# text = sapply(
#     letters[sample(26, nrow(bed), replace = TRUE)], 
#     function(x) strrep(x, sample(4:6, 1))
# )
# bed$text = text
#
# circos.stackedText(bed[, 1], bed[, 2], bed$text, cex = 0.7)
# circos.genomicIdeogram()
# circos.clear()
#
# }
circos.stackedText = function(sectors, x, text, 
    col = par("col"), font = par("font"), cex = par("cex"), family = par("family"),
    bg.border = "black", bg.col = "#FF8080",
    niceFacing = FALSE, 
    side = c("outside", "inside")) {

    side = match.arg(side)[1]

    if(niceFacing) {
        stop_wrap("Current `niceFacing` is not supported.")
    }

    n = length(x)

    if(length(bg.border) == 1) {
        bg.border = rep(bg.border, n)
    }
    if(length(bg.col) == 1) {
        bg.col = rep(bg.col, n)
    }
    if(length(col) == 1) {
        col = rep(col, n)
    }
    if(length(font) == 1) {
        font = rep(font, n)
    }
    if(length(cex) == 1) {
        cex = rep(cex, n)
    }

    op = circos.par("points.overflow.warning")
    circos.par("points.overflow.warning" = FALSE)

    circos.track(ylim = c(0, 1), bg.border = NA)

    df = refer_to_one_sector(sectors, x)
    set.current.sector.index(df[1, 1])

    x = df$x
    text = text[df$order]

    tw = circos.strwidth(text, cex = cex, font = font, family = family)
    th = circos.strheight(text, cex = cex, font = font, family = family)
    ta = circos.strAscent(text, cex = cex, font = font, family = family)
    td = circos.strDescent(text, cex = cex, font = font, family = family)

    lt = stack_text(x, text, strwidth = circos.strwidth, strheight = circos.strheight, margin = 0.2,
        cex = cex, font = font, family = family)

    if(side == "outside") {
        y = mm_y(4.5)
    } else {
        y = 1 - mm_y(4.5)
    }

    for(i in seq_along(lt)) {
        ind = lt[[i]]
        if(side == "outside") {
            circos.segments(x[ind], 0, x[ind], y, col = bg.border)
            y = y + mm_y(0.5) + max(td[ind] + ta[ind]) + mm_y(0.5) + mm_y(0.5)
        } else {
            circos.segments(x[ind], 1, x[ind], y, col = bg.border)
            y = y - mm_y(0.5) - max(td[ind] + ta[ind]) - mm_y(0.5) - mm_y(0.5)
        }
        
    }

    if(side == "outside") {
        y = mm_y(4.5)
    } else {
        y = 1 - mm_y(4.5)
    }

    for(i in seq_along(lt)) {
        ind = lt[[i]]
        if(side == "outside") {
            tw = circos.strwidth(text, h = y, cex = cex, font = font, family = family)
            circos.rect(x[ind] - tw[ind]/2 - mm_x(0.5), 
                y, 
                x[ind] + tw[ind]/2 + mm_x(0.5), 
                y + mm_y(0.5) + td[ind] + ta[ind] + mm_y(0.5), 
                col = bg.col[ind], border = bg.border[ind])
            circos.text(x[ind], y + mm_y(0.5) + td[ind] + th[ind]/2, 
                text[ind], facing = "bending.inside", niceFacing = niceFacing, 
                col = col[ind], cex = cex[ind], font = font[ind], family = family)
            y = y + mm_y(0.5) + max(td[ind] + ta[ind]) + mm_y(0.5) + mm_y(0.5)
        } else {
            tw = circos.strwidth(text, h = y, cex = cex, font = font, family = family)
            circos.rect(x[ind] - tw[ind]/2 - mm_x(0.5), 
                y, 
                x[ind] + tw[ind]/2 + mm_x(0.5), 
                y - mm_y(0.5) - td[ind] - ta[ind] - mm_y(0.5), 
                col = bg.col[ind], border = bg.border[ind])
            circos.text(x[ind], y - mm_y(0.5) - (ta[ind] - th[ind]/2), 
                text[ind], facing = "bending.inside", niceFacing = niceFacing,
                col = col[ind], cex = cex[ind], font = font[ind], family = family)
            y = y - mm_y(0.5) - max(td[ind] + ta[ind]) - mm_y(0.5) - mm_y(0.5)
        }
    }

    circos.par("points.overflow.warning" = op)
}
jokergoo/circlize documentation built on Nov. 17, 2023, 11:32 a.m.