Skip to content

Commit

Permalink
Move functions from protractor to math/ (#1394)
Browse files Browse the repository at this point in the history
These math functions weren't tested, and had some lurking bugs which I've
now fixed.

Issue: LEMS-2135

## Test plan:

`yarn test`

Author: benchristel

Reviewers: jeremywiebe, mark-fitzgerald, nishasy

Required Reviewers:

Approved By: jeremywiebe

Checks: ✅ codecov/project, ✅ codecov/patch, ✅ Upload Coverage (ubuntu-latest, 20.x), ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Jest Coverage (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ gerald

Pull Request URL: #1394
  • Loading branch information
benchristel authored Jul 8, 2024
1 parent caf7148 commit 8ae3d18
Show file tree
Hide file tree
Showing 10 changed files with 78 additions and 27 deletions.
5 changes: 5 additions & 0 deletions .changeset/new-eyes-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus": patch
---

Internal: move angle functions to math/angle.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {vec} from "mafs";
import * as React from "react";

import {calculateAngleInDegrees} from "../../math";
import {useTransformVectorsToPixels} from "../use-transform";
import {calculateAngleInDegrees} from "../utils";

import {Arrowhead} from "./arrowhead";
import {SVGLine} from "./svg-line";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import type {CollinearTuple} from "../../../perseus-types";
import type {Interval, vec} from "mafs";

export function calculateAngleInDegrees([x, y]: vec.Vector2) {
return (Math.atan2(y, x) * 180) / Math.PI;
}

/**
* Given a ray and a rectangular box, find the point where the ray intersects
* the edge of the box. Assumes the `initialPoint` is inside the box.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@ import {lockedFigureColors} from "../../../perseus-types";
import {Arrowhead} from "../graphs/components/arrowhead";
import {Vector} from "../graphs/components/vector";
import {useTransformVectorsToPixels} from "../graphs/use-transform";
import {
calculateAngleInDegrees,
getIntersectionOfRayWithBox,
} from "../graphs/utils";
import {X, Y} from "../math";
import {getIntersectionOfRayWithBox} from "../graphs/utils";
import {X, Y, calculateAngleInDegrees} from "../math";

import type {LockedLineType} from "../../../perseus-types";
import type {Interval} from "mafs";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {calculateAngleInDegrees} from "./angles";

describe("calculateAngleInDegrees", () => {
it.each`
x | y | expected | note
${0} | ${0} | ${0} | ${": zero vector has an angle of zero"}
${1} | ${0} | ${0} | ${""}
${1} | ${-0} | ${-0} | ${": a y-coord of -0 produces -0deg"}
${0} | ${1} | ${90} | ${""}
${-1} | ${0} | ${180} | ${""}
${-1} | ${-0} | ${-180} | ${": a y-coord of -0 produces -180deg"}
${0} | ${-1} | ${-90} | ${""}
`("returns $expected degrees given [$x, $y]$note", (params) => {
const {expected, x, y} = params;
expect(calculateAngleInDegrees([x, y])).toBe(expected);
});
});
11 changes: 11 additions & 0 deletions packages/perseus/src/widgets/interactive-graphs/math/angles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import type {vec} from "mafs";

// This file contains helper functions for working with angles.

export function convertDegreesToRadians(degrees: number): number {
return (degrees / 180) * Math.PI;
}

// Returns a value between -180 and 180, inclusive. The angle is measured
// between the positive x-axis and the given vector.
export function calculateAngleInDegrees([x, y]: vec.Vector2): number {
return (Math.atan2(y, x) * 180) / Math.PI;
}

export function polar(r: number | vec.Vector2, th: number): vec.Vector2 {
if (typeof r === "number") {
r = [r, r];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@ export {snap} from "./snap";
export {inset, clampToBox} from "./box";
export {X, Y} from "./coordinates";
export {MIN, MAX, size} from "./interval";
export {findAngle, polar} from "./angles";
export {lerp} from "./interpolation";
export {
calculateAngleInDegrees,
convertDegreesToRadians,
findAngle,
polar,
} from "./angles";
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {lerp} from "./interpolation";

describe("lerp", () => {
it.each`
a | b | fraction | expected | note
${3} | ${7} | ${0} | ${3} | ${""}
${3} | ${7} | ${1} | ${7} | ${""}
${3} | ${7} | ${0.5} | ${5} | ${": midpoint of 3 and 7"}
${0} | ${4} | ${0.25} | ${1} | ${": 25% of the way between 0 and 4"}
${4} | ${0} | ${0.25} | ${3} | ${""}
${0} | ${4} | ${2} | ${4} | ${": fraction > 1 is treated as 1"}
${0} | ${4} | ${-1} | ${0} | ${": fraction < 0 is treated as 0"}
`("given $a, $b, $fraction, returns $expected$note", (params) => {
const {a, b, fraction, expected} = params;
expect(lerp(a, b, fraction)).toBe(expected);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {clamp} from "./clamp";

// [L]inear Int[erp]olation: gets the weighted average of two values `a` and
// `b`, with the given `fraction` specifying how much weight `a` and `b` get:
// - if `fraction` is 0, `lerp` returns `a`.
// - if `fraction` is 0.5, `lerp` returns the average of `a` and `b`.
// - if `fraction` is 1, `lerp` returns `b`.
export function lerp(a: number, b: number, fraction: number): number {
return (b - a) * clamp(fraction, 0, 1) + a;
}
24 changes: 8 additions & 16 deletions packages/perseus/src/widgets/interactive-graphs/protractor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import {pathBuilder} from "../../util/svg";

import {useDraggable} from "./graphs/use-draggable";
import {useTransformVectorsToPixels} from "./graphs/use-transform";
import {calculateAngleInDegrees} from "./graphs/utils";
import {X, Y} from "./math";
import {
calculateAngleInDegrees,
convertDegreesToRadians,
lerp,
X,
Y,
} from "./math";
import useGraphConfig from "./reducer/use-graph-config";
import {bound, TARGET_SIZE} from "./utils";

Expand Down Expand Up @@ -84,7 +89,7 @@ export function Protractor() {
function RotationArrow() {
const radius = 175;
const angleDeg = 10;
const angleRad = degreesToRadians(angleDeg);
const angleRad = convertDegreesToRadians(angleDeg);
const endX = radius * (1 - Math.cos(angleRad));
const endY = radius * -Math.sin(angleRad);
const rotationArrow = pathBuilder()
Expand Down Expand Up @@ -171,16 +176,3 @@ function useDraggablePx(args: {
{target, eventOptions: {passive: false}},
);
}

// [L]inear Int[erp]olation: gets the weighted average of two values `a` and
// `b`, with the given `fraction` specifying how much weight `a` and `b` get:
// - if `fraction` is 0, `lerp` returns `a`.
// - if `fraction` is 0.5, `lerp` returns the average of `a` and `b`.
// - if `fraction` is 1, `lerp` returns `b`.
function lerp(a: number, b: number, fraction: number): number {
return (b - a) * fraction + a;
}

function degreesToRadians(degrees) {
return (degrees / 180) * Math.PI;
}

0 comments on commit 8ae3d18

Please sign in to comment.