👔Canvas+ext

browsercanvas ⟩ +ext

// (Canvas+ext v1.1)
// 2023.01.22 - 21:29 (/) .drawPath() -> 1. opts destructuring, 2. line dash
//                    (/) .moveToPoint() -> remove `newPath` param
//                    (/) fix .drawArcTo(), .drawPolylineTo()
//                    (+) .drawPolyline(), .drawQuadraticCurve(), .drawCubicBezierCurve()

// 2023.01.21 - 09:02 (/) .drawPolygon() - fix start angle
// 2023.01.21 - 08:14 (+) .drawPolygon()
// 2023.01.20 - 23:36 (•) first version
// -----------------------------------------------------------------

const {PI} = Math;

// ⭐ import
import { deg } from './Number+ext.js';        // 👔 Number+ext
import { vec, polar } from './Vector.js';     // 👔 Vector
import { range } from './Iterable+ext.js';    // 👔 Iterable+ext
// -----------------------------------------------------------------


// ⭐ HTMLCanvasElement + extensions
// -----------------------------------------------------------------
// 🔹 .draw2D()
//
Object.defineProperties(HTMLCanvasElement.prototype, {
    // 🔹 .draw2D()
    draw2D: {
        value: function(draw){
            draw(this.getContext("2d"));
        },
    },
});

