Fractal Tree Generator

In this example below you will see how to do a Fractal Tree Generator with some HTML / CSS and Javascript

An exploration of applications of simple fractal branching.

Thumbnail
This awesome code was written by hippiefuturist, you can see more from this user in the personal repository.
You can find the original code on Codepen.io
Copyright hippiefuturist ©

Technologies

  • HTML
  • CSS
  • JavaScript
<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  <title>Fractal Tree Generator</title>
  
  
  
      <link rel="stylesheet" href="css/style.css">

  
</head>

<body>

  <html lang="en">
<head>
<meta charset="UTF-8">
<title>Fractal Tree Generator</title>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.0/themes/dot-luv/jquery-ui.css">

<script src="https://code.jquery.com/jquery-1.10.2.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.matchHeight/0.7.0/jquery.matchHeight-min.js"></script>

</head>
<body>
<div class="header">
	
</div>
<div class="section group">
	<div id="animation" class="col span_5_of_8 matchheight">
		<canvas id="canvas-fractal-tree" resize="true"></canvas>
	</div>
	<div id="controls" class="col span_3_of_8 matchheight">
		<center>
			<h2>Fractal Tree Generator</h2>
			<hr>
			<h3>
				Controls
				<a class="tooltip" href="#" title="For more precise control, enter values in input fields or adjust sliders with arrow keys."><span title="">ⓘ</span></a>
			</h3>
		</center>
			<span class="input-label">
				<label for="presetSelect">Presets</label>
				<select name="presetSelect" id="presetSelect">
				<optgroup label="Low Complexity (fast load)">
				  <option>Default</option>
				  <option>Binary Fractal</option>
				  <option>Dance</option>
				  <option>Spiral 1</option>
				  <option>Spiral 2</option>
				  <option>Kaleidoscope</option>
				  <option>Polygons 1</option>
				  <option>Grid</option>
				  <option>Honeycomb 1</option>
				  <option>Pinwheel</option>
				  <option>Holiday 1</option>
				 </optgroup>
				 <optgroup label="High Complexity (slow load)">
				  <option>Canopy</option>
				  <option>Bloom</option>
				  <option>Star</option>
				  <option>Pentagon</option>
				  <option>Flower of Life</option>
				  <option>Polygons 2</option>
				  <option>Holiday 2</option>
				  <option>Honeycomb 2</option>
				  
				  <option>Network</option>
				  </optgroup>
				</select>
			</span>
			<hr>
			<span class="input-label">
				Tree Size
				<input type="number" id="inputFieldTreeSize" name="inputFieldTreeSize">
				<input type="checkbox" id="checkDrawTrunk" class=""><label for="checkDrawTrunk">Draw Trunk</label>
				<input type="checkbox" id="checkDrawFruit" class=""><label for="checkDrawFruit">Draw Fruit</label>
			</span>
			<div id="sliderTreeSize" class="slider"></div>
			<hr>
			<fieldset>
			<legend>Angle</legend>
				<span class="input-label">
					Branch Angle
					<input type="number" id="inputFieldBranchAngle" name="inputFieldBranchAngle">
					<input type="checkbox" id="checkAnimateBranchAngle" class=""><label for="checkAnimateBranchAngle">Animate</label>
				</span>
				<div id="sliderBranchAngle" class="slider"></div>
				<span class="input-label">
					Spiral Factor
					<input type="number" id="inputFieldSpiralFactor" name="inputFieldSpiralFactor">
				</span>
				<div id="sliderSpiralFactor" class="slider"></div>
			</fieldset>
			<fieldset>
			<legend>Complexity </legend>
				<span class="input-label">
					<div id="accordion">
					<h4>Complexity Reference Table <strong>ⓘ</strong></h4>
					  <div>
						<div class="tg">
						The complexity of the generated image is proportional to the number of nodes (B^L). <br>
						Stay in the green range for best performance. <br>
						Brown may crash your browser tab!
						</div>
						<table class="tg">
						  <tr>
							<th class="tg-amwm">exp (R)→<br>↓base (B)</th>
							<th class="tg-9hbo">2</th>
							<th class="tg-9hbo">3</th>
							<th class="tg-9hbo">4</th>
							<th class="tg-9hbo">5</th>
							<th class="tg-9hbo">6</th>
							<th class="tg-9hbo">7</th>
							<th class="tg-9hbo">8</th>
							<th class="tg-9hbo">9</th>
							<th class="tg-9hbo">10</th>
						  </tr>
						  <tr>
							<td class="tg-9hbo">2</td>
							<td class="tg-jhhl">2^2</td>
							<td class="tg-5ex6">2^3</td>
							<td class="tg-jhhl">2^4</td>
							<td class="tg-5ex6">2^5</td>
							<td class="tg-jhhl">2^6</td>
							<td class="tg-5ex6">2^7</td>
							<td class="tg-jhhl">2^8</td>
							<td class="tg-5ex6">2^9</td>
							<td class="tg-jhhl">2^10</td>
						  </tr>
						  <tr>
							<td class="tg-9hbo">3</td>
							<td class="tg-jhhl">3^2</td>
							<td class="tg-5ex6">3^3</td>
							<td class="tg-jhhl">3^4</td>
							<td class="tg-5ex6">3^5</td>
							<td class="tg-jhhl">3^6</td>
							<td class="tg-5ex6">3^7</td>
							<td class="tg-gmyu">3^8</td>
							<td class="tg-mejs">3^9</td>
							<td class="tg-xi51">3^10</td>
						  </tr>
						  <tr>
							<td class="tg-9hbo">4</td>
							<td class="tg-jhhl">4^2</td>
							<td class="tg-5ex6">4^3</td>
							<td class="tg-jhhl">4^4</td>
							<td class="tg-5ex6">4^5</td>
							<td class="tg-ekto">4^6</td>
							<td class="tg-mejs">4^7</td>
							<td class="tg-xi51">4^8</td>
							<td class="tg-mejs">4^9</td>
							<td class="tg-gxjt">4^10</td>
						  </tr>
						  <tr>
							<td class="tg-9hbo">5</td>
							<td class="tg-jhhl">5^2</td>
							<td class="tg-5ex6">5^3</td>
							<td class="tg-jhhl">5^4</td>
							<td class="tg-0kqp">5^5</td>
							<td class="tg-xi51">5^6</td>
							<td class="tg-mejs">5^7</td>
							<td class="tg-xi51">5^8</td>
							<td class="tg-y80k">5^9</td>
							<td class="tg-gxjt">5^10</td>
						  </tr>
						  <tr>
							<td class="tg-9hbo">6</td>
							<td class="tg-jhhl">6^2</td>
							<td class="tg-5ex6">6^3</td>
							<td class="tg-jhhl">6^4</td>
							<td class="tg-0kqp">6^5</td>
							<td class="tg-xi51">6^6</td>
							<td class="tg-mejs">6^7</td>
							<td class="tg-gxjt">6^8</td>
							<td class="tg-y80k">6^9</td>
							<td class="tg-gxjt">6^10</td>
						  </tr>
						  <tr>
							<td class="tg-9hbo">7</td>
							<td class="tg-jhhl">7^2</td>
							<td class="tg-5ex6">7^3</td>
							<td class="tg-343t">7^4</td>
							<td class="tg-mejs">7^5</td>
							<td class="tg-xi51">7^6</td>
							<td class="tg-y80k">7^7</td>
							<td class="tg-gxjt">7^8</td>
							<td class="tg-y80k">7^9</td>
							<td class="tg-gxjt">7^10</td>
						  </tr>
						  <tr>
							<td class="tg-9hbo">8</td>
							<td class="tg-jhhl">8^2</td>
							<td class="tg-5ex6">8^3</td>
							<td class="tg-343t">8^4</td>
							<td class="tg-mejs">8^5</td>
							<td class="tg-xi51">8^6</td>
							<td class="tg-y80k">8^7</td>
							<td class="tg-gxjt">8^8</td>
							<td class="tg-y80k">8^9</td>
							<td class="tg-gxjt">8^10</td>
						  </tr>
						</table>
					  </div>
					</div>
				</span>
				<span class="input-label">
					Recursion Levels (R)
					<input type="number" id="inputFieldRecursionLevels" name="inputFieldRecursionLevels">
				</span>
				<div id="sliderRecursionLevels" class="slider"></div>
				<span class="input-label">
					Number of Branches (B)
					<input type="number" id="inputFieldNumberBranches" name="inputFieldNumberBranches">
				</span>
				<div id="sliderNumberBranches" class="slider"></div>
				<span class="input-label">Number of Nodes (B^R)= <span id="number-of-nodes"></span>
				<br>
				<span class="input-label">Complexity Level (R*B^R): <span id="complexity-level"></span>
					<strong>
						<a class="tooltip" id="complexity-level-tooltip" href="#" title="Lower one of the sliders to improve performance."><span title="">ⓘ</span></a>
					</strong>
				</span>
			</fieldset>
			<center><div id="about">
				<h3>About</h3>
				<a href="http://www.hippiefuturist.me/4-d/fractal-tree-generator">hippiefuturist.me</a>
				<br>
				Built with <a href="http://www.paperjs.org" target="_blank">Paper.js</a> and <a href="https://jqueryui.com/" target="_blank">jQuery UI</a>
			</div></center>
	</div>
