Skip to contents

Generate the data specifying a flametree

Usage

flametree_grow(
  seed = 286,
  time = 6,
  scale = c(0.6, 0.8, 0.9),
  angle = c(-10, 10, 20),
  split = 2,
  trees = 1,
  seg_col = spark_linear(tree = 2, time = 1),
  seg_wid = spark_decay(time = 0.3, multiplier = 5, constant = 0.1),
  shift_x = spark_random(multiplier = 3),
  shift_y = spark_nothing(),
  prune = spark_nothing()
)

Arguments

seed

Integer seed for the random number generator

time

Number of generations to run the iterative process

scale

Vector of possible values for the "size rescaling" at each iteration

angle

Vector of possible angle changes (in degrees) at each iteration

split

Number of splits at each time point

trees

Number of trees to generate

seg_col

Spark function to control the segment colour

seg_wid

Spark function to control the segment width

shift_x

Spark function to control horizontal jitter

shift_y

Spark function to control vertical jitter

prune

Spark function to control the probability that a shoot stops growing

Value

The output of flametree_grow() is a tibble with the following columns: coord_x, coord_y, id_tree, id_time, id_path, id_leaf, id_pathtree, id_step, seg_deg, seg_len, seg_col, and seg_wid. Each row in the tibble specifies a single point: every curved segment is defined by three such rows.

The two "coord" columns are numeric variables that specify the location of the point itself. The "id" columns are used as indicators of various kinds. The id_tree column contains numbers specifying which tree each point belongs to, and similarly the id_time column is a numeric identifier that specifies the time point at which the point was generated (i.e., the iteration of the generative process). The id_step column contains a number (0, 1, or 2) indicating whether the point is the first point, the midpoint, or the end point of the relevant curved segment in a tree. In addition, there are two identifier columns used to denote the segments themselves. The id_path column is numeric, and assigns value 1 to the "first" segment (i.e., the lowest part of the tree trunk) for every tree, with values increasing numerically for each subsequent segment. The id_leaf column is TRUE for terminal segments: those grown in the final iteration of the process, and any earlier segment whose growth was stopped early by the prune argument. Values for id_path will uniquely identify a segment within a tree, but when multiple trees are generated there will be multiple segments that have the same id_path value. If a unique identifier across trees is needed, use the id_pathtree column, which is a character vector constructed by pasting the id_path and id_tree values into a string, with an underscore as the separator character.

In addition to the two coordinate columns and the six identifier columns, the data generated by flametree_grow() contains four "seg" columns that are intended to map onto different visual characteristics of a plot. The seg_deg column specifies the orientation of the segment, whereas seg_len denotes the length of the segment, seg_col specifies the colour (as a numeric value that could be interpreted by a palette), and seg_wid specifies the width of the segment. Note that this information use used differently by the flametree_plot() function, depending on what style of plot is generated.

Details

Generative art created with flametree is a visualisation of a data structure created by calling flametree_grow(). The underlying algorithm is an iterative branching process: each tree starts out as a single vertical segment, to which multiple new segments are added at the end of the first iteration. Over multiple iterations this creates a tree-like structure.

The user can control how this iterative process unfolds. By setting the seed argument the random number generator is reset using set.seed(). The trees argument specifies the number of trees to create using this process, the time argument specifies how many iterations of the branching process will be run (at least two), and the split argument specifies how many new segments (at least one) will be created each time a branching occurs. Setting split = 1 disables branching altogether, producing a single winding path per tree rather than a branching structure.

Because the number of segments grows by a factor of split at every one of the time iterations, the size of the resulting data grows exponentially: roughly trees * split ^ time segments are created in total (each represented by three rows in the output). Large values of time and/or split (especially in combination with a large number of trees) can therefore produce very large data frames and may exhaust available memory; flametree_grow() will warn when this is likely to happen.

When a new segment is created, its size and orientation are controlled by the scale and angle arguments. The scale argument takes a vector of at least two positive numbers. One of these numbers is selected at random whenever a new segment is created, and the length of the new segment is equal to the length of the "parent" segment from which it was created, multiplied by this scaling factor. The orientation of the new segment is controlled by the angle argument in an analogous way. Every time a new segment is generated, one of these angles (interpreted in degrees, not radians) is selected at random. The orientation of the new segment is equal to the orientation of the parent segment plus the sampled angle. Like the scale argument, angle must contain at least two values.

The remaining arguments (seg_col, seg_wid, shift_x, shift_y, and prune) all take functions as their input, and are used to control how the colours (seg_col) and width (seg_wid) of the segments are created, the horizontal (shift_x) and vertical (shift_y) displacement of the trees, and the probability that growth of a given shoot is pruned (prune). Functions passed to these arguments take four inputs: coord_x, coord_y, id_tree, and id_time. Any function that takes these variables as input can be used for this purpose. However, as a convenience, four "spark" functions are provided that can be used to create functions that are suitable for this purpose: spark_linear(), spark_decay(), spark_random(), and spark_nothing().

