SWSector Reference

A SketchWave class for representing "pizza-slice" sectors in 2D space

Back to SWSector Demo

Quick Reference

SWSector is a SketchWave class for representing circular sector ("pizza-slice") shapes in 2D space. A sector is defined by a vertex point (the tip), a radius, and an angular size theta. It supports spin animation, radius breathing, theta breathing, and hue cycling — all independently or simultaneously.

  • Extends: Nothing (standalone class)
  • Dependencies: SWPoint, SWColor, SWSinusoid, SWGrid, p5.js
  • Key Features: Custom fill/stroke colors, arc length/area calculations, spin rotation, radius and theta breathing animations, hue cycling, vertex point display
  • Common Uses: Pie charts, compass needles, fan/blade shapes, animated "Pac-Man" effects, interactive angle visualizations

Overview

The SWSector class represents a circular sector — the region bounded by two radii and the arc between them — drawn on an SWGrid. Think of it as a "pizza slice": it has a vertex (the tip), a radius, and an angular opening theta. The sector's starting orientation is controlled by startAngle, and it can be continuously spun via rotate().

Angle Convention

SWSector uses math-space (user-space) angles: angles are measured counterclockwise (CCW) from the positive x-axis, and y increases upward — the standard Cartesian convention. Because p5.js uses a y-down screen space, all angles are negated internally when calling arc(). You never need to worry about this conversion; just pass CCW degrees from +x.

// Default: startAngle=0, theta=90 → sector opens into Q1 (from +x axis upward)
let sector = new SWSector(new SWPoint(0,0), 5, 90);

// startAngle=90 → sector opens into Q2 (from +y axis leftward)
let sector2 = new SWSector(new SWPoint(0,0), 5, 90, 90);

Key Capabilities

  • Flexible Positioning: Vertex defined by an SWPoint instance
  • Full Styling Control: Independent fill and stroke colors using SWColor
  • Geometric Properties: Automatic arc length and area calculations
  • Spin Animation: Continuous rotation about the vertex via rotate()
  • Breathing Animations: Oscillate radius or theta independently with SWSinusoid
  • Hue Cycling: Animate fill color hue externally via SWSinusoid
  • Dual Coordinate Systems: Draw in screen pixels or grid coordinates
  • Vertex Point Display: Optional visualization of the sector tip

Typical Workflow

  1. Create an SWSector with vertex point, radius, theta, startAngle, and colors
  2. Draw the sector each frame using drawOnGrid()
  3. Call rotate() before drawing to spin; call breatheRadius() or breatheTheta() after drawing to modulate next frame
  4. Use the elapsed-time pattern for pause/resume of each animation
  5. Call reset() to restore the original geometry and color

Constructor

new SWSector(vertex, radius, theta, startAngle, thickness, fillColor, strokeColor)

Creates a new SWSector instance with the given geometry and styling.

Parameters
Parameter Type Default Description
vertex SWPoint required Tip/origin point of the sector (the pizza crust point)
radius number required Radius in user units (> 0)
theta number required Angular size in degrees; clamped to [0, 360]
startAngle number 0 First-edge angle in degrees CCW from +x axis; sets initial orientation
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: vertex, radius, and theta
let s1 = new SWSector(new SWPoint(0, 0), 5, 90);

// With explicit startAngle (opens into Q2)
let s2 = new SWSector(new SWPoint(0, 0), 5, 90, 90);

// With fill color and border
let fillCol   = new SWColor(30, 100, 80, 80, "orange");
let strokeCol = new SWColor(0, 0, 0, 100, "black");
let s3 = new SWSector(new SWPoint(0, 0), 5, 120, 0, 2, fillCol, strokeCol);

// Fully positioned sector pointing right, half open
let v = new SWPoint(-4, 2);
let s4 = new SWSector(v, 6, 60, 270, 3, fillCol, strokeCol);

Properties

vertex SWPoint

The tip point of the sector. Changing this moves the whole sector.

sector.vertex.x = 3; sector.vertex.y = -2;
radius number

The radius of the sector in user units. Changing this directly does not update arcLength or area — use setRadius() for that.

