2  stack layout

stack_layout() arranges plots either horizontally or vertically. Based on whether we want to align the discrete or continuous variables, there are two types of stack layouts:

stack_layout() integrates the functionalities of stack_discrete() and stack_continuous() into a single interface. The first argument for these three functions is direction which should be a single string indicating the direction of the stack layout, either "h"(horizontal) or "v"(vertical).

Several aliases are available for convenience:

Code
library(ggalign)
set.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)))

2.1 Input data

As discussed in Section 1.3, when aligning discrete variables, we typically use a matrix. For continuous axes, we can still use the long-formatted data frame, which is the same as in ggplot2.

  • For stack_continuous(), a data frame is required, and the input will be automatically converted using fortify_data_frame() if needed.
  • For stack_discrete(), a matrix is required, and the input will be automatically converted using fortify_matrix() if needed.

By default, fortify_data_frame() will invoke the ggplot2::fortify() function for conversion. Note, for matrix, it will be converted to a long-formatted data frame which is different from the ggplot2::fortify().

stack_discrete()/stack_continuous() will set up the layout, but no plot will be drawn until you add a plot element:

stack_discretev(small_mat) +
    layout_annotation(
        theme = theme(plot.background = element_rect(color = "red"))
    )
# the same for `stack_continuous()`
1
initialize a vertical stack layout.
2
Add a plot background in for the entire layout.

In this example, we use layout_annotation() to insert a plot background in the entire layout, it can also be used to control the theme of title, subtitle, caption (layout_title()), guides, margins, panel.border, panel.spacing.

2.2 Layout Customize

When aligning discrete variables, the package offers a suite of align_* functions designed to give you precise control over the ordering and groups. Instead of detailing each align_* function individually, we will focus on the general usage and how to combine them with stack_discrete().

Here, we remain take align_dendro() as an example, it can reorder the observations, split them into groups, and can add a plot for visualization.

When used for stack_discreteh(), the observations are aligned along the y-axis:

stack_discreteh(small_mat) +
    align_dendro()
1
initialize a horizontal stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot.

When used for stack_discretev(), the observations are aligned along the x-axis:

stack_discretev(small_mat) +
    align_dendro()
1
initialize a vertical stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot.

When align_dendro() is added to the layout, it performs following actions:

  1. reorder the observations.
  2. set the active plot to the dendrogram.

The active plot refers to the plot that subsequent ggplot2 components will target. In this case, the active plot is the dendrogram, and any new layers added will be applied to it. For instance, we can add additional layers to visualize the dendrogram’s structure or data. The default data underlying the ggplot object of align_dendro() consists of the dendrogram node data. It is also possible to use the dendrogram’s edge data for customization, which I will introduce in Section 5.4.

stack_discreteh(small_mat) +
    align_dendro() +
    geom_point()
1
initialize a horizontal stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot.
3
add a point layer to the dendrogram

The active argument controls whether a plot should be set as the active plot. It accepts an active() object with the use argument to specify if the plot should be active when added.

stack_discreteh(small_mat) +
    align_dendro(active = active(use = FALSE)) +
    geom_point()
#> Error in `chain_layout_add()`:
#> ! Cannot add `geom_point()` to the horizontal `stack_discrete()`
#> ℹ No active plot component
#> ℹ Did you forget to initialize a <ggplot> object with `ggalign()` or
#>   `ggfree()`?
1
initialize a horizontal stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, but don’t set the active plot to this plot.
3
try to add a point layer to the dendrogram, should fail due to no active plot

Usually, you don’t need to set this manually, as the active context is automatically applied only for functions that add plot areas. You can inspect whether a align_* function will add a plot by print it:

align_dendro()
#> <Class: AlignDendro AlignHclust Align AlignProto>
#>      plot: yes
#>   reorder: yes
#>     split: no

You might find it confusing that we mentioned align_dendro() will split observations into groups, while the print output shows split = "no". This happens because we haven’t specified the k/h argument in align_dendro().

align_dendro(k = 3L)
#> <Class: AlignDendro AlignHclust Align AlignProto>
#>      plot: yes
#>   reorder: yes
#>     split: yes

You don’t need to explicitly provide data to align_dendro(). By default, it inherits data from the layout. However, you can always provide another data source, but note that this package uses the concept of number of observations (NROW()). When aligning the observations, you must ensure the number of observations is consistent across all plots.

set.seed(123)
stack_discreteh(small_mat) +
    align_dendro(data = matrix(rnorm(56), nrow = 8)) +
    theme(axis.text.y = element_text())
#> Error in interact_layout(..., self = self): object 'layout_name' not found
1
initialize a horizontal stack layout.
2
reorder the observations based on hierarchical clustering, add a dendrogram tree, and set the active plot to this one, using self-provided data. This should fail because the number of observations is inconsistent.
3
try to add y-axis text to the dendrogram.
set.seed(123)
stack_discreteh(small_mat) +
    align_dendro(data = matrix(rnorm(70), nrow = 7)) +
    theme(axis.text.y = element_text())
1
initialize a horizontal stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot, using self-provided data
3
add y-axis text to the dendrogram.

Alternatively, you can provide a function (or purrr-lambda) that will be applied to the layout’s matrix. For layouts that align observations, a matrix is always required, so the data input must be in matrix form.

set.seed(123)
stack_discreteh(small_mat) +
    align_dendro(data = ~ .x[sample(nrow(.x)), ]) +
    theme(axis.text.y = element_text())
1
initialize a horizontal stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot, using self-provided data function
3
add y-axis text to the dendrogram.