// ⭐ CanvasRenderingContext2D + extensions
// -----------------------------------------------------------------
// ⭐ .drawPath()         // general method
// 🔹 .drawCircle()
// 🔹 .drawWedge()
// 🔹 .drawEllipse()
// -----------------------------------------------------------------
// 🔹 .moveToPoint()
// -----------------------------------------------------------------
// 🔹 .drawArc()
// 🔹 .drawArcTo()        // for rounded corners
// 🔹 .drawPolyline()
// 🔹 .drawPolylineTo()
// 🔹 .drawQuadraticCurve()
// 🔹 .drawQuadraticCurveTo()
// 🔹 .drawCubicBezierCurve()
// 🔹 .drawCubicBezierCurveTo()
// -----------------------------------------------------------------
// 🔹 .drawRect()
// 🔹 .drawPolygon()
//
Object.defineProperties(CanvasRenderingContext2D.prototype, {

    // 🔹 .moveToPoint()
    moveToPoint: {
        value: function(p, {
            // newPath = false,        // begin new subpath ? (default = false)
        }={}) {
            // if (newPath) this.beginPath();
            this.moveTo(...p.coords);        // 👔 Vector
        }
    },

    // ⭐ .drawPath()
    drawPath: {
        value: function(draw, {
            // path
            newPath = true,        // begin new subpath ?
            closePath = true,      // close subpath ?
            // fill/stroke
            fill = true,
            stroke = true,
            fillStyle,
            strokeStyle,
            // line dash
            dash,
        }={}){
            
            // new subpath ?
            if (newPath) this.beginPath();    // start new path if necessary
            
            // draw path
            draw();

            // close path ?
            if (closePath) this.closePath();
            
            // fill ?
            if (fill) {
                if (fillStyle) this.fillStyle = fillStyle;
                this.fill();
            };
            
            // stroke ?
            if (stroke) {
                if (strokeStyle) this.strokeStyle = strokeStyle;
                if (dash) this.setLineDash(dash);
                this.stroke();
            };
            
        },
    },

    // --------------------------
    //     arc/circle related
    // --------------------------
    
    // 🔹 .drawArc()
    drawArc: {
        value: function(center, radius, {
            // path: arc related
            start = 0,            // start angle
            end = 2 * PI,         // end angle
            clockwise = true,
            // path: general
            ...opts
        }={}){
            this.drawPath(() => {
                this.arc(...center.coords, radius, start, end, !clockwise);
            }, { ...opts });
        },
    },

    // 🔹 .drawArcTo()
    // - for rounded corners
    drawArcTo: {
        value: function(control, target, radius, {
            // arc specific
            start,                 // start point
            // path: general
            ...opts
        }={}){
            // arc specific
            //      ╭────────── default ──────────╮
            opts = {newPath: false, closePath:false, ...opts};
            // if (start) { opts.newPath = true }    // new subpath if has start point
            // draw arc to ...
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start);
                // connect arc
                this.arcTo(...control.coords, ...target.coords, radius);
            }, { ...opts });
        },
    },

    // 🔹 .drawCircle()
    drawCircle: {
        value: function(center, radius, {
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.arc(...center.coords, radius, 0, 2*PI);
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawEllipse()
    drawEllipse: {
        value: function(center, radiusX, radiusY, {
            // path: ellipse related
            start = 0,
            end = 2 * PI,
            rotation = 0,
            clockwise = true,
            // path: general
            newPath = true,
            closePath = true,
            // fill/stroke
            fill = true,
            stroke = true,
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.ellipse(
                    ...center.coords, radiusX, radiusY, 
                    rotation, start, end, !clockwise
                );
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawWedge()
    drawWedge: {
        value: function(center, radius, {
            // path: arc related
            start = 0,            // start angle
            end = 90 * deg,       // end angle    // 👔 Number+ext
            clockwise = true,
            // path: general
            ...opts
        }={}){
            // wedge specific parameters
            opts = {
                ...opts, 
                newPath: true, 
                closePath: true,    // add line back to center
            };
            // draw wedge
            this.drawPath(() => {
                this.moveTo(...center.coords);           // center
                // ⭐ arc() adds a line from `center` to arc start.
                this.arc(...center.coords, radius, start, end, !clockwise);
            }, { ...opts });
        },
    },

    // --------------------------
    //     bezier curve
    // --------------------------

    // 🔹 .drawQuadraticCurveTo()
    drawQuadraticCurveTo: {
        value: function(control, target, {
            // bezier curve specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path?
            closePath = false,
            // fill/stroke
            fill = false,          // fill?
            stroke = false,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect bezier curve
                this.quadraticCurveTo(...control.coords, ...target.coords);
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawQuadraticCurve()
    drawQuadraticCurve: {
        value: function(p1, ctrl, p2, {
            ...opts
        }={}){
            // curve specific
            //      ╭───────────────── default ────────────────╮
            opts = {newPath: true, fill: false, closePath: false, ...opts};
            this.drawPath(() => {
                // move to start point
                this.moveToPoint(p1);
                // connect bezier curve
                this.quadraticCurveTo(...ctrl.coords, ...p2.coords);
            }, { ...opts });
        },
    },

    // 🔹 .drawCubicBezierCurveTo()
    drawCubicBezierCurveTo: {
        value: function(control1, control2, target, {
            // bezier curve specific
            start,                 // start point
            // path: general
            newPath = false,       // start new path ?    (default: false)
            closePath = false,     // close subpath ?     (default: false)
            // fill/stroke
            fill = false,          // fill ?              (default: false)
            stroke = false,        // stroke ?            (default: false)
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start, {newPath});
                // connect bezier curve
                this.bezierCurveTo(
                    ...control1.coords, ...control2.coords, 
                    ...target.coords,
                );
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawCubicBezierCurve()
    drawCubicBezierCurve: {
        value: function(p1, c1, c2, p2, {
            ...opts
        }={}){
            // curve specific
            //      ╭───────────────── default ────────────────╮
            opts = {newPath: true, fill: false, closePath: false, ...opts};
            this.drawPath(() => {
                // move to start point 
                this.moveToPoint(p1);
                // connect bezier curve
                this.bezierCurveTo(
                    ...c1.coords, ...c2.coords, 
                    ...p2.coords,
                );
            }, { ...opts });
        },
    },

    // --------------------------
    //     polygon
    // --------------------------

    // 🔹 .drawRect()
    drawRect: {
        value: function(rect, {...opts}={}){
            this.drawPath(() => {
                this.rect(...rect.coords);        // 👔 Rect
            }, { ...opts });
        },
    },

    // 🔹 .drawPolygon()
    drawPolygon: {
        value: function(n, {
            // polygon specific
            center = vec(100, 100),   // 👔 Vector
            radius = 50,
            rotate = 0,               // rotation
            clockwise = true,         // path direction
            // path: general
            ...opts
        }={}){
            this.drawPath(() => {
                
                // dθ = ± 2π / n
                const dt = 2 * Math.PI / n * (clockwise ? 1 : -1);
                // start angle
                const a0 = -90 * deg + rotate + (n.isEven ? dt/2 : 0);

                // vertices
                const points = range(0, n-1)
                    .map(i => center.plus(polar(radius, a0 + dt * i)))
                    .array;
                
                // draw polyline
                this.drawPolylineTo(points.slice(1), {
                    start: points[0],
                });
                
            }, { ...opts });
        },
    },

    // 🔹 .drawPolyline()
    drawPolyline: {
        value: function(points, {
            ...opts
        }={}){
            // polyline specific
            //      ╭───────── default ─────────╮
            opts = {fill: false, closePath: false, ...opts};
            this.drawPath(() => {
                this.drawPolylineTo(points.slice(1), {
                    start: points[0],
                });
            }, { ...opts });
            
        },
    },
    
    // 🔹 .drawPolylineTo()
    drawPolylineTo: {
        value: function(points, {
            // polyline specific
            start,                 // start point
            newPath = false,       // start new path?
            closePath = false,
            // path: general
            ...opts
        }={}){
            // polyline specific
            if (start) newPath = true;    // new subpath if has start point
            //      ╭───────── default ─────────╮
            opts = {fill: false, closePath: false, ...opts, newPath};
            // draw polyline to ...
            this.drawPath(() => {
                // move to start point if necessary
                if (start) this.moveToPoint(start);
                // connect arc
                points.forEach(p => {
                    this.lineTo(...p.coords);
                });
            }, { ...opts });
        },
    },
    
});

// ⭐ export
// -----------------------------------------------------------------
export {};        // ES module export

History

0: (•) first version
1: (+) .drawPolygon()

Last updated