SWDisk Reference

A SketchWave class for representing circles and disks in 2D space

Back to SWDisk Demo

Quick Reference

SWDisk is a SketchWave class for representing circles and disks in 2D space with full styling control, including fill color, stroke color, and geometric properties like area and circumference.

  • Extends: Nothing (standalone class)
  • Dependencies: SWPoint, SWColor, p5.js
  • Key Features: Custom fill/stroke colors, area/circumference calculations, breathing animation, center point display, hit detection
  • Common Uses: Color wheels, circular UI elements, animated disks, interactive buttons, visualization of circular data

Overview

The SWDisk class represents circular shapes (disks) in 2D coordinate space. It provides complete control over appearance, position, and behavior, making it ideal for creating color wheels, interactive circular elements, and animated visual effects.

Key Capabilities

  • Flexible Positioning: Center defined by an SWPoint instance
  • Full Styling Control: Independent fill and stroke colors using SWColor
  • Geometric Properties: Automatic area and circumference calculations
  • Animation Support: Built-in "breathing" effect using SWSinusoid
  • Interactive Features: Hit detection with containsPoint() method
  • Dual Coordinate Systems: Draw in screen pixels or grid coordinates
  • Center Point Display: Optional visualization of disk center with automatic color selection

Typical Workflow

  1. Create an SWDisk with center point, radius, and colors
  2. Optionally configure center point visibility and styling
  3. Draw the disk in your animation loop using draw() or drawOnGrid()
  4. Interact with the disk using hit detection or modify properties dynamically
  5. Apply animations like breathing or color cycling as needed

Constructor

new SWDisk(center, radius, thickness, fillColor, strokeColor)

Creates a new SWDisk instance with specified geometric and styling properties.

Parameters
Parameter Type Default Description
center SWPoint required Center point of the disk
radius number required Radius in user units
thickness number 2 Border (stroke) thickness in pixels
fillColor SWColor undefined Interior fill color (no fill if undefined)
strokeColor SWColor undefined Border color (no stroke if undefined)
Constructor Examples
// Minimal: center and radius only
let disk1 = new SWDisk(new SWPoint(0, 0), 5);

// With fill color
let disk2 = new SWDisk(new SWPoint(0, 0), 5, 2, swBlue);

// With fill and stroke
let disk3 = new SWDisk(new SWPoint(0, 0), 5, 3, swYellow, swBlack);

// Fully configured disk
let centerPt = new SWPoint(10, 10);
let fillCol = new SWColor(120, 80, 90, 100, "greenFill");
let strokeCol = new SWColor(0, 0, 0, 100, "blackStroke");
let disk4 = new SWDisk(centerPt, 8, 4, fillCol, strokeCol);

Properties

SWDisk instances have the following properties:

center SWPoint

The center point of the disk as an SWPoint instance. Changing this moves the disk.

disk.center.x = 5; disk.center.y = 10; // Move disk to (5, 10)
radius number

The radius of the disk in user units. Directly modifying this updates area and circumference automatically.

disk.radius = 10; // Set radius to 10 units
thickness number

The stroke (border) thickness in pixels.

disk.thickness = 5; // Thicker border
fillColor SWColor

The interior fill color as an SWColor instance. Set to undefined for no fill.

disk.fillColor = new SWColor(240, 100, 100, 100, "blue");
strokeColor SWColor

The border color as an SWColor instance. Set to undefined for no stroke.

disk.strokeColor = swBlack;
originalFillColor SWColor

A copy of the fill color provided at construction. Used by resetFillColor() to restore the original color.

// Read-only reference to original color
originalRadius number

The radius value at construction. Used by reset() to restore the original size after animations.

// Used internally for reset()
area number read-only

The computed area of the disk (πr²). Automatically updated when radius changes.

console.log(`Area: ${disk.area.toFixed(2)}`);
circumference number read-only

The computed circumference of the disk (2πr). Automatically updated when radius changes.

console.log(`Circumference: ${disk.circumference.toFixed(2)}`);
shouldShowCenter boolean

Whether to display the center point when drawing. Default is true.

