R/AnnotationFunction-function.R
402ff791
 
10fbf31c
 # == title
 # Empty Annotation
 #
 # == param
 # -which Whether it is a column annotation or a row annotation?
1a56796e
 # -border Whether draw borders of the annotation region?
91e2f1a1
 # -zoom If it is true and when the heatmap is split, the empty annotation slices will have	
 #       equal height or width, and you can see the correspondance between the annotation slices	
 #       and the original heatmap slices.
1a56796e
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
8ed806b6
 # -show_name Whether to show annotation name.
10fbf31c
 #
 # == details
 # It creates an empty annotation and holds space, later users can add graphics
 # by `decorate_annotation`. This function is useful when users have difficulty to
 # implement `AnnotationFunction` object.
 #
 # In following example, an empty annotation is first created and later points are added:
 #
 # 	m = matrix(rnorm(100), 10)
 # 	ht = Heatmap(m, top_annotation = HeatmapAnnotation(pt = anno_empty()))
 # 	ht = draw(ht)
 # 	co = column_order(ht)[[1]]
 # 	pt_value = 1:10
 # 	decorate_annotation("pt", {
 # 		pushViewport(viewport(xscale = c(0.5, ncol(mat)+0.5), yscale = range(pt_value)))
 # 		grid.points(seq_len(ncol(mat)), pt_value[co], pch = 16, default.units = "native")
 # 		grid.yaxis()
 # 		popViewport()
 # 	})
 #
 # And it is similar as using `anno_points`:
 #
 # 	Heatmap(m, top_annotation = HeatmapAnnotation(pt = anno_points(pt_value)))
 #
 # == value
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
33a5a58f
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#empty-annotation
 #
10fbf31c
 # == examples
 # anno = anno_empty()
 # draw(anno, test = "anno_empty")
 # anno = anno_empty(border = FALSE)
 # draw(anno, test = "anno_empty without border")
91e2f1a1
 anno_empty = function(which = c("column", "row"), border = TRUE, zoom = FALSE,
8ed806b6
 	width = NULL, height = NULL, show_name = FALSE) {
402ff791
 	
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
 	anno_size = anno_width_and_height(which, width, height, unit(1, "cm"))
 	
fcefb322
 	
402ff791
 	fun = function(index) {
 		if(border) grid.rect()
 	}
 
 	anno = AnnotationFunction(
 		fun = fun,
c4a66bf9
 		n = NA,
402ff791
 		fun_name = "anno_empty",
 		which = which,
91e2f1a1
 		var_import = list(border, zoom),
402ff791
 		subset_rule = list(),
8ed806b6
 		subsettable = TRUE,
402ff791
 		height = anno_size$height,
933e808c
 		width = anno_size$width,
8ed806b6
 		show_name = show_name
402ff791
 	)
fcefb322
 	
402ff791
 	return(anno) 
 }
 
ad35494a
 # == title
 # Subset the Matrix by Rows
 #
 # == param
33a5a58f
 # -x A matrix.
ad35494a
 # -i The row indices.
 #
33a5a58f
 # == details
 # Mainly used for constructing the `AnnotationFunction-class` object.
 #
402ff791
 subset_matrix_by_row = function(x, i) x[i, , drop = FALSE]
ad35494a
 
 # == title
 # Subset the vector
 #
 # == param
33a5a58f
 # -x A vector.
 # -i The indices.
 #
 # == details
 # Mainly used for constructing the `AnnotationFunction-class` object.
ad35494a
 #
402ff791
 subset_vector = function(x, i) x[i]
 
c1b9be21
 # == title
 # Do not do subseting
 #
 # == param
 # -x A vector.
 # -i The indices.
 #
 # == details
 # Mainly used for constructing the `AnnotationFunction-class` object.
 #
 subset_no = function(x, i) x
 
10fbf31c
 # == title
 # Simple Annotation
 #
 # == param
 # -x The value vector. The value can be a vector or a matrix. The length of the vector
1a56796e
 #    or the nrow of the matrix is taken as the number of the observations of the annotation.
10fbf31c
 #    The value can be numeric or character and NA value is allowed.
 # -col Color that maps to ``x``. If ``x`` is numeric and needs a continuous mapping, ``col`` 
 #      should be a color mapping function which accepts a vector of values and returns a
 #      vector of colors. Normally it is generated by `circlize::colorRamp2`. If ``x`` is discrete
 #      (numeric or character) and needs a discrete color mapping, ``col`` should be a vector of 
 #      colors with levels in ``x`` as vector names. If ``col`` is not specified, the color mapping
ad35494a
 #      is randomly generated by ``ComplexHeatmap:::default_col``.
10fbf31c
 # -na_col Color for NA value.
 # -which Whether it is a column annotation or a row annotation?
 # -border Wether draw borders of the annotation region?
 # -gp Graphic parameters for grid borders. The ``fill`` parameter is disabled.
 # -pch Points/symbols that are added on top of the annotation grids. The value can be numeric
 #      or single letters. It can be a vector if ``x`` is a vector and a matrix if ``x`` is a matrix.
 #      No points are drawn if the corresponding values are NA.
 # -pt_size Size of the points/symbols. It should be a `grid::unit` object. If ``x`` is a vector,
 #          the value of ``pt_size`` can be a vector, while if ``x`` is a matrix, ``pt_size`` can
 #          only be a single value.
 # -pt_gp Graphic parameters for points/symbols. The length setting is same as ``pt_size``.
8f1e2188
 #     If ``pch`` is set as letters, the fontsize should be set as ``pt_gp = gpar(fontsize = ...)``.
0c139e20
 # -simple_anno_size size of the simple annotation.
1a56796e
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
10fbf31c
 #
 # == details
 # The "simple annotation" is the most widely used annotation type which is heatmap-like, where
 # the grid colors correspond to the values. `anno_simple` also supports to add points/symbols
1a56796e
 # on top of the grids where the it can be normal point (when ``pch`` is set as numbers) or letters (when
 # ``pch`` is set as single letters).
10fbf31c
 #
 # == value
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
33a5a58f
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#simple-annotation-as-an-annotation-function
 #
10fbf31c
 # == example
 # anno = anno_simple(1:10)
 # draw(anno, test = "a numeric vector")
 #
 # anno = anno_simple(cbind(1:10, 10:1))
 # draw(anno, test = "a matrix")
 #
 # anno = anno_simple(1:10, pch = c(1:4, NA, 6:8, NA, 10))
 # draw(anno, test = "pch has NA values")
 #
ad35494a
 # anno = anno_simple(1:10, pch = c(rep("A", 5), rep(NA, 5)))
10fbf31c
 # draw(anno, test = "pch has NA values")
 #
 # pch = matrix(1:20, nc = 2)
 # pch[sample(length(pch), 10)] = NA
 # anno = anno_simple(cbind(1:10, 10:1), pch = pch)
 # draw(anno, test = "matrix, pch is a matrix with NA values")
402ff791
 anno_simple = function(x, col, na_col = "grey", 
7e46f0a9
 	which = c("column", "row"), border = FALSE, gp = gpar(),
402ff791
 	pch = NULL, pt_size = unit(1, "snpc")*0.8, pt_gp = gpar(), 
0c139e20
 	simple_anno_size = ht_opt$simple_anno_size,
402ff791
 	width = NULL, height = NULL) {
 
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
 	if(is.data.frame(x)) x = as.matrix(x)
 	if(is.matrix(x)) {
 		if(ncol(x) == 1) {
 			x = x[, 1]
 		}
 	}
 	input_is_matrix = is.matrix(x)
 
 	anno_size = anno_width_and_height(which, width, height, 
0c139e20
 		simple_anno_size*ifelse(input_is_matrix, ncol(x), 1))
402ff791
 	
 	if(missing(col)) {
 		col = default_col(x)
 	}
 	if(is.atomic(col)) {
 		color_mapping = ColorMapping(name = "foo", colors = col, na_col = na_col)
     } else if(is.function(col)) {
         color_mapping = ColorMapping(name = "foo", col_fun = col, na_col = na_col)
     } else if(inherits(col, "ColorMapping")) {
     	color_mapping = col
     } else {
     	stop_wrap("`col` should be a named vector/a color mapping function/a ColorMapping object.")
     }
 
     value = x
     gp = subset_gp(gp, 1)  # gp controls border
 
     if(is.matrix(value)) {
 		n = nrow(value)
 		nr = n
 		nc = ncol(value)
 	} else {
 		n = length(value)
 		nr = n
 		nc = 1
 	}
 	
     if(!is.null(pch)) {
     	if(input_is_matrix) {
 		    pch = normalize_graphic_param_to_mat(pch, ifelse(is.matrix(x), ncol(x), 1), n, "pch")
 		    pt_size = pt_size[1]*(1/nc)
 		    pt_gp = subset_gp(pt_gp, 1)
 		} else {
 			if(length(pch) == 1) pch = rep(pch, n)
 			if(length(pt_size) == 1) pt_size = rep(pt_size, n)
 			pt_gp = recycle_gp(pt_gp, n)
 		}
 	}
 
 	row_fun = function(index) {
 		
168cc247
 	    n = length(index)
 	    y = (n - seq_len(n) + 0.5) / n
 	    if(is.matrix(value)) {
950f27c5
 
7539e693
 			nc = ncol(value)
 			pch = pch[index, , drop = FALSE]
950f27c5
 
7539e693
 			for(i in seq_len(nc)) {
8ed806b6
 				if(color_mapping@type == "continuous" || !is.null(gp$col)) {
7e46f0a9
 				    fill = map_to_colors(color_mapping, value[index, i])
8ed806b6
 				    flag = 0
 	                if(is.null(gp$col)) {
 	                	gp$col = fill
 	                	flag = 1
 	                }
7e46f0a9
 				    grid.rect(x = (i-0.5)/nc, y, height = 1/n, width = 1/nc, gp = do.call("gpar", c(list(fill = fill), gp)))
8ed806b6
 				    if(flag) gp$col = NULL
7e46f0a9
 				} else {
 					r = rle(value[index, i])
 					fill = map_to_colors(color_mapping, r$values)
 					if(is.null(gp$col)) gp$col = fill
 					grid.rect(x = (i-0.5)/nc, y = 1 - cumsum(r$lengths)/n, height = r$length/n, width = 1/nc, just = "bottom", gp = do.call("gpar", c(list(fill = fill), gp)))
 				}
7539e693
 			    if(!is.null(pch)) {
 					l = !is.na(pch[, i])
 					if(any(l)) {
6fd997f5
 						if(is.character(pch)) {
 							text_gp = subset_gp(pt_gp, i)
 							text_gp$fontsize = convertHeight({if(length(pt_size) == 1) pt_size else pt_size[i]}, "pt", valueOnly = TRUE)
 							grid.text(pch[l, i], x = rep((i-0.5)/nc, sum(l)), y = y[l],
 							    gp = text_gp)
 						} else {
 							grid.points(x = rep((i-0.5)/nc, sum(l)), y = y[l], pch = pch[l, i], 
 							    size = {if(length(pt_size) == 1) pt_size else pt_size[i]}, 
 							    gp = subset_gp(pt_gp, i))
 						}
7539e693
 					}
 			    }
 			}
168cc247
 	    } else {
8ed806b6
 	    	if(color_mapping@type == "continuous" || !is.null(gp$col)) {
7e46f0a9
 				fill = map_to_colors(color_mapping, value[index])
 				if(is.null(gp$col)) gp$col = fill
 				grid.rect(x = 0.5, y, height = 1/n, width = 1, gp = do.call("gpar", c(list(fill = fill), gp)))
 			} else {
 				r = rle(value[index])
 				fill = map_to_colors(color_mapping, r$values)
 				if(is.null(gp$col)) gp$col = fill
 				grid.rect(x = 0.5, y = 1 - cumsum(r$lengths)/n, height = r$length/n, width = 1, just = "bottom", gp = do.call("gpar", c(list(fill = fill), gp)))
 			}
7539e693
 			if(!is.null(pch)) {
 			    pch = pch[index]
 			    pt_size = pt_size[index]
 			    pt_gp = subset_gp(pt_gp, index)
 			    l = !is.na(pch)
 			    if(any(l)) {
6fd997f5
 			    	if(is.character(pch)) {
 			    		text_gp = subset_gp(pt_gp, which(l))
 			    		text_gp$fontsize = convertHeight(pt_size[l], "pt", valueOnly = TRUE)
 			    		grid.text(pch[l], x = rep(0.5, sum(l)), y = y[l],
 							gp = text_gp)
 			    	} else {
 					    grid.points(x = rep(0.5, sum(l)), y = y[l], pch = pch[l], size = pt_size[l], 
 							gp = subset_gp(pt_gp, which(l)))
 					}
7539e693
 				}
 			}
168cc247
 	    }
402ff791
         if(border) grid.rect(gp = gpar(fill = "transparent"))
 	}
 
 	column_fun = function(index) {
 
 		n = length(index)
 		x = (seq_len(n) - 0.5) / n
         if(is.matrix(value)) {
950f27c5
 
402ff791
             nc = ncol(value)
7539e693
 		    pch = pch[index, , drop = FALSE]
 	        for(i in seq_len(nc)) {
8ed806b6
 	        	if(color_mapping@type == "continuous" || !is.null(gp$col)) {
7e46f0a9
 	                fill = map_to_colors(color_mapping, value[index, i])
8ed806b6
 	                flag = 0
 	                if(is.null(gp$col)) {
 	                	gp$col = fill
 	                	flag = 1
 	                }
7e46f0a9
 	                grid.rect(x, y = (nc-i +0.5)/nc, width = 1/n, height = 1/nc, gp = do.call("gpar", c(list(fill = fill), gp)))
8ed806b6
 	                if(flag) gp$col = NULL
7e46f0a9
 	            } else {
 	            	r = rle(value[index, i])
 					fill = map_to_colors(color_mapping, r$values)
 					if(is.null(gp$col)) gp$col = fill
 					grid.rect(cumsum(r$lengths)/n, y = (nc-i +0.5)/nc, width = r$length/n, height = 1/nc, just = "right", gp = do.call("gpar", c(list(fill = fill), gp)))
 	            }
7539e693
 				if(!is.null(pch)){
 				    l = !is.na(pch[, i])
 				    if(any(l)) {
6fd997f5
 				    	if(is.character(pch)) {
 				    		text_gp = subset_gp(pt_gp, i)
 				    		text_gp$fontsize = convertHeight({if(length(pt_size) == 1) pt_size else pt_size[i]}, "pt", valueOnly = TRUE)
 				    		 grid.text(pch[l, i], x = x[l], y = rep((nc-i +0.5)/nc, sum(l)),
 								gp = text_gp)
 				    	} else {
 						    grid.points(x[l], y = rep((nc-i +0.5)/nc, sum(l)), pch = pch[l, i], 
 								size = {if(length(pt_size) == 1) pt_size else pt_size[i]}, 
 								gp = subset_gp(pt_gp, i))
 						}
7539e693
 					}
 				}
 		    }
402ff791
         } else {
8ed806b6
         	if(color_mapping@type == "continuous" || !is.null(gp$col)) {
7e46f0a9
 				fill = map_to_colors(color_mapping, value[index])
 				if(is.null(gp$col)) gp$col = fill
 				grid.rect(x, y = 0.5, width = 1/n, height = 1, gp = do.call("gpar", c(list(fill = fill), gp)))
 			} else {
 				r = rle(value[index])
 				fill = map_to_colors(color_mapping, r$values)
 				if(is.null(gp$col)) gp$col = fill
 				grid.rect(cumsum(r$lengths)/n, y = 0.5, width = r$length/n, height = 1, just = "right", gp = do.call("gpar", c(list(fill = fill), gp)))
 			}
7539e693
 			if(!is.null(pch)) {
 				pch = pch[index]
 				pt_size = pt_size[index]
 				pt_gp = subset_gp(pt_gp, index)
 				l = !is.na(pch)
 				if(any(l)) {
6fd997f5
 					if(is.character(pch)) {
 						text_gp = subset_gp(pt_gp, which(l))
 						text_gp$fontsize = convertHeight(pt_size[l], "pt", valueOnly = TRUE)
 						grid.text(pch[l], x = x[l], y = rep(0.5, sum(l)),
 						    gp = text_gp)
 					} else {
 						grid.points(x[l], y = rep(0.5, sum(l)), pch = pch[l], size = pt_size[l], 
 							gp = subset_gp(pt_gp, which(l)))
 					}
7539e693
 				}
 			}
402ff791
         }
         if(border) grid.rect(gp = gpar(fill = "transparent"))
 	}
 
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_simple",
 		which = which,
 		width = anno_size$width,
 		height = anno_size$height,
 		n = n,
 		data_scale = c(0.5, nc + 0.5),
 		var_import = list(value, gp, border, color_mapping, pt_gp, pt_size, pch)
 	)
 
 	anno@subset_rule = list()
 	if(input_is_matrix) {
 		anno@subset_rule$value = subset_matrix_by_row
 		if(!is.null(pch)) {
 			anno@subset_rule$pch = subset_matrix_by_row
 		}
 	} else {
 		anno@subset_rule$value = subset_vector
 		if(!is.null(pch)) {
 			anno@subset_rule$pch = subset_vector
 			anno@subset_rule$pt_size = subset_vector
 			anno@subset_rule$pt_gp = subset_gp
 		}
 	}
 
8ed806b6
 	anno@subsettable = TRUE
402ff791
 
 	return(anno)      
 }
 
7e46f0a9
 
10fbf31c
 # == title
 # Image Annotation
 #
 # == param
 # -image A vector of file paths of images. The format of the image is inferred from the suffix name of the image file.
 #       NA values or empty strings in the vector means no image to drawn.
 # -which Whether it is a column annotation or a row annotation?
 # -border Wether draw borders of the annotation region?
 # -gp Graphic parameters for annotation grids. If the image has transparent background, the ``fill`` parameter 
 #     can be used to control the background color in the annotation grids.
 # -space The space around the image to the annotation grid borders. The value should be a `grid::unit` object.
1a56796e
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
10fbf31c
 #
 # == details
 # This function supports image formats in ``png``, ``svg``, ``pdf``, ``eps``, ``jpeg/jpg``, ``tiff``. 
 # ``png``, ``jpeg/jpg`` and ``tiff`` images are imported by `png::readPNG`, `jpeg::readJPEG` and 
1d6f282b
 # `tiff::readTIFF`, and drawn by `grid::grid.raster`. ``svg`` images are firstly reformatted by ``rsvg::rsvg_svg``
10fbf31c
 # and then imported by `grImport2::readPicture` and drawn by `grImport2::grid.picture`. ``pdf`` and ``eps``
 # images are imported by `grImport::PostScriptTrace` and `grImport::readPicture`, later drawn by `grImport::grid.picture`.
 #
 # Different image formats can be mixed in the ``image`` vector.
 #
 # == value
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
33a5a58f
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#image-annotation
 #
10fbf31c
 # == example
 # # download the free icons from https://github.com/Keyamoon/IcoMoon-Free
 # \dontrun{
 # image = sample(dir("~/Downloads/IcoMoon-Free-master/PNG/64px", full.names = TRUE), 10)
 # anno = anno_image(image)
 # draw(anno, test = "png")
 # image[1:5] = ""
 # anno = anno_image(image)
 # draw(anno, test = "some of png")
 # }
402ff791
 anno_image = function(image, which = c("column", "row"), border = TRUE, 
10fbf31c
 	gp = gpar(fill = NA, col = NA), space = unit(1, "mm"), 
 	width = NULL, height = NULL) {
402ff791
 
10fbf31c
 	image[is.na(image)] = ""
 	l = grepl("^\\s*$", image)
 	image[l] = ""
 	
402ff791
 	allowed_image_type = c("png", "svg", "pdf", "eps", "jpeg", "jpg", "tiff")
 
 	if(inherits(image, "character")) { ## they are file path
 		image_type = tolower(gsub("^.*\\.(\\w+)$", "\\1", image))
10fbf31c
 		if(! all(image_type[image_type != ""] %in% allowed_image_type)) {
c4a66bf9
 			stop_wrap("image file should be of png/svg/pdf/eps/jpeg/jpg/tiff.")
402ff791
 		}
 	} else {
c4a66bf9
 		stop_wrap("`image` should be a vector of path.")
402ff791
 	}
 
 	n_image = length(image)
 	image_list = vector("list", n_image)
 	image_class = vector("character", n_image)
 	for(i in seq_along(image)) {
10fbf31c
 		if(image[i] == "") {
 			image_list[[i]] = NA
 			image_class[i] = NA
 		} else if(image_type[i] == "png") {
402ff791
 			if(!requireNamespace("png")) {
c4a66bf9
 				stop_wrap("Need png package to read png images.")
402ff791
 			}
 			image_list[[i]] = png::readPNG(image[i])
 			image_class[i] = "raster"
 		} else if(image_type[i] %in% c("jpeg", "jpg")) {
 			if(!requireNamespace("jpeg")) {
c4a66bf9
 				stop_wrap("Need jpeg package to read jpeg/jpg images.")
402ff791
 			}
 			image_list[[i]] = jpeg::readJPEG(image[i])
 			image_class[i] = "raster"
 		} else if(image_type[i] == "tiff") {
 			if(!requireNamespace("tiff")) {
c4a66bf9
 				stop_wrap("Need tiff package to read tiff images.")
402ff791
 			}
 			image_list[[i]] = tiff::readTIFF(image[i])
 			image_class[i] = "raster"
 		} else if(image_type[i] %in% c("pdf", "eps")) {
 			if(!requireNamespace("grImport")) {
c4a66bf9
 				stop_wrap("Need grImport package to read pdf/eps images.")
402ff791
 			}
 			temp_file = tempfile()
 			getFromNamespace("PostScriptTrace", ns = "grImport")(image[[i]], temp_file)
 			image_list[[i]] = grImport::readPicture(temp_file)
 			file.remove(temp_file)
 			image_class[i] = "grImport::Picture"
 		} else if(image_type[i] == "svg") {
 			if(!requireNamespace("grImport2")) {
c4a66bf9
 				stop_wrap("Need grImport2 package to read svg images.")
402ff791
 			}
1d6f282b
 			# if(!requireNamespace("rsvg")) {
 			# 	stop_wrap("Need rsvg package to convert svg images.")
 			# }
 			temp_file = tempfile()
 			# get it work on bioconductor build server
 			oe = try(getFromNamespace("rsvg_svg", ns = "rsvg")(image[i], temp_file))
 			if(inherits(oe, "try-error")) {
c4a66bf9
 				stop_wrap("Need rsvg package to convert svg images.")
402ff791
 			}
 			image_list[[i]] = grImport2::readPicture(temp_file)
 			file.remove(temp_file)
 			image_class[i] = "grImport2::Picture"
 		}
 	}
 	yx_asp = sapply(image_list, function(x) {
 		if(inherits(x, "array")) {
 			nrow(x)/ncol(x)
 		} else if(inherits(x, "Picture")) {
 			max(x@summary@yscale)/max(x@summary@xscale)
10fbf31c
 		} else {
 			1
402ff791
 		}
 	})
 
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
 	space = space[1]
 
 	anno_size = anno_width_and_height(which, width, height, unit(1, "cm"))
 
 	gp = recycle_gp(gp, n_image)
 	
 	column_fun = function(index) {
 		n = length(index)
 
 		pushViewport(viewport())
ad35494a
 		asp = convertHeight(unit(1, "npc") - space*2, "mm", valueOnly = TRUE)/convertWidth(unit(1/n, "npc") - space*2, "mm", valueOnly = TRUE)
402ff791
 		grid.rect(x = (1:n - 0.5)/n, width = 1/n, gp = subset_gp(gp, index))
 		for(i in seq_len(n)) {
c1485e37
 			if(identical(image_list[[ index[i] ]], NA)) next
402ff791
 			if(yx_asp[ index[i] ] > asp) {
 				height = unit(1, "npc") - space*2
 				width = convertHeight(height, "mm")*yx_asp[ index[i] ]
 			} else {
 				width = unit(1/n, "npc") - space*2
 				height = yx_asp[ index[i] ]*convertWidth(width, "mm")
 			}
 			if(image_class[ index[i] ] == "raster") {
c1485e37
 				grid.raster(image_list[[ index[i] ]], x = (i-0.5)/n, width = width, height = height)
402ff791
 			} else if(image_class[ index[i] ] == "grImport::Picture") {
 				grid.picture = getFromNamespace("grid.picture", ns = "grImport")
c1485e37
 				grid.picture(image_list[[ index[i] ]], x = (i-0.5)/n, width = width, height = height)
402ff791
 			} else if(image_class[ index[i] ] == "grImport2::Picture") {
 				grid.picture = getFromNamespace("grid.picture", ns = "grImport2")
c1485e37
 				grid.picture(image_list[[ index[i] ]], x = (i-0.5)/n, width = width, height = height)
402ff791
 			}
 		}
8ed806b6
 		if(is.logical(border)) {
 			if(border) {
 				grid.rect(gp = gpar(fill = "transparent"))
 			}
 		} else {
 			grid.rect(gp = gpar(fill = "transparent", col = border))
 		}
402ff791
 		popViewport()
 	}
 
 	row_fun = function(index) {
 		n = length(index)
 
 		pushViewport(viewport())
ad35494a
 		asp = convertHeight(unit(1/n, "npc") - space*2, "mm", valueOnly = TRUE)/convertWidth(unit(1, "npc") - space*2, "mm", valueOnly = TRUE)
402ff791
 		grid.rect(y = (n - 1:n + 0.5)/n, height = 1/n, gp = subset_gp(gp, index))
 		for(i in seq_len(n)) {
c1485e37
 			if(identical(image_list[[ index[i] ]], NA)) next
402ff791
 			if(yx_asp[ index[i] ] > asp) {
 				height = unit(1/n, "npc") - space*2
 				width = convertHeight(height, "mm")*(1/yx_asp[ index[i] ])
 			} else {
 				width = unit(1, "npc") - space*2
 				height = yx_asp[ index[i] ]*convertWidth(width, "mm")
 			}
 			if(image_class[ index[i] ] == "raster") {
c1485e37
 				grid.raster(image_list[[ index[i] ]], y = (n - i + 0.5)/n, width = width, height = height)
402ff791
 			} else if(image_class[ index[i] ] == "grImport::Picture") {
 				grid.picture = getFromNamespace("grid.picture", ns = "grImport")
c1485e37
 				grid.picture(image_list[[ index[i] ]], y = (n - i + 0.5)/n, width = width, height = height)
402ff791
 			} else if(image_class[ index[i] ] == "grImport2::Picture") {
 				grid.picture = getFromNamespace("grid.picture", ns = "grImport2")
c1485e37
 				grid.picture(image_list[[ index[i] ]], y = (n - i + 0.5)/n, width = width, height = height)
402ff791
 			}
 		}
8ed806b6
 		if(is.logical(border)) {
 			if(border) {
 				grid.rect(gp = gpar(fill = "transparent"))
 			}
 		} else {
 			grid.rect(gp = gpar(fill = "transparent", col = border))
 		}
402ff791
 		popViewport()
 	}
 	
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_image",
 		which = which,
 		width = anno_size$width,
 		height = anno_size$height,
 		n = n_image,
 		data_scale = c(0.5, 1.5),
 		var_import = list(gp, border, space, yx_asp, image_list, image_class)
 	)
 
 	anno@subset_rule$gp = subset_vector
 	anno@subset_rule$image_list = subset_vector
 	anno@subset_rule$image_class = subset_vector
 
