SketchWave Point Reference

Understanding and Using SWPoint - Stage

Back to SWPoint Demo

...loading...

SWPoint Class Reference

Overview

The SWPoint class represents points or vectors in 2D or 3D space with styling options. It is designed to work seamlessly within the p5.js environment and integrates with the SketchWave ecosystem, particularly the SWColor and SWGrid classes.

Key Features:

  • Support for 2D and 3D coordinates
  • Customizable stroke weight and color
  • Optional text labels with flexible positioning
  • Interactive drag-and-drop functionality
  • Pen trail functionality to track point movement
  • Drawing in both screen and user (grid) coordinates
  • Vector operations (distance calculations)
  • Automatic label boundary detection
  • Extensible design for animation and interaction

Constructor

new SWPoint(x, y, z, strokeWeight, strokeColor, label)

Parameters:

Parameter Type Default Description
x number required X coordinate of the point
y number required Y coordinate of the point
z number undefined Z coordinate (optional, for 3D space)
strokeWeight number 1 Weight/thickness of the point when drawn
strokeColor SWColor undefined An SWColor instance for the point's color
label string undefined Label text (typically a single letter like "A", "B", etc.)
Example:
// Create a simple 2D point
let point1 = new SWPoint(100, 200);

// Create a styled 2D point
let redColor = new SWColor(0, 100, 100);  // HSB: red
let point2 = new SWPoint(150, 250, undefined, 5, redColor);

// Create a 3D point
let blueColor = new SWColor(240, 100, 100);  // HSB: blue
let point3 = new SWPoint(100, 150, 50, 3, blueColor);

// Create a labeled point
let greenColor = new SWColor(108, 50, 78);  // HSB: light green
let point4 = new SWPoint(200, 100, undefined, 8, greenColor, "A");
point4.showLabel = true;

Properties

Property Type Description
x number X coordinate of the point
y number Y coordinate of the point
z number | undefined Z coordinate (undefined for 2D points)
strokeWeight number The weight/thickness when drawing the point
strokeColor SWColor The SWColor instance for the point's color
label string | undefined Label text (typically a single letter)
showLabel boolean Whether to display the label (default: false)
labelOffset object Label position offset {x, y} in pixels (default: {x: 10, y: -10})
labelFontSize number Font size for label text in pixels (default: 12)
isDraggable boolean Whether the point can be dragged (default: false)
penOn boolean Whether the pen trail is active (default: false)
trail Array Array of previous positions [{x, y, z}]
maxTrailLength number Maximum number of trail points (default: 500)
Note: All properties are directly accessible and can be modified. However, when manually changing x, y, or z, call _updateTrail() or use the move() method to ensure the trail is updated properly if the pen is on.

Methods

Static Methods

SWPoint.copy(other)

Creates and returns a deep copy of the given SWPoint instance, including all properties, labelOffset, and trail. Uses SWColor.copy() to deep-copy the stroke color.

Parameters:
  • other (SWPoint): The SWPoint instance to copy. Throws an Error if the argument is not an SWPoint instance.
Returns: SWPoint — a new independent copy of other
let original = new SWPoint(100, 200, undefined, 5, redColor, "A");
original.showLabel = true;
original.setPen(true);
let copy = SWPoint.copy(original);  // Deep copy — independent of original

Drawing Methods

draw(defaultColor)

Draws the point using p5.js in screen coordinates. If the point's strokeColor is null, it will use defaultColor if provided, otherwise falls back to black (swBlack).

Parameters:
  • defaultColor (SWColor, optional): Fallback color to use when strokeColor is null
function draw() {
    background(0, 0, 86);  // HSB: light gray
    myPoint.draw();  // Uses strokeColor, or black if null

    // With a fallback color:
    let fallback = new SWColor(0, 100, 100);  // red
    myPoint.draw(fallback);  // Uses red if strokeColor is null
}
drawOnGrid(grid, defaultColor)

Draws the point using user (grid) coordinates mapped by the given SWGrid instance. If the point's strokeColor is null, it will use defaultColor if provided, otherwise falls back to black (swBlack).

Parameters:
  • grid (SWGrid): The grid instance for coordinate transformation
  • defaultColor (SWColor, optional): Fallback color to use when strokeColor is null
let myGrid = new SWGrid(-10, 10, -10, 10);
myPoint.drawOnGrid(myGrid);  // Uses strokeColor, or black if null

