Quick Reference
SWArc is a SketchWave class for drawing circular arcs — the curved "crust" of a circle — with no interior fill. An arc is defined by a center SWPoint, a radius, an angular size theta, a startAngle, a stroke thickness, and a stroke color. It supports five independent animations: spin, breathe radius, breathe theta, breathe thickness (unique to SWArc!), and hue cycling.
- Extends: Nothing (standalone class)
- Dependencies: SWPoint, SWColor, SWSinusoid, SWGrid, p5.js
- Key Features: Stroke-only arc (no fill), configurable stroke cap, arc length calculation, spin, breathe radius, breathe theta, breathe thickness, and hue cycling
- Common Uses: Loading spinners, progress rings, radar sweeps, rainbow-band effects, celestial orbit paths, lens flares
Overview
The SWArc class represents a circular arc — the curved boundary of a sector, drawn as a stroke with no fill. Think of an SWSector pizza slice where the interior filling and the two straight-edge cuts are discarded, leaving only the outer crust.
Because the arc has no fill, its thickness (stroke weight) becomes the primary visual variable — thick arcs look like solid ribbons, thin arcs look like fine lines. This makes breatheThickness() especially interesting: the arc's visual mass pulses.
Angle Convention
SWArc uses the same math-space (user-space) angle convention as SWSector: angles are measured counterclockwise (CCW) from the positive x-axis, and y-values increase upward. Because p5.js uses a y-down screen space, all angles are negated internally. Pass CCW degrees from +x; the class handles the rest.
// startAngle=0, theta=180 → semicircle opening upward (into Q1 and Q2)
let arc1 = new SWArc(new SWPoint(0,0), 5, 180);
// startAngle=90, theta=90 → quarter arc from +y axis leftward (Q2)
let arc2 = new SWArc(new SWPoint(0,0), 5, 90, 90);
SWArc vs SWSector
| Feature | SWSector | SWArc |
|---|---|---|
| Pivot point | vertex (tip) | center (circle center) |
| Fill | Yes (fillColor) | No (stroke only) |
| Stroke | Optional border | Primary visual element |
| Area | area property | None (no interior) |
| p5 arc mode | PIE | OPEN |
| Stroke cap | N/A | capStyle: round / butt / square |
| Unique animation | None | breatheThickness() |
Stroke Cap Styles
The capStyle property controls how the two arc endpoints are rendered. It accepts the raw HTML Canvas lineCap string values (which correspond to p5.js constants):
| capStyle value | p5 constant | Appearance |
|---|---|---|
'round' | ROUND | Semicircular ends — smooth, organic look. Default. |
'butt' | SQUARE | Flat ends, flush with the arc endpoint. Clean, precise. |
'square' | PROJECT | Flat ends extending half-thickness past the endpoint. Makes short arcs appear slightly longer. |
Typical Workflow
- Create an SWArc with center, radius, theta, startAngle, thickness, and strokeColor
- Call
drawOnGrid(grid)each frame - For animations, call timing helpers each frame:
rotate(delta)before draw (takes effect immediately)breatheRadius(),breatheTheta(),breatheThickness()after draw (affect next frame)- Hue cycling: mutate
arc.strokeColor.hand rebuildarc.strokeColor.colbefore draw
- Use the elapsed-time pattern for pause/resume of each animation
- Call
reset()to restore original geometry and color
Constructor
new SWArc(center, radius, theta, startAngle, thickness, strokeColor, capStyle)Creates a new SWArc instance with the given geometry and styling.
| Parameter | Type | Default | Description |
|---|---|---|---|
center |
SWPoint | required | Center of the underlying circle (not the vertex tip) |
radius |
number | required | Radius in user units (> 0) |
theta |
number | required | Angular size in degrees; clamped to [0, 360] |
startAngle |
number | 0 | Arc start angle in degrees CCW from +x axis |
thickness |
number | 6 | Stroke weight in pixels (the primary visual variable) |
strokeColor |
SWColor | undefined | Arc color; no arc drawn if undefined |
capStyle |
string | 'round' |
End-cap style: 'round', 'butt', or 'square' |
// Minimal: center, radius, theta (defaults to 6px thick, no color)
let a1 = new SWArc(new SWPoint(0, 0), 5, 180);
// With start angle: arc opens from the top (90°) sweeping 90° CCW
let a2 = new SWArc(new SWPoint(0, 0), 4, 90, 90);
// Full constructor: cobalt blue, 10px thick, round caps
let center = new SWPoint(0, 0);
let arcColor = new SWColor(213, 100, 80, 90, "cobalt");
let a3 = new SWArc(center, 5, 180, 0, 10, arcColor, 'round');
// Flat cap arc useful for tick marks or progress indicators
let a4 = new SWArc(new SWPoint(0, 0), 7, 45, 270, 4, arcColor, 'butt');
Properties
center — SWPoint
The center of the underlying circle. Drag this point to reposition the arc. The arc draws around this point at the given radius.
radius — number
Radius of the arc in user units. Modified by breatheRadius() or setRadius(). Also updates arcLength.
theta — number
Angular size of the arc in degrees, clamped to [0, 360]. 180° = semicircle, 360° = full circle. Modified by breatheTheta() or setTheta().
startAngle — number
In degrees CCW from the +x axis; sets the arc's initial orientation (where it begins). Does not change with rotate(); rotation is accumulated separately in rotation.
rotation — number
Accumulated rotation in degrees (CCW positive). Added to startAngle when rendering. Modified each frame by rotate(delta).
thickness — number
Stroke weight in pixels — this is the primary visual variable for arcs. Thick arcs look like solid bands; thin arcs look like fine lines. Modified by breatheThickness() or setStrokeWeight().
strokeColor — SWColor
The arc's visible color — the only color property (no fill). Modify strokeColor.h then rebuild strokeColor.col for hue cycling. Set via setStrokeColor().
capStyle — string
End-cap style for the arc endpoints. One of 'round', 'butt', or 'square'. Passed directly to the HTML Canvas lineCap property via p5's strokeCap().
arcLength — number (computed, read-only)
The arc length in user units: (theta / 360) × 2π × radius. Automatically recalculated whenever radius or theta changes.
shouldShowCenter — boolean
When true (default), the center SWPoint is rendered as a visible marker. Toggle with setShowCenter().
Original-state Properties (used by reset())
Set from constructor arguments and updated by slider interactions. Used to restore the arc to its initial state.
originalRadiusoriginalThetaoriginalStartAngleoriginalRotation(always 0 at construction)originalThicknessoriginalStrokeColor(deep copy of strokeColor at construction)
Methods
Drawing
draw()
Draws the arc in screen (pixel) coordinates, using the center's raw x/y as pixel positions. Use drawOnGrid() for user-coordinate rendering.
drawOnGrid(grid)
Draws the arc mapped through the given SWGrid. User coordinates (center.x, center.y, radius) are converted to screen pixels via the grid's scale and offset. Use this in a demo that has an SWGrid.
// Typical setup in draw()
grid.draw();
arc1.drawOnGrid(grid);
Rotation Animation
rotate(deltaAngle)
Increments rotation by deltaAngle degrees. CCW positive, CW negative. Call before draw each frame to spin the arc.
| Parameter | Type | Description |
|---|---|---|
deltaAngle | number | Degrees to add to rotation; negative = clockwise |
// In draw(): spin at 45 deg/sec
arc1.rotate(45 * deltaT); // deltaT = seconds since last frame
arc1.drawOnGrid(grid);
Breathing Animations
All breathing animations use the elapsed-time pattern: accumulate paused time so pause/resume keeps the wave in sync.
breatheRadius(sinusoid, t)
Sets radius to the value of sinusoid.getValue(t). Configure the sinusoid's amplitude and period to control the range and speed of the breathing. Clamps to 0.01 minimum.
| Parameter | Type | Description |
|---|---|---|
sinusoid | SWSinusoid | Configured with desired period and min/max values |
t | number | Elapsed time in seconds (use accumulated elapsed time for pause/resume) |
breatheTheta(sinusoid, t)
Sets theta to the value of sinusoid.getValue(t), then clamps it to [0, 360] and updates arcLength. Makes the arc span open and close like a mouth.
// Setup (once):
let thetaSin = SWSinusoid.copy(UNIT_SINUSOID);
thetaSin.setPeriod(3);
thetaSin.adjustWaveUsingExtrema(10, 350); // swings 10°–350°
// In draw():
arc1.breatheTheta(thetaSin, elapsed + (t - startTime));
breatheThickness(sinusoid, t) ⭐ Unique to SWArc
Sets thickness to the value of sinusoid.getValue(t). The arc line itself swells and thins. Clamps to 1 pixel minimum. Combine with breatheRadius() for a pulsing-rope effect.
| Parameter | Type | Description |
|---|---|---|
sinusoid | SWSinusoid | Configured sinusoid (use adjustWaveUsingExtrema for intuitive min/max pixel values) |
t | number | Elapsed animated time in seconds |
// Setup (once):
let thickSin = SWSinusoid.copy(UNIT_SINUSOID);
thickSin.setPeriod(2.5);
thickSin.adjustWaveUsingExtrema(2, 20); // 2px thin → 20px thick
// In draw():
arc1.breatheThickness(thickSin, elapsed + (t - startTime));
Color Methods
setStrokeAlpha(alpha)
Sets the arc color's alpha (transparency) value, clamped to [0, 100]. Updates both strokeColor.a and strokeColor.col.
setStrokeColor(swColor)
Sets a new arc stroke color. Deep-copies the provided SWColor to avoid aliasing of shared color objects.
Reset Methods
reset()
Restores all animated values — radius, theta, startAngle, rotation, thickness, and strokeColor — to the originals stored at construction. Also recomputes arcLength.
resetStrokeColor()
Restores only the stroke color to the original stored at construction. Useful when stopping hue cycling without resetting geometry.
Setter Methods
setStrokeWeight(w)
Sets the arc's stroke weight (thickness) in pixels directly.
setRadius(r)
Sets the radius and recomputes arcLength.
setTheta(degrees)
Sets the angular size and recomputes arcLength (which clamps theta to [0, 360]).
setStartAngle(degrees)
Sets the arc's starting angle (CCW from +x) without affecting the accumulated rotation.
setCapStyle(cap)
Sets the endpoint cap style. Accepts 'round', 'butt', or 'square'.
setShowCenter(show)
Shows or hides the center marker point. Defaults to true.
Static Methods
static copy(other)
Returns a deep copy of the given SWArc instance: deep-copies center, color, and all geometry. Throws if argument is not an SWArc.
let arcCopy = SWArc.copy(arc1);
Utility Methods
toString()
Returns a human-readable description: center, radius, theta, startAngle, rotation, thickness, capStyle, and arcLength.
console.log(arc1.toString());
// → SWArc(center: SWPoint(0.00, 0.00), radius: 5.00, theta: 180.0°,
// startAngle: 0°, rotation: 0.0°, thickness: 8.0, cap: 'round', arcLength: 15.71)
Animation Guide
Elapsed-Time Pattern
All three breathe animations use the same pause/resume approach: accumulate elapsed seconds when paused, so the wave doesn't jump when you resume.
// Globals:
let shouldBreatheThickness = false;
let thicknessSin;
let thicknessBreathStartTime = 0, thicknessBreathElapsed = 0;
// Toggle on/off:
function toggleBreatheThickness() {
const t = millis() / 1000;
if (!shouldBreatheThickness) {
thicknessBreathStartTime = t;
shouldBreatheThickness = true;
} else {
thicknessBreathElapsed += (t - thicknessBreathStartTime);
shouldBreatheThickness = false;
}
}
// In draw():
if (shouldBreatheThickness) {
const bwt = thicknessBreathElapsed + (millis()/1000 - thicknessBreathStartTime);
arc1.breatheThickness(thicknessSin, bwt);
}
Hue Cycling
Hue is cycled externally — mutate strokeColor.h then rebuild strokeColor.col before the draw call:
// In draw() — cycle hue smoothly:
if (shouldCycleHue) {
const hueT = hueCycleElapsed + (millis()/1000 - hueCycleStartTime);
arc1.strokeColor.h = hueSinusoid.getValue(hueT);
arc1.strokeColor.col = color(
arc1.strokeColor.h,
arc1.strokeColor.s,
arc1.strokeColor.b,
arc1.strokeColor.a
);
}
arc1.drawOnGrid(grid);
Running All Five Animations Simultaneously
// In draw() order — spin + hue BEFORE draw, breathe AFTER draw:
const t = millis() / 1000;
const deltaT = t - prevT; prevT = t;
background(bgColor);
if (showGrid) grid.draw();
// 1. Spin
if (shouldSpin) arc1.rotate(spinSpeed * deltaT);
// 2. Hue cycle
if (shouldCycleHue) {
arc1.strokeColor.h = hueSin.getValue(hueCycleElapsed + t - hueCycleStart);
arc1.strokeColor.col = color(arc1.strokeColor.h, arc1.strokeColor.s,
arc1.strokeColor.b, arc1.strokeColor.a);
}
// 3. Draw
arc1.drawOnGrid(grid);
// 4. Breathe (take effect next frame)
if (shouldBreatheRadius) arc1.breatheRadius(radiusSin, ...);
if (shouldBreatheTheta) arc1.breatheTheta(thetaSin, ...);
if (shouldBreatheThickness) arc1.breatheThickness(thickSin, ...);
Source Code
The full swArc.js source code is shown below for reference.
/*
File: swArc.js
Date: 2026-03-28
Author: klp
App: SketchWaveTNT2026-03-19-Stg7
Purpose: SWArc class for SketchWaveJS
SWArc represents a circular arc — just the "crust" of the pizza, without the
filling. It is the stroke-only counterpart to SWSector. Defined by:
- A center SWPoint (the center of the underlying circle)
- A radius (in user units)
- A theta (angular size in degrees)
- A startAngle (degrees CCW from the +x axis, where the arc begins)
- A rotation offset (degrees, accumulated via rotate())
- A thickness (stroke weight in pixels — the primary visual variable)
- A strokeColor (SWColor instance)
- A capStyle (end-cap type: 'round' | 'butt' | 'square')
Key difference from SWSector:
SWArc has NO FILL — it is drawn in OPEN arc mode (pure stroke).
The arc's thickness becomes the primary visual/animation variable.
New animation vs SWSector:
breatheThickness(sinusoid, t): oscillates the stroke weight.
All SWSector animations also apply:
rotate(delta): spin the arc about its center.
breatheRadius(sinusoid, t): oscillate the radius.
breatheTheta(sinusoid, t): oscillate the angular size.
Angle convention (identical to SWSector):
User space (math): angles are CCW from +x axis, y increases upward.
p5 / screen space: angles are CW from +x axis, y increases downward.
Conversion: p5_angle = -user_angle (negate because y is flipped).
Drawing an arc of size theta starting at totalAngle = startAngle + rotation:
p5 arc start = -radians(totalAngle + theta)
p5 arc stop = -radians(totalAngle)
arc(cx, cy, 2r, 2r, p5Start, p5Stop, OPEN) — OPEN mode = no lines to center.
capStyle values (raw HTML canvas lineCap strings accepted by p5 strokeCap()):
'round' = ROUND — smooth semicircular ends
'butt' = SQUARE — flat ends, flush with arc endpoints
'square' = PROJECT — flat ends that extend past endpoint by half thickness
*/
console.log("[swArc.js] SWArc class loaded.");
class SWArc {
/**
* @param {SWPoint} center - Center of the underlying circle (SWPoint)
* @param {number} radius - Radius in user units (> 0)
* @param {number} theta - Angular size in degrees (clamped 0–360)
* @param {number} [startAngle=0] - Arc start angle (degrees CCW from +x axis)
* @param {number} [thickness=6] - Stroke weight in pixels (the primary visual variable)
* @param {SWColor} [strokeColor] - Arc color (SWColor instance)
* @param {string} [capStyle='round'] - End-cap: 'round', 'butt', or 'square'
*/
constructor(center, radius, theta, startAngle = 0, thickness = 6,
strokeColor = undefined, capStyle = 'round') {
this.center = center;
this.radius = radius;
this.theta = theta;
this.startAngle = startAngle;
this.rotation = 0; // accumulated rotation (degrees, CCW)
this.thickness = thickness;
// Always copy color to avoid mutating shared objects
this.strokeColor = strokeColor ? SWColor.copy(strokeColor) : undefined;
this.capStyle = capStyle; // 'round' | 'butt' | 'square'
// Originals for reset()
this.originalRadius = radius;
this.originalTheta = theta;
this.originalStartAngle = startAngle;
this.originalRotation = 0;
this.originalThickness = thickness;
this.originalStrokeColor = strokeColor ? SWColor.copy(strokeColor) : undefined;
this.shouldShowCenter = true; // draw center point marker
this._updateGeometry();
}//end constructor
// ─── Geometry ──────────────────────────────────────────────────────────────
_updateGeometry() {
this.theta = Math.max(0, Math.min(360, this.theta));
this.arcLength = (this.theta / 360) * 2 * Math.PI * this.radius;
}//end _updateGeometry
// ─── Drawing ───────────────────────────────────────────────────────────────
draw() {
this._drawArc(this.center.x, this.center.y, this.radius);
if (this.shouldShowCenter && this.center && this.center.draw) {
this.center.draw(this.strokeColor);
}
}//end draw
drawOnGrid(grid) {
const { x: cx, y: cy } = grid.userToScreen(this.center.x, this.center.y);
const rScreen = (grid.xScale * this.radius + grid.yScale * this.radius) / 2;
this._drawArc(cx, cy, rScreen);
if (this.shouldShowCenter && this.center && this.center.drawOnGrid) {
this.center.drawOnGrid(grid, this.strokeColor);
}
}//end drawOnGrid
_drawArc(cx, cy, r) {
const totalAngle = this.startAngle + this.rotation;
const p5Start = -radians(totalAngle + this.theta);
const p5Stop = -radians(totalAngle);
noFill();
if (this.strokeColor && this.strokeColor.col) {
stroke(this.strokeColor.col);
} else {
noStroke();
}
strokeWeight(this.thickness);
strokeCap(this.capStyle);
arc(cx, cy, 2 * r, 2 * r, p5Start, p5Stop, OPEN);
noStroke();
strokeWeight(1);
strokeCap(ROUND);
}//end _drawArc
// ─── Rotation animation ────────────────────────────────────────────────────
rotate(deltaAngle) {
this.rotation += deltaAngle;
}//end rotate
// ─── Breathing animations ──────────────────────────────────────────────────
breatheRadius(sinusoid, t) {
this.radius = Math.max(0.01, sinusoid.getValue(t));
this._updateGeometry();
}//end breatheRadius
breatheTheta(sinusoid, t) {
this.theta = sinusoid.getValue(t);
this._updateGeometry();
}//end breatheTheta
breatheThickness(sinusoid, t) {
this.thickness = Math.max(1, sinusoid.getValue(t));
}//end breatheThickness
// ─── Color helpers ─────────────────────────────────────────────────────────
setStrokeAlpha(alpha) {
if (this.strokeColor) {
this.strokeColor.a = Math.max(0, Math.min(100, alpha));
this.strokeColor.col = color(
this.strokeColor.h, this.strokeColor.s,
this.strokeColor.b, this.strokeColor.a
);
}
}//end setStrokeAlpha
// ─── Reset ─────────────────────────────────────────────────────────────────
reset() {
this.radius = this.originalRadius;
this.theta = this.originalTheta;
this.startAngle = this.originalStartAngle;
this.rotation = this.originalRotation;
this.thickness = this.originalThickness;
if (this.originalStrokeColor) {
this.strokeColor = SWColor.copy(this.originalStrokeColor);
}
this._updateGeometry();
}//end reset
resetStrokeColor() {
if (this.originalStrokeColor) {
this.strokeColor = SWColor.copy(this.originalStrokeColor);
}
}//end resetStrokeColor
// ─── Setters ───────────────────────────────────────────────────────────────
setStrokeColor(swColor) {
this.strokeColor = swColor ? SWColor.copy(swColor) : undefined;
}//end setStrokeColor
setStrokeWeight(w) {
this.thickness = w;
}//end setStrokeWeight
setRadius(r) {
this.radius = r;
this._updateGeometry();
}//end setRadius
setTheta(degrees) {
this.theta = degrees;
this._updateGeometry();
}//end setTheta
setStartAngle(degrees) {
this.startAngle = degrees;
}//end setStartAngle
setCapStyle(cap) {
this.capStyle = cap;
}//end setCapStyle
setShowCenter(show = true) {
this.shouldShowCenter = show;
}//end setShowCenter
// ─── Copy ──────────────────────────────────────────────────────────────────
static copy(other) {
if (!(other instanceof SWArc)) {
throw new Error('Argument to SWArc.copy must be an SWArc instance');
}
const a = new SWArc(
SWPoint.copy(other.center),
other.originalRadius,
other.originalTheta,
other.originalStartAngle,
other.originalThickness,
other.strokeColor,
other.capStyle
);
a.radius = other.radius;
a.theta = other.theta;
a.startAngle = other.startAngle;
a.rotation = other.rotation;
a.thickness = other.thickness;
a.shouldShowCenter = other.shouldShowCenter;
a._updateGeometry();
return a;
}//end copy
// ─── toString ──────────────────────────────────────────────────────────────
toString() {
return `SWArc(center: ${this.center.toString()}, ` +
`radius: ${this.radius.toFixed(2)}, theta: ${this.theta.toFixed(1)}°, ` +
`startAngle: ${this.startAngle}°, rotation: ${this.rotation.toFixed(1)}°, ` +
`thickness: ${this.thickness.toFixed(1)}, cap: '${this.capStyle}', ` +
`arcLength: ${this.arcLength.toFixed(2)})`;
}//end toString
}//end SWArc class