# == title
# Create plotting regions for a whole track
#
# == param
# -factors A `factor` or a character vector which represents categories of data, if it is ``NULL``,
# then it uses all sector index.
# -x Data on x-axis. It is only used if ``panel.fun`` is set.
# -y Data on y-axis
# -ylim Range of data on y-axis
# -force.ylim Whether to force all cells in the track to share the same ``ylim``. Normally,
# all cells on a same track should have same ``ylim``.
# -track.index Index for the track which is going to be created/updated. If the specified track has already
# been created, this function just updated corresponding track with new plot. If the specified track
# is ``NULL`` or has not been created, this function just creates it. Note the value for this
# argument should not exceed maximum track index plus 1.
# -track.height Height of the track. It is the percentage to the radius of the unit circles. The value can be set by `uh` to an absolute unit.
# If updating a track (with proper ``track.index`` value), this argument is ignored.
# -track.margin only affect current track
# -cell.padding only affect current track
# -bg.col Background color for the plotting regions. It can be vector which has the same length of sectors.
# -bg.border Color for the border of the plotting regions. It can be vector which has the same length of sectors.
# -bg.lty Line style for the border of the plotting regions. It can be vector which has the same length of sectors.
# -bg.lwd Line width for the border of the plotting regions. It can be vector which has the same length of sectors.
# -panel.fun Panel function to add graphics in each cell, see "details" section
# and vignette for explanation.
#
# == details
# This function tends to be a high-level plotting function, which means,
# you must first call this function to create plotting regions, then those
# low-level graphic function such as `circos.points`, `circos.lines` can be
# applied.
#
# Currently, all the cells that are created in a same track sharing same height, which means,
# there is no cell has larger height than others.
#
# Since ranges for values on x-axis has already been defined by `circos.initialize`, only
# ranges for values on y-axis should be specified in this function.
# There are two ways to identify the ranges for values on y-axes either by ``y``
# or ``ylim``. If ``y`` is set, it must has the same length as ``factors`` and the ``ylim`` for each cell is calculated
# from y values. Also, the ylim can be specified from ``ylim`` which can be a two-element vector or a matrix which
# has two columns and the number of rows is the same as the length of the levels of the factors.
#
# If there is no enough space for the new track or the new track overlaps with other tracks,
# there will be an error.
#
# If ``factors`` does not cover all sectors, the cells in remaining unselected
# sectors would also be created but without drawing anything. The ``ylim`` for these cells
# are the same as that in the last created cell.
#
# The function can also update a already-created track if the index for the track
# is specified. If updating an existed track, those parameters related to the position (such as track height and track margin)
# of the plotting region can not be changed.
#
# == Panel function
#
# ``panel.fun`` provides a convenient way to add graphics in each cell when initializing the
# tracks. The self-defined function needs two arguments: ``x`` and ``y`` which correspond to the data points
# in the current cell. When ``factors``, ``x``, and ``y`` are set in `circos.trackPlotRegion`, a subset of ``x``
# and ``y`` are split by ``factors`` and are sent to ``panel.fun`` in the "current" cell.
# `circos.trackPlotRegion` creates plotting regions one by one on the track and
# ``panel.fun`` adds graphics in the 'current' cell after the plotting region for a certain cell has been
# created.
#
# See vignette for examples of how to use this feature.
#
# == seealso
# http://jokergoo.github.io/circlize_book/book/circular-layout.html
circos.trackPlotRegion = function(factors = NULL, x = NULL, y = NULL, ylim = NULL,
force.ylim = TRUE, track.index = NULL,
track.height = circos.par("track.height"),
track.margin = circos.par("track.margin"),
cell.padding = circos.par("cell.padding"),
bg.col = NA, bg.border = "black", bg.lty = par("lty"), bg.lwd = par("lwd"),
panel.fun = function(x, y) {NULL}) {
if(!is.circos.initialized()) {
stop("Your circular plot has not been initialized yet!")
}
o.track.margin = circos.par("track.margin")
o.cell.padding = circos.par("cell.padding")
circos.par(track.margin = track.margin)
circos.par(cell.padding = cell.padding)
# if there is no factors, default are all the available factors
if(is.null(factors)) {
factors = get.all.sector.index()
factors = factor(factors, levels = factors)
}
# although ``x`` and ``y`` are not necessary, but once they are set, they must
# have same length as ``factors``
if(!is.null(y) && length(y) != length(factors) ||
!is.null(x) && length(x) != length(factors)) {
stop("Length of data and length of factors differ.")
}
# need to be a factor
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 = ", "), ".")
}
tracks = get.all.track.index()
last.track.index = ifelse(length(tracks), tracks[length(tracks)], 0)
flag_createNewTrack = 0
if(is.null(track.index)) {
# new track should inside the most recently created track
set.current.track.index(last.track.index + 1)
track.index = get.current.track.index()
flag_createNewTrack = 1
} else if(track.index == last.track.index + 1) {
# if the track.index is next to the most recently created track
set.current.track.index(track.index)
track.index = get.current.track.index()
flag_createNewTrack = 1
} else {
if(track.index > last.track.index + 1) {
stop("Wrong track index: it should be no more than ", last.track.index + 1, ".")
}
# update an existed track
if(track.index <= tracks[length(tracks)]) {
# ignore track.height from args
track.height = get.cell.meta.data("track.height", sector.index = factors[1], track.index = track.index)
# ignore track.margin
circos.par("track.margin" = get.cell.meta.data("track.margin", sector.index = factors[1], track.index = track.index))
circos.par("cell.padding" = get.cell.meta.data("cell.padding", sector.index = factors[1], track.index = track.index))
}
if(is.null(ylim) && is.null(y)) {
for(sid in get.all.sector.index()) {
ylim = rbind(ylim, get.cell.meta.data("ylim", sector.index = sid, track.index = track.index))
}
}
set.current.track.index(track.index)
}
le = levels(factors)
nlevel = length(le)
bg.col = recycle.with.levels(bg.col, le)
bg.border = recycle.with.levels(bg.border, le)
bg.lty = recycle.with.levels(bg.lty, le)
bg.lwd = recycle.with.levels(bg.lwd, le)
# whether to force ylim for all cells in a track same
if(is.null(ylim)) {
if(is.null(y)) {
stop("You have to specify either `y` or `ylim`.")
}
if(force.ylim) {
y.range = range(y)
y.range = matrix(rep(y.range, nlevel), ncol = 2, byrow = TRUE)
} else {
y.range = tapply(y, factors, range)
y.range = matrix(unlist(y.range), ncol = 2, byrow = TRUE)
}
}
if(flag_createNewTrack) {
if(track.index == 1) {
track.start = 1 - circos.par("track.margin")[2]
} else {
track.start = get.cell.meta.data("cell.bottom.radius", track.index = track.index - 1) -
get.cell.meta.data("track.margin", track.index = track.index - 1)[1] -
circos.par("track.margin")[2]
}
} else {
track.start = get.cell.meta.data("cell.top.radius", track.index = track.index)
}
# check whether there is enough space for the new track and whether the new space
# overlap with other tracks. Only for creatation mode.
if(flag_createNewTrack) {
check.track.position(track.index, track.start, track.height)
}
# if `ylim` is specified
if(!is.null(ylim)) {
if(is.vector(ylim) && length(ylim) == 2) {
ylim = matrix(rep(ylim, length(le)), ncol = 2, byrow = TRUE)
} else if(is.matrix(ylim) && ncol(ylim) == 2 && nrow(ylim) == length(le)) {
} else {
stop("Wrong `ylim` format.")
}
}
# now for each factor, create plotting region
for(i in seq_along(le)) {
# `ylim` is prior to `y`
if(is.null(ylim)) {
ylim2 = y.range[i, ]
} else {
ylim2 = ylim[i, ]
}
# create plotting region for single cell
circos.createPlotRegion(track.start = track.start,
track.height = track.height, sector.index = le[i],
track.index = track.index,
ylim = ylim2, bg.col = bg.col[i],
bg.border = bg.border[i], bg.lty = bg.lty[i], bg.lwd = bg.lwd[i])
l = factors == le[i]
if(! is.null(panel.fun)) {
if(is.null(x)) {
nx = NULL
} else {
nx = x[l]
}
if(is.null(y)) {
ny = NULL
} else {
ny = y[l]
}
panel.fun(nx, ny)
}
}
# and those sectors not include in factors
le2 = setdiff(get.all.sector.index(), levels(factors))
if(length(le2)) {
for(i in seq_along(le2)) {
# ylim is the most recent ``ylim2``
circos.createPlotRegion(track.start = track.start,
track.height = track.height, sector.index = le2[i],
track.index = track.index,
ylim = ylim2, bg.col = NA,
bg.border = NA)
}
}
circos.par(track.margin = o.track.margin)
circos.par(cell.padding = o.cell.padding)
return(invisible(NULL))
}
# == title
# Create plotting regions for a whole track
#
# == param
# -... pass to `circos.trackPlotRegion`
#
# == details
# Shortcut function of `circos.trackPlotRegion`.
#
circos.track = function(...) {
circos.trackPlotRegion(...)
}
# == title
# Update the plotting region in an existed cell
#
# == param
# -sector.index Index for the sector
# -track.index Index for the track
# -bg.col Background color for the plotting region
# -bg.border Color for the border of the plotting region
# -bg.lty Line style for the border of the plotting region
# -bg.lwd Line width for the border of the plotting region
#
# == details
# You can update an existed cell by this function by erasing all the graphics.
# But the ``xlim`` and ``ylim`` inside the cell still remain unchanged.
#
# Note if you use `circos.track` to update an already created track,
# you can re-define ``ylim`` in these cells.
circos.updatePlotRegion = function(sector.index = get.cell.meta.data("sector.index"),
track.index = get.cell.meta.data("track.index"),
bg.col = NA, bg.border = "black", bg.lty = par("lty"), bg.lwd = par("lwd")) {
if(!has.cell(sector.index, track.index)) {
stop("You can only update an existed cell.")
}
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)
set.current.sector.index(sector.index)
set.current.track.index(track.index)
# cover the exsited region by fill with white
lwd = get.cell.meta.data("bg.lwd", sector.index = sector.index, track.index = track.index)
circos.rect(cell.xlim[1], cell.ylim[1], cell.xlim[2], cell.ylim[2],
col = "white", border = "white", lty = 1, lwd = lwd)
circos.rect(cell.xlim[1], cell.ylim[1], cell.xlim[2], cell.ylim[2],
col = bg.col, border = bg.border, lty = bg.lty, lwd = bg.lwd)
return(invisible(NULL))
}
# == title
# Create plotting regions for a whole track
#
# == param
# -... pass to `circos.updatePlotRegion`
#
# == details
# shortcut function of `circos.updatePlotRegion`.
#
circos.update = function(...) {
circos.updatePlotRegion(...)
}
# internal, so we do not need to check arguments
circos.createPlotRegion = function(track.start, track.height = circos.par("track.height"),
sector.index = get.cell.meta.data("sector.index"), track.index = get.cell.meta.data("track.index"), ylim,
bg.col = NA, bg.border = "black", bg.lty = par("lty"), bg.lwd = par("lwd")) {
# we do not have such meta for the cell, so we need to calculate them
sector.data = get.sector.data(sector.index)
cell.xlim = c(sector.data["min.value"], sector.data["max.value"])
names(cell.xlim) = NULL
cell.padding = circos.par("cell.padding")
xlim = c(sector.data["min.data"], sector.data["max.data"])
if(cell.padding[1] + cell.padding[3] >= track.height) {
stop("Summation of cell padding on y-direction are larger than the height of the cells.")
}
if(ylim[2] == ylim[1]) {
stop("range of `ylim` should be different.")
}
yl = numeric(2)
yl[1] = ylim[1] - (ylim[2] - ylim[1])*cell.padding[1] / track.height
yl[2] = ylim[2] + (ylim[2] - ylim[1])*cell.padding[3] / track.height
set.cell.data(sector.index = sector.index,
track.index = track.index,
xlim = xlim,
ylim = ylim,
cell.xlim = cell.xlim,
cell.ylim = yl,
track.start = track.start,
track.height = track.height,
track.margin = circos.par("track.margin"),
cell.padding = circos.par("cell.padding"),
bg.col = bg.col,
bg.border = bg.border,
bg.lty = bg.lty,
bg.lwd = bg.lwd)
set.current.sector.index(sector.index)
# The plotting region is a rectangle
cell.ylim = yl
circos.rect(cell.xlim[1], cell.ylim[1], cell.xlim[2], cell.ylim[2], sector.index = sector.index, track.index = track.index,
col = bg.col, border = bg.border, lty = bg.lty, lwd = bg.lwd)
return(invisible(NULL))
}
# == 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.
circos.points = function(x, y, sector.index = get.cell.meta.data("sector.index"),
track.index = get.cell.meta.data("track.index"),
pch = par("pch"), col = par("col"), cex = par("cex"), bg = par("bg")) {
if(!has.cell(sector.index, track.index)) {
stop("'circos.points' can only be used after the plotting region has been created")
}
if(length(x) != length(y)) {
stop("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
# -factors A `factor` or a character vector which represents the categories of data
# -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`.
#
circos.trackPoints = function(factors = NULL, x, y, track.index = get.cell.meta.data("track.index"),
pch = par("pch"), col = par("col"), cex = par("cex"), bg = par("bg")) {
# basic check here
if(length(x) != length(factors) || length(y) != length(factors)) {
stop("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``.
circos.lines = function(x, y, sector.index = get.cell.meta.data("sector.index"),
track.index = get.cell.meta.data("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("`area.baseline` is deprecated, please use `baseline` instead.")
}
if(length(x) != length(y)) {
stop("Length of x and y differ.")
}
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("'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
# -factors A `factor` or a character vector which represents the categories of data
# -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(factors, x, y, track.index = get.cell.meta.data("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")) {
if(!is.null(area.baseline)) {
baseline = area.baseline
warning("`area.baseline` is deprecated, please use `baseline` instead.")
}
# basic check here
if(length(x) != length(factors) || length(y) != length(factors)) {
stop("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
# -... 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.
circos.rect = function(xleft, ybottom, xright, ytop,
sector.index = get.cell.meta.data("sector.index"),
track.index = get.cell.meta.data("track.index"), ...) {
# 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("'circos.rect' can only be used after the plotting region been created.")
}
if(! (length(xleft) == length(ybottom) && length(ybottom) == length(xright) && length(xright) == length(ytop)) ) {
stop("xleft, ybottom, xright, ytop should have same length.")
}
# # 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)
# }
x = unlist(lapply(seq_along(xleft), function(i) c(xleft[i], xleft[i], xright[i], xright[i], xleft[i], NA)))
y = unlist(lapply(seq_along(ybottom), function(i) c(ybottom[i], ytop[i], ytop[i], ybottom[i], ybottom[i], NA)))
x = x[-length(x)]
y = y[-length(y)]
circos.polygon(x, y, sector.index = sector.index, track.index = track.index, ...)
return(invisible(NULL))
}
# == 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,
#
circos.polygon = function(x, y, sector.index = get.cell.meta.data("sector.index"),
track.index = get.cell.meta.data("track.index"), ...) {
if(!has.cell(sector.index, track.index)) {
stop("'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`
#
circos.segments = function(x0, y0, x1, y1, sector.index = get.cell.meta.data("sector.index"),
track.index = get.cell.meta.data("track.index"), straight = FALSE,
col = par("col"), lwd = par("lwd"), lty = par("lty"), ...) {
if(! (length(x0) == length(y0) && length(y0) == length(x1) && length(x1) == length(y1)) ) {
stop("x0, y0, x1, y1 should have same length.")
}
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("'circos.polygon' 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
# http://jokergoo.github.io/circlize_book/book/graphics.html#text
circos.text = function(x, y, labels, sector.index = get.cell.meta.data("sector.index"),
track.index = get.cell.meta.data("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(length(x) != length(y)) {
stop("Length of x and y differ.")
}
if(!has.cell(sector.index, track.index)) {
stop("'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("`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("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("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("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("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("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
# -factors A `factor` or a character vector which represents the categories of data
# -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(factors, x, y, labels, track.index = get.cell.meta.data("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")) {
# basic check here
if(length(x) != length(factors) || length(y) != length(factors)) {
stop("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 would be
# no minor 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.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.percentage not used. Length of the major ticks. It is the percentage to the height of the cell.
# -labels.away.percentage not used. The distance for the axis labels to the major ticks. It is the percentage to the height of the cell.
# -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.
# -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. The value can be a vector
# of length two which correspond to the first label and the last label.
#
# == details
# It can only draw axes on x-direction.
#
# == seealso
# `circos.yaxis` draws axes on y-direction.
circos.axis = function(h = "top", major.at = NULL, labels = TRUE, major.tick = TRUE,
sector.index = get.cell.meta.data("sector.index"),
track.index = get.cell.meta.data("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.percentage = 0.1, labels.away.percentage = major.tick.percentage/2,
major.tick.length = convert_y(1, "mm", sector.index, track.index),
lwd = par("lwd"), col = par("col"), labels.col = par("col"), labels.pos.adjust = TRUE) {
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("`labels.direction` is deprecated, please use `labels.facing` instead.")
}
direction = direction[1]
if(! direction %in% c("outside", "inside")) {
stop("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)
# 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)
if(n >= 1) {
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)
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)
}
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) && labels) {
add_axis_labels(major.at[l], rep(h, sum(l)) + (major.tick.length + convert_y(0.5, "mm", 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.logical(labels) && !labels) {
} else if(length(labels)) {
add_axis_labels(major.at[l], rep(h, sum(l)) + (major.tick.length + convert_y(0.5, "mm", 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`
#
circos.xaxis = function(...) {
circos.axis(...)
}
.default.major.by = function(sector.index = get.cell.meta.data("sector.index"),
track.index = get.cell.meta.data("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.
#
circos.yaxis = function(side = c("left", "right"), at = NULL, labels = TRUE, tick = TRUE,
sector.index = get.cell.meta.data("sector.index"),
track.index = get.cell.meta.data("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)
labels = at
}
ylim2 = ylim
# circos.lines(c(ifelse(at[1] >= ylim2[1], at[1], ylim2[1]),
# ifelse(at[length(at)] <= ylim2[2], at[length(at)], ylim2[2])),
# c(v, v), sector.index = sector.index, track.index = track.index, lwd = lwd)
# 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") {
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
# -factors Factors which represent the categories of data
# -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.
# -force.ylim Whether to force all cells in the track to share the same ``ylim``. Btw, ``ylim`` is calculated automatically.
# -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.
circos.trackHist = function(factors, x, track.height = circos.par("track.height"),
track.index = 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) {
# basic check here
if(length(x) != length(factors)) {
stop("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(factors = fa, y=yy, track.height = track.height,
track.index = track.index, 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(factors = 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
# Draw sectors or rings in a circle
#
# == param
# -start.degree start degree for the sector
# -end.degree end degree for the sector
# -rou1 Radius for one of the arc in the sector
# -rou2 Radius for the other arc in the sector
# -center Center of the circle
# -clock.wise The direction from ``start.degree`` to ``end.degree``
# -col Filled color
# -border Border color
# -lwd Line width
# -lty Line style
#
# == details
# If the interval between ``start`` and ``end`` (larger or equal to 360 or smaller or equal to -360)
# it would draw a full circle or ring. If ``rou2`` is set, it would draw part of a ring.
#
draw.sector = function(start.degree = 0, end.degree = 360, rou1 = 1, rou2 = NULL,
center = c(0, 0), clock.wise = TRUE, col = NA, border = "black", lwd = par("lwd"),
lty = par("lty")) {
is.circular = function(start.degree, end.degree) {
(end.degree - start.degree) %% 360 == 0 && (end.degree - start.degree) != 0
}
degree_diff = function(start, end, clock.wise = TRUE) {
if(is.circular(start, end)) {
360
} else {
start = start %% 360
end = end %% 360
if(clock.wise) (start - end) %% 360
else (end - start) %% 360
}
}
# from start to end
degree_seq = function(start, end, clock.wise = TRUE, ...) {
if(is.circular(start, end)) {
seq(0, 360, ...)
} else {
start = start %% 360
end = end %% 360
if(clock.wise) {
# make start is larger than end, but the difference is less than 360
if(start < end) start = start + 360
seq(start, end, ...)
} else {
if(start > end) start = start - 360
seq(start, end, ...)
}
}
}
d1 = NULL
# calculate the number of segments of the up arc
l1 = as.radian(degree_diff(start.degree, end.degree, clock.wise)) * rou1
ncut1 = l1/ (2*pi/circos.par("unit.circle.segments"))
ncut1 = floor(ncut1)
ncut1 = ifelse(ncut1 < 2, 2, ncut1)
# d1 is from the start.degree to end.degree
d1 = rbind(d1, cbind(degree_seq(start.degree, end.degree, clock.wise, length.out = ncut1), rep(rou1, ncut1)))
# d2 is from end.degree to start.degree
d2 = NULL
if(!is.null(rou2)) {
# calculate the number of segments of the bottom arc
l2 = as.radian(degree_diff(start.degree, end.degree, clock.wise)) * rou2
ncut2 = l2/ (2*pi/circos.par("unit.circle.segments"))
ncut2 = floor(ncut2)
ncut2 = ifelse(ncut2 < 2, 2, ncut2)
d2 = rbind(d2, cbind(degree_seq(end.degree, start.degree, !clock.wise, length.out = ncut2), rep(rou2, ncut2)))
}
if(is.null(rou2)) {
m1 = polar2Cartesian(d1)
if(is.circular(start.degree, end.degree)) { # it is a circle
m = m1
} else {
m = rbind(m1, c(0, 0))
}
# and shift to the center
m[, 1] = m[, 1] + center[1]
m[, 2] = m[, 2] + center[2]
polygon(m, col = col, border = border, lwd = lwd, lty = lty)
} else {
m1 = polar2Cartesian(d1)
m2 = polar2Cartesian(d2)
if(is.circular(start.degree, end.degree)) { # a ring
m = rbind(m1, m2[rev(seq_len(nrow(m2))), ,drop = FALSE])
m[, 1] = m[, 1] + center[1]
m[, 2] = m[, 2] + center[2]
polygon(m, col = col, border = NA, lwd = 0.1)
# two borders
#lines(m1[, 1]+center[1], m1[, 2]+center[2], col = "white", lwd = lwd, lty = 1)
#lines(m2[, 1]+center[1], m2[, 2]+center[2], col = "white", lwd = lwd, lty = 1)
lines(m1[, 1]+center[1], m1[, 2]+center[2], col = border, lwd = lwd, lty = lty)
lines(m2[, 1]+center[1], m2[, 2]+center[2], col = border, lwd = lwd, lty = lty)
} else {
m = rbind(m1, m2)
m[, 1] = m[, 1] + center[1]
m[, 2] = m[, 2] + center[2]
polygon(m, col = col, border = border, lwd = lwd, lty = lty)
}
}
return(invisible(NULL))
}
# == title
# Highlight sectors and tracks
#
# == param
# -sector.index A vector of sector index
# -track.index A vector of track index that you want to highlight
# -col Color for highlighting. Note the color should be semi-transparent.
# -border Border of the highlighted region
# -lwd Width of borders
# -lty Style of borders
# -padding Padding for the highlighted region. It should contain four values
# representing ratios of the width or height of the highlighted region
# -text text added in the highlight region, only support plotting one string at a time
# -text.vjust adjustment on 'vertical' (radical) direction. Besides to set it as numeric values,
# the value can also be a string contain absoute unit, e.g. "2.1mm", "-1 inche", but only
# "mm", "cm", "inches"/"inche" are allowed.
# -text.col color for the text
# -... pass to `circos.text`
#
# == details
# You can use `circos.info` to find out index for all sectors and all tracks.
#
# The function calls `draw.sector`.
highlight.sector = function(sector.index, track.index = get.all.track.index(),
col = "#FF000040", border = NA, lwd = par("lwd"), lty = par("lty"),
padding = c(0, 0, 0, 0), text = NULL, text.col = par("col"),
text.vjust = 0.5, ...) {
sectors = get.all.sector.index()
if(!all(sector.index %in% sectors)) {
stop("`chr` contains index that does not beling to available sectors.")
}
tracks = get.all.track.index()
if(!all(track.index %in% tracks)) {
stop("`track.index` contains index that does not belong to available tracks.")
}
y_offset = NULL
if(!is.numeric(text.vjust)) {
y_offset = parse_unit(text.vjust)
text.vjust = 0.5
}
# if all sectors are selected
if(length(setdiff(sectors, sector.index)) == 0) {
track.index = sort(unique(track.index))
ts = continuousIndexSegment(track.index)
for(i in seq_along(ts)) {
track.index.vector = ts[[i]]
start.degree = 0
end.degree = 360
rou1 = get.cell.meta.data("cell.top.radius", sectors[1], track.index.vector[1])
rou2 = get.cell.meta.data("cell.bottom.radius", sectors[1], track.index.vector[length(track.index.vector)])
d2 = rou1 - rou2
rou1 = rou1 + d2*padding[3]
rou2 = rou2 - d2*padding[1]
draw.sector(start.degree = start.degree, end.degree = end.degree, rou1 = rou1, rou2 = rou2, col = col, border = border, lwd = lwd, lty = lty)
if(!is.null(text)) {
# map to most recent cell
pos = reverse.circlize((start.degree + end.degree)/2 + ifelse(start.degree < end.degree, 180, 0), (rou1 + rou2)/2)
op_warning = circos.par("points.overflow.warning")
circos.par("points.overflow.warning" = FALSE)
if(is.null(y_offset)) {
circos.text(pos[1,1], pos[1,2], text, adj = c(0.5, text.vjust), col = text.col, ...)
} else {
circos.text(pos[1,1], pos[1,2] + uy(y_offset[[1]], y_offset[[2]]), text, adj = c(0.5, 0.5), col = text.col, ...)
}
circos.par(points.overflow.warning = op_warning)
}
}
} else {
sector.numeric.index = which(sectors %in% sector.index)
ss = continuousIndexSegment(sector.numeric.index, n = length(sectors), loop = TRUE)
track.index = sort(unique(track.index))
ts = continuousIndexSegment(track.index)
for(j in seq_along(ss)) {
sector.index.vector = sectors[ ss[[j]] ]
for(i in seq_along(ts)) {
track.index.vector = ts[[i]]
start.degree = get.cell.meta.data("cell.start.degree", sector.index.vector[1], track.index = 1)
end.degree = get.cell.meta.data("cell.end.degree", sector.index.vector[length(sector.index.vector)], track.index = 1)
rou1 = get.cell.meta.data("cell.top.radius", sector.index.vector[1], track.index.vector[1])
rou2 = get.cell.meta.data("cell.bottom.radius", sector.index.vector[1], track.index.vector[length(track.index.vector)])
d1 = end.degree - start.degree
d2 = rou1 - rou2
start.degree = start.degree - d1*padding[2]
end.degree = end.degree + d1*padding[4]
rou1 = rou1 + d2*padding[3]
rou2 = rou2 - d2*padding[1]
draw.sector(start.degree = start.degree, end.degree = end.degree, rou1 = rou1, rou2 = rou2, col = col, border = border, lwd = lwd, lty = lty)
if(!is.null(text)) {
# map to most recent cell
pos = reverse.circlize((start.degree + end.degree)/2 + ifelse(start.degree < end.degree, 180, 0), (rou1 + rou2)/2)
op_warning = circos.par("points.overflow.warning")
circos.par("points.overflow.warning" = FALSE)
if(is.null(y_offset)) {
circos.text(pos[1,1], pos[1,2], text, adj = c(0.5, text.vjust), col = text.col, ...)
} else {
circos.text(pos[1,1], pos[1,2] + uy(y_offset[[1]], y_offset[[2]]), text, adj = c(0.5, 0.5), col = text.col, ...)
}
circos.par(points.overflow.warning = op_warning)
}
}
}
}
}
parse_unit = function(str) {
if(grepl("^(-?\\d+(\\.\\d+)?)\\s*(mm|cm|inche|inches)$", str)) {
m = regexpr("^(-?\\d+(\\.\\d+)?)", str)
v = regmatches(str, m)
m = regexpr("(mm|cm|inche|inches)$", str)
u = regmatches(str, m)
return(list(value = as.numeric(v), unit = u))
} else {
stop("Format of the unit is incorrect. It should be like '2mm', '-2.1 inches'.")
}
}
# == 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.
#
# == details
# Assuming there are ``n`` nodes in the dendrogram, the positions for leaves on x-axis is ``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.
#
circos.dendrogram = function(dend, facing = c("outside", "inside"), max_height = NULL) {
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
}
}
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")
d1 = dend[[1]] # child tree 1
d2 = dend[[2]] # child tree 2
height = attr(dend, "height")
midpoint = attr(dend, "midpoint")
if(is.leaf(d1)) {
x1 = x[as.character(attr(d1, "label"))]
} else {
x1 = attr(d1, "midpoint") + x[as.character(labels(d1))[1]]
}
y1 = attr(d1, "height")
if(is.leaf(d2)) {
x2 = x[as.character(attr(d2, "label"))]
} else {
x2 = attr(d2, "midpoint") + x[as.character(labels(d2))[1]]
}
y2 = attr(d2, "height")
# graphic parameter for current branch
# only for lines, there are lwd, col, lty
edge_par1 = do.call("lines_par", as.list(attr(d1, "edgePar"))) # as.list to convert NULL to list()
edge_par2 = do.call("lines_par", as.list(attr(d2, "edgePar")))
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") {
circos.lines(c(x1, x1), max_height - c(y1, height), col = edge_par1$col, lty = edge_par1$lty, lwd = edge_par1$lwd, straight = TRUE)
circos.lines(c(x1, (x1+x2)/2), max_height - c(height, height), col = edge_par1$col, lty = edge_par1$lty, lwd = edge_par1$lwd)
circos.lines(c(x2, x2), max_height - c(y2, height), col = edge_par2$col, lty = edge_par2$lty, lwd = edge_par2$lwd, straight = TRUE)
circos.lines(c(x2, (x1+x2)/2), max_height - c(height, height), col = edge_par2$col, lty = edge_par2$lty, lwd = edge_par2$lwd)
if(!is.null(node_par)) {
circos.points((x1+x2)/2, max_height - height, col = node_par$col, pch = node_par$pch, cex = node_par$cex)
}
} else if(facing == "inside") {
circos.lines(c(x1, x1), c(y1, height), col = edge_par1$col, lty = edge_par1$lty, lwd = edge_par1$lwd, straight = TRUE)
circos.lines(c(x1, (x1+x2)/2), c(height, height), col = edge_par1$col, lty = edge_par1$lty, lwd = edge_par1$lwd)
circos.lines(c(x2, x2), c(y2, height), col = edge_par2$col, lty = edge_par2$lty, lwd = edge_par2$lwd, straight = TRUE)
circos.lines(c(x2, (x1+x2)/2), c(height, height), col = edge_par2$col, lty = edge_par2$lty, lwd = edge_par2$lwd)
if(!is.null(node_par)) {
circos.points((x1+x2)/2, height, col = node_par$col, pch = node_par$pch, cex = node_par$cex)
}
}
# do it recursively
if(is.leaf(d1)) {
node_par = attr(d1, "nodePar")
if(!is.null(node_par)) node_par = do.call("points_par", as.list(attr(d1, "nodePar")))
if(facing == "outside") {
if(!is.null(node_par)) {
circos.points(x1, 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(x1, 0, col = node_par$col, pch = node_par$pch, cex = node_par$cex)
}
}
} else {
draw.d(d1, max_height, facing, max_width)
}
if(is.leaf(d2)) {
node_par = attr(d2, "nodePar")
if(!is.null(node_par)) node_par = do.call("points_par", as.list(attr(d2, "nodePar")))
if(facing == "outside") {
if(!is.null(node_par)) {
circos.points(x2, 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(x2, 0, col = node_par$col, pch = node_par$pch, cex = node_par$cex)
}
}
} else {
draw.d(d2, max_height, facing, max_width)
}
}
labels = as.character(labels(dend))
x = seq_along(labels) - 0.5
names(x) = labels
n = length(labels)
draw.d(dend, max_height, facing, max_width = n)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.