8ed806b6
 	anno@subsettable = TRUE
402ff791
 
 	return(anno)   
 }
 
10fbf31c
 # == title
 # The Default Parameters for Annotation Axis
 #
 # == param
 # -which Whether it is for column annotation or row annotation?
 #
 # == details
 # There are following parameters for the annotation axis:
 #
 # -at The breaks of axis. By default it is automatically inferred.
 # -labels The corresponding axis labels.
 # -labels_rot The rotation of the axis labels.
 # -gp Graphc parameters of axis labels. The value should be a `grid::unit` object.
 # -side If it is for column annotation, the value should only be one of ``left`` and ``right``. If
 #       it is for row annotation, the value should only be one of ``top`` and ``bottom``.
 # -facing Whether the axis faces to the outside of the annotation region or inside. Sometimes when
 #         appending more than one heatmaps, the axes of column annotations of one heatmap might
 #         overlap to the neighbouring heatmap, setting ``facing`` to ``inside`` may invoild it.
5ffb507b
 # -direction The direction of the axis. Value should be "normal" or "reverse".
 #
 # All the parameters are passed to `annotation_axis_grob` to construct an axis grob.
10fbf31c
 #
 # == example
 # default_axis_param("column")
 # default_axis_param("row")
402ff791
 default_axis_param = function(which) {
 	list(
 		at = NULL, 
 		labels = NULL, 
 		labels_rot = ifelse(which == "column", 0, 90), 
 		gp = gpar(fontsize = 8), 
 		side = ifelse(which == "column", "left", "bottom"), 
5ffb507b
 		facing = "outside",
 		direction = "normal"
402ff791
 	)
 }
 
 validate_axis_param = function(axis_param, which) {
 	dft = default_axis_param(which)
 	for(nm in names(axis_param)) {
 		dft[[nm]] = axis_param[[nm]]
 	}
64d651fe
 	if(which == "row") {
 		if(dft$side %in% c("left", "right")) {
 			stop_wrap("axis side can only be set to 'top' or 'bottom' for row annotations.")
 		}
 	}
 	if(which == "column") {
 		if(dft$side %in% c("top", "bottom")) {
 			stop_wrap("axis side can only be set to 'left' or 'right' for row annotations.")
 		}
 	}
402ff791
 	return(dft)
 }
 
e79e87db
 construct_axis_grob = function(axis_param, which, data_scale, format = NULL) {
402ff791
 	axis_param_default = default_axis_param(which)
 
 	for(nm in setdiff(names(axis_param_default), names(axis_param))) {
 		axis_param[[nm]] = axis_param_default[[nm]]
 	}
 	
 	if(is.null(axis_param$at)) {
 		at = pretty_breaks(data_scale)
 		axis_param$at = at
e79e87db
 		if(is.null(format)) {
 			axis_param$labels = at
 		} else {
 			axis_param$labels = format(at)
 		}
402ff791
 	}
5ffb507b
 
402ff791
 	if(is.null(axis_param$labels)) {
e79e87db
 		if(is.null(format)) {
 			axis_param$labels = axis_param$at
 		} else {
 			axis_param$labels = format(axis_param$at)
 		}
402ff791
 	}
5ffb507b
 	axis_param$scale = data_scale
402ff791
 	axis_grob = do.call(annotation_axis_grob, axis_param)
 	return(axis_grob)
 }
 
 # == title
10fbf31c
 # Points Annotation
402ff791
 #
 # == param
10fbf31c
 # -x The value vector. The value can be a vector or a matrix. The length of the vector
 #    or the number of rows of the matrix is taken as the number of the observations of the annotation.
 # -which Whether it is a column annotation or a row annotation?
 # -border Wether draw borders of the annotation region?
 # -gp Graphic parameters for points. The length of each graphic parameter can be 1, length of ``x`` if ``x``
 #     is a vector, or number of columns of ``x`` is ``x`` is a matrix.
 # -pch Point type. The length setting is the same as ``gp``.
 # -size Point size, the value should be a `grid::unit` object. The length setting is the same as ``gp``.
 # -ylim Data ranges. By default it is ``range(x)``.
 # -extend The extension to both side of ``ylim``. The value is a percent value corresponding to ``ylim[2] - ylim[1]``.
 # -axis Whether to add axis?
 # -axis_param parameters for controlling axis. See `default_axis_param` for all possible settings and default parameters.
1a56796e
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
41afe8bf
 # -... Other arguments.
402ff791
 #
 # == value
10fbf31c
 # An annotation function which can be used in `HeatmapAnnotation`.
402ff791
 #
33a5a58f
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#points-annotation
 #
10fbf31c
 # == example
 # anno = anno_points(runif(10))
 # draw(anno, test = "anno_points")
 # anno = anno_points(matrix(runif(20), nc = 2), pch = 1:2)
 # draw(anno, test = "matrix")
402ff791
 anno_points = function(x, which = c("column", "row"), border = TRUE, gp = gpar(), pch = 16, 
 	size = unit(2, "mm"), ylim = NULL, extend = 0.05, axis = TRUE,
41afe8bf
 	axis_param = default_axis_param(which), width = NULL, height = NULL, ...) {
 
 	other_args = list(...)
 	if(length(other_args)) {
 		if("axis_gp" %in% names(other_args)) {
 			stop_wrap("`axis_gp` is removed from the arguments. Use `axis_param = list(gp = ...)` instead.")
 		}
 		if("axis_direction" %in% names(other_args)) {
 			stop_wrap("`axis_direction` is not supported any more.")
 		}
 	}
260b45a1
 	if("pch_as_image" %in% names(other_args)) {
 		pch_as_image = other_args$pch_as_image
 	} else {
 		pch_as_image = FALSE
 	}
402ff791
 
04a1dc38
 	ef = function() NULL
402ff791
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
04a1dc38
 		dev.null()
 		ef = dev.off2
402ff791
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
04a1dc38
 	on.exit(ef())
 
402ff791
 	if(is.data.frame(x)) x = as.matrix(x)
 	if(is.matrix(x)) {
 		if(ncol(x) == 1) {
 			x = x[, 1]
 		}
 	}
 	input_is_matrix = is.matrix(x)
 
 	anno_size = anno_width_and_height(which, width, height, unit(1, "cm"))
 
 	if(is.matrix(x)) {
 		n = nrow(x)
 		nr = n
 		nc = ncol(x)
 	} else {
 		n = length(x)
 		nr = n
 		nc = 1
 	}
 
10fbf31c
 	if(input_is_matrix) {
402ff791
 		gp = recycle_gp(gp, nc)
 		if(length(pch) == 1) pch = rep(pch, nc)
 		if(length(size) == 1) size = rep(size, nc)
10fbf31c
 	} else if(is.atomic(x)) {
 		gp = recycle_gp(gp, n)
 		if(length(pch) == 1) pch = rep(pch, n)
 		if(length(size) == 1) size = rep(size, n)
402ff791
 	}
10fbf31c
 
402ff791
 	if(is.null(ylim)) {
 		data_scale = range(x, na.rm = TRUE)
 	} else {
 		data_scale = ylim
 	}
826b321e
 	if(data_scale[1] == data_scale[2]) data_scale[2] = data_scale[1] + 1
402ff791
 	data_scale = data_scale + c(-extend, extend)*(data_scale[2] - data_scale[1])
 
 	value = x
 
 	axis_param = validate_axis_param(axis_param, which)
 	axis_grob = if(axis) construct_axis_grob(axis_param, which, data_scale) else NULL
 
ad35494a
 	row_fun = function(index, k = 1, N = 1) {
c3ef40cd
 		
402ff791
 		n = length(index)
 
5ffb507b
 		if(axis_param$direction == "reverse") {
 			value = data_scale[2] - value + data_scale[1]
 		}
 
402ff791
 		pushViewport(viewport(xscale = data_scale, yscale = c(0.5, n+0.5)))
 		if(is.matrix(value)) {
 			for(i in seq_len(ncol(value))) {
 				grid.points(value[index, i], n - seq_along(index) + 1, gp = subset_gp(gp, i), 
 					default.units = "native", pch = pch[i], size = size[i])
 			}
 		} else {
260b45a1
 			if(pch_as_image) {
 				for(ii in seq_along(index)) {
 					pch_image = png::readPNG(pch[ index[ii] ])
 					grid.raster(pch_image, y = n - ii + 1, x = value[ index[ii] ], 
 						default.units = "native", width = size[ index[ii] ], 
 						height = size[ index[ii] ]*(nrow(pch_image)/ncol(pch_image)))
 				}
 			} else {
 				grid.points(value[index], n - seq_along(index) + 1, gp = subset_gp(gp, index), default.units = "native", 
 					pch = pch[index], size = size[index])
 			}
402ff791
 		}
ad35494a
 		if(axis_param$side == "top") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "bottom") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
 
ad35494a
 	column_fun = function(index, k = 1, N = 1) {
c3ef40cd
 		
402ff791
 		n = length(index)
5ffb507b
 
 		if(axis_param$direction == "reverse") {
 			value = data_scale[2] - value + data_scale[1]
 		}
402ff791
 		
 		pushViewport(viewport(yscale = data_scale, xscale = c(0.5, n+0.5)))
 		if(is.matrix(value)) {
 			for(i in seq_len(ncol(value))) {
260b45a1
 				grid.points(seq_along(index), value[index, i], gp = subset_gp(gp, i), 
 					default.units = "native", pch = pch[i], size = size[i])
402ff791
 			}
 		} else {
260b45a1
 			if(pch_as_image) {
 				for(ii in seq_along(index)) {
 					pch_image = png::readPNG(pch[ index[ii] ])
 					grid.raster(pch_image, x = ii, value[ index[ii] ], 
 						default.units = "native", width = size[ index[ii] ], 
 						height = size[ index[ii] ]*(nrow(pch_image)/ncol(pch_image)))
 				}
 			} else {
 				grid.points(seq_along(index), value[index], gp = subset_gp(gp, index), 
 					default.units = "native", pch = pch[index], size = size[index])
 			}
402ff791
 		}
ad35494a
 		if(axis_param$side == "left") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "right") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
 
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_points",
 		which = which,
 		width = anno_size$width,
 		height = anno_size$height,
 		n = n,
 		data_scale = data_scale,
260b45a1
 		var_import = list(value, gp, border, pch, size, axis, axis_param, axis_grob, data_scale, pch_as_image)
402ff791
 	)
 
 	anno@subset_rule$gp = subset_vector
 	if(input_is_matrix) {
 		anno@subset_rule$value = subset_matrix_by_row
a6954cea
 		if(ncol(value) > 1) {
 			anno@subset_rule$gp = NULL
 		}
402ff791
 	} else {
 		anno@subset_rule$value = subset_vector
 		anno@subset_rule$gp = subset_gp
 		anno@subset_rule$size = subset_vector
 		anno@subset_rule$pch = subset_vector
 	}
 
8ed806b6
 	anno@subsettable = TRUE
402ff791
 
 	anno@extended = update_anno_extend(anno, axis_grob, axis_param)
 		
 	return(anno) 
 }
 
 update_anno_extend = function(anno, axis_grob, axis_param) {
 	extended = anno@extended
5ec801a3
 	if(is.null(axis_grob)) {
 		return(extended)
 	}
 
402ff791
 	if(axis_param$facing == "outside") {
 		if(axis_param$side == "left") {
d574e5ea
 			extended[2] = convertWidth(grobWidth(axis_grob), "mm")
402ff791
 		} else if(axis_param$side == "right") {
d574e5ea
 			extended[4] = convertWidth(grobWidth(axis_grob), "mm")
402ff791
 		} else if(axis_param$side == "top") {
d574e5ea
 			extended[3] = convertHeight(grobHeight(axis_grob), "mm")
402ff791
 		} else if(axis_param$side == "bottom") {
d574e5ea
 			extended[1] = convertHeight(grobHeight(axis_grob), "mm")
402ff791
 		}
 	}
 	return(extended)
 }
 
10fbf31c
 # == title
 # Lines Annotation
 #
 # == param
 # -x The value vector. The value can be a vector or a matrix. The length of the vector
 #    or the number of rows of the matrix is taken as the number of the observations of the annotation.
 # -which Whether it is a column annotation or a row annotation?
 # -border Wether draw borders of the annotation region?
 # -gp Graphic parameters for lines. The length of each graphic parameter can be 1, or number of columns of ``x`` is ``x`` is a matrix.
 # -add_points Whether to add points on the lines?
1a56796e
 # -smooth If it is ``TRUE``, smoothing by `stats::loess` is performed. If it is ``TRUE``, ``add_points`` is set to ``TRUE`` by default.
10fbf31c
 # -pch Point type. The length setting is the same as ``gp``.
 # -size Point size, the value should be a `grid::unit` object. The length setting is the same as ``gp``.
ad35494a
 # -pt_gp Graphic parameters for points. The length setting is the same as ``gp``.
10fbf31c
 # -ylim Data ranges. By default it is ``range(x)``.
 # -extend The extension to both side of ``ylim``. The value is a percent value corresponding to ``ylim[2] - ylim[1]``.
 # -axis Whether to add axis?
 # -axis_param parameters for controlling axis. See `default_axis_param` for all possible settings and default parameters.
1a56796e
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
10fbf31c
 #
 # == value
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
33a5a58f
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#lines-annotation
 #
10fbf31c
 # == example
 # anno = anno_lines(runif(10))
 # draw(anno, test = "anno_lines")
 # anno = anno_lines(cbind(c(1:5, 1:5), c(5:1, 5:1)), gp = gpar(col = 2:3))
 # draw(anno, test = "matrix")
 # anno = anno_lines(cbind(c(1:5, 1:5), c(5:1, 5:1)), gp = gpar(col = 2:3),
 # 	add_points = TRUE, pt_gp = gpar(col = 5:6), pch = c(1, 16))
 # draw(anno, test = "matrix")
402ff791
 anno_lines = function(x, which = c("column", "row"), border = TRUE, gp = gpar(), 
1a56796e
 	add_points = smooth, smooth = FALSE, pch = 16, size = unit(2, "mm"), pt_gp = gpar(), ylim = NULL, 
402ff791
 	extend = 0.05, axis = TRUE, axis_param = default_axis_param(which),
 	width = NULL, height = NULL) {
 
04a1dc38
 	ef = function() NULL
402ff791
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
04a1dc38
 		dev.null()
 		ef = dev.off2
402ff791
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
04a1dc38
 	on.exit(ef())
 
402ff791
 	if(is.data.frame(x)) x = as.matrix(x)
 	if(is.matrix(x)) {
 		if(ncol(x) == 1) {
 			x = x[, 1]
 		}
 	}
 	input_is_matrix = is.matrix(x)
 
 	anno_size = anno_width_and_height(which, width, height, unit(1, "cm"))
 
 	if(is.matrix(x)) {
 		n = nrow(x)
 		nr = n
 		nc = ncol(x)
 	} else {
 		n = length(x)
 		nr = n
 		nc = 1
 	}
 
10fbf31c
 	if(input_is_matrix) {
402ff791
 		gp = recycle_gp(gp, nc)
 		pt_gp = recycle_gp(pt_gp, nc)
 		if(length(pch) == 1) pch = rep(pch, nc)
 		if(length(size) == 1) size = rep(size, nc)
10fbf31c
 	} else if(is.atomic(x)) {
 		gp = recycle_gp(gp, 1)
 		pt_gp = recycle_gp(pt_gp, n)
 		if(length(pch) == 1) pch = rep(pch, n)
 		if(length(size) == 1) size = rep(size, n)
402ff791
 	}
10fbf31c
 
402ff791
 	if(is.null(ylim)) {
 		data_scale = range(x, na.rm = TRUE)
 	} else {
 		data_scale = ylim
 	}
826b321e
 	if(data_scale[1] == data_scale[2]) data_scale[2] = data_scale[1] + 1
402ff791
 	data_scale = data_scale + c(-extend, extend)*(data_scale[2] - data_scale[1])
 
 	value = x
 
 	axis_param = validate_axis_param(axis_param, which)
 	axis_grob = if(axis) construct_axis_grob(axis_param, which, data_scale) else NULL
 
ad35494a
 	row_fun = function(index, k = 1, N = 1) {
402ff791
 		n = length(index)
 
5ffb507b
 		if(axis_param$direction == "reverse") {
 			value = data_scale[2] - value + data_scale[1]
 		}
 
402ff791
 		pushViewport(viewport(xscale = data_scale, yscale = c(0.5, n+0.5)))
 		if(is.matrix(value)) {
 			for(i in seq_len(ncol(value))) {
933e808c
 				x = n - seq_along(index) + 1
 				y = value[index, i]
 				if(smooth) {
 					fit = loess(y ~ x)
131f3219
 					x2 = seq(x[1], x[length(x)], length.out = 100)
933e808c
 					y2 = predict(fit, x2)
 					grid.lines(y2, x2, gp = subset_gp(gp, i), default.units = "native")
 				} else {
 					grid.lines(y, x, gp = subset_gp(gp, i), default.units = "native")
 				}
d635156e
 				if(length(add_points) == ncol(value)) {
 					if(add_points[i]) {
 						grid.points(y, x, gp = subset_gp(pt_gp, i), 
 							default.units = "native", pch = pch[i], size = size[i])
 					}
 				} else {
 					if(add_points) {
 						grid.points(y, x, gp = subset_gp(pt_gp, i), 
 							default.units = "native", pch = pch[i], size = size[i])
 					}
402ff791
 				}
 			}
 		} else {
933e808c
 			x = n - seq_along(index) + 1
 			y = value[index]
 			if(smooth) {
 				fit = loess(y ~ x)
131f3219
 				x2 = seq(x[1], x[length(x)], length.out = 100)
933e808c
 				y2 = predict(fit, x2)
 				grid.lines(y2, x2, gp = gp, default.units = "native")
 			} else {
 				grid.lines(y, x, gp = gp, default.units = "native")
 			}
402ff791
 			if(add_points) {
c3ef40cd
 				grid.points(y, x, gp = subset_gp(pt_gp, index), default.units = "native", 
402ff791
 					pch = pch[index], size = size[index])
 			}
 		}
ad35494a
 		if(axis_param$side == "top") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "bottom") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
 
ad35494a
 	column_fun = function(index, k = 1, N = 1) {
402ff791
 		n = length(index)
10fbf31c
 
5ffb507b
 		if(axis_param$direction == "reverse") {
 			value = data_scale[2] - value + data_scale[1]
 		}
 
402ff791
 		pushViewport(viewport(yscale = data_scale, xscale = c(0.5, n+0.5)))
 		if(is.matrix(value)) {
 			for(i in seq_len(ncol(value))) {
933e808c
 				x = seq_along(index)
 				y = value[index, i]
 				if(smooth) {
 					fit = loess(y ~ x)
131f3219
 					x2 = seq(x[1], x[length(x)], length.out = 100)
933e808c
 					y2 = predict(fit, x2)
 					grid.lines(x2, y2, gp = subset_gp(gp, i), default.units = "native")
 				} else {
 					grid.lines(x, y, gp = subset_gp(gp, i), default.units = "native")
 				}
d635156e
 				if(length(add_points) == ncol(value)) {
 					if(add_points[i]) {
 						grid.points(x, y, gp = subset_gp(pt_gp, i), 
 							default.units = "native", pch = pch[i], size = size[i])
 					}
 				} else {
 					if(add_points) {
 						grid.points(x, y, gp = subset_gp(pt_gp, i), 
 							default.units = "native", pch = pch[i], size = size[i])
 					}
402ff791
 				}
 			}
 		} else {
933e808c
 			x = seq_along(index)
 			y = value[index]
 			if(smooth) {
 				fit = loess(y ~ x)
131f3219
 				x2 = seq(x[1], x[length(x)], length.out = 100)
933e808c
 				y2 = predict(fit, x2)
 				grid.lines(x2, y2, gp = gp, default.units = "native")
 			} else {
 				grid.lines(x, y, gp = gp, default.units = "native")
 			}
402ff791
 			if(add_points) {
c3ef40cd
 				grid.points(seq_along(index), value[index], gp = subset_gp(pt_gp, index), default.units = "native", 
402ff791
 					pch = pch[index], size = size[index])
 			}
 		}
ad35494a
 		if(axis_param$side == "left") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "right") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
 
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_points",
 		which = which,
 		width = anno_size$width,
 		height = anno_size$height,
 		n = n,
 		data_scale = data_scale,
933e808c
 		var_import = list(value, gp, border, pch, size, pt_gp, axis, axis_param, 
 			axis_grob, data_scale, add_points, smooth)
402ff791
 	)
 
 	anno@subset_rule$gp = subset_vector
 	if(input_is_matrix) {
 		anno@subset_rule$value = subset_matrix_by_row
a6954cea
 		if(ncol(value) > 1) {
 			anno@subset_rule$gp = NULL
 		}
402ff791
 	} else {
 		anno@subset_rule$value = subset_vector
 		anno@subset_rule$gp = subset_gp
 		anno@subset_rule$pt_gp = subset_gp
 		anno@subset_rule$size = subset_vector
 		anno@subset_rule$pch = subset_vector
 	}
 
