🥧 SWPie Reference

A SketchWave composite class for representing a circular pie divided into n equal radial slices

Back to SWPie Demo

Quick Reference

SWPie is a SketchWave composite class that represents a circular disk divided into n equal radial "pizza slice" sectors. Each slice is an independent SWSector instance with its own fill color, drawn on an SWGrid. SWPie is not a subclass of SWSector or SWDisk — it owns an internal array of SWSector objects and delegates all drawing, animation, and styling to them through a unified API.

  • Design Pattern: Composition (not inheritance)
  • Internal Structure: Array of n SWSector objects, each with theta = 360 / n
  • Dependencies: SWPoint, SWColor, SWSinusoid, SWGrid, SWSector, p5.js
  • Key Features: Per-slice fill colors (cycled from array), shared border color, spin rotation, radius breathing, fill opacity control, dynamic slice-count changes
  • Common Uses: ROYGBIV spectrum displays, pie charts, color wheels, pizza graphics, animated spinning disks with distinct sections

Overview

The SWPie class represents a full circular disk cut into n equal radial slices arranged around a shared center. Each slice is an SWSector with an angular size of 360 / n degrees and its own fill color taken from a supplied colors array (cycled if the array is shorter than n). All slices share the same center, radius, stroke weight, and border color.

Why composition instead of inheritance? A single SWDisk has one fill color, one draw(), and one breatheRadius() — none of which are appropriate for a multi-colored pie. The IS-A test fails: an SWPie is not an SWDisk. Composition lets each slice own its own fill independently while the SWPie presents a clean unified API at the top level.

Angle Convention

SWPie uses the same user-space (math-space) angle convention as all other SketchWaveJS classes: angles are measured counterclockwise (CCW) from the positive x-axis, and y increases upward. The p5.js y-flip is handled internally. You never deal with radians or y-negation.

// Default: 6 slices, startAngle=0 → first edge at +x axis
let pie = new SWPie(new SWPoint(0, 0), 6, 6, colors);

// startAngle=90 → first edge points straight up (+y axis)
let pie2 = new SWPie(new SWPoint(0, 0), 6, 7, roygbivColors, 90);

Internal Slice Geometry

Each SWSector (slice) is assigned a fixed angular offset of i × (360 / n) degrees for i = 0, 1, ..., n-1. This offset is the sector's own startAngle and never changes. The pie's current startAngle + rotation is written into every sector's rotation property each frame by the internal _syncSlices() method, so the sector draws at: its fixed offset + the pie's accumulated orientation.

Key Capabilities

  • Per-Slice Colors: Each slice gets its own fill color cycled from the supplied array
  • Shared Border: A single strokeColor and strokeWeight apply to all radial edges simultaneously
  • Spin Animation: Continuously rotate all slices together via rotate()
  • Radius Breathing: Oscillate the radius of all slices with an SWSinusoid
  • Fill Opacity: Set transparency of all slice fills simultaneously with setFillAlpha()
  • Dynamic Slice Count: Change n at runtime via setN(); colors re-cycle automatically
  • Individual Slice Recoloring: Change any single slice's color via setSliceColor(index, color)
  • Dual Coordinate Systems: Draw in screen pixels or grid user coordinates
  • Center Marker: Optional display of the shared center SWPoint

Typical Workflow

  1. Create an array of SWColor fill colors (one per slice, or fewer to cycle)
  2. Construct an SWPie with center, radius, n, colors array, startAngle, and optional border
  3. Draw each frame using drawOnGrid(grid)
  4. Call rotate() before drawing to spin; call breatheRadius() after drawing to oscillate the radius
  5. Use setN() to dynamically change the slice count; colors re-apply automatically
  6. Call reset() to restore all original values

Constructor

new SWPie(center, radius, n, colors, startAngle, thickness, strokeColor)

Creates a new SWPie instance with the given geometry and styling, and immediately builds the internal SWSector array.

