> For the complete documentation index, see [llms.txt](https://lochiwei.gitbook.io/web/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lochiwei.gitbook.io/web/browser/canvas/+ext.md).

# Canvas+ext

[browser](/web/browser.md) ⟩ [canvas](/web/browser/canvas.md) ⟩ +ext

{% hint style="success" %}
extend [CanvasRenderingContext2D](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D) and [HTMLCanvasElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement).

<img src="/files/PBY9gpcuS4DntZni4Q2T" alt="" data-size="original">

:point\_right: [custom](/web/appendix/custom.md)
{% endhint %}

{% tabs %}
{% tab title="💾 程式" %}

* replit ⟩ [Canvas+ext (v1.1)](https://replit.com/@pegasusroe/Canvasext-v11#js/ext/Canvas+ext.js)  -> require [Rect](/web/appendix/custom/class/rect.md), [Vector](/web/appendix/custom/class/vector.md), [Number+ext](/web/js/val/prim/num/number+ext.md), [Iterable+ext](/web/js/iteration/iterable+ext.md)

```javascript
// (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
```

{% endtab %}

{% tab title="💈範例" %}

* [regular polygons](/web/browser/canvas/examples/polygon.md)

* (this example)

```javascript
const { log } = console
const { sqrt } = Math;

// ⭐️ import
import { $ } from './js/ext/Node_ext.js';        // 👔 Node + ext
import { deg } from './js/ext/Number+ext.js';    // 👔 Number+ext
import { size } from './js/ext/Size.js';         // 👔 Size
import { rect } from './js/ext/Rect.js';         // 👔 Rect
import { vec, polar, linearCombination as lc } from './js/ext/Vector.js'; // 👔 Vector
import {  } from './js/ext/Element+boxes.js';    // 👔 Element + boxes
import {  } from './js/ext/Canvas+ext.js';       // 👔 Canvas + ext
import {  } from './js/ext/MouseEvent+ext.js';   // 👔 MouseEvent + ext
import { range } from './js/ext/Iterable+ext.js';   // 👔 Iterable + ext
// --------------------------------------------------------------------

// elements
const canvas = $('#canvas');

// canvas
const {width: W, height: H} = canvas.paddingBox;
const ROWS = 3, COLS = 3;

// layout
const dx = 20, dy = 20;             // padding
const w = 120, h = 120;    // box size

const STEPX = w + dx;                // translation
const STEPY = h + dy;                // translation

// expected canvas size
const SIZE = size(COLS*STEPX + dx, ROWS*STEPY + dy);  
log(`${ROWS} rows x ${COLS} cols: needs canvas size ${SIZE}`);

const u = vec(STEPX, 0);    // vectors for translation
const v = vec(0, STEPY);    // vectors for translation

const INSET = 10;

// styles
const HUE = 30;

// first rect
const p0 = vec(dx, dy);    // origin of first rect
const diag = vec(w, h);    // diagonal vector
const rect0 = rect(p0, diag);

const p1 = p0.plus(STEPX, 0);

// rects
const rects = range(0,2).map(i => 
    range(0,2)
        .map(j => rect0.translate(lc([j,i], [u,v])))  // 👔 Rect (v1.1)
        .array
).array;

// draw rects
canvas.draw2D(ctx => {

    // ---------
    //   inset
    // ---------
    
    // positive inset
    range(0, 4).forEach(i => {                // 👔 Iterable + ext
        ctx.drawRect(rects[0][0].inset(INSET * i), {
            fillStyle: `hsl(${HUE * i ** 1.3} 90% 50%)`
        });
    })

    ctx.drawRect(rects[0][1], {dash: [5, 5]});

    // ⭐️ negative inset
    ctx.drawRect(rects[0][1].inset(-10), {fillStyle: `#eee6`});

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

    ctx.drawPolygon(4, {
        center: rects[0][2].center, 
        radius: w / sqrt(2),
    });
});

