This simple program can be used to compute and display a 2D fractal tree.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | #/usr/bin/env python
import Tkinter, sys
from math import sqrt, pi, atan2
from cmath import exp
from optparse import OptionParser
def get_parent_indices(i, j, nbran):
return (i-1, j//nbran)
class fractal_tree:
def __init__(self, ratio=0.8, angle=pi/2., ngen=3, nbran=2):
"""
ratio: ratio of branch length with respect to previous generation
angle: fan angle, how wide spread the branches will be (0<angle<2*pi)
ngen: number of generations.
nbran: number of new branches.
"""
# coords in complex notation
self.ratio = ratio
self.angle = angle
self.ngen = ngen
self.nbran = nbran
# the root is at (0,0)
# the top of the trunk is at position (0,1), this will be the
# starting node
self.pts2xy = {(-1,0): 0j, (0,0): 1j}
self.xmax, self.ymax = 0, 1
alpha_0 = angle/2.
delta_alpha = angle / float(nbran-1)
for i in range(1, ngen):
for j in range(nbran**i):
# child
ij = (i,j)
# parent
kl = get_parent_indices(i, j, nbran)
k, l = kl
xy_kl = self.pts2xy[kl]
xy_mn = self.pts2xy[(k-1, l//nbran)]
ph_ij = atan2(xy_kl.imag - xy_mn.imag, xy_kl.real - xy_mn.real)
self.pts2xy[ij] = self.pts2xy[kl] + \
ratio**i * \
exp(1j*(ph_ij+ alpha_0 - (j%nbran)*delta_alpha))
x, y = self.pts2xy[ij].real, self.pts2xy[ij].imag
self.xmax = max(x, self.xmax)
self.ymax = max(y, self.ymax)
def get_pixel_coordinates(self, x, y, width, height):
xpix = 0.5 * width * (1 + x/self.xmax)
ypix = height * (1. - y/self.ymax)
return (xpix, ypix)
def draw(self, root, height=600):
"""
Draw the tree using Tkinter
"""
aspect_ratio = 2*self.xmax/self.ymax
width = aspect_ratio*height
canvas = Tkinter.Canvas(root, height=height, width=width,
background='white')
canvas.pack()
for i in range(0, self.ngen):
for j in range(max(1, self.nbran**i)):
ij = (i,j)
x, y = self.pts2xy[ij].real, self.pts2xy[ij].imag
xpix, ypix = self.get_pixel_coordinates(x,y,
width=width,
height=height)
# parent
kl = get_parent_indices(i,j, self.nbran)
u, v = self.pts2xy[kl].real, self.pts2xy[kl].imag
upix, vpix = self.get_pixel_coordinates(u, v,
width=width,
height=height)
canvas.create_line(upix,vpix, xpix,ypix, fill='black')
##############################################################################
def main():
parser = OptionParser()
parser.add_option('-r', '--ratio', action='store', type="float",
dest="ratio",
help='Ratio of branch length with respect to previous generation (<1.).',
default=0.5,
)
parser.add_option('-n', '--ngen', action='store', type="int",
dest="ngen",
help='Number of generations (>1).',
default=4,
)
parser.add_option('-y', '--ysize', action='store', type="int",
dest="ysize",
help='Number of vertical pixels.',
default=400,
)
parser.add_option('-a', '--angle', action='store', type="float",
dest="angle",
help='Fan angle between group of branches in deg.',
default=90,
)
parser.add_option('-N', '--Nbran', action='store', type="int",
dest="nbran",
help='Number of new branches.',
default=2,
)
options, args = parser.parse_args(sys.argv)
t = fractal_tree(ngen=options.ngen, ratio=options.ratio,
angle=options.angle*pi/180.0, nbran=options.nbran)
root = Tkinter.Tk()
t.draw(root, height=options.ysize)
root.mainloop()
if __name__=='__main__': main()
|
A fractal tree is constructed iteratively by growing new branches, which have a specific length ratio with respect to the parent branch (scaling factor). The fan angle, the number of new branches and number of generations are user controlled. The number of branches grows exponentially. Use ngen~10 for best results. The displayed tree exhibits self-similarity, i.e. the same patterns at different scales.
The code uses a dictionary to store the node coordinates as complex numbers and Tkinter for graphics. It is a good example of how Python's powerful data structures, combined with a vast body of modules can be used to write concise and user friendly code. The code was tested with Python 2.3 and 2.4.
You may also want to check out a similar recipe: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/347736.
It is well known that when the fan angle is 120 deg, the branches will asymptotically touch if one sets the scaling factor to 0.6180339887498949, that is the inverse of the golden ratio. To see this, type
python fractal_tree.py -a 120 -r 0.6180339887498949 -n 15