Parameters
Parameter Type Default Description
center SWPoint required Center point of the pie — shared by all slices
radius number required Radius in user units (> 0)
n number 6 Number of equal slices (integer, clamped to ≥ 2)
colors SWColor[] [] Array of fill colors for slices; cycled (modulo) if shorter than n. Pass [] for no fill.
startAngle number 0 First slice edge angle in degrees CCW from +x axis
thickness number 2 Border (stroke) thickness in pixels applied to all radial edges
strokeColor SWColor undefined Shared border color for all slice edges; undefined = no borders
Constructor Examples
// Simple 6-slice pie with cycling colors
const colors = [
    new SWColor(0,   100, 100, 100, "red"),
    new SWColor(120, 100, 100, 100, "green"),
    new SWColor(240, 100, 100, 100, "blue"),
];
let pie = new SWPie(new SWPoint(0, 0), 6, 6, colors);

// ROYGBIV 7-slice pie, first edge at top (+y axis), with dark border
const roygbiv = [
    SWColor.copy(swRed), SWColor.copy(swOrange), SWColor.copy(swYellow),
    SWColor.copy(swGreen), SWColor.copy(swBlue), SWColor.copy(swIndigo),
    SWColor.copy(swViolet),
];
const border = new SWColor(0, 0, 20, 100, "darkBorder");
let spectrumPie = new SWPie(new SWPoint(0, 0), 6, 7, roygbiv, 90, 2, border);

// Minimal: 4-slice no-border pie
let simple = new SWPie(new SWPoint(2, 1), 4, 4, colors, 45);

Properties

center SWPoint

The center point shared by all slices. Changing center.x / center.y moves the entire pie. The center is drawn as a small dot when shouldShowCenter is true.

pie.center.x = 3; pie.center.y = -1;
radius number

The radius in user units shared by all slices. Use setRadius(r) to update — calling it propagates the new radius to every SWSector immediately.

pie.setRadius(8);
n number

The number of slices (integer, clamped to ≥ 2). Each slice subtends 360 / n degrees. Use setN() to change this at runtime; the internal sector array is rebuilt and colors re-cycle automatically. The accumulated rotation is reset to 0 on rebuild to avoid a visual jump.

pie.setN(12); // divide into 12 slices of 30° each
colors SWColor[]

The fill color array used at slice-build time. Colors are cycled (modulo) if the array is shorter than n. This array is the source for rebuilds; individual slice colors live in the sector objects. Use setSliceColor() to change a single slice after construction.

// Re-cycle happens automatically when setN() is called
startAngle number

The static orientation of the first slice edge, in degrees CCW from +x. This is the baseline before any rotation accumulates. Use setStartAngle() to change it. startAngle = 90 places the first edge pointing straight up.

pie.setStartAngle(90); // first edge at top
rotation number

Accumulated rotation in degrees (CCW positive). Set to 0 by the constructor and by setN(); incremented by rotate(). The effective orientation of slice i's first edge is: i × (360/n) + startAngle + rotation.

console.log(pie.rotation.toFixed(1) + "°");
thickness number

The stroke (border) weight in pixels, shared by all slices. Use setStrokeWeight(w) to update all slices simultaneously.

pie.setStrokeWeight(3);
strokeColor SWColor | undefined

The shared border color applied to all radial edges. Pass undefined to setStrokeColor() to remove all borders. Each slice receives an independent copy of this color.

pie.setStrokeColor(new SWColor(0, 0, 0, 100, "black"));
pie.setStrokeColor(undefined); // remove borders
fillAlpha number

The fill opacity for all slices, in the range [0, 100]. 100 = fully opaque, 0 = fully transparent. Use setFillAlpha() to update all slice fill alphas simultaneously. Survives setN() rebuilds.

pie.setFillAlpha(70); // 70% opaque slices
shouldShowCenter boolean

Whether to draw the center SWPoint marker. Default is true. Useful for interactive demos where the user drags the center to reposition the pie.

pie.setShowCenter(false); // hide center dot
sectors SWSector[]