8ed806b6
 	anno@subsettable = TRUE
402ff791
 
 	anno@extended = update_anno_extend(anno, axis_grob, axis_param)
 		
 	return(anno) 
 }
 
 # == title
10fbf31c
 # Barplot Annotation
402ff791
 #
 # == param
10fbf31c
 # -x The value vector. The value can be a vector or a matrix. The length of the vector
 #    or the number of rows of the matrix is taken as the number of the observations of the annotation.
 #    If ``x`` is a vector, the barplots will be represented as stacked barplots.
 # -baseline baseline of bars. The value should be "min" or "max", or a numeric value. It is enforced to be zero
402ff791
 #       for stacked barplots.
10fbf31c
 # -which Whether it is a column annotation or a row annotation?
 # -border Wether draw borders of the annotation region?
 # -bar_width Relative width of the bars. The value should be smaller than one.
80d025e2
 # -beside When ``x`` is a matrix, will bars be positioned beside each other or as stacked bars?
8ed806b6
 # -attach When ``beside`` is ``TRUE``, it controls whether bars should be attached.
324ca36c
 # -gp Graphic parameters for bars. The length of each graphic parameter can be 1, length of ``x`` if ``x``
10fbf31c
 #     is a vector, or number of columns of ``x`` is ``x`` is a matrix.
 # -ylim Data ranges. By default it is ``range(x)`` if ``x`` is a vector, or ``range(rowSums(x))`` if ``x`` is a matrix.
 # -extend The extension to both side of ``ylim``. The value is a percent value corresponding to ``ylim[2] - ylim[1]``.
 # -axis Whether to add axis?
 # -axis_param parameters for controlling axis. See `default_axis_param` for all possible settings and default parameters.
00490f9b
 # -add_numbers Whether to add numbers to the bars. It only works when ``x`` is a simple vector.
 # -numbers_gp Graphics parameters for the numbers.
 # -numbers_rot Rotation of numbers.
 # -numbers_offset Offset to the default positions (1mm away the top of the bars).
1a56796e
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
41afe8bf
 # -... Other arguments.
402ff791
 #
 # == value
10fbf31c
 # An annotation function which can be used in `HeatmapAnnotation`.
402ff791
 #
33a5a58f
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#barplot_annotation
 #
10fbf31c
 # == example
 # anno = anno_barplot(1:10)
 # draw(anno, test = "a vector")
402ff791
 #
10fbf31c
 # m = matrix(runif(4*10), nc = 4)
 # m = t(apply(m, 1, function(x) x/sum(x)))
 # anno = anno_barplot(m, gp = gpar(fill = 2:5), bar_width = 1, height = unit(6, "cm"))
 # draw(anno, test = "proportion matrix")
8ed806b6
 anno_barplot = function(x, baseline = 0, which = c("column", "row"), border = TRUE, bar_width = 0.6, 
 	beside = FALSE, attach = FALSE,
402ff791
 	gp = gpar(fill = "#CCCCCC"), ylim = NULL, extend = 0.05, axis = TRUE, 
00490f9b
 	axis_param = default_axis_param(which), 
 	add_numbers = FALSE, numbers_gp = gpar(fontsize = 8), 
 	numbers_rot = ifelse(which == "column", 45, 0), numbers_offset = unit(2, "mm"),
41afe8bf
 	width = NULL, height = NULL, ...) {
 
 	other_args = list(...)
 	if(length(other_args)) {
 		if("axis_gp" %in% names(other_args)) {
 			stop_wrap("`axis_gp` is removed from the arguments. Use `axis_param = list(gp = ...)` instead.")
 		}
 		if("axis_side" %in% names(other_args)) {
 			stop_wrap("`axis_side` is removed from the arguments. Use `axis_param = list(side = ...)` instead.")
 		}
 		if("axis_direction" %in% names(other_args)) {
 			stop_wrap("`axis_direction` is not supported any more.")
 		}
 	}
402ff791
 
 	if(inherits(x, "list")) x = do.call("cbind", x)
 	if(inherits(x, "data.frame")) x = as.matrix(x)
 	if(inherits(x, "matrix")) {
 		sg = apply(x, 1, function(xx) all(sign(xx) %in% c(1, 0)) || all(sign(xx) %in% c(-1, 0)))
 		if(!all(sg)) {
 			stop_wrap("Since `x` is a matrix, the sign of each row should be either all positive or all negative.")
 		}
 	}
e79e87db
 
 	labels_format = attr(x, "labels_format")
402ff791
 	# convert everything to matrix
 	if(is.null(dim(x))) x = matrix(x, ncol = 1)
 	nc = ncol(x)
 	if(missing(gp)) {
131f3219
 		gp = gpar(fill = grey(seq(0, 1, length.out = nc+2))[-c(1, nc+2)])
402ff791
 	}
 
80d025e2
 	if(beside) {
 		data_scale = range(x, na.rm = TRUE)
 	} else {
 		data_scale = range(rowSums(x, na.rm = TRUE), na.rm = TRUE)
 	}
826b321e
 
267a2219
 	if(data_scale[1] == data_scale[2]) data_scale[2] = data_scale[1] + .Machine$double.eps*1.1
826b321e
 
402ff791
 	if(!is.null(ylim)) data_scale = ylim
 	if(baseline == "min") {
 		data_scale = data_scale + c(0, extend)*(data_scale[2] - data_scale[1])
267a2219
 		baseline = min(x, na.rm = TRUE)
402ff791
 	} else if(baseline == "max") {
 		data_scale = data_scale + c(-extend, 0)*(data_scale[2] - data_scale[1])
267a2219
 		baseline = max(x, na.rm = TRUE)
402ff791
 	} else {
699db0a0
 		if(is.numeric(baseline)) {
267a2219
 			if(baseline == 0 && all(abs(rowSums(x, na.rm = TRUE) - 1) < 1e-6) && !beside) {
402ff791
 				data_scale = c(0, 1)
699db0a0
 			} else if(baseline <= data_scale[1]) {
402ff791
 				data_scale = c(baseline, extend*(data_scale[2] - baseline) + data_scale[2])
699db0a0
 			} else if(baseline >= data_scale[2]) {
402ff791
 				data_scale = c(-extend*(baseline - data_scale[1]) + data_scale[1], baseline)
 			} else {
 				data_scale = data_scale + c(-extend, extend)*(data_scale[2] - data_scale[1])
 			}
 		}
 	}
 
04a1dc38
 	ef = function() NULL
402ff791
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
04a1dc38
 		dev.null()
 		ef = dev.off2
402ff791
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
04a1dc38
 	on.exit(ef())
 
402ff791
 	anno_size = anno_width_and_height(which, width, height, unit(1, "cm"))
 
 	if(nc == 1) {
 		gp = recycle_gp(gp, nrow(x))
 	} else  {
 		gp = recycle_gp(gp, nc)
 	}
 
 	value = x
e79e87db
 	attr(value, "labels_format") = labels_format
00490f9b
 
 	if(ncol(value) == 1) {
 		if(add_numbers) {
 			if(which == "column") {
01e3bc5b
 				if(numbers_rot == 0) {
 					extend = convertHeight(max_text_height(value, gp = numbers_gp) + numbers_offset + unit(2, "mm"), "mm", valueOnly = TRUE)/convertHeight(anno_size$height, "mm", valueOnly = TRUE)*(data_scale[2] - data_scale[1])
 				} else {
 					extend = convertHeight(sin(numbers_rot/180*pi)*max_text_width(value, gp = numbers_gp) + numbers_offset + unit(4, "mm"), "mm", valueOnly = TRUE)/convertHeight(anno_size$height, "mm", valueOnly = TRUE)*(data_scale[2] - data_scale[1])
 				}
00490f9b
 				data_scale[2] = data_scale[2] + extend
 			} else if(which == "row") {
 				extend = convertWidth(cos(numbers_rot/180*pi)*max_text_width(value, gp = numbers_gp) + numbers_offset + unit(4, "mm"), "mm", valueOnly = TRUE)/convertWidth(anno_size$width, "mm", valueOnly = TRUE)*(data_scale[2] - data_scale[1])
 				data_scale[2] = data_scale[2] + extend
 			}
 		}
 	}
 
402ff791
 	axis_param = validate_axis_param(axis_param, which)
e79e87db
 	axis_grob = if(axis) construct_axis_grob(axis_param, which, data_scale, format = labels_format) else NULL
402ff791
 
ad35494a
 	row_fun = function(index, k = 1, N = 1) {
402ff791
 		n = length(index)
 		
5ffb507b
 		if(axis_param$direction == "reverse") {
 			value_origin = value
 			value = data_scale[2] - value + data_scale[1]
 			baseline = data_scale[2] - baseline + data_scale[1]
 		}
 
402ff791
 		pushViewport(viewport(xscale = data_scale, yscale = c(0.5, n+0.5)))
 		if(ncol(value) == 1) {
 			width = value[index] - baseline
 			x_coor = width/2+baseline
 			grid.rect(x = x_coor, y = n - seq_along(index) + 1, width = abs(width), height = 1*bar_width, default.units = "native", gp = subset_gp(gp, index))
00490f9b
 			if(add_numbers) {
 				if(axis_param$direction == "normal") {
e79e87db
 					txt = value[index]
 					if(!is.null(attr(value, "labels_format"))) {
 						txt = attr(value, "labels_format")(value[index])
 					}
 					grid.text(txt, x = unit(baseline + width, "native") + numbers_offset, y = n - seq_along(index) + 1, default.units = "native", gp = subset_gp(numbers_gp, index), just = c("left"), rot = numbers_rot)
00490f9b
 				} else {
e79e87db
 					txt = value_origin[index]
 					if(!is.null(attr(value, "labels_format"))) {
acb4f4b2
 						txt = attr(value, "labels_format")(value_origin[index])
e79e87db
 					}
 					grid.text(txt, x = unit(baseline + width, "native") - numbers_offset, y = n - seq_along(index) + 1, default.units = "native", gp = subset_gp(numbers_gp, index), just = c("right"), rot = numbers_rot)
00490f9b
 				}
 			}
402ff791
 		} else {
80d025e2
 			if(beside) {
 				nbar = ncol(value)
 				nr = nrow(value)
 				for(i in seq_along(index)) {
 			        for(j in 1:nbar) {
8ed806b6
 			        	if(attach) {
 			        		if(axis_param$direction == "normal") {
 					        	grid.rect(x = baseline, y = nr-i+0.5 + (1-bar_width)/2 + (nbar - j + 0.5)/nbar*bar_width, width = value[index[i], j], height = 1/nbar*bar_width, just = c("left"),
 					        		default.units = "native", gp = subset_gp(gp, j))
 					        } else {
 					        	grid.rect(x = baseline, y = nr-i+0.5 + (1-bar_width)/2 + (nbar - j + 0.5)/nbar*bar_width, width = value[index[i], j], height = 1/nbar*bar_width, just = c("right"),
 					        		default.units = "native", gp = subset_gp(gp, j))
 					        }
 			        	} else {
 				        	if(axis_param$direction == "normal") {
 					        	grid.rect(x = baseline, y = nr-i+0.5 + (nbar - j + 0.5)/nbar, width = value[index[i], j], height = 1/nbar*bar_width, just = c("left"),
 					        		default.units = "native", gp = subset_gp(gp, j))
 					        } else {
 					        	grid.rect(x = baseline, y = nr-i+0.5 + (nbar - j + 0.5)/nbar, width = value[index[i], j], height = 1/nbar*bar_width, just = c("right"),
 					        		default.units = "native", gp = subset_gp(gp, j))
 					        }
 					    }
80d025e2
 			        }
 			    }
 			} else {
 				for(i in seq_len(ncol(value))) {
 					if(axis_param$direction == "normal") {
 						width = abs(value[index, i])
 						x_coor = rowSums(value[index, seq_len(i-1), drop = FALSE]) + width/2
 						grid.rect(x = x_coor, y = n - seq_along(index) + 1, width = abs(width), height = 1*bar_width, default.units = "native", gp = subset_gp(gp, i))
 					} else {
 						width = value_origin[index, i] # the original width
 						x_coor = rowSums(value_origin[index, seq_len(i-1), drop = FALSE]) + width/2 #distance to the right
 						x_coor = data_scale[2] - x_coor + data_scale[1]
 						grid.rect(x = x_coor, y = n - seq_along(index) + 1, width = abs(width), height = 1*bar_width, default.units = "native", gp = subset_gp(gp, i))
 					}
5ffb507b
 				}
402ff791
 			}
 		}
ad35494a
 		if(axis_param$side == "top") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "bottom") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
ad35494a
 	column_fun = function(index, k = 1, N = 1) {
402ff791
 		n = length(index)
5ffb507b
 		
 		if(axis_param$direction == "reverse") {
 			value_origin = value
 			value = data_scale[2] - value + data_scale[1]
 			baseline = data_scale[2] - baseline + data_scale[1]
 		}
 
402ff791
 		pushViewport(viewport(yscale = data_scale, xscale = c(0.5, n+0.5)))
 		if(ncol(value) == 1) {
 			height = value[index] - baseline
 			y_coor = height/2+baseline
 			grid.rect(y = y_coor, x = seq_along(index), height = abs(height), width = 1*bar_width, default.units = "native", gp = subset_gp(gp, index))
00490f9b
 			if(add_numbers) {
e79e87db
 				txt = value[index]
 				if(!is.null(attr(value, "labels_format"))) {
 					txt = attr(value, "labels_format")(value[index])
 				}
3e2efb6e
 				numbers_rot = numbers_rot %% 360
01e3bc5b
 				if(numbers_rot == 0) {
 					grid.text(txt, x = seq_along(index), y = unit(baseline + height, "native") + numbers_offset, default.units = "native", gp = subset_gp(numbers_gp, index), just = c("bottom"))
 				} else {
 					grid.text(txt, x = seq_along(index), y = unit(baseline + height, "native") + numbers_offset, default.units = "native", gp = subset_gp(numbers_gp, index), just = c("left"), rot = numbers_rot)
 				}
00490f9b
 			}
402ff791
 		} else {
80d025e2
 			if(beside) {
 				nbar = ncol(value)
 				nr = nrow(value)
 				for(i in seq_along(index)) {
 			        for(j in 1:nbar) {
8ed806b6
 			        	if(attach) {
 				        	if(axis_param$direction == "normal") {
 					        	grid.rect(y = baseline, x = i-0.5 + (1-bar_width)/2 + (j-0.5)/nbar*bar_width, height = value[index[i], j], width = 1/nbar*bar_width, just = c("bottom"),
 					        		default.units = "native", gp = subset_gp(gp, j))
 					        } else {
 					        	grid.rect(y = baseline, x = i-0.5 + (1-bar_width)/2 + (j-0.5)/nbar*bar_width, height = value[index[i], j], width = 1/nbar*bar_width, just = c("top"),
 					        		default.units = "native", gp = subset_gp(gp, j))
 					        }
 					    } else {
 					    	if(axis_param$direction == "normal") {
 					        	grid.rect(y = baseline, x = i-0.5 + (j-0.5)/nbar, height = value[index[i], j], width = 1/nbar*bar_width, just = c("bottom"),
 					        		default.units = "native", gp = subset_gp(gp, j))
 					        } else {
 					        	grid.rect(y = baseline, x = i-0.5 + (j-0.5)/nbar, height = value[index[i], j], width = 1/nbar*bar_width, just = c("top"),
 					        		default.units = "native", gp = subset_gp(gp, j))
 					        }
 					    }
80d025e2
 			        }
 			    }
 			} else {
 				for(i in seq_len(ncol(value))) {
 					if(axis_param$direction == "normal") {
 						height = value[index, i]
 						y_coor = rowSums(value[index, seq_len(i-1), drop = FALSE]) + height/2
 						grid.rect(y = y_coor, x = seq_along(index), height = abs(height), width = 1*bar_width, default.units = "native", gp = subset_gp(gp, i))
 					} else {
 						height = value_origin[index, i]
 						y_coor = rowSums(value_origin[index, seq_len(i-1), drop = FALSE]) + height/2
 						y_coor = data_scale[2] - y_coor + data_scale[1]
 						grid.rect(y = y_coor, x = seq_along(index), height = abs(height), width = 1*bar_width, default.units = "native", gp = subset_gp(gp, i))
 					}
5ffb507b
 				}
402ff791
 			}
 		}
ad35494a
 		if(axis_param$side == "left") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "right") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
 	
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 	n = nrow(value)
 
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_barplot",
 		which = which,
 		width = anno_size$width,
 		height = anno_size$height,
 		n = n,
 		data_scale = data_scale,
8ed806b6
 		var_import = list(value, gp, border, bar_width, baseline, beside, attach, axis, axis_param, axis_grob, data_scale, add_numbers, numbers_gp, numbers_offset, numbers_rot)
402ff791
 	)
 
 	anno@subset_rule$value = subset_matrix_by_row
 	if(ncol(value) == 1) {
 		anno@subset_rule$gp = subset_gp
 	}
 		
8ed806b6
 	anno@subsettable = TRUE
402ff791
 
 	anno@extended = update_anno_extend(anno, axis_grob, axis_param)
 
 	return(anno) 
 }
 
 # == title
10fbf31c
 # Boxplot Annotation
402ff791
 #
 # == param
10fbf31c
 # -x A matrix or a list. If ``x`` is a matrix and if ``which`` is ``column``, statistics for boxplots
 #    are calculated by columns, if ``which`` is ``row``, the calculation is done by rows.
 # -which Whether it is a column annotation or a row annotation?
 # -border Wether draw borders of the annotation region?
 # -gp Graphic parameters for the boxes. The length of the graphic parameters should be one or the number of observations.
 # -ylim Data ranges.
 # -extend The extension to both side of ``ylim``. The value is a percent value corresponding to ``ylim[2] - ylim[1]``.
 # -outline Whether draw outline of boxplots?
 # -box_width Relative width of boxes. The value should be smaller than one.
 # -pch Point style.
 # -size Point size.
 # -axis Whether to add axis?
 # -axis_param parameters for controlling axis. See `default_axis_param` for all possible settings and default parameters.
1a56796e
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
41afe8bf
 # -... Other arguments.
402ff791
 #
 # == value
10fbf31c
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
33a5a58f
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#box-annotation
 #
10fbf31c
 # == example
 # set.seed(123)
 # m = matrix(rnorm(100), 10)
 # anno = anno_boxplot(m, height = unit(4, "cm"))
 # draw(anno, test = "anno_boxplot")
 # anno = anno_boxplot(m, height = unit(4, "cm"), gp = gpar(fill = 1:10))
 # draw(anno, test = "anno_boxplot with gp")
