Browse code

add anno_link() which connect labels and subset of rows

git-svn-id: https://hedgehog.fhcrc.org/bioconductor/trunk/madman/Rpacks/ComplexHeatmap@111679 bc3139a8-67e5-0310-9ffc-ced21a209358

z.gu authored on 20/12/2015 21:19:09
Showing 9 changed files

... ...
@@ -1,12 +1,12 @@
1 1
 Package: ComplexHeatmap
2 2
 Type: Package
3 3
 Title: Making Complex Heatmaps
4
-Version: 1.7.3
4
+Version: 1.7.4
5 5
 Date: 2015-11-24
6 6
 Author: Zuguang Gu
7 7
 Maintainer: Zuguang Gu <z.gu@dkfz.de>
8 8
 Depends: R (>= 3.1.2), grid, graphics, stats, grDevices
9
-Imports: methods, circlize (>= 0.3.1), GetoptLong, colorspace,
9
+Imports: methods, circlize (>= 0.3.4), GetoptLong, colorspace,
10 10
     RColorBrewer, dendextend (>= 1.0.1), GlobalOptions (>= 0.0.6)
11 11
 Suggests: testthat (>= 0.3), knitr, markdown, cluster, MASS, pvclust, 
12 12
     dendsort, HilbertCurve
... ...
@@ -43,8 +43,9 @@ export(anno_barplot)
43 43
 exportMethods(row_dend)
44 44
 export(row_anno_boxplot)
45 45
 export(max_text_width)
46
-export(decorate_row_title)
46
+export(anno_link)
47 47
 exportMethods(make_layout)
48
+export(decorate_row_title)
48 49
 export(anno_histogram)
49 50
 export(plotDataFrame)
50 51
 exportMethods(draw_dimnames)
... ...
@@ -68,9 +69,10 @@ export(Heatmap)
68 69
 exportMethods(draw_annotation_legend)
69 70
 exportMethods(column_dend)
70 71
 exportMethods(set_component_height)
72
+export(row_anno_link)
73
+exportMethods(make_column_cluster)
71 74
 export(HeatmapList)
72 75
 exportMethods(draw_annotation)
73
-exportMethods(make_column_cluster)
74 76
 export(SingleAnnotation)
75 77
 export(row_anno_histogram)
76 78
 exportMethods(get_color_mapping_list)
... ...
@@ -83,6 +85,7 @@ export(grid.dendrogram)
83 85
 export(decorate_title)
84 86
 export(decorate_dend)
85 87
 export(rowAnnotation)
88
+export(column_anno_link)
86 89
 export(columnAnnotation)
87 90
 export(decorate_row_names)
88 91
 export(anno_text)
... ...
@@ -94,6 +97,7 @@ importFrom("methods", setMethod)
94 97
 importFrom("methods", setGeneric)
95 98
 importFrom("circlize", colorRamp2)
96 99
 importFrom("circlize", rand_color)
100
+importFrom("circlize", smartAlign)
97 101
 importFrom("GetoptLong", qq)
98 102
 importFrom("GetoptLong", qqcat)
99 103
 importFrom("GetoptLong", qq.options)
... ...
@@ -101,6 +105,7 @@ importFrom("colorspace", rainbow_hcl)
101 105
 importFrom("colorspace", diverge_hcl)
102 106
 importFrom("RColorBrewer", brewer.pal)
103 107
 importFrom("dendextend", get_branches_heights)
108
+importFrom("dendextend", nnodes)
104 109
 import(graphics)
105 110
 import(stats)
106 111
 import(grDevices)
... ...
@@ -1,9 +1,19 @@
1
+CHANEGS in VERSION 1.7.4
2
+
3
+* width of the heatmap body are calculated correctly if it is set as a fixed unit
4
+* there is no dendrogram is nrows in a row-slice is 1
5
+* add `anno_link()` annotation function
6
+
7
+===============================
8
+
1 9
 CHANGES in VERSION 1.7.3
