Published
Edited
Jan 16, 2020
2 stars
Insert cell
md`# Fern`
Insert cell
paper = require("paper");
Insert cell
// n = 7, ratio = 2.3 (used for controlling size of each segment.)
fern(7, 2.3)
Insert cell
// n = 2, ratio = 3 (used for controlling size of each segment.)
fern(2, 3)
Insert cell
// n = 3, ratio = 2.75 (used for controlling size of each segment.)
fern(3, 2.75)
Insert cell
// n = 4, ratio = 2.55 (used for controlling size of each segment.)
fern(4, 2.55)
Insert cell
// n = 5, ratio = 2.45 (used for controlling size of each segment.)
fern(5, 2.45)
Insert cell
// n = 6, ratio = 2.35 (used for controlling size of each segment.)
fern(6, 2.35)
Insert cell
// n = 7, ratio = 2.30 (used for controlling size of each segment.)
fern(7, 2.3)
Insert cell
/*
n = 7
ratio = 2.15 (used for controlling size of each segment.)
angle = 120 (each turn is now 120 degrees)
# the remaining arg control the size of the figure
x_start = 325 (origin x-loc)
width = 950 (width of the figure)
height = 800 (height of the figure)
y_start = 750 (origin y-loc)
*/
fern(7, 2.15, 120, 325, 950, 800, 750)
Insert cell
/*
n = 7
ratio = 2.27 (used for controlling size of each segment.)
angle = 45 (each turn is now 120 degrees)
# the remaining arg control the size of the figure
x_start = 225 (origin x-loc)
width = 550 (width of the figure)
*/
fern(7, 2.27, 45, 225, 550)
Insert cell
/*
n = 7
ratio = 2.2 (used for controlling size of each segment.)
angle = 90 (each turn is now 120 degrees)
# the remaining arg control the size of the figure
x_start = 225 (origin x-loc)
width = 550 (width of the figure)
*/
fern(7, 2.2, 90, 225, 550)
Insert cell
/*
n = 7
ratio = 2.4 (used for controlling size of each segment.)
angle = 10 (each turn is now 120 degrees)
# the remaining arg control the size of the figure
x_start = 75 (origin x-loc)
width = 200 (width of the figure)
*/
fern(6, 2.40, 10, 75, 200)
Insert cell
function fern(n=7, ratio=2.35, angle=25, x_start=150, width=400, height=500, y_start=475) {
/* The primary plotting function for plotting a fern.
*
* Parameters
* ----------
* n : The number of iterations.
* ratio : The ratio of the number of iterations to the size of the image.
* angle : The angle of splitting branches.
* width : The width of the resulting image.
* height : The height of the resulting image.
* y_start : The location (from the top) from which the image draws,
* The x_start is always in the center.
*/
// This block initializes the canvas and hooks up Paper.js to the canvas.
let canvas = this || DOM.canvas(width, height);
let ps = canvas.ps || (() => {
let ps = new paper.PaperScope();
ps.setup(canvas);
return ps;
})();
canvas.ps = ps;
// The axiom is the initial (n = 0) starting system or string.
let axiom = ["X"];
// The grammar (set of productions rules P) which control the iterative production
// of the strings for each iteration.
let grammar = {
"X": ["F+[[X]-X]-F[-FX]+X"],
"F": "FF",
"+": "+",
"-": "-",
"[": "[",
"]": "]",
}
// Build up the code (or string) by repeatedly (n times) mapping each symbol in the
// current string to its corresponding code in the grammar.
let code = axiom;
for (let i = 0; i < n; i++) {
code = code.map(l => grammar[l]).join("").split("")
}
// The magnitude controls the lenght of each segment of the fern.
let magnitude = height / Math.pow(ratio, n);
draw(ps, code, angle, magnitude, x_start, y_start);
return canvas;
}
Insert cell
function draw(ps, code, rotate, magnitude, x_start, y_start) {
/* Produces an image given the code (or string).
*
* Parameters
* ----------
* ps : Paper context. Using this will draw things on the canvas.
* code : The description of the current fern.
* rotate : How much to rotate when turning.
* magnitude : How large each step forward is.
* x_start : Where to start the fractal (from the left.)
* y_start : Where to start the fractal (from the bottom.)
*/
// The current direction (by default, 90 degrees -- e.g. pointing up.)
let angle = Math.PI / 2;
let start = new ps.Point(x_start, y_start);
let stack = []
let curr = start;
let next = start;
var state;
// For each character in the code, execute a command to render or otherwise
// update the current state.
for (let char of code) {
switch(char) {
case "F":
// Draw a line forward.
next = new ps.Point(get_next(curr, angle, magnitude));
let seg = ps.Path.Line(curr, next);
seg.strokeColor = "black";
curr = next;
break;
case "-":
// Turn the turtle right.
angle = turn_right(angle, rotate);
break;
case "+":
// Turn the turtle left.
angle = turn_left(angle, rotate);
break;
case "[":
// Save the current state.
let s = new State(curr, angle)
stack.push(s);
break;
case "]":
// Reset the state to the previous state.
state = stack.pop();
curr = state.point;
angle = state.angle;
break;
}
}
}

Insert cell
function turn_left(angle, rotate) {
const left = rotate / 180 * Math.PI;
return (angle + left) % (2 * Math.PI);
}
Insert cell
function turn_right(angle, rotate) {
const right = rotate / 180 * Math.PI;
return (angle - right) % (2 * Math.PI);
}
Insert cell
function get_next(curr, angle, magnitude) {
let x = magnitude * Math.cos(angle);
let y = magnitude * Math.sin(angle);
return new paper.Point(curr.x + x, curr.y - y);
}
Insert cell
class State {
constructor(point, angle) {
this.point = point;
this.angle = angle;
}
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more