Introduction¶

Few times ago, I supported some people on a forum and there were a question regarding a problem with the library turtle. I decided to take this opportunity to play a bit with this library. It gaves me the opportunity to mix this with something I'm often struggling with. This is recursive functions. To train on both, the best was to design fractals !

NB : Turtle doesn't allow to save the canvas as png so I saved a ps file or render it in a notebook. This has been converted afterward only. If you want to play with it, just copy the code to any py script

Fractal 1 : Triangle Spiral¶

This fractal is the one from the question on the forum. The code is quite simple and doesn't really require a recursive function but for the exercice I made it recursive.

The purpose is to use equilateral triangles and take only half of the base to create a part of the spiral. Each triangle is then turned by 30 degree and smaller to have the side equal to the previous height of the triangle and this until the side is above a threshold.

In [ ]:
import math
import turtle

from itertools import cycle
In [ ]:
def draw(l, color, threshold=5):
if l <= threshold:
return
else:
cursor.pencolor(color)
cursor.forward(l)
cursor.left(120)
cursor.width(3)
cursor.pencolor("blue")
cursor.forward(l / 2)
cursor.pencolor(color)
cursor.width(1)
cursor.forward(l / 2)
cursor.left(120)
cursor.forward(l)
cursor.left(150)
draw(l*math.sqrt(3)/2, next(gen))

gen = cycle(["red", "green"])

cursor = turtle.Turtle()
cursor.speed(5)
draw(200, next(gen))
cv = turtle.getcanvas().postscript(file="triangle_spiral.ps", colormode='color')
cursor.done()
Result :¶ Fractal 2 : Recursive Tree¶

This one is well known. You take a straight line and then split in n branches. Those n branches are "travelled" and recursively splitted again. Of course at each depth the lenght is reduced and the criterion to stop the iteration is the length of the segment.

In this implementation, you can play with the angle between branches and the number of branches (+ of course the initial length of a branch)

In [ ]:
def draw(l, branches=2, angle=30, threshold=5, factor=0.5):
if l <= threshold:
return
else:
cursor.forward(l)
cursor.left(angle*(branches-1)/2)
for _ in range(branches):
draw(l*factor, branches, angle, threshold, factor)
cursor.right(angle)
cursor.left(angle*(branches+1)/2)  # +1 to compensate the last cursor right
cursor.backward(l)

cursor = turtle.Turtle()
cursor.speed(30)

cursor.right(90)
cursor.forward(200)
cursor.left(180)

draw(250, branches=2, angle=60, threshold=3, factor=0.5)
cv = turtle.getcanvas().postscript(file="fractal_tree.ps", colormode='color')
turtle.done()
Result :¶ Fractal 3 : 2D Merger Sponge¶

This one is also a well known Fractal. Given a Square, split it in 9 sub squares and draw a square a the one fof the center. Then for each other 8 square, repeat !

In [ ]:
def make_square(l):
for _ in range(4):
cursor.forward(l)
cursor.left(90)

def draw(l, start_x, start_y, threshold=5):
if l <= threshold:
return
else:
cursor.penup()
cursor.setpos(start_x + l/3, start_y + l/3)
cursor.pendown()
make_square(l/3)
draw(l / 3, start_x, start_y)
draw(l / 3, start_x + l/3, start_y)
draw(l / 3, start_x + 2*l/3, start_y)
draw(l / 3, start_x + 2*l/3, start_y + l/3)
draw(l / 3, start_x + 2*l/3, start_y + 2*l/3)
draw(l / 3, start_x + l/3, start_y + 2*l/3)
draw(l / 3, start_x, start_y + 2*l/3)
draw(l / 3, start_x, start_y + l/3)

cursor = turtle.Turtle()
cursor.speed("fastest")

w, h = turtle.window_width(), turtle.window_height()

p = min(w, h)/2 - 100

cursor.penup()
cursor.setpos(-p, -p)
cursor.pendown()
make_square(2*p)

draw(2*p, start_x=-p, start_y=-p, threshold=10)
cv = turtle.getcanvas().postscript(file="merger_sponge.ps", colormode='color')
turtle.done()
Result :¶ Fractal 4 : Sierpinski Triangle¶

If you do the same with a Triangle, by changing some factor, reverseing some triangle you end up with the well known Sierpinski Triangle