// ⭐ draw paths
canvas.draw2D(ctx => {            // 👔 Node+ext, 👔 Canvas+ext

    const c21 = rect0.translate(v).center;           // center (👔 Vector)
    const R = h/2 - 10;
    const r = 35;
    const d = 2*R + dx;                // traslation offset
    // const u = vec(d, 0);               // translation vector

    ctx.lineWidth = 4;                 // stroke width

    // ⭐ 1. polygon
    // --------------------------------------
    ctx.drawPolygon(6, {
        center: rects[1][0].center,
        radius: R+10, 
        fillStyle: 'yellow',
        dash: [],
    });

    // ⭐ circle
    // --------------------------------------
    ctx.drawCircle(rects[1][0].center, R-10, { fillStyle: 'red' });
    
    // ⭐ 2. ellipse 
    // --------------------------------------
    ctx.drawEllipse(rects[1][1].center, R, r, {
        rotation: 30*deg, start: 90*deg,           // 👔 Number+ext
        fillStyle: "orange",
    });

    // ⭐ 3. wedge 1 (bigger)
    // --------------------------------------
    ctx.drawWedge(rects[1][2].center, R, {
        end: 300*deg, fillStyle: 'yellow'
    });

    // ⭐ wedge 2 (smaller)
    const c23_1 = rects[1][2].center.plus(polar(dx, -29 * deg));
    ctx.drawWedge(c23_1, R, {start: -60*deg, end: 0, fillStyle: 'orange'});

    // ⭐ 4. partial rounded sqaure
    // --------------------------------------
    const rect31 = rects[2][0].inset(10);
    const p0 = rect31.top;                  // top middle
    const [p3, p2, p1, p4] = rect31.points; // BL, BR, TR, TL
    
    // begin in top middle.
    ctx.drawArcTo(p1, p2, 20, {start: p0, newPath: true});
    ctx.drawArcTo(p2, p3, 20);
    
    ctx.drawArcTo(p3, p4, 20, {
        closePath: true, 
        stroke: true, fill: true, 
        fillStyle: 'hsl(120 90% 50% / 70%)',    // green
    });

    // ⭐ 5. quadratic Bezier curve (1 control point)
    // --------------------------------------
    const p21 = p3.plus(u);
    const p22 = p21.plus(lc([0.3, -0.7],[u,v])); // control point
    const p23 = p21.plus(w, 0);
    const offset = vec(5,5);        // control point size, 👔 Vector

    ctx.drawQuadraticCurve(p21, p22, p23, {
        fill: true, 
        fillStyle: 'hsl(225 90% 50% / 70%)',    // blue
    }); 
    
    // ⭐ 6. cubic Bezier curve (2 control points)
    // --------------------------------------
    const p31 = p21.plus(d, -R);
    const p34 = p31.plus(2*R, 0);
    const p32 = p31.plus(30, -R);    // control point
    const p33 = p34.plus(-30, R);    // control point

    ctx.drawCubicBezierCurve(p31, p32, p33, p34, {
        fill: true, 
        fillStyle: 'hsl(270 90% 50% / 70%)',    // tranparent green
    });
    
    // ------------
    //   polyline
    // ------------

    const q1 = rects[0][2].bottomLeft.plus(10, -10), 
        q2 = rects[0][2].top.plus(0, 10), 
        q3 = rects[0][2].bottomRight.plus(-10, -10);
    
    ctx.lineWidth = 1;
    ctx.setLineDash([3, 6]);         // ⭐ dash line
    
    ctx.drawPolyline([p21, p22, p23]);   
    ctx.drawPolyline([q1, q2, q3]);
    ctx.drawPolyline([p31, p32, p33, p34]);

    // ⭐ return to solid lines
    ctx.setLineDash([]);        
    
    // draw control points
    [p21, p22, p23, p31, p32, p33, p34].forEach(p => {
        ctx.drawRect(rect(p, offset, { center: true }), { 
            fillStyle: 'tomato',
        });   
    });
    
});
```

{% endtab %}

{% tab title="🗺️ 圖表" %}
{% embed url="<https://replit.com/@pegasusroe/Canvasext-v11#index.js>" %}
{% endtab %}

{% tab title="🔴 主題" %}

* [drawOnCanvas2D()](/web/browser/canvas/+ext/drawoncanvas2d.md)
* [ctx.polyline()](/web/browser/canvas/+ext/ctx.polyline.md)
* [ctx.point()](/web/browser/canvas/+ext/ctx.point.md)
* [ctx.gradient()](/web/browser/canvas/state/styles/gradient/ctx.gradient.md)
* [regular polygons](/web/browser/canvas/examples/polygon.md) - add subpath
  {% endtab %}
  {% endtabs %}

## History

{% tabs %}
{% tab title="idx" %}

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

{% endtab %}

{% tab title="0" %}

```javascript
// 2023.01.20 - 23:36 
// -----------------------------------------------------------------

const {PI} = Math;