The internal array of SWSector objects — one per slice. Rebuilt whenever setN() is called. Generally accessed via the SWPie API, but can be read directly for advanced use (e.g., reading a specific sector's current radius).

console.log(`Slice count: ${pie.sectors.length}`);
originalRadius / originalN / originalStartAngle / originalThickness / originalStrokeColor / originalColors / originalFillAlpha various restore targets

Snapshot values captured at construction. reset() uses all of these to restore the pie to its initial state. originalColors is a deep copy of the colors array.

// Read-only; used internally by reset()

Methods

Core Drawing Methods

draw()

Draws all slices in screen (pixel) coordinates using p5.js. Rarely used directly — prefer drawOnGrid() for user-coordinate rendering.

Returns

void

Example
function draw() {
    background(220);
    pie.draw(); // center.x/y treated as screen pixels
}
drawOnGrid(grid)

Draws all slices mapped through the given SWGrid's coordinate system. Converts center position and radius from user units to screen pixels. This is the standard method to call in a p5.js draw() loop.

Parameters
  • grid (SWGrid) — the coordinate grid
Returns

void

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

Rotation Animation

rotate(deltaAngle)

Increments the pie's accumulated rotation by deltaAngle degrees (CCW positive, CW negative). All slices rotate together about the shared center. Call each frame before drawOnGrid().

Parameters
  • deltaAngle (number) — degrees to add to rotation
Example
// Spin at 45°/second using elapsed time (deltaT)
pie.rotate(spinSpeed * deltaT);  // call BEFORE drawOnGrid

// Fixed increment per frame
pie.rotate(1); // 1 degree per frame, CCW

Breathing Animation

breatheRadius(sinusoid, t)

Modulates the radius of all slices using an SWSinusoid. The radius is set to sinusoid.getValue(t), clamped to a minimum of 0.01. Call after drawOnGrid() so the new value takes effect on the next frame.

Parameters
  • sinusoid (SWSinusoid) — controls radius oscillation
  • t (number) — elapsed time in seconds
Example
// Radius oscillates between 2 and 8 over 4 seconds
// Using adjustWaveUsingExtrema for convenient setup:
let radSin = SWSinusoid.copy(UNIT_SINUSOID);
radSin.setPeriod(4);
radSin.adjustWaveUsingExtrema(2, 8);

pie.drawOnGrid(grid);
pie.breatheRadius(radSin, elapsedSeconds);  // call AFTER draw

Setter Methods

setRadius(r)

Sets the radius for all slices immediately.

Parameters
  • r (number) — new radius in user units
Example
pie.setRadius(7);
setStrokeColor(swColor)

Sets the border color for all slices. Pass undefined to remove borders from all slices entirely.

Parameters
  • swColor (SWColor | undefined) — new border color, or undefined to remove
Example
pie.setStrokeColor(new SWColor(0, 0, 20, 100, "darkBorder"));
pie.setStrokeColor(undefined); // remove all borders
setStrokeWeight(w)

Sets the border thickness in pixels for all slices.

Parameters
  • w (number) — thickness in pixels
Example
pie.setStrokeWeight(4);
setStartAngle(degrees)

Sets the static starting orientation of the first slice edge (degrees CCW from +x), without affecting accumulated rotation.

Parameters
  • degrees (number) — new start angle
Example
pie.setStartAngle(90); // first edge points straight up
setSliceColor(index, swColor)

Sets the fill color for a single slice by index (0-based). Does nothing if the index is out of range. Useful for highlighting or animating individual slices after the pie is built.

Parameters
  • index (number) — slice index (0 to n-1)
  • swColor (SWColor) — new fill color for that slice
Example
// Highlight the first slice in bright white
pie.setSliceColor(0, new SWColor(0, 0, 100, 100, "white"));
setFillAlpha(alpha)

Sets the fill opacity for all slices simultaneously. Clamped to [0, 100]. The alpha value is stored in pie.fillAlpha and re-applied whenever slices are rebuilt via setN().

Parameters
  • alpha (number) — 0 = fully transparent, 100 = fully opaque
Example
pie.setFillAlpha(60); // 60% opaque fills — all slices
setN(newN)

Changes the number of slices and rebuilds the internal sector array. Colors are re-cycled from the current colors array. Current radius, startAngle, thickness, strokeColor, and fillAlpha are all preserved. Accumulated rotation is reset to 0 to avoid a visual jump.

Parameters
  • newN (number) — new slice count (clamped to ≥ 2)
Example
pie.setN(12); // rebuild with 12 slices of 30° each
setShowCenter(show)

Controls whether the center SWPoint dot is drawn.

Parameters
  • show (boolean) — true to show, false to hide (default: true)
Example
pie.setShowCenter(false);

Reset Method

reset()

Restores all animated / slider-driven properties — radius, n, startAngle, thickness, strokeColor, colors, and fillAlpha — to their originals captured at construction. Rotation is reset to 0. Does not move the center position.

Example
pie.reset(); // full restore to factory state

Utility Methods

static copy(other)

Returns a deep copy of an SWPie instance. All geometry and color values are independently duplicated. The current rotation is preserved in the copy.

Parameters
  • other (SWPie) — the pie to copy
Returns

SWPie — a new independent instance

Example
let pieCopy = SWPie.copy(pie1);
toString()

Returns a string summarizing the pie's key properties: center, radius, n, startAngle, rotation, and computed sliceTheta.

Returns

string

Example
console.log(pie.toString());
// "SWPie(center=SWPoint(x:0, y:0), r=6.00, n=7, startAngle=90.0, rotation=0.0, sliceTheta=51.43)"

Usage Examples

Example 1: Basic ROYGBIV Pie

let grid;
let pie;

function setup() {
    createCanvas(400, 400);
    colorMode(HSB, 360, 100, 100, 100);
    initializeSWColors(); // loads swRed, swOrange, ... swViolet globals

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

    const colors = [
        SWColor.copy(swRed), SWColor.copy(swOrange), SWColor.copy(swYellow),
        SWColor.copy(swGreen), SWColor.copy(swBlue), SWColor.copy(swIndigo),
        SWColor.copy(swViolet),
    ];
    const border = new SWColor(0, 0, 20, 100, "border");
    pie = new SWPie(new SWPoint(0, 0), 6, 7, colors, 90, 2, border);
}

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

Example 2: Spinning Pie (Elapsed-Time Approach)

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

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 colors = [
        SWColor.copy(swRed), SWColor.copy(swOrange), SWColor.copy(swYellow),
        SWColor.copy(swGreen), SWColor.copy(swBlue), SWColor.copy(swIndigo),
        SWColor.copy(swViolet),
    ];
    pie = new SWPie(new SWPoint(0, 0), 6, 7, colors, 90, 2,
                    new SWColor(0, 0, 20, 100, "border"));
}

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

    // deltaT in seconds; spin BEFORE drawing
    const t = millis() / 1000;
    const deltaT = (prevT > 0) ? (t - prevT) : 0;
    prevT = t;

    pie.rotate(SPIN_SPEED * deltaT);  // CCW positive; use negative for CW
    pie.drawOnGrid(grid);
}