// With a fallback color:
let fallback = new SWColor(0, 100, 100);  // red
myPoint.drawOnGrid(myGrid, fallback);  // Uses red if strokeColor is null
drawTrail()

Draws the pen trail in screen coordinates. Only draws if pen is on and trail has at least 2 points.

myPoint.setPen(true);
// ... move the point around ...
myPoint.drawTrail(); // Shows the path
drawTrailOnGrid(grid)

Draws the pen trail in user (grid) coordinates.

Parameters:
  • grid (SWGrid): The grid instance for coordinate transformation

Movement Methods

move(dx, dy, dz)

Moves the point by the specified deltas. Automatically updates the trail if pen is on.

Parameters:
  • dx (number): Change in x coordinate
  • dy (number): Change in y coordinate
  • dz (number): Change in z coordinate (default: 0)
// Move point right by 10 and up by 5
myPoint.move(10, -5);

// Animate movement
function draw() {
    background(0, 0, 86);  // HSB: light gray
    myPoint.move(1, sin(frameCount * 0.05) * 2);
    myPoint.drawTrail();
    myPoint.draw();
}

Pen Trail Methods

setPen(on)

Enables or disables the pen trail. When disabled, clears the trail.

Parameters:
  • on (boolean): true to enable, false to disable (default: true)
myPoint.setPen(true);  // Start recording trail
myPoint.setPen(false); // Stop and clear trail
setMaxTrailLength(n)

Sets the maximum number of points in the trail. Older points are removed when limit is exceeded.

Parameters:
  • n (number): Maximum trail length
myPoint.setMaxTrailLength(100); // Keep last 100 positions
clearTrail()

Clears the trail history without changing the pen state.

myPoint.clearTrail(); // Remove all trail points

Style Methods

setStrokeColor(swColor)

Sets the stroke color using an SWColor instance.

Parameters:
  • swColor (SWColor): The new color
let newColor = new SWColor(120, 100, 100);  // HSB: green
myPoint.setStrokeColor(newColor);
setStrokeWeight(w)

Sets the stroke weight (thickness).

Parameters:
  • w (number): The new stroke weight
myPoint.setStrokeWeight(8);

Label Methods

setLabel(label)

Sets the label text (typically a single letter).

Parameters:
  • label (string): Label text
myPoint.setLabel("A");
setShowLabel(show)

Sets whether to display the label.

Parameters:
  • show (boolean): True to show label (default: true)
myPoint.setShowLabel(true);  // Show label
myPoint.setShowLabel(false); // Hide label
setLabelFontSize(size)

Sets the font size for the label text.

Parameters:
  • size (number): Font size in pixels
myPoint.setLabelFontSize(18); // Larger label
setLabelPosition(offsetX, offsetY)

Manually sets the label position using explicit x,y offsets from the point.

Parameters:
  • offsetX (number): Horizontal offset in pixels
  • offsetY (number): Vertical offset in pixels (negative is up)
myPoint.setLabelPosition(10, -15); // 10 right, 15 up
setLabelAbove(distance)

Positions label above the point.

Parameters:
  • distance (number): Distance above in pixels (default: 15)
myPoint.setLabelAbove(20);
setLabelBelow(distance)

Positions label below the point.

Parameters:
  • distance (number): Distance below in pixels (default: 15)
myPoint.setLabelBelow(20);
setLabelLeft(distance)

Positions label to the left of the point.

Parameters:
  • distance (number): Distance to the left in pixels (default: 15)
myPoint.setLabelLeft(20);
setLabelRight(distance)

Positions label to the right of the point.

Parameters:
  • distance (number): Distance to the right in pixels (default: 15)
myPoint.setLabelRight(20);
nudgeLabel(dx, dy)

Adjusts label position by small amounts for fine-tuning.

Parameters:
  • dx (number): Horizontal adjustment in pixels
  • dy (number): Vertical adjustment in pixels
myPoint.nudgeLabel(2, -3); // Fine-tune position
setLabelOffsetFromSlope(slope, distance)

Positions label perpendicular to a line with the given slope. Useful for labeling points on lines.

Parameters:
  • slope (number): Slope of the line (use Infinity for vertical, 0 for horizontal)
  • distance (number): Distance from point (default: 15)
let slope = 2; // Line with slope 2
myPoint.setLabelOffsetFromSlope(slope, 20);
labelProximityCheck(grid, margin)

Automatically adjusts label position if it goes off canvas boundaries. Keeps labels visible.

Parameters:
  • grid (SWGrid): Grid for boundary checking
  • margin (number): Minimum distance from edge in pixels (default: 5)
