diff --git a/DESCRIPTION b/DESCRIPTION index 292779f9..7290c7ae 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Package: ComplexHeatmap Type: Package Title: Make Complex Heatmaps -Version: 2.3.2 -Date: 2020-03-05 +Version: 2.3.3 +Date: 2020-03-21 Author: Zuguang Gu Maintainer: Zuguang Gu Depends: R (>= 3.1.2), methods, grid, graphics, stats, grDevices diff --git a/NAMESPACE b/NAMESPACE index c7dad645..c5508fb8 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -12,6 +12,8 @@ S3method("[", "SingleAnnotation") export("[.SingleAnnotation") S3method("[", "comb_mat") export("[.comb_mat") +S3method("[", "gridtext") +export("[.gridtext") S3method("c", "ColorMapping") export("c.ColorMapping") S3method("c", "HeatmapAnnotation") @@ -173,6 +175,7 @@ export("getXY_in_parent_vp") export("grid.annotation_axis") export("grid.boxplot") export("grid.dendrogram") +export("gt_render") export("ht_global_opt") export("ht_opt") export("is_abs_unit") diff --git a/NEWS b/NEWS index 61f46366..3108e4ee 100755 --- a/NEWS +++ b/NEWS @@ -1,3 +1,9 @@ +CHANGES in VERSION 2.3.3 + +* support **gridtext** package + +======================== + CHANGES in VERSION 2.3.2 * `anno_simple()`: fixed a bug when pch are all NA in a slice @@ -5,7 +11,8 @@ CHANGES in VERSION 2.3.2 * move scripts in test_not_run/ to tests/ folder * `Heatmap()`: `cluster_row_slice`/`cluster_column_slice` set to TRUE by default for character matrix and when dendrogram is already provided. -* `smartAlign2()`: improved the code +* `smartAlign2()`: improved the code that positions are correctly calculated. +* row titles are in the correct order if they are set as "template". ======================== diff --git a/R/AnnotationFunction-function.R b/R/AnnotationFunction-function.R index 2190ffbd..a8a4c175 100644 --- a/R/AnnotationFunction-function.R +++ b/R/AnnotationFunction-function.R @@ -2017,10 +2017,6 @@ anno_text = function(x, which = c("column", "row"), gp = gpar(), rot = guess_rot(), just = guess_just(), offset = guess_location(), location = guess_location(), width = NULL, height = NULL) { - - if(inherits(x, "richtext_grob")) { - return(anno_richtext(x, which = which, width = width, height = height)) - } ef = function() NULL if(is.null(.ENV$current_annotation_which)) { @@ -2154,81 +2150,6 @@ anno_text = function(x, which = c("column", "row"), gp = gpar(), return(anno) } -anno_richtext = function(x, which = c("column", "row"), width = NULL, height = NULL) { - - # ef = function() NULL - # if(is.null(.ENV$current_annotation_which)) { - # which = match.arg(which)[1] - # dev.null() - # ef = dev.off2 - # } else { - # which = .ENV$current_annotation_which - # } - - # on.exit(ef()) - - # n = length(x) - - # if(which == "column") { - # if(missing(height)) { - # height = grobHeight(x) - # height = convertHeight(height, "mm") - # } - # if(missing(width)) { - # width = unit(1, "npc") - # } - # } - # if(which == "row") { - # if(missing(width)) { - # width = grobWidth(x) - # 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) - # gb = value[index] - # gb = update_xy(gb, y = unit((n - seq_along(index) + 0.5)/n, "native")) - # grid.draw(gb) - # } - # column_fun = function(index, k = NULL, N = NULL, vp_name = NULL) { - # n = length(index) - # gb = value[index] - # gb = update_xy(gb, x = unit((seq_along(index) - 0.5)/n, "native")) - # grid.draw(gb) - # } - - # if(which == "row") { - # fun = row_fun - # } else if(which == "column") { - # fun = column_fun - # } - - # anno = AnnotationFunction( - # fun = fun, - # fun_name = "anno_richtext", - # which = which, - # width = width, - # height = height, - # n = n, - # var_import = list(value), - # show_name = FALSE - # ) - - # anno@subset_rule$value = subset_vector - - # anno@subsetable = TRUE - - # return(anno) -} - # == title # Joyplot Annotation # diff --git a/R/gridtext.R b/R/gridtext.R new file mode 100644 index 00000000..90694816 --- /dev/null +++ b/R/gridtext.R @@ -0,0 +1,258 @@ +# `[.grob` = function(x, i) { +# x2 = x +# for(nm in SUBSETABLE_FIELDS[[ intersect(names(SUBSETABLE_FIELDS), class(x)) ]]) { +# if(inherits(x2[[nm]], "gpar")) { +# # change to the class defined here +# class(x2[[nm]]) = "gpar" +# } + +# if(length(x2[[nm]]) > 1) { +# x2[[nm]] = x2[[nm]][i] +# } +# } +# x2 +# } + +# `[.gpar` = function(x, i) { +# lapply(x, function(y) { +# if(length(y) > 1) { +# y[i] +# } else { +# y +# } +# }) +# } + +# SUBSETABLE_FIELDS = list( +# "text" = c("label", "x", "y", "gp"), +# "richtext_grob" = c("gp", "children", "childrenOrder") +# ) + +# length.text = function(x) { +# length(x$label) +# } + +# length.richtext_grob = function(x) { +# length(x$children) +# } + +# update_xy = function (gb, x, y, ...) { +# UseMethod("update_xy") +# } + +# update_xy.text = function(gb, x, y, ...) { +# n = length(gb$label) +# if(!missing(x)) { +# if(n > 1 & length(x) > 1 && n != length(x)) { +# stop_wrap("Length of `x` should be the same as the length of labels.") +# } +# gb$x = x +# } +# if(!missing(y)) { +# if(n > 1 & length(y) > 1 && n != length(y)) { +# stop_wrap("Length of `y` should be the same as the length of labels.") +# } +# gb$y = y +# } +# gb +# } + +# update_xy.richtext_grob = function(gb, x, y, ...) { +# n = length(gb$children) + +# if(!missing(x)) { +# if(n > 1 & length(x) > 1 && n != length(x)) { +# stop_wrap("Length of `x` should be the same as the length of labels.") +# } +# for(i in 1:n) { +# if(length(x) == 1) { +# gb$children[[i]]$vp$x = x +# } else { +# gb$children[[i]]$vp$x = x[i] +# } +# } +# } +# if(!missing(y)) { +# if(n > 1 & length(y) > 1 && n != length(y)) { +# stop_wrap("Length of `y` should be the same as the length of labels.") +# } +# for(i in 1:n) { +# if(length(y) == 1) { +# gb$children[[i]]$vp$y = y +# } else { +# gb$children[[i]]$vp$y = y[i] +# } +# } +# } +# gb +# } + +# textGrob = function(label, ...) { +# if(inherits(label, "grob")) { +# return(label) +# } else { +# grid::textGrob(label, ...) +# } +# } + +# grid.text = function(label, x = unit(0.5, "npc"), y = unit(0.5, "npc"), ...) { +# if(inherits(label, "grob")) { +# gb = label +# gb = update_xy(gb, x, y) +# grid.draw(gb) +# } else { +# grid::grid.text(label, x, y, ...) +# } +# } + +# == title +# Mark the text for the rendering by gridtext package +# +# == param +# -x Text labels. The value can be a vector. +# -... Other parameters passed to `gridtext::richtext_grob`. +# +# == details +# Text marked by `gt_render` will be rendered by `gridtext::richtext_grob` function. +# +# == example +# if(requireNamespace("gridtext")) { +# mat = matrix(rnorm(100), 10) +# rownames(mat) = letters[1:10] +# ht = Heatmap(mat, +# column_title = gt_render("Some blue text **in bold.**
And *italics text.*
And some large text.", r = unit(2, "pt"), padding = unit(c(2, 2, 2, 2), "pt")), +# column_title_gp = gpar(box_fill = "orange"), +# row_labels = gt_render(letters[1:10], padding = unit(c(2, 10, 2, 10), "pt")), +# row_names_gp = gpar(box_col = "red"), +# row_km = 2, +# row_title = gt_render(c("title1", "title2")), +# row_title_gp = gpar(box_fill = "yellow"), +# heatmap_legend_param = list( +# title = gt_render("**Legend title**"), +# title_gp = gpar(box_fill = "grey"), +# at = c(-3, 0, 3), +# labels = gt_render(c("*negative* three", "zero", "*positive* three")) +# )) +# ht = rowAnnotation( +# foo = anno_text(gt_render(sapply(LETTERS[1:10], strrep, 10), align_widths = TRUE), +# gp = gpar(box_col = "blue", box_lwd = 2), +# just = "right", +# location = unit(1, "npc") +# )) + ht +# draw(ht) +# +# } +gt_render = function(x, ...) { + if(!requireNamespace("gridtext")) { + stop_wrap("gridtext package needs to be installed.") + } + param = list(...) + class(x) = "gridtext" + attr(x, "param") = param + return(x) +} + +# == title +# Subset method of gridtext class +# +# == param +# -x A vector of labels generated by `gt_render`. +# -index Index +# +# == details +# Internally used. +"[.gridtext" = function(x, index) { + cl = class(x) + param = attr(x, "param") + class(x) = NULL + x = x[index] + class(x) = cl + attr(x, "param") = param + x +} + +grid.text = function(label, x = unit(0.5, "npc"), y = unit(0.5, "npc"), ...) { + if(inherits(label, "gridtext")) { + # cat("draw text by gridtext:\n") + # print(label) + # cat("\n") + grid.draw(richtext_grob2(label, x, y, ...)) + } else { + grid::grid.text(label, x, y, ...) + } +} + +textGrob = function(label, ...) { + if(inherits(label, "gridtext")) { + richtext_grob2(label, ...) + } else { + grid::textGrob(label, ...) + } +} + +normalize_just = function(x) { + if(is.character(x)) { + x[x == "centre"] = "center" + if(identical(x, "center")) { + return(c(0.5, 0.5)) + } else if(identical(x, c("center", "center"))) { + return(c(0.5, 0.5)) + } else if(identical(x, c("left", "center"))) { + return(c(0, 0.5)) + } else if(identical(x, c("right", "center"))) { + return(c(1, 0.5)) + } else if(identical(x, c("center", "top"))) { + return(c(0.5, 1)) + } else if(identical(x, c("center", "bottom"))) { + return(c(0.5, 0)) + } else if(identical(x, c("left", "bottom"))) { + return(c(0, 0)) + } else if(identical(x, c("left", "top"))) { + return(c(0, 1)) + } else if(identical(x, c("right", "bottom"))) { + return(c(1, 0)) + } else if(identical(x, c("right", "top"))) { + return(c(1, 1)) + } else if(identical(x, c("left"))) { + return(c(0, 0.5)) + } else if(identical(x, c("right"))) { + return(c(1, 0.5)) + } else if(identical(x, c("bottom"))) { + return(c(0.5, 0)) + } else if(identical(x, c("top"))) { + return(c(0.5, 1)) + } + } + if(length(x) == 1) x = c(x, x) + return(x) +} + +# argument from grid.text directly pass to richtext_grob, so some argument need +# to be adjusted, e.g. those not supported in richtext_grob +richtext_grob2 = function(label, ...) { + param0 = attr(label, "param") + param = list(text = label, ...) + for(nm in names(param0)) { + param[[nm]] = param0[[nm]] + } + if("just" %in% names(param)) { + j = normalize_just(param$just) + param$just = NULL + param$hjust = j[1] + param$vjust = j[2] + } + if("gp" %in% names(param)) { + gp = param$gp + l_box = grepl("^box_", names(gp)) + if(any(l_box)) { + box_gp = gp[l_box] + gp[l_box] = NULL + names(box_gp) = gsub("^box_", "", names(box_gp)) + class(gp) = "gpar" + class(box_gp) = "gpar" + param$gp = gp + param$box_gp = box_gp + } + } + do.call(gridtext::richtext_grob, param) +} diff --git a/R/grob_utils.R b/R/grob_utils.R deleted file mode 100644 index f007b78f..00000000 --- a/R/grob_utils.R +++ /dev/null @@ -1,106 +0,0 @@ -# `[.grob` = function(x, i) { -# x2 = x -# for(nm in SUBSETABLE_FIELDS[[ intersect(names(SUBSETABLE_FIELDS), class(x)) ]]) { -# if(inherits(x2[[nm]], "gpar")) { -# # change to the class defined here -# class(x2[[nm]]) = "gpar" -# } - -# if(length(x2[[nm]]) > 1) { -# x2[[nm]] = x2[[nm]][i] -# } -# } -# x2 -# } - -# `[.gpar` = function(x, i) { -# lapply(x, function(y) { -# if(length(y) > 1) { -# y[i] -# } else { -# y -# } -# }) -# } - -# SUBSETABLE_FIELDS = list( -# "text" = c("label", "x", "y", "gp"), -# "richtext_grob" = c("gp", "children", "childrenOrder") -# ) - -# length.text = function(x) { -# length(x$label) -# } - -# length.richtext_grob = function(x) { -# length(x$children) -# } - -# update_xy = function (gb, x, y, ...) { -# UseMethod("update_xy") -# } - -# update_xy.text = function(gb, x, y, ...) { -# n = length(gb$label) -# if(!missing(x)) { -# if(n > 1 & length(x) > 1 && n != length(x)) { -# stop_wrap("Length of `x` should be the same as the length of labels.") -# } -# gb$x = x -# } -# if(!missing(y)) { -# if(n > 1 & length(y) > 1 && n != length(y)) { -# stop_wrap("Length of `y` should be the same as the length of labels.") -# } -# gb$y = y -# } -# gb -# } - -# update_xy.richtext_grob = function(gb, x, y, ...) { -# n = length(gb$children) - -# if(!missing(x)) { -# if(n > 1 & length(x) > 1 && n != length(x)) { -# stop_wrap("Length of `x` should be the same as the length of labels.") -# } -# for(i in 1:n) { -# if(length(x) == 1) { -# gb$children[[i]]$vp$x = x -# } else { -# gb$children[[i]]$vp$x = x[i] -# } -# } -# } -# if(!missing(y)) { -# if(n > 1 & length(y) > 1 && n != length(y)) { -# stop_wrap("Length of `y` should be the same as the length of labels.") -# } -# for(i in 1:n) { -# if(length(y) == 1) { -# gb$children[[i]]$vp$y = y -# } else { -# gb$children[[i]]$vp$y = y[i] -# } -# } -# } -# gb -# } - -# textGrob = function(label, ...) { -# if(inherits(label, "grob")) { -# return(label) -# } else { -# grid::textGrob(label, ...) -# } -# } - -# grid.text = function(label, x = unit(0.5, "npc"), y = unit(0.5, "npc"), ...) { -# if(inherits(label, "grob")) { -# gb = label -# gb = update_xy(gb, x, y) -# grid.draw(gb) -# } else { -# grid::grid.text(label, x, y, ...) -# } -# } diff --git a/man/Extract.gridtext.Rd b/man/Extract.gridtext.Rd new file mode 100644 index 00000000..e8b6cd14 --- /dev/null +++ b/man/Extract.gridtext.Rd @@ -0,0 +1,26 @@ +\name{[.gridtext} +\alias{[.gridtext} +\alias{Extract.gridtext} +\title{ +Subset method of gridtext class +} +\description{ +Subset method of gridtext class +} +\usage{ +\method{[}{gridtext}(x, index) +} +\arguments{ + + \item{x}{A vector of labels generated by \code{\link{gt_render}}.} + \item{index}{Index} + +} +\details{ +Internally used. +} +\examples{ +# There is no example +NULL + +} diff --git a/man/gt_render.Rd b/man/gt_render.Rd new file mode 100644 index 00000000..9ccad7c3 --- /dev/null +++ b/man/gt_render.Rd @@ -0,0 +1,48 @@ +\name{gt_render} +\alias{gt_render} +\title{ +Mark the text for the rendering by gridtext package +} +\description{ +Mark the text for the rendering by gridtext package +} +\usage{ +gt_render(x, ...) +} +\arguments{ + + \item{x}{Text labels. The value can be a vector.} + \item{...}{Other parameters passed to \code{\link[gridtext]{richtext_grob}}.} + +} +\details{ +Text marked by \code{\link{gt_render}} will be rendered by \code{\link[gridtext]{richtext_grob}} function. +} +\examples{ +if(requireNamespace("gridtext")) { +mat = matrix(rnorm(100), 10) +rownames(mat) = letters[1:10] +ht = Heatmap(mat, + column_title = gt_render("Some blue text **in bold.**
And *italics text.*
And some large text.", r = unit(2, "pt"), padding = unit(c(2, 2, 2, 2), "pt")), + column_title_gp = gpar(box_fill = "orange"), + row_labels = gt_render(letters[1:10], padding = unit(c(2, 10, 2, 10), "pt")), + row_names_gp = gpar(box_col = "red"), + row_km = 2, + row_title = gt_render(c("title1", "title2")), + row_title_gp = gpar(box_fill = "yellow"), + heatmap_legend_param = list( + title = gt_render("**Legend title**"), + title_gp = gpar(box_fill = "grey"), + at = c(-3, 0, 3), + labels = gt_render(c("*negative* three", "zero", "*positive* three")) + )) +ht = rowAnnotation( + foo = anno_text(gt_render(sapply(LETTERS[1:10], strrep, 10), align_widths = TRUE), + gp = gpar(box_col = "blue", box_lwd = 2), + just = "right", + location = unit(1, "npc") + )) + ht +draw(ht) + +} +} diff --git a/tests/test-gridtext.R b/tests/test-gridtext.R index e1dff2e6..0bee4c11 100644 --- a/tests/test-gridtext.R +++ b/tests/test-gridtext.R @@ -1,36 +1,29 @@ -library(gridtext) +library(ComplexHeatmap) -if(0) { +if(requireNamespace("gridtext")) { ##### test anno_richtext #### -anno = anno_text(month.name) +mat = matrix(rnorm(100), 10) +rownames(mat) = letters[1:10] +ht = Heatmap(mat, + column_title = gt_render("Some blue text **in bold.**
And *italics text.*
And some large text.", r = unit(2, "pt"), padding = unit(c(2, 2, 2, 2), "pt")), + column_title_gp = gpar(box_fill = "orange"), + row_labels = gt_render(letters[1:10], padding = unit(c(2, 10, 2, 10), "pt")), + row_names_gp = gpar(box_col = "red"), + row_km = 2, + row_title = gt_render(c("title1", "title2")), + row_title_gp = gpar(box_fill = "yellow"), + heatmap_legend_param = list( + title = gt_render("**Legend title**"), + title_gp = gpar(box_fill = "grey"), + at = c(-3, 0, 3), + labels = gt_render(c("*negative* three", "zero", "*positive* three")) + )) +ht = rowAnnotation( + foo = anno_text(gt_render(sapply(LETTERS[1:10], strrep, 10), align_widths = TRUE), + gp = gpar(box_col = "blue", box_lwd = 2), + just = "right", + location = unit(1, "npc") + )) + ht +draw(ht) -anno = anno_richtext(richtext_grob(month.name, box_gp = gpar(col = "red"), rot = 90, hjust = 1, y = unit(1, "npc"))) -draw(anno, test = "month names") -anno = anno_richtext(richtext_grob(month.name, box_gp = gpar(col = "red"), hjust = 0, x = unit(0, "npc")), which = "row") -draw(anno, test = "month names") - - -reprex({ -library(gridtext) -library(grid) -gb = richtext_grob(month.name, rot = 90, align_widths = FALSE) -convertHeight(grobHeight(gb), "mm") -gb = richtext_grob(month.name, rot = 90, align_widths = TRUE) -convertHeight(grobHeight(gb), "mm") - -# only September -gb = richtext_grob(month.name[9], rot = 90, align_widths = FALSE) -convertHeight(grobHeight(gb), "mm") -}) - -m = matrix(rnorm(144), 12) -rownames(m) = month.name -colnames(m) = month.name - -Heatmap(m, row_labels = richtext_grob(rownames(m), align_widths = TRUE, box_gp = gpar(col = "red"), x = 0, hjust = 0), - column_labels = richtext_grob(colnames(m), box_gp = gpar(col = "blue"), y = 1, hjust = 1, rot = 90)) - - - -Heatmap(m) } diff --git a/tests/test-utils.R b/tests/test-utils.R index aaae598e..6c099cb4 100755 --- a/tests/test-utils.R +++ b/tests/test-utils.R @@ -39,4 +39,3 @@ make_plot(pos1, smartAlign2(pos1, range = range), range) pos1 = rbind(c(1, 8), c(3, 10)) make_plot(pos1, smartAlign2(pos1, range = range), range) -make_plot(pos1, smartAlign2(pos1, range = range, range_fixed = FALSE), range)