Don’t worry about a thing ’cause every little thing gonna be alright (Three Little Birds, Bob Marley)

One of my favourite books is *The Computational Beauty of Nature* by Gary William Flake where there is a fantastic chapter about fractals in which I discovered the L-Systems.

L-Systems were conceived in 1968 by Aristide Lindenmayer, a Hungarian biologist, as a mathematical description of plant growth. Apart from the Wikipedia, there are many places on the Internet where you can read about them. If you are interested, don’t miss The Algorithmic Beauty of Plants, an awesome book by Przemysław Prusinkiewicz that you can obtain here for free.

Roughly speaking, a L-System is a very efficient way to *make drawings*. In its simplest way consists in two different actions: *draw a straigh line* and *change the angle*. This is just what you need, for example, to draw a square: draw a straigh line of any length, turn 90 degrees (without drawing), draw another straigh line of the same length, turn 90 degrees in the same direction, draw, turn and draw again. Denoting `F`

as the action of *drawing a line of length d* and `+`

as *turning 90 degrees right*, the whole process to draw a square can be represented as `F+F+F+F`

.

L-Systems are quite simple to program in R. You only need to substitute the rules iteratively into the axiom (I use `gsubfn`

function to do it) and split the resulting chain into parts with `str_extract_all`

, for example. The result is a set of very simple actions (*draw* or *turn*) that can be visualized with `ggplot`

and its path geometry. There are four important parameters in L-Systems:

- The seed of the drawing, called
**axiom** - The
*substitutions*to be applied iteratively, called**rules** - How many times to apply substitutions, called
**depth** **Angle**of each turning

For example, let’s define the next L-System:

**Axiom:**`F-F-F-F`

**Rule:**`F → F−F+F+FF−F−F+F`

The rule means that every `F`

must be replaced by `F−F+F+FF−F−F+F`

while `+`

means right turning and `-`

left one. After one iteration, the axiom is replaced by `F-F+F+FF-F-F+F-F-F+F+FF-F-F+F-F-F+F+FF-F-F+F-F-F+F+FF-F-F+F`

and iterating again, the new string is `F-F+F+FF-F-F+F-F-F+F+FF-F-F+F+F-F+F+FF-F-F+F+F-F+F+FF-F-F+FF-F+F+FF-F-F+F-F-F+F+FF-F-F+F-F-F+F+FF-F-F+F+F-F+F+FF-F-F+F-F-F+F+FF-F-F+F-F-F+F+FF-F-F+F+F-F+F+FF-F-F+F+F-F+F+FF-F-F+FF-F+F+FF-F-F+F-F-F+F+FF-F-F+F-F-F+F+FF-F-F+F+F-F+F+FF-F-F+F-F-F+F+FF-F-F+F-F-F+F+FF-F-F+F+F-F+F+FF-F-F+F+F-F+F+FF-F-F+FF-F+F+FF-F-F+F-F-F+F+FF-F-F+F-F-F+F+FF-F-F+F+F-F+F+FF-F-F+F-F-F+F+FF-F-F+F-F-F+F+FF-F-F+F+F-F+F+FF-F-F+F+F-F+F+FF-F-F+FF-F+F+FF-F-F+F-F-F+F+FF-F-F+F-F-F+F+FF-F-F+F+F-F+F+FF-F-F+F`

. As you can see, the length of the string grows exponentially. Converting last string into *actions*, produces this drawing, called Koch Island:

It is funny how different axioms and rules produce very different drawings. I have done a Shiny App to play with L-systems. Although it is quite simple, it has two interesting features I would like to undeline:

**Delay reactions**with`eventReactive`

to allow to set depth and angle values before refreshing the plot**Build a dynamic UI that reacts to user input**depending on the curve choosen

There are twelve curves in the application: Koch Island (and 6 variations), cuadratic snowflake, Sierpinsky triangle, hexagonal Gosper, quadratic Gosper and Dragon curve. These are their plots:

The definition of all these curves (axiom and rules) can be found in the first chapter of the Prusinkiewicz’s book. The *magic* comes when you modify angles and colors. These are some examples among the infinite number of possibilities that can be created:

I enjoyed a lot doing and playing with the app. You can try it here. If you do a nice drawing, please let me know in Twitter or dropping me an email. This is the code of the App:

** ui.R**:

library(shiny) shinyUI(fluidPage( titlePanel("Curves based on L-systems"), sidebarLayout( sidebarPanel( selectInput("cur", "Choose a curve:", c("","Koch Island", "Cuadratic Snowflake", "Koch Variation 1", "Koch Variation 2", "Koch Variation 3", "Koch Variation 4", "Koch Variation 5", "Koch Variation 6", "Sierpinsky Triangle", "Dragon Curve", "Hexagonal Gosper Curve", "Quadratic Gosper Curve"), selected = ""), conditionalPanel( condition = "input.cur != \"\"", uiOutput("Iterations")), conditionalPanel( condition = "input.cur != \"\"", uiOutput("Angle")), conditionalPanel( condition = "input.cur != \"\"", selectInput("lic", label = "Line color:", choices = colors(), selected = "black")), conditionalPanel( condition = "input.cur != \"\"", selectInput("bac", label = "Background color:", choices = colors(), selected = "white")), conditionalPanel( condition = "input.cur != \"\"", actionButton(inputId = "go", label = "Go!", style="color: #fff; background-color: #337ab7; border-color: #2e6da4")) ), mainPanel(plotOutput("curve", height="550px", width = "100%")) ) ))