sector.setRadius(8); // also updates arcLength and area
theta number

The angular size of the sector in degrees, clamped to [0, 360]. Use setTheta() to keep geometry in sync.

sector.setTheta(180); // half-circle sector
startAngle number

The static starting angle (degrees CCW from +x) for the first edge. This is the orientation before any rotation accumulates. Use setStartAngle() to change it.

sector.setStartAngle(45); // first edge at 45°
rotation number

Accumulated rotation in degrees (CCW positive). Set to 0 by the constructor; incremented by rotate(). The effective angle of the first edge is startAngle + rotation.

// Read current rotation console.log(sector.rotation.toFixed(1) + "°");
thickness number

The stroke (border) thickness in pixels. Use setStrokeWeight() to update.

sector.setStrokeWeight(4);
fillColor SWColor

The interior fill color as an SWColor instance. Colors are always copied to avoid shared-mutation bugs.

sector.setFillColor(new SWColor(120, 80, 90, 80, "green"));
strokeColor SWColor

The border color as an SWColor instance.

sector.setStrokeColor(swBlack);
originalRadius / originalTheta / originalStartAngle / originalRotation / originalFillColor various restore targets

Snapshot values taken at construction. reset() uses all of these to restore the sector to its initial state.

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

Whether to draw the vertex SWPoint marker. Default is true.

sector.setShowVertex(false); // hide vertex dot
arcLength number computed

The arc length of the sector's curved edge: (theta / 360) × 2πr. Updated automatically when radius or theta changes.

console.log(`Arc length: ${sector.arcLength.toFixed(2)}`);
area number computed

The area of the sector: (theta / 360) × πr². Updated automatically when radius or theta changes.

console.log(`Area: ${sector.area.toFixed(2)}`);

Methods

Core Drawing Methods

draw()

Draws the sector in screen (pixel) coordinates using p5.js. Rarely used directly — prefer drawOnGrid().

Returns

void

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

Draws the sector in user (grid) coordinates. Converts the vertex position and radius through the SWGrid's coordinate mapping.

Parameters
  • grid (SWGrid) — the coordinate grid
Returns

void

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

Rotation Animation

rotate(deltaAngle)

Increments the sector's accumulated rotation by deltaAngle degrees (CCW positive, CW negative). Call each frame to spin the sector about its vertex.

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

// Or use p5 millis() for a fixed-step spin
sector.rotate(1); // 1 degree per frame

Breathing Animations

breatheRadius(sinusoid, t)

Modulates the radius using an SWSinusoid. The radius is set to the sinusoid's value at time 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
let radSin = new SWSinusoid(5, 3, 1/4, 0); // center=5, amp=3, freq=0.25 Hz
sector.drawOnGrid(grid);
sector.breatheRadius(radSin, elapsedSeconds);
breatheTheta(sinusoid, t)

Modulates the angular size (theta) using an SWSinusoid. Call after drawOnGrid(). _updateGeometry() is called automatically to clamp theta and refresh area/arcLength.

Parameters
  • sinusoid (SWSinusoid) — controls theta oscillation
  • t (number) — elapsed time in seconds
Example
// Theta oscillates between 20° and 160° over 3 seconds
let thetaSin = new SWSinusoid(90, 70, 1/3, 0); // center=90°, amp=70°, freq=0.33 Hz
sector.drawOnGrid(grid);
sector.breatheTheta(thetaSin, elapsedSeconds);

Color and Styling Methods

setFillColor(swColor)

Sets the fill color. A copy is made automatically.

Parameters
  • swColor (SWColor) — new fill color
Example
sector.setFillColor(swBlue);
resetFillColor()

Restores the fill color to the original stored at construction.

Example
sector.resetFillColor();
setStrokeColor(swColor)

Sets the border (stroke) color.

Parameters
  • swColor (SWColor) — new stroke color
Example
sector.setStrokeColor(swBlack);
setStrokeWeight(w)

Sets the border thickness in pixels.

Parameters
  • w (number) — thickness in pixels
Example
sector.setStrokeWeight(4);
setFillAlpha(alpha)