Example 3: Breathing Radius

let grid, pie, radSin;
let breathStart = 0, breathElapsed = 0;
let shouldBreathe = false;

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 colors = [
        SWColor.copy(swRed), SWColor.copy(swGreen), SWColor.copy(swBlue),
    ];
    pie = new SWPie(new SWPoint(0, 0), 5, 3, colors, 90, 2,
                    new SWColor(0, 0, 20, 100, "border"));

    // Radius oscillates between 2 and 8 over 4 seconds
    radSin = SWSinusoid.copy(UNIT_SINUSOID);
    radSin.setPeriod(4);
    radSin.adjustWaveUsingExtrema(2, 8);
}

function draw() {
    background(0, 0, 93);
    grid.draw();
    pie.drawOnGrid(grid);  // draw first

    if (shouldBreathe) {
        const t = millis() / 1000;
        breathElapsed += (t - breathStart);
        breathStart = t;
        pie.breatheRadius(radSin, breathElapsed);  // breathe AFTER draw
    }
}

function keyPressed() {
    if (key === 'b') {
        shouldBreathe = !shouldBreathe;
        breathStart = millis() / 1000;
    }
    if (key === 'r') { pie.reset(); breathElapsed = 0; }
}

Example 4: Dynamically Changing Slice Count

let grid, pie;
let n = 7;

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) });
    buildPie(n);
}

