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 = 360polygon 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)
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 bysetRotation(). Persists across frames; survivesreset().rotation— Accumulated rotation incremented byrotate(). Starts at 0; cleared byreset().
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) ordrawOnGrid()(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().
| Parameter | Type | Default | Description |
|---|---|---|---|
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. |
// 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 SWPointThe 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 numberSize 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 booleanSelects 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 numberStatic base rotation in CCW degrees. Set by setRotation(); survives reset().
c.setRotation(90); // point cusp upwardrotation numberAccumulated rotation in degrees, incremented each frame by rotate(). Starts at 0; cleared by reset().
// Cleared automatically by reset(); read-only in normal useSWCardioid.SAMPLE_COUNT staticNumber 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
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 = 12setUseCosine(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 defaultsstatic 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...