402ff791
 anno_boxplot = function(x, which = c("column", "row"), border = TRUE,
 	gp = gpar(fill = "#CCCCCC"), ylim = NULL, extend = 0.05, outline = TRUE, box_width = 0.6,
 	pch = 1, size = unit(2, "mm"), axis = TRUE, axis_param = default_axis_param(which),
41afe8bf
 	width = NULL, height = NULL, ...) {
 
 	other_args = list(...)
 	if(length(other_args)) {
 		if("axis_gp" %in% names(other_args)) {
 			stop_wrap("`axis_gp` is removed from the arguments. Use `axis_param = list(gp = ...)` instead.")
 		}
 		if("axis_side" %in% names(other_args)) {
 			stop_wrap("`axis_side` is removed from the arguments. Use `axis_param = list(side = ...)` instead.")
 		}
 		if("axis_direction" %in% names(other_args)) {
 			stop_wrap("`axis_direction` is not supported any more.")
 		}
 	}
402ff791
 
04a1dc38
 	ef = function() NULL
402ff791
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
04a1dc38
 		dev.null()
 		ef = dev.off2
402ff791
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
04a1dc38
 	on.exit(ef())
 
402ff791
 	anno_size = anno_width_and_height(which, width, height, unit(2, "cm"))
 
 	## convert matrix all to list (or data frame)
 	if(is.matrix(x)) {
 		if(which == "column") {
 			value = as.data.frame(x)
 		} else if(which == "row") {
 			value = as.data.frame(t(x))
 		}
 	} else {
 		value = x
 	}
 
 	if(is.null(ylim)) {
 		if(!outline) {
 			boxplot_stats = boxplot(value, plot = FALSE)$stats
 			data_scale = range(boxplot_stats)
 		} else {
 			data_scale = range(value, na.rm = TRUE)
 		}
 	} else {
 		data_scale = ylim
 	}
826b321e
 	if(data_scale[1] == data_scale[2]) data_scale[2] = data_scale[1] + 1
402ff791
 	data_scale = data_scale + c(-extend, extend)*(data_scale[2] - data_scale[1])
 
 	n = length(value)
 	gp = recycle_gp(gp, n)
 	if(length(pch) == 1) pch = rep(pch, n)
 	if(length(size) == 1) size = rep(size, n)
 
 	axis_param = validate_axis_param(axis_param, which)
 	axis_grob = if(axis) construct_axis_grob(axis_param, which, data_scale) else NULL
 
ad35494a
 	row_fun = function(index, k = 1, N = 1) {
402ff791
 
5ffb507b
 		if(axis_param$direction == "reverse") {
 			value = lapply(value, function(x) data_scale[2] - x + data_scale[1])
 		}
 
402ff791
 		n_all = length(value)
 		value = value[index]
 		boxplot_stats = boxplot(value, plot = FALSE)$stats
 		
 		n = length(index)
 		gp = subset_gp(gp, index)
c3ef40cd
 		pch = pch[index]
 		size = size[index]
402ff791
 		pushViewport(viewport(xscale = data_scale, yscale = c(0.5, n+0.5)))
 		
 		grid.rect(x = boxplot_stats[2, ], y = n - seq_along(index) + 1,  
 			height = 1*box_width, width = boxplot_stats[4, ] - boxplot_stats[2, ], just = "left", 
 			default.units = "native", gp = gp)
 
 		grid.segments(boxplot_stats[5, ], n - seq_along(index) + 1 - 0.5*box_width, 
 			          boxplot_stats[5, ], n - seq_along(index) + 1 + 0.5*box_width, 
 			          default.units = "native", gp = gp)
 		grid.segments(boxplot_stats[5, ], n - seq_along(index) + 1,
 			          boxplot_stats[4, ], n - seq_along(index) + 1, 
 			          default.units = "native", gp = gp)
 		grid.segments(boxplot_stats[1, ], n - seq_along(index) + 1, 
 			          boxplot_stats[2, ], n - seq_along(index) + 1, 
 			          default.units = "native", gp = gp)
 		grid.segments(boxplot_stats[1, ], n - seq_along(index) + 1 - 0.5*box_width, 
 			          boxplot_stats[1, ], n - seq_along(index) + 1 + 0.5*box_width, 
 			          default.units = "native", gp = gp)
 		grid.segments(boxplot_stats[3, ], n - seq_along(index) + 1 - 0.5*box_width, 
 			          boxplot_stats[3, ], n - seq_along(index) + 1 + 0.5*box_width, 
 			          default.units = "native", gp = gp)
 		if(outline) {
 			for(i in seq_along(value)) {
 				l1 = value[[i]] > boxplot_stats[5,i]
daedc41b
 				l1[is.na(l1)] = FALSE
402ff791
 				if(sum(l1)) grid.points(y = rep(n - i + 1, sum(l1)), x = value[[i]][l1], 
 					default.units = "native", gp = subset_gp(gp, i), pch = pch[i], size = size[i])
 				l2 = value[[i]] < boxplot_stats[1,i]
daedc41b
 				l2[is.na(l2)] = FALSE
402ff791
 				if(sum(l2)) grid.points(y = rep(n - i + 1, sum(l2)), x = value[[i]][l2], 
 					default.units = "native", gp = subset_gp(gp, i), pch = pch[i], size = size[i])
 			}
 		}
ad35494a
 		if(axis_param$side == "top") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "bottom") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
ad35494a
 	column_fun = function(index, k = 1, N = 1) {
5ffb507b
 
 		if(axis_param$direction == "reverse") {
 			value = lapply(value, function(x) data_scale[2] - x + data_scale[1])
 		}
 
402ff791
 		value = value[index]
 		boxplot_stats = boxplot(value, plot = FALSE)$stats
 
 		n = length(index)
 		gp = subset_gp(gp, index)
c3ef40cd
 		pch = pch[index]
 		size = size[index]
402ff791
 		pushViewport(viewport(xscale = c(0.5, n+0.5), yscale = data_scale))
 		grid.rect(x = seq_along(index), y = boxplot_stats[2, ], 
 			height = boxplot_stats[4, ] - boxplot_stats[2, ], width = 1*box_width, just = "bottom", 
 			default.units = "native", gp = gp)
 		
 		grid.segments(seq_along(index) - 0.5*box_width, boxplot_stats[5, ],
 			          seq_along(index) + 0.5*box_width, boxplot_stats[5, ], 
 			          default.units = "native", gp = gp)
 		grid.segments(seq_along(index), boxplot_stats[5, ],
 			          seq_along(index), boxplot_stats[4, ], 
 			          default.units = "native", gp = gp)
 		grid.segments(seq_along(index), boxplot_stats[1, ],
 			          seq_along(index), boxplot_stats[2, ], 
 			          default.units = "native", gp = gp)
 		grid.segments(seq_along(index) - 0.5*box_width, boxplot_stats[1, ],
 			          seq_along(index) + 0.5*box_width, boxplot_stats[1, ], 
 			          default.units = "native", gp = gp)
 		grid.segments(seq_along(index) - 0.5*box_width, boxplot_stats[3, ],
 			          seq_along(index) + 0.5*box_width, boxplot_stats[3, ], 
 			          default.units = "native", gp = gp)
 		if(outline) {	
 			for(i in seq_along(value)) {
 				l1 = value[[i]] > boxplot_stats[5,i]
daedc41b
 				l1[is.na(l1)] = FALSE
402ff791
 				if(sum(l1)) grid.points(x = rep(i, sum(l1)), y = value[[i]][l1], 
 					default.units = "native", gp = subset_gp(gp, i), pch = pch[i], size = size[i])
 				l2 = value[[i]] < boxplot_stats[1,i]
daedc41b
 				l2[is.na(l2)] = FALSE
402ff791
 				if(sum(l2)) grid.points(x = rep(i, sum(l2)), y = value[[i]][l2], 
 					default.units = "native", gp = subset_gp(gp, i), pch = pch[i], size = size[i])
 			}
 		}
ad35494a
 		if(axis_param$side == "left") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "right") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
 	
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_boxplot",
 		which = which,
 		n = n,
 		width = anno_size$width,
 		height = anno_size$height,
 		data_scale = data_scale,
 		var_import = list(value, gp, border, box_width, axis, axis_param, axis_grob, data_scale, pch, size, outline)
 	)
 
 	anno@subset_rule$value = subset_vector
 	anno@subset_rule$gp = subset_gp
 	anno@subset_rule$pch = subset_vector
 	anno@subset_rule$size = subset_vector
 	
8ed806b6
 	anno@subsettable = TRUE
402ff791
 
 	anno@extended = update_anno_extend(anno, axis_grob, axis_param)
 
 	return(anno) 
 }
 
 # == title
10fbf31c
 # Histogram Annotation
402ff791
 #
 # == param
10fbf31c
 # -x A matrix or a list. If ``x`` is a matrix and if ``which`` is ``column``, statistics for boxplots
 #    are calculated by columns, if ``which`` is ``row``, the calculation is done by rows.
 # -which Whether it is a column annotation or a row annotation?
 # -n_breaks Number of breaks for calculating histogram.
 # -border Wether draw borders of the annotation region?
 # -gp Graphic parameters for the boxes. The length of the graphic parameters should be one or the number of observations.
 # -axis Whether to add axis?
 # -axis_param parameters for controlling axis. See `default_axis_param` for all possible settings and default parameters.
1a56796e
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
402ff791
 #
 # == value
10fbf31c
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
33a5a58f
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#histogram-annotation
 #
10fbf31c
 # == example
 # m = matrix(rnorm(1000), nc = 10)
 # anno = anno_histogram(t(m), which = "row")
 # draw(anno, test = "row histogram")
 # anno = anno_histogram(t(m), which = "row", gp = gpar(fill = 1:10))
 # draw(anno, test = "row histogram with color")
 # anno = anno_histogram(t(m), which = "row", n_breaks = 20)
 # draw(anno, test = "row histogram with color")
402ff791
 anno_histogram = function(x, which = c("column", "row"), n_breaks = 11, 
 	border = FALSE, gp = gpar(fill = "#CCCCCC"), 
 	axis = TRUE, axis_param = default_axis_param(which), 
 	width = NULL, height = NULL) {
 	
04a1dc38
 	ef = function() NULL
402ff791
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
04a1dc38
 		dev.null()
 		ef = dev.off2
402ff791
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
04a1dc38
 	on.exit(ef())
 
402ff791
 	anno_size = anno_width_and_height(which, width, height, unit(4, "cm"))
 
 	## convert matrix all to list (or data frame)
 	if(is.matrix(x)) {
 		if(which == "column") {
 			value = as.data.frame(x)
 		} else if(which == "row") {
 			value = as.data.frame(t(x))
 		}
 	} else {
 		value = x
 	}
 
 	n = length(value)
 	x_range =range(unlist(value), na.rm = TRUE)
131f3219
 	histogram_stats = lapply(value, hist, plot = FALSE, breaks = seq(x_range[1], x_range[2], length.out = n_breaks))
402ff791
 	histogram_breaks = lapply(histogram_stats, function(x) x$breaks)
 	histogram_counts = lapply(histogram_stats, function(x) x$counts)
 
 	xscale = range(unlist(histogram_breaks), na.rm = TRUE)
97eef737
 	xscale = xscale + c(-0.025, 0.025)*(xscale[2] - xscale[1])
402ff791
 	yscale = c(0, max(unlist(histogram_counts)))
 	yscale[2] = yscale[2]*1.05
 
 	gp = recycle_gp(gp, n)
529f6dba
 	axis_param$direction = "normal"
402ff791
 	axis_param = validate_axis_param(axis_param, which)
 	axis_grob = if(axis) construct_axis_grob(axis_param, which, xscale) else NULL
 
ad35494a
 	row_fun = function(index, k = 1, N = 1) {
5ffb507b
 
402ff791
 		n_all = length(value)
 		value = value[index]
 		
 		n = length(index)
 		histogram_breaks = histogram_breaks[index]
 		histogram_counts = histogram_counts[index]
 
 		gp = subset_gp(gp, index)
 		for(i in seq_len(n)) {
 			n_breaks = length(histogram_breaks[[i]])
 			pushViewport(viewport(x = unit(0, "npc"), y = unit((n-i)/n, "npc"), height = unit(1/n, "npc"), just = c("left", "bottom"), xscale = xscale, yscale = yscale))
 			grid.rect(x = histogram_breaks[[i]][-1], y = 0, width = histogram_breaks[[i]][-1] - histogram_breaks[[i]][-n_breaks], height = histogram_counts[[i]], just = c("right", "bottom"), default.units = "native", gp = subset_gp(gp, i))	
 			popViewport()
 		}
 		pushViewport(viewport(xscale = xscale))
ad35494a
 		if(axis_param$side == "top") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "bottom") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
ad35494a
 	column_fun = function(index, k = 1, N = 1) {
5ffb507b
 
402ff791
 		n_all = length(value)
 		value = value[index]
 		
 		foo = yscale
 		yscale = xscale
 		xscale = foo
 		histogram_breaks = histogram_breaks[index]
 		histogram_counts = histogram_counts[index]
 
 		n = length(index)
 		
 		gp = subset_gp(gp, index)
 		for(i in seq_len(n)) {
 			n_breaks = length(histogram_breaks[[i]])
 			pushViewport(viewport(y = unit(0, "npc"), x = unit(i/n, "npc"), width = unit(1/n, "npc"), 
 				just = c("right", "bottom"), xscale = xscale, yscale = yscale))
 			grid.rect(y = histogram_breaks[[i]][-1], x = 0, height = histogram_breaks[[i]][-1] - histogram_breaks[[i]][-n_breaks], 
c3ef40cd
 				width = histogram_counts[[i]], just = c("left", "top"), default.units = "native", gp = subset_gp(gp, i))	
402ff791
 			popViewport()
 		}
 		pushViewport(viewport(yscale = yscale))
ad35494a
 		if(axis_param$side == "left") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "right") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
 	
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_histogram",
 		which = which,
 		width = anno_size$width,
 		height = anno_size$height,
 		n = n,
 		data_scale = xscale,
 		var_import = list(value, gp, border, axis, axis_param, axis_grob, xscale, yscale,
 			histogram_breaks, histogram_counts)
 	)
 
 	anno@subset_rule$value = subset_vector
 	anno@subset_rule$gp = subset_gp
 	anno@subset_rule$histogram_breaks = subset_vector
 	anno@subset_rule$histogram_counts = subset_vector
 	
8ed806b6
 	anno@subsettable = TRUE
402ff791
 
 	anno@extended = update_anno_extend(anno, axis_grob, axis_param)
 
 	return(anno) 
 }
 
 # == title
10fbf31c
 # Density Annotation
402ff791
 #
 # == param
10fbf31c
 # -x A matrix or a list. If ``x`` is a matrix and if ``which`` is ``column``, statistics for boxplots
 #    are calculated by columns, if ``which`` is ``row``, the calculation is done by rows.
 # -which Whether it is a column annotation or a row annotation?
 # -type Type of graphics to represent density distribution. "lines" for normal density plot; "violine" for violin plot
 #       and "heatmap" for heatmap visualization of density distribution.
97eef737
 # -xlim Range on x-axis.
c94e524a
 # -max_density Maximal density values in the plot. Normally you don't need to manually set it, but when you have multiple density annotations
 #   and you want to compare between them, you should manually set this argument to make density distributions are in a same scale.
10fbf31c
 # -heatmap_colors A vector of colors for interpolating density values.
 # -joyplot_scale Relative height of density distribution. A value higher than 1 increases the height of the density
 #               distribution and the plot will represented as so-called "joyplot".
 # -border Wether draw borders of the annotation region?
 # -gp Graphic parameters for the boxes. The length of the graphic parameters should be one or the number of observations.
 # -axis Whether to add axis?
 # -axis_param parameters for controlling axis. See `default_axis_param` for all possible settings and default parameters.
1a56796e
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
402ff791
 #
 # == value
10fbf31c
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
33a5a58f
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#density-annotation
 #
10fbf31c
 # == example
ad35494a
 # m = matrix(rnorm(100), 10)
 # anno = anno_density(m, which = "row")
10fbf31c
 # draw(anno, test = "normal density")
ad35494a
 # anno = anno_density(m, which = "row", type = "violin")
10fbf31c
 # draw(anno, test = "violin")
ad35494a
 # anno = anno_density(m, which = "row", type = "heatmap")
10fbf31c
 # draw(anno, test = "heatmap")
ad35494a
 # anno = anno_density(m, which = "row", type = "heatmap", 
10fbf31c
 #     heatmap_colors = c("white", "orange"))
 # draw(anno, test = "heatmap, colors")
 anno_density = function(x, which = c("column", "row"),
c94e524a
 	type = c("lines", "violin", "heatmap"), xlim = NULL, max_density = NULL,
402ff791
 	heatmap_colors = rev(brewer.pal(name = "RdYlBu", n = 11)), 
10fbf31c
 	joyplot_scale = 1, border = TRUE, gp = gpar(fill = "#CCCCCC"),
402ff791
 	axis = TRUE, axis_param = default_axis_param(which),
 	width = NULL, height = NULL) {
 	
04a1dc38
 	ef = function() NULL
402ff791
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
04a1dc38
 		dev.null()
 		ef = dev.off2
402ff791
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
04a1dc38
 	on.exit(ef())
 
402ff791
 	anno_size = anno_width_and_height(which, width, height, unit(4, "cm"))
 
 	## convert matrix all to list (or data frame)
 	if(is.matrix(x)) {
 		if(which == "column") {
 			value = as.data.frame(x)
 		} else if(which == "row") {
 			value = as.data.frame(t(x))
 		}
 	} else {
 		value = x
 	}
 
 	n = length(value)
 	gp = recycle_gp(gp, n)
 	type = match.arg(type)[1]
 
 	n_all = length(value)
daedc41b
 	density_stats = lapply(value, density, na.rm = TRUE)
402ff791
 	density_x = lapply(density_stats, function(x) x$x)
 	density_y = lapply(density_stats, function(x) x$y)
 	
 	min_density_x = min(unlist(density_x))
 	max_density_x = max(unlist(density_x))
 	
97eef737
 	if(is.null(xlim)) {
 		xscale = range(unlist(density_x), na.rm = TRUE)
 	} else {
 		xscale = xlim
 		for(i in seq_len(n)) {
 			l = density_x[[i]] >= xscale[1] & density_x[[i]] <= xscale[2]
 			density_x[[i]] = density_x[[i]][l]
 			density_y[[i]] = density_y[[i]][l]
 
 			density_x[[i]] = c(density_x[[i]][ 1 ], density_x[[i]], density_x[[i]][ length(density_x[[i]]) ])
 			density_y[[i]] = c(0, density_y[[i]], 0)
 		}
 	}
d635156e
 	
402ff791
 	if(type == "lines") {
d635156e
 		xscale = xscale + c(-0.025, 0.025)*(xscale[2] - xscale[1])
c94e524a
 		if(is.null(max_density)) {
 			yscale = c(0, max(unlist(density_y)))
 		} else {
 			yscale = c(0, max_density)
 		}
402ff791
 		yscale[2] = yscale[2]*1.05
 	} else if(type == "violin") {
d635156e
 		xscale = xscale + c(-0.025, 0.025)*(xscale[2] - xscale[1])
c94e524a
 		if(is.null(max_density)) {
 			yscale = max(unlist(density_y))
 		} else {
 			yscale = max_density
 		}
402ff791
 		yscale = c(-yscale*1.05, yscale*1.05)
 	} else if(type == "heatmap") {
 		yscale = c(0, 1)
c94e524a
 		if(is.null(max_density)) {
 			min_y = min(unlist(density_y))
 			max_y = max(unlist(density_y))
 		} else {
 			min_y = 0
 			max_y = max_density
 		}
402ff791
 		col_fun = colorRamp2(seq(min_y, max_y, 
131f3219
 			length.out = length(heatmap_colors)), heatmap_colors)
402ff791
 	}
 
529f6dba
 	axis_param$direction = "normal"
402ff791
 	axis_param = validate_axis_param(axis_param, which)
 	axis_grob = if(axis) construct_axis_grob(axis_param, which, xscale) else NULL
 
ad35494a
 	row_fun = function(index, k = 1, N = 1) {
402ff791
 		
 		n = length(index)
 		value = value[index]
 		
 		gp = subset_gp(gp, index)
 		density_x = density_x[index]
 		density_y = density_y[index]
 
 		for(i in seq_len(n)) {
 			pushViewport(viewport(x = unit(0, "npc"), y = unit((n-i)/n, "npc"), 
 				just = c("left", "bottom"), height = unit(1/n, "npc"), xscale = xscale, 
 				yscale = yscale))
 			if(type == "lines") {
 				grid.polygon(x = density_x[[i]], y = density_y[[i]]*joyplot_scale, 
 					default.units = "native", gp = subset_gp(gp, i))
 			} else if(type == "violin") {
 				grid.polygon(x = c(density_x[[i]], rev(density_x[[i]])), 
 					y = c(density_y[[i]], -rev(density_y[[i]])), default.units = "native", 
 					gp = subset_gp(gp, i))
 				box_stat = boxplot(value[[i]], plot = FALSE)$stat
 				grid.lines(box_stat[1:2, 1], c(0, 0), default.units = "native", 
 					gp = subset_gp(gp, i))
 				grid.lines(box_stat[4:5, 1], c(0, 0), default.units = "native", 
 					gp = subset_gp(gp, i))
 				grid.points(box_stat[3, 1], 0, default.units = "native", pch = 3, 
 					size = unit(1, "mm"), gp = subset_gp(gp, i))
 			} else if(type == "heatmap") {
 				n_breaks = length(density_x[[i]])
 				grid.rect(x = density_x[[i]][-1], y = 0, 
 					width = density_x[[i]][-1] - density_x[[i]][-n_breaks], height = 1, 
 					just = c("right", "bottom"), default.units = "native", 
 					gp = gpar(fill = col_fun((density_y[[i]][-1] + density_y[[i]][-n_breaks])/2), 
 						col = NA))
 				grid.rect(x = density_x[[i]][1], y = 0, width = density_x[[i]][1] - min_density_x, 
 					height = 1, just = c("right", "bottom"), default.units = "native", 
 					gp = gpar(fill = col_fun(0), col = NA))
 				grid.rect(x = density_x[[i]][n_breaks], y = 0, 
 					width = max_density_x - density_x[[i]][n_breaks], height = 1, 
 					just = c("left", "bottom"), default.units = "native", 
 					gp = gpar(fill = col_fun(0), col = NA))
 			}
 			popViewport()
 		}
 		pushViewport(viewport(xscale = xscale))
ad35494a
 		if(axis_param$side == "top") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "bottom") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
ad35494a
 	column_fun = function(index, k = 1, N = 1) {
402ff791
 
 		n_all = length(value)
 		value = value[index]
 		
 		foo = yscale
 		yscale = xscale
 		xscale = foo
 
 		density_x = density_x[index]
 		density_y = density_y[index]
 		
 		yscale = range(unlist(density_x), na.rm = TRUE)
 		yscale = yscale + c(0, 0.05)*(yscale[2] - yscale[1])
 		if(type == "lines") {
 			xscale = c(0, max(unlist(density_y)))
 			xscale[2] = xscale[2]*1.05
 		} else if(type == "violin") {
 			xscale = max(unlist(density_y))
 			xscale = c(-xscale*1.05, xscale*1.05)
 		} else if(type == "heatmap") {
 			yscale = range(unlist(density_x), na.rm = TRUE)
 			xscale = c(0, 1)
 			min_y = min(unlist(density_y))
 			max_y = max(unlist(density_y))
 			col_fun = colorRamp2(seq(min_y, max_y, 
131f3219
 				length.out = length(heatmap_colors)), heatmap_colors)
402ff791
 		}
 
 		n = length(index)
 		gp = subset_gp(gp, index)
 
 		for(i in rev(seq_len(n))) {
 			pushViewport(viewport(y = unit(0, "npc"), x = unit(i/n, "npc"), width = unit(1/n, "npc"), 
 				just = c("right", "bottom"), xscale = xscale, yscale = yscale))
 			if(type == "lines") {
 				grid.polygon(y = density_x[[i]], x = density_y[[i]]*joyplot_scale, 
c3ef40cd
 					default.units = "native", gp = subset_gp(gp, i))
402ff791
 			} else if(type == "violin") {
 				grid.polygon(y = c(density_x[[i]], rev(density_x[[i]])), 
 					x = c(density_y[[i]], -rev(density_y[[i]])), default.units = "native", 
c3ef40cd
 					gp = subset_gp(gp, i))
402ff791
 				box_stat = boxplot(value[[i]], plot = FALSE)$stat
 				grid.lines(y = box_stat[1:2, 1], x = c(0, 0), default.units = "native", 
 					gp = subset_gp(gp, i))
 				grid.lines(y = box_stat[4:5, 1], x = c(0, 0), default.units = "native", 
 					gp = subset_gp(gp, i))
 				grid.points(y = box_stat[3, 1], x = 0, default.units = "native", pch = 3, 
 					size = unit(1, "mm"), gp = subset_gp(gp, i))	
 			} else if(type == "heatmap") {
 				n_breaks = length(density_x[[i]])
 				grid.rect(y = density_x[[i]][-1], x = 0, 
 					height = density_x[[i]][-1] - density_x[[i]][-n_breaks], width = 1, 
 					just = c("left", "top"), default.units = "native", 
 					gp = gpar(fill = col_fun((density_y[[i]][-1] + density_y[[i]][-n_breaks])/2), 
 						col = NA))
 				grid.rect(y = density_x[[i]][1], x = 0, height = density_x[[i]][1] - min_density_x, 
 					width = 1, just = c("left", "top"), default.units = "native", 
 					gp = gpar(fill = col_fun(0), col = NA))
 				grid.rect(y = density_x[[i]][n_breaks], x = 0, 
 					height = max_density_x - density_x[[i]][n_breaks], width = 1, 
 					just = c("left", "bottom"), default.units = "native", 
 					gp = gpar(fill = col_fun(0), col = NA))
 			}
 			popViewport()
 		}
 		pushViewport(viewport(yscale = yscale))
ad35494a
 		if(axis_param$side == "left") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "right") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
 	
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_density",
 		which = which,
 		width = anno_size$width,
 		height = anno_size$height,
 		n = n,
 		data_scale = xscale,
 		var_import = list(value, gp, border, type, axis, axis_param, axis_grob, xscale, yscale, density_x,
ad35494a
 			density_y, min_density_x, max_density_x, joyplot_scale, heatmap_colors)
402ff791
 	)
 
 	if(type == "heatmap") {
 		anno@var_env$col_fun = col_fun
 	}
 
 	anno@subset_rule$value = subset_vector
 	anno@subset_rule$gp = subset_gp
 	anno@subset_rule$density_x = subset_vector
 	anno@subset_rule$density_y = subset_vector
 	