myPoint.labelProximityCheck(myGrid); // Auto-adjust near edges

Interaction Methods

setDraggable(draggable)

Enables or disables draggability for this point.

Parameters:
  • draggable (boolean): True to enable dragging (default: true)
myPoint.setDraggable(true);  // Enable dragging
myPoint.setDraggable(false); // Disable dragging
setPosition(x, y)

Sets the point to a new position in user coordinates. Updates trail if pen is on.

Parameters:
  • x (number): New x coordinate
  • y (number): New y coordinate
myPoint.setPosition(5, 7); // Move to (5, 7)
containsPoint(screenX, screenY, grid, tolerance)

Checks if a screen coordinate is within the point's hit area. Used for drag detection.

Parameters:
  • screenX (number): Mouse x in screen coordinates
  • screenY (number): Mouse y in screen coordinates
  • grid (SWGrid): Grid for coordinate conversion
  • tolerance (number): Hit detection radius in pixels (default: 10)
Returns: boolean - True if coordinate is within tolerance
if (myPoint.containsPoint(mouseX, mouseY, myGrid)) {
    // Mouse is over the point
}

Utility Methods

distanceTo(otherSWPt)

Returns the Euclidean distance to another SWPoint in user coordinates.

Parameters:
  • otherSWPt (SWPoint): The other point
Returns: number - The distance
let point1 = new SWPoint(0, 0);
let point2 = new SWPoint(3, 4);
let distance = point1.distanceTo(point2); // Returns 5
toString()

Returns a simple string representation of the point. Coordinates are shown with 2 decimal places.

Returns: string - Format: "Pt.A @ (x, y), colorName" if labeled, or "Pt @ (x, y), colorName" if unlabeled
let pt = new SWPoint(100.5, 200.75, undefined, 5, new SWColor(0, 100, 100, 100, "red"), "A");
console.log(pt.toString());
// Output: "Pt.A @ (100.50, 200.75), red"

let pt2 = new SWPoint(3.14159, 2.71828);
console.log(pt2.toString());
// Output: "Pt @ (3.14, 2.72), no color"
logAllInfo()

Returns a detailed string representation with all properties, useful for debugging.

Returns: string - Complete property listing
console.log(myPoint.logAllInfo());
// Output: "SWPoint(x: 100, y: 200, strokeWeight: 1, strokeColor: ..., label: A, showLabel: true, penOn: false, trailLen: 0)"

Usage Examples

Example 1: Basic Point Drawing

let myPoint;

function setup() {
    createCanvas(400, 400);
    colorMode(HSB, 360, 100, 100);
    let red = new SWColor(0, 100, 100);  // HSB: red
    myPoint = new SWPoint(200, 200, undefined, 10, red);
}

function draw() {
    background(0, 0, 86);  // HSB: light gray
    myPoint.draw();
}

Example 2: Animated Point with Trail

let myPoint;
let angle = 0;

function setup() {
    createCanvas(400, 400);
    colorMode(HSB, 360, 100, 100);
    let blue = new SWColor(216, 100, 100);  // HSB: light blue
    myPoint = new SWPoint(200, 200, undefined, 5, blue);
    myPoint.setPen(true);
    myPoint.setMaxTrailLength(200);
}

function draw() {
    background(0, 0, 86);  // HSB: light gray
    
    // Circular motion
    let radius = 100;
    let dx = cos(angle) * radius - myPoint.x + width/2;
    let dy = sin(angle) * radius - myPoint.y + height/2;
    myPoint.move(dx * 0.1, dy * 0.1);
    
    angle += 0.05;
    
    myPoint.drawTrail();
    myPoint.draw();
}

Example 3: Using with SWGrid

let myGrid;
let myPoint;

function setup() {
    createCanvas(400, 400);
    colorMode(HSB, 360, 100, 100);
    myGrid = new SWGrid(-10, 10, -10, 10);
    
    let green = new SWColor(120, 100, 78);  // HSB: green
    // Point at user coordinates (5, 3)
    myPoint = new SWPoint(5, 3, undefined, 8, green);
}

function draw() {
    background(0, 0, 86);  // HSB: light gray
    myGrid.draw();
    myPoint.drawOnGrid(myGrid);
}

Example 4: Interactive Point

let myPoint;

