Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Support ellipses #19

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/examples/example-simple/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# example-simple
[example.html](./example.html) | [example.ts](./example.ts)

An example that creates a single editor and adds it to the document.
8 changes: 8 additions & 0 deletions docs/examples/example-simple/build-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"bundledFiles": [
{
"name": "jsdraw",
"inPath": "./example.ts"
}
]
}
24 changes: 24 additions & 0 deletions docs/examples/example-simple/example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<title>Simple Example | js-draw examples</title>
<style>
.imageEditorContainer {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;

width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
Loading...
</body>
<script src="./example.bundle.js"></script>
</html>
31 changes: 31 additions & 0 deletions docs/examples/example-simple/example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as jsdraw from 'js-draw';
import 'js-draw/styles';

const editor = new jsdraw.Editor(document.body);
editor.addToolbar();

// TODO: DERIVED FROM MDN. REPLACE!!!
// See original: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
editor.loadFromSVG(`
<svg width="300" height="300" xmlns="http://www.w3.org/2000/svg">
<path d="M 10 315
L 110 215
A 30 50 0 0 1 162.6 162.5
L 172.6 152.5
A 30 50 -45 0 1 215 110
L 315 10" stroke="red" stroke-width="3"/>
<path d="M 110 215 l 1 1" stroke="green" stroke-width="6"/>
<path d="M 162.55 162.45 l 1 1" stroke="orange" stroke-width="6"/>
<path d="M 172.55 152.45 l 1 1" stroke="brown" stroke-width="6"/>
<path d="M 215.1 109.9 l 1 1" stroke="purple" stroke-width="6"/>
</svg>
`);

/*
M 10 315
L 110 215
A 30 50 0 0 1 162.55 162.45
L 172.55 152.45
A 30 50 -45 0 1 215.1 109.9
L 315 10
*/
19 changes: 19 additions & 0 deletions docs/examples/example-simple/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@js-draw/example-simple",
"version": "0.0.1",
"description": "Simple example",
"license": "MIT",
"private": true,
"scripts": {
"bundle": "ts-node scripts/bundle.ts",
"watchBundle": "ts-node scripts/watchBundle.ts",
"build": "build-tool build",
"watch": "build-tool watch"
},
"dependencies": {
"js-draw": "^0.23.1"
},
"devDependencies": {
"@js-draw/build-tool": "^0.0.1"
}
}
7 changes: 7 additions & 0 deletions docs/examples/example-simple/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../../tsconfig.json",

"include": [
"**/*.ts",
]
}
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions packages/js-draw/src/math/Mat33.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,4 +241,41 @@ describe('Mat33 tests', () => {
0, 0, 1,
));
});

describe('should find correct eigenvalues and eigenvectors of 2x2 submatricies', () => {
it('should return eigenvalue 1 for identity matrix', () => {
const eigvals = Mat33.identity.mat22EigenValues();
expect(eigvals).toHaveLength(1);
expect(eigvals[0]).toBeCloseTo(1);

// Even though the identity matrix has one eigenvalue, it should be observed that
// any vector is an eigenvector. Thus, it should have (at least) the coordinate axes.
const eigvecs = Mat33.identity.mat22EigenVectors();
expect(eigvecs).toHaveLength(2);
expect(eigvecs[0]).objEq(Vec2.of(1, 0));
expect(eigvecs[1]).objEq(Vec2.of(0, 1));
});

it('should return no eigenvalues/eigenvectors for a small rotation', () => {
const mat = Mat33.zRotation(Math.PI / 4);
expect(mat.mat22EigenValues()).toHaveLength(0);
expect(mat.mat22EigenVectors()).toHaveLength(0);
});

it('a scaling and shearing matrix should have a single eigenvalue', () => {
const mat = new Mat33(
2, 1, 0,
0, 2, 0,
0, 0, 1
);

const eigvals = mat.mat22EigenValues();
expect(eigvals).toHaveLength(1);
expect(eigvals[0]).toBeCloseTo(2);

const eigvecs = mat.mat22EigenVectors();
expect(eigvecs).toHaveLength(1);
expect(eigvecs[0]).objEq(Vec2.of(0, -1));
});
});
});
71 changes: 71 additions & 0 deletions packages/js-draw/src/math/Mat33.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Point2, Vec2 } from './Vec2';
import Vec3 from './Vec3';
import solveQuadratic from './polynomial/solveQuadratic';