Sets the alpha (transparency) of the fill color. Clamped to [0, 100]; updates the underlying p5.js color immediately.

Parameters
  • alpha (number) — 0 = fully transparent, 100 = fully opaque
Example
sector.setFillAlpha(60); // 60% opaque fill
setStrokeAlpha(alpha)

Sets the alpha of the stroke (border) color. Clamped to [0, 100].

Parameters
  • alpha (number) — 0 = fully transparent, 100 = fully opaque
Example
sector.setStrokeAlpha(80);

Geometry Methods

setRadius(r)

Sets the radius and automatically updates arcLength and area.

Parameters
  • r (number) — new radius in user units
Example
sector.setRadius(7);
console.log(sector.area.toFixed(2));
setTheta(degrees)

Sets the angular size (theta) and automatically updates arcLength and area. Value is clamped to [0, 360].

Parameters
  • degrees (number) — new angular size in degrees
Example
sector.setTheta(45); // narrow sector
setStartAngle(degrees)

Sets the static starting angle (degrees CCW from +x) without affecting accumulated rotation.

Parameters
  • degrees (number) — new start angle
Example
sector.setStartAngle(180); // first edge pointing left
setShowVertex(show)

Controls whether the vertex SWPoint dot is drawn.

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

Reset Methods

reset()

Restores all animated properties — radius, theta, startAngle, rotation, and fillColor — to their originals captured at construction.

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

Utility Methods

static copy(other)

Returns a deep copy of an SWSector instance. All geometry and color values are independently duplicated.

Parameters
  • other (SWSector) — the sector to copy
Returns

SWSector — a new independent instance

Example
let copy = SWSector.copy(sector1);
toString()

Returns a string representation of the sector with all its key properties.

Returns

string

Example
console.log(sector.toString());
// "SWSector(vertex: SWPoint(x: 0, y: 0), radius: 5.00, theta: 90.0°, startAngle: 0°, rotation: 0.0°, area: 19.63, arcLength: 7.85)"

Usage Examples

Example 1: Basic Sector with Grid

let grid;
let sector;

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

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

    let vertex    = new SWPoint(0, 0);
    let fillColor = new SWColor(30, 100, 80, 80, "orange");
    let strokeCol = new SWColor(0, 0, 0, 100, "black");
    sector = new SWSector(vertex, 5, 90, 0, 2, fillColor, strokeCol);
}

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

Example 2: Spinning Sector (Elapsed-Time Approach)

let grid, sector;
let prevT = 0;
const SPIN_SPEED = 60; // degrees per second

function setup() {
    createCanvas(400, 400);
    colorMode(HSB, 360, 100, 100, 100);
    grid   = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
    sector = new SWSector(new SWPoint(0, 0), 5, 60, 0, 2,
                          new SWColor(200, 80, 90, 80, "blue"), swBlack);
}

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

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

    sector.rotate(SPIN_SPEED * deltaT);  // CCW positive
    sector.drawOnGrid(grid);
}

Example 3: Breathing Radius

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

function setup() {
    createCanvas(400, 400);
    colorMode(HSB, 360, 100, 100, 100);
    grid   = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
    sector = new SWSector(new SWPoint(0, 0), 5, 90, 0, 2,
                          new SWColor(120, 80, 80, 80, "green"), swBlack);
    // Center=5, amplitude=3 → range [2, 8], period=4 sec → freq=0.25 Hz
    radSin = new SWSinusoid(5, 3, 0.25, 0);
}

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

    if (shouldBreathe) {
        const t = millis() / 1000;
        breathElapsed += (t - breathStart);
        breathStart = t;
        sector.breatheRadius(radSin, breathElapsed);  // call AFTER draw
    }
    text(`radius: ${sector.radius.toFixed(2)}  area: ${sector.area.toFixed(2)}`, 10, 20);
}

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

Example 4: Breathing Theta (Pac-Man Effect)

let grid, sector, thetaSin;
let thetaStart = 0, thetaElapsed = 0;