function setup() {
    createCanvas(400, 400);
    colorMode(HSB, 360, 100, 100);
    let purple = new SWColor(285, 100, 78);  // HSB: purple
    myPoint = new SWPoint(mouseX, mouseY, undefined, 6, purple);
    myPoint.setPen(true);
}

function draw() {
    background(0, 0, 86, 10);  // HSB: light gray with fade effect
    
    // Follow mouse
    myPoint.x = mouseX;
    myPoint.y = mouseY;
    myPoint._updateTrail(); // Update trail manually
    
    myPoint.drawTrail();
    myPoint.draw();
}

function mousePressed() {
    myPoint.clearTrail(); // Clear trail on click
}

Example 5: Labeled Points on Grid

let myGrid;
let points = [];
let labels = ["A", "B", "C", "D"];
let colors;

function setup() {
    createCanvas(400, 400);
    colorMode(HSB, 360, 100, 100);
    
    myGrid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
    
    // Create labeled points
    colors = [
        new SWColor(0, 80, 90),    // Red
        new SWColor(120, 80, 90),  // Green
        new SWColor(240, 80, 90),  // Blue
        new SWColor(60, 80, 90)    // Yellow
    ];
    
    let positions = [{x: -5, y: 5}, {x: 5, y: 5}, {x: -5, y: -5}, {x: 5, y: -5}];
    
    for (let i = 0; i < 4; i++) {
        let pt = new SWPoint(positions[i].x, positions[i].y, undefined, 10, colors[i], labels[i]);
        pt.showLabel = true;
        pt.setLabelFontSize(16);
        pt.setLabelAbove(15);
        pt.labelProximityCheck(myGrid); // Keep labels on canvas
        points.push(pt);
    }
}

function draw() {
    background(0, 0, 86);  // HSB: light gray
    myGrid.draw();
    
    for (let pt of points) {
        pt.drawOnGrid(myGrid);
    }
}

Example 6: Draggable Points

let myGrid;
let points = [];
let draggedPoint = null;

function setup() {
    createCanvas(400, 400);
    colorMode(HSB, 360, 100, 100);
    
    myGrid = new SWGrid({UL: new SWPoint(-10, 10), LR: new SWPoint(10, -10)});
    
    // Create draggable points
    for (let i = 0; i < 3; i++) {
        let x = -5 + i * 5;
        let y = 0;
        let color = new SWColor(i * 120, 80, 90);
        let pt = new SWPoint(x, y, undefined, 10, color, String.fromCharCode(65 + i));
        pt.showLabel = true;
        pt.setDraggable(true);
        points.push(pt);
    }
}

function draw() {
    background(0, 0, 86);  // HSB: light gray
    myGrid.draw();
    
    for (let pt of points) {
        pt.drawOnGrid(myGrid);
    }
}

function mousePressed() {
    // Check if clicking on a point
    for (let pt of points) {
        if (pt.containsPoint(mouseX, mouseY, myGrid)) {
            draggedPoint = pt;
            break;
        }
    }
}

function mouseDragged() {
    if (draggedPoint) {
        let userCoords = myGrid.screenToUser(mouseX, mouseY);
        draggedPoint.setPosition(userCoords.x, userCoords.y);
        draggedPoint.labelProximityCheck(myGrid);
        redraw();
    }
}

function mouseReleased() {
    draggedPoint = null;
}

Extending SWPoint

The SWPoint class is designed to be extended for more specialized behavior. Here are some examples:

Example: Animated Point Class

class AnimatedPoint extends SWPoint {
    constructor(x, y, strokeWeight, strokeColor) {
        super(x, y, undefined, strokeWeight, strokeColor);
        this.velocity = {x: random(-2, 2), y: random(-2, 2)};
        this.acceleration = {x: 0, y: 0};
    }
    
    update() {
        this.velocity.x += this.acceleration.x;
        this.velocity.y += this.acceleration.y;
        this.move(this.velocity.x, this.velocity.y);
        
        // Bounce off edges
        if (this.x < 0 || this.x > width) this.velocity.x *= -1;
        if (this.y < 0 || this.y > height) this.velocity.y *= -1;
    }
    
    applyForce(fx, fy) {
        this.acceleration.x = fx;
        this.acceleration.y = fy;
    }
}

Example: Particle System Using SWPoint

class Particle extends SWPoint {
    constructor(x, y) {
        let randomColor = new SWColor(random(360), random(100), random(100));  // HSB: random color
        super(x, y, undefined, random(2, 8), randomColor);
        this.setPen(true);
        this.setMaxTrailLength(50);
        this.lifespan = 255;
    }
    
