Skip to content

Instantly share code, notes, and snippets.

@topologicallytony
Last active January 20, 2017 06:33
Show Gist options
  • Save topologicallytony/465a6a8025554d0ddd63d2eba43528b4 to your computer and use it in GitHub Desktop.
Save topologicallytony/465a6a8025554d0ddd63d2eba43528b4 to your computer and use it in GitHub Desktop.
Lindenmayer System Generator

Lindenmayer Systems

In the above L-system generator, the following commands can be used to create a turtle graphic

Generating a Turtle Graphic

  • Drawing Commands: Letters in the alphabet (A-G) that tell the turtle to move forward in the direction currently pointed
  • Constant Commands: Letters in the alphabet (X,Y,Z) that do not correspond to any change in the turtle's position
  • + : Turn the turtle to the left by the angle specified
  • - : Turn the turtle to the right by the angle specified
  • ( ) or [ ] : Upon reaching a closing bracket, the turtle will return to the position prior to the corresponding opening bracket
  • { } : If the Axiom is enclosed in curly braces, the shape will be filled
  • Axiom: Initializes the system with a string composed of characters in the alphabet
  • Angle: Determines the angle at which the turtle will turn on a given + or -
  • Rule: A rule replaces a letter in the alphabet (A-G, X-Z) with a specified string

You can read more about L-systems on Wiki: https://en.wikipedia.org/wiki/L-system