2 10
 
3 11
 * `oncoPrint()`: add `barplot_ignore` option to remove alterations
4 12
   that are not put on the barplot.
5 13
 * `oncoPrint()`: delete extra alter_fun if they are not in the matrix
6 14
 
15
+================================
16
+
7 17
 CHANGES in VERSION 1.7.2
8 18
 
9 19
 * for `anno_points()`, `anno_barplot()`, `anno_boxplot()`, the name is assigned
... ...
@@ -1310,6 +1310,9 @@ setMethod(f = "draw_dend",
1310 1310
 
1311 1311
     dend = as.dendrogram(hc)
1312 1312
     n = length(labels(dend))
1313
+    if(nnodes(dend) <= 1) {
1314
+        return(invisible(NULL))
1315
+    }
1313 1316
 
1314 1317
     dend_padding = unit(1, "mm")
1315 1318
     pushViewport(viewport(name = paste(object@name, which, "cluster", k, sep = "_"), ...))
... ...
@@ -1555,7 +1558,7 @@ setMethod(f = "component_width",
1555 1558
                 if(!is.unit(object@heatmap_param$width)) {
1556 1559
                     unit(1, "null")
1557 1560
                 } else {
1558
-                    object@heatmap_param$width - sum(component_width(object, c(1:3, 5:7)))
1561
+                    object@heatmap_param$width
1559 1562
                 }
1560 1563
             }
1561 1564
         } else if(k == 5) {
... ...
@@ -1699,7 +1702,6 @@ setMethod(f = "draw",
1699 1702
             pushViewport(viewport(layout = layout))
1700 1703
             ht_layout_index = object@layout$layout_index
1701 1704
             ht_graphic_fun_list = object@layout$graphic_fun_list
1702
-            
1703 1705
             for(j in seq_len(nrow(ht_layout_index))) {
1704 1706
                 if(ht_layout_index[j, 1] == 5 && ht_layout_index[j, 2] == 4) {
1705 1707
                     pushViewport(viewport(layout.pos.row = ht_layout_index[j, 1], layout.pos.col = ht_layout_index[j, 2], name = paste(object@name, "heatmap_body_wrap", sep = "_")))
... ...
@@ -951,3 +951,161 @@ column_anno_text = function(...) {
951 951
 	anno_text(..., which = "column")
952 952
 }
953 953
 
954
+# == title
955
+# Link annotation with labels
956
+#
957
+# == param
958
+# -at numeric index in the original matrix
959
+# -labels corresponding labels
960
+# -which column annotaiton or row annotation
961
+# -side side of the labels. If it is a column annotation, permitted values are "top" and "bottom";
962
+#       If it is a row annotation, permitted values are "left" and "right".
963
+# -lines_gp graphic settings for the segments
964
+# -labels_gp graphic settings for the labels
965
+# -padding padding between labels if they are attached to each other
966
+# -link_width, width of the segments.
967
+#
968
+# == details
969
+# Sometimes there are many rows or columns in the heatmap and we want to mark some of the rows.
970
+# This annotation function is used to mark these rows and connect labels and corresponding rows
971
+# with links.
972
+#
973
+# == author
974
+# Zuguang Gu <z.gu@dkfz.de>
975
+anno_link = function(at, labels, which = c("column", "row"), side = ifelse(which == "column", "top", "right"),
976
+	lines_gp = gpar(), labels_gp = gpar(), padding = 0.25, link_width = NULL) {
977
+
978
+	at = at
979
+	if(!is.numeric(at)) {
980
+		stop("`at` should be numeric index.")
981
+	}
982
+	labels = labels
983
+	which = match.arg(which)[1]
984
+	lines_gp = check_gp(lines_gp)
985
+	labels_gp = check_gp(labels_gp)
986
+	padding = padding
987
+
988
+	od = order(at)
989
+	at = at[od]
990
+	labels = labels[od]
991
+
992
+	f = switch(which,
993
+		row = function(index, k = NULL, N = NULL, vp_name = NULL) {
994
+			n = length(index)
995
+			l = which(at %in% index)
996
+			at = at[l]
997
+			labels = labels[l]
998
+			int = intersect(index, at)
999
+			int = structure(seq_along(int), names = int)
1000
+			labels = rev(labels[int[as.character(intersect(at, index))]])
1001
+			
1002
+			pushViewport(viewport(xscale = c(0, 1), yscale = c(0.5, n+0.5)))
1003
+			if(length(labels)) {
1004
+				text_height = convertHeight(grobHeight(textGrob(labels, gp = labels_gp))*(1+padding), "native", valueOnly = TRUE)
1005
+				i2 = rev(which(index %in% at))
1006
+				h1 = n-i2+1 - text_height*0.5
1007
+				h2 = n-i2+1 + text_height*0.5
1008
+				pos = rev(smartAlign(h1, h2, c(0.5, n+0.5)))
1009
+				h = (pos[, 1] + pos[, 2])/2
1010
+
1011
+				if(is.null(link_width)) {
1012
+					if(convertWidth(unit(1, "npc") - max_text_width(labels, gp = labels_gp), "mm", valueOnly = TRUE) < 0) {
1013
+						link_width = unit(0.5, "npc")
1014
+					} else {
1015
+						link_width = unit(1, "npc") - max_text_width(labels, gp = labels_gp)
1016
+					}
1017
+				}
1018
+				n2 = length(labels)
1019
+				if(side == "right") {
1020
+					grid.text(labels, rep(link_width, n2), h, default.units = "native", gp = labels_gp, just = "left")
1021
+					link_width = link_width - unit(1, "mm")
1022
+					grid.segments(unit(rep(0, n2), "npc"), n-i2+1, rep(link_width*(1/3), n2), n-i2+1, default.units = "native", gp = lines_gp)
1023
+					grid.segments(rep(link_width*(1/3), n2), n-i2+1, rep(link_width*(2/3), n2), h, default.units = "native", gp = lines_gp)
1024
+					grid.segments(rep(link_width*(2/3), n2), h, rep(link_width, n2), h, default.units = "native", gp = lines_gp)
1025
+				} else {
1026
+					grid.text(labels, rep(link_width, n2), h, default.units = "native", gp = labels_gp, just = "right")
1027
+					link_width = link_width - unit(1, "mm")
1028
+					grid.segments(unit(rep(1, n2), "npc"), n-i2+1, unit(1, "npc")-rep(link_width*(1/3), n2), n-i2+1, default.units = "native", gp = lines_gp)
1029
+					grid.segments(unit(1, "npc")-rep(link_width*(1/3), n2), n-i2+1, unit(1, "npc")-rep(link_width*(2/3), n2), h, default.units = "native", gp = lines_gp)
1030
+					grid.segments(unit(1, "npc")-rep(link_width*(2/3), n2), h, unit(1, "npc")-rep(link_width, n2), h, default.units = "native", gp = lines_gp)
1031
+				}
1032
+			}
1033
+			upViewport()
1034
+		},
1035
+		column = function(index, vp_name = NULL) {
1036
+			n = length(index)
1037
+			int = intersect(index, at)
1038
+			int = structure(seq_along(int), names = int)
1039
+			labels = rev(labels[int[as.character(intersect(at, index))]])
1040
+			pushViewport(viewport(yscale = c(0, 1), xscale = c(0.5, n+0.5)))
1041
+			text_height = convertWidth(grobHeight(textGrob(labels, gp = labels_gp))*(1+padding), "native", valueOnly = TRUE)
1042
+			i2 = which(index %in% at)
1043
+			h1 = i2 - text_height*0.5
1044
+			h2 = i2 + text_height*0.5
1045
+			pos = smartAlign(h1, h2, c(0.5, n+0.5))
1046
+			h = (pos[, 1] + pos[, 2])/2
1047
+			if(is.null(link_width)) {
1048
+				if(convertHeight(unit(1, "npc") - max_text_width(labels, gp = labels_gp), "mm", valueOnly = TRUE) < 0) {
1049
+					link_width = unit(0.5, "npc")
1050
+				} else {
1051
+					link_width = unit(1, "npc") - max_text_width(labels, gp = labels_gp)
1052
+				}
1053
+			}
1054
+			n2 = length(labels)
1055
+			if(side == "top") {
1056
+				grid.text(labels, h, rep(link_width, n2), default.units = "native", gp = labels_gp, rot = 90, just = "left")
1057
+				link_width = link_width - unit(1, "mm")
1058
+				grid.segments(i2, unit(rep(0, n2), "npc"), i2, rep(link_width*(1/3), n2), default.units = "native", gp = lines_gp)
1059
+				grid.segments(i2, rep(link_width*(1/3), n2), h, rep(link_width*(2/3), n2), default.units = "native", gp = lines_gp)
1060
+				grid.segments(h, rep(link_width*(2/3), n2), h, rep(link_width, n), default.units = "native", gp = lines_gp)
1061
+			} else {
1062
+				grid.text(labels, h, rep(link_width, n2), default.units = "native", gp = labels_gp, rot = 90, just = "right")
1063
+				link_width = link_width - unit(1, "mm")
1064
+				grid.segments(i2, unit(rep(1, n2), "npc"), i2, unit(1, "npc")-rep(link_width*(1/3), n2), default.units = "native", gp = lines_gp)
1065
+				grid.segments(i2, unit(1, "npc")-rep(link_width*(1/3), n2), h, unit(1, "npc")-rep(link_width*(2/3), n2), default.units = "native", gp = lines_gp)
1066
+				grid.segments(h, unit(1, "npc")-rep(link_width*(2/3), n2), h, unit(1, "npc")-rep(link_width, n2), default.units = "native", gp = lines_gp)
1067
+			}
1068
+			upViewport()
1069
+		})
1070
+	attr(f, "which") = which
1071
+	attr(f, "fun") = "anno_link"
1072
+	return(f)
1073
+}
1074
+
1075
+# == title
1076
+# Column annotation which is represented as links
1077
+#
1078
+# == param
1079
+# -... pass to `anno_link`
1080
+#
1081
+# == details
1082
+# A wrapper of `anno_link` with pre-defined ``which`` to ``row``.
1083
+#
1084
+# == value
1085
+# See help page of `anno_link`
1086
+#
1087
+# == author
1088
+# Zuguang Gu <z.gu@dkfz.de>
1089
+#
1090
+row_anno_link = function(...) {
1091
+	anno_link(..., which = "row")
1092
+}
1093
+
1094
+# == title
1095
+# Column annotation which is represented as links
1096
+#
1097
+# == param
1098
+# -... pass to `anno_link`
1099
+#
1100
+# == details
1101
+# A wrapper of `anno_link` with pre-defined ``which`` to ``column``.
1102
+#
1103
+# == value
1104
+# See help page of `anno_link`
1105
+#
1106
+# == author
1107
+# Zuguang Gu <z.gu@dkfz.de>
1108
+#
1109
+column_anno_link = function(...) {
1110
+	anno_link(..., which = "column")
1111
+}
954 1112
new file mode 100644
... ...
@@ -0,0 +1,36 @@
1
+\name{anno_link}
2
+\alias{anno_link}
3
+\title{
4
+Link annotation with labels
5
+}
6
+\description{
7
+Link annotation with labels
8
+}
9
+\usage{
10
+anno_link(at, labels, which = c("column", "row"), side = ifelse(which == "column", "top", "right"),
11
+    lines_gp = gpar(), labels_gp = gpar(), padding = 0.25, link_width = NULL)
12
+}
13
+\arguments{
14
+
15
+  \item{at}{numeric index in the original matrix}
16
+  \item{labels}{corresponding labels}
17
+  \item{which}{column annotaiton or row annotation}
18
+  \item{side}{side of the labels. If it is a column annotation, permitted values are "top" and "bottom"; If it is a row annotation, permitted values are "left" and "right".}
19
+  \item{lines_gp}{graphic settings for the segments}
20
+  \item{labels_gp}{graphic settings for the labels}
21
+  \item{padding}{padding between labels if they are attached to each other}
22
+  \item{link_width,}{width of the segments.}
23
+
24
+}
25
+\details{
26
+Sometimes there are many rows or columns in the heatmap and we want to mark some of the rows.
27
+This annotation function is used to mark these rows and connect labels and corresponding rows
28
+with links.
29
+}
30
+\author{
31
+Zuguang Gu <z.gu@dkfz.de>
32
+}
33
+\examples{
34
+# There is no example
35
+NULL
36
+}
0 37
new file mode 100644
... ...
@@ -0,0 +1,29 @@
1
+\name{column_anno_link}
2
+\alias{column_anno_link}
3
+\title{
4
+Column annotation which is represented as links
5
+}
6
+\description{
7
+Column annotation which is represented as links
8
+}
9
+\usage{
10
+column_anno_link(...)
11
+}
12
+\arguments{
13
+
14
+  \item{...}{pass to \code{\link{anno_link}}}
15
+
16
+}
17
+\details{
18
+A wrapper of \code{\link{anno_link}} with pre-defined \code{which} to \code{column}.
19
+}
20
+\value{
21
+See help page of \code{\link{anno_link}}
22
+}
23
+\author{
24
+Zuguang Gu <z.gu@dkfz.de>
25
+}
26
+\examples{
27
+# There is no example
28
+NULL
29
+}
0 30
new file mode 100644
... ...
@@ -0,0 +1,29 @@
1
+\name{row_anno_link}
2
+\alias{row_anno_link}
3
+\title{
4
+Column annotation which is represented as links
5
+}
6
+\description{
7
+Column annotation which is represented as links
8
+}
9
+\usage{
10
+row_anno_link(...)
11
+}
12
+\arguments{
13
+
14
+  \item{...}{pass to \code{\link{anno_link}}}
15
+
16
+}
17
+\details{
18
+A wrapper of \code{\link{anno_link}} with pre-defined \code{which} to \code{row}.
19
+}
20
+\value{
21
+See help page of \code{\link{anno_link}}
22
+}
23
+\author{
24
+Zuguang Gu <z.gu@dkfz.de>
25
+}
26
+\examples{
27
+# There is no example
28
+NULL
29
+}
... ...
@@ -529,6 +529,23 @@ draw(ht_list, column_title = "reverse axis direction", newpage = FALSE)
529 529
 upViewport(2)
530 530
 ```
531 531
 
532
+## Mark some of the rows/columns
533
+
534
+From version 1.7.4, a new annotation function `anno_link()` was added which connects labels and subset of the rows
535
+by links. It is helpful when there are many rows/columns and we want to mark some of the rows (e.g. in a gene expression
536
+matrix, we want to mark some important genes of interest.)
537
+
538
+```{r}
539
+mat = matrix(rnorm(10000), nr = 1000)
540
+labels = sample(letters, 20, replace = TRUE)
541
+Heatmap(mat, show_row_dend = FALSE, show_column_dend = FALSE) + 
542
+rowAnnotation(link = row_anno_link(at = sample(1000, 20), labels = labels),
543
+  width = unit(1, "cm") + max_text_width(labels))
544
+# here unit(1, "cm") is width of segments
545
+```
546
+
547
+There are also two shortcut functions: `row_anno_link()` and `column_anno_link()`.
548
+
532 549
 ## Session info
533 550
 
534 551
 ```{r}