8ed806b6
 	anno@subsettable = TRUE
402ff791
 
 	anno@extended = update_anno_extend(anno, axis_grob, axis_param)
 
 	return(anno)
 }
 
 # == title
10fbf31c
 # Text Annotation
402ff791
 #
 # == param
10fbf31c
 # -x A vector of text.
 # -which Whether it is a column annotation or a row annotation?
 # -gp Graphic parameters.
 # -rot Rotation of the text, pass to `grid::grid.text`.
 # -just Justification of text, pass to `grid::grid.text`.
 # -offset Depracated, use ``location`` instead.
 # -location Position of the text. By default ``rot``, ``just`` and ``location`` are automatically
 #           inferred according to whether it is a row annotation or column annotation. The value
 #           of ``location`` should be a `grid::unit` object, normally in ``npc`` unit. E.g. ``unit(0, 'npc')``
 #           means the most left of the annotation region and ``unit(1, 'npc')`` means the most right of
 #           the annotation region. 
1a56796e
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
fc048989
 # -show_name Whether to show the annotation name.
402ff791
 #
 # == value
10fbf31c
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
33a5a58f
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#text-annotation
 #
10fbf31c
 # == example
 # anno = anno_text(month.name)
 # draw(anno, test = "month names")
 # anno = anno_text(month.name, gp = gpar(fontsize = 16))
 # draw(anno, test = "month names with fontsize")
 # anno = anno_text(month.name, gp = gpar(fontsize = 1:12+4))
 # draw(anno, test = "month names with changing fontsize")
 # anno = anno_text(month.name, which = "row")
 # draw(anno, test = "month names on rows")
 # anno = anno_text(month.name, location = 0, rot = 45, 
 #     just = "left", gp = gpar(col = 1:12))
 # draw(anno, test = "with rotations")
 # anno = anno_text(month.name, location = 1, 
 #     rot = 45, just = "right", gp = gpar(fontsize = 1:12+4))
 # draw(anno, test = "with rotations")
402ff791
 anno_text = function(x, which = c("column", "row"), gp = gpar(), 
 	rot = guess_rot(), just = guess_just(), 
 	offset = guess_location(), location = guess_location(),
fc048989
 	width = NULL, height = NULL, show_name = FALSE) {
5b47a845
 
04a1dc38
 	ef = function() NULL
ad35494a
 	if(is.null(.ENV$current_annotation_which)) {
402ff791
 		which = match.arg(which)[1]
04a1dc38
 		dev.null()
 		ef = dev.off2
ad35494a
 	} else {
 		which = .ENV$current_annotation_which
402ff791
 	}
 
04a1dc38
 	on.exit(ef())
 
402ff791
 	n = length(x)
 	gp = recycle_gp(gp, n)
 
 	guess_rot = function() {
 		ifelse(which == "column", 90, 0)
 	}
 
 	guess_just = function() {
 		ifelse(which == "column", "right", "left")
 	}
 
 	guess_location = function() {
 		unit(ifelse(which == "column", 1, 0), "npc")
 	}
 
 	rot = rot[1] %% 360
 	just = just[1]
 	if(!missing(offset)) {
6f036283
 		warning_wrap("`offset` is deprecated, use `location` instead.")
402ff791
 		if(missing(location)) {
 			location = offset
 		}
 	}
 	location = location[1]
 	if(!inherits(location, "unit")) {
 		location = unit(location, "npc")
 	}
 
 	if(which == "column") {
 		if("right" %in% just) {
 			if(rot < 180) {
 				location = location - 0.5*grobHeight(textGrob("A", gp = gp))*abs(cos(rot/180*pi))
 			} else {
 				location = location + 0.5*grobHeight(textGrob("A", gp = gp))*abs(cos(rot/180*pi))
 			}
 		} else if("left" %in% just) {
 			if(rot < 180) {
 				location = location + 0.5*grobHeight(textGrob("A", gp = gp))*abs(cos(rot/180*pi))
 			} else {
 				location = location - 0.5*grobHeight(textGrob("A", gp = gp))*abs(cos(rot/180*pi))
 			}
 		}
 	}
 
 	if(which == "column") {
 		if(missing(height)) {
 			height = max_text_width(x, gp = gp)*abs(sin(rot/180*pi)) + grobHeight(textGrob("A", gp = gp))*abs(cos(rot/180*pi))
 			height = convertHeight(height, "mm")
 		}
 		if(missing(width)) {
 			width = unit(1, "npc")
 		}
 	}
 	if(which == "row") {
 		if(missing(width)) {
 			width = max_text_width(x, gp = gp)*abs(cos(rot/180*pi)) + grobHeight(textGrob("A", gp = gp))*abs(sin(rot/180*pi))
 			width = convertWidth(width, "mm")
 		}
 		if(missing(height)) {
 			height = unit(1, "npc")
 		}
 	}
 
 	anno_size = list(width = width, height = height)
 
 	value = x
 
 	row_fun = function(index) {
 		n = length(index)
9c4d56c4
 		gp = subset_gp(gp, index)
 		gp2 = gp
         if("border" %in% names(gp2)) gp2$col = gp2$border
         if("fill" %in% names(gp2)) {
         	if(!"border" %in% names(gp2)) gp2$col = gp2$fill
         }
         if(any(c("border", "fill") %in% names(gp2))) {
         	grid.rect(y = (n - seq_along(index) + 0.5)/n, height = 1/n, gp = gp2)
         }
         
 		grid.text(value[index], location, (n - seq_along(index) + 0.5)/n, gp = gp, just = just, rot = rot)
691588bd
 		# if(add_lines) {
 		# 	if(n > 1) {
 		# 		grid.segments(0, (n - seq_along(index)[-n])/n, 1, (n - seq_along(index)[-n])/n, default.units = "native")
 		# 	}
 		# }
402ff791
 	}
 	column_fun = function(index, k = NULL, N = NULL, vp_name = NULL) {
 		n = length(index)
9c4d56c4
 		gp = subset_gp(gp, index)
 		gp2 = gp
         if("border" %in% names(gp2)) gp2$col = gp2$border
         if("fill" %in% names(gp2)) {
         	if(!"border" %in% names(gp2)) gp2$col = gp2$fill
         }
         if(any(c("border", "fill") %in% names(gp2))) {
         	grid.rect(x = (seq_along(index) - 0.5)/n, width = 1/n, gp = gp2)
         }
         
 		grid.text(value[index], (seq_along(index) - 0.5)/n, location, gp = gp, just = just, rot = rot)
402ff791
 	}
 
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_text",
 		which = which,
 		width = width,
 		height = height,
 		n = n,
933e808c
 		var_import = list(value, gp, just, rot, location),
fc048989
 		show_name = show_name
402ff791
 	)
 
 	anno@subset_rule$value = subset_vector
 	anno@subset_rule$gp = subset_gp
 
8ed806b6
 	anno@subsettable = TRUE
5b47a845
 
 	return(anno)
 }
 
10fbf31c
 # == title
 # Joyplot Annotation
 #
 # == param
 # -x A matrix or a list. If ``x`` is a matrix or a data frame, columns correspond to observations.
 # -which Whether it is a column annotation or a row annotation?
 # -gp Graphic parameters for the boxes. The length of the graphic parameters should be one or the number of observations.
 # -scale Relative height of the curve. A value higher than 1 increases the height of the curve.
 # -transparency Transparency of the filled colors. Value should be between 0 and 1.
 # -axis Whether to add axis?
 # -axis_param parameters for controlling axis. See `default_axis_param` for all possible settings and default parameters.
1a56796e
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
10fbf31c
 #
 # == value
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
33a5a58f
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#joyplot-annotation
 #
10fbf31c
 # == example
 # m = matrix(rnorm(1000), nc = 10)
 # lt = apply(m, 2, function(x) data.frame(density(x)[c("x", "y")]))
 # anno = anno_joyplot(lt, width = unit(4, "cm"), which = "row")
 # draw(anno, test = "joyplot")
 # anno = anno_joyplot(lt, width = unit(4, "cm"), which = "row", gp = gpar(fill = 1:10))
 # draw(anno, test = "joyplot + col")
 # anno = anno_joyplot(lt, width = unit(4, "cm"), which = "row", scale = 1)
 # draw(anno, test = "joyplot + scale")
 #
 # m = matrix(rnorm(5000), nc = 50)
 # lt = apply(m, 2, function(x) data.frame(density(x)[c("x", "y")]))
 # anno = anno_joyplot(lt, width = unit(4, "cm"), which = "row", gp = gpar(fill = NA), scale = 4)
 # draw(anno, test = "joyplot")
402ff791
 anno_joyplot = function(x, which = c("column", "row"), gp = gpar(fill = "#000000"),
 	scale = 2, transparency = 0.6,
 	axis = TRUE, axis_param = default_axis_param(which),
 	width = NULL, height = NULL) {
 	
04a1dc38
 	ef = function() NULL
402ff791
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
04a1dc38
 		dev.null()
 		ef = dev.off2
402ff791
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
04a1dc38
 	on.exit(ef())
 
402ff791
 	anno_size = anno_width_and_height(which, width, height, unit(4, "cm"))
 
 	## convert matrix all to list (or data frame)
 	if(is.matrix(x) || is.data.frame(x)) {
 		value = vector("list", ncol(x))
 		for(i in seq_len(ncol(x))) {
ad35494a
 			value[[i]] = cbind(seq_len(nrow(x)), x[, i])
402ff791
 		}
 	} else if(inherits(x, "list")){
 		if(all(sapply(x, is.atomic))) {
 			if(length(unique(sapply(x, length))) == 1) {
 				value = vector("list", length(x))
 				for(i in seq_len(length(x))) {
 					value[[i]] = cbind(seq_along(x[[i]]), x[[i]])
 				}
 			} else {
c4a66bf9
 				stop_wrap("Since x is a list, x need to be a list of two-column matrices.")
402ff791
 			}
 		} else {
 			value = x
 		}
 	} else {
c4a66bf9
 		stop_wrap("The input should be a list of two-column matrices or a matrix/data frame.")
402ff791
 	}
 
 	xscale = range(lapply(value, function(x) x[, 1]), na.rm = TRUE)
97eef737
 	xscale = xscale + c(-0.025, 0.025)*(xscale[2] - xscale[1])
402ff791
 	yscale = range(lapply(value, function(x) x[, 2]), na.rm = TRUE)
 	yscale[1] = 0
 	yscale[2] = yscale[2]*1.05
 
 	n = length(value)
 
 	if(!"fill" %in% names(gp)) {
 		gp$fill = "#000000"
 	} 
 	gp = recycle_gp(gp, n)
 	gp$fill = add_transparency(gp$fill, transparency)
529f6dba
 	axis_param$direction = "normal"
402ff791
 	axis_param = validate_axis_param(axis_param, which)
 	axis_grob = if(axis) construct_axis_grob(axis_param, which, xscale) else NULL
 
ad35494a
 	row_fun = function(index, k = 1, N = 1) {
402ff791
 
 		n_all = length(value)
 		value = value[index]
 		
 		n = length(index)
 		gp = subset_gp(gp, index)
 
 		for(i in seq_len(n)) {
 			pushViewport(viewport(x = unit(0, "npc"), y = unit((n-i)/n, "npc"), 
 				just = c("left", "bottom"), height = unit(1/n, "npc"), xscale = xscale, 
 				yscale = yscale))
 			
 			x0 = value[[i]][, 1]
 			y0 = value[[i]][, 2]*scale
 			x0 = c(x0[1], x0, x0[length(x0)])
 			y0 = c(0, y0, 0)
 			gppp = subset_gp(gp, i); gppp$col = NA
 			grid.polygon(x = x0, y = y0, default.units = "native", gp = gppp)
 			grid.lines(x = x0, y = y0, default.units = "native", 
 				gp = subset_gp(gp, i))
 			
 			popViewport()
 		}
 		pushViewport(viewport(xscale = xscale))
ad35494a
 		if(axis_param$side == "top") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "bottom") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		popViewport()
 	}
ad35494a
 	column_fun = function(index, k = 1, N = 1) {
402ff791
 
 		n_all = length(value)
 		value = value[index]
 		
 		foo = yscale
 		yscale = xscale
 		xscale = foo
 		
 		n = length(index)
 		
c3ef40cd
 		gp = subset_gp(gp, index)
402ff791
 
 		for(i in seq_len(n)) {
 			pushViewport(viewport(y = unit(0, "npc"), x = unit(i/n, "npc"), 
 				width = unit(1/n, "npc"), just = c("right", "bottom"), xscale = xscale, 
 				yscale = yscale))
 			
 			x0 = value[[i]][, 2]*scale
 			y0 = value[[i]][ ,1]
 			x0 = c(0, x0, 0)
 			y0 = c(y0[1], y0, y0[length(y0)])
 			gppp = subset_gp(gp, i); gppp$col = NA
 			grid.polygon(y = y0, x = x0, default.units = "native", gp = gppp)
 			grid.lines(y = y0, x = x0, default.units = "native", 
 				gp = subset_gp(gp, i))
 			
 			popViewport()
 		}
 		pushViewport(viewport(yscale = yscale))
ad35494a
 		if(axis_param$side == "left") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "right") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		popViewport()
 	}
 	
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_joyplot",
 		which = which,
 		width = anno_size$width,
 		height = anno_size$height,
 		n = n,
 		data_scale = xscale,
 		var_import = list(value, gp, axis, axis_param, axis_grob, scale, yscale, xscale)
 	)
 
 	anno@subset_rule$value = subset_vector
 	anno@subset_rule$gp = subset_gp
 
8ed806b6
 	anno@subsettable = TRUE
402ff791
 
 	anno@extended = update_anno_extend(anno, axis_grob, axis_param)
 
 	return(anno)
 }
 
10fbf31c
 # == title
 # Horizon chart Annotation
 #
 # == param
 # -x A matrix or a list. If ``x`` is a matrix or a data frame, columns correspond to observations.
 # -which Whether it is a column annotation or a row annotation?
 # -gp Graphic parameters for the boxes. The length of the graphic parameters should be one or the number of observations.
ad35494a
 #     There are two unstandard parameters specificly for horizon chart: ``pos_fill`` and ``neg_fill`` controls the filled
10fbf31c
 #     color for positive values and negative values.
 # -n_slice Number of slices on y-axis.
 # -slice_size Height of the slice. If the value is not ``NULL``, ``n_slice`` will be recalculated. 
 # -negative_from_top Whether the areas for negative values start from the top or the bottom of the plotting region?
7db3856b
 # -normalize Whether normalize ``x`` by max(abs(x)).
10fbf31c
 # -gap Gap size of neighbouring horizon chart.
 # -axis Whether to add axis?
 # -axis_param parameters for controlling axis. See `default_axis_param` for all possible settings and default parameters.
1a56796e
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
10fbf31c
 #
 # == detail
 # Horizon chart as row annotation is only supported.
 #
 # == value
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
33a5a58f
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#horizon-chart-annotation
 #
ad35494a
 # == example
10fbf31c
 # lt = lapply(1:20, function(x) cumprod(1 + runif(1000, -x/100, x/100)) - 1)
 # anno = anno_horizon(lt, which = "row")
 # draw(anno, test = "horizon chart")
 # anno = anno_horizon(lt, which = "row", 
 #     gp = gpar(pos_fill = "orange", neg_fill = "darkgreen"))
 # draw(anno, test = "horizon chart, col")
 # anno = anno_horizon(lt, which = "row", negative_from_top = TRUE)
 # draw(anno, test = "horizon chart + negative_from_top")
 # anno = anno_horizon(lt, which = "row", gap = unit(1, "mm"))
 # draw(anno, test = "horizon chart + gap")
 # anno = anno_horizon(lt, which = "row", 
 #     gp = gpar(pos_fill = rep(c("orange", "red"), each = 10),
 #     neg_fill = rep(c("darkgreen", "blue"), each = 10)))
 # draw(anno, test = "horizon chart, col")
402ff791
 anno_horizon = function(x, which = c("column", "row"), 
 	gp = gpar(pos_fill = "#D73027", neg_fill = "#313695"),
 	n_slice = 4, slice_size = NULL, negative_from_top = FALSE, 
10fbf31c
 	normalize = TRUE, gap = unit(0, "mm"),
402ff791
 	axis = TRUE, axis_param = default_axis_param(which),
 	width = NULL, height = NULL) {
 
04a1dc38
 	ef = function() NULL
402ff791
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
04a1dc38
 		dev.null()
 		ef = dev.off2
402ff791
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
04a1dc38
 	on.exit(ef())
 
402ff791
 	anno_size = anno_width_and_height(which, width, height, unit(4, "cm"))
 
 	## convert matrix all to list (or data frame)
 	if(is.matrix(x) || is.data.frame(x)) {
 		value = vector("list", ncol(x))
 		for(i in seq_len(ncol(x))) {
ad35494a
 			value[[i]] = cbind(seq_len(nrow(x)), x[, i])
402ff791
 		}
 	} else if(inherits(x, "list")){
 		if(all(sapply(x, is.atomic))) {
 			if(length(unique(sapply(x, length))) == 1) {
 				value = vector("list", length(x))
 				for(i in seq_len(length(x))) {
 					value[[i]] = cbind(seq_along(x[[i]]), x[[i]])
 				}
 			} else {
c4a66bf9
 				stop_wrap("Since x is a list, x need to be a list of two-column matrices.")
402ff791
 			}
 		} else {
 			value = x
 		}
 	} else {
c4a66bf9
 		stop_wrap("The input should be a list of two-column matrices or a matrix/data frame.")
402ff791
 	}
 
 	if(is.null(gp$pos_fill)) gp$pos_fill = "#D73027"
 	if(is.null(gp$neg_fill)) gp$neg_fill = "#313695"
 
 	if("fill" %in% names(gp)) {
 		foo = unlist(lapply(value, function(x) x[, 2]))
 		if(all(foo >= 0)) {
 			gp$pos_fill = gp$fill
 		} else if(all(foo <= 0)) {
 			gp$neg_fill = gp$fill
 		} else {
 			gp = gpar(pos_fill = "#D73027", neg_fill = "#313695")
 		}
 	}
 
 	if(which == "column") {
c3ef40cd
 		stop_wrap("anno_horizon() does not support column annotation.")
402ff791
 	}
 
 	if(normalize) {
 		value = lapply(value, function(m) {
 			m[, 2] = m[, 2]/max(abs(m[, 2]))
 			m
 		})
 	}
 
 	n = length(value)
 	xscale = range(lapply(value, function(x) x[, 1]), na.rm = TRUE)
 	yscale = range(lapply(value, function(x) abs(x[, 2])), na.rm = TRUE)
 	
529f6dba
 	axis_param$direction = "normal"
402ff791
 	axis_param = validate_axis_param(axis_param, which)
 	axis_grob = if(axis) construct_axis_grob(axis_param, which, xscale) else NULL
 
ad35494a
 	row_fun = function(index, k = 1, N = 1) {
402ff791
 
 		n_all = length(value)
 		value = value[index]
 		
 		if(is.null(slice_size)) {
 			slice_size = yscale[2]/n_slice
 		} 
 		n_slice = ceiling(yscale[2]/slice_size)
 		
 		n = length(index)
 		
 		gp = subset_gp(gp, index)
 
 		for(i in seq_len(n)) {
 			pushViewport(viewport(x = unit(0, "npc"), y = unit((n-i)/n, "npc"), just = c("left", "bottom"), 
 				height = unit(1/n, "npc") - gap))
 			sgp = subset_gp(gp, i)
 			horizon_chart(value[[i]][, 1], value[[i]][, 2], n_slice = n_slice, slice_size = slice_size, 
 				negative_from_top = negative_from_top, pos_fill = sgp$pos_fill, neg_fill = sgp$neg_fill)
 			grid.rect(gp = gpar(fill = "transparent"))
 			
 			popViewport()
 		}
 		pushViewport(viewport(xscale = xscale))
ad35494a
 		if(axis_param$side == "top") {
 			if(k > 1) axis = FALSE
 		} else if(axis_param$side == "bottom") {
 			if(k < N) axis = FALSE
 		}
402ff791
 		if(axis) grid.draw(axis_grob)
 		popViewport()
 	}
 	column_fun = function(index) {
 
 	}
 	
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_horizon",
 		which = which,
 		width = anno_size$width,
 		height = anno_size$height,
 		n = n,
 		data_scale = xscale,
10fbf31c
 		var_import = list(value, gp, axis, axis_param, axis_grob, n_slice, slice_size,
402ff791
 			negative_from_top, xscale, yscale, gap)
 	)
 
 	anno@subset_rule$value = subset_vector
 	anno@subset_rule$gp = subset_gp
 
8ed806b6
 	anno@subsettable = TRUE
402ff791
 
 	anno@extended = update_anno_extend(anno, axis_grob, axis_param)
 
 	return(anno)
 }
 
 horizon_chart = function(x, y, n_slice = 4, slice_size, pos_fill = "#D73027", neg_fill = "#313695",
 	negative_from_top = FALSE) {
 
 	if(missing(slice_size)) {
 		slice_size = max(abs(y))/n_slice
 	}
 	n_slice = ceiling(max(abs(y))/slice_size)
 
 	if(n_slice == 0) {
 		return(invisible(NULL))
 	}
 
 	pos_col_fun = colorRamp2(c(0, n_slice), c("white", pos_fill))
 	neg_col_fun = colorRamp2(c(0, n_slice), c("white", neg_fill))
 	pushViewport(viewport(xscale = range(x), yscale = c(0, slice_size)))
 	for(i in seq_len(n_slice)) {
 		l1 = y >= (i-1)*slice_size & y < i*slice_size
 		l2 = y < (i-1)*slice_size
 		l3 = y >= i*slice_size
 		if(any(l1)) {
 			x2 = x
 			y2 = y
 			y2[l1] = y2[l1] - slice_size*(i-1)
 			y2[l3] = slice_size
 			x2[l2] = NA
 			y2[l2] = NA
 
 			add_horizon_polygon(x2, y2, gp = gpar(fill = pos_col_fun(i), col = NA), 
 				default.units = "native")
 		}
 	}
 	y = -y
 	for(i in seq_len(n_slice)) {
 		l1 = y >= (i-1)*slice_size & y < i*slice_size
 		l2 = y < (i-1)*slice_size
 		l3 = y >= i*slice_size
 		if(any(l1)) {
 			x2 = x
 			y2 = y
 			y2[l1] = y2[l1] - slice_size*(i-1)
 			y2[l3] = slice_size
 			x2[l2] = NA
 			y2[l2] = NA
 			add_horizon_polygon(x2, y2, slice_size = slice_size, from_top = negative_from_top, 
 				gp = gpar(fill = neg_col_fun(i), col = NA), default.units = "native")
 		}
 	}
 	popViewport()
 }
 
 # x and y may contain NA, split x and y by NA gaps, align the bottom to y = 0
 add_horizon_polygon = function(x, y, slice_size = NULL, from_top = FALSE, ...) {
 	ltx = split_vec_by_NA(x)
 	lty = split_vec_by_NA(y)
 
 	for(i in seq_along(ltx)) {
 		x0 = ltx[[i]]
 		y0 = lty[[i]]
 		if(from_top) {
 			x0 = c(x0[1], x0, x0[length(x0)])
 			y0 = c(slice_size, slice_size - y0, slice_size)
 		} else {
 			x0 = c(x0[1], x0, x0[length(x0)])
 			y0 = c(0, y0, 0)
 		}
 		grid.polygon(x0, y0, ...)
 	}
 }
 
 # https://stat.ethz.ch/pipermail/r-help/2010-April/237031.html
 split_vec_by_NA = function(x) {
 	idx = 1 + cumsum(is.na(x))
 	not.na = !is.na(x)
 	split(x[not.na], idx[not.na])
 }
 
 
 # == title
