SWDiskStack Class Reference

A composite shape that stacks interpolated SWDisk layers between two endpoint disks.

Last update:

Overview

SWDiskStack is a Core composite class built from SWDisk objects. You provide a bigDisk, a smallDisk, and a step count. SWDiskStack then creates an internal layered stack that linearly blends:

  • center location (x, y)
  • radius
  • border thickness
  • fill and stroke color (HSBA)

Result: a layered disk-stack transition shape that can be drawn directly or on an SWGrid.

Constructor

let diskStack = new SWDiskStack(bigDisk, smallDisk, steps);

// Required endpoint disks
let bigDisk = new SWDisk(new SWPoint(-3, 2), 6, 2, swCyan, swBlue);
let smallDisk = new SWDisk(new SWPoint(3, -2), 2, 2, swPaleYellow, swBrown);

// Minimum steps is clamped to 2
let diskStack = new SWDiskStack(bigDisk, smallDisk, 18);

Properties

bigDisk
Starting endpoint disk (copied internally for safe composition).
smallDisk
Ending endpoint disk (copied internally for safe composition).
steps
Number of blended layers (integer, clamped to at least 2).
layers
Internal array of interpolated SWDisk objects used for rendering.

Methods

Configuration

setSteps(n)
Update layer count and rebuild interpolation.
setBigDisk(disk)
Replace the big endpoint and rebuild.
setSmallDisk(disk)
Replace the small endpoint and rebuild.
setEndpoints(bigDisk, smallDisk)
Replace both endpoints and rebuild.

Rendering

draw()
Draw all layers in screen coordinates.
drawOnGrid(grid)
Draw all layers in grid/user coordinates.

Utility

getLayerCount()
Return current layer count.
toString()
Return summary text for debugging.

Usage Examples

Example 1: Basic Disk Stack On Grid

let grid;
let diskStack;

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

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

    let big = new SWDisk(new SWPoint(-3, 2), 6, 2, new SWColor(190, 85, 95, 75), new SWColor(210, 80, 45, 100));
    let small = new SWDisk(new SWPoint(3, -2), 2, 2, new SWColor(45, 35, 98, 100), new SWColor(25, 85, 70, 100));

    diskStack = new SWDiskStack(big, small, 20);
}

function draw() {
    background(0, 0, 95);
    grid.draw();
    diskStack.drawOnGrid(grid);
}

Example 2: Animate Endpoints

// Move endpoints, then rebuild each frame
function draw() {
    background(0, 0, 95);
    grid.draw();

    big.center.x = -3 + 2 * sin(frameCount * 0.02);
    small.center.y = -2 + 1.5 * cos(frameCount * 0.03);

    diskStack.setEndpoints(big, small);
    diskStack.drawOnGrid(grid);
}

Example 3: Dynamic Layer Resolution

// Increase/decrease smoothness with keyboard
function keyPressed() {
    if (key === '+') diskStack.setSteps(diskStack.getLayerCount() + 1);
    if (key === '-') diskStack.setSteps(diskStack.getLayerCount() - 1);
}

Integration Notes

Load scripts in dependency order:

<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.js"></script>
<script src="../shapeClasses/swColor.js"></script>
<script src="../shapeClasses/swPoint.js"></script>
<script src="../shapeClasses/swGrid.js"></script>
<script src="../shapeClasses/swDisk.js"></script>
<script src="../shapeClasses/swDiskStack.js"></script>
<script src="../sketches/yourSketch.js"></script>

Source Code

The complete SWDiskStack class implementation:

Show/Hide Source Code
/*
File: swDiskStack.js
Date: 2026-05-28
Author: klp + Copilot
Purpose: SWDiskStack class for SketchWaveJS

An SWDiskStack is a stack of interpolated SWDisk instances that transition
from a big endpoint disk to a small endpoint disk.

Dependencies:
- p5.js
- SWColor
- SWPoint
- SWDisk
*/

console.log("[swDiskStack.js] SWDiskStack class loaded.");

class SWDiskStack {
    /**
     * @param {SWDisk} bigDisk - Starting disk in the stack
     * @param {SWDisk} smallDisk - Ending disk in the stack
     * @param {number} [steps=12] - Number of disks in the blended stack
     */
    constructor(bigDisk, smallDisk, steps = 12) {
        this.bigDisk = SWDiskStack.copyDisk(bigDisk);
        this.smallDisk = SWDiskStack.copyDisk(smallDisk);
        this.steps = SWDiskStack.clampSteps(steps);
        this.layers = [];
        this.rebuildLayers();
    }//end constructor