Without adding another plot, it’s difficult to appreciate the benefits. Let’s now explore how to incorporate a plot.

2.3 Plot initialize

There are two primary functions for adding plots:

  • ggalign(): Initialize a ggplot object and align the axes.
  • ggfree(): Initialize a ggplot object without aligning the axes.

Both functions initialize a ggplot object and, by default, set the active plot when added to the layout.

stack_discreteh(small_mat) +
    align_dendro() +
    ggalign(data = rowSums) +
    geom_bar(aes(value, .discrete_y), stat = "identity") +
    theme(axis.text.y = element_text())
1
initialize a horizontal stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot.
3
initialize a ggplot object, and set the active plot to this plot, using self-provided data function
4
add a bar to the plot
5
add y-axis text

You can build the plot separately and then add it to the layout:

my_bar <- ggalign(data = rowSums) +
    geom_bar(aes(value, .discrete_y), stat = "identity") +
    theme(axis.text.y = element_text())
stack_discreteh(small_mat) +
    align_dendro() +
    my_bar
1
Create the bar plot.
2
initialize a horizontal stack layout.
3
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot.
4
Add another bar plot to the layout.

The active argument can also control the place of the plot area to be added. It accepts an active() object with the order argument to specify the order of the plot area.

stack_discreteh(small_mat) +
    align_dendro() +
    ggalign(data = rowSums, active = active(order = 1)) +
    geom_bar(aes(value, .discrete_y), stat = "identity") +
    theme(axis.text.y = element_text()) 

You can also stack plots vertically using stack_discretev():

stack_discretev(small_mat) + 
    align_dendro() +
    ggalign(data = rowSums) +
    geom_bar(aes(.discrete_x, value), stat = "identity") +
    theme(axis.text.y = element_text()) 

ggfree() focuses on layout integration without enforcing strict axis alignment.

stack_discretev() +
    ggfree(mpg, aes(displ, hwy, colour = class)) +
    geom_point(size = 2) +
    ggfree(mpg, aes(displ, hwy, colour = class)) +
    geom_point(size = 2) &
    scale_color_brewer(palette = "Dark2") &
    theme_bw()

The & operator applies the added element to all plots in the layout, similar to its functionality in the patchwork package.

For stack_continuous(), only free plots (ggfree()) can be added. This layout arranges plots in one row or column without enforcing axis alignment:

stack_continuousv(mpg) +
    ggfree(mapping = aes(displ, hwy, colour = class)) +
    geom_point(size = 2) +
    ggfree(mapping = aes(displ, hwy, colour = class)) +
    geom_point(size = 2) &
    scale_color_brewer(palette = "Dark2") &
    theme_bw()

By default, ggfree() will also inherit data from the layout and call fortify_data_frame() to convert the data to a data frame. So, note that if the layout data is a matrix, it will be converted into a long-formatted data frame.

2.4 Plot Size

Both ggalign() and ggfree() functions have a size argument to control the relative width (for horizontal stack layout) or height (for vertical stack layout) of the plot’s panel area.

stack_continuousv(mpg) +
    ggfree(mapping = aes(displ, hwy, colour = class), size = 2) +
    geom_point(size = 2) +
    ggfree(mapping = aes(displ, hwy, colour = class), size = 1) +
    geom_point(size = 2) &
    scale_color_brewer(palette = "Dark2") &
    theme_bw()

Alternatively, you can define an absolute size by using a unit() object:

stack_continuousv(mpg) +
    ggfree(mapping = aes(displ, hwy, colour = class), size = unit(1, "cm")) +
    geom_point(size = 2) +
    ggfree(mapping = aes(displ, hwy, colour = class)) +
    geom_point(size = 2) &
    scale_color_brewer(palette = "Dark2") &
    theme_bw()

2.5 active plot

As mentioned earlier, the active plot refers to the plot that subsequent ggplot2 components will target. The package provide two functions to work with active plot for stack_layout.

  • stack_switch(): switch the active context
  • stack_active(): An alias for stack_switch(), which sets what = NULL

The stack_switch() function accepts the what argument, which can either be the index of the plot added (based on its adding order) or the plot name specified via the active() object using the name argument.

Note that the what argument must be explicitly named, as it is placed second in the function signature. This is because, in most cases, we don’t need to switch the active plot, manually—adjusting the order of plot additions typically suffices.

stack_alignh(small_mat) +
    align_dendro() +
    ggalign(data = rowSums) +
    geom_bar(aes(value, .discrete_y), stat = "identity") +
    stack_switch(what = 1) +
    geom_point() +
    theme(axis.text.y = element_text()) +
    layout_title(title = "switch by integer")

stack_alignh(small_mat) +
    align_dendro(active = active(name = "tree")) +
    ggalign(data = rowSums) +
    geom_bar(aes(value, .discrete_y), stat = "identity") +
    stack_switch(what = "tree") +
    geom_point() +
    theme(axis.text.y = element_text()) +
    layout_title(title = "switch by string")

In the example, we use layout_title() to insert a title for the entire layout. Alternatively, you can add a title to a single plot with ggtitle().

By setting what = NULL (or alias stack_active()), we remove the active plot. This is particularly useful when the active plot is a nested Layout object, as any additions would otherwise be directed to that nested Layout. By removing the active plot, you can continue adding components to the StackLayout. We’ll introduce the nested layout of StackLayout in Chapter 8.

In the next chapter, we will dive into the HeatmapLayout, which can take the StackLayout as input. Heatmap layouts offer additional features for aligning observations in both directions. Let’s move ahead and explore how heatmaps can be seamlessly integrated into your layout workflows.