disk.shouldShowCenter = false; // Hide center point
centerColor SWColor

The color of the center point, automatically determined from stroke or fill color.

// Automatically managed by updateCenterColor()

Methods

Core Drawing Methods

draw()

Draws the disk in screen (pixel) coordinates using p5.js.

Returns

void

Example
function draw() {
    background(220);
    disk.draw(); // Draws at screen coordinates
}
drawOnGrid(grid)

Draws the disk in user (grid) coordinates, converting to screen space using the provided SWGrid.

Parameters
  • grid (SWGrid) - Grid for coordinate transformation
Returns

void

Example
function draw() {
    background(220);
    grid.draw();
    disk.drawOnGrid(grid); // Draws in grid coordinates
}

Color and Styling Methods

setFillColor(swColor)

Sets the fill color of the disk. A copy is made to avoid mutating shared color objects.

Parameters
  • swColor (SWColor) - New fill color
Example
disk.setFillColor(swRed); // Change fill to red
resetFillColor()

Restores the fill color to its original value from construction.

Example
disk.resetFillColor(); // Restore original color
setStrokeColor(swColor)

Sets the border (stroke) color of the disk.

Parameters
  • swColor (SWColor) - New stroke color
Example
disk.setStrokeColor(swBlack); // Black border
setStrokeWeight(w)

Sets the border thickness in pixels.

Parameters
  • w (number) - Thickness in pixels
Example
disk.setStrokeWeight(5); // Thicker border
setFillAlpha(alpha)

Sets the alpha (transparency) of the fill color. The alpha value is clamped between 0 and 100 and the underlying p5.js color object is updated immediately.

Parameters
  • alpha (number) - Alpha value 0–100 (0 = fully transparent, 100 = fully opaque)
Example
disk.setFillAlpha(50); // 50% transparent fill
setStrokeAlpha(alpha)

Sets the alpha (transparency) of the stroke (border) color. The alpha value is clamped between 0 and 100, the p5.js color object is updated, and updateCenterColor() is called automatically.

Parameters
  • alpha (number) - Alpha value 0–100 (0 = fully transparent, 100 = fully opaque)
Example
disk.setStrokeAlpha(75); // Semi-transparent border
updateCenterColor()

Updates the center point color according to these rules: uses strokeColor if present, otherwise uses a 20% darker version of fillColor, otherwise uses black.

Example
// Called automatically when colors change
disk.updateCenterColor();

Geometry Methods

setRadius(r)

Sets the radius and automatically updates area and circumference properties.

Parameters
  • r (number) - New radius in user units
Example
disk.setRadius(10);
console.log(disk.area); // Updated automatically
setShowCenter(show)

Controls whether the center point is visible when drawing.

Parameters
  • show (boolean) - true to show center, false to hide (default: true)
Example
disk.setShowCenter(false); // Hide center point

Animation Methods

breathe(sinusoid, t)

Modulates the radius using a SWSinusoid, creating a "breathing" animation effect. The radius is set to the sinusoid's value at time t.

Parameters
  • sinusoid (SWSinusoid) - Sinusoid controlling radius modulation
  • t (number) - Time or parameter value
Example
let breathSin = new SWSinusoid(5, 2, 0.5, 0); // Center at 5, amplitude 2
disk.breathe(breathSin, frameCount * 0.01);
reset()

Resets the radius to its original value and recalculates area and circumference.

Example
disk.reset(); // Restore original radius

Interaction Methods

containsPoint(x, y, grid)

Tests if a screen (pixel) coordinate is inside the disk. Useful for mouse/touch interaction.

Parameters
  • x (number) - X coordinate in screen pixels
  • y (number) - Y coordinate in screen pixels
  • grid (SWGrid) - Grid for coordinate mapping
Returns

boolean - true if point is inside disk, false otherwise

Example
function mousePressed() {
    if (disk.containsPoint(mouseX, mouseY, grid)) {
        console.log("Disk clicked!");
    }
}

Utility Methods

toString()

Returns a string representation of the disk with all its properties.

Returns

string - Formatted description of the disk