function setup() {
    createCanvas(400, 400);
    colorMode(HSB, 360, 100, 100, 100);
    grid   = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
    sector = new SWSector(new SWPoint(0, 0), 5, 300, 30, 2,
                          new SWColor(55, 100, 95, 100, "yellow"), swBlack);
    // Theta oscillates between 20° and 340°, period=2 sec
    thetaSin = new SWSinusoid(180, 160, 0.5, 0);
    thetaStart = millis() / 1000;
}

function draw() {
    background(0, 0, 20); // dark background
    grid.draw();
    sector.drawOnGrid(grid);
    const t = millis() / 1000;
    thetaElapsed += (t - thetaStart);
    thetaStart = t;
    sector.breatheTheta(thetaSin, thetaElapsed);
}

Example 5: Simultaneous Spin + Radius Breathe + Hue Cycle

let grid, sector, radSin, hueSin;
let prevT = 0;
let rStart = 0, rElapsed = 0;
let hStart = 0, hElapsed = 0;

function setup() {
    createCanvas(400, 400);
    colorMode(HSB, 360, 100, 100, 100);
    grid   = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
    sector = new SWSector(new SWPoint(0, 0), 5, 90, 0, 2,
                          new SWColor(30, 100, 80, 80, "orange"), swBlack);
    radSin = new SWSinusoid(5, 3, 0.25, 0); // radius 2–8
    hueSin = new SWSinusoid(180, 180, 1/3, 0); // hue 0–360
}

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

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

    // 1. Apply spin BEFORE draw
    sector.rotate(45 * deltaT);

    // 2. Apply hue cycling BEFORE draw
    hElapsed += deltaT;
    sector.fillColor.h = hueSin.getValue(hElapsed);
    sector.fillColor.col = color(sector.fillColor.h,
                                  sector.fillColor.s,
                                  sector.fillColor.b,
                                  sector.fillColor.a);

    sector.drawOnGrid(grid);

    // 3. Apply breathing AFTER draw
    rElapsed += deltaT;
    sector.breatheRadius(radSin, rElapsed);
}

Example 6: Pie Chart Sectors

let grid;
let sectors = [];

function setup() {
    createCanvas(400, 400);
    colorMode(HSB, 360, 100, 100, 100);
    grid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});

    // Data: percentage slices
    const data   = [30, 25, 20, 15, 10]; // must sum to 100
    const origin = new SWPoint(0, 0);
    let cumAngle = 0;

    data.forEach((pct, i) => {
        const theta     = (pct / 100) * 360;
        const hue       = (i / data.length) * 360;
        const fillColor = new SWColor(hue, 80, 85, 90, `slice${i}`);
        const strokeCol = new SWColor(0, 0, 100, 100, "white");
        sectors.push(new SWSector(SWPoint.copy(origin), 6, theta, cumAngle, 2, fillColor, strokeCol));
        cumAngle += theta;
    });
}

function draw() {
    background(0, 0, 95);
    grid.draw();
    sectors.forEach(s => s.drawOnGrid(grid));
}

Best Practices

1. Animation Ordering

  • Spin/hue changes BEFORE draw: These affect what is rendered this frame
  • Breathing AFTER draw: The new value takes effect next frame, matching SWDisk convention
sector.rotate(speed * deltaT);   // spin  → before draw
sector.drawOnGrid(grid);          // draw
sector.breatheRadius(sin, t);     // breathe → after draw

2. Elapsed Time vs. frameCount

  • Use elapsed seconds (not frame count) for all sinusoid time parameters; this decouples animation speed from frame rate
  • 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, isBrething = false;

function toggleBreathe() {
    isBrething = !isBrething;
    if (isBrething) breathStart = millis() / 1000;
}

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

3. Angle Convention

  • Always pass user-space degrees (CCW from +x) to SWSector; the class handles the p5.js y-flip internally
  • startAngle=0 means the first edge lies on the +x axis, opening CCW into Q1 by default
  • Positive rotate() deltas spin CCW; negative values spin CW

4. Color Management

  • Always pass SWColor instances; the constructor copies them to prevent shared-mutation bugs
  • For hue cycling, modify sector.fillColor.h directly and rebuild .col before drawing
  • Call reset() or resetFillColor() to cleanly restore original colors