10fbf31c
 # Points as Row Annotation
402ff791
 #
 # == param
33a5a58f
 # -... pass to `anno_points`.
402ff791
 #
 # == details
 # A wrapper of `anno_points` with pre-defined ``which`` to ``row``.
 #
10fbf31c
 # You can directly use `anno_points` for row annotation if you call it in `rowAnnotation`.
 #
402ff791
 # == value
33a5a58f
 # See help page of `anno_points`.
402ff791
 #
 row_anno_points = function(...) {
 	if(exists(".__under_SingleAnnotation__", envir = parent.frame())) {
33a5a58f
 		message_wrap("From version 1.99.0, you can directly use `anno_points()` for row annotation if you call it in `rowAnnotation()`.")
402ff791
 	}
 	anno_points(..., which = "row")
 }
 
 
 # == title
10fbf31c
 # Barplots as Row Annotation
402ff791
 #
 # == param
33a5a58f
 # -... pass to `anno_barplot`.
402ff791
 #
 # == details
 # A wrapper of `anno_barplot` with pre-defined ``which`` to ``row``.
 #
10fbf31c
 # You can directly use `anno_barplot` for row annotation if you call it in `rowAnnotation`.
 #
402ff791
 # == value
33a5a58f
 # See help page of `anno_barplot`.
402ff791
 #
 row_anno_barplot = function(...) {
ad35494a
 	if(exists(".__under_SingleAnnotation__", envir = parent.frame())) {
33a5a58f
 		message_wrap("From version 1.99.0, you can directly use `anno_barplot()` for row annotation if you call it in `rowAnnotation()`.")
ad35494a
 	}
402ff791
 	anno_barplot(..., which = "row")
 }
 
 
 # == title
10fbf31c
 # Boxplots as Row Annotation
402ff791
 #
 # == param
33a5a58f
 # -... pass to `anno_boxplot`.
402ff791
 #
 # == details
 # A wrapper of `anno_boxplot` with pre-defined ``which`` to ``row``.
 #
10fbf31c
 # You can directly use `anno_boxplot` for row annotation if you call it in `rowAnnotation`.
 #
402ff791
 # == value
33a5a58f
 # See help page of `anno_boxplot`.
402ff791
 #
 row_anno_boxplot = function(...) {
ad35494a
 	if(exists(".__under_SingleAnnotation__", envir = parent.frame())) {
33a5a58f
 		message_wrap("From version 1.99.0, you can directly use `anno_boxplot()` for row annotation if you call it in `rowAnnotation()`.")
ad35494a
 	}
402ff791
 	anno_boxplot(..., which = "row")
 }
 
 # == title
10fbf31c
 # Histograms as Row Annotation
402ff791
 #
 # == param
33a5a58f
 # -... pass to `anno_histogram`.
402ff791
 #
 # == details
 # A wrapper of `anno_histogram` with pre-defined ``which`` to ``row``.
 #
10fbf31c
 # You can directly use `anno_histogram` for row annotation if you call it in `rowAnnotation`.
 #
402ff791
 # == value
33a5a58f
 # See help page of `anno_histogram`.
402ff791
 #
 row_anno_histogram = function(...) {
ad35494a
 	if(exists(".__under_SingleAnnotation__", envir = parent.frame())) {
33a5a58f
 		message_wrap("From version 1.99.0, you can directly use `anno_histogram()` for row annotation if you call it in `rowAnnotation()`.")
ad35494a
 	}
402ff791
 	anno_histogram(..., which = "row")
 }
 
 # == title
10fbf31c
 # Density as Row Annotation
402ff791
 #
 # == param
33a5a58f
 # -... pass to `anno_density`.
402ff791
 #
 # == details
 # A wrapper of `anno_density` with pre-defined ``which`` to ``row``.
 #
10fbf31c
 # You can directly use `anno_density` for row annotation if you call it in `rowAnnotation`.
 #
402ff791
 # == value
33a5a58f
 # See help page of `anno_density`.
402ff791
 #
 row_anno_density = function(...) {
ad35494a
 	if(exists(".__under_SingleAnnotation__", envir = parent.frame())) {
33a5a58f
 		message_wrap("From version 1.99.0, you can directly use `anno_density()` for row annotation if you call it in `rowAnnotation()`.")
ad35494a
 	}
402ff791
 	anno_density(..., which = "row")
 }
 
 # == title
10fbf31c
 # Text as Row Annotation
402ff791
 #
 # == param
33a5a58f
 # -... pass to `anno_text`.
402ff791
 #
 # == details
 # A wrapper of `anno_text` with pre-defined ``which`` to ``row``.
 #
10fbf31c
 # You can directly use `anno_text` for row annotation if you call it in `rowAnnotation`.
 #
402ff791
 # == value
33a5a58f
 # See help page of `anno_text`.
402ff791
 #
 row_anno_text = function(...) {
ad35494a
 	if(exists(".__under_SingleAnnotation__", envir = parent.frame())) {
33a5a58f
 		message_wrap("From version 1.99.0, you can directly use `anno_text()` for row annotation if you call it in `rowAnnotation()`.")
ad35494a
 	}
402ff791
 	anno_text(..., which = "row")
 }
 
 # == title
 # Link annotation with labels
 #
 # == param
10fbf31c
 # -at Numeric index from the original matrix.
 # -labels Corresponding labels.
 # -which Whether it is a column annotation or a row annotation?
 # -side Side of the labels. If it is a column annotation, valid values are "top" and "bottom";
 #       If it is a row annotation, valid values are "left" and "right".
 # -lines_gp Please use ``link_gp`` instead.
 # -link_gp Graphic settings for the segments.
 # -labels_gp Graphic settings for the labels.
f4dac009
 # -labels_rot Rotations of labels, scalar.
10fbf31c
 # -padding Padding between neighbouring labels in the plot.
 # -link_width Width of the segments.
fda8e6f1
 # -link_height Similar as ``link_width``, used for column annotation.
10fbf31c
 # -extend By default, the region for the labels has the same width (if it is a column annotation) or
402ff791
 #         same height (if it is a row annotation) as the heatmap. The size can be extended by this options.
 #         The value can be a proportion number or  a `grid::unit` object. The length can be either one or two.
 #
 # == details
 # Sometimes there are many rows or columns in the heatmap and we want to mark some of the rows.
 # This annotation function is used to mark these rows and connect labels and corresponding rows
 # with links.
 #
 # == value
10fbf31c
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
33a5a58f
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#mark-annotation
 #
10fbf31c
 # == example
 # anno = anno_mark(at = c(1:4, 20, 60, 97:100), labels = month.name[1:10], which = "row")
 # draw(anno, index = 1:100, test = "anno_mark")
 #
 # m = matrix(1:1000, byrow = TRUE, nr = 100)
 # anno = anno_mark(at = c(1:4, 20, 60, 97:100), labels = month.name[1:10], which = "row")
ad35494a
 # Heatmap(m, cluster_rows = FALSE, cluster_columns = FALSE) + rowAnnotation(mark = anno)
10fbf31c
 # Heatmap(m) + rowAnnotation(mark = anno)
 anno_mark = function(at, labels, which = c("column", "row"), 
 	side = ifelse(which == "column", "top", "right"),
f4dac009
 	lines_gp = gpar(), labels_gp = gpar(), 
1dbbe82a
 	labels_rot = ifelse(which == "column", 90, 0), padding = unit(1, "mm"), 
fda8e6f1
 	link_width = unit(5, "mm"), link_height = link_width,
 	link_gp = lines_gp, 
10fbf31c
 	extend = unit(0, "mm")) {
402ff791
 
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
 	if(!is.numeric(at)) {
c4a66bf9
 		stop_wrap(paste0("`at` should be numeric ", which, " index corresponding to the matrix."))
402ff791
 	}
 	
68498544
 	if(is.logical(at)) at = which(at)
 
402ff791
 	n = length(at)
46b5212b
 
3a141273
 	if(n < 1) {
46b5212b
 		return(anno_empty(which = which, border = FALSE))
3a141273
 	}
46b5212b
 
402ff791
 	link_gp = recycle_gp(link_gp, n)
 	labels_gp = recycle_gp(labels_gp, n)
46b5212b
 	
 	od = order(at)
 	at = at[od]
 	labels = labels[od]
 	link_gp = subset_gp(link_gp, od)
 	labels_gp = subset_gp(labels_gp, od)
 
741f3ed5
 	labels2index = structure(seq_along(at), names = as.character(labels))
402ff791
 	at2labels = structure(labels, names = at)
 
 	if(length(extend) == 1) extend = rep(extend, 2)
 	if(length(extend) > 2) extend = extend[1:2]
 	if(!inherits(extend, "unit")) extend = unit(extend, "npc")
 
 	if(which == "row") {
 		height = unit(1, "npc")
f4dac009
 		width = link_width + max_text_width(labels, gp = labels_gp, rot = labels_rot)
402ff791
 	} else {
f4dac009
 		height = link_width + max_text_height(labels, gp = labels_gp, rot = labels_rot)
402ff791
 		width = unit(1, "npc")
 	}
 
138dc569
 	.pos = NULL
 	.scale = NULL
 
1dbbe82a
 	labels_rot = labels_rot %% 360
 
446e8a2c
 	if(!inherits(padding, "unit")) {
 		padding = convertHeight(padding*grobHeight(textGrob("a", gp = subset_gp(labels_gp, 1))), "mm")
 	}
 
933e808c
 	# a map between row index and positions
 	# pos_map = 
402ff791
 	row_fun = function(index) {
2b464013
 
 		if(is_RStudio_current_dev()) {
 			if(ht_opt$message) {
 				message_wrap("It seems you are using RStudio IDE. `anno_mark()` needs to work with the physical size of the graphics device. It only generates correct plot in the figure panel, while in the zoomed plot (by clicking the icon 'Zoom') or in the exported plot (by clicking the icon 'Export'), the connection to heatmap rows/columns might be wrong. You can directly use e.g. pdf() to save the plot into a file.\n\nUse `ht_opt$message = FALSE` to turn off this message.")
 			}
 		}
 
402ff791
 		n = length(index)
 		# adjust at and labels
 		at = intersect(index, at)
 		if(length(at) == 0) {
 			return(NULL)
 		}
 		labels = rev(at2labels[as.character(at)])
 
741f3ed5
 		labels_gp = subset_gp(labels_gp, labels2index[as.character(labels)])
 		link_gp = subset_gp(link_gp, labels2index[as.character(labels)])
402ff791
 
138dc569
 		if(is.null(.scale)) {
 			.scale = c(0.5, n+0.5)
 		}
 		pushViewport(viewport(xscale = c(0, 1), yscale = .scale))
d9a1645d
 		if(inherits(extend, "unit")) extend = convertHeight(extend, "native", valueOnly = TRUE)
1dbbe82a
 		if(labels_rot %in% c(90, 270)) {
 			text_height = convertHeight(text_width(labels, gp = labels_gp) + padding, "native", valueOnly = TRUE)
 		} else {
 			text_height = convertHeight(text_height(labels, gp = labels_gp) + padding, "native", valueOnly = TRUE)
 		}
138dc569
 		if(is.null(.pos)) {
 			i2 = rev(which(index %in% at))
 			pos = n-i2+1 # position of rows
 		} else {
 			pos = .pos[rev(which(index %in% at))]
 		}
 		h1 = pos - text_height*0.5
 		h2 = pos + text_height*0.5
fda8e6f1
 		pos_adjusted = smartAlign(h1, h2, c(.scale[1] - extend[1], .scale[2] + extend[2]))
138dc569
 		h = (pos_adjusted[, 1] + pos_adjusted[, 2])/2
402ff791
 
 		n2 = length(labels)
 		if(side == "right") {
1dbbe82a
 			if(labels_rot == 90) {
 				just = c("center", "top")
 			} else if(labels_rot == 270) {
 				just = c("center", "bottom")
 			} else if(labels_rot > 90 & labels_rot < 270 ) {
 				just = c("right", "center")
 			} else {
 				just = c("left", "center")
 			}
 		} else {
 			if(labels_rot == 90) {
 				just = c("center", "bottom")
 			} else if(labels_rot == 270) {
 				just = c("center", "top")
 			} else if(labels_rot > 90 & labels_rot < 270 ) {
 				just = c("left", "center")
 			} else {
 				just = c("right", "center")
 			}
 		}
 		if(side == "right") {
 			grid.text(labels, rep(link_width, n2), h, default.units = "native", gp = labels_gp, rot = labels_rot, just = just)
402ff791
 			link_width = link_width - unit(1, "mm")
138dc569
 			grid.segments(unit(rep(0, n2), "npc"), pos, rep(link_width*(1/3), n2), pos, default.units = "native", gp = link_gp)
 			grid.segments(rep(link_width*(1/3), n2), pos, rep(link_width*(2/3), n2), h, default.units = "native", gp = link_gp)
402ff791
 			grid.segments(rep(link_width*(2/3), n2), h, rep(link_width, n2), h, default.units = "native", gp = link_gp)
 		} else {
1dbbe82a
 			grid.text(labels, unit(1, "npc")-rep(link_width, n2), h, default.units = "native", gp = labels_gp, rot = labels_rot, just = just)
402ff791
 			link_width = link_width - unit(1, "mm")
138dc569
 			grid.segments(unit(rep(1, n2), "npc"), pos, unit(1, "npc")-rep(link_width*(1/3), n2), pos, default.units = "native", gp = link_gp)
 			grid.segments(unit(1, "npc")-rep(link_width*(1/3), n2), pos, unit(1, "npc")-rep(link_width*(2/3), n2), h, default.units = "native", gp = link_gp)
402ff791
 			grid.segments(unit(1, "npc")-rep(link_width*(2/3), n2), h, unit(1, "npc")-rep(link_width, n2), h, default.units = "native", gp = link_gp)
 		}
 		upViewport()
 	}
 	column_fun = function(index) {
2b464013
 
 		if(is_RStudio_current_dev()) {
 			if(ht_opt$message) {
 				message_wrap("It seems you are using RStudio IDE. `anno_mark()` needs to work with the physical size of the graphics device. It only generates correct plot in the figure panel, while in the zoomed plot (by clicking the icon 'Zoom') or in the exported plot (by clicking the icon 'Export'), the connection to heatmap rows/columns might be wrong. You can directly use e.g. pdf() to save the plot into a file.\n\nUse `ht_opt$message = FALSE` to turn off this message.")
 			}
 		}
 		
402ff791
 		n = length(index)
 		# adjust at and labels
 		at = intersect(index, at)
138dc569
 		if(length(at) == 0) {
 			return(NULL)
 		}
402ff791
 		labels = at2labels[as.character(at)]
 		
741f3ed5
 		labels_gp = subset_gp(labels_gp, labels2index[as.character(labels)])
 		link_gp = subset_gp(link_gp, labels2index[as.character(labels)])
402ff791
 
138dc569
 		if(is.null(.scale)) {
 			.scale = c(0.5, n+0.5)
 		}
 		pushViewport(viewport(yscale = c(0, 1), xscale = .scale))
402ff791
 		if(inherits(extend, "unit")) extend = convertWidth(extend, "native", valueOnly = TRUE)
1dbbe82a
 		if(labels_rot %in% c(0, 180)) {
 			text_height = convertWidth(text_width(labels, gp = labels_gp) + padding, "native", valueOnly = TRUE)
 		} else {
 			text_height = convertWidth(text_height(labels, gp = labels_gp) + padding, "native", valueOnly = TRUE)
 		}
138dc569
 		if(is.null(.pos)) {
 			i2 = which(index %in% at)
 			pos = i2 # position of rows
 		} else {
 			pos = .pos[which(index %in% at)]
402ff791
 		}
138dc569
 		h1 = pos - text_height*0.5
 		h2 = pos + text_height*0.5
 		pos_adjusted = smartAlign(h1, h2, c(.scale[1] - extend[1], .scale[2] + extend[2]))
 		h = (pos_adjusted[, 1] + pos_adjusted[, 2])/2
 
402ff791
 		n2 = length(labels)
 		if(side == "top") {
1dbbe82a
 			if(labels_rot == 0) {
 				just = c("center", "bottom")
 			} else if(labels_rot == 180) {
 				just = c("center", "top")
 			} else if(labels_rot > 0 & labels_rot < 180 ) {
 				just = c("left", "center")
 			} else {
 				just = c("right", "center")
 			}
 		} else {
 			if(labels_rot == 0) {
 				just = c("center", "top")
 			} else if(labels_rot == 180) {
 				just = c("center", "bottom")
 			} else if(labels_rot > 0 & labels_rot < 180 ) {
 				just = c("right", "center")
 			} else {
 				just = c("left", "center")
 			}
 		}
 		if(side == "top") {
 			grid.text(labels, h, rep(link_height, n2), default.units = "native", gp = labels_gp, rot = labels_rot, just = just)
fda8e6f1
 			link_height = link_height - unit(1, "mm")
 			grid.segments(pos, unit(rep(0, n2), "npc"), pos, rep(link_height*(1/3), n2), default.units = "native", gp = link_gp)
 			grid.segments(pos, rep(link_height*(1/3), n2), h, rep(link_height*(2/3), n2), default.units = "native", gp = link_gp)
 			grid.segments(h, rep(link_height*(2/3), n2), h, rep(link_height, n), default.units = "native", gp = link_gp)
402ff791
 		} else {
1dbbe82a
 			grid.text(labels, h, unit(1, "npc")-rep(link_height, n2), default.units = "native", gp = labels_gp, rot = labels_rot, just = just)
fda8e6f1
 			link_height = link_height - unit(1, "mm")
 			grid.segments(pos, unit(rep(1, n2), "npc"), pos, unit(1, "npc")-rep(link_height*(1/3), n2), default.units = "native", gp = link_gp)
 			grid.segments(pos, unit(1, "npc")-rep(link_height*(1/3), n2), h, unit(1, "npc")-rep(link_height*(2/3), n2), default.units = "native", gp = link_gp)
 			grid.segments(h, unit(1, "npc")-rep(link_height*(2/3), n2), h, unit(1, "npc")-rep(link_height, n2), default.units = "native", gp = link_gp)
402ff791
 		}
 		upViewport()
 	}
 
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 	
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_mark",
 		which = which,
 		width = width,
 		height = height,
 		n = -1,
f4dac009
 		var_import = list(at, labels2index, at2labels, link_gp, labels_gp, labels_rot, padding, .pos, .scale,
fda8e6f1
 			side, link_width, link_height, extend),
933e808c
 		show_name = FALSE
402ff791
 	)
 
 	anno@subset_rule$at = subset_by_intersect
 
8ed806b6
 	anno@subsettable = TRUE
46b5212b
 
 	attr(anno, "called_args") = list(
 		at = at, 
 		labels = labels, 
 		which = which, 
 		side = side,
 		labels_gp = labels_gp, 
 		labels_rot = labels_rot, 
 		padding = padding, 
 		link_width = link_width, 
 		link_height = link_height,
 		link_gp = link_gp, 
 		extend = extend
 	)
402ff791
 	return(anno)
 }
 
 subset_by_intersect = function(x, i) {
 	intersect(x, i)
 }
 
 # == title
609b403c
 # Link Annotation
402ff791
 #
 # == param
609b403c
 # -... Pass to `anno_zoom`.
402ff791
 #
 # == details
609b403c
 # This function is the same as `anno_zoom`. It links subsets of rows or columns to a list of graphic regions.
402ff791
 #
10fbf31c
 anno_link = function(...) {
609b403c
 	anno_zoom(...)
402ff791
 }
 
1a56796e
 # == title
 # Summary Annotation
 #
 # == param
 # -which Whether it is a column annotation or a row annotation?
 # -border Wether draw borders of the annotation region?
 # -bar_width Relative width of the bars. The value should be smaller than one.
 # -axis Whether to add axis?
 # -axis_param parameters for controlling axis. See `default_axis_param` for all possible settings and default parameters.
 # -ylim Data ranges. ``ylim`` for barplot is enforced to be ``c(0, 1)``.
 # -extend The extension to both side of ``ylim``. The value is a percent value corresponding to ``ylim[2] - ylim[1]``. This argument is only for boxplot.
 # -outline Whether draw outline of boxplots?
 # -box_width Relative width of boxes. The value should be smaller than one.
 # -pch Point style.
 # -size Point size.
 # -gp Graphic parameters.
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
 #
 # == detail
 # ``anno_summary`` is a special annotation function that it only works for one-column or one-row heatmap. 
 # It shows the summary of the values in the heatmap. If the values in the heatmap is discrete, 
 # the proportion of each level (the sum is normalized to 1) is visualized as stacked barplot. If the heatmap
 # is split into multiple slices, multiple bars are put in the annotation. If the value is continuous, boxplot is used.
 #
 # In the barplot, the color schema is used as the same as the heatmap, while for the boxplot, the color needs
 # to be controlled by ``gp``.
 #
33a5a58f
 # == value
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#summary-annotation
 #
1a56796e
 # == example
 # ha = HeatmapAnnotation(summary = anno_summary(height = unit(4, "cm")))
 # v = sample(letters[1:2], 50, replace = TRUE)
 # split = sample(letters[1:2], 50, replace = TRUE)
 # Heatmap(v, top_annotation = ha, width = unit(1, "cm"), split = split)
 #
 # ha = HeatmapAnnotation(summary = anno_summary(gp = gpar(fill = 2:3), height = unit(4, "cm")))
 # v = rnorm(50)
 # Heatmap(v, top_annotation = ha, width = unit(1, "cm"), split = split)
 #