function buildPie(count) {
    const colors = [];
    for (let i = 0; i < count; i++) {
        colors.push(new SWColor((i / count) * 360, 90, 90, 100, `c${i}`));
    }
    pie = new SWPie(new SWPoint(0, 0), 6, count, colors, 90, 2,
                    new SWColor(0, 0, 20, 100, "border"));
}

function draw() {
    background(0, 0, 93);
    grid.draw();
    pie.drawOnGrid(grid);
    text(`n = ${n} slices  (↑↓ to change)`, 10, height - 10);
}

function keyPressed() {
    if (keyCode === UP_ARROW) { n = min(n + 1, 16); pie.setN(n); }
    if (keyCode === DOWN_ARROW) { n = max(n - 1, 2);  pie.setN(n); }
}

Example 5: Spin + Breathe Radius Together

let grid, pie, radSin;
let prevT = 0;
let rStart = 0, rElapsed = 0;
const SPIN_SPEED = 30;

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 colors = [
        SWColor.copy(swRed), SWColor.copy(swOrange), SWColor.copy(swYellow),
        SWColor.copy(swGreen), SWColor.copy(swBlue), SWColor.copy(swIndigo),
        SWColor.copy(swViolet),
    ];
    pie = new SWPie(new SWPoint(0, 0), 5, 7, colors, 90, 2,
                    new SWColor(0, 0, 20, 100, "border"));

    radSin = SWSinusoid.copy(UNIT_SINUSOID);
    radSin.setPeriod(3);
    radSin.adjustWaveUsingExtrema(2, 8);

    rStart = millis() / 1000;
}

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

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

    // 1. Spin BEFORE draw
    pie.rotate(SPIN_SPEED * deltaT);

    pie.drawOnGrid(grid);

    // 2. Breathe AFTER draw
    rElapsed += deltaT;
    pie.breatheRadius(radSin, rElapsed);
}

Example 6: Two Concentric Pies

let grid, pieOuter, pieInner;
let prevT = 0;

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) });

    // Outer pie: 6 warm-cool slices, slow CW spin
    const outerColors = [
        new SWColor(0,   90, 90, 100, "red"),
        new SWColor(60,  90, 90, 100, "yellow"),
        new SWColor(120, 90, 90, 100, "green"),
        new SWColor(180, 90, 90, 100, "cyan"),
        new SWColor(240, 90, 90, 100, "blue"),
        new SWColor(300, 90, 90, 100, "magenta"),
    ];
    pieOuter = new SWPie(new SWPoint(0, 0), 8, 6, outerColors, 0, 2,
                         new SWColor(0, 0, 100, 100, "white"));

    // Inner pie: 3 slices, fast CCW spin, semi-transparent
    const innerColors = [
        new SWColor(0, 0, 100, 80, "white"),
        new SWColor(0, 0, 50, 80, "gray"),
        new SWColor(0, 0, 0, 80, "black"),
    ];
    pieInner = new SWPie(new SWPoint(0, 0), 4, 3, innerColors, 0, 2,
                         new SWColor(0, 0, 100, 100, "white"));
}

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

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

    pieOuter.rotate(-20 * deltaT);  // CW (negative)
    pieInner.rotate(60 * deltaT);   // CCW (positive)

    pieOuter.drawOnGrid(grid);
    pieInner.drawOnGrid(grid);
}

Best Practices

1. Animation Ordering

  • Spin BEFORE draw: rotate() updates the orientation used during this frame's render
  • Breathing AFTER draw: breatheRadius() sets the new value that takes effect next frame — matching the SWSector/SWDisk convention
pie.rotate(speed * deltaT);     // spin  → before draw
pie.drawOnGrid(grid);            // draw
pie.breatheRadius(radSin, t);   // breathe → after draw

2. Elapsed Time vs. frameCount

  • Use elapsed seconds (not frame count) for all sinusoid time parameters; this keeps animation speed frame-rate-independent
  • Track startTime and elapsed separately per animation so each can be paused and resumed independently
