--- title: "Customizing fractal trees" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{customizing-fractal-trees} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(fractalforest) ``` This vignette presents some features of the `fractalforest` package for customizing fractal trees. ## Install and load developer version of the package Install developer version. ```{r, eval = FALSE} # install.packages('devtools') library(devtools) install_github('sergiocostafh/fractalforest') ``` Load fractalforest package. ```{r} library(fractalforest) ``` ## Building a simple tree The fractalforest package uses the Lindenmayer system (L-system) to generate fractal trees. By applying recursive production rules, the package simulates natural branching patterns, allowing for the creation of realistic and customizable tree structures. We will start by defining the L-system rules for constructing a simple binary tree, and then we will go into the details. ```{r} binary_tree_rules <- data.frame(inp = c("0", "1"), out = c("1[-0]+0", "1"), stringsAsFactors = FALSE) ``` Use the `iterate_lsystem` function to build the string based on the rules (`rules`), the axiom (`init`) and the number of iterations (`n`). ```{r} tree_string <- iterate_lsystem(init = "0", rules = binary_tree_rules, n = 5) tree_string ``` Build the tree data.frame from the string, and visualize the output. ```{r} tree <- build_tree(string = tree_string, angle = 15) head(tree) ``` Visualize the tree. ```{r, results='hide', out.width='100%', fig.width=7, fig.height=5, dpi = 200} plot_tree(tree) ``` ## Exploring rules To set the rules, the following characters are recognized: "+" Turn by positive angle. "-" Turn by negative angle. "[" Save current position and heading. "]" Restore saved position and heading (allows one to go back). ")" Reduces length and diameters of branches. "(" Increment length and diameters of branches. Any other characters in the rule string will be recognized as "a move forward, drawing as you go" instruction. This means that they must be declared in the `inp` column of the rules data frame, and also its corresponding substitution rule (`out` column). The following example demonstrates the use of parentheses to reduce/increase branch lengths. ```{r, results='hide', out.width='100%', fig.width=7, fig.height=5, dpi = 200} binary_tree_rules <- data.frame(inp = c("0", "1"), out = c("1[-(0)]+(0)", "1"), stringsAsFactors = FALSE) tree_string <- iterate_lsystem(init = "0", rules = binary_tree_rules, n = 5) tree <- build_tree(string = tree_string, angle = 15) plot_tree(tree) ``` ## The `iterate_lsystem` function The `n` argument controls the number of iterations do build the plant. A large `n` value can be time and memory consuming, so be careful. ```{r, message = F, warning=FALSE, results='hide', out.width='100%', fig.width=7, fig.height=5, dpi = 200} library(ggplot2) library(patchwork) library(dplyr) trees_n <- lapply(1:8, function(x) { iterate_lsystem(init = '0', rules = binary_tree_rules, n = x) %>% build_tree(string = ., angle = 15) %>% plot_tree()+ labs(title = paste0('n = ', x)) }) wrap_plots(trees_n, nrow = 2, heights = c(1,1)) ``` Below are some other rules for simulating plants with different morphologies. ```{r, message = F, warning=FALSE, results='hide', out.width='100%', fig.width=7, fig.height=5, dpi = 200} alternate_bush_rules <- data.frame(inp = c("0", "1"), out = c("1[+1][--(0)]+1-1[+++(0)]-(0)", "1"), stringsAsFactors = FALSE) tree_string <- iterate_lsystem(init = "0", rules = alternate_bush_rules, n = 6) tree1 <- build_tree(string = tree_string, angle = 10) %>% plot_tree()+ labs(title = 'alternate tree') arrow_weed_rules <- data.frame(inp = c("0", "1"), out = c("1[+(0)][-(0)](10)", "1"), stringsAsFactors = FALSE) tree_string <- iterate_lsystem(init = "0", rules = arrow_weed_rules, n = 6) tree2 <- build_tree(string = tree_string, angle = 30) %>% plot_tree()+ labs(title = 'arrow weed') twiggy_weed_rules <- data.frame(inp = c("0", "1"), out = c("1[-(0)]1[-(0)]+(0)", "1"), stringsAsFactors = FALSE) tree_string <- iterate_lsystem(init = "0", rules = twiggy_weed_rules, n = 6) tree3 <- build_tree(string = tree_string, angle = 25) %>% plot_tree()+ labs(title = 'twiggy weed') tree1 + tree2 + tree3 ``` ## Predefined models The fractalforest package has some predefined models for constructing fractal trees from L-system rules. These models can be accessed from the `fractal_tree_model` function. The function takes a mandatory argument (`tree_model`) and an optional one (`n_iter`) that controls the number of iterations for string construction. There are 10 pre-implemented tree templates, as follows (default `n_iter` between parentheses): -1 or "binary_tree" (6); -2 or "alternate_tree" (5); -3 or "arrow_weed" (5); -4 or "twiggy_weed" (5); -5 or "stochastic_fuzzy_weed" (4); -6 or "crooked_binary_tree" (6); -7 or "crooked_alternate_tree" (5); -8 or "crooked_arrow_weed" (5); -9 or "crooked_twiggy_weed" (5); -10 or "crooked_stochastic_fuzzy_weed" (4). ```{r, message = F, warning=FALSE, results='hide', out.width='100%', fig.width=7, fig.height=5, dpi = 200} fractal_tree_model(5) %>% build_tree() %>% plot_tree() ``` The crooked models add random tortuosity to the tree models. For this to take effect, it is necessary to set the `randomness = TRUE` argument in the `build_tree` function. All predefined tree models are presented below. ```{r, include = FALSE, message = F, warning=FALSE, results='hide', out.width='100%', fig.width=7, fig.height=5, dpi = 200} for(i in 1:10){ if(i > 5){rdns = TRUE} else{rdns = FALSE} t <- fractal_tree_model(i) %>% build_tree(randomness = rdns) %>% mutate(model = i) if(i == 1){ ts <- t } else{ ts <- bind_rows(ts, t) } } ts <- ts %>% mutate(model = paste0('model = ',model)) ts <- ts %>% mutate(model = factor(model, levels = unique(ts$model), ordered = T)) library(sf) df_geom <- ts[1:4] %>% dplyr::mutate(ID = dplyr::row_number()) %>% dplyr::select(5,1,3,2,4) rows <- df_geom %>% split(., seq(nrow(.))) lines <- lapply(rows, function(row) { lmat <- matrix(unlist(row[2:5]), ncol = 2, byrow = TRUE) sf::st_linestring(lmat) }) lines <- sf::st_sfc(lines) lines_sf <- sf::st_sf('ID' = df_geom$ID, 'geometry' = lines) plot_df <- ts %>% dplyr::mutate(geometry = sf::st_buffer(lines_sf, dist = .00001, endCapStyle = 'SQUARE') %>% dplyr::pull(geometry)) %>% sf::st_as_sf() %>% dplyr::group_by(type, model) %>% dplyr::summarise() ggplot2::ggplot()+ ggplot2::geom_sf(data = plot_df)+ ggplot2::theme_void()+ facet_wrap(~model, nrow = 2) ``` ## The `build_tree` function The purpose of this function is to build a data frame that contains the coordinate matrix and some other information about the plant, such as the differentiation between branches and leaves and the diameters along the branches. The `h_reduction` parameter in the `build_tree` function, controls the reduction factor of the length. ```{r, message = F, warning=FALSE, results='hide', out.width='100%', fig.width=10, fig.height=5, dpi = 200} tree1 <- build_tree(string = tree_string, angle = 15, h_reduction = .7) %>% plot_tree()+ labs(title = 'h_reduction = .7') tree2 <- build_tree(string = tree_string, angle = 15) %>% plot_tree()+ labs(title = 'h_reduction = .61803 (default)') tree3 <- build_tree(string = tree_string, angle = 15, h_reduction = .5) %>% plot_tree()+ labs(title = 'h_reduction = .5') tree1 + tree2 + tree3 ``` The `randomness` argument, as the name suggests, allows one to apply randomness to the angles and lengths of the plant. It must be set to `TRUE` if its effects are desired The amount of randomness can be controlled by the `angle_cv` and `length_cv` arguments, which define the angle and length variation coefficients, respectively. By default, these two arguments are set to 0.1, ```{r, message = F, warning=FALSE, results='hide', out.width='100%', fig.width=10, fig.height=7, dpi = 200} tree1 <- build_tree(string = tree_string, angle = 15, randomness = TRUE) %>% plot_tree()+ labs(title = 'length_cv = .1 (default)') tree2 <- build_tree(string = tree_string, angle = 15, randomness = TRUE, length_cv = .5) %>% plot_tree()+ labs(title = 'length_cv = .5') tree3 <- build_tree(string = tree_string, angle = 15, randomness = TRUE) %>% plot_tree()+ labs(title = 'angle_cv = .1 (default)') tree4 <- build_tree(string = tree_string, angle = 15, randomness = TRUE, angle_cv = .5) %>% plot_tree()+ labs(title = 'angle_cv = .5') (tree1 + tree2) / (tree3 + tree4) ``` It's possible to set the plant height setting the `height` parameter in the `build_tree` function. It will affect the resulting coordinates in the output. To visualize the y axis with the height measure, `ggplot2` functions can be used together with `plot_tree` function. ```{r, message = F, warning=FALSE, results='hide', out.width='100%', fig.width=7, fig.height=5, dpi = 200} tree <- build_tree(string = tree_string, angle = 15, height = 10) plot_tree(tree)+ labs(x = '', y = 'Height (m)')+ theme_bw() ``` As well as the `height`, in the `build_tree` function it is possible to define the `diameter` of branches, whose reduction is defined by the parentheses in the rules declared in the `iterate_lsystem` function, in the same way as lengths. The argument `d_reduction` controls the reduction factor for the diameters. The density of leaves in the tree crown can be customized by the `leaf_size` argument. ```{r, message = F, warning=FALSE, results='hide', out.width='100%', fig.width=10, fig.height=5, dpi = 200} p1 <- build_tree(string = tree_string, angle = 15, height = 10, diameter = 20) %>% plot_tree(d_col = diameter)+ labs(title = 'leaf_size = NULL (default)') p2 <- build_tree(string = tree_string, angle = 15, height = 10, diameter = 20, leaf_size = 10) %>% plot_tree(d_col = diameter,)+ labs(title = 'leaf_size = 10') p3 <- build_tree(string = tree_string, angle = 15, height = 10, diameter = 20, leaf_size = 20) %>% plot_tree(d_col = diameter)+ labs(title = 'leaf_size = 20') p1 + p2 + p3 ``` ## Visualizing the plant and the `plot_tree` function This function was implemented on top of `ggplot2` and for this reason it can be used together with several functions of the package. To visualize the diameters along the plant, the diameter column must be declared in the `d_col` argument. ```{r, message = F, warning=FALSE, results='hide', out.width='100%', fig.width=10, fig.height=5, dpi = 200} tree1 <- build_tree(string = tree_string, angle = 15, height = 5, diameter = 7, d_reduction = .7) %>% plot_tree(d_col = diameter)+ labs(title = 'd_reduction = .7') tree2 <- build_tree(string = tree_string, angle = 15, height = 5, diameter = 7) %>% plot_tree(d_col = diameter)+ labs(title = 'd_reduction = .61803 (default)') tree3 <- build_tree(string = tree_string, angle = 15, height = 5, diameter = 7, d_reduction = .5) %>% plot_tree(d_col = diameter)+ labs(title = 'd_reduction = .5') tree1 + tree2 + tree3 ``` Colors can be used to differentiate leaves and branches, by declaring the `leaf_color` and `branch_color` arguments. The `build_tree` function identifies as leaves all the branches positioned at the ends of the plant. This information is stored in the "type" column, which can be used to assign colors. ```{r, message = F, warning=FALSE, results='hide', out.width='100%', fig.width=7, fig.height=5, dpi = 200} library(patchwork) tree_string <- iterate_lsystem(init = "0", rules = binary_tree_rules, n = 10) tree <- build_tree(string = tree_string, angle = 15, height = 10, diameter = 20) plot_tree(tree, d_col = diameter, branch_color = 'lightsalmon4', leaf_color = 'darkgreen') ```