** server.R**:

library(shiny) library(gsubfn) library(stringr) library(dplyr) library(ggplot2) library(rlist) shinyServer(function(input, output) { curves=list( list(name="Koch Island", axiom="F-F-F-F", rules=list("F"="F-F+F+FF-F-F+F"), angle=90, n=2, alfa0=90), list(name="Cuadratic Snowflake", axiom="-F", rules=list("F"="F+F-F-F+F"), angle=90, n=4, alfa0=90), list(name="Koch Variation 1", axiom="F-F-F-F", rules=list("F"="FF-F-F-F-F-F+F"), angle=90, n=3, alfa0=90), list(name="Koch Variation 2", axiom="F-F-F-F", rules=list("F"="FF-F-F-F-FF"), angle=90, n=4, alfa0=90), list(name="Koch Variation 3", axiom="F-F-F-F", rules=list("F"="FF-F+F-F-FF"), angle=90, n=3, alfa0=90), list(name="Koch Variation 4", axiom="F-F-F-F", rules=list("F"="FF-F--F-F"), angle=90, n=4, alfa0=90), list(name="Koch Variation 5", axiom="F-F-F-F", rules=list("F"="F-FF--F-F"), angle=90, n=5, alfa0=90), list(name="Koch Variation 6", axiom="F-F-F-F", rules=list("F"="F-F+F-F-F"), angle=90, n=4, alfa0=90), list(name="Sierpinsky Triangle", axiom="R", rules=list("L"="R+L+R", "R"="L-R-L"), angle=60, n=6, alfa0=0), list(name="Dragon Curve", axiom="L", rules=list("L"="L+R+", "R"="-L-R"), angle=90, n=10, alfa0=90), list(name="Hexagonal Gosper Curve", axiom="L", rules=list("L"="L+R++R-L--LL-R+", "R"="-L+RR++R+L--L-R"), angle=60, n=4, alfa0=60), list(name="Quadratic Gosper Curve", axiom="-R", rules=list("L"="LL-R-R+L+L-R-RL+R+LLR-L+R+LL+R-LR-R-L+L+RR-", "R"="+LL-R-R+L+LR+L-RR-L-R+LRR-L-RL+L+R-R-L+L+RR"), angle=90, n=2, alfa0=90)) output$Iterations <- renderUI({ if (input$cur!="") curve=list.filter(curves, name==input$cur) else curve=list.filter(curves, name=="Koch Island") iterations=list.select(curve, n) %>% unlist numericInput("ite", "Depth:", iterations, min = 1, max = (iterations+2)) }) output$Angle <- renderUI({ curve=list.filter(curves, name==input$cur) angle=list.select(curve, angle) %>% unlist numericInput("ang", "Angle:", angle, min = 0, max = 360) }) data <- eventReactive(input$go, { curve=list.filter(curves, name==input$cur) axiom=list.select(curve, axiom) %>% unlist rules=list.select(curve, rules)[[1]]$rules alfa0=list.select(curve, alfa0) %>% unlist for (i in 1:input$ite) axiom=gsubfn(".", rules, axiom) actions=str_extract_all(axiom, "\\d*\\+|\\d*\\-|F|L|R|\\[|\\]|\\|") %>% unlist points=data.frame(x=0, y=0, alfa=alfa0) for (i in 1:length(actions)) { if (actions[i]=="F"|actions[i]=="L"|actions[i]=="R") { x=points[nrow(points), "x"]+cos(points[nrow(points), "alfa"]*(pi/180)) y=points[nrow(points), "y"]+sin(points[nrow(points), "alfa"]*(pi/180)) alfa=points[nrow(points), "alfa"] points %>% rbind(data.frame(x=x, y=y, alfa=alfa)) -> points } else{ alfa=points[nrow(points), "alfa"] points[nrow(points), "alfa"]=eval(parse(text=paste0("alfa",actions[i], input$ang))) } } return(points) }) output$curve <- renderPlot({ ggplot(data(), aes(x, y)) + geom_path(color=input$lic) + coord_fixed(ratio = 1) + theme(legend.position="none", panel.background = element_rect(fill=input$bac), panel.grid=element_blank(), axis.ticks=element_blank(), axis.title=element_blank(), axis.text=element_blank()) }) })

Hey,

This looks really cool. For some reason I can’t get the demo app to run. The link is going to . Thanks in advance for any pointers!

These Lindenmayer systems are fun and provocative, nice app. For those wanting to draw other systems, there is an R package LindenmayeR which will accept any set of instructions and produce a plot using grid graphics. Note: I am the author of LindenmayeR.

As published, this code will fail

the output$Iterations renderUI function is incomplete … missing default definition/value for ‘iterations’

output$Iterations > …. missing statement for ‘iterations’ …. here <% unlist}

numericInput(“ite”, “Depth:”, iterations, min = 1, max = (iterations+2))

})

Got it working …. Advise using {} for

if … else clauses to avoid parsing ambiguities

I understand from you that there are some missing brackets {}

Could you please write out for me the exact full corrected code part that has to come in place of the code below. Thanks in advance for your help.

output$Iterations % unlist

numericInput(“ite”, “Depth:”, iterations, min = 1, max = (iterations+2))

})

I really enjoyed your post. Thank you for taking the time to write such a nice summary!