5. SWSinusoid Setup for Breathing

  • The sinusoid's center value is the midpoint of the animation range
  • The amplitude is half the desired peak-to-peak swing
  • Example: range [2, 8] → center = 5, amplitude = 3
// Radius breathes between minVal and maxVal
const center    = (minVal + maxVal) / 2;
const amplitude = (maxVal - minVal) / 2;
const frequency = 1 / period;   // Hz (cycles per second)
let radSin = new SWSinusoid(center, amplitude, frequency, 0);

Integration with Other SketchWave Classes

Script Loading Order

Load dependencies before SWSector:

<!-- 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>

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

Working with SWPoint

SWSector uses SWPoint for its vertex (tip):

  • The vertex is a full SWPoint instance — it is drawn as a small dot when shouldShowVertex is true
  • Drag or reposition the sector by changing sector.vertex.x and sector.vertex.y
// Move vertex to user coordinates (2, -3)
sector.vertex.x = 2;
sector.vertex.y = -3;

Working with SWColor

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

  • Colors are automatically copied to prevent shared-mutation bugs
  • For hue cycling, mutate sector.fillColor.h then rebuild sector.fillColor.col
// Hue cycling by direct mutation (done before drawOnGrid)
sector.fillColor.h   = hueSin.getValue(hElapsed);
sector.fillColor.col = color(sector.fillColor.h,
                              sector.fillColor.s,
                              sector.fillColor.b,
                              sector.fillColor.a);

Working with SWGrid

SWSector integrates with SWGrid for coordinate mapping:

  • Use drawOnGrid(grid) to draw in user units — the grid converts both position and radius to screen pixels
  • The average of grid.xScale and grid.yScale is used for the radius, keeping the sector circular even on non-square grids

Working with SWSinusoid

SWSector's breathing methods accept SWSinusoid instances:

  • One sinusoid for breatheRadius(), a separate one for breatheTheta()
  • The sinusoid's getValue(t) is called with elapsed time in seconds

Source Code

The complete SWSector class implementation:

Show/Hide Source Code
/*
File: swSector.js
Date: 2026-03-28
Author: klp
App:  SketchWaveTNT2026-03-19-Stg7
Purpose: SWSector class for SketchWaveJS

SWSector represents a "pizza-slice" (sector) shape defined by:
  - A vertex SWPoint (the tip of the slice)
  - A radius (in user units)
  - A theta (angular size in degrees)
  - A startAngle (degrees CCW from the +x axis, where the first edge lies)
  - A rotation offset (degrees, accumulated via rotate())

Default orientation: one edge lies along the +x axis, sector opens CCW
into the first quadrant.  Set startAngle to establish a different static
starting position before rotation begins.

Animations (all independent, all composable):
  - rotate(delta): call each frame to spin the sector about its vertex.
  - breatheRadius(sinusoid, t): oscillates the radius with an SWSinusoid.
  - breatheTheta(sinusoid, t):  oscillates the angular size with an SWSinusoid.

Color cycling: mutate fillColor.h and rebuild fillColor.col before drawing.

Angle convention:
  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 a sector whose first edge is at totalAngle = startAngle + rotation,
spanning theta degrees CCW:
  p5 arc start = -radians(totalAngle + theta)
  p5 arc stop  = -radians(totalAngle)
  arc(cx, cy, 2r, 2r, p5Start, p5Stop, PIE)  — p5 arc is clockwise.
*/

console.log("[swSector.js] SWSector class loaded.");

class SWSector {

    constructor(vertex, radius, theta, startAngle = 0, thickness = 2,
                fillColor = undefined, strokeColor = undefined) {

        this.vertex     = vertex;
        this.radius     = radius;
        this.theta      = theta;
        this.startAngle = startAngle;
        this.rotation   = 0;
        this.thickness  = thickness;

        this.fillColor   = fillColor   ? SWColor.copy(fillColor)   : undefined;
        this.strokeColor = strokeColor ? SWColor.copy(strokeColor) : undefined;

        this.originalRadius     = radius;
        this.originalTheta      = theta;
        this.originalStartAngle = startAngle;
        this.originalRotation   = 0;
        this.originalFillColor  = fillColor ? SWColor.copy(fillColor) : undefined;

        this.shouldShowVertex = true;

        this._updateGeometry();
    }

