├── .gitignore ├── cottrell2020 ├── cottrell2020.R ├── cottrell2020_final.png ├── fish.png └── readme.md ├── craven2019 ├── craven2019.R ├── craven2019_final.png └── readme.md ├── dannenberg2020 ├── dannenberg2020.R ├── dannenberg2020_final.png ├── data │ └── world_phenoregions_L1.tif └── readme.md ├── engemann2020 ├── engemann2020.R ├── engemann2020_final.png └── readme.md ├── ggplot tips and tricks by Cédric Scherer.pdf ├── guyton2020 ├── animals │ ├── buffalo.png │ ├── impala.png │ ├── oribi.png │ ├── reedbuck.png │ ├── warthog.png │ └── waterbuck.png ├── data │ └── Mimosa_RRA_FOO.csv ├── guyton2020.R ├── guyton2020_final.png └── readme.md ├── haase2020 ├── haase2020.R ├── haase2020_final.png └── readme.md ├── janssen2020 ├── janssen2020-fig3_final.png ├── janssen2020_fig3.R └── readme.md ├── klingbeil2021 ├── animals │ ├── PhyloPic.3baf6f47.Ferran-Sayol.Mesitornis_Mesitornis-unicolor_Mesitornithidae.png │ ├── PhyloPic.822f98c6.Mason-McNair.Paspalum_Paspalum-vaginatum.png │ ├── PhyloPic.ad11bfb7.Ferran-Sayol.Threskiornis_Threskiornis-aethiopicus_Threskiornithidae_Threskiornithinae.png │ └── PhyloPic.ca1082e0.George-Edward-Lodge-modified-by-T-Michael-Keesey.Aves.png ├── klingbeil2021.R ├── klingbeil2021_final.png └── readme.md ├── macdougall2020 ├── macdougall2020.R ├── macdougall2020_final.png └── readme.md ├── nerlekar2020 ├── nerlekar2020.R ├── nerlekar2020_final.png ├── point_data.csv └── readme.md ├── ober2020 ├── ober2020.R ├── ober2020_final.png └── readme.md ├── readme.md ├── reyes2019 ├── readme.md ├── reyes2019.R └── reyes2019_final.png ├── suggitt2019 ├── data │ ├── Koppen_Geiger Edited and Completed │ │ ├── Read me.txt │ │ ├── Shapefiles │ │ │ ├── world_climates_completed_koppen_geiger.cpg │ │ │ ├── world_climates_completed_koppen_geiger.dbf │ │ │ ├── world_climates_completed_koppen_geiger.prj │ │ │ ├── world_climates_completed_koppen_geiger.qpj │ │ │ ├── world_climates_completed_koppen_geiger.shp │ │ │ └── world_climates_completed_koppen_geiger.shx │ │ └── Style │ │ │ └── world_climate.qml │ └── Vellend_data_updated.csv ├── readme.md ├── suggitt2019.R ├── suggitt2019_final.png └── suggitt2019_original.jpg ├── sullivan2020 ├── map_points.geojson ├── readme.md ├── sullivan2020.R └── sullivan2020_final.png ├── thein2020 ├── create_data.R ├── readme.md ├── simulated_data.csv ├── thein2020.R └── thein2020_final.png ├── yan2019 ├── readme.md ├── yan2019.R └── yan2019_final.png └── zhang2020 ├── readme.md ├── zhang2020.R └── zhang2020_final.png /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # User-specific files 9 | .Ruserdata 10 | 11 | # Example code in package build process 12 | *-Ex.R 13 | 14 | # Output files from R CMD build 15 | /*.tar.gz 16 | 17 | # Output files from R CMD check 18 | /*.Rcheck/ 19 | 20 | # RStudio files 21 | .Rproj.user/ 22 | 23 | # produced vignettes 24 | vignettes/*.html 25 | vignettes/*.pdf 26 | 27 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 28 | .httr-oauth 29 | 30 | # knitr and R markdown default cache directories 31 | *_cache/ 32 | /cache/ 33 | 34 | # Temporary files created by R markdown 35 | *.utf8.md 36 | *.knit.md 37 | 38 | # R Environment Variables 39 | .Renviron 40 | -------------------------------------------------------------------------------- /cottrell2020/cottrell2020.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(cowplot) 3 | library(ggnewscale) 4 | library(png) 5 | 6 | ################################## 7 | # This is a replication of Figure 1 from the following paper 8 | # Cottrell, R.S., Blanchard, J.L., Halpern, B.S. et al. Global adoption of novel 9 | # aquaculture feeds could substantially reduce forage fish demand by 2030. 10 | # Nature Food 1, 301–308 (2020). https://doi.org/10.1038/s43016-020-0078-x 11 | 12 | # This figure uses simulated data and is not an exact copy. 13 | # It's meant for educational purposes only. 14 | ############################################# 15 | # First generate random data to use in the figure 16 | generate_random_timeseries = function(n,initial_value, trend,error){ 17 | ts = 1:n 18 | ts[1] = initial_value 19 | for(t in 2:n){ 20 | ts[t] = ts[t-1] + trend + rnorm(1, mean=0, sd=error) 21 | } 22 | return(ts) 23 | } 24 | 25 | set.seed(2) 26 | 27 | years=1960:2013 28 | timeseries_data = tibble(aquaculture = generate_random_timeseries(n=length(years),initial_value = 0, trend=4e4, error = 2e6)) 29 | timeseries_data$total_animal_feed = timeseries_data$aquaculture + 8e6 + rnorm(nrow(timeseries_data), mean=0,sd=2e6) 30 | timeseries_data$historic_supply = timeseries_data$aquaculture + 20e6 + rnorm(nrow(timeseries_data), mean=0,sd=2e6) 31 | timeseries_data$year = years 32 | 33 | # Make the timeseries data tidy for use in ggplot 34 | timeseries_data_long = timeseries_data %>% 35 | pivot_longer(cols =c(aquaculture, total_animal_feed, historic_supply), names_to = 'demand_type', values_to = 'tonnes') 36 | 37 | # It's common to have values with _ and stuff in them so they're easier to use column names and stuff, 38 | # but those usually make for bad figure text. Use a factor to assign nice labels, and at the same time 39 | # specify the order of those labels. Which specified the order they get colored and the order in the legend. 40 | demand_type_values = c('historic_supply','total_animal_feed','aquaculture') 41 | demand_type_labels = c('Historical Supply','Total animal feed use','Aquaculture use') 42 | 43 | timeseries_data_long$demand_type = factor(timeseries_data_long$demand_type, levels=demand_type_values, 44 | labels = demand_type_labels, ordered = TRUE) 45 | ###################################### 46 | avg_value_labels = tribble( 47 | ~label_y, ~avg_label, 48 | 32e6, 'Average Supply\n1980-2013', 49 | 24e6, 'Proposed\nEBFM limit', 50 | 18e6, 'Average feed use\n1980-2013' 51 | ) 52 | 53 | timeseries_plot = ggplot(timeseries_data_long,aes(x=year, y=tonnes, color=demand_type)) + 54 | geom_line(size=2) + 55 | scale_color_manual(values=c('steelblue1','black','grey50')) + 56 | ggnewscale::new_scale_color() + 57 | geom_segment(x=2013,xend=2035,y=30e6,yend=30e6, color='black', linetype='solid', size=1.5) + # avg supply '80-'13 # These 3 lines and labels off to the right are easiest to do 1 at a time. 58 | geom_segment(x=2013,xend=2035,y=22e6,yend=22e6, color='black',linetype='dashed', size=1.5) + # proposed limit # Its possible to make a data.frame defining each one as was done with the text, but 59 | geom_segment(x=2013,xend=2035,y=20e6,yend=20e6, color='grey50',linetype='solid', size=1.5) + # avg feed use # it gets complicated because of the different colors also used in the mains lines and forming a single legend. 60 | geom_text(data=avg_value_labels,aes(x=2014,y=label_y,label=avg_label),size=8,hjust=0,inherit.aes = F) + # inherit.aes=FALSE here because the label data.frame does not have demand type, specified in the main ggplot call 61 | geom_vline(xintercept = 2013, color='grey50',linetype='dashed') + # y, and x values are different than whats in the main ggplot() call. 62 | scale_x_continuous(breaks=c(1970,1990,2010,2030), limits = c(1960,2035)) + 63 | #scale_y_continuous(labels = scales::label_math(.x, format=function(l){paste(l/10e5,'x 10^6')})) + 64 | #scale_y_continuous(labels = function(l){paste(l/10e5,'x 10^6')}) + 65 | scale_y_continuous(limits = c(0,4.1e7),breaks = c(0,1e7,2e7,3e7,4e7), expand = c(0,0), # Expand here takes the buffer off the top and bottom, putting the 0 values directly on the bottom axis line. 66 | labels=parse(text=c('0','10 %*% 10^6','20 %*% 10^6','30 %*% 10^6','40 %*% 10^6'))) + # scales::label_math or scales::label_scientific are probably able to make these labels automatically, 67 | theme_classic(35) + # but I could not figure out how to get this exact format. 68 | theme(legend.position = c(0.18,0.98), 69 | legend.text = element_text(size=25), 70 | legend.key.width = unit(25,'mm'), 71 | legend.title = element_blank(), 72 | legend.background = element_blank(), 73 | axis.line = element_line(color='grey80'), 74 | axis.text = element_text(color='black')) + 75 | labs(y='Forage fish demand (tonnes)',x='Year') 76 | 77 | ######################################################## 78 | # The bar plot 79 | 80 | # eyeball the mean values 81 | barplot_data = tribble( 82 | ~scenario,~demand,~demand_sd, 83 | 'BAU', 22e6, 2e6, 84 | 'Rap.Gr.',26e6, 2e6, 85 | 'Cons.Shft',37e6, 2e6 86 | ) 87 | 88 | # Make scenario and put them in the same order specified in the tribble 89 | # to specify the left-> right order on the x-axis. 90 | barplot_data$scenario = fct_inorder(barplot_data$scenario) 91 | 92 | barplot = ggplot(barplot_data, aes(x=scenario, y=demand, fill=scenario)) + 93 | geom_col(width=0.5) + 94 | scale_fill_manual(values=c('midnightblue','palegreen3','deeppink3')) + # interesting color choices here... 95 | geom_errorbar(aes(ymax = demand+demand_sd, ymin=demand-demand_sd), width=0.1, size=1) + 96 | geom_hline(yintercept = 30e6, color='black', linetype='solid',size=1.5) + # avg supply '80-'13 97 | geom_hline(yintercept=22e6, color='black',linetype='dashed',size=1.5) + # proposed limit 98 | geom_hline(yintercept=20e6, color='grey50',linetype='solid',size=1.5) + # avg feed use 99 | scale_y_continuous(limits = c(0,4.1e7),breaks = c(0,1e7,2e7,3e7,4e7), expand = c(0,0), 100 | labels=parse(text=c('0','10 %*% 10^6','20 %*% 10^6','30 %*% 10^6','40 %*% 10^6'))) + 101 | theme_bw(35) + 102 | theme(legend.position = 'none', 103 | panel.border = element_blank(), 104 | panel.grid = element_blank(), 105 | axis.line = element_line(color='grey80'), 106 | axis.text = element_text(color='black')) + 107 | labs(y='Forage fish demand (tonnes)', x='Aquaculture 2030 scenario') 108 | 109 | ####################################################### 110 | fish = png::readPNG('cottrell2020/fish.png') 111 | 112 | final_figure = plot_grid(timeseries_plot, barplot, nrow = 1, 113 | rel_heights = c(0.9,0.9), rel_widths = c(1,0.7), 114 | labels = c('a','b'), label_size = 40) + 115 | draw_image(fish,x=0.44, y=0.82, width=0.12, height=0.12) 116 | 117 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 118 | 119 | final_figure = ggdraw(final_figure) + 120 | geom_rect(data=data.frame(xmin=0.05,ymin=0.05), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.4,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 121 | draw_text(water_mark, x=0.05, y=0.1, size=20, hjust = 0) 122 | 123 | save_plot('./cottrell2020/cottrell2020_final.png', plot=final_figure, 124 | base_height = 30, base_width = 70, units='cm', dpi=50) 125 | 126 | -------------------------------------------------------------------------------- /cottrell2020/cottrell2020_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/cottrell2020/cottrell2020_final.png -------------------------------------------------------------------------------- /cottrell2020/fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/cottrell2020/fish.png -------------------------------------------------------------------------------- /cottrell2020/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Cottrell, R.S., Blanchard, J.L., Halpern, B.S. et al. Global adoption of novel aquaculture feeds could substantially reduce forage fish demand by 2030. Nature Food 1, 301–308 (2020). https://doi.org/10.1038/s43016-020-0078-x 3 | 4 | Figure 1 5 | 6 | ## Notes 7 | 8 | A good example on how to get non-standard y-axis label. It's probably possible to have the 40x10**6 format being done automatically using `scales::label_math` or `scales::label_scientific`, but I could not figure that out. 9 | 10 | ## Reproduced Figure 11 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/cottrell2020/cottrell2020_final.png) 12 | -------------------------------------------------------------------------------- /craven2019/craven2019.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(cowplot) # cowplot is used here just for the disclaimer watermark at the end. Its not needed for the primary figure. 3 | 4 | ################################## 5 | # This is a replication of Figure 5 from the following paper 6 | # Craven, D., Knight, T.M., Barton, K.E., Bialic-Murphy, L. and Chase, J.M., 2019. 7 | # Dissecting macroecological and macroevolutionary patterns of forest biodiversity across the Hawaiian archipelago. 8 | # Proceedings of the National Academy of Sciences, 116(33), pp.16436-16441. https://doi.org/10.1073/pnas.1901954116 9 | 10 | # Data used here are simulated, and not meant to replicate the original figure exactly. 11 | ################################## 12 | set.seed(1) 13 | 14 | data_starter = tribble( 15 | ~island, ~species, ~beta_s_mean, 16 | "Hawai'i",'All species', 8, 17 | "Hawai'i",'Native species', 7, 18 | "Maui Nui",'All species', 7, 19 | "Maui Nui",'Native species', 6.5, 20 | "O'ahu",'All species', 9, 21 | "O'ahu",'Native species', 11, 22 | "Kaua'i",'All species', 10, 23 | "Kaua'i",'Native species', 17, 24 | ) 25 | 26 | # Make the order as the same specified above. This affects the left-right ordering on the x-axis. 27 | data_starter$island = fct_inorder(data_starter$island) 28 | 29 | figure_data = data_starter %>% 30 | group_by(island, species) %>% 31 | summarise(beta_s = rnorm(n=100, mean=beta_s_mean, sd=2)) %>% 32 | ungroup() 33 | 34 | # The fading effect around the points of this plot are points of individual observations. 35 | # They have a low alpha value, so when a lot are stacked together it becomes darker. 36 | # Its done here with geom_point() and the alpha argument. 37 | 38 | # How does position_dodge() know to split them up by species?? It references the group argument in the first aes() call, 39 | # which is not set explicitly but gets set automatically when color is set. Try replacing color with group to see what happens. 40 | 41 | final_figure = ggplot(figure_data, aes(x=island, y=beta_s, color=species)) + 42 | geom_point(position = position_dodge(width = 0.5),size=3, alpha=0.1) + 43 | stat_summary(geom='point', fun = 'mean', # This places the mean point at the center. 44 | size=7, 45 | position = position_dodge(width=0.5)) + 46 | scale_color_manual(values = c("purple4","darkorange")) + 47 | theme_bw(25) + 48 | theme(legend.position = 'top', 49 | legend.direction = 'horizontal', 50 | panel.grid = element_blank(), 51 | panel.background = element_rect(size = 2, color='black'), 52 | axis.text = element_text(face = 'bold',color='black'), 53 | legend.text = element_text(face='bold'), 54 | axis.title = element_text(face='bold')) + 55 | labs(x='', y='βs', color='') + # Note the beta symbol is a unicode character. Easiest method is copy pasting from wikipedia directly into R. 56 | guides(color=guide_legend(override.aes = list(size=10))) # With guides the legend symbols can be made bigger than whats in the plot. 57 | 58 | ############################################# 59 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 60 | 61 | final_figure = ggdraw(final_figure) + 62 | geom_rect(data=data.frame(xmin=0.05,ymin=0.15), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.5,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 63 | draw_text(water_mark, x=0.05, y=0.2, size=10, hjust = 0) 64 | 65 | save_plot('./craven2019/craven2019_final.png',plot = final_figure, base_height = 6.5, base_width = 10) 66 | -------------------------------------------------------------------------------- /craven2019/craven2019_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/craven2019/craven2019_final.png -------------------------------------------------------------------------------- /craven2019/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Craven, D., Knight, T.M., Barton, K.E., Bialic-Murphy, L. and Chase, J.M., 2019. Dissecting macroecological and macroevolutionary patterns of forest biodiversity across the Hawaiian archipelago. Proceedings of the National Academy of Sciences, 116(33), pp.16436-16441. https://doi.org/10.1073/pnas.1901954116 3 | 4 | Figure 5 5 | 6 | ## Notes 7 | A good example for: 8 | - using `position_dodge()` with points. 9 | - creating a nice fade effect using points with the `alpha` argument. 10 | - having special sympbols in the axis labels. 11 | 12 | ## Reproduced Figure 13 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/craven2019/craven2019_final.png) 14 | -------------------------------------------------------------------------------- /dannenberg2020/dannenberg2020.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(sf) 3 | library(cowplot) 4 | 5 | ################################## 6 | # This is a replication of Figure 4 from the following paper 7 | # Dannenberg, M., Wang, X., Yan, D., & Smith, W. (2020). Phenological Characteristics of 8 | # Global Ecosystems Based on Optical, Fluorescence, and Microwave Remote Sensing. 9 | # Remote Sensing, 12(4), 671. https://doi.org/10.3390/rs12040671 10 | 11 | # This figure uses simulated data and is not an exact copy. 12 | # It's meant for educational purposes only. 13 | ################################## 14 | 15 | 16 | # The data is supplied as a raster. Which is converted to an sf polygon object 17 | # by way of a SpatialPolygonDataframe. 18 | # The reason for this is the sf package has great support for plotting maps in different 19 | # projections. 20 | # The phenoregion raster is from the paper. downloaded from https://data.mendeley.com/datasets/k35ry274gv/1 21 | phenoregion_raster = raster::raster('./dannenberg2020/data/world_phenoregions_L1.tif') 22 | phenoregion_polygon = raster::rasterToPolygons(phenoregion_raster) 23 | phenoregions = st_as_sf(phenoregion_polygon) %>% 24 | rename(phenoregion = world_phenoregions_L1) %>% 25 | group_by(phenoregion) %>% 26 | summarise() %>% 27 | ungroup() 28 | 29 | # The colors here are done with 4 base colors and 3 levels of "adding" more white to them. 30 | # This can be done by adjusting the transparency in ggplot thru the alpha argument. 31 | # This method of representing 2 variables is well researched see: 32 | # Kaye et al. 2012. Mapping the climate: Guidance on appropriate techniques to map climate variables and their uncertainty. 33 | # Geoscientific Model Development, 5(1), 245–256. https://doi.org/10.5194/gmd-5-245-2012 34 | 35 | # #483248 purple 36 | # #33b69b bluish 37 | # #e29601 gold 38 | # #eb6b51 red 39 | phenoregion_colors = tribble( 40 | ~phenoregion, ~color, ~alpha, ~seasonality, ~productivity, 41 | 1, '#eb6b51', 0.5, 3, 1, 42 | 2, '#e29601', 0.5, 3, 2, 43 | 3, '#33b69b', 0.5, 3, 3, 44 | 4, '#483248', 0.5, 3, 4, 45 | 5, '#eb6b51', 0.75, 2, 1, 46 | 6, '#e29601', 0.75, 2, 2, 47 | 7, '#33b69b', 0.75, 2, 3, 48 | 8, '#483248', 0.75, 2, 4, 49 | 9, '#eb6b51', 1.0, 1, 1, 50 | 10, '#e29601', 1.0, 1, 2, 51 | 11, '#33b69b', 1.0, 1, 3, 52 | 12, '#483248', 1.0, 1, 4 53 | ) 54 | 55 | ################################################################################# 56 | # Setup the legend. 57 | # The style of legend can't be done automatically in ggplot. But it can be done 58 | # manually be creating a 2nd smaller plot which is a 3x4 grid. 59 | 60 | base_legend = ggplot(phenoregion_colors, aes(x=productivity, y=seasonality, 61 | fill=as.factor(phenoregion), alpha=as.factor(phenoregion))) + 62 | geom_tile(colour='black', size=0.5) + 63 | geom_text(aes(label=phenoregion), size=4, alpha=1, color='black') + 64 | scale_fill_manual(values = phenoregion_colors$color) + 65 | scale_alpha_manual(values = phenoregion_colors$alpha) + 66 | theme_nothing() + 67 | labs(title = 'Phenoregion')+ 68 | theme(plot.title = element_text(hjust=0.5, vjust=0, face = 'bold', size=15)) 69 | 70 | full_legend= ggdraw() + 71 | draw_plot(base_legend, scale=0.6, hjust = -0.1, vjust=-0.1) + 72 | draw_text('Productivity', x = 0.55, y=0.1, size=14) + 73 | draw_line(x=c(0.4, 0.8), y=c(0.23,0.23), size=0.8, arrow = arrow(length=unit(4,'mm'))) + 74 | draw_text('Seasonality', x = 0.14, y=0.35, size=14) + 75 | draw_line(x=c(0.27, 0.27), y=c(0.35,0.75), size=0.8, arrow = arrow(length=unit(4,'mm'))) 76 | 77 | ############################################ 78 | # Pie chart 79 | # Getting the order and the labels on this pie chart was a huge pain. 80 | # Note that the final pie chart labels are still a bit off. 81 | # Pie charts also have well studied problems. See https://stats.stackexchange.com/questions/8974/problems-with-pie-charts/ 82 | # If you're thinking of doing something similar please consider just a barchart. 83 | 84 | # The pie chart by default goes clockwise from numbers 1->12 85 | # The original pie chart in the paper is not exactly that so it must be coerced manually. 86 | pie_chart_order = data.frame(phenoregion = c(12,8,4,11,7,3,10,6,2,9,5,1)) 87 | pie_chart_order$pie_order = 1:nrow(pie_chart_order) 88 | 89 | phenoregions$area = st_area(phenoregions) 90 | 91 | pie_chart_data = phenoregions %>% 92 | as_tibble() %>% 93 | select(phenoregion, area) %>% 94 | mutate(area = as.numeric(round(area/sum(area),2))) %>% 95 | mutate(chart_label = paste0(round(area*100,0),'%')) %>% 96 | left_join(phenoregion_colors, by='phenoregion') %>% 97 | left_join(pie_chart_order, by='phenoregion') %>% 98 | arrange(pie_order) 99 | 100 | # The phenoregion ordering within the factor is now coerced to the order specified above in pie_chart_order 101 | pie_chart_data$phenoregion = fct_inorder(factor(pie_chart_data$phenoregion)) 102 | 103 | # The labels around the pie chart can be done by labelling specific spots 104 | # on the y axis, which is then wrapped around. The exact locations of 105 | # each labely are confusingly calculated here... 106 | pie_chart_data$label_y_pos = NA 107 | pie_chart_data$label_y_pos[1] = pie_chart_data$area[1]/2 108 | for(phenoregion_i in 2:nrow(pie_chart_data)){ 109 | pie_chart_data$label_y_pos[phenoregion_i] = sum(pie_chart_data$area[1:(phenoregion_i-1)]) + (pie_chart_data$area[phenoregion_i]/2) 110 | } 111 | pie_chart_data$label_y_pos = 1-pie_chart_data$label_y_pos 112 | 113 | 114 | pie_chart = ggplot(pie_chart_data, aes(x="", y=area, fill=phenoregion,alpha=phenoregion)) + 115 | geom_bar(stat='identity', width=1, color='black') + 116 | scale_fill_manual(values=pie_chart_data$color) + 117 | scale_alpha_manual(values = pie_chart_data$alpha) + 118 | scale_y_continuous(breaks = pie_chart_data$label_y_pos, labels=pie_chart_data$chart_label) + 119 | #geom_text(aes(label=area), position = position_stack(vjust=0.5), hjust=1) + 120 | coord_polar('y',start=0, direction=-1) + 121 | theme_nothing() + 122 | theme(panel.grid = element_blank(), 123 | panel.border = element_blank(), 124 | axis.title = element_blank(), 125 | axis.ticks = element_blank(), 126 | axis.text = element_text(size=10, hjust = 2), 127 | legend.position = 'none', 128 | legend.background = element_rect(color='black'), 129 | legend.title = element_blank()) 130 | 131 | ############################################# 132 | # The map 133 | 134 | map = ggplot(phenoregions) + 135 | geom_sf(aes(fill=as.factor(phenoregion), alpha=as.factor(phenoregion)), color='transparent') + 136 | scale_fill_manual(values = phenoregion_colors$color) + 137 | scale_alpha_manual(values = phenoregion_colors$alpha) + 138 | coord_sf(crs = "+proj=natearth +wktext") + 139 | theme_minimal() + 140 | theme(axis.text = element_blank(), 141 | legend.position = 'none') 142 | 143 | ############################################# 144 | # Put it together. 145 | 146 | final_figure = ggdraw() + 147 | draw_plot(map, x=0,y=0, scale=0.8, hjust = 0.1) + 148 | draw_plot(pie_chart, scale=0.4, x=0.38, y=0.1) + 149 | draw_plot(full_legend, scale=0.35, x=0.35, y=-0.3) 150 | 151 | 152 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 153 | 154 | final_figure = ggdraw(final_figure) + 155 | geom_rect(data=data.frame(xmin=0.05,ymin=0.05), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.4,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 156 | draw_text(water_mark, x=0.05, y=0.1, size=10, hjust = 0) 157 | 158 | save_plot('./dannenberg2020/dannenberg2020_final.png', plot = final_figure, base_height = 5, base_width = 12) 159 | -------------------------------------------------------------------------------- /dannenberg2020/dannenberg2020_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/dannenberg2020/dannenberg2020_final.png -------------------------------------------------------------------------------- /dannenberg2020/data/world_phenoregions_L1.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/dannenberg2020/data/world_phenoregions_L1.tif -------------------------------------------------------------------------------- /dannenberg2020/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Dannenberg, M., Wang, X., Yan, D., & Smith, W. (2020). Phenological Characteristics of Global Ecosystems Based on Optical, Fluorescence, and Microwave Remote Sensing. Remote Sensing, 12(4), 671. https://doi.org/10.3390/rs12040671 3 | 4 | Figure 4 5 | 6 | ## Notes 7 | The authors provided the raster dataset for this figure which made it pretty simple. The bicolor map is a really clever way to present it (see [this study](https://doi.org/10.5194/gmd-5-245-2012) for research into it), but it was not trivial to do. 8 | 9 | The hardest part was actually the pie chart since there is not native way to label around the edge like in the original figure. If you're looking to create something similar please just use a barchart. 10 | 11 | ## Reproduced Figure 12 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/dannenberg2020/dannenberg2020_final.png) 13 | -------------------------------------------------------------------------------- /engemann2020/engemann2020.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(cowplot) # cowplot is used here just for the disclaimer watermark at the end. Its not needed for the primary figure. 3 | 4 | ################################## 5 | # This is a replication of Figure 1 from the following paper 6 | # Engemann et al. 2020. Associations between growing up in natural 7 | # environments and subsequent psychiatric disorders in Denmark. 8 | # Environmental Research. https://doi.org/10.1016/j.envres.2020.109788. 9 | 10 | # Data used here are simulated, and not meant to replicate the original figure exactly. 11 | ################################## 12 | 13 | hazard_data = tribble( 14 | ~space_type, ~space_subtype, ~hazard_ratio, ~hazard_sd, 15 | 'Agriculture', 'Agriculture basic', 0.88, 0.01, 16 | 'Agriculture', 'Agriculture adjusted', 0.89, 0.01, 17 | 'Green space', 'Near-natural green space basic', 0.78, 0.05, 18 | 'Green space', 'Near-natural green space adjusted', 0.85, 0.05, 19 | 'Blue space', 'Blue space basic', 0.84, 0.025, 20 | 'Blue space', 'Blue space adjusted', 0.86, 0.025, 21 | 'Urban', 'Urban', 1.0, 0) 22 | 23 | # Use fct_inorder to set the factor order specified in the table above. 24 | # Otherwise the order will be alphabetical. 25 | hazard_data$space_subtype = fct_inorder(hazard_data$space_subtype) 26 | hazard_data$space_type = fct_inorder(hazard_data$space_type) 27 | 28 | final_figure = ggplot(hazard_data, aes(x=space_type, y=hazard_ratio, color=space_subtype)) + 29 | geom_hline(yintercept = 1, linetype='dotted', size=1) + # put the horizontal line first so points are drawn on top of it 30 | geom_point(position = position_dodge(width=0.2), size=4) + 31 | scale_color_manual(values = c('gold','goldenrod4','darkolivegreen3','green4','skyblue2','blue4','grey80')) + 32 | geom_errorbar(aes(ymax = hazard_ratio + hazard_sd, ymin = hazard_ratio - hazard_sd), width=0.05, 33 | position = position_dodge(width = 0.2)) + 34 | theme_bw(20) + 35 | theme(panel.grid = element_blank(), 36 | panel.border = element_blank(), # Turn off the full border but initialize the bottom and left lines 37 | axis.line.x.bottom = element_line(size=0.5), 38 | axis.line.y.left = element_line(size=0.5), 39 | axis.text.x = element_text(angle=45, hjust=1), 40 | legend.text = element_text(size=12), 41 | legend.title = element_blank()) + 42 | labs(x='',y='Hazard ratio (95% CI)') 43 | 44 | ############################################# 45 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 46 | 47 | final_figure = ggdraw(final_figure) + 48 | geom_rect(data=data.frame(xmin=0.1,ymin=0.25), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.5,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 49 | draw_text(water_mark, x=0.12, y=0.3, size=8, hjust = 0) 50 | 51 | 52 | save_plot('engemann2020/engemann2020_final.png', plot = final_figure, base_width = 10, base_height = 5) 53 | -------------------------------------------------------------------------------- /engemann2020/engemann2020_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/engemann2020/engemann2020_final.png -------------------------------------------------------------------------------- /engemann2020/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Engemann et al. 2020. Associations between growing up in natural environments and subsequent psychiatric disorders in Denmark. Environmental Research. https://doi.org/10.1016/j.envres.2020.109788. 3 | 4 | Figure 1 5 | 6 | ## Notes 7 | This figure pairs observations via two fields which get mapped to the x-axis and color. `position_dodge()` is used to separate the points slightly. 8 | 9 | 10 | ## Reproduced Figure 11 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/engemann2020/engemann2020_final.png) 12 | -------------------------------------------------------------------------------- /ggplot tips and tricks by Cédric Scherer.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/ggplot tips and tricks by Cédric Scherer.pdf -------------------------------------------------------------------------------- /guyton2020/animals/buffalo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/guyton2020/animals/buffalo.png -------------------------------------------------------------------------------- /guyton2020/animals/impala.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/guyton2020/animals/impala.png -------------------------------------------------------------------------------- /guyton2020/animals/oribi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/guyton2020/animals/oribi.png -------------------------------------------------------------------------------- /guyton2020/animals/reedbuck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/guyton2020/animals/reedbuck.png -------------------------------------------------------------------------------- /guyton2020/animals/warthog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/guyton2020/animals/warthog.png -------------------------------------------------------------------------------- /guyton2020/animals/waterbuck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/guyton2020/animals/waterbuck.png -------------------------------------------------------------------------------- /guyton2020/data/Mimosa_RRA_FOO.csv: -------------------------------------------------------------------------------- 1 | Species,Year,Season,Mean mimosa RRA,SEM Mimosa RRA,Mimosa FOO,Number of samples 2 | Waterbuck,2013,early dry,0.394033333,0.04860809,0.8,50 3 | Waterbuck,2015,early dry,0.331408333,0.033239899,0.9375,80 4 | Waterbuck,2016,early dry,0.362784314,0.084072107,0.823529412,17 5 | Waterbuck,2017,early dry,0.246729167,0.066045307,0.8125,16 6 | Waterbuck,2018,early dry,0.159577778,0.054935802,0.6,15 7 | Warthog,2013,early dry,0.013444444,0.005316793,0.366666667,30 8 | Warthog,2015,early dry,0.003631579,0.002225567,0.157894737,19 9 | Warthog,2016,early dry,0.0038,0.0038,0.2,5 10 | Warthog,2017,early dry,0.016787879,0.0159405,0.181818182,11 11 | Warthog,2018,early dry,0.022955556,0.008299542,0.6,15 12 | Reedbuck,2015,early dry,0.185541667,0.053099791,0.875,16 13 | Reedbuck,2016,early dry,0.462904762,0.135919438,0.857142857,7 14 | Reedbuck,2017,early dry,0.296,0.086345887,0.636363636,11 15 | Reedbuck,2018,early dry,0.201285714,0.076065583,0.928571429,14 16 | Impala,2015,early dry,0.592962963,0.121587664,1,9 17 | Impala,2016,early dry,0.557833333,0.130313267,1,8 18 | Impala,2017,early dry,0.281692308,0.068337726,0.769230769,13 19 | Impala,2018,early dry,0.222288889,0.066029686,0.8,15 20 | Oribi,2015,early dry,0.351333333,0.124113568,1,5 21 | Oribi,2016,early dry,0.353111111,0.107921868,0.888888889,9 22 | Oribi,2017,early dry,0.361138889,0.117984922,0.833333333,12 23 | Oribi,2018,early dry,0.266422222,0.088637682,0.733333333,15 24 | Buffalo,2015,early dry,0.409740741,0.031844755,0.962962963,27 25 | Impala,2017,late dry,0.149,0.149,0.25,4 26 | Impala,2018,late dry,0.086833333,0.055443582,0.5,6 27 | Oribi,2017,late dry,0.210208333,0.113107196,0.625,8 28 | Oribi,2018,late dry,0.723888889,0.15034722,0.8333333,6 29 | Reedbuck,2017,late dry,0.191,NA,1,1 30 | Reedbuck,2018,late dry,0.181833333,0.181833333,0.25,4 31 | Warthog,2017,late dry,0,0,0,5 32 | Warthog,2018,late dry,0.008066667,0.008066667,0.2,5 33 | Waterbuck,2017,late dry,0.309,0.165246012,0.8,5 34 | Waterbuck,2018,late dry,0.236444444,0.098060891,0.8333333,6 -------------------------------------------------------------------------------- /guyton2020/guyton2020.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(janitor) 3 | library(cowplot) 4 | library(png) 5 | 6 | ################################## 7 | # This is a replication of Figure 5 from the following paper 8 | # Guyton, J. A. et al. (2020). Trophic rewilding revives biotic resistance to shrub invasion. 9 | # Nature Ecology & Evolution, 1-13. https://doi.org/10.1038/s41559-019-1068-y 10 | 11 | # This figure uses simulated data and is not an exact copy. 12 | # It's meant for educational purposes only. 13 | ################################## 14 | 15 | buffalo = png::readPNG('guyton2020/animals/buffalo.png') 16 | impala = png::readPNG('guyton2020/animals/impala.png') 17 | oribi = png::readPNG('guyton2020/animals/oribi.png') 18 | reedbuck = png::readPNG('guyton2020/animals/reedbuck.png') 19 | waterbuck = png::readPNG('guyton2020/animals/waterbuck.png') 20 | warthog = png::readPNG('guyton2020/animals/warthog.png') 21 | 22 | 23 | ################################ 24 | # The dataset for this figure is available from https://doi.org/10.5061/dryad.sxksn02zc 25 | animal_data = read_csv('guyton2020/data/Mimosa_RRA_FOO.csv') %>% 26 | janitor::clean_names() %>% 27 | filter(season=='early dry') 28 | 29 | # Add in NA values for missing years of some animals. 30 | # This will ensure the spacing of the bar graph columns are correct. 31 | # Comment out this part to see how it affects the figure. 32 | animal_data = animal_data %>% 33 | complete(year, species) 34 | 35 | animal_order = c('Warthog','Waterbuck','Reedbuck','Impala','Oribi','Buffalo') 36 | animal_data$species = factor(animal_data$species, levels = animal_order, ordered = TRUE) 37 | 38 | # 2013, 2015, 2016, 2017, 2018 39 | year_colors = c('yellow2','orange2','palegreen4','tan3','steelblue4') 40 | animal_data$year = as.factor(animal_data$year) 41 | 42 | ############################################ 43 | # Left side plot 44 | barplot_a = ggplot(animal_data, aes(x=species, y=mean_mimosa_rra, fill=as.factor(year))) + 45 | geom_col(position = position_dodge(width=1), color='black') + 46 | geom_text(aes(y=mean_mimosa_rra + sem_mimosa_rra + 0.05, label=number_of_samples), 47 | position = position_dodge(width=1)) + # note sure why geom_text needs the width set in position dodge while geom_col and geom_errobar do not 48 | geom_errorbar(aes(ymin = mean_mimosa_rra - sem_mimosa_rra, ymax=mean_mimosa_rra + sem_mimosa_rra), 49 | width=0.5,position = position_dodge(width=1)) + 50 | scale_fill_manual(values=year_colors) + 51 | ylim(0, 0.9) + 52 | labs(x='LMH',y='M. pigra relative sequence read abundance per sample') + 53 | theme_bw() + 54 | theme(panel.grid = element_blank(), 55 | axis.ticks.x = element_blank(), 56 | axis.line.x.bottom = element_line(size=0.5), 57 | axis.line.y.left = element_line(size=0.5), 58 | axis.text = element_text(face='bold', size=12), 59 | panel.background = element_blank(), 60 | panel.border = element_blank(), 61 | legend.position = c(0.08,0.75)) + 62 | guides(fill = guide_legend(title = 'Year')) 63 | 64 | barplot_a_with_animals = ggdraw() + 65 | draw_plot(barplot_a, height=0.95) + 66 | draw_image(buffalo, x=0.85, y=0.62, width=0.08, height=0.08) + # buffalo 67 | draw_image(oribi, x=0.71, y=0.65, width=0.08, height=0.08) + # oribi 68 | draw_image(impala, x=0.55, y=0.8, width=0.08, height=0.08) + # impala 69 | draw_image(reedbuck, x=0.45, y=0.72, width=0.08, height=0.08) + # reedbuck 70 | draw_image(waterbuck, x=0.27, y=0.62, width=0.08, height=0.08) + # waterbuck 71 | draw_image(warthog, x=0.12, y=0.25, width=0.08, height=0.08) # warthog 72 | 73 | ############################################ 74 | # Right side plot 75 | barplot_b = ggplot(animal_data, aes(x=species, y=mimosa_foo, fill=as.factor(year))) + 76 | geom_col(position = position_dodge(), color='black') + 77 | scale_fill_manual(values=year_colors) + 78 | ylim(0, 1) + 79 | labs(x='LMH',y='Proportional occurrence of M. pigra accross samples') + 80 | theme_bw() + 81 | theme(panel.grid = element_blank(), 82 | axis.ticks.x = element_blank(), 83 | axis.line.x.bottom = element_line(size=0.5), 84 | axis.line.y.left = element_line(size=0.5), 85 | axis.text = element_text(face='bold', size=12), 86 | panel.background = element_blank(), 87 | panel.border = element_blank(), 88 | legend.position = 'none') 89 | 90 | barplot_b_with_animals =ggdraw() + 91 | draw_plot(barplot_b, height=0.95) + 92 | draw_image(buffalo, x=0.85, y=0.88, width=0.08, height=0.08) + # buffalo 93 | draw_image(oribi, x=0.71, y=0.9, width=0.08, height=0.08) + # oribi 94 | draw_image(impala, x=0.55, y=0.91, width=0.08, height=0.08) + # impala 95 | draw_image(reedbuck, x=0.42, y=0.85, width=0.08, height=0.08) + # reedbuck 96 | draw_image(waterbuck, x=0.25, y=0.88, width=0.08, height=0.08) + # waterbuck 97 | draw_image(warthog, x=0.10, y=0.5, width=0.08, height=0.08) # warthog 98 | 99 | ##############################################################3 100 | 101 | final_figure = plot_grid(barplot_a_with_animals, barplot_b_with_animals, labels = c('A','B')) 102 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 103 | 104 | final_figure = ggdraw(final_figure) + 105 | geom_rect(data=data.frame(xmin=0.05,ymin=0.05), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.4,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 106 | draw_text(water_mark, x=0.05, y=0.1, size=10, hjust = 0) 107 | 108 | save_plot('./guyton2020/guyton2020_final.png', plot=final_figure, base_height = 6, base_width = 13) 109 | -------------------------------------------------------------------------------- /guyton2020/guyton2020_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/guyton2020/guyton2020_final.png -------------------------------------------------------------------------------- /guyton2020/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Guyton, J.A., Pansu, J., Hutchinson, M.C., Kartzinel, T.R., Potter, A.B., Coverdale, T.C., Daskin, J.H., da Conceição, A.G., Peel, M.J., Stalmans, M.E. and Pringle, R.M. (2020). Trophic rewilding revives biotic resistance to shrub invasion. Nature Ecology & Evolution, 1-13. https://doi.org/10.1038/s41559-019-1068-y 3 | 4 | Figure 3 5 | 6 | ## Notes 7 | 8 | An important aspect here was filling in missing values for each animal so that spacing on the x-axis is done correctly. Placement of animal silhouettes required fine tuning of each one using x,y arguments in `cowplot::draw_image()`. Also note how the primary bar graphs have their `height` in `draw_plot()` adjusted down slightly to make room for the silhouettes. The animal silhouettes are not exact matches for each species. 9 | 10 | ## Reproduced Figure 11 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/guyton2020/guyton2020_final.png) 12 | -------------------------------------------------------------------------------- /haase2020/haase2020.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(cowplot) 3 | 4 | #-------------- 5 | # This is a replication of Figure 2 from the following paper 6 | # Haase, Catherine G., et al. 2020. "Body mass and hibernation microclimate may 7 | # predict bat susceptibility to white‐nose syndrome." Ecology and Evolution. 8 | # https://doi.org/10.1002/ece3.7070 9 | 10 | # This figure uses simulated data and is not an exact copy. 11 | # It's meant for educational purposes only. 12 | #-------------- 13 | 14 | figure_data = tribble( 15 | ~species, ~survival, ~survival_sd, 16 | 'Eptesicus fuscus', 105, 50, 17 | 'Corynorhinus townsendii', 60, 110, 18 | 'Myotis velifer', 60, 100, 19 | 'Perimyotis subflavus', -10, 25, 20 | 'Myotis thysanodes', -50, 100, 21 | 'Myotis volans', -55, 90, 22 | 'Myotis lucifugus', -60, 90, 23 | 'Myotis evotis', -70, 80, 24 | 'Myotis ciliolabrum', -90, 25 25 | ) 26 | 27 | # Calculate low and high values for the error bars 28 | figure_data = figure_data %>% 29 | mutate(survival_low = survival - survival_sd, 30 | survival_high = survival + survival_sd) 31 | 32 | # Make the y-axis a factor with order according to the survival (lowest to highest) 33 | # note here the first position will be at the bottom (y = 0). 34 | # If this is not done then the y-axis will be ordered alphabetically 35 | figure_data$species = fct_reorder(figure_data$species, figure_data$survival) 36 | 37 | 38 | final_figure = ggplot(figure_data, aes(y=species, x=survival)) + 39 | geom_errorbarh(aes(xmin = survival_low, xmax=survival_high), height=0.2) + # Put error bars first so the points get drawn on top. 40 | geom_point(size=4, shape=21, fill='#1b9e77', color='black', stroke=0.4) + # the "21" shape is a point with an outer stroke. With this the fill arg becomes 41 | geom_vline(xintercept = 0, linetype='dashed') + # the main color, and the color arg becomes the outer line color, and stroke the outer line width. 42 | theme_bw() + 43 | theme(panel.grid = element_blank(), 44 | axis.title.x = element_text(size=12, color='black'), 45 | axis.text.x = element_text(size=12, color='black'), 46 | axis.text.y = element_text(size=14, color='black', face = 'italic')) + 47 | labs(x='Difference between Predicted Survival and Winter Duration (days)', y='') 48 | 49 | 50 | #-------------- 51 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 52 | 53 | final_figure = ggdraw(final_figure) + 54 | geom_rect(data=data.frame(xmin=0.05,ymin=0.05), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.6,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 55 | draw_text(water_mark, x=0.05, y=0.1, size=10, hjust = 0) 56 | 57 | save_plot('./haase2020/haase2020_final.png', plot=final_figure, base_height = 5, base_width = 8) 58 | -------------------------------------------------------------------------------- /haase2020/haase2020_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/haase2020/haase2020_final.png -------------------------------------------------------------------------------- /haase2020/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Haase, Catherine G., et al. 2020. "Body mass and hibernation microclimate may predict bat susceptibility to white‐nose syndrome." Ecology and Evolution. https://doi.org/10.1002/ece3.7070 3 | 4 | Figure 2 5 | 6 | ## Notes 7 | Good example of ordering a discrete variable by the plot values. Which makes a nice cascade affect with the points. 8 | 9 | ## Reproduced Figure 10 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/haase2020/haase2020_final.png) 11 | -------------------------------------------------------------------------------- /janssen2020/janssen2020-fig3_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/janssen2020/janssen2020-fig3_final.png -------------------------------------------------------------------------------- /janssen2020/janssen2020_fig3.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(cowplot) # cowplot is used here just for the disclaimer watermark at the end. Its not needed for the primary figure. 3 | 4 | ################################## 5 | # This is a replication of Figure 2 from the following paper 6 | # Janssen, T., Fleischer, K., Luyssaert, S., Naudts, K. and Dolman, H., 2020. Drought resistance increases from the individual to the ecosystem level in highly diverse neotropical rain forest: a meta-analysis of leaf, tree and ecosystem responses to drought. Biogeosciences. https://doi.org/10.5194/bg-17-2621-2020 7 | 8 | # Data used here are simulated, and not meant to replicate the original figure exactly. 9 | 10 | ############################## 11 | # Specify each y-axis label 12 | leaf_measurements = c('Midday stomatal conductance', 13 | 'Midday photosynthesis', 14 | 'Midday intrinsic water use efficiency', 15 | 'Predawn water leaf potental', 16 | 'Midday leaf water potential') 17 | 18 | tree_measurements = c('Midday water potential gradient', 19 | 'Midday soil-leaf hydraulic conductance', 20 | 'Midday crown conduncance', 21 | 'Daily transpiration', 22 | 'Stem diameter growth', 23 | 'Leaf flushing', 24 | 'Litterfall') 25 | 26 | ecosystem_measurements = c('Evapotranspiration', 27 | 'Net ecosystem productivity', 28 | 'Net primary producivity', 29 | 'Above-ground net primary productivity', 30 | 'Gross ecosystem productivity', 31 | 'Ecosystem respiration', 32 | 'Ecosystem water use efficiency') 33 | 34 | drought_types = c('seasonal','eposodic') 35 | 36 | # Setup a tidy data frame so each row represents a single point/line 37 | leaf_entries = expand_grid(measurement_level='leaf', measurement = leaf_measurements, drought_type = drought_types) 38 | tree_entries = expand_grid(measurement_level='tree', measurement = tree_measurements, drought_type = drought_types) 39 | ecosystem_entries = expand_grid(measurement_level='ecosystem', measurement = ecosystem_measurements, drought_type = drought_types) 40 | 41 | all_data = leaf_entries %>% 42 | bind_rows(tree_entries) %>% 43 | bind_rows(ecosystem_entries) 44 | 45 | # Simulate some data to get the mean, and upper/lower 95 confidence intervals in the 0-1 bounds 46 | set.seed(2) 47 | all_data$change_mean = runif(nrow(all_data), min=-1,max=0.25) 48 | all_data$change_upper = pmin(all_data$change_mean + rbeta(nrow(all_data), 2,5),1) 49 | all_data$change_lower = pmax(all_data$change_mean - rbeta(nrow(all_data), 2,5),-1) 50 | 51 | # Simulate random significance tests 52 | all_data$sig_factor = sample(c('n.s.','*','**','***'), size=nrow(all_data), replace = TRUE) 53 | all_data$sample_size = round(runif(n=nrow(all_data), min=5,max=50),0) 54 | 55 | # Build a text label to show significance test results and sample size adjacent to each line. 56 | all_data = all_data %>% 57 | mutate(measurement_text = paste0(sig_factor,'(',sample_size,')')) 58 | 59 | # The small offset between the red/blue values can be done by making a small nudge value 60 | # based on the type. y_nudge is then used inside position_nudge in ggplot 61 | all_data = all_data %>% 62 | mutate(y_line_nudge = case_when( 63 | drought_type == 'seasonal' ~ 0.15, 64 | drought_type == 'eposodic' ~ -0.15), 65 | y_text_nudge = case_when( 66 | drought_type == 'seasonal' ~ 0.5, 67 | drought_type == 'eposodic' ~ -0.5)) 68 | 69 | # Setup a factor to put the measurement level in the top->down order we want in the facet_wrap 70 | # Also use it to assign a nice label with A-B labelling 71 | measurement_levels = c('leaf','tree','ecosystem') 72 | measurement_level_labels = c('(a) Leaf','(b) Tree','(c) Ecosystem') 73 | all_data$measurement_level = factor(all_data$measurement_level, levels = measurement_levels, labels = measurement_level_labels, ordered = TRUE) 74 | 75 | # Specify the ordering of the y-axis measurments. 76 | # This will be ordered in the order specified for each above. 77 | # Note ggplot will start the first entry at 0 and go up. Thus this uses fct_rev() in the first ggplot line to reverse that. 78 | all_data$measurement = factor(all_data$measurement, levels = c(leaf_measurements, tree_measurements, ecosystem_measurements), ordered = TRUE) 79 | 80 | final_figure = ggplot(all_data, aes(y=fct_rev(measurement), x=change_mean, color=drought_type)) + 81 | geom_vline(xintercept = 0) + 82 | geom_point(position = position_dodge(width = 0.5), size=2) + 83 | geom_errorbarh(aes(xmin=change_lower, xmax=change_upper), position = position_dodge(width=0.5), 84 | height=0, size=1) + 85 | geom_text(aes(label = measurement_text), position = position_dodge(width = 2), # Note the dodge with for the text is higher to push them above/below the lines 86 | size=3.5) + 87 | scale_color_manual(values=c( "#0072B2", "#D55E00")) + 88 | scale_x_continuous(breaks=c(-1,-0.5,0,0.5), labels = function(x){round(x*100,0)}) + ## Use a function in the x_axis labels to convert the percentage to a whole number 89 | facet_wrap(~measurement_level, ncol=1, scales='free_y') + ## The scales='free_y' argument here makes it so each plot only has 90 | theme_bw(15) + # the respective measurements for each level, and not empty values for others. 91 | theme(legend.position = 'none', # remove it to see what happens 92 | strip.background = element_blank(), 93 | strip.text = element_text(hjust=0, size=18, face = 'bold')) + 94 | labs(x='',y='') 95 | 96 | ############################################# 97 | 98 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 99 | 100 | final_figure = ggdraw(final_figure) + 101 | geom_rect(data=data.frame(xmin=0.05,ymin=0.05), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.5,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 102 | draw_text(water_mark, x=0.05, y=0.1, size=10, hjust = 0) 103 | 104 | save_plot('./janssen2020/janssen2020-fig3_final.png',plot = final_figure, base_height = 10, base_width = 10) 105 | -------------------------------------------------------------------------------- /janssen2020/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Janssen, T., Fleischer, K., Luyssaert, S., Naudts, K. and Dolman, H., 2020. Drought resistance increases from the individual to the ecosystem level in highly diverse neotropical rain forest: a meta-analysis of leaf, tree and ecosystem responses to drought. Biogeosciences. https://doi.org/10.5194/bg-17-2621-2020 3 | 4 | ## Figure 3 Notes 5 | Great example of a multi-plot figure with a shared x-axis, and where each subplot has a unique and categorical y-axis. 6 | 7 | ## Reproduced Figure 3 8 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/janssen2020/janssen2020-fig3_final.png) 9 | -------------------------------------------------------------------------------- /klingbeil2021/animals/PhyloPic.3baf6f47.Ferran-Sayol.Mesitornis_Mesitornis-unicolor_Mesitornithidae.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/klingbeil2021/animals/PhyloPic.3baf6f47.Ferran-Sayol.Mesitornis_Mesitornis-unicolor_Mesitornithidae.png -------------------------------------------------------------------------------- /klingbeil2021/animals/PhyloPic.822f98c6.Mason-McNair.Paspalum_Paspalum-vaginatum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/klingbeil2021/animals/PhyloPic.822f98c6.Mason-McNair.Paspalum_Paspalum-vaginatum.png -------------------------------------------------------------------------------- /klingbeil2021/animals/PhyloPic.ad11bfb7.Ferran-Sayol.Threskiornis_Threskiornis-aethiopicus_Threskiornithidae_Threskiornithinae.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/klingbeil2021/animals/PhyloPic.ad11bfb7.Ferran-Sayol.Threskiornis_Threskiornis-aethiopicus_Threskiornithidae_Threskiornithinae.png -------------------------------------------------------------------------------- /klingbeil2021/animals/PhyloPic.ca1082e0.George-Edward-Lodge-modified-by-T-Michael-Keesey.Aves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/klingbeil2021/animals/PhyloPic.ca1082e0.George-Edward-Lodge-modified-by-T-Michael-Keesey.Aves.png -------------------------------------------------------------------------------- /klingbeil2021/klingbeil2021.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(cowplot) 3 | library(png) 4 | 5 | 6 | #------------------------------- 7 | # This is a replication of Figure 2 from the following paper 8 | # Klingbeil, B.T., Cohen, J.B., Correll, M.D. et al. High uncertainty over the future of 9 | # tidal marsh birds under current sea-level rise projections. Biodivers Conserv (2021). 10 | # https://doi.org/10.1007/s10531-020-02098-z 11 | 12 | # It's meant for educational purposes only. 13 | #------------------------------- 14 | 15 | # Data estimated from the original plot. 16 | 17 | figure_data = tribble( 18 | ~bird_type, ~model_type, ~marsh_area_percent, 19 | 'AREA', 'dynamic', 0.12, 20 | 'AREA', 'static', 0.10, 21 | 'CLRA', 'dynamic', 0.10, 22 | 'CLRA', 'static', 0.05, 23 | 'NESP', 'dynamic', 0.20, 24 | 'NESP', 'static', 0.25, 25 | 'SALS', 'dynamic', 0.18, 26 | 'SALS', 'static', 0.10, 27 | 'SESP', 'dynamic', 0.10, 28 | 'SESP', 'static', 0.05, 29 | 'WILL', 'dynamic', 0.11, 30 | 'WILL', 'static', 0.07 31 | ) 32 | 33 | 34 | # Make the bird_type a factor with ordering as the order seen in the data.frame. This determines the x-axis order. 35 | # This isn't strictly necccesary, as the order seen in the original plot is alphabetical, 36 | # and ggplot will order it that way by default. 37 | figure_data$bird_type = forcats::fct_inorder(figure_data$bird_type) 38 | 39 | #------------------------------- 40 | # load images 41 | #------------------------------- 42 | bird1 = png::readPNG('klingbeil2021/animals/PhyloPic.3baf6f47.Ferran-Sayol.Mesitornis_Mesitornis-unicolor_Mesitornithidae.png') 43 | bird2 = png::readPNG('klingbeil2021/animals/PhyloPic.ad11bfb7.Ferran-Sayol.Threskiornis_Threskiornis-aethiopicus_Threskiornithidae_Threskiornithinae.png') 44 | bird3 = png::readPNG('klingbeil2021/animals/PhyloPic.ca1082e0.George-Edward-Lodge-modified-by-T-Michael-Keesey.Aves.png') 45 | grass = png::readPNG('klingbeil2021/animals/PhyloPic.822f98c6.Mason-McNair.Paspalum_Paspalum-vaginatum.png') 46 | 47 | #------------------------------- 48 | 49 | main_figure = ggplot(figure_data, aes(x=bird_type, y=marsh_area_percent, fill=model_type)) + 50 | geom_col(aes(y=1), position = position_dodge(width = 0.9),width=0.8, alpha=0.2) + # This first geom_col is for the faint colored bars. 51 | geom_col(position = position_dodge(width=0.9), width = 0.8) + # This geom_col draws the primary colored bars 52 | # Both sets of bars have the same fill color, but the larger ones are made fainter with alpha. 53 | # The width inside position_dodge() controls distance between grey/green bars. 54 | # Width for geom_col controls distance between paired bars (ie. between the bird types) 55 | scale_fill_manual(values=c('darkgreen','grey20')) + 56 | scale_y_continuous(breaks=c(0,0.25,0.5,0.75,1.0), labels = function(x){paste0(ceiling(x*100),'%')}) + # y axis is 0-1 but the label function converts to % 57 | theme_bw() + 58 | theme(legend.position = 'none', 59 | panel.grid = element_blank(), 60 | panel.border = element_blank(), # Turn off the full border but initialize the bottom and left lines 61 | axis.line.x.bottom = element_line(size=0.5), 62 | axis.line.y.left = element_line(size=0.5), 63 | axis.title = element_text(size=15, face = 'bold'), 64 | axis.text = element_text(color='black', size=10, face='bold'), 65 | axis.ticks.x = element_blank()) + 66 | labs(y='Percent of 2010 Estimate', x='SLR Response') 67 | 68 | #--------------------------- 69 | # Add the silhouettes. The position and size of these takes a few minutes of 70 | # trial and error. 71 | 72 | figure_with_animals = ggdraw() + 73 | draw_plot(main_figure, height=0.9) + 74 | draw_image(grass, x=0.11, y=0.87, width=0.1, height=0.1) + # AREA 75 | draw_image(bird1, x=0.28, y=0.86, width=0.1, height=0.1) + # CLRA 76 | draw_image(bird2, x=0.40, y=0.86, width=0.1, height=0.1) + # NESP 77 | draw_image(bird3, x=0.56, y=0.86, width=0.1, height=0.1) + # SALS 78 | draw_image(bird1, x=0.70, y=0.86, width=0.1, height=0.1) + # SESP 79 | draw_image(bird2, x=0.85, y=0.86, width=0.1, height=0.1) # WILL 80 | 81 | #--------------------------- 82 | # Add the watermark 83 | 84 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 85 | 86 | final_figure = ggdraw(figure_with_animals) + 87 | geom_rect(data=data.frame(xmin=0.05,ymin=0.01), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.6,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 88 | draw_text(water_mark, x=0.05, y=0.05, size=10, hjust = 0) 89 | 90 | save_plot('./klingbeil2021/klingbeil2021_final.png', plot=final_figure, base_height = 6, base_width = 10) 91 | -------------------------------------------------------------------------------- /klingbeil2021/klingbeil2021_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/klingbeil2021/klingbeil2021_final.png -------------------------------------------------------------------------------- /klingbeil2021/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Klingbeil, B.T., Cohen, J.B., Correll, M.D. et al. High uncertainty over the future of tidal marsh birds under current sea-level rise projections. Biodivers Conserv (2021). https://doi.org/10.1007/s10531-020-02098-z 3 | 4 | Figure 2 5 | 6 | ## Notes 7 | 8 | This is a clever use of transparent columns to fill in the complete 100% of area for the bar plots and provide a nice guide for the eye. 9 | 10 | Note how the data is organized. Every unique bar value has a line of data which is labeled with the bird, and model type (green/grey). Birds are the x-axis values while the model type (green/grey) is separated with `position_dodge()`. 11 | 12 | Silhouettes are from http://phylopic.org/ 13 | bird 1: http://phylopic.org/image/ca1082e0-718c-48dc-a011-995511e48180/ 14 | bird 2: http://phylopic.org/image/3baf6f47-7496-45cc-b21f-3f0bee99d65b/ 15 | bird 3: http://phylopic.org/image/ad11bfb7-4ab8-47d2-8e25-0e11ab947e20/ 16 | grass: http://phylopic.org/image/822f98c6-4272-43ba-9414-a71345547455/ 17 | 18 | ## Reproduced Figure 19 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/klingbeil2021/klingbeil2021_final.png) 20 | -------------------------------------------------------------------------------- /macdougall2020/macdougall2020.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(cowplot) 3 | 4 | ################################## 5 | # This is a replication of Figure 7 from the following paper 6 | # MacDougall, Andrew H., et al. "Is there warming in the pipeline? A multi-model analysis of the zero emission commitment from CO2." 7 | # Biogeosciences Discussions (2020): 1-45. https://doi.org/10.5194/bg-17-2987-2020 8 | 9 | # Data used here are mostly simulated, and not meant to replicate the original figure exactly. 10 | 11 | ############################################# 12 | 13 | # Here I judged by eye the value of everything. Note there are newline characters (\n) in the model 14 | # names for the ones to wrap to 2 lines. Alternatilly you could use stringr::str_wrap(), but that would 15 | # not give as much control in exactly how they look. 16 | flux_data = tribble( 17 | ~model, ~delta_n, ~f_ocean, ~f_land, ~zec, 18 | 'PLASIM-\nGENIE', 0.9, -0.7, -0.2, -0.38, 19 | 'GFDL\nESM2M', 0.6, -0.4, -0.2, -0.23, 20 | 'MPI-ESMI1.2', 0.6, -0.4, -0.4, -0.3, 21 | 'CanESM5', 0.85, -0.25, -0.5, -0.15, 22 | 'MIROC-\nlite', 0.5, -0.5, -0.25, -0.05, 23 | 'MIROC-\nES2L', 0.65, -0.4, -0.3, -0.05, 24 | 'LOVE\nCLIM 1.2', 1.2, -0.35, -0.5, -0.04, 25 | 'UVic\nESCM 2.10',0.6, -0.55, -0.1, -0.04, 26 | 'Bern3D\n-LPX', 0.65, -0.4, -0.3, 0.02, 27 | 'ACCESS-\nESM1.5', 0.55, -0.35, -0.1, 0.02, 28 | 'MESM', 0.62, -0.75, -0.22, 0.03, 29 | 'CNRM-ESM2', 0.88, -0.25, -0.5, 0.05, 30 | 'DCESS', 0.85, -0.4, -0.25, 0.05, 31 | 'UKESM1', 1.00, -0.43, -0.22, 0.25, 32 | 'IAPRAS', 1.4, -0.8, -0.1, 0.251 33 | ) 34 | 35 | # The left to right ordering of models is small->large, based on the ZEC value of the lower bar plot. 36 | # Thats specified using this function in the forecats library, where the factor order is matched to some numeric value. 37 | flux_data$model = fct_reorder(flux_data$model, flux_data$zec) 38 | 39 | # The 3 variables in the a figure (delta_n, f_ocean, and f_land) need to be in a tidy format 40 | # where they share a common column for the flux amount, and a new column is used to identify 41 | # the flux type. The zec variable is excluded here as its used in the b figure. 42 | flux_data_long = flux_data %>% 43 | select(-zec) %>% 44 | pivot_longer(cols=c('delta_n','f_ocean','f_land'), names_to = 'flux_var', values_to = 'flux_value') 45 | 46 | # Generate random values for the error bars 47 | set.seed(2) 48 | flux_data_long$flux_value_sd = runif(n=nrow(flux_data_long), 0.1, 0.3) 49 | flux_data_long$flux_value_sd = with(flux_data_long, ifelse(flux_var=='f_ocean',NA,flux_value_sd)) 50 | 51 | # The lower error bars for f_land are tricky here. The bars are specified with position_stack, but there is no 52 | # corresponding way to "stack" the error bars. See https://stackoverflow.com/a/30873811 53 | # The solution is to manually specify the error bars. So here the upper one is the sd around only the delta_n, 54 | # while the lower one is the sd around the sum(f_ocean,f_land). Each will get there own geom_errorbar call. 55 | upper_error_bars = flux_data_long %>% 56 | filter(flux_var == 'delta_n') %>% 57 | group_by(model) %>% 58 | summarise(ymax = flux_value + flux_value_sd, 59 | ymin = flux_value - flux_value_sd) %>% 60 | ungroup() 61 | 62 | lower_error_bars = flux_data_long %>% 63 | filter(flux_var %in% c('f_ocean','f_land')) %>% 64 | group_by(model) %>% 65 | summarise(ymax = sum(flux_value) + flux_value_sd, 66 | ymin = sum(flux_value) - flux_value_sd) %>% 67 | ungroup() 68 | 69 | 70 | # Specify the desired order of the 3 variables and assign the legend labels. 71 | # This affects the stacking and coloring order. I'm not sure exactly how this interacts with the negative values. 72 | # If you have have a similar problem I recommend adjusting the ordering here until you get a solution you like. 73 | # This can also potentially be adjusted by using reverse=TRUE insdie the position_stack() call for geom_col(). 74 | # Note also by default the legend will go, top to bottom, f_land, f_ocean, delta_n. Thats reversed in the guide() call, which 75 | # does not affect the stacking/color order. 76 | # Δ symbol was copy pasted from wikipedia. It can go straight into the label without any special calls. 77 | flux_data_long$flux_var = factor(flux_data_long$flux_var, levels = c('f_land','f_ocean','delta_n'), labels = c('F land','F ocean','-Δ N'), ordered = T) 78 | 79 | # The flux amount column is then used for the y-axis, while they are assigned a color fill with 80 | # the flux_var column. 81 | barplot_a = ggplot(flux_data_long,aes(x=model, y=flux_value, fill=flux_var)) + 82 | geom_col(width=0.5, position = position_stack()) + 83 | geom_errorbar(data = upper_error_bars, aes(x=model,ymin = ymin, ymax = ymax), width=0, inherit.aes = FALSE) + # Note these have to not inherit the aes, the error bar data.frames 84 | geom_errorbar(data = lower_error_bars, aes(x=model,ymin = ymin, ymax = ymax), width=0, inherit.aes = FALSE) + # are expected to have a flux_value and flux_var columns. 85 | geom_hline(yintercept = 0) + 86 | scale_fill_manual(values=c('darkgreen','dodgerblue2','deepskyblue4')) + 87 | scale_y_continuous(breaks=c(-1,-0.5,0,0.5,1.0,1.5), limits = c(-1.2,1.6)) + 88 | theme_bw(5) + 89 | theme(panel.grid = element_blank(), 90 | axis.text = element_text(color='black'), 91 | axis.text.y= element_text(size=6), 92 | legend.title = element_blank(), 93 | legend.text = element_text(size=6), 94 | legend.position = c(0.07,0.91)) + 95 | guides(fill=guide_legend(reverse = TRUE, keyheight = unit(2,'mm'))) + 96 | labs(x='', y=bquote('Flux'~(Wm^-2))) # The superscript in the axis label is done via bquote. Some tutorials also use expression() 97 | 98 | 99 | barplot_b = ggplot(flux_data, aes(x=model, y=zec)) + 100 | geom_col(width=0.5, fill='black') + 101 | geom_hline(yintercept = 0) + 102 | ylim(-0.4,0.4) + 103 | theme_bw(5) + 104 | theme(panel.grid = element_blank(), 105 | panel.border = element_blank(), # Turn off the full border but initialize the bottom and left lines 106 | axis.line.x.bottom = element_line(size=0.3), 107 | axis.line.y.left = element_line(size=0.3), 108 | axis.text = element_text(color='black'), 109 | axis.text.y= element_text(size=6), 110 | legend.title = element_blank()) + 111 | labs(x='', y='ZEC (°C)') 112 | 113 | final_figure = plot_grid(barplot_a, barplot_b, ncol=1, rel_heights = c(1,0.5), align = 'h', axis='lr', 114 | labels = c('(a)','(b)'), label_size=7, label_x = 0.96, label_y=0.99, label_fontface = 'plain') 115 | 116 | ############################################# 117 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 118 | 119 | final_figure = ggdraw(final_figure) + 120 | geom_rect(data=data.frame(xmin=0.1,ymin=0.25), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.5,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 121 | draw_text(water_mark, x=0.12, y=0.3, size=6, hjust = 0) 122 | 123 | save_plot('macdougall2020/macdougall2020_final.png', plot = final_figure) 124 | 125 | 126 | -------------------------------------------------------------------------------- /macdougall2020/macdougall2020_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/macdougall2020/macdougall2020_final.png -------------------------------------------------------------------------------- /macdougall2020/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | MacDougall, Andrew H., et al. "Is there warming in the pipeline? A multi-model analysis of the zero emission commitment from CO2." Biogeosciences Discussions (2020): 1-45. https://doi.org/10.5194/bg-17-2987-2020 3 | 4 | Figure 7 5 | 6 | ## Notes 7 | - positioning of the lower error bars needs to be done manually, since they can't be combined with the "stacking" of the bar plots. 8 | - getting the stacking order and color assignments correct is a bit finicky. 9 | - the x-axis order is specified not alphabetically, but by increasing ZEC value. 10 | 11 | ## Reproduced Figure 12 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/macdougall2020/macdougall2020_final.png) 13 | -------------------------------------------------------------------------------- /nerlekar2020/nerlekar2020.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(sf) 3 | library(rnaturalearth) 4 | library(patchwork) 5 | library(cowplot) # note cowplot is only used to attach the watermark at the end. 6 | 7 | 8 | #-------------- 9 | # This is a replication of Figure 1 from the following paper 10 | # Nerlekar, A.N. and Veldman, J.W., 2020. High plant diversity and slow assembly 11 | # of old-growth grasslands. Proceedings of the National Academy of Sciences, 117(31), pp.18550-18556. 12 | # https://doi.org/10.1073/pnas.1922266117 13 | 14 | # This figure uses simulated data and is not an exact copy. 15 | # It's meant for educational purposes only. 16 | #-------------- 17 | 18 | # The data is a csv with lat,lon, map (mean annual precip), mat (mean annual temp), and the continent. 19 | # This converts it to a spatial object so it can be mapped with the sf package. 20 | point_data = read_csv('nerlekar2020/point_data.csv') %>% 21 | st_as_sf(coords = c('lon','lat'), crs=4326) 22 | 23 | # Make labels for the legend which include the sample size. 24 | point_data = point_data %>% 25 | group_by(continent) %>% 26 | mutate(continent_label = paste0(continent, ' (n=',n(),')')) %>% 27 | ungroup() 28 | 29 | # Note this palette is not color-blind friendly. 30 | # africa, asia, aus, europe, n amer, s amer 31 | continent_colors = c('salmon','black','#d55e00','#e6ab02','#56b4e9','#7570b3') 32 | 33 | # To get the affect of only land outlines as simply as possible we'll combine 34 | # two free datasets. The country and coastlines from Natural Earth. https://www.naturalearthdata.com/ 35 | # vai the rnaturalearth package. 36 | # The coastline data is a *line* object, therefore it's impossible to give it a fill 37 | # color, for that we need a *polygon* object, so the country one will work. 38 | countries = ne_countries(scale=110, returnclass = 'sf') 39 | coastlines = ne_coastline(returnclass = 'sf') 40 | 41 | # The country polygon object will have the grey fill, but the lines of each country will be set to transparent. 42 | # the coastline line object will be black to create the desired affect. 43 | # Try commenting out to the 2 different lines or changing the fill/color values to see what happens. 44 | 45 | world_map_panel = ggplot() + 46 | geom_sf(data=countries, fill='grey90', color='transparent') + 47 | geom_sf(data=coastlines, color='black', size=0.25) + 48 | geom_sf(data=point_data, aes(color=continent_label),size=3) + 49 | scale_color_manual(values=continent_colors) + 50 | coord_sf(ylim = c(-50, 80)) + # Zoom in a bit to exclude antarctica 51 | theme(panel.background = element_blank(), 52 | panel.grid = element_blank(), 53 | axis.text = element_blank(), 54 | axis.title = element_blank(), 55 | legend.text = element_text(color='black', size=10), 56 | legend.key = element_blank(), # this element turns off the background of the points inside the legend. 57 | legend.title = element_blank(), 58 | legend.position = c(0.1,0.2)) 59 | 60 | 61 | mat_map_panel = ggplot(point_data, aes(x=map, y=mat, color=continent_label)) + 62 | geom_point(size=4) + 63 | scale_color_manual(values=continent_colors) + 64 | scale_x_continuous(breaks = seq(0,2500,500)) + 65 | scale_y_continuous(breaks = seq(0,25,5)) + 66 | theme(panel.grid = element_blank(), 67 | panel.background = element_blank(), 68 | panel.border = element_blank(), # Turn off the full border but initialize the bottom and left lines 69 | axis.line.x.bottom = element_line(size=0.5), 70 | axis.line.y.left = element_line(size=0.5), 71 | axis.text = element_text(size=12, face='bold', color='black'), 72 | axis.title = element_text(size=13, face='bold'), 73 | legend.position = 'none') + 74 | labs(x='Mean annual precipitation (mm)', y='Mean annual temperature (°C)') 75 | 76 | # Combining plots here is done using the patchwork library. 77 | # See https://patchwork.data-imaginist.com/articles/guides/layout.html 78 | final_figure = world_map_panel + mat_map_panel + 79 | plot_layout(nrow = 1, widths = c(3,1.5)) + 80 | plot_annotation(tag_levels = 'A') & # This & is special for the patchwork library and applies the theme to all plots. 81 | theme(plot.tag = element_text(size = 16, face = 'bold')) # See https://patchwork.data-imaginist.com/articles/guides/annotation.html for detail 82 | 83 | 84 | #-------------- 85 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 86 | 87 | final_figure = ggdraw(final_figure) + 88 | geom_rect(data=data.frame(xmin=0.05,ymin=0.05), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.6,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 89 | draw_text(water_mark, x=0.05, y=0.1, size=10, hjust = 0) 90 | 91 | save_plot('./nerlekar2020/nerlekar2020_final.png', plot=final_figure, base_height = 4, base_width = 12) 92 | -------------------------------------------------------------------------------- /nerlekar2020/nerlekar2020_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/nerlekar2020/nerlekar2020_final.png -------------------------------------------------------------------------------- /nerlekar2020/point_data.csv: -------------------------------------------------------------------------------- 1 | continent,mat,map,lon,lat 2 | North America,23,1200,-105.1171875,39.639537564366684 3 | North America,22,1250,-87.5390625,39.232253141714885 4 | North America,21,1550,-84.287109375,31.653381399664 5 | North America,15,2000,-82.6171875,33.063924198120645 6 | Europe,15,500,17.75390625,60.23981116999893 7 | Europe,15.3,450,9.580078125,52.3755991766591 8 | Europe,15.5,550,4.921875,45.82879925192134 9 | Europe,13,800,26.19140625,49.66762782262194 10 | Europe,10,500,31.025390625,47.45780853075031 11 | Europe,10.5,550,23.37890625,46.255846818480315 12 | Europe,6.5,530,32.16796875,50.84757295365389 13 | Europe,8,750,18.45703125,53.54030739150022 14 | Europe,8.5,800,15.8203125,44.96479793033101 15 | Europe,10,750,11.42578125,47.15984001304432 16 | Europe,8.5,900,10.810546875,45.336701909968134 17 | Europe,8.7,930,36.9140625,49.439556958940855 18 | Europe,6.5,800,13.359375,53.592504809039376 19 | Asia,3,250,104.4140625,36.87962060502676 20 | Asia,2.5,550,75.5859375,50.51342652633956 21 | Asia,2.4,500,69.2578125,56.9449741808516 22 | Australia,15.5,450,146.865234375,-21.289374355860424 23 | Australia,23,530,145.1953125,-36.738884124394296 24 | Africa,22,1010,29.46533203125,-26.64745870265937 25 | Africa,12.5,1050,29.94873046875,-28.362401735238222 26 | South America,21,1200,-46.05468749999999,-22.187404991398775 27 | South America,20.5,1250,-49.833984375,-23.96617587126503 28 | South America,20,1600,-50.625,-23.644524198573677 29 | South America,15,2100,-52.91015625,-30.751277776257812 30 | -------------------------------------------------------------------------------- /nerlekar2020/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Nerlekar, A.N. and Veldman, J.W., 2020. High plant diversity and slow assembly of old-growth grasslands. Proceedings of the National Academy of Sciences, 117(31), pp.18550-18556. https://doi.org/10.1073/pnas.1922266117 3 | 4 | Figure 1 5 | 6 | ## Notes 7 | Great world map example with attached data plot and matching points. 8 | 9 | ## Reproduced Figure 10 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/nerlekar2020/nerlekar2020_final.png) 11 | -------------------------------------------------------------------------------- /ober2020/ober2020.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(cowplot) # cowplot is used here just for the disclaimer watermark at the end. Its not needed for the primary figure. 3 | 4 | ################################## 5 | # This is a replication of Figure 2 from the following paper 6 | # Ober, H.K., Jones, G.M., Gottlieb, I.G., Johnson, S.A., Smith, L., Brosi, B. and Fletcher, R.J., Jr. (2020), 7 | # Bat community response to intensification of biomass production 8 | # for bioenergy across the southeastern USA. Ecol Appl 9 | # https://doi.org/10.1002/eap.2155 10 | 11 | # Data used here are simulated, and not meant to replicate the original figure exactly. 12 | ################################## 13 | bats = c('Big brown*','Eastern red*','Hoary','Southeastern myotis', 14 | 'Little brown','Evening','Tri-colored','Brazillian free-tailed') 15 | 16 | habitats = c('Corn','Residue removed','Residue left','Unthinned','Thinned','Young','Mature','Reference') 17 | 18 | all_data = expand_grid(bat = bats, habitat = habitats) 19 | 20 | # Simulate some data to get the mean, and upper/lower 95 confidence intervals in the 0-1 bounds 21 | all_data$occurance_prob_mean = runif(nrow(all_data), min=0.01,max=0.95) 22 | all_data$occurance_prob_upper = pmin(all_data$occurance_prob_mean + rbeta(nrow(all_data), 2,5),1) 23 | all_data$occurance_prob_lower = pmax(all_data$occurance_prob_mean - rbeta(nrow(all_data), 2,5),0) 24 | 25 | ########################################## 26 | # Create factors in the data data.frame with the explicit order of the vectors specified above. 27 | # This affects the ordering of the facets (bats) and y-axis (habitat) of the plot. 28 | all_data$bat = factor(all_data$bat, levels = bats, ordered = TRUE) 29 | all_data$habitat = factor(all_data$habitat, levels = habitats, ordered = TRUE) 30 | 31 | ########################################## 32 | # These lines are all that is needed for the basic figure outline. 33 | # Everything else is just theme elements. 34 | primary_figure = ggplot(all_data, aes(y=fct_rev(habitat), x=occurance_prob_mean)) + 35 | geom_errorbarh(aes(xmax = occurance_prob_upper, xmin = occurance_prob_lower),size=1, height=0) + # Height here turns off vertical ends on error bars 36 | geom_point(size=4, color='black') + # You could used the shape=1 argument here to get a circle instead of a solid point. But the error bar 37 | geom_point(size=2, color='white') + # would be visible through it. Instead draw a small white point on top of a slightly larger black point. 38 | scale_x_continuous(breaks = c(0.2,0.5,0.8)) + 39 | facet_wrap(~bat, nrow=2) + 40 | labs(x='Occurrence probability (95% CRI)', y='') 41 | 42 | 43 | final_figure = primary_figure + 44 | theme_bw(10) + 45 | theme(strip.background = element_blank(), # Turn off strip background so the bat names "float" 46 | panel.grid = element_blank(), # turn off x/y grid lines 47 | panel.background = element_rect(size=2, color='black'), # make the border on each subplot bigger 48 | axis.ticks = element_line(size = 1), 49 | axis.text = element_text(size=10, face = 'bold', color='black'), 50 | axis.title = element_text(size=10, face = 'bold', color='black'), 51 | strip.text = element_text(size=10, face = 'bold', color='black')) 52 | 53 | ############################################# 54 | 55 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 56 | 57 | final_figure = ggdraw(final_figure) + 58 | geom_rect(data=data.frame(xmin=0.05,ymin=0.05), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.5,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 59 | draw_text(water_mark, x=0.05, y=0.1, size=10, hjust = 0) 60 | 61 | save_plot('./ober2020/ober2020_final.png',plot = final_figure, base_height = 6.5, base_width = 10) 62 | -------------------------------------------------------------------------------- /ober2020/ober2020_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/ober2020/ober2020_final.png -------------------------------------------------------------------------------- /ober2020/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Ober, H.K., Jones, G.M., Gottlieb, I.G., Johnson, S.A., Smith, L., Brosi, B. and Fletcher, R.J., Jr. (2020), Bat community response to intensification of biomass production for bioenergy across the southeastern USA. Ecolical Applications. https://doi.org/10.1002/eap.2155 3 | 4 | Figure 2 5 | 6 | ## Notes 7 | An extremely straightforward figure. Simpler is sometimes better. 8 | 9 | ## Reproduced Figure 10 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/ober2020/ober2020_final.png) 11 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ggplot scientific figure gallery 2 | 3 | This repo hosts R code for creating non-trivial figures inspired from actual scientific articles. 4 | Everything is done in R using the packages `ggplot2`,`cowplot`, and sometimes `patchwork`. When the original data is provided in a supplement it's used here, but otherwise data is simulated to produce a similar looking figure. The point is not to reproduce a figure exactly pixel by pixel, but give an example on how to structure and build them from scratch. 5 | 6 | # Gallery 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /reyes2019/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Reyes, J. J., & Elias, E. (2019). Spatio-temporal variation of crop loss in the United States from 2001 to 2016. Environmental Research Letters, 14(7), 074017. https://doi.org/10.1088/1748-9326/ab1ac9 3 | 4 | Figure 5 5 | 6 | ## Notes 7 | A nice way of showing variation across many categories. 8 | Note this is done using `geom_point()`, the X/O significance are different shapes. 9 | 10 | ## Reproduced Figure 11 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/reyes2019/reyes2019_final.png) 12 | -------------------------------------------------------------------------------- /reyes2019/reyes2019.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(cowplot) # cowplot is used here just for the disclaimer watermark at the end. Its not needed for the primary figure. 3 | 4 | 5 | cause_of_loss = c('Wind/Excess Wind','Hot Wind','Heat','Hail','Freeze','Flood','Failure Irrig Supply', 6 | 'Excess Moisture/Precip/Rain','Drought','Cold Wet Weather') 7 | time_periods = c('Annual','JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC') 8 | 9 | all_data = expand_grid(cause = cause_of_loss, time = time_periods) 10 | 11 | # Order them in the order specified above. Note for the cause loss the order will be reversed 12 | # in ggplot call because I made the order top->bottom, but ggplot orders it bottom->top (ie. starting at 0 on the y-axis) 13 | all_data$cause = factor(all_data$cause, levels = cause_of_loss, ordered = TRUE) 14 | all_data$time = factor(all_data$time, levels = time_periods, ordered = TRUE) 15 | 16 | set.seed(1) 17 | # Make some random significance values 18 | all_data$tau = rnorm(n=nrow(all_data), mean=0, sd=0.3) 19 | all_data$sig = ifelse(abs(all_data$tau) > 0.6 , 'yes','no') 20 | 21 | 22 | final_figure = ggplot(all_data, aes(x=time, y=fct_rev(cause), color=tau, shape=sig)) + 23 | geom_point(size=14, stroke=5) + 24 | scale_color_distiller(palette = 'RdBu', limits=c(-1,1)) + 25 | scale_shape_manual(values=c(1,4)) + 26 | theme_bw(28) + 27 | theme(panel.grid = element_blank(), 28 | legend.position = 'bottom', 29 | axis.title = element_blank(), 30 | legend.direction = 'horizontal') + 31 | guides(color=guide_colorbar(title.position = 'top', title = 'tau', barwidth = unit(8,'cm'), barheight = unit(1.5,'cm')), 32 | shape=guide_legend(title.position = 'top', title = 'Significance', override.aes = list(size=8,stroke=3))) 33 | 34 | ############################################# 35 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 36 | 37 | final_figure = ggdraw(final_figure) + 38 | geom_rect(data=data.frame(xmin=0.05,ymin=0.15), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.5,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 39 | draw_text(water_mark, x=0.05, y=0.2, size=15, hjust = 0) 40 | 41 | ggsave(plot = final_figure, filename = 'reyes2019/reyes2019_final.png', width=45, height = 25, unit='cm', dpi=50) 42 | -------------------------------------------------------------------------------- /reyes2019/reyes2019_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/reyes2019/reyes2019_final.png -------------------------------------------------------------------------------- /suggitt2019/data/Koppen_Geiger Edited and Completed/Read me.txt: -------------------------------------------------------------------------------- 1 | The climate Shapefile is based on the Koppen-Geiger 2 | 3 | Vectorised by by Geoafrikana.com 4 | 5 | based on raster file from 6 | Kottek, M., J. Grieser, C. Beck, B. Rudolf, and F. Rubel, 2006: World Map of the K?ppen-Geiger climate classification updated. Meteorol. Z., 15, 259-263. DOI: 10.1127/0941-2948/2006/0130. 7 | -------------------------------------------------------------------------------- /suggitt2019/data/Koppen_Geiger Edited and Completed/Shapefiles/world_climates_completed_koppen_geiger.cpg: -------------------------------------------------------------------------------- 1 | UTF-8 -------------------------------------------------------------------------------- /suggitt2019/data/Koppen_Geiger Edited and Completed/Shapefiles/world_climates_completed_koppen_geiger.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/suggitt2019/data/Koppen_Geiger Edited and Completed/Shapefiles/world_climates_completed_koppen_geiger.dbf -------------------------------------------------------------------------------- /suggitt2019/data/Koppen_Geiger Edited and Completed/Shapefiles/world_climates_completed_koppen_geiger.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /suggitt2019/data/Koppen_Geiger Edited and Completed/Shapefiles/world_climates_completed_koppen_geiger.qpj: -------------------------------------------------------------------------------- 1 | GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] 2 | -------------------------------------------------------------------------------- /suggitt2019/data/Koppen_Geiger Edited and Completed/Shapefiles/world_climates_completed_koppen_geiger.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/suggitt2019/data/Koppen_Geiger Edited and Completed/Shapefiles/world_climates_completed_koppen_geiger.shp -------------------------------------------------------------------------------- /suggitt2019/data/Koppen_Geiger Edited and Completed/Shapefiles/world_climates_completed_koppen_geiger.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/suggitt2019/data/Koppen_Geiger Edited and Completed/Shapefiles/world_climates_completed_koppen_geiger.shx -------------------------------------------------------------------------------- /suggitt2019/data/Koppen_Geiger Edited and Completed/Style/world_climate.qml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | dn 797 | 798 | 799 | 800 | 801 | 802 | 0 803 | 0 804 | 1 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 826 | 827 | 828 | 829 | 830 | 831 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 863 | 864 | 865 | 866 | 0 867 | 868 | 885 | 0 886 | generatedlayout 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | dn 902 | 903 | 2 904 | 905 | -------------------------------------------------------------------------------- /suggitt2019/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Suggitt, A. J., Lister, D. G., & Thomas, C. D. (2019). Widespread effects ofclimate change on local plant diversity. Current Biology, 29(17), 2905-2911. https://doi.org/10.1016/j.cub.2019.06.079 3 | 4 | Figure 1 5 | 6 | ## Notes 7 | 8 | Each of the 5 subplots in this figure are relatively easy to make on their own. The hardest part here was getting them to align correctly using several calls to `cowplow::plot_grid()`. It took many iterations making small adjustments across all the different `rel_width`,`rel_height`,and `scale` arguments to get it just right. 9 | 10 | ## Reproduced Figure 11 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/suggitt2019/suggitt2019_final.png) 12 | -------------------------------------------------------------------------------- /suggitt2019/suggitt2019.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(sf) 3 | 4 | ################################## 5 | # This is a replication of Figure 1 from the following paper 6 | # Suggitt, A. J., Lister, D. G., & Thomas, C. D. (2019). Widespread effects of 7 | # climate change on local plant diversity. Current Biology, 29(17), 2905-2911. 8 | # https://doi.org/10.1016/j.cub.2019.06.079 9 | 10 | # Data used here are roughly approximated from the original figure, and 11 | # not meant to replicate it exactly. 12 | ################################## 13 | 14 | ############################################################### 15 | ############################################################### 16 | # Plot A, the map. 17 | 18 | # Kopen polygons from http://koeppen-geiger.vu-wien.ac.at/present.htm 19 | kopen_climate_regions = sf::read_sf('./suggitt2019/data/Koppen_Geiger Edited and Completed/Shapefiles/world_climates_completed_koppen_geiger.shp') 20 | 21 | # Match up the kopen classifications with the regions defined in the paper. 22 | suggitt_regions = tribble( 23 | ~kopen_group, ~region, ~fill_color, 24 | 'B', 'Arid', 'yellow', 25 | 'A', 'Equatorial', 'green4', 26 | 'C', 'Warm temperate', 'purple4', 27 | 'D', 'Cold', 'grey80', 28 | 'E', 'Polar', 'cyan2' 29 | ) 30 | kopen_climate_regions = kopen_climate_regions %>% 31 | mutate(kopen_group = stringr::str_sub(climates_f,1,1)) %>% 32 | left_join(suggitt_regions, by='kopen_group') 33 | 34 | # simplify polygon lines for quicker rendering 35 | kopen_climate_regions = kopen_climate_regions %>% 36 | st_simplify(dTolerance = 0.25) 37 | 38 | # Make 1 polygon entry per region. 39 | # See https://stackoverflow.com/questions/57625471/create-new-geometry-on-grouped-column-in-r-sf 40 | kopen_climate_regions = kopen_climate_regions %>% 41 | group_by(region) %>% 42 | summarise() %>% 43 | ungroup() %>% 44 | filter(!is.na(region)) 45 | 46 | # Convert the region column to a factor with the order of the suggitt_regions data.frame 47 | # This allows the colors to line up with the scale_fill_manual line below. 48 | # Note this also defines the ordering of the map legend. 49 | kopen_climate_regions$region = factor(kopen_climate_regions$region, levels = suggitt_regions$region, ordered = T) 50 | 51 | # These are the sites from Vellend et al. 2016 supp. info 52 | # https://esajournals.onlinelibrary.wiley.com/doi/abs/10.1002/ecy.1660 53 | map_points = read_csv('suggitt2019/data/Vellend_data_updated.csv') %>% 54 | st_as_sf(coords = c('Longitude','Latitude'), crs=4326) 55 | 56 | plot_A_map = ggplot() + 57 | geom_sf(data=kopen_climate_regions, aes(fill=region), color='transparent') + 58 | scale_fill_manual(values=suggitt_regions$fill_color) + 59 | geom_sf(data=map_points, shape=1,stroke=1, size=4) + 60 | theme_minimal() + 61 | theme(legend.position = c(0.15,0.35), 62 | legend.background = element_rect(color='black'), 63 | legend.title = element_blank(), 64 | legend.text = element_text(size=8), 65 | legend.key.size = unit(5,'mm'), 66 | panel.grid = element_blank()) 67 | 68 | 69 | ############################################################### 70 | ############################################################### 71 | # Plots B and C, Pie Charts 72 | 73 | pie_chart_data = tribble( 74 | ~region, ~fill_color, ~pie_B, ~pie_C, 75 | 'Arid', 'yellow', 5, 24, 76 | 'Equatorial', 'green4', 10, 15, 77 | 'Warm temperate', 'purple4', 45, 15, 78 | 'Cold', 'grey80', 30, 30, 79 | 'Polar', 'cyan2', 20,22 80 | ) 81 | 82 | # Matching the ordering is tricky here. The ggplot fill argument wants a factor, so by 83 | # default it turn the region column into a factor ordered alphabetially. 84 | # We must make a factor first using the order specified in the tribble above. 85 | 86 | pie_chart_data$region = fct_inorder(pie_chart_data$region) 87 | 88 | pie_chart_B = ggplot(pie_chart_data, aes(x="", y=pie_B, fill=region)) + 89 | geom_bar(stat='identity', width=1, color='black') + 90 | scale_fill_manual(values=pie_chart_data$fill_color) + 91 | coord_polar('y',start=0, direction=-1) + 92 | theme_bw() + 93 | theme(panel.grid = element_blank(), 94 | panel.border = element_blank(), 95 | axis.title = element_blank(), 96 | axis.ticks = element_blank(), 97 | axis.text = element_blank(), 98 | legend.position = 'none', 99 | legend.background = element_rect(color='black'), 100 | legend.title = element_blank()) 101 | 102 | pie_chart_C = ggplot(pie_chart_data, aes(x="", y=pie_C, fill=region)) + 103 | geom_bar(stat='identity', width=1, color='black') + 104 | scale_fill_manual(values=pie_chart_data$fill_color) + 105 | coord_polar('y',start=0, direction=-1) + 106 | theme_bw() + 107 | theme(panel.grid = element_blank(), 108 | panel.border = element_blank(), 109 | axis.title = element_blank(), 110 | axis.ticks = element_blank(), 111 | axis.text = element_blank(), 112 | legend.position = 'none') 113 | 114 | ################################################## 115 | ################################################## 116 | # Plot D temp/precip change plot 117 | 118 | plot_D_data = tribble( 119 | ~region, ~fill_color, ~precip_mean, ~precip_sd, ~temp_mean, ~temp_sd, 120 | 'Arid', 'yellow', 0, 0.2, 0.102, 0.02, 121 | 'Equatorial', 'green4', 0.2, 0.6, 0.04, 0.02, 122 | 'Warm temperate', 'purple4',0.18, 0.55, 0.075, 0.02, 123 | 'Cold', 'grey80', 0.3, 0.25, 0.14, 0.02, 124 | 'Polar', 'cyan2', 0.06, 0.2, 0.109, 0.04 125 | ) 126 | 127 | # There is no good way to place axis text in the middle of the plot, instead of on the left margin. 128 | # so this places it manually with geom_text 129 | y_axis_text = data.frame(y=seq(0, 0.18, 0.02),x=-0.1) 130 | 131 | plot_D_data$region = fct_inorder(plot_D_data$region) 132 | 133 | # note that ordering of the diferent geoms matter here. 134 | # The points should be last so they overlay the error bars, and x/y axis lines. 135 | 136 | plot_D = ggplot(plot_D_data, aes(x=precip_mean,y=temp_mean)) + 137 | geom_hline(yintercept = 0, size=2) + 138 | geom_vline(xintercept = 0, size=2) + 139 | geom_errorbar(aes(ymin = temp_mean - temp_sd, ymax=temp_mean+temp_sd), width=0) + # height/width of 0 here gets rid of 140 | geom_errorbarh(aes(xmin=precip_mean-precip_sd, xmax=precip_mean+precip_sd), height=0) + # the error bar ends 141 | geom_point(aes(color=region), size=5) + 142 | geom_point(shape=1, stroke=1, size=5, color='black') + 143 | scale_color_manual(values=plot_D_data$fill_color) + 144 | scale_x_continuous(breaks=round(seq(-0.6,1,0.2),1), limits=c(-0.6,1)) + 145 | geom_text(data=y_axis_text, aes(x=x,y=y,label=y)) + 146 | labs(y='Temperature change (+ °C per decade)', x='Precipitation change (± mm per decade', 147 | color='') + 148 | theme_bw() + 149 | theme(axis.text.y = element_blank(), 150 | axis.text.x = element_text(size=12), 151 | panel.grid = element_blank(), 152 | panel.border = element_blank(), 153 | axis.title = element_text(), 154 | axis.ticks = element_blank(), 155 | legend.position = 'bottom') 156 | 157 | ################################################## 158 | ################################################## 159 | # Plot E temp/precip change plot 160 | 161 | plot_E_data = tribble( 162 | ~region, ~fill_color, ~y0, ~y25, ~y50, ~y75, ~y100, 163 | 'Arid', 'yellow', -0.5, -0.35, -0.2, 0.05, 0.25, 164 | 'Equatorial', 'green4', -0.25, -0.05, 0.01, 0.02, 0.25, 165 | 'Warm temperate', 'purple4',-0.8,-0.3, 0, 0.25, 0.9, 166 | 'Cold', 'grey80', -0.5, -0.2, 0.1, 0.4, 1.0, 167 | 'Polar', 'cyan2', -0.4, -0.05, 0.2, 0.25, 0.5 168 | ) 169 | 170 | # Order the region to what is specifed in the tribble() 171 | # Note that its reveresed for the x axis ordering, but not for the fill order. 172 | plot_E_data$region = fct_inorder(plot_E_data$region) 173 | 174 | plot_E = ggplot(plot_E_data, aes(x=fct_rev(region), fill=region)) + 175 | geom_boxplot(aes(ymin = y0, lower = y25, middle = y50, upper = y75, ymax = y100), stat='identity', position = 'identity', 176 | width=0.8, color='black') + 177 | scale_fill_manual(values = plot_E_data$fill_color) + 178 | scale_y_continuous(breaks = c(-1,0,1), limits=c(-1,1), position='right') + 179 | geom_hline(yintercept = 0, linetype='dashed') + 180 | coord_flip() + # running just the above code makes vertical box plots. coord_flip makes them horizontal. 181 | theme_bw() + 182 | labs(x='',y='', title = 'Ln (SR2/SR1) per decade') + 183 | theme(panel.grid = element_blank(), 184 | plot.title = element_text(hjust=0.5), 185 | axis.text = element_text(size=12), 186 | legend.position = 'none') 187 | 188 | 189 | ################################################## 190 | ################################################## 191 | # put it all together! 192 | # Cowplot is the best option here for fine tuning the sizing and scales of the 5 subplots. 193 | library(cowplot) 194 | 195 | middle_legend = get_legend(pie_chart_B + theme(legend.position = c(0.5,0.4), 196 | legend.text = element_text(size=8), 197 | legend.key.size = unit(5,'mm'), 198 | legend.direction = 'vertical')) 199 | middle_row = plot_grid(pie_chart_B,middle_legend, pie_chart_C, nrow=1, rel_widths = c(1,0.5,1), rel_heights = c(1,0.5,1), labels = c('B','','C')) 200 | 201 | # Scale here is important to "shrink" the D plot a tad. 202 | bottom_row = plot_grid(plot_D, plot_E, labels = c('D','E'), 203 | rel_widths = c(1,0.8), scale = c(0.9,0.9)) 204 | 205 | top_row = plot_grid(plot_A_map, labels = c('A')) 206 | 207 | final_figure = plot_grid(top_row, middle_row, bottom_row, ncol=1, rel_widths = c(1,0.8,1), rel_heights = c(1.5,1,1.5)) 208 | 209 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 210 | 211 | final_figure = ggdraw(final_figure) + 212 | geom_rect(data=data.frame(xmin=0.05,ymin=0.05), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.4,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 213 | draw_text(water_mark, x=0.05, y=0.1, size=10, hjust = 0) 214 | 215 | save_plot('./suggitt2019/suggitt2019_final.png',plot = final_figure, base_height = 13, base_width = 10) 216 | -------------------------------------------------------------------------------- /suggitt2019/suggitt2019_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/suggitt2019/suggitt2019_final.png -------------------------------------------------------------------------------- /suggitt2019/suggitt2019_original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/suggitt2019/suggitt2019_original.jpg -------------------------------------------------------------------------------- /sullivan2020/map_points.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": { 7 | "continent": "S America" 8 | }, 9 | "geometry": { 10 | "type": "Point", 11 | "coordinates": [ 12 | -50.625, 13 | -4.390228926463384 14 | ] 15 | } 16 | }, 17 | { 18 | "type": "Feature", 19 | "properties": { 20 | "continent": "S America" 21 | }, 22 | "geometry": { 23 | "type": "Point", 24 | "coordinates": [ 25 | -65.91796875, 26 | -1.318243056862001 27 | ] 28 | } 29 | }, 30 | { 31 | "type": "Feature", 32 | "properties": { 33 | "continent": "S America" 34 | }, 35 | "geometry": { 36 | "type": "Point", 37 | "coordinates": [ 38 | -66.62109375, 39 | -7.623886853120036 40 | ] 41 | } 42 | }, 43 | { 44 | "type": "Feature", 45 | "properties": { 46 | "continent": "S America" 47 | }, 48 | "geometry": { 49 | "type": "Point", 50 | "coordinates": [ 51 | -60.64453125000001, 52 | 0.7909904981540058 53 | ] 54 | } 55 | }, 56 | { 57 | "type": "Feature", 58 | "properties": { 59 | "continent": "S America" 60 | }, 61 | "geometry": { 62 | "type": "Point", 63 | "coordinates": [ 64 | -60.029296875, 65 | -5.44102230371796 66 | ] 67 | } 68 | }, 69 | { 70 | "type": "Feature", 71 | "properties": { 72 | "continent": "S America" 73 | }, 74 | "geometry": { 75 | "type": "Point", 76 | "coordinates": [ 77 | -69.169921875, 78 | 2.28455066023697 79 | ] 80 | } 81 | }, 82 | { 83 | "type": "Feature", 84 | "properties": { 85 | "continent": "S America" 86 | }, 87 | "geometry": { 88 | "type": "Point", 89 | "coordinates": [ 90 | -60.64453125000001, 91 | -9.44906182688142 92 | ] 93 | } 94 | }, 95 | { 96 | "type": "Feature", 97 | "properties": { 98 | "continent": "S America" 99 | }, 100 | "geometry": { 101 | "type": "Point", 102 | "coordinates": [ 103 | -73.388671875, 104 | -1.1425024037061522 105 | ] 106 | } 107 | }, 108 | { 109 | "type": "Feature", 110 | "properties": { 111 | "continent": "S America" 112 | }, 113 | "geometry": { 114 | "type": "Point", 115 | "coordinates": [ 116 | -73.47656249999999, 117 | -6.577303118123875 118 | ] 119 | } 120 | }, 121 | { 122 | "type": "Feature", 123 | "properties": { 124 | "continent": "S America" 125 | }, 126 | "geometry": { 127 | "type": "Point", 128 | "coordinates": [ 129 | -57.48046875, 130 | -5.9657536710655235 131 | ] 132 | } 133 | }, 134 | { 135 | "type": "Feature", 136 | "properties": { 137 | "continent": "S America" 138 | }, 139 | "geometry": { 140 | "type": "Point", 141 | "coordinates": [ 142 | -70.83984375, 143 | -10.574222078332806 144 | ] 145 | } 146 | }, 147 | { 148 | "type": "Feature", 149 | "properties": { 150 | "continent": "S America" 151 | }, 152 | "geometry": { 153 | "type": "Point", 154 | "coordinates": [ 155 | -62.84179687499999, 156 | 6.664607562172573 157 | ] 158 | } 159 | }, 160 | { 161 | "type": "Feature", 162 | "properties": { 163 | "continent": "S America" 164 | }, 165 | "geometry": { 166 | "type": "Point", 167 | "coordinates": [ 168 | -71.015625, 169 | 5.266007882805498 170 | ] 171 | } 172 | }, 173 | { 174 | "type": "Feature", 175 | "properties": { 176 | "continent": "S America" 177 | }, 178 | "geometry": { 179 | "type": "Point", 180 | "coordinates": [ 181 | -51.85546874999999, 182 | -6.926426847059551 183 | ] 184 | } 185 | }, 186 | { 187 | "type": "Feature", 188 | "properties": { 189 | "continent": "S America" 190 | }, 191 | "geometry": { 192 | "type": "Point", 193 | "coordinates": [ 194 | -54.404296875, 195 | -0.4394488164139641 196 | ] 197 | } 198 | }, 199 | { 200 | "type": "Feature", 201 | "properties": { 202 | "continent": "S America" 203 | }, 204 | "geometry": { 205 | "type": "Point", 206 | "coordinates": [ 207 | -75.41015624999999, 208 | -9.622414142924805 209 | ] 210 | } 211 | }, 212 | { 213 | "type": "Feature", 214 | "properties": { 215 | "continent": "S America" 216 | }, 217 | "geometry": { 218 | "type": "Point", 219 | "coordinates": [ 220 | -67.8515625, 221 | -2.108898659243126 222 | ] 223 | } 224 | }, 225 | { 226 | "type": "Feature", 227 | "properties": { 228 | "continent": "S America" 229 | }, 230 | "geometry": { 231 | "type": "Point", 232 | "coordinates": [ 233 | -64.599609375, 234 | -11.092165893501988 235 | ] 236 | } 237 | }, 238 | { 239 | "type": "Feature", 240 | "properties": { 241 | "continent": "S America" 242 | }, 243 | "geometry": { 244 | "type": "Point", 245 | "coordinates": [ 246 | -64.423828125, 247 | 4.214943141390651 248 | ] 249 | } 250 | }, 251 | { 252 | "type": "Feature", 253 | "properties": { 254 | "continent": "S America" 255 | }, 256 | "geometry": { 257 | "type": "Point", 258 | "coordinates": [ 259 | -73.564453125, 260 | 3.0746950723696944 261 | ] 262 | } 263 | }, 264 | { 265 | "type": "Feature", 266 | "properties": { 267 | "continent": "S America" 268 | }, 269 | "geometry": { 270 | "type": "Point", 271 | "coordinates": [ 272 | -65.91796875, 273 | -14.093957177836224 274 | ] 275 | } 276 | }, 277 | { 278 | "type": "Feature", 279 | "properties": { 280 | "continent": "S America" 281 | }, 282 | "geometry": { 283 | "type": "Point", 284 | "coordinates": [ 285 | -71.89453125, 286 | -13.838079936422462 287 | ] 288 | } 289 | }, 290 | { 291 | "type": "Feature", 292 | "properties": { 293 | "continent": "S America" 294 | }, 295 | "geometry": { 296 | "type": "Point", 297 | "coordinates": [ 298 | -76.552734375, 299 | -3.337953961416472 300 | ] 301 | } 302 | }, 303 | { 304 | "type": "Feature", 305 | "properties": { 306 | "continent": "S America" 307 | }, 308 | "geometry": { 309 | "type": "Point", 310 | "coordinates": [ 311 | -76.11328125, 312 | 1.5818302639606454 313 | ] 314 | } 315 | }, 316 | { 317 | "type": "Feature", 318 | "properties": { 319 | "continent": "S America" 320 | }, 321 | "geometry": { 322 | "type": "Point", 323 | "coordinates": [ 324 | -64.86328125, 325 | -3.337953961416472 326 | ] 327 | } 328 | }, 329 | { 330 | "type": "Feature", 331 | "properties": { 332 | "continent": "S America" 333 | }, 334 | "geometry": { 335 | "type": "Point", 336 | "coordinates": [ 337 | -67.1484375, 338 | 7.710991655433217 339 | ] 340 | } 341 | }, 342 | { 343 | "type": "Feature", 344 | "properties": { 345 | "continent": "Africa" 346 | }, 347 | "geometry": { 348 | "type": "Point", 349 | "coordinates": [ 350 | -11.162109375, 351 | 7.623886853120049 352 | ] 353 | } 354 | }, 355 | { 356 | "type": "Feature", 357 | "properties": { 358 | "continent": "Africa" 359 | }, 360 | "geometry": { 361 | "type": "Point", 362 | "coordinates": [ 363 | -5.537109374999999, 364 | 6.751896464843375 365 | ] 366 | } 367 | }, 368 | { 369 | "type": "Feature", 370 | "properties": { 371 | "continent": "Africa" 372 | }, 373 | "geometry": { 374 | "type": "Point", 375 | "coordinates": [ 376 | -7.294921874999999, 377 | 7.18810087117902 378 | ] 379 | } 380 | }, 381 | { 382 | "type": "Feature", 383 | "properties": { 384 | "continent": "Africa" 385 | }, 386 | "geometry": { 387 | "type": "Point", 388 | "coordinates": [ 389 | -7.91015625, 390 | 10.401377554543553 391 | ] 392 | } 393 | }, 394 | { 395 | "type": "Feature", 396 | "properties": { 397 | "continent": "Africa" 398 | }, 399 | "geometry": { 400 | "type": "Point", 401 | "coordinates": [ 402 | -2.548828125, 403 | 7.710991655433217 404 | ] 405 | } 406 | }, 407 | { 408 | "type": "Feature", 409 | "properties": { 410 | "continent": "Africa" 411 | }, 412 | "geometry": { 413 | "type": "Point", 414 | "coordinates": [ 415 | -0.703125, 416 | 9.188870084473406 417 | ] 418 | } 419 | }, 420 | { 421 | "type": "Feature", 422 | "properties": { 423 | "continent": "Africa" 424 | }, 425 | "geometry": { 426 | "type": "Point", 427 | "coordinates": [ 428 | -0.087890625, 429 | 6.926426847059551 430 | ] 431 | } 432 | }, 433 | { 434 | "type": "Feature", 435 | "properties": { 436 | "continent": "Africa" 437 | }, 438 | "geometry": { 439 | "type": "Point", 440 | "coordinates": [ 441 | -2.109375, 442 | 7.100892668623654 443 | ] 444 | } 445 | }, 446 | { 447 | "type": "Feature", 448 | "properties": { 449 | "continent": "Africa" 450 | }, 451 | "geometry": { 452 | "type": "Point", 453 | "coordinates": [ 454 | -3.076171875, 455 | 10.746969318460001 456 | ] 457 | } 458 | }, 459 | { 460 | "type": "Feature", 461 | "properties": { 462 | "continent": "Africa" 463 | }, 464 | "geometry": { 465 | "type": "Point", 466 | "coordinates": [ 467 | -1.318359375, 468 | 5.878332109674327 469 | ] 470 | } 471 | }, 472 | { 473 | "type": "Feature", 474 | "properties": { 475 | "continent": "Africa" 476 | }, 477 | "geometry": { 478 | "type": "Point", 479 | "coordinates": [ 480 | 1.0546875, 481 | 9.795677582829743 482 | ] 483 | } 484 | }, 485 | { 486 | "type": "Feature", 487 | "properties": { 488 | "continent": "Africa" 489 | }, 490 | "geometry": { 491 | "type": "Point", 492 | "coordinates": [ 493 | 9.4921875, 494 | 4.653079918274051 495 | ] 496 | } 497 | }, 498 | { 499 | "type": "Feature", 500 | "properties": { 501 | "continent": "Africa" 502 | }, 503 | "geometry": { 504 | "type": "Point", 505 | "coordinates": [ 506 | 12.041015625, 507 | 4.8282597468669755 508 | ] 509 | } 510 | }, 511 | { 512 | "type": "Feature", 513 | "properties": { 514 | "continent": "Africa" 515 | }, 516 | "geometry": { 517 | "type": "Point", 518 | "coordinates": [ 519 | 11.689453125, 520 | 3.2502085616531686 521 | ] 522 | } 523 | }, 524 | { 525 | "type": "Feature", 526 | "properties": { 527 | "continent": "Africa" 528 | }, 529 | "geometry": { 530 | "type": "Point", 531 | "coordinates": [ 532 | 10.810546875, 533 | 2.5479878714713835 534 | ] 535 | } 536 | }, 537 | { 538 | "type": "Feature", 539 | "properties": { 540 | "continent": "Africa" 541 | }, 542 | "geometry": { 543 | "type": "Point", 544 | "coordinates": [ 545 | 10.634765625, 546 | 2.1088986592431382 547 | ] 548 | } 549 | }, 550 | { 551 | "type": "Feature", 552 | "properties": { 553 | "continent": "Africa" 554 | }, 555 | "geometry": { 556 | "type": "Point", 557 | "coordinates": [ 558 | 11.25, 559 | 0.17578097424708533 560 | ] 561 | } 562 | }, 563 | { 564 | "type": "Feature", 565 | "properties": { 566 | "continent": "Africa" 567 | }, 568 | "geometry": { 569 | "type": "Point", 570 | "coordinates": [ 571 | 13.095703125, 572 | 2.3723687086440504 573 | ] 574 | } 575 | }, 576 | { 577 | "type": "Feature", 578 | "properties": { 579 | "continent": "Africa" 580 | }, 581 | "geometry": { 582 | "type": "Point", 583 | "coordinates": [ 584 | 14.765625, 585 | 5.353521355337334 586 | ] 587 | } 588 | }, 589 | { 590 | "type": "Feature", 591 | "properties": { 592 | "continent": "Africa" 593 | }, 594 | "geometry": { 595 | "type": "Point", 596 | "coordinates": [ 597 | 13.447265624999998, 598 | 0.17578097424708533 599 | ] 600 | } 601 | }, 602 | { 603 | "type": "Feature", 604 | "properties": { 605 | "continent": "Africa" 606 | }, 607 | "geometry": { 608 | "type": "Point", 609 | "coordinates": [ 610 | 11.865234375, 611 | -0.7031073524364783 612 | ] 613 | } 614 | }, 615 | { 616 | "type": "Feature", 617 | "properties": { 618 | "continent": "Africa" 619 | }, 620 | "geometry": { 621 | "type": "Point", 622 | "coordinates": [ 623 | 15.8203125, 624 | 2.5479878714713835 625 | ] 626 | } 627 | }, 628 | { 629 | "type": "Feature", 630 | "properties": { 631 | "continent": "Africa" 632 | }, 633 | "geometry": { 634 | "type": "Point", 635 | "coordinates": [ 636 | 13.18359375, 637 | -2.108898659243126 638 | ] 639 | } 640 | }, 641 | { 642 | "type": "Feature", 643 | "properties": { 644 | "continent": "Africa" 645 | }, 646 | "geometry": { 647 | "type": "Point", 648 | "coordinates": [ 649 | 15.556640624999998, 650 | 0.7909904981540058 651 | ] 652 | } 653 | }, 654 | { 655 | "type": "Feature", 656 | "properties": { 657 | "continent": "Africa" 658 | }, 659 | "geometry": { 660 | "type": "Point", 661 | "coordinates": [ 662 | 13.447265624999998, 663 | 6.140554782450308 664 | ] 665 | } 666 | }, 667 | { 668 | "type": "Feature", 669 | "properties": { 670 | "continent": "Africa" 671 | }, 672 | "geometry": { 673 | "type": "Point", 674 | "coordinates": [ 675 | 26.7626953125, 676 | 2.591888984149953 677 | ] 678 | } 679 | }, 680 | { 681 | "type": "Feature", 682 | "properties": { 683 | "continent": "Africa" 684 | }, 685 | "geometry": { 686 | "type": "Point", 687 | "coordinates": [ 688 | 24.6533203125, 689 | 0.615222552406841 690 | ] 691 | } 692 | }, 693 | { 694 | "type": "Feature", 695 | "properties": { 696 | "continent": "Africa" 697 | }, 698 | "geometry": { 699 | "type": "Point", 700 | "coordinates": [ 701 | 38.14453125, 702 | -6.926426847059551 703 | ] 704 | } 705 | }, 706 | { 707 | "type": "Feature", 708 | "properties": { 709 | "continent": "Africa" 710 | }, 711 | "geometry": { 712 | "type": "Point", 713 | "coordinates": [ 714 | 36.9580078125, 715 | -8.146242825034385 716 | ] 717 | } 718 | }, 719 | { 720 | "type": "Feature", 721 | "properties": { 722 | "continent": "Africa" 723 | }, 724 | "geometry": { 725 | "type": "Point", 726 | "coordinates": [ 727 | 36.8701171875, 728 | -5.922044619883305 729 | ] 730 | } 731 | }, 732 | { 733 | "type": "Feature", 734 | "properties": { 735 | "continent": "Africa" 736 | }, 737 | "geometry": { 738 | "type": "Point", 739 | "coordinates": [ 740 | 38.759765625, 741 | -4.915832801313164 742 | ] 743 | } 744 | }, 745 | { 746 | "type": "Feature", 747 | "properties": { 748 | "continent": "Africa" 749 | }, 750 | "geometry": { 751 | "type": "Point", 752 | "coordinates": [ 753 | 36.8701171875, 754 | -6.708253968671543 755 | ] 756 | } 757 | }, 758 | { 759 | "type": "Feature", 760 | "properties": { 761 | "continent": "Africa" 762 | }, 763 | "geometry": { 764 | "type": "Point", 765 | "coordinates": [ 766 | 37.8369140625, 767 | -4.34641127533318 768 | ] 769 | } 770 | }, 771 | { 772 | "type": "Feature", 773 | "properties": { 774 | "continent": "Africa" 775 | }, 776 | "geometry": { 777 | "type": "Point", 778 | "coordinates": [ 779 | 38.056640625, 780 | -5.0909441750333855 781 | ] 782 | } 783 | }, 784 | { 785 | "type": "Feature", 786 | "properties": { 787 | "continent": "Africa" 788 | }, 789 | "geometry": { 790 | "type": "Point", 791 | "coordinates": [ 792 | 38.056640625, 793 | -7.972197714386866 794 | ] 795 | } 796 | }, 797 | { 798 | "type": "Feature", 799 | "properties": { 800 | "continent": "Africa" 801 | }, 802 | "geometry": { 803 | "type": "Point", 804 | "coordinates": [ 805 | 37.08984375, 806 | -5.484768018141262 807 | ] 808 | } 809 | }, 810 | { 811 | "type": "Feature", 812 | "properties": { 813 | "continent": "Africa" 814 | }, 815 | "geometry": { 816 | "type": "Point", 817 | "coordinates": [ 818 | 28.696289062499996, 819 | 2.767477951092084 820 | ] 821 | } 822 | }, 823 | { 824 | "type": "Feature", 825 | "properties": { 826 | "continent": "Africa" 827 | }, 828 | "geometry": { 829 | "type": "Point", 830 | "coordinates": [ 831 | 33.0029296875, 832 | 2.943040910055132 833 | ] 834 | } 835 | }, 836 | { 837 | "type": "Feature", 838 | "properties": { 839 | "continent": "Africa" 840 | }, 841 | "geometry": { 842 | "type": "Point", 843 | "coordinates": [ 844 | 31.113281249999996, 845 | 2.591888984149953 846 | ] 847 | } 848 | }, 849 | { 850 | "type": "Feature", 851 | "properties": { 852 | "continent": "Africa" 853 | }, 854 | "geometry": { 855 | "type": "Point", 856 | "coordinates": [ 857 | 33.8818359375, 858 | 2.28455066023697 859 | ] 860 | } 861 | }, 862 | { 863 | "type": "Feature", 864 | "properties": { 865 | "continent": "Africa" 866 | }, 867 | "geometry": { 868 | "type": "Point", 869 | "coordinates": [ 870 | 32.1240234375, 871 | 2.152813583128846 872 | ] 873 | } 874 | }, 875 | { 876 | "type": "Feature", 877 | "properties": { 878 | "continent": "Africa" 879 | }, 880 | "geometry": { 881 | "type": "Point", 882 | "coordinates": [ 883 | 19.775390625, 884 | -0.08789059053082422 885 | ] 886 | } 887 | }, 888 | { 889 | "type": "Feature", 890 | "properties": { 891 | "continent": "Africa" 892 | }, 893 | "geometry": { 894 | "type": "Point", 895 | "coordinates": [ 896 | 22.32421875, 897 | 1.845383988573187 898 | ] 899 | } 900 | }, 901 | { 902 | "type": "Feature", 903 | "properties": { 904 | "continent": "Africa" 905 | }, 906 | "geometry": { 907 | "type": "Point", 908 | "coordinates": [ 909 | 18.369140624999996, 910 | 0 911 | ] 912 | } 913 | }, 914 | { 915 | "type": "Feature", 916 | "properties": { 917 | "continent": "Africa" 918 | }, 919 | "geometry": { 920 | "type": "Point", 921 | "coordinates": [ 922 | 19.6875, 923 | 2.1967272417616712 924 | ] 925 | } 926 | }, 927 | { 928 | "type": "Feature", 929 | "properties": { 930 | "continent": "Africa" 931 | }, 932 | "geometry": { 933 | "type": "Point", 934 | "coordinates": [ 935 | 22.5, 936 | 0.5712795966325395 937 | ] 938 | } 939 | }, 940 | { 941 | "type": "Feature", 942 | "properties": { 943 | "continent": "Asia" 944 | }, 945 | "geometry": { 946 | "type": "Point", 947 | "coordinates": [ 948 | 100.01953125, 949 | -0.04394530819134536 950 | ] 951 | } 952 | }, 953 | { 954 | "type": "Feature", 955 | "properties": { 956 | "continent": "Asia" 957 | }, 958 | "geometry": { 959 | "type": "Point", 960 | "coordinates": [ 961 | 116.76269531249999, 962 | 6.096859818887948 963 | ] 964 | } 965 | }, 966 | { 967 | "type": "Feature", 968 | "properties": { 969 | "continent": "Asia" 970 | }, 971 | "geometry": { 972 | "type": "Point", 973 | "coordinates": [ 974 | 116.3232421875, 975 | 3.6888551431470478 976 | ] 977 | } 978 | }, 979 | { 980 | "type": "Feature", 981 | "properties": { 982 | "continent": "Asia" 983 | }, 984 | "geometry": { 985 | "type": "Point", 986 | "coordinates": [ 987 | 117.1142578125, 988 | 2.5040852618529215 989 | ] 990 | } 991 | }, 992 | { 993 | "type": "Feature", 994 | "properties": { 995 | "continent": "Asia" 996 | }, 997 | "geometry": { 998 | "type": "Point", 999 | "coordinates": [ 1000 | 114.43359375, 1001 | 2.4601811810210052 1002 | ] 1003 | } 1004 | }, 1005 | { 1006 | "type": "Feature", 1007 | "properties": { 1008 | "continent": "Asia" 1009 | }, 1010 | "geometry": { 1011 | "type": "Point", 1012 | "coordinates": [ 1013 | 116.49902343749999, 1014 | 0.4394488164139768 1015 | ] 1016 | } 1017 | }, 1018 | { 1019 | "type": "Feature", 1020 | "properties": { 1021 | "continent": "Asia" 1022 | }, 1023 | "geometry": { 1024 | "type": "Point", 1025 | "coordinates": [ 1026 | 113.818359375, 1027 | 0 1028 | ] 1029 | } 1030 | }, 1031 | { 1032 | "type": "Feature", 1033 | "properties": { 1034 | "continent": "Asia" 1035 | }, 1036 | "geometry": { 1037 | "type": "Point", 1038 | "coordinates": [ 1039 | 115.53222656249999, 1040 | -0.3515602939922709 1041 | ] 1042 | } 1043 | }, 1044 | { 1045 | "type": "Feature", 1046 | "properties": { 1047 | "continent": "Asia" 1048 | }, 1049 | "geometry": { 1050 | "type": "Point", 1051 | "coordinates": [ 1052 | 111.796875, 1053 | 1.7136116598836224 1054 | ] 1055 | } 1056 | }, 1057 | { 1058 | "type": "Feature", 1059 | "properties": { 1060 | "continent": "Asia" 1061 | }, 1062 | "geometry": { 1063 | "type": "Point", 1064 | "coordinates": [ 1065 | 113.4228515625, 1066 | 2.3723687086440504 1067 | ] 1068 | } 1069 | }, 1070 | { 1071 | "type": "Feature", 1072 | "properties": { 1073 | "continent": "Asia" 1074 | }, 1075 | "geometry": { 1076 | "type": "Point", 1077 | "coordinates": [ 1078 | 115.31249999999999, 1079 | 3.2940822283128175 1080 | ] 1081 | } 1082 | }, 1083 | { 1084 | "type": "Feature", 1085 | "properties": { 1086 | "continent": "Asia" 1087 | }, 1088 | "geometry": { 1089 | "type": "Point", 1090 | "coordinates": [ 1091 | 117.0703125, 1092 | 4.8282597468669755 1093 | ] 1094 | } 1095 | }, 1096 | { 1097 | "type": "Feature", 1098 | "properties": { 1099 | "continent": "Asia" 1100 | }, 1101 | "geometry": { 1102 | "type": "Point", 1103 | "coordinates": [ 1104 | 117.0703125, 1105 | 1.845383988573187 1106 | ] 1107 | } 1108 | }, 1109 | { 1110 | "type": "Feature", 1111 | "properties": { 1112 | "continent": "Australia" 1113 | }, 1114 | "geometry": { 1115 | "type": "Point", 1116 | "coordinates": [ 1117 | 144.140625, 1118 | -14.944784875088372 1119 | ] 1120 | } 1121 | }, 1122 | { 1123 | "type": "Feature", 1124 | "properties": { 1125 | "continent": "Australia" 1126 | }, 1127 | "geometry": { 1128 | "type": "Point", 1129 | "coordinates": [ 1130 | 144.931640625, 1131 | -16.8886597873816 1132 | ] 1133 | } 1134 | }, 1135 | { 1136 | "type": "Feature", 1137 | "properties": { 1138 | "continent": "Australia" 1139 | }, 1140 | "geometry": { 1141 | "type": "Point", 1142 | "coordinates": [ 1143 | 145.3271484375, 1144 | -17.14079039331664 1145 | ] 1146 | } 1147 | }, 1148 | { 1149 | "type": "Feature", 1150 | "properties": { 1151 | "continent": "Australia" 1152 | }, 1153 | "geometry": { 1154 | "type": "Point", 1155 | "coordinates": [ 1156 | 144.3603515625, 1157 | -16.720385051693988 1158 | ] 1159 | } 1160 | }, 1161 | { 1162 | "type": "Feature", 1163 | "properties": { 1164 | "continent": "Australia" 1165 | }, 1166 | "geometry": { 1167 | "type": "Point", 1168 | "coordinates": [ 1169 | 145.3271484375, 1170 | -17.769612247142653 1171 | ] 1172 | } 1173 | }, 1174 | { 1175 | "type": "Feature", 1176 | "properties": { 1177 | "continent": "Australia" 1178 | }, 1179 | "geometry": { 1180 | "type": "Point", 1181 | "coordinates": [ 1182 | 144.2724609375, 1183 | -17.392579271057766 1184 | ] 1185 | } 1186 | }, 1187 | { 1188 | "type": "Feature", 1189 | "properties": { 1190 | "continent": "Australia" 1191 | }, 1192 | "geometry": { 1193 | "type": "Point", 1194 | "coordinates": [ 1195 | 144.580078125, 1196 | -18.18760655249461 1197 | ] 1198 | } 1199 | } 1200 | ] 1201 | } -------------------------------------------------------------------------------- /sullivan2020/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Sullivan et al. 2020. Long-term thermal sensitivity of Earth’s tropical forests. Science. 368:6493, 869-874. https://doi.org/10.1126/science.aaw7578 3 | 4 | Figure 1 5 | 6 | ## Notes 7 | A good example of: 8 | 9 | - When you have multiple plots with exactly the same style, setting up a function to create them instead of doing each one individually. 10 | - Nice map using the `sf` package. 11 | - Superscript and potentially other math equations in the axis label. 12 | 13 | 14 | ## Reproduced Figure 15 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/sullivan2020/sullivan2020_final.png) 16 | -------------------------------------------------------------------------------- /sullivan2020/sullivan2020.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(sf) 3 | library(rnaturalearth) 4 | library(cowplot) 5 | 6 | ################################## 7 | # This is a replication of Figure 1 from the following paper 8 | # Sullivan et al. 2020. Long-term thermal sensitivity of Earth’s tropical forests. 9 | # Science. 368:6493, 869-874. https://doi.org/10.1126/science.aaw7578 10 | 11 | # Data used here are simulated, and not meant to replicate the original figure exactly. 12 | 13 | ############################################# 14 | # These points are randomly placed in the same vicinity of the original map. Done using http://geojson.io 15 | # Any shapefile format should work with st_read() 16 | map_points = st_read('sullivan2020/map_points.geojson') 17 | # Randomly assign census type to have open and closed points 18 | map_points$plot_type = sample(c('multicensus','single_census'), size=nrow(map_points), replace = T) 19 | 20 | # Make contient a factor with a specific order to order, the same as the barchart below, so the colors get assigned the same 21 | map_points$continent = factor(map_points$continent, levels = c('S America','Africa','Asia','Australia'), ordered = T) 22 | 23 | countries = rnaturalearth::ne_countries(returnclass = "sf") 24 | 25 | map = ggplot() + 26 | geom_sf(data = countries, fill='transparent', color='grey60', size=0.2) + 27 | geom_sf(data=map_points, aes(color=continent, shape=plot_type), size=1, stroke=1) + 28 | coord_sf(xlim = c(-80,140), ylim = c(-20,20)) + 29 | scale_color_brewer(palette = 'Dark2') + 30 | scale_shape_manual(values = c(1,16)) + 31 | theme_minimal() + 32 | theme(panel.background = element_rect(color='black'), 33 | panel.grid = element_blank(), 34 | axis.text = element_blank(), 35 | plot.margin = margin(0,0,0,0), 36 | legend.position = 'none') 37 | 38 | ############################################## 39 | 40 | # The text labels in the boxplots are a bit tricky. Here I specify the exact text and y postion I want each one to be placed. 41 | # Note I have to do it separately for the black and blue text. The x placement will be continent, the x-axis value. 42 | # They're further refined below, where the two different colored texts are nudged slightly apart. 43 | # The exact placement and nudging values are done thru a lot of trail and error by making small changes. 44 | 45 | # The mean_value and sd_values are just used to create random data points to use with the boxplot 46 | 47 | barchart_data_starter = tribble( 48 | ~carbon_metric, ~continent, ~mean_value, ~sd_value, ~text1, ~text2, ~text_y, 49 | 'stocks', 'S America', 120, 50, 'a', '[a]', 400, 50 | 'stocks', 'Africa', 160, 50, 'b', '[b]', 400, 51 | 'stocks', 'Asia', 190, 60, 'c', '[b]', 400, 52 | 'stocks', 'Australia', 200, 50, 'c', '[ab]', 400, 53 | 'gains', 'S America', 2.1, 1, 'a', '[a]', 6, 54 | 'gains', 'Africa', 2.5, 1, 'b', '[a]', 6, 55 | 'gains', 'Asia', 3.5, 0.8, 'c', '[b]', 6, 56 | 'gains', 'Australia', 2.2, 1, 'ab', '[a]', 6, 57 | 'time', 'S America', 50, 25, 'a', '[a]', 200, 58 | 'time', 'Africa', 60, 25, 'b', '[b]', 200, 59 | 'time', 'Asia', 60, 25, 'b', '[ab]', 200, 60 | 'time', 'Australia', 100, 30, 'c', '[ab]', 200 61 | ) 62 | 63 | set.seed(1) 64 | 65 | # Get some random values to make the boxplots 66 | barchart_data = barchart_data_starter %>% 67 | group_by(carbon_metric, continent) %>% 68 | summarise(carbon_value=rnorm(n=100,mean=mean_value, sd=sd_value)) %>% 69 | ungroup() 70 | 71 | # Make contient a factor with a specific order to order the x-axis 72 | barchart_data$continent = factor(barchart_data$continent, levels = c('S America','Africa','Asia','Australia'), ordered = T) 73 | 74 | # The style of all 3 bar plots is exactly the same, with the underlying data the difference. So here 75 | # we use a function to return the same chart, with metric specifying which of the 3 data attributes to use. 76 | get_barchart = function(metric, y_label){ 77 | df_data = barchart_data %>% 78 | filter(carbon_metric==metric) 79 | 80 | df_text = barchart_data_starter %>% 81 | filter(carbon_metric==metric) 82 | 83 | ggplot(df_data, aes(x=continent, y=carbon_value, fill=continent)) + 84 | stat_boxplot(geom ='errorbar',width=0.2, size=0.3, color='black') + # Draw an error bar separately to get the top/bottom horizontal lines 85 | geom_boxplot(size=0.2, linetype='solid',color='black', # Size here dictates the thickness of the boxplot outline. 86 | outlier.size = 1, outlier.shape = 1) + 87 | scale_fill_brewer(palette = 'Dark2') + 88 | geom_text(data = df_text, aes(y=text_y, label=text1), size=2, position = position_nudge(x=-0.1)) + # The text needs to be done twice since they have different colors. 89 | geom_text(data = df_text, aes(y=text_y, label=text2), size=2, position = position_nudge(x=0.17), color='blue') + # They are both nudged slightly to the left/right, otherwise they would be 90 | theme_bw(7) + # drawn on top of eachother. The x-axis placement (continent) is inherited from 91 | theme(legend.position = 'none', # the primary ggplot call. 92 | plot.margin = margin(0,5,0,5), # Put a little space between boxplots, but not on top or bottom. 93 | panel.grid = element_blank(), 94 | panel.border = element_blank(), # Turn off the full border but initialize the bottom and 95 | axis.line.x.bottom = element_line(size=0.3), # left lines. 96 | axis.line.y.left = element_line(size=0.3), 97 | axis.text = element_text(color='black'), 98 | axis.text.x = element_text(angle=90, hjust=1)) + # continent labels being rotated is done here. 99 | labs(y=y_label, x='') 100 | } 101 | 102 | stock_plot = get_barchart(metric='stocks', y_label = expression(Carbon~Stocks~(Mg~C~ha^{-1}))) # The expression calls here allow the superscript to be used 103 | gains_plot = get_barchart(metric='gains', y_label = expression(Carbon~Gains~(Mg~C~ha^{-1}~yr^{-1}))) # thru plotmath calls. 104 | time_plot = get_barchart(metric='time', y_label = expression(Carbon~residence~time~(years))) # This 3rd graph doesn't need any superscript, but it's passed thru expression 105 | # anyway to match the font style 106 | box_plots = plot_grid(stock_plot, gains_plot, time_plot, nrow = 1) 107 | 108 | 109 | final_figure = plot_grid(map, box_plots, ncol=1, rel_heights = c(0.8,1), scale=c(0.95,0.93), # Scale them down a tad to create a whitespace buffer. 110 | labels = c('A','B'), vjust=c(1.9,1.2), label_size = 10) # vjust nudges the A,B labels 111 | 112 | 113 | ############################################# 114 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 115 | 116 | final_figure = ggdraw(final_figure) + 117 | geom_rect(data=data.frame(xmin=0.05,ymin=0.15), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.5,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 118 | draw_text(water_mark, x=0.05, y=0.2, size=6, hjust = 0) 119 | 120 | 121 | save_plot('sullivan2020/sullivan2020_final.png', plot = final_figure) 122 | 123 | -------------------------------------------------------------------------------- /sullivan2020/sullivan2020_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/sullivan2020/sullivan2020_final.png -------------------------------------------------------------------------------- /thein2020/create_data.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | 3 | 4 | #--------------------------------------- 5 | # Here we create a fake dataset similar to the Thein 2020 figure. 6 | # It will have the following form: 7 | # elev season depot_encounter removal_from_depot total_removal species_removed 8 | # 0.7965260 Wet Season 19.15570 37.21124 19.50298 19.24666 9 | # 1.1163717 Wet Season 18.09308 33.13282 17.06157 16.98486 10 | # 1.7185601 Wet Season 17.57028 36.90911 18.87783 17.99193 11 | # ....... 12 | # ....... 13 | #--------------------------------------- 14 | 15 | set.seed(1) 16 | 17 | upward_curve = function(x, height, noise=2){ 18 | curve = 3*x*x - 9*x + height 19 | curve = curve + rnorm(length(curve), mean=0, sd=noise) # add some noise 20 | curve = pmax(curve, 0) # but keep it above 0 21 | return(curve) 22 | } 23 | downward_curve = function(x, height, noise=2){ 24 | curve = -1.5*x*x + 5*x + height 25 | curve = curve + rnorm(length(curve), mean=0, sd=noise) # add some noise 26 | curve = pmax(curve, 0) # but keep it above 0 27 | return(curve) 28 | } 29 | 30 | 31 | wet_season_n = 25 32 | dry_season_n = 25 33 | 34 | wet_season = data.frame(elev = runif(wet_season_n, 0.5,3)) 35 | wet_season$season = 'Wet Season' 36 | wet_season$depot_encounter = upward_curve(wet_season$elev, height=25) 37 | wet_season$removal_from_depot = upward_curve(wet_season$elev, height=40) 38 | wet_season$total_removal = upward_curve(wet_season$elev, height=25) 39 | wet_season$species_removed = upward_curve(wet_season$elev, height=22) 40 | 41 | dry_season = data.frame(elev = runif(dry_season_n, 0.5,3)) 42 | dry_season$season = 'Dry Season' 43 | dry_season$depot_encounter = downward_curve(dry_season$elev, height=1) 44 | dry_season$removal_from_depot = downward_curve(dry_season$elev, height=22) 45 | dry_season$total_removal = downward_curve(dry_season$elev, height=1) 46 | dry_season$species_removed = downward_curve(dry_season$elev, height=5) 47 | 48 | 49 | both_seasons = wet_season %>% 50 | bind_rows(dry_season) 51 | 52 | write_csv(both_seasons, 'thein2020/simulated_data.csv') 53 | 54 | # both_seasons %>% 55 | # pivot_longer(c(-elev, -season), names_to='metric', values_to='metric_value') %>% 56 | # ggplot(aes(x=elev, y=metric_value, color=season)) + 57 | # geom_point() + 58 | # geom_smooth(method='lm', formula = y~poly(x,2)) + 59 | # facet_wrap(~metric, ncol=2, scales='free') 60 | -------------------------------------------------------------------------------- /thein2020/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Thein, M. M., Wu, L. M., Corlett, R. T., Quan, R. C., & Wang, B. (2020). Changes in seed predation along a 2300‐m elevational gradient on a tropical mountain in Myanmar: a standardized test with 32 non‐native plant species. Ecography. https://doi.org/10.1111/ecog.05385 3 | 4 | Figure 1 5 | 6 | ## Notes 7 | 8 | This figure has 4 panels which share the same x-axis variable, but have different y-axis variables. There are two ways to go about this and both are presented in the example script. 9 | 1) create 4 different panels and combine them using a special library (here using patchwork, but can also be done with cowplot) 10 | 2) use `facet_wrap`, where the facet labels become the y-axis titles. 11 | 12 | ## Reproduced Figure 13 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/thein2020/thein2020_final.png) 14 | -------------------------------------------------------------------------------- /thein2020/simulated_data.csv: -------------------------------------------------------------------------------- 1 | elev,season,depot_encounter,removal_from_depot,total_removal,species_removed 2 | 1.16377165785525,Wet Season,18.010225346807776,36.06575669589098,18.357497849870313,18.10118612879642 3 | 1.4303097490919754,Wet Season,17.666139957420235,32.70587762950633,16.634632775475033,16.55791897420556 4 | 1.932133408379741,Wet Season,17.987196182323544,37.32602402753509,19.29474480963305,18.40884245304054 5 | 2.7705194749869406,Wet Season,23.597106105275536,39.21415139073938,20.242462419498274,18.346134985474404 6 | 1.0042048275936395,Wet Season,17.20359630437522,33.08187061383805,19.7193208050428,16.004180478936426 7 | 2.7459742124192417,Wet Season,23.778721812752686,36.24326862180558,23.404180511786443,18.14561176753616 8 | 2.8616881715133786,Wet Season,21.337507185458428,36.47944293514893,23.943160392661586,22.005102062539663 9 | 2.151994481217116,Wet Season,19.076754640056112,32.394109249836134,19.56360319393328,16.764725693191803 10 | 2.072785109747201,Wet Season,19.989039637807746,31.106684243702333,19.74892510015541,15.66990059119944 11 | 0.6544656761689112,Wet Season,20.66145759995917,37.70785887262985,19.096764722911693,20.306761680462174 12 | 1.0149364372482523,Wet Season,20.564238999218073,35.61995423687304,18.71752245489218,16.41389916111764 13 | 0.9413918813224882,Wet Season,19.071915541989668,33.73147170790778,20.51440049054551,18.17921694784554 14 | 2.217557116644457,Wet Season,20.801880591408775,35.32693937028553,21.996602851329495,18.358383016141836 15 | 1.4602592955343425,Wet Season,20.426276695065653,32.50133253360702,18.54228093229042,13.701184727245085 16 | 2.424603549996391,Wet Season,19.432767494604125,40.697404431786964,20.579167977665882,16.582695358581947 17 | 1.744248105213046,Wet Season,15.859772702956114,31.838293176189747,16.604834676803815,15.522132016310483 18 | 2.294046270661056,Wet Season,20.2349807842289,35.03177349242904,17.266355958192236,14.880756884650811 19 | 2.979765237076208,Wet Season,24.347702357698616,40.31939811628592,23.22493642043369,22.97255303437059 20 | 1.4500879485858604,Wet Season,17.171697128608596,34.4939602257616,20.765639851529045,12.695974775052456 21 | 2.4436130532994866,Wet Season,20.054596148157977,35.575969777779825,22.4655011546806,21.172111390001426 22 | 2.836763077764772,Wet Season,22.31186328463297,34.16300603020556,23.171775324718556,19.609413386220027 23 | 1.030356303206645,Wet Season,20.365197100584087,31.38446683587202,18.062075039058612,19.26829002144577 24 | 2.129184415214695,Wet Season,21.741442593221574,35.15507687698988,18.599658886203258,15.61257931008238 25 | 0.81388773990329355,Wet Season,21.64657083125663,34.6401591434337,21.656223822183243,14.717676429353913 26 | 1.16805167181883,Wet Season,17.721542858763062,31.699270752509605,18.02901301957077,15.631334812922432 27 | 1.7773992458824068,Dry Season,6.573606724619822,27.048648313062323,5.078822053894459,9.96707778981888 28 | 1.1440531520638615,Dry Season,4.609850529945769,25.719859672769147,6.332258549458745,12.134725910606514 29 | 0.6161522173788399,Dry Season,3.436027411488812,23.875159005335217,7.6617857717274775,10.684472621306849 30 | 1.5446406458504498,Dry Season,3.7810101845209934,24.285606847124903,7.199116019559844,8.482515540666775 31 | 2.6350037556840107,Dry Season,3.111611045224153,21.785230969433822,6.175968386490199,3.1896805191318576 32 | 1.368076694314368,Dry Season,5.153253590151678,23.882548116051286,2.5702858661665595,14.028255888950977 33 | 0.828605801332742,Dry Season,2.9353586731409687,27.113205253088125,6.080938785767055,9.447279979191283 34 | 1.436217161361128,Dry Season,6.14999859018846,24.84447281532967,5.526855812244618,10.169660876850717 35 | 2.0785505709936842,Dry Season,1.8754059771275906,23.143340445932182,1.9776940825166798,8.885395094409347 36 | 1.475197334191762,Dry Season,5.724791630328619,29.85025715359625,6.153721394045366,10.131892754654942 37 | 2.2240696219960228,Dry Season,1.6277199376766442,26.550820339496713,4.383110375319784,8.371867921212482 38 | 2.2235335311852396,Dry Season,4.0995633557953255,25.22422140764248,7.630690233408141,9.542904895977573 39 | 1.8872515578987077,Dry Season,4.0371203164168765,28.21064622272493,3.5615161260975596,8.293186637351601 40 | 1.5740610194625333,Dry Season,3.849613396463525,27.926648260575394,4.2933794499684295,6.413387202730602 41 | 1.6318001570180058,Dry Season,5.051049600729262,24.926357059961752,3.312624161669172,11.140519691333804 42 | 1.266108147217892,Dry Season,1.0972771240521517,30.33820090449311,4.571788052539095,11.965486026411266 43 | 1.9458848600042984,Dry Season,7.402889091467695,25.53966840714854,5.853746026403246,8.432241328979343 44 | 2.7759257606230676,Dry Season,0,21.471993759953587,1.8574867141399911,4.8144035491638215 45 | 0.8565102053107694,Dry Season,3.255075625906832,24.893337224943163,5.84288276481495,9.46661904020725 46 | 1.5376190632814541,Dry Season,2.9098465306717753,26.556763419222154,2.725521168148536,9.052268466969508 47 | 1.0273143766680732,Dry Season,3.0518716381901028,30.16946643869572,2.4575408149615154,5.087072826927321 48 | 1.571875927154906,Dry Season,9.327521831477581,26.36479347600831,8.03550415390945,9.157452459581426 49 | 0.831724938005209,Dry Season,4.155766370662896,26.03497274212322,2.089280200667094,6.8603744634201 50 | 1.6502411146648228,Dry Season,2.5936609076601673,26.011956097815755,5.9902113931638485,8.484324808808008 51 | 2.857392648118548,Dry Season,0,23.37192243757676,2.2777720200920077,4.72677939703814 52 | -------------------------------------------------------------------------------- /thein2020/thein2020.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(patchwork) 3 | library(cowplot) 4 | 5 | #-------------- 6 | # This is a replication of Figure 1 from the following paper 7 | # Thein, M. M., Wu, L. M., Corlett, R. T., Quan, R. C., & Wang, B. (2020). Changes in seed predation 8 | # along a 2300‐m elevational gradient on a tropical mountain in Myanmar: a standardized test 9 | # with 32 non‐native plant species. Ecography. https://doi.org/10.1111/ecog.05385 10 | 11 | # This figure uses simulated data and is not an exact copy. 12 | # It's meant for educational purposes only. 13 | #-------------- 14 | # The reproduced plot is made creating 4 different panels and combining them using the patchwork library. 15 | # An alternative method is at the end to create the same figure using facets instead. 16 | #-------------- 17 | 18 | 19 | figure_data = read_csv('thein2020/simulated_data.csv') 20 | 21 | # Here we make 4 unique plots for the 4 variables. 22 | # They share everything in column except the data, so a common 23 | # base_figure can be specified with everything except the geoms. 24 | # 25 | # Note how the x and color values here get passed on to geom_point() and 26 | # geom_smooth() for all plots. 27 | base_figure = ggplot(figure_data, aes(x=elev, color=season)) + 28 | scale_color_manual(values = c('darkorange2','dodgerblue')) + 29 | scale_x_continuous(breaks=c(0.5, 1, 1.5, 2, 2.5, 3)) + 30 | theme_bw() + 31 | theme(legend.position = 'none') + # Turn off the legend for all plots. This can be turned back on in the plot where it's needed. 32 | labs(x='Elevation (km)') 33 | 34 | point_size = 3 35 | 36 | panel_a = base_figure + 37 | geom_smooth(aes(y=depot_encounter),method='lm', formula = y~poly(x,2)) + # Fit a 2nd order polynomial smoothing line, this needs to be repeated for each 38 | geom_point(aes(y=depot_encounter), size=point_size) + # panel using the respective variable. 39 | labs(y='Seed depot encounter (%)') 40 | 41 | panel_b = base_figure + 42 | geom_smooth(aes(y=removal_from_depot),method='lm', formula = y~poly(x,2)) + 43 | geom_point(aes(y=removal_from_depot), size=point_size) + 44 | labs(y='Seed removal from\n encountered depots (%)') # Note the \n to get the text to wrap 45 | 46 | panel_c = base_figure + 47 | geom_smooth(aes(y=total_removal),method='lm', formula = y~poly(x,2), show.legend = FALSE) + 48 | geom_point(aes(y=total_removal), size=point_size) + 49 | theme(legend.title = element_blank(), # Add the legend to the (c) panel. Position is relative to this panel only. 50 | legend.background = element_blank(), # show.legend = FALSE in the geom_smooth for this panel turns off the line inside the legend 51 | legend.position = c(0.5,0.91)) + 52 | labs(y='Total seed removal (%)') + 53 | guides(color = guide_legend(override.aes = list(size=4.5))) # Make the points in the legend slightly larger to distinquish from surrounding points. 54 | 55 | panel_d = base_figure + 56 | geom_smooth(aes(y=species_removed),method='lm', formula = y~poly(x,2)) + 57 | geom_point(aes(y=species_removed), size=point_size) + 58 | labs(y='Number of species removed') 59 | 60 | 61 | # put them together with patchwork 62 | primary_figure = panel_a + panel_b + panel_c + panel_d + 63 | plot_layout(ncol=2) + 64 | plot_annotation(tag_levels = 'a', tag_prefix = '(', tag_suffix = ')') & # This & is special for the patchwork library and applies the theme to all plots. 65 | theme(plot.tag.position = c(0.2,0.95), # See https://patchwork.data-imaginist.com/articles/guides/annotation.html for detail 66 | plot.tag = element_text(face = 'bold'), 67 | panel.grid = element_blank(), 68 | axis.text = element_text(size=10), 69 | axis.title = element_text(size=14)) 70 | 71 | #-------------- 72 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 73 | 74 | final_figure = ggdraw(primary_figure) + 75 | geom_rect(data=data.frame(xmin=0.05,ymin=0.05), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.6,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 76 | draw_text(water_mark, x=0.05, y=0.1, size=10, hjust = 0) 77 | 78 | save_plot('./thein2020/thein2020_final.png', plot=final_figure, base_height = 8, base_width = 8) 79 | 80 | 81 | 82 | #-------------- 83 | # Alternative method using facets 84 | # This uses the tagger package to label a-c on the panels. 85 | # https://github.com/eliocamp/tagger 86 | #-------------- 87 | # 88 | # This can also be done using a facet instead of 4 separate plots 89 | # 1st need to convert the data to a long format, where there is a new column for the variable of interest 90 | figure_data_long = figure_data %>% 91 | pivot_longer(c(-elev, -season), names_to='variable', values_to='variable_value') 92 | 93 | # Convert the variable to a factor column, where the factor labels are the ultimate title on each panel 94 | # The order here also defines the panel order (left -> right, top -> bottom) 95 | variables = c('depot_encounter', 96 | 'removal_from_depot', 97 | 'total_removal', 98 | 'species_removed') 99 | nice_variable_names = c('Seed depot encounter (%)', 100 | 'Seed removal from\n encountered depots (%)', 101 | 'Total seed removal (%)', 102 | 'Number of species removed') 103 | 104 | figure_data_long$variable = factor(figure_data_long$variable, levels = variables, labels = nice_variable_names, ordered = TRUE) 105 | 106 | 107 | alternate_method_figure = ggplot(figure_data_long, aes(x=elev, y=variable_value, color=season)) + 108 | geom_point(size=3) + 109 | geom_smooth(method='lm', formula = y~poly(x,2), show.legend = FALSE) + 110 | scale_color_manual(values = c('darkorange2','dodgerblue')) + 111 | scale_x_continuous(breaks=c(0.5, 1, 1.5, 2, 2.5, 3)) + 112 | facet_wrap(~variable, scales='free', strip.position = 'left') + # set te "strip" to the left side 113 | tagger::tag_facets(tag_prefix = '(', tag_suffix = ')') + 114 | theme_bw() + 115 | theme(panel.grid = element_blank(), 116 | legend.position = c(0.75,0.425), 117 | legend.title = element_blank(), 118 | axis.text = element_text(size=10), 119 | axis.title = element_text(size=14), 120 | strip.text = element_text(size=18), 121 | strip.background = element_blank(), # Turn off the grey strip background, and 122 | strip.placement = 'outside') + # put it outside the axis numbers. 123 | labs(x='Elevation (km)', y='') + 124 | guides(color = guide_legend(override.aes = list(size=4.5))) # Make the points in the legend slightly larger to distinquish from surrounding points. 125 | 126 | 127 | -------------------------------------------------------------------------------- /thein2020/thein2020_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/thein2020/thein2020_final.png -------------------------------------------------------------------------------- /yan2019/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Yan, D., Scott, R. L., Moore, D. J. P., Biederman, J. A., & Smith, W. K. (2019). Understanding the relationship between vegetation greenness and productivity across dryland ecosystems through the integration of PhenoCam, satellite, and eddy covariance data. Remote Sensing of Environment, 223, 50–62. https://doi.org/10.1016/j.rse.2018.12.029 3 | 4 | Figure 5 5 | 6 | ## Notes 7 | 8 | This uses two base figures. One for the scatterplot and one for the barcharts. The scatter plot uses facet_wrap, but all the row, column, and axis titles are inserted manually using `cowplot::draw_plot() + geom_text()`. facet_wrap does not have a way to insert text like this outside the normal facet_wrap strip labels. 9 | 10 | ## Reproduced Figure 11 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/yan2019/yan2019_final.png) 12 | -------------------------------------------------------------------------------- /yan2019/yan2019.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(cowplot) 3 | 4 | ################################## 5 | # This is a replication of Figure 5 from the following paper 6 | # Yan, D., Scott, R. L., Moore, D. J. P., Biederman, J. A., & Smith, W. K. (2019). 7 | # Understanding the relationship between vegetation greenness and productivity across 8 | # dryland ecosystems through the integration of PhenoCam, satellite, and eddy covariance data. 9 | # Remote Sensing of Environment, 223, 50–62. https://doi.org/10.1016/j.rse.2018.12.029 10 | 11 | # This figure uses simulated data and is not an exact copy. 12 | # It's meant for educational purposes only. 13 | ################################## 14 | 15 | ##################################### 16 | # Generate some similar looking data for the figures 17 | generate_random_timeseries = function(n,initial_value,error){ 18 | ts = rep(NA, n) 19 | ts[1] = initial_value 20 | for(t in 2:n){ 21 | ts[t] = ts[t-1] + rnorm(1, mean=0, sd=error) 22 | } 23 | return(ts) 24 | } 25 | 26 | generate_correlated_timeseries = function(ts, error, scale_min, scale_max){ 27 | n=length(ts) 28 | new_ts = ts + rnorm(n=n, mean=0, sd=error) 29 | return(scales::rescale(new_ts, to=c(scale_min,scale_max))) 30 | } 31 | 32 | set.seed(1) 33 | 34 | # The data consist of 3 locations, each with a GPP measurement (y axis) and 3 correlated visual indexes (Phenocam, landsat, & MODIS) 35 | grassland = data.frame(gpp = generate_random_timeseries(n=76, initial_value = 0, error = 0.5), site='grassland') 36 | grassland$phenocam = generate_correlated_timeseries(grassland$gpp, error=2, scale_min=-0.35,scale_max = -0.05) 37 | grassland$landsat = generate_correlated_timeseries(grassland$gpp, error=2, scale_min=0.15, scale_max = 0.35) 38 | grassland$modis = generate_correlated_timeseries(grassland$gpp, error=2, scale_min=0.15, scale_max = 0.4) 39 | 40 | savanna = data.frame(gpp = generate_random_timeseries(n=42, initial_value = 0, error = 0.5), site='savanna') 41 | savanna$phenocam = generate_correlated_timeseries(savanna$gpp, error=2, scale_min=0.35,scale_max = 0.4) 42 | savanna$landsat = generate_correlated_timeseries(savanna$gpp, error=2, scale_min=0.3, scale_max = 0.4) 43 | savanna$modis = generate_correlated_timeseries(savanna$gpp, error=2, scale_min=0.32, scale_max = 0.4) 44 | 45 | shrubland = data.frame(gpp = generate_random_timeseries(n=73, initial_value = 0, error = 0.5), site='shrubland') 46 | shrubland$phenocam = generate_correlated_timeseries(shrubland$gpp, error=2, scale_min=-0.45,scale_max = -0.2) 47 | shrubland$landsat = generate_correlated_timeseries(shrubland$gpp, error=2, scale_min=0.1, scale_max = 0.28) 48 | shrubland$modis = generate_correlated_timeseries(shrubland$gpp, error=2, scale_min=0.12, scale_max = 0.3) 49 | 50 | scatter_plot_data = grassland %>% 51 | bind_rows(savanna) %>% 52 | bind_rows(shrubland) %>% 53 | pivot_longer(cols=c(phenocam, landsat, modis), names_to = 'sensor', values_to = 'sensor_value') 54 | 55 | 56 | 57 | #### Now eyeball the data for the barplots 58 | barplot_data = tribble( 59 | ~site, ~sensor, ~landcover, ~percent, 60 | 'grassland','PhenoCam','Mesquite',10, 61 | 'grassland','PhenoCam','Grass',80, 62 | 'grassland','PhenoCam','Soil',10, 63 | 'grassland','Landsat','Mesquite',0, 64 | 'grassland','Landsat','Grass',75, 65 | 'grassland','Landsat','Soil',25, 66 | 'grassland','MODIS','Mesquite',4, 67 | 'grassland','MODIS','Grass',48, 68 | 'grassland','MODIS','Soil',48, 69 | 'grassland','Footprint','Mesquite',5, 70 | 'grassland','Footprint','Grass',45, 71 | 'grassland','Footprint','Soil',50, 72 | 73 | 'savanna','PhenoCam','Mesquite',52, 74 | 'savanna','PhenoCam','Grass',43, 75 | 'savanna','PhenoCam','Soil',5, 76 | 'savanna','Landsat','Mesquite',25, 77 | 'savanna','Landsat','Grass',60, 78 | 'savanna','Landsat','Soil',15, 79 | 'savanna','MODIS','Mesquite',30, 80 | 'savanna','MODIS','Grass',50, 81 | 'savanna','MODIS','Soil',20, 82 | 'savanna','Footprint','Mesquite',30, 83 | 'savanna','Footprint','Grass',30, 84 | 'savanna','Footprint','Soil',40, 85 | 86 | 'shrubland','PhenoCam','Shrub',70, 87 | 'shrubland','PhenoCam','Soil',30, 88 | 'shrubland','Landsat','Shrub',38, 89 | 'shrubland','Landsat','Soil',62, 90 | 'shrubland','MODIS','Shrub',45, 91 | 'shrubland','MODIS','Soil',55, 92 | 'shrubland','Footprint','Shrub',60, 93 | 'shrubland','Footprint','Soil',40 94 | ) 95 | 96 | 97 | #############################################################3 98 | # 1st the scatter plot 99 | # 100 | 101 | sensor_values = c('phenocam','landsat','modis') 102 | sensor_labels = c('PhenoCam','Landsat','MODIS') 103 | site_values = c('grassland','savanna','shrubland') 104 | site_labels = c('WKG-Grassland','SRM-Savanna','WHS-Shrubland') 105 | 106 | scatter_plot_data$sensor = factor(scatter_plot_data$sensor, levels = sensor_values, labels = sensor_labels, ordered = T) 107 | scatter_plot_data$site = factor(scatter_plot_data$site, levels = site_values, labels = site_labels, ordered = T) 108 | 109 | # Note there are many ways to extract summary and model statistics 110 | # in a group_by operation. group_by + do, using purrr, there are also methods in 111 | # the ggpmisc package which insert summary stats directly in figures. 112 | get_r2 = function(x,y){summary(lm(y~x))$r.squared} 113 | 114 | scatter_plot_labels = scatter_plot_data %>% 115 | group_by(site, sensor) %>% 116 | summarise(r2 = get_r2(x=sensor_value, y=gpp), 117 | n=n(), 118 | x_pos = min(sensor_value), 119 | y_pos = max(gpp) - (0.03* diff(range(gpp)))) %>% 120 | ungroup() %>% 121 | mutate(r2_label = paste0('bold(R^2==',round(r2,2),')'), 122 | n_label = paste0('n =',n)) 123 | 124 | # Order by site and sensor so the letter labels get ordered correctly. 125 | scatter_plot_labels = arrange(scatter_plot_labels, site, sensor) 126 | scatter_plot_labels$letter = c('(a)','(b)','(c)','(e)','(f)','(g)','(i)','(j)','(k)') 127 | 128 | 129 | # Here the best option for the 3x3 grid of plots is facet_wrap, since it handles all the alignment 130 | # it is too usefull not to use. The labelling of the rows and columns as in the original figure is 131 | # not possible with facet_wrap though. So here we'll essentially "turn off" the default labels 132 | # by setting strip.background and strip.text to element_blank(), and then adding the 6 row/column 133 | # labels manually. The alignment of the text inside each plot is done manually in the scatter_plot_labels 134 | # data.frame, and nudged slightly by hand below using the hjust/vjust arguments. 135 | 136 | scatter_plot = ggplot(scatter_plot_data, aes(x=sensor_value, y=gpp)) + 137 | geom_point() + 138 | geom_smooth(method='lm', se=FALSE, linetype='dashed', color='red', size=0.2) + 139 | geom_text(data=scatter_plot_labels, aes(x=x_pos, y=y_pos, label=letter), hjust=0.4, vjust=0.1, fontface='bold') + 140 | geom_text(data=scatter_plot_labels, aes(x=x_pos, y=y_pos, label=r2_label),hjust=0.15, vjust=1.5, fontface='bold', parse=TRUE) + 141 | geom_text(data=scatter_plot_labels, aes(x=x_pos, y=y_pos, label=n_label), hjust=0.25, vjust=3.8, fontface='bold') + 142 | scale_y_continuous(expand = expansion(mult=0.02)) + 143 | facet_wrap(site~sensor, scales='free', strip.position = 'left') + 144 | theme_bw() + 145 | theme(strip.background = element_blank(), 146 | strip.text = element_blank(), 147 | axis.title = element_blank(), 148 | panel.grid = element_blank(), 149 | axis.text = element_text(face='bold', size=12)) 150 | 151 | top_y = 0.98 152 | left_x_site = 0.01 153 | left_x_axis = 0.05 154 | bottom_y = 0.02 155 | scatter_plot_axis_labels = tribble( 156 | ~label, ~x, ~y, ~angle, 157 | 'WKG-Grassland', left_x_site, 0.8, 90, 158 | 'SRM-Savanna', left_x_site, 0.5, 90, 159 | 'WHS-Shrubland', left_x_site, 0.2, 90, 160 | 161 | 'GPP', left_x_axis, 0.8, 90, 162 | 'GPP', left_x_axis, 0.5, 90, 163 | 'GPP', left_x_axis, 0.2, 90, 164 | 165 | 'PhenoCam', 0.2, top_y, 0, 166 | 'Landsat', 0.55, top_y, 0, 167 | 'MODIS', 0.85, top_y, 0, 168 | 169 | 'VI', 0.2, bottom_y, 0, 170 | 'VI', 0.5, bottom_y, 0, 171 | 'VI', 0.85, bottom_y, 0 172 | ) 173 | 174 | # Insert the 3x3 scatter plot using draw_plot so it can be scaled slightly 175 | 176 | scatter_plot_with_labels = ggdraw() + 177 | draw_plot(scatter_plot, scale=0.95, hjust = -0.02) + 178 | geom_text(data = scatter_plot_axis_labels, aes(x=x,y=y,label=label,angle=angle), 179 | hjust=0.5, fontface='bold', size=5) 180 | 181 | ################################################## 182 | # The barplots 183 | 184 | barplot_sensor_order = c('PhenoCam','Landsat','MODIS','Footprint') 185 | barplot_data$sensor = factor(barplot_data$sensor, levels = barplot_sensor_order, ordered = TRUE) 186 | 187 | barplot_landcover_details = tribble( 188 | ~landcover, ~fill_color, 189 | 'Soil', 'grey70', 190 | 'Grass', 'darkorange2', 191 | 'Mesquite', 'purple4', 192 | 'Shrub', 'tan4' 193 | ) 194 | 195 | barplot_data$landcover = factor(barplot_data$landcover, levels=barplot_landcover_details$landcover, ordered = TRUE) 196 | 197 | # This is "close enough" version of the bar plot. Replicating the original one from Yan 2019 exactly would 198 | # be clumsy here, as the each of the 3 barplots would need to be done individually to each have their own 199 | # legend. 200 | barplot = ggplot(barplot_data, aes(x=sensor, y=percent, fill=landcover)) + 201 | geom_col(width=0.4) + 202 | scale_fill_manual(values = barplot_landcover_details$fill_color) + 203 | scale_y_continuous(breaks=c(0,20,40,60,80), labels = function(x){paste0(x,'%')}, expand=c(0,0)) + 204 | facet_grid(site~.) + 205 | theme_bw() + 206 | theme(strip.background = element_blank(), 207 | strip.text = element_blank(), 208 | panel.grid = element_blank(), 209 | plot.title = element_text(face='bold', hjust=0.5), 210 | axis.text.x = element_text(face = 'bold', size=12), 211 | axis.text.y = element_text(face = 'bold', size=12, angle = 90, hjust=0.5), 212 | legend.key.height = unit(20,'mm'), 213 | ) + 214 | labs(x='',y='',title='Land cover proportions') + 215 | guides(fill = guide_legend(direction = 'vertical', label.position = 'right', title='', 216 | keyheight = unit(20,'mm'), keywidth = unit(5,'mm'), 217 | label.theme = element_text(angle=90, hjust=0.5, face='bold'))) 218 | 219 | #################################### 220 | # And combine them 221 | 222 | aligned=align_plots(scatter_plot_with_labels, barplot, align = 'v', axis='tb') 223 | final_figure = plot_grid(aligned[[1]], aligned[[2]], nrow=1, rel_widths = c(1,0.5)) 224 | 225 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 226 | 227 | final_figure = ggdraw(final_figure) + 228 | geom_rect(data=data.frame(xmin=0.05,ymin=0.05), aes(xmin=xmin,ymin=ymin, xmax=xmin+0.4,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 229 | draw_text(water_mark, x=0.05, y=0.1, size=10, hjust = 0) 230 | 231 | save_plot('./yan2019/yan2019_final.png', plot=final_figure, base_height = 8, base_width = 14) 232 | -------------------------------------------------------------------------------- /yan2019/yan2019_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/yan2019/yan2019_final.png -------------------------------------------------------------------------------- /zhang2020/readme.md: -------------------------------------------------------------------------------- 1 | ## Paper 2 | Zhang, Qiang, et al. "Trait–environment relationships differ between mixed‐species flocking and non‐flocking bird assemblages." 2020. Ecology. https://doi.org/10.1002/ecy.3124 3 | 4 | ## Reproduced Figure 3 5 | ![](https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/master/zhang2020/zhang2020_final.png) 6 | -------------------------------------------------------------------------------- /zhang2020/zhang2020.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(cowplot) 3 | 4 | ################################## 5 | # This is a replication of Figure 3 from the following paper 6 | # Zhang, Qiang, et al. "Trait–environment relationships differ between mixed‐species flocking and non‐flocking bird assemblages." 7 | # 2020. Ecology. https://doi.org/10.1002/ecy.3124 8 | 9 | # This figure uses simulated data and is not an exact copy. 10 | # It's meant for educational purposes only. 11 | ############################################# 12 | 13 | predictors = c('Elevation','NDVI','Arbor','Shrub','Herb','DBHmean','Heightmax','IVdomi','PC1','PC2') 14 | traits = c('Diet.FRU','Diet.INS','Diet.OMN','Subs.CAN','Subs.GRU','Subs.MID','Subs.UND','Breeding','Nest', 15 | 'Clutch','Body','Bill','Tarsus','Disperse','Habitat','Vertical') 16 | 17 | # A plot like this requires a data.frame row for every row x columnn combination in the figure. expand_grid does this 18 | # to make data for this example, but your data may need reshaping. 19 | 20 | plot_a_data = expand_grid(trait = traits, predictor = predictors) 21 | plot_b_data = expand_grid(trait = traits, predictor = predictors) 22 | 23 | # Mark some random locations for color coding by grey/red/blue, indicating no/positive/negative associationes. 24 | set.seed(1) 25 | plot_a_data$significance = 'none' 26 | plot_a_data$significance[sample.int(160, 3)] = 'negative' # 3 random entires choses out of a possible 160 27 | plot_a_data$significance[sample.int(160, 2)] = 'positive' # 28 | 29 | plot_b_data$significance = 'none' 30 | plot_b_data$significance[sample.int(160, 12)] = 'negative' # 3 random entires choses out of a possible 160 31 | plot_b_data$significance[sample.int(160, 20)] = 'positive' # 32 | 33 | ####################### 34 | # Correctly order everything using factors. 35 | # For trait and predictor this dictactes the order of the axis labels. For significance this dictates 36 | # the order the colors are assigned, which allows us to easily match the correct color using scale_fill_manual() 37 | 38 | # The ordered=TRUE arguments sets the order as the one specified in the levels argument. For predictor and trait this 39 | # ends up being the order specified in the first variables above. 40 | # Note that in the ggplot call trait is wrapped with fct_rev. Since the traits are ordered top->bottom, but ggplot 41 | # orders from bottom->top (ie. starting at 0) 42 | plot_a_data$significance = factor(plot_a_data$significance, levels=c('none','positive','negative')) 43 | plot_a_data$predictor = factor(plot_a_data$predictor, levels=predictors, ordered = TRUE) 44 | plot_a_data$trait = factor(plot_a_data$trait, levels=traits, ordered = TRUE) 45 | 46 | plot_b_data$significance = factor(plot_b_data$significance, levels=c('none','positive','negative')) 47 | plot_b_data$predictor = factor(plot_b_data$predictor, levels=predictors, ordered = TRUE) 48 | plot_b_data$trait = factor(plot_b_data$trait, levels=traits, ordered = TRUE) 49 | ####################### 50 | 51 | # Both x and y axis are discrete here. So to draw lines intercepting them we can 52 | # place geom_vline and geom_hline at the appropriate 0.5 intervals. 53 | # Note there are probably better visuals to distinguish the different trait groups... 54 | trait_grey_lines = c(10.5,11.5,12.5,14.5,15.5) 55 | trait_black_lines = c(0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5,9.5,13.5,16.5) 56 | 57 | plot_a = ggplot(plot_a_data, aes(x=predictor, y=fct_rev(trait), fill=significance)) + 58 | geom_tile() + 59 | scale_fill_manual(values=c('grey80','deepskyblue','red2')) + 60 | geom_hline(yintercept = trait_black_lines, color='black', size=1) + 61 | geom_hline(yintercept = trait_grey_lines, color='grey95', size=1) + 62 | geom_vline(xintercept = seq(0.5,11.5,1), color='black', size=1) + # predictor lines are at every interval 63 | scale_x_discrete(position = 'top', expand=c(0,0)) + # put the x axis text on top. expand = c(0,0) drops the buffer at the edges of the plot 64 | scale_y_discrete(expand=c(0,0)) + 65 | theme(axis.title = element_blank(), 66 | axis.text = element_text(color='black',size=18), 67 | axis.text.x = element_text(angle=90, hjust=0), 68 | axis.ticks = element_blank(), 69 | legend.position = 'none') 70 | 71 | plot_b = ggplot(plot_b_data, aes(x=predictor, y=fct_rev(trait), fill=significance)) + 72 | geom_tile() + 73 | scale_fill_manual(values=c('grey80','deepskyblue','red2')) + 74 | geom_hline(yintercept = trait_black_lines, color='black', size=1) + 75 | geom_hline(yintercept = trait_grey_lines, color='grey95', size=1) + 76 | geom_vline(xintercept = seq(0.5,11.5,1), color='black', size=1) + # predictor lines are at every interval 77 | scale_x_discrete(position = 'top', expand=c(0,0)) + # put the x axis text on top. expand = c(0,0) drops the buffer at the edges of the plot 78 | scale_y_discrete(expand=c(0,0)) + 79 | theme(axis.title = element_blank(), 80 | axis.text = element_text(color='black',size=18), 81 | axis.text.x = element_text(angle=90, hjust=0), 82 | axis.ticks = element_blank(), 83 | legend.position = 'none') 84 | 85 | final_figure = plot_grid(plot_a, plot_b, nrow=1, labels=c('(a)','(b)'), label_size = 22) 86 | 87 | ################################################# 88 | water_mark = 'Example figure for educational purposes only. Not made with real data.\n See github.com/sdtaylor/complex_figure_examples' 89 | 90 | final_figure = ggdraw(final_figure) + 91 | geom_rect(data=data.frame(xmin=0.05,ymin=0.05), aes(xmin=xmin,ymin=ymin, xmax=xmin+2.4,ymax=ymin+0.1),alpha=0.9, fill='grey90', color='black') + 92 | draw_text(water_mark, x=0.05, y=0.1, size=20, hjust = 0) 93 | 94 | save_plot('./zhang2020/zhang2020_final.png', plot=final_figure, base_height = 9, base_width = 12, dpi=50) 95 | -------------------------------------------------------------------------------- /zhang2020/zhang2020_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdtaylor/complex_figure_examples/bbfb102e71f6b9a6177e0845996594d962225dbc/zhang2020/zhang2020_final.png --------------------------------------------------------------------------------