@khanacademy/perseus 50.1.0 → 51.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.js +558 -2400
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +58 -11
- package/dist/es/strings.js.map +1 -1
- package/dist/index.d.ts +3 -8
- package/dist/index.js +500 -2318
- package/dist/index.js.map +1 -1
- package/dist/renderer-util.d.ts +1 -2
- package/dist/renderer.d.ts +2 -2
- package/dist/server-item-renderer.d.ts +1 -1
- package/dist/strings.d.ts +204 -13
- package/dist/strings.js +75 -10
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +1 -14
- package/dist/util/scoring.d.ts +1 -2
- package/dist/util/test-utils.d.ts +1 -2
- package/dist/util.d.ts +0 -14
- package/dist/widget-ai-utils/categorizer/categorizer-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/dropdown/dropdown-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/expression/expression-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/grapher/grapher-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/input-number/input-number-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/label-image/label-image-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/matcher/matcher-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/matrix/matrix-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/mock-widget/prompt-utils.d.ts +1 -1
- package/dist/widget-ai-utils/number-line/number-line-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/numeric-input/prompt-utils.d.ts +1 -1
- package/dist/widget-ai-utils/orderer/orderer-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/radio/radio-ai-utils.d.ts +1 -1
- package/dist/widget-ai-utils/sorter/sorter-ai-utils.d.ts +1 -1
- package/dist/widgets/__shared__/score-noop.d.ts +1 -1
- package/dist/widgets/categorizer/categorizer.d.ts +2 -2
- package/dist/widgets/cs-program/cs-program.d.ts +1 -2
- package/dist/widgets/definition/definition.d.ts +1 -1
- package/dist/widgets/deprecated-standin/deprecated-standin.d.ts +1 -1
- package/dist/widgets/dropdown/dropdown.d.ts +2 -2
- package/dist/widgets/explanation/explanation.d.ts +1 -1
- package/dist/widgets/expression/expression.d.ts +5 -6
- package/dist/widgets/graded-group/graded-group.d.ts +1 -1
- package/dist/widgets/graded-group-set/graded-group-set.d.ts +1 -1
- package/dist/widgets/grapher/grapher.d.ts +1 -2
- package/dist/widgets/grapher/util.d.ts +5 -13
- package/dist/widgets/group/group.d.ts +2 -2
- package/dist/widgets/group/score-group.d.ts +1 -2
- package/dist/widgets/iframe/iframe.d.ts +1 -2
- package/dist/widgets/image/image.d.ts +1 -1
- package/dist/widgets/input-number/input-number.d.ts +1 -2
- package/dist/widgets/interaction/interaction.d.ts +1 -1
- package/dist/widgets/interactive-graph.d.ts +2 -3
- package/dist/widgets/interactive-graphs/graphs/linear-system.d.ts +2 -0
- package/dist/widgets/interactive-graphs/graphs/quadratic.d.ts +1 -3
- package/dist/widgets/interactive-graphs/graphs/ray.d.ts +2 -0
- package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +4 -9
- package/dist/widgets/interactive-graphs/graphs/utils.d.ts +4 -0
- package/dist/widgets/interactive-graphs/math/index.d.ts +0 -1
- package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/types.d.ts +2 -1
- package/dist/widgets/label-image/label-image.d.ts +2 -4
- package/dist/widgets/label-image/marker.d.ts +1 -2
- package/dist/widgets/matcher/matcher.d.ts +1 -2
- package/dist/widgets/matrix/matrix.d.ts +2 -4
- package/dist/widgets/measurer/measurer.d.ts +1 -1
- package/dist/widgets/mock-widgets/mock-widget.d.ts +1 -1
- package/dist/widgets/mock-widgets/score-mock-widget.d.ts +1 -2
- package/dist/widgets/molecule/molecule.d.ts +1 -1
- package/dist/widgets/number-line/number-line.d.ts +2 -3
- package/dist/widgets/numeric-input/numeric-input.d.ts +1 -2
- package/dist/widgets/orderer/orderer.d.ts +1 -2
- package/dist/widgets/passage/passage.d.ts +1 -1
- package/dist/widgets/passage-ref/passage-ref.d.ts +1 -1
- package/dist/widgets/passage-ref-target/passage-ref-target.d.ts +1 -1
- package/dist/widgets/plotter/plotter.d.ts +1 -2
- package/dist/widgets/radio/radio-component.d.ts +1 -1
- package/dist/widgets/radio/radio.d.ts +1 -1
- package/dist/widgets/sorter/sorter.d.ts +1 -2
- package/dist/widgets/table/table.d.ts +1 -2
- package/dist/widgets/video/video.d.ts +1 -1
- package/package.json +46 -46
- package/dist/tex-wrangler.d.ts +0 -7
- package/dist/util/geometry.d.ts +0 -28
- package/dist/validation.types.d.ts +0 -166
- package/dist/widgets/categorizer/score-categorizer.d.ts +0 -5
- package/dist/widgets/categorizer/validate-categorizer.d.ts +0 -13
- package/dist/widgets/cs-program/score-cs-program.d.ts +0 -4
- package/dist/widgets/dropdown/score-dropdown.d.ts +0 -4
- package/dist/widgets/dropdown/validate-dropdown.d.ts +0 -8
- package/dist/widgets/expression/get-decimal-separator.d.ts +0 -5
- package/dist/widgets/expression/score-expression.d.ts +0 -5
- package/dist/widgets/expression/validate-expression.d.ts +0 -12
- package/dist/widgets/grapher/grapher-types.d.ts +0 -58
- package/dist/widgets/grapher/score-grapher.d.ts +0 -4
- package/dist/widgets/iframe/score-iframe.d.ts +0 -3
- package/dist/widgets/input-number/score-input-number.d.ts +0 -39
- package/dist/widgets/interactive-graphs/math/angles.d.ts +0 -7
- package/dist/widgets/interactive-graphs/score-interactive-graph.d.ts +0 -4
- package/dist/widgets/label-image/score-label-image.d.ts +0 -10
- package/dist/widgets/label-image/types.d.ts +0 -11
- package/dist/widgets/matcher/score-matcher.d.ts +0 -4
- package/dist/widgets/matrix/score-matrix.d.ts +0 -5
- package/dist/widgets/matrix/validate-matrix.d.ts +0 -13
- package/dist/widgets/number-line/score-number-line.d.ts +0 -4
- package/dist/widgets/number-line/validate-number-line.d.ts +0 -12
- package/dist/widgets/numeric-input/score-numeric-input.d.ts +0 -6
- package/dist/widgets/orderer/score-orderer.d.ts +0 -3
- package/dist/widgets/orderer/validate-orderer.d.ts +0 -10
- package/dist/widgets/plotter/score-plotter.d.ts +0 -4
- package/dist/widgets/plotter/validate-plotter.d.ts +0 -10
- package/dist/widgets/radio/score-radio.d.ts +0 -5
- package/dist/widgets/radio/validate-radio.d.ts +0 -12
- package/dist/widgets/sorter/score-sorter.d.ts +0 -4
- package/dist/widgets/sorter/validate-sorter.d.ts +0 -11
- package/dist/widgets/table/score-table.d.ts +0 -5
- package/dist/widgets/table/utils.d.ts +0 -7
- package/dist/widgets/table/validate-table.d.ts +0 -4
package/dist/es/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as KAS from '@khanacademy/kas';
|
|
2
2
|
import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
|
|
3
|
+
import { PerseusError, Errors, getDecimalSeparator, approximateDeepEqual, pluck, mapObject, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getMatrixSize, addLibraryVersionToPerseusDebug, lockedFigureColorNames, plotterPlotTypes, ItemExtras } from '@khanacademy/perseus-core';
|
|
3
4
|
import * as PerseusLinter from '@khanacademy/perseus-linter';
|
|
4
5
|
import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
|
|
6
|
+
import { scoreExpression, KhanAnswerTypes, scoreInputNumber, inputNumberAnswerTypes, scoreNumericInput, scoreRadio, scoreCategorizer, scoreCSProgram, scoreDropdown, scoreGrapher, scoreIframe, scoreInteractiveGraph, scoreLabelImage, scoreLabelImageMarker, scoreMatcher, scoreMatrix, scoreNumberLine, scoreOrderer, scorePlotter, scoreSorter, scoreTable } from '@khanacademy/perseus-score';
|
|
5
7
|
import { View, RenderStateRoot, Id, useOnMountEffect, useLatestRef, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
|
|
6
8
|
import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
|
|
7
9
|
import { HeadingMedium, LabelSmall, LabelLarge, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
|
|
@@ -12,17 +14,15 @@ import React__default, { useContext, useState, useEffect, useRef, createContext,
|
|
|
12
14
|
import * as ReactDOM from 'react-dom';
|
|
13
15
|
import ReactDOM__default from 'react-dom';
|
|
14
16
|
import _, { debounce as debounce$1 } from 'underscore';
|
|
15
|
-
import { ErrorCodes, KhanAnswerTypes } from '@khanacademy/perseus-score';
|
|
16
17
|
import Clickable from '@khanacademy/wonder-blocks-clickable';
|
|
17
18
|
import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
|
|
18
19
|
import { color, spacing, font } from '@khanacademy/wonder-blocks-tokens';
|
|
19
20
|
import $ from 'jquery';
|
|
20
21
|
import { v4 } from 'uuid';
|
|
21
|
-
import { PerseusError, Errors, pluck, mapObject, lockedFigureColors, lockedFigureFillStyles, addLibraryVersionToPerseusDebug, lockedFigureColorNames, plotterPlotTypes, ItemExtras } from '@khanacademy/perseus-core';
|
|
22
22
|
import PropTypes from 'prop-types';
|
|
23
23
|
import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
|
|
24
24
|
import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
|
|
25
|
-
import { point, KhanMath, number as number$1, vector as vector$
|
|
25
|
+
import { point, KhanMath, number as number$1, vector as vector$3, line, geometry, angles, coefficients } from '@khanacademy/kmath';
|
|
26
26
|
import SimpleMarkdown from '@khanacademy/simple-markdown';
|
|
27
27
|
import { pureMarkdownRules } from '@khanacademy/pure-markdown';
|
|
28
28
|
import { TextField } from '@khanacademy/wonder-blocks-form';
|
|
@@ -33,7 +33,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
|
|
|
33
33
|
import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
|
|
34
34
|
import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
|
|
35
35
|
import debounce$2 from 'lodash.debounce';
|
|
36
|
-
import { vec, Coordinates, Ellipse, Plot as Plot$
|
|
36
|
+
import { vec, Coordinates, Ellipse, Plot as Plot$2, Line as Line$4, Point as Point$2, Polygon, Text as Text$1, useTransformContext, Polyline, Mafs } from 'mafs';
|
|
37
37
|
import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
|
|
38
38
|
import questionIcon from '@phosphor-icons/core/regular/question.svg';
|
|
39
39
|
import { useDrag } from '@use-gesture/react';
|
|
@@ -231,7 +231,15 @@ const strings = {
|
|
|
231
231
|
srAngleSideAtCoordinates: "Point %(point)s, %(side)s at %(x)s comma %(y)s",
|
|
232
232
|
srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
|
|
233
233
|
srAngleGraphAriaLabel: "An angle on a coordinate plane.",
|
|
234
|
-
srAngleGraphAriaDescription: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s"
|
|
234
|
+
srAngleGraphAriaDescription: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s",
|
|
235
|
+
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
236
|
+
srLinearSystemPoints: "Line %(lineNumber)s has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
|
|
237
|
+
srLinearSystemPoint: "Point %(pointSequence)s on line %(lineNumber)s at %(x)s comma %(y)s.",
|
|
238
|
+
srRayGraph: "A ray on a coordinate plane.",
|
|
239
|
+
srRayPoints: "The endpoint is at %(point1X)s comma %(point1Y)s and the ray goes through point %(point2X)s comma %(point2Y)s.",
|
|
240
|
+
srRayGrabHandle: "Ray with endpoint %(point1X)s comma %(point1Y)s going through point %(point2X)s comma %(point2Y)s.",
|
|
241
|
+
srRayEndpoint: "Endpoint at %(point1X)s comma %(point1Y)s.",
|
|
242
|
+
srRayTerminalPoint: "Through point at %(point2X)s comma %(point2Y)s."
|
|
235
243
|
// The above strings are used for interactive graph SR descriptions.
|
|
236
244
|
};
|
|
237
245
|
|
|
@@ -500,18 +508,59 @@ const mockStrings = {
|
|
|
500
508
|
startingSideY,
|
|
501
509
|
endingSideX,
|
|
502
510
|
endingSideY
|
|
503
|
-
}) => `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the starting side at ${startingSideX} comma ${startingSideY} and a point on the ending side at ${endingSideX} comma ${endingSideY}
|
|
511
|
+
}) => `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the starting side at ${startingSideX} comma ${startingSideY} and a point on the ending side at ${endingSideX} comma ${endingSideY}.`,
|
|
512
|
+
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
513
|
+
srLinearSystemPoints: ({
|
|
514
|
+
lineNumber,
|
|
515
|
+
point1X,
|
|
516
|
+
point1Y,
|
|
517
|
+
point2X,
|
|
518
|
+
point2Y
|
|
519
|
+
}) => `Line ${lineNumber} has two points, point 1 at ${point1X} comma ${point1Y} and point 2 at ${point2X} comma ${point2Y}.`,
|
|
520
|
+
srLinearSystemPoint: ({
|
|
521
|
+
lineNumber,
|
|
522
|
+
pointSequence,
|
|
523
|
+
x,
|
|
524
|
+
y
|
|
525
|
+
}) => `Point ${pointSequence} on line ${lineNumber} at ${x} comma ${y}.`,
|
|
526
|
+
srRayGraph: "A ray on a coordinate plane.",
|
|
527
|
+
srRayPoints: ({
|
|
528
|
+
point1X,
|
|
529
|
+
point1Y,
|
|
530
|
+
point2X,
|
|
531
|
+
point2Y
|
|
532
|
+
}) => `The endpoint is at ${point1X} comma ${point1Y} and the ray goes through point ${point2X} comma ${point2Y}.`,
|
|
533
|
+
srRayGrabHandle: ({
|
|
534
|
+
point1X,
|
|
535
|
+
point1Y,
|
|
536
|
+
point2X,
|
|
537
|
+
point2Y
|
|
538
|
+
}) => `Ray with endpoint ${point1X} comma ${point1Y} going through point ${point2X} comma ${point2Y}.`,
|
|
539
|
+
srRayEndpoint: ({
|
|
540
|
+
x,
|
|
541
|
+
y
|
|
542
|
+
}) => `Endpoint at ${x} comma ${y}.`,
|
|
543
|
+
srRayTerminalPoint: ({
|
|
544
|
+
x,
|
|
545
|
+
y
|
|
546
|
+
}) => `Through point at ${x} comma ${y}.`
|
|
504
547
|
// The above strings are used for interactive graph SR descriptions.
|
|
505
548
|
};
|
|
506
549
|
|
|
550
|
+
// This type helps us make sure all error codes are mapped to strings
|
|
551
|
+
|
|
507
552
|
const errorToString = {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
553
|
+
MISSING_PERCENT_ERROR: strings.MISSING_PERCENT_ERROR,
|
|
554
|
+
NEEDS_TO_BE_SIMPLIFIED_ERROR: strings.NEEDS_TO_BE_SIMPLFIED_ERROR,
|
|
555
|
+
APPROXIMATED_PI_ERROR: strings.APPROXIMATED_PI_ERROR,
|
|
556
|
+
EXTRA_SYMBOLS_ERROR: strings.EXTRA_SYMBOLS_ERROR,
|
|
557
|
+
WRONG_CASE_ERROR: strings.WRONG_CASE_ERROR,
|
|
558
|
+
WRONG_LETTER_ERROR: strings.WRONG_LETTER_ERROR,
|
|
559
|
+
MULTIPLICATION_SIGN_ERROR: strings.MULTIPLICATION_SIGN_ERROR,
|
|
560
|
+
INVALID_SELECTION_ERROR: strings.invalidSelection,
|
|
561
|
+
CHOOSE_CORRECT_NUM_ERROR: strings.chooseCorrectNum,
|
|
562
|
+
NOT_NONE_ABOVE_ERROR: strings.notNoneOfTheAbove,
|
|
563
|
+
FILL_ALL_CELLS_ERROR: strings.fillAllCells
|
|
515
564
|
};
|
|
516
565
|
function mapErrorToString(err) {
|
|
517
566
|
if (!err) {
|
|
@@ -1455,205 +1504,6 @@ const getPromptJSON$t = (renderProps, userInput) => {
|
|
|
1455
1504
|
};
|
|
1456
1505
|
};
|
|
1457
1506
|
|
|
1458
|
-
/**
|
|
1459
|
-
* Get the character used for separating decimals.
|
|
1460
|
-
*/
|
|
1461
|
-
const getDecimalSeparator = locale => {
|
|
1462
|
-
var _match$;
|
|
1463
|
-
switch (locale) {
|
|
1464
|
-
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
1465
|
-
// locale properly.
|
|
1466
|
-
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
1467
|
-
//
|
|
1468
|
-
// Supported locales in Chrome:
|
|
1469
|
-
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
1470
|
-
case "ka":
|
|
1471
|
-
return ",";
|
|
1472
|
-
default:
|
|
1473
|
-
const numberWithDecimalSeparator = 1.1;
|
|
1474
|
-
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
1475
|
-
// support Safari 12.
|
|
1476
|
-
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
1477
|
-
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
1478
|
-
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
1479
|
-
.match(/[^\d\u0661\u06F1]/);
|
|
1480
|
-
return (_match$ = match == null ? void 0 : match[0]) != null ? _match$ : ".";
|
|
1481
|
-
}
|
|
1482
|
-
};
|
|
1483
|
-
|
|
1484
|
-
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
1485
|
-
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
1486
|
-
// which get's the injected logger.
|
|
1487
|
-
/**
|
|
1488
|
-
* Provides logging infrastructure for Perseus
|
|
1489
|
-
*/
|
|
1490
|
-
const Log = {
|
|
1491
|
-
// Logs a message
|
|
1492
|
-
log: (message, extra) => {
|
|
1493
|
-
getDependencies().Log.log(message, extra);
|
|
1494
|
-
},
|
|
1495
|
-
// Logs an error with a message
|
|
1496
|
-
error: (message, kind, extra) => {
|
|
1497
|
-
getDependencies().Log.error(message, kind, extra);
|
|
1498
|
-
}
|
|
1499
|
-
};
|
|
1500
|
-
|
|
1501
|
-
/**
|
|
1502
|
-
* Checks user input from the expression widget to see if it is scorable.
|
|
1503
|
-
*
|
|
1504
|
-
* Note: Most of the expression widget's validation requires the Rubric because
|
|
1505
|
-
* of its use of KhanAnswerTypes as a core part of scoring.
|
|
1506
|
-
*
|
|
1507
|
-
* @see `scoreExpression()` for more details.
|
|
1508
|
-
*/
|
|
1509
|
-
function validateExpression(userInput) {
|
|
1510
|
-
if (userInput === "") {
|
|
1511
|
-
return {
|
|
1512
|
-
type: "invalid",
|
|
1513
|
-
message: null
|
|
1514
|
-
};
|
|
1515
|
-
}
|
|
1516
|
-
return null;
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
/* Content creators input a list of answers which are matched from top to
|
|
1520
|
-
* bottom. The intent is that they can include spcific solutions which should
|
|
1521
|
-
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
1522
|
-
* general.
|
|
1523
|
-
*
|
|
1524
|
-
* We iterate through each answer, trying to match it with the user's input
|
|
1525
|
-
* using the following angorithm:
|
|
1526
|
-
* - Try to parse the user's input. If it doesn't parse then return "not
|
|
1527
|
-
* graded".
|
|
1528
|
-
* - For each answer:
|
|
1529
|
-
* ~ Try to validate the user's input against the answer. The answer is
|
|
1530
|
-
* expected to parse.
|
|
1531
|
-
* ~ If the user's input validates (the validator judges it "correct"), we've
|
|
1532
|
-
* matched and can stop considering answers.
|
|
1533
|
-
* - If there were no matches or the matching answer is considered "ungraded",
|
|
1534
|
-
* show the user an error. TODO(joel) - what error?
|
|
1535
|
-
* - Otherwise, pass through the resulting points and message.
|
|
1536
|
-
*/
|
|
1537
|
-
function scoreExpression(userInput, rubric, strings, locale) {
|
|
1538
|
-
const validationError = validateExpression(userInput);
|
|
1539
|
-
if (validationError) {
|
|
1540
|
-
return validationError;
|
|
1541
|
-
}
|
|
1542
|
-
const options = _.clone(rubric);
|
|
1543
|
-
_.extend(options, {
|
|
1544
|
-
decimal_separator: getDecimalSeparator(locale)
|
|
1545
|
-
});
|
|
1546
|
-
const createValidator = answer => {
|
|
1547
|
-
// We give options to KAS.parse here because it is parsing the
|
|
1548
|
-
// solution answer, not the student answer, and we don't want a
|
|
1549
|
-
// solution to work if the student is using a different language
|
|
1550
|
-
// (different from the content creation language, ie. English).
|
|
1551
|
-
const expression = KAS.parse(answer.value, rubric);
|
|
1552
|
-
// An answer may not be parsed if the expression was defined
|
|
1553
|
-
// incorrectly. For example if the answer is using a symbol defined
|
|
1554
|
-
// in the function variables list for the expression.
|
|
1555
|
-
if (!expression.parsed) {
|
|
1556
|
-
/* c8 ignore next */
|
|
1557
|
-
Log.error("Unable to parse solution answer for expression", Errors.InvalidInput, {
|
|
1558
|
-
loggedMetadata: {
|
|
1559
|
-
rubric: JSON.stringify(rubric)
|
|
1560
|
-
}
|
|
1561
|
-
});
|
|
1562
|
-
return null;
|
|
1563
|
-
}
|
|
1564
|
-
return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, _({}).extend(options, {
|
|
1565
|
-
simplify: answer.simplify,
|
|
1566
|
-
form: answer.form
|
|
1567
|
-
}));
|
|
1568
|
-
};
|
|
1569
|
-
|
|
1570
|
-
// Find the first answer form that matches the user's input and that
|
|
1571
|
-
// is considered correct. Also, track whether the input is
|
|
1572
|
-
// considered "empty" for all answer forms, and keep the validation
|
|
1573
|
-
// result for the first answer form for which the user's input was
|
|
1574
|
-
// considered "ungraded".
|
|
1575
|
-
// (Terminology reminder: the answer forms are provided by the
|
|
1576
|
-
// assessment items; they are not the user's input. Each one might
|
|
1577
|
-
// represent a correct answer, an incorrect one (if the exercise
|
|
1578
|
-
// creator has predicted certain common wrong answers and wants to
|
|
1579
|
-
// provide guidance via a message), or an ungraded one (same idea,
|
|
1580
|
-
// but without giving the user an incorrect mark for the question).
|
|
1581
|
-
let matchingAnswerForm;
|
|
1582
|
-
let matchMessage;
|
|
1583
|
-
let allEmpty = true;
|
|
1584
|
-
let firstUngradedResult;
|
|
1585
|
-
for (const answerForm of rubric.answerForms || []) {
|
|
1586
|
-
const validator = createValidator(answerForm);
|
|
1587
|
-
if (!validator) {
|
|
1588
|
-
continue;
|
|
1589
|
-
}
|
|
1590
|
-
const result = validator(userInput);
|
|
1591
|
-
|
|
1592
|
-
// Short-circuit as soon as the user's input matches some answer
|
|
1593
|
-
// (independently of whether the answer is correct)
|
|
1594
|
-
if (result.correct) {
|
|
1595
|
-
matchingAnswerForm = answerForm;
|
|
1596
|
-
matchMessage = result.message || "";
|
|
1597
|
-
break;
|
|
1598
|
-
}
|
|
1599
|
-
allEmpty = allEmpty && result.empty;
|
|
1600
|
-
// If this answer form is correct and the user's input is considered
|
|
1601
|
-
// "ungraded" for it, we'll want to keep the evaluation result for
|
|
1602
|
-
// later. If the user's input doesn't match any answer forms, we'll
|
|
1603
|
-
// show the message from this validation.
|
|
1604
|
-
if (answerForm.considered === "correct" && result.ungraded && !firstUngradedResult) {
|
|
1605
|
-
firstUngradedResult = result;
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
// Now check to see if we matched any answer form at all, and if
|
|
1610
|
-
// we did, whether it's considered correct, incorrect, or ungraded
|
|
1611
|
-
if (!matchingAnswerForm) {
|
|
1612
|
-
if (firstUngradedResult) {
|
|
1613
|
-
// While we didn't directly match with any answer form, we
|
|
1614
|
-
// did at some point get an "ungraded" validation result,
|
|
1615
|
-
// which might indicate e.g. a mismatch in variable casing.
|
|
1616
|
-
// We'll return "invalid", which will let the user try again
|
|
1617
|
-
// with no penalty, and the hopefully helpful validation
|
|
1618
|
-
// message.
|
|
1619
|
-
return {
|
|
1620
|
-
type: "invalid",
|
|
1621
|
-
message: firstUngradedResult.message,
|
|
1622
|
-
suppressAlmostThere: firstUngradedResult.suppressAlmostThere
|
|
1623
|
-
};
|
|
1624
|
-
}
|
|
1625
|
-
if (allEmpty) {
|
|
1626
|
-
// If everything graded as empty, it's invalid.
|
|
1627
|
-
return {
|
|
1628
|
-
type: "invalid",
|
|
1629
|
-
message: null
|
|
1630
|
-
};
|
|
1631
|
-
}
|
|
1632
|
-
// We fell through all the possibilities and we're not empty,
|
|
1633
|
-
// so the answer is considered incorrect.
|
|
1634
|
-
return {
|
|
1635
|
-
type: "points",
|
|
1636
|
-
earned: 0,
|
|
1637
|
-
total: 1
|
|
1638
|
-
};
|
|
1639
|
-
}
|
|
1640
|
-
if (matchingAnswerForm.considered === "ungraded") {
|
|
1641
|
-
return {
|
|
1642
|
-
type: "invalid",
|
|
1643
|
-
message: matchMessage
|
|
1644
|
-
};
|
|
1645
|
-
}
|
|
1646
|
-
// We matched a graded answer form, so we can now tell the user
|
|
1647
|
-
// whether their input was correct or incorrect, and hand out
|
|
1648
|
-
// points accordingly
|
|
1649
|
-
return {
|
|
1650
|
-
type: "points",
|
|
1651
|
-
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
1652
|
-
total: 1,
|
|
1653
|
-
message: matchMessage
|
|
1654
|
-
};
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
1507
|
// Map of international operator names to their English equivalents
|
|
1658
1508
|
const englishOperators = {
|
|
1659
1509
|
arctg: "arctan",
|
|
@@ -2414,56 +2264,6 @@ function constrainedTickStepsFromTickSteps(tickSteps, ranges) {
|
|
|
2414
2264
|
return [constrainTickStep(tickSteps[0], ranges[0]), constrainTickStep(tickSteps[1], ranges[1])];
|
|
2415
2265
|
}
|
|
2416
2266
|
|
|
2417
|
-
/**
|
|
2418
|
-
* Approximate equality on numbers and primitives.
|
|
2419
|
-
*/
|
|
2420
|
-
function eq$4(x, y) {
|
|
2421
|
-
if (typeof x === "number" && typeof y === "number") {
|
|
2422
|
-
return Math.abs(x - y) < 1e-9;
|
|
2423
|
-
}
|
|
2424
|
-
return x === y;
|
|
2425
|
-
}
|
|
2426
|
-
|
|
2427
|
-
/**
|
|
2428
|
-
* Deep approximate equality on primitives, numbers, arrays, and objects.
|
|
2429
|
-
* Recursive.
|
|
2430
|
-
*/
|
|
2431
|
-
function deepEq$7(x, y) {
|
|
2432
|
-
if (Array.isArray(x) && Array.isArray(y)) {
|
|
2433
|
-
if (x.length !== y.length) {
|
|
2434
|
-
return false;
|
|
2435
|
-
}
|
|
2436
|
-
for (let i = 0; i < x.length; i++) {
|
|
2437
|
-
if (!deepEq$7(x[i], y[i])) {
|
|
2438
|
-
return false;
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
return true;
|
|
2442
|
-
}
|
|
2443
|
-
if (Array.isArray(x) || Array.isArray(y)) {
|
|
2444
|
-
return false;
|
|
2445
|
-
}
|
|
2446
|
-
if (typeof x === "function" && typeof y === "function") {
|
|
2447
|
-
return eq$4(x, y);
|
|
2448
|
-
}
|
|
2449
|
-
if (typeof x === "function" || typeof y === "function") {
|
|
2450
|
-
return false;
|
|
2451
|
-
}
|
|
2452
|
-
if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
|
|
2453
|
-
return x === y || _.all(x, function (v, k) {
|
|
2454
|
-
// @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
|
|
2455
|
-
return deepEq$7(y[k], v);
|
|
2456
|
-
}) && _.all(y, function (v, k) {
|
|
2457
|
-
// @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
|
|
2458
|
-
return deepEq$7(x[k], v);
|
|
2459
|
-
});
|
|
2460
|
-
}
|
|
2461
|
-
if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
|
|
2462
|
-
return false;
|
|
2463
|
-
}
|
|
2464
|
-
return eq$4(x, y);
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
2267
|
/**
|
|
2468
2268
|
* Query String Parser
|
|
2469
2269
|
*
|
|
@@ -2680,15 +2480,6 @@ const textarea = {
|
|
|
2680
2480
|
*/
|
|
2681
2481
|
const unescapeMathMode$1 = label => label.startsWith("$") && label.endsWith("$") ? label.slice(1, -1) : label;
|
|
2682
2482
|
const random$1 = seededRNG$1(new Date().getTime() & 0xffffffff);
|
|
2683
|
-
|
|
2684
|
-
// TODO(benchristel): in the future, we may want to make deepClone work for
|
|
2685
|
-
// Record<string, Cloneable> as well. Currently, it only does arrays.
|
|
2686
|
-
function deepClone(obj) {
|
|
2687
|
-
if (Array.isArray(obj)) {
|
|
2688
|
-
return obj.map(deepClone);
|
|
2689
|
-
}
|
|
2690
|
-
return obj;
|
|
2691
|
-
}
|
|
2692
2483
|
const Util = {
|
|
2693
2484
|
inputPathsEqual,
|
|
2694
2485
|
nestedMap: nestedMap$2,
|
|
@@ -2709,8 +2500,6 @@ const Util = {
|
|
|
2709
2500
|
gridStepFromTickStep,
|
|
2710
2501
|
tickStepFromNumTicks,
|
|
2711
2502
|
constrainedTickStepsFromTickSteps,
|
|
2712
|
-
eq: eq$4,
|
|
2713
|
-
deepEq: deepEq$7,
|
|
2714
2503
|
parseQueryString,
|
|
2715
2504
|
updateQueryString: updateQueryString$2,
|
|
2716
2505
|
strongEncodeURIComponent,
|
|
@@ -2728,8 +2517,24 @@ const Util = {
|
|
|
2728
2517
|
getDataUrl: getDataUrl,
|
|
2729
2518
|
textarea,
|
|
2730
2519
|
unescapeMathMode: unescapeMathMode$1,
|
|
2731
|
-
random: random$1
|
|
2732
|
-
|
|
2520
|
+
random: random$1
|
|
2521
|
+
};
|
|
2522
|
+
|
|
2523
|
+
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
2524
|
+
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
2525
|
+
// which get's the injected logger.
|
|
2526
|
+
/**
|
|
2527
|
+
* Provides logging infrastructure for Perseus
|
|
2528
|
+
*/
|
|
2529
|
+
const Log = {
|
|
2530
|
+
// Logs a message
|
|
2531
|
+
log: (message, extra) => {
|
|
2532
|
+
getDependencies().Log.log(message, extra);
|
|
2533
|
+
},
|
|
2534
|
+
// Logs an error with a message
|
|
2535
|
+
error: (message, kind, extra) => {
|
|
2536
|
+
getDependencies().Log.error(message, kind, extra);
|
|
2537
|
+
}
|
|
2733
2538
|
};
|
|
2734
2539
|
|
|
2735
2540
|
// For offline exercises in the mobile app, we download the graphie data
|
|
@@ -8277,7 +8082,7 @@ var Tex$1 = {
|
|
|
8277
8082
|
const {
|
|
8278
8083
|
processMath: processMath$1
|
|
8279
8084
|
} = Tex$1;
|
|
8280
|
-
function polar$
|
|
8085
|
+
function polar$2(r, th) {
|
|
8281
8086
|
if (typeof r === "number") {
|
|
8282
8087
|
r = [r, r];
|
|
8283
8088
|
}
|
|
@@ -8436,7 +8241,7 @@ class Graphie$1 {
|
|
|
8436
8241
|
// See: http://stackoverflow.com/questions/13932704/how-to-draw-sine-waves-with-svg-js
|
|
8437
8242
|
const xCoords = [x0, x0 * 2 / 3 + x1 * 1 / 3, x0 * 1 / 3 + x1 * 2 / 3, x1];
|
|
8438
8243
|
const yCoords = [computeSine(x0), computeSine(x0) + computeDerivative(x0) * (x1 - x0) / 3, computeSine(x1) - computeDerivative(x1) * (x1 - x0) / 3, computeSine(x1)];
|
|
8439
|
-
const points = vector$
|
|
8244
|
+
const points = vector$3.zip(xCoords, yCoords);
|
|
8440
8245
|
return points.map(this.scalePoint);
|
|
8441
8246
|
};
|
|
8442
8247
|
|
|
@@ -8769,8 +8574,8 @@ class Graphie$1 {
|
|
|
8769
8574
|
endAngle = (endAngle % 360 + 360) % 360;
|
|
8770
8575
|
const cent = this.scalePoint(center);
|
|
8771
8576
|
const radii = this.scaleVector(radius);
|
|
8772
|
-
const startVector = polar$
|
|
8773
|
-
const endVector = polar$
|
|
8577
|
+
const startVector = polar$2(radius, startAngle);
|
|
8578
|
+
const endVector = polar$2(radius, endAngle);
|
|
8774
8579
|
|
|
8775
8580
|
// We round the coordinates to make testing easier, because trig
|
|
8776
8581
|
// operations return unround numbers.
|
|
@@ -8875,7 +8680,7 @@ class Graphie$1 {
|
|
|
8875
8680
|
// Apply padding and offset to points to convert from
|
|
8876
8681
|
// canvas coordinates to pixel coordinates relative to bounding box
|
|
8877
8682
|
points = points.map(function (point) {
|
|
8878
|
-
return vector$
|
|
8683
|
+
return vector$3.add(vector$3.subtract(point, topLeftOfBoundingBox), vector$3.scale(padding, 0.5));
|
|
8879
8684
|
});
|
|
8880
8685
|
|
|
8881
8686
|
// Calculate <div> dimensions
|
|
@@ -8939,8 +8744,8 @@ class Graphie$1 {
|
|
|
8939
8744
|
const extraOffset = [Math.min(start[0], end[0]), Math.min(start[1], end[1])];
|
|
8940
8745
|
|
|
8941
8746
|
// Apply padding and offset to start, end points
|
|
8942
|
-
start = vector$
|
|
8943
|
-
end = vector$
|
|
8747
|
+
start = vector$3.add(vector$3.subtract(start, extraOffset), vector$3.scale(padding, 0.5));
|
|
8748
|
+
end = vector$3.add(vector$3.subtract(end, extraOffset), vector$3.scale(padding, 0.5));
|
|
8944
8749
|
|
|
8945
8750
|
// Calculate <div> dimensions
|
|
8946
8751
|
const left = extraOffset[0] - padding[0] / 2;
|
|
@@ -9896,10 +9701,10 @@ const getClipPoint = function getClipPoint(graph, coord, angle) {
|
|
|
9896
9701
|
// shoot a point off into the distance ...
|
|
9897
9702
|
const distance = xExtent + yExtent;
|
|
9898
9703
|
// we need to scale the point according to the scale of the axes
|
|
9899
|
-
const angleVec = graph.unscaleVector(vector$
|
|
9900
|
-
const distVec = vector$
|
|
9901
|
-
const farCoord = vector$
|
|
9902
|
-
const scaledAngle = vector$
|
|
9704
|
+
const angleVec = graph.unscaleVector(vector$3.cartFromPolarDeg(1, angle));
|
|
9705
|
+
const distVec = vector$3.scale(vector$3.normalize(angleVec), distance);
|
|
9706
|
+
const farCoord = vector$3.add(coord, distVec);
|
|
9707
|
+
const scaledAngle = vector$3.polarDegFromCart(angleVec)[1];
|
|
9903
9708
|
// ... and then bring it back
|
|
9904
9709
|
const clipPoint = graph.constrainToBoundsOnAngle(farCoord, 4, scaledAngle * Math.PI / 180);
|
|
9905
9710
|
return clipPoint;
|
|
@@ -9950,7 +9755,7 @@ class WrappedDrawing {
|
|
|
9950
9755
|
return this.visibleShape[0];
|
|
9951
9756
|
}
|
|
9952
9757
|
moveTo(point) {
|
|
9953
|
-
const delta = vector$
|
|
9758
|
+
const delta = vector$3.subtract(this.graphie.scalePoint(point), this.graphie.scalePoint(this.initialPoint));
|
|
9954
9759
|
const do3dTransform = InteractiveUtil.getCanUse3dTransform();
|
|
9955
9760
|
const transformation = "translateX(" + delta[0] + "px) " + "translateY(" + delta[1] + "px)" + (do3dTransform ? " translateZ(0)" : "");
|
|
9956
9761
|
this.transform(transformation);
|
|
@@ -10005,9 +9810,9 @@ class Arrowhead$1 extends WrappedPath {
|
|
|
10005
9810
|
const center = [0.75, 0];
|
|
10006
9811
|
const points = [[-3, 4], [-2.75, 2.5], [0, 0.25], center, [0, -0.25], [-2.75, -2.5], [-3, -4]].map(point => {
|
|
10007
9812
|
// Scale points by `Arrowhead.scale` around `center`
|
|
10008
|
-
const pv = vector$
|
|
10009
|
-
const pvScaled = vector$
|
|
10010
|
-
return vector$
|
|
9813
|
+
const pv = vector$3.subtract(point, center);
|
|
9814
|
+
const pvScaled = vector$3.scale(pv, Arrowhead$1.scale);
|
|
9815
|
+
return vector$3.add(center, pvScaled);
|
|
10011
9816
|
});
|
|
10012
9817
|
const unscaledPoints = points.map(graphie.unscalePoint);
|
|
10013
9818
|
super(graphie, unscaledPoints, {
|
|
@@ -10078,7 +9883,7 @@ class WrappedEllipse extends WrappedDrawing {
|
|
|
10078
9883
|
const wrapper = this.wrapper;
|
|
10079
9884
|
wrapper.style.filter = filter;
|
|
10080
9885
|
this.moveTo = function (point) {
|
|
10081
|
-
const delta = vector$
|
|
9886
|
+
const delta = vector$3.subtract(this.graphie.scalePoint(point), this.graphie.scalePoint(this.initialPoint));
|
|
10082
9887
|
const do3dTransform = InteractiveUtil.getCanUse3dTransform();
|
|
10083
9888
|
const transform = "translateX(" + Math.round(delta[0]) + "px) " + "translateY(" + Math.round(delta[1]) + "px)" + (do3dTransform ? " translateZ(0)" : "");
|
|
10084
9889
|
this.transform(transform);
|
|
@@ -10147,10 +9952,10 @@ class WrappedLine extends WrappedDrawing {
|
|
|
10147
9952
|
const scaledEnd = this.graphie.scalePoint(end);
|
|
10148
9953
|
|
|
10149
9954
|
// Compute transformation parameters
|
|
10150
|
-
const polarDiff = vector$
|
|
9955
|
+
const polarDiff = vector$3.polarDegFromCart(vector$3.subtract(scaledEnd, scaledStart));
|
|
10151
9956
|
const lineLength = polarDiff[0];
|
|
10152
9957
|
const angle = KhanMath.bound(polarDiff[1]);
|
|
10153
|
-
const delta = vector$
|
|
9958
|
+
const delta = vector$3.subtract(scaledStart, this.initialPoint);
|
|
10154
9959
|
const scale = KhanMath.bound(lineLength / this.initialLength);
|
|
10155
9960
|
|
|
10156
9961
|
// Construct and apply transformation string
|
|
@@ -10160,260 +9965,11 @@ class WrappedLine extends WrappedDrawing {
|
|
|
10160
9965
|
}
|
|
10161
9966
|
}
|
|
10162
9967
|
|
|
10163
|
-
/**
|
|
10164
|
-
* A collection of geomtry-related utility functions
|
|
10165
|
-
*/
|
|
10166
|
-
const {
|
|
10167
|
-
eq: eq$3,
|
|
10168
|
-
deepEq: deepEq$6
|
|
10169
|
-
} = Util;
|
|
10170
|
-
|
|
10171
|
-
// This should really be a readonly tuple of [number, number]
|
|
10172
|
-
|
|
10173
|
-
// a, b, c
|
|
10174
|
-
|
|
10175
|
-
// Given a number, return whether it is positive (1), negative (-1), or zero (0)
|
|
10176
|
-
function sign(val) {
|
|
10177
|
-
if (eq$3(val, 0)) {
|
|
10178
|
-
return 0;
|
|
10179
|
-
}
|
|
10180
|
-
return val > 0 ? 1 : -1;
|
|
10181
|
-
}
|
|
10182
|
-
|
|
10183
|
-
// Determine whether three points are collinear (0), for a clockwise turn (negative),
|
|
10184
|
-
// or counterclockwise turn (positive)
|
|
10185
|
-
function ccw(a, b, c) {
|
|
10186
|
-
return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
|
|
10187
|
-
}
|
|
10188
|
-
function collinear(a, b, c) {
|
|
10189
|
-
return eq$3(ccw(a, b, c), 0);
|
|
10190
|
-
}
|
|
10191
|
-
|
|
10192
|
-
// Given rect bounding points A and B, whether point C is inside the rect
|
|
10193
|
-
function pointInRect(a, b, c) {
|
|
10194
|
-
return c[0] <= Math.max(a[0], b[0]) && c[0] >= Math.min(a[0], b[0]) && c[1] <= Math.max(a[1], b[1]) && c[1] >= Math.min(a[1], b[1]);
|
|
10195
|
-
}
|
|
10196
|
-
|
|
10197
|
-
// Whether line segment AB intersects line segment CD
|
|
10198
|
-
// http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
|
|
10199
|
-
function intersects(ab, cd) {
|
|
10200
|
-
const triplets = [[ab[0], ab[1], cd[0]], [ab[0], ab[1], cd[1]], [cd[0], cd[1], ab[0]], [cd[0], cd[1], ab[1]]];
|
|
10201
|
-
const orientations = _.map(triplets, function (triplet) {
|
|
10202
|
-
return sign(ccw(...triplet));
|
|
10203
|
-
});
|
|
10204
|
-
if (orientations[0] !== orientations[1] && orientations[2] !== orientations[3]) {
|
|
10205
|
-
return true;
|
|
10206
|
-
}
|
|
10207
|
-
for (let i = 0; i < 4; i++) {
|
|
10208
|
-
if (orientations[i] === 0 && pointInRect(...triplets[i])) {
|
|
10209
|
-
return true;
|
|
10210
|
-
}
|
|
10211
|
-
}
|
|
10212
|
-
return false;
|
|
10213
|
-
}
|
|
10214
|
-
|
|
10215
|
-
// Whether any two sides of a polygon intersect each other
|
|
10216
|
-
function polygonSidesIntersect(vertices) {
|
|
10217
|
-
for (let i = 0; i < vertices.length; i++) {
|
|
10218
|
-
for (let k = i + 1; k < vertices.length; k++) {
|
|
10219
|
-
// If any two vertices are the same point, sides overlap
|
|
10220
|
-
if (point.equal(vertices[i], vertices[k])) {
|
|
10221
|
-
return true;
|
|
10222
|
-
}
|
|
10223
|
-
|
|
10224
|
-
// Find the other end of the sides starting at vertices i and k
|
|
10225
|
-
const iNext = (i + 1) % vertices.length;
|
|
10226
|
-
const kNext = (k + 1) % vertices.length;
|
|
10227
|
-
|
|
10228
|
-
// Adjacent sides always intersect (at the vertex); skip those
|
|
10229
|
-
if (iNext === k || kNext === i) {
|
|
10230
|
-
continue;
|
|
10231
|
-
}
|
|
10232
|
-
const side1 = [vertices[i], vertices[iNext]];
|
|
10233
|
-
const side2 = [vertices[k], vertices[kNext]];
|
|
10234
|
-
if (intersects(side1, side2)) {
|
|
10235
|
-
return true;
|
|
10236
|
-
}
|
|
10237
|
-
}
|
|
10238
|
-
}
|
|
10239
|
-
return false;
|
|
10240
|
-
}
|
|
10241
|
-
function vector(a, b) {
|
|
10242
|
-
return _.map(_.zip(a, b), function (pair) {
|
|
10243
|
-
return pair[0] - pair[1];
|
|
10244
|
-
});
|
|
10245
|
-
}
|
|
10246
|
-
function reverseVector(vector) {
|
|
10247
|
-
return [-vector[0], -vector[1]];
|
|
10248
|
-
}
|
|
10249
|
-
|
|
10250
|
-
// Returns whether connecting the given sequence of `points` forms a clockwise
|
|
10251
|
-
// path (assuming a closed loop, where the last point connects back to the
|
|
10252
|
-
// first).
|
|
10253
|
-
function clockwise$1(points) {
|
|
10254
|
-
const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
|
|
10255
|
-
const areas = _.map(segments, function (segment) {
|
|
10256
|
-
const p1 = segment[0];
|
|
10257
|
-
const p2 = segment[1];
|
|
10258
|
-
return (p2[0] - p1[0]) * (p2[1] + p1[1]);
|
|
10259
|
-
});
|
|
10260
|
-
return sum$1(areas) > 0;
|
|
10261
|
-
}
|
|
10262
|
-
function magnitude(v) {
|
|
10263
|
-
return Math.sqrt(_.reduce(v, function (memo, el) {
|
|
10264
|
-
// @ts-expect-error - TS2345 - Argument of type 'Coord' is not assignable to parameter of type 'number'.
|
|
10265
|
-
return memo + Math.pow(el, 2);
|
|
10266
|
-
}, 0));
|
|
10267
|
-
}
|
|
10268
|
-
function dotProduct(a, b) {
|
|
10269
|
-
return _.reduce(_.zip(a, b), function (memo, pair) {
|
|
10270
|
-
return memo + pair[0] * pair[1];
|
|
10271
|
-
}, 0);
|
|
10272
|
-
}
|
|
10273
|
-
function sideLengths(coords) {
|
|
10274
|
-
const segments = _.zip(coords, rotate(coords));
|
|
10275
|
-
return segments.map(function (segment) {
|
|
10276
|
-
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
10277
|
-
return magnitude(vector(...segment));
|
|
10278
|
-
});
|
|
10279
|
-
}
|
|
10280
|
-
|
|
10281
|
-
// Based on http://math.stackexchange.com/a/151149
|
|
10282
|
-
function angleMeasures(coords) {
|
|
10283
|
-
const triplets = _.zip(rotate(coords, -1), coords, rotate(coords, 1));
|
|
10284
|
-
const offsets = _.map(triplets, function (triplet) {
|
|
10285
|
-
const p = vector(triplet[1], triplet[0]);
|
|
10286
|
-
const q = vector(triplet[2], triplet[1]);
|
|
10287
|
-
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'Coord'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
|
|
10288
|
-
const raw = Math.acos(dotProduct(p, q) / (magnitude(p) * magnitude(q)));
|
|
10289
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
10290
|
-
return sign(ccw(...triplet)) > 0 ? raw : -raw;
|
|
10291
|
-
});
|
|
10292
|
-
const sum = _.reduce(offsets, function (memo, arg) {
|
|
10293
|
-
return memo + arg;
|
|
10294
|
-
}, 0);
|
|
10295
|
-
return _.map(offsets, function (offset) {
|
|
10296
|
-
return sum > 0 ? Math.PI - offset : Math.PI + offset;
|
|
10297
|
-
});
|
|
10298
|
-
}
|
|
10299
|
-
|
|
10300
|
-
// Whether two polygons are similar (or if specified, congruent)
|
|
10301
|
-
function similar(coords1, coords2, tolerance) {
|
|
10302
|
-
if (coords1.length !== coords2.length) {
|
|
10303
|
-
return false;
|
|
10304
|
-
}
|
|
10305
|
-
const n = coords1.length;
|
|
10306
|
-
const angles1 = angleMeasures(coords1);
|
|
10307
|
-
const angles2 = angleMeasures(coords2);
|
|
10308
|
-
const sides1 = sideLengths(coords1);
|
|
10309
|
-
const sides2 = sideLengths(coords2);
|
|
10310
|
-
for (let i = 0; i < 2 * n; i++) {
|
|
10311
|
-
let angles = angles2.slice();
|
|
10312
|
-
let sides = sides2.slice();
|
|
10313
|
-
|
|
10314
|
-
// Reverse angles and sides to allow matching reflected polygons
|
|
10315
|
-
if (i >= n) {
|
|
10316
|
-
angles.reverse();
|
|
10317
|
-
sides.reverse();
|
|
10318
|
-
// Since sides are calculated from two coordinates,
|
|
10319
|
-
// simply reversing results in an off by one error
|
|
10320
|
-
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
10321
|
-
sides = rotate(sides, 1);
|
|
10322
|
-
}
|
|
10323
|
-
|
|
10324
|
-
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
10325
|
-
angles = rotate(angles, i);
|
|
10326
|
-
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
10327
|
-
sides = rotate(sides, i);
|
|
10328
|
-
if (deepEq$6(angles1, angles)) {
|
|
10329
|
-
const sidePairs = _.zip(sides1, sides);
|
|
10330
|
-
const factors = _.map(sidePairs, function (pair) {
|
|
10331
|
-
return pair[0] / pair[1];
|
|
10332
|
-
});
|
|
10333
|
-
const same = _.all(factors, function (factor) {
|
|
10334
|
-
return eq$3(factors[0], factor);
|
|
10335
|
-
});
|
|
10336
|
-
const congruentEnough = _.all(sidePairs, function (pair) {
|
|
10337
|
-
return number$1.equal(pair[0], pair[1], tolerance);
|
|
10338
|
-
});
|
|
10339
|
-
if (same && congruentEnough) {
|
|
10340
|
-
return true;
|
|
10341
|
-
}
|
|
10342
|
-
}
|
|
10343
|
-
}
|
|
10344
|
-
return false;
|
|
10345
|
-
}
|
|
10346
|
-
|
|
10347
|
-
// Given triangle with sides ABC return angle opposite side C in degrees
|
|
10348
|
-
function lawOfCosines(a, b, c) {
|
|
10349
|
-
return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180 / Math.PI;
|
|
10350
|
-
}
|
|
10351
|
-
function canonicalSineCoefficients$1([amplitude, angularFrequency, phase, verticalOffset]) {
|
|
10352
|
-
// For a curve of the form f(x) = a * Sin(b * x - c) + d,
|
|
10353
|
-
// this function ensures that a, b > 0, and c is its
|
|
10354
|
-
// smallest possible positive value.
|
|
10355
|
-
|
|
10356
|
-
// Guarantee a > 0
|
|
10357
|
-
if (amplitude < 0) {
|
|
10358
|
-
amplitude *= -1;
|
|
10359
|
-
angularFrequency *= -1;
|
|
10360
|
-
phase *= -1;
|
|
10361
|
-
}
|
|
10362
|
-
const period = 2 * Math.PI;
|
|
10363
|
-
// Guarantee b > 0
|
|
10364
|
-
if (angularFrequency < 0) {
|
|
10365
|
-
angularFrequency *= -1;
|
|
10366
|
-
phase *= -1;
|
|
10367
|
-
phase += period / 2;
|
|
10368
|
-
}
|
|
10369
|
-
|
|
10370
|
-
// Guarantee c is smallest possible positive value
|
|
10371
|
-
while (phase > 0) {
|
|
10372
|
-
phase -= period;
|
|
10373
|
-
}
|
|
10374
|
-
while (phase < 0) {
|
|
10375
|
-
phase += period;
|
|
10376
|
-
}
|
|
10377
|
-
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
10378
|
-
}
|
|
10379
|
-
|
|
10380
|
-
// e.g. rotate([1, 2, 3]) -> [2, 3, 1]
|
|
10381
|
-
function rotate(array, n) {
|
|
10382
|
-
n = typeof n === "undefined" ? 1 : n % array.length;
|
|
10383
|
-
return array.slice(n).concat(array.slice(0, n));
|
|
10384
|
-
}
|
|
10385
|
-
function getLineEquation(first, second) {
|
|
10386
|
-
if (eq$3(first[0], second[0])) {
|
|
10387
|
-
return "x = " + first[0].toFixed(3);
|
|
10388
|
-
}
|
|
10389
|
-
const m = (second[1] - first[1]) / (second[0] - first[0]);
|
|
10390
|
-
const b = first[1] - m * first[0];
|
|
10391
|
-
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
10392
|
-
}
|
|
10393
|
-
|
|
10394
|
-
// Stolen from the wikipedia article
|
|
10395
|
-
// http://en.wikipedia.org/wiki/Line-line_intersection
|
|
10396
|
-
function getLineIntersection(
|
|
10397
|
-
// TODO(LP-10725): update these to be 2-tuples
|
|
10398
|
-
firstPoints, secondPoints) {
|
|
10399
|
-
const x1 = firstPoints[0][0];
|
|
10400
|
-
const y1 = firstPoints[0][1];
|
|
10401
|
-
const x2 = firstPoints[1][0];
|
|
10402
|
-
const y2 = firstPoints[1][1];
|
|
10403
|
-
const x3 = secondPoints[0][0];
|
|
10404
|
-
const y3 = secondPoints[0][1];
|
|
10405
|
-
const x4 = secondPoints[1][0];
|
|
10406
|
-
const y4 = secondPoints[1][1];
|
|
10407
|
-
const determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
|
10408
|
-
if (Math.abs(determinant) < 1e-9) {
|
|
10409
|
-
return "Lines are parallel";
|
|
10410
|
-
}
|
|
10411
|
-
const x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / determinant;
|
|
10412
|
-
const y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / determinant;
|
|
10413
|
-
return "Intersection: (" + x.toFixed(3) + ", " + y.toFixed(3) + ")";
|
|
10414
|
-
}
|
|
10415
|
-
|
|
10416
9968
|
/* eslint-disable @babel/no-invalid-this */
|
|
9969
|
+
const {
|
|
9970
|
+
clockwise: clockwise$3,
|
|
9971
|
+
reverseVector: reverseVector$1
|
|
9972
|
+
} = geometry;
|
|
10417
9973
|
function scaledDistanceFromAngle(angle) {
|
|
10418
9974
|
const a = 3.51470560176242 * 20;
|
|
10419
9975
|
const b = 0.5687298702748785 * 20;
|
|
@@ -10482,10 +10038,10 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10482
10038
|
let sRadius = sPadding + scaledDistanceFromAngle(angle);
|
|
10483
10039
|
const temp = [];
|
|
10484
10040
|
if (Math.abs(angle - 90) < 1e-9 && options.showRightAngleMarker) {
|
|
10485
|
-
const v1 = vector$
|
|
10486
|
-
const v2 = vector$
|
|
10041
|
+
const v1 = vector$3.add(sVertex, scaledPolarDeg(sRadius, startAngle));
|
|
10042
|
+
const v2 = vector$3.add(sVertex, scaledPolarDeg(sRadius, endAngle));
|
|
10487
10043
|
sRadius *= Math.SQRT2;
|
|
10488
|
-
const v3 = vector$
|
|
10044
|
+
const v3 = vector$3.add(sVertex, scaledPolarDeg(sRadius, halfAngle));
|
|
10489
10045
|
_.each([v1, v2], function (v) {
|
|
10490
10046
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
10491
10047
|
temp.push(graphie.scaledPath([v, v3], options.style));
|
|
@@ -10506,7 +10062,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10506
10062
|
text = text.replace(match[0], KhanMath.toFixedApprox(angle, precision) + "^{\\circ}");
|
|
10507
10063
|
}
|
|
10508
10064
|
const sOffset = scaledPolarDeg(sRadius + 15, halfAngle);
|
|
10509
|
-
const sPosition = vector$
|
|
10065
|
+
const sPosition = vector$3.add(sVertex, sOffset);
|
|
10510
10066
|
const position = graphie.unscalePoint(sPosition);
|
|
10511
10067
|
|
|
10512
10068
|
// Reuse label if possible
|
|
@@ -10557,7 +10113,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10557
10113
|
const sOffset = sSpacing * (i - (n - 1) / 2);
|
|
10558
10114
|
const sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
|
|
10559
10115
|
const sHeightVector = scaledPolarRad(sHeight, perpendicularAngle);
|
|
10560
|
-
const sPath = [vector$
|
|
10116
|
+
const sPath = [vector$3.add(sMidpoint, sOffsetVector, sHeightVector), vector$3.add(sMidpoint, sOffsetVector, reverseVector$1(sHeightVector))];
|
|
10561
10117
|
|
|
10562
10118
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
10563
10119
|
temp.push(graphie.scaledPath(sPath, style));
|
|
@@ -10582,9 +10138,9 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10582
10138
|
const sOffset = sCumulativeOffset + sSpacing * i;
|
|
10583
10139
|
let sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
|
|
10584
10140
|
if (start !== p1) {
|
|
10585
|
-
sOffsetVector = reverseVector(sOffsetVector);
|
|
10141
|
+
sOffsetVector = reverseVector$1(sOffsetVector);
|
|
10586
10142
|
}
|
|
10587
|
-
const sEnd = vector$
|
|
10143
|
+
const sEnd = vector$3.add(sMidpoint, sOffsetVector);
|
|
10588
10144
|
|
|
10589
10145
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
10590
10146
|
temp.push(graphie.scaledPath([sStart, sEnd], style));
|
|
@@ -10600,7 +10156,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10600
10156
|
}
|
|
10601
10157
|
const sOffset = 20;
|
|
10602
10158
|
const sOffsetVector = scaledPolarRad(sOffset, perpendicularAngle);
|
|
10603
|
-
const sPosition = vector$
|
|
10159
|
+
const sPosition = vector$3.add(sMidpoint, sOffsetVector);
|
|
10604
10160
|
const position = graphie.unscalePoint(sPosition);
|
|
10605
10161
|
|
|
10606
10162
|
// Reuse label if possible
|
|
@@ -10658,7 +10214,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10658
10214
|
}
|
|
10659
10215
|
const sRadius = 10 + scaledDistanceFromAngle(360 - angle);
|
|
10660
10216
|
const sOffsetVector = scaledPolarDeg(sRadius, halfAngle);
|
|
10661
|
-
const sPosition = vector$
|
|
10217
|
+
const sPosition = vector$3.add(sVertex, sOffsetVector);
|
|
10662
10218
|
const position = graphie.unscalePoint(sPosition);
|
|
10663
10219
|
|
|
10664
10220
|
// Reuse label if possible
|
|
@@ -10934,7 +10490,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10934
10490
|
|
|
10935
10491
|
// Adjust the target coordinate by accounting for the gesture's
|
|
10936
10492
|
// offset.
|
|
10937
|
-
let coord = vector$
|
|
10493
|
+
let coord = vector$3.add(graph.getMouseCoord(event), offset);
|
|
10938
10494
|
coord = applySnapAndConstraints(coord);
|
|
10939
10495
|
let coordX = coord[0];
|
|
10940
10496
|
let coordY = coord[1];
|
|
@@ -11045,7 +10601,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
11045
10601
|
const startCoord = movablePoint.coord;
|
|
11046
10602
|
const startMouseCoord = graph.getMouseCoord(event);
|
|
11047
10603
|
const isMouse = !("ontouchstart" in window);
|
|
11048
|
-
const touchOffset = isMouse ? [0, 0] : vector$
|
|
10604
|
+
const touchOffset = isMouse ? [0, 0] : vector$3.subtract(startCoord, startMouseCoord);
|
|
11049
10605
|
movablePoint.grab(touchOffset);
|
|
11050
10606
|
}
|
|
11051
10607
|
});
|
|
@@ -11265,17 +10821,17 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
11265
10821
|
const getScaledAngle = function getScaledAngle(line) {
|
|
11266
10822
|
const scaledA = line.graph.scalePoint(line.coordA);
|
|
11267
10823
|
const scaledZ = line.graph.scalePoint(line.coordZ);
|
|
11268
|
-
return vector$
|
|
10824
|
+
return vector$3.polarDegFromCart(vector$3.subtract(scaledZ, scaledA))[1];
|
|
11269
10825
|
};
|
|
11270
10826
|
const getClipPoint = function getClipPoint(graph, coord, angle) {
|
|
11271
10827
|
graph = lineSegment.graph;
|
|
11272
10828
|
const xExtent = graph.range[0][1] - graph.range[0][0];
|
|
11273
10829
|
const yExtent = graph.range[1][1] - graph.range[1][0];
|
|
11274
10830
|
const distance = xExtent + yExtent;
|
|
11275
|
-
const angleVec = graph.unscaleVector(vector$
|
|
11276
|
-
const distVec = vector$
|
|
11277
|
-
const farCoord = vector$
|
|
11278
|
-
const scaledAngle = vector$
|
|
10831
|
+
const angleVec = graph.unscaleVector(vector$3.cartFromPolarDeg(1, angle));
|
|
10832
|
+
const distVec = vector$3.scale(vector$3.normalize(angleVec), distance);
|
|
10833
|
+
const farCoord = vector$3.add(coord, distVec);
|
|
10834
|
+
const scaledAngle = vector$3.polarDegFromCart(angleVec)[1];
|
|
11279
10835
|
const clipPoint = graph.constrainToBoundsOnAngle(farCoord, 4, scaledAngle * Math.PI / 180);
|
|
11280
10836
|
return clipPoint;
|
|
11281
10837
|
};
|
|
@@ -11649,7 +11205,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
11649
11205
|
// Temporary objects
|
|
11650
11206
|
_.invoke(polygon.temp, "remove");
|
|
11651
11207
|
polygon.temp = [];
|
|
11652
|
-
const isClockwise = clockwise$
|
|
11208
|
+
const isClockwise = clockwise$3(polygon.coords);
|
|
11653
11209
|
|
|
11654
11210
|
// Update angle labels
|
|
11655
11211
|
if (polygon.angleLabels.length || polygon.showRightAngleMarkers.length) {
|
|
@@ -12177,8 +11733,8 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12177
11733
|
const drawRotateHandle = function drawRotateHandle(graphie, center, radius, halfWidth, lengthAngle, angle, interacting) {
|
|
12178
11734
|
const getRotateHandlePoint = function getRotateHandlePoint(offset, distanceFromArrowMidline) {
|
|
12179
11735
|
const distFromRotationCenter = radius + distanceFromArrowMidline;
|
|
12180
|
-
const vec = vector$
|
|
12181
|
-
const absolute = vector$
|
|
11736
|
+
const vec = vector$3.cartFromPolarDeg(distFromRotationCenter, angle + offset);
|
|
11737
|
+
const absolute = vector$3.add(center, vec);
|
|
12182
11738
|
const pixels = graphie.scalePoint(absolute);
|
|
12183
11739
|
return pixels[0] + "," + pixels[1];
|
|
12184
11740
|
};
|
|
@@ -12218,7 +11774,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12218
11774
|
};
|
|
12219
11775
|
}
|
|
12220
11776
|
const rotateHandle = graph.addMovablePoint({
|
|
12221
|
-
coord: point.addVector(rotatePoint.coord, vector$
|
|
11777
|
+
coord: point.addVector(rotatePoint.coord, vector$3.cartFromPolarDeg(radius, options.angleDeg || 0)),
|
|
12222
11778
|
constraints: {
|
|
12223
11779
|
fixedDistance: {
|
|
12224
11780
|
dist: radius,
|
|
@@ -12243,8 +11799,8 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12243
11799
|
return; // Don't draw anything!
|
|
12244
11800
|
}
|
|
12245
11801
|
|
|
12246
|
-
const handleVec = vector$
|
|
12247
|
-
const handlePolar = vector$
|
|
11802
|
+
const handleVec = vector$3.subtract(handleCoord, rotatePoint.coord);
|
|
11803
|
+
const handlePolar = vector$3.polarDegFromCart(handleVec);
|
|
12248
11804
|
const angle = handlePolar[1];
|
|
12249
11805
|
if (drawnRotateHandle) {
|
|
12250
11806
|
drawnRotateHandle.remove();
|
|
@@ -12256,8 +11812,8 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12256
11812
|
// the rotationHandle as well, or it will end up out
|
|
12257
11813
|
// of sync
|
|
12258
11814
|
$(rotatePoint).on("move." + id, function () {
|
|
12259
|
-
const delta = vector$
|
|
12260
|
-
rotateHandle.setCoord(vector$
|
|
11815
|
+
const delta = vector$3.subtract(rotatePoint.coord, rotatePointPrevCoord);
|
|
11816
|
+
rotateHandle.setCoord(vector$3.add(rotateHandle.coord, delta));
|
|
12261
11817
|
redrawRotateHandle(rotateHandle.coord);
|
|
12262
11818
|
rotatePointPrevCoord = rotatePoint.coord;
|
|
12263
11819
|
rotateHandle.constraints.fixedDistance.point = rotatePoint;
|
|
@@ -12272,8 +11828,8 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12272
11828
|
}
|
|
12273
11829
|
let coord = [x, y];
|
|
12274
11830
|
if (options.onMove) {
|
|
12275
|
-
const oldPolar = vector$
|
|
12276
|
-
const newPolar = vector$
|
|
11831
|
+
const oldPolar = vector$3.polarDegFromCart(vector$3.subtract(rotateHandlePrevCoord, rotatePoint.coord));
|
|
11832
|
+
const newPolar = vector$3.polarDegFromCart(vector$3.subtract(coord, rotatePoint.coord));
|
|
12277
11833
|
const oldAngle = oldPolar[1];
|
|
12278
11834
|
const newAngle = newPolar[1];
|
|
12279
11835
|
let result = options.onMove(newAngle, oldAngle);
|
|
@@ -12281,7 +11837,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12281
11837
|
if (result === false) {
|
|
12282
11838
|
result = oldAngle;
|
|
12283
11839
|
}
|
|
12284
|
-
coord = vector$
|
|
11840
|
+
coord = vector$3.add(rotatePoint.coord, vector$3.cartFromPolarDeg(oldPolar[0], result));
|
|
12285
11841
|
}
|
|
12286
11842
|
}
|
|
12287
11843
|
redrawRotateHandle(coord);
|
|
@@ -12292,8 +11848,8 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12292
11848
|
isRotating = false;
|
|
12293
11849
|
redrawRotateHandle(rotateHandle.coord);
|
|
12294
11850
|
if (options.onMoveEnd) {
|
|
12295
|
-
const oldPolar = vector$
|
|
12296
|
-
const newPolar = vector$
|
|
11851
|
+
const oldPolar = vector$3.polarDegFromCart(vector$3.subtract(rotateHandleStartCoord, rotatePoint.coord));
|
|
11852
|
+
const newPolar = vector$3.polarDegFromCart(vector$3.subtract(rotateHandle.coord, rotatePoint.coord));
|
|
12297
11853
|
options.onMoveEnd(newPolar[1], oldPolar[1]);
|
|
12298
11854
|
}
|
|
12299
11855
|
};
|
|
@@ -12338,14 +11894,14 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12338
11894
|
if (point.equal(lineCoords[0], lineCoords[1])) {
|
|
12339
11895
|
lineCoords = [lineCoords[0], point.addVector(lineCoords[0], [1, 1])];
|
|
12340
11896
|
}
|
|
12341
|
-
const lineDirection = vector$
|
|
12342
|
-
const lineVec = vector$
|
|
12343
|
-
const centerVec = vector$
|
|
12344
|
-
const leftCenterVec = vector$
|
|
12345
|
-
const rightCenterVec = vector$
|
|
12346
|
-
const negLineVec = vector$
|
|
12347
|
-
const leftVec = vector$
|
|
12348
|
-
const rightVec = vector$
|
|
11897
|
+
const lineDirection = vector$3.normalize(vector$3.subtract(lineCoords[1], lineCoords[0]));
|
|
11898
|
+
const lineVec = vector$3.scale(lineDirection, size / 2);
|
|
11899
|
+
const centerVec = vector$3.scale(lineDirection, distanceFromCenter);
|
|
11900
|
+
const leftCenterVec = vector$3.rotateDeg(centerVec, 90);
|
|
11901
|
+
const rightCenterVec = vector$3.rotateDeg(centerVec, -90);
|
|
11902
|
+
const negLineVec = vector$3.negate(lineVec);
|
|
11903
|
+
const leftVec = vector$3.rotateDeg(lineVec, 90);
|
|
11904
|
+
const rightVec = vector$3.rotateDeg(lineVec, -90);
|
|
12349
11905
|
const leftCenter = point.addVectors(buttonCoord, leftCenterVec);
|
|
12350
11906
|
const rightCenter = point.addVectors(buttonCoord, rightCenterVec);
|
|
12351
11907
|
const leftCoord1 = point.addVectors(buttonCoord, leftCenterVec, lineVec, leftVec);
|
|
@@ -12472,7 +12028,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12472
12028
|
// @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
|
|
12473
12029
|
return new Ruler(this, options || {});
|
|
12474
12030
|
},
|
|
12475
|
-
addPoints: vector$
|
|
12031
|
+
addPoints: vector$3.add
|
|
12476
12032
|
});
|
|
12477
12033
|
function Protractor$1(graph, center) {
|
|
12478
12034
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
@@ -12746,10 +12302,10 @@ function Ruler(graphie, options) {
|
|
|
12746
12302
|
});
|
|
12747
12303
|
const width = options.units * options.pixelsPerUnit;
|
|
12748
12304
|
const height = 50;
|
|
12749
|
-
const leftBottom = graphie.unscalePoint(vector$
|
|
12305
|
+
const leftBottom = graphie.unscalePoint(vector$3.subtract(graphie.scalePoint(options.center), vector$3.scale([width, -height], 0.5)));
|
|
12750
12306
|
const graphieUnitsPerUnit = options.pixelsPerUnit / graphie.scale[0];
|
|
12751
12307
|
const graphieUnitsHeight = height / graphie.scale[0];
|
|
12752
|
-
const rightTop = vector$
|
|
12308
|
+
const rightTop = vector$3.add(leftBottom, [options.units * graphieUnitsPerUnit, graphieUnitsHeight]);
|
|
12753
12309
|
const tickHeight = 1.0;
|
|
12754
12310
|
let tickHeightMap;
|
|
12755
12311
|
if (options.ticksPerUnit === 10) {
|
|
@@ -12896,11 +12452,11 @@ function Ruler(graphie, options) {
|
|
|
12896
12452
|
const dy = rightBottomHandle.coord[1] - y;
|
|
12897
12453
|
const dx = rightBottomHandle.coord[0] - x;
|
|
12898
12454
|
const angle = Math.atan2(dy, dx) * 180 / Math.PI;
|
|
12899
|
-
const center = vector$
|
|
12455
|
+
const center = vector$3.scale(vector$3.add([x, y], rightBottomHandle.coord), 0.5);
|
|
12900
12456
|
const scaledCenter = graphie.scalePoint(center);
|
|
12901
|
-
const oldCenter = vector$
|
|
12457
|
+
const oldCenter = vector$3.scale(vector$3.add(leftBottomHandle.coord, rightBottomHandle.coord), 0.5);
|
|
12902
12458
|
const scaledOldCenter = graphie.scalePoint(oldCenter);
|
|
12903
|
-
const diff = vector$
|
|
12459
|
+
const diff = vector$3.subtract(scaledCenter, scaledOldCenter);
|
|
12904
12460
|
set.rotate(-angle, scaledOldCenter[0], scaledOldCenter[1]);
|
|
12905
12461
|
set.translate(diff[0], diff[1]);
|
|
12906
12462
|
}
|
|
@@ -12923,11 +12479,11 @@ function Ruler(graphie, options) {
|
|
|
12923
12479
|
const dy = y - leftBottomHandle.coord[1];
|
|
12924
12480
|
const dx = x - leftBottomHandle.coord[0];
|
|
12925
12481
|
const angle = Math.atan2(dy, dx) * 180 / Math.PI;
|
|
12926
|
-
const center = vector$
|
|
12482
|
+
const center = vector$3.scale(vector$3.add([x, y], leftBottomHandle.coord), 0.5);
|
|
12927
12483
|
const scaledCenter = graphie.scalePoint(center);
|
|
12928
|
-
const oldCenter = vector$
|
|
12484
|
+
const oldCenter = vector$3.scale(vector$3.add(leftBottomHandle.coord, rightBottomHandle.coord), 0.5);
|
|
12929
12485
|
const scaledOldCenter = graphie.scalePoint(oldCenter);
|
|
12930
|
-
const diff = vector$
|
|
12486
|
+
const diff = vector$3.subtract(scaledCenter, scaledOldCenter);
|
|
12931
12487
|
set.rotate(-angle, scaledOldCenter[0], scaledOldCenter[1]);
|
|
12932
12488
|
set.translate(diff[0], diff[1]);
|
|
12933
12489
|
}
|
|
@@ -13070,12 +12626,12 @@ _.extend(MovableAngle.prototype, {
|
|
|
13070
12626
|
points[1].onMove = function (x, y) {
|
|
13071
12627
|
const oldVertex = points[1].coord;
|
|
13072
12628
|
const newVertex = [x, y];
|
|
13073
|
-
const delta = vector$
|
|
12629
|
+
const delta = vector$3.add(newVertex, reverseVector$1(oldVertex));
|
|
13074
12630
|
let valid = true;
|
|
13075
12631
|
const newPoints = {};
|
|
13076
12632
|
_.each([0, 2], function (i) {
|
|
13077
12633
|
const oldPoint = points[i].coord;
|
|
13078
|
-
let newPoint = vector$
|
|
12634
|
+
let newPoint = vector$3.add(oldPoint, delta);
|
|
13079
12635
|
let angle = GraphUtils.findAngleDeprecated(newVertex, newPoint);
|
|
13080
12636
|
angle *= Math.PI / 180;
|
|
13081
12637
|
newPoint = graphie.constrainToBoundsOnAngle(newPoint, 10, angle);
|
|
@@ -13112,7 +12668,7 @@ _.extend(MovableAngle.prototype, {
|
|
|
13112
12668
|
let angle = GraphUtils.findAngleDeprecated(newPoint, vertex);
|
|
13113
12669
|
angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
|
|
13114
12670
|
const distance = GraphUtils.getDistance(newPoint, vertex);
|
|
13115
|
-
return vector$
|
|
12671
|
+
return vector$3.add(vertex, polar$2(distance, angle));
|
|
13116
12672
|
}
|
|
13117
12673
|
return true;
|
|
13118
12674
|
};
|
|
@@ -13263,7 +12819,6 @@ _.extend(MovableAngle.prototype, {
|
|
|
13263
12819
|
|
|
13264
12820
|
/* eslint-disable @babel/no-invalid-this */
|
|
13265
12821
|
const nestedMap$1 = Util.nestedMap;
|
|
13266
|
-
const deepEq$5 = Util.deepEq;
|
|
13267
12822
|
|
|
13268
12823
|
/**
|
|
13269
12824
|
* A base class for all Graphie Movables
|
|
@@ -13360,7 +12915,7 @@ const createSimpleClass = function createSimpleClass(addFunction) {
|
|
|
13360
12915
|
this._prevProps = this.props;
|
|
13361
12916
|
},
|
|
13362
12917
|
modify: function (graphie) {
|
|
13363
|
-
if (!
|
|
12918
|
+
if (!approximateDeepEqual(this.props, this._prevProps)) {
|
|
13364
12919
|
this.remove();
|
|
13365
12920
|
this.add(graphie);
|
|
13366
12921
|
this._prevProps = this.props;
|
|
@@ -13721,7 +13276,7 @@ function getEventName$1(key) {
|
|
|
13721
13276
|
const getScaledAngle = function getScaledAngle(line) {
|
|
13722
13277
|
const scaledA = line.graphie.scalePoint(line.coord(0));
|
|
13723
13278
|
const scaledZ = line.graphie.scalePoint(line.coord(1));
|
|
13724
|
-
return vector$
|
|
13279
|
+
return vector$3.polarDegFromCart(vector$3.subtract(scaledZ, scaledA))[1];
|
|
13725
13280
|
};
|
|
13726
13281
|
|
|
13727
13282
|
/**
|
|
@@ -13880,11 +13435,11 @@ const constraints$2 = {
|
|
|
13880
13435
|
if (snap === null) {
|
|
13881
13436
|
return true;
|
|
13882
13437
|
}
|
|
13883
|
-
let delta = vector$
|
|
13438
|
+
let delta = vector$3.subtract(coord, prevCoord);
|
|
13884
13439
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
13885
13440
|
snap = snap || this.graphie.snap;
|
|
13886
13441
|
delta = point.roundTo(delta, snap);
|
|
13887
|
-
return vector$
|
|
13442
|
+
return vector$3.add(prevCoord, delta);
|
|
13888
13443
|
};
|
|
13889
13444
|
},
|
|
13890
13445
|
bound: function (range, snap, paddingPx) {
|
|
@@ -13898,7 +13453,7 @@ const constraints$2 = {
|
|
|
13898
13453
|
return function (coord, prevCoord) {
|
|
13899
13454
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
13900
13455
|
const graphie = this.graphie;
|
|
13901
|
-
const delta = vector$
|
|
13456
|
+
const delta = vector$3.subtract(coord, prevCoord);
|
|
13902
13457
|
range = range || graphie.range;
|
|
13903
13458
|
// A null snap means no snap; an undefined snap means
|
|
13904
13459
|
// default to graphie's
|
|
@@ -13918,8 +13473,8 @@ const constraints$2 = {
|
|
|
13918
13473
|
const deltaBounds = _.map(
|
|
13919
13474
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
13920
13475
|
this.coords(), function (coord, i) {
|
|
13921
|
-
const max = vector$
|
|
13922
|
-
const min = vector$
|
|
13476
|
+
const max = vector$3.subtract(absoluteUpper, coord);
|
|
13477
|
+
const min = vector$3.subtract(absoluteLower, coord);
|
|
13923
13478
|
return [min, max];
|
|
13924
13479
|
});
|
|
13925
13480
|
|
|
@@ -13933,7 +13488,7 @@ const constraints$2 = {
|
|
|
13933
13488
|
const deltaY = Math.max(lower[1], Math.min(upper[1], delta[1]));
|
|
13934
13489
|
return [deltaX, deltaY];
|
|
13935
13490
|
}, delta);
|
|
13936
|
-
return vector$
|
|
13491
|
+
return vector$3.add(prevCoord, boundedDelta);
|
|
13937
13492
|
};
|
|
13938
13493
|
}
|
|
13939
13494
|
};
|
|
@@ -13942,10 +13497,10 @@ const constraints$2 = {
|
|
|
13942
13497
|
constraints$2.standard = null;
|
|
13943
13498
|
const onMove$1 = {
|
|
13944
13499
|
updatePoints: function (coord, prevCoord) {
|
|
13945
|
-
const actualDelta = vector$
|
|
13500
|
+
const actualDelta = vector$3.subtract(coord, prevCoord);
|
|
13946
13501
|
// @ts-expect-error - TS2339 - Property 'state' does not exist on type '{ readonly updatePoints: (coord: Coord, prevCoord: Coord) => void; }'.
|
|
13947
13502
|
_.each(this.state.points, function (point) {
|
|
13948
|
-
point.setCoord(vector$
|
|
13503
|
+
point.setCoord(vector$3.add(point.coord(), actualDelta));
|
|
13949
13504
|
});
|
|
13950
13505
|
}
|
|
13951
13506
|
};
|
|
@@ -14107,9 +13662,9 @@ _.extend(MovableLine$4.prototype, {
|
|
|
14107
13662
|
self._fireEvent(self.state.onMoveStart, self.coord(0), self.coord(0));
|
|
14108
13663
|
},
|
|
14109
13664
|
onMove: function (mouseCoord, prevMouseCoord) {
|
|
14110
|
-
const delta = vector$
|
|
14111
|
-
self._totalDelta = vector$
|
|
14112
|
-
let refCoord = vector$
|
|
13665
|
+
const delta = vector$3.subtract(mouseCoord, prevMouseCoord);
|
|
13666
|
+
self._totalDelta = vector$3.add(self._totalDelta, delta);
|
|
13667
|
+
let refCoord = vector$3.add(self._initialRefCoord, self._totalDelta);
|
|
14113
13668
|
refCoord = self._applyConstraints(refCoord, self._prevRefCoord);
|
|
14114
13669
|
if (refCoord === false) {
|
|
14115
13670
|
return;
|
|
@@ -14621,7 +14176,7 @@ class MovablePoint$5 {
|
|
|
14621
14176
|
// touch-based interaction.
|
|
14622
14177
|
if (state.touchOffset == null) {
|
|
14623
14178
|
const isMouse = !("ontouchstart" in window);
|
|
14624
|
-
state.touchOffset = isMouse ? [0, 0] : vector$
|
|
14179
|
+
state.touchOffset = isMouse ? [0, 0] : vector$3.subtract(startCoord, startMouseCoord);
|
|
14625
14180
|
}
|
|
14626
14181
|
const svgElem = state.visibleShape.wrapper;
|
|
14627
14182
|
if (state.shadow) {
|
|
@@ -14648,7 +14203,7 @@ class MovablePoint$5 {
|
|
|
14648
14203
|
self.draw();
|
|
14649
14204
|
},
|
|
14650
14205
|
onMove: (mouseCoord, prevMouseCoord) => {
|
|
14651
|
-
const transformedCoord = vector$
|
|
14206
|
+
const transformedCoord = vector$3.add(mouseCoord, state.touchOffset);
|
|
14652
14207
|
self.moveTo(transformedCoord);
|
|
14653
14208
|
if (state.showHairlines) {
|
|
14654
14209
|
if (!this.state.outOfBounds) {
|
|
@@ -15041,7 +14596,7 @@ function sum(array) {
|
|
|
15041
14596
|
return memo + arg;
|
|
15042
14597
|
}, 0);
|
|
15043
14598
|
}
|
|
15044
|
-
function clockwise(points) {
|
|
14599
|
+
function clockwise$2(points) {
|
|
15045
14600
|
const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
|
|
15046
14601
|
const areas = _.map(segments, function (segment) {
|
|
15047
14602
|
const p1 = segment[0];
|
|
@@ -15107,7 +14662,7 @@ const draw$1 = {
|
|
|
15107
14662
|
const graphie = this.graphie;
|
|
15108
14663
|
const self = this;
|
|
15109
14664
|
const coords = _.invoke(state.points, "coord");
|
|
15110
|
-
const isClockwise = clockwise(coords);
|
|
14665
|
+
const isClockwise = clockwise$2(coords);
|
|
15111
14666
|
const n = coords.length;
|
|
15112
14667
|
|
|
15113
14668
|
// graphie.labelAngle and similar methods attempt to re-use the label
|
|
@@ -15278,7 +14833,7 @@ const constraints = {
|
|
|
15278
14833
|
return function (coord, prevCoord) {
|
|
15279
14834
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
15280
14835
|
const graphie = this.graphie;
|
|
15281
|
-
const delta = vector$
|
|
14836
|
+
const delta = vector$3.subtract(coord, prevCoord);
|
|
15282
14837
|
range = range || graphie.range;
|
|
15283
14838
|
// A null snap means no snap; an undefined snap means
|
|
15284
14839
|
// default to graphie's
|
|
@@ -15297,8 +14852,8 @@ const constraints = {
|
|
|
15297
14852
|
// Calculate the bounds for the delta.
|
|
15298
14853
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
15299
14854
|
const deltaBounds = _.map(this.coords(), function (coord, i) {
|
|
15300
|
-
const max = vector$
|
|
15301
|
-
const min = vector$
|
|
14855
|
+
const max = vector$3.subtract(absoluteUpper, coord);
|
|
14856
|
+
const min = vector$3.subtract(absoluteLower, coord);
|
|
15302
14857
|
return [min, max];
|
|
15303
14858
|
});
|
|
15304
14859
|
|
|
@@ -15312,7 +14867,7 @@ const constraints = {
|
|
|
15312
14867
|
const deltaY = Math.max(lower[1], Math.min(upper[1], delta[1]));
|
|
15313
14868
|
return [deltaX, deltaY];
|
|
15314
14869
|
}, delta);
|
|
15315
|
-
return vector$
|
|
14870
|
+
return vector$3.add(prevCoord, boundedDelta);
|
|
15316
14871
|
};
|
|
15317
14872
|
}
|
|
15318
14873
|
};
|
|
@@ -15321,10 +14876,10 @@ const constraints = {
|
|
|
15321
14876
|
constraints.standard = null;
|
|
15322
14877
|
const onMove = {
|
|
15323
14878
|
updatePoints: function (coord, prevCoord) {
|
|
15324
|
-
const actualDelta = vector$
|
|
14879
|
+
const actualDelta = vector$3.subtract(coord, prevCoord);
|
|
15325
14880
|
// @ts-expect-error - TS2339 - Property 'state' does not exist on type '{ readonly updatePoints: (coord: Coord, prevCoord: Coord) => undefined; }'.
|
|
15326
14881
|
_.each(this.state.points, function (point) {
|
|
15327
|
-
point.setCoord(vector$
|
|
14882
|
+
point.setCoord(vector$3.add(point.coord(), actualDelta));
|
|
15328
14883
|
});
|
|
15329
14884
|
}
|
|
15330
14885
|
};
|
|
@@ -15498,9 +15053,9 @@ _.extend(MovablePolygon.prototype, {
|
|
|
15498
15053
|
self._fireEvent(self.state.onMoveStart, self.coord(0), self.coord(0));
|
|
15499
15054
|
},
|
|
15500
15055
|
onMove: function (mouseCoord, prevMouseCoord) {
|
|
15501
|
-
const delta = vector$
|
|
15502
|
-
self._totalDelta = vector$
|
|
15503
|
-
let refCoord = vector$
|
|
15056
|
+
const delta = vector$3.subtract(mouseCoord, prevMouseCoord);
|
|
15057
|
+
self._totalDelta = vector$3.add(self._totalDelta, delta);
|
|
15058
|
+
let refCoord = vector$3.add(self._initialRefCoord, self._totalDelta);
|
|
15504
15059
|
refCoord = self._applyConstraints(refCoord, self._prevRefCoord);
|
|
15505
15060
|
if (refCoord === false) {
|
|
15506
15061
|
return;
|
|
@@ -15815,7 +15370,7 @@ const Parabola = GraphieClasses.createClass({
|
|
|
15815
15370
|
this.parabola.toFront();
|
|
15816
15371
|
}
|
|
15817
15372
|
});
|
|
15818
|
-
const Sinusoid
|
|
15373
|
+
const Sinusoid = GraphieClasses.createClass({
|
|
15819
15374
|
displayName: "Sinusoid",
|
|
15820
15375
|
movableProps: ["children"],
|
|
15821
15376
|
add: function (graphie) {
|
|
@@ -15837,7 +15392,7 @@ const Sinusoid$1 = GraphieClasses.createClass({
|
|
|
15837
15392
|
this.sinusoid.toFront();
|
|
15838
15393
|
}
|
|
15839
15394
|
});
|
|
15840
|
-
const Plot$
|
|
15395
|
+
const Plot$1 = GraphieClasses.createSimpleClass((graphie, props) => {
|
|
15841
15396
|
return graphie.plot(props.fn, props.range, props.style);
|
|
15842
15397
|
});
|
|
15843
15398
|
const PlotParametric$1 = GraphieClasses.createSimpleClass((graphie, props) => {
|
|
@@ -15895,17 +15450,16 @@ var Movables = {
|
|
|
15895
15450
|
MovablePoint: MovablePoint$4,
|
|
15896
15451
|
Parabola: Parabola,
|
|
15897
15452
|
Path: Path,
|
|
15898
|
-
Plot: Plot$
|
|
15453
|
+
Plot: Plot$1,
|
|
15899
15454
|
PlotParametric: PlotParametric$1,
|
|
15900
15455
|
Point: Point$1,
|
|
15901
|
-
Sinusoid: Sinusoid
|
|
15456
|
+
Sinusoid: Sinusoid,
|
|
15902
15457
|
Rect: Rect$1
|
|
15903
15458
|
};
|
|
15904
15459
|
|
|
15905
15460
|
const GraphieMovable = GraphieClasses.GraphieMovable;
|
|
15906
15461
|
const createGraphie = GraphUtils.createGraphie;
|
|
15907
15462
|
const {
|
|
15908
|
-
deepEq: deepEq$4,
|
|
15909
15463
|
nestedMap
|
|
15910
15464
|
} = Util;
|
|
15911
15465
|
const {
|
|
@@ -16147,7 +15701,7 @@ class Graphie extends React.Component {
|
|
|
16147
15701
|
if (this.props.setup !== prevProps.setup) {
|
|
16148
15702
|
Log.error("<Graphie> was given a new setup function. " + "This is a bad idea; please refactor your code to give " + "the same setup function reference to <Graphie> on " + "every render.", Errors.Internal);
|
|
16149
15703
|
}
|
|
16150
|
-
if (!
|
|
15704
|
+
if (!approximateDeepEqual(this.props.options, prevProps.options) || !approximateDeepEqual(this.props.box, prevProps.box) || !approximateDeepEqual(this.props.range, prevProps.range)) {
|
|
16151
15705
|
this._setupGraphie();
|
|
16152
15706
|
}
|
|
16153
15707
|
this._updateMovables();
|
|
@@ -20902,230 +20456,6 @@ const getPromptJSON$s = (renderProps, userInput) => {
|
|
|
20902
20456
|
};
|
|
20903
20457
|
};
|
|
20904
20458
|
|
|
20905
|
-
/*
|
|
20906
|
-
* In this file, an `expression` is some portion of valid TeX enclosed in
|
|
20907
|
-
* curly brackets.
|
|
20908
|
-
*/
|
|
20909
|
-
|
|
20910
|
-
/*
|
|
20911
|
-
* Find the index at which an expression ends, i.e., has an unmatched
|
|
20912
|
-
* closing curly bracket. This method assumes that we start with a non-open
|
|
20913
|
-
* bracket character and end when we've seen more left than right brackets
|
|
20914
|
-
* (rather than assuming that we start with a bracket character and wait for
|
|
20915
|
-
* bracket equality).
|
|
20916
|
-
*/
|
|
20917
|
-
function findEndpoint(tex, currentIndex) {
|
|
20918
|
-
let bracketDepth = 0;
|
|
20919
|
-
for (let i = currentIndex, len = tex.length; i < len; i++) {
|
|
20920
|
-
const c = tex[i];
|
|
20921
|
-
if (c === "{") {
|
|
20922
|
-
bracketDepth++;
|
|
20923
|
-
} else if (c === "}") {
|
|
20924
|
-
bracketDepth--;
|
|
20925
|
-
}
|
|
20926
|
-
if (bracketDepth < 0) {
|
|
20927
|
-
return i;
|
|
20928
|
-
}
|
|
20929
|
-
}
|
|
20930
|
-
// If we never see unbalanced curly brackets, default to the
|
|
20931
|
-
// entire string
|
|
20932
|
-
return tex.length;
|
|
20933
|
-
}
|
|
20934
|
-
|
|
20935
|
-
/*
|
|
20936
|
-
* Parses an individual set of curly brackets into TeX.
|
|
20937
|
-
*/
|
|
20938
|
-
function parseNextExpression(tex, currentIndex, handler) {
|
|
20939
|
-
// Find the first '{' and grab subsequent TeX
|
|
20940
|
-
// Ex) tex: '{3}{7}', and we want the '3'
|
|
20941
|
-
const openBracketIndex = tex.indexOf("{", currentIndex);
|
|
20942
|
-
const nextExpIndex = openBracketIndex + 1;
|
|
20943
|
-
|
|
20944
|
-
// Truncate to only contain remaining TeX
|
|
20945
|
-
const endpoint = findEndpoint(tex, nextExpIndex);
|
|
20946
|
-
const expressionTeX = tex.substring(nextExpIndex, endpoint);
|
|
20947
|
-
const parsedExp = walkTex(expressionTeX, handler);
|
|
20948
|
-
return {
|
|
20949
|
-
endpoint: endpoint,
|
|
20950
|
-
expression: parsedExp
|
|
20951
|
-
};
|
|
20952
|
-
}
|
|
20953
|
-
function getNextFracIndex(tex, currentIndex) {
|
|
20954
|
-
const dfrac = "\\dfrac";
|
|
20955
|
-
const frac = "\\frac";
|
|
20956
|
-
const nextFrac = tex.indexOf(frac, currentIndex);
|
|
20957
|
-
const nextDFrac = tex.indexOf(dfrac, currentIndex);
|
|
20958
|
-
if (nextFrac > -1 && nextDFrac > -1) {
|
|
20959
|
-
return Math.min(nextFrac, nextDFrac);
|
|
20960
|
-
}
|
|
20961
|
-
if (nextFrac > -1) {
|
|
20962
|
-
return nextFrac;
|
|
20963
|
-
}
|
|
20964
|
-
if (nextDFrac > -1) {
|
|
20965
|
-
return nextDFrac;
|
|
20966
|
-
}
|
|
20967
|
-
return -1;
|
|
20968
|
-
}
|
|
20969
|
-
function walkTex(tex, handler) {
|
|
20970
|
-
if (!tex) {
|
|
20971
|
-
return "";
|
|
20972
|
-
}
|
|
20973
|
-
|
|
20974
|
-
// Ex) tex: '2 \dfrac {3}{7}'
|
|
20975
|
-
let parsedString = "";
|
|
20976
|
-
let currentIndex = 0;
|
|
20977
|
-
let nextFrac = getNextFracIndex(tex, currentIndex);
|
|
20978
|
-
|
|
20979
|
-
// For each \dfrac, find the two expressions (wrapped in {}) and recur
|
|
20980
|
-
while (nextFrac > -1) {
|
|
20981
|
-
// Gather first fragment, preceding \dfrac
|
|
20982
|
-
// Ex) parsedString: '2 '
|
|
20983
|
-
parsedString += tex.substring(currentIndex, nextFrac);
|
|
20984
|
-
|
|
20985
|
-
// Remove everything preceding \dfrac, which has been parsed
|
|
20986
|
-
currentIndex = nextFrac;
|
|
20987
|
-
|
|
20988
|
-
// Parse first expression and move index past it
|
|
20989
|
-
// Ex) firstParsedExpression.expression: '3'
|
|
20990
|
-
const firstParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
20991
|
-
currentIndex = firstParsedExpression.endpoint + 1;
|
|
20992
|
-
|
|
20993
|
-
// Parse second expression
|
|
20994
|
-
// Ex) secondParsedExpression.expression: '7'
|
|
20995
|
-
const secondParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
20996
|
-
currentIndex = secondParsedExpression.endpoint + 1;
|
|
20997
|
-
|
|
20998
|
-
// Add expressions to running total of parsed expressions
|
|
20999
|
-
if (parsedString.length) {
|
|
21000
|
-
parsedString += " ";
|
|
21001
|
-
}
|
|
21002
|
-
|
|
21003
|
-
// Apply a custom handler based on the parsed subexpressions
|
|
21004
|
-
parsedString += handler(firstParsedExpression.expression, secondParsedExpression.expression);
|
|
21005
|
-
|
|
21006
|
-
// Find next DFrac, relative to currentIndex
|
|
21007
|
-
nextFrac = getNextFracIndex(tex, currentIndex);
|
|
21008
|
-
}
|
|
21009
|
-
|
|
21010
|
-
// Add remaining TeX, which is \dfrac-free
|
|
21011
|
-
parsedString += tex.slice(currentIndex);
|
|
21012
|
-
return parsedString;
|
|
21013
|
-
}
|
|
21014
|
-
|
|
21015
|
-
/*
|
|
21016
|
-
* Modify a TeX expression, returning another TeX expression. The resulting
|
|
21017
|
-
* expression will have its innermost fractions stubbed out with \fracs
|
|
21018
|
-
* (as opposed to \dfracs). All other content will remain untouched.
|
|
21019
|
-
*/
|
|
21020
|
-
function modifyTex(tex) {
|
|
21021
|
-
function isNestedFraction(tex) {
|
|
21022
|
-
return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
|
|
21023
|
-
}
|
|
21024
|
-
const handler = function handler(exp1, exp2) {
|
|
21025
|
-
let prefix;
|
|
21026
|
-
if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
|
|
21027
|
-
prefix = "\\dfrac";
|
|
21028
|
-
} else {
|
|
21029
|
-
prefix = "\\frac";
|
|
21030
|
-
}
|
|
21031
|
-
return prefix + " {" + exp1 + "}{" + exp2 + "}";
|
|
21032
|
-
};
|
|
21033
|
-
return walkTex(tex, handler);
|
|
21034
|
-
}
|
|
21035
|
-
|
|
21036
|
-
/*
|
|
21037
|
-
* Parse a TeX expression into something interpretable by input-number.
|
|
21038
|
-
* The process is concerned with: (1) parsing fractions, i.e., \dfracs; and
|
|
21039
|
-
* (2) removing backslash-escaping from certain characters (right now, only
|
|
21040
|
-
* percent signs).
|
|
21041
|
-
*
|
|
21042
|
-
* The basic algorithm for handling \dfracs splits on \dfracs and then recurs
|
|
21043
|
-
* on the subsequent "expressions", i.e., the {} pairs that follow \dfrac. The
|
|
21044
|
-
* recursion is to allow for nested \dfrac elements.
|
|
21045
|
-
*
|
|
21046
|
-
* Backslash-escapes are removed with a simple search-and-replace.
|
|
21047
|
-
*/
|
|
21048
|
-
function parseTex(tex) {
|
|
21049
|
-
const handler = function handler(exp1, exp2) {
|
|
21050
|
-
return exp1 + "/" + exp2;
|
|
21051
|
-
};
|
|
21052
|
-
const texWithoutFracs = walkTex(tex, handler);
|
|
21053
|
-
return texWithoutFracs.replace("\\%", "%");
|
|
21054
|
-
}
|
|
21055
|
-
var TexWrangler = {
|
|
21056
|
-
parseTex,
|
|
21057
|
-
modifyTex
|
|
21058
|
-
};
|
|
21059
|
-
|
|
21060
|
-
const ParseTex$1 = TexWrangler.parseTex;
|
|
21061
|
-
const answerTypes = {
|
|
21062
|
-
number: {
|
|
21063
|
-
name: "Numbers",
|
|
21064
|
-
forms: "integer, decimal, proper, improper, mixed"
|
|
21065
|
-
},
|
|
21066
|
-
decimal: {
|
|
21067
|
-
name: "Decimals",
|
|
21068
|
-
forms: "decimal"
|
|
21069
|
-
},
|
|
21070
|
-
integer: {
|
|
21071
|
-
name: "Integers",
|
|
21072
|
-
forms: "integer"
|
|
21073
|
-
},
|
|
21074
|
-
rational: {
|
|
21075
|
-
name: "Fractions and mixed numbers",
|
|
21076
|
-
forms: "integer, proper, improper, mixed"
|
|
21077
|
-
},
|
|
21078
|
-
improper: {
|
|
21079
|
-
name: "Improper numbers (no mixed)",
|
|
21080
|
-
forms: "integer, proper, improper"
|
|
21081
|
-
},
|
|
21082
|
-
mixed: {
|
|
21083
|
-
name: "Mixed numbers (no improper)",
|
|
21084
|
-
forms: "integer, proper, mixed"
|
|
21085
|
-
},
|
|
21086
|
-
percent: {
|
|
21087
|
-
name: "Numbers or percents",
|
|
21088
|
-
forms: "integer, decimal, proper, improper, mixed, percent"
|
|
21089
|
-
},
|
|
21090
|
-
pi: {
|
|
21091
|
-
name: "Numbers with pi",
|
|
21092
|
-
forms: "pi"
|
|
21093
|
-
}
|
|
21094
|
-
};
|
|
21095
|
-
function scoreInputNumber(userInput, rubric, strings) {
|
|
21096
|
-
if (rubric.answerType == null) {
|
|
21097
|
-
rubric.answerType = "number";
|
|
21098
|
-
}
|
|
21099
|
-
|
|
21100
|
-
// note(matthewc): this will get immediately parsed again by
|
|
21101
|
-
// `KhanAnswerTypes.number.convertToPredicate`, but a string is
|
|
21102
|
-
// expected here
|
|
21103
|
-
const stringValue = `${rubric.value}`;
|
|
21104
|
-
const val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
|
|
21105
|
-
simplify: rubric.simplify,
|
|
21106
|
-
inexact: rubric.inexact || undefined,
|
|
21107
|
-
maxError: rubric.maxError,
|
|
21108
|
-
forms: answerTypes[rubric.answerType].forms
|
|
21109
|
-
});
|
|
21110
|
-
|
|
21111
|
-
// We may have received TeX; try to parse it before grading.
|
|
21112
|
-
// If `currentValue` is not TeX, this should be a no-op.
|
|
21113
|
-
const currentValue = ParseTex$1(userInput.currentValue);
|
|
21114
|
-
const result = val(currentValue);
|
|
21115
|
-
if (result.empty) {
|
|
21116
|
-
return {
|
|
21117
|
-
type: "invalid",
|
|
21118
|
-
message: result.message
|
|
21119
|
-
};
|
|
21120
|
-
}
|
|
21121
|
-
return {
|
|
21122
|
-
type: "points",
|
|
21123
|
-
earned: result.correct ? 1 : 0,
|
|
21124
|
-
total: 1,
|
|
21125
|
-
message: result.message
|
|
21126
|
-
};
|
|
21127
|
-
}
|
|
21128
|
-
|
|
21129
20459
|
const formExamples$1 = {
|
|
21130
20460
|
integer: function (options, strings) {
|
|
21131
20461
|
return strings.integerExample;
|
|
@@ -21228,7 +20558,7 @@ class InputNumber extends React.Component {
|
|
|
21228
20558
|
strings
|
|
21229
20559
|
} = this.context;
|
|
21230
20560
|
const type = this.props.answerType;
|
|
21231
|
-
const forms =
|
|
20561
|
+
const forms = inputNumberAnswerTypes[type].forms.split(/\s*,\s*/);
|
|
21232
20562
|
const examples = _.map(forms, form => formExamples$1[form](this.props, strings));
|
|
21233
20563
|
return [strings.yourAnswer].concat(examples);
|
|
21234
20564
|
}
|
|
@@ -21358,140 +20688,6 @@ const getPromptJSON$r = (renderProps, userInput) => {
|
|
|
21358
20688
|
};
|
|
21359
20689
|
};
|
|
21360
20690
|
|
|
21361
|
-
const ParseTex = TexWrangler.parseTex;
|
|
21362
|
-
const answerFormButtons = [{
|
|
21363
|
-
title: "Integers",
|
|
21364
|
-
value: "integer",
|
|
21365
|
-
content: "6"
|
|
21366
|
-
}, {
|
|
21367
|
-
title: "Decimals",
|
|
21368
|
-
value: "decimal",
|
|
21369
|
-
content: "0.75"
|
|
21370
|
-
}, {
|
|
21371
|
-
title: "Proper fractions",
|
|
21372
|
-
value: "proper",
|
|
21373
|
-
content: "\u2157"
|
|
21374
|
-
}, {
|
|
21375
|
-
title: "Improper fractions",
|
|
21376
|
-
value: "improper",
|
|
21377
|
-
content: "\u2077\u2044\u2084"
|
|
21378
|
-
}, {
|
|
21379
|
-
title: "Mixed numbers",
|
|
21380
|
-
value: "mixed",
|
|
21381
|
-
content: "1\u00BE"
|
|
21382
|
-
}, {
|
|
21383
|
-
title: "Numbers with \u03C0",
|
|
21384
|
-
value: "pi",
|
|
21385
|
-
content: "\u03C0"
|
|
21386
|
-
}];
|
|
21387
|
-
|
|
21388
|
-
// This function checks if the user inputted a percent value, parsing
|
|
21389
|
-
// it as a number (and maybe scaling) so that it can be graded.
|
|
21390
|
-
// NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
|
|
21391
|
-
// can accept several input forms with or without "%", the decision
|
|
21392
|
-
// to parse based on the presence of "%" in the input, is so that we
|
|
21393
|
-
// don't accidently scale the user typed value before grading, CP-930.
|
|
21394
|
-
function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
21395
|
-
// If the input value is not a string ending with "%", then there's
|
|
21396
|
-
// nothing more to do. The value will be graded as inputted by user.
|
|
21397
|
-
if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
|
|
21398
|
-
return inputValue;
|
|
21399
|
-
}
|
|
21400
|
-
const value = parseFloat(inputValue.slice(0, -1));
|
|
21401
|
-
// If the input value stripped of the "%" cannot be parsed as a
|
|
21402
|
-
// number (the slice is not really necessary for parseFloat to work
|
|
21403
|
-
// if the string starts with a number) then return the original
|
|
21404
|
-
// input for grading.
|
|
21405
|
-
if (isNaN(value)) {
|
|
21406
|
-
return inputValue;
|
|
21407
|
-
}
|
|
21408
|
-
|
|
21409
|
-
// Next, if all correct answers are in the range of |0,1| then we
|
|
21410
|
-
// scale the user typed value. We assume this is the correct thing
|
|
21411
|
-
// to do since the input value ends with "%".
|
|
21412
|
-
if (normalizedAnswerExpected) {
|
|
21413
|
-
return value / 100;
|
|
21414
|
-
}
|
|
21415
|
-
|
|
21416
|
-
// Otherwise, we return input value (number) stripped of the "%".
|
|
21417
|
-
return value;
|
|
21418
|
-
}
|
|
21419
|
-
function scoreNumericInput(userInput, rubric, strings) {
|
|
21420
|
-
var _matchedAnswer$messag;
|
|
21421
|
-
const defaultAnswerForms = answerFormButtons.map(e => e["value"])
|
|
21422
|
-
// Don't default to validating the answer as a pi answer
|
|
21423
|
-
// if answerForm isn't set on the answer
|
|
21424
|
-
// https://khanacademy.atlassian.net/browse/LC-691
|
|
21425
|
-
.filter(e => e !== "pi");
|
|
21426
|
-
const createValidator = answer => {
|
|
21427
|
-
var _answer$answerForms;
|
|
21428
|
-
const stringAnswer = `${answer.value}`;
|
|
21429
|
-
|
|
21430
|
-
// Always validate against the provided answer forms (pi, decimal, etc.)
|
|
21431
|
-
const validatorForms = [...((_answer$answerForms = answer.answerForms) != null ? _answer$answerForms : [])];
|
|
21432
|
-
|
|
21433
|
-
// When an answer is set to strict, we validate using ONLY
|
|
21434
|
-
// the provided answerForms. If strict is false, or if there
|
|
21435
|
-
// were no provided answer forms, we will include all
|
|
21436
|
-
// of the default answer forms in our validator.
|
|
21437
|
-
if (!answer.strict || validatorForms.length === 0) {
|
|
21438
|
-
validatorForms.push(...defaultAnswerForms);
|
|
21439
|
-
}
|
|
21440
|
-
return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
21441
|
-
message: answer.message,
|
|
21442
|
-
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
21443
|
-
inexact: true,
|
|
21444
|
-
// TODO(merlob) backfill / delete
|
|
21445
|
-
maxError: answer.maxError,
|
|
21446
|
-
forms: validatorForms
|
|
21447
|
-
});
|
|
21448
|
-
};
|
|
21449
|
-
|
|
21450
|
-
// We may have received TeX; try to parse it before grading.
|
|
21451
|
-
// If `currentValue` is not TeX, this should be a no-op.
|
|
21452
|
-
const currentValue = ParseTex(userInput.currentValue);
|
|
21453
|
-
const normalizedAnswerExpected = rubric.answers.filter(answer => answer.status === "correct").every(answer => answer.value != null && Math.abs(answer.value) <= 1);
|
|
21454
|
-
|
|
21455
|
-
// The coefficient is an attribute of the widget
|
|
21456
|
-
let localValue = currentValue;
|
|
21457
|
-
if (rubric.coefficient) {
|
|
21458
|
-
if (!localValue) {
|
|
21459
|
-
localValue = 1;
|
|
21460
|
-
} else if (localValue === "-") {
|
|
21461
|
-
localValue = -1;
|
|
21462
|
-
}
|
|
21463
|
-
}
|
|
21464
|
-
const matchedAnswer = rubric.answers.map(answer => {
|
|
21465
|
-
const validateFn = createValidator(answer);
|
|
21466
|
-
const score = validateFn(maybeParsePercentInput(localValue, normalizedAnswerExpected));
|
|
21467
|
-
return _extends({}, answer, {
|
|
21468
|
-
score
|
|
21469
|
-
});
|
|
21470
|
-
}).find(answer => {
|
|
21471
|
-
// NOTE: "answer.score.correct" indicates a match via the validate function.
|
|
21472
|
-
// It does NOT indicate that the answer itself is correct.
|
|
21473
|
-
return answer.score.correct || answer.status === "correct" && answer.score.empty;
|
|
21474
|
-
});
|
|
21475
|
-
const result = (matchedAnswer == null ? void 0 : matchedAnswer.status) === "correct" ? matchedAnswer.score : {
|
|
21476
|
-
empty: (matchedAnswer == null ? void 0 : matchedAnswer.status) === "ungraded",
|
|
21477
|
-
correct: (matchedAnswer == null ? void 0 : matchedAnswer.status) === "correct",
|
|
21478
|
-
message: (_matchedAnswer$messag = matchedAnswer == null ? void 0 : matchedAnswer.message) != null ? _matchedAnswer$messag : null,
|
|
21479
|
-
guess: localValue
|
|
21480
|
-
};
|
|
21481
|
-
if (result.empty) {
|
|
21482
|
-
return {
|
|
21483
|
-
type: "invalid",
|
|
21484
|
-
message: result.message
|
|
21485
|
-
};
|
|
21486
|
-
}
|
|
21487
|
-
return {
|
|
21488
|
-
type: "points",
|
|
21489
|
-
earned: result.correct ? 1 : 0,
|
|
21490
|
-
total: 1,
|
|
21491
|
-
message: result.message
|
|
21492
|
-
};
|
|
21493
|
-
}
|
|
21494
|
-
|
|
21495
20691
|
const formExamples = {
|
|
21496
20692
|
integer: (form, strings) => strings.integerExample,
|
|
21497
20693
|
proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
|
|
@@ -25479,72 +24675,6 @@ const styles$o = StyleSheet.create({
|
|
|
25479
24675
|
}
|
|
25480
24676
|
});
|
|
25481
24677
|
|
|
25482
|
-
/**
|
|
25483
|
-
* Checks if the user has selected at least one option. Additional validation
|
|
25484
|
-
* is done in scoreRadio to check if the number of selected options is correct
|
|
25485
|
-
* and if the user has selected both a correct option and the "none of the above"
|
|
25486
|
-
* option.
|
|
25487
|
-
* @param userInput
|
|
25488
|
-
* @see `scoreRadio` for the additional validation logic and the scoring logic.
|
|
25489
|
-
*/
|
|
25490
|
-
function validateRadio(userInput) {
|
|
25491
|
-
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
25492
|
-
return sum + (selected ? 1 : 0);
|
|
25493
|
-
}, 0);
|
|
25494
|
-
if (numSelected === 0) {
|
|
25495
|
-
return {
|
|
25496
|
-
type: "invalid",
|
|
25497
|
-
message: null
|
|
25498
|
-
};
|
|
25499
|
-
}
|
|
25500
|
-
return null;
|
|
25501
|
-
}
|
|
25502
|
-
|
|
25503
|
-
function scoreRadio(userInput, rubric, strings) {
|
|
25504
|
-
const validationError = validateRadio(userInput);
|
|
25505
|
-
if (validationError) {
|
|
25506
|
-
return validationError;
|
|
25507
|
-
}
|
|
25508
|
-
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
25509
|
-
return sum + (selected ? 1 : 0);
|
|
25510
|
-
}, 0);
|
|
25511
|
-
const numCorrect = rubric.choices.reduce((sum, currentChoice) => {
|
|
25512
|
-
return currentChoice.correct ? sum + 1 : sum;
|
|
25513
|
-
}, 0);
|
|
25514
|
-
if (numCorrect > 1 && numSelected !== numCorrect) {
|
|
25515
|
-
return {
|
|
25516
|
-
type: "invalid",
|
|
25517
|
-
message: strings.chooseCorrectNum
|
|
25518
|
-
};
|
|
25519
|
-
// If NOTA and some other answer are checked, ...
|
|
25520
|
-
}
|
|
25521
|
-
|
|
25522
|
-
const noneOfTheAboveSelected = rubric.choices.some((choice, index) => choice.isNoneOfTheAbove && userInput.choicesSelected[index]);
|
|
25523
|
-
if (noneOfTheAboveSelected && numSelected > 1) {
|
|
25524
|
-
return {
|
|
25525
|
-
type: "invalid",
|
|
25526
|
-
message: strings.notNoneOfTheAbove
|
|
25527
|
-
};
|
|
25528
|
-
}
|
|
25529
|
-
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
25530
|
-
let isCorrect;
|
|
25531
|
-
if (rubric.choices[i].isNoneOfTheAbove) {
|
|
25532
|
-
isCorrect = rubric.choices.every((choice, j) => {
|
|
25533
|
-
return i === j || !choice.correct;
|
|
25534
|
-
});
|
|
25535
|
-
} else {
|
|
25536
|
-
isCorrect = !!rubric.choices[i].correct;
|
|
25537
|
-
}
|
|
25538
|
-
return isCorrect === selected;
|
|
25539
|
-
});
|
|
25540
|
-
return {
|
|
25541
|
-
type: "points",
|
|
25542
|
-
earned: correct ? 1 : 0,
|
|
25543
|
-
total: 1,
|
|
25544
|
-
message: null
|
|
25545
|
-
};
|
|
25546
|
-
}
|
|
25547
|
-
|
|
25548
24678
|
// RenderProps is the return type for radio.jsx#transform
|
|
25549
24679
|
|
|
25550
24680
|
class Radio$1 extends React.Component {
|
|
@@ -25650,7 +24780,7 @@ class Radio$1 extends React.Component {
|
|
|
25650
24780
|
choiceStates
|
|
25651
24781
|
} = this.props;
|
|
25652
24782
|
if (choiceStates) {
|
|
25653
|
-
const score = scoreRadio(this.getUserInput(), rubric
|
|
24783
|
+
const score = scoreRadio(this.getUserInput(), rubric);
|
|
25654
24784
|
const widgetCorrect = score.type === "points" && score.total === score.earned;
|
|
25655
24785
|
const newStates = choiceStates.map(state => _extends({}, state, {
|
|
25656
24786
|
highlighted: state.selected,
|
|
@@ -26037,44 +25167,6 @@ function getCategorizerPublicWidgetOptions(options) {
|
|
|
26037
25167
|
};
|
|
26038
25168
|
}
|
|
26039
25169
|
|
|
26040
|
-
/**
|
|
26041
|
-
* Checks userInput from the categorizer widget to see if the user has selected
|
|
26042
|
-
* a category for each item.
|
|
26043
|
-
* @param userInput - The user's input corresponding to an array of indices that
|
|
26044
|
-
* represent the selected category for each row/item.
|
|
26045
|
-
* @param validationData - An array of strings corresponding to each row/item
|
|
26046
|
-
* @param strings - Used to provide a validation message
|
|
26047
|
-
*/
|
|
26048
|
-
function validateCategorizer(userInput, validationData, strings) {
|
|
26049
|
-
const incomplete = validationData.items.some((_, i) => userInput.values[i] == null);
|
|
26050
|
-
if (incomplete) {
|
|
26051
|
-
return {
|
|
26052
|
-
type: "invalid",
|
|
26053
|
-
message: strings.invalidSelection
|
|
26054
|
-
};
|
|
26055
|
-
}
|
|
26056
|
-
return null;
|
|
26057
|
-
}
|
|
26058
|
-
|
|
26059
|
-
function scoreCategorizer(userInput, scoringData, strings) {
|
|
26060
|
-
const validationError = validateCategorizer(userInput, scoringData, strings);
|
|
26061
|
-
if (validationError) {
|
|
26062
|
-
return validationError;
|
|
26063
|
-
}
|
|
26064
|
-
let allCorrect = true;
|
|
26065
|
-
scoringData.values.forEach((value, i) => {
|
|
26066
|
-
if (userInput.values[i] !== value) {
|
|
26067
|
-
allCorrect = false;
|
|
26068
|
-
}
|
|
26069
|
-
});
|
|
26070
|
-
return {
|
|
26071
|
-
type: "points",
|
|
26072
|
-
earned: allCorrect ? 1 : 0,
|
|
26073
|
-
total: 1,
|
|
26074
|
-
message: null
|
|
26075
|
-
};
|
|
26076
|
-
}
|
|
26077
|
-
|
|
26078
25170
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
26079
25171
|
class Categorizer extends React.Component {
|
|
26080
25172
|
constructor(..._args) {
|
|
@@ -26316,31 +25408,6 @@ const getPromptJSON$m = () => {
|
|
|
26316
25408
|
return getUnsupportedPromptJSON("cs-program");
|
|
26317
25409
|
};
|
|
26318
25410
|
|
|
26319
|
-
function scoreCSProgram(state) {
|
|
26320
|
-
// The iframe can tell us whether it's correct or incorrect,
|
|
26321
|
-
// and pass an optional message
|
|
26322
|
-
if (state.status === "correct") {
|
|
26323
|
-
return {
|
|
26324
|
-
type: "points",
|
|
26325
|
-
earned: 1,
|
|
26326
|
-
total: 1,
|
|
26327
|
-
message: state.message || null
|
|
26328
|
-
};
|
|
26329
|
-
}
|
|
26330
|
-
if (state.status === "incorrect") {
|
|
26331
|
-
return {
|
|
26332
|
-
type: "points",
|
|
26333
|
-
earned: 0,
|
|
26334
|
-
total: 1,
|
|
26335
|
-
message: state.message || null
|
|
26336
|
-
};
|
|
26337
|
-
}
|
|
26338
|
-
return {
|
|
26339
|
-
type: "invalid",
|
|
26340
|
-
message: "Keep going, you're not there yet!"
|
|
26341
|
-
};
|
|
26342
|
-
}
|
|
26343
|
-
|
|
26344
25411
|
/**
|
|
26345
25412
|
* This widget is for embedding Khan Academy CS programs.
|
|
26346
25413
|
*/
|
|
@@ -26613,34 +25680,6 @@ const getPromptJSON$k = (renderProps, userInput) => {
|
|
|
26613
25680
|
};
|
|
26614
25681
|
};
|
|
26615
25682
|
|
|
26616
|
-
/**
|
|
26617
|
-
* Checks if the user has selected an item from the dropdown before scoring.
|
|
26618
|
-
* This is shown with a userInput value / index other than 0.
|
|
26619
|
-
*/
|
|
26620
|
-
function validateDropdown(userInput) {
|
|
26621
|
-
if (userInput.value === 0) {
|
|
26622
|
-
return {
|
|
26623
|
-
type: "invalid",
|
|
26624
|
-
message: null
|
|
26625
|
-
};
|
|
26626
|
-
}
|
|
26627
|
-
return null;
|
|
26628
|
-
}
|
|
26629
|
-
|
|
26630
|
-
function scoreDropdown(userInput, rubric) {
|
|
26631
|
-
const validationError = validateDropdown(userInput);
|
|
26632
|
-
if (validationError) {
|
|
26633
|
-
return validationError;
|
|
26634
|
-
}
|
|
26635
|
-
const correct = rubric.choices[userInput.value - 1].correct;
|
|
26636
|
-
return {
|
|
26637
|
-
type: "points",
|
|
26638
|
-
earned: correct ? 1 : 0,
|
|
26639
|
-
total: 1,
|
|
26640
|
-
message: null
|
|
26641
|
-
};
|
|
26642
|
-
}
|
|
26643
|
-
|
|
26644
25683
|
class Dropdown extends React.Component {
|
|
26645
25684
|
constructor(...args) {
|
|
26646
25685
|
super(...args);
|
|
@@ -27703,408 +26742,21 @@ const getPromptJSON$g = (renderProps, userInput) => {
|
|
|
27703
26742
|
};
|
|
27704
26743
|
};
|
|
27705
26744
|
|
|
27706
|
-
|
|
27707
|
-
|
|
26745
|
+
const movableTypeToComponent = {
|
|
26746
|
+
// @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
|
|
26747
|
+
PLOT: Graphie.Plot,
|
|
26748
|
+
// @ts-expect-error - TS2339 - Property 'Parabola' does not exist on type 'typeof Graphie'.
|
|
26749
|
+
PARABOLA: Graphie.Parabola,
|
|
26750
|
+
// @ts-expect-error - TS2339 - Property 'Sinusoid' does not exist on type 'typeof Graphie'.
|
|
26751
|
+
SINUSOID: Graphie.Sinusoid
|
|
26752
|
+
};
|
|
27708
26753
|
const DEFAULT_BACKGROUND_IMAGE = {
|
|
27709
26754
|
url: null
|
|
27710
26755
|
};
|
|
27711
|
-
|
|
27712
|
-
// TODO(charlie): These really need to go into a utility file as they're being
|
|
27713
|
-
// used by both interactive-graph and now grapher.
|
|
27714
|
-
function canonicalSineCoefficients(coeffs) {
|
|
27715
|
-
// For a curve of the form f(x) = a * Sin(b * x - c) + d,
|
|
27716
|
-
// this function ensures that a, b > 0, and c is its
|
|
27717
|
-
// smallest possible positive value.
|
|
27718
|
-
let amplitude = coeffs[0];
|
|
27719
|
-
let angularFrequency = coeffs[1];
|
|
27720
|
-
let phase = coeffs[2];
|
|
27721
|
-
const verticalOffset = coeffs[3];
|
|
27722
|
-
|
|
27723
|
-
// Guarantee a > 0
|
|
27724
|
-
if (amplitude < 0) {
|
|
27725
|
-
amplitude *= -1;
|
|
27726
|
-
angularFrequency *= -1;
|
|
27727
|
-
phase *= -1;
|
|
27728
|
-
}
|
|
27729
|
-
const period = 2 * Math.PI;
|
|
27730
|
-
// Guarantee b > 0
|
|
27731
|
-
if (angularFrequency < 0) {
|
|
27732
|
-
angularFrequency *= -1;
|
|
27733
|
-
phase *= -1;
|
|
27734
|
-
phase += period / 2;
|
|
27735
|
-
}
|
|
27736
|
-
|
|
27737
|
-
// Guarantee c is smallest possible positive value
|
|
27738
|
-
while (phase > 0) {
|
|
27739
|
-
phase -= period;
|
|
27740
|
-
}
|
|
27741
|
-
while (phase < 0) {
|
|
27742
|
-
phase += period;
|
|
27743
|
-
}
|
|
27744
|
-
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
27745
|
-
}
|
|
27746
|
-
function canonicalTangentCoefficients(coeffs) {
|
|
27747
|
-
// For a curve of the form f(x) = a * Tan(b * x - c) + d,
|
|
27748
|
-
// this function ensures that a, b > 0, and c is its
|
|
27749
|
-
// smallest possible positive value.
|
|
27750
|
-
let amplitude = coeffs[0];
|
|
27751
|
-
let angularFrequency = coeffs[1];
|
|
27752
|
-
let phase = coeffs[2];
|
|
27753
|
-
const verticalOffset = coeffs[3];
|
|
27754
|
-
|
|
27755
|
-
// Guarantee a > 0
|
|
27756
|
-
if (amplitude < 0) {
|
|
27757
|
-
amplitude *= -1;
|
|
27758
|
-
angularFrequency *= -1;
|
|
27759
|
-
phase *= -1;
|
|
27760
|
-
}
|
|
27761
|
-
const period = Math.PI;
|
|
27762
|
-
// Guarantee b > 0
|
|
27763
|
-
if (angularFrequency < 0) {
|
|
27764
|
-
angularFrequency *= -1;
|
|
27765
|
-
phase *= -1;
|
|
27766
|
-
phase += period / 2;
|
|
27767
|
-
}
|
|
27768
|
-
|
|
27769
|
-
// Guarantee c is smallest possible positive value
|
|
27770
|
-
while (phase > 0) {
|
|
27771
|
-
phase -= period;
|
|
27772
|
-
}
|
|
27773
|
-
while (phase < 0) {
|
|
27774
|
-
phase += period;
|
|
27775
|
-
}
|
|
27776
|
-
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
27777
|
-
}
|
|
27778
|
-
const PlotDefaults = {
|
|
27779
|
-
areEqual: function (coeffs1, coeffs2) {
|
|
27780
|
-
return Util.deepEq(coeffs1, coeffs2);
|
|
27781
|
-
},
|
|
27782
|
-
Movable: Plot$1,
|
|
27783
|
-
getPropsForCoeffs: function (coeffs) {
|
|
27784
|
-
return {
|
|
27785
|
-
// @ts-expect-error - TS2339 - Property 'getFunctionForCoeffs' does not exist on type '{ readonly areEqual: (coeffs1: any, coeffs2: any) => boolean; readonly Movable: any; readonly getPropsForCoeffs: (coeffs: any) => any; }'.
|
|
27786
|
-
fn: _.partial(this.getFunctionForCoeffs, coeffs)
|
|
27787
|
-
};
|
|
27788
|
-
}
|
|
27789
|
-
};
|
|
27790
|
-
const Linear = _.extend({}, PlotDefaults, {
|
|
27791
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/67aaf581e6d9ef9038c10558a1f70ac21c11c9f8.png",
|
|
27792
|
-
defaultCoords: [[0.25, 0.75], [0.75, 0.75]],
|
|
27793
|
-
getCoefficients: function (coords) {
|
|
27794
|
-
const p1 = coords[0];
|
|
27795
|
-
const p2 = coords[1];
|
|
27796
|
-
const denom = p2[0] - p1[0];
|
|
27797
|
-
const num = p2[1] - p1[1];
|
|
27798
|
-
if (denom === 0) {
|
|
27799
|
-
return;
|
|
27800
|
-
}
|
|
27801
|
-
const m = num / denom;
|
|
27802
|
-
const b = p2[1] - m * p2[0];
|
|
27803
|
-
return [m, b];
|
|
27804
|
-
},
|
|
27805
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
27806
|
-
const m = coeffs[0];
|
|
27807
|
-
const b = coeffs[1];
|
|
27808
|
-
return m * x + b;
|
|
27809
|
-
},
|
|
27810
|
-
getEquationString: function (coords) {
|
|
27811
|
-
const coeffs = this.getCoefficients(coords);
|
|
27812
|
-
const m = coeffs[0];
|
|
27813
|
-
const b = coeffs[1];
|
|
27814
|
-
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
27815
|
-
}
|
|
27816
|
-
});
|
|
27817
|
-
const Quadratic = _.extend({}, PlotDefaults, {
|
|
27818
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/e23d36e6fc29ee37174e92c9daba2a66677128ab.png",
|
|
27819
|
-
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
27820
|
-
// @ts-expect-error - TS2339 - Property 'Parabola' does not exist on type 'typeof Graphie'.
|
|
27821
|
-
Movable: Graphie.Parabola,
|
|
27822
|
-
getCoefficients: function (coords) {
|
|
27823
|
-
const p1 = coords[0];
|
|
27824
|
-
const p2 = coords[1];
|
|
27825
|
-
|
|
27826
|
-
// Parabola with vertex (h, k) has form: y = a * (h - k)^2 + k
|
|
27827
|
-
const h = p1[0];
|
|
27828
|
-
const k = p1[1];
|
|
27829
|
-
|
|
27830
|
-
// Use these to calculate familiar a, b, c
|
|
27831
|
-
const a = (p2[1] - k) / ((p2[0] - h) * (p2[0] - h));
|
|
27832
|
-
const b = -2 * h * a;
|
|
27833
|
-
const c = a * h * h + k;
|
|
27834
|
-
return [a, b, c];
|
|
27835
|
-
},
|
|
27836
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
27837
|
-
const a = coeffs[0];
|
|
27838
|
-
const b = coeffs[1];
|
|
27839
|
-
const c = coeffs[2];
|
|
27840
|
-
return (a * x + b) * x + c;
|
|
27841
|
-
},
|
|
27842
|
-
getPropsForCoeffs: function (coeffs) {
|
|
27843
|
-
return {
|
|
27844
|
-
a: coeffs[0],
|
|
27845
|
-
b: coeffs[1],
|
|
27846
|
-
c: coeffs[2]
|
|
27847
|
-
};
|
|
27848
|
-
},
|
|
27849
|
-
getEquationString: function (coords) {
|
|
27850
|
-
const coeffs = this.getCoefficients(coords);
|
|
27851
|
-
const a = coeffs[0];
|
|
27852
|
-
const b = coeffs[1];
|
|
27853
|
-
const c = coeffs[2];
|
|
27854
|
-
return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
|
|
27855
|
-
}
|
|
27856
|
-
});
|
|
27857
|
-
const Sinusoid = _.extend({}, PlotDefaults, {
|
|
27858
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/3d68e7718498475f53b206c2ab285626baf8857e.png",
|
|
27859
|
-
defaultCoords: [[0.5, 0.5], [0.6, 0.6]],
|
|
27860
|
-
// @ts-expect-error - TS2339 - Property 'Sinusoid' does not exist on type 'typeof Graphie'.
|
|
27861
|
-
Movable: Graphie.Sinusoid,
|
|
27862
|
-
getCoefficients: function (coords) {
|
|
27863
|
-
const p1 = coords[0];
|
|
27864
|
-
const p2 = coords[1];
|
|
27865
|
-
const a = p2[1] - p1[1];
|
|
27866
|
-
const b = Math.PI / (2 * (p2[0] - p1[0]));
|
|
27867
|
-
const c = p1[0] * b;
|
|
27868
|
-
const d = p1[1];
|
|
27869
|
-
return [a, b, c, d];
|
|
27870
|
-
},
|
|
27871
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
27872
|
-
const a = coeffs[0];
|
|
27873
|
-
const b = coeffs[1];
|
|
27874
|
-
const c = coeffs[2];
|
|
27875
|
-
const d = coeffs[3];
|
|
27876
|
-
return a * Math.sin(b * x - c) + d;
|
|
27877
|
-
},
|
|
27878
|
-
getPropsForCoeffs: function (coeffs) {
|
|
27879
|
-
return {
|
|
27880
|
-
a: coeffs[0],
|
|
27881
|
-
b: coeffs[1],
|
|
27882
|
-
c: coeffs[2],
|
|
27883
|
-
d: coeffs[3]
|
|
27884
|
-
};
|
|
27885
|
-
},
|
|
27886
|
-
getEquationString: function (coords) {
|
|
27887
|
-
const coeffs = this.getCoefficients(coords);
|
|
27888
|
-
const a = coeffs[0];
|
|
27889
|
-
const b = coeffs[1];
|
|
27890
|
-
const c = coeffs[2];
|
|
27891
|
-
const d = coeffs[3];
|
|
27892
|
-
return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
|
|
27893
|
-
},
|
|
27894
|
-
areEqual: function (coeffs1, coeffs2) {
|
|
27895
|
-
return Util.deepEq(canonicalSineCoefficients(coeffs1), canonicalSineCoefficients(coeffs2));
|
|
27896
|
-
}
|
|
27897
|
-
});
|
|
27898
|
-
const Tangent = _.extend({}, PlotDefaults, {
|
|
27899
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/7db80d23c35214f98659fe1cf0765811c1bbfbba.png",
|
|
27900
|
-
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
27901
|
-
getCoefficients: function (coords) {
|
|
27902
|
-
const p1 = coords[0];
|
|
27903
|
-
const p2 = coords[1];
|
|
27904
|
-
const a = p2[1] - p1[1];
|
|
27905
|
-
const b = Math.PI / (4 * (p2[0] - p1[0]));
|
|
27906
|
-
const c = p1[0] * b;
|
|
27907
|
-
const d = p1[1];
|
|
27908
|
-
return [a, b, c, d];
|
|
27909
|
-
},
|
|
27910
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
27911
|
-
const a = coeffs[0];
|
|
27912
|
-
const b = coeffs[1];
|
|
27913
|
-
const c = coeffs[2];
|
|
27914
|
-
const d = coeffs[3];
|
|
27915
|
-
return a * Math.tan(b * x - c) + d;
|
|
27916
|
-
},
|
|
27917
|
-
getEquationString: function (coords) {
|
|
27918
|
-
const coeffs = this.getCoefficients(coords);
|
|
27919
|
-
const a = coeffs[0];
|
|
27920
|
-
const b = coeffs[1];
|
|
27921
|
-
const c = coeffs[2];
|
|
27922
|
-
const d = coeffs[3];
|
|
27923
|
-
return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
|
|
27924
|
-
},
|
|
27925
|
-
areEqual: function (coeffs1, coeffs2) {
|
|
27926
|
-
return Util.deepEq(canonicalTangentCoefficients(coeffs1), canonicalTangentCoefficients(coeffs2));
|
|
27927
|
-
}
|
|
27928
|
-
});
|
|
27929
|
-
const Exponential = _.extend({}, PlotDefaults, {
|
|
27930
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/9cbfad55525e3ce755a31a631b074670a5dad611.png",
|
|
27931
|
-
defaultCoords: [[0.5, 0.55], [0.75, 0.75]],
|
|
27932
|
-
defaultAsymptote: [[0, 0.5], [1.0, 0.5]],
|
|
27933
|
-
/**
|
|
27934
|
-
* Add extra constraints for movement of the points or asymptote (below):
|
|
27935
|
-
* newCoord: [x, y]
|
|
27936
|
-
* The end position of the point or asymptote endpoint
|
|
27937
|
-
* oldCoord: [x, y]
|
|
27938
|
-
* The old position of the point or asymptote endpoint
|
|
27939
|
-
* coords:
|
|
27940
|
-
* An array of coordinates representing the proposed end configuration
|
|
27941
|
-
* of the plot coordinates.
|
|
27942
|
-
* asymptote:
|
|
27943
|
-
* An array of coordinates representing the proposed end configuration
|
|
27944
|
-
* of the asymptote.
|
|
27945
|
-
*
|
|
27946
|
-
* Return: either a coordinate (to be used as the resulting coordinate of
|
|
27947
|
-
* the move) or a boolean, where `true` uses newCoord as the resulting
|
|
27948
|
-
* coordinate, and `false` uses oldCoord as the resulting coordinate.
|
|
27949
|
-
*/
|
|
27950
|
-
extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
27951
|
-
const y = asymptote[0][1];
|
|
27952
|
-
return _.all(coords, coord => coord[1] !== y);
|
|
27953
|
-
},
|
|
27954
|
-
extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
27955
|
-
const y = newCoord[1];
|
|
27956
|
-
const isValid = _.all(coords, coord => coord[1] > y) || _.all(coords, coord => coord[1] < y);
|
|
27957
|
-
if (isValid) {
|
|
27958
|
-
return [oldCoord[0], y];
|
|
27959
|
-
}
|
|
27960
|
-
// Snap the asymptote as close as possible, i.e., if the user moves
|
|
27961
|
-
// the mouse really quickly into an invalid region
|
|
27962
|
-
const oldY = oldCoord[1];
|
|
27963
|
-
const wasBelow = _.all(coords, coord => coord[1] > oldY);
|
|
27964
|
-
if (wasBelow) {
|
|
27965
|
-
const bottomMost = _.min(_.map(coords, coord => coord[1]));
|
|
27966
|
-
return [oldCoord[0], bottomMost - graph.snapStep[1]];
|
|
27967
|
-
}
|
|
27968
|
-
const topMost = _.max(_.map(coords, coord => coord[1]));
|
|
27969
|
-
return [oldCoord[0], topMost + graph.snapStep[1]];
|
|
27970
|
-
},
|
|
27971
|
-
allowReflectOverAsymptote: true,
|
|
27972
|
-
getCoefficients: function (coords, asymptote) {
|
|
27973
|
-
const p1 = coords[0];
|
|
27974
|
-
const p2 = coords[1];
|
|
27975
|
-
const c = asymptote[0][1];
|
|
27976
|
-
const b = Math.log((p1[1] - c) / (p2[1] - c)) / (p1[0] - p2[0]);
|
|
27977
|
-
const a = (p1[1] - c) / Math.exp(b * p1[0]);
|
|
27978
|
-
return [a, b, c];
|
|
27979
|
-
},
|
|
27980
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
27981
|
-
const a = coeffs[0];
|
|
27982
|
-
const b = coeffs[1];
|
|
27983
|
-
const c = coeffs[2];
|
|
27984
|
-
return a * Math.exp(b * x) + c;
|
|
27985
|
-
},
|
|
27986
|
-
getEquationString: function (coords, asymptote) {
|
|
27987
|
-
if (!asymptote) {
|
|
27988
|
-
return null;
|
|
27989
|
-
}
|
|
27990
|
-
const coeffs = this.getCoefficients(coords, asymptote);
|
|
27991
|
-
const a = coeffs[0];
|
|
27992
|
-
const b = coeffs[1];
|
|
27993
|
-
const c = coeffs[2];
|
|
27994
|
-
return "y = " + a.toFixed(3) + "e^(" + b.toFixed(3) + "x) + " + c.toFixed(3);
|
|
27995
|
-
}
|
|
27996
|
-
});
|
|
27997
|
-
const Logarithm = _.extend({}, PlotDefaults, {
|
|
27998
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/f6491e99d34af34d924bfe0231728ad912068dc3.png",
|
|
27999
|
-
defaultCoords: [[0.55, 0.5], [0.75, 0.75]],
|
|
28000
|
-
defaultAsymptote: [[0.5, 0], [0.5, 1.0]],
|
|
28001
|
-
extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
28002
|
-
const x = asymptote[0][0];
|
|
28003
|
-
return _.all(coords, coord => coord[0] !== x) && coords[0][1] !== coords[1][1];
|
|
28004
|
-
},
|
|
28005
|
-
extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
28006
|
-
const x = newCoord[0];
|
|
28007
|
-
const isValid = _.all(coords, coord => coord[0] > x) || _.all(coords, coord => coord[0] < x);
|
|
28008
|
-
if (isValid) {
|
|
28009
|
-
return [x, oldCoord[1]];
|
|
28010
|
-
}
|
|
28011
|
-
// Snap the asymptote as close as possible, i.e., if the user moves
|
|
28012
|
-
// the mouse really quickly into an invalid region
|
|
28013
|
-
const oldX = oldCoord[0];
|
|
28014
|
-
const wasLeft = _.all(coords, coord => coord[0] > oldX);
|
|
28015
|
-
if (wasLeft) {
|
|
28016
|
-
const leftMost = _.min(_.map(coords, coord => coord[0]));
|
|
28017
|
-
return [leftMost - graph.snapStep[0], oldCoord[1]];
|
|
28018
|
-
}
|
|
28019
|
-
const rightMost = _.max(_.map(coords, coord => coord[0]));
|
|
28020
|
-
return [rightMost + graph.snapStep[0], oldCoord[1]];
|
|
28021
|
-
},
|
|
28022
|
-
allowReflectOverAsymptote: true,
|
|
28023
|
-
getCoefficients: function (coords, asymptote) {
|
|
28024
|
-
// It's easiest to calculate the logarithm's coefficients by thinking
|
|
28025
|
-
// about it as the inverse of the exponential, so we flip x and y and
|
|
28026
|
-
// perform some algebra on the coefficients. This also unifies the
|
|
28027
|
-
// logic between the two 'models'.
|
|
28028
|
-
const flip = coord => [coord[1], coord[0]];
|
|
28029
|
-
const inverseCoeffs = Exponential.getCoefficients(_.map(coords, flip), _.map(asymptote, flip));
|
|
28030
|
-
if (inverseCoeffs) {
|
|
28031
|
-
const c = -inverseCoeffs[2] / inverseCoeffs[0];
|
|
28032
|
-
const b = 1 / inverseCoeffs[0];
|
|
28033
|
-
const a = 1 / inverseCoeffs[1];
|
|
28034
|
-
return [a, b, c];
|
|
28035
|
-
}
|
|
28036
|
-
},
|
|
28037
|
-
getFunctionForCoeffs: function (coeffs, x, asymptote) {
|
|
28038
|
-
const a = coeffs[0];
|
|
28039
|
-
const b = coeffs[1];
|
|
28040
|
-
const c = coeffs[2];
|
|
28041
|
-
return a * Math.log(b * x + c);
|
|
28042
|
-
},
|
|
28043
|
-
getEquationString: function (coords, asymptote) {
|
|
28044
|
-
if (!asymptote) {
|
|
28045
|
-
return null;
|
|
28046
|
-
}
|
|
28047
|
-
const coeffs = this.getCoefficients(coords, asymptote);
|
|
28048
|
-
const a = coeffs[0];
|
|
28049
|
-
const b = coeffs[1];
|
|
28050
|
-
const c = coeffs[2];
|
|
28051
|
-
return "y = ln(" + a.toFixed(3) + "x + " + b.toFixed(3) + ") + " + c.toFixed(3);
|
|
28052
|
-
}
|
|
28053
|
-
});
|
|
28054
|
-
const AbsoluteValue = _.extend({}, PlotDefaults, {
|
|
28055
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/8256a630175a0cb1d11de223d6de0266daf98721.png",
|
|
28056
|
-
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
28057
|
-
getCoefficients: function (coords) {
|
|
28058
|
-
const p1 = coords[0];
|
|
28059
|
-
const p2 = coords[1];
|
|
28060
|
-
const denom = p2[0] - p1[0];
|
|
28061
|
-
const num = p2[1] - p1[1];
|
|
28062
|
-
if (denom === 0) {
|
|
28063
|
-
return;
|
|
28064
|
-
}
|
|
28065
|
-
let m = Math.abs(num / denom);
|
|
28066
|
-
if (p2[1] < p1[1]) {
|
|
28067
|
-
m *= -1;
|
|
28068
|
-
}
|
|
28069
|
-
const horizontalOffset = p1[0];
|
|
28070
|
-
const verticalOffset = p1[1];
|
|
28071
|
-
return [m, horizontalOffset, verticalOffset];
|
|
28072
|
-
},
|
|
28073
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
28074
|
-
const m = coeffs[0];
|
|
28075
|
-
const horizontalOffset = coeffs[1];
|
|
28076
|
-
const verticalOffset = coeffs[2];
|
|
28077
|
-
return m * Math.abs(x - horizontalOffset) + verticalOffset;
|
|
28078
|
-
},
|
|
28079
|
-
getEquationString: function (coords) {
|
|
28080
|
-
const coeffs = this.getCoefficients(coords);
|
|
28081
|
-
const m = coeffs[0];
|
|
28082
|
-
const horizontalOffset = coeffs[1];
|
|
28083
|
-
const verticalOffset = coeffs[2];
|
|
28084
|
-
return "y = " + m.toFixed(3) + "| x - " + horizontalOffset.toFixed(3) + "| + " + verticalOffset.toFixed(3);
|
|
28085
|
-
}
|
|
28086
|
-
});
|
|
28087
|
-
|
|
28088
|
-
/* Utility functions for dealing with graphing interfaces. */
|
|
28089
|
-
const functionTypeMapping = {
|
|
28090
|
-
linear: Linear,
|
|
28091
|
-
quadratic: Quadratic,
|
|
28092
|
-
sinusoid: Sinusoid,
|
|
28093
|
-
tangent: Tangent,
|
|
28094
|
-
exponential: Exponential,
|
|
28095
|
-
logarithm: Logarithm,
|
|
28096
|
-
absolute_value: AbsoluteValue
|
|
28097
|
-
};
|
|
28098
|
-
const allTypes = _.keys(functionTypeMapping);
|
|
28099
|
-
function functionForType(type) {
|
|
28100
|
-
// @ts-expect-error: TypeScript doesn't know how to use deal with generics
|
|
28101
|
-
// and conditional types in this way.
|
|
28102
|
-
return functionTypeMapping[type];
|
|
28103
|
-
}
|
|
28104
26756
|
const getEquationString = props => {
|
|
28105
26757
|
const plot = props.plot;
|
|
28106
26758
|
if (plot.type && plot.coords) {
|
|
28107
|
-
const handler = functionForType(plot.type);
|
|
26759
|
+
const handler = GrapherUtil$1.functionForType(plot.type);
|
|
28108
26760
|
const result = handler.getEquationString(plot.coords, plot.asymptote);
|
|
28109
26761
|
return result || "";
|
|
28110
26762
|
}
|
|
@@ -28162,7 +26814,7 @@ const defaultPlotProps = (type, graph) => {
|
|
|
28162
26814
|
// extra information. It would be valid to always submit the default
|
|
28163
26815
|
// widget before even reading the question; you can't lose, but you
|
|
28164
26816
|
// might get a free win.
|
|
28165
|
-
const model = functionForType(type);
|
|
26817
|
+
const model = GrapherUtil$1.functionForType(type);
|
|
28166
26818
|
const defaultAsymptote = "defaultAsymptote" in model ? model.defaultAsymptote : null;
|
|
28167
26819
|
const gridStep = [1, 1];
|
|
28168
26820
|
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type '[number, number]'.
|
|
@@ -28208,70 +26860,12 @@ const typeToButton = type => {
|
|
|
28208
26860
|
value: type,
|
|
28209
26861
|
title: capitalized,
|
|
28210
26862
|
content: /*#__PURE__*/React.createElement("img", {
|
|
28211
|
-
src: staticUrl(functionForType(type).url),
|
|
26863
|
+
src: staticUrl(GrapherUtil$1.functionForType(type).url),
|
|
28212
26864
|
alt: capitalized
|
|
28213
26865
|
})
|
|
28214
26866
|
};
|
|
28215
26867
|
};
|
|
28216
26868
|
|
|
28217
|
-
function getCoefficientsByType(data) {
|
|
28218
|
-
if (data.coords == null) {
|
|
28219
|
-
return undefined;
|
|
28220
|
-
}
|
|
28221
|
-
if (data.type === "exponential" || data.type === "logarithm") {
|
|
28222
|
-
const grader = functionForType(data.type);
|
|
28223
|
-
return grader.getCoefficients(data.coords, data.asymptote);
|
|
28224
|
-
} else if (data.type === "linear" || data.type === "quadratic" || data.type === "absolute_value" || data.type === "sinusoid" || data.type === "tangent") {
|
|
28225
|
-
const grader = functionForType(data.type);
|
|
28226
|
-
return grader.getCoefficients(data.coords);
|
|
28227
|
-
} else {
|
|
28228
|
-
throw new PerseusError("Invalid grapher type", Errors.InvalidInput);
|
|
28229
|
-
}
|
|
28230
|
-
}
|
|
28231
|
-
function scoreGrapher(userInput, rubric) {
|
|
28232
|
-
if (userInput.type !== rubric.correct.type) {
|
|
28233
|
-
return {
|
|
28234
|
-
type: "points",
|
|
28235
|
-
earned: 0,
|
|
28236
|
-
total: 1,
|
|
28237
|
-
message: null
|
|
28238
|
-
};
|
|
28239
|
-
}
|
|
28240
|
-
|
|
28241
|
-
// We haven't moved the coords
|
|
28242
|
-
if (userInput.coords == null) {
|
|
28243
|
-
return {
|
|
28244
|
-
type: "invalid",
|
|
28245
|
-
message: null
|
|
28246
|
-
};
|
|
28247
|
-
}
|
|
28248
|
-
|
|
28249
|
-
// Get new function handler for grading
|
|
28250
|
-
const grader = functionForType(userInput.type);
|
|
28251
|
-
const guessCoeffs = getCoefficientsByType(userInput);
|
|
28252
|
-
const correctCoeffs = getCoefficientsByType(rubric.correct);
|
|
28253
|
-
if (guessCoeffs == null || correctCoeffs == null) {
|
|
28254
|
-
return {
|
|
28255
|
-
type: "invalid",
|
|
28256
|
-
message: null
|
|
28257
|
-
};
|
|
28258
|
-
}
|
|
28259
|
-
if (grader.areEqual(guessCoeffs, correctCoeffs)) {
|
|
28260
|
-
return {
|
|
28261
|
-
type: "points",
|
|
28262
|
-
earned: 1,
|
|
28263
|
-
total: 1,
|
|
28264
|
-
message: null
|
|
28265
|
-
};
|
|
28266
|
-
}
|
|
28267
|
-
return {
|
|
28268
|
-
type: "points",
|
|
28269
|
-
earned: 0,
|
|
28270
|
-
total: 1,
|
|
28271
|
-
message: null
|
|
28272
|
-
};
|
|
28273
|
-
}
|
|
28274
|
-
|
|
28275
26869
|
// @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
|
|
28276
26870
|
const MovablePoint$3 = Graphie.MovablePoint;
|
|
28277
26871
|
// @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
|
|
@@ -28319,7 +26913,8 @@ class FunctionGrapher extends React.Component {
|
|
|
28319
26913
|
return;
|
|
28320
26914
|
}
|
|
28321
26915
|
const functionProps = model.getPropsForCoeffs(coeffs, xRange);
|
|
28322
|
-
|
|
26916
|
+
const Movable = movableTypeToComponent[model.movable];
|
|
26917
|
+
return /*#__PURE__*/React.createElement(Movable, _extends({}, functionProps, {
|
|
28323
26918
|
key: this.props.model.url,
|
|
28324
26919
|
range: xRange,
|
|
28325
26920
|
style: style
|
|
@@ -28335,8 +26930,8 @@ class FunctionGrapher extends React.Component {
|
|
|
28335
26930
|
return asymptote && /*#__PURE__*/React.createElement(MovableLine$2, {
|
|
28336
26931
|
onMove: (newCoord, oldCoord) => {
|
|
28337
26932
|
// Calculate and apply displacement
|
|
28338
|
-
const delta = vector$
|
|
28339
|
-
const newAsymptote = _.map(this._asymptote(), coord => vector$
|
|
26933
|
+
const delta = vector$3.subtract(newCoord, oldCoord);
|
|
26934
|
+
const newAsymptote = _.map(this._asymptote(), coord => vector$3.add(coord, delta));
|
|
28340
26935
|
this.props.onChange({
|
|
28341
26936
|
asymptote: newAsymptote
|
|
28342
26937
|
});
|
|
@@ -28347,8 +26942,8 @@ class FunctionGrapher extends React.Component {
|
|
|
28347
26942
|
// @ts-expect-error - TS2339 - Property 'constraints' does not exist on type '(graphie: any, movable: any, options: any) => void'.
|
|
28348
26943
|
Interactive2.MovableLine.constraints.snap(), (newCoord, oldCoord) => {
|
|
28349
26944
|
// Calculate and apply proposed displacement
|
|
28350
|
-
const delta = vector$
|
|
28351
|
-
const proposedAsymptote = _.map(this._asymptote(), coord => vector$
|
|
26945
|
+
const delta = vector$3.subtract(newCoord, oldCoord);
|
|
26946
|
+
const proposedAsymptote = _.map(this._asymptote(), coord => vector$3.add(coord, delta));
|
|
28352
26947
|
// Verify that resulting asymptote is valid for graph
|
|
28353
26948
|
if (model.extraAsymptoteConstraint) {
|
|
28354
26949
|
return model.extraAsymptoteConstraint(newCoord, oldCoord, this._coords(), proposedAsymptote, graph);
|
|
@@ -28620,7 +27215,7 @@ class Grapher extends React.Component {
|
|
|
28620
27215
|
setup: this._setupGraphie
|
|
28621
27216
|
},
|
|
28622
27217
|
onChange: this.handlePlotChanges,
|
|
28623
|
-
model: type && functionForType(type),
|
|
27218
|
+
model: type && GrapherUtil$1.functionForType(type),
|
|
28624
27219
|
coords: coords,
|
|
28625
27220
|
asymptote: asymptote,
|
|
28626
27221
|
static: this.props.static,
|
|
@@ -28857,31 +27452,6 @@ const getPromptJSON$e = () => {
|
|
|
28857
27452
|
return getUnsupportedPromptJSON("iframe");
|
|
28858
27453
|
};
|
|
28859
27454
|
|
|
28860
|
-
function scoreIframe(userInput) {
|
|
28861
|
-
// The iframe can tell us whether it's correct or incorrect,
|
|
28862
|
-
// and pass an optional message
|
|
28863
|
-
if (userInput.status === "correct") {
|
|
28864
|
-
return {
|
|
28865
|
-
type: "points",
|
|
28866
|
-
earned: 1,
|
|
28867
|
-
total: 1,
|
|
28868
|
-
message: userInput.message || null
|
|
28869
|
-
};
|
|
28870
|
-
}
|
|
28871
|
-
if (userInput.status === "incorrect") {
|
|
28872
|
-
return {
|
|
28873
|
-
type: "points",
|
|
28874
|
-
earned: 0,
|
|
28875
|
-
total: 1,
|
|
28876
|
-
message: userInput.message || null
|
|
28877
|
-
};
|
|
28878
|
-
}
|
|
28879
|
-
return {
|
|
28880
|
-
type: "invalid",
|
|
28881
|
-
message: "Keep going, you're not there yet!"
|
|
28882
|
-
};
|
|
28883
|
-
}
|
|
28884
|
-
|
|
28885
27455
|
/**
|
|
28886
27456
|
* This is an iframe widget. It is used for rendering an iframe that
|
|
28887
27457
|
* then communicates its state via window.postMessage
|
|
@@ -29315,7 +27885,7 @@ class Interaction extends React.Component {
|
|
|
29315
27885
|
const xDiff = this._eval("(" + options.endX + ")-(" + options.startX + ")");
|
|
29316
27886
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
29317
27887
|
const yDiff = this._eval("(" + options.endY + ")-(" + options.startY + ")");
|
|
29318
|
-
const endCoord = vector$
|
|
27888
|
+
const endCoord = vector$3.add(startCoord, [xDiff, yDiff]);
|
|
29319
27889
|
const variables = _.clone(this.state.variables);
|
|
29320
27890
|
variables["x_" + options.startSubscript] = startCoord[0];
|
|
29321
27891
|
variables["y_" + options.startSubscript] = startCoord[1];
|
|
@@ -29903,15 +28473,15 @@ class Graph extends React.Component {
|
|
|
29903
28473
|
if (this.props.markings === "graph") {
|
|
29904
28474
|
graphie.graphInit({
|
|
29905
28475
|
range: range,
|
|
29906
|
-
scale: vector$
|
|
28476
|
+
scale: vector$3.map(gridConfig, g => g.scale),
|
|
29907
28477
|
axisArrows: "<->",
|
|
29908
28478
|
labelFormat: function (s) {
|
|
29909
28479
|
return "\\small{" + s + "}";
|
|
29910
28480
|
},
|
|
29911
28481
|
gridStep: this.props.gridStep,
|
|
29912
|
-
tickStep: vector$
|
|
28482
|
+
tickStep: vector$3.map(gridConfig, g => g.tickStep),
|
|
29913
28483
|
labelStep: 1,
|
|
29914
|
-
unityLabels: vector$
|
|
28484
|
+
unityLabels: vector$3.map(gridConfig, g => g.unityLabel),
|
|
29915
28485
|
isMobile: this.props.isMobile
|
|
29916
28486
|
});
|
|
29917
28487
|
graphie.label([0, range[1][1]], labels[1], this.props.isMobile ? "below right" : "above");
|
|
@@ -29919,7 +28489,7 @@ class Graph extends React.Component {
|
|
|
29919
28489
|
} else if (this.props.markings === "grid") {
|
|
29920
28490
|
graphie.graphInit({
|
|
29921
28491
|
range: range,
|
|
29922
|
-
scale: vector$
|
|
28492
|
+
scale: vector$3.map(gridConfig, g => g.scale),
|
|
29923
28493
|
gridStep: this.props.gridStep,
|
|
29924
28494
|
axes: false,
|
|
29925
28495
|
ticks: false,
|
|
@@ -29929,7 +28499,7 @@ class Graph extends React.Component {
|
|
|
29929
28499
|
} else if (this.props.markings === "none") {
|
|
29930
28500
|
graphie.init({
|
|
29931
28501
|
range: range,
|
|
29932
|
-
scale: vector$
|
|
28502
|
+
scale: vector$3.map(gridConfig, g => g.scale),
|
|
29933
28503
|
isMobile: this.props.isMobile
|
|
29934
28504
|
});
|
|
29935
28505
|
}
|
|
@@ -30002,7 +28572,7 @@ class Graph extends React.Component {
|
|
|
30002
28572
|
}
|
|
30003
28573
|
};
|
|
30004
28574
|
this._getGridConfig = () => {
|
|
30005
|
-
return vector$
|
|
28575
|
+
return vector$3.map(this.props.step, (step, i) => Util.gridDimensionConfig(step, this.props.range[i], this.props.box[i], this.props.gridStep[i]));
|
|
30006
28576
|
};
|
|
30007
28577
|
this._updateProtractor = () => {
|
|
30008
28578
|
if (this.protractor) {
|
|
@@ -30444,61 +29014,6 @@ function findIntersectionOfRays([[a, b], [c, d]], [[p, q], [r, s]]) {
|
|
|
30444
29014
|
}
|
|
30445
29015
|
}
|
|
30446
29016
|
|
|
30447
|
-
// This file contains helper functions for working with angles.
|
|
30448
|
-
|
|
30449
|
-
function convertDegreesToRadians(degrees) {
|
|
30450
|
-
return degrees / 180 * Math.PI;
|
|
30451
|
-
}
|
|
30452
|
-
|
|
30453
|
-
// Returns a value between -180 and 180, inclusive. The angle is measured
|
|
30454
|
-
// between the positive x-axis and the given vector.
|
|
30455
|
-
function calculateAngleInDegrees([x, y]) {
|
|
30456
|
-
return Math.atan2(y, x) * 180 / Math.PI;
|
|
30457
|
-
}
|
|
30458
|
-
|
|
30459
|
-
// Converts polar coordinates to cartesian. The th(eta) parameter is in degrees.
|
|
30460
|
-
function polar(r, th) {
|
|
30461
|
-
if (typeof r === "number") {
|
|
30462
|
-
r = [r, r];
|
|
30463
|
-
}
|
|
30464
|
-
th = th * Math.PI / 180;
|
|
30465
|
-
return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
|
|
30466
|
-
}
|
|
30467
|
-
// This function calculates the angle between two points and an optional vertex.
|
|
30468
|
-
// If the vertex is not provided, the angle is measured between the two points.
|
|
30469
|
-
// This does not account for reflex angles or clockwise position.
|
|
30470
|
-
const getAngleFromVertex = (point, vertex) => {
|
|
30471
|
-
const x = point[0] - vertex[0];
|
|
30472
|
-
const y = point[1] - vertex[1];
|
|
30473
|
-
if (!x && !y) {
|
|
30474
|
-
return 0;
|
|
30475
|
-
}
|
|
30476
|
-
return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
|
|
30477
|
-
};
|
|
30478
|
-
|
|
30479
|
-
// This function calculates the clockwise angle between three points,
|
|
30480
|
-
// and is used to generate the labels and equation strings of the
|
|
30481
|
-
// current angle for the interactive graph.
|
|
30482
|
-
const getClockwiseAngle = (coords, allowReflexAngles = false) => {
|
|
30483
|
-
const coordsCopy = [...coords];
|
|
30484
|
-
// The coords are saved as [point1, vertex, point2] in the interactive graph
|
|
30485
|
-
const areClockwise = clockwise$1([coordsCopy[0], coordsCopy[2], coordsCopy[1]]);
|
|
30486
|
-
|
|
30487
|
-
// We may need to reverse the coordinates if we allow
|
|
30488
|
-
// reflex angles and the points are not in clockwise order.
|
|
30489
|
-
const shouldReverseCoords = !areClockwise || allowReflexAngles;
|
|
30490
|
-
|
|
30491
|
-
// Reverse the coordinates accordingly to ensure the angle is calculated correctly
|
|
30492
|
-
const clockwiseCoords = shouldReverseCoords ? coordsCopy.reverse() : coordsCopy;
|
|
30493
|
-
|
|
30494
|
-
// Calculate the angles between the two points and get the difference
|
|
30495
|
-
// between the two angles to get the clockwise angle.
|
|
30496
|
-
const startAngle = getAngleFromVertex(clockwiseCoords[0], clockwiseCoords[1]);
|
|
30497
|
-
const endAngle = getAngleFromVertex(clockwiseCoords[2], clockwiseCoords[1]);
|
|
30498
|
-
const angle = (startAngle + 360 - endAngle) % 360;
|
|
30499
|
-
return angle;
|
|
30500
|
-
};
|
|
30501
|
-
|
|
30502
29017
|
const defaultGraphConfig = {
|
|
30503
29018
|
range: [[0, 1], [0, 1]],
|
|
30504
29019
|
tickStep: [1, 1],
|
|
@@ -31669,11 +30184,11 @@ const LockedFunction = props => {
|
|
|
31669
30184
|
"aria-label": hasAria ? props.ariaLabel : undefined,
|
|
31670
30185
|
"aria-hidden": !hasAria,
|
|
31671
30186
|
role: "img"
|
|
31672
|
-
}, directionalAxis === "x" && /*#__PURE__*/React.createElement(Plot$
|
|
30187
|
+
}, directionalAxis === "x" && /*#__PURE__*/React.createElement(Plot$2.OfX, _extends({
|
|
31673
30188
|
y: x => equation.eval({
|
|
31674
30189
|
x
|
|
31675
30190
|
})
|
|
31676
|
-
}, plotProps)), directionalAxis === "y" && /*#__PURE__*/React.createElement(Plot$
|
|
30191
|
+
}, plotProps)), directionalAxis === "y" && /*#__PURE__*/React.createElement(Plot$2.OfY, _extends({
|
|
31677
30192
|
x: y => equation.eval({
|
|
31678
30193
|
y
|
|
31679
30194
|
})
|
|
@@ -31699,6 +30214,9 @@ function SVGLine(props) {
|
|
|
31699
30214
|
});
|
|
31700
30215
|
}
|
|
31701
30216
|
|
|
30217
|
+
const {
|
|
30218
|
+
calculateAngleInDegrees: calculateAngleInDegrees$3
|
|
30219
|
+
} = angles;
|
|
31702
30220
|
function Vector(props) {
|
|
31703
30221
|
const {
|
|
31704
30222
|
tail,
|
|
@@ -31708,7 +30226,7 @@ function Vector(props) {
|
|
|
31708
30226
|
} = props;
|
|
31709
30227
|
const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
|
|
31710
30228
|
const direction = vec.sub(tipPx, tailPx);
|
|
31711
|
-
const angle = calculateAngleInDegrees(direction);
|
|
30229
|
+
const angle = calculateAngleInDegrees$3(direction);
|
|
31712
30230
|
return /*#__PURE__*/React.createElement("g", {
|
|
31713
30231
|
style: {
|
|
31714
30232
|
stroke: color,
|
|
@@ -31725,6 +30243,14 @@ function Vector(props) {
|
|
|
31725
30243
|
}));
|
|
31726
30244
|
}
|
|
31727
30245
|
|
|
30246
|
+
function srFormatNumber(a, locale) {
|
|
30247
|
+
// adding zero here converts negative zero to positive zero.
|
|
30248
|
+
return (0 + a).toLocaleString(locale, {
|
|
30249
|
+
maximumFractionDigits: 3,
|
|
30250
|
+
useGrouping: false // no thousands separators
|
|
30251
|
+
});
|
|
30252
|
+
}
|
|
30253
|
+
|
|
31728
30254
|
/**
|
|
31729
30255
|
* Given a ray and a rectangular box, find the point where the ray intersects
|
|
31730
30256
|
* the edge of the box. Assumes the `initialPoint` is inside the box.
|
|
@@ -31770,7 +30296,45 @@ function getArrayWithoutDuplicates(array) {
|
|
|
31770
30296
|
}
|
|
31771
30297
|
return returnArray;
|
|
31772
30298
|
}
|
|
30299
|
+
function getSlopeStringForLine(line, strings) {
|
|
30300
|
+
const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
|
|
30301
|
+
if (!Number.isFinite(slope)) {
|
|
30302
|
+
return strings.srLinearGraphSlopeVertical;
|
|
30303
|
+
}
|
|
30304
|
+
if (slope === 0) {
|
|
30305
|
+
return strings.srLinearGraphSlopeHorizontal;
|
|
30306
|
+
}
|
|
30307
|
+
return slope > 0 ? strings.srLinearGraphSlopeIncreasing : strings.srLinearGraphSlopeDecreasing;
|
|
30308
|
+
}
|
|
30309
|
+
function getInterceptStringForLine(line, strings, locale) {
|
|
30310
|
+
const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
|
|
30311
|
+
const xIntercept = (0 - line[0][1]) / slope + line[0][0];
|
|
30312
|
+
const yIntercept = line[0][1] - slope * line[0][0];
|
|
30313
|
+
|
|
30314
|
+
// Check if the line fully overlaps with an axis.
|
|
30315
|
+
const overlapsXAxis = line[0][1] === 0 && line[1][1] === 0;
|
|
30316
|
+
const overlapsYAxis = line[0][0] === 0 && line[1][0] === 0;
|
|
30317
|
+
const hasXIntercept = Number.isFinite(xIntercept) && !overlapsXAxis;
|
|
30318
|
+
const hasYIntercept = Number.isFinite(yIntercept) && !overlapsYAxis;
|
|
30319
|
+
if (hasXIntercept && hasYIntercept) {
|
|
30320
|
+
// Describe both intercepts in the same sentence.
|
|
30321
|
+
return xIntercept === 0 && yIntercept === 0 ? strings.srLinearGraphOriginIntercept : strings.srLinearGraphBothIntercepts({
|
|
30322
|
+
xIntercept: srFormatNumber(xIntercept, locale),
|
|
30323
|
+
yIntercept: srFormatNumber(yIntercept, locale)
|
|
30324
|
+
});
|
|
30325
|
+
}
|
|
30326
|
+
|
|
30327
|
+
// Describe only one intercept.
|
|
30328
|
+
return hasXIntercept ? strings.srLinearGraphXOnlyIntercept({
|
|
30329
|
+
xIntercept: srFormatNumber(xIntercept, locale)
|
|
30330
|
+
}) : strings.srLinearGraphYOnlyIntercept({
|
|
30331
|
+
yIntercept: srFormatNumber(yIntercept, locale)
|
|
30332
|
+
});
|
|
30333
|
+
}
|
|
31773
30334
|
|
|
30335
|
+
const {
|
|
30336
|
+
calculateAngleInDegrees: calculateAngleInDegrees$2
|
|
30337
|
+
} = angles;
|
|
31774
30338
|
const LockedLine = props => {
|
|
31775
30339
|
const {
|
|
31776
30340
|
color: color$1,
|
|
@@ -31802,7 +30366,7 @@ const LockedLine = props => {
|
|
|
31802
30366
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
31803
30367
|
const [tailPx, tipPx] = useTransformVectorsToPixels(point2.coord, point1.coord);
|
|
31804
30368
|
const direction = vec.sub(tailPx, tipPx);
|
|
31805
|
-
let angle = calculateAngleInDegrees(direction);
|
|
30369
|
+
let angle = calculateAngleInDegrees$2(direction);
|
|
31806
30370
|
const startArrowHead = kind !== "segment" && /*#__PURE__*/React.createElement(Arrowhead, {
|
|
31807
30371
|
angle: angle,
|
|
31808
30372
|
tip: arrowTip,
|
|
@@ -32212,6 +30776,12 @@ const SvgDefs = () => /*#__PURE__*/React__default.createElement("defs", null, /*
|
|
|
32212
30776
|
in: "SourceGraphic"
|
|
32213
30777
|
})));
|
|
32214
30778
|
|
|
30779
|
+
const {
|
|
30780
|
+
clockwise: clockwise$1
|
|
30781
|
+
} = geometry;
|
|
30782
|
+
const {
|
|
30783
|
+
getAngleFromVertex: getAngleFromVertex$1
|
|
30784
|
+
} = angles;
|
|
32215
30785
|
const PolygonAngle = ({
|
|
32216
30786
|
centerPoint,
|
|
32217
30787
|
endPoints,
|
|
@@ -32300,8 +30870,8 @@ const Angle = ({
|
|
|
32300
30870
|
const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
|
|
32301
30871
|
|
|
32302
30872
|
// Calculate the angles between the two points
|
|
32303
|
-
const startAngle = getAngleFromVertex(clockwiseCoords[0], vertex);
|
|
32304
|
-
const endAngle = getAngleFromVertex(clockwiseCoords[1], vertex);
|
|
30873
|
+
const startAngle = getAngleFromVertex$1(clockwiseCoords[0], vertex);
|
|
30874
|
+
const endAngle = getAngleFromVertex$1(clockwiseCoords[1], vertex);
|
|
32305
30875
|
const angle = (startAngle + 360 - endAngle) % 360;
|
|
32306
30876
|
|
|
32307
30877
|
// Check if the angle is reflexive
|
|
@@ -32657,14 +31227,6 @@ function range(min, max, step = 1) {
|
|
|
32657
31227
|
return result;
|
|
32658
31228
|
}
|
|
32659
31229
|
|
|
32660
|
-
function srFormatNumber(a, locale) {
|
|
32661
|
-
// adding zero here converts negative zero to positive zero.
|
|
32662
|
-
return (0 + a).toLocaleString(locale, {
|
|
32663
|
-
maximumFractionDigits: 3,
|
|
32664
|
-
useGrouping: false // no thousands separators
|
|
32665
|
-
});
|
|
32666
|
-
}
|
|
32667
|
-
|
|
32668
31230
|
// The hitbox size of 48px by 48px is preserved from the legacy interactive
|
|
32669
31231
|
// graph.
|
|
32670
31232
|
const hitboxSizePx = 48;
|
|
@@ -33052,6 +31614,11 @@ const MovablePoint$1 = /*#__PURE__*/React.forwardRef((props, pointRef) => {
|
|
|
33052
31614
|
return /*#__PURE__*/React.createElement(React.Fragment, null, focusableHandle, visiblePoint);
|
|
33053
31615
|
});
|
|
33054
31616
|
|
|
31617
|
+
const {
|
|
31618
|
+
calculateAngleInDegrees: calculateAngleInDegrees$1,
|
|
31619
|
+
getClockwiseAngle: getClockwiseAngle$2,
|
|
31620
|
+
polar: polar$1
|
|
31621
|
+
} = angles;
|
|
33055
31622
|
function renderAngleGraph(state, dispatch) {
|
|
33056
31623
|
return {
|
|
33057
31624
|
graph: /*#__PURE__*/React.createElement(AngleGraph, {
|
|
@@ -33121,7 +31688,7 @@ function AngleGraph(props) {
|
|
|
33121
31688
|
strings,
|
|
33122
31689
|
locale
|
|
33123
31690
|
} = usePerseusI18n();
|
|
33124
|
-
const angleMeasure = srFormatNumber(getClockwiseAngle([endPoints[0], centerPoint, endPoints[1]], allowReflexAngles), locale);
|
|
31691
|
+
const angleMeasure = srFormatNumber(getClockwiseAngle$2([endPoints[0], centerPoint, endPoints[1]], allowReflexAngles), locale);
|
|
33125
31692
|
const wholeAngleAriaLabel = strings.srAngleGraphAriaLabel;
|
|
33126
31693
|
const wholeAngleDescription = strings.srAngleGraphAriaDescription({
|
|
33127
31694
|
angleMeasure,
|
|
@@ -33185,9 +31752,7 @@ function AngleGraph(props) {
|
|
|
33185
31752
|
ariaLabel: initialSideAriaLabel
|
|
33186
31753
|
}), /*#__PURE__*/React.createElement("g", {
|
|
33187
31754
|
id: "angle-description",
|
|
33188
|
-
style:
|
|
33189
|
-
display: "hidden"
|
|
33190
|
-
}
|
|
31755
|
+
style: a11y.srOnly
|
|
33191
31756
|
}, wholeAngleDescription));
|
|
33192
31757
|
}
|
|
33193
31758
|
const positiveX = [1, 0];
|
|
@@ -33196,7 +31761,7 @@ const positiveY = [0, 1];
|
|
|
33196
31761
|
const negativeY = [0, -1];
|
|
33197
31762
|
function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
33198
31763
|
var _findIntersectionOfRa, _findIntersectionOfRa2, _findIntersectionOfRa3, _findIntersectionOfRa4;
|
|
33199
|
-
const currentAngle = calculateAngleInDegrees(vec.sub(sidePoint, vertex));
|
|
31764
|
+
const currentAngle = calculateAngleInDegrees$1(vec.sub(sidePoint, vertex));
|
|
33200
31765
|
|
|
33201
31766
|
// Find the rays that start at the current point and point up, down, left
|
|
33202
31767
|
// and right.
|
|
@@ -33213,8 +31778,8 @@ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
|
33213
31778
|
|
|
33214
31779
|
// find the rays that start from the vertex and point in the direction of
|
|
33215
31780
|
// the angles we just computed.
|
|
33216
|
-
const counterClockwiseRay = [vertex, vec.add(vertex, polar(1, oneStepCounterClockwise))];
|
|
33217
|
-
const clockwiseRay = [vertex, vec.add(vertex, polar(1, oneStepClockwise))];
|
|
31781
|
+
const counterClockwiseRay = [vertex, vec.add(vertex, polar$1(1, oneStepCounterClockwise))];
|
|
31782
|
+
const clockwiseRay = [vertex, vec.add(vertex, polar$1(1, oneStepClockwise))];
|
|
33218
31783
|
|
|
33219
31784
|
// find the intersections of those rays with the horizontal and vertical
|
|
33220
31785
|
// rays extending from the sidePoint. These intersections are the points to
|
|
@@ -33231,6 +31796,9 @@ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
|
33231
31796
|
};
|
|
33232
31797
|
}
|
|
33233
31798
|
|
|
31799
|
+
const {
|
|
31800
|
+
clockwise
|
|
31801
|
+
} = geometry;
|
|
33234
31802
|
function getGradableGraph(state, initialGraph) {
|
|
33235
31803
|
if (!state.hasBeenInteractedWith) {
|
|
33236
31804
|
return _extends({}, initialGraph);
|
|
@@ -33299,7 +31867,7 @@ function getGradableGraph(state, initialGraph) {
|
|
|
33299
31867
|
// by the order of the points in the coords array, and we want to maintain
|
|
33300
31868
|
// the same angle scoring with the legacy graph (which had a bug).
|
|
33301
31869
|
// (LEMS-2190): When we remove the legacy graph, move this logic to the scoring function.
|
|
33302
|
-
const areClockwise = clockwise
|
|
31870
|
+
const areClockwise = clockwise([state.coords[0], state.coords[2], state.coords[1]]);
|
|
33303
31871
|
const shouldReverseCoords = areClockwise && !state.allowReflexAngles;
|
|
33304
31872
|
const coords = shouldReverseCoords ? state.coords.slice().reverse() : state.coords;
|
|
33305
31873
|
return _extends({}, initialGraph, {
|
|
@@ -33411,14 +31979,10 @@ function CircleGraph(props) {
|
|
|
33411
31979
|
}
|
|
33412
31980
|
}), /*#__PURE__*/React.createElement("g", {
|
|
33413
31981
|
id: radiusId,
|
|
33414
|
-
style:
|
|
33415
|
-
display: "hidden"
|
|
33416
|
-
}
|
|
31982
|
+
style: a11y.srOnly
|
|
33417
31983
|
}, srCircleRadius), /*#__PURE__*/React.createElement("g", {
|
|
33418
31984
|
id: outerPointsId,
|
|
33419
|
-
style:
|
|
33420
|
-
display: "hidden"
|
|
33421
|
-
}
|
|
31985
|
+
style: a11y.srOnly
|
|
33422
31986
|
}, srCircleOuterPoints));
|
|
33423
31987
|
}
|
|
33424
31988
|
function MovableCircle(props) {
|
|
@@ -33603,49 +32167,16 @@ const LinearGraph = (props, key) => {
|
|
|
33603
32167
|
point2X: srFormatNumber(line[1][0], locale),
|
|
33604
32168
|
point2Y: srFormatNumber(line[1][1], locale)
|
|
33605
32169
|
});
|
|
33606
|
-
|
|
33607
|
-
|
|
33608
|
-
const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
|
|
33609
|
-
let slopeString = "";
|
|
33610
|
-
if (slope === Infinity || slope === -Infinity) {
|
|
33611
|
-
slopeString = strings.srLinearGraphSlopeVertical;
|
|
33612
|
-
} else if (slope === 0) {
|
|
33613
|
-
slopeString = strings.srLinearGraphSlopeHorizontal;
|
|
33614
|
-
} else {
|
|
33615
|
-
slopeString = slope > 0 ? strings.srLinearGraphSlopeIncreasing : strings.srLinearGraphSlopeDecreasing;
|
|
33616
|
-
}
|
|
33617
|
-
|
|
33618
|
-
// Intersection description
|
|
33619
|
-
const xIntercept = (0 - line[0][1]) / slope + line[0][0];
|
|
33620
|
-
const yIntercept = line[0][1] - slope * line[0][0];
|
|
33621
|
-
const hasXIntercept = xIntercept !== Infinity && xIntercept !== -Infinity;
|
|
33622
|
-
const hasYIntercept = yIntercept !== Infinity && yIntercept !== -Infinity;
|
|
33623
|
-
let interceptString;
|
|
33624
|
-
if (hasXIntercept && hasYIntercept) {
|
|
33625
|
-
// Describe both intercepts in the same sentence.
|
|
33626
|
-
interceptString = xIntercept === 0 && yIntercept === 0 ? strings.srLinearGraphOriginIntercept : strings.srLinearGraphBothIntercepts({
|
|
33627
|
-
xIntercept: srFormatNumber(xIntercept, locale),
|
|
33628
|
-
yIntercept: srFormatNumber(yIntercept, locale)
|
|
33629
|
-
});
|
|
33630
|
-
} else {
|
|
33631
|
-
// Describe only one intercept.
|
|
33632
|
-
interceptString = hasXIntercept ? strings.srLinearGraphXOnlyIntercept({
|
|
33633
|
-
xIntercept: srFormatNumber(xIntercept, locale)
|
|
33634
|
-
}) : strings.srLinearGraphYOnlyIntercept({
|
|
33635
|
-
yIntercept: srFormatNumber(yIntercept, locale)
|
|
33636
|
-
});
|
|
33637
|
-
}
|
|
32170
|
+
const slopeString = getSlopeStringForLine(line, strings);
|
|
32171
|
+
const interceptString = getInterceptStringForLine(line, strings, locale);
|
|
33638
32172
|
|
|
33639
32173
|
// Linear graphs only have one line
|
|
33640
32174
|
// (LEMS-2050): Update the reducer so that we have a separate action for moving one line
|
|
33641
32175
|
// and another action for moving multiple lines
|
|
33642
32176
|
return /*#__PURE__*/React.createElement("g", {
|
|
33643
|
-
className: "linear-graph-container"
|
|
33644
32177
|
// Outer line minimal description
|
|
33645
|
-
,
|
|
33646
32178
|
"aria-label": strings.srLinearGraph,
|
|
33647
|
-
"aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}
|
|
33648
|
-
role: "figure"
|
|
32179
|
+
"aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`
|
|
33649
32180
|
}, /*#__PURE__*/React.createElement(MovableLine, {
|
|
33650
32181
|
key: 0,
|
|
33651
32182
|
ariaLabels: {
|
|
@@ -33664,19 +32195,13 @@ const LinearGraph = (props, key) => {
|
|
|
33664
32195
|
color: "var(--movable-line-stroke-color)"
|
|
33665
32196
|
}), /*#__PURE__*/React.createElement("g", {
|
|
33666
32197
|
id: pointsDescriptionId,
|
|
33667
|
-
style:
|
|
33668
|
-
display: "hidden"
|
|
33669
|
-
}
|
|
32198
|
+
style: a11y.srOnly
|
|
33670
32199
|
}, linearGraphPointsDescription), /*#__PURE__*/React.createElement("g", {
|
|
33671
32200
|
id: interceptDescriptionId,
|
|
33672
|
-
style:
|
|
33673
|
-
display: "hidden"
|
|
33674
|
-
}
|
|
32201
|
+
style: a11y.srOnly
|
|
33675
32202
|
}, interceptString), /*#__PURE__*/React.createElement("g", {
|
|
33676
32203
|
id: slopeDescriptionId,
|
|
33677
|
-
style:
|
|
33678
|
-
display: "hidden"
|
|
33679
|
-
}
|
|
32204
|
+
style: a11y.srOnly
|
|
33680
32205
|
}, slopeString));
|
|
33681
32206
|
};
|
|
33682
32207
|
|
|
@@ -33686,7 +32211,9 @@ function renderLinearSystemGraph(state, dispatch) {
|
|
|
33686
32211
|
graphState: state,
|
|
33687
32212
|
dispatch: dispatch
|
|
33688
32213
|
}),
|
|
33689
|
-
interactiveElementsDescription:
|
|
32214
|
+
interactiveElementsDescription: /*#__PURE__*/React.createElement(LinearSystemGraphDescription, {
|
|
32215
|
+
state: state
|
|
32216
|
+
})
|
|
33690
32217
|
};
|
|
33691
32218
|
}
|
|
33692
32219
|
const LinearSystemGraph = props => {
|
|
@@ -33696,9 +32223,53 @@ const LinearSystemGraph = props => {
|
|
|
33696
32223
|
const {
|
|
33697
32224
|
coords: lines
|
|
33698
32225
|
} = props.graphState;
|
|
33699
|
-
|
|
32226
|
+
const {
|
|
32227
|
+
strings,
|
|
32228
|
+
locale
|
|
32229
|
+
} = usePerseusI18n();
|
|
32230
|
+
const id = React.useId();
|
|
32231
|
+
const linesAriaInfo = lines.map((line, i) => {
|
|
32232
|
+
return {
|
|
32233
|
+
pointsDescriptionId: `${id}-line${i + 1}-points`,
|
|
32234
|
+
interceptDescriptionId: `${id}-line${i + 1}-intercept`,
|
|
32235
|
+
slopeDescriptionId: `${id}-line${i + 1}-slope`,
|
|
32236
|
+
pointsDescription: strings.srLinearSystemPoints({
|
|
32237
|
+
lineNumber: i + 1,
|
|
32238
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32239
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32240
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32241
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32242
|
+
}),
|
|
32243
|
+
interceptDescription: getInterceptStringForLine(line, strings, locale),
|
|
32244
|
+
slopeDescription: getSlopeStringForLine(line, strings)
|
|
32245
|
+
};
|
|
32246
|
+
});
|
|
32247
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
32248
|
+
"aria-label": strings.srLinearSystemGraph,
|
|
32249
|
+
"aria-describedby": linesAriaInfo.map(({
|
|
32250
|
+
pointsDescriptionId,
|
|
32251
|
+
interceptDescriptionId,
|
|
32252
|
+
slopeDescriptionId
|
|
32253
|
+
}) => `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`).join(" ")
|
|
32254
|
+
}, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(MovableLine, {
|
|
33700
32255
|
key: i,
|
|
33701
32256
|
points: line,
|
|
32257
|
+
ariaLabels: {
|
|
32258
|
+
point1AriaLabel: strings.srLinearSystemPoint({
|
|
32259
|
+
lineNumber: i + 1,
|
|
32260
|
+
pointSequence: 1,
|
|
32261
|
+
x: srFormatNumber(line[0][0], locale),
|
|
32262
|
+
y: srFormatNumber(line[0][1], locale)
|
|
32263
|
+
}),
|
|
32264
|
+
point2AriaLabel: strings.srLinearSystemPoint({
|
|
32265
|
+
lineNumber: i + 1,
|
|
32266
|
+
pointSequence: 2,
|
|
32267
|
+
x: srFormatNumber(line[1][0], locale),
|
|
32268
|
+
y: srFormatNumber(line[1][1], locale)
|
|
32269
|
+
}),
|
|
32270
|
+
grabHandleAriaLabel: `${linesAriaInfo[i].interceptDescription} ${linesAriaInfo[i].slopeDescription}`
|
|
32271
|
+
},
|
|
32272
|
+
ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId}`,
|
|
33702
32273
|
onMoveLine: delta => {
|
|
33703
32274
|
dispatch(actions.linearSystem.moveLine(i, delta));
|
|
33704
32275
|
},
|
|
@@ -33708,8 +32279,63 @@ const LinearSystemGraph = props => {
|
|
|
33708
32279
|
},
|
|
33709
32280
|
onMovePoint: (endpointIndex, destination) => dispatch(actions.linearSystem.movePointInFigure(i, endpointIndex, destination)),
|
|
33710
32281
|
color: "var(--movable-line-stroke-color)"
|
|
33711
|
-
})),
|
|
32282
|
+
})), linesAriaInfo.map(({
|
|
32283
|
+
pointsDescriptionId,
|
|
32284
|
+
interceptDescriptionId,
|
|
32285
|
+
slopeDescriptionId,
|
|
32286
|
+
pointsDescription,
|
|
32287
|
+
interceptDescription,
|
|
32288
|
+
slopeDescription
|
|
32289
|
+
}) => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("g", {
|
|
32290
|
+
key: pointsDescriptionId,
|
|
32291
|
+
id: pointsDescriptionId,
|
|
32292
|
+
style: a11y.srOnly
|
|
32293
|
+
}, pointsDescription), /*#__PURE__*/React.createElement("g", {
|
|
32294
|
+
key: interceptDescriptionId,
|
|
32295
|
+
id: interceptDescriptionId,
|
|
32296
|
+
style: a11y.srOnly
|
|
32297
|
+
}, interceptDescription), /*#__PURE__*/React.createElement("g", {
|
|
32298
|
+
key: slopeDescriptionId,
|
|
32299
|
+
id: slopeDescriptionId,
|
|
32300
|
+
style: a11y.srOnly
|
|
32301
|
+
}, slopeDescription))));
|
|
33712
32302
|
};
|
|
32303
|
+
function LinearSystemGraphDescription({
|
|
32304
|
+
state
|
|
32305
|
+
}) {
|
|
32306
|
+
// The reason that LinearSystemGraphDescription is a component (rather
|
|
32307
|
+
// than a function that returns a string) is because it needs to use a
|
|
32308
|
+
// hook: `usePerseusI18n`.
|
|
32309
|
+
const i18n = usePerseusI18n();
|
|
32310
|
+
return describeLinearSystemGraph(state, i18n);
|
|
32311
|
+
}
|
|
32312
|
+
|
|
32313
|
+
// Exported for testing
|
|
32314
|
+
function describeLinearSystemGraph(state, i18n) {
|
|
32315
|
+
const {
|
|
32316
|
+
strings,
|
|
32317
|
+
locale
|
|
32318
|
+
} = i18n;
|
|
32319
|
+
const {
|
|
32320
|
+
coords: lines
|
|
32321
|
+
} = state;
|
|
32322
|
+
const graphDescription = strings.srLinearSystemGraph;
|
|
32323
|
+
const lineDescriptions = lines.map((line, i) => {
|
|
32324
|
+
const point1 = line[0];
|
|
32325
|
+
const point2 = line[1];
|
|
32326
|
+
return strings.srLinearSystemPoints({
|
|
32327
|
+
lineNumber: i + 1,
|
|
32328
|
+
point1X: srFormatNumber(point1[0], locale),
|
|
32329
|
+
point1Y: srFormatNumber(point1[1], locale),
|
|
32330
|
+
point2X: srFormatNumber(point2[0], locale),
|
|
32331
|
+
point2Y: srFormatNumber(point2[1], locale)
|
|
32332
|
+
});
|
|
32333
|
+
});
|
|
32334
|
+
const allDescriptions = [graphDescription, ...lineDescriptions];
|
|
32335
|
+
return strings.srInteractiveElements({
|
|
32336
|
+
elements: allDescriptions.join(" ")
|
|
32337
|
+
});
|
|
32338
|
+
}
|
|
33713
32339
|
|
|
33714
32340
|
function renderPointGraph(state, dispatch) {
|
|
33715
32341
|
return {
|
|
@@ -34148,7 +32774,7 @@ function QuadraticGraph(props) {
|
|
|
34148
32774
|
// to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
|
|
34149
32775
|
// While we should technically never have invalid coordinates, we want to ensure that we have a fallback
|
|
34150
32776
|
const coeffRef = React.useRef([0, 0, 0]);
|
|
34151
|
-
const coeffs = getQuadraticCoefficients$
|
|
32777
|
+
const coeffs = getQuadraticCoefficients$2(coords);
|
|
34152
32778
|
if (coeffs !== undefined) {
|
|
34153
32779
|
coeffRef.current = coeffs;
|
|
34154
32780
|
}
|
|
@@ -34158,7 +32784,7 @@ function QuadraticGraph(props) {
|
|
|
34158
32784
|
|
|
34159
32785
|
// Calculate the y value based on the current x value
|
|
34160
32786
|
const y = x => (a * x + b) * x + c;
|
|
34161
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$
|
|
32787
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$2.OfX, {
|
|
34162
32788
|
y: y,
|
|
34163
32789
|
color: color.blue
|
|
34164
32790
|
}), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
@@ -34173,7 +32799,7 @@ function QuadraticGraph(props) {
|
|
|
34173
32799
|
// These equations were originally set up in 2013 and may require some
|
|
34174
32800
|
// additional comments to help clarify the quadratic formula manipulations
|
|
34175
32801
|
// Origin: https://phabricator.khanacademy.org/D2413
|
|
34176
|
-
const getQuadraticCoefficients$
|
|
32802
|
+
const getQuadraticCoefficients$2 = coords => {
|
|
34177
32803
|
const p1 = coords[0];
|
|
34178
32804
|
const p2 = coords[1];
|
|
34179
32805
|
const p3 = coords[2];
|
|
@@ -34196,7 +32822,9 @@ function renderRayGraph(state, dispatch) {
|
|
|
34196
32822
|
graphState: state,
|
|
34197
32823
|
dispatch: dispatch
|
|
34198
32824
|
}),
|
|
34199
|
-
interactiveElementsDescription:
|
|
32825
|
+
interactiveElementsDescription: /*#__PURE__*/React.createElement(RayGraphDescription, {
|
|
32826
|
+
state: state
|
|
32827
|
+
})
|
|
34200
32828
|
};
|
|
34201
32829
|
}
|
|
34202
32830
|
const RayGraph = props => {
|
|
@@ -34208,18 +32836,103 @@ const RayGraph = props => {
|
|
|
34208
32836
|
} = props.graphState;
|
|
34209
32837
|
const handleMoveLine = delta => dispatch(actions.ray.moveRay(delta));
|
|
34210
32838
|
const handleMovePoint = (pointIndex, newPoint) => dispatch(actions.ray.movePoint(pointIndex, newPoint));
|
|
32839
|
+
const {
|
|
32840
|
+
strings,
|
|
32841
|
+
locale
|
|
32842
|
+
} = usePerseusI18n();
|
|
32843
|
+
const id = React.useId();
|
|
32844
|
+
const pointsDescriptionId = id + "-points";
|
|
32845
|
+
|
|
32846
|
+
// Aria label strings
|
|
32847
|
+
const {
|
|
32848
|
+
srRayGraph,
|
|
32849
|
+
srRayPoints,
|
|
32850
|
+
srRayEndpoint,
|
|
32851
|
+
srRayTerminalPoint,
|
|
32852
|
+
srRayGrabHandle
|
|
32853
|
+
} = describeRayGraph(props.graphState, {
|
|
32854
|
+
strings,
|
|
32855
|
+
locale
|
|
32856
|
+
});
|
|
34211
32857
|
|
|
34212
32858
|
// Ray graphs only have one line
|
|
34213
|
-
return /*#__PURE__*/React.createElement(
|
|
32859
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
32860
|
+
// Outer line minimal description
|
|
32861
|
+
"aria-label": srRayGraph,
|
|
32862
|
+
"aria-describedby": pointsDescriptionId
|
|
32863
|
+
}, /*#__PURE__*/React.createElement(MovableLine, {
|
|
34214
32864
|
points: line,
|
|
32865
|
+
ariaLabels: {
|
|
32866
|
+
point1AriaLabel: srRayEndpoint,
|
|
32867
|
+
point2AriaLabel: srRayTerminalPoint,
|
|
32868
|
+
grabHandleAriaLabel: srRayGrabHandle
|
|
32869
|
+
},
|
|
34215
32870
|
onMoveLine: handleMoveLine,
|
|
34216
32871
|
onMovePoint: handleMovePoint,
|
|
34217
32872
|
extend: {
|
|
34218
32873
|
start: false,
|
|
34219
32874
|
end: true
|
|
34220
32875
|
}
|
|
34221
|
-
})
|
|
32876
|
+
}), /*#__PURE__*/React.createElement("g", {
|
|
32877
|
+
id: pointsDescriptionId,
|
|
32878
|
+
style: a11y.srOnly
|
|
32879
|
+
}, srRayPoints));
|
|
34222
32880
|
};
|
|
32881
|
+
function RayGraphDescription({
|
|
32882
|
+
state
|
|
32883
|
+
}) {
|
|
32884
|
+
// The reason that RayGraphDescription is a component (rather than a
|
|
32885
|
+
// function that returns a string) is because it needs to use a
|
|
32886
|
+
// hook: `usePerseusI18n`.
|
|
32887
|
+
const i18n = usePerseusI18n();
|
|
32888
|
+
const strings = describeRayGraph(state, i18n);
|
|
32889
|
+
return strings.srRayInteractiveElement;
|
|
32890
|
+
}
|
|
32891
|
+
|
|
32892
|
+
// Exported for testing
|
|
32893
|
+
function describeRayGraph(state, i18n) {
|
|
32894
|
+
const {
|
|
32895
|
+
coords: line
|
|
32896
|
+
} = state;
|
|
32897
|
+
const {
|
|
32898
|
+
strings,
|
|
32899
|
+
locale
|
|
32900
|
+
} = i18n;
|
|
32901
|
+
|
|
32902
|
+
// Aria label strings
|
|
32903
|
+
const srRayGraph = strings.srRayGraph;
|
|
32904
|
+
const srRayPoints = strings.srRayPoints({
|
|
32905
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32906
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32907
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32908
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32909
|
+
});
|
|
32910
|
+
const srRayEndpoint = strings.srRayEndpoint({
|
|
32911
|
+
x: srFormatNumber(line[0][0], locale),
|
|
32912
|
+
y: srFormatNumber(line[0][1], locale)
|
|
32913
|
+
});
|
|
32914
|
+
const srRayTerminalPoint = strings.srRayTerminalPoint({
|
|
32915
|
+
x: srFormatNumber(line[1][0], locale),
|
|
32916
|
+
y: srFormatNumber(line[1][1], locale)
|
|
32917
|
+
});
|
|
32918
|
+
const srRayGrabHandle = strings.srRayGrabHandle({
|
|
32919
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32920
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32921
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32922
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32923
|
+
});
|
|
32924
|
+
const srRayInteractiveElement = strings.srInteractiveElements({
|
|
32925
|
+
elements: [srRayGraph, srRayPoints].join(" ")
|
|
32926
|
+
});
|
|
32927
|
+
return {
|
|
32928
|
+
srRayGraph,
|
|
32929
|
+
srRayPoints,
|
|
32930
|
+
srRayEndpoint,
|
|
32931
|
+
srRayTerminalPoint,
|
|
32932
|
+
srRayGrabHandle,
|
|
32933
|
+
srRayInteractiveElement
|
|
32934
|
+
};
|
|
32935
|
+
}
|
|
34223
32936
|
|
|
34224
32937
|
function renderSegmentGraph(state, dispatch) {
|
|
34225
32938
|
return {
|
|
@@ -34287,7 +33000,7 @@ function SinusoidGraph(props) {
|
|
|
34287
33000
|
if (coeffs !== undefined) {
|
|
34288
33001
|
coeffRef.current = coeffs;
|
|
34289
33002
|
}
|
|
34290
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$
|
|
33003
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$2.OfX, {
|
|
34291
33004
|
y: x => computeSine(x, coeffRef.current),
|
|
34292
33005
|
color: color.blue
|
|
34293
33006
|
}), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
@@ -34334,6 +33047,10 @@ const getSinusoidCoefficients$1 = coords => {
|
|
|
34334
33047
|
};
|
|
34335
33048
|
};
|
|
34336
33049
|
|
|
33050
|
+
const {
|
|
33051
|
+
calculateAngleInDegrees,
|
|
33052
|
+
convertDegreesToRadians
|
|
33053
|
+
} = angles;
|
|
34337
33054
|
const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
|
|
34338
33055
|
|
|
34339
33056
|
// The vector from the center of the protractor to the top left corner of the
|
|
@@ -35035,6 +33752,10 @@ function mafsStateToInteractiveGraph(state, originalGraph) {
|
|
|
35035
33752
|
}
|
|
35036
33753
|
}
|
|
35037
33754
|
|
|
33755
|
+
const {
|
|
33756
|
+
magnitude: magnitude$2,
|
|
33757
|
+
vector: vector$2
|
|
33758
|
+
} = geometry;
|
|
35038
33759
|
function initializeGraphState(params) {
|
|
35039
33760
|
var _graph$snapTo;
|
|
35040
33761
|
const {
|
|
@@ -35314,7 +34035,7 @@ const getAngleCoords = params => {
|
|
|
35314
34035
|
defaultCoords = normalizePoints(range, step, defaultCoords, true);
|
|
35315
34036
|
|
|
35316
34037
|
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
35317
|
-
const radius = magnitude(vector(...defaultCoords));
|
|
34038
|
+
const radius = magnitude$2(vector$2(...defaultCoords));
|
|
35318
34039
|
|
|
35319
34040
|
// We're adding a placeholder for the third point to appease ts and so that we
|
|
35320
34041
|
// can calculate it after we've adjusted the first point by the angleOffsetDeg
|
|
@@ -35328,6 +34049,24 @@ const getAngleCoords = params => {
|
|
|
35328
34049
|
return coords;
|
|
35329
34050
|
};
|
|
35330
34051
|
|
|
34052
|
+
const {
|
|
34053
|
+
getAngleFromVertex,
|
|
34054
|
+
getClockwiseAngle: getClockwiseAngle$1,
|
|
34055
|
+
polar
|
|
34056
|
+
} = angles;
|
|
34057
|
+
const {
|
|
34058
|
+
angleMeasures: angleMeasures$1,
|
|
34059
|
+
ccw: ccw$1,
|
|
34060
|
+
lawOfCosines: lawOfCosines$1,
|
|
34061
|
+
magnitude: magnitude$1,
|
|
34062
|
+
polygonSidesIntersect,
|
|
34063
|
+
reverseVector,
|
|
34064
|
+
sign: sign$1,
|
|
34065
|
+
vector: vector$1
|
|
34066
|
+
} = geometry;
|
|
34067
|
+
const {
|
|
34068
|
+
getQuadraticCoefficients: getQuadraticCoefficients$1
|
|
34069
|
+
} = coefficients;
|
|
35331
34070
|
const minDistanceBetweenAngleVertexAndSidePoint = 2;
|
|
35332
34071
|
function interactiveGraphReducer(state, action) {
|
|
35333
34072
|
switch (action.type) {
|
|
@@ -35846,11 +34585,9 @@ const getChange = (coords, delta, constraintOpts) => {
|
|
|
35846
34585
|
const [dx, dy] = getDeltaVertex(maxMoves, minMoves, delta);
|
|
35847
34586
|
return [dx, dy];
|
|
35848
34587
|
};
|
|
35849
|
-
const eq$2 = Util.eq;
|
|
35850
|
-
|
|
35851
34588
|
// Less than or approximately equal
|
|
35852
34589
|
function leq$1(a, b) {
|
|
35853
|
-
return a < b ||
|
|
34590
|
+
return a < b || approximateEqual(a, b);
|
|
35854
34591
|
}
|
|
35855
34592
|
function boundAndSnapToGrid(point, {
|
|
35856
34593
|
snapStep,
|
|
@@ -35994,7 +34731,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
|
|
|
35994
34731
|
});
|
|
35995
34732
|
|
|
35996
34733
|
// Gets the radian angles between the coords and maps them to degrees
|
|
35997
|
-
const angles = angleMeasures(coordsCopy).map(angle => angle * 180 / Math.PI);
|
|
34734
|
+
const angles = angleMeasures$1(coordsCopy).map(angle => angle * 180 / Math.PI);
|
|
35998
34735
|
|
|
35999
34736
|
// Gets the relative index of a point
|
|
36000
34737
|
const rel = j => {
|
|
@@ -36006,7 +34743,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
|
|
|
36006
34743
|
angles[rel(j)] = Math.round(angles[rel(j)]);
|
|
36007
34744
|
});
|
|
36008
34745
|
const getAngle = function getAngle(a, vertex, b) {
|
|
36009
|
-
const angle = getClockwiseAngle([coordsCopy[rel(a)], coordsCopy[rel(vertex)], coordsCopy[rel(b)]]);
|
|
34746
|
+
const angle = getClockwiseAngle$1([coordsCopy[rel(a)], coordsCopy[rel(vertex)], coordsCopy[rel(b)]]);
|
|
36010
34747
|
return angle;
|
|
36011
34748
|
};
|
|
36012
34749
|
const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
|
|
@@ -36018,9 +34755,9 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
|
|
|
36018
34755
|
})) {
|
|
36019
34756
|
return startingPoint;
|
|
36020
34757
|
}
|
|
36021
|
-
const knownSide = magnitude(
|
|
34758
|
+
const knownSide = magnitude$1(
|
|
36022
34759
|
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
|
|
36023
|
-
vector(coordsCopy[rel(-1)], coordsCopy[rel(1)]));
|
|
34760
|
+
vector$1(coordsCopy[rel(-1)], coordsCopy[rel(1)]));
|
|
36024
34761
|
|
|
36025
34762
|
// Returns true if the points form a counter-clockwise turn;
|
|
36026
34763
|
// a.k.a if the point is on the left or right of the polygon.
|
|
@@ -36028,7 +34765,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
|
|
|
36028
34765
|
// we want to add the inner angle to the outer angle, and if it's on the right,
|
|
36029
34766
|
// we want to subtract the inner angle from the outer angle. The angle solved
|
|
36030
34767
|
// for is then used in the polar function to determine the new point.
|
|
36031
|
-
const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
|
|
34768
|
+
const onLeft = sign$1(ccw$1(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
|
|
36032
34769
|
|
|
36033
34770
|
// Solve for side by using the law of sines
|
|
36034
34771
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
@@ -36040,7 +34777,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
|
|
|
36040
34777
|
// and the angle between the first and second sides of the
|
|
36041
34778
|
// polygon (angular coordinate) to determine how to adjust the point
|
|
36042
34779
|
const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
36043
|
-
return vector$
|
|
34780
|
+
return vector$3.add(coordsCopy[rel(-1)], offset);
|
|
36044
34781
|
}
|
|
36045
34782
|
function boundAndSnapToSides(destinationPoint, {
|
|
36046
34783
|
range,
|
|
@@ -36066,7 +34803,7 @@ function boundAndSnapToSides(destinationPoint, {
|
|
|
36066
34803
|
};
|
|
36067
34804
|
const sides = _.map([[coordsCopy[rel(-1)], coordsCopy[index]], [coordsCopy[index], coordsCopy[rel(1)]], [coordsCopy[rel(-1)], coordsCopy[rel(1)]]], function (coordsCopy) {
|
|
36068
34805
|
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
36069
|
-
return magnitude(vector(...coordsCopy));
|
|
34806
|
+
return magnitude$1(vector$1(...coordsCopy));
|
|
36070
34807
|
});
|
|
36071
34808
|
|
|
36072
34809
|
// Round the sides to left and right of the current point
|
|
@@ -36081,7 +34818,7 @@ function boundAndSnapToSides(destinationPoint, {
|
|
|
36081
34818
|
|
|
36082
34819
|
// Solve for angle by using the law of cosines
|
|
36083
34820
|
// Angle at the first vertex of the polygon
|
|
36084
|
-
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
34821
|
+
const innerAngle = lawOfCosines$1(sides[0], sides[2], sides[1]);
|
|
36085
34822
|
|
|
36086
34823
|
// Angle at the second vertex of the polygon
|
|
36087
34824
|
const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
|
|
@@ -36092,13 +34829,13 @@ function boundAndSnapToSides(destinationPoint, {
|
|
|
36092
34829
|
// we want to add the inner angle to the outer angle, and if it's on the right,
|
|
36093
34830
|
// we want to subtract the inner angle from the outer angle. The angle solved
|
|
36094
34831
|
// for is then used in the polar function to determine the new point.
|
|
36095
|
-
const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
|
|
34832
|
+
const onLeft = sign$1(ccw$1(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
|
|
36096
34833
|
|
|
36097
34834
|
// Uses the length of the first side of the polygon (radial coordinate)
|
|
36098
34835
|
// and the angle between the first and second sides of the
|
|
36099
34836
|
// polygon (angular coordinate) to determine how to adjust the point
|
|
36100
34837
|
const offset = polar(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
36101
|
-
return vector$
|
|
34838
|
+
return vector$3.add(coordsCopy[rel(-1)], offset);
|
|
36102
34839
|
}
|
|
36103
34840
|
|
|
36104
34841
|
// Returns the vector from the given point to the top-right corner of the graph when snapped to the grid
|
|
@@ -36128,7 +34865,7 @@ function minMove({
|
|
|
36128
34865
|
});
|
|
36129
34866
|
return vec.sub(bottomLeft, point);
|
|
36130
34867
|
}
|
|
36131
|
-
const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && vector$
|
|
34868
|
+
const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && vector$3.equal(coord, c)));
|
|
36132
34869
|
function updateAtIndex(args) {
|
|
36133
34870
|
const {
|
|
36134
34871
|
array,
|
|
@@ -36223,216 +34960,26 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
36223
34960
|
}));
|
|
36224
34961
|
});
|
|
36225
34962
|
|
|
36226
|
-
const
|
|
36227
|
-
|
|
36228
|
-
|
|
36229
|
-
|
|
36230
|
-
|
|
36231
|
-
|
|
36232
|
-
|
|
36233
|
-
|
|
36234
|
-
|
|
36235
|
-
|
|
36236
|
-
|
|
36237
|
-
|
|
36238
|
-
|
|
36239
|
-
|
|
36240
|
-
|
|
36241
|
-
|
|
36242
|
-
const hasValue = Boolean(
|
|
36243
|
-
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
36244
|
-
userInput.coords ||
|
|
36245
|
-
// @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
|
|
36246
|
-
userInput.center && userInput.radius);
|
|
36247
|
-
if (userInput.type === rubric.correct.type && hasValue) {
|
|
36248
|
-
if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
|
|
36249
|
-
const guess = userInput.coords;
|
|
36250
|
-
const correct = rubric.correct.coords;
|
|
36251
|
-
|
|
36252
|
-
// If both of the guess points are on the correct line, it's
|
|
36253
|
-
// correct.
|
|
36254
|
-
if (collinear(correct[0], correct[1], guess[0]) && collinear(correct[0], correct[1], guess[1])) {
|
|
36255
|
-
return {
|
|
36256
|
-
type: "points",
|
|
36257
|
-
earned: 1,
|
|
36258
|
-
total: 1,
|
|
36259
|
-
message: null
|
|
36260
|
-
};
|
|
36261
|
-
}
|
|
36262
|
-
} else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
|
|
36263
|
-
const guess = userInput.coords;
|
|
36264
|
-
const correct = rubric.correct.coords;
|
|
36265
|
-
if (collinear(correct[0][0], correct[0][1], guess[0][0]) && collinear(correct[0][0], correct[0][1], guess[0][1]) && collinear(correct[1][0], correct[1][1], guess[1][0]) && collinear(correct[1][0], correct[1][1], guess[1][1]) || collinear(correct[0][0], correct[0][1], guess[1][0]) && collinear(correct[0][0], correct[0][1], guess[1][1]) && collinear(correct[1][0], correct[1][1], guess[0][0]) && collinear(correct[1][0], correct[1][1], guess[0][1])) {
|
|
36266
|
-
return {
|
|
36267
|
-
type: "points",
|
|
36268
|
-
earned: 1,
|
|
36269
|
-
total: 1,
|
|
36270
|
-
message: null
|
|
36271
|
-
};
|
|
36272
|
-
}
|
|
36273
|
-
} else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
|
|
36274
|
-
// If the parabola coefficients match, it's correct.
|
|
36275
|
-
const guessCoeffs = getQuadraticCoefficients(userInput.coords);
|
|
36276
|
-
const correctCoeffs = getQuadraticCoefficients(rubric.correct.coords);
|
|
36277
|
-
if (deepEq$3(guessCoeffs, correctCoeffs)) {
|
|
36278
|
-
return {
|
|
36279
|
-
type: "points",
|
|
36280
|
-
earned: 1,
|
|
36281
|
-
total: 1,
|
|
36282
|
-
message: null
|
|
36283
|
-
};
|
|
36284
|
-
}
|
|
36285
|
-
} else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
|
|
36286
|
-
const guessCoeffs = getSinusoidCoefficients(userInput.coords);
|
|
36287
|
-
const correctCoeffs = getSinusoidCoefficients(rubric.correct.coords);
|
|
36288
|
-
const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
|
|
36289
|
-
const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
|
|
36290
|
-
// If the canonical coefficients match, it's correct.
|
|
36291
|
-
if (deepEq$3(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
|
|
36292
|
-
return {
|
|
36293
|
-
type: "points",
|
|
36294
|
-
earned: 1,
|
|
36295
|
-
total: 1,
|
|
36296
|
-
message: null
|
|
36297
|
-
};
|
|
36298
|
-
}
|
|
36299
|
-
} else if (userInput.type === "circle" && rubric.correct.type === "circle") {
|
|
36300
|
-
if (deepEq$3(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
|
|
36301
|
-
return {
|
|
36302
|
-
type: "points",
|
|
36303
|
-
earned: 1,
|
|
36304
|
-
total: 1,
|
|
36305
|
-
message: null
|
|
36306
|
-
};
|
|
36307
|
-
}
|
|
36308
|
-
} else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
|
|
36309
|
-
let correct = rubric.correct.coords;
|
|
36310
|
-
if (correct == null) {
|
|
36311
|
-
throw new Error("Point graph rubric has null coords");
|
|
36312
|
-
}
|
|
36313
|
-
const guess = userInput.coords.slice();
|
|
36314
|
-
correct = correct.slice();
|
|
36315
|
-
// Everything's already rounded so we shouldn't need to do an
|
|
36316
|
-
// eq() comparison but _.isEqual(0, -0) is false, so we'll use
|
|
36317
|
-
// eq() anyway. The sort should be fine because it'll stringify
|
|
36318
|
-
// it and -0 converted to a string is "0"
|
|
36319
|
-
guess == null || guess.sort();
|
|
36320
|
-
// @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
|
|
36321
|
-
correct.sort();
|
|
36322
|
-
if (deepEq$3(guess, correct)) {
|
|
36323
|
-
return {
|
|
36324
|
-
type: "points",
|
|
36325
|
-
earned: 1,
|
|
36326
|
-
total: 1,
|
|
36327
|
-
message: null
|
|
36328
|
-
};
|
|
36329
|
-
}
|
|
36330
|
-
} else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
|
|
36331
|
-
const guess = userInput.coords.slice();
|
|
36332
|
-
const correct = rubric.correct.coords.slice();
|
|
36333
|
-
let match;
|
|
36334
|
-
if (rubric.correct.match === "similar") {
|
|
36335
|
-
match = similar(guess, correct, Number.POSITIVE_INFINITY);
|
|
36336
|
-
} else if (rubric.correct.match === "congruent") {
|
|
36337
|
-
match = similar(guess, correct, number$1.DEFAULT_TOLERANCE);
|
|
36338
|
-
} else if (rubric.correct.match === "approx") {
|
|
36339
|
-
match = similar(guess, correct, 0.1);
|
|
36340
|
-
} else {
|
|
36341
|
-
/* exact */
|
|
36342
|
-
guess.sort();
|
|
36343
|
-
correct.sort();
|
|
36344
|
-
match = deepEq$3(guess, correct);
|
|
36345
|
-
}
|
|
36346
|
-
if (match) {
|
|
36347
|
-
return {
|
|
36348
|
-
type: "points",
|
|
36349
|
-
earned: 1,
|
|
36350
|
-
total: 1,
|
|
36351
|
-
message: null
|
|
36352
|
-
};
|
|
36353
|
-
}
|
|
36354
|
-
} else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
|
|
36355
|
-
let guess = Util.deepClone(userInput.coords);
|
|
36356
|
-
let correct = Util.deepClone(rubric.correct.coords);
|
|
36357
|
-
guess = _.invoke(guess, "sort").sort();
|
|
36358
|
-
correct = _.invoke(correct, "sort").sort();
|
|
36359
|
-
if (deepEq$3(guess, correct)) {
|
|
36360
|
-
return {
|
|
36361
|
-
type: "points",
|
|
36362
|
-
earned: 1,
|
|
36363
|
-
total: 1,
|
|
36364
|
-
message: null
|
|
36365
|
-
};
|
|
36366
|
-
}
|
|
36367
|
-
} else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
|
|
36368
|
-
const guess = userInput.coords;
|
|
36369
|
-
const correct = rubric.correct.coords;
|
|
36370
|
-
if (deepEq$3(guess[0], correct[0]) && collinear(correct[0], correct[1], guess[1])) {
|
|
36371
|
-
return {
|
|
36372
|
-
type: "points",
|
|
36373
|
-
earned: 1,
|
|
36374
|
-
total: 1,
|
|
36375
|
-
message: null
|
|
36376
|
-
};
|
|
36377
|
-
}
|
|
36378
|
-
} else if (userInput.type === "angle" && rubric.correct.type === "angle") {
|
|
36379
|
-
const guess = userInput.coords;
|
|
36380
|
-
const correct = rubric.correct.coords;
|
|
36381
|
-
const allowReflexAngles = rubric.correct.allowReflexAngles;
|
|
36382
|
-
let match;
|
|
36383
|
-
if (rubric.correct.match === "congruent") {
|
|
36384
|
-
const angles = _.map([guess, correct], function (coords) {
|
|
36385
|
-
if (!coords) {
|
|
36386
|
-
return false;
|
|
36387
|
-
}
|
|
36388
|
-
const angle = getClockwiseAngle(coords, allowReflexAngles);
|
|
36389
|
-
return angle;
|
|
36390
|
-
});
|
|
36391
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
36392
|
-
match = eq$1(...angles);
|
|
36393
|
-
} else {
|
|
36394
|
-
/* exact */
|
|
36395
|
-
match =
|
|
36396
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
36397
|
-
deepEq$3(guess[1], correct[1]) &&
|
|
36398
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
36399
|
-
collinear(correct[1], correct[0], guess[0]) &&
|
|
36400
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
36401
|
-
collinear(correct[1], correct[2], guess[2]);
|
|
36402
|
-
}
|
|
36403
|
-
if (match) {
|
|
36404
|
-
return {
|
|
36405
|
-
type: "points",
|
|
36406
|
-
earned: 1,
|
|
36407
|
-
total: 1,
|
|
36408
|
-
message: null
|
|
36409
|
-
};
|
|
36410
|
-
}
|
|
36411
|
-
}
|
|
36412
|
-
}
|
|
36413
|
-
|
|
36414
|
-
// The input wasn't correct, so check if it's a blank input or if it's
|
|
36415
|
-
// actually just wrong
|
|
36416
|
-
if (!hasValue || _.isEqual(userInput, rubric.graph)) {
|
|
36417
|
-
// We're where we started.
|
|
36418
|
-
return {
|
|
36419
|
-
type: "invalid",
|
|
36420
|
-
message: null
|
|
36421
|
-
};
|
|
36422
|
-
}
|
|
36423
|
-
return {
|
|
36424
|
-
type: "points",
|
|
36425
|
-
earned: 0,
|
|
36426
|
-
total: 1,
|
|
36427
|
-
message: null
|
|
36428
|
-
};
|
|
36429
|
-
}
|
|
36430
|
-
|
|
34963
|
+
const {
|
|
34964
|
+
getClockwiseAngle
|
|
34965
|
+
} = angles;
|
|
34966
|
+
const {
|
|
34967
|
+
angleMeasures,
|
|
34968
|
+
ccw,
|
|
34969
|
+
collinear,
|
|
34970
|
+
getLineEquation,
|
|
34971
|
+
getLineIntersection,
|
|
34972
|
+
intersects,
|
|
34973
|
+
lawOfCosines,
|
|
34974
|
+
magnitude,
|
|
34975
|
+
rotate,
|
|
34976
|
+
sign,
|
|
34977
|
+
vector
|
|
34978
|
+
} = geometry;
|
|
36431
34979
|
const TRASH_ICON_URI = "https://ka-perseus-graphie.s3.amazonaws.com/b1452c0d79fd0f7ff4c3af9488474a0a0decb361.png";
|
|
36432
34980
|
const defaultBackgroundImage = {
|
|
36433
34981
|
url: null
|
|
36434
34982
|
};
|
|
36435
|
-
const eq = Util.eq;
|
|
36436
34983
|
const UNLIMITED = "unlimited";
|
|
36437
34984
|
|
|
36438
34985
|
// Sample background image:
|
|
@@ -36445,7 +34992,7 @@ function defaultVal(actual, defaultValue) {
|
|
|
36445
34992
|
|
|
36446
34993
|
// Less than or approximately equal
|
|
36447
34994
|
function leq(a, b) {
|
|
36448
|
-
return a < b ||
|
|
34995
|
+
return a < b || approximateEqual(a, b);
|
|
36449
34996
|
}
|
|
36450
34997
|
function capitalize(str) {
|
|
36451
34998
|
return str.replace(/(?:^|-)(.)/g, function (match, letter) {
|
|
@@ -37272,7 +35819,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
37272
35819
|
// Solve for side by using the law of sines
|
|
37273
35820
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
37274
35821
|
const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
|
|
37275
|
-
const offset = polar$
|
|
35822
|
+
const offset = polar$2(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
37276
35823
|
return (_this$graphie5 = this.graphie) == null ? void 0 : _this$graphie5.addPoints(coords[rel(-1)], offset);
|
|
37277
35824
|
}
|
|
37278
35825
|
// @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
|
|
@@ -37297,7 +35844,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
37297
35844
|
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
37298
35845
|
const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
|
|
37299
35846
|
const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
|
|
37300
|
-
const offset = polar$
|
|
35847
|
+
const offset = polar$2(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
37301
35848
|
return (_this$graphie6 = this.graphie) == null ? void 0 : _this$graphie6.addPoints(coords[rel(-1)], offset);
|
|
37302
35849
|
}
|
|
37303
35850
|
// Snap to grid (already done)
|
|
@@ -37959,12 +36506,12 @@ class InteractiveGraph extends React.Component {
|
|
|
37959
36506
|
}
|
|
37960
36507
|
static getLinearEquationString(props) {
|
|
37961
36508
|
const coords = InteractiveGraph.getLineCoords(props.graph, props);
|
|
37962
|
-
if (
|
|
36509
|
+
if (approximateEqual(coords[0][0], coords[1][0])) {
|
|
37963
36510
|
return "x = " + coords[0][0].toFixed(3);
|
|
37964
36511
|
}
|
|
37965
36512
|
const m = (coords[1][1] - coords[0][1]) / (coords[1][0] - coords[0][0]);
|
|
37966
36513
|
const b = coords[0][1] - m * coords[0][0];
|
|
37967
|
-
if (
|
|
36514
|
+
if (approximateEqual(m, 0)) {
|
|
37968
36515
|
return "y = " + b.toFixed(3);
|
|
37969
36516
|
}
|
|
37970
36517
|
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
@@ -38490,59 +37037,6 @@ const styles$a = StyleSheet.create({
|
|
|
38490
37037
|
}
|
|
38491
37038
|
});
|
|
38492
37039
|
|
|
38493
|
-
// Question state for marker as result of user selected answers.
|
|
38494
|
-
|
|
38495
|
-
function scoreMarker(marker) {
|
|
38496
|
-
const score = {
|
|
38497
|
-
hasAnswers: false,
|
|
38498
|
-
isCorrect: false
|
|
38499
|
-
};
|
|
38500
|
-
if (marker.selected && marker.selected.length > 0) {
|
|
38501
|
-
score.hasAnswers = true;
|
|
38502
|
-
}
|
|
38503
|
-
if (marker.answers.length > 0) {
|
|
38504
|
-
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
38505
|
-
// All correct answers are selected by the user.
|
|
38506
|
-
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
38507
|
-
}
|
|
38508
|
-
} else if (!marker.selected || marker.selected.length === 0) {
|
|
38509
|
-
// Correct as no answers should be selected by the user.
|
|
38510
|
-
score.isCorrect = true;
|
|
38511
|
-
}
|
|
38512
|
-
return score;
|
|
38513
|
-
}
|
|
38514
|
-
|
|
38515
|
-
// TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
|
|
38516
|
-
function scoreLabelImage(userInput, rubric) {
|
|
38517
|
-
let numAnswered = 0;
|
|
38518
|
-
let numCorrect = 0;
|
|
38519
|
-
for (const marker of userInput.markers) {
|
|
38520
|
-
const score = scoreMarker(marker);
|
|
38521
|
-
if (score.hasAnswers) {
|
|
38522
|
-
numAnswered++;
|
|
38523
|
-
}
|
|
38524
|
-
if (score.isCorrect) {
|
|
38525
|
-
numCorrect++;
|
|
38526
|
-
}
|
|
38527
|
-
}
|
|
38528
|
-
|
|
38529
|
-
// We expect all question markers to be answered before grading.
|
|
38530
|
-
if (numAnswered !== userInput.markers.length) {
|
|
38531
|
-
return {
|
|
38532
|
-
type: "invalid",
|
|
38533
|
-
message: null
|
|
38534
|
-
};
|
|
38535
|
-
}
|
|
38536
|
-
return {
|
|
38537
|
-
type: "points",
|
|
38538
|
-
// Markers with no expected answers are graded as correct if user
|
|
38539
|
-
// makes no answer selection.
|
|
38540
|
-
earned: numCorrect === userInput.markers.length ? 1 : 0,
|
|
38541
|
-
total: 1,
|
|
38542
|
-
message: null
|
|
38543
|
-
};
|
|
38544
|
-
}
|
|
38545
|
-
|
|
38546
37040
|
/**
|
|
38547
37041
|
* Represents a direction vector.
|
|
38548
37042
|
* No diagonals allowed.
|
|
@@ -38764,7 +37258,7 @@ class LabelImage extends React.Component {
|
|
|
38764
37258
|
onChange
|
|
38765
37259
|
} = this.props;
|
|
38766
37260
|
const updatedMarkers = markers.map(marker => {
|
|
38767
|
-
const score =
|
|
37261
|
+
const score = scoreLabelImageMarker(marker);
|
|
38768
37262
|
return _extends({}, marker, {
|
|
38769
37263
|
// Reveal correctness state for markers with answers.
|
|
38770
37264
|
showCorrectness: score.hasAnswers ? score.isCorrect ? "correct" : "incorrect" : undefined
|
|
@@ -38911,7 +37405,7 @@ class LabelImage extends React.Component {
|
|
|
38911
37405
|
bottom: "top"
|
|
38912
37406
|
}[markerPosition];
|
|
38913
37407
|
}
|
|
38914
|
-
const score =
|
|
37408
|
+
const score = scoreLabelImageMarker(marker);
|
|
38915
37409
|
// Once the question is answered, show markers
|
|
38916
37410
|
// with correct answers, otherwise passthrough
|
|
38917
37411
|
// the correctness state.
|
|
@@ -39843,16 +38337,6 @@ const getPromptJSON$9 = (renderProps, userInput) => {
|
|
|
39843
38337
|
};
|
|
39844
38338
|
};
|
|
39845
38339
|
|
|
39846
|
-
function scoreMatcher(state, rubric) {
|
|
39847
|
-
const correct = _.isEqual(state.left, rubric.left) && _.isEqual(state.right, rubric.right);
|
|
39848
|
-
return {
|
|
39849
|
-
type: "points",
|
|
39850
|
-
earned: correct ? 1 : 0,
|
|
39851
|
-
total: 1,
|
|
39852
|
-
message: null
|
|
39853
|
-
};
|
|
39854
|
-
}
|
|
39855
|
-
|
|
39856
38340
|
const {
|
|
39857
38341
|
shuffle: shuffle$1,
|
|
39858
38342
|
seededRNG
|
|
@@ -40075,78 +38559,6 @@ const getPromptJSON$8 = (renderProps, userInput) => {
|
|
|
40075
38559
|
};
|
|
40076
38560
|
};
|
|
40077
38561
|
|
|
40078
|
-
/**
|
|
40079
|
-
* Checks user input from the matrix widget to see if it is scorable.
|
|
40080
|
-
*
|
|
40081
|
-
* Note: The matrix widget cannot do much validation without the Scoring
|
|
40082
|
-
* Data because of its use of KhanAnswerTypes as a core part of scoring.
|
|
40083
|
-
*
|
|
40084
|
-
* @see `scoreMatrix()` for more details.
|
|
40085
|
-
*/
|
|
40086
|
-
function validateMatrix(userInput, validationData, strings) {
|
|
40087
|
-
const supplied = userInput.answers;
|
|
40088
|
-
const suppliedSize = getMatrixSize(supplied);
|
|
40089
|
-
for (let row = 0; row < suppliedSize[0]; row++) {
|
|
40090
|
-
for (let col = 0; col < suppliedSize[1]; col++) {
|
|
40091
|
-
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
40092
|
-
return {
|
|
40093
|
-
type: "invalid",
|
|
40094
|
-
message: strings.fillAllCells
|
|
40095
|
-
};
|
|
40096
|
-
}
|
|
40097
|
-
}
|
|
40098
|
-
}
|
|
40099
|
-
return null;
|
|
40100
|
-
}
|
|
40101
|
-
|
|
40102
|
-
function scoreMatrix(userInput, rubric, strings) {
|
|
40103
|
-
const validationResult = validateMatrix(userInput, rubric, strings);
|
|
40104
|
-
if (validationResult != null) {
|
|
40105
|
-
return validationResult;
|
|
40106
|
-
}
|
|
40107
|
-
const solution = rubric.answers;
|
|
40108
|
-
const supplied = userInput.answers;
|
|
40109
|
-
const solutionSize = getMatrixSize(solution);
|
|
40110
|
-
const suppliedSize = getMatrixSize(supplied);
|
|
40111
|
-
const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
|
|
40112
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
40113
|
-
let message = null;
|
|
40114
|
-
let incorrect = false;
|
|
40115
|
-
_(suppliedSize[0]).times(row => {
|
|
40116
|
-
_(suppliedSize[1]).times(col => {
|
|
40117
|
-
if (!incorrectSize) {
|
|
40118
|
-
const validator = createValidator(
|
|
40119
|
-
// @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
|
|
40120
|
-
solution[row][col], {
|
|
40121
|
-
simplify: true
|
|
40122
|
-
});
|
|
40123
|
-
const result = validator(supplied[row][col]);
|
|
40124
|
-
if (result.message) {
|
|
40125
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
40126
|
-
message = result.message;
|
|
40127
|
-
}
|
|
40128
|
-
if (!result.correct) {
|
|
40129
|
-
incorrect = true;
|
|
40130
|
-
}
|
|
40131
|
-
}
|
|
40132
|
-
});
|
|
40133
|
-
});
|
|
40134
|
-
if (incorrectSize) {
|
|
40135
|
-
return {
|
|
40136
|
-
type: "points",
|
|
40137
|
-
earned: 0,
|
|
40138
|
-
total: 1,
|
|
40139
|
-
message: null
|
|
40140
|
-
};
|
|
40141
|
-
}
|
|
40142
|
-
return {
|
|
40143
|
-
type: "points",
|
|
40144
|
-
earned: incorrect ? 0 : 1,
|
|
40145
|
-
total: 1,
|
|
40146
|
-
message: message
|
|
40147
|
-
};
|
|
40148
|
-
}
|
|
40149
|
-
|
|
40150
38562
|
const {
|
|
40151
38563
|
assert: assert$2
|
|
40152
38564
|
} = InteractiveUtil;
|
|
@@ -40195,29 +38607,6 @@ const getRefForPath$1 = function getRefForPath(path) {
|
|
|
40195
38607
|
const column = getColumnFromPath$1(path);
|
|
40196
38608
|
return "answer" + row + "," + column;
|
|
40197
38609
|
};
|
|
40198
|
-
function getMatrixSize(matrix) {
|
|
40199
|
-
const matrixSize = [1, 1];
|
|
40200
|
-
|
|
40201
|
-
// We need to find the widest row and tallest column to get the correct
|
|
40202
|
-
// matrix size.
|
|
40203
|
-
_(matrix).each((matrixRow, row) => {
|
|
40204
|
-
let rowWidth = 0;
|
|
40205
|
-
_(matrixRow).each((matrixCol, col) => {
|
|
40206
|
-
if (matrixCol != null && matrixCol.toString().length) {
|
|
40207
|
-
rowWidth = col + 1;
|
|
40208
|
-
}
|
|
40209
|
-
});
|
|
40210
|
-
|
|
40211
|
-
// Matrix width:
|
|
40212
|
-
matrixSize[1] = Math.max(matrixSize[1], rowWidth);
|
|
40213
|
-
|
|
40214
|
-
// Matrix height:
|
|
40215
|
-
if (rowWidth > 0) {
|
|
40216
|
-
matrixSize[0] = Math.max(matrixSize[0], row + 1);
|
|
40217
|
-
}
|
|
40218
|
-
});
|
|
40219
|
-
return matrixSize;
|
|
40220
|
-
}
|
|
40221
38610
|
class Matrix extends React.Component {
|
|
40222
38611
|
constructor(...args) {
|
|
40223
38612
|
super(...args);
|
|
@@ -41608,59 +39997,6 @@ const getPromptJSON$6 = (renderProps, userInput) => {
|
|
|
41608
39997
|
};
|
|
41609
39998
|
};
|
|
41610
39999
|
|
|
41611
|
-
/**
|
|
41612
|
-
* Checks user input is within the allowed range and not the same as the initial
|
|
41613
|
-
* state.
|
|
41614
|
-
* @param userInput
|
|
41615
|
-
* @see 'scoreNumberLine' for the scoring logic.
|
|
41616
|
-
*/
|
|
41617
|
-
function validateNumberLine(userInput) {
|
|
41618
|
-
const divisionRange = userInput.divisionRange;
|
|
41619
|
-
const outsideAllowedRange = userInput.numDivisions > divisionRange[1] || userInput.numDivisions < divisionRange[0];
|
|
41620
|
-
|
|
41621
|
-
// TODO: I don't think isTickCrtl is a thing anymore
|
|
41622
|
-
if (userInput.isTickCrtl && outsideAllowedRange) {
|
|
41623
|
-
return {
|
|
41624
|
-
type: "invalid",
|
|
41625
|
-
message: "Number of divisions is outside the allowed range."
|
|
41626
|
-
};
|
|
41627
|
-
}
|
|
41628
|
-
return null;
|
|
41629
|
-
}
|
|
41630
|
-
|
|
41631
|
-
function scoreNumberLine(userInput, scoringData) {
|
|
41632
|
-
const validationError = validateNumberLine(userInput);
|
|
41633
|
-
if (validationError) {
|
|
41634
|
-
return validationError;
|
|
41635
|
-
}
|
|
41636
|
-
const range = scoringData.range;
|
|
41637
|
-
const start = scoringData.initialX != null ? scoringData.initialX : range[0];
|
|
41638
|
-
const startRel = scoringData.isInequality ? "ge" : "eq";
|
|
41639
|
-
const correctRel = scoringData.correctRel || "eq";
|
|
41640
|
-
const correctPos = number$1.equal(userInput.numLinePosition, scoringData.correctX || 0);
|
|
41641
|
-
if (correctPos && correctRel === userInput.rel) {
|
|
41642
|
-
return {
|
|
41643
|
-
type: "points",
|
|
41644
|
-
earned: 1,
|
|
41645
|
-
total: 1,
|
|
41646
|
-
message: null
|
|
41647
|
-
};
|
|
41648
|
-
}
|
|
41649
|
-
if (userInput.numLinePosition === start && userInput.rel === startRel) {
|
|
41650
|
-
// We're where we started.
|
|
41651
|
-
return {
|
|
41652
|
-
type: "invalid",
|
|
41653
|
-
message: null
|
|
41654
|
-
};
|
|
41655
|
-
}
|
|
41656
|
-
return {
|
|
41657
|
-
type: "points",
|
|
41658
|
-
earned: 0,
|
|
41659
|
-
total: 1,
|
|
41660
|
-
message: null
|
|
41661
|
-
};
|
|
41662
|
-
}
|
|
41663
|
-
|
|
41664
40000
|
// @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
|
|
41665
40001
|
const MovablePoint = Graphie.MovablePoint;
|
|
41666
40002
|
// @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
|
|
@@ -42229,36 +40565,6 @@ const getPromptJSON$5 = (renderProps, userInput) => {
|
|
|
42229
40565
|
};
|
|
42230
40566
|
};
|
|
42231
40567
|
|
|
42232
|
-
/**
|
|
42233
|
-
* Checks user input from the orderer widget to see if the user has started
|
|
42234
|
-
* ordering the options, making the widget scorable.
|
|
42235
|
-
* @param userInput
|
|
42236
|
-
* @see `scoreOrderer` for more details.
|
|
42237
|
-
*/
|
|
42238
|
-
function validateOrderer(userInput) {
|
|
42239
|
-
if (userInput.current.length === 0) {
|
|
42240
|
-
return {
|
|
42241
|
-
type: "invalid",
|
|
42242
|
-
message: null
|
|
42243
|
-
};
|
|
42244
|
-
}
|
|
42245
|
-
return null;
|
|
42246
|
-
}
|
|
42247
|
-
|
|
42248
|
-
function scoreOrderer(userInput, rubric) {
|
|
42249
|
-
const validateError = validateOrderer(userInput);
|
|
42250
|
-
if (validateError) {
|
|
42251
|
-
return validateError;
|
|
42252
|
-
}
|
|
42253
|
-
const correct = _.isEqual(userInput.current, rubric.correctOptions.map(option => option.content));
|
|
42254
|
-
return {
|
|
42255
|
-
type: "points",
|
|
42256
|
-
earned: correct ? 1 : 0,
|
|
42257
|
-
total: 1,
|
|
42258
|
-
message: null
|
|
42259
|
-
};
|
|
42260
|
-
}
|
|
42261
|
-
|
|
42262
40568
|
class PlaceholderCard extends React.Component {
|
|
42263
40569
|
render() {
|
|
42264
40570
|
return /*#__PURE__*/React.createElement("div", {
|
|
@@ -43074,42 +41380,6 @@ const getPromptJSON$3 = () => {
|
|
|
43074
41380
|
return getUnsupportedPromptJSON("plotter");
|
|
43075
41381
|
};
|
|
43076
41382
|
|
|
43077
|
-
const {
|
|
43078
|
-
deepEq: deepEq$2
|
|
43079
|
-
} = Util;
|
|
43080
|
-
|
|
43081
|
-
/**
|
|
43082
|
-
* Checks user input to confirm it is not the same as the starting values for the graph.
|
|
43083
|
-
* This means the user has modified the graph, and the question can be scored.
|
|
43084
|
-
*
|
|
43085
|
-
* @see 'scorePlotter' for more details on scoring.
|
|
43086
|
-
*/
|
|
43087
|
-
function validatePlotter(userInput, validationData) {
|
|
43088
|
-
if (deepEq$2(userInput, validationData.starting)) {
|
|
43089
|
-
return {
|
|
43090
|
-
type: "invalid",
|
|
43091
|
-
message: null
|
|
43092
|
-
};
|
|
43093
|
-
}
|
|
43094
|
-
return null;
|
|
43095
|
-
}
|
|
43096
|
-
|
|
43097
|
-
const {
|
|
43098
|
-
deepEq: deepEq$1
|
|
43099
|
-
} = Util;
|
|
43100
|
-
function scorePlotter(userInput, scoringData) {
|
|
43101
|
-
const validationError = validatePlotter(userInput, scoringData);
|
|
43102
|
-
if (validationError) {
|
|
43103
|
-
return validationError;
|
|
43104
|
-
}
|
|
43105
|
-
return {
|
|
43106
|
-
type: "points",
|
|
43107
|
-
earned: deepEq$1(userInput, scoringData.correct) ? 1 : 0,
|
|
43108
|
-
total: 1,
|
|
43109
|
-
message: null
|
|
43110
|
-
};
|
|
43111
|
-
}
|
|
43112
|
-
|
|
43113
41383
|
/* eslint-disable react/no-unsafe */
|
|
43114
41384
|
class Plotter extends React.Component {
|
|
43115
41385
|
constructor(..._args) {
|
|
@@ -43989,43 +42259,6 @@ const getPromptJSON$1 = userInput => {
|
|
|
43989
42259
|
};
|
|
43990
42260
|
};
|
|
43991
42261
|
|
|
43992
|
-
/**
|
|
43993
|
-
* Checks user input for the sorter widget to ensure that the user has made
|
|
43994
|
-
* changes before attempting to score the widget.
|
|
43995
|
-
* @param userInput
|
|
43996
|
-
* @see 'scoreSorter' in 'packages/perseus/src/widgets/sorter/score-sorter.ts'
|
|
43997
|
-
* for more details on how the sorter widget is scored.
|
|
43998
|
-
*/
|
|
43999
|
-
function validateSorter(userInput) {
|
|
44000
|
-
// If the sorter widget hasn't been changed yet, we treat it as "empty" which
|
|
44001
|
-
// prevents the "Check" button from becoming active. We want the user
|
|
44002
|
-
// to make a change before trying to move forward. This makes an
|
|
44003
|
-
// assumption that the initial order isn't the correct order! However,
|
|
44004
|
-
// this should be rare if it happens, and interacting with the list
|
|
44005
|
-
// will enable the button, so they won't be locked out of progressing.
|
|
44006
|
-
if (!userInput.changed) {
|
|
44007
|
-
return {
|
|
44008
|
-
type: "invalid",
|
|
44009
|
-
message: null
|
|
44010
|
-
};
|
|
44011
|
-
}
|
|
44012
|
-
return null;
|
|
44013
|
-
}
|
|
44014
|
-
|
|
44015
|
-
function scoreSorter(userInput, rubric) {
|
|
44016
|
-
const validationError = validateSorter(userInput);
|
|
44017
|
-
if (validationError) {
|
|
44018
|
-
return validationError;
|
|
44019
|
-
}
|
|
44020
|
-
const correct = Util.deepEq(userInput.options, rubric.correct);
|
|
44021
|
-
return {
|
|
44022
|
-
type: "points",
|
|
44023
|
-
earned: correct ? 1 : 0,
|
|
44024
|
-
total: 1,
|
|
44025
|
-
message: null
|
|
44026
|
-
};
|
|
44027
|
-
}
|
|
44028
|
-
|
|
44029
42262
|
const {
|
|
44030
42263
|
shuffle
|
|
44031
42264
|
} = Util;
|
|
@@ -44118,81 +42351,6 @@ var Sorter$1 = {
|
|
|
44118
42351
|
scorer: scoreSorter
|
|
44119
42352
|
};
|
|
44120
42353
|
|
|
44121
|
-
/**
|
|
44122
|
-
* Filters the given table (modelled as a 2D array) to remove any rows that are
|
|
44123
|
-
* completely empty.
|
|
44124
|
-
*
|
|
44125
|
-
* @returns A new table with only non-empty rows.
|
|
44126
|
-
*/
|
|
44127
|
-
const filterNonEmpty = function filterNonEmpty(table) {
|
|
44128
|
-
return table.filter(function (row) {
|
|
44129
|
-
// Return only rows that are non-empty.
|
|
44130
|
-
return row.some(cell => cell);
|
|
44131
|
-
});
|
|
44132
|
-
};
|
|
44133
|
-
|
|
44134
|
-
function validateTable(userInput) {
|
|
44135
|
-
const supplied = filterNonEmpty(userInput);
|
|
44136
|
-
const hasEmptyCell = supplied.some(function (row) {
|
|
44137
|
-
return row.some(function (cell) {
|
|
44138
|
-
return cell === "";
|
|
44139
|
-
});
|
|
44140
|
-
});
|
|
44141
|
-
if (hasEmptyCell || !supplied.length) {
|
|
44142
|
-
return {
|
|
44143
|
-
type: "invalid",
|
|
44144
|
-
message: null
|
|
44145
|
-
};
|
|
44146
|
-
}
|
|
44147
|
-
return null;
|
|
44148
|
-
}
|
|
44149
|
-
|
|
44150
|
-
function scoreTable(userInput, rubric, strings) {
|
|
44151
|
-
const validationResult = validateTable(userInput);
|
|
44152
|
-
if (validationResult != null) {
|
|
44153
|
-
return validationResult;
|
|
44154
|
-
}
|
|
44155
|
-
const supplied = filterNonEmpty(userInput);
|
|
44156
|
-
const solution = filterNonEmpty(rubric.answers);
|
|
44157
|
-
if (supplied.length !== solution.length) {
|
|
44158
|
-
return {
|
|
44159
|
-
type: "points",
|
|
44160
|
-
earned: 0,
|
|
44161
|
-
total: 1,
|
|
44162
|
-
message: null
|
|
44163
|
-
};
|
|
44164
|
-
}
|
|
44165
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
44166
|
-
let message = null;
|
|
44167
|
-
const allCorrect = solution.every(function (rowSolution) {
|
|
44168
|
-
for (let i = 0; i < supplied.length; i++) {
|
|
44169
|
-
const rowSupplied = supplied[i];
|
|
44170
|
-
const correct = rowSupplied.every(function (cellSupplied, i) {
|
|
44171
|
-
const cellSolution = rowSolution[i];
|
|
44172
|
-
const validator = createValidator(cellSolution, {
|
|
44173
|
-
simplify: true
|
|
44174
|
-
});
|
|
44175
|
-
const result = validator(cellSupplied);
|
|
44176
|
-
if (result.message) {
|
|
44177
|
-
message = result.message;
|
|
44178
|
-
}
|
|
44179
|
-
return result.correct;
|
|
44180
|
-
});
|
|
44181
|
-
if (correct) {
|
|
44182
|
-
supplied.splice(i, 1);
|
|
44183
|
-
return true;
|
|
44184
|
-
}
|
|
44185
|
-
}
|
|
44186
|
-
return false;
|
|
44187
|
-
});
|
|
44188
|
-
return {
|
|
44189
|
-
type: "points",
|
|
44190
|
-
earned: allCorrect ? 1 : 0,
|
|
44191
|
-
total: 1,
|
|
44192
|
-
message
|
|
44193
|
-
};
|
|
44194
|
-
}
|
|
44195
|
-
|
|
44196
42354
|
const {
|
|
44197
42355
|
assert
|
|
44198
42356
|
} = InteractiveUtil;
|
|
@@ -44595,7 +42753,7 @@ const init = function init() {
|
|
|
44595
42753
|
|
|
44596
42754
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44597
42755
|
const libName = "@khanacademy/perseus";
|
|
44598
|
-
const libVersion = "
|
|
42756
|
+
const libVersion = "51.0.0";
|
|
44599
42757
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44600
42758
|
|
|
44601
42759
|
const apiVersion = {
|
|
@@ -46556,7 +44714,6 @@ const getValidWidgetIds = perseusItem => {
|
|
|
46556
44714
|
return keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
|
|
46557
44715
|
};
|
|
46558
44716
|
|
|
46559
|
-
const deepEq = Util.deepEq;
|
|
46560
44717
|
function isRealJSONParse(jsonParse) {
|
|
46561
44718
|
const randomPhrase = buildRandomPhrase();
|
|
46562
44719
|
const randomHintPhrase = buildRandomPhrase();
|
|
@@ -46613,7 +44770,7 @@ function isRealJSONParse(jsonParse) {
|
|
|
46613
44770
|
const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
|
|
46614
44771
|
const parsedTestJSON = jsonParse(testJSON);
|
|
46615
44772
|
const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
|
|
46616
|
-
return
|
|
44773
|
+
return approximateDeepEqual(parsedTestItemData, testingObject);
|
|
46617
44774
|
}
|
|
46618
44775
|
function buildRandomString(capitalize = false) {
|
|
46619
44776
|
let randomString = "";
|
|
@@ -46813,7 +44970,9 @@ function object(schema) {
|
|
|
46813
44970
|
for (const [prop, propParser] of Object.entries(schema)) {
|
|
46814
44971
|
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
46815
44972
|
if (isSuccess(result)) {
|
|
46816
|
-
|
|
44973
|
+
if (result.value !== undefined || prop in rawValue) {
|
|
44974
|
+
ret[prop] = result.value;
|
|
44975
|
+
}
|
|
46817
44976
|
} else {
|
|
46818
44977
|
mismatches.push(...result.detail);
|
|
46819
44978
|
}
|
|
@@ -48422,7 +46581,6 @@ const EditorJsonify = {
|
|
|
48422
46581
|
* Main entry point
|
|
48423
46582
|
*/
|
|
48424
46583
|
const GrapherUtil = {
|
|
48425
|
-
allTypes,
|
|
48426
46584
|
DEFAULT_GRAPHER_PROPS,
|
|
48427
46585
|
chooseType,
|
|
48428
46586
|
defaultPlotProps,
|
|
@@ -48437,5 +46595,5 @@ const ScoringUtil = {
|
|
|
48437
46595
|
* Types
|
|
48438
46596
|
*/
|
|
48439
46597
|
|
|
48440
|
-
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords,
|
|
46598
|
+
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isFailure, isItemRenderableByVersion, isSuccess, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parseDataFromJSONP, parsePerseusItem, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
|
|
48441
46599
|
//# sourceMappingURL=index.js.map
|