    update() {
        this.move(random(-2, 2), random(-2, 2));
        this.lifespan -= 2;
    }
    
    isDead() {
        return this.lifespan <= 0;
    }
    
    display() {
        this.strokeColor.setAlpha(this.lifespan);
        this.drawTrail();
        this.draw();
    }
}

Integration with SketchWave Ecosystem

Dependencies

  • p5.js: Required for all drawing operations
  • SWColor: Required for color management
  • SWGrid: Optional, for grid-based coordinate systems

Loading Order

Ensure scripts are loaded in this order in your HTML:

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

<!-- SketchWave classes -->
<script src="scripts/swColor.js"></script>
<script src="scripts/swPoint.js"></script>
<script src="scripts/swGrid.js"></script>
<!-- Your sketch script -->
<script src="scripts/mySketch.js"></script>

Best Practices

  • Always use SWColor instances for colors rather than raw p5.js colors
  • Use move() method rather than directly modifying x/y if pen trail is active
  • Limit maxTrailLength for performance in animations with many points
  • Clear trails periodically in long-running animations to prevent memory buildup
  • When working with grids, use drawOnGrid() for proper coordinate transformation

Complete Source Code

View the complete, documented source code for the SWPoint class:

Show/Hide Source Code
/*
File: swPoint.js
Date: 2026-02-17
Author: klp
Workspace: SWLineDemo2026-02-12-Stg1
Purpose: SWLine class for SketchWaveJS
Comment(s):

An important update: If a 'null' color is passed for the strokeColor, the point will use a default color (which can be set when drawing) instead of no color. This allows for more flexible styling where the point can inherit a color from its context if not explicitly set.

TODO:

=== Notes ===:
- This class assumes p5.js and SWColor are loaded in the environment.
- SWPoint class represents points or vectors in 2D or 3D space with styling options.
- Includes properties for position (x, y, z), stroke weight, and stroke color.
- Supports pen trail functionality to track and draw the path of the point.
- Designed to be compatible with p5.js and the SWColor class.
- Methods for drawing the point and its trail in screen coordinates.
- Additional methods for movement and vector math can be added as needed.
- For heavy math, consider extending or wrapping p5.Vector.
- strokeColor should be an SWColor instance for consistency.
- Optionally, you can add fillColor or other style properties.
- Pen trail logic includes enabling/disabling the pen, storing trail points, and limiting trail length.
- You can extend this class for animated or interactive points.
*/

console.log("[swPoint.js] SWPoint class loaded.");

class SWPoint {
    /**
     * @param {number} x - X coordinate
     * @param {number} y - Y coordinate
     * @param {number} [z] - Z coordinate (optional, for 3D)
     * @param {number} [strokeWeight=1] - Stroke weight
     * @param {SWColor} [strokeColor] - Stroke color (SWColor instance)
     * @param {string} [label] - Label (single letter, optional)
     */
    
    constructor(x, y, z = undefined, strokeWeight = 1, strokeColor = undefined, label = undefined) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.strokeWeight = strokeWeight;
        this.strokeColor = strokeColor; // Always store SWColor instance
        this.label = label; // Label (typically a single letter)
        this.showLabel = false; // Whether to display the label
        this.labelOffset = { x: 10, y: -10 }; // Offset for label position
        this.labelFontSize = 12; // Font size for label text
        this.isDraggable = false; // Whether the point can be dragged