// Pattern for pauseable elapsed-time animation
let breathStart = 0, breathElapsed = 0, isBreathing = false;

function toggleBreathe() {
    isBreathing = !isBreathing;
    if (isBreathing) breathStart = millis() / 1000;
    else breathElapsed += (millis() / 1000) - breathStart;
}

// In draw():
if (isBreathing) {
    const t = millis() / 1000;
    breathElapsed += (t - breathStart);
    breathStart = t;
    pie.breatheRadius(radSin, breathElapsed);
}

3. Angle Convention

  • Always pass user-space degrees (CCW from +x) to SWPie; the class handles the p5.js y-flip internally
  • startAngle=0 places the first slice edge along the +x axis; startAngle=90 places it at the top (+y axis)
  • Positive rotate() deltas spin CCW; negative values spin CW

4. Colors Array Design

  • Always pass SWColor instances; the constructor copies each one independently to prevent shared-mutation bugs
  • If the array is shorter than n, colors are cycled modulo — useful for repeating palettes
  • If the array is longer than n, only the first n colors are used
  • Use setSliceColor(i, color) to change individual slices after construction without a full rebuild
  • After a setN() rebuild, individual slice color changes are lost — reapply them if needed

5. SWSinusoid Setup for Breathing

  • The most convenient setup uses the UNIT_SINUSOID global, setPeriod(), and adjustWaveUsingExtrema(min, max)
  • Or use the constructor directly: center = (min+max)/2, amplitude = (max-min)/2, frequency = 1/period
// Radius breathes between 2 and 8 with a 4-second period
let radSin = SWSinusoid.copy(UNIT_SINUSOID);
radSin.setPeriod(4);
radSin.adjustWaveUsingExtrema(2, 8);

// Equivalent long form:
let radSin2 = new SWSinusoid(5, 3, 0.25, 0);

6. Preserving State During setN() Rebuilds

  • setN() rebuilds the entire sector array — individual slice color changes, setShowVertex states, and any manual sector tweaks are lost
  • Preserve the center position before calling setN() if it was moved by dragging, and reapply it afterward
  • The demo uses this pattern: save cx/cy, call setN(), restore center, then reapply slider values
// Safe n-slider rebuild pattern
const cx = pie.center.x;
const cy = pie.center.y;
pie.setN(newN);
pie.center.x = cx;
pie.center.y = cy;
pie.setRadius(currentRadius);
pie.setFillAlpha(currentAlpha);

Integration with Other SketchWave Classes

Script Loading Order

SWPie depends on SWSector, which must itself be loaded after SWGrid, SWPoint, SWColor, and SWSinusoid:

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

<!-- SketchWaveJS classes in dependency order -->
<script src="shapeClasses/swSinusoid.js"></script>
<script src="shapeClasses/swColor.js"></script>
<script src="shapeClasses/swPoint.js"></script>
<script src="shapeClasses/swGrid.js"></script>
<script src="shapeClasses/swSector.js"></script>   <!-- must precede swPie -->
<script src="shapeClasses/swPie.js"></script>

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

Working with SWSector

SWPie's slices are SWSector instances. Understanding SWSector helps explain SWPie's behavior:

  • Each sector has its own startAngle (the fixed slice offset: i × 360/n) that never changes
  • The pie writes its accumulated startAngle + rotation into every sector's rotation property each frame via _syncSlices()
  • SWSector draws in PIE mode (two radii + arc, connected to vertex/center)

Working with SWPoint

SWPie uses SWPoint for its center:

  • The center is a full SWPoint instance — it is drawn as a small dot when shouldShowCenter is true
  • Drag or reposition the pie by changing pie.center.x and pie.center.y directly
  • Each internal sector gets its own copy of the center coordinates each frame via _syncSlices()

Working with SWColor

SWPie uses SWColor for all color management (HSB mode):

  • Colors in the array are individually copied at slice-build time — mutations to the source array after construction have no effect
  • The shared strokeColor is also copied; each sector receives its own independent copy
  • Predefined globals (swRed, swOrange, etc.) are available after calling initializeSWColors() in setup()