    static clampSteps(n) {
        return Math.max(2, Math.round(Number(n) || 2));
    }//end clampSteps

    static copyDisk(disk) {
        if (!(disk instanceof SWDisk)) {
            throw new Error("SWDiskStack expects SWDisk endpoints.");
        }

        const centerCopy = SWPoint.copy(disk.center);
        const fillCopy = disk.fillColor ? SWColor.copy(disk.fillColor) : undefined;
        const strokeCopy = disk.strokeColor ? SWColor.copy(disk.strokeColor) : undefined;
        const copy = new SWDisk(centerCopy, disk.radius, disk.thickness, fillCopy, strokeCopy);
        copy.setShowCenter(!!disk.shouldShowCenter);
        return copy;
    }//end copyDisk

    static lerpNumber(a, b, t) {
        return a + (b - a) * t;
    }//end lerpNumber

    static lerpHue(h1, h2, t) {
        const d = ((h2 - h1 + 540) % 360) - 180;
        let out = h1 + d * t;
        out = ((out % 360) + 360) % 360;
        return out;
    }//end lerpHue

    static lerpColor(c1, c2, t, name = "orbColor") {
        if (!c1 && !c2) return undefined;
        if (!c1) return SWColor.copy(c2);
        if (!c2) return SWColor.copy(c1);

        const h = SWDiskStack.lerpHue(c1.h, c2.h, t);
        const s = SWDiskStack.lerpNumber(c1.s, c2.s, t);
        const b = SWDiskStack.lerpNumber(c1.b, c2.b, t);
        const a = SWDiskStack.lerpNumber(c1.a, c2.a, t);
        return new SWColor(h, s, b, a, name);
    }//end lerpColor

    setSteps(n) {
        this.steps = SWDiskStack.clampSteps(n);
        this.rebuildLayers();
    }//end setSteps

    setBigDisk(disk) {
        this.bigDisk = SWDiskStack.copyDisk(disk);
        this.rebuildLayers();
    }//end setBigDisk

    setSmallDisk(disk) {
        this.smallDisk = SWDiskStack.copyDisk(disk);
        this.rebuildLayers();
    }//end setSmallDisk

    setEndpoints(bigDisk, smallDisk) {
        this.bigDisk = SWDiskStack.copyDisk(bigDisk);
        this.smallDisk = SWDiskStack.copyDisk(smallDisk);
        this.rebuildLayers();
    }//end setEndpoints

    rebuildLayers() {
        this.layers = [];
        const n = this.steps;

        for (let i = 0; i < n; i++) {
            const t = (n === 1) ? 0 : i / (n - 1);

            const cx = SWDiskStack.lerpNumber(this.bigDisk.center.x, this.smallDisk.center.x, t);
            const cy = SWDiskStack.lerpNumber(this.bigDisk.center.y, this.smallDisk.center.y, t);
            const radius = SWDiskStack.lerpNumber(this.bigDisk.radius, this.smallDisk.radius, t);
            const thickness = SWDiskStack.lerpNumber(this.bigDisk.thickness, this.smallDisk.thickness, t);

            const fillColor = SWDiskStack.lerpColor(this.bigDisk.fillColor, this.smallDisk.fillColor, t, `diskStackFill_${i}`);
            const strokeColor = SWDiskStack.lerpColor(this.bigDisk.strokeColor, this.smallDisk.strokeColor, t, `diskStackStroke_${i}`);

            const center = new SWPoint(cx, cy, undefined, 1, strokeColor);
            const layerDisk = new SWDisk(center, radius, thickness, fillColor, strokeColor);
            layerDisk.setShowCenter(false);
            this.layers.push(layerDisk);
        }
    }//end rebuildLayers

    draw() {
        for (const layer of this.layers) {
            layer.draw();
        }
    }//end draw

    drawOnGrid(grid) {
        for (const layer of this.layers) {
            layer.drawOnGrid(grid);
        }
    }//end drawOnGrid

    getLayerCount() {
        return this.layers.length;
    }//end getLayerCount

    toString() {
        return `SWDiskStack(steps: ${this.steps}, bigRadius: ${this.bigDisk.radius}, smallRadius: ${this.smallDisk.radius}, layers: ${this.layers.length})`;
    }//end toString
}//end class SWDiskStack