Fractal Trees¶

Plotly and IPython Notebook Widgets¶

In [7]:
from IPython.display import Image
Image(url='http://i.imgur.com/l9Yzfcs.gif')

Out[7]:
In [1]:
from math import pi as PI
from math import sin, cos
import random

from plotly.widgets import GraphWidget
from plotly.graph_objs import *
import plotly.plotly as py
import plotly.tools as tls

from IPython.html import widgets
from IPython.display import display, clear_output

root = 12

/Users/chris/anaconda/lib/python2.7/site-packages/pandas/computation/expressions.py:21: UserWarning: The installed version of numexpr 2.0.1 is not supported in pandas and will be not be used
The minimum supported version is 2.1

"version is 2.1\n".format(ver=ver), UserWarning)

In [2]:
# color the tree with a gradient from root_col to tip_col
# interpolate linearly to get color at a given position in the gradient
def get_col(root_col, tip_col, iterat):
r = ((iterat*1.0/root)*(root_col[0]-tip_col[0]))+tip_col[0]
g = ((iterat*1.0/root)*(root_col[1]-tip_col[1]))+tip_col[1]
b = ((iterat*1.0/root)*(root_col[2]-tip_col[2]))+tip_col[2]
return '#%02x%02x%02x' % (r,g,b)

def tree_graph(iterat=12, branch_angle=18.0):
# experiment with trunk length (try 120)
t = 120
# experiment with factor to contract the trunk each iteratation (try 0.7)
r = 0.7
# starting orientation (initial 90 deg)
# experiment with angle of the branch (try 18 deg)
# experiment with gradient color choices
root_col = (40,40,40)
tip_col = (250,250,250)
# experiment with factor to increase random angle variation as child branches get smaller
iterscale = 6.0
# center of bottom
origin = (250, 500)
root=iterat
# make the tree

def fractal_tree(lines, iterat, origin, t, r, theta, dtheta, root_col, tip_col, randomize=False):
"""
draws branches
iterat:     iteratation number, stop when iterat == 0
origin:   x,y coordinates of the start of this branch
t:        current trunk length
r:        factor to contract the trunk each iteratation
theta:    starting orientation
dtheta:   angle of the branch
"""
if iterat == 0:
return lines
# render the branch
x0, y0 = origin

# randomize the length
randt = random.random()*t
if randomize:
x, y = x0 + randt * cos(theta), y0 - randt * sin(theta)
else:
x, y = x0 + cos(theta), y0 - sin(theta)
# color the branch according to its position in the tree
col = get_col(root_col, tip_col, iterat)
lines.append(Scatter(x=[x0, x], y=[y0, y], mode='lines', line=Line(color=col, width=1)))
# recursive calls
if randomize:
fractal_tree(lines, iterat-1, (x,y), t * r, r, theta + (random.random())*(iterscale/(iterat+1))*dtheta, dtheta, root_col, tip_col, randomize)
fractal_tree(lines, iterat-1, (x,y), t * r, r, theta - (random.random())*(iterscale/(iterat+1))*dtheta, dtheta, root_col, tip_col, randomize)
else:
fractal_tree(lines, iterat-1, (x,y), t * r, r, theta + dtheta, dtheta, root_col, tip_col, randomize)
fractal_tree(lines, iterat-1, (x,y), t * r, r, theta - dtheta, dtheta, root_col, tip_col, randomize)

lines = []
fractal_tree(lines, iterat, origin, t, r, theta, dtheta, root_col, tip_col, True)

# group the lines by similar color
branches = {}
for line in lines:
color = line['line']['color']
if color not in branches:
branches[color] = line
else:
branches[color]['x'].extend(line['x'])
branches[color]['y'].extend(line['y'])
branches[color]['x'].append(None)
branches[color]['y'].append(None)

branch_data = [branches[c] for c in branches]

return branch_data

In [3]:
g = GraphWidget('https://plot.ly/~chris/4103')

iterations = widgets.IntSliderWidget()
iterations.min= 2
iterations.max= 14
iterations.value = 12
iterations.description = 'iterations'

branch_angle = widgets.IntSliderWidget()
branch_angle.min = 5
branch_angle.max = 90
branch_angle.value = 18
branch_angle.description = 'branch angle'

seed = widgets.ButtonWidget()
seed.description = 'Grow a new tree'

def regraph_tree(_):
branch_data = tree_graph(iterations.value, branch_angle.value)

g.restyle({'x': [[]], 'y': [[]]})

iterations.on_trait_change(regraph_tree, 'value')
branch_angle.on_trait_change(regraph_tree, 'value')
seed.on_click(regraph_tree)

display(iterations)
display(branch_angle)
display(seed)
display(g)

In [4]:
layout = Layout(yaxis=YAxis(autorange='reversed'), width=500, showlegend=False)

g.relayout(layout)

In [5]:
# CSS styling within IPython notebook - feel free to re-use
from IPython.core.display import HTML
import urllib2