@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/index.js
CHANGED
|
@@ -4,7 +4,9 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var KAS = require('@khanacademy/kas');
|
|
6
6
|
var mathInput = require('@khanacademy/math-input');
|
|
7
|
+
var perseusCore = require('@khanacademy/perseus-core');
|
|
7
8
|
var PerseusLinter = require('@khanacademy/perseus-linter');
|
|
9
|
+
var perseusScore = require('@khanacademy/perseus-score');
|
|
8
10
|
var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
|
|
9
11
|
var Tooltip$1 = require('@khanacademy/wonder-blocks-tooltip');
|
|
10
12
|
var wonderBlocksTypography = require('@khanacademy/wonder-blocks-typography');
|
|
@@ -13,13 +15,11 @@ var classNames$1 = require('classnames');
|
|
|
13
15
|
var React = require('react');
|
|
14
16
|
var ReactDOM = require('react-dom');
|
|
15
17
|
var _ = require('underscore');
|
|
16
|
-
var perseusScore = require('@khanacademy/perseus-score');
|
|
17
18
|
var Clickable = require('@khanacademy/wonder-blocks-clickable');
|
|
18
19
|
var wonderBlocksPopover = require('@khanacademy/wonder-blocks-popover');
|
|
19
20
|
var wonderBlocksTokens = require('@khanacademy/wonder-blocks-tokens');
|
|
20
21
|
var $ = require('jquery');
|
|
21
22
|
var uuid = require('uuid');
|
|
22
|
-
var perseusCore = require('@khanacademy/perseus-core');
|
|
23
23
|
var PropTypes = require('prop-types');
|
|
24
24
|
var wonderStuffCore = require('@khanacademy/wonder-stuff-core');
|
|
25
25
|
var wonderBlocksProgressSpinner = require('@khanacademy/wonder-blocks-progress-spinner');
|
|
@@ -282,7 +282,15 @@ const strings = {
|
|
|
282
282
|
srAngleSideAtCoordinates: "Point %(point)s, %(side)s at %(x)s comma %(y)s",
|
|
283
283
|
srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
|
|
284
284
|
srAngleGraphAriaLabel: "An angle on a coordinate plane.",
|
|
285
|
-
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"
|
|
285
|
+
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",
|
|
286
|
+
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
287
|
+
srLinearSystemPoints: "Line %(lineNumber)s has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
|
|
288
|
+
srLinearSystemPoint: "Point %(pointSequence)s on line %(lineNumber)s at %(x)s comma %(y)s.",
|
|
289
|
+
srRayGraph: "A ray on a coordinate plane.",
|
|
290
|
+
srRayPoints: "The endpoint is at %(point1X)s comma %(point1Y)s and the ray goes through point %(point2X)s comma %(point2Y)s.",
|
|
291
|
+
srRayGrabHandle: "Ray with endpoint %(point1X)s comma %(point1Y)s going through point %(point2X)s comma %(point2Y)s.",
|
|
292
|
+
srRayEndpoint: "Endpoint at %(point1X)s comma %(point1Y)s.",
|
|
293
|
+
srRayTerminalPoint: "Through point at %(point2X)s comma %(point2Y)s."
|
|
286
294
|
// The above strings are used for interactive graph SR descriptions.
|
|
287
295
|
};
|
|
288
296
|
|
|
@@ -671,18 +679,77 @@ const mockStrings = {
|
|
|
671
679
|
endingSideY
|
|
672
680
|
} = _ref40;
|
|
673
681
|
return `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}.`;
|
|
682
|
+
},
|
|
683
|
+
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
684
|
+
srLinearSystemPoints: _ref41 => {
|
|
685
|
+
let {
|
|
686
|
+
lineNumber,
|
|
687
|
+
point1X,
|
|
688
|
+
point1Y,
|
|
689
|
+
point2X,
|
|
690
|
+
point2Y
|
|
691
|
+
} = _ref41;
|
|
692
|
+
return `Line ${lineNumber} has two points, point 1 at ${point1X} comma ${point1Y} and point 2 at ${point2X} comma ${point2Y}.`;
|
|
693
|
+
},
|
|
694
|
+
srLinearSystemPoint: _ref42 => {
|
|
695
|
+
let {
|
|
696
|
+
lineNumber,
|
|
697
|
+
pointSequence,
|
|
698
|
+
x,
|
|
699
|
+
y
|
|
700
|
+
} = _ref42;
|
|
701
|
+
return `Point ${pointSequence} on line ${lineNumber} at ${x} comma ${y}.`;
|
|
702
|
+
},
|
|
703
|
+
srRayGraph: "A ray on a coordinate plane.",
|
|
704
|
+
srRayPoints: _ref43 => {
|
|
705
|
+
let {
|
|
706
|
+
point1X,
|
|
707
|
+
point1Y,
|
|
708
|
+
point2X,
|
|
709
|
+
point2Y
|
|
710
|
+
} = _ref43;
|
|
711
|
+
return `The endpoint is at ${point1X} comma ${point1Y} and the ray goes through point ${point2X} comma ${point2Y}.`;
|
|
712
|
+
},
|
|
713
|
+
srRayGrabHandle: _ref44 => {
|
|
714
|
+
let {
|
|
715
|
+
point1X,
|
|
716
|
+
point1Y,
|
|
717
|
+
point2X,
|
|
718
|
+
point2Y
|
|
719
|
+
} = _ref44;
|
|
720
|
+
return `Ray with endpoint ${point1X} comma ${point1Y} going through point ${point2X} comma ${point2Y}.`;
|
|
721
|
+
},
|
|
722
|
+
srRayEndpoint: _ref45 => {
|
|
723
|
+
let {
|
|
724
|
+
x,
|
|
725
|
+
y
|
|
726
|
+
} = _ref45;
|
|
727
|
+
return `Endpoint at ${x} comma ${y}.`;
|
|
728
|
+
},
|
|
729
|
+
srRayTerminalPoint: _ref46 => {
|
|
730
|
+
let {
|
|
731
|
+
x,
|
|
732
|
+
y
|
|
733
|
+
} = _ref46;
|
|
734
|
+
return `Through point at ${x} comma ${y}.`;
|
|
674
735
|
}
|
|
675
736
|
// The above strings are used for interactive graph SR descriptions.
|
|
676
737
|
};
|
|
677
738
|
|
|
739
|
+
// This type helps us make sure all error codes are mapped to strings
|
|
740
|
+
|
|
678
741
|
const errorToString = {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
742
|
+
MISSING_PERCENT_ERROR: strings.MISSING_PERCENT_ERROR,
|
|
743
|
+
NEEDS_TO_BE_SIMPLIFIED_ERROR: strings.NEEDS_TO_BE_SIMPLFIED_ERROR,
|
|
744
|
+
APPROXIMATED_PI_ERROR: strings.APPROXIMATED_PI_ERROR,
|
|
745
|
+
EXTRA_SYMBOLS_ERROR: strings.EXTRA_SYMBOLS_ERROR,
|
|
746
|
+
WRONG_CASE_ERROR: strings.WRONG_CASE_ERROR,
|
|
747
|
+
WRONG_LETTER_ERROR: strings.WRONG_LETTER_ERROR,
|
|
748
|
+
MULTIPLICATION_SIGN_ERROR: strings.MULTIPLICATION_SIGN_ERROR,
|
|
749
|
+
INVALID_SELECTION_ERROR: strings.invalidSelection,
|
|
750
|
+
CHOOSE_CORRECT_NUM_ERROR: strings.chooseCorrectNum,
|
|
751
|
+
NOT_NONE_ABOVE_ERROR: strings.notNoneOfTheAbove,
|
|
752
|
+
FILL_ALL_CELLS_ERROR: strings.fillAllCells
|
|
686
753
|
};
|
|
687
754
|
function mapErrorToString(err) {
|
|
688
755
|
if (!err) {
|
|
@@ -1616,204 +1683,6 @@ const getPromptJSON$t = (renderProps, userInput) => {
|
|
|
1616
1683
|
};
|
|
1617
1684
|
};
|
|
1618
1685
|
|
|
1619
|
-
/**
|
|
1620
|
-
* Get the character used for separating decimals.
|
|
1621
|
-
*/
|
|
1622
|
-
const getDecimalSeparator = locale => {
|
|
1623
|
-
switch (locale) {
|
|
1624
|
-
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
1625
|
-
// locale properly.
|
|
1626
|
-
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
1627
|
-
//
|
|
1628
|
-
// Supported locales in Chrome:
|
|
1629
|
-
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
1630
|
-
case "ka":
|
|
1631
|
-
return ",";
|
|
1632
|
-
default:
|
|
1633
|
-
const numberWithDecimalSeparator = 1.1;
|
|
1634
|
-
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
1635
|
-
// support Safari 12.
|
|
1636
|
-
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
1637
|
-
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
1638
|
-
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
1639
|
-
.match(/[^\d\u0661\u06F1]/);
|
|
1640
|
-
return match?.[0] ?? ".";
|
|
1641
|
-
}
|
|
1642
|
-
};
|
|
1643
|
-
|
|
1644
|
-
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
1645
|
-
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
1646
|
-
// which get's the injected logger.
|
|
1647
|
-
/**
|
|
1648
|
-
* Provides logging infrastructure for Perseus
|
|
1649
|
-
*/
|
|
1650
|
-
const Log = {
|
|
1651
|
-
// Logs a message
|
|
1652
|
-
log: (message, extra) => {
|
|
1653
|
-
getDependencies().Log.log(message, extra);
|
|
1654
|
-
},
|
|
1655
|
-
// Logs an error with a message
|
|
1656
|
-
error: (message, kind, extra) => {
|
|
1657
|
-
getDependencies().Log.error(message, kind, extra);
|
|
1658
|
-
}
|
|
1659
|
-
};
|
|
1660
|
-
|
|
1661
|
-
/**
|
|
1662
|
-
* Checks user input from the expression widget to see if it is scorable.
|
|
1663
|
-
*
|
|
1664
|
-
* Note: Most of the expression widget's validation requires the Rubric because
|
|
1665
|
-
* of its use of KhanAnswerTypes as a core part of scoring.
|
|
1666
|
-
*
|
|
1667
|
-
* @see `scoreExpression()` for more details.
|
|
1668
|
-
*/
|
|
1669
|
-
function validateExpression(userInput) {
|
|
1670
|
-
if (userInput === "") {
|
|
1671
|
-
return {
|
|
1672
|
-
type: "invalid",
|
|
1673
|
-
message: null
|
|
1674
|
-
};
|
|
1675
|
-
}
|
|
1676
|
-
return null;
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
/* Content creators input a list of answers which are matched from top to
|
|
1680
|
-
* bottom. The intent is that they can include spcific solutions which should
|
|
1681
|
-
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
1682
|
-
* general.
|
|
1683
|
-
*
|
|
1684
|
-
* We iterate through each answer, trying to match it with the user's input
|
|
1685
|
-
* using the following angorithm:
|
|
1686
|
-
* - Try to parse the user's input. If it doesn't parse then return "not
|
|
1687
|
-
* graded".
|
|
1688
|
-
* - For each answer:
|
|
1689
|
-
* ~ Try to validate the user's input against the answer. The answer is
|
|
1690
|
-
* expected to parse.
|
|
1691
|
-
* ~ If the user's input validates (the validator judges it "correct"), we've
|
|
1692
|
-
* matched and can stop considering answers.
|
|
1693
|
-
* - If there were no matches or the matching answer is considered "ungraded",
|
|
1694
|
-
* show the user an error. TODO(joel) - what error?
|
|
1695
|
-
* - Otherwise, pass through the resulting points and message.
|
|
1696
|
-
*/
|
|
1697
|
-
function scoreExpression(userInput, rubric, strings, locale) {
|
|
1698
|
-
const validationError = validateExpression(userInput);
|
|
1699
|
-
if (validationError) {
|
|
1700
|
-
return validationError;
|
|
1701
|
-
}
|
|
1702
|
-
const options = ___default["default"].clone(rubric);
|
|
1703
|
-
___default["default"].extend(options, {
|
|
1704
|
-
decimal_separator: getDecimalSeparator(locale)
|
|
1705
|
-
});
|
|
1706
|
-
const createValidator = answer => {
|
|
1707
|
-
// We give options to KAS.parse here because it is parsing the
|
|
1708
|
-
// solution answer, not the student answer, and we don't want a
|
|
1709
|
-
// solution to work if the student is using a different language
|
|
1710
|
-
// (different from the content creation language, ie. English).
|
|
1711
|
-
const expression = KAS__namespace.parse(answer.value, rubric);
|
|
1712
|
-
// An answer may not be parsed if the expression was defined
|
|
1713
|
-
// incorrectly. For example if the answer is using a symbol defined
|
|
1714
|
-
// in the function variables list for the expression.
|
|
1715
|
-
if (!expression.parsed) {
|
|
1716
|
-
/* c8 ignore next */
|
|
1717
|
-
Log.error("Unable to parse solution answer for expression", perseusCore.Errors.InvalidInput, {
|
|
1718
|
-
loggedMetadata: {
|
|
1719
|
-
rubric: JSON.stringify(rubric)
|
|
1720
|
-
}
|
|
1721
|
-
});
|
|
1722
|
-
return null;
|
|
1723
|
-
}
|
|
1724
|
-
return perseusScore.KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
|
|
1725
|
-
simplify: answer.simplify,
|
|
1726
|
-
form: answer.form
|
|
1727
|
-
}));
|
|
1728
|
-
};
|
|
1729
|
-
|
|
1730
|
-
// Find the first answer form that matches the user's input and that
|
|
1731
|
-
// is considered correct. Also, track whether the input is
|
|
1732
|
-
// considered "empty" for all answer forms, and keep the validation
|
|
1733
|
-
// result for the first answer form for which the user's input was
|
|
1734
|
-
// considered "ungraded".
|
|
1735
|
-
// (Terminology reminder: the answer forms are provided by the
|
|
1736
|
-
// assessment items; they are not the user's input. Each one might
|
|
1737
|
-
// represent a correct answer, an incorrect one (if the exercise
|
|
1738
|
-
// creator has predicted certain common wrong answers and wants to
|
|
1739
|
-
// provide guidance via a message), or an ungraded one (same idea,
|
|
1740
|
-
// but without giving the user an incorrect mark for the question).
|
|
1741
|
-
let matchingAnswerForm;
|
|
1742
|
-
let matchMessage;
|
|
1743
|
-
let allEmpty = true;
|
|
1744
|
-
let firstUngradedResult;
|
|
1745
|
-
for (const answerForm of rubric.answerForms || []) {
|
|
1746
|
-
const validator = createValidator(answerForm);
|
|
1747
|
-
if (!validator) {
|
|
1748
|
-
continue;
|
|
1749
|
-
}
|
|
1750
|
-
const result = validator(userInput);
|
|
1751
|
-
|
|
1752
|
-
// Short-circuit as soon as the user's input matches some answer
|
|
1753
|
-
// (independently of whether the answer is correct)
|
|
1754
|
-
if (result.correct) {
|
|
1755
|
-
matchingAnswerForm = answerForm;
|
|
1756
|
-
matchMessage = result.message || "";
|
|
1757
|
-
break;
|
|
1758
|
-
}
|
|
1759
|
-
allEmpty = allEmpty && result.empty;
|
|
1760
|
-
// If this answer form is correct and the user's input is considered
|
|
1761
|
-
// "ungraded" for it, we'll want to keep the evaluation result for
|
|
1762
|
-
// later. If the user's input doesn't match any answer forms, we'll
|
|
1763
|
-
// show the message from this validation.
|
|
1764
|
-
if (answerForm.considered === "correct" && result.ungraded && !firstUngradedResult) {
|
|
1765
|
-
firstUngradedResult = result;
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
// Now check to see if we matched any answer form at all, and if
|
|
1770
|
-
// we did, whether it's considered correct, incorrect, or ungraded
|
|
1771
|
-
if (!matchingAnswerForm) {
|
|
1772
|
-
if (firstUngradedResult) {
|
|
1773
|
-
// While we didn't directly match with any answer form, we
|
|
1774
|
-
// did at some point get an "ungraded" validation result,
|
|
1775
|
-
// which might indicate e.g. a mismatch in variable casing.
|
|
1776
|
-
// We'll return "invalid", which will let the user try again
|
|
1777
|
-
// with no penalty, and the hopefully helpful validation
|
|
1778
|
-
// message.
|
|
1779
|
-
return {
|
|
1780
|
-
type: "invalid",
|
|
1781
|
-
message: firstUngradedResult.message,
|
|
1782
|
-
suppressAlmostThere: firstUngradedResult.suppressAlmostThere
|
|
1783
|
-
};
|
|
1784
|
-
}
|
|
1785
|
-
if (allEmpty) {
|
|
1786
|
-
// If everything graded as empty, it's invalid.
|
|
1787
|
-
return {
|
|
1788
|
-
type: "invalid",
|
|
1789
|
-
message: null
|
|
1790
|
-
};
|
|
1791
|
-
}
|
|
1792
|
-
// We fell through all the possibilities and we're not empty,
|
|
1793
|
-
// so the answer is considered incorrect.
|
|
1794
|
-
return {
|
|
1795
|
-
type: "points",
|
|
1796
|
-
earned: 0,
|
|
1797
|
-
total: 1
|
|
1798
|
-
};
|
|
1799
|
-
}
|
|
1800
|
-
if (matchingAnswerForm.considered === "ungraded") {
|
|
1801
|
-
return {
|
|
1802
|
-
type: "invalid",
|
|
1803
|
-
message: matchMessage
|
|
1804
|
-
};
|
|
1805
|
-
}
|
|
1806
|
-
// We matched a graded answer form, so we can now tell the user
|
|
1807
|
-
// whether their input was correct or incorrect, and hand out
|
|
1808
|
-
// points accordingly
|
|
1809
|
-
return {
|
|
1810
|
-
type: "points",
|
|
1811
|
-
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
1812
|
-
total: 1,
|
|
1813
|
-
message: matchMessage
|
|
1814
|
-
};
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
1686
|
// Map of international operator names to their English equivalents
|
|
1818
1687
|
const englishOperators = {
|
|
1819
1688
|
arctg: "arctan",
|
|
@@ -1924,7 +1793,7 @@ class Expression extends React__namespace.Component {
|
|
|
1924
1793
|
// make it so that solution answers with ','s or '.'s work
|
|
1925
1794
|
const options = ___default["default"].pick(props || this.props, "functions");
|
|
1926
1795
|
___default["default"].extend(options, {
|
|
1927
|
-
decimal_separator: getDecimalSeparator(this.context.locale)
|
|
1796
|
+
decimal_separator: perseusCore.getDecimalSeparator(this.context.locale)
|
|
1928
1797
|
});
|
|
1929
1798
|
return KAS__namespace.parse(normalizeTex(value), options);
|
|
1930
1799
|
};
|
|
@@ -2202,7 +2071,7 @@ var Expression$1 = {
|
|
|
2202
2071
|
isLintable: true,
|
|
2203
2072
|
// TODO(LEMS-2656): remove TS suppression
|
|
2204
2073
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusExpressionUserInput'.
|
|
2205
|
-
scorer: scoreExpression,
|
|
2074
|
+
scorer: perseusScore.scoreExpression,
|
|
2206
2075
|
// TODO(LEMS-2656): remove TS suppression
|
|
2207
2076
|
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusExpressionRubric'.
|
|
2208
2077
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
@@ -2572,56 +2441,6 @@ function constrainedTickStepsFromTickSteps(tickSteps, ranges) {
|
|
|
2572
2441
|
return [constrainTickStep(tickSteps[0], ranges[0]), constrainTickStep(tickSteps[1], ranges[1])];
|
|
2573
2442
|
}
|
|
2574
2443
|
|
|
2575
|
-
/**
|
|
2576
|
-
* Approximate equality on numbers and primitives.
|
|
2577
|
-
*/
|
|
2578
|
-
function eq$4(x, y) {
|
|
2579
|
-
if (typeof x === "number" && typeof y === "number") {
|
|
2580
|
-
return Math.abs(x - y) < 1e-9;
|
|
2581
|
-
}
|
|
2582
|
-
return x === y;
|
|
2583
|
-
}
|
|
2584
|
-
|
|
2585
|
-
/**
|
|
2586
|
-
* Deep approximate equality on primitives, numbers, arrays, and objects.
|
|
2587
|
-
* Recursive.
|
|
2588
|
-
*/
|
|
2589
|
-
function deepEq$7(x, y) {
|
|
2590
|
-
if (Array.isArray(x) && Array.isArray(y)) {
|
|
2591
|
-
if (x.length !== y.length) {
|
|
2592
|
-
return false;
|
|
2593
|
-
}
|
|
2594
|
-
for (let i = 0; i < x.length; i++) {
|
|
2595
|
-
if (!deepEq$7(x[i], y[i])) {
|
|
2596
|
-
return false;
|
|
2597
|
-
}
|
|
2598
|
-
}
|
|
2599
|
-
return true;
|
|
2600
|
-
}
|
|
2601
|
-
if (Array.isArray(x) || Array.isArray(y)) {
|
|
2602
|
-
return false;
|
|
2603
|
-
}
|
|
2604
|
-
if (typeof x === "function" && typeof y === "function") {
|
|
2605
|
-
return eq$4(x, y);
|
|
2606
|
-
}
|
|
2607
|
-
if (typeof x === "function" || typeof y === "function") {
|
|
2608
|
-
return false;
|
|
2609
|
-
}
|
|
2610
|
-
if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
|
|
2611
|
-
return x === y || ___default["default"].all(x, function (v, k) {
|
|
2612
|
-
// @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
|
|
2613
|
-
return deepEq$7(y[k], v);
|
|
2614
|
-
}) && ___default["default"].all(y, function (v, k) {
|
|
2615
|
-
// @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
|
|
2616
|
-
return deepEq$7(x[k], v);
|
|
2617
|
-
});
|
|
2618
|
-
}
|
|
2619
|
-
if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
|
|
2620
|
-
return false;
|
|
2621
|
-
}
|
|
2622
|
-
return eq$4(x, y);
|
|
2623
|
-
}
|
|
2624
|
-
|
|
2625
2444
|
/**
|
|
2626
2445
|
* Query String Parser
|
|
2627
2446
|
*
|
|
@@ -2836,15 +2655,6 @@ const textarea = {
|
|
|
2836
2655
|
*/
|
|
2837
2656
|
const unescapeMathMode$1 = label => label.startsWith("$") && label.endsWith("$") ? label.slice(1, -1) : label;
|
|
2838
2657
|
const random$1 = seededRNG$1(new Date().getTime() & 0xffffffff);
|
|
2839
|
-
|
|
2840
|
-
// TODO(benchristel): in the future, we may want to make deepClone work for
|
|
2841
|
-
// Record<string, Cloneable> as well. Currently, it only does arrays.
|
|
2842
|
-
function deepClone(obj) {
|
|
2843
|
-
if (Array.isArray(obj)) {
|
|
2844
|
-
return obj.map(deepClone);
|
|
2845
|
-
}
|
|
2846
|
-
return obj;
|
|
2847
|
-
}
|
|
2848
2658
|
const Util = {
|
|
2849
2659
|
inputPathsEqual,
|
|
2850
2660
|
nestedMap: nestedMap$2,
|
|
@@ -2865,8 +2675,6 @@ const Util = {
|
|
|
2865
2675
|
gridStepFromTickStep,
|
|
2866
2676
|
tickStepFromNumTicks,
|
|
2867
2677
|
constrainedTickStepsFromTickSteps,
|
|
2868
|
-
eq: eq$4,
|
|
2869
|
-
deepEq: deepEq$7,
|
|
2870
2678
|
parseQueryString,
|
|
2871
2679
|
updateQueryString: updateQueryString$2,
|
|
2872
2680
|
strongEncodeURIComponent,
|
|
@@ -2884,8 +2692,24 @@ const Util = {
|
|
|
2884
2692
|
getDataUrl: getDataUrl,
|
|
2885
2693
|
textarea,
|
|
2886
2694
|
unescapeMathMode: unescapeMathMode$1,
|
|
2887
|
-
random: random$1
|
|
2888
|
-
|
|
2695
|
+
random: random$1
|
|
2696
|
+
};
|
|
2697
|
+
|
|
2698
|
+
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
2699
|
+
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
2700
|
+
// which get's the injected logger.
|
|
2701
|
+
/**
|
|
2702
|
+
* Provides logging infrastructure for Perseus
|
|
2703
|
+
*/
|
|
2704
|
+
const Log = {
|
|
2705
|
+
// Logs a message
|
|
2706
|
+
log: (message, extra) => {
|
|
2707
|
+
getDependencies().Log.log(message, extra);
|
|
2708
|
+
},
|
|
2709
|
+
// Logs an error with a message
|
|
2710
|
+
error: (message, kind, extra) => {
|
|
2711
|
+
getDependencies().Log.error(message, kind, extra);
|
|
2712
|
+
}
|
|
2889
2713
|
};
|
|
2890
2714
|
|
|
2891
2715
|
// For offline exercises in the mobile app, we download the graphie data
|
|
@@ -8434,7 +8258,7 @@ var Tex$1 = {
|
|
|
8434
8258
|
const {
|
|
8435
8259
|
processMath: processMath$1
|
|
8436
8260
|
} = Tex$1;
|
|
8437
|
-
function polar$
|
|
8261
|
+
function polar$2(r, th) {
|
|
8438
8262
|
if (typeof r === "number") {
|
|
8439
8263
|
r = [r, r];
|
|
8440
8264
|
}
|
|
@@ -8773,8 +8597,8 @@ class Graphie$1 {
|
|
|
8773
8597
|
endAngle = (endAngle % 360 + 360) % 360;
|
|
8774
8598
|
const cent = this.scalePoint(center);
|
|
8775
8599
|
const radii = this.scaleVector(radius);
|
|
8776
|
-
const startVector = polar$
|
|
8777
|
-
const endVector = polar$
|
|
8600
|
+
const startVector = polar$2(radius, startAngle);
|
|
8601
|
+
const endVector = polar$2(radius, endAngle);
|
|
8778
8602
|
|
|
8779
8603
|
// We round the coordinates to make testing easier, because trig
|
|
8780
8604
|
// operations return unround numbers.
|
|
@@ -10318,261 +10142,11 @@ class WrappedLine extends WrappedDrawing {
|
|
|
10318
10142
|
}
|
|
10319
10143
|
}
|
|
10320
10144
|
|
|
10321
|
-
/**
|
|
10322
|
-
* A collection of geomtry-related utility functions
|
|
10323
|
-
*/
|
|
10324
|
-
const {
|
|
10325
|
-
eq: eq$3,
|
|
10326
|
-
deepEq: deepEq$6
|
|
10327
|
-
} = Util;
|
|
10328
|
-
|
|
10329
|
-
// This should really be a readonly tuple of [number, number]
|
|
10330
|
-
|
|
10331
|
-
// a, b, c
|
|
10332
|
-
|
|
10333
|
-
// Given a number, return whether it is positive (1), negative (-1), or zero (0)
|
|
10334
|
-
function sign(val) {
|
|
10335
|
-
if (eq$3(val, 0)) {
|
|
10336
|
-
return 0;
|
|
10337
|
-
}
|
|
10338
|
-
return val > 0 ? 1 : -1;
|
|
10339
|
-
}
|
|
10340
|
-
|
|
10341
|
-
// Determine whether three points are collinear (0), for a clockwise turn (negative),
|
|
10342
|
-
// or counterclockwise turn (positive)
|
|
10343
|
-
function ccw(a, b, c) {
|
|
10344
|
-
return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
|
|
10345
|
-
}
|
|
10346
|
-
function collinear(a, b, c) {
|
|
10347
|
-
return eq$3(ccw(a, b, c), 0);
|
|
10348
|
-
}
|
|
10349
|
-
|
|
10350
|
-
// Given rect bounding points A and B, whether point C is inside the rect
|
|
10351
|
-
function pointInRect(a, b, c) {
|
|
10352
|
-
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]);
|
|
10353
|
-
}
|
|
10354
|
-
|
|
10355
|
-
// Whether line segment AB intersects line segment CD
|
|
10356
|
-
// http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
|
|
10357
|
-
function intersects(ab, cd) {
|
|
10358
|
-
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]]];
|
|
10359
|
-
const orientations = ___default["default"].map(triplets, function (triplet) {
|
|
10360
|
-
return sign(ccw(...triplet));
|
|
10361
|
-
});
|
|
10362
|
-
if (orientations[0] !== orientations[1] && orientations[2] !== orientations[3]) {
|
|
10363
|
-
return true;
|
|
10364
|
-
}
|
|
10365
|
-
for (let i = 0; i < 4; i++) {
|
|
10366
|
-
if (orientations[i] === 0 && pointInRect(...triplets[i])) {
|
|
10367
|
-
return true;
|
|
10368
|
-
}
|
|
10369
|
-
}
|
|
10370
|
-
return false;
|
|
10371
|
-
}
|
|
10372
|
-
|
|
10373
|
-
// Whether any two sides of a polygon intersect each other
|
|
10374
|
-
function polygonSidesIntersect(vertices) {
|
|
10375
|
-
for (let i = 0; i < vertices.length; i++) {
|
|
10376
|
-
for (let k = i + 1; k < vertices.length; k++) {
|
|
10377
|
-
// If any two vertices are the same point, sides overlap
|
|
10378
|
-
if (kmath.point.equal(vertices[i], vertices[k])) {
|
|
10379
|
-
return true;
|
|
10380
|
-
}
|
|
10381
|
-
|
|
10382
|
-
// Find the other end of the sides starting at vertices i and k
|
|
10383
|
-
const iNext = (i + 1) % vertices.length;
|
|
10384
|
-
const kNext = (k + 1) % vertices.length;
|
|
10385
|
-
|
|
10386
|
-
// Adjacent sides always intersect (at the vertex); skip those
|
|
10387
|
-
if (iNext === k || kNext === i) {
|
|
10388
|
-
continue;
|
|
10389
|
-
}
|
|
10390
|
-
const side1 = [vertices[i], vertices[iNext]];
|
|
10391
|
-
const side2 = [vertices[k], vertices[kNext]];
|
|
10392
|
-
if (intersects(side1, side2)) {
|
|
10393
|
-
return true;
|
|
10394
|
-
}
|
|
10395
|
-
}
|
|
10396
|
-
}
|
|
10397
|
-
return false;
|
|
10398
|
-
}
|
|
10399
|
-
function vector(a, b) {
|
|
10400
|
-
return ___default["default"].map(___default["default"].zip(a, b), function (pair) {
|
|
10401
|
-
return pair[0] - pair[1];
|
|
10402
|
-
});
|
|
10403
|
-
}
|
|
10404
|
-
function reverseVector(vector) {
|
|
10405
|
-
return [-vector[0], -vector[1]];
|
|
10406
|
-
}
|
|
10407
|
-
|
|
10408
|
-
// Returns whether connecting the given sequence of `points` forms a clockwise
|
|
10409
|
-
// path (assuming a closed loop, where the last point connects back to the
|
|
10410
|
-
// first).
|
|
10411
|
-
function clockwise$1(points) {
|
|
10412
|
-
const segments = ___default["default"].zip(points, points.slice(1).concat(points.slice(0, 1)));
|
|
10413
|
-
const areas = ___default["default"].map(segments, function (segment) {
|
|
10414
|
-
const p1 = segment[0];
|
|
10415
|
-
const p2 = segment[1];
|
|
10416
|
-
return (p2[0] - p1[0]) * (p2[1] + p1[1]);
|
|
10417
|
-
});
|
|
10418
|
-
return kmath.sum(areas) > 0;
|
|
10419
|
-
}
|
|
10420
|
-
function magnitude(v) {
|
|
10421
|
-
return Math.sqrt(___default["default"].reduce(v, function (memo, el) {
|
|
10422
|
-
// @ts-expect-error - TS2345 - Argument of type 'Coord' is not assignable to parameter of type 'number'.
|
|
10423
|
-
return memo + Math.pow(el, 2);
|
|
10424
|
-
}, 0));
|
|
10425
|
-
}
|
|
10426
|
-
function dotProduct(a, b) {
|
|
10427
|
-
return ___default["default"].reduce(___default["default"].zip(a, b), function (memo, pair) {
|
|
10428
|
-
return memo + pair[0] * pair[1];
|
|
10429
|
-
}, 0);
|
|
10430
|
-
}
|
|
10431
|
-
function sideLengths(coords) {
|
|
10432
|
-
const segments = ___default["default"].zip(coords, rotate(coords));
|
|
10433
|
-
return segments.map(function (segment) {
|
|
10434
|
-
// @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.
|
|
10435
|
-
return magnitude(vector(...segment));
|
|
10436
|
-
});
|
|
10437
|
-
}
|
|
10438
|
-
|
|
10439
|
-
// Based on http://math.stackexchange.com/a/151149
|
|
10440
|
-
function angleMeasures(coords) {
|
|
10441
|
-
const triplets = ___default["default"].zip(rotate(coords, -1), coords, rotate(coords, 1));
|
|
10442
|
-
const offsets = ___default["default"].map(triplets, function (triplet) {
|
|
10443
|
-
const p = vector(triplet[1], triplet[0]);
|
|
10444
|
-
const q = vector(triplet[2], triplet[1]);
|
|
10445
|
-
// @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[]'.
|
|
10446
|
-
const raw = Math.acos(dotProduct(p, q) / (magnitude(p) * magnitude(q)));
|
|
10447
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
10448
|
-
return sign(ccw(...triplet)) > 0 ? raw : -raw;
|
|
10449
|
-
});
|
|
10450
|
-
const sum = ___default["default"].reduce(offsets, function (memo, arg) {
|
|
10451
|
-
return memo + arg;
|
|
10452
|
-
}, 0);
|
|
10453
|
-
return ___default["default"].map(offsets, function (offset) {
|
|
10454
|
-
return sum > 0 ? Math.PI - offset : Math.PI + offset;
|
|
10455
|
-
});
|
|
10456
|
-
}
|
|
10457
|
-
|
|
10458
|
-
// Whether two polygons are similar (or if specified, congruent)
|
|
10459
|
-
function similar(coords1, coords2, tolerance) {
|
|
10460
|
-
if (coords1.length !== coords2.length) {
|
|
10461
|
-
return false;
|
|
10462
|
-
}
|
|
10463
|
-
const n = coords1.length;
|
|
10464
|
-
const angles1 = angleMeasures(coords1);
|
|
10465
|
-
const angles2 = angleMeasures(coords2);
|
|
10466
|
-
const sides1 = sideLengths(coords1);
|
|
10467
|
-
const sides2 = sideLengths(coords2);
|
|
10468
|
-
for (let i = 0; i < 2 * n; i++) {
|
|
10469
|
-
let angles = angles2.slice();
|
|
10470
|
-
let sides = sides2.slice();
|
|
10471
|
-
|
|
10472
|
-
// Reverse angles and sides to allow matching reflected polygons
|
|
10473
|
-
if (i >= n) {
|
|
10474
|
-
angles.reverse();
|
|
10475
|
-
sides.reverse();
|
|
10476
|
-
// Since sides are calculated from two coordinates,
|
|
10477
|
-
// simply reversing results in an off by one error
|
|
10478
|
-
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
10479
|
-
sides = rotate(sides, 1);
|
|
10480
|
-
}
|
|
10481
|
-
|
|
10482
|
-
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
10483
|
-
angles = rotate(angles, i);
|
|
10484
|
-
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
10485
|
-
sides = rotate(sides, i);
|
|
10486
|
-
if (deepEq$6(angles1, angles)) {
|
|
10487
|
-
const sidePairs = ___default["default"].zip(sides1, sides);
|
|
10488
|
-
const factors = ___default["default"].map(sidePairs, function (pair) {
|
|
10489
|
-
return pair[0] / pair[1];
|
|
10490
|
-
});
|
|
10491
|
-
const same = ___default["default"].all(factors, function (factor) {
|
|
10492
|
-
return eq$3(factors[0], factor);
|
|
10493
|
-
});
|
|
10494
|
-
const congruentEnough = ___default["default"].all(sidePairs, function (pair) {
|
|
10495
|
-
return kmath.number.equal(pair[0], pair[1], tolerance);
|
|
10496
|
-
});
|
|
10497
|
-
if (same && congruentEnough) {
|
|
10498
|
-
return true;
|
|
10499
|
-
}
|
|
10500
|
-
}
|
|
10501
|
-
}
|
|
10502
|
-
return false;
|
|
10503
|
-
}
|
|
10504
|
-
|
|
10505
|
-
// Given triangle with sides ABC return angle opposite side C in degrees
|
|
10506
|
-
function lawOfCosines(a, b, c) {
|
|
10507
|
-
return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180 / Math.PI;
|
|
10508
|
-
}
|
|
10509
|
-
function canonicalSineCoefficients$1(_ref) {
|
|
10510
|
-
let [amplitude, angularFrequency, phase, verticalOffset] = _ref;
|
|
10511
|
-
// For a curve of the form f(x) = a * Sin(b * x - c) + d,
|
|
10512
|
-
// this function ensures that a, b > 0, and c is its
|
|
10513
|
-
// smallest possible positive value.
|
|
10514
|
-
|
|
10515
|
-
// Guarantee a > 0
|
|
10516
|
-
if (amplitude < 0) {
|
|
10517
|
-
amplitude *= -1;
|
|
10518
|
-
angularFrequency *= -1;
|
|
10519
|
-
phase *= -1;
|
|
10520
|
-
}
|
|
10521
|
-
const period = 2 * Math.PI;
|
|
10522
|
-
// Guarantee b > 0
|
|
10523
|
-
if (angularFrequency < 0) {
|
|
10524
|
-
angularFrequency *= -1;
|
|
10525
|
-
phase *= -1;
|
|
10526
|
-
phase += period / 2;
|
|
10527
|
-
}
|
|
10528
|
-
|
|
10529
|
-
// Guarantee c is smallest possible positive value
|
|
10530
|
-
while (phase > 0) {
|
|
10531
|
-
phase -= period;
|
|
10532
|
-
}
|
|
10533
|
-
while (phase < 0) {
|
|
10534
|
-
phase += period;
|
|
10535
|
-
}
|
|
10536
|
-
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
10537
|
-
}
|
|
10538
|
-
|
|
10539
|
-
// e.g. rotate([1, 2, 3]) -> [2, 3, 1]
|
|
10540
|
-
function rotate(array, n) {
|
|
10541
|
-
n = typeof n === "undefined" ? 1 : n % array.length;
|
|
10542
|
-
return array.slice(n).concat(array.slice(0, n));
|
|
10543
|
-
}
|
|
10544
|
-
function getLineEquation(first, second) {
|
|
10545
|
-
if (eq$3(first[0], second[0])) {
|
|
10546
|
-
return "x = " + first[0].toFixed(3);
|
|
10547
|
-
}
|
|
10548
|
-
const m = (second[1] - first[1]) / (second[0] - first[0]);
|
|
10549
|
-
const b = first[1] - m * first[0];
|
|
10550
|
-
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
10551
|
-
}
|
|
10552
|
-
|
|
10553
|
-
// Stolen from the wikipedia article
|
|
10554
|
-
// http://en.wikipedia.org/wiki/Line-line_intersection
|
|
10555
|
-
function getLineIntersection(
|
|
10556
|
-
// TODO(LP-10725): update these to be 2-tuples
|
|
10557
|
-
firstPoints, secondPoints) {
|
|
10558
|
-
const x1 = firstPoints[0][0];
|
|
10559
|
-
const y1 = firstPoints[0][1];
|
|
10560
|
-
const x2 = firstPoints[1][0];
|
|
10561
|
-
const y2 = firstPoints[1][1];
|
|
10562
|
-
const x3 = secondPoints[0][0];
|
|
10563
|
-
const y3 = secondPoints[0][1];
|
|
10564
|
-
const x4 = secondPoints[1][0];
|
|
10565
|
-
const y4 = secondPoints[1][1];
|
|
10566
|
-
const determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
|
10567
|
-
if (Math.abs(determinant) < 1e-9) {
|
|
10568
|
-
return "Lines are parallel";
|
|
10569
|
-
}
|
|
10570
|
-
const x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / determinant;
|
|
10571
|
-
const y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / determinant;
|
|
10572
|
-
return "Intersection: (" + x.toFixed(3) + ", " + y.toFixed(3) + ")";
|
|
10573
|
-
}
|
|
10574
|
-
|
|
10575
10145
|
/* eslint-disable @babel/no-invalid-this */
|
|
10146
|
+
const {
|
|
10147
|
+
clockwise: clockwise$3,
|
|
10148
|
+
reverseVector: reverseVector$1
|
|
10149
|
+
} = kmath.geometry;
|
|
10576
10150
|
function scaledDistanceFromAngle(angle) {
|
|
10577
10151
|
const a = 3.51470560176242 * 20;
|
|
10578
10152
|
const b = 0.5687298702748785 * 20;
|
|
@@ -10716,7 +10290,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
10716
10290
|
const sOffset = sSpacing * (i - (n - 1) / 2);
|
|
10717
10291
|
const sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
|
|
10718
10292
|
const sHeightVector = scaledPolarRad(sHeight, perpendicularAngle);
|
|
10719
|
-
const sPath = [kmath.vector.add(sMidpoint, sOffsetVector, sHeightVector), kmath.vector.add(sMidpoint, sOffsetVector, reverseVector(sHeightVector))];
|
|
10293
|
+
const sPath = [kmath.vector.add(sMidpoint, sOffsetVector, sHeightVector), kmath.vector.add(sMidpoint, sOffsetVector, reverseVector$1(sHeightVector))];
|
|
10720
10294
|
|
|
10721
10295
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
10722
10296
|
temp.push(graphie.scaledPath(sPath, style));
|
|
@@ -10741,7 +10315,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
10741
10315
|
const sOffset = sCumulativeOffset + sSpacing * i;
|
|
10742
10316
|
let sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
|
|
10743
10317
|
if (start !== p1) {
|
|
10744
|
-
sOffsetVector = reverseVector(sOffsetVector);
|
|
10318
|
+
sOffsetVector = reverseVector$1(sOffsetVector);
|
|
10745
10319
|
}
|
|
10746
10320
|
const sEnd = kmath.vector.add(sMidpoint, sOffsetVector);
|
|
10747
10321
|
|
|
@@ -11808,7 +11382,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
|
|
|
11808
11382
|
// Temporary objects
|
|
11809
11383
|
___default["default"].invoke(polygon.temp, "remove");
|
|
11810
11384
|
polygon.temp = [];
|
|
11811
|
-
const isClockwise = clockwise$
|
|
11385
|
+
const isClockwise = clockwise$3(polygon.coords);
|
|
11812
11386
|
|
|
11813
11387
|
// Update angle labels
|
|
11814
11388
|
if (polygon.angleLabels.length || polygon.showRightAngleMarkers.length) {
|
|
@@ -13229,7 +12803,7 @@ ___default["default"].extend(MovableAngle.prototype, {
|
|
|
13229
12803
|
points[1].onMove = function (x, y) {
|
|
13230
12804
|
const oldVertex = points[1].coord;
|
|
13231
12805
|
const newVertex = [x, y];
|
|
13232
|
-
const delta = kmath.vector.add(newVertex, reverseVector(oldVertex));
|
|
12806
|
+
const delta = kmath.vector.add(newVertex, reverseVector$1(oldVertex));
|
|
13233
12807
|
let valid = true;
|
|
13234
12808
|
const newPoints = {};
|
|
13235
12809
|
___default["default"].each([0, 2], function (i) {
|
|
@@ -13271,7 +12845,7 @@ ___default["default"].extend(MovableAngle.prototype, {
|
|
|
13271
12845
|
let angle = GraphUtils.findAngleDeprecated(newPoint, vertex);
|
|
13272
12846
|
angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
|
|
13273
12847
|
const distance = GraphUtils.getDistance(newPoint, vertex);
|
|
13274
|
-
return kmath.vector.add(vertex, polar$
|
|
12848
|
+
return kmath.vector.add(vertex, polar$2(distance, angle));
|
|
13275
12849
|
}
|
|
13276
12850
|
return true;
|
|
13277
12851
|
};
|
|
@@ -13422,7 +12996,6 @@ ___default["default"].extend(MovableAngle.prototype, {
|
|
|
13422
12996
|
|
|
13423
12997
|
/* eslint-disable @babel/no-invalid-this */
|
|
13424
12998
|
const nestedMap$1 = Util.nestedMap;
|
|
13425
|
-
const deepEq$5 = Util.deepEq;
|
|
13426
12999
|
|
|
13427
13000
|
/**
|
|
13428
13001
|
* A base class for all Graphie Movables
|
|
@@ -13519,7 +13092,7 @@ const createSimpleClass = function (addFunction) {
|
|
|
13519
13092
|
this._prevProps = this.props;
|
|
13520
13093
|
},
|
|
13521
13094
|
modify: function (graphie) {
|
|
13522
|
-
if (!
|
|
13095
|
+
if (!perseusCore.approximateDeepEqual(this.props, this._prevProps)) {
|
|
13523
13096
|
this.remove();
|
|
13524
13097
|
this.add(graphie);
|
|
13525
13098
|
this._prevProps = this.props;
|
|
@@ -15242,7 +14815,7 @@ function sum(array) {
|
|
|
15242
14815
|
return memo + arg;
|
|
15243
14816
|
}, 0);
|
|
15244
14817
|
}
|
|
15245
|
-
function clockwise(points) {
|
|
14818
|
+
function clockwise$2(points) {
|
|
15246
14819
|
const segments = ___default["default"].zip(points, points.slice(1).concat(points.slice(0, 1)));
|
|
15247
14820
|
const areas = ___default["default"].map(segments, function (segment) {
|
|
15248
14821
|
const p1 = segment[0];
|
|
@@ -15308,7 +14881,7 @@ const draw$1 = {
|
|
|
15308
14881
|
const graphie = this.graphie;
|
|
15309
14882
|
const self = this;
|
|
15310
14883
|
const coords = ___default["default"].invoke(state.points, "coord");
|
|
15311
|
-
const isClockwise = clockwise(coords);
|
|
14884
|
+
const isClockwise = clockwise$2(coords);
|
|
15312
14885
|
const n = coords.length;
|
|
15313
14886
|
|
|
15314
14887
|
// graphie.labelAngle and similar methods attempt to re-use the label
|
|
@@ -16018,7 +15591,7 @@ const Parabola = GraphieClasses.createClass({
|
|
|
16018
15591
|
this.parabola.toFront();
|
|
16019
15592
|
}
|
|
16020
15593
|
});
|
|
16021
|
-
const Sinusoid
|
|
15594
|
+
const Sinusoid = GraphieClasses.createClass({
|
|
16022
15595
|
displayName: "Sinusoid",
|
|
16023
15596
|
movableProps: ["children"],
|
|
16024
15597
|
add: function (graphie) {
|
|
@@ -16040,7 +15613,7 @@ const Sinusoid$1 = GraphieClasses.createClass({
|
|
|
16040
15613
|
this.sinusoid.toFront();
|
|
16041
15614
|
}
|
|
16042
15615
|
});
|
|
16043
|
-
const Plot$
|
|
15616
|
+
const Plot$1 = GraphieClasses.createSimpleClass((graphie, props) => {
|
|
16044
15617
|
return graphie.plot(props.fn, props.range, props.style);
|
|
16045
15618
|
});
|
|
16046
15619
|
const PlotParametric$1 = GraphieClasses.createSimpleClass((graphie, props) => {
|
|
@@ -16098,17 +15671,16 @@ var Movables = {
|
|
|
16098
15671
|
MovablePoint: MovablePoint$4,
|
|
16099
15672
|
Parabola: Parabola,
|
|
16100
15673
|
Path: Path,
|
|
16101
|
-
Plot: Plot$
|
|
15674
|
+
Plot: Plot$1,
|
|
16102
15675
|
PlotParametric: PlotParametric$1,
|
|
16103
15676
|
Point: Point$1,
|
|
16104
|
-
Sinusoid: Sinusoid
|
|
15677
|
+
Sinusoid: Sinusoid,
|
|
16105
15678
|
Rect: Rect$1
|
|
16106
15679
|
};
|
|
16107
15680
|
|
|
16108
15681
|
const GraphieMovable = GraphieClasses.GraphieMovable;
|
|
16109
15682
|
const createGraphie = GraphUtils.createGraphie;
|
|
16110
15683
|
const {
|
|
16111
|
-
deepEq: deepEq$4,
|
|
16112
15684
|
nestedMap
|
|
16113
15685
|
} = Util;
|
|
16114
15686
|
const {
|
|
@@ -16146,7 +15718,7 @@ class Graphie extends React__namespace.Component {
|
|
|
16146
15718
|
if (this.props.setup !== prevProps.setup) {
|
|
16147
15719
|
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.", perseusCore.Errors.Internal);
|
|
16148
15720
|
}
|
|
16149
|
-
if (!
|
|
15721
|
+
if (!perseusCore.approximateDeepEqual(this.props.options, prevProps.options) || !perseusCore.approximateDeepEqual(this.props.box, prevProps.box) || !perseusCore.approximateDeepEqual(this.props.range, prevProps.range)) {
|
|
16150
15722
|
this._setupGraphie();
|
|
16151
15723
|
}
|
|
16152
15724
|
this._updateMovables();
|
|
@@ -21157,230 +20729,6 @@ const getPromptJSON$s = (renderProps, userInput) => {
|
|
|
21157
20729
|
};
|
|
21158
20730
|
};
|
|
21159
20731
|
|
|
21160
|
-
/*
|
|
21161
|
-
* In this file, an `expression` is some portion of valid TeX enclosed in
|
|
21162
|
-
* curly brackets.
|
|
21163
|
-
*/
|
|
21164
|
-
|
|
21165
|
-
/*
|
|
21166
|
-
* Find the index at which an expression ends, i.e., has an unmatched
|
|
21167
|
-
* closing curly bracket. This method assumes that we start with a non-open
|
|
21168
|
-
* bracket character and end when we've seen more left than right brackets
|
|
21169
|
-
* (rather than assuming that we start with a bracket character and wait for
|
|
21170
|
-
* bracket equality).
|
|
21171
|
-
*/
|
|
21172
|
-
function findEndpoint(tex, currentIndex) {
|
|
21173
|
-
let bracketDepth = 0;
|
|
21174
|
-
for (let i = currentIndex, len = tex.length; i < len; i++) {
|
|
21175
|
-
const c = tex[i];
|
|
21176
|
-
if (c === "{") {
|
|
21177
|
-
bracketDepth++;
|
|
21178
|
-
} else if (c === "}") {
|
|
21179
|
-
bracketDepth--;
|
|
21180
|
-
}
|
|
21181
|
-
if (bracketDepth < 0) {
|
|
21182
|
-
return i;
|
|
21183
|
-
}
|
|
21184
|
-
}
|
|
21185
|
-
// If we never see unbalanced curly brackets, default to the
|
|
21186
|
-
// entire string
|
|
21187
|
-
return tex.length;
|
|
21188
|
-
}
|
|
21189
|
-
|
|
21190
|
-
/*
|
|
21191
|
-
* Parses an individual set of curly brackets into TeX.
|
|
21192
|
-
*/
|
|
21193
|
-
function parseNextExpression(tex, currentIndex, handler) {
|
|
21194
|
-
// Find the first '{' and grab subsequent TeX
|
|
21195
|
-
// Ex) tex: '{3}{7}', and we want the '3'
|
|
21196
|
-
const openBracketIndex = tex.indexOf("{", currentIndex);
|
|
21197
|
-
const nextExpIndex = openBracketIndex + 1;
|
|
21198
|
-
|
|
21199
|
-
// Truncate to only contain remaining TeX
|
|
21200
|
-
const endpoint = findEndpoint(tex, nextExpIndex);
|
|
21201
|
-
const expressionTeX = tex.substring(nextExpIndex, endpoint);
|
|
21202
|
-
const parsedExp = walkTex(expressionTeX, handler);
|
|
21203
|
-
return {
|
|
21204
|
-
endpoint: endpoint,
|
|
21205
|
-
expression: parsedExp
|
|
21206
|
-
};
|
|
21207
|
-
}
|
|
21208
|
-
function getNextFracIndex(tex, currentIndex) {
|
|
21209
|
-
const dfrac = "\\dfrac";
|
|
21210
|
-
const frac = "\\frac";
|
|
21211
|
-
const nextFrac = tex.indexOf(frac, currentIndex);
|
|
21212
|
-
const nextDFrac = tex.indexOf(dfrac, currentIndex);
|
|
21213
|
-
if (nextFrac > -1 && nextDFrac > -1) {
|
|
21214
|
-
return Math.min(nextFrac, nextDFrac);
|
|
21215
|
-
}
|
|
21216
|
-
if (nextFrac > -1) {
|
|
21217
|
-
return nextFrac;
|
|
21218
|
-
}
|
|
21219
|
-
if (nextDFrac > -1) {
|
|
21220
|
-
return nextDFrac;
|
|
21221
|
-
}
|
|
21222
|
-
return -1;
|
|
21223
|
-
}
|
|
21224
|
-
function walkTex(tex, handler) {
|
|
21225
|
-
if (!tex) {
|
|
21226
|
-
return "";
|
|
21227
|
-
}
|
|
21228
|
-
|
|
21229
|
-
// Ex) tex: '2 \dfrac {3}{7}'
|
|
21230
|
-
let parsedString = "";
|
|
21231
|
-
let currentIndex = 0;
|
|
21232
|
-
let nextFrac = getNextFracIndex(tex, currentIndex);
|
|
21233
|
-
|
|
21234
|
-
// For each \dfrac, find the two expressions (wrapped in {}) and recur
|
|
21235
|
-
while (nextFrac > -1) {
|
|
21236
|
-
// Gather first fragment, preceding \dfrac
|
|
21237
|
-
// Ex) parsedString: '2 '
|
|
21238
|
-
parsedString += tex.substring(currentIndex, nextFrac);
|
|
21239
|
-
|
|
21240
|
-
// Remove everything preceding \dfrac, which has been parsed
|
|
21241
|
-
currentIndex = nextFrac;
|
|
21242
|
-
|
|
21243
|
-
// Parse first expression and move index past it
|
|
21244
|
-
// Ex) firstParsedExpression.expression: '3'
|
|
21245
|
-
const firstParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
21246
|
-
currentIndex = firstParsedExpression.endpoint + 1;
|
|
21247
|
-
|
|
21248
|
-
// Parse second expression
|
|
21249
|
-
// Ex) secondParsedExpression.expression: '7'
|
|
21250
|
-
const secondParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
21251
|
-
currentIndex = secondParsedExpression.endpoint + 1;
|
|
21252
|
-
|
|
21253
|
-
// Add expressions to running total of parsed expressions
|
|
21254
|
-
if (parsedString.length) {
|
|
21255
|
-
parsedString += " ";
|
|
21256
|
-
}
|
|
21257
|
-
|
|
21258
|
-
// Apply a custom handler based on the parsed subexpressions
|
|
21259
|
-
parsedString += handler(firstParsedExpression.expression, secondParsedExpression.expression);
|
|
21260
|
-
|
|
21261
|
-
// Find next DFrac, relative to currentIndex
|
|
21262
|
-
nextFrac = getNextFracIndex(tex, currentIndex);
|
|
21263
|
-
}
|
|
21264
|
-
|
|
21265
|
-
// Add remaining TeX, which is \dfrac-free
|
|
21266
|
-
parsedString += tex.slice(currentIndex);
|
|
21267
|
-
return parsedString;
|
|
21268
|
-
}
|
|
21269
|
-
|
|
21270
|
-
/*
|
|
21271
|
-
* Modify a TeX expression, returning another TeX expression. The resulting
|
|
21272
|
-
* expression will have its innermost fractions stubbed out with \fracs
|
|
21273
|
-
* (as opposed to \dfracs). All other content will remain untouched.
|
|
21274
|
-
*/
|
|
21275
|
-
function modifyTex(tex) {
|
|
21276
|
-
function isNestedFraction(tex) {
|
|
21277
|
-
return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
|
|
21278
|
-
}
|
|
21279
|
-
const handler = function (exp1, exp2) {
|
|
21280
|
-
let prefix;
|
|
21281
|
-
if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
|
|
21282
|
-
prefix = "\\dfrac";
|
|
21283
|
-
} else {
|
|
21284
|
-
prefix = "\\frac";
|
|
21285
|
-
}
|
|
21286
|
-
return prefix + " {" + exp1 + "}{" + exp2 + "}";
|
|
21287
|
-
};
|
|
21288
|
-
return walkTex(tex, handler);
|
|
21289
|
-
}
|
|
21290
|
-
|
|
21291
|
-
/*
|
|
21292
|
-
* Parse a TeX expression into something interpretable by input-number.
|
|
21293
|
-
* The process is concerned with: (1) parsing fractions, i.e., \dfracs; and
|
|
21294
|
-
* (2) removing backslash-escaping from certain characters (right now, only
|
|
21295
|
-
* percent signs).
|
|
21296
|
-
*
|
|
21297
|
-
* The basic algorithm for handling \dfracs splits on \dfracs and then recurs
|
|
21298
|
-
* on the subsequent "expressions", i.e., the {} pairs that follow \dfrac. The
|
|
21299
|
-
* recursion is to allow for nested \dfrac elements.
|
|
21300
|
-
*
|
|
21301
|
-
* Backslash-escapes are removed with a simple search-and-replace.
|
|
21302
|
-
*/
|
|
21303
|
-
function parseTex(tex) {
|
|
21304
|
-
const handler = function (exp1, exp2) {
|
|
21305
|
-
return exp1 + "/" + exp2;
|
|
21306
|
-
};
|
|
21307
|
-
const texWithoutFracs = walkTex(tex, handler);
|
|
21308
|
-
return texWithoutFracs.replace("\\%", "%");
|
|
21309
|
-
}
|
|
21310
|
-
var TexWrangler = {
|
|
21311
|
-
parseTex,
|
|
21312
|
-
modifyTex
|
|
21313
|
-
};
|
|
21314
|
-
|
|
21315
|
-
const ParseTex$1 = TexWrangler.parseTex;
|
|
21316
|
-
const answerTypes = {
|
|
21317
|
-
number: {
|
|
21318
|
-
name: "Numbers",
|
|
21319
|
-
forms: "integer, decimal, proper, improper, mixed"
|
|
21320
|
-
},
|
|
21321
|
-
decimal: {
|
|
21322
|
-
name: "Decimals",
|
|
21323
|
-
forms: "decimal"
|
|
21324
|
-
},
|
|
21325
|
-
integer: {
|
|
21326
|
-
name: "Integers",
|
|
21327
|
-
forms: "integer"
|
|
21328
|
-
},
|
|
21329
|
-
rational: {
|
|
21330
|
-
name: "Fractions and mixed numbers",
|
|
21331
|
-
forms: "integer, proper, improper, mixed"
|
|
21332
|
-
},
|
|
21333
|
-
improper: {
|
|
21334
|
-
name: "Improper numbers (no mixed)",
|
|
21335
|
-
forms: "integer, proper, improper"
|
|
21336
|
-
},
|
|
21337
|
-
mixed: {
|
|
21338
|
-
name: "Mixed numbers (no improper)",
|
|
21339
|
-
forms: "integer, proper, mixed"
|
|
21340
|
-
},
|
|
21341
|
-
percent: {
|
|
21342
|
-
name: "Numbers or percents",
|
|
21343
|
-
forms: "integer, decimal, proper, improper, mixed, percent"
|
|
21344
|
-
},
|
|
21345
|
-
pi: {
|
|
21346
|
-
name: "Numbers with pi",
|
|
21347
|
-
forms: "pi"
|
|
21348
|
-
}
|
|
21349
|
-
};
|
|
21350
|
-
function scoreInputNumber(userInput, rubric, strings) {
|
|
21351
|
-
if (rubric.answerType == null) {
|
|
21352
|
-
rubric.answerType = "number";
|
|
21353
|
-
}
|
|
21354
|
-
|
|
21355
|
-
// note(matthewc): this will get immediately parsed again by
|
|
21356
|
-
// `KhanAnswerTypes.number.convertToPredicate`, but a string is
|
|
21357
|
-
// expected here
|
|
21358
|
-
const stringValue = `${rubric.value}`;
|
|
21359
|
-
const val = perseusScore.KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
|
|
21360
|
-
simplify: rubric.simplify,
|
|
21361
|
-
inexact: rubric.inexact || undefined,
|
|
21362
|
-
maxError: rubric.maxError,
|
|
21363
|
-
forms: answerTypes[rubric.answerType].forms
|
|
21364
|
-
});
|
|
21365
|
-
|
|
21366
|
-
// We may have received TeX; try to parse it before grading.
|
|
21367
|
-
// If `currentValue` is not TeX, this should be a no-op.
|
|
21368
|
-
const currentValue = ParseTex$1(userInput.currentValue);
|
|
21369
|
-
const result = val(currentValue);
|
|
21370
|
-
if (result.empty) {
|
|
21371
|
-
return {
|
|
21372
|
-
type: "invalid",
|
|
21373
|
-
message: result.message
|
|
21374
|
-
};
|
|
21375
|
-
}
|
|
21376
|
-
return {
|
|
21377
|
-
type: "points",
|
|
21378
|
-
earned: result.correct ? 1 : 0,
|
|
21379
|
-
total: 1,
|
|
21380
|
-
message: result.message
|
|
21381
|
-
};
|
|
21382
|
-
}
|
|
21383
|
-
|
|
21384
20732
|
const formExamples$1 = {
|
|
21385
20733
|
integer: function (options, strings) {
|
|
21386
20734
|
return strings.integerExample;
|
|
@@ -21492,7 +20840,7 @@ class InputNumber extends React__namespace.Component {
|
|
|
21492
20840
|
strings
|
|
21493
20841
|
} = this.context;
|
|
21494
20842
|
const type = this.props.answerType;
|
|
21495
|
-
const forms =
|
|
20843
|
+
const forms = perseusScore.inputNumberAnswerTypes[type].forms.split(/\s*,\s*/);
|
|
21496
20844
|
const examples = ___default["default"].map(forms, form => formExamples$1[form](this.props, strings));
|
|
21497
20845
|
return [strings.yourAnswer].concat(examples);
|
|
21498
20846
|
}
|
|
@@ -21588,7 +20936,7 @@ var InputNumber$1 = {
|
|
|
21588
20936
|
isLintable: true,
|
|
21589
20937
|
// TODO(LEMS-2656): remove TS suppression
|
|
21590
20938
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInputNumberUserInput'.
|
|
21591
|
-
scorer: scoreInputNumber,
|
|
20939
|
+
scorer: perseusScore.scoreInputNumber,
|
|
21592
20940
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
21593
20941
|
if (rubric.value == null) {
|
|
21594
20942
|
return;
|
|
@@ -21611,139 +20959,6 @@ const getPromptJSON$r = (renderProps, userInput) => {
|
|
|
21611
20959
|
};
|
|
21612
20960
|
};
|
|
21613
20961
|
|
|
21614
|
-
const ParseTex = TexWrangler.parseTex;
|
|
21615
|
-
const answerFormButtons = [{
|
|
21616
|
-
title: "Integers",
|
|
21617
|
-
value: "integer",
|
|
21618
|
-
content: "6"
|
|
21619
|
-
}, {
|
|
21620
|
-
title: "Decimals",
|
|
21621
|
-
value: "decimal",
|
|
21622
|
-
content: "0.75"
|
|
21623
|
-
}, {
|
|
21624
|
-
title: "Proper fractions",
|
|
21625
|
-
value: "proper",
|
|
21626
|
-
content: "\u2157"
|
|
21627
|
-
}, {
|
|
21628
|
-
title: "Improper fractions",
|
|
21629
|
-
value: "improper",
|
|
21630
|
-
content: "\u2077\u2044\u2084"
|
|
21631
|
-
}, {
|
|
21632
|
-
title: "Mixed numbers",
|
|
21633
|
-
value: "mixed",
|
|
21634
|
-
content: "1\u00BE"
|
|
21635
|
-
}, {
|
|
21636
|
-
title: "Numbers with \u03C0",
|
|
21637
|
-
value: "pi",
|
|
21638
|
-
content: "\u03C0"
|
|
21639
|
-
}];
|
|
21640
|
-
|
|
21641
|
-
// This function checks if the user inputted a percent value, parsing
|
|
21642
|
-
// it as a number (and maybe scaling) so that it can be graded.
|
|
21643
|
-
// NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
|
|
21644
|
-
// can accept several input forms with or without "%", the decision
|
|
21645
|
-
// to parse based on the presence of "%" in the input, is so that we
|
|
21646
|
-
// don't accidently scale the user typed value before grading, CP-930.
|
|
21647
|
-
function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
21648
|
-
// If the input value is not a string ending with "%", then there's
|
|
21649
|
-
// nothing more to do. The value will be graded as inputted by user.
|
|
21650
|
-
if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
|
|
21651
|
-
return inputValue;
|
|
21652
|
-
}
|
|
21653
|
-
const value = parseFloat(inputValue.slice(0, -1));
|
|
21654
|
-
// If the input value stripped of the "%" cannot be parsed as a
|
|
21655
|
-
// number (the slice is not really necessary for parseFloat to work
|
|
21656
|
-
// if the string starts with a number) then return the original
|
|
21657
|
-
// input for grading.
|
|
21658
|
-
if (isNaN(value)) {
|
|
21659
|
-
return inputValue;
|
|
21660
|
-
}
|
|
21661
|
-
|
|
21662
|
-
// Next, if all correct answers are in the range of |0,1| then we
|
|
21663
|
-
// scale the user typed value. We assume this is the correct thing
|
|
21664
|
-
// to do since the input value ends with "%".
|
|
21665
|
-
if (normalizedAnswerExpected) {
|
|
21666
|
-
return value / 100;
|
|
21667
|
-
}
|
|
21668
|
-
|
|
21669
|
-
// Otherwise, we return input value (number) stripped of the "%".
|
|
21670
|
-
return value;
|
|
21671
|
-
}
|
|
21672
|
-
function scoreNumericInput(userInput, rubric, strings) {
|
|
21673
|
-
const defaultAnswerForms = answerFormButtons.map(e => e["value"])
|
|
21674
|
-
// Don't default to validating the answer as a pi answer
|
|
21675
|
-
// if answerForm isn't set on the answer
|
|
21676
|
-
// https://khanacademy.atlassian.net/browse/LC-691
|
|
21677
|
-
.filter(e => e !== "pi");
|
|
21678
|
-
const createValidator = answer => {
|
|
21679
|
-
const stringAnswer = `${answer.value}`;
|
|
21680
|
-
|
|
21681
|
-
// Always validate against the provided answer forms (pi, decimal, etc.)
|
|
21682
|
-
const validatorForms = [...(answer.answerForms ?? [])];
|
|
21683
|
-
|
|
21684
|
-
// When an answer is set to strict, we validate using ONLY
|
|
21685
|
-
// the provided answerForms. If strict is false, or if there
|
|
21686
|
-
// were no provided answer forms, we will include all
|
|
21687
|
-
// of the default answer forms in our validator.
|
|
21688
|
-
if (!answer.strict || validatorForms.length === 0) {
|
|
21689
|
-
validatorForms.push(...defaultAnswerForms);
|
|
21690
|
-
}
|
|
21691
|
-
return perseusScore.KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
21692
|
-
message: answer.message,
|
|
21693
|
-
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
21694
|
-
inexact: true,
|
|
21695
|
-
// TODO(merlob) backfill / delete
|
|
21696
|
-
maxError: answer.maxError,
|
|
21697
|
-
forms: validatorForms
|
|
21698
|
-
});
|
|
21699
|
-
};
|
|
21700
|
-
|
|
21701
|
-
// We may have received TeX; try to parse it before grading.
|
|
21702
|
-
// If `currentValue` is not TeX, this should be a no-op.
|
|
21703
|
-
const currentValue = ParseTex(userInput.currentValue);
|
|
21704
|
-
const normalizedAnswerExpected = rubric.answers.filter(answer => answer.status === "correct").every(answer => answer.value != null && Math.abs(answer.value) <= 1);
|
|
21705
|
-
|
|
21706
|
-
// The coefficient is an attribute of the widget
|
|
21707
|
-
let localValue = currentValue;
|
|
21708
|
-
if (rubric.coefficient) {
|
|
21709
|
-
if (!localValue) {
|
|
21710
|
-
localValue = 1;
|
|
21711
|
-
} else if (localValue === "-") {
|
|
21712
|
-
localValue = -1;
|
|
21713
|
-
}
|
|
21714
|
-
}
|
|
21715
|
-
const matchedAnswer = rubric.answers.map(answer => {
|
|
21716
|
-
const validateFn = createValidator(answer);
|
|
21717
|
-
const score = validateFn(maybeParsePercentInput(localValue, normalizedAnswerExpected));
|
|
21718
|
-
return {
|
|
21719
|
-
...answer,
|
|
21720
|
-
score
|
|
21721
|
-
};
|
|
21722
|
-
}).find(answer => {
|
|
21723
|
-
// NOTE: "answer.score.correct" indicates a match via the validate function.
|
|
21724
|
-
// It does NOT indicate that the answer itself is correct.
|
|
21725
|
-
return answer.score.correct || answer.status === "correct" && answer.score.empty;
|
|
21726
|
-
});
|
|
21727
|
-
const result = matchedAnswer?.status === "correct" ? matchedAnswer.score : {
|
|
21728
|
-
empty: matchedAnswer?.status === "ungraded",
|
|
21729
|
-
correct: matchedAnswer?.status === "correct",
|
|
21730
|
-
message: matchedAnswer?.message ?? null,
|
|
21731
|
-
guess: localValue
|
|
21732
|
-
};
|
|
21733
|
-
if (result.empty) {
|
|
21734
|
-
return {
|
|
21735
|
-
type: "invalid",
|
|
21736
|
-
message: result.message
|
|
21737
|
-
};
|
|
21738
|
-
}
|
|
21739
|
-
return {
|
|
21740
|
-
type: "points",
|
|
21741
|
-
earned: result.correct ? 1 : 0,
|
|
21742
|
-
total: 1,
|
|
21743
|
-
message: result.message
|
|
21744
|
-
};
|
|
21745
|
-
}
|
|
21746
|
-
|
|
21747
20962
|
const formExamples = {
|
|
21748
20963
|
integer: (form, strings) => strings.integerExample,
|
|
21749
20964
|
proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
|
|
@@ -21980,7 +21195,7 @@ var NumericInput$1 = {
|
|
|
21980
21195
|
isLintable: true,
|
|
21981
21196
|
// TODO(LEMS-2656): remove TS suppression
|
|
21982
21197
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumericInputUserInput'.
|
|
21983
|
-
scorer: scoreNumericInput,
|
|
21198
|
+
scorer: perseusScore.scoreNumericInput,
|
|
21984
21199
|
// TODO(LEMS-2656): remove TS suppression
|
|
21985
21200
|
// @ts-expect-error: Type 'Rubric' is not assignable to type 'PerseusNumericInputRubric'
|
|
21986
21201
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
@@ -25743,72 +24958,6 @@ const styles$o = aphrodite.StyleSheet.create({
|
|
|
25743
24958
|
}
|
|
25744
24959
|
});
|
|
25745
24960
|
|
|
25746
|
-
/**
|
|
25747
|
-
* Checks if the user has selected at least one option. Additional validation
|
|
25748
|
-
* is done in scoreRadio to check if the number of selected options is correct
|
|
25749
|
-
* and if the user has selected both a correct option and the "none of the above"
|
|
25750
|
-
* option.
|
|
25751
|
-
* @param userInput
|
|
25752
|
-
* @see `scoreRadio` for the additional validation logic and the scoring logic.
|
|
25753
|
-
*/
|
|
25754
|
-
function validateRadio(userInput) {
|
|
25755
|
-
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
25756
|
-
return sum + (selected ? 1 : 0);
|
|
25757
|
-
}, 0);
|
|
25758
|
-
if (numSelected === 0) {
|
|
25759
|
-
return {
|
|
25760
|
-
type: "invalid",
|
|
25761
|
-
message: null
|
|
25762
|
-
};
|
|
25763
|
-
}
|
|
25764
|
-
return null;
|
|
25765
|
-
}
|
|
25766
|
-
|
|
25767
|
-
function scoreRadio(userInput, rubric, strings) {
|
|
25768
|
-
const validationError = validateRadio(userInput);
|
|
25769
|
-
if (validationError) {
|
|
25770
|
-
return validationError;
|
|
25771
|
-
}
|
|
25772
|
-
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
25773
|
-
return sum + (selected ? 1 : 0);
|
|
25774
|
-
}, 0);
|
|
25775
|
-
const numCorrect = rubric.choices.reduce((sum, currentChoice) => {
|
|
25776
|
-
return currentChoice.correct ? sum + 1 : sum;
|
|
25777
|
-
}, 0);
|
|
25778
|
-
if (numCorrect > 1 && numSelected !== numCorrect) {
|
|
25779
|
-
return {
|
|
25780
|
-
type: "invalid",
|
|
25781
|
-
message: strings.chooseCorrectNum
|
|
25782
|
-
};
|
|
25783
|
-
// If NOTA and some other answer are checked, ...
|
|
25784
|
-
}
|
|
25785
|
-
|
|
25786
|
-
const noneOfTheAboveSelected = rubric.choices.some((choice, index) => choice.isNoneOfTheAbove && userInput.choicesSelected[index]);
|
|
25787
|
-
if (noneOfTheAboveSelected && numSelected > 1) {
|
|
25788
|
-
return {
|
|
25789
|
-
type: "invalid",
|
|
25790
|
-
message: strings.notNoneOfTheAbove
|
|
25791
|
-
};
|
|
25792
|
-
}
|
|
25793
|
-
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
25794
|
-
let isCorrect;
|
|
25795
|
-
if (rubric.choices[i].isNoneOfTheAbove) {
|
|
25796
|
-
isCorrect = rubric.choices.every((choice, j) => {
|
|
25797
|
-
return i === j || !choice.correct;
|
|
25798
|
-
});
|
|
25799
|
-
} else {
|
|
25800
|
-
isCorrect = !!rubric.choices[i].correct;
|
|
25801
|
-
}
|
|
25802
|
-
return isCorrect === selected;
|
|
25803
|
-
});
|
|
25804
|
-
return {
|
|
25805
|
-
type: "points",
|
|
25806
|
-
earned: correct ? 1 : 0,
|
|
25807
|
-
total: 1,
|
|
25808
|
-
message: null
|
|
25809
|
-
};
|
|
25810
|
-
}
|
|
25811
|
-
|
|
25812
24961
|
// RenderProps is the return type for radio.jsx#transform
|
|
25813
24962
|
|
|
25814
24963
|
class Radio$1 extends React__namespace.Component {
|
|
@@ -25996,7 +25145,7 @@ class Radio$1 extends React__namespace.Component {
|
|
|
25996
25145
|
choiceStates
|
|
25997
25146
|
} = this.props;
|
|
25998
25147
|
if (choiceStates) {
|
|
25999
|
-
const score = scoreRadio(this.getUserInput(), rubric
|
|
25148
|
+
const score = perseusScore.scoreRadio(this.getUserInput(), rubric);
|
|
26000
25149
|
const widgetCorrect = score.type === "points" && score.total === score.earned;
|
|
26001
25150
|
const newStates = choiceStates.map(state => ({
|
|
26002
25151
|
...state,
|
|
@@ -26279,7 +25428,7 @@ var Radio = {
|
|
|
26279
25428
|
isLintable: true,
|
|
26280
25429
|
// TODO(LEMS-2656): remove TS suppression
|
|
26281
25430
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusRadioUserInput
|
|
26282
|
-
scorer: scoreRadio
|
|
25431
|
+
scorer: perseusScore.scoreRadio
|
|
26283
25432
|
};
|
|
26284
25433
|
|
|
26285
25434
|
/* eslint-disable import/no-named-as-default */
|
|
@@ -26316,44 +25465,6 @@ function getCategorizerPublicWidgetOptions(options) {
|
|
|
26316
25465
|
};
|
|
26317
25466
|
}
|
|
26318
25467
|
|
|
26319
|
-
/**
|
|
26320
|
-
* Checks userInput from the categorizer widget to see if the user has selected
|
|
26321
|
-
* a category for each item.
|
|
26322
|
-
* @param userInput - The user's input corresponding to an array of indices that
|
|
26323
|
-
* represent the selected category for each row/item.
|
|
26324
|
-
* @param validationData - An array of strings corresponding to each row/item
|
|
26325
|
-
* @param strings - Used to provide a validation message
|
|
26326
|
-
*/
|
|
26327
|
-
function validateCategorizer(userInput, validationData, strings) {
|
|
26328
|
-
const incomplete = validationData.items.some((_, i) => userInput.values[i] == null);
|
|
26329
|
-
if (incomplete) {
|
|
26330
|
-
return {
|
|
26331
|
-
type: "invalid",
|
|
26332
|
-
message: strings.invalidSelection
|
|
26333
|
-
};
|
|
26334
|
-
}
|
|
26335
|
-
return null;
|
|
26336
|
-
}
|
|
26337
|
-
|
|
26338
|
-
function scoreCategorizer(userInput, scoringData, strings) {
|
|
26339
|
-
const validationError = validateCategorizer(userInput, scoringData, strings);
|
|
26340
|
-
if (validationError) {
|
|
26341
|
-
return validationError;
|
|
26342
|
-
}
|
|
26343
|
-
let allCorrect = true;
|
|
26344
|
-
scoringData.values.forEach((value, i) => {
|
|
26345
|
-
if (userInput.values[i] !== value) {
|
|
26346
|
-
allCorrect = false;
|
|
26347
|
-
}
|
|
26348
|
-
});
|
|
26349
|
-
return {
|
|
26350
|
-
type: "points",
|
|
26351
|
-
earned: allCorrect ? 1 : 0,
|
|
26352
|
-
total: 1,
|
|
26353
|
-
message: null
|
|
26354
|
-
};
|
|
26355
|
-
}
|
|
26356
|
-
|
|
26357
25468
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
26358
25469
|
class Categorizer extends React__namespace.Component {
|
|
26359
25470
|
static contextType = PerseusI18nContext;
|
|
@@ -26535,7 +25646,7 @@ var Categorizer$1 = {
|
|
|
26535
25646
|
isLintable: true,
|
|
26536
25647
|
// TODO(LEMS-2656): remove TS suppression
|
|
26537
25648
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
26538
|
-
scorer: scoreCategorizer,
|
|
25649
|
+
scorer: perseusScore.scoreCategorizer,
|
|
26539
25650
|
getPublicWidgetOptions: getCategorizerPublicWidgetOptions
|
|
26540
25651
|
};
|
|
26541
25652
|
|
|
@@ -26599,31 +25710,6 @@ const getPromptJSON$m = () => {
|
|
|
26599
25710
|
return getUnsupportedPromptJSON("cs-program");
|
|
26600
25711
|
};
|
|
26601
25712
|
|
|
26602
|
-
function scoreCSProgram(state) {
|
|
26603
|
-
// The iframe can tell us whether it's correct or incorrect,
|
|
26604
|
-
// and pass an optional message
|
|
26605
|
-
if (state.status === "correct") {
|
|
26606
|
-
return {
|
|
26607
|
-
type: "points",
|
|
26608
|
-
earned: 1,
|
|
26609
|
-
total: 1,
|
|
26610
|
-
message: state.message || null
|
|
26611
|
-
};
|
|
26612
|
-
}
|
|
26613
|
-
if (state.status === "incorrect") {
|
|
26614
|
-
return {
|
|
26615
|
-
type: "points",
|
|
26616
|
-
earned: 0,
|
|
26617
|
-
total: 1,
|
|
26618
|
-
message: state.message || null
|
|
26619
|
-
};
|
|
26620
|
-
}
|
|
26621
|
-
return {
|
|
26622
|
-
type: "invalid",
|
|
26623
|
-
message: "Keep going, you're not there yet!"
|
|
26624
|
-
};
|
|
26625
|
-
}
|
|
26626
|
-
|
|
26627
25713
|
/**
|
|
26628
25714
|
* This widget is for embedding Khan Academy CS programs.
|
|
26629
25715
|
*/
|
|
@@ -26775,7 +25861,7 @@ var CSProgram$1 = {
|
|
|
26775
25861
|
hidden: true,
|
|
26776
25862
|
// TODO(LEMS-2656): remove TS suppression
|
|
26777
25863
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
26778
|
-
scorer: scoreCSProgram
|
|
25864
|
+
scorer: perseusScore.scoreCSProgram
|
|
26779
25865
|
};
|
|
26780
25866
|
|
|
26781
25867
|
const getPromptJSON$l = renderProps => {
|
|
@@ -26900,34 +25986,6 @@ const getPromptJSON$k = (renderProps, userInput) => {
|
|
|
26900
25986
|
};
|
|
26901
25987
|
};
|
|
26902
25988
|
|
|
26903
|
-
/**
|
|
26904
|
-
* Checks if the user has selected an item from the dropdown before scoring.
|
|
26905
|
-
* This is shown with a userInput value / index other than 0.
|
|
26906
|
-
*/
|
|
26907
|
-
function validateDropdown(userInput) {
|
|
26908
|
-
if (userInput.value === 0) {
|
|
26909
|
-
return {
|
|
26910
|
-
type: "invalid",
|
|
26911
|
-
message: null
|
|
26912
|
-
};
|
|
26913
|
-
}
|
|
26914
|
-
return null;
|
|
26915
|
-
}
|
|
26916
|
-
|
|
26917
|
-
function scoreDropdown(userInput, rubric) {
|
|
26918
|
-
const validationError = validateDropdown(userInput);
|
|
26919
|
-
if (validationError) {
|
|
26920
|
-
return validationError;
|
|
26921
|
-
}
|
|
26922
|
-
const correct = rubric.choices[userInput.value - 1].correct;
|
|
26923
|
-
return {
|
|
26924
|
-
type: "points",
|
|
26925
|
-
earned: correct ? 1 : 0,
|
|
26926
|
-
total: 1,
|
|
26927
|
-
message: null
|
|
26928
|
-
};
|
|
26929
|
-
}
|
|
26930
|
-
|
|
26931
25989
|
class Dropdown extends React__namespace.Component {
|
|
26932
25990
|
static contextType = PerseusI18nContext;
|
|
26933
25991
|
static defaultProps = {
|
|
@@ -27028,7 +26086,7 @@ var Dropdown$1 = {
|
|
|
27028
26086
|
transform: optionsTransform,
|
|
27029
26087
|
// TODO(LEMS-2656): remove TS suppression
|
|
27030
26088
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusDropdownUserInput'.
|
|
27031
|
-
scorer: scoreDropdown
|
|
26089
|
+
scorer: perseusScore.scoreDropdown
|
|
27032
26090
|
};
|
|
27033
26091
|
|
|
27034
26092
|
const getPromptJSON$j = renderProps => {
|
|
@@ -27999,408 +27057,21 @@ const getPromptJSON$g = (renderProps, userInput) => {
|
|
|
27999
27057
|
};
|
|
28000
27058
|
};
|
|
28001
27059
|
|
|
28002
|
-
|
|
28003
|
-
|
|
27060
|
+
const movableTypeToComponent = {
|
|
27061
|
+
// @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
|
|
27062
|
+
PLOT: Graphie.Plot,
|
|
27063
|
+
// @ts-expect-error - TS2339 - Property 'Parabola' does not exist on type 'typeof Graphie'.
|
|
27064
|
+
PARABOLA: Graphie.Parabola,
|
|
27065
|
+
// @ts-expect-error - TS2339 - Property 'Sinusoid' does not exist on type 'typeof Graphie'.
|
|
27066
|
+
SINUSOID: Graphie.Sinusoid
|
|
27067
|
+
};
|
|
28004
27068
|
const DEFAULT_BACKGROUND_IMAGE = {
|
|
28005
27069
|
url: null
|
|
28006
27070
|
};
|
|
28007
|
-
|
|
28008
|
-
// TODO(charlie): These really need to go into a utility file as they're being
|
|
28009
|
-
// used by both interactive-graph and now grapher.
|
|
28010
|
-
function canonicalSineCoefficients(coeffs) {
|
|
28011
|
-
// For a curve of the form f(x) = a * Sin(b * x - c) + d,
|
|
28012
|
-
// this function ensures that a, b > 0, and c is its
|
|
28013
|
-
// smallest possible positive value.
|
|
28014
|
-
let amplitude = coeffs[0];
|
|
28015
|
-
let angularFrequency = coeffs[1];
|
|
28016
|
-
let phase = coeffs[2];
|
|
28017
|
-
const verticalOffset = coeffs[3];
|
|
28018
|
-
|
|
28019
|
-
// Guarantee a > 0
|
|
28020
|
-
if (amplitude < 0) {
|
|
28021
|
-
amplitude *= -1;
|
|
28022
|
-
angularFrequency *= -1;
|
|
28023
|
-
phase *= -1;
|
|
28024
|
-
}
|
|
28025
|
-
const period = 2 * Math.PI;
|
|
28026
|
-
// Guarantee b > 0
|
|
28027
|
-
if (angularFrequency < 0) {
|
|
28028
|
-
angularFrequency *= -1;
|
|
28029
|
-
phase *= -1;
|
|
28030
|
-
phase += period / 2;
|
|
28031
|
-
}
|
|
28032
|
-
|
|
28033
|
-
// Guarantee c is smallest possible positive value
|
|
28034
|
-
while (phase > 0) {
|
|
28035
|
-
phase -= period;
|
|
28036
|
-
}
|
|
28037
|
-
while (phase < 0) {
|
|
28038
|
-
phase += period;
|
|
28039
|
-
}
|
|
28040
|
-
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
28041
|
-
}
|
|
28042
|
-
function canonicalTangentCoefficients(coeffs) {
|
|
28043
|
-
// For a curve of the form f(x) = a * Tan(b * x - c) + d,
|
|
28044
|
-
// this function ensures that a, b > 0, and c is its
|
|
28045
|
-
// smallest possible positive value.
|
|
28046
|
-
let amplitude = coeffs[0];
|
|
28047
|
-
let angularFrequency = coeffs[1];
|
|
28048
|
-
let phase = coeffs[2];
|
|
28049
|
-
const verticalOffset = coeffs[3];
|
|
28050
|
-
|
|
28051
|
-
// Guarantee a > 0
|
|
28052
|
-
if (amplitude < 0) {
|
|
28053
|
-
amplitude *= -1;
|
|
28054
|
-
angularFrequency *= -1;
|
|
28055
|
-
phase *= -1;
|
|
28056
|
-
}
|
|
28057
|
-
const period = Math.PI;
|
|
28058
|
-
// Guarantee b > 0
|
|
28059
|
-
if (angularFrequency < 0) {
|
|
28060
|
-
angularFrequency *= -1;
|
|
28061
|
-
phase *= -1;
|
|
28062
|
-
phase += period / 2;
|
|
28063
|
-
}
|
|
28064
|
-
|
|
28065
|
-
// Guarantee c is smallest possible positive value
|
|
28066
|
-
while (phase > 0) {
|
|
28067
|
-
phase -= period;
|
|
28068
|
-
}
|
|
28069
|
-
while (phase < 0) {
|
|
28070
|
-
phase += period;
|
|
28071
|
-
}
|
|
28072
|
-
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
28073
|
-
}
|
|
28074
|
-
const PlotDefaults = {
|
|
28075
|
-
areEqual: function (coeffs1, coeffs2) {
|
|
28076
|
-
return Util.deepEq(coeffs1, coeffs2);
|
|
28077
|
-
},
|
|
28078
|
-
Movable: Plot$1,
|
|
28079
|
-
getPropsForCoeffs: function (coeffs) {
|
|
28080
|
-
return {
|
|
28081
|
-
// @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; }'.
|
|
28082
|
-
fn: ___default["default"].partial(this.getFunctionForCoeffs, coeffs)
|
|
28083
|
-
};
|
|
28084
|
-
}
|
|
28085
|
-
};
|
|
28086
|
-
const Linear = ___default["default"].extend({}, PlotDefaults, {
|
|
28087
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/67aaf581e6d9ef9038c10558a1f70ac21c11c9f8.png",
|
|
28088
|
-
defaultCoords: [[0.25, 0.75], [0.75, 0.75]],
|
|
28089
|
-
getCoefficients: function (coords) {
|
|
28090
|
-
const p1 = coords[0];
|
|
28091
|
-
const p2 = coords[1];
|
|
28092
|
-
const denom = p2[0] - p1[0];
|
|
28093
|
-
const num = p2[1] - p1[1];
|
|
28094
|
-
if (denom === 0) {
|
|
28095
|
-
return;
|
|
28096
|
-
}
|
|
28097
|
-
const m = num / denom;
|
|
28098
|
-
const b = p2[1] - m * p2[0];
|
|
28099
|
-
return [m, b];
|
|
28100
|
-
},
|
|
28101
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
28102
|
-
const m = coeffs[0];
|
|
28103
|
-
const b = coeffs[1];
|
|
28104
|
-
return m * x + b;
|
|
28105
|
-
},
|
|
28106
|
-
getEquationString: function (coords) {
|
|
28107
|
-
const coeffs = this.getCoefficients(coords);
|
|
28108
|
-
const m = coeffs[0];
|
|
28109
|
-
const b = coeffs[1];
|
|
28110
|
-
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
28111
|
-
}
|
|
28112
|
-
});
|
|
28113
|
-
const Quadratic = ___default["default"].extend({}, PlotDefaults, {
|
|
28114
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/e23d36e6fc29ee37174e92c9daba2a66677128ab.png",
|
|
28115
|
-
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
28116
|
-
// @ts-expect-error - TS2339 - Property 'Parabola' does not exist on type 'typeof Graphie'.
|
|
28117
|
-
Movable: Graphie.Parabola,
|
|
28118
|
-
getCoefficients: function (coords) {
|
|
28119
|
-
const p1 = coords[0];
|
|
28120
|
-
const p2 = coords[1];
|
|
28121
|
-
|
|
28122
|
-
// Parabola with vertex (h, k) has form: y = a * (h - k)^2 + k
|
|
28123
|
-
const h = p1[0];
|
|
28124
|
-
const k = p1[1];
|
|
28125
|
-
|
|
28126
|
-
// Use these to calculate familiar a, b, c
|
|
28127
|
-
const a = (p2[1] - k) / ((p2[0] - h) * (p2[0] - h));
|
|
28128
|
-
const b = -2 * h * a;
|
|
28129
|
-
const c = a * h * h + k;
|
|
28130
|
-
return [a, b, c];
|
|
28131
|
-
},
|
|
28132
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
28133
|
-
const a = coeffs[0];
|
|
28134
|
-
const b = coeffs[1];
|
|
28135
|
-
const c = coeffs[2];
|
|
28136
|
-
return (a * x + b) * x + c;
|
|
28137
|
-
},
|
|
28138
|
-
getPropsForCoeffs: function (coeffs) {
|
|
28139
|
-
return {
|
|
28140
|
-
a: coeffs[0],
|
|
28141
|
-
b: coeffs[1],
|
|
28142
|
-
c: coeffs[2]
|
|
28143
|
-
};
|
|
28144
|
-
},
|
|
28145
|
-
getEquationString: function (coords) {
|
|
28146
|
-
const coeffs = this.getCoefficients(coords);
|
|
28147
|
-
const a = coeffs[0];
|
|
28148
|
-
const b = coeffs[1];
|
|
28149
|
-
const c = coeffs[2];
|
|
28150
|
-
return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
|
|
28151
|
-
}
|
|
28152
|
-
});
|
|
28153
|
-
const Sinusoid = ___default["default"].extend({}, PlotDefaults, {
|
|
28154
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/3d68e7718498475f53b206c2ab285626baf8857e.png",
|
|
28155
|
-
defaultCoords: [[0.5, 0.5], [0.6, 0.6]],
|
|
28156
|
-
// @ts-expect-error - TS2339 - Property 'Sinusoid' does not exist on type 'typeof Graphie'.
|
|
28157
|
-
Movable: Graphie.Sinusoid,
|
|
28158
|
-
getCoefficients: function (coords) {
|
|
28159
|
-
const p1 = coords[0];
|
|
28160
|
-
const p2 = coords[1];
|
|
28161
|
-
const a = p2[1] - p1[1];
|
|
28162
|
-
const b = Math.PI / (2 * (p2[0] - p1[0]));
|
|
28163
|
-
const c = p1[0] * b;
|
|
28164
|
-
const d = p1[1];
|
|
28165
|
-
return [a, b, c, d];
|
|
28166
|
-
},
|
|
28167
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
28168
|
-
const a = coeffs[0];
|
|
28169
|
-
const b = coeffs[1];
|
|
28170
|
-
const c = coeffs[2];
|
|
28171
|
-
const d = coeffs[3];
|
|
28172
|
-
return a * Math.sin(b * x - c) + d;
|
|
28173
|
-
},
|
|
28174
|
-
getPropsForCoeffs: function (coeffs) {
|
|
28175
|
-
return {
|
|
28176
|
-
a: coeffs[0],
|
|
28177
|
-
b: coeffs[1],
|
|
28178
|
-
c: coeffs[2],
|
|
28179
|
-
d: coeffs[3]
|
|
28180
|
-
};
|
|
28181
|
-
},
|
|
28182
|
-
getEquationString: function (coords) {
|
|
28183
|
-
const coeffs = this.getCoefficients(coords);
|
|
28184
|
-
const a = coeffs[0];
|
|
28185
|
-
const b = coeffs[1];
|
|
28186
|
-
const c = coeffs[2];
|
|
28187
|
-
const d = coeffs[3];
|
|
28188
|
-
return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
|
|
28189
|
-
},
|
|
28190
|
-
areEqual: function (coeffs1, coeffs2) {
|
|
28191
|
-
return Util.deepEq(canonicalSineCoefficients(coeffs1), canonicalSineCoefficients(coeffs2));
|
|
28192
|
-
}
|
|
28193
|
-
});
|
|
28194
|
-
const Tangent = ___default["default"].extend({}, PlotDefaults, {
|
|
28195
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/7db80d23c35214f98659fe1cf0765811c1bbfbba.png",
|
|
28196
|
-
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
28197
|
-
getCoefficients: function (coords) {
|
|
28198
|
-
const p1 = coords[0];
|
|
28199
|
-
const p2 = coords[1];
|
|
28200
|
-
const a = p2[1] - p1[1];
|
|
28201
|
-
const b = Math.PI / (4 * (p2[0] - p1[0]));
|
|
28202
|
-
const c = p1[0] * b;
|
|
28203
|
-
const d = p1[1];
|
|
28204
|
-
return [a, b, c, d];
|
|
28205
|
-
},
|
|
28206
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
28207
|
-
const a = coeffs[0];
|
|
28208
|
-
const b = coeffs[1];
|
|
28209
|
-
const c = coeffs[2];
|
|
28210
|
-
const d = coeffs[3];
|
|
28211
|
-
return a * Math.tan(b * x - c) + d;
|
|
28212
|
-
},
|
|
28213
|
-
getEquationString: function (coords) {
|
|
28214
|
-
const coeffs = this.getCoefficients(coords);
|
|
28215
|
-
const a = coeffs[0];
|
|
28216
|
-
const b = coeffs[1];
|
|
28217
|
-
const c = coeffs[2];
|
|
28218
|
-
const d = coeffs[3];
|
|
28219
|
-
return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
|
|
28220
|
-
},
|
|
28221
|
-
areEqual: function (coeffs1, coeffs2) {
|
|
28222
|
-
return Util.deepEq(canonicalTangentCoefficients(coeffs1), canonicalTangentCoefficients(coeffs2));
|
|
28223
|
-
}
|
|
28224
|
-
});
|
|
28225
|
-
const Exponential = ___default["default"].extend({}, PlotDefaults, {
|
|
28226
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/9cbfad55525e3ce755a31a631b074670a5dad611.png",
|
|
28227
|
-
defaultCoords: [[0.5, 0.55], [0.75, 0.75]],
|
|
28228
|
-
defaultAsymptote: [[0, 0.5], [1.0, 0.5]],
|
|
28229
|
-
/**
|
|
28230
|
-
* Add extra constraints for movement of the points or asymptote (below):
|
|
28231
|
-
* newCoord: [x, y]
|
|
28232
|
-
* The end position of the point or asymptote endpoint
|
|
28233
|
-
* oldCoord: [x, y]
|
|
28234
|
-
* The old position of the point or asymptote endpoint
|
|
28235
|
-
* coords:
|
|
28236
|
-
* An array of coordinates representing the proposed end configuration
|
|
28237
|
-
* of the plot coordinates.
|
|
28238
|
-
* asymptote:
|
|
28239
|
-
* An array of coordinates representing the proposed end configuration
|
|
28240
|
-
* of the asymptote.
|
|
28241
|
-
*
|
|
28242
|
-
* Return: either a coordinate (to be used as the resulting coordinate of
|
|
28243
|
-
* the move) or a boolean, where `true` uses newCoord as the resulting
|
|
28244
|
-
* coordinate, and `false` uses oldCoord as the resulting coordinate.
|
|
28245
|
-
*/
|
|
28246
|
-
extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
28247
|
-
const y = asymptote[0][1];
|
|
28248
|
-
return ___default["default"].all(coords, coord => coord[1] !== y);
|
|
28249
|
-
},
|
|
28250
|
-
extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
28251
|
-
const y = newCoord[1];
|
|
28252
|
-
const isValid = ___default["default"].all(coords, coord => coord[1] > y) || ___default["default"].all(coords, coord => coord[1] < y);
|
|
28253
|
-
if (isValid) {
|
|
28254
|
-
return [oldCoord[0], y];
|
|
28255
|
-
}
|
|
28256
|
-
// Snap the asymptote as close as possible, i.e., if the user moves
|
|
28257
|
-
// the mouse really quickly into an invalid region
|
|
28258
|
-
const oldY = oldCoord[1];
|
|
28259
|
-
const wasBelow = ___default["default"].all(coords, coord => coord[1] > oldY);
|
|
28260
|
-
if (wasBelow) {
|
|
28261
|
-
const bottomMost = ___default["default"].min(___default["default"].map(coords, coord => coord[1]));
|
|
28262
|
-
return [oldCoord[0], bottomMost - graph.snapStep[1]];
|
|
28263
|
-
}
|
|
28264
|
-
const topMost = ___default["default"].max(___default["default"].map(coords, coord => coord[1]));
|
|
28265
|
-
return [oldCoord[0], topMost + graph.snapStep[1]];
|
|
28266
|
-
},
|
|
28267
|
-
allowReflectOverAsymptote: true,
|
|
28268
|
-
getCoefficients: function (coords, asymptote) {
|
|
28269
|
-
const p1 = coords[0];
|
|
28270
|
-
const p2 = coords[1];
|
|
28271
|
-
const c = asymptote[0][1];
|
|
28272
|
-
const b = Math.log((p1[1] - c) / (p2[1] - c)) / (p1[0] - p2[0]);
|
|
28273
|
-
const a = (p1[1] - c) / Math.exp(b * p1[0]);
|
|
28274
|
-
return [a, b, c];
|
|
28275
|
-
},
|
|
28276
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
28277
|
-
const a = coeffs[0];
|
|
28278
|
-
const b = coeffs[1];
|
|
28279
|
-
const c = coeffs[2];
|
|
28280
|
-
return a * Math.exp(b * x) + c;
|
|
28281
|
-
},
|
|
28282
|
-
getEquationString: function (coords, asymptote) {
|
|
28283
|
-
if (!asymptote) {
|
|
28284
|
-
return null;
|
|
28285
|
-
}
|
|
28286
|
-
const coeffs = this.getCoefficients(coords, asymptote);
|
|
28287
|
-
const a = coeffs[0];
|
|
28288
|
-
const b = coeffs[1];
|
|
28289
|
-
const c = coeffs[2];
|
|
28290
|
-
return "y = " + a.toFixed(3) + "e^(" + b.toFixed(3) + "x) + " + c.toFixed(3);
|
|
28291
|
-
}
|
|
28292
|
-
});
|
|
28293
|
-
const Logarithm = ___default["default"].extend({}, PlotDefaults, {
|
|
28294
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/f6491e99d34af34d924bfe0231728ad912068dc3.png",
|
|
28295
|
-
defaultCoords: [[0.55, 0.5], [0.75, 0.75]],
|
|
28296
|
-
defaultAsymptote: [[0.5, 0], [0.5, 1.0]],
|
|
28297
|
-
extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
28298
|
-
const x = asymptote[0][0];
|
|
28299
|
-
return ___default["default"].all(coords, coord => coord[0] !== x) && coords[0][1] !== coords[1][1];
|
|
28300
|
-
},
|
|
28301
|
-
extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
28302
|
-
const x = newCoord[0];
|
|
28303
|
-
const isValid = ___default["default"].all(coords, coord => coord[0] > x) || ___default["default"].all(coords, coord => coord[0] < x);
|
|
28304
|
-
if (isValid) {
|
|
28305
|
-
return [x, oldCoord[1]];
|
|
28306
|
-
}
|
|
28307
|
-
// Snap the asymptote as close as possible, i.e., if the user moves
|
|
28308
|
-
// the mouse really quickly into an invalid region
|
|
28309
|
-
const oldX = oldCoord[0];
|
|
28310
|
-
const wasLeft = ___default["default"].all(coords, coord => coord[0] > oldX);
|
|
28311
|
-
if (wasLeft) {
|
|
28312
|
-
const leftMost = ___default["default"].min(___default["default"].map(coords, coord => coord[0]));
|
|
28313
|
-
return [leftMost - graph.snapStep[0], oldCoord[1]];
|
|
28314
|
-
}
|
|
28315
|
-
const rightMost = ___default["default"].max(___default["default"].map(coords, coord => coord[0]));
|
|
28316
|
-
return [rightMost + graph.snapStep[0], oldCoord[1]];
|
|
28317
|
-
},
|
|
28318
|
-
allowReflectOverAsymptote: true,
|
|
28319
|
-
getCoefficients: function (coords, asymptote) {
|
|
28320
|
-
// It's easiest to calculate the logarithm's coefficients by thinking
|
|
28321
|
-
// about it as the inverse of the exponential, so we flip x and y and
|
|
28322
|
-
// perform some algebra on the coefficients. This also unifies the
|
|
28323
|
-
// logic between the two 'models'.
|
|
28324
|
-
const flip = coord => [coord[1], coord[0]];
|
|
28325
|
-
const inverseCoeffs = Exponential.getCoefficients(___default["default"].map(coords, flip), ___default["default"].map(asymptote, flip));
|
|
28326
|
-
if (inverseCoeffs) {
|
|
28327
|
-
const c = -inverseCoeffs[2] / inverseCoeffs[0];
|
|
28328
|
-
const b = 1 / inverseCoeffs[0];
|
|
28329
|
-
const a = 1 / inverseCoeffs[1];
|
|
28330
|
-
return [a, b, c];
|
|
28331
|
-
}
|
|
28332
|
-
},
|
|
28333
|
-
getFunctionForCoeffs: function (coeffs, x, asymptote) {
|
|
28334
|
-
const a = coeffs[0];
|
|
28335
|
-
const b = coeffs[1];
|
|
28336
|
-
const c = coeffs[2];
|
|
28337
|
-
return a * Math.log(b * x + c);
|
|
28338
|
-
},
|
|
28339
|
-
getEquationString: function (coords, asymptote) {
|
|
28340
|
-
if (!asymptote) {
|
|
28341
|
-
return null;
|
|
28342
|
-
}
|
|
28343
|
-
const coeffs = this.getCoefficients(coords, asymptote);
|
|
28344
|
-
const a = coeffs[0];
|
|
28345
|
-
const b = coeffs[1];
|
|
28346
|
-
const c = coeffs[2];
|
|
28347
|
-
return "y = ln(" + a.toFixed(3) + "x + " + b.toFixed(3) + ") + " + c.toFixed(3);
|
|
28348
|
-
}
|
|
28349
|
-
});
|
|
28350
|
-
const AbsoluteValue = ___default["default"].extend({}, PlotDefaults, {
|
|
28351
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/8256a630175a0cb1d11de223d6de0266daf98721.png",
|
|
28352
|
-
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
28353
|
-
getCoefficients: function (coords) {
|
|
28354
|
-
const p1 = coords[0];
|
|
28355
|
-
const p2 = coords[1];
|
|
28356
|
-
const denom = p2[0] - p1[0];
|
|
28357
|
-
const num = p2[1] - p1[1];
|
|
28358
|
-
if (denom === 0) {
|
|
28359
|
-
return;
|
|
28360
|
-
}
|
|
28361
|
-
let m = Math.abs(num / denom);
|
|
28362
|
-
if (p2[1] < p1[1]) {
|
|
28363
|
-
m *= -1;
|
|
28364
|
-
}
|
|
28365
|
-
const horizontalOffset = p1[0];
|
|
28366
|
-
const verticalOffset = p1[1];
|
|
28367
|
-
return [m, horizontalOffset, verticalOffset];
|
|
28368
|
-
},
|
|
28369
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
28370
|
-
const m = coeffs[0];
|
|
28371
|
-
const horizontalOffset = coeffs[1];
|
|
28372
|
-
const verticalOffset = coeffs[2];
|
|
28373
|
-
return m * Math.abs(x - horizontalOffset) + verticalOffset;
|
|
28374
|
-
},
|
|
28375
|
-
getEquationString: function (coords) {
|
|
28376
|
-
const coeffs = this.getCoefficients(coords);
|
|
28377
|
-
const m = coeffs[0];
|
|
28378
|
-
const horizontalOffset = coeffs[1];
|
|
28379
|
-
const verticalOffset = coeffs[2];
|
|
28380
|
-
return "y = " + m.toFixed(3) + "| x - " + horizontalOffset.toFixed(3) + "| + " + verticalOffset.toFixed(3);
|
|
28381
|
-
}
|
|
28382
|
-
});
|
|
28383
|
-
|
|
28384
|
-
/* Utility functions for dealing with graphing interfaces. */
|
|
28385
|
-
const functionTypeMapping = {
|
|
28386
|
-
linear: Linear,
|
|
28387
|
-
quadratic: Quadratic,
|
|
28388
|
-
sinusoid: Sinusoid,
|
|
28389
|
-
tangent: Tangent,
|
|
28390
|
-
exponential: Exponential,
|
|
28391
|
-
logarithm: Logarithm,
|
|
28392
|
-
absolute_value: AbsoluteValue
|
|
28393
|
-
};
|
|
28394
|
-
const allTypes = ___default["default"].keys(functionTypeMapping);
|
|
28395
|
-
function functionForType(type) {
|
|
28396
|
-
// @ts-expect-error: TypeScript doesn't know how to use deal with generics
|
|
28397
|
-
// and conditional types in this way.
|
|
28398
|
-
return functionTypeMapping[type];
|
|
28399
|
-
}
|
|
28400
27071
|
const getEquationString = props => {
|
|
28401
27072
|
const plot = props.plot;
|
|
28402
27073
|
if (plot.type && plot.coords) {
|
|
28403
|
-
const handler = functionForType(plot.type);
|
|
27074
|
+
const handler = perseusCore.GrapherUtil.functionForType(plot.type);
|
|
28404
27075
|
const result = handler.getEquationString(plot.coords, plot.asymptote);
|
|
28405
27076
|
return result || "";
|
|
28406
27077
|
}
|
|
@@ -28458,7 +27129,7 @@ const defaultPlotProps = (type, graph) => {
|
|
|
28458
27129
|
// extra information. It would be valid to always submit the default
|
|
28459
27130
|
// widget before even reading the question; you can't lose, but you
|
|
28460
27131
|
// might get a free win.
|
|
28461
|
-
const model = functionForType(type);
|
|
27132
|
+
const model = perseusCore.GrapherUtil.functionForType(type);
|
|
28462
27133
|
const defaultAsymptote = "defaultAsymptote" in model ? model.defaultAsymptote : null;
|
|
28463
27134
|
const gridStep = [1, 1];
|
|
28464
27135
|
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type '[number, number]'.
|
|
@@ -28504,70 +27175,12 @@ const typeToButton = type => {
|
|
|
28504
27175
|
value: type,
|
|
28505
27176
|
title: capitalized,
|
|
28506
27177
|
content: /*#__PURE__*/React__namespace.createElement("img", {
|
|
28507
|
-
src: staticUrl(functionForType(type).url),
|
|
27178
|
+
src: staticUrl(perseusCore.GrapherUtil.functionForType(type).url),
|
|
28508
27179
|
alt: capitalized
|
|
28509
27180
|
})
|
|
28510
27181
|
};
|
|
28511
27182
|
};
|
|
28512
27183
|
|
|
28513
|
-
function getCoefficientsByType(data) {
|
|
28514
|
-
if (data.coords == null) {
|
|
28515
|
-
return undefined;
|
|
28516
|
-
}
|
|
28517
|
-
if (data.type === "exponential" || data.type === "logarithm") {
|
|
28518
|
-
const grader = functionForType(data.type);
|
|
28519
|
-
return grader.getCoefficients(data.coords, data.asymptote);
|
|
28520
|
-
} else if (data.type === "linear" || data.type === "quadratic" || data.type === "absolute_value" || data.type === "sinusoid" || data.type === "tangent") {
|
|
28521
|
-
const grader = functionForType(data.type);
|
|
28522
|
-
return grader.getCoefficients(data.coords);
|
|
28523
|
-
} else {
|
|
28524
|
-
throw new perseusCore.PerseusError("Invalid grapher type", perseusCore.Errors.InvalidInput);
|
|
28525
|
-
}
|
|
28526
|
-
}
|
|
28527
|
-
function scoreGrapher(userInput, rubric) {
|
|
28528
|
-
if (userInput.type !== rubric.correct.type) {
|
|
28529
|
-
return {
|
|
28530
|
-
type: "points",
|
|
28531
|
-
earned: 0,
|
|
28532
|
-
total: 1,
|
|
28533
|
-
message: null
|
|
28534
|
-
};
|
|
28535
|
-
}
|
|
28536
|
-
|
|
28537
|
-
// We haven't moved the coords
|
|
28538
|
-
if (userInput.coords == null) {
|
|
28539
|
-
return {
|
|
28540
|
-
type: "invalid",
|
|
28541
|
-
message: null
|
|
28542
|
-
};
|
|
28543
|
-
}
|
|
28544
|
-
|
|
28545
|
-
// Get new function handler for grading
|
|
28546
|
-
const grader = functionForType(userInput.type);
|
|
28547
|
-
const guessCoeffs = getCoefficientsByType(userInput);
|
|
28548
|
-
const correctCoeffs = getCoefficientsByType(rubric.correct);
|
|
28549
|
-
if (guessCoeffs == null || correctCoeffs == null) {
|
|
28550
|
-
return {
|
|
28551
|
-
type: "invalid",
|
|
28552
|
-
message: null
|
|
28553
|
-
};
|
|
28554
|
-
}
|
|
28555
|
-
if (grader.areEqual(guessCoeffs, correctCoeffs)) {
|
|
28556
|
-
return {
|
|
28557
|
-
type: "points",
|
|
28558
|
-
earned: 1,
|
|
28559
|
-
total: 1,
|
|
28560
|
-
message: null
|
|
28561
|
-
};
|
|
28562
|
-
}
|
|
28563
|
-
return {
|
|
28564
|
-
type: "points",
|
|
28565
|
-
earned: 0,
|
|
28566
|
-
total: 1,
|
|
28567
|
-
message: null
|
|
28568
|
-
};
|
|
28569
|
-
}
|
|
28570
|
-
|
|
28571
27184
|
// @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
|
|
28572
27185
|
const MovablePoint$3 = Graphie.MovablePoint;
|
|
28573
27186
|
// @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
|
|
@@ -28629,7 +27242,8 @@ class FunctionGrapher extends React__namespace.Component {
|
|
|
28629
27242
|
return;
|
|
28630
27243
|
}
|
|
28631
27244
|
const functionProps = model.getPropsForCoeffs(coeffs, xRange);
|
|
28632
|
-
|
|
27245
|
+
const Movable = movableTypeToComponent[model.movable];
|
|
27246
|
+
return /*#__PURE__*/React__namespace.createElement(Movable, _extends({}, functionProps, {
|
|
28633
27247
|
key: this.props.model.url,
|
|
28634
27248
|
range: xRange,
|
|
28635
27249
|
style: style
|
|
@@ -28922,7 +27536,7 @@ class Grapher extends React__namespace.Component {
|
|
|
28922
27536
|
setup: this._setupGraphie
|
|
28923
27537
|
},
|
|
28924
27538
|
onChange: this.handlePlotChanges,
|
|
28925
|
-
model: type && functionForType(type),
|
|
27539
|
+
model: type && perseusCore.GrapherUtil.functionForType(type),
|
|
28926
27540
|
coords: coords,
|
|
28927
27541
|
asymptote: asymptote,
|
|
28928
27542
|
static: this.props.static,
|
|
@@ -28971,7 +27585,7 @@ var Grapher$1 = {
|
|
|
28971
27585
|
staticTransform: staticTransform$4,
|
|
28972
27586
|
// TODO(LEMS-2656): remove TS suppression
|
|
28973
27587
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusGrapherUserInput'.
|
|
28974
|
-
scorer: scoreGrapher
|
|
27588
|
+
scorer: perseusScore.scoreGrapher
|
|
28975
27589
|
};
|
|
28976
27590
|
|
|
28977
27591
|
const getPromptJSON$f = rendererJSON => {
|
|
@@ -29157,31 +27771,6 @@ const getPromptJSON$e = () => {
|
|
|
29157
27771
|
return getUnsupportedPromptJSON("iframe");
|
|
29158
27772
|
};
|
|
29159
27773
|
|
|
29160
|
-
function scoreIframe(userInput) {
|
|
29161
|
-
// The iframe can tell us whether it's correct or incorrect,
|
|
29162
|
-
// and pass an optional message
|
|
29163
|
-
if (userInput.status === "correct") {
|
|
29164
|
-
return {
|
|
29165
|
-
type: "points",
|
|
29166
|
-
earned: 1,
|
|
29167
|
-
total: 1,
|
|
29168
|
-
message: userInput.message || null
|
|
29169
|
-
};
|
|
29170
|
-
}
|
|
29171
|
-
if (userInput.status === "incorrect") {
|
|
29172
|
-
return {
|
|
29173
|
-
type: "points",
|
|
29174
|
-
earned: 0,
|
|
29175
|
-
total: 1,
|
|
29176
|
-
message: userInput.message || null
|
|
29177
|
-
};
|
|
29178
|
-
}
|
|
29179
|
-
return {
|
|
29180
|
-
type: "invalid",
|
|
29181
|
-
message: "Keep going, you're not there yet!"
|
|
29182
|
-
};
|
|
29183
|
-
}
|
|
29184
|
-
|
|
29185
27774
|
/**
|
|
29186
27775
|
* This is an iframe widget. It is used for rendering an iframe that
|
|
29187
27776
|
* then communicates its state via window.postMessage
|
|
@@ -29316,7 +27905,7 @@ var Iframe$1 = {
|
|
|
29316
27905
|
hidden: true,
|
|
29317
27906
|
// TODO(LEMS-2656): remove TS suppression
|
|
29318
27907
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusIframeUserInput'.
|
|
29319
|
-
scorer: scoreIframe
|
|
27908
|
+
scorer: perseusScore.scoreIframe
|
|
29320
27909
|
};
|
|
29321
27910
|
|
|
29322
27911
|
const getPromptJSON$d = renderProps => {
|
|
@@ -30763,63 +29352,6 @@ function findIntersectionOfRays(_ref3, _ref4) {
|
|
|
30763
29352
|
}
|
|
30764
29353
|
}
|
|
30765
29354
|
|
|
30766
|
-
// This file contains helper functions for working with angles.
|
|
30767
|
-
|
|
30768
|
-
function convertDegreesToRadians(degrees) {
|
|
30769
|
-
return degrees / 180 * Math.PI;
|
|
30770
|
-
}
|
|
30771
|
-
|
|
30772
|
-
// Returns a value between -180 and 180, inclusive. The angle is measured
|
|
30773
|
-
// between the positive x-axis and the given vector.
|
|
30774
|
-
function calculateAngleInDegrees(_ref) {
|
|
30775
|
-
let [x, y] = _ref;
|
|
30776
|
-
return Math.atan2(y, x) * 180 / Math.PI;
|
|
30777
|
-
}
|
|
30778
|
-
|
|
30779
|
-
// Converts polar coordinates to cartesian. The th(eta) parameter is in degrees.
|
|
30780
|
-
function polar(r, th) {
|
|
30781
|
-
if (typeof r === "number") {
|
|
30782
|
-
r = [r, r];
|
|
30783
|
-
}
|
|
30784
|
-
th = th * Math.PI / 180;
|
|
30785
|
-
return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
|
|
30786
|
-
}
|
|
30787
|
-
// This function calculates the angle between two points and an optional vertex.
|
|
30788
|
-
// If the vertex is not provided, the angle is measured between the two points.
|
|
30789
|
-
// This does not account for reflex angles or clockwise position.
|
|
30790
|
-
const getAngleFromVertex = (point, vertex) => {
|
|
30791
|
-
const x = point[0] - vertex[0];
|
|
30792
|
-
const y = point[1] - vertex[1];
|
|
30793
|
-
if (!x && !y) {
|
|
30794
|
-
return 0;
|
|
30795
|
-
}
|
|
30796
|
-
return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
|
|
30797
|
-
};
|
|
30798
|
-
|
|
30799
|
-
// This function calculates the clockwise angle between three points,
|
|
30800
|
-
// and is used to generate the labels and equation strings of the
|
|
30801
|
-
// current angle for the interactive graph.
|
|
30802
|
-
const getClockwiseAngle = function (coords) {
|
|
30803
|
-
let allowReflexAngles = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
|
30804
|
-
const coordsCopy = [...coords];
|
|
30805
|
-
// The coords are saved as [point1, vertex, point2] in the interactive graph
|
|
30806
|
-
const areClockwise = clockwise$1([coordsCopy[0], coordsCopy[2], coordsCopy[1]]);
|
|
30807
|
-
|
|
30808
|
-
// We may need to reverse the coordinates if we allow
|
|
30809
|
-
// reflex angles and the points are not in clockwise order.
|
|
30810
|
-
const shouldReverseCoords = !areClockwise || allowReflexAngles;
|
|
30811
|
-
|
|
30812
|
-
// Reverse the coordinates accordingly to ensure the angle is calculated correctly
|
|
30813
|
-
const clockwiseCoords = shouldReverseCoords ? coordsCopy.reverse() : coordsCopy;
|
|
30814
|
-
|
|
30815
|
-
// Calculate the angles between the two points and get the difference
|
|
30816
|
-
// between the two angles to get the clockwise angle.
|
|
30817
|
-
const startAngle = getAngleFromVertex(clockwiseCoords[0], clockwiseCoords[1]);
|
|
30818
|
-
const endAngle = getAngleFromVertex(clockwiseCoords[2], clockwiseCoords[1]);
|
|
30819
|
-
const angle = (startAngle + 360 - endAngle) % 360;
|
|
30820
|
-
return angle;
|
|
30821
|
-
};
|
|
30822
|
-
|
|
30823
29355
|
const defaultGraphConfig = {
|
|
30824
29356
|
range: [[0, 1], [0, 1]],
|
|
30825
29357
|
tickStep: [1, 1],
|
|
@@ -32030,6 +30562,9 @@ function SVGLine(props) {
|
|
|
32030
30562
|
});
|
|
32031
30563
|
}
|
|
32032
30564
|
|
|
30565
|
+
const {
|
|
30566
|
+
calculateAngleInDegrees: calculateAngleInDegrees$3
|
|
30567
|
+
} = kmath.angles;
|
|
32033
30568
|
function Vector(props) {
|
|
32034
30569
|
const {
|
|
32035
30570
|
tail,
|
|
@@ -32039,7 +30574,7 @@ function Vector(props) {
|
|
|
32039
30574
|
} = props;
|
|
32040
30575
|
const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
|
|
32041
30576
|
const direction = mafs.vec.sub(tipPx, tailPx);
|
|
32042
|
-
const angle = calculateAngleInDegrees(direction);
|
|
30577
|
+
const angle = calculateAngleInDegrees$3(direction);
|
|
32043
30578
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
32044
30579
|
style: {
|
|
32045
30580
|
stroke: color,
|
|
@@ -32056,6 +30591,14 @@ function Vector(props) {
|
|
|
32056
30591
|
}));
|
|
32057
30592
|
}
|
|
32058
30593
|
|
|
30594
|
+
function srFormatNumber(a, locale) {
|
|
30595
|
+
// adding zero here converts negative zero to positive zero.
|
|
30596
|
+
return (0 + a).toLocaleString(locale, {
|
|
30597
|
+
maximumFractionDigits: 3,
|
|
30598
|
+
useGrouping: false // no thousands separators
|
|
30599
|
+
});
|
|
30600
|
+
}
|
|
30601
|
+
|
|
32059
30602
|
/**
|
|
32060
30603
|
* Given a ray and a rectangular box, find the point where the ray intersects
|
|
32061
30604
|
* the edge of the box. Assumes the `initialPoint` is inside the box.
|
|
@@ -32104,7 +30647,45 @@ function getArrayWithoutDuplicates(array) {
|
|
|
32104
30647
|
}
|
|
32105
30648
|
return returnArray;
|
|
32106
30649
|
}
|
|
30650
|
+
function getSlopeStringForLine(line, strings) {
|
|
30651
|
+
const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
|
|
30652
|
+
if (!Number.isFinite(slope)) {
|
|
30653
|
+
return strings.srLinearGraphSlopeVertical;
|
|
30654
|
+
}
|
|
30655
|
+
if (slope === 0) {
|
|
30656
|
+
return strings.srLinearGraphSlopeHorizontal;
|
|
30657
|
+
}
|
|
30658
|
+
return slope > 0 ? strings.srLinearGraphSlopeIncreasing : strings.srLinearGraphSlopeDecreasing;
|
|
30659
|
+
}
|
|
30660
|
+
function getInterceptStringForLine(line, strings, locale) {
|
|
30661
|
+
const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
|
|
30662
|
+
const xIntercept = (0 - line[0][1]) / slope + line[0][0];
|
|
30663
|
+
const yIntercept = line[0][1] - slope * line[0][0];
|
|
30664
|
+
|
|
30665
|
+
// Check if the line fully overlaps with an axis.
|
|
30666
|
+
const overlapsXAxis = line[0][1] === 0 && line[1][1] === 0;
|
|
30667
|
+
const overlapsYAxis = line[0][0] === 0 && line[1][0] === 0;
|
|
30668
|
+
const hasXIntercept = Number.isFinite(xIntercept) && !overlapsXAxis;
|
|
30669
|
+
const hasYIntercept = Number.isFinite(yIntercept) && !overlapsYAxis;
|
|
30670
|
+
if (hasXIntercept && hasYIntercept) {
|
|
30671
|
+
// Describe both intercepts in the same sentence.
|
|
30672
|
+
return xIntercept === 0 && yIntercept === 0 ? strings.srLinearGraphOriginIntercept : strings.srLinearGraphBothIntercepts({
|
|
30673
|
+
xIntercept: srFormatNumber(xIntercept, locale),
|
|
30674
|
+
yIntercept: srFormatNumber(yIntercept, locale)
|
|
30675
|
+
});
|
|
30676
|
+
}
|
|
30677
|
+
|
|
30678
|
+
// Describe only one intercept.
|
|
30679
|
+
return hasXIntercept ? strings.srLinearGraphXOnlyIntercept({
|
|
30680
|
+
xIntercept: srFormatNumber(xIntercept, locale)
|
|
30681
|
+
}) : strings.srLinearGraphYOnlyIntercept({
|
|
30682
|
+
yIntercept: srFormatNumber(yIntercept, locale)
|
|
30683
|
+
});
|
|
30684
|
+
}
|
|
32107
30685
|
|
|
30686
|
+
const {
|
|
30687
|
+
calculateAngleInDegrees: calculateAngleInDegrees$2
|
|
30688
|
+
} = kmath.angles;
|
|
32108
30689
|
const LockedLine = props => {
|
|
32109
30690
|
const {
|
|
32110
30691
|
color,
|
|
@@ -32136,7 +30717,7 @@ const LockedLine = props => {
|
|
|
32136
30717
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
32137
30718
|
const [tailPx, tipPx] = useTransformVectorsToPixels(point2.coord, point1.coord);
|
|
32138
30719
|
const direction = mafs.vec.sub(tailPx, tipPx);
|
|
32139
|
-
let angle = calculateAngleInDegrees(direction);
|
|
30720
|
+
let angle = calculateAngleInDegrees$2(direction);
|
|
32140
30721
|
const startArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
|
|
32141
30722
|
angle: angle,
|
|
32142
30723
|
tip: arrowTip,
|
|
@@ -32548,6 +31129,12 @@ const SvgDefs = () => /*#__PURE__*/React__default["default"].createElement("defs
|
|
|
32548
31129
|
in: "SourceGraphic"
|
|
32549
31130
|
})));
|
|
32550
31131
|
|
|
31132
|
+
const {
|
|
31133
|
+
clockwise: clockwise$1
|
|
31134
|
+
} = kmath.geometry;
|
|
31135
|
+
const {
|
|
31136
|
+
getAngleFromVertex: getAngleFromVertex$1
|
|
31137
|
+
} = kmath.angles;
|
|
32551
31138
|
const PolygonAngle = _ref => {
|
|
32552
31139
|
let {
|
|
32553
31140
|
centerPoint,
|
|
@@ -32638,8 +31225,8 @@ const Angle = _ref2 => {
|
|
|
32638
31225
|
const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
|
|
32639
31226
|
|
|
32640
31227
|
// Calculate the angles between the two points
|
|
32641
|
-
const startAngle = getAngleFromVertex(clockwiseCoords[0], vertex);
|
|
32642
|
-
const endAngle = getAngleFromVertex(clockwiseCoords[1], vertex);
|
|
31228
|
+
const startAngle = getAngleFromVertex$1(clockwiseCoords[0], vertex);
|
|
31229
|
+
const endAngle = getAngleFromVertex$1(clockwiseCoords[1], vertex);
|
|
32643
31230
|
const angle = (startAngle + 360 - endAngle) % 360;
|
|
32644
31231
|
|
|
32645
31232
|
// Check if the angle is reflexive
|
|
@@ -33000,14 +31587,6 @@ function range(min, max) {
|
|
|
33000
31587
|
return result;
|
|
33001
31588
|
}
|
|
33002
31589
|
|
|
33003
|
-
function srFormatNumber(a, locale) {
|
|
33004
|
-
// adding zero here converts negative zero to positive zero.
|
|
33005
|
-
return (0 + a).toLocaleString(locale, {
|
|
33006
|
-
maximumFractionDigits: 3,
|
|
33007
|
-
useGrouping: false // no thousands separators
|
|
33008
|
-
});
|
|
33009
|
-
}
|
|
33010
|
-
|
|
33011
31590
|
// The hitbox size of 48px by 48px is preserved from the legacy interactive
|
|
33012
31591
|
// graph.
|
|
33013
31592
|
const hitboxSizePx = 48;
|
|
@@ -33400,6 +31979,11 @@ const MovablePoint$1 = /*#__PURE__*/React__namespace.forwardRef((props, pointRef
|
|
|
33400
31979
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, focusableHandle, visiblePoint);
|
|
33401
31980
|
});
|
|
33402
31981
|
|
|
31982
|
+
const {
|
|
31983
|
+
calculateAngleInDegrees: calculateAngleInDegrees$1,
|
|
31984
|
+
getClockwiseAngle: getClockwiseAngle$2,
|
|
31985
|
+
polar: polar$1
|
|
31986
|
+
} = kmath.angles;
|
|
33403
31987
|
function renderAngleGraph(state, dispatch) {
|
|
33404
31988
|
return {
|
|
33405
31989
|
graph: /*#__PURE__*/React__namespace.createElement(AngleGraph, {
|
|
@@ -33470,7 +32054,7 @@ function AngleGraph(props) {
|
|
|
33470
32054
|
strings,
|
|
33471
32055
|
locale
|
|
33472
32056
|
} = usePerseusI18n();
|
|
33473
|
-
const angleMeasure = srFormatNumber(getClockwiseAngle([endPoints[0], centerPoint, endPoints[1]], allowReflexAngles), locale);
|
|
32057
|
+
const angleMeasure = srFormatNumber(getClockwiseAngle$2([endPoints[0], centerPoint, endPoints[1]], allowReflexAngles), locale);
|
|
33474
32058
|
const wholeAngleAriaLabel = strings.srAngleGraphAriaLabel;
|
|
33475
32059
|
const wholeAngleDescription = strings.srAngleGraphAriaDescription({
|
|
33476
32060
|
angleMeasure,
|
|
@@ -33538,9 +32122,7 @@ function AngleGraph(props) {
|
|
|
33538
32122
|
ariaLabel: initialSideAriaLabel
|
|
33539
32123
|
}), /*#__PURE__*/React__namespace.createElement("g", {
|
|
33540
32124
|
id: "angle-description",
|
|
33541
|
-
style:
|
|
33542
|
-
display: "hidden"
|
|
33543
|
-
}
|
|
32125
|
+
style: a11y.srOnly
|
|
33544
32126
|
}, wholeAngleDescription));
|
|
33545
32127
|
}
|
|
33546
32128
|
const positiveX = [1, 0];
|
|
@@ -33548,7 +32130,7 @@ const negativeX = [-1, 0];
|
|
|
33548
32130
|
const positiveY = [0, 1];
|
|
33549
32131
|
const negativeY = [0, -1];
|
|
33550
32132
|
function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
33551
|
-
const currentAngle = calculateAngleInDegrees(mafs.vec.sub(sidePoint, vertex));
|
|
32133
|
+
const currentAngle = calculateAngleInDegrees$1(mafs.vec.sub(sidePoint, vertex));
|
|
33552
32134
|
|
|
33553
32135
|
// Find the rays that start at the current point and point up, down, left
|
|
33554
32136
|
// and right.
|
|
@@ -33565,8 +32147,8 @@ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
|
33565
32147
|
|
|
33566
32148
|
// find the rays that start from the vertex and point in the direction of
|
|
33567
32149
|
// the angles we just computed.
|
|
33568
|
-
const counterClockwiseRay = [vertex, mafs.vec.add(vertex, polar(1, oneStepCounterClockwise))];
|
|
33569
|
-
const clockwiseRay = [vertex, mafs.vec.add(vertex, polar(1, oneStepClockwise))];
|
|
32150
|
+
const counterClockwiseRay = [vertex, mafs.vec.add(vertex, polar$1(1, oneStepCounterClockwise))];
|
|
32151
|
+
const clockwiseRay = [vertex, mafs.vec.add(vertex, polar$1(1, oneStepClockwise))];
|
|
33570
32152
|
|
|
33571
32153
|
// find the intersections of those rays with the horizontal and vertical
|
|
33572
32154
|
// rays extending from the sidePoint. These intersections are the points to
|
|
@@ -33583,6 +32165,9 @@ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
|
33583
32165
|
};
|
|
33584
32166
|
}
|
|
33585
32167
|
|
|
32168
|
+
const {
|
|
32169
|
+
clockwise
|
|
32170
|
+
} = kmath.geometry;
|
|
33586
32171
|
function getGradableGraph(state, initialGraph) {
|
|
33587
32172
|
if (!state.hasBeenInteractedWith) {
|
|
33588
32173
|
return {
|
|
@@ -33664,7 +32249,7 @@ function getGradableGraph(state, initialGraph) {
|
|
|
33664
32249
|
// by the order of the points in the coords array, and we want to maintain
|
|
33665
32250
|
// the same angle scoring with the legacy graph (which had a bug).
|
|
33666
32251
|
// (LEMS-2190): When we remove the legacy graph, move this logic to the scoring function.
|
|
33667
|
-
const areClockwise = clockwise
|
|
32252
|
+
const areClockwise = clockwise([state.coords[0], state.coords[2], state.coords[1]]);
|
|
33668
32253
|
const shouldReverseCoords = areClockwise && !state.allowReflexAngles;
|
|
33669
32254
|
const coords = shouldReverseCoords ? state.coords.slice().reverse() : state.coords;
|
|
33670
32255
|
return {
|
|
@@ -33777,14 +32362,10 @@ function CircleGraph(props) {
|
|
|
33777
32362
|
}
|
|
33778
32363
|
}), /*#__PURE__*/React__namespace.createElement("g", {
|
|
33779
32364
|
id: radiusId,
|
|
33780
|
-
style:
|
|
33781
|
-
display: "hidden"
|
|
33782
|
-
}
|
|
32365
|
+
style: a11y.srOnly
|
|
33783
32366
|
}, srCircleRadius), /*#__PURE__*/React__namespace.createElement("g", {
|
|
33784
32367
|
id: outerPointsId,
|
|
33785
|
-
style:
|
|
33786
|
-
display: "hidden"
|
|
33787
|
-
}
|
|
32368
|
+
style: a11y.srOnly
|
|
33788
32369
|
}, srCircleOuterPoints));
|
|
33789
32370
|
}
|
|
33790
32371
|
function MovableCircle(props) {
|
|
@@ -33970,49 +32551,16 @@ const LinearGraph = (props, key) => {
|
|
|
33970
32551
|
point2X: srFormatNumber(line[1][0], locale),
|
|
33971
32552
|
point2Y: srFormatNumber(line[1][1], locale)
|
|
33972
32553
|
});
|
|
33973
|
-
|
|
33974
|
-
|
|
33975
|
-
const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
|
|
33976
|
-
let slopeString = "";
|
|
33977
|
-
if (slope === Infinity || slope === -Infinity) {
|
|
33978
|
-
slopeString = strings.srLinearGraphSlopeVertical;
|
|
33979
|
-
} else if (slope === 0) {
|
|
33980
|
-
slopeString = strings.srLinearGraphSlopeHorizontal;
|
|
33981
|
-
} else {
|
|
33982
|
-
slopeString = slope > 0 ? strings.srLinearGraphSlopeIncreasing : strings.srLinearGraphSlopeDecreasing;
|
|
33983
|
-
}
|
|
33984
|
-
|
|
33985
|
-
// Intersection description
|
|
33986
|
-
const xIntercept = (0 - line[0][1]) / slope + line[0][0];
|
|
33987
|
-
const yIntercept = line[0][1] - slope * line[0][0];
|
|
33988
|
-
const hasXIntercept = xIntercept !== Infinity && xIntercept !== -Infinity;
|
|
33989
|
-
const hasYIntercept = yIntercept !== Infinity && yIntercept !== -Infinity;
|
|
33990
|
-
let interceptString;
|
|
33991
|
-
if (hasXIntercept && hasYIntercept) {
|
|
33992
|
-
// Describe both intercepts in the same sentence.
|
|
33993
|
-
interceptString = xIntercept === 0 && yIntercept === 0 ? strings.srLinearGraphOriginIntercept : strings.srLinearGraphBothIntercepts({
|
|
33994
|
-
xIntercept: srFormatNumber(xIntercept, locale),
|
|
33995
|
-
yIntercept: srFormatNumber(yIntercept, locale)
|
|
33996
|
-
});
|
|
33997
|
-
} else {
|
|
33998
|
-
// Describe only one intercept.
|
|
33999
|
-
interceptString = hasXIntercept ? strings.srLinearGraphXOnlyIntercept({
|
|
34000
|
-
xIntercept: srFormatNumber(xIntercept, locale)
|
|
34001
|
-
}) : strings.srLinearGraphYOnlyIntercept({
|
|
34002
|
-
yIntercept: srFormatNumber(yIntercept, locale)
|
|
34003
|
-
});
|
|
34004
|
-
}
|
|
32554
|
+
const slopeString = getSlopeStringForLine(line, strings);
|
|
32555
|
+
const interceptString = getInterceptStringForLine(line, strings, locale);
|
|
34005
32556
|
|
|
34006
32557
|
// Linear graphs only have one line
|
|
34007
32558
|
// (LEMS-2050): Update the reducer so that we have a separate action for moving one line
|
|
34008
32559
|
// and another action for moving multiple lines
|
|
34009
32560
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
34010
|
-
className: "linear-graph-container"
|
|
34011
32561
|
// Outer line minimal description
|
|
34012
|
-
,
|
|
34013
32562
|
"aria-label": strings.srLinearGraph,
|
|
34014
|
-
"aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}
|
|
34015
|
-
role: "figure"
|
|
32563
|
+
"aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`
|
|
34016
32564
|
}, /*#__PURE__*/React__namespace.createElement(MovableLine, {
|
|
34017
32565
|
key: 0,
|
|
34018
32566
|
ariaLabels: {
|
|
@@ -34031,19 +32579,13 @@ const LinearGraph = (props, key) => {
|
|
|
34031
32579
|
color: "var(--movable-line-stroke-color)"
|
|
34032
32580
|
}), /*#__PURE__*/React__namespace.createElement("g", {
|
|
34033
32581
|
id: pointsDescriptionId,
|
|
34034
|
-
style:
|
|
34035
|
-
display: "hidden"
|
|
34036
|
-
}
|
|
32582
|
+
style: a11y.srOnly
|
|
34037
32583
|
}, linearGraphPointsDescription), /*#__PURE__*/React__namespace.createElement("g", {
|
|
34038
32584
|
id: interceptDescriptionId,
|
|
34039
|
-
style:
|
|
34040
|
-
display: "hidden"
|
|
34041
|
-
}
|
|
32585
|
+
style: a11y.srOnly
|
|
34042
32586
|
}, interceptString), /*#__PURE__*/React__namespace.createElement("g", {
|
|
34043
32587
|
id: slopeDescriptionId,
|
|
34044
|
-
style:
|
|
34045
|
-
display: "hidden"
|
|
34046
|
-
}
|
|
32588
|
+
style: a11y.srOnly
|
|
34047
32589
|
}, slopeString));
|
|
34048
32590
|
};
|
|
34049
32591
|
|
|
@@ -34053,7 +32595,9 @@ function renderLinearSystemGraph(state, dispatch) {
|
|
|
34053
32595
|
graphState: state,
|
|
34054
32596
|
dispatch: dispatch
|
|
34055
32597
|
}),
|
|
34056
|
-
interactiveElementsDescription:
|
|
32598
|
+
interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(LinearSystemGraphDescription, {
|
|
32599
|
+
state: state
|
|
32600
|
+
})
|
|
34057
32601
|
};
|
|
34058
32602
|
}
|
|
34059
32603
|
const LinearSystemGraph = props => {
|
|
@@ -34063,9 +32607,56 @@ const LinearSystemGraph = props => {
|
|
|
34063
32607
|
const {
|
|
34064
32608
|
coords: lines
|
|
34065
32609
|
} = props.graphState;
|
|
34066
|
-
|
|
32610
|
+
const {
|
|
32611
|
+
strings,
|
|
32612
|
+
locale
|
|
32613
|
+
} = usePerseusI18n();
|
|
32614
|
+
const id = React__namespace.useId();
|
|
32615
|
+
const linesAriaInfo = lines.map((line, i) => {
|
|
32616
|
+
return {
|
|
32617
|
+
pointsDescriptionId: `${id}-line${i + 1}-points`,
|
|
32618
|
+
interceptDescriptionId: `${id}-line${i + 1}-intercept`,
|
|
32619
|
+
slopeDescriptionId: `${id}-line${i + 1}-slope`,
|
|
32620
|
+
pointsDescription: strings.srLinearSystemPoints({
|
|
32621
|
+
lineNumber: i + 1,
|
|
32622
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32623
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32624
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32625
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32626
|
+
}),
|
|
32627
|
+
interceptDescription: getInterceptStringForLine(line, strings, locale),
|
|
32628
|
+
slopeDescription: getSlopeStringForLine(line, strings)
|
|
32629
|
+
};
|
|
32630
|
+
});
|
|
32631
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
32632
|
+
"aria-label": strings.srLinearSystemGraph,
|
|
32633
|
+
"aria-describedby": linesAriaInfo.map(_ref => {
|
|
32634
|
+
let {
|
|
32635
|
+
pointsDescriptionId,
|
|
32636
|
+
interceptDescriptionId,
|
|
32637
|
+
slopeDescriptionId
|
|
32638
|
+
} = _ref;
|
|
32639
|
+
return `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`;
|
|
32640
|
+
}).join(" ")
|
|
32641
|
+
}, lines?.map((line, i) => /*#__PURE__*/React__namespace.createElement(MovableLine, {
|
|
34067
32642
|
key: i,
|
|
34068
32643
|
points: line,
|
|
32644
|
+
ariaLabels: {
|
|
32645
|
+
point1AriaLabel: strings.srLinearSystemPoint({
|
|
32646
|
+
lineNumber: i + 1,
|
|
32647
|
+
pointSequence: 1,
|
|
32648
|
+
x: srFormatNumber(line[0][0], locale),
|
|
32649
|
+
y: srFormatNumber(line[0][1], locale)
|
|
32650
|
+
}),
|
|
32651
|
+
point2AriaLabel: strings.srLinearSystemPoint({
|
|
32652
|
+
lineNumber: i + 1,
|
|
32653
|
+
pointSequence: 2,
|
|
32654
|
+
x: srFormatNumber(line[1][0], locale),
|
|
32655
|
+
y: srFormatNumber(line[1][1], locale)
|
|
32656
|
+
}),
|
|
32657
|
+
grabHandleAriaLabel: `${linesAriaInfo[i].interceptDescription} ${linesAriaInfo[i].slopeDescription}`
|
|
32658
|
+
},
|
|
32659
|
+
ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId}`,
|
|
34069
32660
|
onMoveLine: delta => {
|
|
34070
32661
|
dispatch(actions.linearSystem.moveLine(i, delta));
|
|
34071
32662
|
},
|
|
@@ -34075,8 +32666,67 @@ const LinearSystemGraph = props => {
|
|
|
34075
32666
|
},
|
|
34076
32667
|
onMovePoint: (endpointIndex, destination) => dispatch(actions.linearSystem.movePointInFigure(i, endpointIndex, destination)),
|
|
34077
32668
|
color: "var(--movable-line-stroke-color)"
|
|
34078
|
-
})),
|
|
32669
|
+
})), linesAriaInfo.map(_ref2 => {
|
|
32670
|
+
let {
|
|
32671
|
+
pointsDescriptionId,
|
|
32672
|
+
interceptDescriptionId,
|
|
32673
|
+
slopeDescriptionId,
|
|
32674
|
+
pointsDescription,
|
|
32675
|
+
interceptDescription,
|
|
32676
|
+
slopeDescription
|
|
32677
|
+
} = _ref2;
|
|
32678
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("g", {
|
|
32679
|
+
key: pointsDescriptionId,
|
|
32680
|
+
id: pointsDescriptionId,
|
|
32681
|
+
style: a11y.srOnly
|
|
32682
|
+
}, pointsDescription), /*#__PURE__*/React__namespace.createElement("g", {
|
|
32683
|
+
key: interceptDescriptionId,
|
|
32684
|
+
id: interceptDescriptionId,
|
|
32685
|
+
style: a11y.srOnly
|
|
32686
|
+
}, interceptDescription), /*#__PURE__*/React__namespace.createElement("g", {
|
|
32687
|
+
key: slopeDescriptionId,
|
|
32688
|
+
id: slopeDescriptionId,
|
|
32689
|
+
style: a11y.srOnly
|
|
32690
|
+
}, slopeDescription));
|
|
32691
|
+
}));
|
|
34079
32692
|
};
|
|
32693
|
+
function LinearSystemGraphDescription(_ref3) {
|
|
32694
|
+
let {
|
|
32695
|
+
state
|
|
32696
|
+
} = _ref3;
|
|
32697
|
+
// The reason that LinearSystemGraphDescription is a component (rather
|
|
32698
|
+
// than a function that returns a string) is because it needs to use a
|
|
32699
|
+
// hook: `usePerseusI18n`.
|
|
32700
|
+
const i18n = usePerseusI18n();
|
|
32701
|
+
return describeLinearSystemGraph(state, i18n);
|
|
32702
|
+
}
|
|
32703
|
+
|
|
32704
|
+
// Exported for testing
|
|
32705
|
+
function describeLinearSystemGraph(state, i18n) {
|
|
32706
|
+
const {
|
|
32707
|
+
strings,
|
|
32708
|
+
locale
|
|
32709
|
+
} = i18n;
|
|
32710
|
+
const {
|
|
32711
|
+
coords: lines
|
|
32712
|
+
} = state;
|
|
32713
|
+
const graphDescription = strings.srLinearSystemGraph;
|
|
32714
|
+
const lineDescriptions = lines.map((line, i) => {
|
|
32715
|
+
const point1 = line[0];
|
|
32716
|
+
const point2 = line[1];
|
|
32717
|
+
return strings.srLinearSystemPoints({
|
|
32718
|
+
lineNumber: i + 1,
|
|
32719
|
+
point1X: srFormatNumber(point1[0], locale),
|
|
32720
|
+
point1Y: srFormatNumber(point1[1], locale),
|
|
32721
|
+
point2X: srFormatNumber(point2[0], locale),
|
|
32722
|
+
point2Y: srFormatNumber(point2[1], locale)
|
|
32723
|
+
});
|
|
32724
|
+
});
|
|
32725
|
+
const allDescriptions = [graphDescription, ...lineDescriptions];
|
|
32726
|
+
return strings.srInteractiveElements({
|
|
32727
|
+
elements: allDescriptions.join(" ")
|
|
32728
|
+
});
|
|
32729
|
+
}
|
|
34080
32730
|
|
|
34081
32731
|
function renderPointGraph(state, dispatch) {
|
|
34082
32732
|
return {
|
|
@@ -34518,7 +33168,7 @@ function QuadraticGraph(props) {
|
|
|
34518
33168
|
// to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
|
|
34519
33169
|
// While we should technically never have invalid coordinates, we want to ensure that we have a fallback
|
|
34520
33170
|
const coeffRef = React__namespace.useRef([0, 0, 0]);
|
|
34521
|
-
const coeffs = getQuadraticCoefficients$
|
|
33171
|
+
const coeffs = getQuadraticCoefficients$2(coords);
|
|
34522
33172
|
if (coeffs !== undefined) {
|
|
34523
33173
|
coeffRef.current = coeffs;
|
|
34524
33174
|
}
|
|
@@ -34543,7 +33193,7 @@ function QuadraticGraph(props) {
|
|
|
34543
33193
|
// These equations were originally set up in 2013 and may require some
|
|
34544
33194
|
// additional comments to help clarify the quadratic formula manipulations
|
|
34545
33195
|
// Origin: https://phabricator.khanacademy.org/D2413
|
|
34546
|
-
const getQuadraticCoefficients$
|
|
33196
|
+
const getQuadraticCoefficients$2 = coords => {
|
|
34547
33197
|
const p1 = coords[0];
|
|
34548
33198
|
const p2 = coords[1];
|
|
34549
33199
|
const p3 = coords[2];
|
|
@@ -34566,7 +33216,9 @@ function renderRayGraph(state, dispatch) {
|
|
|
34566
33216
|
graphState: state,
|
|
34567
33217
|
dispatch: dispatch
|
|
34568
33218
|
}),
|
|
34569
|
-
interactiveElementsDescription:
|
|
33219
|
+
interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(RayGraphDescription, {
|
|
33220
|
+
state: state
|
|
33221
|
+
})
|
|
34570
33222
|
};
|
|
34571
33223
|
}
|
|
34572
33224
|
const RayGraph = props => {
|
|
@@ -34578,18 +33230,104 @@ const RayGraph = props => {
|
|
|
34578
33230
|
} = props.graphState;
|
|
34579
33231
|
const handleMoveLine = delta => dispatch(actions.ray.moveRay(delta));
|
|
34580
33232
|
const handleMovePoint = (pointIndex, newPoint) => dispatch(actions.ray.movePoint(pointIndex, newPoint));
|
|
33233
|
+
const {
|
|
33234
|
+
strings,
|
|
33235
|
+
locale
|
|
33236
|
+
} = usePerseusI18n();
|
|
33237
|
+
const id = React__namespace.useId();
|
|
33238
|
+
const pointsDescriptionId = id + "-points";
|
|
33239
|
+
|
|
33240
|
+
// Aria label strings
|
|
33241
|
+
const {
|
|
33242
|
+
srRayGraph,
|
|
33243
|
+
srRayPoints,
|
|
33244
|
+
srRayEndpoint,
|
|
33245
|
+
srRayTerminalPoint,
|
|
33246
|
+
srRayGrabHandle
|
|
33247
|
+
} = describeRayGraph(props.graphState, {
|
|
33248
|
+
strings,
|
|
33249
|
+
locale
|
|
33250
|
+
});
|
|
34581
33251
|
|
|
34582
33252
|
// Ray graphs only have one line
|
|
34583
|
-
return /*#__PURE__*/React__namespace.createElement(
|
|
33253
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
33254
|
+
// Outer line minimal description
|
|
33255
|
+
"aria-label": srRayGraph,
|
|
33256
|
+
"aria-describedby": pointsDescriptionId
|
|
33257
|
+
}, /*#__PURE__*/React__namespace.createElement(MovableLine, {
|
|
34584
33258
|
points: line,
|
|
33259
|
+
ariaLabels: {
|
|
33260
|
+
point1AriaLabel: srRayEndpoint,
|
|
33261
|
+
point2AriaLabel: srRayTerminalPoint,
|
|
33262
|
+
grabHandleAriaLabel: srRayGrabHandle
|
|
33263
|
+
},
|
|
34585
33264
|
onMoveLine: handleMoveLine,
|
|
34586
33265
|
onMovePoint: handleMovePoint,
|
|
34587
33266
|
extend: {
|
|
34588
33267
|
start: false,
|
|
34589
33268
|
end: true
|
|
34590
33269
|
}
|
|
34591
|
-
})
|
|
33270
|
+
}), /*#__PURE__*/React__namespace.createElement("g", {
|
|
33271
|
+
id: pointsDescriptionId,
|
|
33272
|
+
style: a11y.srOnly
|
|
33273
|
+
}, srRayPoints));
|
|
34592
33274
|
};
|
|
33275
|
+
function RayGraphDescription(_ref) {
|
|
33276
|
+
let {
|
|
33277
|
+
state
|
|
33278
|
+
} = _ref;
|
|
33279
|
+
// The reason that RayGraphDescription is a component (rather than a
|
|
33280
|
+
// function that returns a string) is because it needs to use a
|
|
33281
|
+
// hook: `usePerseusI18n`.
|
|
33282
|
+
const i18n = usePerseusI18n();
|
|
33283
|
+
const strings = describeRayGraph(state, i18n);
|
|
33284
|
+
return strings.srRayInteractiveElement;
|
|
33285
|
+
}
|
|
33286
|
+
|
|
33287
|
+
// Exported for testing
|
|
33288
|
+
function describeRayGraph(state, i18n) {
|
|
33289
|
+
const {
|
|
33290
|
+
coords: line
|
|
33291
|
+
} = state;
|
|
33292
|
+
const {
|
|
33293
|
+
strings,
|
|
33294
|
+
locale
|
|
33295
|
+
} = i18n;
|
|
33296
|
+
|
|
33297
|
+
// Aria label strings
|
|
33298
|
+
const srRayGraph = strings.srRayGraph;
|
|
33299
|
+
const srRayPoints = strings.srRayPoints({
|
|
33300
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
33301
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
33302
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
33303
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
33304
|
+
});
|
|
33305
|
+
const srRayEndpoint = strings.srRayEndpoint({
|
|
33306
|
+
x: srFormatNumber(line[0][0], locale),
|
|
33307
|
+
y: srFormatNumber(line[0][1], locale)
|
|
33308
|
+
});
|
|
33309
|
+
const srRayTerminalPoint = strings.srRayTerminalPoint({
|
|
33310
|
+
x: srFormatNumber(line[1][0], locale),
|
|
33311
|
+
y: srFormatNumber(line[1][1], locale)
|
|
33312
|
+
});
|
|
33313
|
+
const srRayGrabHandle = strings.srRayGrabHandle({
|
|
33314
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
33315
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
33316
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
33317
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
33318
|
+
});
|
|
33319
|
+
const srRayInteractiveElement = strings.srInteractiveElements({
|
|
33320
|
+
elements: [srRayGraph, srRayPoints].join(" ")
|
|
33321
|
+
});
|
|
33322
|
+
return {
|
|
33323
|
+
srRayGraph,
|
|
33324
|
+
srRayPoints,
|
|
33325
|
+
srRayEndpoint,
|
|
33326
|
+
srRayTerminalPoint,
|
|
33327
|
+
srRayGrabHandle,
|
|
33328
|
+
srRayInteractiveElement
|
|
33329
|
+
};
|
|
33330
|
+
}
|
|
34593
33331
|
|
|
34594
33332
|
function renderSegmentGraph(state, dispatch) {
|
|
34595
33333
|
return {
|
|
@@ -34704,6 +33442,10 @@ const getSinusoidCoefficients$1 = coords => {
|
|
|
34704
33442
|
};
|
|
34705
33443
|
};
|
|
34706
33444
|
|
|
33445
|
+
const {
|
|
33446
|
+
calculateAngleInDegrees,
|
|
33447
|
+
convertDegreesToRadians
|
|
33448
|
+
} = kmath.angles;
|
|
34707
33449
|
const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
|
|
34708
33450
|
|
|
34709
33451
|
// The vector from the center of the protractor to the top left corner of the
|
|
@@ -35419,6 +34161,10 @@ function mafsStateToInteractiveGraph(state, originalGraph) {
|
|
|
35419
34161
|
}
|
|
35420
34162
|
}
|
|
35421
34163
|
|
|
34164
|
+
const {
|
|
34165
|
+
magnitude: magnitude$2,
|
|
34166
|
+
vector: vector$2
|
|
34167
|
+
} = kmath.geometry;
|
|
35422
34168
|
function initializeGraphState(params) {
|
|
35423
34169
|
const {
|
|
35424
34170
|
graph,
|
|
@@ -35706,7 +34452,7 @@ const getAngleCoords = params => {
|
|
|
35706
34452
|
defaultCoords = normalizePoints(range, step, defaultCoords, true);
|
|
35707
34453
|
|
|
35708
34454
|
// @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.
|
|
35709
|
-
const radius = magnitude(vector(...defaultCoords));
|
|
34455
|
+
const radius = magnitude$2(vector$2(...defaultCoords));
|
|
35710
34456
|
|
|
35711
34457
|
// We're adding a placeholder for the third point to appease ts and so that we
|
|
35712
34458
|
// can calculate it after we've adjusted the first point by the angleOffsetDeg
|
|
@@ -35720,6 +34466,24 @@ const getAngleCoords = params => {
|
|
|
35720
34466
|
return coords;
|
|
35721
34467
|
};
|
|
35722
34468
|
|
|
34469
|
+
const {
|
|
34470
|
+
getAngleFromVertex,
|
|
34471
|
+
getClockwiseAngle: getClockwiseAngle$1,
|
|
34472
|
+
polar
|
|
34473
|
+
} = kmath.angles;
|
|
34474
|
+
const {
|
|
34475
|
+
angleMeasures: angleMeasures$1,
|
|
34476
|
+
ccw: ccw$1,
|
|
34477
|
+
lawOfCosines: lawOfCosines$1,
|
|
34478
|
+
magnitude: magnitude$1,
|
|
34479
|
+
polygonSidesIntersect,
|
|
34480
|
+
reverseVector,
|
|
34481
|
+
sign: sign$1,
|
|
34482
|
+
vector: vector$1
|
|
34483
|
+
} = kmath.geometry;
|
|
34484
|
+
const {
|
|
34485
|
+
getQuadraticCoefficients: getQuadraticCoefficients$1
|
|
34486
|
+
} = kmath.coefficients;
|
|
35723
34487
|
const minDistanceBetweenAngleVertexAndSidePoint = 2;
|
|
35724
34488
|
function interactiveGraphReducer(state, action) {
|
|
35725
34489
|
switch (action.type) {
|
|
@@ -36264,11 +35028,9 @@ const getChange = (coords, delta, constraintOpts) => {
|
|
|
36264
35028
|
const [dx, dy] = getDeltaVertex(maxMoves, minMoves, delta);
|
|
36265
35029
|
return [dx, dy];
|
|
36266
35030
|
};
|
|
36267
|
-
const eq$2 = Util.eq;
|
|
36268
|
-
|
|
36269
35031
|
// Less than or approximately equal
|
|
36270
35032
|
function leq$1(a, b) {
|
|
36271
|
-
return a < b ||
|
|
35033
|
+
return a < b || perseusCore.approximateEqual(a, b);
|
|
36272
35034
|
}
|
|
36273
35035
|
function boundAndSnapToGrid(point, _ref) {
|
|
36274
35036
|
let {
|
|
@@ -36418,7 +35180,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
|
|
|
36418
35180
|
});
|
|
36419
35181
|
|
|
36420
35182
|
// Gets the radian angles between the coords and maps them to degrees
|
|
36421
|
-
const angles = angleMeasures(coordsCopy).map(angle => angle * 180 / Math.PI);
|
|
35183
|
+
const angles = angleMeasures$1(coordsCopy).map(angle => angle * 180 / Math.PI);
|
|
36422
35184
|
|
|
36423
35185
|
// Gets the relative index of a point
|
|
36424
35186
|
const rel = j => {
|
|
@@ -36430,7 +35192,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
|
|
|
36430
35192
|
angles[rel(j)] = Math.round(angles[rel(j)]);
|
|
36431
35193
|
});
|
|
36432
35194
|
const getAngle = function (a, vertex, b) {
|
|
36433
|
-
const angle = getClockwiseAngle([coordsCopy[rel(a)], coordsCopy[rel(vertex)], coordsCopy[rel(b)]]);
|
|
35195
|
+
const angle = getClockwiseAngle$1([coordsCopy[rel(a)], coordsCopy[rel(vertex)], coordsCopy[rel(b)]]);
|
|
36434
35196
|
return angle;
|
|
36435
35197
|
};
|
|
36436
35198
|
const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
|
|
@@ -36442,9 +35204,9 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
|
|
|
36442
35204
|
})) {
|
|
36443
35205
|
return startingPoint;
|
|
36444
35206
|
}
|
|
36445
|
-
const knownSide = magnitude(
|
|
35207
|
+
const knownSide = magnitude$1(
|
|
36446
35208
|
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
|
|
36447
|
-
vector(coordsCopy[rel(-1)], coordsCopy[rel(1)]));
|
|
35209
|
+
vector$1(coordsCopy[rel(-1)], coordsCopy[rel(1)]));
|
|
36448
35210
|
|
|
36449
35211
|
// Returns true if the points form a counter-clockwise turn;
|
|
36450
35212
|
// a.k.a if the point is on the left or right of the polygon.
|
|
@@ -36452,7 +35214,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
|
|
|
36452
35214
|
// we want to add the inner angle to the outer angle, and if it's on the right,
|
|
36453
35215
|
// we want to subtract the inner angle from the outer angle. The angle solved
|
|
36454
35216
|
// for is then used in the polar function to determine the new point.
|
|
36455
|
-
const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
|
|
35217
|
+
const onLeft = sign$1(ccw$1(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
|
|
36456
35218
|
|
|
36457
35219
|
// Solve for side by using the law of sines
|
|
36458
35220
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
@@ -36491,7 +35253,7 @@ function boundAndSnapToSides(destinationPoint, _ref7, index) {
|
|
|
36491
35253
|
};
|
|
36492
35254
|
const sides = ___default["default"].map([[coordsCopy[rel(-1)], coordsCopy[index]], [coordsCopy[index], coordsCopy[rel(1)]], [coordsCopy[rel(-1)], coordsCopy[rel(1)]]], function (coordsCopy) {
|
|
36493
35255
|
// @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.
|
|
36494
|
-
return magnitude(vector(...coordsCopy));
|
|
35256
|
+
return magnitude$1(vector$1(...coordsCopy));
|
|
36495
35257
|
});
|
|
36496
35258
|
|
|
36497
35259
|
// Round the sides to left and right of the current point
|
|
@@ -36506,7 +35268,7 @@ function boundAndSnapToSides(destinationPoint, _ref7, index) {
|
|
|
36506
35268
|
|
|
36507
35269
|
// Solve for angle by using the law of cosines
|
|
36508
35270
|
// Angle at the first vertex of the polygon
|
|
36509
|
-
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
35271
|
+
const innerAngle = lawOfCosines$1(sides[0], sides[2], sides[1]);
|
|
36510
35272
|
|
|
36511
35273
|
// Angle at the second vertex of the polygon
|
|
36512
35274
|
const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
|
|
@@ -36517,7 +35279,7 @@ function boundAndSnapToSides(destinationPoint, _ref7, index) {
|
|
|
36517
35279
|
// we want to add the inner angle to the outer angle, and if it's on the right,
|
|
36518
35280
|
// we want to subtract the inner angle from the outer angle. The angle solved
|
|
36519
35281
|
// for is then used in the polar function to determine the new point.
|
|
36520
|
-
const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
|
|
35282
|
+
const onLeft = sign$1(ccw$1(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
|
|
36521
35283
|
|
|
36522
35284
|
// Uses the length of the first side of the polygon (radial coordinate)
|
|
36523
35285
|
// and the angle between the first and second sides of the
|
|
@@ -36651,216 +35413,26 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
36651
35413
|
}));
|
|
36652
35414
|
});
|
|
36653
35415
|
|
|
36654
|
-
const
|
|
36655
|
-
|
|
36656
|
-
|
|
36657
|
-
|
|
36658
|
-
|
|
36659
|
-
|
|
36660
|
-
|
|
36661
|
-
|
|
36662
|
-
|
|
36663
|
-
|
|
36664
|
-
|
|
36665
|
-
|
|
36666
|
-
|
|
36667
|
-
|
|
36668
|
-
|
|
36669
|
-
|
|
36670
|
-
const hasValue = Boolean(
|
|
36671
|
-
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
36672
|
-
userInput.coords ||
|
|
36673
|
-
// @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
|
|
36674
|
-
userInput.center && userInput.radius);
|
|
36675
|
-
if (userInput.type === rubric.correct.type && hasValue) {
|
|
36676
|
-
if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
|
|
36677
|
-
const guess = userInput.coords;
|
|
36678
|
-
const correct = rubric.correct.coords;
|
|
36679
|
-
|
|
36680
|
-
// If both of the guess points are on the correct line, it's
|
|
36681
|
-
// correct.
|
|
36682
|
-
if (collinear(correct[0], correct[1], guess[0]) && collinear(correct[0], correct[1], guess[1])) {
|
|
36683
|
-
return {
|
|
36684
|
-
type: "points",
|
|
36685
|
-
earned: 1,
|
|
36686
|
-
total: 1,
|
|
36687
|
-
message: null
|
|
36688
|
-
};
|
|
36689
|
-
}
|
|
36690
|
-
} else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
|
|
36691
|
-
const guess = userInput.coords;
|
|
36692
|
-
const correct = rubric.correct.coords;
|
|
36693
|
-
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])) {
|
|
36694
|
-
return {
|
|
36695
|
-
type: "points",
|
|
36696
|
-
earned: 1,
|
|
36697
|
-
total: 1,
|
|
36698
|
-
message: null
|
|
36699
|
-
};
|
|
36700
|
-
}
|
|
36701
|
-
} else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
|
|
36702
|
-
// If the parabola coefficients match, it's correct.
|
|
36703
|
-
const guessCoeffs = getQuadraticCoefficients(userInput.coords);
|
|
36704
|
-
const correctCoeffs = getQuadraticCoefficients(rubric.correct.coords);
|
|
36705
|
-
if (deepEq$3(guessCoeffs, correctCoeffs)) {
|
|
36706
|
-
return {
|
|
36707
|
-
type: "points",
|
|
36708
|
-
earned: 1,
|
|
36709
|
-
total: 1,
|
|
36710
|
-
message: null
|
|
36711
|
-
};
|
|
36712
|
-
}
|
|
36713
|
-
} else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
|
|
36714
|
-
const guessCoeffs = getSinusoidCoefficients(userInput.coords);
|
|
36715
|
-
const correctCoeffs = getSinusoidCoefficients(rubric.correct.coords);
|
|
36716
|
-
const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
|
|
36717
|
-
const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
|
|
36718
|
-
// If the canonical coefficients match, it's correct.
|
|
36719
|
-
if (deepEq$3(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
|
|
36720
|
-
return {
|
|
36721
|
-
type: "points",
|
|
36722
|
-
earned: 1,
|
|
36723
|
-
total: 1,
|
|
36724
|
-
message: null
|
|
36725
|
-
};
|
|
36726
|
-
}
|
|
36727
|
-
} else if (userInput.type === "circle" && rubric.correct.type === "circle") {
|
|
36728
|
-
if (deepEq$3(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
|
|
36729
|
-
return {
|
|
36730
|
-
type: "points",
|
|
36731
|
-
earned: 1,
|
|
36732
|
-
total: 1,
|
|
36733
|
-
message: null
|
|
36734
|
-
};
|
|
36735
|
-
}
|
|
36736
|
-
} else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
|
|
36737
|
-
let correct = rubric.correct.coords;
|
|
36738
|
-
if (correct == null) {
|
|
36739
|
-
throw new Error("Point graph rubric has null coords");
|
|
36740
|
-
}
|
|
36741
|
-
const guess = userInput.coords.slice();
|
|
36742
|
-
correct = correct.slice();
|
|
36743
|
-
// Everything's already rounded so we shouldn't need to do an
|
|
36744
|
-
// eq() comparison but _.isEqual(0, -0) is false, so we'll use
|
|
36745
|
-
// eq() anyway. The sort should be fine because it'll stringify
|
|
36746
|
-
// it and -0 converted to a string is "0"
|
|
36747
|
-
guess?.sort();
|
|
36748
|
-
// @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
|
|
36749
|
-
correct.sort();
|
|
36750
|
-
if (deepEq$3(guess, correct)) {
|
|
36751
|
-
return {
|
|
36752
|
-
type: "points",
|
|
36753
|
-
earned: 1,
|
|
36754
|
-
total: 1,
|
|
36755
|
-
message: null
|
|
36756
|
-
};
|
|
36757
|
-
}
|
|
36758
|
-
} else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
|
|
36759
|
-
const guess = userInput.coords.slice();
|
|
36760
|
-
const correct = rubric.correct.coords.slice();
|
|
36761
|
-
let match;
|
|
36762
|
-
if (rubric.correct.match === "similar") {
|
|
36763
|
-
match = similar(guess, correct, Number.POSITIVE_INFINITY);
|
|
36764
|
-
} else if (rubric.correct.match === "congruent") {
|
|
36765
|
-
match = similar(guess, correct, kmath.number.DEFAULT_TOLERANCE);
|
|
36766
|
-
} else if (rubric.correct.match === "approx") {
|
|
36767
|
-
match = similar(guess, correct, 0.1);
|
|
36768
|
-
} else {
|
|
36769
|
-
/* exact */
|
|
36770
|
-
guess.sort();
|
|
36771
|
-
correct.sort();
|
|
36772
|
-
match = deepEq$3(guess, correct);
|
|
36773
|
-
}
|
|
36774
|
-
if (match) {
|
|
36775
|
-
return {
|
|
36776
|
-
type: "points",
|
|
36777
|
-
earned: 1,
|
|
36778
|
-
total: 1,
|
|
36779
|
-
message: null
|
|
36780
|
-
};
|
|
36781
|
-
}
|
|
36782
|
-
} else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
|
|
36783
|
-
let guess = Util.deepClone(userInput.coords);
|
|
36784
|
-
let correct = Util.deepClone(rubric.correct.coords);
|
|
36785
|
-
guess = ___default["default"].invoke(guess, "sort").sort();
|
|
36786
|
-
correct = ___default["default"].invoke(correct, "sort").sort();
|
|
36787
|
-
if (deepEq$3(guess, correct)) {
|
|
36788
|
-
return {
|
|
36789
|
-
type: "points",
|
|
36790
|
-
earned: 1,
|
|
36791
|
-
total: 1,
|
|
36792
|
-
message: null
|
|
36793
|
-
};
|
|
36794
|
-
}
|
|
36795
|
-
} else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
|
|
36796
|
-
const guess = userInput.coords;
|
|
36797
|
-
const correct = rubric.correct.coords;
|
|
36798
|
-
if (deepEq$3(guess[0], correct[0]) && collinear(correct[0], correct[1], guess[1])) {
|
|
36799
|
-
return {
|
|
36800
|
-
type: "points",
|
|
36801
|
-
earned: 1,
|
|
36802
|
-
total: 1,
|
|
36803
|
-
message: null
|
|
36804
|
-
};
|
|
36805
|
-
}
|
|
36806
|
-
} else if (userInput.type === "angle" && rubric.correct.type === "angle") {
|
|
36807
|
-
const guess = userInput.coords;
|
|
36808
|
-
const correct = rubric.correct.coords;
|
|
36809
|
-
const allowReflexAngles = rubric.correct.allowReflexAngles;
|
|
36810
|
-
let match;
|
|
36811
|
-
if (rubric.correct.match === "congruent") {
|
|
36812
|
-
const angles = ___default["default"].map([guess, correct], function (coords) {
|
|
36813
|
-
if (!coords) {
|
|
36814
|
-
return false;
|
|
36815
|
-
}
|
|
36816
|
-
const angle = getClockwiseAngle(coords, allowReflexAngles);
|
|
36817
|
-
return angle;
|
|
36818
|
-
});
|
|
36819
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
36820
|
-
match = eq$1(...angles);
|
|
36821
|
-
} else {
|
|
36822
|
-
/* exact */
|
|
36823
|
-
match =
|
|
36824
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
36825
|
-
deepEq$3(guess[1], correct[1]) &&
|
|
36826
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
36827
|
-
collinear(correct[1], correct[0], guess[0]) &&
|
|
36828
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
36829
|
-
collinear(correct[1], correct[2], guess[2]);
|
|
36830
|
-
}
|
|
36831
|
-
if (match) {
|
|
36832
|
-
return {
|
|
36833
|
-
type: "points",
|
|
36834
|
-
earned: 1,
|
|
36835
|
-
total: 1,
|
|
36836
|
-
message: null
|
|
36837
|
-
};
|
|
36838
|
-
}
|
|
36839
|
-
}
|
|
36840
|
-
}
|
|
36841
|
-
|
|
36842
|
-
// The input wasn't correct, so check if it's a blank input or if it's
|
|
36843
|
-
// actually just wrong
|
|
36844
|
-
if (!hasValue || ___default["default"].isEqual(userInput, rubric.graph)) {
|
|
36845
|
-
// We're where we started.
|
|
36846
|
-
return {
|
|
36847
|
-
type: "invalid",
|
|
36848
|
-
message: null
|
|
36849
|
-
};
|
|
36850
|
-
}
|
|
36851
|
-
return {
|
|
36852
|
-
type: "points",
|
|
36853
|
-
earned: 0,
|
|
36854
|
-
total: 1,
|
|
36855
|
-
message: null
|
|
36856
|
-
};
|
|
36857
|
-
}
|
|
36858
|
-
|
|
35416
|
+
const {
|
|
35417
|
+
getClockwiseAngle
|
|
35418
|
+
} = kmath.angles;
|
|
35419
|
+
const {
|
|
35420
|
+
angleMeasures,
|
|
35421
|
+
ccw,
|
|
35422
|
+
collinear,
|
|
35423
|
+
getLineEquation,
|
|
35424
|
+
getLineIntersection,
|
|
35425
|
+
intersects,
|
|
35426
|
+
lawOfCosines,
|
|
35427
|
+
magnitude,
|
|
35428
|
+
rotate,
|
|
35429
|
+
sign,
|
|
35430
|
+
vector
|
|
35431
|
+
} = kmath.geometry;
|
|
36859
35432
|
const TRASH_ICON_URI = "https://ka-perseus-graphie.s3.amazonaws.com/b1452c0d79fd0f7ff4c3af9488474a0a0decb361.png";
|
|
36860
35433
|
const defaultBackgroundImage = {
|
|
36861
35434
|
url: null
|
|
36862
35435
|
};
|
|
36863
|
-
const eq = Util.eq;
|
|
36864
35436
|
const UNLIMITED = "unlimited";
|
|
36865
35437
|
|
|
36866
35438
|
// Sample background image:
|
|
@@ -36873,7 +35445,7 @@ function defaultVal(actual, defaultValue) {
|
|
|
36873
35445
|
|
|
36874
35446
|
// Less than or approximately equal
|
|
36875
35447
|
function leq(a, b) {
|
|
36876
|
-
return a < b ||
|
|
35448
|
+
return a < b || perseusCore.approximateEqual(a, b);
|
|
36877
35449
|
}
|
|
36878
35450
|
function capitalize(str) {
|
|
36879
35451
|
return str.replace(/(?:^|-)(.)/g, function (match, letter) {
|
|
@@ -37735,7 +36307,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
|
|
|
37735
36307
|
// Solve for side by using the law of sines
|
|
37736
36308
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
37737
36309
|
const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
|
|
37738
|
-
const offset = polar$
|
|
36310
|
+
const offset = polar$2(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
37739
36311
|
return this.graphie?.addPoints(coords[rel(-1)], offset);
|
|
37740
36312
|
}
|
|
37741
36313
|
// @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
|
|
@@ -37759,7 +36331,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
|
|
|
37759
36331
|
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
37760
36332
|
const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
|
|
37761
36333
|
const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
|
|
37762
|
-
const offset = polar$
|
|
36334
|
+
const offset = polar$2(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
37763
36335
|
return this.graphie?.addPoints(coords[rel(-1)], offset);
|
|
37764
36336
|
}
|
|
37765
36337
|
// Snap to grid (already done)
|
|
@@ -38371,12 +36943,12 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
38371
36943
|
}
|
|
38372
36944
|
static getLinearEquationString(props) {
|
|
38373
36945
|
const coords = InteractiveGraph.getLineCoords(props.graph, props);
|
|
38374
|
-
if (
|
|
36946
|
+
if (perseusCore.approximateEqual(coords[0][0], coords[1][0])) {
|
|
38375
36947
|
return "x = " + coords[0][0].toFixed(3);
|
|
38376
36948
|
}
|
|
38377
36949
|
const m = (coords[1][1] - coords[0][1]) / (coords[1][0] - coords[0][0]);
|
|
38378
36950
|
const b = coords[0][1] - m * coords[0][0];
|
|
38379
|
-
if (
|
|
36951
|
+
if (perseusCore.approximateEqual(m, 0)) {
|
|
38380
36952
|
return "y = " + b.toFixed(3);
|
|
38381
36953
|
}
|
|
38382
36954
|
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
@@ -38518,7 +37090,7 @@ var InteractiveGraph$1 = {
|
|
|
38518
37090
|
staticTransform: staticTransform$3,
|
|
38519
37091
|
// TODO(LEMS-2656): remove TS suppression
|
|
38520
37092
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusInteractiveGraphUserInput'.
|
|
38521
|
-
scorer: scoreInteractiveGraph
|
|
37093
|
+
scorer: perseusScore.scoreInteractiveGraph
|
|
38522
37094
|
};
|
|
38523
37095
|
|
|
38524
37096
|
// TODO: can we use WB typography?
|
|
@@ -38895,59 +37467,6 @@ const styles$a = aphrodite.StyleSheet.create({
|
|
|
38895
37467
|
}
|
|
38896
37468
|
});
|
|
38897
37469
|
|
|
38898
|
-
// Question state for marker as result of user selected answers.
|
|
38899
|
-
|
|
38900
|
-
function scoreMarker(marker) {
|
|
38901
|
-
const score = {
|
|
38902
|
-
hasAnswers: false,
|
|
38903
|
-
isCorrect: false
|
|
38904
|
-
};
|
|
38905
|
-
if (marker.selected && marker.selected.length > 0) {
|
|
38906
|
-
score.hasAnswers = true;
|
|
38907
|
-
}
|
|
38908
|
-
if (marker.answers.length > 0) {
|
|
38909
|
-
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
38910
|
-
// All correct answers are selected by the user.
|
|
38911
|
-
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
38912
|
-
}
|
|
38913
|
-
} else if (!marker.selected || marker.selected.length === 0) {
|
|
38914
|
-
// Correct as no answers should be selected by the user.
|
|
38915
|
-
score.isCorrect = true;
|
|
38916
|
-
}
|
|
38917
|
-
return score;
|
|
38918
|
-
}
|
|
38919
|
-
|
|
38920
|
-
// TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
|
|
38921
|
-
function scoreLabelImage(userInput, rubric) {
|
|
38922
|
-
let numAnswered = 0;
|
|
38923
|
-
let numCorrect = 0;
|
|
38924
|
-
for (const marker of userInput.markers) {
|
|
38925
|
-
const score = scoreMarker(marker);
|
|
38926
|
-
if (score.hasAnswers) {
|
|
38927
|
-
numAnswered++;
|
|
38928
|
-
}
|
|
38929
|
-
if (score.isCorrect) {
|
|
38930
|
-
numCorrect++;
|
|
38931
|
-
}
|
|
38932
|
-
}
|
|
38933
|
-
|
|
38934
|
-
// We expect all question markers to be answered before grading.
|
|
38935
|
-
if (numAnswered !== userInput.markers.length) {
|
|
38936
|
-
return {
|
|
38937
|
-
type: "invalid",
|
|
38938
|
-
message: null
|
|
38939
|
-
};
|
|
38940
|
-
}
|
|
38941
|
-
return {
|
|
38942
|
-
type: "points",
|
|
38943
|
-
// Markers with no expected answers are graded as correct if user
|
|
38944
|
-
// makes no answer selection.
|
|
38945
|
-
earned: numCorrect === userInput.markers.length ? 1 : 0,
|
|
38946
|
-
total: 1,
|
|
38947
|
-
message: null
|
|
38948
|
-
};
|
|
38949
|
-
}
|
|
38950
|
-
|
|
38951
37470
|
/**
|
|
38952
37471
|
* Represents a direction vector.
|
|
38953
37472
|
* No diagonals allowed.
|
|
@@ -39171,7 +37690,7 @@ class LabelImage extends React__namespace.Component {
|
|
|
39171
37690
|
onChange
|
|
39172
37691
|
} = this.props;
|
|
39173
37692
|
const updatedMarkers = markers.map(marker => {
|
|
39174
|
-
const score =
|
|
37693
|
+
const score = perseusScore.scoreLabelImageMarker(marker);
|
|
39175
37694
|
return {
|
|
39176
37695
|
...marker,
|
|
39177
37696
|
// Reveal correctness state for markers with answers.
|
|
@@ -39320,7 +37839,7 @@ class LabelImage extends React__namespace.Component {
|
|
|
39320
37839
|
bottom: "top"
|
|
39321
37840
|
}[markerPosition];
|
|
39322
37841
|
}
|
|
39323
|
-
const score =
|
|
37842
|
+
const score = perseusScore.scoreLabelImageMarker(marker);
|
|
39324
37843
|
// Once the question is answered, show markers
|
|
39325
37844
|
// with correct answers, otherwise passthrough
|
|
39326
37845
|
// the correctness state.
|
|
@@ -39528,7 +38047,7 @@ var LabelImage$1 = {
|
|
|
39528
38047
|
isLintable: true,
|
|
39529
38048
|
// TODO(LEMS-2656): remove TS suppression
|
|
39530
38049
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusLabelImageUserInput'.
|
|
39531
|
-
scorer: scoreLabelImage
|
|
38050
|
+
scorer: perseusScore.scoreLabelImage
|
|
39532
38051
|
};
|
|
39533
38052
|
|
|
39534
38053
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -40254,16 +38773,6 @@ const getPromptJSON$9 = (renderProps, userInput) => {
|
|
|
40254
38773
|
};
|
|
40255
38774
|
};
|
|
40256
38775
|
|
|
40257
|
-
function scoreMatcher(state, rubric) {
|
|
40258
|
-
const correct = ___default["default"].isEqual(state.left, rubric.left) && ___default["default"].isEqual(state.right, rubric.right);
|
|
40259
|
-
return {
|
|
40260
|
-
type: "points",
|
|
40261
|
-
earned: correct ? 1 : 0,
|
|
40262
|
-
total: 1,
|
|
40263
|
-
message: null
|
|
40264
|
-
};
|
|
40265
|
-
}
|
|
40266
|
-
|
|
40267
38776
|
const {
|
|
40268
38777
|
shuffle: shuffle$1,
|
|
40269
38778
|
seededRNG
|
|
@@ -40469,7 +38978,7 @@ var Matcher$1 = {
|
|
|
40469
38978
|
isLintable: true,
|
|
40470
38979
|
// TODO(LEMS-2656): remove TS suppression
|
|
40471
38980
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatcherUserInput'.
|
|
40472
|
-
scorer: scoreMatcher
|
|
38981
|
+
scorer: perseusScore.scoreMatcher
|
|
40473
38982
|
};
|
|
40474
38983
|
|
|
40475
38984
|
const getPromptJSON$8 = (renderProps, userInput) => {
|
|
@@ -40485,78 +38994,6 @@ const getPromptJSON$8 = (renderProps, userInput) => {
|
|
|
40485
38994
|
};
|
|
40486
38995
|
};
|
|
40487
38996
|
|
|
40488
|
-
/**
|
|
40489
|
-
* Checks user input from the matrix widget to see if it is scorable.
|
|
40490
|
-
*
|
|
40491
|
-
* Note: The matrix widget cannot do much validation without the Scoring
|
|
40492
|
-
* Data because of its use of KhanAnswerTypes as a core part of scoring.
|
|
40493
|
-
*
|
|
40494
|
-
* @see `scoreMatrix()` for more details.
|
|
40495
|
-
*/
|
|
40496
|
-
function validateMatrix(userInput, validationData, strings) {
|
|
40497
|
-
const supplied = userInput.answers;
|
|
40498
|
-
const suppliedSize = getMatrixSize(supplied);
|
|
40499
|
-
for (let row = 0; row < suppliedSize[0]; row++) {
|
|
40500
|
-
for (let col = 0; col < suppliedSize[1]; col++) {
|
|
40501
|
-
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
40502
|
-
return {
|
|
40503
|
-
type: "invalid",
|
|
40504
|
-
message: strings.fillAllCells
|
|
40505
|
-
};
|
|
40506
|
-
}
|
|
40507
|
-
}
|
|
40508
|
-
}
|
|
40509
|
-
return null;
|
|
40510
|
-
}
|
|
40511
|
-
|
|
40512
|
-
function scoreMatrix(userInput, rubric, strings) {
|
|
40513
|
-
const validationResult = validateMatrix(userInput, rubric, strings);
|
|
40514
|
-
if (validationResult != null) {
|
|
40515
|
-
return validationResult;
|
|
40516
|
-
}
|
|
40517
|
-
const solution = rubric.answers;
|
|
40518
|
-
const supplied = userInput.answers;
|
|
40519
|
-
const solutionSize = getMatrixSize(solution);
|
|
40520
|
-
const suppliedSize = getMatrixSize(supplied);
|
|
40521
|
-
const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
|
|
40522
|
-
const createValidator = perseusScore.KhanAnswerTypes.number.createValidatorFunctional;
|
|
40523
|
-
let message = null;
|
|
40524
|
-
let incorrect = false;
|
|
40525
|
-
___default["default"](suppliedSize[0]).times(row => {
|
|
40526
|
-
___default["default"](suppliedSize[1]).times(col => {
|
|
40527
|
-
if (!incorrectSize) {
|
|
40528
|
-
const validator = createValidator(
|
|
40529
|
-
// @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
|
|
40530
|
-
solution[row][col], {
|
|
40531
|
-
simplify: true
|
|
40532
|
-
});
|
|
40533
|
-
const result = validator(supplied[row][col]);
|
|
40534
|
-
if (result.message) {
|
|
40535
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
40536
|
-
message = result.message;
|
|
40537
|
-
}
|
|
40538
|
-
if (!result.correct) {
|
|
40539
|
-
incorrect = true;
|
|
40540
|
-
}
|
|
40541
|
-
}
|
|
40542
|
-
});
|
|
40543
|
-
});
|
|
40544
|
-
if (incorrectSize) {
|
|
40545
|
-
return {
|
|
40546
|
-
type: "points",
|
|
40547
|
-
earned: 0,
|
|
40548
|
-
total: 1,
|
|
40549
|
-
message: null
|
|
40550
|
-
};
|
|
40551
|
-
}
|
|
40552
|
-
return {
|
|
40553
|
-
type: "points",
|
|
40554
|
-
earned: incorrect ? 0 : 1,
|
|
40555
|
-
total: 1,
|
|
40556
|
-
message: message
|
|
40557
|
-
};
|
|
40558
|
-
}
|
|
40559
|
-
|
|
40560
38997
|
const {
|
|
40561
38998
|
assert: assert$2
|
|
40562
38999
|
} = InteractiveUtil;
|
|
@@ -40605,29 +39042,6 @@ const getRefForPath$1 = function (path) {
|
|
|
40605
39042
|
const column = getColumnFromPath$1(path);
|
|
40606
39043
|
return "answer" + row + "," + column;
|
|
40607
39044
|
};
|
|
40608
|
-
function getMatrixSize(matrix) {
|
|
40609
|
-
const matrixSize = [1, 1];
|
|
40610
|
-
|
|
40611
|
-
// We need to find the widest row and tallest column to get the correct
|
|
40612
|
-
// matrix size.
|
|
40613
|
-
___default["default"](matrix).each((matrixRow, row) => {
|
|
40614
|
-
let rowWidth = 0;
|
|
40615
|
-
___default["default"](matrixRow).each((matrixCol, col) => {
|
|
40616
|
-
if (matrixCol != null && matrixCol.toString().length) {
|
|
40617
|
-
rowWidth = col + 1;
|
|
40618
|
-
}
|
|
40619
|
-
});
|
|
40620
|
-
|
|
40621
|
-
// Matrix width:
|
|
40622
|
-
matrixSize[1] = Math.max(matrixSize[1], rowWidth);
|
|
40623
|
-
|
|
40624
|
-
// Matrix height:
|
|
40625
|
-
if (rowWidth > 0) {
|
|
40626
|
-
matrixSize[0] = Math.max(matrixSize[0], row + 1);
|
|
40627
|
-
}
|
|
40628
|
-
});
|
|
40629
|
-
return matrixSize;
|
|
40630
|
-
}
|
|
40631
39045
|
class Matrix extends React__namespace.Component {
|
|
40632
39046
|
static contextType = PerseusI18nContext;
|
|
40633
39047
|
// @ts-expect-error - TS2564 - Property 'cursorPosition' has no initializer and is not definitely assigned in the constructor.
|
|
@@ -40798,7 +39212,7 @@ class Matrix extends React__namespace.Component {
|
|
|
40798
39212
|
INPUT_HEIGHT,
|
|
40799
39213
|
INPUT_WIDTH
|
|
40800
39214
|
} = dimensions;
|
|
40801
|
-
const matrixSize = getMatrixSize(this.props.answers);
|
|
39215
|
+
const matrixSize = perseusCore.getMatrixSize(this.props.answers);
|
|
40802
39216
|
const maxRows = this.props.matrixBoardSize[0];
|
|
40803
39217
|
const maxCols = this.props.matrixBoardSize[1];
|
|
40804
39218
|
const cursorRow = this.props.cursorPosition[0];
|
|
@@ -40970,7 +39384,7 @@ var Matrix$1 = {
|
|
|
40970
39384
|
isLintable: true,
|
|
40971
39385
|
// TODO(LEMS-2656): remove TS suppression
|
|
40972
39386
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusMatrixUserInput'.
|
|
40973
|
-
scorer: scoreMatrix
|
|
39387
|
+
scorer: perseusScore.scoreMatrix
|
|
40974
39388
|
};
|
|
40975
39389
|
|
|
40976
39390
|
const getPromptJSON$7 = () => {
|
|
@@ -42015,59 +40429,6 @@ const getPromptJSON$6 = (renderProps, userInput) => {
|
|
|
42015
40429
|
};
|
|
42016
40430
|
};
|
|
42017
40431
|
|
|
42018
|
-
/**
|
|
42019
|
-
* Checks user input is within the allowed range and not the same as the initial
|
|
42020
|
-
* state.
|
|
42021
|
-
* @param userInput
|
|
42022
|
-
* @see 'scoreNumberLine' for the scoring logic.
|
|
42023
|
-
*/
|
|
42024
|
-
function validateNumberLine(userInput) {
|
|
42025
|
-
const divisionRange = userInput.divisionRange;
|
|
42026
|
-
const outsideAllowedRange = userInput.numDivisions > divisionRange[1] || userInput.numDivisions < divisionRange[0];
|
|
42027
|
-
|
|
42028
|
-
// TODO: I don't think isTickCrtl is a thing anymore
|
|
42029
|
-
if (userInput.isTickCrtl && outsideAllowedRange) {
|
|
42030
|
-
return {
|
|
42031
|
-
type: "invalid",
|
|
42032
|
-
message: "Number of divisions is outside the allowed range."
|
|
42033
|
-
};
|
|
42034
|
-
}
|
|
42035
|
-
return null;
|
|
42036
|
-
}
|
|
42037
|
-
|
|
42038
|
-
function scoreNumberLine(userInput, scoringData) {
|
|
42039
|
-
const validationError = validateNumberLine(userInput);
|
|
42040
|
-
if (validationError) {
|
|
42041
|
-
return validationError;
|
|
42042
|
-
}
|
|
42043
|
-
const range = scoringData.range;
|
|
42044
|
-
const start = scoringData.initialX != null ? scoringData.initialX : range[0];
|
|
42045
|
-
const startRel = scoringData.isInequality ? "ge" : "eq";
|
|
42046
|
-
const correctRel = scoringData.correctRel || "eq";
|
|
42047
|
-
const correctPos = kmath.number.equal(userInput.numLinePosition, scoringData.correctX || 0);
|
|
42048
|
-
if (correctPos && correctRel === userInput.rel) {
|
|
42049
|
-
return {
|
|
42050
|
-
type: "points",
|
|
42051
|
-
earned: 1,
|
|
42052
|
-
total: 1,
|
|
42053
|
-
message: null
|
|
42054
|
-
};
|
|
42055
|
-
}
|
|
42056
|
-
if (userInput.numLinePosition === start && userInput.rel === startRel) {
|
|
42057
|
-
// We're where we started.
|
|
42058
|
-
return {
|
|
42059
|
-
type: "invalid",
|
|
42060
|
-
message: null
|
|
42061
|
-
};
|
|
42062
|
-
}
|
|
42063
|
-
return {
|
|
42064
|
-
type: "points",
|
|
42065
|
-
earned: 0,
|
|
42066
|
-
total: 1,
|
|
42067
|
-
message: null
|
|
42068
|
-
};
|
|
42069
|
-
}
|
|
42070
|
-
|
|
42071
40432
|
// @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
|
|
42072
40433
|
const MovablePoint = Graphie.MovablePoint;
|
|
42073
40434
|
// @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
|
|
@@ -42627,7 +40988,7 @@ var NumberLine$1 = {
|
|
|
42627
40988
|
staticTransform: staticTransform$1,
|
|
42628
40989
|
// TODO(LEMS-2656): remove TS suppression
|
|
42629
40990
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusNumberLineUserInput'.
|
|
42630
|
-
scorer: scoreNumberLine
|
|
40991
|
+
scorer: perseusScore.scoreNumberLine
|
|
42631
40992
|
};
|
|
42632
40993
|
|
|
42633
40994
|
const getPromptJSON$5 = (renderProps, userInput) => {
|
|
@@ -42642,36 +41003,6 @@ const getPromptJSON$5 = (renderProps, userInput) => {
|
|
|
42642
41003
|
};
|
|
42643
41004
|
};
|
|
42644
41005
|
|
|
42645
|
-
/**
|
|
42646
|
-
* Checks user input from the orderer widget to see if the user has started
|
|
42647
|
-
* ordering the options, making the widget scorable.
|
|
42648
|
-
* @param userInput
|
|
42649
|
-
* @see `scoreOrderer` for more details.
|
|
42650
|
-
*/
|
|
42651
|
-
function validateOrderer(userInput) {
|
|
42652
|
-
if (userInput.current.length === 0) {
|
|
42653
|
-
return {
|
|
42654
|
-
type: "invalid",
|
|
42655
|
-
message: null
|
|
42656
|
-
};
|
|
42657
|
-
}
|
|
42658
|
-
return null;
|
|
42659
|
-
}
|
|
42660
|
-
|
|
42661
|
-
function scoreOrderer(userInput, rubric) {
|
|
42662
|
-
const validateError = validateOrderer(userInput);
|
|
42663
|
-
if (validateError) {
|
|
42664
|
-
return validateError;
|
|
42665
|
-
}
|
|
42666
|
-
const correct = ___default["default"].isEqual(userInput.current, rubric.correctOptions.map(option => option.content));
|
|
42667
|
-
return {
|
|
42668
|
-
type: "points",
|
|
42669
|
-
earned: correct ? 1 : 0,
|
|
42670
|
-
total: 1,
|
|
42671
|
-
message: null
|
|
42672
|
-
};
|
|
42673
|
-
}
|
|
42674
|
-
|
|
42675
41006
|
class PlaceholderCard extends React__namespace.Component {
|
|
42676
41007
|
render() {
|
|
42677
41008
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
@@ -43211,7 +41542,7 @@ var Orderer$1 = {
|
|
|
43211
41542
|
isLintable: true,
|
|
43212
41543
|
// TODO(LEMS-2656): remove TS suppression
|
|
43213
41544
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusOrdererUserInput
|
|
43214
|
-
scorer: scoreOrderer
|
|
41545
|
+
scorer: perseusScore.scoreOrderer
|
|
43215
41546
|
};
|
|
43216
41547
|
|
|
43217
41548
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
@@ -43483,42 +41814,6 @@ const getPromptJSON$3 = () => {
|
|
|
43483
41814
|
return getUnsupportedPromptJSON("plotter");
|
|
43484
41815
|
};
|
|
43485
41816
|
|
|
43486
|
-
const {
|
|
43487
|
-
deepEq: deepEq$2
|
|
43488
|
-
} = Util;
|
|
43489
|
-
|
|
43490
|
-
/**
|
|
43491
|
-
* Checks user input to confirm it is not the same as the starting values for the graph.
|
|
43492
|
-
* This means the user has modified the graph, and the question can be scored.
|
|
43493
|
-
*
|
|
43494
|
-
* @see 'scorePlotter' for more details on scoring.
|
|
43495
|
-
*/
|
|
43496
|
-
function validatePlotter(userInput, validationData) {
|
|
43497
|
-
if (deepEq$2(userInput, validationData.starting)) {
|
|
43498
|
-
return {
|
|
43499
|
-
type: "invalid",
|
|
43500
|
-
message: null
|
|
43501
|
-
};
|
|
43502
|
-
}
|
|
43503
|
-
return null;
|
|
43504
|
-
}
|
|
43505
|
-
|
|
43506
|
-
const {
|
|
43507
|
-
deepEq: deepEq$1
|
|
43508
|
-
} = Util;
|
|
43509
|
-
function scorePlotter(userInput, scoringData) {
|
|
43510
|
-
const validationError = validatePlotter(userInput, scoringData);
|
|
43511
|
-
if (validationError) {
|
|
43512
|
-
return validationError;
|
|
43513
|
-
}
|
|
43514
|
-
return {
|
|
43515
|
-
type: "points",
|
|
43516
|
-
earned: deepEq$1(userInput, scoringData.correct) ? 1 : 0,
|
|
43517
|
-
total: 1,
|
|
43518
|
-
message: null
|
|
43519
|
-
};
|
|
43520
|
-
}
|
|
43521
|
-
|
|
43522
41817
|
/* eslint-disable react/no-unsafe */
|
|
43523
41818
|
class Plotter extends React__namespace.Component {
|
|
43524
41819
|
static contextType = PerseusI18nContext;
|
|
@@ -44323,7 +42618,7 @@ var Plotter$1 = {
|
|
|
44323
42618
|
staticTransform: staticTransform,
|
|
44324
42619
|
// TODO(LEMS-2656): remove TS suppression
|
|
44325
42620
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusPlotterUserInput
|
|
44326
|
-
scorer: scorePlotter
|
|
42621
|
+
scorer: perseusScore.scorePlotter
|
|
44327
42622
|
};
|
|
44328
42623
|
|
|
44329
42624
|
const getPromptJSON$2 = () => {
|
|
@@ -44400,43 +42695,6 @@ const getPromptJSON$1 = userInput => {
|
|
|
44400
42695
|
};
|
|
44401
42696
|
};
|
|
44402
42697
|
|
|
44403
|
-
/**
|
|
44404
|
-
* Checks user input for the sorter widget to ensure that the user has made
|
|
44405
|
-
* changes before attempting to score the widget.
|
|
44406
|
-
* @param userInput
|
|
44407
|
-
* @see 'scoreSorter' in 'packages/perseus/src/widgets/sorter/score-sorter.ts'
|
|
44408
|
-
* for more details on how the sorter widget is scored.
|
|
44409
|
-
*/
|
|
44410
|
-
function validateSorter(userInput) {
|
|
44411
|
-
// If the sorter widget hasn't been changed yet, we treat it as "empty" which
|
|
44412
|
-
// prevents the "Check" button from becoming active. We want the user
|
|
44413
|
-
// to make a change before trying to move forward. This makes an
|
|
44414
|
-
// assumption that the initial order isn't the correct order! However,
|
|
44415
|
-
// this should be rare if it happens, and interacting with the list
|
|
44416
|
-
// will enable the button, so they won't be locked out of progressing.
|
|
44417
|
-
if (!userInput.changed) {
|
|
44418
|
-
return {
|
|
44419
|
-
type: "invalid",
|
|
44420
|
-
message: null
|
|
44421
|
-
};
|
|
44422
|
-
}
|
|
44423
|
-
return null;
|
|
44424
|
-
}
|
|
44425
|
-
|
|
44426
|
-
function scoreSorter(userInput, rubric) {
|
|
44427
|
-
const validationError = validateSorter(userInput);
|
|
44428
|
-
if (validationError) {
|
|
44429
|
-
return validationError;
|
|
44430
|
-
}
|
|
44431
|
-
const correct = Util.deepEq(userInput.options, rubric.correct);
|
|
44432
|
-
return {
|
|
44433
|
-
type: "points",
|
|
44434
|
-
earned: correct ? 1 : 0,
|
|
44435
|
-
total: 1,
|
|
44436
|
-
message: null
|
|
44437
|
-
};
|
|
44438
|
-
}
|
|
44439
|
-
|
|
44440
42698
|
const {
|
|
44441
42699
|
shuffle
|
|
44442
42700
|
} = Util;
|
|
@@ -44523,84 +42781,9 @@ var Sorter$1 = {
|
|
|
44523
42781
|
isLintable: true,
|
|
44524
42782
|
// TODO(LEMS-2656): remove TS suppression
|
|
44525
42783
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusSorterUserInput
|
|
44526
|
-
scorer: scoreSorter
|
|
44527
|
-
};
|
|
44528
|
-
|
|
44529
|
-
/**
|
|
44530
|
-
* Filters the given table (modelled as a 2D array) to remove any rows that are
|
|
44531
|
-
* completely empty.
|
|
44532
|
-
*
|
|
44533
|
-
* @returns A new table with only non-empty rows.
|
|
44534
|
-
*/
|
|
44535
|
-
const filterNonEmpty = function (table) {
|
|
44536
|
-
return table.filter(function (row) {
|
|
44537
|
-
// Return only rows that are non-empty.
|
|
44538
|
-
return row.some(cell => cell);
|
|
44539
|
-
});
|
|
42784
|
+
scorer: perseusScore.scoreSorter
|
|
44540
42785
|
};
|
|
44541
42786
|
|
|
44542
|
-
function validateTable(userInput) {
|
|
44543
|
-
const supplied = filterNonEmpty(userInput);
|
|
44544
|
-
const hasEmptyCell = supplied.some(function (row) {
|
|
44545
|
-
return row.some(function (cell) {
|
|
44546
|
-
return cell === "";
|
|
44547
|
-
});
|
|
44548
|
-
});
|
|
44549
|
-
if (hasEmptyCell || !supplied.length) {
|
|
44550
|
-
return {
|
|
44551
|
-
type: "invalid",
|
|
44552
|
-
message: null
|
|
44553
|
-
};
|
|
44554
|
-
}
|
|
44555
|
-
return null;
|
|
44556
|
-
}
|
|
44557
|
-
|
|
44558
|
-
function scoreTable(userInput, rubric, strings) {
|
|
44559
|
-
const validationResult = validateTable(userInput);
|
|
44560
|
-
if (validationResult != null) {
|
|
44561
|
-
return validationResult;
|
|
44562
|
-
}
|
|
44563
|
-
const supplied = filterNonEmpty(userInput);
|
|
44564
|
-
const solution = filterNonEmpty(rubric.answers);
|
|
44565
|
-
if (supplied.length !== solution.length) {
|
|
44566
|
-
return {
|
|
44567
|
-
type: "points",
|
|
44568
|
-
earned: 0,
|
|
44569
|
-
total: 1,
|
|
44570
|
-
message: null
|
|
44571
|
-
};
|
|
44572
|
-
}
|
|
44573
|
-
const createValidator = perseusScore.KhanAnswerTypes.number.createValidatorFunctional;
|
|
44574
|
-
let message = null;
|
|
44575
|
-
const allCorrect = solution.every(function (rowSolution) {
|
|
44576
|
-
for (let i = 0; i < supplied.length; i++) {
|
|
44577
|
-
const rowSupplied = supplied[i];
|
|
44578
|
-
const correct = rowSupplied.every(function (cellSupplied, i) {
|
|
44579
|
-
const cellSolution = rowSolution[i];
|
|
44580
|
-
const validator = createValidator(cellSolution, {
|
|
44581
|
-
simplify: true
|
|
44582
|
-
});
|
|
44583
|
-
const result = validator(cellSupplied);
|
|
44584
|
-
if (result.message) {
|
|
44585
|
-
message = result.message;
|
|
44586
|
-
}
|
|
44587
|
-
return result.correct;
|
|
44588
|
-
});
|
|
44589
|
-
if (correct) {
|
|
44590
|
-
supplied.splice(i, 1);
|
|
44591
|
-
return true;
|
|
44592
|
-
}
|
|
44593
|
-
}
|
|
44594
|
-
return false;
|
|
44595
|
-
});
|
|
44596
|
-
return {
|
|
44597
|
-
type: "points",
|
|
44598
|
-
earned: allCorrect ? 1 : 0,
|
|
44599
|
-
total: 1,
|
|
44600
|
-
message
|
|
44601
|
-
};
|
|
44602
|
-
}
|
|
44603
|
-
|
|
44604
42787
|
const {
|
|
44605
42788
|
assert
|
|
44606
42789
|
} = InteractiveUtil;
|
|
@@ -44817,7 +43000,7 @@ var Table$1 = {
|
|
|
44817
43000
|
isLintable: true,
|
|
44818
43001
|
// TODO(LEMS-2656): remove TS suppression
|
|
44819
43002
|
// @ts-expect-error: Type UserInput is not assignable to type PerseusTableUserInput
|
|
44820
|
-
scorer: scoreTable
|
|
43003
|
+
scorer: perseusScore.scoreTable
|
|
44821
43004
|
};
|
|
44822
43005
|
|
|
44823
43006
|
const getPromptJSON = () => {
|
|
@@ -45002,7 +43185,7 @@ const init = function () {
|
|
|
45002
43185
|
|
|
45003
43186
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
45004
43187
|
const libName = "@khanacademy/perseus";
|
|
45005
|
-
const libVersion = "
|
|
43188
|
+
const libVersion = "51.0.0";
|
|
45006
43189
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
45007
43190
|
|
|
45008
43191
|
const apiVersion = {
|
|
@@ -46971,7 +45154,6 @@ const getValidWidgetIds = perseusItem => {
|
|
|
46971
45154
|
return wonderStuffCore.keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
|
|
46972
45155
|
};
|
|
46973
45156
|
|
|
46974
|
-
const deepEq = Util.deepEq;
|
|
46975
45157
|
function isRealJSONParse(jsonParse) {
|
|
46976
45158
|
const randomPhrase = buildRandomPhrase();
|
|
46977
45159
|
const randomHintPhrase = buildRandomPhrase();
|
|
@@ -47028,7 +45210,7 @@ function isRealJSONParse(jsonParse) {
|
|
|
47028
45210
|
const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
|
|
47029
45211
|
const parsedTestJSON = jsonParse(testJSON);
|
|
47030
45212
|
const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
|
|
47031
|
-
return
|
|
45213
|
+
return perseusCore.approximateDeepEqual(parsedTestItemData, testingObject);
|
|
47032
45214
|
}
|
|
47033
45215
|
function buildRandomString() {
|
|
47034
45216
|
let capitalize = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
@@ -47235,7 +45417,9 @@ function object(schema) {
|
|
|
47235
45417
|
for (const [prop, propParser] of Object.entries(schema)) {
|
|
47236
45418
|
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
47237
45419
|
if (isSuccess(result)) {
|
|
47238
|
-
|
|
45420
|
+
if (result.value !== undefined || prop in rawValue) {
|
|
45421
|
+
ret[prop] = result.value;
|
|
45422
|
+
}
|
|
47239
45423
|
} else {
|
|
47240
45424
|
mismatches.push(...result.detail);
|
|
47241
45425
|
}
|
|
@@ -48854,7 +47038,6 @@ const EditorJsonify = {
|
|
|
48854
47038
|
* Main entry point
|
|
48855
47039
|
*/
|
|
48856
47040
|
const GrapherUtil = {
|
|
48857
|
-
allTypes,
|
|
48858
47041
|
DEFAULT_GRAPHER_PROPS,
|
|
48859
47042
|
chooseType,
|
|
48860
47043
|
defaultPlotProps,
|
|
@@ -48922,7 +47105,6 @@ exports.getAngleCoords = getAngleCoords;
|
|
|
48922
47105
|
exports.getAnswerFromUserInput = getAnswerFromUserInput;
|
|
48923
47106
|
exports.getAnswersFromWidgets = getAnswersFromWidgets;
|
|
48924
47107
|
exports.getCircleCoords = getCircleCoords;
|
|
48925
|
-
exports.getClockwiseAngle = getClockwiseAngle;
|
|
48926
47108
|
exports.getCorrectAnswerForWidgetId = getCorrectAnswerForWidgetId;
|
|
48927
47109
|
exports.getImagesWithoutAltData = getImagesWithoutAltData;
|
|
48928
47110
|
exports.getInteractiveBoxFromSizeClass = getInteractiveBoxFromSizeClass;
|