👔Canvas+ext
Last updated
Last updated
extend CanvasRenderingContext2D and HTMLCanvasElement.
replit ⟩ Canvas+ext (v1.1) -> require Rect, Vector, Number+ext, Iterable+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
(this example)
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',
});
});
});
0: (•) first version
1: (+) .drawPolygon()
// 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
// 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
replit ⟩ Canvas+ext , -> require Rect, Vector, Number+ext, Iterable+ext
// 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