export type Mat33Array = [
number, number, number,
Expand Down Expand Up @@ -221,6 +222,76 @@ export default class Mat33 {
);
}

/**
* Returns the eigenvalues of the top left corner of this matrix.
*/
public mat22EigenValues(): [number,number]|[number]|[] {
// If A is the 2x2 submatrix in the top-left corner of this, we want
// to find all vectors v such that
// ∃λ∈ℝ s.t. Av = λv.
// Using Algebra,
// Av = λv ⟹ Av - λv = 0
// ⟹ Av - λIv = 0
// ⟹ (A - λI)v = 0
// ⟹ v ∈ Kernel(A - λI)
// We're intereseted in λ for which Kernel(A - λI) ≠ ∅,
// thus, where A - λI is not invertable, hence,
// 0 = det(A - λI)
// ⎡ a1 a2 ⎤ ⎡ λ 0 ⎤
// = det( ⎣ b1 b2 ⎦ - ⎣ 0 λ ⎦ )
// ⎡ a1-λ a2 ⎤
// = det ⎣ b1 b2-λ ⎦
// = (a1-λ)(b2-λ) - (a2)(b1)
// = a1 b2 - λ b2 - λ a1 + λ² - (a2)(b1)
// = λ² + (λ)(-a1 - b2) + a1 b2 - a2 b1
// Solving gives the eigenvalues.
const [ lambda1, lambda2 ] = solveQuadratic(
1, -this.a1 - this.b2, this.a1 * this.b2 - this.a2 * this.b1
);

// No solutions
if (isNaN(lambda1) && isNaN(lambda2)) {
return [];
}

// One solution (float equality check is okay here because solveQuadratic
// produces two solutions that are *exactly* the same).
if (lambda1 === lambda2) {
return [lambda1];
}

return [ lambda1, lambda2 ];
}

/** Returns the unit eigenvectors of the top left 2x2 submatrix of this. */
public mat22EigenVectors(): Vec2[] {
const eigvals = this.mat22EigenValues();
const eigvecs = [];

for (const eigval of eigvals) {
let row1 = Vec2.of(this.a1 - eigval, this.a2);
const row2 = Vec2.of(this.b1, this.b2 - eigval);

if (row1.eq(Vec2.zero)) {
row1 = row2;
}

// Because an eigenvalue, λ, corresponds to a case where
// A - λI is singular, we must have that row1 is a multiple of row2.
// Hence, for all φ ∈ ℝ,
// φ v.x + φ v.y = 0 is equivalent to the system of equations above.

if (row1.eq(Vec2.zero)) {
eigvecs.push(Vec2.unitX);
eigvecs.push(Vec2.unitY);
} else {
eigvecs.push(Vec2.of(row1.x, -row1.y).normalized());
}
}

return eigvecs;
}

/** @returns true iff this is the identity matrix. */
public isIdentity(): boolean {
if (this === Mat33.identity) {
Expand Down
3 changes: 2 additions & 1 deletion packages/js-draw/src/math/Vec3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export default class Vec3 {
/**
* Return this' angle in the XY plane (treats this as a Vec2).
*
* This is equivalent to `Math.atan2(vec.y, vec.x)`.
* This is equivalent to `Math.atan2(vec.y, vec.x)`. Thus, the output angle is in the range
* `(-pi, pi]`.
*/
public angle(): number {
return Math.atan2(this.y, this.x);
Expand Down
3 changes: 2 additions & 1 deletion packages/js-draw/src/math/polynomial/solveQuadratic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
* Solves an equation of the form ax² + bx + c = 0.
* The larger solution is returned first.
*
* It is possible that the two solutions returned by this function are the same.
* If there are no solutions, returns `[NaN, NaN]`. If there is one solution,
* repeats the solution twice in the result.
*/
const solveQuadratic = (a: number, b: number, c: number): [number, number] => {
// See also https://en.wikipedia.org/wiki/Quadratic_formula
Expand Down
2 changes: 1 addition & 1 deletion packages/js-draw/src/math/shapes/Abstract2DShape.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Point2 } from '../Vec2';
import Rect2 from './Rect2';

abstract class Abstract2DShape {
protected static readonly smallValue = 1e-12;
protected static readonly smallValue = 1e-13;

/**
* @returns the distance from `point` to this shape. If `point` is within this shape,
Expand Down
Loading