</div>

</body>
</html>
  <script src='http://cdnjs.cloudflare.com/ajax/libs/paper.js/0.9.25/paper-full.min.js'></script>

  

    <script  src="js/index.js"></script>




</body>

</html>

/*Downloaded from https://www.codeseek.co/hippiefuturist/fractal-tree-generator-NRWOxr */
/* Paper JS */

html,
body {
    margin: 0;
    height: 100%;
	background: #EAEAEA;
}

canvas[resize] {
		width: 100%;
		height: 100%;
	}
	
.slider,
.input-label {
	margin-top: 10px;
	margin-bottom: 5px;
	margin-left: 15px;
	margin-right: 15px;
}
.ui-selectmenu-button {
	margin-left: 15px;
	margin-right: 15px;
}

select {
	float:right;
}

.ui-button {
	padding: 0.1em .5em !important;
	font-size: 0.9em !important;
}

.ui-menu {
	padding: 0.1em .5em !important;
	font-size: 0.9em !important;
}

.ui-accordion,
.ui-accordion-content {
	padding: 0px !important;
	margin-bottom: 5px;
	font-size: 0.9em !important;
	margin: 0px auto;
	text-align:center;
	vertical-align: middle;
}

/* COMPLEXITY REFERENCE */
.tg  {border-collapse:collapse;border-spacing:0;margin:0px auto;}
.tg td{font-family:Arial, sans-serif;font-size:14px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;}
.tg th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;}
.tg .tg-jhhl{color:#009901;vertical-align:top}
.tg .tg-343t{color:#ffcc67;vertical-align:top}
.tg .tg-0kqp{color:#ffc702;vertical-align:top}
.tg .tg-y80k{color:#680100;vertical-align:top}
.tg .tg-gmyu{color:#f8a102;vertical-align:top}
.tg .tg-amwm{font-weight:bold;text-align:center;vertical-align:top}
.tg .tg-9hbo{font-weight:bold;vertical-align:top}
.tg .tg-5ex6{color:#009901;vertical-align:top}
.tg .tg-mejs{color:#fe0000;vertical-align:top}
.tg .tg-xi51{color:#fe0000;vertical-align:top}
.tg .tg-ekto{color:#ffc702;vertical-align:top}
.tg .tg-gxjt{color:#680100;vertical-align:top}

/* TOOLTIP */
.tooltip{
    display: inline;
    position: relative;
	text-decoration:none;
}
.tooltip:hover:after{
    background: #333;
    background: rgba(0,0,0,.8);
    border-radius: 5px;
    top: 26px;
    color: #fff;
    content: attr(title);
    right: -100px;
    padding: 5px 15px;
    position: absolute;
    z-index: 98;
    width: 180px;
}
.tooltip:hover:before{
    border: solid;
    border-color: #333 transparent;
    border-width: 0 6px 6px  6px;
    top: 20px;
    content: "";
    right: 50%;
    position: absolute;
    z-index: 99;
}

/*  SECTIONS  */
.section {
	clear: both;
	padding: 0px;
	margin: 0px;
	background: #EAEAEA;
}

/*  COLUMN SETUP  */
.col {
	display: block;
	float:left;
	margin: 1% 0 1% 1.6%;
	background: #DEDEDE;
	height: 100%;
}
.col:first-child { margin-left: 0; }

/*  GROUPING  */
.group:before,
.group:after { content:""; display:table; }
.group:after { clear:both;}
.group { zoom:1; /* For IE 6/7 */ }

/*  GRID OF EIGHT  */
.span_8_of_8 {
	width: 100%;
}

.span_7_of_8 {
  	width: 87.3%;
}

.span_6_of_8 {
  	width: 74.6%;
}

.span_5_of_8 {
  	width: 61.9%;
}

.span_4_of_8 {
  	width: 49.2%;
}

.span_3_of_8 {
  	width: 36.5%;
}

.span_2_of_8 {
  	width: 23.8%;
}

.span_1_of_8 {
  	width: 11.1%;
}

/*  GO FULL WIDTH BELOW 480 PIXELS */
@media only screen and (max-width: 480px) {
	.col {
		margin: 1% 0 1% 0%;
	}
	.span_1_of_8,
	.span_2_of_8,
	.span_3_of_8,
	.span_4_of_8,
	.span_5_of_8,
	.span_6_of_8,
	.span_7_of_8,
	.span_8_of_8 {
		width: 100%; 
	}
}

/*Downloaded from https://www.codeseek.co/hippiefuturist/fractal-tree-generator-NRWOxr */
   

/**
 * Fractal_Tree fractalTreeApp constructor
 *
 */
function Fractal_Tree () {
	
	/**
		Constants
	**/
	
	/* Controls Variables */
	
	this.presetsOptions = null;
	this.currentPreset = null;
	

	
	this.lengthScale = 0
	
	this.initialPoint = null;
	this.initialVector = null;

	this.treeSize = 0;
	this.treeSizeMin = 0;
	this.treeSizeMax = 0;
	this.treeSizeIncrement = 0;
	
	this.branchAngle = 0;
	this.branchAngleMin = 0;
	this.branchAngleMax = 0;
	this.branchAngleIncrement = 0;
	
	this.numberBranches = 0;
	this.numberBranchesMin = 0;
	this.numberBranchesMax = 0;
	this.numberBranchesIncrement = 0;
	
	this.spiralFactor = 0;
	this.spiralFactorMin = 0;
	this.spiralFactorMax = 0;
	this.spiralFactorIncrement = 0;
	
	this.recursionLevels = 0;
	this.recursionLevelsMin = 0;
	this.recursionLevelsMax = 0;
	this.recursionLevelsIncrement = 0;
	
	this.drawTrunk = false;
	this.animateBranchAngle = false;
	
	// UI Variables
	this.presetSelect = null;
	
	this.inputFieldTreeSize = null;
	this.sliderTreeSize = null;
	
	this.inputFieldBranchAngle = null;
	this.sliderBranchAngle = null;
	
	this.checkDrawTrunk = null;
	this.checkDrawFruit = null;
	this.checkAnimateBranchAngle = null;
	
	this.progressbar = null;
	
	// Container components
	this.controlsContainer = null;
	this.animationContainer = null;
	
	this.window = null;
	
	/* View Variables */
	this.locationX = 0;
	this.locationY = 0;
	this.textDisplayLocationX = 0;
	this.textDisplayLocationY = 0;
	this.creditsLocationX = 0;
	this.creditsLocationY = 0;
	
	this.labelFontSize = 0;
	
	this.initialTimeStart = 0;
	
	this.colorRgbMap = {};
	
	// Calculated values
	this.circumference = 0;
	this.pointCircleCenter = 0;
	this.pointCircleStart = 0;	
	
	this.contentGroup = null;
	this.detailsGroup = null;
  
	this.initialized = false;
	
	this.iterations = 0;

}

/**
 * Fractal_Tree fractalTreeApp class
 *
 */
Fractal_Tree.prototype = {
  
  constructor: Fractal_Tree,
  
  initControls: function() {
	  this.initControlsValues();
	  this.initControlsContainer();
	  this.initControlsUI();
  },
  initView: function() {
	  this.initViewValues();
	  this.initViewAnimation();
	  
	  this.updateUIValues()
	  // Initialize Drawing
   	  this.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
  },
  initControlsValues: function() {
	  
	this.window = $(window);
	
	this.lengthScale = 1.61803; // PHI
	
	this.initialPoint = new Point(this.window.width()/3, this.window.height()/(3/2));
	this.initialVector = new Point(0, -this.treeSize);

	this.treeSize = this.formatDecimal(0.3 * this.window.height(), this.treeSizeIncrement);
	this.treeSizeMin = 0;
	this.treeSizeMax = this.treeSize * 3;
	this.treeSizeIncrement = 1;
	
	this.branchAngle = 90;
	this.branchAngleMin = 0;
	this.branchAngleMax = 1080;
	this.branchAngleIncrement = 1;
	
	this.numberBranches = 2;
	this.numberBranchesMin = 1;
	this.numberBranchesMax = 8;
	this.numberBranchesIncrement = 1;
	
	this.spiralFactor = 0;
	this.spiralFactorMin = 0;
	this.spiralFactorMax = 10;
	this.spiralFactorIncrement = 1;
	
	this.recursionLevels = 7;
	this.recursionLevelsMin = 1;
	this.recursionLevelsMax = 10;
	this.recursionLevelsIncrement = 1;
	
	// Load Preset
	this.presetsOptions = this.generatePresets();
	
	// Initialize time
	this.initialTimeStart = Date.now();
  },
  generatePresets: function() {
	  var presetsObject = {
		  "Default": {
			  branchAngle: 40,
			  spiralFactor: 0,
			  numberBranches: 2,
			  recursionLevels: 8,
			  drawTrunk: true,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Binary Fractal": {
			  branchAngle: 70,
			  spiralFactor: 0,
			  numberBranches: 2,
			  recursionLevels: 9,
			  drawTrunk: true,
			  drawFruit: false,
			  animateBranchAngle: true
			  },
		"Dance": {
			  branchAngle: 0,
			  spiralFactor: 1,
			  numberBranches: 2,
			  recursionLevels: 3,
			  drawTrunk: true,
			  drawFruit: true,
			  animateBranchAngle: true
			  },
		"Kaleidoscope": {
			  branchAngle: 0,
			  spiralFactor: 5,
			  numberBranches: 6,
			  recursionLevels: 3,
			  drawTrunk: false,
			  drawFruit: true,
			  animateBranchAngle: true
			  },
		"Honeycomb 1": {
			  branchAngle: 300,
			  spiralFactor: 1,
			  numberBranches: 3,
			  recursionLevels: 6,
			  drawTrunk: false,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Honeycomb 2": {
			  branchAngle: 72,
			  spiralFactor: 0,
			  numberBranches: 4,
			  recursionLevels: 7,
			  drawTrunk: false,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Polygons 1": {
			  branchAngle: 216,
			  spiralFactor: 1,
			  numberBranches: 6,
			  recursionLevels: 4,
			  drawTrunk: false,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Polygons 2": {
			  branchAngle: 216,
			  spiralFactor: 1,
			  numberBranches: 6,
			  recursionLevels: 7,
			  drawTrunk: false,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Flower of Life": {
			  branchAngle: 54,
			  spiralFactor: 1,
			  numberBranches: 5,
			  recursionLevels: 7,
			  drawTrunk: false,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Spiral 1": {
			  branchAngle: 40,
			  spiralFactor: 1,
			  numberBranches: 2,
			  recursionLevels: 9,
			  drawTrunk: true,
			  drawFruit: false,
			  animateBranchAngle: true
			  },
		"Spiral 2": {
			  branchAngle: 40,
			  spiralFactor: 1,
			  numberBranches: 3,
			  recursionLevels: 6,
			  drawTrunk: true,
			  drawFruit: false,
			  animateBranchAngle: true
			  },
		"Spiral 3": {
			  branchAngle: 200,
			  spiralFactor: 3,
			  numberBranches: 2,
			  recursionLevels: 10,
			  drawTrunk: true,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Bloom": {
			  branchAngle: 25,
			  spiralFactor: 1,
			  numberBranches: 3,
			  recursionLevels: 9,
			  drawTrunk: true,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Pinwheel": {
			  branchAngle: 216,
			  spiralFactor: 4,
			  numberBranches: 5,
			  recursionLevels: 5,
			  drawTrunk: true,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Canopy": {
			  branchAngle: 30,
			  spiralFactor: 0,
			  numberBranches: 4,
			  recursionLevels: 7,
			  drawTrunk: true,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Grid": {
			  branchAngle: 180,
			  spiralFactor: 0,
			  numberBranches: 2,
			  recursionLevels: 10,
			  drawTrunk: true,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Holiday 1": {
			  branchAngle: 240,
			  spiralFactor: 1,
			  numberBranches: 3,
			  recursionLevels: 8,
			  drawTrunk: true,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Holiday 2": {
			  branchAngle: 108,
			  spiralFactor: 0,
			  numberBranches: 3,
			  recursionLevels: 10,
			  drawTrunk: true,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Star": {
			  branchAngle: 108,
			  spiralFactor: 3,
			  numberBranches: 5,
			  recursionLevels: 6,
			  drawTrunk: false,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Pentagon": {
			  branchAngle: 72,
			  spiralFactor: 0,
			  numberBranches: 5,
			  recursionLevels: 7,
			  drawTrunk: false,
			  drawFruit: false,
			  animateBranchAngle: false
			  },
		"Network": {
			  branchAngle: 270,
			  spiralFactor: 7,
			  numberBranches: 3,
			  recursionLevels: 9,
			  drawTrunk: false,
			  drawFruit: false,
			  animateBranchAngle: false
			  }
		
	  };
	  return presetsObject;
  },
  loadPreset: function(presetsOptions, presetName) {
	  
	var preset = presetsOptions[presetName];
	
	this.branchAngle = preset.branchAngle;
	this.spiralFactor = preset.spiralFactor;
	this.numberBranches = preset.numberBranches;
	this.recursionLevels = preset.recursionLevels;
	this.drawTrunk = preset.drawTrunk;
	this.drawFruit = preset.drawFruit;
	this.animateBranchAngle = preset.animateBranchAngle;
	
	if(this.contentGroup) {
		this.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
	}
  },
  initControlsContainer: function() {
	 // UI Components
	this.presetSelect = $("#presetSelect");
	
	this.inputFieldTreeSize = $("#inputFieldTreeSize");
	this.sliderTreeSize = $("#sliderTreeSize");
	
	this.inputFieldBranchAngle = $("#inputFieldBranchAngle");
	this.sliderBranchAngle = $("#sliderBranchAngle");
	
	this.inputFieldNumberBranches = $("#inputFieldNumberBranches");
	this.sliderNumberBranches = $("#sliderNumberBranches");
	
	this.inputFieldSpiralFactor = $("#inputFieldSpiralFactor");
	this.sliderSpiralFactor = $("#sliderSpiralFactor");
	
	this.inputFieldRecursionLevels = $("#inputFieldRecursionLevels");
	this.sliderRecursionLevels = $("#sliderRecursionLevels");
	
	this.progressbar = $("#progressbar");

	this.checkDrawTrunk = $("#checkDrawTrunk");
	this.checkDrawFruit = $("#checkDrawFruit");
	this.checkAnimateBranchAngle = $("#checkAnimateBranchAngle");
	
	// Container components
	this.controlsContainer = $("#controls");
	this.animationContainer = $("#animation");
	
  },
  initControlsUI: function() {
	var fractalTreeApp = this;
	
	/* Select Preset */
	this.presetSelect.selectmenu({
      change: function( event, data ) {
		fractalTreeApp.currentPreset = data.item.value;	
		fractalTreeApp.loadPreset(fractalTreeApp.presetsOptions, fractalTreeApp.currentPreset);
		fractalTreeApp.updateUIValues();
      }
     });
	 // Select first preset
	this.currentPreset = Object.keys(this.presetsOptions)[0];
	this.loadPreset(this.presetsOptions, this.currentPreset);
	  
	/* Control Tree Size */
	this.inputFieldTreeSize.attr('min', this.treeSizeMin);
	this.inputFieldTreeSize.attr('max', this.treeSizeMax);
	this.inputFieldTreeSize.attr('step', this.treeSizeIncrement);
	this.inputFieldTreeSize.change(function () {
		var value = fractalTreeApp.formatDecimal(this.value, fractalTreeApp.treeSizeIncrement);
		fractalTreeApp.treeSize = value;
		fractalTreeApp.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		fractalTreeApp.updateUIValues();
	});
	// Slider to control radius
	this.sliderTreeSize.slider({
		range: "min",
		min: this.treeSizeMin,
		max: this.treeSizeMax,
		value: this.radius,
		step: this.treeSizeIncrement,
		slide: function( event, ui ) {
		  fractalTreeApp.treeSize = fractalTreeApp.formatDecimal(ui.value, fractalTreeApp.treeSizeIncrement);
		  fractalTreeApp.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		  fractalTreeApp.updateUIValues();
	  }
	});
	  
	/* Control Branch Angle */
	this.inputFieldBranchAngle.attr('min', this.branchAngleMin);
	this.inputFieldBranchAngle.attr('max', this.branchAngleMax);
	this.inputFieldBranchAngle.attr('step', this.branchAngleIncrement);
	this.inputFieldBranchAngle.change(function () {
		var value = fractalTreeApp.formatDecimal(this.value, fractalTreeApp.baseSpeedIncrement);
		fractalTreeApp.branchAngle = value;
		fractalTreeApp.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		fractalTreeApp.updateUIValues();
	});
	// Slider to control base/number of points
	this.sliderBranchAngle.slider({
		range: "min",
		min: this.branchAngleMin,
		max: this.branchAngleMax,
		value: this.branchAngle,
		step: this.branchAngleIncrement,
		slide: function( event, ui ) {
	//	  fractalTreeApp.base = fractalTreeApp.formatDecimal(ui.value, fractalTreeApp.baseSpeedIncrement);
		  fractalTreeApp.branchAngle = fractalTreeApp.formatDecimal(ui.value, fractalTreeApp.branchAngleIncrement);
		  fractalTreeApp.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		  fractalTreeApp.updateUIValues();
	  }
	});
	
	/* Control Number of Branches */
	this.inputFieldNumberBranches.attr('min', this.numberBranchesMin);
	this.inputFieldNumberBranches.attr('max', this.numberBranchesMax);
	this.inputFieldNumberBranches.attr('step', this.numberBranchesIncrement);
	this.inputFieldNumberBranches.change(function () {
		var value = fractalTreeApp.formatDecimal(this.value, fractalTreeApp.numberBranchesIncrement);
		fractalTreeApp.numberBranches = value;
		fractalTreeApp.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		fractalTreeApp.updateUIValues();
	});
	// Slider to control base/number of points
	this.sliderNumberBranches.slider({
		range: "min",
		min: this.numberBranchesMin,
		max: this.numberBranchesMax,
		value: this.numberBranches,
		step: this.numberBranchesIncrement,
		slide: function( event, ui ) {
		  fractalTreeApp.numberBranches = fractalTreeApp.formatDecimal(ui.value, fractalTreeApp.numberBranchesIncrement);
		  fractalTreeApp.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		  fractalTreeApp.updateUIValues();
	  }
	});
	
	/* Control Spiral Factor */
	this.inputFieldSpiralFactor.attr('min', this.spiralFactorMin);
	this.inputFieldSpiralFactor.attr('max', this.spiralFactorMax);
	this.inputFieldSpiralFactor.attr('step', this.spiralFactorIncrement);
	this.inputFieldSpiralFactor.change(function () {
		var value = fractalTreeApp.formatDecimal(this.value, fractalTreeApp.spiralFactorIncrement);
		fractalTreeApp.spiralFactor = value;
		fractalTreeApp.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		fractalTreeApp.updateUIValues();
	});
	// Slider to control Spiral Factor
	this.sliderSpiralFactor.slider({
		range: "min",
		min: this.spiralFactorMin,
		max: this.spiralFactorMax,
		value: this.spiralFactor,
		step: this.spiralFactorIncrement,
		slide: function( event, ui ) {
		  fractalTreeApp.spiralFactor = fractalTreeApp.formatDecimal(ui.value, fractalTreeApp.spiralFactorIncrement);
		  fractalTreeApp.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		  fractalTreeApp.updateUIValues();
	  }
	});
	
	/* Control Recursion Level */
	this.inputFieldRecursionLevels.attr('min', this.recursionLevelsMin);
	this.inputFieldRecursionLevels.attr('max', this.recursionLevelsMax);
	this.inputFieldRecursionLevels.attr('step', this.recursionLevelsIncrement);
	this.inputFieldRecursionLevels.change(function () {
		var value = fractalTreeApp.formatDecimal(this.value, fractalTreeApp.recursionLevelsIncrement);
		fractalTreeApp.recursionLevels = value;
		fractalTreeApp.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		fractalTreeApp.updateUIValues();
	});
	// Slider to control base/number of points
	this.sliderRecursionLevels.slider({
		range: "min",
		min: this.recursionLevelsMin,
		max: this.recursionLevelsMax,
		value: this.recursionLevels,
		step: this.recursionLevelsIncrement,
		slide: function( event, ui ) {
		  fractalTreeApp.recursionLevels = fractalTreeApp.formatDecimal(ui.value, fractalTreeApp.recursionLevelsIncrement);
		  fractalTreeApp.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		  fractalTreeApp.updateUIValues();
	  }
	});
	
	// Toggle button to control drawing the trunk
	this.checkDrawTrunk.button();
	this.checkDrawTrunk.click( function() {
		fractalTreeApp.drawTrunk = !fractalTreeApp.drawTrunk;
		fractalTreeApp.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		fractalTreeApp.updateUIValues();
	});
	
	// Toggle button to control drawing the fruit
	this.checkDrawFruit.button();
	this.checkDrawFruit.click( function() {
		fractalTreeApp.drawFruit = !fractalTreeApp.drawFruit;
		fractalTreeApp.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		fractalTreeApp.updateUIValues();
	});
	
	// Toggle button to control animation
	this.checkAnimateBranchAngle.button();
	this.checkAnimateBranchAngle.click( function() {
		fractalTreeApp.animateBranchAngle = !fractalTreeApp.animateBranchAngle;
		fractalTreeApp.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		fractalTreeApp.updateUIValues();
	});
	
	$( "#accordion" ).accordion({
      collapsible: true,
	  active: false
    });
  },
  initViewValues: function() {
	this.resize();
	
	this.locationX = view.size.width / 2;
	this.locationY = view.size.height/(9/7);
	
	this.initialPoint = new Point(this.locationX, this.locationY);
	
	// Calculated values
	this.pointCircleCenter = new Point(this.locationX, this.locationY);
	this.pointCircleStart = new Point(this.locationX-this.radius, this.locationY);
  },
  initViewAnimation: function() {
    view.draw();
  },
  updateUITreeSize: function() {
	  /* Radius */
		this.inputFieldTreeSize.val(this.treeSize);
		this.sliderTreeSize.slider("value", this.treeSize);
  },
  updateUIBranchAngle: function() {
	  /* Base */
		this.inputFieldBranchAngle.val(this.branchAngle);
		this.sliderBranchAngle.slider("value", this.branchAngle);
  },
  updateUINumberBranches: function() {
	  /* Number Branches */
		this.inputFieldNumberBranches.val(this.numberBranches);
		this.sliderNumberBranches.slider("value", this.numberBranches);
  },
  updateUISpiralFactor: function() {
	  /* Spiral Factor */
		this.inputFieldSpiralFactor.val(this.spiralFactor);
		this.sliderSpiralFactor.slider("value", this.spiralFactor);
  },
  updateUIRecursionLevels: function() {
	  /* Recursion Levels */
		this.inputFieldRecursionLevels.val(this.recursionLevels);
		this.sliderRecursionLevels.slider("value", this.recursionLevels);
  },
  updateUITrunk: function() {
	this.checkDrawTrunk.attr('checked', (this.drawTrunk ? true : false ));
	this.checkDrawTrunk.button('refresh');
  },
  updateUIFruit: function() {
	this.checkDrawFruit.attr('checked', (this.drawFruit ? true : false ));
	this.checkDrawFruit.button('refresh');
  },
  updateUIAnimateBranchAngle: function() {
	this.checkAnimateBranchAngle.attr('checked', (this.animateBranchAngle ? true : false ));
	this.checkAnimateBranchAngle.button('refresh');
  },
  updateUIComplexityLevel: function() {
	  
	  var numberNodes = Math.pow(this.numberBranches, this.recursionLevels);
	  var complexityLevel = numberNodes*this.recursionLevels;
	$("#number-of-nodes").text(this.numberBranches + "^" + this.recursionLevels + " = " + numberNodes);
	  if(complexityLevel <= 40000) {
		  $("#complexity-level").text("Low ");
		  $("#complexity-level").css('color', 'green');
		  $("#complexity-level-tooltip").attr("title", "This animation should run smoothly.");
	  } else if(complexityLevel <= 100000) {
		  $("#complexity-level").text("Medium ");
		  $("#complexity-level").css('color', 'orange');
		  $("#complexity-level-tooltip").attr("title", "Performance may be slow. Reduce one of complexity sliders to improve.");
	  } else {
		  $("#complexity-level").text("High ");
		  $("#complexity-level").css('color', 'red');
		  $("#complexity-level-tooltip").attr("title", "Performance may be very slow. Reduce one of complexity sliders to improve.");
	  }
  },
  updateUIValues: function() {
		// Update UI values of the slider controls
		this.updateUITreeSize();
		this.updateUIBranchAngle();
		this.updateUINumberBranches();
		this.updateUIRecursionLevels();
		this.updateUISpiralFactor();
		this.updateUIComplexityLevel();
		this.updateUIAnimateBranchAngle();
		
		this.updateUITrunk();
		this.updateUIFruit();
	},
	updateAnimateBase: function() {
		if((!this.animateBase && this.baseSpeed > 0)
		|| (this.animateBase && this.baseSpeed == 0)) {
			this.animateBase = !this.animateBase;
		}
	},
	updateAnimateMultiplyFactor: function() {
		if((!this.animateMultiplyFactor && this.multiplyFactorSpeed > 0)
		|| (this.animateMultiplyFactor && this.multiplyFactorSpeed == 0)) {
			this.animateMultiplyFactor = !this.animateMultiplyFactor;
		}
	},
  getAngle: function (distance, base) {
		return (2 * Math.PI / (base)) * distance;
	},
  getCartesianCoordinates: function (angle, radius) {
		return [Math.cos(angle)*radius, Math.sin(angle)*radius];
	},
	getLabelFontSize() {
		return Math.min(15, 5 + this.branchAngleMax/(2 * this.base));
	},
	formatDecimal: function(value, sample) {
		valueParsed = Number(value);
		sampleParsed = Number(sample);
		var decimalPortion = sampleParsed.toString().split(".")[1];
		var decimalPlaces = 0;
		if(decimalPortion) {
			decimalPlaces = decimalPortion.length;
		}
		var base = Math.pow(10, decimalPlaces)
		return Number((Math.round(valueParsed*base)/base).toFixed(decimalPlaces));
	},
  /**
	* Gets the rgb value array for the given recursion level. Calculates or retrieves the cached value.
	**/
	getRgb: function(level) {
		if(this.colorRgbMap[level] == null) {
			// Keep hue value in range [0, 1]
			var hue = (level / 10) % 1;
			var rgb = this.hslToRgb(hue, 0.5, 0.5);
			this.colorRgbMap[level] = rgb;
		}
		return this.colorRgbMap[level];
	},
	
	/**
	 * Converts an HSL color value to RGB. Conversion formula
	 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
	 * Assumes h, s, and l are contained in the set [0, 1] and
	 * returns r, g, and b in the set [0, 255].
	 *
	 * @param   {number}  h       The hue
	 * @param   {number}  s       The saturation
	 * @param   {number}  l       The lightness
	 * @return  {Array}           The RGB representation
	 */
	hslToRgb: function(h, s, l){
		var r, g, b;

		if(s == 0){
			r = g = b = l; // achromatic
		}else{
			var hue2rgb = function hue2rgb(p, q, t){
				if(t < 0) t += 1;
				if(t > 1) t -= 1;
				if(t < 1/6) return p + (q - p) * 6 * t;
				if(t < 1/2) return q;
				if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
				return p;
			}

			var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
			var p = 2 * l - q;
			r = hue2rgb(p, q, h + 1/3);
			g = hue2rgb(p, q, h);
			b = hue2rgb(p, q, h - 1/3);
		}

		return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
	},
	drawLine: function(point0, point1) {
		var segment = new Path();
		segment.strokeWidth = 1;
		segment.add(point0);
		segment.add(point1);
		var rgb = this.getRgb(this.multiplyFactor);
		segment.strokeColor = new Color(rgb[0]/255, rgb[1]/255, rgb[2]/255);
		return segment;
	},
	drawContent: function() {

		this.iterations = 0;
		this.initialVector = new Point(0, -this.treeSize);
		
		var itemGroup = new Group();
		itemGroup = this.drawRecursion(itemGroup, this.initialPoint, this.initialVector, this.recursionLevels, this.numberBranches, this.branchAngle, this.lengthScale); 
		return itemGroup;
	},
	drawRecursion: function(itemGroup, currentPoint, currentVector, recursionLevels, numberBranches, branchAngle, lengthScale) {
		var currItemGroup = itemGroup;
		
		var nextPoint = new Point(currentPoint.x + currentVector.x, currentPoint.y + currentVector.y);
	
		if(this.iterations != 0 || this.drawTrunk) {
			// Draw new point		
			var segment = new Path();
			segment.strokeWidth = Math.max(1, recursionLevels);
			segment.strokeCap = "round";
			segment.add(currentPoint);
			segment.add(nextPoint);
			var rgb = this.getRgb(recursionLevels);
		//	var rgb = this.getRgb(recursionLevels - this.recursionLevels);
			segment.strokeColor = new Color(rgb[0]/255, rgb[1]/255, rgb[2]/255);
			itemGroup.addChild(segment);	
		}
		this.iterations++;
		
		var nextRecursionLevels = recursionLevels-1;
		if(recursionLevels > 0) {
			var maxIterations = (recursionLevels % this.spiralFactor == 0) ? numberBranches*(this.spiralFactor+1): numberBranches;
			var iterationIncrement = (recursionLevels % this.spiralFactor == 0) ? this.spiralFactor+1: 1;
			
			var i = 0;
			for(i = 0; i < maxIterations; i+=iterationIncrement) {
				var angle = branchAngle*(1/2 + i - numberBranches/2);	
									
				var nextVector = currentVector.rotate(angle);
				nextVector.length /= lengthScale;
				this.drawRecursion(currItemGroup, nextPoint, nextVector, nextRecursionLevels, numberBranches, branchAngle, lengthScale);
			}
		}
		else {
			if(this.drawFruit) {			
				var circle = new Path.Circle(nextPoint, Math.max(1, this.recursionLevelsMax - this.recursionLevels));
			//	circle.strokeWidth = Math.max(1, recursionLevels);
				circle.strokeCap = "round";
				var rgb = this.getRgb(recursionLevels - this.recursionLevels-1);
				circle.fillColor = new Color(rgb[0]/255, rgb[1]/255, rgb[2]/255);
				itemGroup.addChild(circle);
			}
		}
		return currItemGroup;
		
	},
  update: function() {
	  if(this.animateBranchAngle) {	
			this.branchAngle = this.branchAngle + 1;
			fractalTreeApp.updateUIBranchAngle();
			if(this.branchAngle >= this.branchAngleMax) {
				this.branchAngle = this.branchAngleMin;
			}
			this.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
		}
  },
  
  
  redraw: function(drawGroup, drawFunction) {
	 // Remove group and redraw
 	if(drawGroup) {
		drawGroup.remove();
	}
	return drawFunction; 
  },
  resize: function() {
	  
	var height = Math.min(this.window.height(), this.controlsContainer.height())
	var offset = this.animationContainer.offset().top;
	this.animationContainer.height(height);

  },
  start: function() {
	  // Initialize Drawing
	this.contentGroup = fractalTreeApp.redraw(fractalTreeApp.contentGroup, fractalTreeApp.drawContent());
  
    view.onFrame = function (event) {	
		fractalTreeApp.update();
    }
	
	view.onResize = function (event) {
		fractalTreeApp.initView();
	}
  }
  
};

var fractalTreeApp;

$(function() {
	// Setup Paper.JS
	paper.install(window);  
	fractalTreeApp = new Fractal_Tree();

	// Initialize fractalTreeApplication
	fractalTreeApp.initControls();
	fractalTreeApp.resize();
	paper.setup('canvas-fractal-tree');
	fractalTreeApp.initView();
	fractalTreeApp.start();
});

$(window).resize(function() {
	fractalTreeApp.initView();
});

Comments