cc35650a
 anno_summary = function(which = c("column", "row"), border = TRUE, bar_width = 0.8, 
a93897a0
 	axis = TRUE, axis_param = default_axis_param(which),
 	ylim = NULL, extend = 0.05, outline = TRUE, box_width = 0.6,
 	pch = 1, size = unit(2, "mm"), gp = gpar(),
1a56796e
 	width = NULL, height = NULL) {
249fdfa0
 
04a1dc38
 	ef = function() NULL
249fdfa0
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
04a1dc38
 		dev.null()
 		ef = dev.off2
249fdfa0
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
04a1dc38
 	on.exit(ef())
 
249fdfa0
 	anno_size = anno_width_and_height(which, width, height, unit(2, "cm"))
 
cc35650a
 	axis_param = validate_axis_param(axis_param, which)
a93897a0
 	if(is.null(ylim)) {
 		axis_grob = if(axis) construct_axis_grob(axis_param, which, c(0, 1)) else NULL
 	} else {
 		axis_grob = if(axis) construct_axis_grob(axis_param, which, ylim) else NULL
 	}
cc35650a
 
bb20f132
 	row_fun = function(index) {
cc35650a
 		ht = get("object", envir = parent.frame(7))
 		mat = ht@matrix
 		cm = ht@matrix_color_mapping
 		order_list = ht@column_order_list
 		ng = length(order_list)
bb20f132
 
cc35650a
 		if(cm@type == "discrete") {
 			tl = lapply(order_list, function(od) table(mat[1, od]))
 			tl = lapply(tl, function(x) x/sum(x))
 
 			pushViewport(viewport(yscale = c(0.5, ng+0.5), xscale = c(0, 1)))
 			for(i in 1:ng) {
 				x = i
 				y = cumsum(tl[[i]])
 				grid.rect(y, x, height = bar_width, width = tl[[i]], just = "right", gp = gpar(fill = map_to_colors(cm, names(y))), default.units = "native")
 			}
 			if(axis) grid.draw(axis_grob)
 			if(border) grid.rect(gp = gpar(fill = "transparent"))
 			popViewport()
 		} else {
a93897a0
 			
cc35650a
 		}
bb20f132
 	}
 	column_fun = function(index) {
 		ht = get("object", envir = parent.frame(7))
 		mat = ht@matrix
 		cm = ht@matrix_color_mapping
 		order_list = ht@row_order_list
 		ng = length(order_list)
 
 		if(cm@type == "discrete") {
a93897a0
 			if(!is.null(ylim)) {
 				stop_wrap("For discrete matrix, `ylim` is not allowed to set. It is always c(0, 1).")
 			}
bb20f132
 			tl = lapply(order_list, function(od) table(mat[od, 1]))
 			tl = lapply(tl, function(x) x/sum(x))
cc35650a
 
bb20f132
 			pushViewport(viewport(xscale = c(0.5, ng+0.5), yscale = c(0, 1)))
 			for(i in 1:ng) {
 				x = i
 				y = cumsum(tl[[i]])
cc35650a
 				grid.rect(x, y, width = bar_width, height = tl[[i]], just = "top", gp = gpar(fill = map_to_colors(cm, names(y))), default.units = "native")
bb20f132
 			}
cc35650a
 			if(axis) grid.draw(axis_grob)
 			if(border) grid.rect(gp = gpar(fill = "transparent"))
bb20f132
 			popViewport()
cc35650a
 		} else {
a93897a0
 			vl = lapply(order_list, function(od) mat[od, 1])
 			nv = length(vl)
 			if(is.null(ylim)) {
 				if(!outline) {
 					boxplot_stats = boxplot(vl, plot = FALSE)$stats
 					data_scale = range(boxplot_stats)
 				} else {
 					data_scale = range(vl, na.rm = TRUE)
 				}
 			} else {
 				data_scale = ylim
 			}
 			data_scale = data_scale + c(-extend, extend)*(data_scale[2] - data_scale[1])
 
 			if(is.null(ylim)) {
 				axis_param = validate_axis_param(axis_param, which)
 				axis_grob = if(axis) construct_axis_grob(axis_param, which, data_scale) else NULL
 			}
 
 			gp = recycle_gp(gp, nv)
 			if(length(pch) == 1) pch = rep(pch, nv)
 			if(length(size) == 1) size = rep(size, nv)
 
 			pushViewport(viewport(xscale = c(0.5, ng+0.5), yscale = data_scale))
 			for(i in 1:ng) {
 				x = i
 				v = vl[[i]]
 				grid.boxplot(v, pos = x, box_width = box_width, gp = subset_gp(gp, i),
94175e6e
 					pch = pch, size = size, outline = outline)
a93897a0
 			}
 			if(axis) grid.draw(axis_grob)
 			if(border) grid.rect(gp = gpar(fill = "transparent"))
 			popViewport()
bb20f132
 		}
 	}
 
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
249fdfa0
 	
bb20f132
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_summary",
 		which = which,
 		width = width,
 		height = height,
a93897a0
 		var_import = list(bar_width, border, axis, axis_grob, axis_param, which, ylim, extend, 
 			outline, box_width, pch, size, gp),
bb20f132
 		n = 1,
 		show_name = FALSE
 	)
 
8ed806b6
 	anno@subsettable = FALSE
cc35650a
 	anno@extended = update_anno_extend(anno, axis_grob, axis_param)
 
bb20f132
 	return(anno)
249fdfa0
 }
 
8ef41188
 # == title
 # Block annotation
 #
 # == param
8ed806b6
 # -align_to If you don't want to create block annotation for all slices, you can specify a list of indices that cover continuously adjacent
 #        rows or columns.
33a5a58f
 # -gp Graphic parameters.
 # -labels Labels put on blocks.
 # -labels_gp Graphic parameters for labels.
 # -labels_rot Rotation for labels.
d635156e
 # -labels_offset Positions of the labels. It controls offset on y-directions for column annotation and on x-directoin for row annotation.
 # -labels_just Jusification of the labels.
33a5a58f
 # -which Is it a row annotation or a column annotation?
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
7b9eef96
 # -show_name Whether show annotatio name.
8ed806b6
 # -panel_fun A self-defined function that draws graphics in each slice. It must have two arguments: 1. row/column indices for the 
f64d04a9
 #      current slice and 2. a vector of levels from the split variable that correspond to current slice. When ``graphics`` is set,
59e468cd
 #      all other graphics parameters in `anno_block` are ignored.
8ef41188
 #
33a5a58f
 # == details
 # The block annotation is used for representing slices. The length of all arguments should be 1 or the number of slices.
 #
 # == value
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#block-annotation
 #
 # == example
 # Heatmap(matrix(rnorm(100), 10), 
 #     top_annotation = HeatmapAnnotation(foo = anno_block(gp = gpar(fill = 2:4),
 #         labels = c("group1", "group2", "group3"), labels_gp = gpar(col = "white"))),
 #     column_km = 3,
 #     left_annotation = rowAnnotation(foo = anno_block(gp = gpar(fill = 2:4),
 #         labels = c("group1", "group2", "group3"), labels_gp = gpar(col = "white"))),
 #     row_km = 3)
59e468cd
 #
 #
8ed806b6
 # # =============  set the panel_fun argument ==============
59e468cd
 # col = c("1" = "red", "2" = "blue", "A" = "green", "B" = "orange")
 # Heatmap(matrix(rnorm(100), 10), row_km = 2, row_split = sample(c("A", "B"), 10, replace = TRUE)) + 
 # rowAnnotation(foo = anno_block(
8ed806b6
 # 	panel_fun = function(index, levels) {
59e468cd
 # 		grid.rect(gp = gpar(fill = col[levels[2]], col = "black"))
 # 		grid.text(paste(levels, collapse = ","), 0.5, 0.5, rot = 90,
 # 			gp = gpar(col = col[levels[1]]))
 # 	}
 # ))
 #
 # labels = c("1" = "one", "2" = "two", "A" = "Group_A", "B" = "Group_B")
 # Heatmap(matrix(rnorm(100), 10), row_km = 2, row_split = sample(c("A", "B"), 10, replace = TRUE)) + 
8ed806b6
 # rowAnnotation(foo = anno_block(panel_fun = function(index, levels) {
59e468cd
 # 	grid.rect(gp = gpar(fill = col[levels[2]], col = "black"))
 # 	grid.text(paste(labels[levels], collapse = ","), 0.5, 0.5, rot = 90,
 # 		gp = gpar(col = col[levels[1]]))
 # }))
 #
 # Heatmap(matrix(rnorm(100), 10), row_km = 2, row_split = sample(c("A", "B"), 10, replace = TRUE)) + 
 # rowAnnotation(foo = anno_block(
8ed806b6
 # 	panel_fun = function(index, levels) {
59e468cd
 # 		grid.rect(gp = gpar(fill = col[levels[2]], col = "black"))
 # 		txt = paste(levels, collapse = ",")
 # 		txt = paste0(txt, "\n", length(index), " rows")
 # 		grid.text(txt, 0.5, 0.5, rot = 0,
 # 			gp = gpar(col = col[levels[1]]))
 # 	},
 # 	width = unit(3, "cm")
 # ))
 #
8ed806b6
 # # =========== set align_to ################
 # col = c("foo" = "red", "bar" = "blue")
 # Heatmap(matrix(rnorm(100), 10), cluster_rows = FALSE) +
 # rowAnnotation(foo = anno_block(
 # 	align_to = list(foo = 1:4, bar = 6:10),
 # 	panel_fun = function(index, nm) {
 # 		grid.rect(gp = gpar(fill = col[nm]))
 # 		grid.text(nm, 0.5, 0.5)
 # 	},
 # 	width = unit(2, "cm"))
 # )
 anno_block = function(align_to = NULL, gp = gpar(), labels = NULL, labels_gp = gpar(), 
d635156e
 	labels_rot = ifelse(which == "row", 90, 0),
 	labels_offset = unit(0.5, "npc"), labels_just = "center",
59e468cd
 	which = c("column", "row"), width = NULL, height = NULL, show_name = FALSE,
8ed806b6
 	panel_fun = NULL) {
8ef41188
 
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
 	} else {
 		which = .ENV$current_annotation_which
 	}
8ed806b6
 	if(!is.null(align_to)) {
 		if(is.numeric(align_to)) {
 			align_to = list(v = align_to)
 		}
 		if(!is.list(align_to)) {
 			stop_wrap("`align_to` should be a list.")
 		}
 		if(is.null(names(align_to))) {
 			stop_wrap("`align_to` should be a named list.")
 		}
 	}
 
8ef41188
 	if(length(labels)) {
 		if(which == "column") {
82c17797
 			if(missing(height)) {
 				height = grobHeight(textGrob(labels, rot = labels_rot, gp = labels_gp))
 				height = height + unit(5, "mm")
 			} else {
 				if(!inherits(height, "unit")) {
 					stop_wrap("Since you specified `height`, the value should be `unit` object.")
 				}
 			}
8ef41188
 		} else {
82c17797
 			if(missing(width)) {
 				width = grobWidth(textGrob(labels, rot = labels_rot, gp = labels_gp))
 				width = width + unit(5, "mm")
 			} else {
 				if(!inherits(width, "unit")) {
8b302163
 					stop_wrap("Since you specified `width`, the value should be `unit` object.")
 				}
82c17797
 			}
8ef41188
 		}
 	}
 
8ed806b6
 	if(!is.null(panel_fun)) {
 		if(length(as.list(formals(panel_fun))) == 1) {
 	 		formals(panel_fun) = alist(index = , nm = NULL)
 		}
 	}
 
8ef41188
 	anno_size = anno_width_and_height(which, width, height, unit(5, "mm"))
 	
 	fun = function(index, k, n) {
8ed806b6
 		if(!is.null(align_to)) {
 			is_in = sapply(align_to, function(x) any(x %in% index))
 			
 			ind_aln = which(is_in)
 
 			for(ai in ind_aln) {
 				ind = which(index %in% align_to[[ai]])
 				if(any(diff(ind) > 1)) {
 					stop_wrap("Indices in `align_to` should be continuously adjacent in the heatmap.")
 				}
 
 				ni = length(index)
 
 				if(which == "row") {
 					pushViewport(viewport(y = (ni - ind[length(ind)])/ni, height = length(ind)/ni, default.units = "npc", just = "bottom"))
 					panel_fun(index[ind], names(align_to)[ai])
 					popViewport()
 				} else {
 					pushViewport(viewport(x = (ind[length(ind)])/ni, width = length(ind)/ni, default.units = "npc", just = "right"))
 					panel_fun(index[ind], names(align_to)[ai])
 					popViewport()
 				}
 			}
 
 		} else if(is.null(panel_fun)) {
59e468cd
 			gp = subset_gp(recycle_gp(gp, n), k)
 			grid.rect(gp = gp)
 			if(length(labels)) {
 				if(length(labels) != n) {
 					stop_wrap("Length of `labels` should be as same as number of slices.")
 				}
 				label = labels[k]
 				labels_gp = subset_gp(recycle_gp(labels_gp, n), k)
 				x = y = unit(0.5, "npc")
 				if(which == "column") y = labels_offset
 				if(which == "row") x = labels_offset
 				grid.text(label, x = x, y = y, gp = labels_gp, rot = labels_rot, just = labels_just)
 			}
8ed806b6
 		} else  {
59e468cd
 
 			for(ifa in 1:30) {
 				if(exists("ht_main", envir = parent.frame(ifa))) {
 					ht = get("ht_main", envir = parent.frame(ifa))
 					break
 				}
 			}
 			
 			if(which == "row") {
 				split = ht@matrix_param$row_split
 				order_list = ht@row_order_list
 			} else {
 				split = ht@matrix_param$column_split
 				order_list = ht@column_order_list
 			}
 			if(is.null(split)) {
8ed806b6
 				panel_fun(index, NULL)
59e468cd
 			} else {
8ed806b6
 				panel_fun(index, unlist(split[order_list[[k]][1], ]))
8ef41188
 			}
 		}
 	}
 
 	anno = AnnotationFunction(
 		fun = fun,
 		n = NA,
 		fun_name = "anno_block",
 		which = which,
8ed806b6
 		var_import = list(gp, labels, labels_gp, labels_rot, labels_offset, labels_just, panel_fun, which, align_to),
8ef41188
 		subset_rule = list(),
8ed806b6
 		subsettable = TRUE,
8ef41188
 		height = anno_size$height,
 		width = anno_size$width,
7b9eef96
 		show_name = show_name
8ef41188
 	)
 	return(anno) 
 }
fda8e6f1
 
 # == title
 # Zoom annotation
 #
 # == param
817f7632
 # -align_to It defines how the boxes correspond to the rows or the columns in the heatmap.
 #    If the value is a list of indices, each box corresponds to the rows or columns with indices
 #    in one vector in the list. If the value is a categorical variable (e.g. a factor or a character vector)
 #    that has the same length as the rows or columns in the heatmap, each box corresponds to the rows/columns
 #    in each level in the categorical variable.
 # -panel_fun A self-defined function that defines how to draw graphics in the box. The function must have
 #     a ``index`` argument which is the indices for the rows/columns that the box corresponds to. It can 
 #     have second argument ``nm`` which is the "name" of the selected part in the heatmap. The corresponding
 #     value for ``nm`` comes from ``align_to`` if it is specified as a categorical variable or a list with names.
fda8e6f1
 # -which Whether it is a column annotation or a row annotation?
 # -side Side of the boxes If it is a column annotation, valid values are "top" and "bottom";
 #       If it is a row annotation, valid values are "left" and "right".
817f7632
 # -size The size of boxes. It can be pure numeric that they are treated as relative fractions of the total
 #      height/width of the heatmap. The value of ``size`` can also be absolute units.
 # -gap Gaps between boxes.
fda8e6f1
 # -link_gp Graphic settings for the segments.
 # -link_width Width of the segments.
 # -link_height Similar as ``link_width``, used for column annotation.
 # -extend By default, the region for the labels has the same width (if it is a column annotation) or
 #         same height (if it is a row annotation) as the heatmap. The size can be extended by this options.
 #         The value can be a proportion number or  a `grid::unit` object. The length can be either one or two.
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
e83bbdbf
 # -internal_line Internally used.
fda8e6f1
 #
 # == details
817f7632
 # `anno_zoom` creates several plotting regions (boxes) which can be corresponded to subsets of rows/columns in the
 # heatmap.
fda8e6f1
 #
817f7632
 # == value
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
 # == seealso
 # https://jokergoo.github.io/ComplexHeatmap-reference/book/heatmap-annotations.html#zoom-annotation
 #
 # == example
1f0f3def
 # set.seed(123)
817f7632
 # m = matrix(rnorm(100*10), nrow = 100)
1f0f3def
 # subgroup = sample(letters[1:3], 100, replace = TRUE, prob = c(1, 5, 10))
 # rg = range(m)
817f7632
 # panel_fun = function(index, nm) {
1f0f3def
 # 	pushViewport(viewport(xscale = rg, yscale = c(0, 2)))
817f7632
 # 	grid.rect()
1f0f3def
 # 	grid.xaxis(gp = gpar(fontsize = 8))
 # 	grid.boxplot(m[index, ], pos = 1, direction = "horizontal")
 # 	grid.text(paste("distribution of group", nm), mean(rg), y = 1.9, 
 # 		just = "top", default.units = "native", gp = gpar(fontsize = 10))
 # 	popViewport()
817f7632
 # }
1f0f3def
 # anno = anno_zoom(align_to = subgroup, which = "row", panel_fun = panel_fun, 
 # 	size = unit(2, "cm"), gap = unit(1, "cm"), width = unit(4, "cm"))
 # Heatmap(m, right_annotation = rowAnnotation(foo = anno), row_split = subgroup)
 #