Example
console.log(disk.toString());
// Output: "SWDisk(center: SWPoint(x: 0, y: 0), radius: 5, thickness: 2, ...)"

Usage Examples

Example 1: Basic Disk with Grid

let grid;
let disk;

function setup() {
    createCanvas(400, 400);
    
    // Create grid
    grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
    
    // Create a simple disk at origin
    let center = new SWPoint(0, 0);
    let fillColor = new SWColor(240, 80, 90, 100, "blue");
    let strokeColor = new SWColor(0, 0, 0, 100, "black");
    disk = new SWDisk(center, 5, 3, fillColor, strokeColor);
}

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

Example 2: Color Wheel with Multiple Disks

let grid;
let disks = [];

function setup() {
    createCanvas(600, 600);
    grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
    
    // Create 12 disks in a circle (color wheel)
    const numDisks = 12;
    const wheelRadius = 6;
    const diskRadius = 1.5;
    
    for (let i = 0; i < numDisks; i++) {
        const angle = (i / numDisks) * TWO_PI;
        const x = wheelRadius * cos(angle);
        const y = wheelRadius * sin(angle);
        
        const hue = (i / numDisks) * 360;
        const fillColor = new SWColor(hue, 100, 90, 100, `hue${i}`);
        const strokeColor = new SWColor(0, 0, 0, 100, "black");
        
        const center = new SWPoint(x, y);
        const disk = new SWDisk(center, diskRadius, 2, fillColor, strokeColor);
        disks.push(disk);
    }
}

function draw() {
    background(220);
    grid.draw();
    
    disks.forEach(disk => disk.drawOnGrid(grid));
}

Example 3: Breathing Animation

let grid;
let disk;
let breathSinusoid;

function setup() {
    createCanvas(400, 400);
    grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
    
    // Create disk
    let center = new SWPoint(0, 0);
    disk = new SWDisk(center, 5, 3, swBlue, swBlack);
    
    // Create sinusoid for breathing: center=5, amplitude=2
    // Radius will oscillate between 3 and 7
    breathSinusoid = new SWSinusoid(5, 2, 0.5, 0);
}

function draw() {
    background(220);
    grid.draw();
    
    // Apply breathing animation
    disk.breathe(breathSinusoid, frameCount * 0.02);
    disk.drawOnGrid(grid);
    
    // Display current radius
    fill(0);
    text(`Radius: ${disk.radius.toFixed(2)}`, 10, 20);
    text(`Area: ${disk.area.toFixed(2)}`, 10, 40);
}

Example 4: Interactive Disk Selection

let grid;
let disks = [];
let selectedDisk = null;

function setup() {
    createCanvas(600, 600);
    grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
    
    // Create several disks at random positions
    for (let i = 0; i < 8; i++) {
        const x = random(-8, 8);
        const y = random(-8, 8);
        const hue = random(360);
        
        const center = new SWPoint(x, y);
        const fillColor = new SWColor(hue, 80, 90, 100, `disk${i}`);
        const disk = new SWDisk(center, 1.5, 2, fillColor, swBlack);
        disks.push(disk);
    }
}

function draw() {
    background(220);
    grid.draw();
    
    // Draw all disks
    disks.forEach(disk => {
        // Highlight selected disk
        if (disk === selectedDisk) {
            disk.setStrokeWeight(5);
        } else {
            disk.setStrokeWeight(2);
        }
        disk.drawOnGrid(grid);
    });
    
    // Display selection info
    if (selectedDisk) {
        fill(0);
        text(`Selected: ${selectedDisk.fillColor.name}`, 10, 20);
        text(`Center: (${selectedDisk.center.x.toFixed(1)}, ${selectedDisk.center.y.toFixed(1)})`, 10, 40);
    }
}

function mousePressed() {
    // Check which disk was clicked
    selectedDisk = null;
    for (let disk of disks) {
        if (disk.containsPoint(mouseX, mouseY, grid)) {
            selectedDisk = disk;
            console.log(`Selected: ${disk.fillColor.name}`);
            break;
        }
    }
}

Example 5: Dynamic Color Modification