// ⭐ import
import { deg } from './Number+ext.js';        // 👔 Number+ext
// -----------------------------------------------------------------


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

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

    // 🔹 .moveToPoint()
    moveToPoint: {
        value: function(p, {
            newPath=true,        // begin new subpath ? (default = true)
        }={}) {
            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,
        }={}){
            
            // 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;
                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
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.arc(...center.coords, radius, start, end, !clockwise);
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawArcTo()
    // - for rounded corners
    drawArcTo: {
        value: function(control, target, radius, {
            // arc 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 arc
                this.arcTo(...control.coords, ...target.coords, radius);
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .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
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.moveTo(...center.coords);           // center
                // ⭐ arc() adds a line from `center` to arc start.
                this.arc(...center.coords, radius, start, end, !clockwise);
                // this.closePath();                        // add line back to center
            }, { newPath: true, closePath: true, fill, stroke, fillStyle, strokeStyle });
        },
    },

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

    // 🔹 .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 });
        },
    },

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

    // 🔹 .drawRect()
    drawRect: {
        value: function(rect, {
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.rect(...rect.coords);        // 👔 Rect
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawPolylineTo()
    drawPolylineTo: {
        value: function(points, {
            // polyline 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 arc
                points.forEach(p => {
                    this.lineTo(...p.coords);
                });
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },
    
});

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

{% endtab %}

{% tab title="1" %}

```javascript
// 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
// -----------------------------------------------------------------
// 🔹 .moveToPoint()
// -----------------------------------------------------------------
// ⭐ .drawPath()         // general method
// 🔹 .drawArc()
// 🔹 .drawCircle()
// 🔹 .drawWedge()
// 🔹 .drawEllipse()
// -----------------------------------------------------------------
// 🔹 .drawArcTo()        // for rounded corners
// 🔹 .drawPolylineTo()
// 🔹 .drawQuadraticCurveTo()
// 🔹 .drawCubicBezierCurveTo()
// -----------------------------------------------------------------
// 🔹 .drawRect()
// 🔹 .drawPolygon()
//
Object.defineProperties(CanvasRenderingContext2D.prototype, {

    // 🔹 .moveToPoint()
    moveToPoint: {
        value: function(p, {
            newPath=true,        // begin new subpath ? (default = true)
        }={}) {
            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,
        }={}){
            
            // 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;
                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
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.arc(...center.coords, radius, start, end, !clockwise);
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawArcTo()
    // - for rounded corners
    drawArcTo: {
        value: function(control, target, radius, {
            // arc 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 arc
                this.arcTo(...control.coords, ...target.coords, radius);
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .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
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.moveTo(...center.coords);           // center
                // ⭐ arc() adds a line from `center` to arc start.
                this.arc(...center.coords, radius, start, end, !clockwise);
                // this.closePath();                        // add line back to center
            }, { newPath: true, closePath: true, fill, stroke, fillStyle, strokeStyle });
        },
    },

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

    // 🔹 .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 });
        },
    },

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

    // 🔹 .drawRect()
    drawRect: {
        value: function(rect, {
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.rect(...rect.coords);        // 👔 Rect
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

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

                // 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],
                });
                
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawPolylineTo()
    drawPolylineTo: {
        value: function(points, {
            // polyline 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 arc
                points.forEach(p => {
                    this.lineTo(...p.coords);
                });
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },
    
});

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

{% endtab %}

{% tab title="2" %}

* replit ⟩ [Canvas+ext](https://replit.com/@pegasusroe/vector#js/Canvas+ext.js) ,  -> require [Rect](/web/appendix/custom/class/rect.md), [Vector](/web/appendix/custom/class/vector.md), [Number+ext](/web/js/val/prim/num/number+ext.md), [Iterable+ext](/web/js/iteration/iterable+ext.md)

```javascript
// 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
// -----------------------------------------------------------------
// 🔹 .moveToPoint()
// -----------------------------------------------------------------
// ⭐ .drawPath()         // general method
// 🔹 .drawArc()
// 🔹 .drawCircle()
// 🔹 .drawWedge()
// 🔹 .drawEllipse()
// -----------------------------------------------------------------
// 🔹 .drawArcTo()        // for rounded corners
// 🔹 .drawPolylineTo()
// 🔹 .drawQuadraticCurveTo()
// 🔹 .drawCubicBezierCurveTo()
// -----------------------------------------------------------------
// 🔹 .drawRect()
// 🔹 .drawPolygon()
//
Object.defineProperties(CanvasRenderingContext2D.prototype, {

    // 🔹 .moveToPoint()
    moveToPoint: {
        value: function(p, {
            newPath=true,        // begin new subpath ? (default = true)
        }={}) {
            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,
        }={}){
            
            // 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;
                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
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.arc(...center.coords, radius, start, end, !clockwise);
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawArcTo()
    // - for rounded corners
    drawArcTo: {
        value: function(control, target, radius, {
            // arc 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 arc
                this.arcTo(...control.coords, ...target.coords, radius);
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .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
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.moveTo(...center.coords);           // center
                // ⭐ arc() adds a line from `center` to arc start.
                this.arc(...center.coords, radius, start, end, !clockwise);
                // this.closePath();                        // add line back to center
            }, { newPath: true, closePath: true, fill, stroke, fillStyle, strokeStyle });
        },
    },

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

    // 🔹 .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 });
        },
    },

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

    // 🔹 .drawRect()
    drawRect: {
        value: function(rect, {
            // path: general
            newPath = true,       // start new path?
            closePath = true,
            // fill/stroke
            fill = true,          // fill?
            stroke = true,        // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            this.drawPath(() => {
                this.rect(...rect.coords);        // 👔 Rect
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawPolygon()
    drawPolygon: {
        value: function(n, {
            // polygon specific
            center = vec(100, 100),   // 👔 Vector
            radius = 50,
            rotate = 0,               // rotation
            clockwise = true,         // path direction
            // path: general
            newPath = true,           // start new path?
            closePath = true,
            // fill/stroke
            fill = true,              // fill?
            stroke = true,            // stroke?
            fillStyle,
            strokeStyle,
        }={}){
            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],
                });
                
            }, { newPath, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },

    // 🔹 .drawPolylineTo()
    drawPolylineTo: {
        value: function(points, {
            // polyline 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 arc
                points.forEach(p => {
                    this.lineTo(...p.coords);
                });
            }, { newPath: false, closePath, fill, stroke, fillStyle, strokeStyle });
        },
    },
    
});

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

{% endtab %}
{% endtabs %}
