◎ SWLoopyCircle Reference

Spirograph Parametric Curve — Parametric Class — SketchWaveJS

◎ Parametric Mathematics

The Curve Equations

SWLoopyCircle uses a direct parametric form that generates smooth closed curves with inner loops. Two frequency components combine — a primary circle and a secondary oscillation — to produce the characteristic Spirograph-like shape:

x(t) = r · cos(t) + d · cos(b · t)
y(t) = r · sin(t) − d · sin(b · t)

t ∈ [0, 2π),  600 sample points

Understanding the Parameters

ParameterRoleEffect
r Main radius Sets the overall size of the figure. The curve spans roughly r+d in radius.
d Displacement Controls the depth of the inner loops. Larger d = deeper, more dramatic loops. When d > r, loops extend outside the main circle.
b Frequency (integer ≥ 0) Number of complete secondary oscillations per revolution. Creates b+1 inner loops when d > r/b; smooth with dimples otherwise. b=0: offset circle; b=1: ellipse.

Loop Count by b

bLoops (d > r/b)Shape / Description
0Offset circle: circle of radius r shifted d units along x; cos(0)=1, sin(0)=0
1Ellipse: semi-axes (r+d) and (r−d); collapses to a line segment when d = r
233-loop figure; dimpled when d < r/2; loops appear once d > r/2
344-loop curve; balanced and symmetric (default with r=5, d=2)
455-loop figure; resembles a rounded star
566-loop star
677-loop rosette
7–108–11Dense multi-loop rosettes with intricate overlap

Why the Negative Sign in y?

The primary circle terms (r·cos(t), r·sin(t)) rotate counter-clockwise. The secondary terms add oscillation at frequency b. The negative sign in the y-equation — −d·sin(b·t) instead of +d·sin(b·t) — causes the secondary component to rotate in the opposite direction. This opposing rotation is what creates the crossing, looping path geometry.

Boundary Behavior

The curve's overall bounding radius oscillates between |r − d| and r + d. Self-intersection (loops) is governed by a separate threshold:

  • d ≤ r/b — curve is smooth with inward dimples; no self-intersections.
  • d > r/b — curve self-intersects, producing exactly b+1 inner loops.
  • d < r — loops remain interior to the main (navy guide) circle.
  • d = r — inner boundary touches the origin; loop tips become cusps.
  • d > r — loops extend past the main circle into the amber outer boundary region.

Connection to Spirograph / Trochoids

The Spirograph toy (Denys Fisher, 1965) produces hypotrochoids (inner gear rolling) and epitrochoids (outer gear rolling). The SWLoopyCircle parametric form is equivalent to a simplified trochoid with gear ratio b. The parameter b corresponds to the ratio of the fixed ring gear to the rolling gear; with integer b, the curve closes perfectly after one revolution. Non-integer b values produce open spirals that require many revolutions to close — SWLoopyCircle uses integer b only.

Constructor

new SWLoopyCircle(center, r, d, b, fillColor, strokeColor, thickness, rotationDeg)
ParameterTypeDefaultDescription
centerSWPointrequiredCenter position in user (grid) coordinates
rnumber5Main radius in grid units (min 0.01)
dnumber2Displacement arm length in grid units (min 0); loops appear when d > r/b; d=0 gives a pure circle of radius r
binteger3Frequency multiplier (min 0); b+1 loops when d > r/b; fractional values are rounded; 0 = offset circle, 1 = ellipse
fillColorSWColorundefinedFill color; undefined = no fill
strokeColorSWColorundefinedStroke color; undefined = no stroke
thicknessnumber2Stroke weight in pixels
rotationDegnumber0Static base rotation in CCW degrees; preserved across reset()
// 4-loop loopy circle (b=3, d=2 > r/b=1.67)
const center = new SWPoint(0, 0, undefined, 8, new SWColor(235, 60, 25, 100));
const fill   = SWColor.fromHex('#4a6fd8', 35, 'fill');
const stroke = SWColor.fromHex('#1a2d6c', 100, 'stroke');
const lc     = new SWLoopyCircle(center, 5, 2, 3, fill, stroke, 2, 0);
lc.drawOnGrid(grid);

Properties

Live Properties

PropertyTypeDescription
centerSWPointCenter position. Drag to reposition; set center.shouldShow = false to hide the dot
rnumberMain radius in grid units
dnumberDisplacement arm length in grid units
bintegerFrequency multiplier (≥ 0); b+1 inner loops when d > r/b
fillColorSWColorFill color (undefined = transparent fill)
strokeColorSWColorStroke color (undefined = no outline)
thicknessnumberStroke weight in pixels
rotationDegnumberStatic base rotation (CCW degrees); set via setRotation()
rotationnumberAccumulated spin rotation (degrees); incremented by rotate(); cleared by reset()

Static Property

PropertyValueDescription
SWLoopyCircle.SAMPLE_COUNT600Number of parametric sample points per full t ∈ [0, 2π) revolution