fda8e6f1
 anno_zoom = function(align_to, panel_fun = function(index, nm = NULL) { grid.rect() }, 
 	which = c("column", "row"), side = ifelse(which == "column", "top", "right"),
 	size = NULL, gap = unit(1, "mm"), 
 	link_width = unit(5, "mm"), link_height = link_width, link_gp = gpar(),
e83bbdbf
 	extend = unit(0, "mm"), width = NULL, height = NULL, internal_line = TRUE) {
cb0ddbad
 
fda8e6f1
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
 	anno_size = anno_width_and_height(which, width, height, unit(2, "cm") + link_width)
 
 	# align_to should be
 	# 1. a vector of class labels that the length should be same as the nrow of the matrix
 	# 2. a list of numeric indices
 
 	if(is.list(align_to)) {
 		if(!any(sapply(align_to, is.numeric))) {
 			stop_wrap(paste0("`at` should be numeric ", which, " index corresponding to the matrix."))
 		}
 	}
 
 	.pos = NULL # position of the rows
 
 	if(length(as.list(formals(panel_fun))) == 1) {
  		formals(panel_fun) = alist(index = , nm = NULL)
 	}
 
 	if(length(extend) == 1) extend = rep(extend, 2)
 	if(length(extend) > 2) extend = extend[1:2]
 	if(!inherits(extend, "unit")) extend = unit(extend, "npc")
 
 	# anno_zoom is always executed in one-slice mode (which means mulitple slices
 	# are treated as one big slilce)
 	row_fun = function(index) {
2b464013
 
 		if(is_RStudio_current_dev()) {
 			if(ht_opt$message) {
78f8d520
 				message_wrap("It seems you are using RStudio IDE. `anno_zoom()`/`anno_link()` needs to work with the physical size of the graphics device. It only generates correct plot in the figure panel, while in the zoomed plot (by clicking the icon 'Zoom') or in the exported plot (by clicking the icon 'Export'), the connection to heatmap rows/columns might be wrong. You can directly use e.g. pdf() to save the plot into a file.\n\nUse `ht_opt$message = FALSE` to turn off this message.")
2b464013
 			}
 		}
 
fda8e6f1
 		n = length(index)
 		if(is.atomic(align_to)) {
 			if(length(setdiff(align_to, index)) == 0 && !any(duplicated(align_to))) {
 				align_to = list(align_to)
 			} else {
 				if(length(align_to) != n) {
 					stop_wrap("If `align_to` is a vector with group labels, the length should be the same as the number of rows in the heatmap.")
 				}
 				lnm = as.character(unique(align_to[index]))
 				align_to = as.list(tapply(seq_along(align_to), align_to, function(x) x))
 				align_to = align_to[lnm]
 			}
 		}
 
17eda132
 		## adjust index order 
 		align_to = lapply(align_to, function(x) intersect(index, x))
 
fda8e6f1
 		nrl = sapply(align_to, length)
 		align_to_df = lapply(align_to, function(x) {
 			ind = which(index %in% x)
 			n = length(ind)
 			s = NULL
 			e = NULL
 			s[1] = ind[1]
 			if(n > 1) {
 				ind2 = which(ind[2:n] - ind[1:(n-1)] > 1)
 				if(length(ind2)) s = c(s, ind[ ind2 + 1 ])
 				k = length(s)
 				e[k] = ind[length(ind)]
 				if(length(ind2)) e[1:(k-1)] = ind[1:(n-1)][ ind2 ]
 			} else {
 				e = ind[1]
 			}
 			data.frame(s = s, e = e)
 		})
 
 		# pos is from top to bottom
 		if(is.null(.pos)) {
 			pos = (n:1 - 0.5)/n # position of rows
 		} else {
 			pos = .pos
 		}
 
 		.scale = c(0, 1)
 		pushViewport(viewport(xscale = c(0, 1), yscale = .scale))
 		if(inherits(extend, "unit")) extend = convertHeight(extend, "native", valueOnly = TRUE)
 		
 		# the position of boxes initially are put evenly
 		# add the gap
 		n_boxes = length(align_to)
 		if(length(gap) == 1) gap = rep(gap, n_boxes)
 		if(is.null(size)) size = nrl
817f7632
 		if(length(size) == 1) size = rep(size, length(align_to))
fda8e6f1
 		if(length(size) != length(align_to)) {
 			stop_wrap("Length of `size` should be the same as the number of groups of indices.")
 		}
 		if(!inherits(size, "unit")) {
 			size_is_unit = FALSE
 			if(n_boxes == 1) {
 				h = data.frame(bottom = .scale[1] - extend[1], top = .scale[2] + extend[2])
 			} else {
ab596f42
 				size = as.numeric(size)
fda8e6f1
 				gap = convertHeight(gap, "native", valueOnly = TRUE)
 				box_height = size/sum(size) * (1 + sum(extend) - sum(gap[1:(n_boxes-1)]))
 				h = data.frame(
 						top = cumsum(box_height) + cumsum(gap) - gap[length(gap)] - extend[1]
 					)
 				h$bottom = h$top - box_height
 				h = 1 - h[, 2:1]
 				colnames(h) = c("top", "bottom")
 			}
 		} else {
 			size_is_unit = TRUE
 			box_height = size
 			box_height2 = box_height # box_height2 adds the gap
 			for(i in 1:n_boxes) {
 				if(i == 1 || i == n_boxes) {
 					if(n_boxes > 1) {
 						box_height2[i] = box_height2[i] + gap[i]*0.5
 					}
 				} else {
 					box_height2[i] = box_height2[i] + gap[i]
 				}
 			}
 			box_height2 = convertHeight(box_height2, "native", valueOnly = TRUE)
 			# the original positions of boxes
1f0f3def
 			mean_pos = sapply(align_to_df, function(df) mean((pos[df[, 1]] + pos[df[, 2]])/2))
fda8e6f1
 			h1 = mean_pos - box_height2*0.5
 			h2 = mean_pos + box_height2*0.5
1f0f3def
 			h = smartAlign2(rev(h1), rev(h2), c(.scale[1] - extend[1], .scale[2] + extend[2]))
fda8e6f1
 			colnames(h) = c("bottom", "top")
 			h = h[nrow(h):1, , drop = FALSE]
 
 			# recalcualte h to remove gaps
 			gap_height = convertHeight(gap, "native", valueOnly = TRUE)
 			if(n_boxes > 1) {
 				for(i in 1:n_boxes) {
 					if(i == 1) {
 						h[i, "bottom"] = h[i, "bottom"] + gap_height[i]/2
 					} else if(i == n_boxes) {
 						h[i, "top"] = h[i, "top"] - gap_height[i]/2
 					} else {
 						h[i, "bottom"] = h[i, "bottom"] + gap_height[i]/2
 						h[i, "top"] = h[i, "top"] - gap_height[i]/2
 					}
 				}
 			}
 		}
 		popViewport()
 
 		# draw boxes
 		if(side == "right") {
 			pushViewport(viewport(x = link_width, just = "left", width = anno_size$width - link_width))
 		} else {
 			pushViewport(viewport(x = 0, just = "left", width = anno_size$width - link_width))
 		}
 		for(i in 1:n_boxes) {
 			current_vp_name = current.viewport()$name
 			pushViewport(viewport(y = (h[i, "top"] + h[i, "bottom"])/2, height = h[i, "top"] - h[i, "bottom"], 
 				default.units = "native"))
 			if(is.function(panel_fun)) panel_fun(align_to[[i]], names(align_to)[i])
 			popViewport()
 
 			if(current.viewport()$name != current_vp_name) {
 				stop_wrap("If you push viewports `panel_fun`, you need to pop all them out.")
 			}
 		}
 		popViewport()
 		# draw the links
c7d6b92b
 		if(is.null(link_gp$fill)) link_gp$fill = NA
fda8e6f1
 		link_gp = recycle_gp(link_gp, n_boxes)
 		if(side == "right") {
 			pushViewport(viewport(x = unit(0, "npc"), just = "left", width = link_width))
 		} else {
 			pushViewport(viewport(x = unit(1, "npc"), just = "right", width = link_width))
 		}
 		for(i in 1:n_boxes) {
 			df = align_to_df[[i]]
 			for(j in 1:nrow(df)) {
 				# draw each polygon
c7d6b92b
 				if(!internal_line) {
e83bbdbf
 					link_gp3 = link_gp2 = link_gp
 					link_gp2$col = link_gp$fill
 					link_gp2$lty = NULL
 					link_gp3$fill = NA
 					if(side == "right") {
 						grid.polygon(unit.c(unit(c(0, 0), "npc"), rep(link_width, 2)),
 							c(pos[df[j, 2]] - 0.5/n, pos[df[j, 1]] + 0.5/n, h[i, "top"], h[i, "bottom"]),
 							default.units = "native", gp = subset_gp(link_gp2, i))
 
 						grid.lines(unit.c(link_width, unit(c(0, 0), "npc"), link_width),
 							c(h[i, "bottom"], pos[df[j, 2]] - 0.5/n, pos[df[j, 1]] + 0.5/n, h[i, "top"]),
 							default.units = "native", gp = subset_gp(link_gp3, i))
 					} else {
 						grid.polygon(unit.c(rep(link_width, 2), unit(c(0, 0), "npc")),
 							c(pos[df[j, 2]] - 0.5/n, pos[df[j, 1]] + 0.5/n, h[i, "top"], h[i, "bottom"]),
 							default.units = "native", gp = subset_gp(link_gp2, i))
 
 						grid.lines(unit.c(unit(0, "npc"), rep(link_width, 2), unit(0, "npc")),
 							c(h[i, "bottom"], pos[df[j, 2]] - 0.5/n, pos[df[j, 1]] + 0.5/n, h[i, "top"]),
 							default.units = "native", gp = subset_gp(link_gp3, i))
 					}
 
fda8e6f1
 				} else {
e83bbdbf
 					if(side == "right") {
 						grid.polygon(unit.c(unit(c(0, 0), "npc"), rep(link_width, 2)),
 							c(pos[df[j, 2]] - 0.5/n, pos[df[j, 1]] + 0.5/n, h[i, "top"], h[i, "bottom"]),
 							default.units = "native", gp = subset_gp(link_gp, i))
 					} else {
 						grid.polygon(unit.c(rep(link_width, 2), unit(c(0, 0), "npc")),
 							c(pos[df[j, 2]] - 0.5/n, pos[df[j, 1]] + 0.5/n, h[i, "top"], h[i, "bottom"]),
 							default.units = "native", gp = subset_gp(link_gp, i))
 					}
fda8e6f1
 				}
 			}
 		}
 		popViewport()
 	}
 
 	column_fun = function(index) {
2b464013
 
 		if(is_RStudio_current_dev()) {
 			if(ht_opt$message) {
78f8d520
 				message_wrap("It seems you are using RStudio IDE. `anno_zoom()`/`anno_link()` needs to work with the physical size of the graphics device. It only generates correct plot in the figure panel, while in the zoomed plot (by clicking the icon 'Zoom') or in the exported plot (by clicking the icon 'Export'), the connection to heatmap rows/columns might be wrong. You can directly use e.g. pdf() to save the plot into a file.\n\nUse `ht_opt$message = FALSE` to turn off this message.")
2b464013
 			}
 		}
 
fda8e6f1
 		n = length(index)
 		
 		if(is.atomic(align_to)) {
 			if(length(setdiff(align_to, index)) == 0 && !any(duplicated(align_to))) {
 				align_to = list(align_to)
 			} else {
 				if(length(align_to) != n) {
 					stop_wrap("If `align_to` is a vector with group labels, the length should be the same as the number of columns in the heatmap.")
 				}
 				lnm = as.character(unique(align_to[index]))
 				align_to = as.list(tapply(seq_along(align_to), align_to, function(x) x))
 				align_to = align_to[lnm]
 			}
 		}
17eda132
 
 		align_to = lapply(align_to, function(x) intersect(index, x))
 
fda8e6f1
 		nrl = sapply(align_to, length)
 		align_to_df = lapply(align_to, function(x) {
 			ind = which(index %in% x)
 			n = length(ind)
 			s = NULL
 			e = NULL
 			s[1] = ind[1]
 			if(n > 1) {
 				ind2 = which(ind[2:n] - ind[1:(n-1)] > 1)
 				if(length(ind2)) s = c(s, ind[ ind2 + 1 ])
 				k = length(s)
 				e[k] = ind[length(ind)]
 				if(length(ind2)) e[1:(k-1)] = ind[1:(n-1)][ ind2 ]
 			} else {
 				e = ind[1]
 			}
 			data.frame(s = s, e = e)
 		})
 
 		if(is.null(.pos)) {
 			pos = (1:n - 0.5)/n 
 		} else {
 			pos = .pos
 		}
 
 		.scale = c(0, 1)
 		pushViewport(viewport(yscale = c(0, 1), xscale = .scale))
 		if(inherits(extend, "unit")) extend = convertWidth(extend, "native", valueOnly = TRUE)
 		
 		# the position of boxes initially are put evenly
 		# add the gap
 		n_boxes = length(align_to)
 		if(length(gap) == 1) gap = rep(gap, n_boxes)
 		if(is.null(size)) size = nrl
817f7632
 		if(length(size) == 1) size = rep(size, length(align_to))
fda8e6f1
 		if(length(size) != length(align_to)) {
 			stop_wrap("Length of `size` should be the same as the number of groups of indices.")
 		}
 		if(!inherits(size, "unit")) {
 			size_is_unit = FALSE
 			if(n_boxes == 1) {
 				h = data.frame(left = .scale[1] - extend[1], right = .scale[2] + extend[2])
 			} else {
ab596f42
 				size = as.numeric(size)
fda8e6f1
 				gap = convertWidth(gap, "native", valueOnly = TRUE)
 				box_width = size/sum(size) * (1 + sum(extend) - sum(gap[1:(n_boxes-1)]))
 				h = data.frame(
 						right = cumsum(box_width) + cumsum(gap) - gap[length(gap)] - extend[1]
 					)
 				h$left = h$right - box_width
 			}
 		} else {
 			size_is_unit = TRUE
 			box_width = size
 			box_width2 = box_width
 			for(i in 1:n_boxes) {
 				if(i == 1 || i == n_boxes) {
 					if(n_boxes > 1) {
 						box_width2[i] = box_width2[i] + gap[i]*0.5
 					}
 				} else {
 					box_width2[i] = box_width2[i] + gap[i]
 				}
 			}
 			box_width2 = convertWidth(box_width2, "native", valueOnly = TRUE)
 			# the original positions of boxes
1f0f3def
 			mean_pos = sapply(align_to_df, function(df) mean((pos[df[, 1]] + pos[df[, 2]])/2))
fda8e6f1
 			h1 = mean_pos - box_width2*0.5
 			h2 = mean_pos + box_width2*0.5
1f0f3def
 			h = smartAlign2(h1, h2, c(.scale[1] - extend[1], .scale[2] + extend[2]))
fda8e6f1
 			colnames(h) = c("left", "right")
 
 			# recalcualte h to remove gaps
 			gap_width = convertWidth(gap, "native", valueOnly = TRUE)
 			if(n_boxes > 1) {
 				for(i in 1:n_boxes) {
 					if(i == 1) {
 						h[i, "left"] = h[i, "left"] + gap_width[i]/2
 					} else if(i == n_boxes) {
 						h[i, "right"] = h[i, "right"] - gap_width[i]/2
 					} else {
 						h[i, "left"] = h[i, "left"] + gap_width[i]/2
 						h[i, "right"] = h[i, "right"] - gap_width[i]/2
 					}
 				}
 			}
 		}
 		popViewport()
 
 		# draw boxes
 		if(side == "top") {
 			pushViewport(viewport(y = link_height, just = "bottom", height = anno_size$height - link_height))
 		} else {
 			pushViewport(viewport(y = 0, just = "bottom", height = anno_size$height - link_height))
 		}
 		for(i in 1:n_boxes) {
 			current_vp_name = current.viewport()$name
 			pushViewport(viewport(x = (h[i, "right"] + h[i, "left"])/2, width = h[i, "right"] - h[i, "left"], 
 				default.units = "native"))
 			if(is.function(panel_fun)) panel_fun(align_to[[i]], names(align_to)[i])
 			popViewport()
 
 			if(current.viewport()$name != current_vp_name) {
 				stop_wrap("If you push viewports `panel_fun`, you need to pop all them out.")
 			}
 		}
 		popViewport()
 		# draw the links
c7d6b92b
 		if(is.null(link_gp$fill)) link_gp$fill = NA
fda8e6f1
 		link_gp = recycle_gp(link_gp, n_boxes)
 		if(side == "top") {
 			pushViewport(viewport(y = unit(0, "npc"), just = "bottom", height = link_height))
 		} else {
 			pushViewport(viewport(y = unit(1, "npc"), just = "top", height = link_height))
 		}
 		for(i in 1:n_boxes) {
 			df = align_to_df[[i]]
 			for(j in 1:nrow(df)) {
 				# draw each polygon
c7d6b92b
 				if(!internal_line) {
e83bbdbf
 					link_gp3 = link_gp2 = link_gp
 					link_gp2$col = link_gp$fill
 					link_gp2$lty = NULL
 					link_gp3$fill = NA
 
 					if(side == "top") {
 						grid.polygon(
 							c(pos[df[j, 2]] + 0.5/n, pos[df[j, 1]] - 0.5/n, h[i, "left"], h[i, "right"]),
 							unit.c(unit(c(0, 0), "npc"), rep(link_width, 2)),
 							default.units = "native", gp = subset_gp(link_gp2, i))
 
 						grid.lines(
 							c(h[i, "right"], pos[df[j, 2]] + 0.5/n, pos[df[j, 1]] - 0.5/n, h[i, "left"]),
 							unit.c(link_width,unit(c(0, 0), "npc"), link_width),
 							default.units = "native", gp = subset_gp(link_gp3, i))
 					} else {
 						grid.polygon(
 							c(pos[df[j, 2]] + 0.5/n, pos[df[j, 1]] - 0.5/n, h[i, "left"], h[i, "right"]),
 							unit.c(rep(link_width, 2), unit(c(0, 0), "npc")),
 							default.units = "native", gp = subset_gp(link_gp2, i))
 
 						grid.lines(
 							c(h[i, "right"], pos[df[j, 2]] + 0.5/n, pos[df[j, 1]] - 0.5/n, h[i, "left"]),
 							unit.c(unit(0, "npc"), rep(link_width, 2), unit(0, "npc")),
 							default.units = "native", gp = subset_gp(link_gp3, i))
 					}
fda8e6f1
 				} else {
e83bbdbf
 					if(side == "top") {
 						grid.polygon(
 							c(pos[df[j, 2]] + 0.5/n, pos[df[j, 1]] - 0.5/n, h[i, "left"], h[i, "right"]),
 							unit.c(unit(c(0, 0), "npc"), rep(link_width, 2)),
 							default.units = "native", gp = subset_gp(link_gp, i))
 					} else {
 						grid.polygon(
 							c(pos[df[j, 2]] + 0.5/n, pos[df[j, 1]] - 0.5/n, h[i, "left"], h[i, "right"]),
 							unit.c(rep(link_width, 2), unit(c(0, 0), "npc")),
 							default.units = "native", gp = subset_gp(link_gp, i))
 					}
fda8e6f1
 				}
 			}
 		}
 		popViewport()
 		
 	}
 
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 	
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_zoom",
 		which = which,
 		height = anno_size$height,
 		width = anno_size$width,
 		n = -1,
 		var_import = list(align_to, .pos, gap, size, panel_fun, side, anno_size, extend,
e83bbdbf
 			link_width, link_height, link_gp, internal_line),
fda8e6f1
 		show_name = FALSE
 	)
 
 	anno@subset_rule$align_to = function(x, i) {
 		if(is.atomic(x)) {
 			x[i]
 		} else {
 			x = lapply(x, function(x) intersect(x, i))
 			x = x[sapply(x, length) > 0]
 		}
 	}
 
8ed806b6
 	anno@subsettable = TRUE
fda8e6f1
 	return(anno)
 }
 
c1b9be21
 # == title
 # Customized annotation
 #
 # == param
 # -x A categorical variable.
 # -graphics A list of functions that define graphics for each level in ``x``.
 # -which Is it a row annotation or a column annotation?
 # -width Width of the annotation. The value should be an absolute unit. Width is not allowed to be set for column annotation.
 # -height Height of the annotation. The value should be an absolute unit. Height is not allowed to be set for row annotation.
 # -border Whether to draw border.
 # -verbose Whether to print messages.
 #
 # == details
 # Functions in ``graphics`` define simple graphics drawn in each annotation cell. The function takes four arguments:
 #
 # -x,y Center of the annotation cell.
 # -w,h Width and height of the annotation cell.
 #
 # == value
 # An annotation function which can be used in `HeatmapAnnotation`.
 #
 # == example
 # x = sort(sample(letters[1:3], 10, replace = TRUE))
 # graphics = list(
 #     "a" = function(x, y, w, h) grid.points(x, y, pch = 16),
 #     "b" = function(x, y, w, h) grid.rect(x, y, w*0.8, h*0.8, gp = gpar(fill = "red")),
 #     "c" = function(x, y, w, h) grid.segments(x - 0.5*w, y - 0.5*h, x + 0.5*w, y + 0.5*h, gp = gpar(lty = 2))
 # )
 #
 # anno = anno_customize(x, graphics = graphics)
 #
 # m = matrix(rnorm(100), 10)
 # Heatmap(m, top_annotation = HeatmapAnnotation(bar = x, foo = anno))
 #
 # # Add legends for `foo`
 # ht = Heatmap(m, top_annotation = HeatmapAnnotation(bar = x, foo = anno))
 # lgd = Legend(title = "foo", at = names(graphics), graphics = graphics)
 # draw(ht, annotation_legend_list = list(lgd))
 anno_customize = function(x, graphics = list(), which = c("column", "row"),  
 	border = TRUE, width = NULL, height = NULL, verbose = TRUE) {
 
 	if(is.null(.ENV$current_annotation_which)) {
 		which = match.arg(which)[1]
 	} else {
 		which = .ENV$current_annotation_which
 	}
 
 	anno_size = anno_width_and_height(which, width, height, unit(5, "mm"))
 
 	value = as.character(x)
 	n = length(value)
 
 	if(verbose) {
 		nm = setdiff(value, names(graphics))
 		if(length(nm)) {
 			message(qq("Note: following levels in `x` have no graphics defined:\n    @{paste(nm, collapse = ', ')}.\nSet `verbose = FALSE` in `anno_customize()` to turn off this message."))
 		}
 	}
 
 	row_fun = function(index, k = 1, N = 1) {
 		
 		n = length(index)
 
 		pushViewport(viewport(yscale = c(0.5, n+0.5)))
 		for(i in seq_len(n)) {
 			if(!is.null(graphics[[ value[index[i]] ]])) {
 				fun = graphics[[ value[index[i]] ]]
 				pushViewport(viewport(y = n-i+1, height = 1, default.units = "native"))
 				fun(unit(0.5, "npc"), unit(0.5, "npc"), unit(1, "npc"), unit(1, "npc"))
 				popViewport()
 			}
 		}
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
 
 	column_fun = function(index, k = 1, N = 1) {
 		
 		n = length(index)
 
 		pushViewport(viewport(xscale = c(0.5, n+0.5)))
 		for(i in seq_len(n)) {
 			if(!is.null(graphics[[ value[index[i]] ]])) {
 				fun = graphics[[ value[index[i]] ]]
 				pushViewport(viewport(x = i, width = 1, default.units = "native"))
 				fun(unit(0.5, "npc"), unit(0.5, "npc"), unit(1, "npc"), unit(1, "npc"))
 				popViewport()
 			}
 		}
 		if(border) grid.rect(gp = gpar(fill = "transparent"))
 		popViewport()
 	}
 
 	if(which == "row") {
 		fun = row_fun
 	} else if(which == "column") {
 		fun = column_fun
 	}
 
 	anno = AnnotationFunction(
 		fun = fun,
 		fun_name = "anno_customize",
 		which = which,
 		width = anno_size$width,
 		height = anno_size$height,
 		n = n,
 		var_import = list(value, border, graphics)
 	)
 
 	anno@subset_rule$value = subset_vector
 
8ed806b6
 	anno@subsettable = TRUE
c1b9be21
 		
 	return(anno) 
 }
fda8e6f1
 
8ed806b6
 
 
 # == title
 # Numeric labels annotation
 #
 # == param
 # -x A vector of numeric values.
 # -rg Range. A numeric vector of length two.
267a2219
 # -labels_gp Graphics parameters for labels.
 # -x_convert A function applied on ``x``. E.g. when ``x`` contains p-values, to map ``x`` to the heights of bars, a transformation of ``-log10(x)`` 
8ed806b6
 #      is normally applied.
 # -labels_format A function applied on ``x``. E.g., when ``x`` is a numeric, ``labels_format`` can be set to ``function(x) sprintf("\%.2f", x)``.
 # -labels_offset Offset of labels to the left or right of bars.
 # -bg_gp Graphics parameters for the background bars.
 # -bar_width Width of bars. Note it corresponds to the vertical direction.
 # -round_corners Whether to draw bars with round corners?
 # -r Radius of the round corners.
 # -which Row or column. Currently it only supports row annotation.
 # -align_to Which side bars as well as the labels are aligned to. Values can be "left" or "right". If ``x`` contains both positive and negative values,
 #       ``align_to`` can also be set to 0 so that bars are aligned to ``pos = 0``.
 # -width Width of the annotation.
 #
 # == example
 # m = matrix(rnorm(100), 10)
267a2219
 # x = rnorm(10)
8ed806b6
 # Heatmap(m, right_annotation = rowAnnotation(numeric = anno_numeric(x)))
 anno_numeric = function(x, rg = range(x), labels_gp = gpar(), x_convert = NULL, 
 	labels_format = NULL, labels_offset = unit(4, "pt"),
 	bg_gp = gpar(fill = "#8080FF", col = "#8080FF"), 
 	bar_width = unit(1, "npc") - unit(4, "pt"),
 	round_corners = TRUE, r = unit(0.05, "snpc"), 
     which = c("row", "column"), align_to = "left", width = NULL) {
 
 	which = match.arg(which)[1]
 	if(which == "column") {
 		stop_wrap("`anno_numeric()` can only be used as row annotation.")
 	}
 
 	if(!is.numeric(x)) {
 		stop_wrap("Input for `anno_numeric()` should be a numeric vector.")
 	}
 
     if(!is.null(labels_format)) {
     	labels = labels_format(x)
     } else {
     	labels = x
     }
 
 	if(!is.null(x_convert)) {
 		x = x_convert(x)
 		rg = range(x_convert(rg))
 	}
 
267a2219
 	if(rg[1] == rg[2]) {
 		rg[2] = rg[2] + .Machine$double.eps*1.1
 	}
 
8ed806b6
     x[x < rg[1]] = rg[1]
     x[x > rg[2]] = rg[2]
 
     if(missing(align_to) && (any(x > 0) & any(x < 0))) {
     	align_to = 0
     }
 
     cell_fun_pct = function(i) {
 
     	min_x = rg[1]
     	max_x = rg[2]
         pushViewport(viewport(xscale = rg))
         if(align_to == "right") {
         	if(round_corners) {
 	            grid.roundrect(x = unit(1, "npc"), 
 	                width = unit(x[i] - min_x, "native"), height = bar_width, r = r,
 	                just = "right", gp = subset_gp(bg_gp, i))
 	        } else {
 	        	grid.rect(x = unit(1, "npc"), 
 	                width = unit(x[i] - min_x, "native"), height = bar_width,
 	                just = "right", gp = subset_gp(bg_gp, i))
 	        }
             grid.text(labels[i], x = unit(1, "npc") - labels_offset, just = "right", gp = subset_gp(labels_gp, i))
         } else if(align_to == "left") {
         	if(round_corners) {
 	            grid.roundrect(x = unit(0, "npc"), 
 	                width = unit(x[i] - min_x, "native"), height = bar_width,  r = r,
 	                just = "left", gp = subset_gp(bg_gp, i))
 	        } else {
 	        	grid.rect(x = unit(0, "npc"), 
 	                width = unit(x[i] - min_x, "native"), height = bar_width,
 	                just = "left", gp = subset_gp(bg_gp, i))
 	        }
             grid.text(labels[i], x = unit(0, "npc") + labels_offset, just = "left", gp = subset_gp(labels_gp, i))
         } else if(align_to == 0) {
         	if(x[i] <= 0) {
         		if(round_corners) {
 		        	grid.roundrect(x = unit(0, "native"), 
 		                width = unit(-x[i], "native"), height = bar_width,  r = r,
 		                just = "right", gp = subset_gp(bg_gp, 1))
 		        } else {
 		        	grid.rect(x = unit(0, "native"), 
 		                width = unit(-x[i], "native"), height = bar_width, 
 		                just = "right", gp = subset_gp(bg_gp, 1))
 		        }
 	            grid.text(labels[i], x = unit(0, "native") - labels_offset, just = "right", gp = subset_gp(labels_gp, 1))
 	        } else {
 	        	if(round_corners) {
 		        	grid.roundrect(x = unit(0, "native"), 
 		                width = unit(x[i], "native"), height = bar_width,  r = r,
 		                just = "left", gp = subset_gp(bg_gp, 2))
 		        } else {
 		        	grid.rect(x = unit(0, "native"), 
 		                width = unit(x[i], "native"), height = bar_width,
 		                just = "left", gp = subset_gp(bg_gp, 2))
 		        }
 	            grid.text(labels[i], x = unit(0, "native") + labels_offset, just = "left", gp = subset_gp(labels_gp, 2))
 	        }
         }
         popViewport()
     }
 
     if(is.null(width)) {
     	if(align_to == "left" || align_to == "right") {
         	width = convertWidth(max(unit.c(unit(2, "cm"), max_text_width(labels, gp = labels_gp) + labels_offset*2)), "mm")
         } else {
         	l1 = x >= 0
         	l2 = x < 0
         	if(any(l1) && any(l2)) {
         		w1 = max_text_width(labels[l1], gp = subset_gp(labels_gp, l1)) + labels_offset*2
         		w2 = max_text_width(labels[l2], gp = subset_gp(labels_gp, l2)) + labels_offset*2
 				width = convertWidth(max(unit.c(unit(2, "cm"), w1 + w2)), "mm")
         		
         	} else {
         		width = convertWidth(max(unit.c(unit(2, "cm"), max_text_width(labels, gp = labels_gp) + labels_offset*2)), "mm")
         	}
         }
     }
     AnnotationFunction(
         cell_fun = cell_fun_pct,
         var_import = list(rg, labels, x, labels_gp, align_to, bg_gp, bar_width, labels_offset, round_corners, r), 
         which = "row",
         width = width
     )
 }