        // Pen trail logic
        this.penOn = false;
        this.trail = [];
        this.maxTrailLength = 500; // default, can be changed
    }//end constructor

    /**
     * Copy constructor: returns a new SWPoint with the same properties as the given SWPoint instance
     * Creates a deep copy of all properties including trail and labelOffset
     * @param {SWPoint} other - The SWPoint instance to copy
     * @returns {SWPoint} A new SWPoint with copied properties
     */
    static copy(other) {
        if (!(other instanceof SWPoint)) {
            throw new Error('Argument to SWPoint.copy must be an SWPoint instance');
        }
        // Create new point with basic properties
        const newPoint = new SWPoint(
            other.x, 
            other.y, 
            other.z, 
            other.strokeWeight, 
            other.strokeColor ? SWColor.copy(other.strokeColor) : undefined,
            other.label
        );
        
        // Copy additional properties
        newPoint.showLabel = other.showLabel;
        newPoint.labelOffset = { x: other.labelOffset.x, y: other.labelOffset.y };
        newPoint.labelFontSize = other.labelFontSize;
        newPoint.isDraggable = other.isDraggable;
        newPoint.penOn = other.penOn;
        newPoint.maxTrailLength = other.maxTrailLength;
        
        // Deep copy the trail array
        newPoint.trail = other.trail.map(pt => ({ x: pt.x, y: pt.y, z: pt.z }));
        
        return newPoint;
    }//end copy

    /**
     * Draws the point using p5.js in screen coordinates
     * @param {SWColor} [defaultColor] - Color to use if strokeColor is null
     */
    draw(defaultColor = undefined) {
        // Use strokeColor if set, otherwise use defaultColor, otherwise swBlack
        let colorToUse = this.strokeColor;
        if (!colorToUse) {
            colorToUse = defaultColor || swBlack;
        }
        if (colorToUse && colorToUse.col) {
            stroke(colorToUse.col);
        }
        strokeWeight(this.strokeWeight);
        if (this.z !== undefined) {
            point(this.x, this.y, this.z);
        } else {
            point(this.x, this.y);
        }
        noStroke();
        strokeWeight(1);
        
        // Draw label if showLabel is true
        if (this.showLabel && this.label) {
            fill(colorToUse && colorToUse.col ? colorToUse.col : 0);
            textAlign(CENTER, CENTER);
            textSize(this.labelFontSize);
            text(this.label, this.x + this.labelOffset.x, this.y + this.labelOffset.y);
            noFill();
        }
    }//end draw

    /**
     * Draws the point using user (grid) coordinates mapped by the given SWGrid
     * @param {SWGrid} grid
     * @param {SWColor} [defaultColor] - Color to use if strokeColor is null
     */
    drawOnGrid(grid, defaultColor = undefined) {
        const {x: px, y: py} = grid.userToScreen(this.x, this.y);
        // Use strokeColor if set, otherwise use defaultColor, otherwise swBlack
        let colorToUse = this.strokeColor;
        if (!colorToUse) {
            colorToUse = defaultColor || swBlack;
        }
        if (colorToUse && colorToUse.col) {
            stroke(colorToUse.col);
        }
        strokeWeight(this.strokeWeight);
        if (this.z !== undefined) {
            point(px, py, this.z);
        } else {
            point(px, py);
        }
        noStroke();
        strokeWeight(1);
        
        // Draw label if showLabel is true
        if (this.showLabel && this.label) {
            fill(colorToUse && colorToUse.col ? colorToUse.col : 0);
            textAlign(CENTER, CENTER);
            textSize(this.labelFontSize);
            text(this.label, px + this.labelOffset.x, py + this.labelOffset.y);
            noFill();
        }
    }//end drawOnGrid

    /**
     * Pen trail enhancements:
     * - penOn: whether the pen is active
     * - trail: array of previous positions [{x, y, z}]
     * - maxTrailLength: maximum number of points in the trail
     */
    /**
     * Draws the pen trail in screen coordinates
     */
    drawTrail() {
        if (!this.penOn || this.trail.length < 2) return;
        noFill();
        stroke(this.strokeColor && this.strokeColor.col ? this.strokeColor.col : 0);
        strokeWeight(Math.max(1, this.strokeWeight / 2));
        beginShape();
        for (const pt of this.trail) {
            if (this.z !== undefined && pt.z !== undefined) {
                vertex(pt.x, pt.y, pt.z);
            } else {
                vertex(pt.x, pt.y);
            }
        }
        endShape();
        noStroke();
        strokeWeight(1);
    }//end drawTrail

    /**
     * Draws the pen trail in user (grid) coordinates
     * @param {SWGrid} grid
     */
    drawTrailOnGrid(grid) {
        if (!this.penOn || this.trail.length < 2) return;
        noFill();
        stroke(this.strokeColor && this.strokeColor.col ? this.strokeColor.col : 0);
        strokeWeight(Math.max(1, this.strokeWeight / 2));
        beginShape();
        for (const pt of this.trail) {
            const {x, y} = grid.userToScreen(pt.x, pt.y);
            vertex(x, y);
        }
        endShape();
        noStroke();
        strokeWeight(1);
    }//end drawTrailOnGrid

    /**
     * Moves the point by dx, dy, dz
     * If pen is on, records the new position in the trail
     */
    move(dx, dy, dz = 0) {
        this.x += dx;
        this.y += dy;
        if (this.z !== undefined) this.z += dz;
        this._updateTrail();
    }//end move

    /**
     * Call this after manually setting x/y/z to update the trail if pen is on
     * Private method: do not call directly (as indicated by underscore prefix)
     */
    _updateTrail() {
        if (this.penOn) {
            // Only add if position changed
            if (
                this.trail.length === 0 ||
                this.trail[this.trail.length - 1].x !== this.x ||
                this.trail[this.trail.length - 1].y !== this.y ||
                this.trail[this.trail.length - 1].z !== this.z
            ) {
                this.trail.push({ x: this.x, y: this.y, z: this.z });
                if (this.trail.length > this.maxTrailLength) {
                    this.trail.shift();
                }
            }
        }
    }//end _updateTrail

    /**
     * Enable or disable the pen trail
     * @param {boolean} on
     */
    setPen(on = true) {
        this.penOn = on;
        if (!on) this.trail = [];
    }//end setPen

    /**
     * Set the maximum trail length
     * @param {number} n
     */
    setMaxTrailLength(n) {
        this.maxTrailLength = n;
        if (this.trail.length > n) {
            this.trail = this.trail.slice(-n);
        }
    }//end setMaxTrailLength

    /**
     * Clears the pen trail history, regardless of pen state
     */
    clearTrail() {
        this.trail = [];
    }//end clearTrail

    /**
     * Sets the stroke color
     * @param {SWColor} swColor
     */
    setStrokeColor(swColor) {
        this.strokeColor = swColor;
    }//end setStrokeColor

    /**
     * Sets the stroke weight
     * @param {number} w
     */
    setStrokeWeight(w) {
        this.strokeWeight = w;
    }//end setStrokeWeight

    /**
     * Sets whether to show the label
     * @param {boolean} show
     */
    setShowLabel(show = true) {
        this.showLabel = show;
    }//end setShowLabel

    /**
     * Sets the label (typically a single letter)
     * @param {string} label
     */
    setLabel(label) {
        this.label = label;
    }//end setLabel

    /**
     * Sets the label font size
     * @param {number} size - Font size in pixels
     */
    setLabelFontSize(size) {
        this.labelFontSize = size;
    }//end setLabelFontSize

    /**
     * Sets the label offset based on a line slope to position the label perpendicular to the line
     * @param {number} slope - The slope of the line (use Infinity for vertical lines, 0 for horizontal)
     * @param {number} [distance=15] - Distance to offset the label from the point
     */
    setLabelOffsetFromSlope(slope, distance = 15) {
        if (slope === Infinity || slope === -Infinity) {
            this.labelOffset = { x: distance, y: 0 };
        } else if (slope === 0) {
            this.labelOffset = { x: 0, y: -distance };
        } else {
            const perp_x = -slope;
            const perp_y = 1;
            const length = Math.sqrt(perp_x * perp_x + perp_y * perp_y);
            const unit_x = perp_x / length;
            const unit_y = perp_y / length;
            this.labelOffset = { x: unit_x * distance, y: unit_y * distance };
        }
    }//end setLabelOffsetFromSlope

    /**
     * Manually sets the label position using explicit x,y offsets
     * @param {number} offsetX - Horizontal offset from point (pixels)
     * @param {number} offsetY - Vertical offset from point (pixels, negative is up)
     */
    setLabelPosition(offsetX, offsetY) {
        this.labelOffset = { x: offsetX, y: offsetY };
    }//end setLabelPosition

    /**
     * Nudges the label by a small amount in either direction
     * @param {number} dx - Amount to move horizontally (pixels)
     * @param {number} dy - Amount to move vertically (pixels)
     */
    nudgeLabel(dx, dy) {
        this.labelOffset.x += dx;
        this.labelOffset.y += dy;
    }//end nudgeLabel

    /**
     * Sets label to common position above the point
     * @param {number} [distance=15] - Distance above the point
     */
    setLabelAbove(distance = 15) {
        this.labelOffset = { x: 0, y: -distance };
    }//end setLabelAbove

    /**
     * Sets label to common position below the point
     * @param {number} [distance=15] - Distance below the point
     */
    setLabelBelow(distance = 15) {
        this.labelOffset = { x: 0, y: distance };
    }//end setLabelBelow

    /**
     * Sets label to common position to the left of the point
     * @param {number} [distance=15] - Distance to the left
     */
    setLabelLeft(distance = 15) {
        this.labelOffset = { x: -distance, y: 0 };
    }//end setLabelLeft

    /**
     * Sets label to common position to the right of the point
     * @param {number} [distance=15] - Distance to the right
     */
    setLabelRight(distance = 15) {
        this.labelOffset = { x: distance, y: 0 };
    }//end setLabelRight

    /**
     * Automatically adjusts label position if it goes off the canvas boundaries
     * @param {SWGrid} grid - The grid for boundary checking
     * @param {number} [margin=5] - Minimum distance from edge in screen pixels
     */
    labelProximityCheck(grid, margin = 5) {
        if (!this.label || !this.showLabel) return;
        const {x: px, y: py} = grid.userToScreen(this.x, this.y);
        const labelX = px + this.labelOffset.x;
        const labelY = py + this.labelOffset.y;
        const canvasWidth = grid.screenWidth || width;
        const canvasHeight = grid.screenHeight || height;
        const textWidth = 15;
        const textHeight = 12;
        let adjustedOffsetX = this.labelOffset.x;
        let adjustedOffsetY = this.labelOffset.y;
        if (labelX + textWidth/2 > canvasWidth - margin) adjustedOffsetX = -15;
        if (labelX - textWidth/2 < margin) adjustedOffsetX = 15;
        if (labelY - textHeight/2 < margin) adjustedOffsetY = 15;
        if (labelY + textHeight/2 > canvasHeight - margin) adjustedOffsetY = -15;
        this.labelOffset = { x: adjustedOffsetX, y: adjustedOffsetY };
    }//end labelProximityCheck

    /**
     * Checks if a screen coordinate is within the point's hit area
     * @param {number} screenX - Mouse x in screen coordinates
     * @param {number} screenY - Mouse y in screen coordinates
     * @param {SWGrid} grid - The grid for coordinate conversion
     * @param {number} [tolerance=10] - Hit detection radius in screen pixels
     * @returns {boolean} True if the coordinate is within tolerance of the point
     */
    containsPoint(screenX, screenY, grid, tolerance = 10) {
        const {x: px, y: py} = grid.userToScreen(this.x, this.y);
        return dist(screenX, screenY, px, py) <= tolerance;
    }//end containsPoint

    /**
     * Sets the point to a new position in user coordinates
     * @param {number} x - New x coordinate (user coordinates)
     * @param {number} y - New y coordinate (user coordinates)
     */
    setPosition(x, y) {
        this.x = x;
        this.y = y;
        this._updateTrail();
    }//end setPosition

    /**
     * Enables or disables draggability for this point
     * @param {boolean} draggable
     */
    setDraggable(draggable = true) {
        this.isDraggable = draggable;
    }//end setDraggable

    /**
     * Returns a string representation
     */
    logAllInfo() {
        return `SWPoint(x: ${this.x}, y: ${this.y}` +
            (this.z !== undefined ? `, z: ${this.z}` : '') +
            `, strokeWeight: ${this.strokeWeight}, strokeColor: ${this.strokeColor ? this.strokeColor.toString() : 'none'}, label: ${this.label || 'none'}, showLabel: ${this.showLabel}, penOn: ${this.penOn}, trailLen: ${this.trail.length})`;
    }//end logAllInfo

    /**
     * Returns a simple string representation of the point
     * Format: "Pt.A @ (x, y), colorName" if labeled, or "Pt @ (x, y), colorName" if unlabeled
     * @returns {string}
     */
    toString() {
        const labelPart = this.label ? `.${this.label}` : '';
        const x = this.x.toFixed(2);
        const y = this.y.toFixed(2);
        const coords = this.z !== undefined ? `(${x}, ${y}, ${this.z.toFixed(2)})` : `(${x}, ${y})`;
        const colorName = this.strokeColor && this.strokeColor.name ? this.strokeColor.name : 'no color';
        return `Pt${labelPart} @ ${coords}, ${colorName}`;
    }//end toString

    /**
     * Returns the Euclidean distance to another SWPoint in user coordinates
     * @param {SWPoint} otherSWPt
     * @returns {number}
     */
    distanceTo(otherSWPt) {
        const dx = this.x - otherSWPt.x;
        const dy = this.y - otherSWPt.y;
        if (this.z !== undefined && otherSWPt.z !== undefined) {
            const dz = this.z - otherSWPt.z;
            return Math.sqrt(dx * dx + dy * dy + dz * dz);
        } else {
            return Math.sqrt(dx * dx + dy * dy);
        }
    }//end distanceTo
}//end SWPoint class
Developer Notes: This reference guide is part of the SketchWave ecosystem. For questions or contributions, please refer to the SketchWave documentation or contact the development team.