Methods

Drawing

MethodDescription
draw() Draws in raw pixel (screen) coordinates. Use center.x/y as pixel offsets. Prefer drawOnGrid() for standard use.
drawOnGrid(grid) Draws mapped through the given SWGrid. Handles the math-space ↔ screen y-flip automatically. Use this in the p5.js draw loop.

Animation

MethodParametersDescription
rotate(deltaAngle) deltaAngle: degrees (CCW+, CW−) Accumulates spin rotation. Call once per frame: lc.rotate(speed * deltaT)

Setters

MethodParameterDescription
setR(r)number ≥ 0.01Sets main radius. Use during Breathe animation.
setD(d)number ≥ 0Sets displacement arm length. d=0 gives a pure circle of radius r.
setB(b)integer ≥ 0Sets frequency multiplier; fractional values are rounded. 0 = offset circle, 1 = ellipse.
setRotation(deg)number (degrees)Sets static base rotation (CCW). Does not clear accumulated rotation.
setFillColor(fc)SWColorReplaces fill color (deep copy stored).
setStrokeColor(sc)SWColorReplaces stroke color (deep copy stored).
setStrokeWeight(w)numberSets stroke thickness in pixels.
setFillAlpha(alpha)0–100Updates fill opacity and rebuilds the p5 color object.
setStrokeAlpha(alpha)0–100Updates stroke opacity and rebuilds the p5 color object.

Reset & Utility

MethodDescription
reset() Restores r, d, b, rotationDeg, colors, and thickness to constructor values. Clears accumulated spin rotation. Does not move center.
SWLoopyCircle.copy(other) (static) Returns a deep copy of other (including center SWPoint, colors, and accumulated rotation).
toString() Returns a human-readable string: SWLoopyCircle(center:(x, y), r:R, d:D, b:B, rotation:θ°)

Code Examples

1. Basic Setup (p5.js global mode)

let grid, lc;

function setup() {
    createCanvas(400, 400);
    colorMode(HSB, 360, 100, 100, 100);
    initializeSWColors();

    grid = new SWGrid({ UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10) });

    const center = new SWPoint(0, 0, undefined, 8,
        new SWColor(235, 60, 25, 100));
    const fill   = SWColor.fromHex('#4a6fd8', 35, 'fill');
    const stroke = SWColor.fromHex('#1a2d6c', 100, 'stroke');
    lc = new SWLoopyCircle(center, 5, 2, 3, fill, stroke, 2, 0);
}

function draw() {
    background(0, 0, 93);
    grid.draw();
    lc.drawOnGrid(grid);
    grid.updateScreenBounds();
}

2. Spin Animation

let prevT = 0;
const SPIN_SPEED = 45; // degrees per second

function draw() {
    const t = millis() / 1000;
    const deltaT = (prevT > 0) ? (t - prevT) : 0;
    prevT = t;

    background(0, 0, 93);
    grid.draw();
    lc.rotate(SPIN_SPEED * deltaT);  // CCW spin
    lc.drawOnGrid(grid);
    grid.updateScreenBounds();
}

3. Breathe Animation (Main Radius Oscillation)

const BASE_R        = 5.0;
const BREATHE_SPEED  = 0.5;  // Hz
const BREATHE_AMOUNT = 1.5;  // grid units

function draw() {
    const t = millis() / 1000;

    background(0, 0, 93);
    grid.draw();

    // Sinusoidal radius oscillation
    const r = BASE_R + BREATHE_AMOUNT * Math.sin(2 * Math.PI * BREATHE_SPEED * t);
    lc.setR(Math.max(0.5, r));

    lc.drawOnGrid(grid);
    grid.updateScreenBounds();
}

4. Three-Loop Figure (b = 2, d > r/2)

// 3-loop figure — d=3 exceeds threshold r/b = 5/2 = 2.5, so loops appear
const center = new SWPoint(0, 0, undefined, 8,
    new SWColor(235, 60, 25, 100));
const fill   = SWColor.fromHex('#6a3fd8', 40, 'fill');   // purple fill
const stroke = SWColor.fromHex('#2d0a6c', 100, 'stroke');
const lc     = new SWLoopyCircle(center, 5, 3, 2, fill, stroke, 2, 0);

5. Star Rosette (b = 5, d > r)

// 6-loop star (b+1=6) — d=5 > r/b=0.8, and d > r so loops extend beyond main circle
const lc = new SWLoopyCircle(center, 4, 5, 5, fill, stroke, 2, 0);

6. Copy and Reset

// Deep copy
const copy = SWLoopyCircle.copy(lc);

// Reset to constructor values
lc.reset();
// Center position preserved; r, d, b, rotation restored

// toString
console.log(lc.toString());
// → SWLoopyCircle(center:(0.00, 0.00), r:5.00, d:2.00, b:3, rotation:0.0°)

Source Code

Show / Hide swLoopyCircle.js source
Loading source...