This library allows you to generate fractals in two different ways:
npm install fractals #npm
yarn add fractals #yarn
import { IFS, LSystem } from 'fractals';
import type { TBounds } from 'fractals';
// or separtly
import { IFS } from 'fractals/lib/ifs';
import { LSystem } from 'fractals/lib/l';
import type { TBounds } from 'fractals/lib/types';
Both classes implement the IFractal
interface.
type TPoint = [x: number, y: number, meta: Record<string, unknown>];
type TBounds = [maxX: number, maxY: number, minX: number, minY: number];
type TPointCb = (p: TPoint, i: number) => unknown;
interface IFractal {
readonly points: TPoint[]; // Array of generated points
bounds: TBounds; // Bounding box of generated points
run(fn?: TPointCb): void; // Function to start calculation process
}
An excellent material with many examples about IFS can be read here.
interface IIFSMatrix {
// The probability of choosing the current matrix
p: number;
// A set of constants for the equation for calculating the next point
[$key: string]: number;
}
type TEPoint = { x: number; y: number };
// The equation for calculating the next point
type TEquation = (x: number, y: number, m: IIFSMatrix) => TEPoint;
// The type of the result point. matrixNum - the number of the matrix
// that was chosen to generate the point. It can be useful for
// debugging or coloring points depending on the matrix.
type TIFSPoint = [x: number, y: number, meta: { matrixNum: number }];
There are two predefined equations:
Don't worry, as a code these formulas are not as scary as they seem:
export function affine(x: number, y: number, m: IIFSMatrix): TEPoint {
const { a, b, c, d, e, f } = m;
const newX = x * a + y * b + e;
const newY = x * c + y * d + f;
return { x: newX, y: newY };
}
export function radial(x: number, y: number, m: IIFSMatrix): TEPoint {
const { a, b, t, e, f } = m;
const newX = x * a * Math.cos(t) - y * b * Math.sin(t) + e;
const newY = x * a * Math.sin(t) + y * b * Math.cos(t) + f;
return { x: newX, y: newY };
}
Other classes of simple geometric transformations can also be used to construct the IFS. For example, projective:
X' = (ax*X + bx*Y + cx) / (dx*X + ex*Y + fx)
Y' = (ay*X + by*Y + cy) / (dy*X + ey*Y + fy)
or quadratic:
X' = ax*X*X + bx*X*Y + cx*Y*Y + dx*X + ex*Y + fx
Y' = ay*X*X + by*X*Y + cy*Y*Y + dy*X + ey*Y + fy
The required matrix property is p
.
This is the probability of choosing a given matrix.
All other fields depend on the equation. For example,
Barnsley Fern is calculated using affine transformations
with the following matrices:
Transformation | Transition | Probability |
---|---|---|
1% | ||
85% | ||
7% | ||
7% |
The matrices' configuration will be as follows:
const fern = {
matrices: [
{ a: 0, b: 0, c: 0, d: 0.16, e: 0, f: 0, p: 0.01 },
{ a: 0.85, b: 0.04, c: -0.04, d: 0.85, e: 0, f: 1.6, p: 0.85 },
{ a: 0.2, b: -0.26, c: 0.23, d: 0.22, e: 0, f: 1.6, p: 0.07 },
{ a: -0.15, b: 0.28, c: 0.26, d: 0.24, e: 0, f: 0.44, p: 0.07 },
],
};
matrices: IIFSMatrix[]
- array of given matricespoints: TIFSPoint[]
- Calculated points.bounds: TBounds
- Bounding box of the calculated points in format [maxX, maxY, minX, minY]. Will be filled in after calling therun()
method.
constructor(params: IIFSParams)
interface IIFSParams {
// Array of matrices
matrices: IIFSMatrix[];
// Think of this parameter as the scale of the plot.
// The larger the number, the fewer points will be per area unit.
density?: number;
// Number of iterations (points)
iterations?: number;
// Formula for calculating points.
// You can use one of the predefined ones or write your own.
equation?: TEquation;
}
run(callback?)
Starts the calculation process. You can pass a callback function that will be called after each point is calculated. This will help to achieve higher performance by removing the extra cycle for drawing the fractal. Be careful - the bounds of points may be incorrect until the end of the calculation of the entire fractal.
function canvasRenderer(canvas: HTMLCanvasElement, fractal: IFS) {
if (!canvas) {
console.warn('canvas is null');
return;
}
const offsetX = fractal.bounds[2];
const offsetY = fractal.bounds[3];
// margins is 10 px
canvas.height = fractal.bounds[1] + Math.abs(fractal.bounds[3]) + 20;
canvas.width = fractal.bounds[0] + Math.abs(fractal.bounds[2]) + 20;
const ctx = canvas.getContext('2d');
ctx.save();
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// margins
ctx.translate(-offsetX + 10, offsetY + canvas.height - 10);
ctx.scale(1, -1);
const color = 255 / fractal.matrices.length - 1;
for (let i = 0; i < fractal.points.length; i++) {
const [x, y, { matrixNum }] = fractal.points[i];
ctx.fillStyle = `hsl(${color * m}, 100%, 50%)`;
ctx.fillRect(x, y, 1, 1);
}
ctx.restore();
}
document.addEventListener('DOMContentLoaded', () => {
const fractal = new IFS(config);
fractal.run();
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
canvasRenderer(canvas, fractal);
});
An excellent material with many examples about L-system can be read here.
The following commands are supported:
Character | Meaning |
---|---|
F | Move forward by line length drawing a line |
B | Move backward by line length drawing a line |
+ | Turn left by turning angle |
− | Turn right by turning angle |
[ | Push current drawing state onto stack |
] | Pop current drawing state from the stack |
< | Multiply the line length by the line length scale factor |
> | Divide the line length by the line length scale factor |
// The type of the result point.
// paintable - currently used to work with the stack.
// When a point was added as a result of the ']' (pop) command.
type TLPoint = [x: number, y: number, meta: { paintable: boolean }];
points: TLPoint[]
- Calculated points.bounds: TBounds
- Bounding box of the calculated points in format [maxX, maxY, minX, minY]. Will be filled in after calling therun()
method.
constructor(params: ILParams)
interface ILParams {
// Initial string string with commands, e.g. 'X'
axiom: string;
// Hash with replacement rules
// {
// F: 'FF', - All occurrences of the character "F" will be replaced with the sequence "FF"
// X: 'F-[[X]+X]+F[+FX]-X', - and all occurrences of the character "X" will be replaced with the sequence "F-[[X]+X]+F[+FX]-X"
// }
rules: Record<string, string>;
// The number of replacement iterations
iterations: number;
// The initial length of the line.
distance: number;
// Angle of rotation.
angle: number;
// Length scaling factor. See commands `<` and `>`
lengthScale?: number;
}
run(callback?)
Starts the calculation process. You can pass a callback function that will be called after each point is calculated. This will help to achieve higher performance by removing the extra cycle for drawing the fractal. Be careful - the bounds of points may be incorrect until the end of the calculation of the entire fractal.
function canvasRenderer(canvas: HTMLCanvasElement, fractal: LSystem) {
if (!canvas) {
console.warn('canvas is null');
return;
}
const offsetX = -fractal.bounds[2];
const offsetY = -fractal.bounds[3];
// margins is 10 px
canvas.height = fractal.bounds[1] + Math.abs(fractal.bounds[3]) + 20;
canvas.width = fractal.bounds[0] + Math.abs(fractal.bounds[2]) + 20;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#000';
ctx.save();
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.translate(10, 10); // margins
const color = 255 / fractal.points.length - 1;
for (let i = 1; i < fractal.points.length; i++) {
const [x, y, { paintable }] = fractal.points[i];
if (!paintable) {
continue;
}
ctx.beginPath();
const [startX, startY] = fractal.points[i - 1];
ctx.strokeStyle = `hsl(${color * i}, 100%, 50%)`;
ctx.moveTo(startX + offsetX, startY + offsetY);
ctx.lineTo(x + offsetX, y + offsetY);
ctx.stroke();
ctx.closePath();
}
ctx.restore();
}
document.addEventListener('DOMContentLoaded', () => {
const fractal = new LSystem(config);
fractal.run();
const canvas = document.getElementById('canv') as HTMLCanvasElement;
canvasRenderer(canvas, fractal);
});
- L-System User Notes
- IFS manual
- Iterated Function Systems - From here you can take several matrices and look at an example of the IFS implementation in the clojure language.
- Classic Iterated Function Systems - Lots of interesting articles about fractals. List of well-known fractals with matrices to generate them.