ggmark() can be used to add annotation plot for the selected observations. ggmark accepts mark argument, which should be a mark_draw() object to define how to draw the links.
Currently, two helper functions are provided to generate these links:
mark_line(): draws a line to the annotation panel.
mark_tetragon(): draws a quadrilateral shape connecting observations and the panel.
All of these functions specify links as pair_links() as introduced in Chapter 15. Each pair of links will introduce a panel in ggmark to annotate these observations.
Code
library(ggalign)#> Loading required package: ggplot2#> #> Attaching package: 'ggalign'#> The following object is masked from 'package:ggplot2':#> #> element_polygonset.seed(123)small_mat<-matrix(rnorm(56), nrow =7)rownames(small_mat)<-paste0("row", seq_len(nrow(small_mat)))colnames(small_mat)<-paste0("column", seq_len(ncol(small_mat)))
16.1 plot data
By default, if no observations are explicitly selected, ggmark() selects all observations and splits them based on the layout’s grouping.
Calling ggmark() initializes a ggplot object, the underlying data is created using fortify_data_frame(). Please refer to it for more details. In addition, the following columns will be added to the data frame:
.panel: the panel for the aligned axis. It means x-axis for vertical stack layout (including top and bottom annotation), y-axis for horizontal stack layout (including left and right annotation).
.names and .index: a character names (only applicable when names exists) and an integer of index of the original data.
.hand: A factor with levels c("left", "right") for horizontal stack layouts, or c("top", "bottom") for vertical stack layouts, indicating the position of the linked observations.
By default, ggmark() uses facet_wrap to define the facet, and you can use it to control the facet apearance (just ignore the facets argument). We prefer facet_wrap() here because it offers flexibility in positioning the strip on any side of the panel, and typically, we only want to a single dimension to create the annotate the selected observations. However, you can still use facet_grid() to create a two-dimensional plot. Note that for horizontal stack layouts, the row facets, or for vertical stack layouts, the column facets will always be overwritten.
You can wrap the element with I() to recycle it to match the drawing groups. The drawing groups typically correspond to the number of observations for element_line(), as each observation will be linked with the plot panel.
For element_polygon(), the drawing groups usually align with the defined groups. However, if the defined group of observations is separated and cannot be linked with a single quadrilateral, the number of drawing groups will be larger than the number of defined groups.
For stack_discrete(), we usually don’t need to specify both hands in the formula, since they are expected to share the same ordering and group structure. This is because all plots within stack_discrete() maintain a common ordering index across the layout.
However, explicitly specifying another hands becomes useful in stack_cross(), where different observation orderings are involved. This scenario is covered in (stack-cross?).
Both mark_line() and mark_tetragon() are built on top of mark_draw() (strictly, .mark_draw()). This function allows you to define custom mark styles by supplying a drawing function which must return a grob/gList.
The function passed to mark_draw() must accept two arguments:
A data frame representing the panel-side coordinates (ggmark plot)
A data frame representing the observation-side coordinates
Each observation is assumed to occupy a unit length in the layout. Therefore, the observation-side coordinates include two terminal points x, y, xend, yend—representing the start and end of the observation along the linking axis.
Additional columns in the link data frame include:
link_id: The identifier of the link (e.g., following example 4:6 link has id “a”).
link_panel: Indicates which panel the link is drawn to, based on the layout.
link_index: The layout index for positioning.
.hand: Either “left”/“right” (horizontal) or “top”/“bottom” (vertical), specifying the hand of the observation.
.index: The original index of the observation.
Here is an example that prints the structure of the panel and link data frames:
set.seed(123)p<-ggheatmap(small_mat)+theme(axis.text.x =element_text(hjust =0, angle =-60))+anno_right()+align_kmeans(3L)+ggmark(mark_draw(function(panel, link){print(panel)print(link)}, a =4:6, 1:2))pdf(NULL)print(p)#> → heatmap built with `geom_tile()`#> x xend y yend#> 1 0.02720374 0.02720374 0.01235571 0.4938221#> x xend y yend link_id link_panel link_index .hand .index#> 1 0 0 0.8606731 1.0000000 a 3 7 left 4#> 2 0 0 0.1393269 0.2786539 a 1 2 left 5#> 3 0 0 0.2786539 0.4179808 a 1 3 left 6#> x xend y yend#> 1 0.02720374 0.02720374 0.5061779 0.9876443#> x xend y yend link_id link_panel link_index .hand .index#> 1 0 0 0.7213461 0.8606731 2 3 6 left 1#> 2 0 0 0.5696635 0.7089904 2 2 5 left 2invisible(dev.off())
Here, we draw a triangle to connect the
set.seed(123)ggheatmap(small_mat)+theme(axis.text.x =element_text(hjust =0, angle =-60))+anno_right()+align_kmeans(3L)+ggmark(mark_draw(function(panel, link){x<-c((panel$x+panel$xend)/2L, link$x, link$xend)y<-c((panel$y+panel$yend)/2L, link$y, link$yend)grid::polygonGrob(x, y)}, 4, 2)# selecting one observation from each group for simple example)+geom_boxplot(aes(.names, value, fill =.names))+facet_wrap(vars(), scales ="free", strip.position ="right")+theme(plot.margin =margin(l =0.1, t =0.1, unit ="npc"))#> → heatmap built with `geom_tile()`