Skip to content

Commit d498765

Browse files
authored
Move categorizer scoring logic (#2102)
* move categorizer scoring logic * Move NumericInput and InputNumber scoring logic to `perseus-score` (#2105) * move numeric-input and input-number * respond to Jeremy's feedback * Move Radio scorer/validator (#2106) * move radio scorer/validator * lint errorToString better * comment my unique type * respond to Jeremy's feedback * Move scoring logic: CSProgram, Iframe, and Dropdown (#2111) * cs-program * move dropdown * move iframe * Move scoring logic: Table, NumberLine, Matcher (#2112) * move table * number-line * move matcher * Move scorers: Plotter and Sorter (#2113) * plotter * sorter * Move scorer: Orderer (#2114) * orderer * Move scorerer: LabelImage (#2115) * move label-image * rename labelImageScoreMarker * Move scoring logic: Matrix (#2116) * move matrix * Move scoring logic: Expression (#2118) * move expression scorer * respond to Ben's feedback * merge Grapher move * fix conflict again * Move scorer: Grapher (#2119) * move matrix * move expression scorer * move grapher scorer * respond to Ben's feedback * Move scorer: Interactive Graph (#2120) * STOPSHIP some type errors still * add back duplicate declarations * add back Line duplicate * all tests passing * Revert changes to underscore's isEqual (#2125) ## Summary: [Original comment](#2113 (comment)) I made a separate PR because I made this mistake in a couple of PRs so I thought I'd knock them out all at once. Issue: LEMS-2737 ## Test plan: Author: handeyeco Reviewers: jeremywiebe, benchristel Required Reviewers: Approved By: jeremywiebe Checks: ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ❌ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ⏹️ [cancelled] Publish npm snapshot (ubuntu-latest, 20.x), ⏹️ [cancelled] Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ⏹️ [cancelled] Check builds for changes in size (ubuntu-latest, 20.x), ⏹️ [cancelled] Cypress (ubuntu-latest, 20.x), ⏹️ [cancelled] Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ⏹️ [cancelled] Publish npm snapshot (ubuntu-latest, 20.x), ⏹️ [cancelled] Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ⏹️ [cancelled] Check builds for changes in size (ubuntu-latest, 20.x), ⏹️ [cancelled] Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ⏹️ [cancelled] Cypress (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x) Pull Request URL: #2125 * respond to Jeremy's feedback
1 parent fe112da commit d498765

File tree

144 files changed

+1618
-1606
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

144 files changed

+1618
-1606
lines changed

config/test/custom-matchers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Ok here we are
22

3-
import type {PerseusScore} from "../../packages/perseus/src/types";
3+
import type {PerseusScore} from "../../packages/perseus-score/src/validation.types";
44

55
declare global {
66
// eslint-disable-next-line @typescript-eslint/no-namespace

dev/flipbook.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@ import {
3939
} from "./flipbook-model";
4040
import {Header} from "./header";
4141

42-
import type {APIOptions, PerseusScore} from "../packages/perseus/src";
42+
import type {APIOptions} from "../packages/perseus/src";
4343
import type {
4444
InteractiveGraphWidget,
4545
PerseusRenderer,
4646
PerseusWidget,
4747
} from "../packages/perseus-core/src/data-schema";
48+
import type {PerseusScore} from "../packages/perseus-score/src/validation.types";
4849
import type {PropsFor} from "@khanacademy/wonder-blocks-core";
50+
4951
import "../packages/perseus/src/styles/perseus-renderer.less";
5052

5153
const exampleCommands = `

packages/perseus/src/widgets/interactive-graphs/math/angles.ts packages/kmath/src/angles.ts

+6-10
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
1-
import {clockwise} from "../../../util/geometry";
1+
// This file contains helper functions for working with angles.
22

3-
import type {Coord} from "@khanacademy/perseus";
4-
import type {vec} from "mafs";
3+
import {clockwise} from "./geometry";
54

6-
// This file contains helper functions for working with angles.
5+
import type {Coord} from "@khanacademy/perseus-core";
76

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

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

1817
// Converts polar coordinates to cartesian. The th(eta) parameter is in degrees.
19-
export function polar(r: number | vec.Vector2, th: number): vec.Vector2 {
18+
export function polar(r: number | Coord, th: number): Coord {
2019
if (typeof r === "number") {
2120
r = [r, r];
2221
}
@@ -26,10 +25,7 @@ export function polar(r: number | vec.Vector2, th: number): vec.Vector2 {
2625
// This function calculates the angle between two points and an optional vertex.
2726
// If the vertex is not provided, the angle is measured between the two points.
2827
// This does not account for reflex angles or clockwise position.
29-
export const getAngleFromVertex = (
30-
point: vec.Vector2,
31-
vertex: vec.Vector2,
32-
): number => {
28+
export const getAngleFromVertex = (point: Coord, vertex: Coord): number => {
3329
const x = point[0] - vertex[0];
3430
const y = point[1] - vertex[1];
3531
if (!x && !y) {

packages/kmath/src/coefficients.ts

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import type {SineCoefficient} from "./geometry";
2+
import type {Coord} from "@khanacademy/perseus-core";
3+
4+
export type NamedSineCoefficient = {
5+
amplitude: number;
6+
angularFrequency: number;
7+
phase: number;
8+
verticalOffset: number;
9+
};
10+
11+
// TODO: there's another, very similar getSinusoidCoefficients function
12+
// they should probably be merged
13+
export function getSinusoidCoefficients(
14+
coords: ReadonlyArray<Coord>,
15+
): SineCoefficient {
16+
// It's assumed that p1 is the root and p2 is the first peak
17+
const p1 = coords[0];
18+
const p2 = coords[1];
19+
20+
// Resulting coefficients are canonical for this sine curve
21+
const amplitude = p2[1] - p1[1];
22+
const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
23+
const phase = p1[0] * angularFrequency;
24+
const verticalOffset = p1[1];
25+
26+
return [amplitude, angularFrequency, phase, verticalOffset];
27+
}
28+
29+
export type QuadraticCoefficient = [number, number, number];
30+
31+
// TODO: there's another, very similar getQuadraticCoefficients function
32+
// they should probably be merged
33+
export function getQuadraticCoefficients(
34+
coords: ReadonlyArray<Coord>,
35+
): QuadraticCoefficient {
36+
const p1 = coords[0];
37+
const p2 = coords[1];
38+
const p3 = coords[2];
39+
40+
const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
41+
if (denom === 0) {
42+
// Many of the callers assume that the return value is always defined.
43+
// @ts-expect-error - TS2322 - Type 'undefined' is not assignable to type 'QuadraticCoefficient'.
44+
return;
45+
}
46+
const a =
47+
(p3[0] * (p2[1] - p1[1]) +
48+
p2[0] * (p1[1] - p3[1]) +
49+
p1[0] * (p3[1] - p2[1])) /
50+
denom;
51+
const b =
52+
(p3[0] * p3[0] * (p1[1] - p2[1]) +
53+
p2[0] * p2[0] * (p3[1] - p1[1]) +
54+
p1[0] * p1[0] * (p2[1] - p3[1])) /
55+
denom;
56+
const c =
57+
(p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] +
58+
p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] +
59+
p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) /
60+
denom;
61+
return [a, b, c];
62+
}
File renamed without changes.

packages/perseus/src/util/geometry.ts packages/kmath/src/geometry.ts

+12-13
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
* A collection of geomtry-related utility functions
33
*/
44

5-
import {number as knumber, point as kpoint, sum} from "@khanacademy/kmath";
5+
import {
6+
approximateDeepEqual,
7+
approximateEqual,
8+
type Coord,
9+
} from "@khanacademy/perseus-core";
610
import _ from "underscore";
711

8-
import Util from "../util";
9-
10-
import type {Coord, Line} from "../interactive2/types";
12+
import {number as knumber, point as kpoint, sum} from "@khanacademy/kmath";
1113

12-
const {eq, deepEq} = Util;
14+
type Line = [Coord, Coord];
1315

1416
// This should really be a readonly tuple of [number, number]
1517
export type Range = [number, number];
@@ -21,12 +23,9 @@ export type SineCoefficient = [
2123
number, // verticalOffset
2224
];
2325

24-
// a, b, c
25-
export type QuadraticCoefficient = [number, number, number];
26-
2726
// Given a number, return whether it is positive (1), negative (-1), or zero (0)
2827
export function sign(val: number): 0 | 1 | -1 {
29-
if (eq(val, 0)) {
28+
if (approximateEqual(val, 0)) {
3029
return 0;
3130
}
3231
return val > 0 ? 1 : -1;
@@ -39,7 +38,7 @@ export function ccw(a: Coord, b: Coord, c: Coord): number {
3938
}
4039

4140
export function collinear(a: Coord, b: Coord, c: Coord): boolean {
42-
return eq(ccw(a, b, c), 0);
41+
return approximateEqual(ccw(a, b, c), 0);
4342
}
4443

4544
// Given rect bounding points A and B, whether point C is inside the rect
@@ -229,15 +228,15 @@ export function similar(
229228
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
230229
sides = rotate(sides, i);
231230

232-
if (deepEq(angles1, angles)) {
231+
if (approximateDeepEqual(angles1, angles)) {
233232
const sidePairs = _.zip(sides1, sides);
234233

235234
const factors = _.map(sidePairs, function (pair) {
236235
return pair[0] / pair[1];
237236
});
238237

239238
const same = _.all(factors, function (factor) {
240-
return eq(factors[0], factor);
239+
return approximateEqual(factors[0], factor);
241240
});
242241

243242
const congruentEnough = _.all(sidePairs, function (pair) {
@@ -304,7 +303,7 @@ export function rotate<T>(
304303
}
305304

306305
export function getLineEquation(first: Coord, second: Coord): string {
307-
if (eq(first[0], second[0])) {
306+
if (approximateEqual(first[0], second[0])) {
308307
return "x = " + first[0].toFixed(3);
309308
}
310309
const m = (second[1] - first[1]) / (second[0] - first[0]);

packages/kmath/src/index.ts

+8
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,13 @@ export * as vector from "./vector";
55
export * as point from "./point";
66
export * as line from "./line";
77
export * as ray from "./ray";
8+
export * as angles from "./angles";
9+
export * as geometry from "./geometry";
10+
export * as coefficients from "./coefficients";
811

912
export {default as KhanMath, sum} from "./math";
13+
14+
export type {Range, SineCoefficient} from "./geometry";
15+
export type {NamedSineCoefficient, QuadraticCoefficient} from "./coefficients";
16+
17+
export type * from "./types";

packages/kmath/src/types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import type {Coord} from "@khanacademy/perseus-core";
2+
3+
export type QuadraticCoords = [Coord, Coord, Coord];

packages/math-input/src/components/key-handlers/key-translator.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import {getDecimalSeparator} from "@khanacademy/perseus-core";
2+
13
import {MathFieldActionType} from "../../types";
2-
import {getDecimalSeparator} from "../../utils";
34
import {mathQuillInstance} from "../input/mathquill-instance";
45

56
import handleArrow from "./handle-arrow";

packages/math-input/src/components/keypad/button-assets.tsx

+2-5
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ asset.
1010
In the future it would be great if these were included from files so that
1111
no copying and pasting is necessary.
1212
*/
13+
import {getDecimalSeparator} from "@khanacademy/perseus-core";
1314
import * as React from "react";
1415

15-
import {DecimalSeparator, getDecimalSeparator} from "../../utils";
1616
import {useMathInputI18n} from "../i18n-context";
1717

1818
import type Key from "../../data/keys";
@@ -176,10 +176,7 @@ export default function ButtonAsset({id}: Props): React.ReactNode {
176176
case "PERIOD":
177177
// Different locales use different symbols for the decimal separator
178178
// (, vs .)
179-
if (
180-
id === "DECIMAL" &&
181-
getDecimalSeparator(locale) === DecimalSeparator.COMMA
182-
) {
179+
if (id === "DECIMAL" && getDecimalSeparator(locale) !== ".") {
183180
// comma decimal separator
184181
return (
185182
<svg

packages/math-input/src/utils.ts

-37
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,3 @@
1-
export const DecimalSeparator = {
2-
COMMA: ",",
3-
PERIOD: ".",
4-
} as const;
5-
6-
/**
7-
* Get the character used for separating decimals.
8-
*/
9-
export const getDecimalSeparator = (locale: string): string => {
10-
let separator: string = DecimalSeparator.PERIOD;
11-
12-
switch (locale) {
13-
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
14-
// locale properly.
15-
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
16-
//
17-
// Supported locales in Chrome:
18-
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
19-
case "ka":
20-
separator = ",";
21-
break;
22-
23-
default:
24-
const numberWithDecimalSeparator = 1.1;
25-
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
26-
// support Safari 12.
27-
const match = new Intl.NumberFormat(locale)
28-
.format(numberWithDecimalSeparator)
29-
// 0x661 is ARABIC-INDIC DIGIT ONE
30-
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
31-
.match(/[^\d\u0661\u06F1]/);
32-
separator = match?.[0] ?? ".";
33-
}
34-
35-
return separator === "," ? DecimalSeparator.COMMA : DecimalSeparator.PERIOD;
36-
};
37-
381
const CDOT_ONLY = [
392
"az",
403
"cs",

packages/perseus-core/src/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,16 @@ export type {
88
Relationship,
99
} from "./types";
1010
export type {ErrorKind} from "./error/errors";
11+
export type {FunctionTypeMappingKeys} from "./utils/grapher-util";
12+
export type {Coords} from "./utils/grapher-types";
1113

1214
// Careful, `version.ts` uses this function so it _must_ be imported above it
1315
export {addLibraryVersionToPerseusDebug} from "./utils/add-library-version-to-perseus-debug";
16+
export {default as getMatrixSize} from "./utils/get-matrix-size";
17+
export {default as getDecimalSeparator} from "./utils/get-decimal-separator";
18+
export {approximateEqual, approximateDeepEqual} from "./utils/equality";
19+
export {default as deepClone} from "./utils/deep-clone";
20+
export * as GrapherUtil from "./utils/grapher-util";
1421

1522
export {libVersion} from "./version";
1623

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import deepClone from "./deep-clone";
2+
3+
describe("deepClone", () => {
4+
it("does nothing to a primitive", () => {
5+
expect(deepClone(3)).toBe(3);
6+
});
7+
8+
it("copies an array", () => {
9+
const input = [1, 2, 3];
10+
11+
const result = deepClone(input);
12+
13+
expect(result).toEqual(input);
14+
expect(result).not.toBe(input);
15+
});
16+
17+
it("recursively clones array elements", () => {
18+
const input = [[1]];
19+
20+
const result = deepClone(input);
21+
22+
expect(result).toEqual(input);
23+
expect(result[0]).not.toBe(input[0]);
24+
});
25+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// TODO(benchristel): in the future, we may want to make deepClone work for
2+
// Record<string, Cloneable> as well. Currently, it only does arrays.
3+
type Cloneable =
4+
| null
5+
| undefined
6+
| boolean
7+
| string
8+
| number
9+
| Cloneable[]
10+
| readonly Cloneable[];
11+
function deepClone<T extends Cloneable>(obj: T): T {
12+
if (Array.isArray(obj)) {
13+
return obj.map(deepClone) as T;
14+
}
15+
return obj;
16+
}
17+
18+
export default deepClone;

0 commit comments

Comments
 (0)