The prune argument is interpreted as a probability: at each iteration, every newly created shoot has its endpoint passed to the prune function, and is pruned (i.e., stops growing any further) with that probability. The segment that shoot has already grown is still included in the output and is marked as a leaf node (see id_leaf below), but no descendants are grown from it. The default, spark_nothing(), always returns 0 and therefore disables pruning entirely. A constant pruning probability can be obtained with, for example, spark_linear(constant = 0.2), and space- or time-varying pruning can be created using spark_decay() or a custom function.

These functions are documented in their own help files. To give an example, the default behaviour of flametree_grow() adds a random horizontal displacement to each tree to give the impression of multiple trees growing side by side. To suppress this horizontal displacement, set shift_x = spark_nothing().

Examples

# flametree data structure with default parameters
flametree_grow()
#> # A tibble: 381 × 12
#>    coord_x coord_y id_tree id_time id_path id_leaf id_pathtree id_step seg_deg
#>      <dbl>   <dbl>   <int>   <int>   <int> <lgl>   <chr>         <int>   <dbl>
#>  1    1.48    0          1       1       1 FALSE   1_1               0      90
#>  2    1.48    0.5        1       1       1 FALSE   1_1               1      90
#>  3    1.48    1          1       1       1 FALSE   1_1               2      90
#>  4    1.48    1          1       2       2 FALSE   1_2               0      80
#>  5    1.48    1.3        1       2       2 FALSE   1_2               1      80
#>  6    1.59    1.59       1       2       2 FALSE   1_2               2      80
#>  7    1.48    1          1       2       3 FALSE   1_3               0     110
#>  8    1.48    1.4        1       2       3 FALSE   1_3               1     110
#>  9    1.21    1.75       1       2       3 FALSE   1_3               2     110
#> 10    1.59    1.59       1       3       4 FALSE   1_4               0      70
#> # ℹ 371 more rows
#> # ℹ 3 more variables: seg_len <dbl>, seg_col <dbl>, seg_wid <dbl>

# setting time = 10 runs the generative process
# longer resulting in a table with more rows
flametree_grow(time = 10)
#> # A tibble: 6,141 × 12
#>    coord_x coord_y id_tree id_time id_path id_leaf id_pathtree id_step seg_deg
#>      <dbl>   <dbl>   <int>   <int>   <int> <lgl>   <chr>         <int>   <dbl>
#>  1   0.391    0          1       1       1 FALSE   1_1               0      90
#>  2   0.391    0.5        1       1       1 FALSE   1_1               1      90
#>  3   0.391    1          1       1       1 FALSE   1_1               2      90
#>  4   0.391    1          1       2       2 FALSE   1_2               0      80
#>  5   0.391    1.3        1       2       2 FALSE   1_2               1      80
#>  6   0.495    1.59       1       2       2 FALSE   1_2               2      80
#>  7   0.391    1          1       2       3 FALSE   1_3               0     110
#>  8   0.391    1.4        1       2       3 FALSE   1_3               1     110
#>  9   0.118    1.75       1       2       3 FALSE   1_3               2     110
#> 10   0.495    1.59       1       3       4 FALSE   1_4               0      70
#> # ℹ 6,131 more rows
#> # ℹ 3 more variables: seg_len <dbl>, seg_col <dbl>, seg_wid <dbl>

# default behaviour is to randomly displace trees
# by random horizontal perturbation: to switch this
# off use the spark_nothing() function
flametree_grow(shift_x = spark_nothing())
#> # A tibble: 381 × 12
#>      coord_x coord_y id_tree id_time id_path id_leaf id_pathtree id_step seg_deg
#>        <dbl>   <dbl>   <int>   <int>   <int> <lgl>   <chr>         <int>   <dbl>
#>  1  0           0          1       1       1 FALSE   1_1               0      90
#>  2  0           0.5        1       1       1 FALSE   1_1               1      90
#>  3  0           1          1       1       1 FALSE   1_1               2      90
#>  4  0           1          1       2       2 FALSE   1_2               0      80
#>  5  1.84e-17    1.3        1       2       2 FALSE   1_2               1      80
#>  6  1.04e- 1    1.59       1       2       2 FALSE   1_2               2      80
#>  7  0           1          1       2       3 FALSE   1_3               0     110
#>  8  2.45e-17    1.4        1       2       3 FALSE   1_3               1     110
#>  9 -2.74e- 1    1.75       1       2       3 FALSE   1_3               2     110
#> 10  1.04e- 1    1.59       1       3       4 FALSE   1_4               0      70
#> # ℹ 371 more rows
#> # ℹ 3 more variables: seg_len <dbl>, seg_col <dbl>, seg_wid <dbl>