let grid;
let disk;
let saturation = 100;
let brightness = 100;

function setup() {
    createCanvas(600, 400);
    grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
    
    // Create disk with modifiable color
    let center = new SWPoint(0, 0);
    let fillColor = new SWColor(180, saturation, brightness, 100, "cyan");
    disk = new SWDisk(center, 5, 3, fillColor, swBlack);
    
    // Create sliders
    createP('Saturation');
    let satSlider = createSlider(0, 100, 100);
    satSlider.input(() => {
        saturation = satSlider.value();
        updateDiskColor();
    });
    
    createP('Brightness');
    let brightSlider = createSlider(0, 100, 100);
    brightSlider.input(() => {
        brightness = brightSlider.value();
        updateDiskColor();
    });
}

function updateDiskColor() {
    let newColor = new SWColor(180, saturation, brightness, 100, "cyan");
    disk.setFillColor(newColor);
}

function draw() {
    background(220);
    grid.draw();
    disk.drawOnGrid(grid);
    
    // Display values
    fill(0);
    text(`S: ${saturation}, B: ${brightness}`, 10, 20);
}

function keyPressed() {
    if (key === 'r') {
        disk.resetFillColor();
        saturation = 100;
        brightness = 100;
    }
}

Example 6: Multi-Disk Synchronized Breathing

let grid;
let disks = [];
let breathSinusoid;

function setup() {
    createCanvas(600, 600);
    grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
    
    // Create 6 disks in a hexagon pattern
    const numDisks = 6;
    const hexRadius = 5;
    
    for (let i = 0; i < numDisks; i++) {
        const angle = (i / numDisks) * TWO_PI;
        const x = hexRadius * cos(angle);
        const y = hexRadius * sin(angle);
        
        const hue = (i / numDisks) * 360;
        const fillColor = new SWColor(hue, 80, 90, 100, `disk${i}`);
        
        const center = new SWPoint(x, y);
        const disk = new SWDisk(center, 2, 2, fillColor, swWhite);
        disks.push(disk);
    }
    
    // Breathing sinusoid: center=2, amplitude=1 (range: 1 to 3)
    breathSinusoid = new SWSinusoid(2, 1, 0.3, 0);
    
    frameRate(30);
}

function draw() {
    background(220);
    grid.draw();
    
    // All disks breathe in sync
    const t = frameCount * 0.02;
    disks.forEach(disk => {
        disk.breathe(breathSinusoid, t);
        disk.drawOnGrid(grid);
    });
}

Example 7: Disk Without Center Point

let grid;
let disk;

function setup() {
    createCanvas(400, 400);
    grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
    
    // Create disk
    let center = new SWPoint(0, 0);
    disk = new SWDisk(center, 5, 3, swOrange, swBlack);
    
    // Hide center point for clean circular appearance
    disk.setShowCenter(false);
}

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

function mousePressed() {
    // Toggle center point visibility
    disk.setShowCenter(!disk.shouldShowCenter);
}

Best Practices

1. Color Management

  • Use SWColor instances: Always pass SWColor objects for colors rather than raw p5.js color values
  • Store original colors: The class automatically stores originalFillColor for easy reset
  • Copy colors when sharing: SWDisk automatically copies colors to avoid mutation issues
// Good: Using SWColor
let fillColor = new SWColor(240, 80, 90, 100, "blue");
let disk = new SWDisk(center, 5, 2, fillColor);

// Later, reset is safe
disk.resetFillColor();

2. Coordinate Systems

  • Use drawOnGrid() for logical coordinates: When working with mathematical or world coordinates
  • Use draw() for screen coordinates: When positioning relative to canvas pixels
  • Be consistent: Don't mix coordinate systems within the same sketch

3. Animation and Performance

  • Use breathe() for radius animation: Built-in support for smooth sinusoidal scaling
  • Call reset() before changing animations: Restore original state before new effects
  • Limit disk count: Drawing many disks can impact performance; consider culling off-screen disks

4. Interaction

  • Use containsPoint() for hit detection: Accurate circular hit testing
  • Provide visual feedback: Change strokeWeight or color on hover/click
  • Consider touch targets: Make interactive disks large enough for easy clicking