In [ ]:
def make_triangle(l, reversed=False):
if not reversed:
for _ in range(3):
cursor.forward(l)
cursor.left(120)
else:
for _ in range(3):
cursor.forward(l)
cursor.right(120)

def draw(l, start_x, start_y, threshold=5, depth=0):
factor = math.sqrt(3/4)
if l <= threshold:
return
else:
cursor.penup()
cursor.setpos(start_x + l/4, start_y + factor*l/2)
cursor.pendown()
cursor.fillcolor(colors[depth])
cursor.begin_fill()
make_triangle(l/2, reversed=True)
cursor.end_fill()
draw(l / 2, start_x, start_y, threshold=threshold, depth=depth+1)
draw(l / 2, start_x + l/2, start_y, threshold=threshold, depth=depth+1)
draw(l / 2, start_x + l/4, start_y + factor*l/2, threshold=threshold, depth=depth+1)

cursor = turtle.Turtle()
cursor.speed("fastest")

w, h = turtle.window_width(), turtle.window_height()

p = min(w, h)/2 - 100

colors = ["Red", "Orange Red", "Dark Orange", "Orange", "Light Goldenrod", "Light Goldenrod Yellow", "Light Yellow"] \
+ ["White"] * 100 # just to avoid index error

cursor.penup()
cursor.setpos(-p, -p)
cursor.pendown()
make_triangle(2*p)

draw(2*p, start_x=-p, start_y=-p, threshold=20)
cv = turtle.getcanvas().postscript(file="Sierpinski_triangle.ps", colormode='color')
turtle.done()
Result :¶ Fractal 5 : Von Koch Curve¶

This one is more tricky to create. Given a line, split it in 3 equal pieces. Take the middle one and replace it by 2 of the same length to have a triangle. Iterate again over eahc new linees and after few iterations you have the Von Koch Curve !

The problem with this one is that you should not remove lines so the idea is to draw only when you reach the iteration stop. This tooks me time to get it :)

In [ ]:
def draw(l, threshold=5):
if l <= threshold:
cursor.forward(l)
else:
draw(l / 3)
cursor.left(60)
draw(l / 3)
cursor.right(120)
draw(l / 3)
cursor.left(60)
draw(l / 3)

cursor = turtle.Turtle()
cursor.speed(30)

w, h = turtle.window_width(), turtle.window_height()
border = 10

cursor.penup()
cursor.setpos(-w/2+border, -h/4)
cursor.pendown()

draw(w - 2 * border)
cv = turtle.getcanvas().postscript(file="von_koch_curve.ps", colormode='color')
turtle.done()
Result :¶ Fractal 6 : Dragon Curve¶

This fractal was fun to do because it makes me discover the L-system. In addition, it is impressive to see where this shape is coming from. I let you check this video if you want to know more about it !

It's also a fractal which requires 2 functions calling each other.

In [ ]:
def x(n):
if n == 0:
return
x(n-1)
cursor.right(90)
y(n-1)
cursor.forward(l)
# cursor.right(90)

def y(n):
if n == 0:
return
cursor.forward(l)
x(n-1)
cursor.left(90)
y(n-1)
# cursor.left(90)

cursor = turtle.Turtle()
cursor.speed(30)

l=3
depth = 12

cursor.forward(l)
x(depth)
cv = turtle.getcanvas().postscript(file="dragon_curve.ps", colormode='color')
turtle.done()
Result :¶ Fractal 7 : Koch Curve¶

Launched with L-system, I also implemented the Koch Curve which is different from the Von Koch Curve :)

In [ ]:
def draw(n):
if n == 0:
cursor.forward(l)
return
draw(n-1)
cursor.right(90)
draw(n-1)
cursor.left(90)
draw(n-1)
cursor.left(90)
draw(n-1)
cursor.right(90)
draw(n-1)

cursor = turtle.Turtle()
cursor.speed(30)

w, h = turtle.window_width(), turtle.window_height()
border = 10

cursor.penup()
cursor.setpos(-w/4, h/4)
cursor.pendown()

l=3
depth = 5

draw(depth)
cv = turtle.getcanvas().postscript(file="koch_curve.ps", colormode='color')
turtle.done()
Result :¶ Conclusion¶

In this motebook, I played a bit with turtle which was a good library to discover. I also learn a some things about recursive function and L-system so I'm quite pleased of this small project.