Reputation: 478
I am using the mtcars data to create a side by side plot and table in Rmarkdown.
---
title: "document"
author: "Maral Dorri"
date: 'May 2022'
output:
html_document
---
I create a column and add the table on the right:
<div class = "row">
<div class = "col-md-3">
```{r}
raw_dat <- mtcars[1:15, ] %>% rownames_to_column(var = "id") %>% select(id, mpg) %>%
mutate(links = paste(.$id, "And <a href = 'https://www.cars.com//'>here</a>"))
tibble(
name = raw_dat$id,
link = paste(raw_dat$mpg, "And <a href = 'https://www.cars.com//'>here</a>")) %>%
mutate(link = map(link, gt::html)) %>%
gt
```
</div>
Then I create another column and print the plot on the left
<div class = "col-md-9">
```{r, fig.height=5.5}
ggplot(raw_dat, aes(factor(id, rev(id)), mpg)) +
geom_point() +
coord_flip() +
theme(plot.margin = margin(0.6, unit = "cm"))
```
</div>
</div>
The results are:
I know I can set the height of the figure manually and capture the same size as the table on the right, but I want to make this automatic since for my actual data, it will be changing periodically. So the function used for the height of the plot should be in terms of number of rows of data in the table.
The desired output would line up each row of the data in the table on the right with the y axis labels of the plot on the left, as shown (done manually, and not perfectly aligned)
Upvotes: 7
Views: 763
Reputation: 66880
The bstfun
package offers a nice automatic approach for this. (Note the package relies on a variety of other packages, like magick
and webshot
.)
library(patchwork)
# devtools::install_github("ddsjoberg/bstfun")
library(bstfun)
as_ggplot(my_table) + my_plot
If it's ok to be approximate, you can get the table and axis names roughly aligned using:
as_ggplot(my_table) | (plot_spacer() / my_plot) +
plot_layout(heights = c(1,90)) # manual adjustment
Source data
library(gt); library(tidyverse)
raw_dat <- mtcars[1:15, ] %>% rownames_to_column(var = "id") %>% select(id, mpg) %>%
mutate(links = paste(.$id, "And <a href = 'https://www.cars.com//'>here</a>"))
my_table <- tibble(
name = raw_dat$id,
link = paste(raw_dat$mpg, "And <a href = 'https://www.cars.com//'>here</a>")) %>%
mutate(link = map(link, gt::html)) %>%
gt
my_plot <- ggplot(raw_dat, aes(factor(id, rev(id)), mpg)) +
geom_point() +
coord_flip() +
theme(plot.margin = margin(0.6, unit = "cm"))
Upvotes: 1
Reputation: 18754
Alright this will work until the plot has expanded to the point where the plot margin has scaled beyond the height of the table's header row (about 15 rows)
This is the styles and JS.
<style>
.main-container {
max-width: unset;
}
</style>
```{r listen,results="asis",engine="js"}
// tbl font size is 1em (assuming-- rendered 16px; the padding t/b 8px)
// do what you're told
setTimeout(function(){ // add to buttons
ch = document.querySelector('.col-md-3').clientHeight; // how tall is the table
sh = document.querySelector('.col-md-9'); // pull the 2nd column for manipulation
si = document.querySelector('img'); // assuming there's only one plot
sih = si.clientHeight; // plot height
siw = si.clientWidth; // plot width
fs = $('.gt_table').css('line-height'); // size of text
bbt = $('.gt_table_body').css('border-top-width'); // table, head, body border (* 6)
fss = parseInt(fs, 10); // strip the px from the value
bbw = parseInt(bbt, 10); // strip the px from the value
cz = sih/(sih - 11 - bbw - fss); // height of plot - margins - padding - the bottom (ticks, label, values)
cha = ch * cz; // add additional height, was 1.075 before cz
nw = siw/sih * cha; // new width of plot
sh.setAttribute('style', 'height: ' + cha + 'px; width: ' + nw + 'px;'); // shape container for centering
si.setAttribute('style', 'height: ' + cha + 'px; width: ' + nw + 'px;');
mm = document.querySelector('div.main-container > div.row'); // now get and set sizes for centering it all
mc = mm.clientWidth; // row width
co = document.querySelector('.col-md-3').clientWidth; // width of the table
ms = (mc - nw - co)/2; // calc margin sizes
mm.setAttribute('style', 'margin-left: ' + ms + 'px; margin-right: ' + ms + 'px;'); // center the content
}, 100) // you know, in case I'm slow...
```
Here's how this is in my RMD altogether.
---
title: "Untitled"
author: "me"
date: '2022-06-08'
output: html_document
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = F)
library(tidyverse)
library(gt)
data(mtcars)
```
<style>
.main-container {
max-width: unset;
}
</style>
<div class = "row">
<div class = "col-md-3">
```{r tblr}
raw_dat <- mtcars[1:15, ] %>% rownames_to_column(var = "id") %>% select(id, mpg) %>%
mutate(links = paste(.$id, "And <a href = 'https://www.cars.com//'>here</a>"))
tibble(
name = raw_dat$id,
link = paste(raw_dat$mpg, "And <a href = 'https://www.cars.com//'>here</a>")) %>%
mutate(link = map(link, gt::html)) %>%
gt
```
</div>
<div class = "col-md-9">
```{r pltr}
ggplot(raw_dat, aes(factor(id, rev(id)), mpg)) +
geom_point() +
coord_flip() +
theme(plot.margin = margin(0.6, unit = "cm"))
```
</div>
</div>
```{r listen,results="asis",engine="js"}
// tbl font size is 1em (assuming-- rendered 16px; the padding t/b 8px)
// do what you're told
setTimeout(function(){ // add to buttons
ch = document.querySelector('.col-md-3').clientHeight; // how tall is the table
sh = document.querySelector('.col-md-9'); // pull the 2nd column for manipulation
si = document.querySelector('img'); // assuming there's only one plot
sih = si.clientHeight; // plot height
siw = si.clientWidth; // plot width
fs = $('.gt_table').css('line-height'); // size of text
bbt = $('.gt_table_body').css('border-top-width'); // table, head, body border (* 6)
fss = parseInt(fs, 10); // strip the px from the value
bbw = parseInt(bbt, 10); // strip the px from the value
cz = sih/(sih - 11 - bbw - fss); // height of plot - margins - padding - the bottom (ticks, label, values)
cha = ch * cz; // add additional height, was 1.075 before cz
nw = siw/sih * cha; // new width of plot
sh.setAttribute('style', 'height: ' + cha + 'px; width: ' + nw + 'px;'); // shape container for centering
si.setAttribute('style', 'height: ' + cha + 'px; width: ' + nw + 'px;');
mm = document.querySelector('div.main-container > div.row'); // now get and set sizes for centering it all
mc = mm.clientWidth; // row width
co = document.querySelector('.col-md-3').clientWidth; // width of the table
ms = (mc - nw - co)/2; // calc margin sizes
mm.setAttribute('style', 'margin-left: ' + ms + 'px; margin-right: ' + ms + 'px;'); // center the content
}, 100) // you know, in case I'm slow...
```
I didn't realize that you wanted the labels to align between the plot and the table. Sorry about that. This updated JS will create the desired effect.
```{r listenOrElse,results="asis",engine="js"}
// do what you're told
setTimeout(function(){ // add to buttons
ch = document.querySelector('.col-md-3').clientHeight;
sh = document.querySelector('.col-md-9');
si = document.querySelector('img'); // assuming there's only one!
sih = si.clientHeight;
siw = si.clientWidth;
cha = ch * 1.075; // add additional height, to account for plot padding
nw = siw/sih * cha; // new width of plot
console.log(ch);
sh.style.height = cha + 'px';
si.setAttribute('style', 'height: ' + cha + 'px; width: ' + nw + 'px; padding-top: 5px;');
}, 100) // you know, in case I'm slow...
```
If you want the exact height of col-md-3
and col-md-9
to be the same, you could use JS.
The first thing I'll point out is that the max-width
of the main container is a problem. I really suggest that you either unset this or change the value. I think it's set to 960 px. You can change the main container max-width
property with styles.
<style>
.main-container {
max-width: 1200px;
}
</style>
Ensuring that the columns are the same height:
```{r listenOrElse,results="asis",engine="js"}
// do what you're told
setTimeout(function(){
ch = document.querySelector('.col-md-3').clientHeight; // get table height
sh = document.querySelector('.col-md-9');
si = document.querySelector('img'); // assuming there's only one!
sih = si.clientHeight;
siw = si.clientWidth;
nw = siw/sih * ch; // new width of plot (maintain aspect-ratio)
sh.style.height = ch + 'px'; // change height of container
// change height and width of plot
si.setAttribute('style', 'height: ' + ch + 'px; width: ' + nw + 'px');
}, 100) // you know, in case I'm slow...
```
Upvotes: 8