function mousePressed() {
    if (disk.containsPoint(mouseX, mouseY, grid)) {
        disk.setStrokeWeight(5); // Visual feedback
        // Handle click...
    }
}

5. Center Point Display

  • Hide centers for clean UI: Use setShowCenter(false) for interface elements
  • Show centers for debugging: Helpful for visualizing disk positions during development
  • Automatic color selection: Center color is automatically chosen for good contrast

6. Geometric Properties

  • Use setRadius() for updates: Automatically maintains area/circumference consistency
  • Access computed properties: Use disk.area and disk.circumference for calculations
  • Don't modify radius directly during animation: Use breathe() instead for smooth effects

Integration with Other SketchWave Classes

Script Loading Order

Load dependencies before SWDisk:

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

<!-- SketchWave classes in dependency order -->
<script src="scripts/swColor.js"></script>
<script src="scripts/swPoint.js"></script>
<script src="scripts/swGrid.js"></script>
<script src="scripts/swSinusoid.js"></script>
<script src="scripts/swDisk.js"></script>

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

Working with SWPoint

SWDisk uses SWPoint for its center:

  • The center is a full SWPoint instance with all point capabilities
  • Center point is automatically drawn (unless disabled) with appropriate color
  • You can modify the center point directly to move the disk
// Access center point properties
console.log(disk.center.x, disk.center.y);

// Move disk by changing center
disk.center.x = 5;
disk.center.y = 10;

Working with SWColor

SWDisk uses SWColor for all color management:

  • Fill and stroke colors are SWColor instances
  • Colors are automatically copied to prevent shared mutation
  • Use SWColor methods to modify brightness, saturation, etc.
// Modify disk color using SWColor methods
disk.fillColor.brightenBy(0.2); // 20% brighter
disk.fillColor.setAlphaTo(50);  // 50% transparent

Working with SWGrid

SWDisk integrates with SWGrid for coordinate mapping:

  • Use drawOnGrid() to draw in user coordinates
  • Grid scales radius appropriately from user units to screen pixels
  • containsPoint() uses grid for accurate hit detection

Working with SWSinusoid

SWDisk's breathe() method uses SWSinusoid for animation:

  • Create a sinusoid with desired center value, amplitude, and frequency
  • Pass it to breathe() along with a time parameter
  • Radius will follow the sinusoid's waveform
// Create breathing effect centered at radius 5
let breathe = new SWSinusoid(5, 2, 0.5, 0);
disk.breathe(breathe, frameCount * 0.01);

Common Patterns

Pattern 1: Color Wheel Display

Create a circular arrangement of disks showing hue variation:

const numHues = 12;
const wheelRadius = 6;
for (let i = 0; i < numHues; i++) {
    const angle = (i / numHues) * TWO_PI;
    const x = wheelRadius * cos(angle);
    const y = wheelRadius * sin(angle);
    const hue = (i / numHues) * 360;
    const fillColor = new SWColor(hue, 100, 90, 100);
    const center = new SWPoint(x, y);
    const disk = new SWDisk(center, 1.5, 2, fillColor, swBlack);
    disks.push(disk);
}

Pattern 2: Interactive Selection with Visual Feedback

Highlight selected disk with thicker border:

function draw() {
    disks.forEach(disk => {
        disk.setStrokeWeight(disk === selectedDisk ? 6 : 2);
        disk.drawOnGrid(grid);
    });
}

function mousePressed() {
    for (let disk of disks) {
        if (disk.containsPoint(mouseX, mouseY, grid)) {
            selectedDisk = disk;
            break;
        }
    }
}

Pattern 3: Pulsing/Breathing Animation

Create smooth size oscillation:

// Setup
let breathSin = new SWSinusoid(baseRadius, amplitude, frequency, 0);

// Draw loop
disk.breathe(breathSin, frameCount * timeScale);
disk.drawOnGrid(grid);

Pattern 4: Dynamic Color Adjustment

Modify disk color properties in real-time:

