diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..096746c --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +/node_modules/ \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 10899fd..5b1989a 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -24,4 +24,9 @@ module.exports = { { allowConstantExport: true }, ], }, + settings: { + react: { + version: "detect", + } + } }; diff --git a/.github/workflows/code.yml b/.github/workflows/code.yml index 0909b01..95ce987 100644 --- a/.github/workflows/code.yml +++ b/.github/workflows/code.yml @@ -25,10 +25,13 @@ jobs: fetch-depth: 0 - name: Install - run: npm ci + run: npm install - name: Unit Tests run: npm run test + - name: lint + run: npm run lint + - name: Prettier - run: npx prettier --check './src/' \ No newline at end of file + run: npx prettier --check './src/' diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..16c5cc8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM registry.hub.docker.com/library/node:18-alpine as base + +ENV APP_DIR /dedi-web + +COPY . ${APP_DIR} +WORKDIR ${APP_DIR} + +RUN npm install +RUN npm run build + +CMD ["npm", "run", "preview"] \ No newline at end of file diff --git a/README.md b/README.md index be42478..05413dc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,31 @@ # Dedi-web -A browser tool to calculate the beam centering parameters. Like dedi within dawn science. +[![dedi-web code CI](https://github.com/tizayi/dedi-web/actions/workflows/code.yml/badge.svg)](https://github.com/tizayi/dedi-web/actions/workflows/code.yml) + +A browser tool to calculate the beam centering parameters. Like dedi within dawn science. Built using react and vite. + +## Start dev server + +Start up + +```bash + cd dedi-web + npm run dev +``` + +Start up in a container + +```bash + cd dedi-web + docker build -t dedi-web . + docker run -d dedi-web +``` + +## Test + +Run tests with vitest + +```bash + cd dedi-web + npm run test +``` diff --git a/package.json b/package.json index e2d9fa8..1b07b8a 100644 --- a/package.json +++ b/package.json @@ -35,4 +35,4 @@ "vite": "^4.4.5", "vitest": "^0.34.1" } -} +} \ No newline at end of file diff --git a/src/calculations/ray.ts b/src/calculations/ray.ts index 59639f5..66c69bd 100644 --- a/src/calculations/ray.ts +++ b/src/calculations/ray.ts @@ -2,100 +2,148 @@ import Vector2D from "./vector"; import NumericRange from "./numericRange"; export class Ray { - direction: Vector2D; - initial_point: Vector2D; - constructor(direction: Vector2D, initial_point: Vector2D) { - if (direction.length() == 0) - throw TypeError( - "The direction vector of a ray cannot be the zero vector.", - ); - this.direction = direction; - this.initial_point = initial_point; + direction: Vector2D; + initial_point: Vector2D; + constructor(direction: Vector2D, initial_point: Vector2D) { + if (direction.length() == 0) + throw TypeError( + "The direction vector of a ray cannot be the zero vector.", + ); + this.direction = direction; + this.initial_point = initial_point; + } + + getPoint(scalar: number): Vector2D | null { + if (scalar < 0) return null; + const result = new Vector2D(this.direction.x, this.direction.y); + result.scale(scalar); + result.add(this.initial_point); + return result; + } + + getPointAtDistance(distance: number): Vector2D | null { + return this.getPoint(distance / this.direction.length()); + } + + getParameterRange(t1: number, t2: number): NumericRange | null { + if (t1 < 0 && t2 < 0) return null; + + let tMin = Math.min(t1, t2); + const tMax = Math.max(t1, t2); + + if (tMin < 0) tMin = 0; + + return new NumericRange(tMin, tMax); + } + + private getConicIntersectionParameterRange( + coeffOfx2: number, + coeffOfxy: number, + coeffOfy2: number, + coeffOfx: number, + coeffOfy: number, + constant: number, + ) { + let t1: number; + let t2: number; + + const a = + coeffOfx2 * Math.pow(this.direction.x, 2) + + coeffOfxy * this.direction.x * this.direction.y + + coeffOfy2 * Math.pow(this.direction.y, 2); + const b = + 2 * coeffOfx2 * this.direction.x * this.initial_point.x + + coeffOfxy * + (this.direction.x * this.initial_point.y + + this.direction.y * this.initial_point.x) + + 2 * coeffOfy2 * this.direction.y * this.initial_point.y + + coeffOfx * this.direction.x + + coeffOfy * this.direction.y; + const c = + coeffOfx2 * Math.pow(this.initial_point.x, 2) + + coeffOfxy * this.initial_point.x * this.initial_point.y + + coeffOfy2 * Math.pow(this.initial_point.y, 2) + + coeffOfx * this.initial_point.x + + coeffOfy * this.initial_point.y + + constant; + + const discriminant = Math.pow(b, 2) - 4 * a * c; + if (discriminant < 0) return null; + if (a == 0) { + if (b == 0) + return c == 0 ? new NumericRange(0, Number.POSITIVE_INFINITY) : null; + t1 = -c / b; + t2 = -c / b; + } else { + t1 = (0.5 * (-b - Math.sqrt(discriminant))) / a; + t2 = (0.5 * (-b + Math.sqrt(discriminant))) / a; } - - getPoint(scalar: number): Vector2D | null { - if (scalar < 0) return null; - const result = new Vector2D(this.direction.x, this.direction.y); - result.scale(scalar); - result.add(this.initial_point); - return result; - } - - getPointAtDistance(distance: number): Vector2D | null { - return this.getPoint(distance / this.direction.length()); + return this.getParameterRange(t1, t2); + } + + private getEllipseIntersectionParameterRange( + a: number, + b: number, + centre: Vector2D, + ) { + const coeffOfx2 = 1 / Math.pow(a, 2); + const coeffOfy2 = 1 / Math.pow(b, 2); + const coeffOfx = (-2 * centre.x) / Math.pow(a, 2); + const coeffOfy = (-2 * centre.y) / Math.pow(b, 2); + const constant = + Math.pow(centre.x, 2) / Math.pow(a, 2) + + Math.pow(centre.y, 2) / Math.pow(b, 2) - + 1; + + return this.getConicIntersectionParameterRange( + coeffOfx2, + 0, + coeffOfy2, + coeffOfx, + coeffOfy, + constant, + ); + } + + public getCircleIntersectionParameterRange(radius: number, centre: Vector2D) { + return this.getEllipseIntersectionParameterRange(radius, radius, centre); + } + + public getRectangleIntersectionParameterRange( + topLeftCorner: Vector2D, + width: number, + height: number, + ): NumericRange | null { + let result: NumericRange | null; + + const xmax = topLeftCorner.x + width; + const xmin = topLeftCorner.x; + const ymax = topLeftCorner.y; + const ymin = topLeftCorner.y - height; + + if (this.direction.x === 0) { + if (!new NumericRange(xmin, xmax).containsValue(this.initial_point.x)) + return null; + result = new NumericRange(0, Number.POSITIVE_INFINITY); + } else + result = new NumericRange( + (xmin - this.initial_point.x) / this.direction.x, + (xmax - this.initial_point.x) / this.direction.x, + ); + + if (this.direction.y == 0) { + if (!new NumericRange(ymin, ymax).containsValue(this.initial_point.y)) + return null; + return this.getParameterRange(result.min, result.max); } - getParameterRange(t1: number, t2: number): NumericRange | null { - if (t1 < 0 && t2 < 0) return null; - - let tMin = Math.min(t1, t2); - const tMax = Math.max(t1, t2); - - if (tMin < 0) tMin = 0; - - return new NumericRange(tMin, tMax); - } - - private getConicIntersectionParameterRange(coeffOfx2: number, coeffOfxy: number, coeffOfy2: number, coeffOfx: number, coeffOfy: number, constant: number) { - let t1: number; - let t2: number; - - const a = coeffOfx2 * Math.pow(this.direction.x, 2) + coeffOfxy * this.direction.x * this.direction.y + - coeffOfy2 * Math.pow(this.direction.y, 2); - const b = 2 * coeffOfx2 * this.direction.x * this.initial_point.x + coeffOfxy * (this.direction.x * this.initial_point.y + this.direction.y * this.initial_point.x) + - 2 * coeffOfy2 * this.direction.y * this.initial_point.y + coeffOfx * this.direction.x + coeffOfy * this.direction.y; - const c = coeffOfx2 * Math.pow(this.initial_point.x, 2) + coeffOfxy * this.initial_point.x * this.initial_point.y + coeffOfy2 * Math.pow(this.initial_point.y, 2) + - coeffOfx * this.initial_point.x + coeffOfy * this.initial_point.y + constant; - - const discriminant = Math.pow(b, 2) - 4 * a * c; - if (discriminant < 0) return null; - if (a == 0) { - if (b == 0) return (c == 0) ? new NumericRange(0, Number.POSITIVE_INFINITY) : null; - t1 = -c / b; - t2 = -c / b; - } else { - t1 = 0.5 * (-b - Math.sqrt(discriminant)) / a; - t2 = 0.5 * (-b + Math.sqrt(discriminant)) / a; - } - return this.getParameterRange(t1, t2); - } - - private getEllipseIntersectionParameterRange(a: number, b: number, centre: Vector2D) { - const coeffOfx2 = 1 / Math.pow(a, 2); - const coeffOfy2 = 1 / Math.pow(b, 2); - const coeffOfx = -2 * centre.x / Math.pow(a, 2); - const coeffOfy = -2 * centre.y / Math.pow(b, 2); - const constant = Math.pow(centre.x, 2) / Math.pow(a, 2) + Math.pow(centre.y, 2) / Math.pow(b, 2) - 1; - - return this.getConicIntersectionParameterRange(coeffOfx2, 0, coeffOfy2, coeffOfx, coeffOfy, constant); - } - - public getCircleIntersectionParameterRange(radius: number, centre: Vector2D) { - return this.getEllipseIntersectionParameterRange(radius, radius, centre); - } - - public getRectangleIntersectionParameterRange(topLeftCorner: Vector2D, width: number, height: number): NumericRange | null { - let result: NumericRange | null; - - const xmax = topLeftCorner.x + width; - const xmin = topLeftCorner.x; - const ymax = topLeftCorner.y; - const ymin = topLeftCorner.y - height; - - if (this.direction.x === 0) { - if (!new NumericRange(xmin, xmax).containsValue(this.initial_point.x)) return null; - result = new NumericRange(0, Number.POSITIVE_INFINITY); - } else - result = new NumericRange((xmin - this.initial_point.x) / this.direction.x, (xmax - this.initial_point.x) / this.direction.x); - - if (this.direction.y == 0) { - if (!new NumericRange(ymin, ymax).containsValue(this.initial_point.y)) return null; - return this.getParameterRange(result.min, result.max); - } - - result = result.intersect(new NumericRange((ymin - this.initial_point.y) / this.direction.y, (ymax - this.initial_point.y) / this.direction.y)); - - return this.getParameterRange(result!.min, result!.max); - } + result = result.intersect( + new NumericRange( + (ymin - this.initial_point.y) / this.direction.y, + (ymax - this.initial_point.y) / this.direction.y, + ), + ); + return this.getParameterRange(result!.min, result!.max); + } } diff --git a/src/calculations/vector.test.ts b/src/calculations/vector.test.ts index b41e982..72df63f 100644 --- a/src/calculations/vector.test.ts +++ b/src/calculations/vector.test.ts @@ -1,21 +1,20 @@ import { expect, test } from "vitest"; import Vector2D from "./vector"; +test("Vector inplace addition", () => { + const vector1 = new Vector2D(1, 1); + const vector2 = new Vector2D(1, 1); + vector1.add(vector2); + expect(vector1.equals(new Vector2D(2, 2))); +}); -test('Vector inplace addition', () => { - const vector1 = new Vector2D(1, 1) - const vector2 = new Vector2D(1, 1) - vector1.add(vector2) - expect(vector1.equals(new Vector2D(2, 2))) -}) - -test('Vector scale addition', () => { - const vector1 = new Vector2D(2, 3) - vector1.scale(2) - expect(vector1.equals(new Vector2D(4, 6))) -}) +test("Vector scale addition", () => { + const vector1 = new Vector2D(2, 3); + vector1.scale(2); + expect(vector1.equals(new Vector2D(4, 6))); +}); test("Vector length", () => { - const vector1 = new Vector2D(2, 2) - expect(vector1.length()).toBe(2.8284271247461903); -}) \ No newline at end of file + const vector1 = new Vector2D(2, 2); + expect(vector1.length()).toBe(2.8284271247461903); +}); diff --git a/src/calculations/vector.ts b/src/calculations/vector.ts index 0161ced..6a5217e 100644 --- a/src/calculations/vector.ts +++ b/src/calculations/vector.ts @@ -1,31 +1,30 @@ - export default class Vector2D { - x: number; - y: number; - constructor(x: number, y: number) { - this.x = x; - this.y = y; - } + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } - add(vector: Vector2D): void { - this.x += vector.x; - this.y += vector.y; - } + add(vector: Vector2D): void { + this.x += vector.x; + this.y += vector.y; + } - scale(value: number): void { - this.y *= value; - this.x *= value; - } + scale(value: number): void { + this.y *= value; + this.x *= value; + } - length(): number { - return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)); - } + length(): number { + return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)); + } - equals(other: Vector2D): boolean { - return this.x === other.x && this.y === other.y - } + equals(other: Vector2D): boolean { + return this.x === other.x && this.y === other.y; + } - toString(): string { - return `(x:${this.x}, y:${this.y})` - } + toString(): string { + return `(x:${this.x}, y:${this.y})`; + } } diff --git a/tsconfig.json b/tsconfig.json index ad1f246..d429999 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,11 +2,7 @@ "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, - "lib": [ - "ES2020", - "DOM", - "DOM.Iterable" - ], + "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, /* Bundler mode */ @@ -23,13 +19,10 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": [ - "src", - "tests" - ], + "include": ["src", "tests"], "references": [ { "path": "./tsconfig.node.json" } ] -} \ No newline at end of file +} diff --git a/tsconfig.node.json b/tsconfig.node.json index 42872c5..7974a2c 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -4,7 +4,10 @@ "skipLibCheck": true, "module": "ESNext", "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "strictNullChecks": true, }, - "include": ["vite.config.ts"] -} + "include": [ + "vite.config.ts" + ] +} \ No newline at end of file