Customizing fractal trees

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.

# install.packages('devtools')
library(devtools)
install_github('sergiocostafh/fractalforest')

Load fractalforest package.

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.

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).

tree_string <- iterate_lsystem(init = "0", rules = binary_tree_rules, n = 5)
tree_string
#> [1] "1[-1[-1[-1[-1[-0]+0]+1[-0]+0]+1[-1[-0]+0]+1[-0]+0]+1[-1[-1[-0]+0]+1[-0]+0]+1[-1[-0]+0]+1[-0]+0]+1[-1[-1[-1[-0]+0]+1[-0]+0]+1[-1[-0]+0]+1[-0]+0]+1[-1[-1[-0]+0]+1[-0]+0]+1[-1[-0]+0]+1[-0]+0"

Build the tree data.frame from the string, and visualize the output.

tree <- build_tree(string = tree_string, angle = 15)
head(tree)
#> # A tibble: 6 × 5
#>     from_x     to_x from_y  to_y type  
#>      <dbl>    <dbl>  <dbl> <dbl> <chr> 
#> 1 0        1.04e-17  0     0.170 branch
#> 2 1.04e-17 4.39e- 2  0.170 0.333 branch
#> 3 4.39e- 2 1.29e- 1  0.333 0.480 branch
#> 4 1.29e- 1 2.49e- 1  0.480 0.600 branch
#> 5 2.49e- 1 3.95e- 1  0.600 0.685 branch
#> 6 3.95e- 1 5.59e- 1  0.685 0.729 leaf

Visualize the tree.

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.

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.

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.

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).

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.

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.

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,

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.

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.

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.

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.

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')