├── .travis.yml ├── logo.png ├── src ├── painter │ ├── point.js │ ├── segment.js │ ├── circle.js │ ├── circularsector.js │ ├── rect.js │ ├── image.js │ ├── doughnut.js │ ├── text.js │ ├── ellipse.js │ ├── curve.js │ ├── graph.js │ ├── beziercurve.js │ ├── group.js │ └── line.js ├── main.js ├── classes │ ├── graph.js │ ├── curve.js │ ├── rect.js │ ├── contouredobject.js │ ├── group.js │ ├── unitaryobject.js │ ├── beziercurve.js │ ├── quadrilateral.js │ ├── text.js │ ├── doughnut.js │ ├── point.js │ ├── polygon.js │ ├── circle.js │ ├── segment.js │ ├── image.js │ ├── ellipse.js │ ├── circularsector.js │ ├── triangle.js │ ├── line.js │ └── vector.js ├── utility.js ├── unitary.js └── canvas.js ├── .gitignore ├── CONTRIBUTING.md ├── rollup.config.js ├── LICENCE ├── gulpfile.js ├── package.json ├── README.md ├── canvasTest.html ├── graph.html ├── CHANGELOG.md ├── sample.html ├── canvasTest.js └── __tests__ └── main.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6.8.1 4 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandanoir/unitaryjs/HEAD/logo.png -------------------------------------------------------------------------------- /src/painter/point.js: -------------------------------------------------------------------------------- 1 | export default function(obj) { 2 | this.canvas.fillRect(this.X(obj.x), this.Y(obj.y), 1, 1); 3 | }; 4 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Unitary from './unitary.js'; 2 | import Canvas from './canvas.js'; 3 | export default Object.assign({}, Unitary, {Canvas}); 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.swn 4 | .DS_Store 5 | node_modules 6 | bower_components 7 | npm-debug.log 8 | tmp 9 | dist/* 10 | coverage/ 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to create class 2 | 3 | 1. add item to Classes in README.md 4 | 1. create painter 5 | 1. create class 6 | 1. add class to main.js 7 | 1. add painter to canvas.js 8 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | // rollup.config.js 2 | import json from 'rollup-plugin-json'; 3 | 4 | export default { 5 | output: { 6 | format: 'umd', 7 | }, 8 | plugins: [ 9 | json(), 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /src/painter/segment.js: -------------------------------------------------------------------------------- 1 | export default function(obj) { 2 | this.canvas.beginPath(); 3 | this.canvas.moveTo(this.X(obj.points[0].x), this.Y(obj.points[0].y)); 4 | this.canvas.lineTo(this.X(obj.points[1].x), this.Y(obj.points[1].y)); 5 | this.canvas.stroke(); 6 | }; 7 | -------------------------------------------------------------------------------- /src/painter/circle.js: -------------------------------------------------------------------------------- 1 | export default function(obj) { 2 | const center = obj.center, 3 | r = obj.r; 4 | this.canvas.beginPath(); 5 | this.canvas.arc(this.X(center.x), this.Y(center.y), r, 0, 2 * Math.PI, obj.anticlockwise); 6 | if (obj.style.fillStyle !== null) this.canvas.fill(); 7 | this.canvas.stroke(); 8 | }; 9 | -------------------------------------------------------------------------------- /src/painter/circularsector.js: -------------------------------------------------------------------------------- 1 | export default function(obj) { 2 | const center = obj.center, 3 | r = obj.r, 4 | startAngle = obj.startAngle, 5 | endAngle = obj.endAngle; 6 | this.canvas.beginPath(); 7 | this.canvas.moveTo(this.X(center.x), this.Y(center.y)); 8 | this.canvas.arc(this.X(center.x), this.Y(center.y), r, startAngle, endAngle, obj.anticlockwise); 9 | this.canvas.closePath(); 10 | if (obj.style.fillStyle !== null) this.canvas.fill(); 11 | this.canvas.stroke(); 12 | }; 13 | -------------------------------------------------------------------------------- /src/painter/rect.js: -------------------------------------------------------------------------------- 1 | export default function(obj) { 2 | const x = this.X(obj.points[0].x); 3 | const y = this.Y(obj.points[0].y); 4 | const w = obj.points[1].x - obj.points[0].x; 5 | let h = obj.points[1].y - obj.points[0].y; 6 | if (this.mode !== 'normal') { 7 | h = - (obj.points[1].y - obj.points[0].y); // 左下を原点として扱っているからマイナスしないと計算があわない 8 | } 9 | if (obj.style.fillStyle !== null) this.canvas.fillRect(x, y, w, h); // 上でそれぞれX()、Y()適用済み 10 | else this.canvas.strokeRect(x, y, w, h); 11 | }; 12 | -------------------------------------------------------------------------------- /src/classes/graph.js: -------------------------------------------------------------------------------- 1 | import ContouredObject from './contouredobject.js'; 2 | 3 | export default class Graph extends ContouredObject { 4 | constructor(f, scale = 30) { 5 | super(); 6 | this.f = f; 7 | this.scale = scale; 8 | this.start = null; 9 | this.end = null; 10 | } 11 | setRange(start, end) { 12 | this.start = start; 13 | this.end = end; 14 | return this; 15 | } 16 | equals() { return false; } 17 | moveX() {} 18 | moveY() {} 19 | name() { return 'Graph'; } 20 | } -------------------------------------------------------------------------------- /src/classes/curve.js: -------------------------------------------------------------------------------- 1 | import ContouredObject from './contouredobject.js'; 2 | 3 | export default class Curve extends ContouredObject { 4 | constructor(x, y, scale = 30) { 5 | super(); 6 | this.x = x; 7 | this.y = y; 8 | this.scale = scale; 9 | this.start = 0; 10 | this.end = 2 * Math.PI; 11 | } 12 | setRange(start, end) { 13 | this.start = start; 14 | this.end = end; 15 | return this; 16 | } 17 | equals() { return false; } 18 | moveX() {} 19 | moveY() {} 20 | name() { return 'Curve'; } 21 | } 22 | -------------------------------------------------------------------------------- /src/painter/image.js: -------------------------------------------------------------------------------- 1 | export default function(obj) { 2 | const image = obj.__image__; 3 | if (obj.dx !== null && obj.sx !== null) this.canvas.drawImage(image, obj.sx, obj.sy, obj.sw, obj.sh, this.X(obj.dx), this.Y(obj.dy), obj.dw, obj.dh); 4 | else if (obj.dx !== null && obj.sx === null && obj.dw !== null) this.canvas.drawImage(image, this.X(obj.dx), this.Y(obj.dy), obj.dw, obj.dh); 5 | else if (obj.dx !== null && obj.dw === null) { 6 | // obj.sx !== null ならば必ず obj.dw !== nullとなるから、 7 | // 対偶をとり obj.dw === nullならばobj.sx === null 8 | this.canvas.drawImage(image, this.X(obj.dx), this.Y(obj.dy)); 9 | } else if (obj.dx === null) this.canvas.drawImage(image); 10 | }; 11 | -------------------------------------------------------------------------------- /src/painter/doughnut.js: -------------------------------------------------------------------------------- 1 | export default function(obj) { 2 | const center = obj.center, 3 | x = this.X(center.x), 4 | y = this.Y(center.y), 5 | innerRadius = obj.innerRadius, 6 | outerRadius = obj.outerRadius; 7 | if (obj.style.fillStyle !== null) { 8 | this.canvas.beginPath(); 9 | this.canvas.arc(x, y, outerRadius, 0, 2 * Math.PI, false); 10 | this.canvas.arc(x, y, innerRadius, 0, 2 * Math.PI, true); 11 | this.canvas.fill(); 12 | } 13 | 14 | this.canvas.beginPath(); 15 | this.canvas.arc(x, y, outerRadius, 0, 2 * Math.PI, false); 16 | this.canvas.stroke(); 17 | 18 | this.canvas.beginPath(); 19 | this.canvas.arc(x, y, innerRadius, 0, 2 * Math.PI, true); 20 | this.canvas.stroke(); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /src/classes/rect.js: -------------------------------------------------------------------------------- 1 | import Polygon from './polygon.js'; 2 | 3 | export default class Rect extends Polygon{ 4 | constructor(A, B) { 5 | super(A, B); 6 | } 7 | has(P) { 8 | const A = this.points[0], 9 | B = this.points[1]; 10 | return (A.x - P.x) * (B.x - P.x) <= 0 && (A.y - P.y) * (B.y - P.y) <= 0; 11 | } 12 | move(dx, dy) { 13 | if (dx === 0 && dy === 0) return this; 14 | 15 | const newObj = super.move(dx, dy); 16 | return new Rect(...newObj.points).copyFrom(this); 17 | } 18 | rotate(rad, center) { 19 | if (rad % (2 * Math.PI) === 0) return this; 20 | 21 | const newObj = super.rotate(rad, center); 22 | return new Rect(...newObj.points).copyFrom(this); 23 | } 24 | name() { return 'Rect'; } 25 | } 26 | -------------------------------------------------------------------------------- /src/painter/text.js: -------------------------------------------------------------------------------- 1 | export default function(obj) { 2 | this.canvas.textAlign = obj.style.align; 3 | this.canvas.textBaseline = obj.style.baseline; 4 | const x = this.X(obj.P.x); 5 | const y = this.Y(obj.P.y); 6 | const maxWidth = obj.style.maxWidth; 7 | let defaultFont; 8 | if (obj.style.font !== null) { 9 | defaultFont = this.canvas.font; 10 | this.canvas.font = obj.style.font; 11 | } 12 | if (maxWidth === null) { 13 | if (obj.style.strokesOutline) 14 | this.canvas.strokeText(obj.text, x, y); 15 | this.canvas.fillText(obj.text, x, y); 16 | } else { 17 | if (obj.style.strokesOutline) 18 | this.canvas.strokeText(obj.text, x, y, maxWidth); 19 | this.canvas.fillText(obj.text, x, y, maxWidth); 20 | } 21 | if(obj.style.font !== null) this.canvas.font = defaultFont; 22 | }; 23 | -------------------------------------------------------------------------------- /src/utility.js: -------------------------------------------------------------------------------- 1 | const sign = n => n >= 0 ? '+' : '-'; 2 | const abs = n => n > 0 ? n : -n; 3 | const isInteger = n => (0 | n) === n; 4 | const gcd = (m, n) => { 5 | if (m < n) return gcd(n, m); 6 | if (m < 0) return gcd(-m, n); 7 | if (n < 0) return gcd(m, -n); 8 | return n === 0 ? m : gcd(n, m % n); 9 | } 10 | const nearlyEquals = (a, b) => (0 | a*1e6) / 1e6 === (0 | b*1e6) / 1e6; 11 | const nearlyEqualsZero = n => nearlyEquals(n, 0); 12 | const copy = obj => { 13 | if (Array.isArray(obj)) return obj.map(copy); 14 | if ({}.toString.call(obj) !== '[object Object]') return obj; 15 | 16 | const keys = Object.keys(obj); 17 | const res = {}; 18 | for (let i = 0, _i = keys.length; i < _i; i++) { 19 | res[keys[i]] = copy(obj[keys[i]]); 20 | } 21 | return res; 22 | } 23 | 24 | export {sign, abs, isInteger, gcd, nearlyEqualsZero, nearlyEquals, copy}; 25 | -------------------------------------------------------------------------------- /src/painter/ellipse.js: -------------------------------------------------------------------------------- 1 | export default function(obj) { 2 | const {center, a, b} = obj; 3 | this.canvas.beginPath(); 4 | if (this.canvas.ellipse) { 5 | this.canvas.ellipse(this.X(center.x), this.Y(center.y), obj.a, obj.b, obj.angle, 0, 2 * Math.PI, obj.anticlockwise); 6 | if (obj.style.fillStyle !== null) this.canvas.fill(); 7 | this.canvas.stroke(); 8 | } else { 9 | this.canvas.save(); 10 | this.canvas.setTransform(1, 0, 0, 1, 0, 0); 11 | this.canvas.translate(this.X(center.x),this.Y(center.y)); 12 | this.canvas.rotate(obj.angle); 13 | this.canvas.scale(1, obj.b); 14 | this.canvas.arc(this.X(center.x), this.Y(center.y), obj.a, 0, 2 * Math.PI, obj.anticlockwise); 15 | if (obj.style.fillStyle !== null) this.canvas.fill(); 16 | this.canvas.stroke(); 17 | this.canvas.restore(); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/classes/contouredobject.js: -------------------------------------------------------------------------------- 1 | import UnitaryObject from './unitaryobject.js'; 2 | const props = ['lineDash', 'lineCap', 'lineDashOffset', 'lineJoin', 'lineWidth']; 3 | export default class ContouredObject extends UnitaryObject { 4 | constructor() { 5 | super(); 6 | for (let i = 0, _i = props.length; i < _i; i++) { 7 | this[props[i]] = null; 8 | } 9 | this._propsToCopy = this._propsToCopy.concat(['lineDash', 'lineCap', 'lineDashOffset', 'lineJoin', 'lineWidth']); 10 | } 11 | name() { return 'ContouredObject'; } 12 | } 13 | for (let i = 0, _i = props.length; i < _i; i++) { 14 | const capitalized = props[i].replace(/./, s => s.toUpperCase()); 15 | ContouredObject.prototype[`get${capitalized}`] = function() { 16 | return this[props[i]]; 17 | }; 18 | ContouredObject.prototype[`set${capitalized}`] = function(val) { 19 | this[props[i]] = val; 20 | return this; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/classes/group.js: -------------------------------------------------------------------------------- 1 | import ContouredObject from './contouredobject.js'; 2 | 3 | export default class Group extends ContouredObject { 4 | constructor(...args) { 5 | super(); 6 | if (Array.isArray(args[0])) this.group = args[0]; 7 | else this.group = args; 8 | } 9 | has(P) { 10 | for (let i = 0, _i = this.group.length; i < _i; i++) { 11 | if (this.group[i].has && this.group[i].has(P)) return true; 12 | } 13 | return false; 14 | } 15 | move(dx, dy) { 16 | if (dx === 0 && dy === 0) return this; 17 | 18 | return new Group(this.group.map(g => g.move(dx, dy))).copyFrom(this); 19 | } 20 | rotate(rad, center) { 21 | if (rad % (2 * Math.PI) === 0) return this; 22 | 23 | return new Group(this.group.map(g => g.rotate(rad, center))).copyFrom(this); 24 | } 25 | push(...objs) { 26 | return new Group(this.group.concat(objs)).copyFrom(this); 27 | } 28 | name() { return 'Group'; } 29 | } 30 | -------------------------------------------------------------------------------- /src/classes/unitaryobject.js: -------------------------------------------------------------------------------- 1 | import {copy} from '../utility.js'; 2 | export default class UnitaryObject { 3 | constructor() { 4 | this.style = { 5 | fillStyle: null, 6 | strokeStyle: null 7 | }; 8 | this._propsToCopy = ['style']; 9 | } 10 | equals(B) { return this.name() === B.name(); } 11 | setFillColor(color) { this.style.fillStyle = color; return this; } 12 | setFillStyle(color) { this.style.fillStyle = color; return this; } 13 | setStrokeColor(color) { this.style.strokeStyle = color; return this; } 14 | setStrokeStyle(color) { this.style.strokeStyle = color; return this; } 15 | copyFrom(obj) { 16 | for (let i = 0, _i = obj._propsToCopy.length; i < _i; i++) { 17 | const prop = obj._propsToCopy[i]; 18 | this[prop] = copy(obj[prop]); 19 | } 20 | return this; 21 | } 22 | move(dx, dy) { return this; } 23 | moveX(dx) { return this.move(dx, 0); } 24 | moveY(dy) { return this.move(0, dy); } 25 | has(P) { return false; } 26 | name() { return 'UnitaryObject'; } 27 | } 28 | -------------------------------------------------------------------------------- /src/painter/curve.js: -------------------------------------------------------------------------------- 1 | import Unitary from '../unitary.js'; 2 | export default function(obj) { 3 | this.canvas.beginPath(); 4 | const {start, end, x, y} = obj; 5 | const points = []; 6 | const step = 1 / (end - start); 7 | 8 | for (let t = start; t <= end; t += step) { 9 | const slope = new Unitary.Vector((x(t + step) - x(t)) * obj.scale, (y(t + step) - y(t)) * obj.scale).theta / (2 * Math.PI) * 360; 10 | 11 | if (!Number.isNaN(x(t)) && !Number.isNaN(y(t))) points[points.length] = new Unitary.Point(x(t) * obj.scale, y(t) * obj.scale); 12 | if (45 <= slope && slope <= 135 || 225 <= slope && slope <= 315) { 13 | if (!Number.isNaN(x(t + step / 2)) && !Number.isNaN(y(t + step / 2))) points[points.length] = new Unitary.Point(x(t + step / 2) * obj.scale, y(t + step / 2) * obj.scale); 14 | } 15 | } 16 | 17 | this.canvas.moveTo(this.X(points[0].x), this.Y(points[0].y)); 18 | for (let i = 0, _i = points.length; i < _i; i = 0|i+1) { 19 | this.canvas.lineTo(this.X(points[i].x), this.Y(points[i].y)); 20 | } 21 | this.canvas.stroke(); 22 | }; 23 | -------------------------------------------------------------------------------- /src/classes/beziercurve.js: -------------------------------------------------------------------------------- 1 | import ContouredObject from './contouredobject.js'; 2 | 3 | export default class BezierCurve extends ContouredObject { 4 | constructor(...args) { 5 | super(); 6 | if (Array.isArray(args[0])) this.controlPoints = args[0]; 7 | else this.controlPoints = args; 8 | if (this.controlPoints.length > 4) { 9 | let distance = 0; 10 | for (let i = 0, _i = this.controlPoints.length - 1; i < _i; i++) { 11 | const dx = (this.controlPoints[i + 1].x - this.controlPoints[i].x); 12 | const dy = (this.controlPoints[i + 1].y - this.controlPoints[i].y); 13 | distance += Math.sqrt(dx*dx + dy*dy); 14 | } 15 | this.step = 1 / distance; 16 | } 17 | } 18 | setStep(step) { 19 | this.step = step; 20 | return this; 21 | } 22 | move(dx, dy) { 23 | if (dx === 0 && dy === 0) return this; 24 | 25 | return new BezierCurve(this.controlPoints.map(point => point.move(dx, dy))).copyFrom(this); 26 | } 27 | name() { return 'BezierCurve'; } 28 | } 29 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Panda Noir 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/painter/graph.js: -------------------------------------------------------------------------------- 1 | import Unitary from '../unitary.js'; 2 | export default function(obj) { 3 | this.canvas.beginPath(); 4 | let start = obj.start , end = obj.end; 5 | if (start === null) start = -this.origin.x; 6 | if (end === null) end = this.canvasWidth - this.origin.x; 7 | 8 | const points = []; 9 | for (let x = start; x <= end; x = 0|x+1) { 10 | const slope = new Unitary.Vector(1, (obj.f((x + 1) / obj.scale) - obj.f(x / obj.scale)) * obj.scale).theta / (2 * Math.PI) * 360; 11 | 12 | if (!Number.isNaN(obj.f(x / obj.scale))) points[points.length] = new Unitary.Point(x, obj.f(x / obj.scale) * obj.scale); 13 | if (45 <= slope && slope <= 135 || 225 <= slope && slope <= 315) { 14 | if (!Number.isNaN(obj.f((x + 0.5) / obj.scale))) points[points.length] = new Unitary.Point(x + 0.5, obj.f((x + 0.5) / obj.scale) * obj.scale); 15 | } 16 | } 17 | 18 | this.canvas.moveTo(this.X(points[0].x), this.Y(points[0].y)); 19 | for (let i = 0, _i = points.length; i < _i; i = 0|i+1) { 20 | this.canvas.lineTo(this.X(points[i].x), this.Y(points[i].y)); 21 | } 22 | this.canvas.stroke(); 23 | }; 24 | -------------------------------------------------------------------------------- /src/classes/quadrilateral.js: -------------------------------------------------------------------------------- 1 | import Polygon from './polygon.js'; 2 | import Segment from './segment.js'; 3 | import Triangle from './triangle.js'; 4 | 5 | export default class Quadrilateral extends Polygon{ 6 | constructor(A, B, C, D) { 7 | if (A.equals(B) || A.equals(C) || A.equals(D) || B.equals(C) || B.equals(D) || C.equals(D)) { 8 | throw new Error('ABCD is not a quadrilateral.'); 9 | } 10 | if (new Segment(A, D).intersects(new Segment(B, C)) || 11 | new Segment(A, B).intersects(new Segment(C, D)) 12 | ) { 13 | throw new Error('ABCD is not a quadrilateral.'); 14 | } 15 | super(A, B, C, D); 16 | } 17 | getArea() { 18 | const A = this.points[0], 19 | B = this.points[1], 20 | C = this.points[2], 21 | D = this.points[3]; 22 | const S1 = new Triangle(A, B, C).getArea(); 23 | const S2 = new Triangle(A, C, D).getArea(); 24 | return S1 + S2; 25 | } 26 | move(dx, dy) { 27 | if (dx === 0 && dy === 0) return this; 28 | 29 | const newObj = super.move(dx, dy); 30 | return new Quadrilateral(...newObj.points).copyFrom(this); 31 | } 32 | rotate(rad, center) { 33 | if (rad % (2 * Math.PI) === 0) return this; 34 | 35 | const newObj = super.rotate(rad, center); 36 | return new Quadrilateral(...newObj.points).copyFrom(this); 37 | } 38 | name() { return 'Quadrilateral'; } 39 | } 40 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const compilerPackage = require('google-closure-compiler'); 3 | const closureCompiler = compilerPackage.gulp(); 4 | 5 | compilerPackage.compiler.JAR_PATH = undefined; 6 | compilerPackage.compiler.prototype.javaPath = './node_modules/.bin/closure-gun'; 7 | 8 | const rename = require("gulp-rename"); 9 | const gzip = require('gulp-gzip'); 10 | 11 | gulp.task('minify', done => { 12 | closureCompiler({ 13 | js: './dist/canvas.js', 14 | compilation_level: 'SIMPLE', 15 | language_in: 'ECMASCRIPT6_STRICT', 16 | language_out: 'ECMASCRIPT5_STRICT', 17 | js_output_file: 'canvas.min.js' 18 | }).src() 19 | .pipe(gulp.dest('./dist/')); 20 | 21 | closureCompiler({ 22 | js: './dist/unitary.js', 23 | compilation_level: 'SIMPLE', 24 | language_in: 'ECMASCRIPT6_STRICT', 25 | language_out: 'ECMASCRIPT5_STRICT', 26 | js_output_file: 'unitary.min.js' 27 | }).src() 28 | .pipe(gulp.dest('./dist/')) 29 | .pipe(gzip()).pipe(gulp.dest('./dist/')) 30 | .on('end', () => { 31 | const files = ['unitary.browser.js', 'unitary.browser.min.js', 'unitary.browser.min.js.gz']; 32 | for (const file of files) { 33 | gulp.src('./dist/' + file.replace('.browser', '')) 34 | .pipe(rename(file)) 35 | .pipe(gulp.dest('./dist')); 36 | } 37 | done(); 38 | }); 39 | }); 40 | gulp.task('default', gulp.series(gulp.parallel(['minify']))); 41 | -------------------------------------------------------------------------------- /src/classes/text.js: -------------------------------------------------------------------------------- 1 | import UnitaryObject from './unitaryobject.js'; 2 | 3 | export default class Text_ extends UnitaryObject { 4 | constructor(str, P, align = 'left', maxWidth = null) { 5 | super(); 6 | this.P = P; 7 | this.string = str; 8 | this.text = str; 9 | this.strokesOutline = false; 10 | this.style.align = align; 11 | this.style.maxWidth = maxWidth; 12 | this.style.fillStyle = '#000'; 13 | this.style.outlineColor = '#000'; 14 | this.style.baseline = 'alphabetic'; 15 | this.style.font = null; 16 | } 17 | strokeOutline() { 18 | this.strokesOutline = true; 19 | return this; 20 | } 21 | setAlign(align) { 22 | this.style.align = align; 23 | return this; 24 | } 25 | setOutlineColor(color) { 26 | this.style.outlineColor = color; 27 | return this; 28 | } 29 | setBaseline(base) { 30 | this.style.baseline = base; 31 | return this; 32 | } 33 | setFont(font) { 34 | this.style.font = font; 35 | return this; 36 | } 37 | move(dx, dy) { 38 | if (dx === 0 && dy === 0) return this; 39 | 40 | const res = new Text_(this.string, this.P.move(dx, dy)).copyFrom(this); 41 | res.style.align = this.style.align; 42 | res.style.maxWidth = this.style.maxWidth 43 | res.strokesOutline = this.strokesOutline; 44 | 45 | return res; 46 | } 47 | name() { return 'Text'; } 48 | } 49 | -------------------------------------------------------------------------------- /src/classes/doughnut.js: -------------------------------------------------------------------------------- 1 | import ContouredObject from './contouredobject.js'; 2 | import {Vector} from './vector.js'; 3 | import {nearlyEquals as eq} from '../utility.js'; 4 | 5 | export default class Doughnut extends ContouredObject { 6 | constructor(center, innerRadius, outerRadius) { 7 | super(); 8 | this.center = center; 9 | this.innerRadius = innerRadius; 10 | this.outerRadius = outerRadius; 11 | } 12 | get Origin() { 13 | return this.center; 14 | } 15 | moveTo(x, y) { 16 | if (this.center.x === x && this.center.y === y) return this; 17 | 18 | return new Doughnut(this.center.moveTo(x, y), this.innerRadius, this.outerRadius).copyFrom(this); 19 | } 20 | move(dx, dy) { 21 | if (dx === 0 && dy === 0) return this; 22 | 23 | return new Doughnut(this.center.move(dx, dy), this.innerRadius, this.outerRadius).copyFrom(this); 24 | } 25 | equals(C) { 26 | if (!super.equals(C)) { 27 | return false; 28 | } 29 | return this.center.equals(C.center) && 30 | eq(this.innerRadius, C.innerRadius) && 31 | eq(this.outerRadius, C.outerRadius); 32 | } 33 | has(P) { 34 | const distance = new Vector(P).subtract(new Vector(this.center)).abs(); 35 | return (this.innerRadius < distance || eq(this.innerRadius, distance)) && 36 | (distance < this.outerRadius || eq(distance, this.outerRadius)); 37 | } 38 | name() { return 'Doughnut'; } 39 | } 40 | -------------------------------------------------------------------------------- /src/classes/point.js: -------------------------------------------------------------------------------- 1 | import UnitaryObject from './unitaryobject.js'; 2 | import {Vector} from './vector.js'; 3 | import {nearlyEquals as eq} from '../utility.js'; 4 | 5 | export default class Point extends UnitaryObject { 6 | constructor(x, y) { 7 | super(); 8 | this.x = x; 9 | this.y = y; 10 | } 11 | moveTo(x, y) { 12 | if (this.x === x && this.y === y) return this; 13 | return new Point(x, y).copyFrom(this); 14 | } 15 | move(dx, dy) { 16 | if (dx === 0 && dy === 0) return this; 17 | return new Point(this.x + dx, this.y + dy).copyFrom(this); 18 | } 19 | rotate(rad, center) { 20 | if (rad % (2 * Math.PI) === 0) return this; 21 | const dx = this.x - center.x, 22 | dy = this.y - center.y; 23 | const x = Math.cos(rad)*dx - Math.sin(rad)*dy + center.x; 24 | const y = Math.sin(rad)*dx + Math.cos(rad)*dy + center.y; 25 | return new Point(x, y).copyFrom(this); 26 | } 27 | toString() { return '(' + this.x + ', ' + this.y + ')'; } 28 | inspect() { return '(' + this.x + ', ' + this.y + ')'; } 29 | equals(B) { 30 | if (!super.equals(B)) { 31 | return false; 32 | } 33 | return eq(this.x, B.x) && eq(this.y, B.y); 34 | } 35 | toVector() { 36 | return new Vector(this.x, this.y); 37 | } 38 | static fromPolar(r, theta) { 39 | return new Point(r*Math.cos(theta), r*Math.sin(theta)); 40 | } 41 | name() { return 'Point'; } 42 | } 43 | -------------------------------------------------------------------------------- /src/classes/polygon.js: -------------------------------------------------------------------------------- 1 | import ContouredObject from './contouredobject.js'; 2 | import {Vector} from './vector.js'; 3 | import {nearlyEquals as eq} from '../utility.js'; 4 | 5 | export default class Polygon extends ContouredObject { 6 | constructor(...args) { 7 | super(); 8 | const points = 1 <= args.length ? args : []; 9 | if (Array.isArray(points[0])) { 10 | this.points = points[0]; 11 | } else { 12 | this.points = points; 13 | } 14 | } 15 | equals() { 16 | return false; 17 | } 18 | move(dx, dy) { 19 | if (dx === 0 && dy === 0) return this; 20 | 21 | return new Polygon(this.points.map(point => point.move(dx, dy))).copyFrom(this); 22 | } 23 | rotate(rad, center) { 24 | if (rad % (2 * Math.PI) === 0) return this; 25 | 26 | return new Polygon(this.points.map(point => point.rotate(rad, center))).copyFrom(this); 27 | } 28 | has(P) { 29 | let a, b, cos, v; 30 | let before_v = this.points[this.points.length - 1]; 31 | let rad = 0; 32 | for (let i = 0, len = this.points.length; i < len; i = 0|i+1) { 33 | v = this.points[i]; 34 | a = new Vector(v).subtract(new Vector(P)); 35 | b = new Vector(before_v).subtract(new Vector(P)); 36 | if (a.abs() === 0 || b.abs() === 0) return true; // v == P || before_v == p 37 | cos = a.product(b) / (a.abs() * b.abs()); 38 | rad += Math.acos(cos); 39 | before_v = v; 40 | } 41 | return eq(rad, 0) || eq(rad, 2 * Math.PI); 42 | } 43 | name() { return 'Polygon'; } 44 | } 45 | -------------------------------------------------------------------------------- /src/classes/circle.js: -------------------------------------------------------------------------------- 1 | import ContouredObject from './contouredobject.js'; 2 | import {Vector} from './vector.js'; 3 | import {abs, sign, nearlyEquals as eq} from '../utility.js'; 4 | 5 | export default class Circle extends ContouredObject { 6 | constructor(center, radius) { 7 | super(); 8 | this.center = center; 9 | this.r = radius; 10 | this.anticlockwise = false; 11 | this._propsToCopy = this._propsToCopy.concat('anticlockwise'); 12 | } 13 | get Origin() { return this.center; } 14 | get radius() { return this.r; } 15 | moveTo(x, y) { 16 | if (this.center.x === x && this.center.y === y) return this; 17 | 18 | return new Circle(this.center.moveTo(x, y), this.radius).copyFrom(this); 19 | } 20 | move(dx, dy) { 21 | if (dx === 0 && dy === 0) return this; 22 | 23 | return new Circle(this.center.move(dx, dy), this.radius).copyFrom(this); 24 | } 25 | getEquation() { 26 | return `(x${this.center.x === 0 ? '' : sign(-this.center.x) + abs(this.center.x)})^2+(y${this.center.y === 0 ? '' : sign(-this.center.y) + abs(this.center.y)})^2=${this.r}^2` 27 | } 28 | equals(C) { 29 | if (!super.equals(C)) { 30 | return false; 31 | } 32 | return this.center.equals(C.center) && eq(this.r, C.r); 33 | } 34 | has(P) { 35 | return new Vector(P).subtract(new Vector(this.center)).abs() <= this.r; 36 | } 37 | setAnticlockwise(anticlockwise) { 38 | if (this.anticlockwise === anticlockwise) return this; 39 | 40 | const res = new Circle(this.center, this.radius).copyFrom(this); 41 | res.anticlockwise = anticlockwise; 42 | 43 | return res; 44 | } 45 | name() { return 'Circle'; } 46 | } 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unitaryjs", 3 | "version": "0.1.2", 4 | "description": "Canvas Library", 5 | "main": "./dist/unitary.min.js", 6 | "scripts": { 7 | "preversion": "yarn-or-npm test", 8 | "version": "yarn-or-npm run build && yarn-or-npm run build2 && gulp && git add -A", 9 | "postversion": "git push && git push --tags", 10 | "pretest": "rollup src/unitary.js -c -o dist/onlyunitary.js --name Unitary", 11 | "test": "jest", 12 | "precoverage": "rollup src/unitary.js -c -o dist/onlyunitary.js --name Unitary", 13 | "coverage": "jest --coverage", 14 | "postcoverage": "rm dist/onlyunitary.js", 15 | "build": "rollup src/main.js -c -o dist/unitary.js --name Unitary", 16 | "build2": "rollup src/canvas.js -c -o dist/canvas.js --name Canvas", 17 | "compile": "gulp minify" 18 | }, 19 | "files": [ 20 | "CHANGELOG.md", 21 | "LICENCE", 22 | "README.md", 23 | "package.json", 24 | "dist" 25 | ], 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/pandanoir/unitaryjs.git" 29 | }, 30 | "keywords": [ 31 | "canvas", 32 | "library", 33 | "unitaryjs" 34 | ], 35 | "devDependencies": { 36 | "closure-gun": "^0.1.12", 37 | "google-closure-compiler": "^20181210.0.0", 38 | "gulp": "^4.0.0", 39 | "gulp-gzip": "^1.4.2", 40 | "gulp-rename": "^1.4.0", 41 | "jest": "^23.6.0", 42 | "mocha": "^5.2.0", 43 | "rollup": "^1.0.1", 44 | "rollup-plugin-json": "^3.1.0", 45 | "yarn-or-npm": "^2.0.4" 46 | }, 47 | "author": "Naoto Ikuno ", 48 | "license": "MIT", 49 | "bugs": { 50 | "url": "https://github.com/pandanoir/unitaryjs/issues" 51 | }, 52 | "homepage": "https://github.com/pandanoir/unitaryjs" 53 | } 54 | -------------------------------------------------------------------------------- /src/classes/segment.js: -------------------------------------------------------------------------------- 1 | import ContouredObject from './contouredobject.js'; 2 | import Line from './line.js'; 3 | 4 | export default class Segment extends ContouredObject { 5 | constructor(A, B) { 6 | super(); 7 | 8 | if (A.x > B.x) this.points = [B, A]; 9 | else this.points = [A, B]; 10 | 11 | const dx = A.x - B.x, dy = A.y - B.y; 12 | this.length = Math.sqrt(dx*dx + dy*dy); 13 | } 14 | move(dx, dy) { 15 | if (dx === 0 && dy === 0) return this; 16 | return new Segment(this.points[0].move(dx, dy), this.points[1].move(dx, dy)).copyFrom(this); 17 | } 18 | has(P) { 19 | const A = this.points[0]; 20 | const B = this.points[1]; 21 | if (A.x <= P.x && P.x <= B.x) { 22 | if (A.y <= B.y && (A.y <= P.y && P.y <= B.y) || A.y >= B.y && (A.y >= P.y && P.y >= B.y)) { 23 | if (this.toLine().has(P)) { 24 | return true; 25 | } 26 | } 27 | } 28 | return false; 29 | } 30 | intersects(CD) { 31 | let intersection; 32 | if (CD instanceof Line) { 33 | intersection = this.toLine().getIntersection(CD); 34 | } else { 35 | intersection = this.toLine().getIntersection(CD.toLine()); 36 | } 37 | if (intersection === false) { 38 | return false; 39 | } 40 | return this.has(intersection) && CD.has(intersection); 41 | } 42 | toLine() { return new Line(this.points[0], this.points[1]); } 43 | equals(CD) { 44 | if (!super.equals(CD)) { 45 | return false; 46 | } 47 | return this.points[0].equals(CD.points[0]) && this.points[1].equals(CD.points[1]); 48 | } 49 | name() { return 'Segment'; } 50 | } 51 | -------------------------------------------------------------------------------- /src/classes/image.js: -------------------------------------------------------------------------------- 1 | import UnitaryObject from './unitaryobject.js'; 2 | import Point from './point.js'; 3 | 4 | export default class Image_ extends UnitaryObject { 5 | constructor(src, startPoint) { 6 | super(); 7 | this.src = src; 8 | this.startPoint = startPoint; 9 | this.dw = null; 10 | this.dh = null; 11 | this.sw = null; 12 | this.sh = null; 13 | this.sx = null; 14 | this.sy = null; 15 | 16 | this._propsToCopy = this._propsToCopy.concat(['dw', 'dh', 'sw', 'sh', 'sx', 'sy']); 17 | } 18 | get dx() { return this.startPoint.x; } 19 | get dy() { return this.startPoint.y; } 20 | trim(startPoint, sw, sh, dw = sw, dh = sh) { 21 | const res = new Image_(this.src, this.startPoint).copyFrom(this); 22 | res.sx = startPoint.x; 23 | res.sy = startPoint.y; 24 | res.sw = sw; 25 | res.sh = sh; 26 | res.dw = dw; 27 | res.dh = dh; 28 | return res; 29 | }; 30 | resize(dw, dh) { 31 | const res = new Image_(this.src, this.startPoint).copyFrom(this); 32 | res.dw = dw; 33 | res.dh = dh; 34 | res.sw = this.sw; 35 | res.sh = this.sh; 36 | res.sx = this.sx; 37 | res.sy = this.sy; 38 | return res; 39 | } 40 | equals(B) { 41 | if (!super.equals(B)) { 42 | return false; 43 | } 44 | return this.src === B.src && this.dx === B.dx && this.dy === B.dy && this.dw === B.dw && this.dh === B.dh && this.sw === B.sw && this.sh === B.sh && this.sx === B.sx && this.sy === B.sy; 45 | } 46 | move(dx, dy) { 47 | if (dx === 0 && dy === 0) return this; 48 | 49 | const res = new Image_(this.src, this.startPoint.move(dx, dy)).copyFrom(this); 50 | return res; 51 | } 52 | name() { return 'Image'; } 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnitaryJS 2 | 3 | [![Build Status](https://travis-ci.org/pandanoir/unitaryjs.svg?branch=master)](https://travis-ci.org/pandanoir/unitaryjs) 4 | 5 | ![logo.png](logo.png) 6 | 7 | UnitaryJS is a library for handling canvas with objects. 8 | 9 | Demo: [Demo](http://pandanoir.net/unitaryjs/sample.html) 10 | 11 | ## Wiki 12 | [wiki](http://pandanoir.github.io/unitaryjs) 13 | 14 | 15 | ## Quick Example 16 | 17 | ```html 18 | 19 | 20 | 21 | 22 | Sample 23 | 24 | 39 | 40 | 41 | 42 | 43 | 44 | ``` 45 | 46 | ## Getting started 47 | 48 | ``` 49 | npm install unitaryjs 50 | ``` 51 | 52 | and require unitaryjs. 53 | 54 | ```js 55 | const Unitary = require('unitaryjs'); 56 | new Unitary.Vector(1, 1); 57 | ``` 58 | 59 | ## Browser 60 | download [./dist/unitary.min.js](https://unpkg.com/unitaryjs/dist/unitary.min.js) and load it. 61 | 62 | 63 | ```html 64 | 65 | ``` 66 | 67 | ## Classes 68 | 69 | * BezierCurve 70 | * Circle 71 | * CircularSector 72 | * Doughnut 73 | * Graph 74 | * Group 75 | * Image 76 | * Line 77 | * Point 78 | * Polygon 79 | * Quadrilateral 80 | * Rect 81 | * Segment 82 | * Text 83 | * Triangle 84 | * Vector 85 | -------------------------------------------------------------------------------- /src/classes/ellipse.js: -------------------------------------------------------------------------------- 1 | import ContouredObject from './contouredobject.js'; 2 | import {Vector} from './vector.js'; 3 | import {abs, sign} from '../utility.js'; 4 | 5 | export default class Ellipse extends ContouredObject { 6 | constructor(center, a, b) { 7 | super(); 8 | this.center = center; 9 | this.a = a; 10 | this.b = b; 11 | this.majorAxis = 2 * Math.max(a, b); 12 | this.minorAxis = 2 * Math.min(a, b); 13 | this.anticlockwise = false; 14 | this.angle = 0; 15 | this._propsToCopy = this._propsToCopy.concat(['anticlockwise', 'angle']); 16 | } 17 | get Origin() { return this.center; } 18 | moveTo(x, y) { 19 | if (this.center.x === x && this.center.y === y) return this; 20 | 21 | return new Ellipse(this.center.moveTo(x, y), this.a, this.b).copyFrom(this); 22 | } 23 | move(dx, dy) { 24 | if (dx === 0 && dy === 0) return this; 25 | 26 | return new Ellipse(this.center.move(dx, dy), this.a, this.b).copyFrom(this); 27 | } 28 | rotate(rad) { 29 | if (rad % (2 * Math.PI) === 0) return this; 30 | const res = new Ellipse(this.center, this.a, this.b).copyFrom(this); 31 | res.angle += rad; 32 | return res; 33 | } 34 | equals(C) { 35 | if (!super.equals(C)) { 36 | return false; 37 | } 38 | return this.center.equals(C.center) && this.a === C.a && this.b === C.b; 39 | } 40 | has(P) { 41 | P = P.rotate(-this.angle, this.center); 42 | return Math.pow((P.x - this.center.x) / this.a, 2) + Math.pow((P.y - this.center.y) / this.b, 2) <= 1; 43 | } 44 | setAnticlockwise(anticlockwise) { 45 | if (this.anticlockwise === anticlockwise) return this; 46 | 47 | const res = new Ellipse(this.center, this.a, this.b).copyFrom(this); 48 | res.anticlockwise = anticlockwise; 49 | return res; 50 | } 51 | name() { return 'Ellipse'; } 52 | } 53 | -------------------------------------------------------------------------------- /src/painter/beziercurve.js: -------------------------------------------------------------------------------- 1 | import Unitary from '../unitary.js'; 2 | export default function(obj) { 3 | this.canvas.beginPath(); 4 | const controlPoints = obj.controlPoints; 5 | let P = controlPoints; 6 | let nextP = []; 7 | const points = obj.points || [controlPoints[0]]; 8 | let pointsLength = 1; 9 | const step = obj.step; 10 | 11 | if (controlPoints.length <= 1) { 12 | // do nothing 13 | } else if (2 <= controlPoints.length && controlPoints.length <= 4) { 14 | const cp = controlPoints.map(p => ({x: this.X(p.x), y: this.Y(p.y)})); 15 | this.canvas.moveTo(cp[0].x, cp[0].y); 16 | 17 | if (cp.length === 2) this.canvas.lineTo(cp[1].x, cp[1].y); 18 | else if (cp.length === 3) this.canvas.quadraticCurveTo(cp[1].x, cp[1].y, cp[2].x, cp[2].y); 19 | else if (cp.length === 4) this.canvas.bezierCurveTo(cp[1].x, cp[1].y, cp[2].x, cp[2].y, cp[3].x, cp[3].y); 20 | 21 | this.canvas.stroke(); 22 | } else { 23 | if (!obj.points) { 24 | for (let t = 0; t < 1; t += step) { 25 | P = controlPoints.concat(); 26 | for (let i = 0, _i = controlPoints.length - 1; i < _i; i = 0|i+1) { 27 | for (let j = 0, _j = P.length - 1; j < _j; j = 0|j+1) 28 | nextP[j] = new Unitary.Point(P[j + 1].x * t + P[j].x * (1 - t), P[j + 1].y * t + P[j].y * (1 - t)); 29 | P = nextP; 30 | nextP = []; 31 | } 32 | points[pointsLength] = P[0]; 33 | pointsLength = 0 | pointsLength + 1; 34 | } 35 | points[pointsLength] = controlPoints[controlPoints.length - 1]; 36 | } 37 | obj.points = points; 38 | 39 | this.canvas.moveTo(this.X(points[0].x), this.Y(points[0].y)); 40 | for (let i = 0, _i = points.length; i < _i; i = 0|i+1) 41 | this.canvas.lineTo(this.X(points[i].x), this.Y(points[i].y)); 42 | this.canvas.stroke(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/painter/group.js: -------------------------------------------------------------------------------- 1 | export default function(obj) { 2 | const groupStyle = {}; 3 | const lineProps = ['Cap', 'Dash', 'DashOffset', 'Join', 'Width']; 4 | 5 | groupStyle.fillStyle = obj.style.fillStyle; 6 | groupStyle.strokeStyle = obj.style.strokeStyle; 7 | 8 | for (let i = 0, _i = lineProps.length; i < _i; i++) { 9 | const p = lineProps[i]; 10 | if (obj[`getLine${p}`] && obj[`getLine${p}`]() !== null) 11 | groupStyle[p] = obj[`getLine${p}`](); 12 | else groupStyle[p] = null; 13 | } 14 | for (let i = 0, _i = obj.group.length; i < _i; i = 0|i+1) { 15 | // this method sets strokeStyle and fillStyle. 16 | const _obj = obj.group[i]; 17 | const NULL = []; 18 | const before = { 19 | fillStyle: NULL, 20 | strokeStyle: NULL, 21 | }; 22 | 23 | if (_obj.style && _obj.style.fillStyle === null) { 24 | before.fillStyle = _obj.style.fillStyle; 25 | _obj.style.fillStyle = groupStyle.fillStyle; 26 | } 27 | if (_obj.style && _obj.style.strokeStyle === null) { 28 | before.strokeStyle = _obj.style.strokeStyle; 29 | _obj.style.strokeStyle = groupStyle.strokeStyle; 30 | } 31 | for (let j = 0, _j = lineProps.length; j < _j; j++) { 32 | const p = lineProps[j]; 33 | if ((!_obj[`getLine${p}`] || _obj[`getLine${p}`]() === null) && groupStyle[p] != null) { 34 | before[p] = _obj[`getLine${p}`](); 35 | _obj[`setLine${p}`](groupStyle[p]); 36 | } else before[p] = NULL; 37 | } 38 | 39 | this.__drawHelper__(_obj); 40 | if (_obj.style && before.fillStyle !== NULL) _obj.style.fillStyle = before.fillStyle; 41 | if (_obj.style && before.strokeStyle !== NULL) _obj.style.fillStyle = before.fillStyle; 42 | for (let j = 0, _j = lineProps.length; j < _j; j++) { 43 | const p = lineProps[j]; 44 | if (before[p] !== NULL) { 45 | _obj[`setLine${p}`](before[p]); 46 | } 47 | } 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/painter/line.js: -------------------------------------------------------------------------------- 1 | import Unitary from '../unitary.js'; 2 | export default function(obj) { 3 | const S = Unitary.Segment, P = Unitary.Point; 4 | const width = this.canvasWidth, height = this.canvasHeight; 5 | 6 | const leftBorder = new S(new P(0, 0), new P(0, height)).move(-this.origin.x, -this.origin.y); 7 | const rightBorder = new S(new P(width, 0), new P(width, height)).move(-this.origin.x, -this.origin.y); 8 | const topBorder = new S(new P(0, 0), new P(width, 0)).move(-this.origin.x, -this.origin.y); 9 | const bottomBorder = new S(new P(0, height), new P(width, height)).move(-this.origin.x, -this.origin.y); 10 | 11 | let leftEndPoint = null, rightEndPoint = null; 12 | if (leftBorder.intersects(obj)) leftEndPoint = leftBorder.toLine().getIntersection(obj); 13 | if (rightBorder.intersects(obj)) rightEndPoint = rightBorder.toLine().getIntersection(obj); 14 | 15 | if (topBorder.intersects(obj) && bottomBorder.intersects(obj)) { 16 | const inter1 = topBorder.toLine().getIntersection(obj); 17 | const inter2 = bottomBorder.toLine().getIntersection(obj); 18 | if (inter1.x > inter2.x) { 19 | leftEndPoint = inter1; 20 | rightEndPoint = inter2; 21 | } else { 22 | leftEndPoint = inter2; 23 | rightEndPoint = inter1; 24 | } 25 | } else if (topBorder.intersects(obj) && !bottomBorder.intersects(obj)) { 26 | const inter = topBorder.toLine().getIntersection(obj); 27 | if (leftEndPoint == null) { 28 | leftEndPoint = inter; 29 | } else { 30 | rightEndPoint = inter; 31 | } 32 | } else if (!topBorder.intersects(obj) && bottomBorder.intersects(obj)) { 33 | const inter = bottomBorder.toLine().getIntersection(obj); 34 | if (rightEndPoint == null) { 35 | rightEndPoint = inter; 36 | } else { 37 | leftEndPoint = inter; 38 | } 39 | } 40 | 41 | if (leftEndPoint != null && rightEndPoint != null) { 42 | this.canvas.beginPath(); 43 | 44 | this.canvas.moveTo(this.X(leftEndPoint.x), this.Y(leftEndPoint.y)); 45 | this.canvas.lineTo(this.X(rightEndPoint.x), this.Y(rightEndPoint.y)); 46 | this.canvas.stroke(); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /src/classes/circularsector.js: -------------------------------------------------------------------------------- 1 | import ContouredObject from './contouredobject.js'; 2 | import {Vector} from './vector.js'; 3 | import {nearlyEquals as eq} from '../utility.js'; 4 | 5 | export default class CircularSector extends ContouredObject { 6 | constructor(center, radius, endAngle, startAngle = 0) { 7 | super(); 8 | this.center = center; 9 | this.r = radius; 10 | this.endAngle = endAngle; 11 | this.startAngle = startAngle; 12 | this.anticlockwise = false; 13 | this._propsToCopy = this._propsToCopy.concat('anticlockwise'); 14 | } 15 | get Origin() { return this.center; } 16 | get radius() { return this.r; } 17 | moveTo(x, y) { 18 | if (this.center.x === x && this.center.y === y) return this; 19 | 20 | return new CircularSector(this.center.moveTo(x, y), this.r, this.endAngle, this.startAngle).copyFrom(this); 21 | } 22 | move(dx, dy) { 23 | if (dx === 0 && dy === 0) return this; 24 | 25 | return new CircularSector(this.center.move(dx, dy), this.r, this.endAngle, this.startAngle).copyFrom(this); 26 | } 27 | rotate(rad) { 28 | if (rad % (2 * Math.PI) === 0) return this; 29 | 30 | return new CircularSector(this.center, this.r, this.endAngle + rad, this.startAngle + rad).copyFrom(this); 31 | } 32 | equals(C) { 33 | const angleCompare = (A, B) => (A - B) % (2 * Math.PI) === 0; 34 | if (!super.equals(C)) { 35 | return false; 36 | } 37 | return this.center.equals(C.center) && eq(this.r, C.r) && angleCompare(this.startAngle, C.startAngle) && angleCompare(this.endAngle, C.endAngle); 38 | } 39 | has(P) { 40 | const theta = Math.atan2(P.y, P.x); 41 | return new Vector(P).subtract(new Vector(this.center)).abs() <= this.r && 42 | this.startAngle <= theta && 43 | theta <= this.endAngle; 44 | } 45 | setAnticlockwise(anticlockwise) { 46 | if (this.anticlockwise === anticlockwise) return this; 47 | 48 | const res = new CircularSector(this.center, this.r, this.endAngle, this.startAngle).copyFrom(this); 49 | res.anticlockwise = anticlockwise; 50 | 51 | return res; 52 | } 53 | name() { return 'CircularSector'; } 54 | } 55 | -------------------------------------------------------------------------------- /src/unitary.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import UnitaryObject from './classes/unitaryobject.js'; 3 | import ContouredObject from './classes/contouredobject.js'; 4 | 5 | import BezierCurve from './classes/beziercurve.js'; 6 | import Circle from './classes/circle.js'; 7 | import CircularSector from './classes/circularsector.js'; 8 | import Curve from './classes/curve.js'; 9 | import Doughnut from './classes/doughnut.js'; 10 | import Ellipse from './classes/ellipse.js'; 11 | import Graph from './classes/graph.js'; 12 | import Group from './classes/group.js'; 13 | import Image_ from './classes/image.js'; 14 | import Line from './classes/line.js'; 15 | import Point from './classes/point.js'; 16 | import Polygon from './classes/polygon.js'; 17 | import Quadrilateral from './classes/quadrilateral.js'; 18 | import Rect from './classes/rect.js'; 19 | import Segment from './classes/segment.js'; 20 | import Text_ from './classes/text.js'; 21 | import Triangle from './classes/triangle.js'; 22 | import {BaseVector, Vector, Vector3D} from './classes/vector.js'; 23 | import {version as VERSION} from '../package.json'; 24 | 25 | export default { 26 | distance: (A, B) => { 27 | let res; 28 | if (A instanceof Point && B instanceof Point) { 29 | return Math.sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y)); 30 | } 31 | if (A instanceof Point && B instanceof Line) { 32 | res = B.a * A.x + B.b * A.y + B.c; 33 | if (res < 0) { 34 | res *= -1; 35 | } 36 | res /= Math.sqrt(B.a * B.a + B.b * B.b); 37 | return res; 38 | } 39 | if (A instanceof Line && B instanceof Point) { 40 | return distance(B, A); 41 | } 42 | }, 43 | UnitaryObject, 44 | ContouredObject, 45 | BaseVector, 46 | BezierCurve, 47 | Circle, 48 | CircularSector, 49 | Curve, 50 | Doughnut, 51 | Donut: Doughnut, 52 | Ellipse, 53 | Graph, 54 | Group, 55 | Image: Image_, 56 | Line, 57 | Point, 58 | Polygon, 59 | Quadrilateral, 60 | Rect, 61 | Segment, 62 | Text: Text_, 63 | Triangle, 64 | Vector3D, 65 | Vector, 66 | XAxis: new Line(new Point(0, 0), new Point(1, 0)), 67 | YAxis: new Line(new Point(0, 0), new Point(0, 1)), 68 | VERSION 69 | }; 70 | -------------------------------------------------------------------------------- /canvasTest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo 6 | 7 | 8 | 9 | 18 | 19 | 20 |
bezier
21 |
circle
22 |
circular sector
23 |
curve
24 |
donut
25 |
ellipse
26 |
graph
27 |
group
28 |
image
29 |
image2
30 |
line
31 |
point
32 |
polygon
33 |
polygon2
34 |
quadrilateral
35 |
rect
36 |
segment
37 |
text
38 |
triangle
39 | 40 | 41 | -------------------------------------------------------------------------------- /graph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 54 | 59 | 60 | 61 |
62 | Scale:
63 | Function:
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/classes/triangle.js: -------------------------------------------------------------------------------- 1 | import Circle from './circle.js'; 2 | import Point from './point.js'; 3 | import Polygon from './polygon.js'; 4 | import Segment from './segment.js'; 5 | import {Vector} from './vector.js'; 6 | 7 | export default class Triangle extends Polygon{ 8 | constructor(A, B, C) { 9 | if ((A == null) || (B == null) || (C == null)) { 10 | throw new Error('Triangle must have three vertices.'); 11 | } 12 | if (A.equals(B) || B.equals(C) || A.equals(C)) { 13 | throw new Error('Triangle must have three vertices.'); 14 | } 15 | super(A, B, C); 16 | } 17 | getCircumcircle() { 18 | const A = this.points[0], 19 | B = this.points[1], 20 | C = this.points[2]; 21 | const AB = new Segment(A, B), 22 | BC = new Segment(B, C), 23 | CA = new Segment(C, A); 24 | const S = this.getArea(); 25 | const vA = new Vector(A.x, A.y), 26 | vB = new Vector(B.x, B.y), 27 | vC = new Vector(C.x, C.y); 28 | const a = Math.pow(BC.length, 2), 29 | b = Math.pow(CA.length, 2), 30 | c = Math.pow(AB.length, 2); 31 | const vO = new Vector(0, 0) 32 | .add(vA.multiple(a * (b + c - a))) 33 | .add(vB.multiple(b * (c + a - b))) 34 | .add(vC.multiple(c * (a + b - c))) 35 | .multiple(1 / (16 * (Math.pow(S, 2)))); 36 | const O = new Point(vO.x, vO.y); 37 | const cosA = vB.subtract(vA).product(vC.subtract(vA)) / (AB.length * CA.length), 38 | sinA = Math.sqrt(1 - Math.pow(cosA, 2)); 39 | const R = BC.length / sinA / 2; 40 | return new Circle(O, R); 41 | } 42 | getIncircle() { 43 | const vA = new Vector(this.points[0].x, this.points[0].y), 44 | vB = new Vector(this.points[1].x, this.points[1].y), 45 | vC = new Vector(this.points[2].x, this.points[2].y); 46 | const a = vC.subtract(vB).abs(), 47 | b = vC.subtract(vA).abs(), 48 | c = vB.subtract(vA).abs(); 49 | const vO = new Vector(0, 0).add(vA.multiple(a / (a + b + c))) 50 | .add(vB.multiple(b / (a + b + c))) 51 | .add(vC.multiple(c / (a + b + c))); 52 | const O = new Point(vO.x, vO.y); 53 | const r = 2 * this.getArea() / (a + b + c); 54 | return new Circle(O, r); 55 | } 56 | getCenter() { 57 | const A = this.points[0], 58 | B = this.points[1], 59 | C = this.points[2]; 60 | return new Point((A.x + B.x + C.x) / 3, (A.y + B.y + C.y) / 3); 61 | } 62 | getArea() { 63 | const A = this.points[0], 64 | B = this.points[1], 65 | C = this.points[2]; 66 | const AB = new Segment(A, B), 67 | AC = new Segment(A, C); 68 | const vAB = new Vector(B.x - A.x, B.y - A.y), 69 | vAC = new Vector(C.x - A.x, C.y - A.y); 70 | const cosA = vAB.product(vAC) / (AB.length * AC.length), 71 | sinA = Math.sqrt(1 - Math.pow(cosA, 2)); 72 | const S = AB.length * AC.length * sinA / 2; 73 | return S; 74 | } 75 | move(dx, dy) { 76 | if (dx === 0 && dy === 0) return this; 77 | 78 | const newObj = super.move(dx, dy); 79 | return new Triangle(...newObj.points).copyFrom(this); 80 | } 81 | rotate(rad, center) { 82 | if (rad % (2 * Math.PI) === 0) return this; 83 | if (typeof center === 'undefined') { 84 | center = this.getCenter(); 85 | } 86 | 87 | const newObj = super.rotate(rad, center); 88 | return new Triangle(...newObj.points).copyFrom(this); 89 | } 90 | name() { return 'Triangle'; } 91 | } 92 | -------------------------------------------------------------------------------- /src/classes/line.js: -------------------------------------------------------------------------------- 1 | import ContouredObject from './contouredobject.js'; 2 | import Point from './point.js'; 3 | import {Vector} from './vector.js'; 4 | import {sign, abs, isInteger, gcd, nearlyEqualsZero, nearlyEquals as eq} from '../utility.js'; 5 | 6 | export default class Line extends ContouredObject { 7 | constructor(A, B) { 8 | if (A.equals(B)) { 9 | throw new Error('A equals B. So AB couldn\'t construct line.'); 10 | } 11 | super(); 12 | this.points = [A, B]; 13 | this.a = B.y - A.y; 14 | this.b = A.x - B.x; 15 | this.c = A.x * (A.y - B.y) - A.y * (A.x - B.x); 16 | if (isInteger(this.a) && isInteger(this.b) && isInteger(this.c)) { 17 | const g = gcd(gcd(this.a, this.b), this.c); 18 | this.a /= g; 19 | this.b /= g; 20 | this.c /= g; 21 | } 22 | if (this.a < 0) { 23 | this.a *= -1; 24 | this.b *= -1; 25 | this.c *= -1; 26 | } 27 | if (this.a === 0) { 28 | this.c /= this.b; 29 | this.b = 1; 30 | } 31 | if (this.b === 0) { 32 | this.c /= this.a; 33 | this.a = 1; 34 | } 35 | // a > 0 || a == 0 && b > 0 36 | } 37 | move(dx, dy) { 38 | if (dx === 0 && dy === 0) return this; 39 | return new Line(this.points[0].move(dx, dy), this.points[1].move(dx, dy)).copyFrom(this); 40 | } 41 | has(P) { 42 | return nearlyEqualsZero(this.a * P.x + this.b * P.y + this.c); 43 | } 44 | getEquation() { 45 | const a = this.a; 46 | const b = this.b; 47 | const c = this.c; 48 | const n = _n => abs(_n) === 1 ? '' : abs(_n); 49 | 50 | let res = ''; 51 | if (a !== 0) res += sign(a) + n(a) + 'x'; 52 | if (b !== 0) res += sign(b) + n(b) + 'y'; 53 | if (c !== 0) res += sign(c) + abs(c); 54 | return res.slice(1) + '=0'; 55 | } 56 | getNormalVector() { 57 | return new Vector(this.a, this.b); 58 | } 59 | getIntersection(CD) { 60 | if (eq(this.a * CD.b, CD.a * this.b)) { 61 | return false; 62 | } 63 | const y = (CD.a * this.c - this.a * CD.c) / (this.a * CD.b - CD.a * this.b); // this.a * CD.b - CD.a * this.b !== 0 64 | let x; 65 | if (this.a === 0) { 66 | // if this.a === 0 && CD.a === 0, this.b and CD.b are 1. 67 | // So CD.a !== 0. 68 | x = -1 * (CD.b * y + CD.c) / CD.a; 69 | } else { 70 | x = -1 * (this.b * y + this.c) / this.a; 71 | } 72 | return new Point(x, y); 73 | } 74 | equals(CD) { 75 | if (!super.equals(CD)) { 76 | return false; 77 | } 78 | const ratio1 = eq(this.a * CD.b, this.b * CD.a); // a:b = a':b' 79 | const ratio2 = eq(this.b * CD.c, this.c * CD.b); // b:c = b':c' 80 | const ratio3 = eq(this.a * CD.c, this.c * CD.a); // a:c = a':c' 81 | // ratio1 && ratio2 equals a:b:c = a':b':c' 82 | return ratio1 && ratio2 && ratio3; 83 | } 84 | isParallelTo(CD) { 85 | if (this.equals(CD)) { 86 | return false; 87 | } 88 | return eq(this.a * CD.b, this.b * CD.a); 89 | } 90 | isPerpendicularTo(CD) { 91 | if (this.equals(CD)) { 92 | return false; 93 | } 94 | return nearlyEqualsZero(this.a * CD.a + this.b * CD.b); 95 | } 96 | name() { return 'Line'; } 97 | static fromVector(_a, _d) { 98 | let a = _a, d = _d; 99 | if (_a.name() === 'Point') a = _a.toVector(); 100 | if (_d.name() === 'Point') d = _d.toVector(); 101 | return new Line(a, a.add(d)); 102 | } 103 | } 104 | Line.prototype.toString = Line.prototype.inspect = Line.prototype.getEquation; 105 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.2 2 | 3 | ## Features 4 | 5 | * add Point.fromPolar(). 6 | * Group now inherits ContouredObject. So you can set fillStyle, strokeStyle, lineWidth and so on to Group object. However, when objects in Group have its style, it will be applied. 7 | 8 | # 0.1.1 9 | Now dist/unitary.js and dist/canvas.js are merged. 10 | 11 | # 0.1.0 12 | 13 | ## Features 14 | 15 | * call .setStyle() to retain its styles. 16 | * support lineDash, lineCap, lineDashOffset, lineJoin and lineWidth. 17 | * add .push() to Group. 18 | * add .clone() to Image, Circle, CircularSector and Ellipse. 19 | * some methods return itself if there is no changes. 20 | * Canvas.fn.add() now accepts some objects. 21 | * add Ellipse class. 22 | * add Curve class. 23 | * Canvas will create canvas element if there is no elements that has the given id. 24 | * Canvas listens basic events. 25 | Now you don't need to write the code such as `canvas.listen('click')`. 26 | 27 | ## Breaking Changes 28 | 29 | * rename .substract to .subtract() 30 | 31 | ## Bug Fixes 32 | 33 | * fix not drawing the part of line that isn't in first quadrant 34 | * fix the algorithm of .rotate() of Point. 35 | 36 | # 0.0.8 37 | 38 | ## Features 39 | 40 | * add .has() to Line. 41 | * add setAnticlockwise() to Circle and CircularSector. 42 | * add Doughnut class. 43 | * add r and theta property to Vector. 44 | 45 | ## Bug Fixes 46 | 47 | * Segment.fn.has() and Segment.fn.intersects() returned wrong value. 48 | 49 | ## Breaking Changes 50 | 51 | * change default step of BezierCurve. 52 | * change property, fillColor and strokeColor to fillStyle and strokeStyle. 53 | * Canvas.draw() will fill objects before stroking them. 54 | 55 | # 0.0.7 56 | From this version, dist/unitary.browser.js is an alias of dist/unitary.js. You should load dist/unitary.js in browser. 57 | 58 | ## Features 59 | 60 | * creating Canvas instance, you doesn't need to wait window's load event. 61 | * add .move() method to Group. 62 | * add BezierCurve class. 63 | * add Line.fromVector() function 64 | * add toVector() method to Point. 65 | * add toPoint() method to Vector. 66 | * add getNormalVector() method to Line. 67 | * add normalize() method to BaseVector. 68 | * add isParalelTo() method to Line. 69 | * add isPerpendcularTo() method to Line. 70 | * change the line painting algorithm to more efficient one. 71 | 72 | ## Bug Fixes 73 | 74 | * intersects() method of Segment sometimes has returned a wrong value. 75 | 76 | ## Breaking Changes 77 | 78 | * simplify properties a, b and c of Line class. This change may cause changing a, b and c of your existing Line instance. 79 | 80 | # 0.0.6 81 | UnitaryJS now controls events! You can handle objects, such as triangle, circle or rect, as DOM Elements! Your game's parts are no longer <div> or <span>! It's objects drawn in canvas! 82 | 83 | ## Features 84 | 85 | * add CircularSector class. 86 | * add center property to Circle and CircularSector. 87 | * add .has() method to Circle, CircularSector, Group and UnitaryObject. UnitaryObject.fn.has() always returns false. 88 | * load an image once and cache it. If you create Image objects that has same source, Image will use a cache. 89 | * add Canvas.preload(). If you don't use Canvas.preload() and load image, Image instance isn't load until Canvas.fn.draw() is called. 90 | * Group constructor now accepts the array of UnitaryObject. 91 | * UnitaryObject can listen events. If you click the triangle, it feels it was clicked! 92 | 93 | ## Breaking Changes 94 | 95 | * Image.fn.trim() and Image.fn.resize() return new Image object that contain changes. These methods changed itself instead of returning new object before. 96 | 97 | # 0.0.5 98 | 0.0.5 includes many bugfixes. 99 | 100 | ## Features 101 | 102 | * add VERSION property to Unitary. 103 | * rename .minus() .substract(). 104 | * canvas.draw() returns Promise. So you can write `canvas.draw().then(function() {canvas.toDataURL();})`. 105 | 106 | ## Bug Fixes 107 | 108 | * throw context.drawImage() Image object.(f6835ee6dd047e2559b9fddd1fba58192d70f2a9) 109 | * replace obj.maxWidth with obj.style.maxWidth. (6369d8d6c8dd16ff5e9b22de8f842f1a7a4e96b9) 110 | * fill Rect in normal mode.(6443d950440b5abb3aa63d016eff0e5340f1ff3b) 111 | * properly draw Rect in normal mode.(7b4baa6a73977537d7e4bca32e5ba63db7a9a06b) 112 | 113 | # 0.0.4 114 | interpret all scripts into TypeScript. 115 | 116 | ## Features 117 | 118 | * add Vector3D class. 119 | * add BaseVector. this class is a parent of Vector and Vector3D. 120 | * add removeAllObjects() to Canvas. removeAllObjects() removes all objects which were added by Canvas.prototype.add() method. 121 | * add clear() to Canvas. this method clears <canvas>. 122 | * canvas.prototype.draw() behaves more naturally when it was thrown NaN. 123 | * add style property. properties, such as font, fillColor, align, are put into style property. 124 | * add getEquation() method to Line class. 125 | * add getEquation() method to Circle class. 126 | * add Group class. 127 | * add getCenter() to Triangle class. 128 | * add rotate() to Polygon. 129 | 130 | ## Bug Fixes 131 | 132 | * Polygon.fn.move() doesn't work, 133 | 134 | # 0.0.3 135 | 136 | ## Features 137 | 138 | * add x axis and y axis. 139 | * overload Vector constructor. Vector can accept Vector as argument. 140 | * add .has() to Polygon. this method judge whether the point given as argument is in the polygon or not. 141 | * change scale property of Graph. 142 | 143 | # 0.0.2 144 | 145 | this version has no change because I edited only .npmignore. 146 | 147 | # 0.0.1 148 | 149 | ## Features 150 | -------------------------------------------------------------------------------- /src/classes/vector.js: -------------------------------------------------------------------------------- 1 | import UnitaryObject from './unitaryobject.js'; 2 | import Point from './point.js'; 3 | import {nearlyEquals as eq} from '../utility.js'; 4 | 5 | class BaseVector extends UnitaryObject { 6 | constructor(...args) { 7 | super(); 8 | if (args.length === 1 && Array.isArray(args[0])) { 9 | this.component = new Array(args[0].length); 10 | for (let i = 0, _i = args[0].length; i < _i; i = 0|i+1) { 11 | this.component[i] = args[0][i]; 12 | } 13 | } else { 14 | this.component = new Array(args.length); 15 | for (let i = 0, _i = args.length; i < _i; i = 0|i+1) { 16 | this.component[i] = args[i]; 17 | } 18 | } 19 | } 20 | add(CD) { 21 | if (this.component.length !== CD.component.length) { 22 | throw new Error('dimention of each vector are different.'); 23 | } 24 | const component = new Array(this.component.length); 25 | for (let i = 0, _i = this.component.length; i < _i; i = 0|i+1) { 26 | component[i] = this.component[i] + CD.component[i]; 27 | } 28 | return new BaseVector(component); 29 | } 30 | subtract(CD) { 31 | return this.add(CD.multiple(-1)); 32 | } 33 | product(CD) { 34 | if (this.component.length !== CD.component.length) { 35 | throw new Error('dimention of each vector are different.'); 36 | } 37 | let product = 0; 38 | for (let i = 0, _i = this.component.length; i < _i; i = 0|i+1) { 39 | product += this.component[i] * CD.component[i]; 40 | } 41 | return product; 42 | } 43 | multiple(k) { 44 | if (k === 1) return this; 45 | const component = new Array(this.component.length); 46 | for (let i = 0, _i = this.component.length; i < _i; i = 0|i+1 ) component[i] = k * this.component[i]; 47 | return new BaseVector(component); 48 | } 49 | abs() { 50 | let res = 0; 51 | for (let i = 0, _i = this.component.length; i < _i; i = 0|i+1) res += this.component[i] * this.component[i]; 52 | return Math.sqrt(res); 53 | } 54 | normalize() { 55 | return this.multiple(1 / this.abs()); 56 | } 57 | equals(B) { 58 | if (this.component.length !== B.component.length) { 59 | return false; 60 | } 61 | for (let i = 0, _i = this.component.length; i < _i; i = 0|i+1) { 62 | if (this.component[i] !== B.component[i]) { 63 | return false; 64 | } 65 | } 66 | return true; 67 | } 68 | move(...component) { 69 | return this.add(new BaseVector(component)); 70 | } 71 | name() { return 'BaseVector'; } 72 | } 73 | class Vector extends BaseVector{ 74 | constructor(...args) { 75 | if (args.length === 2) { 76 | super(args[0], args[1]); 77 | this.x = args[0]; 78 | this.y = args[1]; 79 | } else if (args.length === 1) { 80 | super(args[0].x, args[0].y); 81 | this.x = args[0].x; 82 | this.y = args[0].y; 83 | } else { 84 | throw new Error('unexpected arguments was given.'); 85 | } 86 | this.r = Math.sqrt(this.x * this.x + this.y * this.y); 87 | this.theta = (Math.atan2(this.y, this.x) + 2 * Math.PI) % (2 * Math.PI); 88 | } 89 | add(CD) { 90 | const newVector = super.add(CD); 91 | return new Vector(newVector.component[0], newVector.component[1]); 92 | } 93 | subtract(CD) { 94 | const newVector = super.subtract(CD); 95 | return new Vector(newVector.component[0], newVector.component[1]); 96 | } 97 | multiple(k) { 98 | const newVector = super.multiple(k); 99 | return new Vector(newVector.component[0], newVector.component[1]); 100 | } 101 | abs() { 102 | return this.r; 103 | } 104 | equals(B) { 105 | if (!super.equals(B)) { 106 | return false; 107 | } 108 | return eq(this.x, B.x) && eq(this.y, B.y); 109 | } 110 | toPoint() { 111 | return new Point(this.x, this.y); 112 | } 113 | move(dx, dy) { 114 | if (dx === 0 && dy === 0) return this; 115 | return new Vector(this.x + dx, this.y + dy); 116 | } 117 | name() { return 'Vector'; } 118 | } 119 | class Vector3D extends BaseVector{ 120 | constructor(...args) { 121 | if (args.length === 3) { 122 | super(args[0], args[1], args[2]); 123 | this.x = args[0]; 124 | this.y = args[1]; 125 | this.z = args[2]; 126 | } else if (args.length === 1) { 127 | super(args[0].x, args[0].y, args[0].z); 128 | this.x = args[0].x; 129 | this.y = args[0].y; 130 | this.z = args[0].z; 131 | } else { 132 | throw new Error('unexpected arguments was given.'); 133 | } 134 | } 135 | add(CD) { 136 | const newVector = super.add(CD); 137 | return new Vector3D(newVector.component[0], newVector.component[1], newVector.component[2]); 138 | } 139 | subtract(CD) { 140 | const newVector = super.subtract(CD); 141 | return new Vector3D(newVector.component[0], newVector.component[1], newVector.component[2]); 142 | } 143 | multiple(k) { 144 | const newVector = super.multiple(k); 145 | return new Vector3D(newVector.component[0], newVector.component[1], newVector.component[2]); 146 | } 147 | move(dx, dy, dz) { 148 | if (dx === 0 && dy === 0 && dz === 0) return this; 149 | return new Vector3D(this.x + dx, this.y + dy, this.z + dz); 150 | } 151 | name() { return 'Vector3D'; } 152 | } 153 | 154 | export { BaseVector, Vector, Vector3D }; 155 | -------------------------------------------------------------------------------- /sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Demo 6 | 7 | 8 | 101 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /canvasTest.js: -------------------------------------------------------------------------------- 1 | const {BezierCurve,Circle, CircularSector, Curve, Donut, Ellipse, Graph, Group, Image: Image_, Line, Point, Polygon, Quadrilateral, Rect, Segment, Text: Text_, Triangle, XAxis, YAxis} = Unitary; 2 | 3 | const canvases = {}; 4 | for (const name of ['bezier', 'circle', 'circularSector', 'curve', 'donut', 'ellipse', 'graph', 'group', 'image', 'image2', 'line', 'point', 'polygon', 'polygon2', 'quadrilateral', 'rect', 'segment', 'text', 'triangle']) { 5 | canvases[name] = new Canvas(name); 6 | } 7 | 8 | canvases.bezier.add( 9 | new BezierCurve(new Point(10, 10), new Point(30, 90), new Point(70, 90), new Point(90, 10)).setLineDash([5, 8]), 10 | new BezierCurve(new Point(10, 10), new Point(50, 90), new Point(90, 10)), 11 | new BezierCurve(new Point(10, 10), new Point(10, 90), new Point(50, 90), new Point(90, 90), new Point(90, 180)).setLineDash([5, 8]) 12 | ); 13 | // ==================== 14 | 15 | canvases.circle.add( 16 | new Circle(new Point(30, 30), 30), 17 | new Circle(new Point(60, 90), 20).setFillColor('red').setLineDash([5, 9]) 18 | ); 19 | // ==================== 20 | 21 | canvases.circularSector.listen('click'); 22 | canvases.circularSector.add( 23 | new CircularSector(new Point(30, 30), 30, Math.PI / 2).on('click', () => console.log('click')), 24 | new CircularSector(new Point(30, 30), 30, Math.PI / 2 * 3).move(50, 30).setFillColor('red'), 25 | new CircularSector(new Point(30, 30), 30, Math.PI / 2 * 3).setAnticlockwise(true).move(100, 30).setFillColor('blue') 26 | ); 27 | // ==================== 28 | 29 | { 30 | const scale = 10; 31 | const a = 3; 32 | const r = 0.5; 33 | const cos = Math.cos.bind(Math), sin = Math.sin.bind(Math); 34 | canvases.curve.origin = new Point(100, 100); 35 | canvases.curve.add( 36 | XAxis, 37 | YAxis, 38 | new Curve(theta => a * (1 + cos(theta)) * cos(theta), theta => a * (1 + cos(theta)) * sin(theta), scale), 39 | new Curve(theta => r * cos(theta) * theta, theta => r * sin(theta) * theta, scale).setRange(0, 8 * Math.PI), 40 | new Text_('1', new Point(scale, 6)), 41 | new Rect(new Point(scale, 0), new Point(scale - 1, 5)).setStrokeColor('transparent').setFillColor('#000'), 42 | new Text_('1', new Point(6, scale)), 43 | new Rect(new Point(0, scale), new Point(5, scale - 1)).setStrokeColor('transparent').setFillColor('#000') 44 | ); 45 | } 46 | // ==================== 47 | 48 | canvases.donut.add( 49 | new Donut(new Point(50, 50), 30, 40), 50 | new Donut(new Point(50, 50), 30, 40).moveX(40, 0).setFillColor('red'), 51 | new Donut(new Point(50, 50), 30, 40).moveX(20, 0).setFillColor('blue') 52 | ); 53 | // ==================== 54 | const ellipse1 = new Ellipse(new Point(50, 50), 30, 40).rotate(1/2 * Math.PI).setStrokeColor('blue'); 55 | const ellipse2 = new Ellipse(new Point(50, 50), 30, 40).move(0, 30).rotate(1/2 * Math.PI); 56 | const ellipse3 = new Ellipse(new Point(50, 50), 40, 30).move(30, 0).setFillColor('yellow'); 57 | const ellipse4 = new Ellipse(new Point(50, 50), 40, 40).move(30, 30).setFillColor('red'); 58 | canvases.ellipse.add( 59 | new Donut(new Point(130, 50), 20, 30), 60 | ellipse1, ellipse2, ellipse3, ellipse4, 61 | new Donut(new Point(130, 130), 20, 30) 62 | ); 63 | // ==================== 64 | 65 | canvases.graph.add( 66 | new Graph(x => x * x, 30), 67 | new Graph(x => Math.log(x), 30), 68 | new Text_('1', new Point(27, 6)), 69 | new Rect(new Point(30, 0), new Point(29, 5)).setStrokeColor('transparent').setFillColor('#000'), 70 | new Text_('1', new Point(6, 26)), 71 | new Rect(new Point(0, 30), new Point(5, 29)).setStrokeColor('transparent').setFillColor('#000') 72 | ); 73 | // ==================== 74 | 75 | const starTriangle = new Triangle(new Point(0, 0), new Point(30, 0), new Point(15, 15 * Math.sqrt(3))); 76 | const star = new Group(starTriangle.move(30, 30), starTriangle.move(30, 30).rotate(Math.PI)); 77 | const images = new Group([ 78 | new Image_('logo.png', new Point(0, 180)), 79 | new Image_('logo.png', new Point(20, 160)) 80 | ]); 81 | canvases.group.add(star, images); 82 | // ==================== 83 | 84 | const logoImage = new Image_('logo.png', new Point(0, 0)), 85 | logoText = new Text_('Logo', new Point(0, 0)).setFillColor('#fff').setFont('50px bold').setBaseline('top'), 86 | logoTextBackground = new Rect(new Point(0, 0),new Point(140, 60)).setFillColor('#c00'); 87 | 88 | canvases.image.mode = 'normal'; 89 | canvases.image.add(logoImage, logoTextBackground, logoText); 90 | // ==================== 91 | 92 | canvases.image2.mode = 'normal'; 93 | canvases.image2.add(logoTextBackground, logoText, logoImage); 94 | // ==================== 95 | 96 | canvases.line.add( 97 | new Line(new Point(0, 0), new Point(30, 30)), 98 | new Line(new Point(0, 50), new Point(10, 50)).setStrokeColor('green').setLineDash([15, 5]), 99 | new Line(new Point(50, 50), new Point(50, 0)).setStrokeColor('blue').setLineWidth(3), 100 | new Line(new Point(30, 50), new Point(50, 30)).setStrokeColor('red') 101 | ); 102 | // ==================== 103 | 104 | canvases.point.add( 105 | new Line(new Point(30, 30), new Point(50, 50)), 106 | new Point(30, 30).setFillColor('#f00'), 107 | new Point(50, 30).setFillColor('#f00') 108 | ); 109 | // ==================== 110 | 111 | canvases.polygon.add(new Polygon(new Point(10, 10), new Point(10, 50), new Point(30, 30), new Point(100, 10), new Point(50, 0)).on('click', () => { 112 | canvases.polygon.clear(); 113 | canvases.polygon.objects[0] = canvases.polygon.objects[0].setFillColor('black'); 114 | canvases.polygon.draw(); 115 | setTimeout(() => { 116 | canvases.polygon.clear(); 117 | canvases.polygon.objects[0] = canvases.polygon.objects[0].setFillColor('white'); 118 | canvases.polygon.draw(); 119 | }, 100); 120 | })); 121 | // ==================== 122 | 123 | canvases.polygon2.add(new Polygon(new Point(10, 10), new Point(30, 30), new Point(100, 10), new Point(50, 0), new Point(10, 50))); 124 | // ==================== 125 | 126 | const quadrilateral = new Quadrilateral(new Point(70, 19), new Point(40, 50), new Point(70, 90), new Point(120, 50)); 127 | 128 | canvases.quadrilateral.add( 129 | new Quadrilateral(new Point(10, 10), new Point(10, 50), new Point(30, 30), new Point(100, 10)), 130 | quadrilateral, 131 | quadrilateral.move(30, 0), 132 | quadrilateral.move(0, 50) 133 | ); 134 | // ==================== 135 | 136 | canvases.rect.add( 137 | new Rect(new Point(10, 10), new Point(100, 40)), 138 | new Rect(new Point(20, 50), new Point(110, 80)).setFillColor('#000') 139 | ); 140 | // ==================== 141 | 142 | canvases.segment.add( 143 | new Line(new Point(0, 0), new Point(30, 30)), 144 | new Line(new Point(0, 50), new Point(10, 50)), 145 | new Line(new Point(50, 50), new Point(50, 0)), 146 | new Line(new Point(30, 50), new Point(50, 30)) 147 | ); 148 | 149 | canvases.segment.add( 150 | new Segment(new Point(0, 0), new Point(30, 30)).setStrokeColor('#f00'), 151 | new Segment(new Point(0, 50), new Point(10, 50)).setStrokeColor('#f00'), 152 | new Segment(new Point(50, 50), new Point(50, 0)).setStrokeColor('#f00'), 153 | new Segment(new Point(30, 50), new Point(50, 30)).setStrokeColor('#f00') 154 | ); 155 | // ==================== 156 | 157 | canvases.text.ready.then(() => { 158 | const size = canvases.text.canvas.measureText('あ').width; 159 | canvases.text.mode = 'normal'; 160 | const wagahaiText = '吾輩は猫である。名前はまだ無い。\n' + 161 | 'どこで生れたかとんと見当がつかぬ。\n' + 162 | '何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。\n' + 163 | '吾輩はここで始めて人間というものを見た。\n' + 164 | 'しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。\n' + 165 | 'この書生というのは時々我々を捕えて煮て食うという話である。\n' + 166 | 'しかしその当時は何という考もなかったから別段恐しいとも思わなかった。\n' + 167 | 'ただ彼の掌に載せられてスーと持ち上げられた時何だかフワフワした感じがあったばかりである。'; 168 | const splitWagahai = wagahaiText.split('\n'); 169 | for (let i = 0, _i = splitWagahai.length; i < _i; i++) { 170 | canvases.text.add(new Text_(splitWagahai[i], new Point(0, size * i)).setBaseline('top')); 171 | } 172 | canvases.text.draw(); 173 | }); 174 | // ==================== 175 | 176 | const triangle = new Triangle(new Point(10, 10), new Point(100, 40), new Point(50, 60)); 177 | canvases.triangle.add( 178 | triangle, 179 | triangle.getCircumcircle(), 180 | triangle.getIncircle(), 181 | triangle.move(40, 60).setFillColor('#f90') 182 | ); 183 | // ==================== 184 | 185 | for (const canvas of Object.values(canvases)) { 186 | canvas.draw(); 187 | } 188 | -------------------------------------------------------------------------------- /src/canvas.js: -------------------------------------------------------------------------------- 1 | import Unitary from './unitary.js'; 2 | const __imageCaches = []; 3 | const errorCatcher = e => { 4 | console.log(e); 5 | }; 6 | import BezierCurvePainter from './painter/beziercurve.js'; 7 | import CirclePainter from './painter/circle.js'; 8 | import CircularSectorPainter from './painter/circularsector.js'; 9 | import CurvePainter from './painter/curve.js'; 10 | import DoughnutPainter from './painter/doughnut.js'; 11 | import EllipsePainter from './painter/ellipse.js'; 12 | import GraphPainter from './painter/graph.js'; 13 | import GroupPainter from './painter/group.js'; 14 | import ImagePainter from './painter/image.js'; 15 | import LinePainter from './painter/line.js'; 16 | import PointPainter from './painter/point.js'; 17 | import RectPainter from './painter/rect.js'; 18 | import SegmentPainter from './painter/segment.js'; 19 | import TextPainter from './painter/text.js'; 20 | 21 | class Canvas { 22 | constructor(id, width = 300, height = 300) { 23 | this.ready = Promise.resolve(); 24 | if (document.readyState !== 'complete') this.ready = new Promise(resolve => document.addEventListener('DOMContentLoaded', resolve)); 25 | this.ready.then(() => { 26 | if (document.getElementById(id) === null) { 27 | const canvas = document.createElement('canvas'); 28 | canvas.setAttribute('id', id); 29 | canvas.width = width; 30 | canvas.height = height; 31 | this.element = canvas; 32 | } else this.element = document.getElementById(id); 33 | 34 | this.canvas = this.element.getContext('2d'); 35 | this.canvasWidth = this.element.width; 36 | this.canvasHeight = this.element.height; 37 | }); 38 | this.id = id; 39 | this.objects = []; 40 | this.mode = 'graph'; 41 | this.origin = new Unitary.Point(0, 0); 42 | const basicListeners = ['click', 'mousedown', 'mouseup', 'touchstart', 'touchend', 'keypress', 'keydown', 'keyup']; 43 | for (let i = 0, _i = basicListeners.length; i < _i; i++) { 44 | this.listen(basicListeners[i]); 45 | } 46 | } 47 | listen(type) { 48 | this.ready.then(() => this.element.addEventListener(type, eventTrigger.bind(this), false)); 49 | } 50 | add(...obj) { 51 | [].push.apply(this.objects, obj); 52 | } 53 | removeAllObjects() { 54 | this.objects = []; 55 | } 56 | clear() { 57 | this.ready.then(() => this.canvas.clearRect(0, 0, this.canvasWidth, this.canvasHeight)); 58 | } 59 | X(x) { 60 | return Math.round(x + this.origin.x); 61 | } 62 | Y(y) { 63 | if (this.mode === 'normal') return Math.round(y + this.origin.y); 64 | else return Math.round(this.canvasHeight - (y + this.origin.y)); 65 | } 66 | draw() { 67 | const promises = []; 68 | const load = (src, obj) => { 69 | promises[promises.length] = new Promise((resolve, rejector) => { 70 | let image; 71 | if (!__imageCaches[src]) { 72 | // 画像を読み込む回数を抑える 73 | image = new Image(); 74 | image.src = src; 75 | __imageCaches[src] = image; 76 | } else{ 77 | image = __imageCaches[src]; 78 | } 79 | if (!image.loaded) { 80 | image.addEventListener('load', () => { 81 | obj.__image__ = image; 82 | image.loaded = true; 83 | resolve(); 84 | }); 85 | image.addEventListener('error', rejector); 86 | } else { 87 | obj.__image__ = image; 88 | resolve(); 89 | } 90 | }).catch(errorCatcher); 91 | } 92 | const loadImage = objects => { 93 | for (let i = 0, _i = objects.length; i < _i; i = 0|i+1){ 94 | const obj = objects[i]; 95 | if (obj.name() === 'Image') { 96 | if (!obj.__image__) { 97 | load(obj.src, obj); 98 | } 99 | } else if(obj.name() === 'Group') { 100 | loadImage(obj.group); 101 | } 102 | } 103 | } 104 | loadImage(this.objects); 105 | return Promise.all(promises.concat(this.ready)).then(() => { 106 | for (let i = 0, _i = this.objects.length; i < _i; i = 0|i+1) { 107 | this.__drawHelper__(this.objects[i]); 108 | } 109 | }).catch(errorCatcher); 110 | } 111 | __drawHelper__(obj) { 112 | // this method sets strokeStyle and fillStyle. 113 | const name = obj.name(); 114 | this.canvas.fillStyle = '#000'; 115 | this.canvas.strokeStyle = '#000'; 116 | if (obj.style && obj.style.fillStyle !== null) { 117 | this.canvas.fillStyle = obj.style.fillStyle; 118 | } 119 | if (obj.style && obj.style.strokeStyle !== null) { 120 | this.canvas.strokeStyle = obj.style.strokeStyle; 121 | } 122 | if (this.canvas.setLineDash) { 123 | if (obj.getLineDash && obj.getLineDash() !== null) this.canvas.setLineDash(obj.getLineDash()); 124 | else this.canvas.setLineDash([]); 125 | } 126 | if (obj.getLineCap && obj.getLineCap() !== null) this.canvas.lineCap = obj.getLineCap(); 127 | else this.canvas.lineCap = 'butt'; 128 | 129 | if (obj.getLineDashOffset && obj.getLineDashOffset() !== null) this.canvas.lineDashOffset = obj.getLineDashOffset(); 130 | else this.canvas.lineDashOffset = 0; 131 | 132 | if (obj.getLineJoin && obj.getLineJoin() !== null) this.canvas.lineJoin = obj.getLineJoin(); 133 | else this.canvas.lineJoin = 'miter'; 134 | 135 | if (obj.getLineWidth && obj.getLineWidth() !== null) this.canvas.lineWidth = obj.getLineWidth(); 136 | else this.canvas.lineWidth = 1; 137 | 138 | Canvas.painter[name].call(this, obj); 139 | } 140 | toDataURL() { 141 | return this.element.toDataURL(); 142 | } 143 | static preload(...args) { 144 | const promises = []; 145 | for (let i = 0, _i = args.length; i < _i; i = 0|i+1) { 146 | const src = args[i]; 147 | if (!__imageCaches[src]) { 148 | promises[promises.length] = new Promise((resolve, reject) => { 149 | const image = new Image(); 150 | image.src = src; 151 | image.addEventListener('load', () => { 152 | image.loaded = true; 153 | resolve(); 154 | }); 155 | image.addEventListener('error', reject); 156 | __imageCaches[src] = image; 157 | }).catch(errorCatcher); 158 | } 159 | } 160 | return Promise.all(promises).catch(errorCatcher); 161 | } 162 | }; 163 | function eventTrigger(e) { 164 | const rect = e.target.getBoundingClientRect(); 165 | const x = this.X(e.clientX - rect.left), 166 | y = this.Y(e.clientY - rect.top); 167 | const P = new Unitary.Point(x, y); 168 | 169 | let stopPropagationCalled = false; 170 | e.stopPropagation = () => {stopPropagationCalled = true}; 171 | for (let i = this.objects.length - 1; i >= 0; i = 0|i-1) { 172 | if (stopPropagationCalled) break; 173 | if (this.objects[i].has && this.objects[i].has(P)) { 174 | this.objects[i].trigger(e.type, e); 175 | } 176 | } 177 | }; 178 | Unitary.UnitaryObject.prototype.on = function(name, handler) { 179 | if (!this.handlers) this.handlers = {}; 180 | if (!this.handlers[name]) this.handlers[name] = []; 181 | this.handlers[name].push(handler); 182 | return this; 183 | } 184 | Unitary.UnitaryObject.prototype.trigger = function(name, e) { 185 | if (!this.handlers || !this.handlers[name]) return this; 186 | for (let i = 0, _i = this.handlers[name].length; i < _i; i = 0|i+1) { 187 | this.handlers[name][i](e); 188 | } 189 | return this; 190 | }; 191 | Canvas.painter = {}; 192 | 193 | Canvas.painter.BezierCurve = BezierCurvePainter; 194 | Canvas.painter.Circle = CirclePainter; 195 | Canvas.painter.CircularSector = CircularSectorPainter; 196 | Canvas.painter.Curve = CurvePainter; 197 | Canvas.painter.Doughnut = DoughnutPainter; 198 | Canvas.painter.Ellipse = EllipsePainter; 199 | Canvas.painter.Graph = GraphPainter; 200 | Canvas.painter.Group = GroupPainter; 201 | Canvas.painter.Image = ImagePainter; 202 | Canvas.painter.Line = LinePainter; 203 | Canvas.painter.Point = PointPainter; 204 | Canvas.painter.Rect = RectPainter; 205 | Canvas.painter.Segment = SegmentPainter; 206 | Canvas.painter.Text = TextPainter; 207 | 208 | Canvas.painter.Polygon = PolygonDrawFunction; 209 | Canvas.painter.Quadrilateral = PolygonDrawFunction; 210 | Canvas.painter.Triangle = PolygonDrawFunction; 211 | 212 | function PolygonDrawFunction(obj) { 213 | this.canvas.beginPath(); 214 | this.canvas.moveTo(this.X(obj.points[0].x), this.Y(obj.points[0].y)); 215 | for (let i = 0, _i = obj.points.length; i < _i; i = 0|i+1) 216 | this.canvas.lineTo(this.X(obj.points[i].x), this.Y(obj.points[i].y)); 217 | 218 | this.canvas.closePath(); 219 | if (obj.style.fillStyle !== null) this.canvas.fill(); 220 | this.canvas.stroke(); 221 | }; 222 | export default Canvas; 223 | -------------------------------------------------------------------------------- /__tests__/main.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const Unitary = require('../dist/onlyunitary.js'); 3 | for (const key of Object.keys(Unitary)) { 4 | global[key] = Unitary[key]; 5 | } 6 | global.Image_ = Unitary.Image; 7 | global.Text_ = Unitary.Text; 8 | const P = (...args) => new Point(...args); 9 | describe('Unitary', () => { 10 | const C = new Point(4, 5); 11 | const D = new Point(1, 1); 12 | const AB = new Line(new Point(3, 3), new Point(0, 0)); 13 | const SegAB = new Segment(new Point(3, 3), new Point(0, 0)); 14 | 15 | describe('UnitaryObject', () => { 16 | it('set*', () => { 17 | const obj = new UnitaryObject(); 18 | assert.strictEqual(obj, obj.setFillColor('#fff')); 19 | assert.equal(obj.style.fillStyle, '#fff'); 20 | assert.strictEqual(obj, obj.setFillStyle('#000')); 21 | assert.equal(obj.style.fillStyle, '#000'); 22 | assert.strictEqual(obj, obj.setStrokeColor('#fff')); 23 | assert.equal(obj.style.strokeStyle, '#fff'); 24 | assert.strictEqual(obj, obj.setStrokeStyle('#000')); 25 | assert.equal(obj.style.strokeStyle, '#000'); 26 | }); 27 | it('has', () => { 28 | const obj = new UnitaryObject(); 29 | assert.equal(obj.has(), false); 30 | }); 31 | it('move', () => { 32 | const obj = new UnitaryObject(); 33 | assert.strictEqual(obj, obj.move(300, 300)); 34 | assert.strictEqual(obj, obj.moveX(300)); 35 | assert.strictEqual(obj, obj.moveY(300)); 36 | }); 37 | it('name', () => { 38 | const obj = new UnitaryObject(); 39 | assert.equal(obj.name(), 'UnitaryObject'); 40 | }); 41 | }); 42 | describe('ContouredObject', () => { 43 | it('set*', () => { 44 | const obj = new ContouredObject(); 45 | assert.deepEqual(obj.lineDash, null); 46 | obj.setLineDash([5, 15]); 47 | assert.deepEqual(obj.lineDash, [5, 15]); 48 | 49 | assert.equal(obj.lineCap, null); 50 | obj.setLineCap('round'); 51 | assert.equal(obj.lineCap, 'round'); 52 | 53 | assert.equal(obj.lineDashOffset, null); 54 | obj.setLineDashOffset(5); 55 | assert.equal(obj.lineDashOffset, 5); 56 | 57 | assert.equal(obj.lineJoin, null); 58 | obj.setLineJoin('round'); 59 | assert.equal(obj.lineJoin, 'round'); 60 | 61 | assert.equal(obj.lineWidth, null); 62 | obj.setLineWidth(5); 63 | assert.equal(obj.lineWidth, 5); 64 | }); 65 | it('get*', () => { 66 | const obj = new ContouredObject(); 67 | assert.deepEqual(obj.getLineDash(), obj.lineDash); 68 | assert.equal(obj.getLineCap(), obj.lineCap); 69 | assert.equal(obj.getLineDashOffset(), obj.lineDashOffset); 70 | assert.equal(obj.getLineJoin(), obj.lineJoin); 71 | assert.equal(obj.getLineWidth(), obj.lineWidth); 72 | }); 73 | it('name', () => { 74 | const obj = new ContouredObject(); 75 | assert.equal(obj.name(), 'ContouredObject'); 76 | }); 77 | }); 78 | describe('BezierCurve', () => { 79 | it('constructor', () => { 80 | new BezierCurve(P(0, 0), P(0, 30), P(30, 30), P(30, 0)); 81 | new BezierCurve(P(0, 0), P(0, 30), P(30, 30), P(30, 0), P(60, 0)); 82 | }); 83 | it('name', () => { 84 | assert.equal(new BezierCurve().name(), 'BezierCurve'); 85 | }); 86 | it('setStep', () => { 87 | const bezier = new BezierCurve().setStep(1000); 88 | assert.equal(bezier.step, 1000); 89 | }); 90 | it('move', () => { 91 | const bezier = new BezierCurve(P(0, 0), P(0, 30), P(30, 30), P(30, 0)); 92 | assert.strictEqual(bezier, bezier.move(0, 0)); 93 | assert.ok(bezier !== bezier.move(3, 3)); 94 | }); 95 | }); 96 | describe('Circle', () => { 97 | it('getter', () => { 98 | const c = new Circle(new Point(0, 0), 3); 99 | assert.strictEqual(c.Origin, c.center); 100 | assert.strictEqual(c.radius, c.r); 101 | }); 102 | it('moveTo', () => { 103 | assert.ok(new Circle(new Point(3, 3), 3).moveTo(0, 0).equals(new Circle(new Point(0, 0), 3))); 104 | const c = new Circle(new Point(0, 0), 3); 105 | assert.strictEqual(c, c.moveTo(0, 0)); 106 | }); 107 | it('move', () => { 108 | assert.ok(new Circle(new Point(3, 3), 3).move(109, 31).equals(new Circle(new Point(112, 34), 3))); 109 | const c = new Circle(new Point(0, 0), 3); 110 | assert.strictEqual(c, c.move(0, 0)); 111 | }); 112 | it('getEquation', () => { 113 | assert.equal(new Unitary.Circle(new Unitary.Point(10, 10), 10).getEquation(), '(x-10)^2+(y-10)^2=10^2'); 114 | assert.equal(new Unitary.Circle(new Unitary.Point(-10, 10), 10).getEquation(), '(x+10)^2+(y-10)^2=10^2'); 115 | assert.equal(new Unitary.Circle(new Unitary.Point(10, -10), 10).getEquation(), '(x-10)^2+(y+10)^2=10^2'); 116 | assert.equal(new Unitary.Circle(new Unitary.Point(-10, -10), 10).getEquation(), '(x+10)^2+(y+10)^2=10^2'); 117 | assert.equal(new Unitary.Circle(new Unitary.Point(0, 0), 10).getEquation(), '(x)^2+(y)^2=10^2'); 118 | }); 119 | it('setAnticlockwise', () => { 120 | const c = new Circle(new Point(0, 0), 1); 121 | const new_c = c.setAnticlockwise(true); 122 | assert.equal(c.anticlockwise, false); 123 | assert.equal(new_c.anticlockwise, true); 124 | const moved_new_c = new_c.move(1, 1); 125 | assert.equal(moved_new_c.anticlockwise, true); 126 | assert.strictEqual(c, c.setAnticlockwise(false)); 127 | assert.strictEqual(new_c, new_c.setAnticlockwise(true)); 128 | }); 129 | it('has', () => { 130 | const c = new Circle(new Point(0, 0), 1); 131 | let theta = Math.PI / 3; 132 | const {cos, sin} = Math; 133 | assert.ok(c.has(new Point(cos(theta), sin(theta)))); 134 | theta = Math.PI / 4; 135 | assert.ok(c.has(new Point(cos(theta), sin(theta)))); 136 | assert.ok(c.has(new Point(0, 0))); 137 | }); 138 | it('equals', () => { 139 | const c = new Circle(new Point(0, 0), 3); 140 | assert.equal(c.equals(new UnitaryObject()), false); 141 | assert.ok(c.equals(new Circle(new Point(0, 0), 3))); 142 | }); 143 | it('name', () => { 144 | assert.equal(new Circle(new Point(3, 3), 3).name(), 'Circle'); 145 | }); 146 | }); 147 | describe('CircularSector', () => { 148 | const rad = Math.PI / 2; 149 | it('getter', () => { 150 | const c = new CircularSector(new Point(0, 0), 3, Math.PI / 2, 0); 151 | assert.strictEqual(c.Origin, c.center); 152 | assert.strictEqual(c.radius, c.r); 153 | }); 154 | it('moveTo', () => { 155 | const circular = new CircularSector(new Point(0, 0), 1, Math.PI / 2, 0); 156 | assert.ok(new CircularSector(new Point(3, 3), 3, rad).moveTo(0, 0).equals(new CircularSector(new Point(0, 0), 3, rad))); 157 | assert.strictEqual(circular, circular.moveTo(0, 0)); 158 | }); 159 | it('move', () => { 160 | const circular = new CircularSector(new Point(0, 0), 1, Math.PI / 2, 0); 161 | assert.ok(new CircularSector(new Point(3, 3), 3, rad).move(109, 31).equals(new CircularSector(new Point(112, 34), 3, rad))); 162 | assert.strictEqual(circular, circular.move(0, 0)); 163 | }); 164 | it('rotate', () => { 165 | assert.ok(new CircularSector(new Point(3, 3), 3, rad, 0).rotate(3).equals(new CircularSector(new Point(3, 3), 3, rad + 3, 3))); 166 | assert.ok(new CircularSector(new Point(3, 3), 3, Math.PI / 2, 0).rotate(Math.PI * 2).equals(new CircularSector(new Point(3, 3), 3, Math.PI / 2, 0))); 167 | }); 168 | it('setAnticlockwise', () => { 169 | const c = new CircularSector(new Point(0, 0), 1, Math.PI / 2, 0); 170 | const new_c = c.setAnticlockwise(true); 171 | assert.equal(c.anticlockwise, false); 172 | assert.equal(new_c.anticlockwise, true); 173 | const moved_new_c = new_c.move(1, 1); 174 | assert.equal(moved_new_c.anticlockwise, true); 175 | assert.strictEqual(c, c.setAnticlockwise(false)); 176 | assert.strictEqual(new_c, new_c.setAnticlockwise(true)); 177 | }); 178 | it('has', () => { 179 | const c = new CircularSector(new Point(0, 0), 1, Math.PI / 2, 0); 180 | let theta = Math.PI / 3; 181 | const {cos, sin} = Math; 182 | assert.ok(c.has(new Point(cos(theta), sin(theta)))); 183 | theta = Math.PI; 184 | assert.ok(!c.has(new Point(cos(theta), sin(theta)))); 185 | assert.ok(c.has(new Point(0, 0))); 186 | }); 187 | it('equals', () => { 188 | const c = new CircularSector(new Point(0, 0), 1, Math.PI / 2, 0); 189 | assert.equal(c.equals(new UnitaryObject()), false); 190 | }); 191 | it('name', () => { 192 | assert.equal(new CircularSector(new Point(3, 3), 3).name(), 'CircularSector'); 193 | }); 194 | }); 195 | describe('Curve', () => { 196 | it('move', () => { 197 | const curve = new Curve(()=>{}, ()=>{}); 198 | curve.moveX(); 199 | curve.moveY(); 200 | }); 201 | it('equals', () => { 202 | const curve = new Curve(()=>{}, ()=>{}); 203 | assert.equal(curve.equals(new UnitaryObject()), false); 204 | assert.equal(curve.equals(curve), false); 205 | assert.equal(curve.equals(), false); 206 | }); 207 | it('setRange', () => { 208 | const curve = new Curve(()=>{}, ()=>{}); 209 | assert.equal(curve.start, 0); 210 | assert.equal(curve.end, 2 * Math.PI); 211 | curve.setRange(50, 100); 212 | assert.equal(curve.start, 50); 213 | assert.equal(curve.end, 100); 214 | }); 215 | it('name', () => { 216 | const curve = new Curve(()=>{}, ()=>{}); 217 | assert.equal(curve.name(), 'Curve'); 218 | }); 219 | }); 220 | describe('Doughnut', () => { 221 | it('moveTo', () => { 222 | const doughnut = new Doughnut(new Point(3, 3), 2, 3); 223 | assert.strictEqual(doughnut, doughnut.moveTo(3, 3)); 224 | assert.ok(doughnut.moveTo(0, 0).equals(new Doughnut(new Point(0, 0), 2, 3))); 225 | }); 226 | it('move', () => { 227 | const doughnut = new Doughnut(new Point(0, 0), 2, 3); 228 | assert.strictEqual(doughnut, doughnut.move(0, 0)); 229 | assert.ok(doughnut.move(3, 3).equals(new Doughnut(new Point(3, 3), 2, 3))); 230 | }); 231 | it('equals', () => { 232 | const doughnut = new Doughnut(new Point(0, 0), 2, 3); 233 | assert.equal(doughnut.equals(new UnitaryObject()), false); 234 | assert.ok(doughnut.equals(new Doughnut(new Point(0, 0), 2, 3))); 235 | }); 236 | it('has', () => { 237 | const doughnut = new Doughnut(new Point(0, 0), 2, 3); 238 | const {cos, sin} = Math; 239 | const theta = Math.PI / 3; 240 | assert.ok(!doughnut.has(new Point(cos(theta), sin(theta)))); 241 | assert.ok(doughnut.has(new Point(2 * cos(theta), 2 * sin(theta)))); 242 | assert.ok(doughnut.has(new Point(2.5 * cos(theta), 2.5 * sin(theta)))); 243 | assert.ok(doughnut.has(new Point(3 * cos(theta), 3 * sin(theta)))); 244 | }); 245 | it('name', () => { 246 | const doughnut = new Doughnut(new Point(0, 0), 2, 3); 247 | assert.equal(doughnut.name(), 'Doughnut'); 248 | }); 249 | }); 250 | describe('Ellipse', () => { 251 | const rad = Math.PI / 2; 252 | it('getter', () => { 253 | const ellipse = new Ellipse(new Point(3, 3), 1, 2); 254 | assert.strictEqual(ellipse.Origin, ellipse.center) 255 | }); 256 | it('moveTo', () => { 257 | const ellipse = new Ellipse(new Point(3, 3), 1, 2); 258 | assert.ok(ellipse.moveTo(1, 1).equals(new Ellipse(new Point(1, 1), 1, 2))); 259 | assert.strictEqual(ellipse, ellipse.moveTo(3, 3)); 260 | }); 261 | it('move', () => { 262 | const ellipse = new Ellipse(new Point(3, 3), 1, 2); 263 | assert.ok(ellipse.move(1, 1).equals(new Ellipse(new Point(4, 4), 1, 2))); 264 | assert.strictEqual(ellipse, ellipse.move(0, 0)); 265 | }); 266 | it('rotate', () => { 267 | const ellipse = new Ellipse(new Point(3, 3), 3, 4); 268 | ellipse.angle = 3; 269 | assert.ok(new Ellipse(new Point(3, 3), 3, 4).rotate(3).equals(ellipse)); 270 | assert.strictEqual(ellipse, ellipse.rotate(0)); 271 | assert.strictEqual(ellipse, ellipse.rotate(2 * Math.PI)); 272 | assert.strictEqual(ellipse, ellipse.rotate(4 * Math.PI)); 273 | }); 274 | it('has', () => { 275 | const ellipse1 = new Ellipse(new Point(3, 3), 3, 4); 276 | const ellipse2 = ellipse1.rotate(1 / 2 * Math.PI); 277 | assert.ok(!ellipse1.has(new Point(3, 3).move(3, 3))); 278 | assert.ok(!ellipse1.has(new Point(3, 4).move(3, 3))); 279 | assert.ok(!ellipse1.has(new Point(4, 3).move(3, 3))); 280 | assert.ok(ellipse1.has(new Point(2, 0).move(3, 3))); 281 | assert.ok(ellipse1.has(new Point(3, 0).move(3, 3))); 282 | assert.ok(!ellipse1.has(new Point(4, 0).move(3, 3))); 283 | assert.ok(ellipse1.has(new Point(0, 2).move(3, 3))); 284 | assert.ok(ellipse1.has(new Point(0, 3).move(3, 3))); 285 | assert.ok(ellipse1.has(new Point(0, 4).move(3, 3))); 286 | 287 | assert.ok(!ellipse2.has(new Point(3, 3).move(3, 3))); 288 | assert.ok(!ellipse2.has(new Point(3, 4).move(3, 3))); 289 | assert.ok(!ellipse2.has(new Point(4, 3).move(3, 3))); 290 | assert.ok(ellipse2.has(new Point(2, 0).move(3, 3))); 291 | assert.ok(ellipse2.has(new Point(2, 0).move(3, 3))); 292 | assert.ok(ellipse2.has(new Point(3, 0).move(3, 3))); 293 | assert.ok(ellipse2.has(new Point(4, 0).move(3, 3))); 294 | assert.ok(ellipse2.has(new Point(0, 2).move(3, 3))); 295 | assert.ok(ellipse2.has(new Point(0, 3).move(3, 3))); 296 | assert.ok(!ellipse2.has(new Point(0, 4).move(3, 3))); 297 | }); 298 | it('setAnticlockwise', () => { 299 | const ellipse = new Ellipse(new Point(3, 3), 3, 4); 300 | const new_e = ellipse.setAnticlockwise(true); 301 | assert.equal(ellipse.anticlockwise, false); 302 | assert.equal(new_e.anticlockwise, true); 303 | const moved_new_e = new_e.move(1, 1); 304 | assert.equal(moved_new_e.anticlockwise, true); 305 | assert.strictEqual(ellipse, ellipse.setAnticlockwise(false)); 306 | assert.strictEqual(new_e, new_e.setAnticlockwise(true)); 307 | }); 308 | it('equals', () => { 309 | assert.equal(new Ellipse(new Point(0, 0), 1, 1).equals(new UnitaryObject()), false); 310 | }); 311 | it('name', () => { 312 | assert.equal(new Ellipse(new Point(3, 3), 3, 4).name(), 'Ellipse'); 313 | }); 314 | }); 315 | describe('Graph', () => { 316 | it('equals', () => { 317 | const graph = new Graph(()=>{}); 318 | assert.equal(graph.equals(new UnitaryObject()), false); 319 | assert.equal(graph.equals(graph), false); 320 | assert.equal(graph.equals(), false); 321 | }); 322 | it('move', () => { 323 | const graph = new Graph(()=>{}); 324 | graph.moveX(); 325 | graph.moveY(); 326 | }); 327 | it('setRange', () => { 328 | const graph = new Graph(()=>{}); 329 | assert.equal(graph.start, null); 330 | assert.equal(graph.end, null); 331 | graph.setRange(50, 100); 332 | assert.equal(graph.start, 50); 333 | assert.equal(graph.end, 100); 334 | }); 335 | it('name', () => { 336 | const graph = new Graph(()=>{}); 337 | assert.equal(graph.name(), 'Graph'); 338 | }); 339 | }); 340 | describe('Group', () => { 341 | it('constructor', () => { 342 | const group = new Group(new Segment(P(0, 0), P(1, 1)), new Segment(P(1, 1), P(2, 0))); 343 | const group2 = new Group([new Segment(P(0, 0), P(1, 1)), new Segment(P(1, 1), P(2, 0))]); 344 | }); 345 | it('move', () => { 346 | const {cos, sin} = Math; 347 | const theta = 2 * Math / 5; 348 | const star = new Group( 349 | new Segment( 350 | P(cos(0), sin(0)), 351 | P(cos(theta * 2), sin(theta * 2)) 352 | ), 353 | new Segment( 354 | P(cos(theta * 2), sin(theta * 2)), 355 | P(cos(theta * 4), sin(theta * 4)) 356 | ), 357 | new Segment( 358 | P(cos(theta * 4), sin(theta * 4)), 359 | P(cos(theta * 1), sin(theta * 1)) 360 | ), 361 | new Segment( 362 | P(cos(theta * 1), sin(theta * 1)), 363 | P(cos(theta * 3), sin(theta * 3)) 364 | ), 365 | new Segment( 366 | P(cos(theta * 3), sin(theta * 3)), 367 | P(cos(0), sin(0)) 368 | )); 369 | assert.strictEqual(star, star.move(0, 0)); 370 | const moved = star.move(1, 1); 371 | for (let i = 0; i < moved.group.length; i++) { 372 | assert.ok(star.group[i].move(1, 1).equals(moved.group[i])); 373 | } 374 | }); 375 | it('push', () => { 376 | const group = new Group(new Segment(P(0, 0), P(1, 1)), new Segment(P(1, 1), P(2, 0))); 377 | const pushed = group.push(new Segment(P(2, 0), P(3, 1))); 378 | assert.ok(new Segment(P(2, 0), P(3, 1)).equals(pushed.group[2])); 379 | }); 380 | it('has', () => { 381 | const group = new Group(new Segment(P(0, 0), P(1, 1)), new Segment(P(1, 1), P(2, 0))); 382 | assert.ok(group.has(P(1, 1))); 383 | assert.ok(!group.has(P(3, 0))); 384 | }); 385 | it('name', () => { 386 | assert.equal(new Group().name(), 'Group'); 387 | }); 388 | }); 389 | describe('Image_', () => { 390 | it('equals', () => { 391 | assert.ok(new Image_('./hoge.png', new Point(3, 3)).equals(new Image_('./hoge.png', new Point(3, 3)))); 392 | const img = new Image_('./hoge.png', new Point(3, 3)); 393 | assert.equal(img.equals(new UnitaryObject()), false); 394 | }); 395 | it('trim', () => { 396 | const img = new Image_('./hoge.png', new Point(3, 3)); 397 | const newImg = img.trim(new Point(10, 10), 30, 30); 398 | assert.equal(img.dw, null); 399 | assert.equal(img.dh, null); 400 | assert.equal(img.sw, null); 401 | assert.equal(img.sh, null); 402 | assert.equal(newImg.dw, 30); 403 | assert.equal(newImg.dh, 30); 404 | assert.equal(newImg.sw, 30); 405 | assert.equal(newImg.sh, 30); 406 | assert.equal(newImg.sx, 10); 407 | assert.equal(newImg.sy, 10); 408 | }); 409 | it('resize', () => { 410 | const img = new Image_('./hoge.png', new Point(3, 3)); 411 | const newImg = img.resize(30, 30); 412 | assert.equal(img.dw, null); 413 | assert.equal(img.dh, null); 414 | assert.equal(newImg.dw, 30); 415 | assert.equal(newImg.dh, 30); 416 | }); 417 | it('move', () => { 418 | const img = new Image_('./hoge.png', new Point(3, 3)); 419 | assert.ok( 420 | new Image_('./hoge.png', new Point(3, 3)).move(31, 31).equals( 421 | new Image_('./hoge.png', new Point(3, 3).move(31, 31)) 422 | ) 423 | ); 424 | assert.strictEqual(img, img.move(0, 0)); 425 | }); 426 | it('name', () => { 427 | assert.equal(new Image_('./hoge.png', new Point(3, 3)).name(), 'Image'); 428 | }); 429 | }); 430 | describe('Line', () => { 431 | const line1a = new Line(new Point(0, 0), new Point(1, 0)), line1b = new Line(new Point(0, 10), new Point(1, 10)); // parallel to X axis. 432 | const line2a = new Line(new Point(0, 0), new Point(0, 1)), line2b = new Line(new Point(10, 0), new Point(10, 1)); // parallel to Y axis. 433 | const line3a = new Line(new Point(0, 0), new Point(1, 1)), line3b = new Line(new Point(0, 10), new Point(1, 11)); // y = x 434 | const lines = [line1a, line1b, line2a, line2b, line3a, line3b]; 435 | it('constructor', () => { 436 | assert.throws(() => { 437 | new Line(new Point(0, 0), new Point(0, 0)); 438 | }); 439 | }); 440 | it('move', () => { 441 | assert.ok(new Line(new Point(3, 3), new Point(0, 0)).move(31, 31).equals(new Line(new Point(3, 3).move(31, 31), new Point(0, 0).move(31, 31)))); 442 | const l = new Line(new Point(3, 3), new Point(0, 0)); 443 | assert.strictEqual(l, l.move(0, 0)); 444 | }); 445 | it('getIntersection', () => { 446 | assert.equal(new Line(new Point(0, 0), new Point(0, 10)).getIntersection(new Line(new Point(10,0), new Point(10,10))), false); 447 | assert.equal(new Line(new Point(0, 0), new Point(10, 0)).getIntersection(new Line(new Point(0,10), new Point(10,10))), false); 448 | assert.ok(new Line(new Point(0, 0), new Point(0, 10)).getIntersection(new Line(new Point(5,10), new Point(10,10))).equals(new Point(0, 10))); 449 | assert.equal(new Line(new Point(0, 0), new Point(10, 10)).getIntersection(new Line(new Point(0, 0), new Point(10,10))), false); 450 | assert.ok(new Line(new Point(0, 0), new Point(10, 10)).getIntersection(new Line(new Point(0, 20), new Point(1,19))).equals(new Point(10, 10))); 451 | }); 452 | it('getNormalVector', () => { 453 | const l = new Line(new Point(0, 0), new Point(10, 10)); 454 | const v = l.getNormalVector(); 455 | assert.equal(v.x, 1); 456 | assert.equal(v.y, -1); 457 | 458 | const l2 = new Line(new Point(10, 10), new Point(0, 0)); 459 | const v2 = l2.getNormalVector(); 460 | assert.equal(v2.x, 1); 461 | assert.equal(v2.y, -1); 462 | }); 463 | it('equals', () => { 464 | const l = new Line(new Point(0, 0), new Point(10, 10)); 465 | assert.equal(l.equals(new UnitaryObject()), false); 466 | assert.ok(lines[0].equals(lines[0])); 467 | assert.ok(!lines[0].equals(lines[1])); 468 | assert.ok(!lines[0].equals(lines[2])); 469 | assert.ok(!lines[0].equals(lines[3])); 470 | assert.ok(!lines[0].equals(lines[4])); 471 | assert.ok(!lines[0].equals(lines[5])); 472 | assert.ok(lines[1].equals(lines[1])); 473 | assert.ok(!lines[1].equals(lines[2])); 474 | assert.ok(!lines[1].equals(lines[3])); 475 | assert.ok(!lines[1].equals(lines[4])); 476 | assert.ok(!lines[1].equals(lines[5])); 477 | assert.ok(lines[2].equals(lines[2])); 478 | assert.ok(!lines[2].equals(lines[3])); 479 | assert.ok(!lines[2].equals(lines[4])); 480 | assert.ok(!lines[2].equals(lines[5])); 481 | assert.ok(lines[3].equals(lines[3])); 482 | assert.ok(!lines[3].equals(lines[4])); 483 | assert.ok(!lines[3].equals(lines[5])); 484 | assert.ok(lines[4].equals(lines[4])); 485 | assert.ok(!lines[4].equals(lines[5])); 486 | assert.ok(lines[5].equals(lines[5])); 487 | }); 488 | it('getEquation', () => { 489 | assert.equal(new Line(new Point(1, 44), new Point(68, 12)).toString(), '32x+67y-2980=0'); 490 | assert.equal(new Line(new Point(90, 31), new Point(90, 94)).toString(), 'x-90=0'); 491 | assert.equal(new Line(new Point(31, 90), new Point(94, 90)).toString(), 'y-90=0'); 492 | assert.equal(new Line(new Point(1, -1), new Point(-2, 2)).toString(), 'x+y=0'); 493 | assert.equal(new Line(new Point(1, 0), new Point(0, 1)).getEquation(), 'x+y-1=0'); 494 | assert.equal(new Line(new Point(1, -1), new Point(0, 1)).getEquation(), '2x+y-1=0'); 495 | assert.equal(new Line(new Point(-1, 0), new Point(0, -1)).getEquation(), 'x+y+1=0'); 496 | assert.equal(new Line(new Point(0, -1), new Point(-1, 0)).getEquation(), 'x+y+1=0'); 497 | assert.equal(new Line(new Point(1, 0), new Point(0, -1)).getEquation(), 'x-y-1=0'); 498 | assert.equal(new Line(new Point(-1, 0), new Point(0, 1)).getEquation(), 'x-y+1=0'); 499 | assert.equal(new Line(new Point(-1, 0), new Point(-1, 1)).getEquation(), 'x+1=0'); 500 | assert.equal(new Line(new Point(0, -1), new Point(1, -1)).getEquation(), 'y+1=0'); 501 | }); 502 | it('toString', () => { 503 | assert.strictEqual(Line.prototype.toString, Line.prototype.getEquation); 504 | assert.strictEqual(Line.prototype.inspect, Line.prototype.getEquation); 505 | }) 506 | it('isParallelTo', () => { 507 | assert.equal(line1a.isParallelTo(line1a), false); 508 | assert.equal(line2a.isParallelTo(line2a), false); 509 | 510 | assert.ok(line1a.isParallelTo(line1b)); 511 | assert.ok(line2a.isParallelTo(line2b)); 512 | assert.ok(line3a.isParallelTo(line3b)); 513 | 514 | assert.ok(!line1a.isParallelTo(line2a)); 515 | assert.ok(!line1a.isParallelTo(line2b)); 516 | assert.ok(!line1a.isParallelTo(line3a)); 517 | assert.ok(!line1a.isParallelTo(line3b)); 518 | 519 | assert.ok(!line2a.isParallelTo(line1a)); 520 | assert.ok(!line2a.isParallelTo(line1b)); 521 | assert.ok(!line2a.isParallelTo(line3a)); 522 | assert.ok(!line2a.isParallelTo(line3b)); 523 | 524 | assert.ok(!line3a.isParallelTo(line1a)); 525 | assert.ok(!line3a.isParallelTo(line1b)); 526 | assert.ok(!line3a.isParallelTo(line2a)); 527 | assert.ok(!line3a.isParallelTo(line2b)); 528 | }); 529 | it('isPerpendicularTo', () => { 530 | const line1a = new Line(new Point(0, 0), new Point(1, 0)), line1b = new Line(new Point(0, 10), new Point(1, 10)); 531 | const line2a = new Line(new Point(0, 0), new Point(0, 1)), line2b = new Line(new Point(10, 0), new Point(10, 1)); 532 | const line3a = new Line(new Point(0, 0), new Point(1, 1)), line3b = new Line(new Point(0, 10), new Point(1, 11)); 533 | 534 | assert.equal(line1a.isPerpendicularTo(line1a), false); 535 | assert.equal(line2a.isPerpendicularTo(line2a), false); 536 | assert.equal(line3a.isPerpendicularTo(line3a), false); 537 | 538 | assert.ok(line1a.isPerpendicularTo(line2a)); 539 | assert.ok(line1a.isPerpendicularTo(line2b)); 540 | assert.ok(line1b.isPerpendicularTo(line2a)); 541 | assert.ok(line1b.isPerpendicularTo(line2b)); 542 | 543 | assert.ok(!line1a.isPerpendicularTo(line1b)); 544 | assert.ok(!line2a.isPerpendicularTo(line2b)); 545 | assert.ok(!line3a.isPerpendicularTo(line3b)); 546 | 547 | assert.ok(!line3a.isPerpendicularTo(line1a)); 548 | assert.ok(!line3a.isPerpendicularTo(line1b)); 549 | assert.ok(!line3a.isPerpendicularTo(line2a)); 550 | assert.ok(!line3a.isPerpendicularTo(line2b)); 551 | 552 | }); 553 | it('name', () => { 554 | assert.equal(new Line(new Point(-66, 76), new Point(135, -20)).name(), 'Line'); 555 | }); 556 | it('Line.fromVector', () => { 557 | assert.ok(Line.fromVector(new Point(1, 1), new Point(1, 2)).equals(new Line(new Point(1, 1), new Point(2, 3)))); 558 | assert.ok(Line.fromVector(new Point(1, 1), new Point(1, 2)).equals(new Line(new Point(1, 1), new Point(2, 3)))); 559 | }); 560 | }); 561 | describe('Point', () => { 562 | it('moveTo', () => { 563 | assert.ok(new Point(3, 3).moveTo(9, 9).equals(new Point(9, 9))); 564 | const p = new Point(100, 100); 565 | assert.strictEqual(p, p.moveTo(100, 100)); 566 | }); 567 | it('move', () => { 568 | assert.ok(new Point(3, 3).move(1, 1).equals(new Point(4, 4))); 569 | assert.ok(new Point(3, 3).move(-1, -1).equals(new Point(2, 2))); 570 | const p = new Point(100, 100); 571 | assert.strictEqual(p, p.move(0, 0)); 572 | }); 573 | it('equals', () => { 574 | assert.ok(new Point(3, 3).equals(new Point(3, 3))); 575 | assert.ok(new Point(3, 3).move(3, 3).equals(new Point(3, 3).move(3, 3))); 576 | assert.equal(new Point(0, 0).equals(new Vector(0, 0)), false); 577 | }); 578 | it('name', () => { 579 | assert.equal(new Point(3, 3).name(), 'Point'); 580 | }); 581 | it('toString', () => { 582 | const p = new Point(100, -100); 583 | assert.equal(p.toString(), '(100, -100)'); 584 | }); 585 | it('inspect', () => { 586 | const p = new Point(100, -100); 587 | assert.equal(p.inspect(), '(100, -100)'); 588 | }); 589 | }); 590 | describe('Polygon', () => { 591 | it('name', () => { 592 | assert.equal(new Polygon(new Point(48,84), new Point(86,65), new Point(29,43), new Point(64,48), new Point(68,41)).name(), 'Polygon'); 593 | }); 594 | it('rotate', () => { 595 | const poly = new Polygon(P(0, 0), P(1, 1), P(1, 0)); 596 | const newPoly = poly.rotate(Math.PI / 2, P(0, 0)); 597 | assert.strictEqual(poly, poly.rotate(0, P(0, 0))); 598 | assert.strictEqual(poly, poly.rotate(2 * Math.PI, P(0, 0))); 599 | assert.strictEqual(poly, poly.rotate(4 * Math.PI, P(0, 0))); 600 | assert.ok(poly.points[0].equals(P(0, 0))); 601 | assert.ok(newPoly.points[0].equals(P(0, 0).rotate(Math.PI / 2, P(0, 0)))); 602 | assert.ok(newPoly.points[1].equals(P(1, 1).rotate(Math.PI / 2, P(0, 0)))); 603 | assert.ok(newPoly.points[2].equals(P(1, 0).rotate(Math.PI / 2, P(0, 0)))); 604 | }); 605 | it('has', () => { 606 | const poly = new Polygon(P(0, 0), P(1, 1), P(1, 0)); 607 | assert.ok(poly.has(P(0, 0))); 608 | assert.ok(poly.has(P(0.5, 0.5))); 609 | assert.equal(poly.has(P(0, 1)), false); 610 | }); 611 | it('move', () => { 612 | const poly = new Polygon(P(0, 0), P(1, 1), P(1, 0)); 613 | assert.deepEqual( 614 | new Polygon(new Point(48,84), new Point(86,65), new Point(29,43), new Point(64,48), new Point(68,41)).move(30, 30), 615 | new Polygon(new Point(78,114), new Point(116,95), new Point(59,73), new Point(94,78), new Point(98,71)) 616 | ); 617 | assert.strictEqual(poly, poly.move(0, 0)); 618 | }); 619 | it('equals', () => { 620 | const poly = new Polygon(P(0, 0), P(1, 1), P(1, 0)); 621 | assert.equal(poly.equals(new UnitaryObject()), false); 622 | assert.equal(poly.equals(poly), false); 623 | }); 624 | }); 625 | describe('Quadrilateral', () => { 626 | it('constructor', () => { 627 | assert.throws(() => { 628 | new Quadrilateral(P(0, 0), P(1, 1), P(0, 1), P(1, 0)); 629 | }); 630 | assert.throws(() => { 631 | new Quadrilateral(P(0, 0), P(0, 1), P(1, 0), P(1, 1)); 632 | }); 633 | assert.throws(() => { 634 | new Quadrilateral(P(0, 0), P(1, 1), P(1, 1), P(0, 0)); 635 | }); 636 | assert.throws(() => { 637 | new Quadrilateral(P(0, 0), P(0, 0), P(0, 0), P(0, 0)); 638 | }); 639 | }); 640 | it('getArea', () => { 641 | const nearlyEquals = (a, b) => (0 | a*1e6) / 1e6 === (0 | b*1e6) / 1e6; 642 | assert.ok(nearlyEquals(new Quadrilateral(P(0, 0), P(2, 0), P(1, 1), P(0, 1)).getArea(), (1 + 2) * 1 / 2)); 643 | }); 644 | it('name', () => { 645 | assert.equal(new Quadrilateral(new Point(48,84), new Point(86,65), new Point(64,48), new Point(29,43)).name(), 'Quadrilateral'); 646 | }); 647 | }); 648 | describe('Rect', () => { 649 | it('has', () => { 650 | const rect = new Rect(new Point(48,84), new Point(64,48)); 651 | assert.ok(!rect.has(new Point(30, 30))); 652 | assert.ok(!rect.has(new Point(30, 50))); 653 | assert.ok(!rect.has(new Point(30, 90))); 654 | assert.ok(!rect.has(new Point(50, 30))); 655 | assert.ok(rect.has(new Point(50, 50))); 656 | assert.ok(!rect.has(new Point(50, 90))); 657 | assert.ok(!rect.has(new Point(70, 30))); 658 | assert.ok(!rect.has(new Point(70, 50))); 659 | assert.ok(!rect.has(new Point(70, 90))); 660 | }); 661 | it('move', () => { 662 | const rect = new Rect(new Point(1, 1), new Point(3, 3)); 663 | assert.strictEqual(rect, rect.move(0, 0)); 664 | const dx = 3, dy = 3; 665 | const newRect = rect.move(dx, dy); 666 | assert.equal(rect.points[0].x + dx, newRect.points[0].x); 667 | assert.equal(rect.points[0].y + dy, newRect.points[0].y); 668 | 669 | assert.equal(rect.points[1].x + dx, newRect.points[1].x); 670 | assert.equal(rect.points[1].y + dy, newRect.points[1].y); 671 | }); 672 | it('name', () => { 673 | assert.equal(new Rect(new Point(48,84), new Point(64,48)).name(), 'Rect'); 674 | }); 675 | }); 676 | describe('Segment', () => { 677 | it('move', () => { 678 | assert.ok(SegAB.move(31,19).equals(new Segment(new Point(3, 3).move(31, 19), new Point(0, 0).move(31, 19)))); 679 | const seg = new Segment(P(0, 0), P(1, 1)); 680 | assert.strictEqual(seg, seg.move(0, 0)); 681 | }); 682 | it('has', () => { 683 | const seg = new Segment(new Point(0, 0), new Point(100, 100)); // y = x 684 | const segParallelToXaxis = new Segment(new Point(0, 0), new Point(100, 0)); 685 | const segParallelToYaxis = new Segment(new Point(0, 0), new Point(0, 100)); 686 | 687 | assert.equal(SegAB.has(new Point(3, 3)), true); 688 | assert.equal(SegAB.has(new Point(0, 0)), true); 689 | assert.equal(SegAB.has(C), false); 690 | assert.equal(SegAB.has(D), true); 691 | 692 | assert.ok(seg.has(new Point(0, 0))); 693 | assert.ok(!seg.has(new Point(100, 0))); 694 | assert.ok(!seg.has(new Point(50, 0))); 695 | assert.ok(!seg.has(new Point(-50, 0))); 696 | assert.ok(!seg.has(new Point(0, 100))); 697 | assert.ok(seg.has(new Point(100, 100))); 698 | assert.ok(seg.has(new Point(50, 50))); 699 | 700 | assert.ok(segParallelToXaxis.has(new Point(0, 0))); 701 | assert.ok(segParallelToXaxis.has(new Point(100, 0))); 702 | assert.ok(segParallelToXaxis.has(new Point(50, 0))); 703 | assert.ok(!segParallelToXaxis.has(new Point(-50, 0))); 704 | assert.ok(!segParallelToXaxis.has(new Point(0, 100))); 705 | assert.ok(!segParallelToXaxis.has(new Point(100, 100))); 706 | 707 | assert.ok(segParallelToYaxis.has(new Point(0, 0))); 708 | assert.ok(segParallelToYaxis.has(new Point(0, 100))); 709 | assert.ok(segParallelToYaxis.has(new Point(0, 50))); 710 | assert.ok(!segParallelToYaxis.has(new Point(0, -50))); 711 | assert.ok(!segParallelToYaxis.has(new Point(100, 0))); 712 | assert.ok(!segParallelToYaxis.has(new Point(100, 100))); 713 | }); 714 | it('intersects', () => { 715 | const A = new Point(10, 10), B = new Point(10, 50), C = new Point(30, 30), D = new Point(100, 10); 716 | const AB = new Segment(A, B); 717 | const AC = new Segment(A, C); 718 | const AD = new Segment(A, D); 719 | const BC = new Segment(B, C); 720 | const BD = new Segment(B, D); 721 | const CD = new Segment(C, D); 722 | assert.equal(new Segment(new Point(48,84), new Point(86,65)).intersects(new Segment(new Point(64,48), new Point(29,43))), false); 723 | assert.equal(new Segment(new Point(48,84), new Point(64,48)).intersects(new Segment(new Point(86,65), new Point(29,43))), true); 724 | 725 | assert.ok(AB.intersects(AC)); 726 | assert.ok(AB.intersects(AD)); 727 | assert.ok(AB.intersects(BC)); 728 | assert.ok(AB.intersects(BD)); 729 | assert.ok(!AB.intersects(CD)); 730 | 731 | assert.ok(AC.intersects(AD)); 732 | assert.ok(AC.intersects(BC)); 733 | assert.ok(!AC.intersects(BD)); 734 | assert.ok(AC.intersects(CD)); 735 | 736 | assert.ok(!AD.intersects(BC)); 737 | assert.ok(AD.intersects(BD)); 738 | assert.ok(AD.intersects(CD)); 739 | 740 | assert.ok(BC.intersects(BD)); 741 | assert.ok(BC.intersects(CD)); 742 | 743 | assert.ok(BD.intersects(CD)); 744 | }); 745 | it('toLine', () => { 746 | assert.equal(new Segment(new Point(48,84), new Point(86,65)).toLine().toString(), 'x+2y-216=0'); 747 | }); 748 | it('equals', () => { 749 | assert.ok(new Segment(new Point(48,84), new Point(64,48)).equals(new Segment(new Point(48,84), new Point(64,48)))); 750 | const seg = new Segment(P(0, 0), P(1, 1)); 751 | assert.equal(seg.equals(new UnitaryObject()), false); 752 | }); 753 | it('name', () => { 754 | assert.equal(SegAB.name(), 'Segment'); 755 | }); 756 | }); 757 | describe('Text_', () => { 758 | it('strokeOutline', () => { 759 | const text = new Text_('Text', new Point(0, 0)); 760 | assert.equal(text.strokesOutline, false); 761 | text.strokeOutline(); 762 | assert.equal(text.strokesOutline, true); 763 | }); 764 | it('setAlign', () => { 765 | const text = new Text_('Text', new Point(0, 0)); 766 | assert.equal(text.style.align, 'left'); 767 | text.setAlign('center'); 768 | assert.equal(text.style.align, 'center'); 769 | }); 770 | it('setOutlineColor', () => { 771 | const text = new Text_('Text', new Point(0, 0)); 772 | assert.equal(text.style.outlineColor, '#000'); 773 | text.setOutlineColor('#fff'); 774 | assert.equal(text.style.outlineColor, '#fff'); 775 | }); 776 | it('setBaseline', () => { 777 | const text = new Text_('Text', new Point(0, 0)); 778 | assert.equal(text.style.baseline, 'alphabetic'); 779 | text.setBaseline('middle'); 780 | assert.equal(text.style.baseline, 'middle'); 781 | }); 782 | it('setFont', () => { 783 | const text = new Text_('Text', new Point(0, 0)); 784 | assert.equal(text.style.font, null); 785 | text.setFont('sans-serif'); 786 | assert.equal(text.style.font, 'sans-serif'); 787 | }); 788 | it('name', () => { 789 | assert.equal(new Text_('Text', new Point(3, 3)).name(), 'Text'); 790 | }); 791 | }); 792 | describe('Triangle', () => { 793 | const EquilateralTriangle = new Triangle(new Point(0, 0), new Point(30, 0), new Point(30 * Math.cos(Math.PI*(1/3)), 30 * Math.sin(Math.PI*(1/3)))); 794 | it('constructor', () => { 795 | assert.throws(() => { 796 | new Triangle(new Point(1, 1), new Point(1, 1), new Point(2, 2)); 797 | new Triangle(new Point(1, 1), new Point(2, 2), new Point(1, 1)); 798 | new Triangle(new Point(2, 2), new Point(1, 1), new Point(1, 1)); 799 | }); 800 | assert.throws(() => { 801 | new Triangle(new Point(1, 1), new Point(2, 2)); 802 | }); 803 | }); 804 | it('getCircumcircle', () => { 805 | const {cos, sin, PI} = Math; 806 | const triangle = new Triangle( P(0, 0), P(30, 0), P(30*cos(PI/3), 30*sin(PI/3)) ); 807 | assert.ok(triangle.getCircumcircle().equals( 808 | new Circle(new Point(15, 15 / Math.sqrt(3)), 15 / Math.sqrt(3) * 2) 809 | )); 810 | }); 811 | it('getIncircle', () => { 812 | const {cos, sin, PI} = Math; 813 | const triangle = new Triangle( P(0, 0), P(30, 0), P(30*cos(PI/3), 30*sin(PI/3)) ); 814 | assert.ok(triangle.getIncircle().equals( 815 | new Circle(new Point(15, 15 / Math.sqrt(3)), 15 / Math.sqrt(3)) 816 | )); 817 | }); 818 | it('getArea', () => { 819 | assert.equal(EquilateralTriangle.getArea(), 30 * 30 * (Math.sqrt(3) / 4)); 820 | }); 821 | it('name', () => { 822 | assert.equal(EquilateralTriangle.name(), 'Triangle'); 823 | }); 824 | }); 825 | describe('BaseVector', () => { 826 | it('add', () => { 827 | assert.throws(() => { 828 | new BaseVector(0, 0).add(new BaseVector(0, 0, 0)); 829 | }); 830 | }); 831 | it('product', () => { 832 | assert.throws(() => { 833 | new BaseVector(0, 0).product(new BaseVector(0, 0, 0)); 834 | }); 835 | }); 836 | it('multiple', () => { 837 | const v = new BaseVector(0, 0); 838 | assert.strictEqual(v, v.multiple(1)); 839 | }); 840 | it('move', () => { 841 | const v = new BaseVector(0, 0); 842 | assert.ok(v.move(1, 0).equals(new BaseVector(1, 0))); 843 | }); 844 | it('equals', () => { 845 | const v1 = new BaseVector(0, 0); 846 | const v2 = new BaseVector(0, 0, 0); 847 | assert.equal(v1.equals(v2), false); 848 | }); 849 | it('name', () => { 850 | assert.equal(new BaseVector(0, 0).name(), 'BaseVector'); 851 | }); 852 | }); 853 | describe('Vector', () => { 854 | it('constructor', () => { 855 | const components = [1, 2]; 856 | const v = new Vector(...components); 857 | const v_from_v = new Vector(v); 858 | const v_from_p = new Vector(new Point(...components)); 859 | assert.ok(v.equals(v_from_v)); 860 | assert.ok(v.equals(v_from_p)); 861 | assert.throws(() => { 862 | new Vector(1, 2, 3); 863 | }); 864 | }); 865 | it('add', () => { 866 | assert.ok(new Vector(new Point(3, 3).x - new Point(0, 0).x, new Point(3, 3).y - new Point(0, 0).y).add(new Vector(C.x - D.x, C.y - D.y)).equals(new Vector(6, 7))); 867 | assert.ok(new Vector(3, 4).add(new Vector(-10, 7)).equals(new Vector(-7, 11))); 868 | }); 869 | it('subtract', () => { 870 | assert.ok(new Vector(new Point(3, 3).x, new Point(3, 3).y).subtract(new Vector(new Point(0, 0).x, new Point(0, 0).y)).equals(new Vector(new Point(3, 3).x - new Point(0, 0).x, new Point(3, 3).y - new Point(0, 0).y))); 871 | }); 872 | it('product', () => { 873 | assert.equal(new Vector(3, 3).product(new Vector(-3, 3)), 0); 874 | assert.equal(new Vector(1, 3).product(new Vector(7, 10)), 37); 875 | }); 876 | it('multiple', () => { 877 | assert.ok(new Vector(48, 84).multiple(86).equals(new Vector(4128, 7224))); 878 | }); 879 | it('abs', () => { 880 | assert.equal(new Vector(48, 84).abs(), Math.sqrt(48*48 + 84*84)); 881 | }); 882 | it('equals', () => { 883 | assert.ok(new Vector(new Point(0, 0).x - new Point(3, 3).x, new Point(0, 0).y - new Point(3, 3).y).equals(new Vector(-3, -3))); 884 | }); 885 | it('normalize', () => { 886 | assert.ok(new Vector(3, 0).normalize().equals(new Vector(1, 0))); 887 | assert.strictEqual(1, new Vector(3, 3).normalize().abs()); 888 | }); 889 | it('move', () => { 890 | const v = new Vector(48, 84); 891 | assert.ok(v.move(7, 79).equals(new Vector(55, 163))); 892 | assert.strictEqual(v, v.move(0, 0)); 893 | }); 894 | it('toPoint', () => { 895 | assert.ok(new Point(3, 3).equals(new Vector(3, 3).toPoint())); 896 | }); 897 | it('name', () => { 898 | assert.equal(new Vector(48, 84).name(), 'Vector'); 899 | }); 900 | }); 901 | describe('Vector3D', () => { 902 | it('constructor', () => { 903 | const v = new Vector3D(1, 2, 3); 904 | const v_from_v = new Vector3D(v); 905 | assert.ok(v.equals(v_from_v)); 906 | assert.throws(() => { 907 | new Vector3D(1, 2, 3, 4); 908 | }); 909 | }); 910 | it('add', () => { 911 | assert.ok(new Vector3D(96, 57, 81).add(new Vector3D(42, 74, 55)).equals(new Vector3D(138, 131, 136))); 912 | }); 913 | it('subtract', () => { 914 | assert.ok(new Vector3D(96, 57, 81).subtract(new Vector3D(42, 74, 55)).equals(new Vector3D(54, -17, 26))); 915 | }); 916 | it('product', () => { 917 | assert.equal(new Vector3D(3, 3, 3).product(new Vector3D(-3, 3, 0)), 0); 918 | assert.equal(new Vector3D(34, 39, 90).product(new Vector3D(17, 59, 60)), 8279); 919 | }); 920 | it('multiple', () => { 921 | assert.ok(new Vector3D(90, 19, 20).multiple(64).equals(new Vector3D(5760, 1216, 1280))); 922 | }); 923 | it('abs', () => { 924 | assert.equal(new Vector3D(64, 33, 38).abs(), Math.sqrt(6629)); 925 | }); 926 | it('equals', () => { 927 | assert.ok(new Vector3D(64, 33, 38).equals(new Vector3D(64, 33, 38))); 928 | }); 929 | it('move', () => { 930 | assert.ok(new Vector3D(96, 57, 81).move(42, 74, 55).equals(new Vector3D(138, 131, 136))); 931 | const v = new Vector3D(1, 2, 3); 932 | assert.strictEqual(v, v.move(0, 0, 0)); 933 | }); 934 | it('name', () => { 935 | assert.equal(new Vector3D(96, 57, 81).name(), 'Vector3D'); 936 | }); 937 | }); 938 | }); 939 | --------------------------------------------------------------------------------