function updateDiskColor(h, s, b, a) {
    let newColor = new SWColor(h, s, b, a, disk.fillColor.name);
    disk.setFillColor(newColor);
}

// Connect to sliders or keyboard input
function keyPressed() {
    if (key === 'b') disk.fillColor.brightenBy(0.1);
    if (key === 'd') disk.fillColor.darkenBy(0.1);
}

Source Code

The complete SWDisk class implementation:

Show/Hide Source Code
/*
File: swDisk.js
Date: 2026-02-17
Author: klp
Workspace: SWaveJS2026-01-30-StgC
Purpose: SWDisk class for SketchWaveJS
Comment(s):

Has an update: if a 'null' color is passed for the center point, it will use
the disk's stroke color (if available) or a darker version of the fill color
(if available) or swBlack as a last resort. This allows the center point to
automatically adapt its color based on the disk's styling, while still
allowing explicit control if desired.

Another update: setFillAlpha and setStrokeAlpha methods have been added to
allow changing the transparency of the fill and stroke colors after the disk
has been created. These methods ensure that the alpha value is clamped
between 0 and 100 and update the corresponding p5.js color object accordingly.
*/

console.log("[swDisk.js] SWDisk class loaded.");

class SWDisk {
    /**
     * @param {SWPoint} center - Center point (SWPoint instance)
     * @param {number} radius - Radius (user units)
     * @param {number} [thickness=2] - Border (stroke) thickness
     * @param {SWColor} [fillColor] - Fill color (SWColor instance)
     * @param {SWColor} [strokeColor] - Border color (SWColor instance)
     */
    constructor(center, radius, thickness = 2, fillColor = undefined, strokeColor = undefined) {
        this.center = center; // SWPoint instance
        this.radius = radius;
        this.thickness = thickness;
        // Store a copy of the original fill color for reset
        this.originalFillColor = fillColor ? SWColor.copy(fillColor) : undefined;
        // fillColor is always a copy, so cycleParameter only affects the current color
        this.fillColor = fillColor ? SWColor.copy(fillColor) : undefined;
        this.strokeColor = strokeColor ? SWColor.copy(strokeColor) : undefined;
        // Store original radius for breathing
        this.originalRadius = radius;
        // Area and circumference
        this.area = Math.PI * radius * radius;
        this.circumference = 2 * Math.PI * radius;
        // Show center point (default: true for this version)
        this.shouldShowCenter = true;
        // Set center point color according to rules
        this.updateCenterColor();
    }//end constructor

    /**
     * Updates the center point color according to the rules:
     * - If strokeColor, use that
     * - Else if fillColor, use 20% darker version
     * - Else use swBlack
     * Note: This DOES NOT modify the center point itself, just stores
     * the color to use when drawing the center as part of this disk.
     */
    updateCenterColor() {
        let centerColor;
        if (this.strokeColor && this.strokeColor.col) {
            centerColor = this.strokeColor;
        } else if (this.fillColor && this.fillColor.col) {
            // Make a color 20% darker (reduce brightness by 20%)
            if (typeof SWColor === 'function') {
                let h = this.fillColor.h !== undefined ? this.fillColor.h : 0;
                let s = this.fillColor.s !== undefined ? this.fillColor.s : 0;
                let b = this.fillColor.b !== undefined ? this.fillColor.b : 100;
                let a = this.fillColor.a !== undefined ? this.fillColor.a : 100;
                let darkerB = Math.max(0, b - 20);
                centerColor = new SWColor(h, s, darkerB, a, 'centerDarker');
            } else {
                centerColor = swBlack;
            }
        } else {
            centerColor = swBlack;
        }
        // Store the color for use when drawing, but DON'T modify the center point
        this.centerColor = centerColor;
    }//end updateCenterColor

    /**
     * Draws the disk in screen coordinates (p5.js)
     */
    draw() {
        if (this.fillColor && this.fillColor.col) {
            fill(this.fillColor.col);
        } else {
            noFill();
        }
        if (this.strokeColor && this.strokeColor.col) {
            stroke(this.strokeColor.col);
        } else {
            noStroke();
        }
        strokeWeight(this.thickness);
        ellipse(this.center.x, this.center.y, 2 * this.radius, 2 * this.radius);
        // Optionally draw center point (pass disk's stroke color as default)
        if (this.shouldShowCenter && this.center && this.center.draw) {
            this.center.draw(this.strokeColor);
        }
        noStroke();
        noFill();
        strokeWeight(1);
    }//end draw

    /**
     * Draws the disk in user (grid) coordinates using the given SWGrid
     * @param {SWGrid} grid
     */
    drawOnGrid(grid) {
        const { x: cx, y: cy } = grid.userToScreen(this.center.x, this.center.y);
        // Convert radius from user units to screen units (average scale)
        const rx = grid.xScale * this.radius;
        const ry = grid.yScale * this.radius;
        const rScreen = (rx + ry) / 2;
        if (this.fillColor && this.fillColor.col) {
            fill(this.fillColor.col);
        } else {
            noFill();
        }
        if (this.strokeColor && this.strokeColor.col) {
            stroke(this.strokeColor.col);
        } else {
            noStroke();
        }
        strokeWeight(this.thickness);
        ellipse(cx, cy, 2 * rScreen, 2 * rScreen);
        // Optionally draw center point (pass disk's stroke color as default)
        if (this.shouldShowCenter && this.center && this.center.drawOnGrid) {
            this.center.drawOnGrid(grid, this.strokeColor);
        }
        noStroke();
        noFill();
        strokeWeight(1);
    }//end drawOnGrid

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

    breathe(sinusoid, t) {
        this.radius = sinusoid.getValue(t);
        this.area = Math.PI * this.radius * this.radius;
        this.circumference = 2 * Math.PI * this.radius;
    }

    reset() {
        this.radius = this.originalRadius;
        this.area = Math.PI * this.radius * this.radius;
        this.circumference = 2 * Math.PI * this.radius;
    }//end reset

    toString() {
        return `SWDisk(center: ${this.center.toString()}, radius: ${this.radius}, thickness: ${this.thickness}, fillColor: ${this.fillColor ? this.fillColor.toString() : 'none'}, strokeColor: ${this.strokeColor ? this.strokeColor.toString() : 'none'}, area: ${this.area.toFixed(2)}, circumference: ${this.circumference.toFixed(2)})`;
    }//end toString

    setFillColor(swColor) {
        // Always make a copy to avoid mutating shared color objects
        this.fillColor = swColor ? SWColor.copy(swColor) : undefined;
        this.updateCenterColor();
    }//end setFillColor

    resetFillColor() {
        if (this.originalFillColor) {
            this.fillColor = SWColor.copy(this.originalFillColor);
            this.updateCenterColor();
        }
    }//end resetFillColor

    setStrokeColor(swColor) {
        this.strokeColor = swColor;
        this.updateCenterColor();
    }//end setStrokeColor

    setStrokeWeight(w) {
        this.thickness = w;
    }//end setStrokeWeight

    setRadius(r) {
        //dwr! changing r, changes other dependent properties
        this.radius = r;
        this.area = Math.PI * r * r;
        this.circumference = 2 * Math.PI * r;
    }//end setRadius

    setFillAlpha(alpha) {
        if (this.fillColor) {
            this.fillColor.a = Math.max(0, Math.min(100, alpha));
            this.fillColor.col = color(this.fillColor.h, this.fillColor.s, this.fillColor.b, this.fillColor.a);
        }
    }//end setFillAlpha

    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);
            this.updateCenterColor();
        }
    }//end setStrokeAlpha

    containsPoint(x, y, grid) {
        // Get disk center in screen coordinates
        const { x: cx, y: cy } = grid.userToScreen(this.center.x, this.center.y);
        // Convert radius from user units to screen units (average scale)
        const rx = grid.xScale * this.radius;
        const ry = grid.yScale * this.radius;
        const rScreen = (rx + ry) / 2;
        // Compute distance from (x, y) to center
        const distSq = (x - cx) * (x - cx) + (y - cy) * (y - cy);
        return distSq <= rScreen * rScreen;
    }//end containsPoint
}//end class SWDisk