    _updateGeometry() {
        this.theta     = Math.max(0, Math.min(360, this.theta));
        this.arcLength = (this.theta / 360) * 2 * Math.PI * this.radius;
        this.area      = (this.theta / 360) * Math.PI * this.radius * this.radius;
    }

    draw() {
        this._drawArc(this.vertex.x, this.vertex.y, this.radius);
        if (this.shouldShowVertex && this.vertex && this.vertex.draw) {
            this.vertex.draw(this.strokeColor);
        }
    }

    drawOnGrid(grid) {
        const { x: cx, y: cy } = grid.userToScreen(this.vertex.x, this.vertex.y);
        const rScreen = (grid.xScale * this.radius + grid.yScale * this.radius) / 2;
        this._drawArc(cx, cy, rScreen);
        if (this.shouldShowVertex && this.vertex && this.vertex.drawOnGrid) {
            this.vertex.drawOnGrid(grid, this.strokeColor);
        }
    }

    _drawArc(cx, cy, r) {
        const totalAngle = this.startAngle + this.rotation;
        const p5Start = -radians(totalAngle + this.theta);
        const p5Stop  = -radians(totalAngle);

        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);
        arc(cx, cy, 2 * r, 2 * r, p5Start, p5Stop, PIE);
        noStroke();
        noFill();
        strokeWeight(1);
    }

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

    breatheRadius(sinusoid, t) {
        this.radius = Math.max(0.01, sinusoid.getValue(t));
        this._updateGeometry();
    }

    breatheTheta(sinusoid, t) {
        this.theta = sinusoid.getValue(t);
        this._updateGeometry();
    }

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

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

    reset() {
        this.radius     = this.originalRadius;
        this.theta      = this.originalTheta;
        this.startAngle = this.originalStartAngle;
        this.rotation   = this.originalRotation;
        if (this.originalFillColor) {
            this.fillColor = SWColor.copy(this.originalFillColor);
        }
        this._updateGeometry();
    }

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

    setFillColor(swColor)   { this.fillColor   = swColor ? SWColor.copy(swColor) : undefined; }
    setStrokeColor(swColor) { this.strokeColor = swColor ? SWColor.copy(swColor) : undefined; }
    setStrokeWeight(w)      { this.thickness   = w; }
    setRadius(r)            { this.radius      = r; this._updateGeometry(); }
    setTheta(degrees)       { this.theta       = degrees; this._updateGeometry(); }
    setStartAngle(degrees)  { this.startAngle  = degrees; }
    setShowVertex(show = true) { this.shouldShowVertex = show; }

    static copy(other) {
        if (!(other instanceof SWSector)) {
            throw new Error('Argument to SWSector.copy must be an SWSector instance');
        }
        const s = new SWSector(
            SWPoint.copy(other.vertex),
            other.originalRadius,
            other.originalTheta,
            other.originalStartAngle,
            other.thickness,
            other.fillColor,
            other.strokeColor
        );
        s.radius           = other.radius;
        s.theta            = other.theta;
        s.startAngle       = other.startAngle;
        s.rotation         = other.rotation;
        s.shouldShowVertex = other.shouldShowVertex;
        s._updateGeometry();
        return s;
    }

    toString() {
        return `SWSector(vertex: ${this.vertex.toString()}, ` +
               `radius: ${this.radius.toFixed(2)}, theta: ${this.theta.toFixed(1) + String.fromCharCode(176)}, ` +
               `startAngle: ${this.startAngle + String.fromCharCode(176)}, ` +
               `rotation: ${this.rotation.toFixed(1) + String.fromCharCode(176)}, ` +
               `area: ${this.area.toFixed(2)}, arcLength: ${this.arcLength.toFixed(2)})`;
    }

}//end SWSector class