Introduction
This vignette shows you how to create custom layouts for adding
titles, subtitles, footnotes, and other text elements around outputs
using the gridifyLayout()
function.
Please note that creating a custom layout may not be required. It is
intended for more substantial changes or when adjustments are being
applied in an identical way across multiple objects and projects. For
minor one-off adjustments please see help(set_cell)
for
more information.
Layout Basics
A layout is a grid structure defined by the number of rows and columns which creates spaces or cells where text elements can then be added. Each cell is then defined by the rows and columns it spans.
library(gridify)
# (to use |> version 4.1.0 of R is required, for lower versions we recommend %>% from magrittr)
library(magrittr)
# In the simple_layout there are three rows and one column
# creating a grid of three cells vertically.
show_layout(simple_layout())
Example Layout
Here is an example layout. All arguments will be explained below.
new_layout <- gridifyLayout(
nrow = 4L,
ncol = 2L,
heights = grid::unit(c(1, 1, 1, 0.05), c("lines", "lines", "null", "npc")),
widths = grid::unit(c(0.5, 0.5), "npc"),
margin = grid::unit(c(t = 0.1, r = 0.1, b = 0.1, l = 0.1), units = "npc"),
global_gpar = grid::gpar(),
adjust_height = FALSE,
object = gridifyObject(row = 3, col = 1:2),
cells = gridifyCells(
company = gridifyCell(row = 1, col = 2, x = 1, hjust = 1, y = 1, vjust = 1),
title = gridifyCell(row = 2, col = 1, x = 1, hjust = 0.5),
footer = gridifyCell(row = 4, col = 1, x = 0, hjust = 0)
)
)
gridify(
object = ggplot2::ggplot(data = mtcars, ggplot2::aes(x = mpg, y = wt)) +
ggplot2::geom_line(),
layout = new_layout
) %>%
set_cell("company", "Company Ltd") %>%
set_cell("title", "This is my title") %>%
set_cell("footer", "This is a very long footer with lots of words")
Nrow and Ncol
The nrow
and ncol
arguments are the number
of rows and columns in the layout. They define the grid structure for
the layout.
When thinking about the number of rows and columns required, an empty
row/column should not be included for a margin space
around the border. The margin is defined in another argument,
margin
. See more details below.
Heights and Widths
Each row and column needs to have the heights and widths,
respectively, set to provide the size of the various cells. The heights
and widths need to be a call to grid::unit()
containing
elements equal to the number of rows/columns in the layout.
Widths would typically take the unit npc (Normalised Parent Coordinates) to be able to take up a percentage of the whole space rather than a specific width in cm, mm or inches. However, any other unit is also a valid entry.
Heights can be more challenging, depending on what is required in the layout. Here is a quick summary of why each type of unit can be useful for the heights:
- npc works best for proportions. However, if the text element is too large for the space, it may overlap others.
- lines is a good unit if you know how many lines of text the element should have, most useful for elements that will have a consistent size across outputs.
- cm/mm/inches are useful when a text element must be a consistent height and when the specification is given in a measurement.
- null can be used with value of 1 which means the text element will take up as much space that is left over when other specific heights have been considered. This is useful for the row the main object is placed in.
When setting the heights of the rows in the layout, ensure to allocate enough height to the object cell row, as the object within the annotated output might not be visible at smaller sizes.
Adjusting Heights automatically
The adjust_height
argument can be used to prevent the
text elements from overlapping each other when they are bigger than the
height of the row they are in. This is done by changing the height of
the row dynamically depending on the space the text element will take
up. Please note that this argument will not affect any
row with a unit of npc as then the row height is not defined by a
measurement but a percentage of available height.
Margin argument
The layout needs to have a margin defined to determine how much space there should be around the output. This is a separate argument so does not need to be considered when defining the number of rows and columns for the text element.
The margin must be a call to grid::unit(...)
where a
vector with arguments t
, r
, b
and
l
are given values, this is for the top, right, bottom and
left margins respectively e.g.
Adding the Object argument
The object’s location is defined in the object
argument
of gridifyLayout()
. It must be a call to
gridifyObject()
specifying the rows and columns the object
should span within the layout.
The arguments height
and width
of
gridifyObject()
determine how much of the space the object
should take up within the cell(s) location. The default values are
1
, which is the equivalent of 100% of the width/height of
the area.
# an object that spans only row 3 but both columns 1 and 2
gridifyObject(row = 3, col = 1:2)
Adding the Scales argument
The scales
argument is seen in the predefined layouts
simple_layout()
and complex_layout()
. It is an
option that lets users select different layout configurations by
adjusting the scale settings. However it is not an argument to the
function gridifyLayout()
, it is custom made in the wrapper
layout functions and so would need to be custom made by the user.
For transparency, this section will explain how the
scales
argument works, and below, it will demonstrate how
to add it to a custom layout.
In the predefined simple and complex layouts, you can choose between
"free"
and "fixed"
scales. When using
"free"
scales, row heights are proportionally 15%, 70% and
15% of the total area. Conversely, with "fixed"
scales, row
heights are determined by the number of lines required for each text
element, with any remaining space allocated to the object.
simple_layout(scales = "free")
#> gridifyLayout object
#> ---------------------
#> Layout dimensions:
#> Number of rows: 3
#> Number of columns: 1
#>
#> Heights of rows:
#> Row 1: 0.15 npc
#> Row 2: 0.7 npc
#> Row 3: 0.15 npc
#>
#> Widths of columns:
#> Column 1: 1 npc
#>
#> Object Position:
#> Row: 2
#> Col: 1
#> Width: 1
#> Height: 1
#>
#> Object Row Heights:
#> Row 2: 0.7 npc
#>
#> Margin:
#> Top: 0.1 npc
#> Right: 0.1 npc
#> Bottom: 0.1 npc
#> Left: 0.1 npc
#>
#> Global graphical parameters:
#> Are not set
#>
#> Default Cell Info:
#> title:
#> row:1, col:1, text:NULL, mch:Inf, x:0.5, y:0.5, hjust:0.5, vjust:0.5, rot:0,
#> footer:
#> row:3, col:1, text:NULL, mch:Inf, x:0.5, y:0.5, hjust:0.5, vjust:0.5, rot:0,
show_layout(simple_layout(scales = "free"))
simple_layout(scales = "fixed")
#> gridifyLayout object
#> ---------------------
#> Layout dimensions:
#> Number of rows: 3
#> Number of columns: 1
#>
#> Heights of rows:
#> Row 1: 0 lines
#> Row 2: 1 null
#> Row 3: 0 lines
#>
#> Widths of columns:
#> Column 1: 1 npc
#>
#> Object Position:
#> Row: 2
#> Col: 1
#> Width: 1
#> Height: 1
#>
#> Object Row Heights:
#> Row 2: 1 null
#>
#> Margin:
#> Top: 0.1 npc
#> Right: 0.1 npc
#> Bottom: 0.1 npc
#> Left: 0.1 npc
#>
#> Global graphical parameters:
#> Are not set
#>
#> Default Cell Info:
#> title:
#> row:1, col:1, text:NULL, mch:Inf, x:0.5, y:0.5, hjust:0.5, vjust:0.5, rot:0,
#> footer:
#> row:3, col:1, text:NULL, mch:Inf, x:0.5, y:0.5, hjust:0.5, vjust:0.5, rot:0,
# As no lines were defined, the object takes up the whole space below
show_layout(simple_layout(scales = "fixed"))
Below is an example of how to add scales to a custom layout with
options being npc
or lines
.
scales_example_layout <- function(
margin = grid::unit(c(t = 0.1, r = 0.1, b = 0.1, l = 0.1), units = "npc"),
global_gpar = grid::gpar(),
scales = c("npc", "lines")) {
scales <- match.arg(scales, c("npc", "lines"))
heights <- if (scales == "npc") {
grid::unit(c(0.15, 0.7, 0.15), "npc")
} else {
grid::unit(c(0, 1, 0), "lines")
}
gridifyLayout(
nrow = 3L,
ncol = 1L,
heights = heights,
widths = grid::unit(1, "npc"),
margin = margin,
global_gpar = global_gpar,
adjust_height = TRUE,
object = gridifyObject(row = 2, col = 1),
cells = gridifyCells(
title = gridifyCell(row = 1, col = 1),
footer = gridifyCell(row = 3, col = 1)
)
)
}
scales_example_layout(scales = "npc")
#> gridifyLayout object
#> ---------------------
#> Layout dimensions:
#> Number of rows: 3
#> Number of columns: 1
#>
#> Heights of rows:
#> Row 1: 0.15 npc
#> Row 2: 0.7 npc
#> Row 3: 0.15 npc
#>
#> Widths of columns:
#> Column 1: 1 npc
#>
#> Object Position:
#> Row: 2
#> Col: 1
#> Width: 1
#> Height: 1
#>
#> Object Row Heights:
#> Row 2: 0.7 npc
#>
#> Margin:
#> Top: 0.1 npc
#> Right: 0.1 npc
#> Bottom: 0.1 npc
#> Left: 0.1 npc
#>
#> Global graphical parameters:
#> Are not set
#>
#> Default Cell Info:
#> title:
#> row:1, col:1, text:NULL, mch:Inf, x:0.5, y:0.5, hjust:0.5, vjust:0.5, rot:0,
#> footer:
#> row:3, col:1, text:NULL, mch:Inf, x:0.5, y:0.5, hjust:0.5, vjust:0.5, rot:0,
show_layout(scales_example_layout(scales = "npc"))
scales_example_layout(scales = "lines")
#> gridifyLayout object
#> ---------------------
#> Layout dimensions:
#> Number of rows: 3
#> Number of columns: 1
#>
#> Heights of rows:
#> Row 1: 0 lines
#> Row 2: 1 lines
#> Row 3: 0 lines
#>
#> Widths of columns:
#> Column 1: 1 npc
#>
#> Object Position:
#> Row: 2
#> Col: 1
#> Width: 1
#> Height: 1
#>
#> Object Row Heights:
#> Row 2: 1 lines
#>
#> Margin:
#> Top: 0.1 npc
#> Right: 0.1 npc
#> Bottom: 0.1 npc
#> Left: 0.1 npc
#>
#> Global graphical parameters:
#> Are not set
#>
#> Default Cell Info:
#> title:
#> row:1, col:1, text:NULL, mch:Inf, x:0.5, y:0.5, hjust:0.5, vjust:0.5, rot:0,
#> footer:
#> row:3, col:1, text:NULL, mch:Inf, x:0.5, y:0.5, hjust:0.5, vjust:0.5, rot:0,
show_layout(scales_example_layout(scales = "lines"))
As you can see from the above example, the show_layout()
function doesn’t always show the complete picture. This is especially
visible when lines
are set to have a height of 0 and space
is only given to them when a text element is assigned to it.
Adding cells
Now that the grid has been specified with the heights and widths of the rows and columns, and the object has been placed in the correct cell, the remaining cells can be added.
The cells are the places a user can add their text elements to the output. If a cell doesn’t exist in the layout then a text element can never be placed there by the user.
The cells are created by making a call to gridifyCells()
which requires named arguments returning calls to
gridifyCell()
. The names of these arguments are the names
the user will be referencing when adding text elements to the output so
make sure they are named appropriately.
gridifyCells(
company = gridifyCell(row = 1, col = 2, x = 1, hjust = 1, y = 1, vjust = 1),
title = gridifyCell(row = 2, col = 1, x = 1, hjust = 0.5),
footer = gridifyCell(row = 4, col = 1, x = 0, hjust = 0)
)
The row and column locations of the cell are defined in the call to
gridifyCell()
along with other possible arguments like the
graphical parameters and the alignment of the text in the cell. See
?gridifyCell
for more details.
Row and Column positions of Cells
The row
and col
arguments of
gridifyCell()
can be set as a value, sequence or span.
Here are some examples to help explain how these arguments can be
set. Replace row
with col
if spanning across
columns:
-
row = 3
- the cell is only positioned in the 3rd row -
row = 2:4
- the cell is positioned across rows 2, 3 and 4 -
row = c(1, 3)
- the cell is positioned across rows 1, 2 and 3
There are four arguments in gridifyCell()
which define
the alignment of the text within the cell: x
,
y
, hjust
and vjust
. They are used
in pairs x
and hjust
, y
and
vjust
to define the point on the graph and the point in the
text, respectively, where the text elements should be. x
and hjust
work on the horizontal and y
and
vjust
work on the vertical.
Imagine pinning a piece of paper to a board; the pin has to go
through the paper and the board. The
hjust
/vjust
value is the point where the pin
goes though the paper and x
/y
is where the pin
hits the board. They all take values between 0 and 1, with a default of
0.5.
Here are some examples to help explain how the alignment works.
Replace x
with y
and hjust
with
vjust
if working in the vertical direction:
-
x = 0, hjust = 0
- the left side of the text is on the left of the cell -
x = 0.5, hjust = 0
- the left side of the text is in the middle of the cell -
x = 1, hjust = 1
- the right side of the text is on the right side of the cell -
x = 0.8, hjust = 1
- the right side of the text is 20% in from the right side of the cell
Maximum Number of Characters per Line for a Cell
Please use the mch
argument of
gridifyCell()
to control the maximum number of characters
per line. In many cases, the output dimensions and font size may not be
known in advance, making it difficult to predict an appropriate value
for mch
. The end user can specify the mch
argument in the set_cell()
function, or alternatively,
provide text with newline characters already included.
Default Graphical Parameters
The graphical parameters can be set at a global level in the argument
global_gpar
of the gridifyLayout()
function.
They can also be set at a cell level in the argument gpar
of the gridifyCell()
function.
Order of precedence for the graphical parameters is:
local > default > global
set_cell(..., gpar)
>
gridifyCell(..., gpar)
>
gridifyLayout(..., global_gpar)
Therefore, any graphical parameter can be overwritten by the users in
the calls to set_cell()
.
new_layout <- gridifyLayout(
nrow = 3L,
ncol = 1L,
heights = grid::unit(c(0.1, 0.8, 0.1), "npc"),
widths = grid::unit(1, "npc"),
margin = grid::unit(c(t = 0.1, r = 0.1, b = 0.1, l = 0.1), units = "inches"),
# default graphics for whole output set in global_gpar
global_gpar = grid::gpar(fontfamiy = "Courier", col = "navy"),
adjust_height = FALSE,
object = gridifyObject(row = 2, col = 1),
cells = gridifyCells(
# default graphics for title cell here:
title = gridifyCell(row = 1, col = 1, gpar = grid::gpar(fontsize = 20)),
footer = gridifyCell(row = 3, col = 1)
)
)
gridify(
object = ggplot2::ggplot(data = mtcars, ggplot2::aes(x = mpg, y = wt)) +
ggplot2::geom_line(),
layout = new_layout
) %>%
set_cell("title", "This is a title") %>%
# graphics specs can be overwritten in set_cell
set_cell("footer", "This is a footer", gpar = grid::gpar(col = "purple"))
The text element fonts can also be customized easily as per the user requirements.
new_layout <- gridifyLayout(
nrow = 3L,
ncol = 1L,
heights = grid::unit(c(0.1, 0.8, 0.1), "npc"),
widths = grid::unit(1, "npc"),
margin = grid::unit(c(t = 0.1, r = 0.1, b = 0.1, l = 0.1), units = "inches"),
global_gpar = grid::gpar(
fontfamiy = "Courier",
col = "navy",
fontface = "italic"
), # default italic text for the whole output
adjust_height = FALSE,
object = gridifyObject(row = 2, col = 1),
cells = gridifyCells(
title = gridifyCell(row = 1, col = 1),
footer = gridifyCell(row = 3, col = 1)
)
)
# overwriting the default fontface in the footer cell in the call to set_cell
# so the footer will now have bold and italic instead of the default italic
gridify(
object = ggplot2::ggplot(data = mtcars, ggplot2::aes(x = mpg, y = wt)) +
ggplot2::geom_line(),
layout = new_layout
) %>%
set_cell("title", "This is a title") %>%
set_cell("footer", "This is a footer", gpar = grid::gpar(fontface = "bold.italic"))
Default cell text
The text
argument of gridifyCell()
can be
used to set the default text for a cell. Please note, the text provided
by the end user with set_cell()
will take higher priority
and overwrite the default.
new_layout <- gridifyLayout(
nrow = 4L,
ncol = 2L,
heights = grid::unit(c(1, 1, 1, 0.05), c("lines", "lines", "null", "npc")),
widths = grid::unit(c(0.5, 0.5), "npc"),
margin = grid::unit(c(t = 0.1, r = 0.1, b = 0.1, l = 0.1), units = "npc"),
global_gpar = grid::gpar(),
adjust_height = FALSE,
object = gridifyObject(row = 3, col = 1:2),
cells = gridifyCells(
company = gridifyCell(row = 1, col = 2, x = 1, hjust = 1, y = 1, vjust = 1),
title = gridifyCell(row = 2, col = 1, text = "Default Title", x = 1, hjust = 0.5),
footer = gridifyCell(row = 4, col = 1, x = 0, hjust = 0)
)
)
gridify(
object = ggplot2::ggplot(data = mtcars, ggplot2::aes(x = mpg, y = wt)) +
ggplot2::geom_line(),
layout = new_layout
) %>%
set_cell("company", "Company Ltd") %>%
set_cell("footer", "This is a very long footer with lots of words")
#> gridifyClass object
#> ---------------------
#> Please run `show_spec(object)` or print the layout to get more specs.
#>
#> Cells:
#> company: filled
#> title: filled
#> footer: filled
Adding a Watermark
To add a watermark to your output, create a cell covering the desired rows and columns and set the transparency of the text, the font size large and the rotation to 45 degrees.
new_layout <- gridifyLayout(
nrow = 3L,
ncol = 1L,
heights = grid::unit(c(0.05, 0.9, 0.05), "npc"),
widths = grid::unit(1, "npc"),
margin = grid::unit(c(t = 0.1, r = 0.1, b = 0.1, l = 0.1), units = "cm"),
global_gpar = grid::gpar(),
adjust_height = FALSE,
object = gridifyObject(row = 2, col = 1),
cells = gridifyCells(
title = gridifyCell(row = 1, col = 1),
footer = gridifyCell(row = 3, col = 1),
watermark = gridifyCell(row = 1:3, col = 1, rot = 45, gpar = grid::gpar(fontsize = 90, alpha = 0.3))
)
)
#> Warning in validityMethod(object): Overlapping cells detected at positions:
#> 1-1, 3-1
gridify(
object = ggplot2::ggplot(data = mtcars, ggplot2::aes(x = mpg, y = wt)) +
ggplot2::geom_line(),
layout = new_layout
) %>%
set_cell("watermark", "DRAFT")