There are also a number of good resources via Google

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Lindenmayer System Generator</title>
<!-- D3.js -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<style type="text/css">
form {
display: table;
}
p {
display: table-row;
}
label {
display: table-cell;
text-align: right;
}
input {
display: table-cell;
}
submit {
display: table-cell;
}
#formContainer {
float: left;
width: 250;
}
#svgContainer {
margin-left: 250;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1px;
}
</style>
</head>
<body>
<div id=formContainer>
<form name="userForm" method="POST" onclick="return false;">
<p>
<label></label>
<b>Axiom</b>
</p>
<p>
<label for="axiom"></label>
<input id="axiom" type="text" value="A">
</p>
<p>
<label></label>
<b>Drawing Rules</b>
</p>
<p>
<label for="a">A=</label>
<input id="a" type="text" value="+B-A-B+">
</p>
<p>
<label for="b">B=</label>
<input id="b" type="text" value="-A+B+A-">
</p>
<p>
<label for="c">C=</label>
<input id="c" type="text">
</p>
<p>
<label for="d">D=</label>
<input id="d" type="text">
</p>
<p>
<label for="e">E=</label>
<input id="e" type="text">
</p>
<p>
<label for="f">F=</label>
<input id="f" type="text">
</p>
<p>
<label for="g">G=</label>
<input id="g" type="text">
</p>
<p>
<label></label>
<b>Constant Rules</b>
</p>
<p>
<label for="x">X=</label>
<input id="x" type="text">
</p>
<p>
<label for="y">Y=</label>
<input id="y" type="text">
</p>
<p>
<label for="z">Z=</label>
<input id="z" type="text">
</p>
<p>
<label></label>
<b>Angle (in degrees)</b>
</p>
<p>
<label for="theta">&#x03B8;=</label>
<input id="theta" type="text" value="60">
</p>
<p>
<label for="submit"></label>
<input type="submit" id="submit">
</p>
</form>
</div>
<div>
Click to Iterate
</div>
<div id=svgContainer></div>
<script type="text/javascript">
//Width and height of the visualization
var w = 500;
var h = 300;
//padding creates a buffer of white space around the chart to make it a little easier to look at
var padding = 15;
//Create a canvas to display the chart on
var svg = d3.select("#svgContainer").append("svg")
.attr("width", '75%')
.attr("height", '75%')
.attr("viewBox", "0 0 " + w + " " + h);
//Create group elements to layer the svg
//This is an easy way to make sure things you want on top are on top, and things you want behind stay behind
//Otherwise it all depends on when things are drawn
var layer1 = svg.append('g');
var layer2 = svg.append('g');
var layer3 = svg.append('g');
//Initialize
var path_pts;
var rules;
//Rules for Drawn segments
var A_rule;
var B_rule;
var C_rule;
var D_rule;
var E_rule;
var F_rule;
var G_rule;
//Rules for constants (not drawn)
var X_rule;
var Y_rule;
var Z_rule;
//Angle corresponding to + (- corresponds to negative angle)
var angle;
//hold the current position and the angle currently pointed in
var x;
var y;
var theta;
//For the scale, we will calculate the min/max x,y
var min_x;
var max_x;
var min_y;
var max_y;
//We will display the iteration number
var it_no;
//Line generator
var line = d3.line()
.defined(function(d) {
return !isNaN(d[1]);
})
//.curve(d3.curveBasis)
.x(function(d) {
return xScale(d[0]);
})
.y(function(d) {
return yScale(d[1]);
});
//This function will iterate the rules string to its next state
function update_rules() {
rules = rules.toLowerCase();
rules = rules.split('a').join(A_rule);
rules = rules.split('b').join(B_rule);
rules = rules.split('c').join(C_rule);
rules = rules.split('d').join(D_rule);
rules = rules.split('e').join(E_rule);
rules = rules.split('f').join(F_rule);
rules = rules.split('g').join(G_rule);
rules = rules.split('x').join(X_rule);
rules = rules.split('y').join(Y_rule);
rules = rules.split('z').join(Z_rule);
rules = rules.toUpperCase();
}
//This function updates the x,y,theta positions
//it also updates the display (aka x,y points along the path)
function update_data() {
path_pts = [
[0, 0]
];
x = 0;
y = 0;
theta = 0;
//Holds the x,y,theta when a '(' is reached
var store_pos = [];
//Holds the popped value of store_pos when ')' is reached
var restore_pos = [];
//for updating the dataset, we can remove all the constants, because they don't change the x,y,theta coordinates
var draw_rules = rules.split('X').join('').split('Y').join('');
//convert the rules (excluding constants) into an array and iterate through them to update the position (x,y,theta)
//if x,y change update the path_pts
draw_rules.split('').map(function(str) {
switch (str) {
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
//For the drawing commands, update (x,y) and path_pts
x = x + Math.cos(theta);
y = y + Math.sin(theta);
path_pts.push([x, y]);
//update max/min for scales
min_x = d3.min([x, min_x]);
max_x = d3.max([x, max_x]);
min_y = d3.min([y, min_y]);
max_y = d3.max([y, max_y]);
break;
case '+':
theta = theta + angle;
break;
case '-':
theta = theta - angle;
break;
case '[':
case '(':
//corresponds to saving current position on stack
store_pos.push([x, y, theta]);
break;
case ']':
case ')':
//corresponds to getting last position from stack
restore_pos = store_pos.pop();
x = restore_pos[0];
y = restore_pos[1];
theta = restore_pos[2];
path_pts.push([null]);
path_pts.push([x, y]);
break;
default:
break;
}
return;
});
}
function display_fn() {
//Create the scales used to map the set
xScale = d3.scaleLinear()
.domain([min_x, max_x])
.range([padding, w - padding]);
yScale = d3.scaleLinear()
.domain([min_y, max_y])
.range([h - padding, padding]);
var delay_len = Math.max(5000, path_pts.length * 10);
//determine if the line should be filled
if (rules[0] == "{") {
var fill_type = "steelblue"
} else {
var fill_type = "none"
};
//Draw the path
var path = layer2.append("path")
.data(path_pts)
.attr("fill", fill_type)
.attr("d", line(path_pts))
.attr("class", "line");
var totalLength = path.node().getTotalLength();
//transition the path as if it was being drawn
path.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition().ease(d3.easeLinear)
.duration(delay_len)
.attr("stroke-dashoffset", 0);
var text = layer1.selectAll(".text").data([0]);
//update
text.text(it_no);
//enter
text.enter()
.append("text")
.attr("x", padding)
.attr("y", 30)
.attr("dy", ".5em")
.attr("font-size", "28px")
.text("0")
.attr("class", "text");
}
function step() {
it_no++;
layer2.selectAll(".line").remove();
update_rules();
update_data();
display_fn();
}
function initialize() {
//Initialize
//Holds the x,y points along the path that will be displayed
var path_pts = [
[0, 0]
];
//initial state
rules = d3.select("#axiom").node().value;
//Rules for Drawn segments
A_rule = d3.select("#a").node().value;
B_rule = d3.select("#b").node().value;
C_rule = d3.select("#c").node().value;
D_rule = d3.select("#d").node().value;
E_rule = d3.select("#e").node().value;
F_rule = d3.select("#f").node().value;
G_rule = d3.select("#g").node().value;
//Rules for constants (not drawn)
X_rule = d3.select("#x").node().value;
Y_rule = d3.select("#y").node().value;
Z_rule = d3.select("#z").node().value;
//Angle corresponding to + (- corresponds to negative angle)
angle = (d3.select("#theta").node().value / 180) * Math.PI;
//Sometimes a minus gets pasted in as a dash, convert them all to a single charater
//also convert to upper case
function clean_rules(str) {
return str.split('\u2212').join('-').toUpperCase();
}
rules = clean_rules(rules);
A_rule = clean_rules(A_rule);
B_rule = clean_rules(B_rule);
C_rule = clean_rules(C_rule);
D_rule = clean_rules(D_rule);
E_rule = clean_rules(E_rule);
F_rule = clean_rules(F_rule);
G_rule = clean_rules(G_rule);
X_rule = clean_rules(X_rule);
Y_rule = clean_rules(Y_rule);
Z_rule = clean_rules(Z_rule);
//hold the current position and the angle currently pointed in
x = 0;
y = 0;
theta = 0;
//For the scale, we will calculate the min/max x,y
min_x = 0;
max_x = 0;
min_y = 0;
max_y = 0;
//We will display the iteration number
it_no = 0;
//don't need to update the rules for the 0th iteration
layer2.selectAll(".line").remove();
update_data();
display_fn();
}
initialize();
d3.select('#submit')
.html('<input type="submit" id="submit">')
.on('click', initialize);
d3.select("#svgContainer")
.on("click", step);
d3.select("#svgContainer")
.on("touchstart", step);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment