❤ SWCardioid Reference

A SketchWave polar class for representing the cardioid: r = a·(1 + cos(θ)) — a single-cusped, heart-shaped polar curve

Back to SWCardioid Demo

Quick Reference

SWCardioid is a SketchWave polar curve class representing the classical cardioid. The curve is defined by r = a·(1 + cos(θ)) (or the sine variant), sampled over θ ∈ [0, 2π) at SAMPLE_COUNT points and drawn as a closed polygon. The parameter a controls the overall size; the maximum radius is 2a and the cusp occurs at the pole (the center point).

  • Design Pattern: Polar curve (not composition or inheritance)
  • Equations: r = a·(1 + cos(θ))  or  r = a·(1 + sin(θ))
  • Always non-negative r: 1 + cos(θ) ≥ 0 and 1 + sin(θ) ≥ 0 for all θ
  • Internal Structure: SAMPLE_COUNT = 360 polygon vertices over [0, 2π); naturally closed
  • Dependencies: SWPoint, SWColor, SWGrid, p5.js
  • Key Features: Configurable size (a), cos/sin toggle, spin animation, pulse animation (oscillating a), draggable center
  • Common Uses: Heart-shaped art, mathematical curve exploration, limaçon family demonstration, animated heartbeat effect

Overview

The SWCardioid class draws a polar cardioid as a closed polygon. The curve is defined in polar coordinates as:

r = a · (1 + cos(θ))    (cosine form, default; cusp at left, opens right)
r = a · (1 + sin(θ))    (sine form; 90° CCW rotation; cusp at bottom, opens upward)
Key geometric facts
Cusp: at the pole (center point), where r = 0 — at θ = π (cos form) or θ = 3π/2 (sin form).
Maximum radius: 2a — reached at θ = 0 (cos form) or θ = π/2 (sin form).
Enclosed area: (3/2)πa²
Arc length: 8a

Non-negative Radius

Because 1 + cos(θ) and 1 + sin(θ) are always ≥ 0, the cardioid never produces a negative r value. This makes SWCardioid simpler than SWRose — no negative-radius handling is needed. The formula x = r·cos(θ), y = r·sin(θ) works cleanly throughout.

Cosine vs Sine Form

The cosine form opens to the right (in standard math-space orientation). The sine form is a pure 90° CCW rotation of the cosine form — unlike the rose curve where the rotation was 90/n degrees, for the cardioid it is always exactly 90°. Both forms produce identical shapes; only the orientation differs.

Limaçon Family

The cardioid is the special case of the limaçon r = b + a·cos(θ) where b = a. When b > a you get a convex limaçon; when b < a you get an inner loop. The cardioid (b = a) is the boundary between these two regimes and produces exactly one cusp at the pole.

Rotation

SWCardioid supports two layers of rotation, both applied about the center point:

  • rotationDeg — Static base rotation set by setRotation(). Persists across frames; survives reset().
  • rotation — Accumulated rotation incremented by rotate(). Starts at 0; cleared by reset().

Effective rotation = rotationDeg + rotation. All angles are CCW positive (user-space convention).

Key Capabilities

  • Polar Cardioid Curves: Both r=a·(1+cosθ) and r=a·(1+sinθ) with a single boolean toggle
  • Spin Animation: Rotate continuously about the center using rotate()
  • Pulse Animation: Oscillate the size parameter (a) sinusoidally using setA() from the sketch layer — like a beating heart
  • Fill & Stroke Colors: Independent color pickers and alpha channels
  • Draggable Center: Move the entire cardioid by repositioning the center SWPoint (the pole)
  • Dual Coordinate Systems: draw() (screen pixels) or drawOnGrid() (grid user coordinates)

Constructor

new SWCardioid(center, a, strokeColor, fillColor, thickness, useCosine, rotationDeg)

Creates a new SWCardioid instance. All constructor values are saved as originals for reset().

Parameters
ParameterTypeDefaultDescription
center SWPoint required The pole (origin) of the cardioid in user (grid) coordinates. The cusp is located here; all other points radiate outward.
a number required Size parameter. The cardioid spans from r = 0 (the cusp) to r = 2a (the widest point). Values below 0.01 are clamped.
strokeColor SWColor | undefined undefined Outline color. undefined = no stroke drawn.
fillColor SWColor | undefined undefined Fill color for the cardioid interior. undefined = no fill (transparent).
thickness number 2 Stroke weight in pixels.
useCosine boolean true true: r=a·(1+cosθ) (opens right; cusp left). false: r=a·(1+sinθ) (90° CCW rotation; opens upward; cusp at bottom).
rotationDeg number 0 Static base rotation in CCW degrees; applied in addition to accumulated rotation.
Constructor Examples
// Default cardioid, crimson stroke, semi-transparent fill
const stroke = SWColor.fromHex('#880044', 100, 'cardStroke');
const fill   = SWColor.fromHex('#cc2266',  55, 'cardFill');
let c = new SWCardioid(new SWPoint(0, 0), 7, stroke, fill, 3);

// Cardioid with sine form (opens upward)
let cu = new SWCardioid(new SWPoint(0, 0), 6, stroke, fill, 3, false);

// Cardioid pre-rotated 45°
let cr = new SWCardioid(new SWPoint(0, 0), 8, stroke, fill, 2, true, 45);

// Stroke only (no fill)
const dark = new SWColor(0, 0, 20, 100, "dark");
let co = new SWCardioid(new SWPoint(0, 0), 5, dark);

Properties

center SWPoint

The cardioid's pole in user (grid) coordinates. The cusp of the cardioid is always at this point. Moving center.x or center.y repositions the entire cardioid.

c.center.x = 2; c.center.y = 1;
a number

Size parameter. The cardioid extends from r = 0 (cusp) to r = 2a (widest point). Use setA() to change it.

c.setA(5); // smaller cardioid, max radius 10
c.setA(9); // larger cardioid, max radius 18
useCosine boolean

Selects the equation form. true (default) uses r=a·(1+cosθ) (opens right); false uses r=a·(1+sinθ) (opens upward, 90° CCW rotation). Use setUseCosine() to toggle.

c.setUseCosine(false); // switch to sine form (opens upward)
rotationDeg number

Static base rotation in CCW degrees. Set by setRotation(); survives reset().

c.setRotation(90); // point cusp upward
rotation number

Accumulated rotation in degrees, incremented each frame by rotate(). Starts at 0; cleared by reset().

// Cleared automatically by reset(); read-only in normal use
SWCardioid.SAMPLE_COUNT static

Number of polygon vertices (default: 360 — one per degree over [0, 2π)). Higher values produce smoother curves. Can be changed globally at any time.

SWCardioid.SAMPLE_COUNT = 72; // faceted look
SWCardioid.SAMPLE_COUNT = 720; // ultra-smooth

Methods

Drawing Methods

draw()

Draws the cardioid in raw screen (pixel) coordinates. The center SWPoint is interpreted as screen pixels. Prefer drawOnGrid() for standard canvas use with an SWGrid.

function draw() {
    background(220);
    cardioid.draw();
}
drawOnGrid(grid)

Draws the cardioid mapped through the given SWGrid's coordinate system. The grid handles the y-flip (math up → screen down) and coordinate scaling.

function draw() {
    background(220);
    grid.draw();
    cardioid.drawOnGrid(grid);
    grid.updateScreenBounds();
}

Rotation Animation

rotate(deltaAngle)

Spins the cardioid about its center (pole) by deltaAngle degrees (CCW+, CW−). Accumulates into this.rotation. Call once per frame in draw() before calling drawOnGrid().

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

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

    background(240);
    grid.draw();
    cardioid.rotate(SPIN_SPEED * deltaT);   // spin BEFORE drawing
    cardioid.drawOnGrid(grid);
    grid.updateScreenBounds();
}

Pulse Animation

Pulse — sinusoidal amplitude oscillation

The pulse effect oscillates the size parameter a sinusoidally, making the cardioid expand and contract like a beating heart. It is implemented in the sketch layer using setA():

currentA = baseA + depth · sin(2π · speed · t)

baseA is the steady-state size (typically from a slider). depth (Δa) is the oscillation amplitude. speed is the frequency in Hz. A speed of ~0.8 Hz approximates a realistic heartbeat rhythm. The result is clamped to a minimum of 0.1. Pulse and Spin can run simultaneously.

const PULSE_SPEED = 0.8; // Hz — approximate heartbeat rhythm
const PULSE_DEPTH = 2.0; // Δa (grid units)
let baseA = 7;

function draw() {
    const t       = millis() / 1000;
    const pulsedA = baseA + PULSE_DEPTH * sin(TWO_PI * PULSE_SPEED * t);
    cardioid.setA(max(0.1, pulsedA));
    cardioid.drawOnGrid(grid);
}

Setter Methods

setA(a)

Updates the size parameter. Max radius = 2a. Values below 0.01 are clamped. Takes effect immediately on the next draw call.

c.setA(6); // max radius = 12
setUseCosine(val)

Switches between cosine (true) and sine (false) form. The sine form is a 90° CCW rotation of the cosine form.

c.setUseCosine(false); // switch to sin form (opens upward)
setStrokeColor(sc)   setFillColor(fc)

Sets the stroke or fill color. Pass an SWColor instance or undefined to remove it.

c.setStrokeColor(SWColor.fromHex('#880044', 100, 's'));
c.setFillColor(undefined); // transparent
setFillAlpha(alpha)   setStrokeAlpha(alpha)

Sets the fill or stroke alpha (0–100) and rebuilds the p5 color object. Requires an existing color to be set first.

c.setFillAlpha(40); // 40% opacity fill
c.setStrokeAlpha(100); // fully opaque stroke
setStrokeWeight(w)

Sets the stroke thickness in pixels.

c.setStrokeWeight(4);
setRotation(deg)

Sets the static base rotation in CCW degrees. Does not affect the accumulated rotation.

c.setRotation(180); // cusp now opens left (flips horizontally)

Reset & Utility Methods

reset()

Restores all animated and slider-driven properties to their original constructor values. Clears accumulated spin rotation. Does not move the center position.

c.reset(); // back to factory defaults
static SWCardioid.copy(other)

Creates a deep copy of the given SWCardioid, preserving all current and original state including rotation accumulation.

const copy = SWCardioid.copy(c);
toString()

Returns a human-readable string describing the cardioid's current state.

console.log(c.toString());
// "SWCardioid(center=SWPoint(x:0, y:0), a=7.00, formula=r=7.00·(1+cos(θ)), rotationDeg=0.0, rotation=0.0)"

Code Examples

Minimal sketch (cardioid on a grid)

let grid, cardioid;

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 stroke = SWColor.fromHex('#880044', 100, 's');
    const fill   = SWColor.fromHex('#cc2266',  55, 'f');
    cardioid = new SWCardioid(new SWPoint(0, 0), 7, stroke, fill, 3);
}

function draw() {
    background(240);
    grid.draw();
    cardioid.drawOnGrid(grid);
    grid.updateScreenBounds();
}

Spinning cardioid

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

function setup() { /* ... create grid and cardioid ... */ }

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

    background(240);
    grid.draw();
    cardioid.rotate(SPIN_SPEED * deltaT);   // spin BEFORE drawing
    cardioid.drawOnGrid(grid);
    grid.updateScreenBounds();
}

Pulsing heartbeat

const PULSE_SPEED = 0.8; // Hz (~heartbeat rhythm)
const PULSE_DEPTH = 2.0; // Δa
let baseA = 7;

function draw() {
    const t = millis() / 1000;
    const pulsedA = baseA + PULSE_DEPTH * sin(TWO_PI * PULSE_SPEED * t);
    cardioid.setA(max(0.1, pulsedA));
    cardioid.drawOnGrid(grid);
}

Using a color picker with SWColor.fromHex()

const picker = document.getElementById('strokePicker');
const alpha  = document.getElementById('alphaSlider');

picker.addEventListener('input', () => {
    const col = SWColor.fromHex(picker.value, Number(alpha.value), 'cardStroke');
    cardioid.setStrokeColor(col);
});

alpha.addEventListener('input', () => {
    const col = SWColor.fromHex(picker.value, Number(alpha.value), 'cardStroke');
    cardioid.setStrokeColor(col);
});

Required script tags (in dependency order)

<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.js"></script>

<!-- SketchWaveJS classes in dependency order -->
<script src="../shapeClasses/swColor.js"></script>
<script src="../shapeClasses/swPoint.js"></script>
<script src="../shapeClasses/swGrid.js"></script>
<script src="../shapeClasses/swCardioid.js"></script>

<!-- Your sketch -->
<script src="../sketches/yourSketch.js"></script>

Design Notes

  • r is always non-negative: Unlike the polar rose, SWCardioid never produces a negative r value because 1 + cos(θ) is always ≥ 0. This simplifies the implementation and avoids the "opposite-petal" behavior of the rose.
  • Exact 90° rotation between forms: For the cardioid, switching from cosine to sine form always rotates the entire figure exactly 90° CCW — there is no n-dependent factor as in the rose curve.
  • Cusp is always at the center point (pole): The cusp occurs at r = 0, which corresponds to the center SWPoint. Dragging the center moves the cusp with it.
  • SAMPLE_COUNT = 360 gives a smooth curve: The cardioid has no sharp corners (except the single cusp at θ = π). 360 samples produces a smooth result at normal canvas sizes.
  • reset() does not move the center: Consistent with all SketchWaveJS polar classes; dragging and repositioning the shape is preserved across resets.
  • Area = (3/2)πa²: For example, a cardioid with a = 7 encloses an area of (3/2)π·49 ≈ 230.9 square grid units.
  • The cardioid is the pedal curve of a circle: Geometrically, a cardioid is the path traced by a point on a circle rolling around another circle of equal radius. This is the "epitrochoid" interpretation.

Source Code

The complete SWCardioid class implementation:

Show/Hide Source Code
// Loading source code...