Working with SWSinusoid

SWPie's breatheRadius() method accepts a SWSinusoid instance:

  • The sinusoid's getValue(t) is called with elapsed time in seconds
  • Use SWSinusoid.copy(UNIT_SINUSOID) with setPeriod() and adjustWaveUsingExtrema() for the most convenient setup

Comparing SWPie and SWTwoTonedDisk

Both are composite classes that own internal sub-shape objects, but they serve different purposes:

Feature SWPie SWTwoTonedDisk
Internal objects n equal SWSectors 2 SWSectors
Number of regions Configurable (2–n) Always 2
Slice angular size Equal: 360 / n degrees Configurable split angle
Colors Array (one per slice, cycled) Two fixed colors
Dynamic resizing setN() rebuilds all slices Not applicable
Per-slice recoloring setSliceColor(i, color) Not available
Fill opacity setFillAlpha() — all slices Not available
Use case Pie charts, ROYGBIV wheels, color wheels Yin-yang style split disks

Source Code

The complete SWPie class implementation:

Show/Hide Source Code
/*
File: swPie.js
Date: 2026-04-21
Author: klp
App:  SketchWaveTNT2026-04-21-Stg8
Purpose: SWPie class for SketchWaveJS

SWPie represents a circular disk divided into n equal "pizza slice" sectors
arranged radially about a shared center.  Each slice is an SWSector with:
  - An equal angular size theta = 360 / n degrees
  - A fixed offset of i × theta from the pie's startAngle
  - Its own fill color (drawn from a colors array, cycled if shorter than n)

Geometry:
  - All slices share the same center, radius, stroke weight, and stroke color.
  - The shared stroke color controls the radial edge borders between slices.
  - Changing n via setN() rebuilds the slices array; colors are re-applied.

Animations (all composable):
  - rotate(delta)         : spins the entire pie CCW (positive) or CW (negative).
  - breatheRadius(sin, t) : oscillates the radius of all slices with an SWSinusoid.

Color model:
  - Each slice gets fillColor = colors[i % colors.length] at build time.
  - A single strokeColor is shared by all slices (undefined = no border).
  - Use setSliceColor(i, color) to change one slice's fill after construction.
  - setFillAlpha(alpha) sets the transparency of all fills simultaneously.

Angle convention (same as SWSector):
  User space:   CCW from +x axis, y increases upward.
  p5 / screen:  CW  from +x axis, y increases downward.

Dependencies: p5.js, SWColor, SWPoint, SWGrid, SWSinusoid, SWSector.

Notes:
  - Consistent API with SWTwoTonedDisk, SWDisk, etc.
  - SWSector must be loaded before SWPie.
*/

console.log("[swPie.js] SWPie class loaded.");

class SWPie {

    constructor(center, radius, n = 6, colors = [], startAngle = 0,
                thickness = 2, strokeColor = undefined) {

        this.center      = center;
        this.radius      = radius;
        this.n           = Math.max(2, Math.round(n));
        this.colors      = colors;
        this.startAngle  = startAngle;
        this.rotation    = 0;
        this.thickness   = thickness;
        this.strokeColor = strokeColor ? SWColor.copy(strokeColor) : undefined;

        this.shouldShowCenter = true;
        this.fillAlpha       = 100;

        this.originalRadius      = radius;
        this.originalN           = this.n;
        this.originalStartAngle  = startAngle;
        this.originalThickness   = thickness;
        this.originalStrokeColor = strokeColor ? SWColor.copy(strokeColor) : undefined;
        this.originalColors      = colors.map(c => c ? SWColor.copy(c) : undefined);
        this.originalFillAlpha   = 100;

        this._buildSlices();
    }

    _buildSlices() {
        const sliceTheta = 360 / this.n;
        this.sectors = [];
        for (let i = 0; i < this.n; i++) {
            const fillColor = this.colors.length > 0
                ? SWColor.copy(this.colors[i % this.colors.length])
                : undefined;
            const sec = new SWSector(
                new SWPoint(this.center.x, this.center.y),
                this.radius,
                sliceTheta,
                i * sliceTheta,
                this.thickness,
                fillColor,
                this.strokeColor ? SWColor.copy(this.strokeColor) : undefined
            );
            sec.setShowVertex(false);
            if (this.fillAlpha !== undefined && this.fillAlpha < 100) {
                sec.setFillAlpha(this.fillAlpha);
            }
            this.sectors.push(sec);
        }
    }

    _syncSlices() {
        const cx = this.center.x;
        const cy = this.center.y;
        const r  = this.startAngle + this.rotation;
        for (const sec of this.sectors) {
            sec.vertex.x = cx;
            sec.vertex.y = cy;
            sec.rotation = r;
        }
    }

    draw() {
        this._syncSlices();
        for (const sec of this.sectors) sec.draw();
        if (this.shouldShowCenter && this.center && this.center.draw) {
            this.center.draw();
        }
    }

    drawOnGrid(grid) {
        this._syncSlices();
        for (const sec of this.sectors) sec.drawOnGrid(grid);
        if (this.shouldShowCenter && this.center && this.center.drawOnGrid) {
            this.center.drawOnGrid(grid);
        }
    }

    rotate(deltaAngle) {
        this.rotation += deltaAngle;
    }

    breatheRadius(sinusoid, t) {
        this.radius = Math.max(0.01, sinusoid.getValue(t));
        for (const sec of this.sectors) sec.setRadius(this.radius);
    }

    setRadius(r) {
        this.radius = r;
        for (const sec of this.sectors) sec.setRadius(r);
    }

    setStrokeColor(swColor) {
        this.strokeColor = swColor ? SWColor.copy(swColor) : undefined;
        for (const sec of this.sectors) {
            sec.setStrokeColor(this.strokeColor ? SWColor.copy(this.strokeColor) : undefined);
        }
    }

    setStrokeWeight(w) {
        this.thickness = w;
        for (const sec of this.sectors) sec.setStrokeWeight(w);
    }

    setStartAngle(degrees) {
        this.startAngle = degrees;
    }

    setSliceColor(index, swColor) {
        if (index < 0 || index >= this.sectors.length) return;
        this.sectors[index].setFillColor(swColor ? SWColor.copy(swColor) : undefined);
    }

    setFillAlpha(alpha) {
        this.fillAlpha = Math.max(0, Math.min(100, alpha));
        for (const sec of this.sectors) sec.setFillAlpha(this.fillAlpha);
    }

    setN(newN) {
        this.n        = Math.max(2, Math.round(newN));
        this.rotation = 0;
        this._buildSlices();
    }

    setShowCenter(show = true) {
        this.shouldShowCenter = show;
    }

    reset() {
        this.rotation   = 0;
        this.n          = this.originalN;
        this.startAngle = this.originalStartAngle;
        this.thickness  = this.originalThickness;
        this.colors     = this.originalColors.map(c => c ? SWColor.copy(c) : undefined);
        this.strokeColor = this.originalStrokeColor
            ? SWColor.copy(this.originalStrokeColor) : undefined;
        this.fillAlpha = this.originalFillAlpha;
        this.radius = this.originalRadius;
        this._buildSlices();
    }

    static copy(other) {
        const colorsCopy = other.colors.map(c => c ? SWColor.copy(c) : undefined);
        const newPie = new SWPie(
            new SWPoint(other.center.x, other.center.y),
            other.radius,
            other.n,
            colorsCopy,
            other.startAngle,
            other.thickness,
            other.strokeColor ? SWColor.copy(other.strokeColor) : undefined
        );
        newPie.rotation        = other.rotation;
        newPie.shouldShowCenter = other.shouldShowCenter;
        return newPie;
    }

    toString() {
        return `SWPie(center=${this.center}, r=${this.radius.toFixed(2)}, ` +
               `n=${this.n}, startAngle=${this.startAngle.toFixed(1)}, ` +
               `rotation=${this.rotation.toFixed(1)}, sliceTheta=${(360/this.n).toFixed(2)})`;
    }

}//end SWPie class