@khanacademy/perseus 50.0.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 +597 -2403
- 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 +537 -2320
- 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 +11 -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 +4 -2
- package/dist/widgets/categorizer/categorizer.util.d.ts +17 -0
- 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/dist/widgets.d.ts +2 -1
- package/package.json +11 -11
- package/dist/tex-wrangler.d.ts +0 -7
- package/dist/util/geometry.d.ts +0 -28
- package/dist/validation.types.d.ts +0 -166
- package/dist/widgets/categorizer/score-categorizer.d.ts +0 -5
- package/dist/widgets/categorizer/validate-categorizer.d.ts +0 -13
- package/dist/widgets/cs-program/score-cs-program.d.ts +0 -4
- package/dist/widgets/dropdown/score-dropdown.d.ts +0 -4
- package/dist/widgets/dropdown/validate-dropdown.d.ts +0 -8
- package/dist/widgets/expression/get-decimal-separator.d.ts +0 -5
- package/dist/widgets/expression/score-expression.d.ts +0 -5
- package/dist/widgets/expression/validate-expression.d.ts +0 -12
- package/dist/widgets/grapher/grapher-types.d.ts +0 -58
- package/dist/widgets/grapher/score-grapher.d.ts +0 -4
- package/dist/widgets/iframe/score-iframe.d.ts +0 -3
- package/dist/widgets/input-number/score-input-number.d.ts +0 -39
- package/dist/widgets/interactive-graphs/math/angles.d.ts +0 -7
- package/dist/widgets/interactive-graphs/score-interactive-graph.d.ts +0 -4
- package/dist/widgets/label-image/score-label-image.d.ts +0 -10
- package/dist/widgets/label-image/types.d.ts +0 -11
- package/dist/widgets/matcher/score-matcher.d.ts +0 -4
- package/dist/widgets/matrix/score-matrix.d.ts +0 -5
- package/dist/widgets/matrix/validate-matrix.d.ts +0 -13
- package/dist/widgets/number-line/score-number-line.d.ts +0 -4
- package/dist/widgets/number-line/validate-number-line.d.ts +0 -12
- package/dist/widgets/numeric-input/score-numeric-input.d.ts +0 -6
- package/dist/widgets/orderer/score-orderer.d.ts +0 -3
- package/dist/widgets/orderer/validate-orderer.d.ts +0 -10
- package/dist/widgets/plotter/score-plotter.d.ts +0 -4
- package/dist/widgets/plotter/validate-plotter.d.ts +0 -10
- package/dist/widgets/radio/score-radio.d.ts +0 -5
- package/dist/widgets/radio/validate-radio.d.ts +0 -12
- package/dist/widgets/sorter/score-sorter.d.ts +0 -4
- package/dist/widgets/sorter/validate-sorter.d.ts +0 -11
- package/dist/widgets/table/score-table.d.ts +0 -5
- package/dist/widgets/table/utils.d.ts +0 -7
- package/dist/widgets/table/validate-table.d.ts +0 -4
package/dist/es/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as KAS from '@khanacademy/kas';
|
|
2
2
|
import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
|
|
3
|
+
import { PerseusError, Errors, getDecimalSeparator, approximateDeepEqual, pluck, mapObject, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getMatrixSize, addLibraryVersionToPerseusDebug, lockedFigureColorNames, plotterPlotTypes, ItemExtras } from '@khanacademy/perseus-core';
|
|
3
4
|
import * as PerseusLinter from '@khanacademy/perseus-linter';
|
|
4
5
|
import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
|
|
6
|
+
import { scoreExpression, KhanAnswerTypes, scoreInputNumber, inputNumberAnswerTypes, scoreNumericInput, scoreRadio, scoreCategorizer, scoreCSProgram, scoreDropdown, scoreGrapher, scoreIframe, scoreInteractiveGraph, scoreLabelImage, scoreLabelImageMarker, scoreMatcher, scoreMatrix, scoreNumberLine, scoreOrderer, scorePlotter, scoreSorter, scoreTable } from '@khanacademy/perseus-score';
|
|
5
7
|
import { View, RenderStateRoot, Id, useOnMountEffect, useLatestRef, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
|
|
6
8
|
import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
|
|
7
9
|
import { HeadingMedium, LabelSmall, LabelLarge, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
|
|
@@ -12,17 +14,15 @@ import React__default, { useContext, useState, useEffect, useRef, createContext,
|
|
|
12
14
|
import * as ReactDOM from 'react-dom';
|
|
13
15
|
import ReactDOM__default from 'react-dom';
|
|
14
16
|
import _, { debounce as debounce$1 } from 'underscore';
|
|
15
|
-
import { ErrorCodes, KhanAnswerTypes } from '@khanacademy/perseus-score';
|
|
16
17
|
import Clickable from '@khanacademy/wonder-blocks-clickable';
|
|
17
18
|
import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
|
|
18
19
|
import { color, spacing, font } from '@khanacademy/wonder-blocks-tokens';
|
|
19
20
|
import $ from 'jquery';
|
|
20
21
|
import { v4 } from 'uuid';
|
|
21
|
-
import { PerseusError, Errors, pluck, mapObject, lockedFigureColors, lockedFigureFillStyles, addLibraryVersionToPerseusDebug, lockedFigureColorNames, plotterPlotTypes, ItemExtras } from '@khanacademy/perseus-core';
|
|
22
22
|
import PropTypes from 'prop-types';
|
|
23
23
|
import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
|
|
24
24
|
import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
|
|
25
|
-
import { point, KhanMath, number as number$1, vector as vector$
|
|
25
|
+
import { point, KhanMath, number as number$1, vector as vector$3, line, geometry, angles, coefficients } from '@khanacademy/kmath';
|
|
26
26
|
import SimpleMarkdown from '@khanacademy/simple-markdown';
|
|
27
27
|
import { pureMarkdownRules } from '@khanacademy/pure-markdown';
|
|
28
28
|
import { TextField } from '@khanacademy/wonder-blocks-form';
|
|
@@ -33,7 +33,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
|
|
|
33
33
|
import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
|
|
34
34
|
import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
|
|
35
35
|
import debounce$2 from 'lodash.debounce';
|
|
36
|
-
import { vec, Coordinates, Ellipse, Plot as Plot$
|
|
36
|
+
import { vec, Coordinates, Ellipse, Plot as Plot$2, Line as Line$4, Point as Point$2, Polygon, Text as Text$1, useTransformContext, Polyline, Mafs } from 'mafs';
|
|
37
37
|
import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
|
|
38
38
|
import questionIcon from '@phosphor-icons/core/regular/question.svg';
|
|
39
39
|
import { useDrag } from '@use-gesture/react';
|
|
@@ -231,7 +231,15 @@ const strings = {
|
|
|
231
231
|
srAngleSideAtCoordinates: "Point %(point)s, %(side)s at %(x)s comma %(y)s",
|
|
232
232
|
srAngleVertexAtCoordinatesWithAngleMeasure: "Point 2, vertex at %(x)s comma %(y)s. Angle %(angleMeasure)s degrees",
|
|
233
233
|
srAngleGraphAriaLabel: "An angle on a coordinate plane.",
|
|
234
|
-
srAngleGraphAriaDescription: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s"
|
|
234
|
+
srAngleGraphAriaDescription: "The angle measure is %(angleMeasure)s degrees with a vertex at %(vertexX)s comma %(vertexY)s, a point on the starting side at %(startingSideX)s comma %(startingSideY)s and a point on the ending side at %(endingSideX)s comma %(endingSideY)s",
|
|
235
|
+
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
236
|
+
srLinearSystemPoints: "Line %(lineNumber)s has two points, point 1 at %(point1X)s comma %(point1Y)s and point 2 at %(point2X)s comma %(point2Y)s.",
|
|
237
|
+
srLinearSystemPoint: "Point %(pointSequence)s on line %(lineNumber)s at %(x)s comma %(y)s.",
|
|
238
|
+
srRayGraph: "A ray on a coordinate plane.",
|
|
239
|
+
srRayPoints: "The endpoint is at %(point1X)s comma %(point1Y)s and the ray goes through point %(point2X)s comma %(point2Y)s.",
|
|
240
|
+
srRayGrabHandle: "Ray with endpoint %(point1X)s comma %(point1Y)s going through point %(point2X)s comma %(point2Y)s.",
|
|
241
|
+
srRayEndpoint: "Endpoint at %(point1X)s comma %(point1Y)s.",
|
|
242
|
+
srRayTerminalPoint: "Through point at %(point2X)s comma %(point2Y)s."
|
|
235
243
|
// The above strings are used for interactive graph SR descriptions.
|
|
236
244
|
};
|
|
237
245
|
|
|
@@ -500,19 +508,66 @@ const mockStrings = {
|
|
|
500
508
|
startingSideY,
|
|
501
509
|
endingSideX,
|
|
502
510
|
endingSideY
|
|
503
|
-
}) => `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the starting side at ${startingSideX} comma ${startingSideY} and a point on the ending side at ${endingSideX} comma ${endingSideY}
|
|
511
|
+
}) => `The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the starting side at ${startingSideX} comma ${startingSideY} and a point on the ending side at ${endingSideX} comma ${endingSideY}.`,
|
|
512
|
+
srLinearSystemGraph: "Two lines on a coordinate plane.",
|
|
513
|
+
srLinearSystemPoints: ({
|
|
514
|
+
lineNumber,
|
|
515
|
+
point1X,
|
|
516
|
+
point1Y,
|
|
517
|
+
point2X,
|
|
518
|
+
point2Y
|
|
519
|
+
}) => `Line ${lineNumber} has two points, point 1 at ${point1X} comma ${point1Y} and point 2 at ${point2X} comma ${point2Y}.`,
|
|
520
|
+
srLinearSystemPoint: ({
|
|
521
|
+
lineNumber,
|
|
522
|
+
pointSequence,
|
|
523
|
+
x,
|
|
524
|
+
y
|
|
525
|
+
}) => `Point ${pointSequence} on line ${lineNumber} at ${x} comma ${y}.`,
|
|
526
|
+
srRayGraph: "A ray on a coordinate plane.",
|
|
527
|
+
srRayPoints: ({
|
|
528
|
+
point1X,
|
|
529
|
+
point1Y,
|
|
530
|
+
point2X,
|
|
531
|
+
point2Y
|
|
532
|
+
}) => `The endpoint is at ${point1X} comma ${point1Y} and the ray goes through point ${point2X} comma ${point2Y}.`,
|
|
533
|
+
srRayGrabHandle: ({
|
|
534
|
+
point1X,
|
|
535
|
+
point1Y,
|
|
536
|
+
point2X,
|
|
537
|
+
point2Y
|
|
538
|
+
}) => `Ray with endpoint ${point1X} comma ${point1Y} going through point ${point2X} comma ${point2Y}.`,
|
|
539
|
+
srRayEndpoint: ({
|
|
540
|
+
x,
|
|
541
|
+
y
|
|
542
|
+
}) => `Endpoint at ${x} comma ${y}.`,
|
|
543
|
+
srRayTerminalPoint: ({
|
|
544
|
+
x,
|
|
545
|
+
y
|
|
546
|
+
}) => `Through point at ${x} comma ${y}.`
|
|
504
547
|
// The above strings are used for interactive graph SR descriptions.
|
|
505
548
|
};
|
|
506
549
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
550
|
+
// This type helps us make sure all error codes are mapped to strings
|
|
551
|
+
|
|
552
|
+
const errorToString = {
|
|
553
|
+
MISSING_PERCENT_ERROR: strings.MISSING_PERCENT_ERROR,
|
|
554
|
+
NEEDS_TO_BE_SIMPLIFIED_ERROR: strings.NEEDS_TO_BE_SIMPLFIED_ERROR,
|
|
555
|
+
APPROXIMATED_PI_ERROR: strings.APPROXIMATED_PI_ERROR,
|
|
556
|
+
EXTRA_SYMBOLS_ERROR: strings.EXTRA_SYMBOLS_ERROR,
|
|
557
|
+
WRONG_CASE_ERROR: strings.WRONG_CASE_ERROR,
|
|
558
|
+
WRONG_LETTER_ERROR: strings.WRONG_LETTER_ERROR,
|
|
559
|
+
MULTIPLICATION_SIGN_ERROR: strings.MULTIPLICATION_SIGN_ERROR,
|
|
560
|
+
INVALID_SELECTION_ERROR: strings.invalidSelection,
|
|
561
|
+
CHOOSE_CORRECT_NUM_ERROR: strings.chooseCorrectNum,
|
|
562
|
+
NOT_NONE_ABOVE_ERROR: strings.notNoneOfTheAbove,
|
|
563
|
+
FILL_ALL_CELLS_ERROR: strings.fillAllCells
|
|
564
|
+
};
|
|
565
|
+
function mapErrorToString(err) {
|
|
566
|
+
if (!err) {
|
|
567
|
+
return err;
|
|
568
|
+
}
|
|
569
|
+
return errorToString[err] || err;
|
|
570
|
+
}
|
|
516
571
|
|
|
517
572
|
/**
|
|
518
573
|
* PerseusI18nContext provides a way to set the strings and locale that are
|
|
@@ -1449,205 +1504,6 @@ const getPromptJSON$t = (renderProps, userInput) => {
|
|
|
1449
1504
|
};
|
|
1450
1505
|
};
|
|
1451
1506
|
|
|
1452
|
-
/**
|
|
1453
|
-
* Get the character used for separating decimals.
|
|
1454
|
-
*/
|
|
1455
|
-
const getDecimalSeparator = locale => {
|
|
1456
|
-
var _match$;
|
|
1457
|
-
switch (locale) {
|
|
1458
|
-
// TODO(somewhatabstract): Remove this when Chrome supports the `ka`
|
|
1459
|
-
// locale properly.
|
|
1460
|
-
// https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
|
|
1461
|
-
//
|
|
1462
|
-
// Supported locales in Chrome:
|
|
1463
|
-
// https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
|
|
1464
|
-
case "ka":
|
|
1465
|
-
return ",";
|
|
1466
|
-
default:
|
|
1467
|
-
const numberWithDecimalSeparator = 1.1;
|
|
1468
|
-
// TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
|
|
1469
|
-
// support Safari 12.
|
|
1470
|
-
const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
|
|
1471
|
-
// 0x661 is ARABIC-INDIC DIGIT ONE
|
|
1472
|
-
// 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
|
|
1473
|
-
.match(/[^\d\u0661\u06F1]/);
|
|
1474
|
-
return (_match$ = match == null ? void 0 : match[0]) != null ? _match$ : ".";
|
|
1475
|
-
}
|
|
1476
|
-
};
|
|
1477
|
-
|
|
1478
|
-
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
1479
|
-
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
1480
|
-
// which get's the injected logger.
|
|
1481
|
-
/**
|
|
1482
|
-
* Provides logging infrastructure for Perseus
|
|
1483
|
-
*/
|
|
1484
|
-
const Log = {
|
|
1485
|
-
// Logs a message
|
|
1486
|
-
log: (message, extra) => {
|
|
1487
|
-
getDependencies().Log.log(message, extra);
|
|
1488
|
-
},
|
|
1489
|
-
// Logs an error with a message
|
|
1490
|
-
error: (message, kind, extra) => {
|
|
1491
|
-
getDependencies().Log.error(message, kind, extra);
|
|
1492
|
-
}
|
|
1493
|
-
};
|
|
1494
|
-
|
|
1495
|
-
/**
|
|
1496
|
-
* Checks user input from the expression widget to see if it is scorable.
|
|
1497
|
-
*
|
|
1498
|
-
* Note: Most of the expression widget's validation requires the Rubric because
|
|
1499
|
-
* of its use of KhanAnswerTypes as a core part of scoring.
|
|
1500
|
-
*
|
|
1501
|
-
* @see `scoreExpression()` for more details.
|
|
1502
|
-
*/
|
|
1503
|
-
function validateExpression(userInput) {
|
|
1504
|
-
if (userInput === "") {
|
|
1505
|
-
return {
|
|
1506
|
-
type: "invalid",
|
|
1507
|
-
message: null
|
|
1508
|
-
};
|
|
1509
|
-
}
|
|
1510
|
-
return null;
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
/* Content creators input a list of answers which are matched from top to
|
|
1514
|
-
* bottom. The intent is that they can include spcific solutions which should
|
|
1515
|
-
* be graded as correct or incorrect (or ungraded!) first, then get more
|
|
1516
|
-
* general.
|
|
1517
|
-
*
|
|
1518
|
-
* We iterate through each answer, trying to match it with the user's input
|
|
1519
|
-
* using the following angorithm:
|
|
1520
|
-
* - Try to parse the user's input. If it doesn't parse then return "not
|
|
1521
|
-
* graded".
|
|
1522
|
-
* - For each answer:
|
|
1523
|
-
* ~ Try to validate the user's input against the answer. The answer is
|
|
1524
|
-
* expected to parse.
|
|
1525
|
-
* ~ If the user's input validates (the validator judges it "correct"), we've
|
|
1526
|
-
* matched and can stop considering answers.
|
|
1527
|
-
* - If there were no matches or the matching answer is considered "ungraded",
|
|
1528
|
-
* show the user an error. TODO(joel) - what error?
|
|
1529
|
-
* - Otherwise, pass through the resulting points and message.
|
|
1530
|
-
*/
|
|
1531
|
-
function scoreExpression(userInput, rubric, strings, locale) {
|
|
1532
|
-
const validationError = validateExpression(userInput);
|
|
1533
|
-
if (validationError) {
|
|
1534
|
-
return validationError;
|
|
1535
|
-
}
|
|
1536
|
-
const options = _.clone(rubric);
|
|
1537
|
-
_.extend(options, {
|
|
1538
|
-
decimal_separator: getDecimalSeparator(locale)
|
|
1539
|
-
});
|
|
1540
|
-
const createValidator = answer => {
|
|
1541
|
-
// We give options to KAS.parse here because it is parsing the
|
|
1542
|
-
// solution answer, not the student answer, and we don't want a
|
|
1543
|
-
// solution to work if the student is using a different language
|
|
1544
|
-
// (different from the content creation language, ie. English).
|
|
1545
|
-
const expression = KAS.parse(answer.value, rubric);
|
|
1546
|
-
// An answer may not be parsed if the expression was defined
|
|
1547
|
-
// incorrectly. For example if the answer is using a symbol defined
|
|
1548
|
-
// in the function variables list for the expression.
|
|
1549
|
-
if (!expression.parsed) {
|
|
1550
|
-
/* c8 ignore next */
|
|
1551
|
-
Log.error("Unable to parse solution answer for expression", Errors.InvalidInput, {
|
|
1552
|
-
loggedMetadata: {
|
|
1553
|
-
rubric: JSON.stringify(rubric)
|
|
1554
|
-
}
|
|
1555
|
-
});
|
|
1556
|
-
return null;
|
|
1557
|
-
}
|
|
1558
|
-
return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, _({}).extend(options, {
|
|
1559
|
-
simplify: answer.simplify,
|
|
1560
|
-
form: answer.form
|
|
1561
|
-
}));
|
|
1562
|
-
};
|
|
1563
|
-
|
|
1564
|
-
// Find the first answer form that matches the user's input and that
|
|
1565
|
-
// is considered correct. Also, track whether the input is
|
|
1566
|
-
// considered "empty" for all answer forms, and keep the validation
|
|
1567
|
-
// result for the first answer form for which the user's input was
|
|
1568
|
-
// considered "ungraded".
|
|
1569
|
-
// (Terminology reminder: the answer forms are provided by the
|
|
1570
|
-
// assessment items; they are not the user's input. Each one might
|
|
1571
|
-
// represent a correct answer, an incorrect one (if the exercise
|
|
1572
|
-
// creator has predicted certain common wrong answers and wants to
|
|
1573
|
-
// provide guidance via a message), or an ungraded one (same idea,
|
|
1574
|
-
// but without giving the user an incorrect mark for the question).
|
|
1575
|
-
let matchingAnswerForm;
|
|
1576
|
-
let matchMessage;
|
|
1577
|
-
let allEmpty = true;
|
|
1578
|
-
let firstUngradedResult;
|
|
1579
|
-
for (const answerForm of rubric.answerForms || []) {
|
|
1580
|
-
const validator = createValidator(answerForm);
|
|
1581
|
-
if (!validator) {
|
|
1582
|
-
continue;
|
|
1583
|
-
}
|
|
1584
|
-
const result = validator(userInput);
|
|
1585
|
-
|
|
1586
|
-
// Short-circuit as soon as the user's input matches some answer
|
|
1587
|
-
// (independently of whether the answer is correct)
|
|
1588
|
-
if (result.correct) {
|
|
1589
|
-
matchingAnswerForm = answerForm;
|
|
1590
|
-
matchMessage = result.message || "";
|
|
1591
|
-
break;
|
|
1592
|
-
}
|
|
1593
|
-
allEmpty = allEmpty && result.empty;
|
|
1594
|
-
// If this answer form is correct and the user's input is considered
|
|
1595
|
-
// "ungraded" for it, we'll want to keep the evaluation result for
|
|
1596
|
-
// later. If the user's input doesn't match any answer forms, we'll
|
|
1597
|
-
// show the message from this validation.
|
|
1598
|
-
if (answerForm.considered === "correct" && result.ungraded && !firstUngradedResult) {
|
|
1599
|
-
firstUngradedResult = result;
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
// Now check to see if we matched any answer form at all, and if
|
|
1604
|
-
// we did, whether it's considered correct, incorrect, or ungraded
|
|
1605
|
-
if (!matchingAnswerForm) {
|
|
1606
|
-
if (firstUngradedResult) {
|
|
1607
|
-
// While we didn't directly match with any answer form, we
|
|
1608
|
-
// did at some point get an "ungraded" validation result,
|
|
1609
|
-
// which might indicate e.g. a mismatch in variable casing.
|
|
1610
|
-
// We'll return "invalid", which will let the user try again
|
|
1611
|
-
// with no penalty, and the hopefully helpful validation
|
|
1612
|
-
// message.
|
|
1613
|
-
return {
|
|
1614
|
-
type: "invalid",
|
|
1615
|
-
message: firstUngradedResult.message,
|
|
1616
|
-
suppressAlmostThere: firstUngradedResult.suppressAlmostThere
|
|
1617
|
-
};
|
|
1618
|
-
}
|
|
1619
|
-
if (allEmpty) {
|
|
1620
|
-
// If everything graded as empty, it's invalid.
|
|
1621
|
-
return {
|
|
1622
|
-
type: "invalid",
|
|
1623
|
-
message: null
|
|
1624
|
-
};
|
|
1625
|
-
}
|
|
1626
|
-
// We fell through all the possibilities and we're not empty,
|
|
1627
|
-
// so the answer is considered incorrect.
|
|
1628
|
-
return {
|
|
1629
|
-
type: "points",
|
|
1630
|
-
earned: 0,
|
|
1631
|
-
total: 1
|
|
1632
|
-
};
|
|
1633
|
-
}
|
|
1634
|
-
if (matchingAnswerForm.considered === "ungraded") {
|
|
1635
|
-
return {
|
|
1636
|
-
type: "invalid",
|
|
1637
|
-
message: matchMessage
|
|
1638
|
-
};
|
|
1639
|
-
}
|
|
1640
|
-
// We matched a graded answer form, so we can now tell the user
|
|
1641
|
-
// whether their input was correct or incorrect, and hand out
|
|
1642
|
-
// points accordingly
|
|
1643
|
-
return {
|
|
1644
|
-
type: "points",
|
|
1645
|
-
earned: matchingAnswerForm.considered === "correct" ? 1 : 0,
|
|
1646
|
-
total: 1,
|
|
1647
|
-
message: matchMessage
|
|
1648
|
-
};
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
1507
|
// Map of international operator names to their English equivalents
|
|
1652
1508
|
const englishOperators = {
|
|
1653
1509
|
arctg: "arctan",
|
|
@@ -2408,56 +2264,6 @@ function constrainedTickStepsFromTickSteps(tickSteps, ranges) {
|
|
|
2408
2264
|
return [constrainTickStep(tickSteps[0], ranges[0]), constrainTickStep(tickSteps[1], ranges[1])];
|
|
2409
2265
|
}
|
|
2410
2266
|
|
|
2411
|
-
/**
|
|
2412
|
-
* Approximate equality on numbers and primitives.
|
|
2413
|
-
*/
|
|
2414
|
-
function eq$4(x, y) {
|
|
2415
|
-
if (typeof x === "number" && typeof y === "number") {
|
|
2416
|
-
return Math.abs(x - y) < 1e-9;
|
|
2417
|
-
}
|
|
2418
|
-
return x === y;
|
|
2419
|
-
}
|
|
2420
|
-
|
|
2421
|
-
/**
|
|
2422
|
-
* Deep approximate equality on primitives, numbers, arrays, and objects.
|
|
2423
|
-
* Recursive.
|
|
2424
|
-
*/
|
|
2425
|
-
function deepEq$7(x, y) {
|
|
2426
|
-
if (Array.isArray(x) && Array.isArray(y)) {
|
|
2427
|
-
if (x.length !== y.length) {
|
|
2428
|
-
return false;
|
|
2429
|
-
}
|
|
2430
|
-
for (let i = 0; i < x.length; i++) {
|
|
2431
|
-
if (!deepEq$7(x[i], y[i])) {
|
|
2432
|
-
return false;
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
return true;
|
|
2436
|
-
}
|
|
2437
|
-
if (Array.isArray(x) || Array.isArray(y)) {
|
|
2438
|
-
return false;
|
|
2439
|
-
}
|
|
2440
|
-
if (typeof x === "function" && typeof y === "function") {
|
|
2441
|
-
return eq$4(x, y);
|
|
2442
|
-
}
|
|
2443
|
-
if (typeof x === "function" || typeof y === "function") {
|
|
2444
|
-
return false;
|
|
2445
|
-
}
|
|
2446
|
-
if (typeof x === "object" && typeof y === "object" && !!x && !!y) {
|
|
2447
|
-
return x === y || _.all(x, function (v, k) {
|
|
2448
|
-
// @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
|
|
2449
|
-
return deepEq$7(y[k], v);
|
|
2450
|
-
}) && _.all(y, function (v, k) {
|
|
2451
|
-
// @ts-expect-error - TS2536 - Type 'CollectionKey<T>' cannot be used to index type 'T'.
|
|
2452
|
-
return deepEq$7(x[k], v);
|
|
2453
|
-
});
|
|
2454
|
-
}
|
|
2455
|
-
if (typeof x === "object" && !!x || typeof y === "object" && !!y) {
|
|
2456
|
-
return false;
|
|
2457
|
-
}
|
|
2458
|
-
return eq$4(x, y);
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2461
2267
|
/**
|
|
2462
2268
|
* Query String Parser
|
|
2463
2269
|
*
|
|
@@ -2674,15 +2480,6 @@ const textarea = {
|
|
|
2674
2480
|
*/
|
|
2675
2481
|
const unescapeMathMode$1 = label => label.startsWith("$") && label.endsWith("$") ? label.slice(1, -1) : label;
|
|
2676
2482
|
const random$1 = seededRNG$1(new Date().getTime() & 0xffffffff);
|
|
2677
|
-
|
|
2678
|
-
// TODO(benchristel): in the future, we may want to make deepClone work for
|
|
2679
|
-
// Record<string, Cloneable> as well. Currently, it only does arrays.
|
|
2680
|
-
function deepClone(obj) {
|
|
2681
|
-
if (Array.isArray(obj)) {
|
|
2682
|
-
return obj.map(deepClone);
|
|
2683
|
-
}
|
|
2684
|
-
return obj;
|
|
2685
|
-
}
|
|
2686
2483
|
const Util = {
|
|
2687
2484
|
inputPathsEqual,
|
|
2688
2485
|
nestedMap: nestedMap$2,
|
|
@@ -2703,8 +2500,6 @@ const Util = {
|
|
|
2703
2500
|
gridStepFromTickStep,
|
|
2704
2501
|
tickStepFromNumTicks,
|
|
2705
2502
|
constrainedTickStepsFromTickSteps,
|
|
2706
|
-
eq: eq$4,
|
|
2707
|
-
deepEq: deepEq$7,
|
|
2708
2503
|
parseQueryString,
|
|
2709
2504
|
updateQueryString: updateQueryString$2,
|
|
2710
2505
|
strongEncodeURIComponent,
|
|
@@ -2722,8 +2517,24 @@ const Util = {
|
|
|
2722
2517
|
getDataUrl: getDataUrl,
|
|
2723
2518
|
textarea,
|
|
2724
2519
|
unescapeMathMode: unescapeMathMode$1,
|
|
2725
|
-
random: random$1
|
|
2726
|
-
|
|
2520
|
+
random: random$1
|
|
2521
|
+
};
|
|
2522
|
+
|
|
2523
|
+
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
2524
|
+
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
2525
|
+
// which get's the injected logger.
|
|
2526
|
+
/**
|
|
2527
|
+
* Provides logging infrastructure for Perseus
|
|
2528
|
+
*/
|
|
2529
|
+
const Log = {
|
|
2530
|
+
// Logs a message
|
|
2531
|
+
log: (message, extra) => {
|
|
2532
|
+
getDependencies().Log.log(message, extra);
|
|
2533
|
+
},
|
|
2534
|
+
// Logs an error with a message
|
|
2535
|
+
error: (message, kind, extra) => {
|
|
2536
|
+
getDependencies().Log.error(message, kind, extra);
|
|
2537
|
+
}
|
|
2727
2538
|
};
|
|
2728
2539
|
|
|
2729
2540
|
// For offline exercises in the mobile app, we download the graphie data
|
|
@@ -8271,7 +8082,7 @@ var Tex$1 = {
|
|
|
8271
8082
|
const {
|
|
8272
8083
|
processMath: processMath$1
|
|
8273
8084
|
} = Tex$1;
|
|
8274
|
-
function polar$
|
|
8085
|
+
function polar$2(r, th) {
|
|
8275
8086
|
if (typeof r === "number") {
|
|
8276
8087
|
r = [r, r];
|
|
8277
8088
|
}
|
|
@@ -8430,7 +8241,7 @@ class Graphie$1 {
|
|
|
8430
8241
|
// See: http://stackoverflow.com/questions/13932704/how-to-draw-sine-waves-with-svg-js
|
|
8431
8242
|
const xCoords = [x0, x0 * 2 / 3 + x1 * 1 / 3, x0 * 1 / 3 + x1 * 2 / 3, x1];
|
|
8432
8243
|
const yCoords = [computeSine(x0), computeSine(x0) + computeDerivative(x0) * (x1 - x0) / 3, computeSine(x1) - computeDerivative(x1) * (x1 - x0) / 3, computeSine(x1)];
|
|
8433
|
-
const points = vector$
|
|
8244
|
+
const points = vector$3.zip(xCoords, yCoords);
|
|
8434
8245
|
return points.map(this.scalePoint);
|
|
8435
8246
|
};
|
|
8436
8247
|
|
|
@@ -8763,8 +8574,8 @@ class Graphie$1 {
|
|
|
8763
8574
|
endAngle = (endAngle % 360 + 360) % 360;
|
|
8764
8575
|
const cent = this.scalePoint(center);
|
|
8765
8576
|
const radii = this.scaleVector(radius);
|
|
8766
|
-
const startVector = polar$
|
|
8767
|
-
const endVector = polar$
|
|
8577
|
+
const startVector = polar$2(radius, startAngle);
|
|
8578
|
+
const endVector = polar$2(radius, endAngle);
|
|
8768
8579
|
|
|
8769
8580
|
// We round the coordinates to make testing easier, because trig
|
|
8770
8581
|
// operations return unround numbers.
|
|
@@ -8869,7 +8680,7 @@ class Graphie$1 {
|
|
|
8869
8680
|
// Apply padding and offset to points to convert from
|
|
8870
8681
|
// canvas coordinates to pixel coordinates relative to bounding box
|
|
8871
8682
|
points = points.map(function (point) {
|
|
8872
|
-
return vector$
|
|
8683
|
+
return vector$3.add(vector$3.subtract(point, topLeftOfBoundingBox), vector$3.scale(padding, 0.5));
|
|
8873
8684
|
});
|
|
8874
8685
|
|
|
8875
8686
|
// Calculate <div> dimensions
|
|
@@ -8933,8 +8744,8 @@ class Graphie$1 {
|
|
|
8933
8744
|
const extraOffset = [Math.min(start[0], end[0]), Math.min(start[1], end[1])];
|
|
8934
8745
|
|
|
8935
8746
|
// Apply padding and offset to start, end points
|
|
8936
|
-
start = vector$
|
|
8937
|
-
end = vector$
|
|
8747
|
+
start = vector$3.add(vector$3.subtract(start, extraOffset), vector$3.scale(padding, 0.5));
|
|
8748
|
+
end = vector$3.add(vector$3.subtract(end, extraOffset), vector$3.scale(padding, 0.5));
|
|
8938
8749
|
|
|
8939
8750
|
// Calculate <div> dimensions
|
|
8940
8751
|
const left = extraOffset[0] - padding[0] / 2;
|
|
@@ -9890,10 +9701,10 @@ const getClipPoint = function getClipPoint(graph, coord, angle) {
|
|
|
9890
9701
|
// shoot a point off into the distance ...
|
|
9891
9702
|
const distance = xExtent + yExtent;
|
|
9892
9703
|
// we need to scale the point according to the scale of the axes
|
|
9893
|
-
const angleVec = graph.unscaleVector(vector$
|
|
9894
|
-
const distVec = vector$
|
|
9895
|
-
const farCoord = vector$
|
|
9896
|
-
const scaledAngle = vector$
|
|
9704
|
+
const angleVec = graph.unscaleVector(vector$3.cartFromPolarDeg(1, angle));
|
|
9705
|
+
const distVec = vector$3.scale(vector$3.normalize(angleVec), distance);
|
|
9706
|
+
const farCoord = vector$3.add(coord, distVec);
|
|
9707
|
+
const scaledAngle = vector$3.polarDegFromCart(angleVec)[1];
|
|
9897
9708
|
// ... and then bring it back
|
|
9898
9709
|
const clipPoint = graph.constrainToBoundsOnAngle(farCoord, 4, scaledAngle * Math.PI / 180);
|
|
9899
9710
|
return clipPoint;
|
|
@@ -9944,7 +9755,7 @@ class WrappedDrawing {
|
|
|
9944
9755
|
return this.visibleShape[0];
|
|
9945
9756
|
}
|
|
9946
9757
|
moveTo(point) {
|
|
9947
|
-
const delta = vector$
|
|
9758
|
+
const delta = vector$3.subtract(this.graphie.scalePoint(point), this.graphie.scalePoint(this.initialPoint));
|
|
9948
9759
|
const do3dTransform = InteractiveUtil.getCanUse3dTransform();
|
|
9949
9760
|
const transformation = "translateX(" + delta[0] + "px) " + "translateY(" + delta[1] + "px)" + (do3dTransform ? " translateZ(0)" : "");
|
|
9950
9761
|
this.transform(transformation);
|
|
@@ -9999,9 +9810,9 @@ class Arrowhead$1 extends WrappedPath {
|
|
|
9999
9810
|
const center = [0.75, 0];
|
|
10000
9811
|
const points = [[-3, 4], [-2.75, 2.5], [0, 0.25], center, [0, -0.25], [-2.75, -2.5], [-3, -4]].map(point => {
|
|
10001
9812
|
// Scale points by `Arrowhead.scale` around `center`
|
|
10002
|
-
const pv = vector$
|
|
10003
|
-
const pvScaled = vector$
|
|
10004
|
-
return vector$
|
|
9813
|
+
const pv = vector$3.subtract(point, center);
|
|
9814
|
+
const pvScaled = vector$3.scale(pv, Arrowhead$1.scale);
|
|
9815
|
+
return vector$3.add(center, pvScaled);
|
|
10005
9816
|
});
|
|
10006
9817
|
const unscaledPoints = points.map(graphie.unscalePoint);
|
|
10007
9818
|
super(graphie, unscaledPoints, {
|
|
@@ -10072,7 +9883,7 @@ class WrappedEllipse extends WrappedDrawing {
|
|
|
10072
9883
|
const wrapper = this.wrapper;
|
|
10073
9884
|
wrapper.style.filter = filter;
|
|
10074
9885
|
this.moveTo = function (point) {
|
|
10075
|
-
const delta = vector$
|
|
9886
|
+
const delta = vector$3.subtract(this.graphie.scalePoint(point), this.graphie.scalePoint(this.initialPoint));
|
|
10076
9887
|
const do3dTransform = InteractiveUtil.getCanUse3dTransform();
|
|
10077
9888
|
const transform = "translateX(" + Math.round(delta[0]) + "px) " + "translateY(" + Math.round(delta[1]) + "px)" + (do3dTransform ? " translateZ(0)" : "");
|
|
10078
9889
|
this.transform(transform);
|
|
@@ -10141,10 +9952,10 @@ class WrappedLine extends WrappedDrawing {
|
|
|
10141
9952
|
const scaledEnd = this.graphie.scalePoint(end);
|
|
10142
9953
|
|
|
10143
9954
|
// Compute transformation parameters
|
|
10144
|
-
const polarDiff = vector$
|
|
9955
|
+
const polarDiff = vector$3.polarDegFromCart(vector$3.subtract(scaledEnd, scaledStart));
|
|
10145
9956
|
const lineLength = polarDiff[0];
|
|
10146
9957
|
const angle = KhanMath.bound(polarDiff[1]);
|
|
10147
|
-
const delta = vector$
|
|
9958
|
+
const delta = vector$3.subtract(scaledStart, this.initialPoint);
|
|
10148
9959
|
const scale = KhanMath.bound(lineLength / this.initialLength);
|
|
10149
9960
|
|
|
10150
9961
|
// Construct and apply transformation string
|
|
@@ -10154,260 +9965,11 @@ class WrappedLine extends WrappedDrawing {
|
|
|
10154
9965
|
}
|
|
10155
9966
|
}
|
|
10156
9967
|
|
|
10157
|
-
/**
|
|
10158
|
-
* A collection of geomtry-related utility functions
|
|
10159
|
-
*/
|
|
10160
|
-
const {
|
|
10161
|
-
eq: eq$3,
|
|
10162
|
-
deepEq: deepEq$6
|
|
10163
|
-
} = Util;
|
|
10164
|
-
|
|
10165
|
-
// This should really be a readonly tuple of [number, number]
|
|
10166
|
-
|
|
10167
|
-
// a, b, c
|
|
10168
|
-
|
|
10169
|
-
// Given a number, return whether it is positive (1), negative (-1), or zero (0)
|
|
10170
|
-
function sign(val) {
|
|
10171
|
-
if (eq$3(val, 0)) {
|
|
10172
|
-
return 0;
|
|
10173
|
-
}
|
|
10174
|
-
return val > 0 ? 1 : -1;
|
|
10175
|
-
}
|
|
10176
|
-
|
|
10177
|
-
// Determine whether three points are collinear (0), for a clockwise turn (negative),
|
|
10178
|
-
// or counterclockwise turn (positive)
|
|
10179
|
-
function ccw(a, b, c) {
|
|
10180
|
-
return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
|
|
10181
|
-
}
|
|
10182
|
-
function collinear(a, b, c) {
|
|
10183
|
-
return eq$3(ccw(a, b, c), 0);
|
|
10184
|
-
}
|
|
10185
|
-
|
|
10186
|
-
// Given rect bounding points A and B, whether point C is inside the rect
|
|
10187
|
-
function pointInRect(a, b, c) {
|
|
10188
|
-
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]);
|
|
10189
|
-
}
|
|
10190
|
-
|
|
10191
|
-
// Whether line segment AB intersects line segment CD
|
|
10192
|
-
// http://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
|
|
10193
|
-
function intersects(ab, cd) {
|
|
10194
|
-
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]]];
|
|
10195
|
-
const orientations = _.map(triplets, function (triplet) {
|
|
10196
|
-
return sign(ccw(...triplet));
|
|
10197
|
-
});
|
|
10198
|
-
if (orientations[0] !== orientations[1] && orientations[2] !== orientations[3]) {
|
|
10199
|
-
return true;
|
|
10200
|
-
}
|
|
10201
|
-
for (let i = 0; i < 4; i++) {
|
|
10202
|
-
if (orientations[i] === 0 && pointInRect(...triplets[i])) {
|
|
10203
|
-
return true;
|
|
10204
|
-
}
|
|
10205
|
-
}
|
|
10206
|
-
return false;
|
|
10207
|
-
}
|
|
10208
|
-
|
|
10209
|
-
// Whether any two sides of a polygon intersect each other
|
|
10210
|
-
function polygonSidesIntersect(vertices) {
|
|
10211
|
-
for (let i = 0; i < vertices.length; i++) {
|
|
10212
|
-
for (let k = i + 1; k < vertices.length; k++) {
|
|
10213
|
-
// If any two vertices are the same point, sides overlap
|
|
10214
|
-
if (point.equal(vertices[i], vertices[k])) {
|
|
10215
|
-
return true;
|
|
10216
|
-
}
|
|
10217
|
-
|
|
10218
|
-
// Find the other end of the sides starting at vertices i and k
|
|
10219
|
-
const iNext = (i + 1) % vertices.length;
|
|
10220
|
-
const kNext = (k + 1) % vertices.length;
|
|
10221
|
-
|
|
10222
|
-
// Adjacent sides always intersect (at the vertex); skip those
|
|
10223
|
-
if (iNext === k || kNext === i) {
|
|
10224
|
-
continue;
|
|
10225
|
-
}
|
|
10226
|
-
const side1 = [vertices[i], vertices[iNext]];
|
|
10227
|
-
const side2 = [vertices[k], vertices[kNext]];
|
|
10228
|
-
if (intersects(side1, side2)) {
|
|
10229
|
-
return true;
|
|
10230
|
-
}
|
|
10231
|
-
}
|
|
10232
|
-
}
|
|
10233
|
-
return false;
|
|
10234
|
-
}
|
|
10235
|
-
function vector(a, b) {
|
|
10236
|
-
return _.map(_.zip(a, b), function (pair) {
|
|
10237
|
-
return pair[0] - pair[1];
|
|
10238
|
-
});
|
|
10239
|
-
}
|
|
10240
|
-
function reverseVector(vector) {
|
|
10241
|
-
return [-vector[0], -vector[1]];
|
|
10242
|
-
}
|
|
10243
|
-
|
|
10244
|
-
// Returns whether connecting the given sequence of `points` forms a clockwise
|
|
10245
|
-
// path (assuming a closed loop, where the last point connects back to the
|
|
10246
|
-
// first).
|
|
10247
|
-
function clockwise$1(points) {
|
|
10248
|
-
const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
|
|
10249
|
-
const areas = _.map(segments, function (segment) {
|
|
10250
|
-
const p1 = segment[0];
|
|
10251
|
-
const p2 = segment[1];
|
|
10252
|
-
return (p2[0] - p1[0]) * (p2[1] + p1[1]);
|
|
10253
|
-
});
|
|
10254
|
-
return sum$1(areas) > 0;
|
|
10255
|
-
}
|
|
10256
|
-
function magnitude(v) {
|
|
10257
|
-
return Math.sqrt(_.reduce(v, function (memo, el) {
|
|
10258
|
-
// @ts-expect-error - TS2345 - Argument of type 'Coord' is not assignable to parameter of type 'number'.
|
|
10259
|
-
return memo + Math.pow(el, 2);
|
|
10260
|
-
}, 0));
|
|
10261
|
-
}
|
|
10262
|
-
function dotProduct(a, b) {
|
|
10263
|
-
return _.reduce(_.zip(a, b), function (memo, pair) {
|
|
10264
|
-
return memo + pair[0] * pair[1];
|
|
10265
|
-
}, 0);
|
|
10266
|
-
}
|
|
10267
|
-
function sideLengths(coords) {
|
|
10268
|
-
const segments = _.zip(coords, rotate(coords));
|
|
10269
|
-
return segments.map(function (segment) {
|
|
10270
|
-
// @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.
|
|
10271
|
-
return magnitude(vector(...segment));
|
|
10272
|
-
});
|
|
10273
|
-
}
|
|
10274
|
-
|
|
10275
|
-
// Based on http://math.stackexchange.com/a/151149
|
|
10276
|
-
function angleMeasures(coords) {
|
|
10277
|
-
const triplets = _.zip(rotate(coords, -1), coords, rotate(coords, 1));
|
|
10278
|
-
const offsets = _.map(triplets, function (triplet) {
|
|
10279
|
-
const p = vector(triplet[1], triplet[0]);
|
|
10280
|
-
const q = vector(triplet[2], triplet[1]);
|
|
10281
|
-
// @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[]'.
|
|
10282
|
-
const raw = Math.acos(dotProduct(p, q) / (magnitude(p) * magnitude(q)));
|
|
10283
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
10284
|
-
return sign(ccw(...triplet)) > 0 ? raw : -raw;
|
|
10285
|
-
});
|
|
10286
|
-
const sum = _.reduce(offsets, function (memo, arg) {
|
|
10287
|
-
return memo + arg;
|
|
10288
|
-
}, 0);
|
|
10289
|
-
return _.map(offsets, function (offset) {
|
|
10290
|
-
return sum > 0 ? Math.PI - offset : Math.PI + offset;
|
|
10291
|
-
});
|
|
10292
|
-
}
|
|
10293
|
-
|
|
10294
|
-
// Whether two polygons are similar (or if specified, congruent)
|
|
10295
|
-
function similar(coords1, coords2, tolerance) {
|
|
10296
|
-
if (coords1.length !== coords2.length) {
|
|
10297
|
-
return false;
|
|
10298
|
-
}
|
|
10299
|
-
const n = coords1.length;
|
|
10300
|
-
const angles1 = angleMeasures(coords1);
|
|
10301
|
-
const angles2 = angleMeasures(coords2);
|
|
10302
|
-
const sides1 = sideLengths(coords1);
|
|
10303
|
-
const sides2 = sideLengths(coords2);
|
|
10304
|
-
for (let i = 0; i < 2 * n; i++) {
|
|
10305
|
-
let angles = angles2.slice();
|
|
10306
|
-
let sides = sides2.slice();
|
|
10307
|
-
|
|
10308
|
-
// Reverse angles and sides to allow matching reflected polygons
|
|
10309
|
-
if (i >= n) {
|
|
10310
|
-
angles.reverse();
|
|
10311
|
-
sides.reverse();
|
|
10312
|
-
// Since sides are calculated from two coordinates,
|
|
10313
|
-
// simply reversing results in an off by one error
|
|
10314
|
-
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
10315
|
-
sides = rotate(sides, 1);
|
|
10316
|
-
}
|
|
10317
|
-
|
|
10318
|
-
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
10319
|
-
angles = rotate(angles, i);
|
|
10320
|
-
// @ts-expect-error - TS4104 - The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.
|
|
10321
|
-
sides = rotate(sides, i);
|
|
10322
|
-
if (deepEq$6(angles1, angles)) {
|
|
10323
|
-
const sidePairs = _.zip(sides1, sides);
|
|
10324
|
-
const factors = _.map(sidePairs, function (pair) {
|
|
10325
|
-
return pair[0] / pair[1];
|
|
10326
|
-
});
|
|
10327
|
-
const same = _.all(factors, function (factor) {
|
|
10328
|
-
return eq$3(factors[0], factor);
|
|
10329
|
-
});
|
|
10330
|
-
const congruentEnough = _.all(sidePairs, function (pair) {
|
|
10331
|
-
return number$1.equal(pair[0], pair[1], tolerance);
|
|
10332
|
-
});
|
|
10333
|
-
if (same && congruentEnough) {
|
|
10334
|
-
return true;
|
|
10335
|
-
}
|
|
10336
|
-
}
|
|
10337
|
-
}
|
|
10338
|
-
return false;
|
|
10339
|
-
}
|
|
10340
|
-
|
|
10341
|
-
// Given triangle with sides ABC return angle opposite side C in degrees
|
|
10342
|
-
function lawOfCosines(a, b, c) {
|
|
10343
|
-
return Math.acos((a * a + b * b - c * c) / (2 * a * b)) * 180 / Math.PI;
|
|
10344
|
-
}
|
|
10345
|
-
function canonicalSineCoefficients$1([amplitude, angularFrequency, phase, verticalOffset]) {
|
|
10346
|
-
// For a curve of the form f(x) = a * Sin(b * x - c) + d,
|
|
10347
|
-
// this function ensures that a, b > 0, and c is its
|
|
10348
|
-
// smallest possible positive value.
|
|
10349
|
-
|
|
10350
|
-
// Guarantee a > 0
|
|
10351
|
-
if (amplitude < 0) {
|
|
10352
|
-
amplitude *= -1;
|
|
10353
|
-
angularFrequency *= -1;
|
|
10354
|
-
phase *= -1;
|
|
10355
|
-
}
|
|
10356
|
-
const period = 2 * Math.PI;
|
|
10357
|
-
// Guarantee b > 0
|
|
10358
|
-
if (angularFrequency < 0) {
|
|
10359
|
-
angularFrequency *= -1;
|
|
10360
|
-
phase *= -1;
|
|
10361
|
-
phase += period / 2;
|
|
10362
|
-
}
|
|
10363
|
-
|
|
10364
|
-
// Guarantee c is smallest possible positive value
|
|
10365
|
-
while (phase > 0) {
|
|
10366
|
-
phase -= period;
|
|
10367
|
-
}
|
|
10368
|
-
while (phase < 0) {
|
|
10369
|
-
phase += period;
|
|
10370
|
-
}
|
|
10371
|
-
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
10372
|
-
}
|
|
10373
|
-
|
|
10374
|
-
// e.g. rotate([1, 2, 3]) -> [2, 3, 1]
|
|
10375
|
-
function rotate(array, n) {
|
|
10376
|
-
n = typeof n === "undefined" ? 1 : n % array.length;
|
|
10377
|
-
return array.slice(n).concat(array.slice(0, n));
|
|
10378
|
-
}
|
|
10379
|
-
function getLineEquation(first, second) {
|
|
10380
|
-
if (eq$3(first[0], second[0])) {
|
|
10381
|
-
return "x = " + first[0].toFixed(3);
|
|
10382
|
-
}
|
|
10383
|
-
const m = (second[1] - first[1]) / (second[0] - first[0]);
|
|
10384
|
-
const b = first[1] - m * first[0];
|
|
10385
|
-
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
10386
|
-
}
|
|
10387
|
-
|
|
10388
|
-
// Stolen from the wikipedia article
|
|
10389
|
-
// http://en.wikipedia.org/wiki/Line-line_intersection
|
|
10390
|
-
function getLineIntersection(
|
|
10391
|
-
// TODO(LP-10725): update these to be 2-tuples
|
|
10392
|
-
firstPoints, secondPoints) {
|
|
10393
|
-
const x1 = firstPoints[0][0];
|
|
10394
|
-
const y1 = firstPoints[0][1];
|
|
10395
|
-
const x2 = firstPoints[1][0];
|
|
10396
|
-
const y2 = firstPoints[1][1];
|
|
10397
|
-
const x3 = secondPoints[0][0];
|
|
10398
|
-
const y3 = secondPoints[0][1];
|
|
10399
|
-
const x4 = secondPoints[1][0];
|
|
10400
|
-
const y4 = secondPoints[1][1];
|
|
10401
|
-
const determinant = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
|
|
10402
|
-
if (Math.abs(determinant) < 1e-9) {
|
|
10403
|
-
return "Lines are parallel";
|
|
10404
|
-
}
|
|
10405
|
-
const x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / determinant;
|
|
10406
|
-
const y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / determinant;
|
|
10407
|
-
return "Intersection: (" + x.toFixed(3) + ", " + y.toFixed(3) + ")";
|
|
10408
|
-
}
|
|
10409
|
-
|
|
10410
9968
|
/* eslint-disable @babel/no-invalid-this */
|
|
9969
|
+
const {
|
|
9970
|
+
clockwise: clockwise$3,
|
|
9971
|
+
reverseVector: reverseVector$1
|
|
9972
|
+
} = geometry;
|
|
10411
9973
|
function scaledDistanceFromAngle(angle) {
|
|
10412
9974
|
const a = 3.51470560176242 * 20;
|
|
10413
9975
|
const b = 0.5687298702748785 * 20;
|
|
@@ -10476,10 +10038,10 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10476
10038
|
let sRadius = sPadding + scaledDistanceFromAngle(angle);
|
|
10477
10039
|
const temp = [];
|
|
10478
10040
|
if (Math.abs(angle - 90) < 1e-9 && options.showRightAngleMarker) {
|
|
10479
|
-
const v1 = vector$
|
|
10480
|
-
const v2 = vector$
|
|
10041
|
+
const v1 = vector$3.add(sVertex, scaledPolarDeg(sRadius, startAngle));
|
|
10042
|
+
const v2 = vector$3.add(sVertex, scaledPolarDeg(sRadius, endAngle));
|
|
10481
10043
|
sRadius *= Math.SQRT2;
|
|
10482
|
-
const v3 = vector$
|
|
10044
|
+
const v3 = vector$3.add(sVertex, scaledPolarDeg(sRadius, halfAngle));
|
|
10483
10045
|
_.each([v1, v2], function (v) {
|
|
10484
10046
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
10485
10047
|
temp.push(graphie.scaledPath([v, v3], options.style));
|
|
@@ -10500,7 +10062,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10500
10062
|
text = text.replace(match[0], KhanMath.toFixedApprox(angle, precision) + "^{\\circ}");
|
|
10501
10063
|
}
|
|
10502
10064
|
const sOffset = scaledPolarDeg(sRadius + 15, halfAngle);
|
|
10503
|
-
const sPosition = vector$
|
|
10065
|
+
const sPosition = vector$3.add(sVertex, sOffset);
|
|
10504
10066
|
const position = graphie.unscalePoint(sPosition);
|
|
10505
10067
|
|
|
10506
10068
|
// Reuse label if possible
|
|
@@ -10551,7 +10113,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10551
10113
|
const sOffset = sSpacing * (i - (n - 1) / 2);
|
|
10552
10114
|
const sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
|
|
10553
10115
|
const sHeightVector = scaledPolarRad(sHeight, perpendicularAngle);
|
|
10554
|
-
const sPath = [vector$
|
|
10116
|
+
const sPath = [vector$3.add(sMidpoint, sOffsetVector, sHeightVector), vector$3.add(sMidpoint, sOffsetVector, reverseVector$1(sHeightVector))];
|
|
10555
10117
|
|
|
10556
10118
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
10557
10119
|
temp.push(graphie.scaledPath(sPath, style));
|
|
@@ -10576,9 +10138,9 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10576
10138
|
const sOffset = sCumulativeOffset + sSpacing * i;
|
|
10577
10139
|
let sOffsetVector = scaledPolarRad(sOffset, parallelAngle);
|
|
10578
10140
|
if (start !== p1) {
|
|
10579
|
-
sOffsetVector = reverseVector(sOffsetVector);
|
|
10141
|
+
sOffsetVector = reverseVector$1(sOffsetVector);
|
|
10580
10142
|
}
|
|
10581
|
-
const sEnd = vector$
|
|
10143
|
+
const sEnd = vector$3.add(sMidpoint, sOffsetVector);
|
|
10582
10144
|
|
|
10583
10145
|
// @ts-expect-error - TS2345 - Argument of type 'any' is not assignable to parameter of type 'never'.
|
|
10584
10146
|
temp.push(graphie.scaledPath([sStart, sEnd], style));
|
|
@@ -10594,7 +10156,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10594
10156
|
}
|
|
10595
10157
|
const sOffset = 20;
|
|
10596
10158
|
const sOffsetVector = scaledPolarRad(sOffset, perpendicularAngle);
|
|
10597
|
-
const sPosition = vector$
|
|
10159
|
+
const sPosition = vector$3.add(sMidpoint, sOffsetVector);
|
|
10598
10160
|
const position = graphie.unscalePoint(sPosition);
|
|
10599
10161
|
|
|
10600
10162
|
// Reuse label if possible
|
|
@@ -10652,7 +10214,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10652
10214
|
}
|
|
10653
10215
|
const sRadius = 10 + scaledDistanceFromAngle(360 - angle);
|
|
10654
10216
|
const sOffsetVector = scaledPolarDeg(sRadius, halfAngle);
|
|
10655
|
-
const sPosition = vector$
|
|
10217
|
+
const sPosition = vector$3.add(sVertex, sOffsetVector);
|
|
10656
10218
|
const position = graphie.unscalePoint(sPosition);
|
|
10657
10219
|
|
|
10658
10220
|
// Reuse label if possible
|
|
@@ -10928,7 +10490,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
10928
10490
|
|
|
10929
10491
|
// Adjust the target coordinate by accounting for the gesture's
|
|
10930
10492
|
// offset.
|
|
10931
|
-
let coord = vector$
|
|
10493
|
+
let coord = vector$3.add(graph.getMouseCoord(event), offset);
|
|
10932
10494
|
coord = applySnapAndConstraints(coord);
|
|
10933
10495
|
let coordX = coord[0];
|
|
10934
10496
|
let coordY = coord[1];
|
|
@@ -11039,7 +10601,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
11039
10601
|
const startCoord = movablePoint.coord;
|
|
11040
10602
|
const startMouseCoord = graph.getMouseCoord(event);
|
|
11041
10603
|
const isMouse = !("ontouchstart" in window);
|
|
11042
|
-
const touchOffset = isMouse ? [0, 0] : vector$
|
|
10604
|
+
const touchOffset = isMouse ? [0, 0] : vector$3.subtract(startCoord, startMouseCoord);
|
|
11043
10605
|
movablePoint.grab(touchOffset);
|
|
11044
10606
|
}
|
|
11045
10607
|
});
|
|
@@ -11259,17 +10821,17 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
11259
10821
|
const getScaledAngle = function getScaledAngle(line) {
|
|
11260
10822
|
const scaledA = line.graph.scalePoint(line.coordA);
|
|
11261
10823
|
const scaledZ = line.graph.scalePoint(line.coordZ);
|
|
11262
|
-
return vector$
|
|
10824
|
+
return vector$3.polarDegFromCart(vector$3.subtract(scaledZ, scaledA))[1];
|
|
11263
10825
|
};
|
|
11264
10826
|
const getClipPoint = function getClipPoint(graph, coord, angle) {
|
|
11265
10827
|
graph = lineSegment.graph;
|
|
11266
10828
|
const xExtent = graph.range[0][1] - graph.range[0][0];
|
|
11267
10829
|
const yExtent = graph.range[1][1] - graph.range[1][0];
|
|
11268
10830
|
const distance = xExtent + yExtent;
|
|
11269
|
-
const angleVec = graph.unscaleVector(vector$
|
|
11270
|
-
const distVec = vector$
|
|
11271
|
-
const farCoord = vector$
|
|
11272
|
-
const scaledAngle = vector$
|
|
10831
|
+
const angleVec = graph.unscaleVector(vector$3.cartFromPolarDeg(1, angle));
|
|
10832
|
+
const distVec = vector$3.scale(vector$3.normalize(angleVec), distance);
|
|
10833
|
+
const farCoord = vector$3.add(coord, distVec);
|
|
10834
|
+
const scaledAngle = vector$3.polarDegFromCart(angleVec)[1];
|
|
11273
10835
|
const clipPoint = graph.constrainToBoundsOnAngle(farCoord, 4, scaledAngle * Math.PI / 180);
|
|
11274
10836
|
return clipPoint;
|
|
11275
10837
|
};
|
|
@@ -11643,7 +11205,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
11643
11205
|
// Temporary objects
|
|
11644
11206
|
_.invoke(polygon.temp, "remove");
|
|
11645
11207
|
polygon.temp = [];
|
|
11646
|
-
const isClockwise = clockwise$
|
|
11208
|
+
const isClockwise = clockwise$3(polygon.coords);
|
|
11647
11209
|
|
|
11648
11210
|
// Update angle labels
|
|
11649
11211
|
if (polygon.angleLabels.length || polygon.showRightAngleMarkers.length) {
|
|
@@ -12171,8 +11733,8 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12171
11733
|
const drawRotateHandle = function drawRotateHandle(graphie, center, radius, halfWidth, lengthAngle, angle, interacting) {
|
|
12172
11734
|
const getRotateHandlePoint = function getRotateHandlePoint(offset, distanceFromArrowMidline) {
|
|
12173
11735
|
const distFromRotationCenter = radius + distanceFromArrowMidline;
|
|
12174
|
-
const vec = vector$
|
|
12175
|
-
const absolute = vector$
|
|
11736
|
+
const vec = vector$3.cartFromPolarDeg(distFromRotationCenter, angle + offset);
|
|
11737
|
+
const absolute = vector$3.add(center, vec);
|
|
12176
11738
|
const pixels = graphie.scalePoint(absolute);
|
|
12177
11739
|
return pixels[0] + "," + pixels[1];
|
|
12178
11740
|
};
|
|
@@ -12212,7 +11774,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12212
11774
|
};
|
|
12213
11775
|
}
|
|
12214
11776
|
const rotateHandle = graph.addMovablePoint({
|
|
12215
|
-
coord: point.addVector(rotatePoint.coord, vector$
|
|
11777
|
+
coord: point.addVector(rotatePoint.coord, vector$3.cartFromPolarDeg(radius, options.angleDeg || 0)),
|
|
12216
11778
|
constraints: {
|
|
12217
11779
|
fixedDistance: {
|
|
12218
11780
|
dist: radius,
|
|
@@ -12237,8 +11799,8 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12237
11799
|
return; // Don't draw anything!
|
|
12238
11800
|
}
|
|
12239
11801
|
|
|
12240
|
-
const handleVec = vector$
|
|
12241
|
-
const handlePolar = vector$
|
|
11802
|
+
const handleVec = vector$3.subtract(handleCoord, rotatePoint.coord);
|
|
11803
|
+
const handlePolar = vector$3.polarDegFromCart(handleVec);
|
|
12242
11804
|
const angle = handlePolar[1];
|
|
12243
11805
|
if (drawnRotateHandle) {
|
|
12244
11806
|
drawnRotateHandle.remove();
|
|
@@ -12250,8 +11812,8 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12250
11812
|
// the rotationHandle as well, or it will end up out
|
|
12251
11813
|
// of sync
|
|
12252
11814
|
$(rotatePoint).on("move." + id, function () {
|
|
12253
|
-
const delta = vector$
|
|
12254
|
-
rotateHandle.setCoord(vector$
|
|
11815
|
+
const delta = vector$3.subtract(rotatePoint.coord, rotatePointPrevCoord);
|
|
11816
|
+
rotateHandle.setCoord(vector$3.add(rotateHandle.coord, delta));
|
|
12255
11817
|
redrawRotateHandle(rotateHandle.coord);
|
|
12256
11818
|
rotatePointPrevCoord = rotatePoint.coord;
|
|
12257
11819
|
rotateHandle.constraints.fixedDistance.point = rotatePoint;
|
|
@@ -12266,8 +11828,8 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12266
11828
|
}
|
|
12267
11829
|
let coord = [x, y];
|
|
12268
11830
|
if (options.onMove) {
|
|
12269
|
-
const oldPolar = vector$
|
|
12270
|
-
const newPolar = vector$
|
|
11831
|
+
const oldPolar = vector$3.polarDegFromCart(vector$3.subtract(rotateHandlePrevCoord, rotatePoint.coord));
|
|
11832
|
+
const newPolar = vector$3.polarDegFromCart(vector$3.subtract(coord, rotatePoint.coord));
|
|
12271
11833
|
const oldAngle = oldPolar[1];
|
|
12272
11834
|
const newAngle = newPolar[1];
|
|
12273
11835
|
let result = options.onMove(newAngle, oldAngle);
|
|
@@ -12275,7 +11837,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12275
11837
|
if (result === false) {
|
|
12276
11838
|
result = oldAngle;
|
|
12277
11839
|
}
|
|
12278
|
-
coord = vector$
|
|
11840
|
+
coord = vector$3.add(rotatePoint.coord, vector$3.cartFromPolarDeg(oldPolar[0], result));
|
|
12279
11841
|
}
|
|
12280
11842
|
}
|
|
12281
11843
|
redrawRotateHandle(coord);
|
|
@@ -12286,8 +11848,8 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12286
11848
|
isRotating = false;
|
|
12287
11849
|
redrawRotateHandle(rotateHandle.coord);
|
|
12288
11850
|
if (options.onMoveEnd) {
|
|
12289
|
-
const oldPolar = vector$
|
|
12290
|
-
const newPolar = vector$
|
|
11851
|
+
const oldPolar = vector$3.polarDegFromCart(vector$3.subtract(rotateHandleStartCoord, rotatePoint.coord));
|
|
11852
|
+
const newPolar = vector$3.polarDegFromCart(vector$3.subtract(rotateHandle.coord, rotatePoint.coord));
|
|
12291
11853
|
options.onMoveEnd(newPolar[1], oldPolar[1]);
|
|
12292
11854
|
}
|
|
12293
11855
|
};
|
|
@@ -12332,14 +11894,14 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12332
11894
|
if (point.equal(lineCoords[0], lineCoords[1])) {
|
|
12333
11895
|
lineCoords = [lineCoords[0], point.addVector(lineCoords[0], [1, 1])];
|
|
12334
11896
|
}
|
|
12335
|
-
const lineDirection = vector$
|
|
12336
|
-
const lineVec = vector$
|
|
12337
|
-
const centerVec = vector$
|
|
12338
|
-
const leftCenterVec = vector$
|
|
12339
|
-
const rightCenterVec = vector$
|
|
12340
|
-
const negLineVec = vector$
|
|
12341
|
-
const leftVec = vector$
|
|
12342
|
-
const rightVec = vector$
|
|
11897
|
+
const lineDirection = vector$3.normalize(vector$3.subtract(lineCoords[1], lineCoords[0]));
|
|
11898
|
+
const lineVec = vector$3.scale(lineDirection, size / 2);
|
|
11899
|
+
const centerVec = vector$3.scale(lineDirection, distanceFromCenter);
|
|
11900
|
+
const leftCenterVec = vector$3.rotateDeg(centerVec, 90);
|
|
11901
|
+
const rightCenterVec = vector$3.rotateDeg(centerVec, -90);
|
|
11902
|
+
const negLineVec = vector$3.negate(lineVec);
|
|
11903
|
+
const leftVec = vector$3.rotateDeg(lineVec, 90);
|
|
11904
|
+
const rightVec = vector$3.rotateDeg(lineVec, -90);
|
|
12343
11905
|
const leftCenter = point.addVectors(buttonCoord, leftCenterVec);
|
|
12344
11906
|
const rightCenter = point.addVectors(buttonCoord, rightCenterVec);
|
|
12345
11907
|
const leftCoord1 = point.addVectors(buttonCoord, leftCenterVec, lineVec, leftVec);
|
|
@@ -12466,7 +12028,7 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
12466
12028
|
// @ts-expect-error - TS2350 - Only a void function can be called with the 'new' keyword.
|
|
12467
12029
|
return new Ruler(this, options || {});
|
|
12468
12030
|
},
|
|
12469
|
-
addPoints: vector$
|
|
12031
|
+
addPoints: vector$3.add
|
|
12470
12032
|
});
|
|
12471
12033
|
function Protractor$1(graph, center) {
|
|
12472
12034
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
@@ -12740,10 +12302,10 @@ function Ruler(graphie, options) {
|
|
|
12740
12302
|
});
|
|
12741
12303
|
const width = options.units * options.pixelsPerUnit;
|
|
12742
12304
|
const height = 50;
|
|
12743
|
-
const leftBottom = graphie.unscalePoint(vector$
|
|
12305
|
+
const leftBottom = graphie.unscalePoint(vector$3.subtract(graphie.scalePoint(options.center), vector$3.scale([width, -height], 0.5)));
|
|
12744
12306
|
const graphieUnitsPerUnit = options.pixelsPerUnit / graphie.scale[0];
|
|
12745
12307
|
const graphieUnitsHeight = height / graphie.scale[0];
|
|
12746
|
-
const rightTop = vector$
|
|
12308
|
+
const rightTop = vector$3.add(leftBottom, [options.units * graphieUnitsPerUnit, graphieUnitsHeight]);
|
|
12747
12309
|
const tickHeight = 1.0;
|
|
12748
12310
|
let tickHeightMap;
|
|
12749
12311
|
if (options.ticksPerUnit === 10) {
|
|
@@ -12890,11 +12452,11 @@ function Ruler(graphie, options) {
|
|
|
12890
12452
|
const dy = rightBottomHandle.coord[1] - y;
|
|
12891
12453
|
const dx = rightBottomHandle.coord[0] - x;
|
|
12892
12454
|
const angle = Math.atan2(dy, dx) * 180 / Math.PI;
|
|
12893
|
-
const center = vector$
|
|
12455
|
+
const center = vector$3.scale(vector$3.add([x, y], rightBottomHandle.coord), 0.5);
|
|
12894
12456
|
const scaledCenter = graphie.scalePoint(center);
|
|
12895
|
-
const oldCenter = vector$
|
|
12457
|
+
const oldCenter = vector$3.scale(vector$3.add(leftBottomHandle.coord, rightBottomHandle.coord), 0.5);
|
|
12896
12458
|
const scaledOldCenter = graphie.scalePoint(oldCenter);
|
|
12897
|
-
const diff = vector$
|
|
12459
|
+
const diff = vector$3.subtract(scaledCenter, scaledOldCenter);
|
|
12898
12460
|
set.rotate(-angle, scaledOldCenter[0], scaledOldCenter[1]);
|
|
12899
12461
|
set.translate(diff[0], diff[1]);
|
|
12900
12462
|
}
|
|
@@ -12917,11 +12479,11 @@ function Ruler(graphie, options) {
|
|
|
12917
12479
|
const dy = y - leftBottomHandle.coord[1];
|
|
12918
12480
|
const dx = x - leftBottomHandle.coord[0];
|
|
12919
12481
|
const angle = Math.atan2(dy, dx) * 180 / Math.PI;
|
|
12920
|
-
const center = vector$
|
|
12482
|
+
const center = vector$3.scale(vector$3.add([x, y], leftBottomHandle.coord), 0.5);
|
|
12921
12483
|
const scaledCenter = graphie.scalePoint(center);
|
|
12922
|
-
const oldCenter = vector$
|
|
12484
|
+
const oldCenter = vector$3.scale(vector$3.add(leftBottomHandle.coord, rightBottomHandle.coord), 0.5);
|
|
12923
12485
|
const scaledOldCenter = graphie.scalePoint(oldCenter);
|
|
12924
|
-
const diff = vector$
|
|
12486
|
+
const diff = vector$3.subtract(scaledCenter, scaledOldCenter);
|
|
12925
12487
|
set.rotate(-angle, scaledOldCenter[0], scaledOldCenter[1]);
|
|
12926
12488
|
set.translate(diff[0], diff[1]);
|
|
12927
12489
|
}
|
|
@@ -13064,12 +12626,12 @@ _.extend(MovableAngle.prototype, {
|
|
|
13064
12626
|
points[1].onMove = function (x, y) {
|
|
13065
12627
|
const oldVertex = points[1].coord;
|
|
13066
12628
|
const newVertex = [x, y];
|
|
13067
|
-
const delta = vector$
|
|
12629
|
+
const delta = vector$3.add(newVertex, reverseVector$1(oldVertex));
|
|
13068
12630
|
let valid = true;
|
|
13069
12631
|
const newPoints = {};
|
|
13070
12632
|
_.each([0, 2], function (i) {
|
|
13071
12633
|
const oldPoint = points[i].coord;
|
|
13072
|
-
let newPoint = vector$
|
|
12634
|
+
let newPoint = vector$3.add(oldPoint, delta);
|
|
13073
12635
|
let angle = GraphUtils.findAngleDeprecated(newVertex, newPoint);
|
|
13074
12636
|
angle *= Math.PI / 180;
|
|
13075
12637
|
newPoint = graphie.constrainToBoundsOnAngle(newPoint, 10, angle);
|
|
@@ -13106,7 +12668,7 @@ _.extend(MovableAngle.prototype, {
|
|
|
13106
12668
|
let angle = GraphUtils.findAngleDeprecated(newPoint, vertex);
|
|
13107
12669
|
angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
|
|
13108
12670
|
const distance = GraphUtils.getDistance(newPoint, vertex);
|
|
13109
|
-
return vector$
|
|
12671
|
+
return vector$3.add(vertex, polar$2(distance, angle));
|
|
13110
12672
|
}
|
|
13111
12673
|
return true;
|
|
13112
12674
|
};
|
|
@@ -13257,7 +12819,6 @@ _.extend(MovableAngle.prototype, {
|
|
|
13257
12819
|
|
|
13258
12820
|
/* eslint-disable @babel/no-invalid-this */
|
|
13259
12821
|
const nestedMap$1 = Util.nestedMap;
|
|
13260
|
-
const deepEq$5 = Util.deepEq;
|
|
13261
12822
|
|
|
13262
12823
|
/**
|
|
13263
12824
|
* A base class for all Graphie Movables
|
|
@@ -13354,7 +12915,7 @@ const createSimpleClass = function createSimpleClass(addFunction) {
|
|
|
13354
12915
|
this._prevProps = this.props;
|
|
13355
12916
|
},
|
|
13356
12917
|
modify: function (graphie) {
|
|
13357
|
-
if (!
|
|
12918
|
+
if (!approximateDeepEqual(this.props, this._prevProps)) {
|
|
13358
12919
|
this.remove();
|
|
13359
12920
|
this.add(graphie);
|
|
13360
12921
|
this._prevProps = this.props;
|
|
@@ -13715,7 +13276,7 @@ function getEventName$1(key) {
|
|
|
13715
13276
|
const getScaledAngle = function getScaledAngle(line) {
|
|
13716
13277
|
const scaledA = line.graphie.scalePoint(line.coord(0));
|
|
13717
13278
|
const scaledZ = line.graphie.scalePoint(line.coord(1));
|
|
13718
|
-
return vector$
|
|
13279
|
+
return vector$3.polarDegFromCart(vector$3.subtract(scaledZ, scaledA))[1];
|
|
13719
13280
|
};
|
|
13720
13281
|
|
|
13721
13282
|
/**
|
|
@@ -13874,11 +13435,11 @@ const constraints$2 = {
|
|
|
13874
13435
|
if (snap === null) {
|
|
13875
13436
|
return true;
|
|
13876
13437
|
}
|
|
13877
|
-
let delta = vector$
|
|
13438
|
+
let delta = vector$3.subtract(coord, prevCoord);
|
|
13878
13439
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
13879
13440
|
snap = snap || this.graphie.snap;
|
|
13880
13441
|
delta = point.roundTo(delta, snap);
|
|
13881
|
-
return vector$
|
|
13442
|
+
return vector$3.add(prevCoord, delta);
|
|
13882
13443
|
};
|
|
13883
13444
|
},
|
|
13884
13445
|
bound: function (range, snap, paddingPx) {
|
|
@@ -13892,7 +13453,7 @@ const constraints$2 = {
|
|
|
13892
13453
|
return function (coord, prevCoord) {
|
|
13893
13454
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
13894
13455
|
const graphie = this.graphie;
|
|
13895
|
-
const delta = vector$
|
|
13456
|
+
const delta = vector$3.subtract(coord, prevCoord);
|
|
13896
13457
|
range = range || graphie.range;
|
|
13897
13458
|
// A null snap means no snap; an undefined snap means
|
|
13898
13459
|
// default to graphie's
|
|
@@ -13912,8 +13473,8 @@ const constraints$2 = {
|
|
|
13912
13473
|
const deltaBounds = _.map(
|
|
13913
13474
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
13914
13475
|
this.coords(), function (coord, i) {
|
|
13915
|
-
const max = vector$
|
|
13916
|
-
const min = vector$
|
|
13476
|
+
const max = vector$3.subtract(absoluteUpper, coord);
|
|
13477
|
+
const min = vector$3.subtract(absoluteLower, coord);
|
|
13917
13478
|
return [min, max];
|
|
13918
13479
|
});
|
|
13919
13480
|
|
|
@@ -13927,7 +13488,7 @@ const constraints$2 = {
|
|
|
13927
13488
|
const deltaY = Math.max(lower[1], Math.min(upper[1], delta[1]));
|
|
13928
13489
|
return [deltaX, deltaY];
|
|
13929
13490
|
}, delta);
|
|
13930
|
-
return vector$
|
|
13491
|
+
return vector$3.add(prevCoord, boundedDelta);
|
|
13931
13492
|
};
|
|
13932
13493
|
}
|
|
13933
13494
|
};
|
|
@@ -13936,10 +13497,10 @@ const constraints$2 = {
|
|
|
13936
13497
|
constraints$2.standard = null;
|
|
13937
13498
|
const onMove$1 = {
|
|
13938
13499
|
updatePoints: function (coord, prevCoord) {
|
|
13939
|
-
const actualDelta = vector$
|
|
13500
|
+
const actualDelta = vector$3.subtract(coord, prevCoord);
|
|
13940
13501
|
// @ts-expect-error - TS2339 - Property 'state' does not exist on type '{ readonly updatePoints: (coord: Coord, prevCoord: Coord) => void; }'.
|
|
13941
13502
|
_.each(this.state.points, function (point) {
|
|
13942
|
-
point.setCoord(vector$
|
|
13503
|
+
point.setCoord(vector$3.add(point.coord(), actualDelta));
|
|
13943
13504
|
});
|
|
13944
13505
|
}
|
|
13945
13506
|
};
|
|
@@ -14101,9 +13662,9 @@ _.extend(MovableLine$4.prototype, {
|
|
|
14101
13662
|
self._fireEvent(self.state.onMoveStart, self.coord(0), self.coord(0));
|
|
14102
13663
|
},
|
|
14103
13664
|
onMove: function (mouseCoord, prevMouseCoord) {
|
|
14104
|
-
const delta = vector$
|
|
14105
|
-
self._totalDelta = vector$
|
|
14106
|
-
let refCoord = vector$
|
|
13665
|
+
const delta = vector$3.subtract(mouseCoord, prevMouseCoord);
|
|
13666
|
+
self._totalDelta = vector$3.add(self._totalDelta, delta);
|
|
13667
|
+
let refCoord = vector$3.add(self._initialRefCoord, self._totalDelta);
|
|
14107
13668
|
refCoord = self._applyConstraints(refCoord, self._prevRefCoord);
|
|
14108
13669
|
if (refCoord === false) {
|
|
14109
13670
|
return;
|
|
@@ -14615,7 +14176,7 @@ class MovablePoint$5 {
|
|
|
14615
14176
|
// touch-based interaction.
|
|
14616
14177
|
if (state.touchOffset == null) {
|
|
14617
14178
|
const isMouse = !("ontouchstart" in window);
|
|
14618
|
-
state.touchOffset = isMouse ? [0, 0] : vector$
|
|
14179
|
+
state.touchOffset = isMouse ? [0, 0] : vector$3.subtract(startCoord, startMouseCoord);
|
|
14619
14180
|
}
|
|
14620
14181
|
const svgElem = state.visibleShape.wrapper;
|
|
14621
14182
|
if (state.shadow) {
|
|
@@ -14642,7 +14203,7 @@ class MovablePoint$5 {
|
|
|
14642
14203
|
self.draw();
|
|
14643
14204
|
},
|
|
14644
14205
|
onMove: (mouseCoord, prevMouseCoord) => {
|
|
14645
|
-
const transformedCoord = vector$
|
|
14206
|
+
const transformedCoord = vector$3.add(mouseCoord, state.touchOffset);
|
|
14646
14207
|
self.moveTo(transformedCoord);
|
|
14647
14208
|
if (state.showHairlines) {
|
|
14648
14209
|
if (!this.state.outOfBounds) {
|
|
@@ -15035,7 +14596,7 @@ function sum(array) {
|
|
|
15035
14596
|
return memo + arg;
|
|
15036
14597
|
}, 0);
|
|
15037
14598
|
}
|
|
15038
|
-
function clockwise(points) {
|
|
14599
|
+
function clockwise$2(points) {
|
|
15039
14600
|
const segments = _.zip(points, points.slice(1).concat(points.slice(0, 1)));
|
|
15040
14601
|
const areas = _.map(segments, function (segment) {
|
|
15041
14602
|
const p1 = segment[0];
|
|
@@ -15101,7 +14662,7 @@ const draw$1 = {
|
|
|
15101
14662
|
const graphie = this.graphie;
|
|
15102
14663
|
const self = this;
|
|
15103
14664
|
const coords = _.invoke(state.points, "coord");
|
|
15104
|
-
const isClockwise = clockwise(coords);
|
|
14665
|
+
const isClockwise = clockwise$2(coords);
|
|
15105
14666
|
const n = coords.length;
|
|
15106
14667
|
|
|
15107
14668
|
// graphie.labelAngle and similar methods attempt to re-use the label
|
|
@@ -15272,7 +14833,7 @@ const constraints = {
|
|
|
15272
14833
|
return function (coord, prevCoord) {
|
|
15273
14834
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
15274
14835
|
const graphie = this.graphie;
|
|
15275
|
-
const delta = vector$
|
|
14836
|
+
const delta = vector$3.subtract(coord, prevCoord);
|
|
15276
14837
|
range = range || graphie.range;
|
|
15277
14838
|
// A null snap means no snap; an undefined snap means
|
|
15278
14839
|
// default to graphie's
|
|
@@ -15291,8 +14852,8 @@ const constraints = {
|
|
|
15291
14852
|
// Calculate the bounds for the delta.
|
|
15292
14853
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
15293
14854
|
const deltaBounds = _.map(this.coords(), function (coord, i) {
|
|
15294
|
-
const max = vector$
|
|
15295
|
-
const min = vector$
|
|
14855
|
+
const max = vector$3.subtract(absoluteUpper, coord);
|
|
14856
|
+
const min = vector$3.subtract(absoluteLower, coord);
|
|
15296
14857
|
return [min, max];
|
|
15297
14858
|
});
|
|
15298
14859
|
|
|
@@ -15306,7 +14867,7 @@ const constraints = {
|
|
|
15306
14867
|
const deltaY = Math.max(lower[1], Math.min(upper[1], delta[1]));
|
|
15307
14868
|
return [deltaX, deltaY];
|
|
15308
14869
|
}, delta);
|
|
15309
|
-
return vector$
|
|
14870
|
+
return vector$3.add(prevCoord, boundedDelta);
|
|
15310
14871
|
};
|
|
15311
14872
|
}
|
|
15312
14873
|
};
|
|
@@ -15315,10 +14876,10 @@ const constraints = {
|
|
|
15315
14876
|
constraints.standard = null;
|
|
15316
14877
|
const onMove = {
|
|
15317
14878
|
updatePoints: function (coord, prevCoord) {
|
|
15318
|
-
const actualDelta = vector$
|
|
14879
|
+
const actualDelta = vector$3.subtract(coord, prevCoord);
|
|
15319
14880
|
// @ts-expect-error - TS2339 - Property 'state' does not exist on type '{ readonly updatePoints: (coord: Coord, prevCoord: Coord) => undefined; }'.
|
|
15320
14881
|
_.each(this.state.points, function (point) {
|
|
15321
|
-
point.setCoord(vector$
|
|
14882
|
+
point.setCoord(vector$3.add(point.coord(), actualDelta));
|
|
15322
14883
|
});
|
|
15323
14884
|
}
|
|
15324
14885
|
};
|
|
@@ -15492,9 +15053,9 @@ _.extend(MovablePolygon.prototype, {
|
|
|
15492
15053
|
self._fireEvent(self.state.onMoveStart, self.coord(0), self.coord(0));
|
|
15493
15054
|
},
|
|
15494
15055
|
onMove: function (mouseCoord, prevMouseCoord) {
|
|
15495
|
-
const delta = vector$
|
|
15496
|
-
self._totalDelta = vector$
|
|
15497
|
-
let refCoord = vector$
|
|
15056
|
+
const delta = vector$3.subtract(mouseCoord, prevMouseCoord);
|
|
15057
|
+
self._totalDelta = vector$3.add(self._totalDelta, delta);
|
|
15058
|
+
let refCoord = vector$3.add(self._initialRefCoord, self._totalDelta);
|
|
15498
15059
|
refCoord = self._applyConstraints(refCoord, self._prevRefCoord);
|
|
15499
15060
|
if (refCoord === false) {
|
|
15500
15061
|
return;
|
|
@@ -15809,7 +15370,7 @@ const Parabola = GraphieClasses.createClass({
|
|
|
15809
15370
|
this.parabola.toFront();
|
|
15810
15371
|
}
|
|
15811
15372
|
});
|
|
15812
|
-
const Sinusoid
|
|
15373
|
+
const Sinusoid = GraphieClasses.createClass({
|
|
15813
15374
|
displayName: "Sinusoid",
|
|
15814
15375
|
movableProps: ["children"],
|
|
15815
15376
|
add: function (graphie) {
|
|
@@ -15831,7 +15392,7 @@ const Sinusoid$1 = GraphieClasses.createClass({
|
|
|
15831
15392
|
this.sinusoid.toFront();
|
|
15832
15393
|
}
|
|
15833
15394
|
});
|
|
15834
|
-
const Plot$
|
|
15395
|
+
const Plot$1 = GraphieClasses.createSimpleClass((graphie, props) => {
|
|
15835
15396
|
return graphie.plot(props.fn, props.range, props.style);
|
|
15836
15397
|
});
|
|
15837
15398
|
const PlotParametric$1 = GraphieClasses.createSimpleClass((graphie, props) => {
|
|
@@ -15889,17 +15450,16 @@ var Movables = {
|
|
|
15889
15450
|
MovablePoint: MovablePoint$4,
|
|
15890
15451
|
Parabola: Parabola,
|
|
15891
15452
|
Path: Path,
|
|
15892
|
-
Plot: Plot$
|
|
15453
|
+
Plot: Plot$1,
|
|
15893
15454
|
PlotParametric: PlotParametric$1,
|
|
15894
15455
|
Point: Point$1,
|
|
15895
|
-
Sinusoid: Sinusoid
|
|
15456
|
+
Sinusoid: Sinusoid,
|
|
15896
15457
|
Rect: Rect$1
|
|
15897
15458
|
};
|
|
15898
15459
|
|
|
15899
15460
|
const GraphieMovable = GraphieClasses.GraphieMovable;
|
|
15900
15461
|
const createGraphie = GraphUtils.createGraphie;
|
|
15901
15462
|
const {
|
|
15902
|
-
deepEq: deepEq$4,
|
|
15903
15463
|
nestedMap
|
|
15904
15464
|
} = Util;
|
|
15905
15465
|
const {
|
|
@@ -16141,7 +15701,7 @@ class Graphie extends React.Component {
|
|
|
16141
15701
|
if (this.props.setup !== prevProps.setup) {
|
|
16142
15702
|
Log.error("<Graphie> was given a new setup function. " + "This is a bad idea; please refactor your code to give " + "the same setup function reference to <Graphie> on " + "every render.", Errors.Internal);
|
|
16143
15703
|
}
|
|
16144
|
-
if (!
|
|
15704
|
+
if (!approximateDeepEqual(this.props.options, prevProps.options) || !approximateDeepEqual(this.props.box, prevProps.box) || !approximateDeepEqual(this.props.range, prevProps.range)) {
|
|
16145
15705
|
this._setupGraphie();
|
|
16146
15706
|
}
|
|
16147
15707
|
this._updateMovables();
|
|
@@ -18248,6 +17808,10 @@ const getWidgetScorer = name => {
|
|
|
18248
17808
|
var _widgets$name$scorer, _widgets$name4;
|
|
18249
17809
|
return (_widgets$name$scorer = (_widgets$name4 = widgets[name]) == null ? void 0 : _widgets$name4.scorer) != null ? _widgets$name$scorer : null;
|
|
18250
17810
|
};
|
|
17811
|
+
const getPublicWidgetOptionsFunction = name => {
|
|
17812
|
+
var _widgets$name$getPubl, _widgets$name5;
|
|
17813
|
+
return (_widgets$name$getPubl = (_widgets$name5 = widgets[name]) == null ? void 0 : _widgets$name5.getPublicWidgetOptions) != null ? _widgets$name$getPubl : i => i;
|
|
17814
|
+
};
|
|
18251
17815
|
const getEditor = name => {
|
|
18252
17816
|
return _.has(editors, name) ? editors[name] : null;
|
|
18253
17817
|
};
|
|
@@ -18552,6 +18116,7 @@ var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
|
18552
18116
|
getWidget: getWidget,
|
|
18553
18117
|
getWidgetExport: getWidgetExport,
|
|
18554
18118
|
getWidgetScorer: getWidgetScorer,
|
|
18119
|
+
getPublicWidgetOptionsFunction: getPublicWidgetOptionsFunction,
|
|
18555
18120
|
getEditor: getEditor,
|
|
18556
18121
|
getTransform: getTransform,
|
|
18557
18122
|
getVersion: getVersion,
|
|
@@ -20891,230 +20456,6 @@ const getPromptJSON$s = (renderProps, userInput) => {
|
|
|
20891
20456
|
};
|
|
20892
20457
|
};
|
|
20893
20458
|
|
|
20894
|
-
/*
|
|
20895
|
-
* In this file, an `expression` is some portion of valid TeX enclosed in
|
|
20896
|
-
* curly brackets.
|
|
20897
|
-
*/
|
|
20898
|
-
|
|
20899
|
-
/*
|
|
20900
|
-
* Find the index at which an expression ends, i.e., has an unmatched
|
|
20901
|
-
* closing curly bracket. This method assumes that we start with a non-open
|
|
20902
|
-
* bracket character and end when we've seen more left than right brackets
|
|
20903
|
-
* (rather than assuming that we start with a bracket character and wait for
|
|
20904
|
-
* bracket equality).
|
|
20905
|
-
*/
|
|
20906
|
-
function findEndpoint(tex, currentIndex) {
|
|
20907
|
-
let bracketDepth = 0;
|
|
20908
|
-
for (let i = currentIndex, len = tex.length; i < len; i++) {
|
|
20909
|
-
const c = tex[i];
|
|
20910
|
-
if (c === "{") {
|
|
20911
|
-
bracketDepth++;
|
|
20912
|
-
} else if (c === "}") {
|
|
20913
|
-
bracketDepth--;
|
|
20914
|
-
}
|
|
20915
|
-
if (bracketDepth < 0) {
|
|
20916
|
-
return i;
|
|
20917
|
-
}
|
|
20918
|
-
}
|
|
20919
|
-
// If we never see unbalanced curly brackets, default to the
|
|
20920
|
-
// entire string
|
|
20921
|
-
return tex.length;
|
|
20922
|
-
}
|
|
20923
|
-
|
|
20924
|
-
/*
|
|
20925
|
-
* Parses an individual set of curly brackets into TeX.
|
|
20926
|
-
*/
|
|
20927
|
-
function parseNextExpression(tex, currentIndex, handler) {
|
|
20928
|
-
// Find the first '{' and grab subsequent TeX
|
|
20929
|
-
// Ex) tex: '{3}{7}', and we want the '3'
|
|
20930
|
-
const openBracketIndex = tex.indexOf("{", currentIndex);
|
|
20931
|
-
const nextExpIndex = openBracketIndex + 1;
|
|
20932
|
-
|
|
20933
|
-
// Truncate to only contain remaining TeX
|
|
20934
|
-
const endpoint = findEndpoint(tex, nextExpIndex);
|
|
20935
|
-
const expressionTeX = tex.substring(nextExpIndex, endpoint);
|
|
20936
|
-
const parsedExp = walkTex(expressionTeX, handler);
|
|
20937
|
-
return {
|
|
20938
|
-
endpoint: endpoint,
|
|
20939
|
-
expression: parsedExp
|
|
20940
|
-
};
|
|
20941
|
-
}
|
|
20942
|
-
function getNextFracIndex(tex, currentIndex) {
|
|
20943
|
-
const dfrac = "\\dfrac";
|
|
20944
|
-
const frac = "\\frac";
|
|
20945
|
-
const nextFrac = tex.indexOf(frac, currentIndex);
|
|
20946
|
-
const nextDFrac = tex.indexOf(dfrac, currentIndex);
|
|
20947
|
-
if (nextFrac > -1 && nextDFrac > -1) {
|
|
20948
|
-
return Math.min(nextFrac, nextDFrac);
|
|
20949
|
-
}
|
|
20950
|
-
if (nextFrac > -1) {
|
|
20951
|
-
return nextFrac;
|
|
20952
|
-
}
|
|
20953
|
-
if (nextDFrac > -1) {
|
|
20954
|
-
return nextDFrac;
|
|
20955
|
-
}
|
|
20956
|
-
return -1;
|
|
20957
|
-
}
|
|
20958
|
-
function walkTex(tex, handler) {
|
|
20959
|
-
if (!tex) {
|
|
20960
|
-
return "";
|
|
20961
|
-
}
|
|
20962
|
-
|
|
20963
|
-
// Ex) tex: '2 \dfrac {3}{7}'
|
|
20964
|
-
let parsedString = "";
|
|
20965
|
-
let currentIndex = 0;
|
|
20966
|
-
let nextFrac = getNextFracIndex(tex, currentIndex);
|
|
20967
|
-
|
|
20968
|
-
// For each \dfrac, find the two expressions (wrapped in {}) and recur
|
|
20969
|
-
while (nextFrac > -1) {
|
|
20970
|
-
// Gather first fragment, preceding \dfrac
|
|
20971
|
-
// Ex) parsedString: '2 '
|
|
20972
|
-
parsedString += tex.substring(currentIndex, nextFrac);
|
|
20973
|
-
|
|
20974
|
-
// Remove everything preceding \dfrac, which has been parsed
|
|
20975
|
-
currentIndex = nextFrac;
|
|
20976
|
-
|
|
20977
|
-
// Parse first expression and move index past it
|
|
20978
|
-
// Ex) firstParsedExpression.expression: '3'
|
|
20979
|
-
const firstParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
20980
|
-
currentIndex = firstParsedExpression.endpoint + 1;
|
|
20981
|
-
|
|
20982
|
-
// Parse second expression
|
|
20983
|
-
// Ex) secondParsedExpression.expression: '7'
|
|
20984
|
-
const secondParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
20985
|
-
currentIndex = secondParsedExpression.endpoint + 1;
|
|
20986
|
-
|
|
20987
|
-
// Add expressions to running total of parsed expressions
|
|
20988
|
-
if (parsedString.length) {
|
|
20989
|
-
parsedString += " ";
|
|
20990
|
-
}
|
|
20991
|
-
|
|
20992
|
-
// Apply a custom handler based on the parsed subexpressions
|
|
20993
|
-
parsedString += handler(firstParsedExpression.expression, secondParsedExpression.expression);
|
|
20994
|
-
|
|
20995
|
-
// Find next DFrac, relative to currentIndex
|
|
20996
|
-
nextFrac = getNextFracIndex(tex, currentIndex);
|
|
20997
|
-
}
|
|
20998
|
-
|
|
20999
|
-
// Add remaining TeX, which is \dfrac-free
|
|
21000
|
-
parsedString += tex.slice(currentIndex);
|
|
21001
|
-
return parsedString;
|
|
21002
|
-
}
|
|
21003
|
-
|
|
21004
|
-
/*
|
|
21005
|
-
* Modify a TeX expression, returning another TeX expression. The resulting
|
|
21006
|
-
* expression will have its innermost fractions stubbed out with \fracs
|
|
21007
|
-
* (as opposed to \dfracs). All other content will remain untouched.
|
|
21008
|
-
*/
|
|
21009
|
-
function modifyTex(tex) {
|
|
21010
|
-
function isNestedFraction(tex) {
|
|
21011
|
-
return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
|
|
21012
|
-
}
|
|
21013
|
-
const handler = function handler(exp1, exp2) {
|
|
21014
|
-
let prefix;
|
|
21015
|
-
if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
|
|
21016
|
-
prefix = "\\dfrac";
|
|
21017
|
-
} else {
|
|
21018
|
-
prefix = "\\frac";
|
|
21019
|
-
}
|
|
21020
|
-
return prefix + " {" + exp1 + "}{" + exp2 + "}";
|
|
21021
|
-
};
|
|
21022
|
-
return walkTex(tex, handler);
|
|
21023
|
-
}
|
|
21024
|
-
|
|
21025
|
-
/*
|
|
21026
|
-
* Parse a TeX expression into something interpretable by input-number.
|
|
21027
|
-
* The process is concerned with: (1) parsing fractions, i.e., \dfracs; and
|
|
21028
|
-
* (2) removing backslash-escaping from certain characters (right now, only
|
|
21029
|
-
* percent signs).
|
|
21030
|
-
*
|
|
21031
|
-
* The basic algorithm for handling \dfracs splits on \dfracs and then recurs
|
|
21032
|
-
* on the subsequent "expressions", i.e., the {} pairs that follow \dfrac. The
|
|
21033
|
-
* recursion is to allow for nested \dfrac elements.
|
|
21034
|
-
*
|
|
21035
|
-
* Backslash-escapes are removed with a simple search-and-replace.
|
|
21036
|
-
*/
|
|
21037
|
-
function parseTex(tex) {
|
|
21038
|
-
const handler = function handler(exp1, exp2) {
|
|
21039
|
-
return exp1 + "/" + exp2;
|
|
21040
|
-
};
|
|
21041
|
-
const texWithoutFracs = walkTex(tex, handler);
|
|
21042
|
-
return texWithoutFracs.replace("\\%", "%");
|
|
21043
|
-
}
|
|
21044
|
-
var TexWrangler = {
|
|
21045
|
-
parseTex,
|
|
21046
|
-
modifyTex
|
|
21047
|
-
};
|
|
21048
|
-
|
|
21049
|
-
const ParseTex$1 = TexWrangler.parseTex;
|
|
21050
|
-
const answerTypes = {
|
|
21051
|
-
number: {
|
|
21052
|
-
name: "Numbers",
|
|
21053
|
-
forms: "integer, decimal, proper, improper, mixed"
|
|
21054
|
-
},
|
|
21055
|
-
decimal: {
|
|
21056
|
-
name: "Decimals",
|
|
21057
|
-
forms: "decimal"
|
|
21058
|
-
},
|
|
21059
|
-
integer: {
|
|
21060
|
-
name: "Integers",
|
|
21061
|
-
forms: "integer"
|
|
21062
|
-
},
|
|
21063
|
-
rational: {
|
|
21064
|
-
name: "Fractions and mixed numbers",
|
|
21065
|
-
forms: "integer, proper, improper, mixed"
|
|
21066
|
-
},
|
|
21067
|
-
improper: {
|
|
21068
|
-
name: "Improper numbers (no mixed)",
|
|
21069
|
-
forms: "integer, proper, improper"
|
|
21070
|
-
},
|
|
21071
|
-
mixed: {
|
|
21072
|
-
name: "Mixed numbers (no improper)",
|
|
21073
|
-
forms: "integer, proper, mixed"
|
|
21074
|
-
},
|
|
21075
|
-
percent: {
|
|
21076
|
-
name: "Numbers or percents",
|
|
21077
|
-
forms: "integer, decimal, proper, improper, mixed, percent"
|
|
21078
|
-
},
|
|
21079
|
-
pi: {
|
|
21080
|
-
name: "Numbers with pi",
|
|
21081
|
-
forms: "pi"
|
|
21082
|
-
}
|
|
21083
|
-
};
|
|
21084
|
-
function scoreInputNumber(userInput, rubric, strings) {
|
|
21085
|
-
if (rubric.answerType == null) {
|
|
21086
|
-
rubric.answerType = "number";
|
|
21087
|
-
}
|
|
21088
|
-
|
|
21089
|
-
// note(matthewc): this will get immediately parsed again by
|
|
21090
|
-
// `KhanAnswerTypes.number.convertToPredicate`, but a string is
|
|
21091
|
-
// expected here
|
|
21092
|
-
const stringValue = `${rubric.value}`;
|
|
21093
|
-
const val = KhanAnswerTypes.number.createValidatorFunctional(stringValue, {
|
|
21094
|
-
simplify: rubric.simplify,
|
|
21095
|
-
inexact: rubric.inexact || undefined,
|
|
21096
|
-
maxError: rubric.maxError,
|
|
21097
|
-
forms: answerTypes[rubric.answerType].forms
|
|
21098
|
-
});
|
|
21099
|
-
|
|
21100
|
-
// We may have received TeX; try to parse it before grading.
|
|
21101
|
-
// If `currentValue` is not TeX, this should be a no-op.
|
|
21102
|
-
const currentValue = ParseTex$1(userInput.currentValue);
|
|
21103
|
-
const result = val(currentValue);
|
|
21104
|
-
if (result.empty) {
|
|
21105
|
-
return {
|
|
21106
|
-
type: "invalid",
|
|
21107
|
-
message: result.message
|
|
21108
|
-
};
|
|
21109
|
-
}
|
|
21110
|
-
return {
|
|
21111
|
-
type: "points",
|
|
21112
|
-
earned: result.correct ? 1 : 0,
|
|
21113
|
-
total: 1,
|
|
21114
|
-
message: result.message
|
|
21115
|
-
};
|
|
21116
|
-
}
|
|
21117
|
-
|
|
21118
20459
|
const formExamples$1 = {
|
|
21119
20460
|
integer: function (options, strings) {
|
|
21120
20461
|
return strings.integerExample;
|
|
@@ -21217,7 +20558,7 @@ class InputNumber extends React.Component {
|
|
|
21217
20558
|
strings
|
|
21218
20559
|
} = this.context;
|
|
21219
20560
|
const type = this.props.answerType;
|
|
21220
|
-
const forms =
|
|
20561
|
+
const forms = inputNumberAnswerTypes[type].forms.split(/\s*,\s*/);
|
|
21221
20562
|
const examples = _.map(forms, form => formExamples$1[form](this.props, strings));
|
|
21222
20563
|
return [strings.yourAnswer].concat(examples);
|
|
21223
20564
|
}
|
|
@@ -21347,140 +20688,6 @@ const getPromptJSON$r = (renderProps, userInput) => {
|
|
|
21347
20688
|
};
|
|
21348
20689
|
};
|
|
21349
20690
|
|
|
21350
|
-
const ParseTex = TexWrangler.parseTex;
|
|
21351
|
-
const answerFormButtons = [{
|
|
21352
|
-
title: "Integers",
|
|
21353
|
-
value: "integer",
|
|
21354
|
-
content: "6"
|
|
21355
|
-
}, {
|
|
21356
|
-
title: "Decimals",
|
|
21357
|
-
value: "decimal",
|
|
21358
|
-
content: "0.75"
|
|
21359
|
-
}, {
|
|
21360
|
-
title: "Proper fractions",
|
|
21361
|
-
value: "proper",
|
|
21362
|
-
content: "\u2157"
|
|
21363
|
-
}, {
|
|
21364
|
-
title: "Improper fractions",
|
|
21365
|
-
value: "improper",
|
|
21366
|
-
content: "\u2077\u2044\u2084"
|
|
21367
|
-
}, {
|
|
21368
|
-
title: "Mixed numbers",
|
|
21369
|
-
value: "mixed",
|
|
21370
|
-
content: "1\u00BE"
|
|
21371
|
-
}, {
|
|
21372
|
-
title: "Numbers with \u03C0",
|
|
21373
|
-
value: "pi",
|
|
21374
|
-
content: "\u03C0"
|
|
21375
|
-
}];
|
|
21376
|
-
|
|
21377
|
-
// This function checks if the user inputted a percent value, parsing
|
|
21378
|
-
// it as a number (and maybe scaling) so that it can be graded.
|
|
21379
|
-
// NOTE(michaelpolyak): Unlike `KhanAnswerTypes.number.percent()` which
|
|
21380
|
-
// can accept several input forms with or without "%", the decision
|
|
21381
|
-
// to parse based on the presence of "%" in the input, is so that we
|
|
21382
|
-
// don't accidently scale the user typed value before grading, CP-930.
|
|
21383
|
-
function maybeParsePercentInput(inputValue, normalizedAnswerExpected) {
|
|
21384
|
-
// If the input value is not a string ending with "%", then there's
|
|
21385
|
-
// nothing more to do. The value will be graded as inputted by user.
|
|
21386
|
-
if (!(typeof inputValue === "string" && inputValue.endsWith("%"))) {
|
|
21387
|
-
return inputValue;
|
|
21388
|
-
}
|
|
21389
|
-
const value = parseFloat(inputValue.slice(0, -1));
|
|
21390
|
-
// If the input value stripped of the "%" cannot be parsed as a
|
|
21391
|
-
// number (the slice is not really necessary for parseFloat to work
|
|
21392
|
-
// if the string starts with a number) then return the original
|
|
21393
|
-
// input for grading.
|
|
21394
|
-
if (isNaN(value)) {
|
|
21395
|
-
return inputValue;
|
|
21396
|
-
}
|
|
21397
|
-
|
|
21398
|
-
// Next, if all correct answers are in the range of |0,1| then we
|
|
21399
|
-
// scale the user typed value. We assume this is the correct thing
|
|
21400
|
-
// to do since the input value ends with "%".
|
|
21401
|
-
if (normalizedAnswerExpected) {
|
|
21402
|
-
return value / 100;
|
|
21403
|
-
}
|
|
21404
|
-
|
|
21405
|
-
// Otherwise, we return input value (number) stripped of the "%".
|
|
21406
|
-
return value;
|
|
21407
|
-
}
|
|
21408
|
-
function scoreNumericInput(userInput, rubric, strings) {
|
|
21409
|
-
var _matchedAnswer$messag;
|
|
21410
|
-
const defaultAnswerForms = answerFormButtons.map(e => e["value"])
|
|
21411
|
-
// Don't default to validating the answer as a pi answer
|
|
21412
|
-
// if answerForm isn't set on the answer
|
|
21413
|
-
// https://khanacademy.atlassian.net/browse/LC-691
|
|
21414
|
-
.filter(e => e !== "pi");
|
|
21415
|
-
const createValidator = answer => {
|
|
21416
|
-
var _answer$answerForms;
|
|
21417
|
-
const stringAnswer = `${answer.value}`;
|
|
21418
|
-
|
|
21419
|
-
// Always validate against the provided answer forms (pi, decimal, etc.)
|
|
21420
|
-
const validatorForms = [...((_answer$answerForms = answer.answerForms) != null ? _answer$answerForms : [])];
|
|
21421
|
-
|
|
21422
|
-
// When an answer is set to strict, we validate using ONLY
|
|
21423
|
-
// the provided answerForms. If strict is false, or if there
|
|
21424
|
-
// were no provided answer forms, we will include all
|
|
21425
|
-
// of the default answer forms in our validator.
|
|
21426
|
-
if (!answer.strict || validatorForms.length === 0) {
|
|
21427
|
-
validatorForms.push(...defaultAnswerForms);
|
|
21428
|
-
}
|
|
21429
|
-
return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer, {
|
|
21430
|
-
message: answer.message,
|
|
21431
|
-
simplify: answer.status === "correct" ? answer.simplify : "optional",
|
|
21432
|
-
inexact: true,
|
|
21433
|
-
// TODO(merlob) backfill / delete
|
|
21434
|
-
maxError: answer.maxError,
|
|
21435
|
-
forms: validatorForms
|
|
21436
|
-
});
|
|
21437
|
-
};
|
|
21438
|
-
|
|
21439
|
-
// We may have received TeX; try to parse it before grading.
|
|
21440
|
-
// If `currentValue` is not TeX, this should be a no-op.
|
|
21441
|
-
const currentValue = ParseTex(userInput.currentValue);
|
|
21442
|
-
const normalizedAnswerExpected = rubric.answers.filter(answer => answer.status === "correct").every(answer => answer.value != null && Math.abs(answer.value) <= 1);
|
|
21443
|
-
|
|
21444
|
-
// The coefficient is an attribute of the widget
|
|
21445
|
-
let localValue = currentValue;
|
|
21446
|
-
if (rubric.coefficient) {
|
|
21447
|
-
if (!localValue) {
|
|
21448
|
-
localValue = 1;
|
|
21449
|
-
} else if (localValue === "-") {
|
|
21450
|
-
localValue = -1;
|
|
21451
|
-
}
|
|
21452
|
-
}
|
|
21453
|
-
const matchedAnswer = rubric.answers.map(answer => {
|
|
21454
|
-
const validateFn = createValidator(answer);
|
|
21455
|
-
const score = validateFn(maybeParsePercentInput(localValue, normalizedAnswerExpected));
|
|
21456
|
-
return _extends({}, answer, {
|
|
21457
|
-
score
|
|
21458
|
-
});
|
|
21459
|
-
}).find(answer => {
|
|
21460
|
-
// NOTE: "answer.score.correct" indicates a match via the validate function.
|
|
21461
|
-
// It does NOT indicate that the answer itself is correct.
|
|
21462
|
-
return answer.score.correct || answer.status === "correct" && answer.score.empty;
|
|
21463
|
-
});
|
|
21464
|
-
const result = (matchedAnswer == null ? void 0 : matchedAnswer.status) === "correct" ? matchedAnswer.score : {
|
|
21465
|
-
empty: (matchedAnswer == null ? void 0 : matchedAnswer.status) === "ungraded",
|
|
21466
|
-
correct: (matchedAnswer == null ? void 0 : matchedAnswer.status) === "correct",
|
|
21467
|
-
message: (_matchedAnswer$messag = matchedAnswer == null ? void 0 : matchedAnswer.message) != null ? _matchedAnswer$messag : null,
|
|
21468
|
-
guess: localValue
|
|
21469
|
-
};
|
|
21470
|
-
if (result.empty) {
|
|
21471
|
-
return {
|
|
21472
|
-
type: "invalid",
|
|
21473
|
-
message: result.message
|
|
21474
|
-
};
|
|
21475
|
-
}
|
|
21476
|
-
return {
|
|
21477
|
-
type: "points",
|
|
21478
|
-
earned: result.correct ? 1 : 0,
|
|
21479
|
-
total: 1,
|
|
21480
|
-
message: result.message
|
|
21481
|
-
};
|
|
21482
|
-
}
|
|
21483
|
-
|
|
21484
20691
|
const formExamples = {
|
|
21485
20692
|
integer: (form, strings) => strings.integerExample,
|
|
21486
20693
|
proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
|
|
@@ -25468,72 +24675,6 @@ const styles$o = StyleSheet.create({
|
|
|
25468
24675
|
}
|
|
25469
24676
|
});
|
|
25470
24677
|
|
|
25471
|
-
/**
|
|
25472
|
-
* Checks if the user has selected at least one option. Additional validation
|
|
25473
|
-
* is done in scoreRadio to check if the number of selected options is correct
|
|
25474
|
-
* and if the user has selected both a correct option and the "none of the above"
|
|
25475
|
-
* option.
|
|
25476
|
-
* @param userInput
|
|
25477
|
-
* @see `scoreRadio` for the additional validation logic and the scoring logic.
|
|
25478
|
-
*/
|
|
25479
|
-
function validateRadio(userInput) {
|
|
25480
|
-
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
25481
|
-
return sum + (selected ? 1 : 0);
|
|
25482
|
-
}, 0);
|
|
25483
|
-
if (numSelected === 0) {
|
|
25484
|
-
return {
|
|
25485
|
-
type: "invalid",
|
|
25486
|
-
message: null
|
|
25487
|
-
};
|
|
25488
|
-
}
|
|
25489
|
-
return null;
|
|
25490
|
-
}
|
|
25491
|
-
|
|
25492
|
-
function scoreRadio(userInput, rubric, strings) {
|
|
25493
|
-
const validationError = validateRadio(userInput);
|
|
25494
|
-
if (validationError) {
|
|
25495
|
-
return validationError;
|
|
25496
|
-
}
|
|
25497
|
-
const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
|
|
25498
|
-
return sum + (selected ? 1 : 0);
|
|
25499
|
-
}, 0);
|
|
25500
|
-
const numCorrect = rubric.choices.reduce((sum, currentChoice) => {
|
|
25501
|
-
return currentChoice.correct ? sum + 1 : sum;
|
|
25502
|
-
}, 0);
|
|
25503
|
-
if (numCorrect > 1 && numSelected !== numCorrect) {
|
|
25504
|
-
return {
|
|
25505
|
-
type: "invalid",
|
|
25506
|
-
message: strings.chooseCorrectNum
|
|
25507
|
-
};
|
|
25508
|
-
// If NOTA and some other answer are checked, ...
|
|
25509
|
-
}
|
|
25510
|
-
|
|
25511
|
-
const noneOfTheAboveSelected = rubric.choices.some((choice, index) => choice.isNoneOfTheAbove && userInput.choicesSelected[index]);
|
|
25512
|
-
if (noneOfTheAboveSelected && numSelected > 1) {
|
|
25513
|
-
return {
|
|
25514
|
-
type: "invalid",
|
|
25515
|
-
message: strings.notNoneOfTheAbove
|
|
25516
|
-
};
|
|
25517
|
-
}
|
|
25518
|
-
const correct = userInput.choicesSelected.every((selected, i) => {
|
|
25519
|
-
let isCorrect;
|
|
25520
|
-
if (rubric.choices[i].isNoneOfTheAbove) {
|
|
25521
|
-
isCorrect = rubric.choices.every((choice, j) => {
|
|
25522
|
-
return i === j || !choice.correct;
|
|
25523
|
-
});
|
|
25524
|
-
} else {
|
|
25525
|
-
isCorrect = !!rubric.choices[i].correct;
|
|
25526
|
-
}
|
|
25527
|
-
return isCorrect === selected;
|
|
25528
|
-
});
|
|
25529
|
-
return {
|
|
25530
|
-
type: "points",
|
|
25531
|
-
earned: correct ? 1 : 0,
|
|
25532
|
-
total: 1,
|
|
25533
|
-
message: null
|
|
25534
|
-
};
|
|
25535
|
-
}
|
|
25536
|
-
|
|
25537
24678
|
// RenderProps is the return type for radio.jsx#transform
|
|
25538
24679
|
|
|
25539
24680
|
class Radio$1 extends React.Component {
|
|
@@ -25639,7 +24780,7 @@ class Radio$1 extends React.Component {
|
|
|
25639
24780
|
choiceStates
|
|
25640
24781
|
} = this.props;
|
|
25641
24782
|
if (choiceStates) {
|
|
25642
|
-
const score = scoreRadio(this.getUserInput(), rubric
|
|
24783
|
+
const score = scoreRadio(this.getUserInput(), rubric);
|
|
25643
24784
|
const widgetCorrect = score.type === "points" && score.total === score.earned;
|
|
25644
24785
|
const newStates = choiceStates.map(state => _extends({}, state, {
|
|
25645
24786
|
highlighted: state.selected,
|
|
@@ -26009,40 +25150,20 @@ const getPromptJSON$n = (renderProps, userInput) => {
|
|
|
26009
25150
|
};
|
|
26010
25151
|
|
|
26011
25152
|
/**
|
|
26012
|
-
*
|
|
26013
|
-
*
|
|
26014
|
-
* @param userInput - The user's input corresponding to an array of indices that
|
|
26015
|
-
* represent the selected category for each row/item.
|
|
26016
|
-
* @param validationData - An array of strings corresponding to each row/item
|
|
26017
|
-
* @param strings - Used to provide a validation message
|
|
25153
|
+
* For details on the individual options, see the
|
|
25154
|
+
* PerseusCategorizerWidgetOptions type
|
|
26018
25155
|
*/
|
|
26019
|
-
function validateCategorizer(userInput, validationData, strings) {
|
|
26020
|
-
const incomplete = validationData.items.some((_, i) => userInput.values[i] == null);
|
|
26021
|
-
if (incomplete) {
|
|
26022
|
-
return {
|
|
26023
|
-
type: "invalid",
|
|
26024
|
-
message: strings.invalidSelection
|
|
26025
|
-
};
|
|
26026
|
-
}
|
|
26027
|
-
return null;
|
|
26028
|
-
}
|
|
26029
25156
|
|
|
26030
|
-
|
|
26031
|
-
|
|
26032
|
-
|
|
26033
|
-
|
|
26034
|
-
|
|
26035
|
-
let allCorrect = true;
|
|
26036
|
-
scoringData.values.forEach((value, i) => {
|
|
26037
|
-
if (userInput.values[i] !== value) {
|
|
26038
|
-
allCorrect = false;
|
|
26039
|
-
}
|
|
26040
|
-
});
|
|
25157
|
+
/**
|
|
25158
|
+
* Given a PerseusCategorizerWidgetOptions object, return a new object with only
|
|
25159
|
+
* the public options that should be exposed to the client.
|
|
25160
|
+
*/
|
|
25161
|
+
function getCategorizerPublicWidgetOptions(options) {
|
|
26041
25162
|
return {
|
|
26042
|
-
|
|
26043
|
-
|
|
26044
|
-
|
|
26045
|
-
|
|
25163
|
+
items: options.items,
|
|
25164
|
+
categories: options.categories,
|
|
25165
|
+
randomizeItems: options.randomizeItems,
|
|
25166
|
+
static: options.static
|
|
26046
25167
|
};
|
|
26047
25168
|
}
|
|
26048
25169
|
|
|
@@ -26224,7 +25345,8 @@ var Categorizer$1 = {
|
|
|
26224
25345
|
isLintable: true,
|
|
26225
25346
|
// TODO(LEMS-2656): remove TS suppression
|
|
26226
25347
|
// @ts-expect-error: Type 'UserInput' is not assignable to type 'PerseusCSProgramUserInput'.
|
|
26227
|
-
scorer: scoreCategorizer
|
|
25348
|
+
scorer: scoreCategorizer,
|
|
25349
|
+
getPublicWidgetOptions: getCategorizerPublicWidgetOptions
|
|
26228
25350
|
};
|
|
26229
25351
|
|
|
26230
25352
|
/**
|
|
@@ -26286,31 +25408,6 @@ const getPromptJSON$m = () => {
|
|
|
26286
25408
|
return getUnsupportedPromptJSON("cs-program");
|
|
26287
25409
|
};
|
|
26288
25410
|
|
|
26289
|
-
function scoreCSProgram(state) {
|
|
26290
|
-
// The iframe can tell us whether it's correct or incorrect,
|
|
26291
|
-
// and pass an optional message
|
|
26292
|
-
if (state.status === "correct") {
|
|
26293
|
-
return {
|
|
26294
|
-
type: "points",
|
|
26295
|
-
earned: 1,
|
|
26296
|
-
total: 1,
|
|
26297
|
-
message: state.message || null
|
|
26298
|
-
};
|
|
26299
|
-
}
|
|
26300
|
-
if (state.status === "incorrect") {
|
|
26301
|
-
return {
|
|
26302
|
-
type: "points",
|
|
26303
|
-
earned: 0,
|
|
26304
|
-
total: 1,
|
|
26305
|
-
message: state.message || null
|
|
26306
|
-
};
|
|
26307
|
-
}
|
|
26308
|
-
return {
|
|
26309
|
-
type: "invalid",
|
|
26310
|
-
message: "Keep going, you're not there yet!"
|
|
26311
|
-
};
|
|
26312
|
-
}
|
|
26313
|
-
|
|
26314
25411
|
/**
|
|
26315
25412
|
* This widget is for embedding Khan Academy CS programs.
|
|
26316
25413
|
*/
|
|
@@ -26583,34 +25680,6 @@ const getPromptJSON$k = (renderProps, userInput) => {
|
|
|
26583
25680
|
};
|
|
26584
25681
|
};
|
|
26585
25682
|
|
|
26586
|
-
/**
|
|
26587
|
-
* Checks if the user has selected an item from the dropdown before scoring.
|
|
26588
|
-
* This is shown with a userInput value / index other than 0.
|
|
26589
|
-
*/
|
|
26590
|
-
function validateDropdown(userInput) {
|
|
26591
|
-
if (userInput.value === 0) {
|
|
26592
|
-
return {
|
|
26593
|
-
type: "invalid",
|
|
26594
|
-
message: null
|
|
26595
|
-
};
|
|
26596
|
-
}
|
|
26597
|
-
return null;
|
|
26598
|
-
}
|
|
26599
|
-
|
|
26600
|
-
function scoreDropdown(userInput, rubric) {
|
|
26601
|
-
const validationError = validateDropdown(userInput);
|
|
26602
|
-
if (validationError) {
|
|
26603
|
-
return validationError;
|
|
26604
|
-
}
|
|
26605
|
-
const correct = rubric.choices[userInput.value - 1].correct;
|
|
26606
|
-
return {
|
|
26607
|
-
type: "points",
|
|
26608
|
-
earned: correct ? 1 : 0,
|
|
26609
|
-
total: 1,
|
|
26610
|
-
message: null
|
|
26611
|
-
};
|
|
26612
|
-
}
|
|
26613
|
-
|
|
26614
25683
|
class Dropdown extends React.Component {
|
|
26615
25684
|
constructor(...args) {
|
|
26616
25685
|
super(...args);
|
|
@@ -27063,7 +26132,7 @@ class GradedGroup extends React.Component {
|
|
|
27063
26132
|
DEFAULT_INVALID_MESSAGE_2
|
|
27064
26133
|
} = this.context.strings;
|
|
27065
26134
|
const status = score.type === "points" ? score.total === score.earned ? GRADING_STATUSES.correct : GRADING_STATUSES.incorrect : GRADING_STATUSES.invalid;
|
|
27066
|
-
const message = score.type === "points" ? score.message || "" : score.message ? `${INVALID_MESSAGE_PREFIX} ${score.message}` : `${INVALID_MESSAGE_PREFIX} ${DEFAULT_INVALID_MESSAGE_1}${DEFAULT_INVALID_MESSAGE_2}`;
|
|
26135
|
+
const message = score.type === "points" ? score.message || "" : score.message ? `${INVALID_MESSAGE_PREFIX} ${mapErrorToString(score.message)}` : `${INVALID_MESSAGE_PREFIX} ${DEFAULT_INVALID_MESSAGE_1}${DEFAULT_INVALID_MESSAGE_2}`;
|
|
27067
26136
|
this.setState({
|
|
27068
26137
|
status: status,
|
|
27069
26138
|
message: message,
|
|
@@ -27673,408 +26742,21 @@ const getPromptJSON$g = (renderProps, userInput) => {
|
|
|
27673
26742
|
};
|
|
27674
26743
|
};
|
|
27675
26744
|
|
|
27676
|
-
|
|
27677
|
-
|
|
26745
|
+
const movableTypeToComponent = {
|
|
26746
|
+
// @ts-expect-error - TS2339 - Property 'Plot' does not exist on type 'typeof Graphie'.
|
|
26747
|
+
PLOT: Graphie.Plot,
|
|
26748
|
+
// @ts-expect-error - TS2339 - Property 'Parabola' does not exist on type 'typeof Graphie'.
|
|
26749
|
+
PARABOLA: Graphie.Parabola,
|
|
26750
|
+
// @ts-expect-error - TS2339 - Property 'Sinusoid' does not exist on type 'typeof Graphie'.
|
|
26751
|
+
SINUSOID: Graphie.Sinusoid
|
|
26752
|
+
};
|
|
27678
26753
|
const DEFAULT_BACKGROUND_IMAGE = {
|
|
27679
26754
|
url: null
|
|
27680
26755
|
};
|
|
27681
|
-
|
|
27682
|
-
// TODO(charlie): These really need to go into a utility file as they're being
|
|
27683
|
-
// used by both interactive-graph and now grapher.
|
|
27684
|
-
function canonicalSineCoefficients(coeffs) {
|
|
27685
|
-
// For a curve of the form f(x) = a * Sin(b * x - c) + d,
|
|
27686
|
-
// this function ensures that a, b > 0, and c is its
|
|
27687
|
-
// smallest possible positive value.
|
|
27688
|
-
let amplitude = coeffs[0];
|
|
27689
|
-
let angularFrequency = coeffs[1];
|
|
27690
|
-
let phase = coeffs[2];
|
|
27691
|
-
const verticalOffset = coeffs[3];
|
|
27692
|
-
|
|
27693
|
-
// Guarantee a > 0
|
|
27694
|
-
if (amplitude < 0) {
|
|
27695
|
-
amplitude *= -1;
|
|
27696
|
-
angularFrequency *= -1;
|
|
27697
|
-
phase *= -1;
|
|
27698
|
-
}
|
|
27699
|
-
const period = 2 * Math.PI;
|
|
27700
|
-
// Guarantee b > 0
|
|
27701
|
-
if (angularFrequency < 0) {
|
|
27702
|
-
angularFrequency *= -1;
|
|
27703
|
-
phase *= -1;
|
|
27704
|
-
phase += period / 2;
|
|
27705
|
-
}
|
|
27706
|
-
|
|
27707
|
-
// Guarantee c is smallest possible positive value
|
|
27708
|
-
while (phase > 0) {
|
|
27709
|
-
phase -= period;
|
|
27710
|
-
}
|
|
27711
|
-
while (phase < 0) {
|
|
27712
|
-
phase += period;
|
|
27713
|
-
}
|
|
27714
|
-
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
27715
|
-
}
|
|
27716
|
-
function canonicalTangentCoefficients(coeffs) {
|
|
27717
|
-
// For a curve of the form f(x) = a * Tan(b * x - c) + d,
|
|
27718
|
-
// this function ensures that a, b > 0, and c is its
|
|
27719
|
-
// smallest possible positive value.
|
|
27720
|
-
let amplitude = coeffs[0];
|
|
27721
|
-
let angularFrequency = coeffs[1];
|
|
27722
|
-
let phase = coeffs[2];
|
|
27723
|
-
const verticalOffset = coeffs[3];
|
|
27724
|
-
|
|
27725
|
-
// Guarantee a > 0
|
|
27726
|
-
if (amplitude < 0) {
|
|
27727
|
-
amplitude *= -1;
|
|
27728
|
-
angularFrequency *= -1;
|
|
27729
|
-
phase *= -1;
|
|
27730
|
-
}
|
|
27731
|
-
const period = Math.PI;
|
|
27732
|
-
// Guarantee b > 0
|
|
27733
|
-
if (angularFrequency < 0) {
|
|
27734
|
-
angularFrequency *= -1;
|
|
27735
|
-
phase *= -1;
|
|
27736
|
-
phase += period / 2;
|
|
27737
|
-
}
|
|
27738
|
-
|
|
27739
|
-
// Guarantee c is smallest possible positive value
|
|
27740
|
-
while (phase > 0) {
|
|
27741
|
-
phase -= period;
|
|
27742
|
-
}
|
|
27743
|
-
while (phase < 0) {
|
|
27744
|
-
phase += period;
|
|
27745
|
-
}
|
|
27746
|
-
return [amplitude, angularFrequency, phase, verticalOffset];
|
|
27747
|
-
}
|
|
27748
|
-
const PlotDefaults = {
|
|
27749
|
-
areEqual: function (coeffs1, coeffs2) {
|
|
27750
|
-
return Util.deepEq(coeffs1, coeffs2);
|
|
27751
|
-
},
|
|
27752
|
-
Movable: Plot$1,
|
|
27753
|
-
getPropsForCoeffs: function (coeffs) {
|
|
27754
|
-
return {
|
|
27755
|
-
// @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; }'.
|
|
27756
|
-
fn: _.partial(this.getFunctionForCoeffs, coeffs)
|
|
27757
|
-
};
|
|
27758
|
-
}
|
|
27759
|
-
};
|
|
27760
|
-
const Linear = _.extend({}, PlotDefaults, {
|
|
27761
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/67aaf581e6d9ef9038c10558a1f70ac21c11c9f8.png",
|
|
27762
|
-
defaultCoords: [[0.25, 0.75], [0.75, 0.75]],
|
|
27763
|
-
getCoefficients: function (coords) {
|
|
27764
|
-
const p1 = coords[0];
|
|
27765
|
-
const p2 = coords[1];
|
|
27766
|
-
const denom = p2[0] - p1[0];
|
|
27767
|
-
const num = p2[1] - p1[1];
|
|
27768
|
-
if (denom === 0) {
|
|
27769
|
-
return;
|
|
27770
|
-
}
|
|
27771
|
-
const m = num / denom;
|
|
27772
|
-
const b = p2[1] - m * p2[0];
|
|
27773
|
-
return [m, b];
|
|
27774
|
-
},
|
|
27775
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
27776
|
-
const m = coeffs[0];
|
|
27777
|
-
const b = coeffs[1];
|
|
27778
|
-
return m * x + b;
|
|
27779
|
-
},
|
|
27780
|
-
getEquationString: function (coords) {
|
|
27781
|
-
const coeffs = this.getCoefficients(coords);
|
|
27782
|
-
const m = coeffs[0];
|
|
27783
|
-
const b = coeffs[1];
|
|
27784
|
-
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
27785
|
-
}
|
|
27786
|
-
});
|
|
27787
|
-
const Quadratic = _.extend({}, PlotDefaults, {
|
|
27788
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/e23d36e6fc29ee37174e92c9daba2a66677128ab.png",
|
|
27789
|
-
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
27790
|
-
// @ts-expect-error - TS2339 - Property 'Parabola' does not exist on type 'typeof Graphie'.
|
|
27791
|
-
Movable: Graphie.Parabola,
|
|
27792
|
-
getCoefficients: function (coords) {
|
|
27793
|
-
const p1 = coords[0];
|
|
27794
|
-
const p2 = coords[1];
|
|
27795
|
-
|
|
27796
|
-
// Parabola with vertex (h, k) has form: y = a * (h - k)^2 + k
|
|
27797
|
-
const h = p1[0];
|
|
27798
|
-
const k = p1[1];
|
|
27799
|
-
|
|
27800
|
-
// Use these to calculate familiar a, b, c
|
|
27801
|
-
const a = (p2[1] - k) / ((p2[0] - h) * (p2[0] - h));
|
|
27802
|
-
const b = -2 * h * a;
|
|
27803
|
-
const c = a * h * h + k;
|
|
27804
|
-
return [a, b, c];
|
|
27805
|
-
},
|
|
27806
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
27807
|
-
const a = coeffs[0];
|
|
27808
|
-
const b = coeffs[1];
|
|
27809
|
-
const c = coeffs[2];
|
|
27810
|
-
return (a * x + b) * x + c;
|
|
27811
|
-
},
|
|
27812
|
-
getPropsForCoeffs: function (coeffs) {
|
|
27813
|
-
return {
|
|
27814
|
-
a: coeffs[0],
|
|
27815
|
-
b: coeffs[1],
|
|
27816
|
-
c: coeffs[2]
|
|
27817
|
-
};
|
|
27818
|
-
},
|
|
27819
|
-
getEquationString: function (coords) {
|
|
27820
|
-
const coeffs = this.getCoefficients(coords);
|
|
27821
|
-
const a = coeffs[0];
|
|
27822
|
-
const b = coeffs[1];
|
|
27823
|
-
const c = coeffs[2];
|
|
27824
|
-
return "y = " + a.toFixed(3) + "x^2 + " + b.toFixed(3) + "x + " + c.toFixed(3);
|
|
27825
|
-
}
|
|
27826
|
-
});
|
|
27827
|
-
const Sinusoid = _.extend({}, PlotDefaults, {
|
|
27828
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/3d68e7718498475f53b206c2ab285626baf8857e.png",
|
|
27829
|
-
defaultCoords: [[0.5, 0.5], [0.6, 0.6]],
|
|
27830
|
-
// @ts-expect-error - TS2339 - Property 'Sinusoid' does not exist on type 'typeof Graphie'.
|
|
27831
|
-
Movable: Graphie.Sinusoid,
|
|
27832
|
-
getCoefficients: function (coords) {
|
|
27833
|
-
const p1 = coords[0];
|
|
27834
|
-
const p2 = coords[1];
|
|
27835
|
-
const a = p2[1] - p1[1];
|
|
27836
|
-
const b = Math.PI / (2 * (p2[0] - p1[0]));
|
|
27837
|
-
const c = p1[0] * b;
|
|
27838
|
-
const d = p1[1];
|
|
27839
|
-
return [a, b, c, d];
|
|
27840
|
-
},
|
|
27841
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
27842
|
-
const a = coeffs[0];
|
|
27843
|
-
const b = coeffs[1];
|
|
27844
|
-
const c = coeffs[2];
|
|
27845
|
-
const d = coeffs[3];
|
|
27846
|
-
return a * Math.sin(b * x - c) + d;
|
|
27847
|
-
},
|
|
27848
|
-
getPropsForCoeffs: function (coeffs) {
|
|
27849
|
-
return {
|
|
27850
|
-
a: coeffs[0],
|
|
27851
|
-
b: coeffs[1],
|
|
27852
|
-
c: coeffs[2],
|
|
27853
|
-
d: coeffs[3]
|
|
27854
|
-
};
|
|
27855
|
-
},
|
|
27856
|
-
getEquationString: function (coords) {
|
|
27857
|
-
const coeffs = this.getCoefficients(coords);
|
|
27858
|
-
const a = coeffs[0];
|
|
27859
|
-
const b = coeffs[1];
|
|
27860
|
-
const c = coeffs[2];
|
|
27861
|
-
const d = coeffs[3];
|
|
27862
|
-
return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
|
|
27863
|
-
},
|
|
27864
|
-
areEqual: function (coeffs1, coeffs2) {
|
|
27865
|
-
return Util.deepEq(canonicalSineCoefficients(coeffs1), canonicalSineCoefficients(coeffs2));
|
|
27866
|
-
}
|
|
27867
|
-
});
|
|
27868
|
-
const Tangent = _.extend({}, PlotDefaults, {
|
|
27869
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/7db80d23c35214f98659fe1cf0765811c1bbfbba.png",
|
|
27870
|
-
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
27871
|
-
getCoefficients: function (coords) {
|
|
27872
|
-
const p1 = coords[0];
|
|
27873
|
-
const p2 = coords[1];
|
|
27874
|
-
const a = p2[1] - p1[1];
|
|
27875
|
-
const b = Math.PI / (4 * (p2[0] - p1[0]));
|
|
27876
|
-
const c = p1[0] * b;
|
|
27877
|
-
const d = p1[1];
|
|
27878
|
-
return [a, b, c, d];
|
|
27879
|
-
},
|
|
27880
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
27881
|
-
const a = coeffs[0];
|
|
27882
|
-
const b = coeffs[1];
|
|
27883
|
-
const c = coeffs[2];
|
|
27884
|
-
const d = coeffs[3];
|
|
27885
|
-
return a * Math.tan(b * x - c) + d;
|
|
27886
|
-
},
|
|
27887
|
-
getEquationString: function (coords) {
|
|
27888
|
-
const coeffs = this.getCoefficients(coords);
|
|
27889
|
-
const a = coeffs[0];
|
|
27890
|
-
const b = coeffs[1];
|
|
27891
|
-
const c = coeffs[2];
|
|
27892
|
-
const d = coeffs[3];
|
|
27893
|
-
return "y = " + a.toFixed(3) + " sin(" + b.toFixed(3) + "x - " + c.toFixed(3) + ") + " + d.toFixed(3);
|
|
27894
|
-
},
|
|
27895
|
-
areEqual: function (coeffs1, coeffs2) {
|
|
27896
|
-
return Util.deepEq(canonicalTangentCoefficients(coeffs1), canonicalTangentCoefficients(coeffs2));
|
|
27897
|
-
}
|
|
27898
|
-
});
|
|
27899
|
-
const Exponential = _.extend({}, PlotDefaults, {
|
|
27900
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/9cbfad55525e3ce755a31a631b074670a5dad611.png",
|
|
27901
|
-
defaultCoords: [[0.5, 0.55], [0.75, 0.75]],
|
|
27902
|
-
defaultAsymptote: [[0, 0.5], [1.0, 0.5]],
|
|
27903
|
-
/**
|
|
27904
|
-
* Add extra constraints for movement of the points or asymptote (below):
|
|
27905
|
-
* newCoord: [x, y]
|
|
27906
|
-
* The end position of the point or asymptote endpoint
|
|
27907
|
-
* oldCoord: [x, y]
|
|
27908
|
-
* The old position of the point or asymptote endpoint
|
|
27909
|
-
* coords:
|
|
27910
|
-
* An array of coordinates representing the proposed end configuration
|
|
27911
|
-
* of the plot coordinates.
|
|
27912
|
-
* asymptote:
|
|
27913
|
-
* An array of coordinates representing the proposed end configuration
|
|
27914
|
-
* of the asymptote.
|
|
27915
|
-
*
|
|
27916
|
-
* Return: either a coordinate (to be used as the resulting coordinate of
|
|
27917
|
-
* the move) or a boolean, where `true` uses newCoord as the resulting
|
|
27918
|
-
* coordinate, and `false` uses oldCoord as the resulting coordinate.
|
|
27919
|
-
*/
|
|
27920
|
-
extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
27921
|
-
const y = asymptote[0][1];
|
|
27922
|
-
return _.all(coords, coord => coord[1] !== y);
|
|
27923
|
-
},
|
|
27924
|
-
extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
27925
|
-
const y = newCoord[1];
|
|
27926
|
-
const isValid = _.all(coords, coord => coord[1] > y) || _.all(coords, coord => coord[1] < y);
|
|
27927
|
-
if (isValid) {
|
|
27928
|
-
return [oldCoord[0], y];
|
|
27929
|
-
}
|
|
27930
|
-
// Snap the asymptote as close as possible, i.e., if the user moves
|
|
27931
|
-
// the mouse really quickly into an invalid region
|
|
27932
|
-
const oldY = oldCoord[1];
|
|
27933
|
-
const wasBelow = _.all(coords, coord => coord[1] > oldY);
|
|
27934
|
-
if (wasBelow) {
|
|
27935
|
-
const bottomMost = _.min(_.map(coords, coord => coord[1]));
|
|
27936
|
-
return [oldCoord[0], bottomMost - graph.snapStep[1]];
|
|
27937
|
-
}
|
|
27938
|
-
const topMost = _.max(_.map(coords, coord => coord[1]));
|
|
27939
|
-
return [oldCoord[0], topMost + graph.snapStep[1]];
|
|
27940
|
-
},
|
|
27941
|
-
allowReflectOverAsymptote: true,
|
|
27942
|
-
getCoefficients: function (coords, asymptote) {
|
|
27943
|
-
const p1 = coords[0];
|
|
27944
|
-
const p2 = coords[1];
|
|
27945
|
-
const c = asymptote[0][1];
|
|
27946
|
-
const b = Math.log((p1[1] - c) / (p2[1] - c)) / (p1[0] - p2[0]);
|
|
27947
|
-
const a = (p1[1] - c) / Math.exp(b * p1[0]);
|
|
27948
|
-
return [a, b, c];
|
|
27949
|
-
},
|
|
27950
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
27951
|
-
const a = coeffs[0];
|
|
27952
|
-
const b = coeffs[1];
|
|
27953
|
-
const c = coeffs[2];
|
|
27954
|
-
return a * Math.exp(b * x) + c;
|
|
27955
|
-
},
|
|
27956
|
-
getEquationString: function (coords, asymptote) {
|
|
27957
|
-
if (!asymptote) {
|
|
27958
|
-
return null;
|
|
27959
|
-
}
|
|
27960
|
-
const coeffs = this.getCoefficients(coords, asymptote);
|
|
27961
|
-
const a = coeffs[0];
|
|
27962
|
-
const b = coeffs[1];
|
|
27963
|
-
const c = coeffs[2];
|
|
27964
|
-
return "y = " + a.toFixed(3) + "e^(" + b.toFixed(3) + "x) + " + c.toFixed(3);
|
|
27965
|
-
}
|
|
27966
|
-
});
|
|
27967
|
-
const Logarithm = _.extend({}, PlotDefaults, {
|
|
27968
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/f6491e99d34af34d924bfe0231728ad912068dc3.png",
|
|
27969
|
-
defaultCoords: [[0.55, 0.5], [0.75, 0.75]],
|
|
27970
|
-
defaultAsymptote: [[0.5, 0], [0.5, 1.0]],
|
|
27971
|
-
extraCoordConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
27972
|
-
const x = asymptote[0][0];
|
|
27973
|
-
return _.all(coords, coord => coord[0] !== x) && coords[0][1] !== coords[1][1];
|
|
27974
|
-
},
|
|
27975
|
-
extraAsymptoteConstraint: function (newCoord, oldCoord, coords, asymptote, graph) {
|
|
27976
|
-
const x = newCoord[0];
|
|
27977
|
-
const isValid = _.all(coords, coord => coord[0] > x) || _.all(coords, coord => coord[0] < x);
|
|
27978
|
-
if (isValid) {
|
|
27979
|
-
return [x, oldCoord[1]];
|
|
27980
|
-
}
|
|
27981
|
-
// Snap the asymptote as close as possible, i.e., if the user moves
|
|
27982
|
-
// the mouse really quickly into an invalid region
|
|
27983
|
-
const oldX = oldCoord[0];
|
|
27984
|
-
const wasLeft = _.all(coords, coord => coord[0] > oldX);
|
|
27985
|
-
if (wasLeft) {
|
|
27986
|
-
const leftMost = _.min(_.map(coords, coord => coord[0]));
|
|
27987
|
-
return [leftMost - graph.snapStep[0], oldCoord[1]];
|
|
27988
|
-
}
|
|
27989
|
-
const rightMost = _.max(_.map(coords, coord => coord[0]));
|
|
27990
|
-
return [rightMost + graph.snapStep[0], oldCoord[1]];
|
|
27991
|
-
},
|
|
27992
|
-
allowReflectOverAsymptote: true,
|
|
27993
|
-
getCoefficients: function (coords, asymptote) {
|
|
27994
|
-
// It's easiest to calculate the logarithm's coefficients by thinking
|
|
27995
|
-
// about it as the inverse of the exponential, so we flip x and y and
|
|
27996
|
-
// perform some algebra on the coefficients. This also unifies the
|
|
27997
|
-
// logic between the two 'models'.
|
|
27998
|
-
const flip = coord => [coord[1], coord[0]];
|
|
27999
|
-
const inverseCoeffs = Exponential.getCoefficients(_.map(coords, flip), _.map(asymptote, flip));
|
|
28000
|
-
if (inverseCoeffs) {
|
|
28001
|
-
const c = -inverseCoeffs[2] / inverseCoeffs[0];
|
|
28002
|
-
const b = 1 / inverseCoeffs[0];
|
|
28003
|
-
const a = 1 / inverseCoeffs[1];
|
|
28004
|
-
return [a, b, c];
|
|
28005
|
-
}
|
|
28006
|
-
},
|
|
28007
|
-
getFunctionForCoeffs: function (coeffs, x, asymptote) {
|
|
28008
|
-
const a = coeffs[0];
|
|
28009
|
-
const b = coeffs[1];
|
|
28010
|
-
const c = coeffs[2];
|
|
28011
|
-
return a * Math.log(b * x + c);
|
|
28012
|
-
},
|
|
28013
|
-
getEquationString: function (coords, asymptote) {
|
|
28014
|
-
if (!asymptote) {
|
|
28015
|
-
return null;
|
|
28016
|
-
}
|
|
28017
|
-
const coeffs = this.getCoefficients(coords, asymptote);
|
|
28018
|
-
const a = coeffs[0];
|
|
28019
|
-
const b = coeffs[1];
|
|
28020
|
-
const c = coeffs[2];
|
|
28021
|
-
return "y = ln(" + a.toFixed(3) + "x + " + b.toFixed(3) + ") + " + c.toFixed(3);
|
|
28022
|
-
}
|
|
28023
|
-
});
|
|
28024
|
-
const AbsoluteValue = _.extend({}, PlotDefaults, {
|
|
28025
|
-
url: "https://ka-perseus-graphie.s3.amazonaws.com/8256a630175a0cb1d11de223d6de0266daf98721.png",
|
|
28026
|
-
defaultCoords: [[0.5, 0.5], [0.75, 0.75]],
|
|
28027
|
-
getCoefficients: function (coords) {
|
|
28028
|
-
const p1 = coords[0];
|
|
28029
|
-
const p2 = coords[1];
|
|
28030
|
-
const denom = p2[0] - p1[0];
|
|
28031
|
-
const num = p2[1] - p1[1];
|
|
28032
|
-
if (denom === 0) {
|
|
28033
|
-
return;
|
|
28034
|
-
}
|
|
28035
|
-
let m = Math.abs(num / denom);
|
|
28036
|
-
if (p2[1] < p1[1]) {
|
|
28037
|
-
m *= -1;
|
|
28038
|
-
}
|
|
28039
|
-
const horizontalOffset = p1[0];
|
|
28040
|
-
const verticalOffset = p1[1];
|
|
28041
|
-
return [m, horizontalOffset, verticalOffset];
|
|
28042
|
-
},
|
|
28043
|
-
getFunctionForCoeffs: function (coeffs, x) {
|
|
28044
|
-
const m = coeffs[0];
|
|
28045
|
-
const horizontalOffset = coeffs[1];
|
|
28046
|
-
const verticalOffset = coeffs[2];
|
|
28047
|
-
return m * Math.abs(x - horizontalOffset) + verticalOffset;
|
|
28048
|
-
},
|
|
28049
|
-
getEquationString: function (coords) {
|
|
28050
|
-
const coeffs = this.getCoefficients(coords);
|
|
28051
|
-
const m = coeffs[0];
|
|
28052
|
-
const horizontalOffset = coeffs[1];
|
|
28053
|
-
const verticalOffset = coeffs[2];
|
|
28054
|
-
return "y = " + m.toFixed(3) + "| x - " + horizontalOffset.toFixed(3) + "| + " + verticalOffset.toFixed(3);
|
|
28055
|
-
}
|
|
28056
|
-
});
|
|
28057
|
-
|
|
28058
|
-
/* Utility functions for dealing with graphing interfaces. */
|
|
28059
|
-
const functionTypeMapping = {
|
|
28060
|
-
linear: Linear,
|
|
28061
|
-
quadratic: Quadratic,
|
|
28062
|
-
sinusoid: Sinusoid,
|
|
28063
|
-
tangent: Tangent,
|
|
28064
|
-
exponential: Exponential,
|
|
28065
|
-
logarithm: Logarithm,
|
|
28066
|
-
absolute_value: AbsoluteValue
|
|
28067
|
-
};
|
|
28068
|
-
const allTypes = _.keys(functionTypeMapping);
|
|
28069
|
-
function functionForType(type) {
|
|
28070
|
-
// @ts-expect-error: TypeScript doesn't know how to use deal with generics
|
|
28071
|
-
// and conditional types in this way.
|
|
28072
|
-
return functionTypeMapping[type];
|
|
28073
|
-
}
|
|
28074
26756
|
const getEquationString = props => {
|
|
28075
26757
|
const plot = props.plot;
|
|
28076
26758
|
if (plot.type && plot.coords) {
|
|
28077
|
-
const handler = functionForType(plot.type);
|
|
26759
|
+
const handler = GrapherUtil$1.functionForType(plot.type);
|
|
28078
26760
|
const result = handler.getEquationString(plot.coords, plot.asymptote);
|
|
28079
26761
|
return result || "";
|
|
28080
26762
|
}
|
|
@@ -28132,7 +26814,7 @@ const defaultPlotProps = (type, graph) => {
|
|
|
28132
26814
|
// extra information. It would be valid to always submit the default
|
|
28133
26815
|
// widget before even reading the question; you can't lose, but you
|
|
28134
26816
|
// might get a free win.
|
|
28135
|
-
const model = functionForType(type);
|
|
26817
|
+
const model = GrapherUtil$1.functionForType(type);
|
|
28136
26818
|
const defaultAsymptote = "defaultAsymptote" in model ? model.defaultAsymptote : null;
|
|
28137
26819
|
const gridStep = [1, 1];
|
|
28138
26820
|
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type '[number, number]'.
|
|
@@ -28178,70 +26860,12 @@ const typeToButton = type => {
|
|
|
28178
26860
|
value: type,
|
|
28179
26861
|
title: capitalized,
|
|
28180
26862
|
content: /*#__PURE__*/React.createElement("img", {
|
|
28181
|
-
src: staticUrl(functionForType(type).url),
|
|
26863
|
+
src: staticUrl(GrapherUtil$1.functionForType(type).url),
|
|
28182
26864
|
alt: capitalized
|
|
28183
26865
|
})
|
|
28184
26866
|
};
|
|
28185
26867
|
};
|
|
28186
26868
|
|
|
28187
|
-
function getCoefficientsByType(data) {
|
|
28188
|
-
if (data.coords == null) {
|
|
28189
|
-
return undefined;
|
|
28190
|
-
}
|
|
28191
|
-
if (data.type === "exponential" || data.type === "logarithm") {
|
|
28192
|
-
const grader = functionForType(data.type);
|
|
28193
|
-
return grader.getCoefficients(data.coords, data.asymptote);
|
|
28194
|
-
} else if (data.type === "linear" || data.type === "quadratic" || data.type === "absolute_value" || data.type === "sinusoid" || data.type === "tangent") {
|
|
28195
|
-
const grader = functionForType(data.type);
|
|
28196
|
-
return grader.getCoefficients(data.coords);
|
|
28197
|
-
} else {
|
|
28198
|
-
throw new PerseusError("Invalid grapher type", Errors.InvalidInput);
|
|
28199
|
-
}
|
|
28200
|
-
}
|
|
28201
|
-
function scoreGrapher(userInput, rubric) {
|
|
28202
|
-
if (userInput.type !== rubric.correct.type) {
|
|
28203
|
-
return {
|
|
28204
|
-
type: "points",
|
|
28205
|
-
earned: 0,
|
|
28206
|
-
total: 1,
|
|
28207
|
-
message: null
|
|
28208
|
-
};
|
|
28209
|
-
}
|
|
28210
|
-
|
|
28211
|
-
// We haven't moved the coords
|
|
28212
|
-
if (userInput.coords == null) {
|
|
28213
|
-
return {
|
|
28214
|
-
type: "invalid",
|
|
28215
|
-
message: null
|
|
28216
|
-
};
|
|
28217
|
-
}
|
|
28218
|
-
|
|
28219
|
-
// Get new function handler for grading
|
|
28220
|
-
const grader = functionForType(userInput.type);
|
|
28221
|
-
const guessCoeffs = getCoefficientsByType(userInput);
|
|
28222
|
-
const correctCoeffs = getCoefficientsByType(rubric.correct);
|
|
28223
|
-
if (guessCoeffs == null || correctCoeffs == null) {
|
|
28224
|
-
return {
|
|
28225
|
-
type: "invalid",
|
|
28226
|
-
message: null
|
|
28227
|
-
};
|
|
28228
|
-
}
|
|
28229
|
-
if (grader.areEqual(guessCoeffs, correctCoeffs)) {
|
|
28230
|
-
return {
|
|
28231
|
-
type: "points",
|
|
28232
|
-
earned: 1,
|
|
28233
|
-
total: 1,
|
|
28234
|
-
message: null
|
|
28235
|
-
};
|
|
28236
|
-
}
|
|
28237
|
-
return {
|
|
28238
|
-
type: "points",
|
|
28239
|
-
earned: 0,
|
|
28240
|
-
total: 1,
|
|
28241
|
-
message: null
|
|
28242
|
-
};
|
|
28243
|
-
}
|
|
28244
|
-
|
|
28245
26869
|
// @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
|
|
28246
26870
|
const MovablePoint$3 = Graphie.MovablePoint;
|
|
28247
26871
|
// @ts-expect-error - TS2339 - Property 'MovableLine' does not exist on type 'typeof Graphie'.
|
|
@@ -28289,7 +26913,8 @@ class FunctionGrapher extends React.Component {
|
|
|
28289
26913
|
return;
|
|
28290
26914
|
}
|
|
28291
26915
|
const functionProps = model.getPropsForCoeffs(coeffs, xRange);
|
|
28292
|
-
|
|
26916
|
+
const Movable = movableTypeToComponent[model.movable];
|
|
26917
|
+
return /*#__PURE__*/React.createElement(Movable, _extends({}, functionProps, {
|
|
28293
26918
|
key: this.props.model.url,
|
|
28294
26919
|
range: xRange,
|
|
28295
26920
|
style: style
|
|
@@ -28305,8 +26930,8 @@ class FunctionGrapher extends React.Component {
|
|
|
28305
26930
|
return asymptote && /*#__PURE__*/React.createElement(MovableLine$2, {
|
|
28306
26931
|
onMove: (newCoord, oldCoord) => {
|
|
28307
26932
|
// Calculate and apply displacement
|
|
28308
|
-
const delta = vector$
|
|
28309
|
-
const newAsymptote = _.map(this._asymptote(), coord => vector$
|
|
26933
|
+
const delta = vector$3.subtract(newCoord, oldCoord);
|
|
26934
|
+
const newAsymptote = _.map(this._asymptote(), coord => vector$3.add(coord, delta));
|
|
28310
26935
|
this.props.onChange({
|
|
28311
26936
|
asymptote: newAsymptote
|
|
28312
26937
|
});
|
|
@@ -28317,8 +26942,8 @@ class FunctionGrapher extends React.Component {
|
|
|
28317
26942
|
// @ts-expect-error - TS2339 - Property 'constraints' does not exist on type '(graphie: any, movable: any, options: any) => void'.
|
|
28318
26943
|
Interactive2.MovableLine.constraints.snap(), (newCoord, oldCoord) => {
|
|
28319
26944
|
// Calculate and apply proposed displacement
|
|
28320
|
-
const delta = vector$
|
|
28321
|
-
const proposedAsymptote = _.map(this._asymptote(), coord => vector$
|
|
26945
|
+
const delta = vector$3.subtract(newCoord, oldCoord);
|
|
26946
|
+
const proposedAsymptote = _.map(this._asymptote(), coord => vector$3.add(coord, delta));
|
|
28322
26947
|
// Verify that resulting asymptote is valid for graph
|
|
28323
26948
|
if (model.extraAsymptoteConstraint) {
|
|
28324
26949
|
return model.extraAsymptoteConstraint(newCoord, oldCoord, this._coords(), proposedAsymptote, graph);
|
|
@@ -28590,7 +27215,7 @@ class Grapher extends React.Component {
|
|
|
28590
27215
|
setup: this._setupGraphie
|
|
28591
27216
|
},
|
|
28592
27217
|
onChange: this.handlePlotChanges,
|
|
28593
|
-
model: type && functionForType(type),
|
|
27218
|
+
model: type && GrapherUtil$1.functionForType(type),
|
|
28594
27219
|
coords: coords,
|
|
28595
27220
|
asymptote: asymptote,
|
|
28596
27221
|
static: this.props.static,
|
|
@@ -28827,31 +27452,6 @@ const getPromptJSON$e = () => {
|
|
|
28827
27452
|
return getUnsupportedPromptJSON("iframe");
|
|
28828
27453
|
};
|
|
28829
27454
|
|
|
28830
|
-
function scoreIframe(userInput) {
|
|
28831
|
-
// The iframe can tell us whether it's correct or incorrect,
|
|
28832
|
-
// and pass an optional message
|
|
28833
|
-
if (userInput.status === "correct") {
|
|
28834
|
-
return {
|
|
28835
|
-
type: "points",
|
|
28836
|
-
earned: 1,
|
|
28837
|
-
total: 1,
|
|
28838
|
-
message: userInput.message || null
|
|
28839
|
-
};
|
|
28840
|
-
}
|
|
28841
|
-
if (userInput.status === "incorrect") {
|
|
28842
|
-
return {
|
|
28843
|
-
type: "points",
|
|
28844
|
-
earned: 0,
|
|
28845
|
-
total: 1,
|
|
28846
|
-
message: userInput.message || null
|
|
28847
|
-
};
|
|
28848
|
-
}
|
|
28849
|
-
return {
|
|
28850
|
-
type: "invalid",
|
|
28851
|
-
message: "Keep going, you're not there yet!"
|
|
28852
|
-
};
|
|
28853
|
-
}
|
|
28854
|
-
|
|
28855
27455
|
/**
|
|
28856
27456
|
* This is an iframe widget. It is used for rendering an iframe that
|
|
28857
27457
|
* then communicates its state via window.postMessage
|
|
@@ -29285,7 +27885,7 @@ class Interaction extends React.Component {
|
|
|
29285
27885
|
const xDiff = this._eval("(" + options.endX + ")-(" + options.startX + ")");
|
|
29286
27886
|
// @ts-expect-error - TS2554 - Expected 2 arguments, but got 1.
|
|
29287
27887
|
const yDiff = this._eval("(" + options.endY + ")-(" + options.startY + ")");
|
|
29288
|
-
const endCoord = vector$
|
|
27888
|
+
const endCoord = vector$3.add(startCoord, [xDiff, yDiff]);
|
|
29289
27889
|
const variables = _.clone(this.state.variables);
|
|
29290
27890
|
variables["x_" + options.startSubscript] = startCoord[0];
|
|
29291
27891
|
variables["y_" + options.startSubscript] = startCoord[1];
|
|
@@ -29873,15 +28473,15 @@ class Graph extends React.Component {
|
|
|
29873
28473
|
if (this.props.markings === "graph") {
|
|
29874
28474
|
graphie.graphInit({
|
|
29875
28475
|
range: range,
|
|
29876
|
-
scale: vector$
|
|
28476
|
+
scale: vector$3.map(gridConfig, g => g.scale),
|
|
29877
28477
|
axisArrows: "<->",
|
|
29878
28478
|
labelFormat: function (s) {
|
|
29879
28479
|
return "\\small{" + s + "}";
|
|
29880
28480
|
},
|
|
29881
28481
|
gridStep: this.props.gridStep,
|
|
29882
|
-
tickStep: vector$
|
|
28482
|
+
tickStep: vector$3.map(gridConfig, g => g.tickStep),
|
|
29883
28483
|
labelStep: 1,
|
|
29884
|
-
unityLabels: vector$
|
|
28484
|
+
unityLabels: vector$3.map(gridConfig, g => g.unityLabel),
|
|
29885
28485
|
isMobile: this.props.isMobile
|
|
29886
28486
|
});
|
|
29887
28487
|
graphie.label([0, range[1][1]], labels[1], this.props.isMobile ? "below right" : "above");
|
|
@@ -29889,7 +28489,7 @@ class Graph extends React.Component {
|
|
|
29889
28489
|
} else if (this.props.markings === "grid") {
|
|
29890
28490
|
graphie.graphInit({
|
|
29891
28491
|
range: range,
|
|
29892
|
-
scale: vector$
|
|
28492
|
+
scale: vector$3.map(gridConfig, g => g.scale),
|
|
29893
28493
|
gridStep: this.props.gridStep,
|
|
29894
28494
|
axes: false,
|
|
29895
28495
|
ticks: false,
|
|
@@ -29899,7 +28499,7 @@ class Graph extends React.Component {
|
|
|
29899
28499
|
} else if (this.props.markings === "none") {
|
|
29900
28500
|
graphie.init({
|
|
29901
28501
|
range: range,
|
|
29902
|
-
scale: vector$
|
|
28502
|
+
scale: vector$3.map(gridConfig, g => g.scale),
|
|
29903
28503
|
isMobile: this.props.isMobile
|
|
29904
28504
|
});
|
|
29905
28505
|
}
|
|
@@ -29972,7 +28572,7 @@ class Graph extends React.Component {
|
|
|
29972
28572
|
}
|
|
29973
28573
|
};
|
|
29974
28574
|
this._getGridConfig = () => {
|
|
29975
|
-
return vector$
|
|
28575
|
+
return vector$3.map(this.props.step, (step, i) => Util.gridDimensionConfig(step, this.props.range[i], this.props.box[i], this.props.gridStep[i]));
|
|
29976
28576
|
};
|
|
29977
28577
|
this._updateProtractor = () => {
|
|
29978
28578
|
if (this.protractor) {
|
|
@@ -30414,61 +29014,6 @@ function findIntersectionOfRays([[a, b], [c, d]], [[p, q], [r, s]]) {
|
|
|
30414
29014
|
}
|
|
30415
29015
|
}
|
|
30416
29016
|
|
|
30417
|
-
// This file contains helper functions for working with angles.
|
|
30418
|
-
|
|
30419
|
-
function convertDegreesToRadians(degrees) {
|
|
30420
|
-
return degrees / 180 * Math.PI;
|
|
30421
|
-
}
|
|
30422
|
-
|
|
30423
|
-
// Returns a value between -180 and 180, inclusive. The angle is measured
|
|
30424
|
-
// between the positive x-axis and the given vector.
|
|
30425
|
-
function calculateAngleInDegrees([x, y]) {
|
|
30426
|
-
return Math.atan2(y, x) * 180 / Math.PI;
|
|
30427
|
-
}
|
|
30428
|
-
|
|
30429
|
-
// Converts polar coordinates to cartesian. The th(eta) parameter is in degrees.
|
|
30430
|
-
function polar(r, th) {
|
|
30431
|
-
if (typeof r === "number") {
|
|
30432
|
-
r = [r, r];
|
|
30433
|
-
}
|
|
30434
|
-
th = th * Math.PI / 180;
|
|
30435
|
-
return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
|
|
30436
|
-
}
|
|
30437
|
-
// This function calculates the angle between two points and an optional vertex.
|
|
30438
|
-
// If the vertex is not provided, the angle is measured between the two points.
|
|
30439
|
-
// This does not account for reflex angles or clockwise position.
|
|
30440
|
-
const getAngleFromVertex = (point, vertex) => {
|
|
30441
|
-
const x = point[0] - vertex[0];
|
|
30442
|
-
const y = point[1] - vertex[1];
|
|
30443
|
-
if (!x && !y) {
|
|
30444
|
-
return 0;
|
|
30445
|
-
}
|
|
30446
|
-
return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
|
|
30447
|
-
};
|
|
30448
|
-
|
|
30449
|
-
// This function calculates the clockwise angle between three points,
|
|
30450
|
-
// and is used to generate the labels and equation strings of the
|
|
30451
|
-
// current angle for the interactive graph.
|
|
30452
|
-
const getClockwiseAngle = (coords, allowReflexAngles = false) => {
|
|
30453
|
-
const coordsCopy = [...coords];
|
|
30454
|
-
// The coords are saved as [point1, vertex, point2] in the interactive graph
|
|
30455
|
-
const areClockwise = clockwise$1([coordsCopy[0], coordsCopy[2], coordsCopy[1]]);
|
|
30456
|
-
|
|
30457
|
-
// We may need to reverse the coordinates if we allow
|
|
30458
|
-
// reflex angles and the points are not in clockwise order.
|
|
30459
|
-
const shouldReverseCoords = !areClockwise || allowReflexAngles;
|
|
30460
|
-
|
|
30461
|
-
// Reverse the coordinates accordingly to ensure the angle is calculated correctly
|
|
30462
|
-
const clockwiseCoords = shouldReverseCoords ? coordsCopy.reverse() : coordsCopy;
|
|
30463
|
-
|
|
30464
|
-
// Calculate the angles between the two points and get the difference
|
|
30465
|
-
// between the two angles to get the clockwise angle.
|
|
30466
|
-
const startAngle = getAngleFromVertex(clockwiseCoords[0], clockwiseCoords[1]);
|
|
30467
|
-
const endAngle = getAngleFromVertex(clockwiseCoords[2], clockwiseCoords[1]);
|
|
30468
|
-
const angle = (startAngle + 360 - endAngle) % 360;
|
|
30469
|
-
return angle;
|
|
30470
|
-
};
|
|
30471
|
-
|
|
30472
29017
|
const defaultGraphConfig = {
|
|
30473
29018
|
range: [[0, 1], [0, 1]],
|
|
30474
29019
|
tickStep: [1, 1],
|
|
@@ -31591,7 +30136,8 @@ const LockedEllipse = props => {
|
|
|
31591
30136
|
return /*#__PURE__*/React.createElement("g", {
|
|
31592
30137
|
className: "locked-ellipse",
|
|
31593
30138
|
"aria-label": hasAria ? ariaLabel : undefined,
|
|
31594
|
-
"aria-hidden": !hasAria
|
|
30139
|
+
"aria-hidden": !hasAria,
|
|
30140
|
+
role: "img"
|
|
31595
30141
|
}, /*#__PURE__*/React.createElement(Ellipse, {
|
|
31596
30142
|
center: center,
|
|
31597
30143
|
radius: radius,
|
|
@@ -31636,12 +30182,13 @@ const LockedFunction = props => {
|
|
|
31636
30182
|
return /*#__PURE__*/React.createElement("g", {
|
|
31637
30183
|
className: "locked-function",
|
|
31638
30184
|
"aria-label": hasAria ? props.ariaLabel : undefined,
|
|
31639
|
-
"aria-hidden": !hasAria
|
|
31640
|
-
|
|
30185
|
+
"aria-hidden": !hasAria,
|
|
30186
|
+
role: "img"
|
|
30187
|
+
}, directionalAxis === "x" && /*#__PURE__*/React.createElement(Plot$2.OfX, _extends({
|
|
31641
30188
|
y: x => equation.eval({
|
|
31642
30189
|
x
|
|
31643
30190
|
})
|
|
31644
|
-
}, plotProps)), directionalAxis === "y" && /*#__PURE__*/React.createElement(Plot$
|
|
30191
|
+
}, plotProps)), directionalAxis === "y" && /*#__PURE__*/React.createElement(Plot$2.OfY, _extends({
|
|
31645
30192
|
x: y => equation.eval({
|
|
31646
30193
|
y
|
|
31647
30194
|
})
|
|
@@ -31667,6 +30214,9 @@ function SVGLine(props) {
|
|
|
31667
30214
|
});
|
|
31668
30215
|
}
|
|
31669
30216
|
|
|
30217
|
+
const {
|
|
30218
|
+
calculateAngleInDegrees: calculateAngleInDegrees$3
|
|
30219
|
+
} = angles;
|
|
31670
30220
|
function Vector(props) {
|
|
31671
30221
|
const {
|
|
31672
30222
|
tail,
|
|
@@ -31676,7 +30226,7 @@ function Vector(props) {
|
|
|
31676
30226
|
} = props;
|
|
31677
30227
|
const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
|
|
31678
30228
|
const direction = vec.sub(tipPx, tailPx);
|
|
31679
|
-
const angle = calculateAngleInDegrees(direction);
|
|
30229
|
+
const angle = calculateAngleInDegrees$3(direction);
|
|
31680
30230
|
return /*#__PURE__*/React.createElement("g", {
|
|
31681
30231
|
style: {
|
|
31682
30232
|
stroke: color,
|
|
@@ -31693,6 +30243,14 @@ function Vector(props) {
|
|
|
31693
30243
|
}));
|
|
31694
30244
|
}
|
|
31695
30245
|
|
|
30246
|
+
function srFormatNumber(a, locale) {
|
|
30247
|
+
// adding zero here converts negative zero to positive zero.
|
|
30248
|
+
return (0 + a).toLocaleString(locale, {
|
|
30249
|
+
maximumFractionDigits: 3,
|
|
30250
|
+
useGrouping: false // no thousands separators
|
|
30251
|
+
});
|
|
30252
|
+
}
|
|
30253
|
+
|
|
31696
30254
|
/**
|
|
31697
30255
|
* Given a ray and a rectangular box, find the point where the ray intersects
|
|
31698
30256
|
* the edge of the box. Assumes the `initialPoint` is inside the box.
|
|
@@ -31738,7 +30296,45 @@ function getArrayWithoutDuplicates(array) {
|
|
|
31738
30296
|
}
|
|
31739
30297
|
return returnArray;
|
|
31740
30298
|
}
|
|
30299
|
+
function getSlopeStringForLine(line, strings) {
|
|
30300
|
+
const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
|
|
30301
|
+
if (!Number.isFinite(slope)) {
|
|
30302
|
+
return strings.srLinearGraphSlopeVertical;
|
|
30303
|
+
}
|
|
30304
|
+
if (slope === 0) {
|
|
30305
|
+
return strings.srLinearGraphSlopeHorizontal;
|
|
30306
|
+
}
|
|
30307
|
+
return slope > 0 ? strings.srLinearGraphSlopeIncreasing : strings.srLinearGraphSlopeDecreasing;
|
|
30308
|
+
}
|
|
30309
|
+
function getInterceptStringForLine(line, strings, locale) {
|
|
30310
|
+
const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
|
|
30311
|
+
const xIntercept = (0 - line[0][1]) / slope + line[0][0];
|
|
30312
|
+
const yIntercept = line[0][1] - slope * line[0][0];
|
|
31741
30313
|
|
|
30314
|
+
// Check if the line fully overlaps with an axis.
|
|
30315
|
+
const overlapsXAxis = line[0][1] === 0 && line[1][1] === 0;
|
|
30316
|
+
const overlapsYAxis = line[0][0] === 0 && line[1][0] === 0;
|
|
30317
|
+
const hasXIntercept = Number.isFinite(xIntercept) && !overlapsXAxis;
|
|
30318
|
+
const hasYIntercept = Number.isFinite(yIntercept) && !overlapsYAxis;
|
|
30319
|
+
if (hasXIntercept && hasYIntercept) {
|
|
30320
|
+
// Describe both intercepts in the same sentence.
|
|
30321
|
+
return xIntercept === 0 && yIntercept === 0 ? strings.srLinearGraphOriginIntercept : strings.srLinearGraphBothIntercepts({
|
|
30322
|
+
xIntercept: srFormatNumber(xIntercept, locale),
|
|
30323
|
+
yIntercept: srFormatNumber(yIntercept, locale)
|
|
30324
|
+
});
|
|
30325
|
+
}
|
|
30326
|
+
|
|
30327
|
+
// Describe only one intercept.
|
|
30328
|
+
return hasXIntercept ? strings.srLinearGraphXOnlyIntercept({
|
|
30329
|
+
xIntercept: srFormatNumber(xIntercept, locale)
|
|
30330
|
+
}) : strings.srLinearGraphYOnlyIntercept({
|
|
30331
|
+
yIntercept: srFormatNumber(yIntercept, locale)
|
|
30332
|
+
});
|
|
30333
|
+
}
|
|
30334
|
+
|
|
30335
|
+
const {
|
|
30336
|
+
calculateAngleInDegrees: calculateAngleInDegrees$2
|
|
30337
|
+
} = angles;
|
|
31742
30338
|
const LockedLine = props => {
|
|
31743
30339
|
const {
|
|
31744
30340
|
color: color$1,
|
|
@@ -31770,7 +30366,7 @@ const LockedLine = props => {
|
|
|
31770
30366
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
31771
30367
|
const [tailPx, tipPx] = useTransformVectorsToPixels(point2.coord, point1.coord);
|
|
31772
30368
|
const direction = vec.sub(tailPx, tipPx);
|
|
31773
|
-
let angle = calculateAngleInDegrees(direction);
|
|
30369
|
+
let angle = calculateAngleInDegrees$2(direction);
|
|
31774
30370
|
const startArrowHead = kind !== "segment" && /*#__PURE__*/React.createElement(Arrowhead, {
|
|
31775
30371
|
angle: angle,
|
|
31776
30372
|
tip: arrowTip,
|
|
@@ -31793,7 +30389,8 @@ const LockedLine = props => {
|
|
|
31793
30389
|
return /*#__PURE__*/React.createElement("g", {
|
|
31794
30390
|
className: kind === "ray" ? "locked-ray" : "locked-line",
|
|
31795
30391
|
"aria-label": hasAria ? ariaLabel : undefined,
|
|
31796
|
-
"aria-hidden": !hasAria
|
|
30392
|
+
"aria-hidden": !hasAria,
|
|
30393
|
+
role: "img"
|
|
31797
30394
|
}, line, showPoint1 && /*#__PURE__*/React.createElement(Point$2, {
|
|
31798
30395
|
x: point1.coord[X],
|
|
31799
30396
|
y: point1.coord[Y],
|
|
@@ -31829,7 +30426,8 @@ const LockedPoint = props => {
|
|
|
31829
30426
|
return /*#__PURE__*/React.createElement("g", {
|
|
31830
30427
|
className: "locked-point",
|
|
31831
30428
|
"aria-label": hasAria ? ariaLabel : undefined,
|
|
31832
|
-
"aria-hidden": !hasAria
|
|
30429
|
+
"aria-hidden": !hasAria,
|
|
30430
|
+
role: "img"
|
|
31833
30431
|
}, /*#__PURE__*/React.createElement(Point$2, {
|
|
31834
30432
|
x: x,
|
|
31835
30433
|
y: y,
|
|
@@ -31855,7 +30453,8 @@ const LockedPolygon = props => {
|
|
|
31855
30453
|
return /*#__PURE__*/React.createElement("g", {
|
|
31856
30454
|
className: "locked-polygon",
|
|
31857
30455
|
"aria-label": hasAria ? props.ariaLabel : undefined,
|
|
31858
|
-
"aria-hidden": !hasAria
|
|
30456
|
+
"aria-hidden": !hasAria,
|
|
30457
|
+
role: "img"
|
|
31859
30458
|
}, /*#__PURE__*/React.createElement(Polygon, {
|
|
31860
30459
|
points: [...points],
|
|
31861
30460
|
fillOpacity: lockedFigureFillStyles[fillStyle],
|
|
@@ -31889,7 +30488,8 @@ const LockedVector = props => {
|
|
|
31889
30488
|
return /*#__PURE__*/React.createElement("g", {
|
|
31890
30489
|
className: "locked-vector",
|
|
31891
30490
|
"aria-label": hasAria ? ariaLabel : undefined,
|
|
31892
|
-
"aria-hidden": !hasAria
|
|
30491
|
+
"aria-hidden": !hasAria,
|
|
30492
|
+
role: "img"
|
|
31893
30493
|
}, /*#__PURE__*/React.createElement(Vector, {
|
|
31894
30494
|
tail: tail,
|
|
31895
30495
|
tip: tip,
|
|
@@ -32176,6 +30776,12 @@ const SvgDefs = () => /*#__PURE__*/React__default.createElement("defs", null, /*
|
|
|
32176
30776
|
in: "SourceGraphic"
|
|
32177
30777
|
})));
|
|
32178
30778
|
|
|
30779
|
+
const {
|
|
30780
|
+
clockwise: clockwise$1
|
|
30781
|
+
} = geometry;
|
|
30782
|
+
const {
|
|
30783
|
+
getAngleFromVertex: getAngleFromVertex$1
|
|
30784
|
+
} = angles;
|
|
32179
30785
|
const PolygonAngle = ({
|
|
32180
30786
|
centerPoint,
|
|
32181
30787
|
endPoints,
|
|
@@ -32264,8 +30870,8 @@ const Angle = ({
|
|
|
32264
30870
|
const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
|
|
32265
30871
|
|
|
32266
30872
|
// Calculate the angles between the two points
|
|
32267
|
-
const startAngle = getAngleFromVertex(clockwiseCoords[0], vertex);
|
|
32268
|
-
const endAngle = getAngleFromVertex(clockwiseCoords[1], vertex);
|
|
30873
|
+
const startAngle = getAngleFromVertex$1(clockwiseCoords[0], vertex);
|
|
30874
|
+
const endAngle = getAngleFromVertex$1(clockwiseCoords[1], vertex);
|
|
32269
30875
|
const angle = (startAngle + 360 - endAngle) % 360;
|
|
32270
30876
|
|
|
32271
30877
|
// Check if the angle is reflexive
|
|
@@ -32621,14 +31227,6 @@ function range(min, max, step = 1) {
|
|
|
32621
31227
|
return result;
|
|
32622
31228
|
}
|
|
32623
31229
|
|
|
32624
|
-
function srFormatNumber(a, locale) {
|
|
32625
|
-
// adding zero here converts negative zero to positive zero.
|
|
32626
|
-
return (0 + a).toLocaleString(locale, {
|
|
32627
|
-
maximumFractionDigits: 3,
|
|
32628
|
-
useGrouping: false // no thousands separators
|
|
32629
|
-
});
|
|
32630
|
-
}
|
|
32631
|
-
|
|
32632
31230
|
// The hitbox size of 48px by 48px is preserved from the legacy interactive
|
|
32633
31231
|
// graph.
|
|
32634
31232
|
const hitboxSizePx = 48;
|
|
@@ -33016,6 +31614,11 @@ const MovablePoint$1 = /*#__PURE__*/React.forwardRef((props, pointRef) => {
|
|
|
33016
31614
|
return /*#__PURE__*/React.createElement(React.Fragment, null, focusableHandle, visiblePoint);
|
|
33017
31615
|
});
|
|
33018
31616
|
|
|
31617
|
+
const {
|
|
31618
|
+
calculateAngleInDegrees: calculateAngleInDegrees$1,
|
|
31619
|
+
getClockwiseAngle: getClockwiseAngle$2,
|
|
31620
|
+
polar: polar$1
|
|
31621
|
+
} = angles;
|
|
33019
31622
|
function renderAngleGraph(state, dispatch) {
|
|
33020
31623
|
return {
|
|
33021
31624
|
graph: /*#__PURE__*/React.createElement(AngleGraph, {
|
|
@@ -33085,7 +31688,7 @@ function AngleGraph(props) {
|
|
|
33085
31688
|
strings,
|
|
33086
31689
|
locale
|
|
33087
31690
|
} = usePerseusI18n();
|
|
33088
|
-
const angleMeasure = srFormatNumber(getClockwiseAngle([endPoints[0], centerPoint, endPoints[1]], allowReflexAngles), locale);
|
|
31691
|
+
const angleMeasure = srFormatNumber(getClockwiseAngle$2([endPoints[0], centerPoint, endPoints[1]], allowReflexAngles), locale);
|
|
33089
31692
|
const wholeAngleAriaLabel = strings.srAngleGraphAriaLabel;
|
|
33090
31693
|
const wholeAngleDescription = strings.srAngleGraphAriaDescription({
|
|
33091
31694
|
angleMeasure,
|
|
@@ -33149,9 +31752,7 @@ function AngleGraph(props) {
|
|
|
33149
31752
|
ariaLabel: initialSideAriaLabel
|
|
33150
31753
|
}), /*#__PURE__*/React.createElement("g", {
|
|
33151
31754
|
id: "angle-description",
|
|
33152
|
-
style:
|
|
33153
|
-
display: "hidden"
|
|
33154
|
-
}
|
|
31755
|
+
style: a11y.srOnly
|
|
33155
31756
|
}, wholeAngleDescription));
|
|
33156
31757
|
}
|
|
33157
31758
|
const positiveX = [1, 0];
|
|
@@ -33160,7 +31761,7 @@ const positiveY = [0, 1];
|
|
|
33160
31761
|
const negativeY = [0, -1];
|
|
33161
31762
|
function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
33162
31763
|
var _findIntersectionOfRa, _findIntersectionOfRa2, _findIntersectionOfRa3, _findIntersectionOfRa4;
|
|
33163
|
-
const currentAngle = calculateAngleInDegrees(vec.sub(sidePoint, vertex));
|
|
31764
|
+
const currentAngle = calculateAngleInDegrees$1(vec.sub(sidePoint, vertex));
|
|
33164
31765
|
|
|
33165
31766
|
// Find the rays that start at the current point and point up, down, left
|
|
33166
31767
|
// and right.
|
|
@@ -33177,8 +31778,8 @@ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
|
33177
31778
|
|
|
33178
31779
|
// find the rays that start from the vertex and point in the direction of
|
|
33179
31780
|
// the angles we just computed.
|
|
33180
|
-
const counterClockwiseRay = [vertex, vec.add(vertex, polar(1, oneStepCounterClockwise))];
|
|
33181
|
-
const clockwiseRay = [vertex, vec.add(vertex, polar(1, oneStepClockwise))];
|
|
31781
|
+
const counterClockwiseRay = [vertex, vec.add(vertex, polar$1(1, oneStepCounterClockwise))];
|
|
31782
|
+
const clockwiseRay = [vertex, vec.add(vertex, polar$1(1, oneStepClockwise))];
|
|
33182
31783
|
|
|
33183
31784
|
// find the intersections of those rays with the horizontal and vertical
|
|
33184
31785
|
// rays extending from the sidePoint. These intersections are the points to
|
|
@@ -33195,6 +31796,9 @@ function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
|
33195
31796
|
};
|
|
33196
31797
|
}
|
|
33197
31798
|
|
|
31799
|
+
const {
|
|
31800
|
+
clockwise
|
|
31801
|
+
} = geometry;
|
|
33198
31802
|
function getGradableGraph(state, initialGraph) {
|
|
33199
31803
|
if (!state.hasBeenInteractedWith) {
|
|
33200
31804
|
return _extends({}, initialGraph);
|
|
@@ -33263,7 +31867,7 @@ function getGradableGraph(state, initialGraph) {
|
|
|
33263
31867
|
// by the order of the points in the coords array, and we want to maintain
|
|
33264
31868
|
// the same angle scoring with the legacy graph (which had a bug).
|
|
33265
31869
|
// (LEMS-2190): When we remove the legacy graph, move this logic to the scoring function.
|
|
33266
|
-
const areClockwise = clockwise
|
|
31870
|
+
const areClockwise = clockwise([state.coords[0], state.coords[2], state.coords[1]]);
|
|
33267
31871
|
const shouldReverseCoords = areClockwise && !state.allowReflexAngles;
|
|
33268
31872
|
const coords = shouldReverseCoords ? state.coords.slice().reverse() : state.coords;
|
|
33269
31873
|
return _extends({}, initialGraph, {
|
|
@@ -33375,14 +31979,10 @@ function CircleGraph(props) {
|
|
|
33375
31979
|
}
|
|
33376
31980
|
}), /*#__PURE__*/React.createElement("g", {
|
|
33377
31981
|
id: radiusId,
|
|
33378
|
-
style:
|
|
33379
|
-
display: "hidden"
|
|
33380
|
-
}
|
|
31982
|
+
style: a11y.srOnly
|
|
33381
31983
|
}, srCircleRadius), /*#__PURE__*/React.createElement("g", {
|
|
33382
31984
|
id: outerPointsId,
|
|
33383
|
-
style:
|
|
33384
|
-
display: "hidden"
|
|
33385
|
-
}
|
|
31985
|
+
style: a11y.srOnly
|
|
33386
31986
|
}, srCircleOuterPoints));
|
|
33387
31987
|
}
|
|
33388
31988
|
function MovableCircle(props) {
|
|
@@ -33567,49 +32167,16 @@ const LinearGraph = (props, key) => {
|
|
|
33567
32167
|
point2X: srFormatNumber(line[1][0], locale),
|
|
33568
32168
|
point2Y: srFormatNumber(line[1][1], locale)
|
|
33569
32169
|
});
|
|
33570
|
-
|
|
33571
|
-
|
|
33572
|
-
const slope = (line[1][1] - line[0][1]) / (line[1][0] - line[0][0]);
|
|
33573
|
-
let slopeString = "";
|
|
33574
|
-
if (slope === Infinity || slope === -Infinity) {
|
|
33575
|
-
slopeString = strings.srLinearGraphSlopeVertical;
|
|
33576
|
-
} else if (slope === 0) {
|
|
33577
|
-
slopeString = strings.srLinearGraphSlopeHorizontal;
|
|
33578
|
-
} else {
|
|
33579
|
-
slopeString = slope > 0 ? strings.srLinearGraphSlopeIncreasing : strings.srLinearGraphSlopeDecreasing;
|
|
33580
|
-
}
|
|
33581
|
-
|
|
33582
|
-
// Intersection description
|
|
33583
|
-
const xIntercept = (0 - line[0][1]) / slope + line[0][0];
|
|
33584
|
-
const yIntercept = line[0][1] - slope * line[0][0];
|
|
33585
|
-
const hasXIntercept = xIntercept !== Infinity && xIntercept !== -Infinity;
|
|
33586
|
-
const hasYIntercept = yIntercept !== Infinity && yIntercept !== -Infinity;
|
|
33587
|
-
let interceptString;
|
|
33588
|
-
if (hasXIntercept && hasYIntercept) {
|
|
33589
|
-
// Describe both intercepts in the same sentence.
|
|
33590
|
-
interceptString = xIntercept === 0 && yIntercept === 0 ? strings.srLinearGraphOriginIntercept : strings.srLinearGraphBothIntercepts({
|
|
33591
|
-
xIntercept: srFormatNumber(xIntercept, locale),
|
|
33592
|
-
yIntercept: srFormatNumber(yIntercept, locale)
|
|
33593
|
-
});
|
|
33594
|
-
} else {
|
|
33595
|
-
// Describe only one intercept.
|
|
33596
|
-
interceptString = hasXIntercept ? strings.srLinearGraphXOnlyIntercept({
|
|
33597
|
-
xIntercept: srFormatNumber(xIntercept, locale)
|
|
33598
|
-
}) : strings.srLinearGraphYOnlyIntercept({
|
|
33599
|
-
yIntercept: srFormatNumber(yIntercept, locale)
|
|
33600
|
-
});
|
|
33601
|
-
}
|
|
32170
|
+
const slopeString = getSlopeStringForLine(line, strings);
|
|
32171
|
+
const interceptString = getInterceptStringForLine(line, strings, locale);
|
|
33602
32172
|
|
|
33603
32173
|
// Linear graphs only have one line
|
|
33604
32174
|
// (LEMS-2050): Update the reducer so that we have a separate action for moving one line
|
|
33605
32175
|
// and another action for moving multiple lines
|
|
33606
32176
|
return /*#__PURE__*/React.createElement("g", {
|
|
33607
|
-
className: "linear-graph-container"
|
|
33608
32177
|
// Outer line minimal description
|
|
33609
|
-
,
|
|
33610
32178
|
"aria-label": strings.srLinearGraph,
|
|
33611
|
-
"aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}
|
|
33612
|
-
role: "figure"
|
|
32179
|
+
"aria-describedby": `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`
|
|
33613
32180
|
}, /*#__PURE__*/React.createElement(MovableLine, {
|
|
33614
32181
|
key: 0,
|
|
33615
32182
|
ariaLabels: {
|
|
@@ -33628,19 +32195,13 @@ const LinearGraph = (props, key) => {
|
|
|
33628
32195
|
color: "var(--movable-line-stroke-color)"
|
|
33629
32196
|
}), /*#__PURE__*/React.createElement("g", {
|
|
33630
32197
|
id: pointsDescriptionId,
|
|
33631
|
-
style:
|
|
33632
|
-
display: "hidden"
|
|
33633
|
-
}
|
|
32198
|
+
style: a11y.srOnly
|
|
33634
32199
|
}, linearGraphPointsDescription), /*#__PURE__*/React.createElement("g", {
|
|
33635
32200
|
id: interceptDescriptionId,
|
|
33636
|
-
style:
|
|
33637
|
-
display: "hidden"
|
|
33638
|
-
}
|
|
32201
|
+
style: a11y.srOnly
|
|
33639
32202
|
}, interceptString), /*#__PURE__*/React.createElement("g", {
|
|
33640
32203
|
id: slopeDescriptionId,
|
|
33641
|
-
style:
|
|
33642
|
-
display: "hidden"
|
|
33643
|
-
}
|
|
32204
|
+
style: a11y.srOnly
|
|
33644
32205
|
}, slopeString));
|
|
33645
32206
|
};
|
|
33646
32207
|
|
|
@@ -33650,7 +32211,9 @@ function renderLinearSystemGraph(state, dispatch) {
|
|
|
33650
32211
|
graphState: state,
|
|
33651
32212
|
dispatch: dispatch
|
|
33652
32213
|
}),
|
|
33653
|
-
interactiveElementsDescription:
|
|
32214
|
+
interactiveElementsDescription: /*#__PURE__*/React.createElement(LinearSystemGraphDescription, {
|
|
32215
|
+
state: state
|
|
32216
|
+
})
|
|
33654
32217
|
};
|
|
33655
32218
|
}
|
|
33656
32219
|
const LinearSystemGraph = props => {
|
|
@@ -33660,9 +32223,53 @@ const LinearSystemGraph = props => {
|
|
|
33660
32223
|
const {
|
|
33661
32224
|
coords: lines
|
|
33662
32225
|
} = props.graphState;
|
|
33663
|
-
|
|
32226
|
+
const {
|
|
32227
|
+
strings,
|
|
32228
|
+
locale
|
|
32229
|
+
} = usePerseusI18n();
|
|
32230
|
+
const id = React.useId();
|
|
32231
|
+
const linesAriaInfo = lines.map((line, i) => {
|
|
32232
|
+
return {
|
|
32233
|
+
pointsDescriptionId: `${id}-line${i + 1}-points`,
|
|
32234
|
+
interceptDescriptionId: `${id}-line${i + 1}-intercept`,
|
|
32235
|
+
slopeDescriptionId: `${id}-line${i + 1}-slope`,
|
|
32236
|
+
pointsDescription: strings.srLinearSystemPoints({
|
|
32237
|
+
lineNumber: i + 1,
|
|
32238
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32239
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32240
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32241
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32242
|
+
}),
|
|
32243
|
+
interceptDescription: getInterceptStringForLine(line, strings, locale),
|
|
32244
|
+
slopeDescription: getSlopeStringForLine(line, strings)
|
|
32245
|
+
};
|
|
32246
|
+
});
|
|
32247
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
32248
|
+
"aria-label": strings.srLinearSystemGraph,
|
|
32249
|
+
"aria-describedby": linesAriaInfo.map(({
|
|
32250
|
+
pointsDescriptionId,
|
|
32251
|
+
interceptDescriptionId,
|
|
32252
|
+
slopeDescriptionId
|
|
32253
|
+
}) => `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`).join(" ")
|
|
32254
|
+
}, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(MovableLine, {
|
|
33664
32255
|
key: i,
|
|
33665
32256
|
points: line,
|
|
32257
|
+
ariaLabels: {
|
|
32258
|
+
point1AriaLabel: strings.srLinearSystemPoint({
|
|
32259
|
+
lineNumber: i + 1,
|
|
32260
|
+
pointSequence: 1,
|
|
32261
|
+
x: srFormatNumber(line[0][0], locale),
|
|
32262
|
+
y: srFormatNumber(line[0][1], locale)
|
|
32263
|
+
}),
|
|
32264
|
+
point2AriaLabel: strings.srLinearSystemPoint({
|
|
32265
|
+
lineNumber: i + 1,
|
|
32266
|
+
pointSequence: 2,
|
|
32267
|
+
x: srFormatNumber(line[1][0], locale),
|
|
32268
|
+
y: srFormatNumber(line[1][1], locale)
|
|
32269
|
+
}),
|
|
32270
|
+
grabHandleAriaLabel: `${linesAriaInfo[i].interceptDescription} ${linesAriaInfo[i].slopeDescription}`
|
|
32271
|
+
},
|
|
32272
|
+
ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId}`,
|
|
33666
32273
|
onMoveLine: delta => {
|
|
33667
32274
|
dispatch(actions.linearSystem.moveLine(i, delta));
|
|
33668
32275
|
},
|
|
@@ -33672,8 +32279,63 @@ const LinearSystemGraph = props => {
|
|
|
33672
32279
|
},
|
|
33673
32280
|
onMovePoint: (endpointIndex, destination) => dispatch(actions.linearSystem.movePointInFigure(i, endpointIndex, destination)),
|
|
33674
32281
|
color: "var(--movable-line-stroke-color)"
|
|
33675
|
-
})),
|
|
32282
|
+
})), linesAriaInfo.map(({
|
|
32283
|
+
pointsDescriptionId,
|
|
32284
|
+
interceptDescriptionId,
|
|
32285
|
+
slopeDescriptionId,
|
|
32286
|
+
pointsDescription,
|
|
32287
|
+
interceptDescription,
|
|
32288
|
+
slopeDescription
|
|
32289
|
+
}) => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("g", {
|
|
32290
|
+
key: pointsDescriptionId,
|
|
32291
|
+
id: pointsDescriptionId,
|
|
32292
|
+
style: a11y.srOnly
|
|
32293
|
+
}, pointsDescription), /*#__PURE__*/React.createElement("g", {
|
|
32294
|
+
key: interceptDescriptionId,
|
|
32295
|
+
id: interceptDescriptionId,
|
|
32296
|
+
style: a11y.srOnly
|
|
32297
|
+
}, interceptDescription), /*#__PURE__*/React.createElement("g", {
|
|
32298
|
+
key: slopeDescriptionId,
|
|
32299
|
+
id: slopeDescriptionId,
|
|
32300
|
+
style: a11y.srOnly
|
|
32301
|
+
}, slopeDescription))));
|
|
33676
32302
|
};
|
|
32303
|
+
function LinearSystemGraphDescription({
|
|
32304
|
+
state
|
|
32305
|
+
}) {
|
|
32306
|
+
// The reason that LinearSystemGraphDescription is a component (rather
|
|
32307
|
+
// than a function that returns a string) is because it needs to use a
|
|
32308
|
+
// hook: `usePerseusI18n`.
|
|
32309
|
+
const i18n = usePerseusI18n();
|
|
32310
|
+
return describeLinearSystemGraph(state, i18n);
|
|
32311
|
+
}
|
|
32312
|
+
|
|
32313
|
+
// Exported for testing
|
|
32314
|
+
function describeLinearSystemGraph(state, i18n) {
|
|
32315
|
+
const {
|
|
32316
|
+
strings,
|
|
32317
|
+
locale
|
|
32318
|
+
} = i18n;
|
|
32319
|
+
const {
|
|
32320
|
+
coords: lines
|
|
32321
|
+
} = state;
|
|
32322
|
+
const graphDescription = strings.srLinearSystemGraph;
|
|
32323
|
+
const lineDescriptions = lines.map((line, i) => {
|
|
32324
|
+
const point1 = line[0];
|
|
32325
|
+
const point2 = line[1];
|
|
32326
|
+
return strings.srLinearSystemPoints({
|
|
32327
|
+
lineNumber: i + 1,
|
|
32328
|
+
point1X: srFormatNumber(point1[0], locale),
|
|
32329
|
+
point1Y: srFormatNumber(point1[1], locale),
|
|
32330
|
+
point2X: srFormatNumber(point2[0], locale),
|
|
32331
|
+
point2Y: srFormatNumber(point2[1], locale)
|
|
32332
|
+
});
|
|
32333
|
+
});
|
|
32334
|
+
const allDescriptions = [graphDescription, ...lineDescriptions];
|
|
32335
|
+
return strings.srInteractiveElements({
|
|
32336
|
+
elements: allDescriptions.join(" ")
|
|
32337
|
+
});
|
|
32338
|
+
}
|
|
33677
32339
|
|
|
33678
32340
|
function renderPointGraph(state, dispatch) {
|
|
33679
32341
|
return {
|
|
@@ -34112,7 +32774,7 @@ function QuadraticGraph(props) {
|
|
|
34112
32774
|
// to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
|
|
34113
32775
|
// While we should technically never have invalid coordinates, we want to ensure that we have a fallback
|
|
34114
32776
|
const coeffRef = React.useRef([0, 0, 0]);
|
|
34115
|
-
const coeffs = getQuadraticCoefficients$
|
|
32777
|
+
const coeffs = getQuadraticCoefficients$2(coords);
|
|
34116
32778
|
if (coeffs !== undefined) {
|
|
34117
32779
|
coeffRef.current = coeffs;
|
|
34118
32780
|
}
|
|
@@ -34122,7 +32784,7 @@ function QuadraticGraph(props) {
|
|
|
34122
32784
|
|
|
34123
32785
|
// Calculate the y value based on the current x value
|
|
34124
32786
|
const y = x => (a * x + b) * x + c;
|
|
34125
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$
|
|
32787
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$2.OfX, {
|
|
34126
32788
|
y: y,
|
|
34127
32789
|
color: color.blue
|
|
34128
32790
|
}), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
@@ -34137,7 +32799,7 @@ function QuadraticGraph(props) {
|
|
|
34137
32799
|
// These equations were originally set up in 2013 and may require some
|
|
34138
32800
|
// additional comments to help clarify the quadratic formula manipulations
|
|
34139
32801
|
// Origin: https://phabricator.khanacademy.org/D2413
|
|
34140
|
-
const getQuadraticCoefficients$
|
|
32802
|
+
const getQuadraticCoefficients$2 = coords => {
|
|
34141
32803
|
const p1 = coords[0];
|
|
34142
32804
|
const p2 = coords[1];
|
|
34143
32805
|
const p3 = coords[2];
|
|
@@ -34160,7 +32822,9 @@ function renderRayGraph(state, dispatch) {
|
|
|
34160
32822
|
graphState: state,
|
|
34161
32823
|
dispatch: dispatch
|
|
34162
32824
|
}),
|
|
34163
|
-
interactiveElementsDescription:
|
|
32825
|
+
interactiveElementsDescription: /*#__PURE__*/React.createElement(RayGraphDescription, {
|
|
32826
|
+
state: state
|
|
32827
|
+
})
|
|
34164
32828
|
};
|
|
34165
32829
|
}
|
|
34166
32830
|
const RayGraph = props => {
|
|
@@ -34172,18 +32836,103 @@ const RayGraph = props => {
|
|
|
34172
32836
|
} = props.graphState;
|
|
34173
32837
|
const handleMoveLine = delta => dispatch(actions.ray.moveRay(delta));
|
|
34174
32838
|
const handleMovePoint = (pointIndex, newPoint) => dispatch(actions.ray.movePoint(pointIndex, newPoint));
|
|
32839
|
+
const {
|
|
32840
|
+
strings,
|
|
32841
|
+
locale
|
|
32842
|
+
} = usePerseusI18n();
|
|
32843
|
+
const id = React.useId();
|
|
32844
|
+
const pointsDescriptionId = id + "-points";
|
|
32845
|
+
|
|
32846
|
+
// Aria label strings
|
|
32847
|
+
const {
|
|
32848
|
+
srRayGraph,
|
|
32849
|
+
srRayPoints,
|
|
32850
|
+
srRayEndpoint,
|
|
32851
|
+
srRayTerminalPoint,
|
|
32852
|
+
srRayGrabHandle
|
|
32853
|
+
} = describeRayGraph(props.graphState, {
|
|
32854
|
+
strings,
|
|
32855
|
+
locale
|
|
32856
|
+
});
|
|
34175
32857
|
|
|
34176
32858
|
// Ray graphs only have one line
|
|
34177
|
-
return /*#__PURE__*/React.createElement(
|
|
32859
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
32860
|
+
// Outer line minimal description
|
|
32861
|
+
"aria-label": srRayGraph,
|
|
32862
|
+
"aria-describedby": pointsDescriptionId
|
|
32863
|
+
}, /*#__PURE__*/React.createElement(MovableLine, {
|
|
34178
32864
|
points: line,
|
|
32865
|
+
ariaLabels: {
|
|
32866
|
+
point1AriaLabel: srRayEndpoint,
|
|
32867
|
+
point2AriaLabel: srRayTerminalPoint,
|
|
32868
|
+
grabHandleAriaLabel: srRayGrabHandle
|
|
32869
|
+
},
|
|
34179
32870
|
onMoveLine: handleMoveLine,
|
|
34180
32871
|
onMovePoint: handleMovePoint,
|
|
34181
32872
|
extend: {
|
|
34182
32873
|
start: false,
|
|
34183
32874
|
end: true
|
|
34184
32875
|
}
|
|
34185
|
-
})
|
|
32876
|
+
}), /*#__PURE__*/React.createElement("g", {
|
|
32877
|
+
id: pointsDescriptionId,
|
|
32878
|
+
style: a11y.srOnly
|
|
32879
|
+
}, srRayPoints));
|
|
34186
32880
|
};
|
|
32881
|
+
function RayGraphDescription({
|
|
32882
|
+
state
|
|
32883
|
+
}) {
|
|
32884
|
+
// The reason that RayGraphDescription is a component (rather than a
|
|
32885
|
+
// function that returns a string) is because it needs to use a
|
|
32886
|
+
// hook: `usePerseusI18n`.
|
|
32887
|
+
const i18n = usePerseusI18n();
|
|
32888
|
+
const strings = describeRayGraph(state, i18n);
|
|
32889
|
+
return strings.srRayInteractiveElement;
|
|
32890
|
+
}
|
|
32891
|
+
|
|
32892
|
+
// Exported for testing
|
|
32893
|
+
function describeRayGraph(state, i18n) {
|
|
32894
|
+
const {
|
|
32895
|
+
coords: line
|
|
32896
|
+
} = state;
|
|
32897
|
+
const {
|
|
32898
|
+
strings,
|
|
32899
|
+
locale
|
|
32900
|
+
} = i18n;
|
|
32901
|
+
|
|
32902
|
+
// Aria label strings
|
|
32903
|
+
const srRayGraph = strings.srRayGraph;
|
|
32904
|
+
const srRayPoints = strings.srRayPoints({
|
|
32905
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32906
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32907
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32908
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32909
|
+
});
|
|
32910
|
+
const srRayEndpoint = strings.srRayEndpoint({
|
|
32911
|
+
x: srFormatNumber(line[0][0], locale),
|
|
32912
|
+
y: srFormatNumber(line[0][1], locale)
|
|
32913
|
+
});
|
|
32914
|
+
const srRayTerminalPoint = strings.srRayTerminalPoint({
|
|
32915
|
+
x: srFormatNumber(line[1][0], locale),
|
|
32916
|
+
y: srFormatNumber(line[1][1], locale)
|
|
32917
|
+
});
|
|
32918
|
+
const srRayGrabHandle = strings.srRayGrabHandle({
|
|
32919
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
32920
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
32921
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
32922
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
32923
|
+
});
|
|
32924
|
+
const srRayInteractiveElement = strings.srInteractiveElements({
|
|
32925
|
+
elements: [srRayGraph, srRayPoints].join(" ")
|
|
32926
|
+
});
|
|
32927
|
+
return {
|
|
32928
|
+
srRayGraph,
|
|
32929
|
+
srRayPoints,
|
|
32930
|
+
srRayEndpoint,
|
|
32931
|
+
srRayTerminalPoint,
|
|
32932
|
+
srRayGrabHandle,
|
|
32933
|
+
srRayInteractiveElement
|
|
32934
|
+
};
|
|
32935
|
+
}
|
|
34187
32936
|
|
|
34188
32937
|
function renderSegmentGraph(state, dispatch) {
|
|
34189
32938
|
return {
|
|
@@ -34251,7 +33000,7 @@ function SinusoidGraph(props) {
|
|
|
34251
33000
|
if (coeffs !== undefined) {
|
|
34252
33001
|
coeffRef.current = coeffs;
|
|
34253
33002
|
}
|
|
34254
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$
|
|
33003
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$2.OfX, {
|
|
34255
33004
|
y: x => computeSine(x, coeffRef.current),
|
|
34256
33005
|
color: color.blue
|
|
34257
33006
|
}), coords.map((coord, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
@@ -34298,6 +33047,10 @@ const getSinusoidCoefficients$1 = coords => {
|
|
|
34298
33047
|
};
|
|
34299
33048
|
};
|
|
34300
33049
|
|
|
33050
|
+
const {
|
|
33051
|
+
calculateAngleInDegrees,
|
|
33052
|
+
convertDegreesToRadians
|
|
33053
|
+
} = angles;
|
|
34301
33054
|
const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
|
|
34302
33055
|
|
|
34303
33056
|
// The vector from the center of the protractor to the top left corner of the
|
|
@@ -34999,6 +33752,10 @@ function mafsStateToInteractiveGraph(state, originalGraph) {
|
|
|
34999
33752
|
}
|
|
35000
33753
|
}
|
|
35001
33754
|
|
|
33755
|
+
const {
|
|
33756
|
+
magnitude: magnitude$2,
|
|
33757
|
+
vector: vector$2
|
|
33758
|
+
} = geometry;
|
|
35002
33759
|
function initializeGraphState(params) {
|
|
35003
33760
|
var _graph$snapTo;
|
|
35004
33761
|
const {
|
|
@@ -35278,7 +34035,7 @@ const getAngleCoords = params => {
|
|
|
35278
34035
|
defaultCoords = normalizePoints(range, step, defaultCoords, true);
|
|
35279
34036
|
|
|
35280
34037
|
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
35281
|
-
const radius = magnitude(vector(...defaultCoords));
|
|
34038
|
+
const radius = magnitude$2(vector$2(...defaultCoords));
|
|
35282
34039
|
|
|
35283
34040
|
// We're adding a placeholder for the third point to appease ts and so that we
|
|
35284
34041
|
// can calculate it after we've adjusted the first point by the angleOffsetDeg
|
|
@@ -35292,6 +34049,24 @@ const getAngleCoords = params => {
|
|
|
35292
34049
|
return coords;
|
|
35293
34050
|
};
|
|
35294
34051
|
|
|
34052
|
+
const {
|
|
34053
|
+
getAngleFromVertex,
|
|
34054
|
+
getClockwiseAngle: getClockwiseAngle$1,
|
|
34055
|
+
polar
|
|
34056
|
+
} = angles;
|
|
34057
|
+
const {
|
|
34058
|
+
angleMeasures: angleMeasures$1,
|
|
34059
|
+
ccw: ccw$1,
|
|
34060
|
+
lawOfCosines: lawOfCosines$1,
|
|
34061
|
+
magnitude: magnitude$1,
|
|
34062
|
+
polygonSidesIntersect,
|
|
34063
|
+
reverseVector,
|
|
34064
|
+
sign: sign$1,
|
|
34065
|
+
vector: vector$1
|
|
34066
|
+
} = geometry;
|
|
34067
|
+
const {
|
|
34068
|
+
getQuadraticCoefficients: getQuadraticCoefficients$1
|
|
34069
|
+
} = coefficients;
|
|
35295
34070
|
const minDistanceBetweenAngleVertexAndSidePoint = 2;
|
|
35296
34071
|
function interactiveGraphReducer(state, action) {
|
|
35297
34072
|
switch (action.type) {
|
|
@@ -35810,11 +34585,9 @@ const getChange = (coords, delta, constraintOpts) => {
|
|
|
35810
34585
|
const [dx, dy] = getDeltaVertex(maxMoves, minMoves, delta);
|
|
35811
34586
|
return [dx, dy];
|
|
35812
34587
|
};
|
|
35813
|
-
const eq$2 = Util.eq;
|
|
35814
|
-
|
|
35815
34588
|
// Less than or approximately equal
|
|
35816
34589
|
function leq$1(a, b) {
|
|
35817
|
-
return a < b ||
|
|
34590
|
+
return a < b || approximateEqual(a, b);
|
|
35818
34591
|
}
|
|
35819
34592
|
function boundAndSnapToGrid(point, {
|
|
35820
34593
|
snapStep,
|
|
@@ -35958,7 +34731,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
|
|
|
35958
34731
|
});
|
|
35959
34732
|
|
|
35960
34733
|
// Gets the radian angles between the coords and maps them to degrees
|
|
35961
|
-
const angles = angleMeasures(coordsCopy).map(angle => angle * 180 / Math.PI);
|
|
34734
|
+
const angles = angleMeasures$1(coordsCopy).map(angle => angle * 180 / Math.PI);
|
|
35962
34735
|
|
|
35963
34736
|
// Gets the relative index of a point
|
|
35964
34737
|
const rel = j => {
|
|
@@ -35970,7 +34743,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
|
|
|
35970
34743
|
angles[rel(j)] = Math.round(angles[rel(j)]);
|
|
35971
34744
|
});
|
|
35972
34745
|
const getAngle = function getAngle(a, vertex, b) {
|
|
35973
|
-
const angle = getClockwiseAngle([coordsCopy[rel(a)], coordsCopy[rel(vertex)], coordsCopy[rel(b)]]);
|
|
34746
|
+
const angle = getClockwiseAngle$1([coordsCopy[rel(a)], coordsCopy[rel(vertex)], coordsCopy[rel(b)]]);
|
|
35974
34747
|
return angle;
|
|
35975
34748
|
};
|
|
35976
34749
|
const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
|
|
@@ -35982,9 +34755,9 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
|
|
|
35982
34755
|
})) {
|
|
35983
34756
|
return startingPoint;
|
|
35984
34757
|
}
|
|
35985
|
-
const knownSide = magnitude(
|
|
34758
|
+
const knownSide = magnitude$1(
|
|
35986
34759
|
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'.
|
|
35987
|
-
vector(coordsCopy[rel(-1)], coordsCopy[rel(1)]));
|
|
34760
|
+
vector$1(coordsCopy[rel(-1)], coordsCopy[rel(1)]));
|
|
35988
34761
|
|
|
35989
34762
|
// Returns true if the points form a counter-clockwise turn;
|
|
35990
34763
|
// a.k.a if the point is on the left or right of the polygon.
|
|
@@ -35992,7 +34765,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
|
|
|
35992
34765
|
// we want to add the inner angle to the outer angle, and if it's on the right,
|
|
35993
34766
|
// we want to subtract the inner angle from the outer angle. The angle solved
|
|
35994
34767
|
// for is then used in the polar function to determine the new point.
|
|
35995
|
-
const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
|
|
34768
|
+
const onLeft = sign$1(ccw$1(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
|
|
35996
34769
|
|
|
35997
34770
|
// Solve for side by using the law of sines
|
|
35998
34771
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
@@ -36004,7 +34777,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, {
|
|
|
36004
34777
|
// and the angle between the first and second sides of the
|
|
36005
34778
|
// polygon (angular coordinate) to determine how to adjust the point
|
|
36006
34779
|
const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
36007
|
-
return vector$
|
|
34780
|
+
return vector$3.add(coordsCopy[rel(-1)], offset);
|
|
36008
34781
|
}
|
|
36009
34782
|
function boundAndSnapToSides(destinationPoint, {
|
|
36010
34783
|
range,
|
|
@@ -36030,7 +34803,7 @@ function boundAndSnapToSides(destinationPoint, {
|
|
|
36030
34803
|
};
|
|
36031
34804
|
const sides = _.map([[coordsCopy[rel(-1)], coordsCopy[index]], [coordsCopy[index], coordsCopy[rel(1)]], [coordsCopy[rel(-1)], coordsCopy[rel(1)]]], function (coordsCopy) {
|
|
36032
34805
|
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
36033
|
-
return magnitude(vector(...coordsCopy));
|
|
34806
|
+
return magnitude$1(vector$1(...coordsCopy));
|
|
36034
34807
|
});
|
|
36035
34808
|
|
|
36036
34809
|
// Round the sides to left and right of the current point
|
|
@@ -36045,7 +34818,7 @@ function boundAndSnapToSides(destinationPoint, {
|
|
|
36045
34818
|
|
|
36046
34819
|
// Solve for angle by using the law of cosines
|
|
36047
34820
|
// Angle at the first vertex of the polygon
|
|
36048
|
-
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
34821
|
+
const innerAngle = lawOfCosines$1(sides[0], sides[2], sides[1]);
|
|
36049
34822
|
|
|
36050
34823
|
// Angle at the second vertex of the polygon
|
|
36051
34824
|
const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
|
|
@@ -36056,13 +34829,13 @@ function boundAndSnapToSides(destinationPoint, {
|
|
|
36056
34829
|
// we want to add the inner angle to the outer angle, and if it's on the right,
|
|
36057
34830
|
// we want to subtract the inner angle from the outer angle. The angle solved
|
|
36058
34831
|
// for is then used in the polar function to determine the new point.
|
|
36059
|
-
const onLeft = sign(ccw(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
|
|
34832
|
+
const onLeft = sign$1(ccw$1(coordsCopy[rel(-1)], coordsCopy[rel(1)], coordsCopy[index])) === 1;
|
|
36060
34833
|
|
|
36061
34834
|
// Uses the length of the first side of the polygon (radial coordinate)
|
|
36062
34835
|
// and the angle between the first and second sides of the
|
|
36063
34836
|
// polygon (angular coordinate) to determine how to adjust the point
|
|
36064
34837
|
const offset = polar(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
36065
|
-
return vector$
|
|
34838
|
+
return vector$3.add(coordsCopy[rel(-1)], offset);
|
|
36066
34839
|
}
|
|
36067
34840
|
|
|
36068
34841
|
// Returns the vector from the given point to the top-right corner of the graph when snapped to the grid
|
|
@@ -36092,7 +34865,7 @@ function minMove({
|
|
|
36092
34865
|
});
|
|
36093
34866
|
return vec.sub(bottomLeft, point);
|
|
36094
34867
|
}
|
|
36095
|
-
const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && vector$
|
|
34868
|
+
const coordsOverlap = coords => coords.some((coord, i) => coords.some((c, j) => i !== j && vector$3.equal(coord, c)));
|
|
36096
34869
|
function updateAtIndex(args) {
|
|
36097
34870
|
const {
|
|
36098
34871
|
array,
|
|
@@ -36187,216 +34960,26 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
36187
34960
|
}));
|
|
36188
34961
|
});
|
|
36189
34962
|
|
|
36190
|
-
const
|
|
36191
|
-
|
|
36192
|
-
|
|
36193
|
-
|
|
36194
|
-
|
|
36195
|
-
|
|
36196
|
-
|
|
36197
|
-
|
|
36198
|
-
|
|
36199
|
-
|
|
36200
|
-
|
|
36201
|
-
|
|
36202
|
-
|
|
36203
|
-
|
|
36204
|
-
|
|
36205
|
-
|
|
36206
|
-
const hasValue = Boolean(
|
|
36207
|
-
// @ts-expect-error - TS2339 - Property 'coords' does not exist on type 'PerseusGraphType'.
|
|
36208
|
-
userInput.coords ||
|
|
36209
|
-
// @ts-expect-error - TS2339 - Property 'center' does not exist on type 'PerseusGraphType'. | TS2339 - Property 'radius' does not exist on type 'PerseusGraphType'.
|
|
36210
|
-
userInput.center && userInput.radius);
|
|
36211
|
-
if (userInput.type === rubric.correct.type && hasValue) {
|
|
36212
|
-
if (userInput.type === "linear" && rubric.correct.type === "linear" && userInput.coords != null) {
|
|
36213
|
-
const guess = userInput.coords;
|
|
36214
|
-
const correct = rubric.correct.coords;
|
|
36215
|
-
|
|
36216
|
-
// If both of the guess points are on the correct line, it's
|
|
36217
|
-
// correct.
|
|
36218
|
-
if (collinear(correct[0], correct[1], guess[0]) && collinear(correct[0], correct[1], guess[1])) {
|
|
36219
|
-
return {
|
|
36220
|
-
type: "points",
|
|
36221
|
-
earned: 1,
|
|
36222
|
-
total: 1,
|
|
36223
|
-
message: null
|
|
36224
|
-
};
|
|
36225
|
-
}
|
|
36226
|
-
} else if (userInput.type === "linear-system" && rubric.correct.type === "linear-system" && userInput.coords != null) {
|
|
36227
|
-
const guess = userInput.coords;
|
|
36228
|
-
const correct = rubric.correct.coords;
|
|
36229
|
-
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])) {
|
|
36230
|
-
return {
|
|
36231
|
-
type: "points",
|
|
36232
|
-
earned: 1,
|
|
36233
|
-
total: 1,
|
|
36234
|
-
message: null
|
|
36235
|
-
};
|
|
36236
|
-
}
|
|
36237
|
-
} else if (userInput.type === "quadratic" && rubric.correct.type === "quadratic" && userInput.coords != null) {
|
|
36238
|
-
// If the parabola coefficients match, it's correct.
|
|
36239
|
-
const guessCoeffs = getQuadraticCoefficients(userInput.coords);
|
|
36240
|
-
const correctCoeffs = getQuadraticCoefficients(rubric.correct.coords);
|
|
36241
|
-
if (deepEq$3(guessCoeffs, correctCoeffs)) {
|
|
36242
|
-
return {
|
|
36243
|
-
type: "points",
|
|
36244
|
-
earned: 1,
|
|
36245
|
-
total: 1,
|
|
36246
|
-
message: null
|
|
36247
|
-
};
|
|
36248
|
-
}
|
|
36249
|
-
} else if (userInput.type === "sinusoid" && rubric.correct.type === "sinusoid" && userInput.coords != null) {
|
|
36250
|
-
const guessCoeffs = getSinusoidCoefficients(userInput.coords);
|
|
36251
|
-
const correctCoeffs = getSinusoidCoefficients(rubric.correct.coords);
|
|
36252
|
-
const canonicalGuessCoeffs = canonicalSineCoefficients$1(guessCoeffs);
|
|
36253
|
-
const canonicalCorrectCoeffs = canonicalSineCoefficients$1(correctCoeffs);
|
|
36254
|
-
// If the canonical coefficients match, it's correct.
|
|
36255
|
-
if (deepEq$3(canonicalGuessCoeffs, canonicalCorrectCoeffs)) {
|
|
36256
|
-
return {
|
|
36257
|
-
type: "points",
|
|
36258
|
-
earned: 1,
|
|
36259
|
-
total: 1,
|
|
36260
|
-
message: null
|
|
36261
|
-
};
|
|
36262
|
-
}
|
|
36263
|
-
} else if (userInput.type === "circle" && rubric.correct.type === "circle") {
|
|
36264
|
-
if (deepEq$3(userInput.center, rubric.correct.center) && eq$1(userInput.radius, rubric.correct.radius)) {
|
|
36265
|
-
return {
|
|
36266
|
-
type: "points",
|
|
36267
|
-
earned: 1,
|
|
36268
|
-
total: 1,
|
|
36269
|
-
message: null
|
|
36270
|
-
};
|
|
36271
|
-
}
|
|
36272
|
-
} else if (userInput.type === "point" && rubric.correct.type === "point" && userInput.coords != null) {
|
|
36273
|
-
let correct = rubric.correct.coords;
|
|
36274
|
-
if (correct == null) {
|
|
36275
|
-
throw new Error("Point graph rubric has null coords");
|
|
36276
|
-
}
|
|
36277
|
-
const guess = userInput.coords.slice();
|
|
36278
|
-
correct = correct.slice();
|
|
36279
|
-
// Everything's already rounded so we shouldn't need to do an
|
|
36280
|
-
// eq() comparison but _.isEqual(0, -0) is false, so we'll use
|
|
36281
|
-
// eq() anyway. The sort should be fine because it'll stringify
|
|
36282
|
-
// it and -0 converted to a string is "0"
|
|
36283
|
-
guess == null || guess.sort();
|
|
36284
|
-
// @ts-expect-error - TS2339 - Property 'sort' does not exist on type 'readonly Coord[]'.
|
|
36285
|
-
correct.sort();
|
|
36286
|
-
if (deepEq$3(guess, correct)) {
|
|
36287
|
-
return {
|
|
36288
|
-
type: "points",
|
|
36289
|
-
earned: 1,
|
|
36290
|
-
total: 1,
|
|
36291
|
-
message: null
|
|
36292
|
-
};
|
|
36293
|
-
}
|
|
36294
|
-
} else if (userInput.type === "polygon" && rubric.correct.type === "polygon" && userInput.coords != null) {
|
|
36295
|
-
const guess = userInput.coords.slice();
|
|
36296
|
-
const correct = rubric.correct.coords.slice();
|
|
36297
|
-
let match;
|
|
36298
|
-
if (rubric.correct.match === "similar") {
|
|
36299
|
-
match = similar(guess, correct, Number.POSITIVE_INFINITY);
|
|
36300
|
-
} else if (rubric.correct.match === "congruent") {
|
|
36301
|
-
match = similar(guess, correct, number$1.DEFAULT_TOLERANCE);
|
|
36302
|
-
} else if (rubric.correct.match === "approx") {
|
|
36303
|
-
match = similar(guess, correct, 0.1);
|
|
36304
|
-
} else {
|
|
36305
|
-
/* exact */
|
|
36306
|
-
guess.sort();
|
|
36307
|
-
correct.sort();
|
|
36308
|
-
match = deepEq$3(guess, correct);
|
|
36309
|
-
}
|
|
36310
|
-
if (match) {
|
|
36311
|
-
return {
|
|
36312
|
-
type: "points",
|
|
36313
|
-
earned: 1,
|
|
36314
|
-
total: 1,
|
|
36315
|
-
message: null
|
|
36316
|
-
};
|
|
36317
|
-
}
|
|
36318
|
-
} else if (userInput.type === "segment" && rubric.correct.type === "segment" && userInput.coords != null) {
|
|
36319
|
-
let guess = Util.deepClone(userInput.coords);
|
|
36320
|
-
let correct = Util.deepClone(rubric.correct.coords);
|
|
36321
|
-
guess = _.invoke(guess, "sort").sort();
|
|
36322
|
-
correct = _.invoke(correct, "sort").sort();
|
|
36323
|
-
if (deepEq$3(guess, correct)) {
|
|
36324
|
-
return {
|
|
36325
|
-
type: "points",
|
|
36326
|
-
earned: 1,
|
|
36327
|
-
total: 1,
|
|
36328
|
-
message: null
|
|
36329
|
-
};
|
|
36330
|
-
}
|
|
36331
|
-
} else if (userInput.type === "ray" && rubric.correct.type === "ray" && userInput.coords != null) {
|
|
36332
|
-
const guess = userInput.coords;
|
|
36333
|
-
const correct = rubric.correct.coords;
|
|
36334
|
-
if (deepEq$3(guess[0], correct[0]) && collinear(correct[0], correct[1], guess[1])) {
|
|
36335
|
-
return {
|
|
36336
|
-
type: "points",
|
|
36337
|
-
earned: 1,
|
|
36338
|
-
total: 1,
|
|
36339
|
-
message: null
|
|
36340
|
-
};
|
|
36341
|
-
}
|
|
36342
|
-
} else if (userInput.type === "angle" && rubric.correct.type === "angle") {
|
|
36343
|
-
const guess = userInput.coords;
|
|
36344
|
-
const correct = rubric.correct.coords;
|
|
36345
|
-
const allowReflexAngles = rubric.correct.allowReflexAngles;
|
|
36346
|
-
let match;
|
|
36347
|
-
if (rubric.correct.match === "congruent") {
|
|
36348
|
-
const angles = _.map([guess, correct], function (coords) {
|
|
36349
|
-
if (!coords) {
|
|
36350
|
-
return false;
|
|
36351
|
-
}
|
|
36352
|
-
const angle = getClockwiseAngle(coords, allowReflexAngles);
|
|
36353
|
-
return angle;
|
|
36354
|
-
});
|
|
36355
|
-
// @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
36356
|
-
match = eq$1(...angles);
|
|
36357
|
-
} else {
|
|
36358
|
-
/* exact */
|
|
36359
|
-
match =
|
|
36360
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
36361
|
-
deepEq$3(guess[1], correct[1]) &&
|
|
36362
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
36363
|
-
collinear(correct[1], correct[0], guess[0]) &&
|
|
36364
|
-
// @ts-expect-error - TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'. | TS2532 - Object is possibly 'undefined'.
|
|
36365
|
-
collinear(correct[1], correct[2], guess[2]);
|
|
36366
|
-
}
|
|
36367
|
-
if (match) {
|
|
36368
|
-
return {
|
|
36369
|
-
type: "points",
|
|
36370
|
-
earned: 1,
|
|
36371
|
-
total: 1,
|
|
36372
|
-
message: null
|
|
36373
|
-
};
|
|
36374
|
-
}
|
|
36375
|
-
}
|
|
36376
|
-
}
|
|
36377
|
-
|
|
36378
|
-
// The input wasn't correct, so check if it's a blank input or if it's
|
|
36379
|
-
// actually just wrong
|
|
36380
|
-
if (!hasValue || _.isEqual(userInput, rubric.graph)) {
|
|
36381
|
-
// We're where we started.
|
|
36382
|
-
return {
|
|
36383
|
-
type: "invalid",
|
|
36384
|
-
message: null
|
|
36385
|
-
};
|
|
36386
|
-
}
|
|
36387
|
-
return {
|
|
36388
|
-
type: "points",
|
|
36389
|
-
earned: 0,
|
|
36390
|
-
total: 1,
|
|
36391
|
-
message: null
|
|
36392
|
-
};
|
|
36393
|
-
}
|
|
36394
|
-
|
|
34963
|
+
const {
|
|
34964
|
+
getClockwiseAngle
|
|
34965
|
+
} = angles;
|
|
34966
|
+
const {
|
|
34967
|
+
angleMeasures,
|
|
34968
|
+
ccw,
|
|
34969
|
+
collinear,
|
|
34970
|
+
getLineEquation,
|
|
34971
|
+
getLineIntersection,
|
|
34972
|
+
intersects,
|
|
34973
|
+
lawOfCosines,
|
|
34974
|
+
magnitude,
|
|
34975
|
+
rotate,
|
|
34976
|
+
sign,
|
|
34977
|
+
vector
|
|
34978
|
+
} = geometry;
|
|
36395
34979
|
const TRASH_ICON_URI = "https://ka-perseus-graphie.s3.amazonaws.com/b1452c0d79fd0f7ff4c3af9488474a0a0decb361.png";
|
|
36396
34980
|
const defaultBackgroundImage = {
|
|
36397
34981
|
url: null
|
|
36398
34982
|
};
|
|
36399
|
-
const eq = Util.eq;
|
|
36400
34983
|
const UNLIMITED = "unlimited";
|
|
36401
34984
|
|
|
36402
34985
|
// Sample background image:
|
|
@@ -36409,7 +34992,7 @@ function defaultVal(actual, defaultValue) {
|
|
|
36409
34992
|
|
|
36410
34993
|
// Less than or approximately equal
|
|
36411
34994
|
function leq(a, b) {
|
|
36412
|
-
return a < b ||
|
|
34995
|
+
return a < b || approximateEqual(a, b);
|
|
36413
34996
|
}
|
|
36414
34997
|
function capitalize(str) {
|
|
36415
34998
|
return str.replace(/(?:^|-)(.)/g, function (match, letter) {
|
|
@@ -37236,7 +35819,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
37236
35819
|
// Solve for side by using the law of sines
|
|
37237
35820
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
37238
35821
|
const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
|
|
37239
|
-
const offset = polar$
|
|
35822
|
+
const offset = polar$2(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
37240
35823
|
return (_this$graphie5 = this.graphie) == null ? void 0 : _this$graphie5.addPoints(coords[rel(-1)], offset);
|
|
37241
35824
|
}
|
|
37242
35825
|
// @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
|
|
@@ -37261,7 +35844,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
37261
35844
|
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
37262
35845
|
const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
|
|
37263
35846
|
const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
|
|
37264
|
-
const offset = polar$
|
|
35847
|
+
const offset = polar$2(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
37265
35848
|
return (_this$graphie6 = this.graphie) == null ? void 0 : _this$graphie6.addPoints(coords[rel(-1)], offset);
|
|
37266
35849
|
}
|
|
37267
35850
|
// Snap to grid (already done)
|
|
@@ -37923,12 +36506,12 @@ class InteractiveGraph extends React.Component {
|
|
|
37923
36506
|
}
|
|
37924
36507
|
static getLinearEquationString(props) {
|
|
37925
36508
|
const coords = InteractiveGraph.getLineCoords(props.graph, props);
|
|
37926
|
-
if (
|
|
36509
|
+
if (approximateEqual(coords[0][0], coords[1][0])) {
|
|
37927
36510
|
return "x = " + coords[0][0].toFixed(3);
|
|
37928
36511
|
}
|
|
37929
36512
|
const m = (coords[1][1] - coords[0][1]) / (coords[1][0] - coords[0][0]);
|
|
37930
36513
|
const b = coords[0][1] - m * coords[0][0];
|
|
37931
|
-
if (
|
|
36514
|
+
if (approximateEqual(m, 0)) {
|
|
37932
36515
|
return "y = " + b.toFixed(3);
|
|
37933
36516
|
}
|
|
37934
36517
|
return "y = " + m.toFixed(3) + "x + " + b.toFixed(3);
|
|
@@ -38454,59 +37037,6 @@ const styles$a = StyleSheet.create({
|
|
|
38454
37037
|
}
|
|
38455
37038
|
});
|
|
38456
37039
|
|
|
38457
|
-
// Question state for marker as result of user selected answers.
|
|
38458
|
-
|
|
38459
|
-
function scoreMarker(marker) {
|
|
38460
|
-
const score = {
|
|
38461
|
-
hasAnswers: false,
|
|
38462
|
-
isCorrect: false
|
|
38463
|
-
};
|
|
38464
|
-
if (marker.selected && marker.selected.length > 0) {
|
|
38465
|
-
score.hasAnswers = true;
|
|
38466
|
-
}
|
|
38467
|
-
if (marker.answers.length > 0) {
|
|
38468
|
-
if (marker.selected && marker.selected.length === marker.answers.length) {
|
|
38469
|
-
// All correct answers are selected by the user.
|
|
38470
|
-
score.isCorrect = marker.selected.every(choice => marker.answers.includes(choice));
|
|
38471
|
-
}
|
|
38472
|
-
} else if (!marker.selected || marker.selected.length === 0) {
|
|
38473
|
-
// Correct as no answers should be selected by the user.
|
|
38474
|
-
score.isCorrect = true;
|
|
38475
|
-
}
|
|
38476
|
-
return score;
|
|
38477
|
-
}
|
|
38478
|
-
|
|
38479
|
-
// TODO(LEMS-2440): May need to pull answers out of PerseusLabelImageWidgetOptions[markers] for the rubric
|
|
38480
|
-
function scoreLabelImage(userInput, rubric) {
|
|
38481
|
-
let numAnswered = 0;
|
|
38482
|
-
let numCorrect = 0;
|
|
38483
|
-
for (const marker of userInput.markers) {
|
|
38484
|
-
const score = scoreMarker(marker);
|
|
38485
|
-
if (score.hasAnswers) {
|
|
38486
|
-
numAnswered++;
|
|
38487
|
-
}
|
|
38488
|
-
if (score.isCorrect) {
|
|
38489
|
-
numCorrect++;
|
|
38490
|
-
}
|
|
38491
|
-
}
|
|
38492
|
-
|
|
38493
|
-
// We expect all question markers to be answered before grading.
|
|
38494
|
-
if (numAnswered !== userInput.markers.length) {
|
|
38495
|
-
return {
|
|
38496
|
-
type: "invalid",
|
|
38497
|
-
message: null
|
|
38498
|
-
};
|
|
38499
|
-
}
|
|
38500
|
-
return {
|
|
38501
|
-
type: "points",
|
|
38502
|
-
// Markers with no expected answers are graded as correct if user
|
|
38503
|
-
// makes no answer selection.
|
|
38504
|
-
earned: numCorrect === userInput.markers.length ? 1 : 0,
|
|
38505
|
-
total: 1,
|
|
38506
|
-
message: null
|
|
38507
|
-
};
|
|
38508
|
-
}
|
|
38509
|
-
|
|
38510
37040
|
/**
|
|
38511
37041
|
* Represents a direction vector.
|
|
38512
37042
|
* No diagonals allowed.
|
|
@@ -38728,7 +37258,7 @@ class LabelImage extends React.Component {
|
|
|
38728
37258
|
onChange
|
|
38729
37259
|
} = this.props;
|
|
38730
37260
|
const updatedMarkers = markers.map(marker => {
|
|
38731
|
-
const score =
|
|
37261
|
+
const score = scoreLabelImageMarker(marker);
|
|
38732
37262
|
return _extends({}, marker, {
|
|
38733
37263
|
// Reveal correctness state for markers with answers.
|
|
38734
37264
|
showCorrectness: score.hasAnswers ? score.isCorrect ? "correct" : "incorrect" : undefined
|
|
@@ -38875,7 +37405,7 @@ class LabelImage extends React.Component {
|
|
|
38875
37405
|
bottom: "top"
|
|
38876
37406
|
}[markerPosition];
|
|
38877
37407
|
}
|
|
38878
|
-
const score =
|
|
37408
|
+
const score = scoreLabelImageMarker(marker);
|
|
38879
37409
|
// Once the question is answered, show markers
|
|
38880
37410
|
// with correct answers, otherwise passthrough
|
|
38881
37411
|
// the correctness state.
|
|
@@ -39807,16 +38337,6 @@ const getPromptJSON$9 = (renderProps, userInput) => {
|
|
|
39807
38337
|
};
|
|
39808
38338
|
};
|
|
39809
38339
|
|
|
39810
|
-
function scoreMatcher(state, rubric) {
|
|
39811
|
-
const correct = _.isEqual(state.left, rubric.left) && _.isEqual(state.right, rubric.right);
|
|
39812
|
-
return {
|
|
39813
|
-
type: "points",
|
|
39814
|
-
earned: correct ? 1 : 0,
|
|
39815
|
-
total: 1,
|
|
39816
|
-
message: null
|
|
39817
|
-
};
|
|
39818
|
-
}
|
|
39819
|
-
|
|
39820
38340
|
const {
|
|
39821
38341
|
shuffle: shuffle$1,
|
|
39822
38342
|
seededRNG
|
|
@@ -40039,78 +38559,6 @@ const getPromptJSON$8 = (renderProps, userInput) => {
|
|
|
40039
38559
|
};
|
|
40040
38560
|
};
|
|
40041
38561
|
|
|
40042
|
-
/**
|
|
40043
|
-
* Checks user input from the matrix widget to see if it is scorable.
|
|
40044
|
-
*
|
|
40045
|
-
* Note: The matrix widget cannot do much validation without the Scoring
|
|
40046
|
-
* Data because of its use of KhanAnswerTypes as a core part of scoring.
|
|
40047
|
-
*
|
|
40048
|
-
* @see `scoreMatrix()` for more details.
|
|
40049
|
-
*/
|
|
40050
|
-
function validateMatrix(userInput, validationData, strings) {
|
|
40051
|
-
const supplied = userInput.answers;
|
|
40052
|
-
const suppliedSize = getMatrixSize(supplied);
|
|
40053
|
-
for (let row = 0; row < suppliedSize[0]; row++) {
|
|
40054
|
-
for (let col = 0; col < suppliedSize[1]; col++) {
|
|
40055
|
-
if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
|
|
40056
|
-
return {
|
|
40057
|
-
type: "invalid",
|
|
40058
|
-
message: strings.fillAllCells
|
|
40059
|
-
};
|
|
40060
|
-
}
|
|
40061
|
-
}
|
|
40062
|
-
}
|
|
40063
|
-
return null;
|
|
40064
|
-
}
|
|
40065
|
-
|
|
40066
|
-
function scoreMatrix(userInput, rubric, strings) {
|
|
40067
|
-
const validationResult = validateMatrix(userInput, rubric, strings);
|
|
40068
|
-
if (validationResult != null) {
|
|
40069
|
-
return validationResult;
|
|
40070
|
-
}
|
|
40071
|
-
const solution = rubric.answers;
|
|
40072
|
-
const supplied = userInput.answers;
|
|
40073
|
-
const solutionSize = getMatrixSize(solution);
|
|
40074
|
-
const suppliedSize = getMatrixSize(supplied);
|
|
40075
|
-
const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
|
|
40076
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
40077
|
-
let message = null;
|
|
40078
|
-
let incorrect = false;
|
|
40079
|
-
_(suppliedSize[0]).times(row => {
|
|
40080
|
-
_(suppliedSize[1]).times(col => {
|
|
40081
|
-
if (!incorrectSize) {
|
|
40082
|
-
const validator = createValidator(
|
|
40083
|
-
// @ts-expect-error - TS2345 - Argument of type 'number' is not assignable to parameter of type 'string'.
|
|
40084
|
-
solution[row][col], {
|
|
40085
|
-
simplify: true
|
|
40086
|
-
});
|
|
40087
|
-
const result = validator(supplied[row][col]);
|
|
40088
|
-
if (result.message) {
|
|
40089
|
-
// @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
|
|
40090
|
-
message = result.message;
|
|
40091
|
-
}
|
|
40092
|
-
if (!result.correct) {
|
|
40093
|
-
incorrect = true;
|
|
40094
|
-
}
|
|
40095
|
-
}
|
|
40096
|
-
});
|
|
40097
|
-
});
|
|
40098
|
-
if (incorrectSize) {
|
|
40099
|
-
return {
|
|
40100
|
-
type: "points",
|
|
40101
|
-
earned: 0,
|
|
40102
|
-
total: 1,
|
|
40103
|
-
message: null
|
|
40104
|
-
};
|
|
40105
|
-
}
|
|
40106
|
-
return {
|
|
40107
|
-
type: "points",
|
|
40108
|
-
earned: incorrect ? 0 : 1,
|
|
40109
|
-
total: 1,
|
|
40110
|
-
message: message
|
|
40111
|
-
};
|
|
40112
|
-
}
|
|
40113
|
-
|
|
40114
38562
|
const {
|
|
40115
38563
|
assert: assert$2
|
|
40116
38564
|
} = InteractiveUtil;
|
|
@@ -40159,29 +38607,6 @@ const getRefForPath$1 = function getRefForPath(path) {
|
|
|
40159
38607
|
const column = getColumnFromPath$1(path);
|
|
40160
38608
|
return "answer" + row + "," + column;
|
|
40161
38609
|
};
|
|
40162
|
-
function getMatrixSize(matrix) {
|
|
40163
|
-
const matrixSize = [1, 1];
|
|
40164
|
-
|
|
40165
|
-
// We need to find the widest row and tallest column to get the correct
|
|
40166
|
-
// matrix size.
|
|
40167
|
-
_(matrix).each((matrixRow, row) => {
|
|
40168
|
-
let rowWidth = 0;
|
|
40169
|
-
_(matrixRow).each((matrixCol, col) => {
|
|
40170
|
-
if (matrixCol != null && matrixCol.toString().length) {
|
|
40171
|
-
rowWidth = col + 1;
|
|
40172
|
-
}
|
|
40173
|
-
});
|
|
40174
|
-
|
|
40175
|
-
// Matrix width:
|
|
40176
|
-
matrixSize[1] = Math.max(matrixSize[1], rowWidth);
|
|
40177
|
-
|
|
40178
|
-
// Matrix height:
|
|
40179
|
-
if (rowWidth > 0) {
|
|
40180
|
-
matrixSize[0] = Math.max(matrixSize[0], row + 1);
|
|
40181
|
-
}
|
|
40182
|
-
});
|
|
40183
|
-
return matrixSize;
|
|
40184
|
-
}
|
|
40185
38610
|
class Matrix extends React.Component {
|
|
40186
38611
|
constructor(...args) {
|
|
40187
38612
|
super(...args);
|
|
@@ -41572,59 +39997,6 @@ const getPromptJSON$6 = (renderProps, userInput) => {
|
|
|
41572
39997
|
};
|
|
41573
39998
|
};
|
|
41574
39999
|
|
|
41575
|
-
/**
|
|
41576
|
-
* Checks user input is within the allowed range and not the same as the initial
|
|
41577
|
-
* state.
|
|
41578
|
-
* @param userInput
|
|
41579
|
-
* @see 'scoreNumberLine' for the scoring logic.
|
|
41580
|
-
*/
|
|
41581
|
-
function validateNumberLine(userInput) {
|
|
41582
|
-
const divisionRange = userInput.divisionRange;
|
|
41583
|
-
const outsideAllowedRange = userInput.numDivisions > divisionRange[1] || userInput.numDivisions < divisionRange[0];
|
|
41584
|
-
|
|
41585
|
-
// TODO: I don't think isTickCrtl is a thing anymore
|
|
41586
|
-
if (userInput.isTickCrtl && outsideAllowedRange) {
|
|
41587
|
-
return {
|
|
41588
|
-
type: "invalid",
|
|
41589
|
-
message: "Number of divisions is outside the allowed range."
|
|
41590
|
-
};
|
|
41591
|
-
}
|
|
41592
|
-
return null;
|
|
41593
|
-
}
|
|
41594
|
-
|
|
41595
|
-
function scoreNumberLine(userInput, scoringData) {
|
|
41596
|
-
const validationError = validateNumberLine(userInput);
|
|
41597
|
-
if (validationError) {
|
|
41598
|
-
return validationError;
|
|
41599
|
-
}
|
|
41600
|
-
const range = scoringData.range;
|
|
41601
|
-
const start = scoringData.initialX != null ? scoringData.initialX : range[0];
|
|
41602
|
-
const startRel = scoringData.isInequality ? "ge" : "eq";
|
|
41603
|
-
const correctRel = scoringData.correctRel || "eq";
|
|
41604
|
-
const correctPos = number$1.equal(userInput.numLinePosition, scoringData.correctX || 0);
|
|
41605
|
-
if (correctPos && correctRel === userInput.rel) {
|
|
41606
|
-
return {
|
|
41607
|
-
type: "points",
|
|
41608
|
-
earned: 1,
|
|
41609
|
-
total: 1,
|
|
41610
|
-
message: null
|
|
41611
|
-
};
|
|
41612
|
-
}
|
|
41613
|
-
if (userInput.numLinePosition === start && userInput.rel === startRel) {
|
|
41614
|
-
// We're where we started.
|
|
41615
|
-
return {
|
|
41616
|
-
type: "invalid",
|
|
41617
|
-
message: null
|
|
41618
|
-
};
|
|
41619
|
-
}
|
|
41620
|
-
return {
|
|
41621
|
-
type: "points",
|
|
41622
|
-
earned: 0,
|
|
41623
|
-
total: 1,
|
|
41624
|
-
message: null
|
|
41625
|
-
};
|
|
41626
|
-
}
|
|
41627
|
-
|
|
41628
40000
|
// @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'.
|
|
41629
40001
|
const MovablePoint = Graphie.MovablePoint;
|
|
41630
40002
|
// @ts-expect-error - TS2339 - Property 'Line' does not exist on type 'typeof Graphie'.
|
|
@@ -42193,36 +40565,6 @@ const getPromptJSON$5 = (renderProps, userInput) => {
|
|
|
42193
40565
|
};
|
|
42194
40566
|
};
|
|
42195
40567
|
|
|
42196
|
-
/**
|
|
42197
|
-
* Checks user input from the orderer widget to see if the user has started
|
|
42198
|
-
* ordering the options, making the widget scorable.
|
|
42199
|
-
* @param userInput
|
|
42200
|
-
* @see `scoreOrderer` for more details.
|
|
42201
|
-
*/
|
|
42202
|
-
function validateOrderer(userInput) {
|
|
42203
|
-
if (userInput.current.length === 0) {
|
|
42204
|
-
return {
|
|
42205
|
-
type: "invalid",
|
|
42206
|
-
message: null
|
|
42207
|
-
};
|
|
42208
|
-
}
|
|
42209
|
-
return null;
|
|
42210
|
-
}
|
|
42211
|
-
|
|
42212
|
-
function scoreOrderer(userInput, rubric) {
|
|
42213
|
-
const validateError = validateOrderer(userInput);
|
|
42214
|
-
if (validateError) {
|
|
42215
|
-
return validateError;
|
|
42216
|
-
}
|
|
42217
|
-
const correct = _.isEqual(userInput.current, rubric.correctOptions.map(option => option.content));
|
|
42218
|
-
return {
|
|
42219
|
-
type: "points",
|
|
42220
|
-
earned: correct ? 1 : 0,
|
|
42221
|
-
total: 1,
|
|
42222
|
-
message: null
|
|
42223
|
-
};
|
|
42224
|
-
}
|
|
42225
|
-
|
|
42226
40568
|
class PlaceholderCard extends React.Component {
|
|
42227
40569
|
render() {
|
|
42228
40570
|
return /*#__PURE__*/React.createElement("div", {
|
|
@@ -43038,42 +41380,6 @@ const getPromptJSON$3 = () => {
|
|
|
43038
41380
|
return getUnsupportedPromptJSON("plotter");
|
|
43039
41381
|
};
|
|
43040
41382
|
|
|
43041
|
-
const {
|
|
43042
|
-
deepEq: deepEq$2
|
|
43043
|
-
} = Util;
|
|
43044
|
-
|
|
43045
|
-
/**
|
|
43046
|
-
* Checks user input to confirm it is not the same as the starting values for the graph.
|
|
43047
|
-
* This means the user has modified the graph, and the question can be scored.
|
|
43048
|
-
*
|
|
43049
|
-
* @see 'scorePlotter' for more details on scoring.
|
|
43050
|
-
*/
|
|
43051
|
-
function validatePlotter(userInput, validationData) {
|
|
43052
|
-
if (deepEq$2(userInput, validationData.starting)) {
|
|
43053
|
-
return {
|
|
43054
|
-
type: "invalid",
|
|
43055
|
-
message: null
|
|
43056
|
-
};
|
|
43057
|
-
}
|
|
43058
|
-
return null;
|
|
43059
|
-
}
|
|
43060
|
-
|
|
43061
|
-
const {
|
|
43062
|
-
deepEq: deepEq$1
|
|
43063
|
-
} = Util;
|
|
43064
|
-
function scorePlotter(userInput, scoringData) {
|
|
43065
|
-
const validationError = validatePlotter(userInput, scoringData);
|
|
43066
|
-
if (validationError) {
|
|
43067
|
-
return validationError;
|
|
43068
|
-
}
|
|
43069
|
-
return {
|
|
43070
|
-
type: "points",
|
|
43071
|
-
earned: deepEq$1(userInput, scoringData.correct) ? 1 : 0,
|
|
43072
|
-
total: 1,
|
|
43073
|
-
message: null
|
|
43074
|
-
};
|
|
43075
|
-
}
|
|
43076
|
-
|
|
43077
41383
|
/* eslint-disable react/no-unsafe */
|
|
43078
41384
|
class Plotter extends React.Component {
|
|
43079
41385
|
constructor(..._args) {
|
|
@@ -43953,43 +42259,6 @@ const getPromptJSON$1 = userInput => {
|
|
|
43953
42259
|
};
|
|
43954
42260
|
};
|
|
43955
42261
|
|
|
43956
|
-
/**
|
|
43957
|
-
* Checks user input for the sorter widget to ensure that the user has made
|
|
43958
|
-
* changes before attempting to score the widget.
|
|
43959
|
-
* @param userInput
|
|
43960
|
-
* @see 'scoreSorter' in 'packages/perseus/src/widgets/sorter/score-sorter.ts'
|
|
43961
|
-
* for more details on how the sorter widget is scored.
|
|
43962
|
-
*/
|
|
43963
|
-
function validateSorter(userInput) {
|
|
43964
|
-
// If the sorter widget hasn't been changed yet, we treat it as "empty" which
|
|
43965
|
-
// prevents the "Check" button from becoming active. We want the user
|
|
43966
|
-
// to make a change before trying to move forward. This makes an
|
|
43967
|
-
// assumption that the initial order isn't the correct order! However,
|
|
43968
|
-
// this should be rare if it happens, and interacting with the list
|
|
43969
|
-
// will enable the button, so they won't be locked out of progressing.
|
|
43970
|
-
if (!userInput.changed) {
|
|
43971
|
-
return {
|
|
43972
|
-
type: "invalid",
|
|
43973
|
-
message: null
|
|
43974
|
-
};
|
|
43975
|
-
}
|
|
43976
|
-
return null;
|
|
43977
|
-
}
|
|
43978
|
-
|
|
43979
|
-
function scoreSorter(userInput, rubric) {
|
|
43980
|
-
const validationError = validateSorter(userInput);
|
|
43981
|
-
if (validationError) {
|
|
43982
|
-
return validationError;
|
|
43983
|
-
}
|
|
43984
|
-
const correct = Util.deepEq(userInput.options, rubric.correct);
|
|
43985
|
-
return {
|
|
43986
|
-
type: "points",
|
|
43987
|
-
earned: correct ? 1 : 0,
|
|
43988
|
-
total: 1,
|
|
43989
|
-
message: null
|
|
43990
|
-
};
|
|
43991
|
-
}
|
|
43992
|
-
|
|
43993
42262
|
const {
|
|
43994
42263
|
shuffle
|
|
43995
42264
|
} = Util;
|
|
@@ -44082,81 +42351,6 @@ var Sorter$1 = {
|
|
|
44082
42351
|
scorer: scoreSorter
|
|
44083
42352
|
};
|
|
44084
42353
|
|
|
44085
|
-
/**
|
|
44086
|
-
* Filters the given table (modelled as a 2D array) to remove any rows that are
|
|
44087
|
-
* completely empty.
|
|
44088
|
-
*
|
|
44089
|
-
* @returns A new table with only non-empty rows.
|
|
44090
|
-
*/
|
|
44091
|
-
const filterNonEmpty = function filterNonEmpty(table) {
|
|
44092
|
-
return table.filter(function (row) {
|
|
44093
|
-
// Return only rows that are non-empty.
|
|
44094
|
-
return row.some(cell => cell);
|
|
44095
|
-
});
|
|
44096
|
-
};
|
|
44097
|
-
|
|
44098
|
-
function validateTable(userInput) {
|
|
44099
|
-
const supplied = filterNonEmpty(userInput);
|
|
44100
|
-
const hasEmptyCell = supplied.some(function (row) {
|
|
44101
|
-
return row.some(function (cell) {
|
|
44102
|
-
return cell === "";
|
|
44103
|
-
});
|
|
44104
|
-
});
|
|
44105
|
-
if (hasEmptyCell || !supplied.length) {
|
|
44106
|
-
return {
|
|
44107
|
-
type: "invalid",
|
|
44108
|
-
message: null
|
|
44109
|
-
};
|
|
44110
|
-
}
|
|
44111
|
-
return null;
|
|
44112
|
-
}
|
|
44113
|
-
|
|
44114
|
-
function scoreTable(userInput, rubric, strings) {
|
|
44115
|
-
const validationResult = validateTable(userInput);
|
|
44116
|
-
if (validationResult != null) {
|
|
44117
|
-
return validationResult;
|
|
44118
|
-
}
|
|
44119
|
-
const supplied = filterNonEmpty(userInput);
|
|
44120
|
-
const solution = filterNonEmpty(rubric.answers);
|
|
44121
|
-
if (supplied.length !== solution.length) {
|
|
44122
|
-
return {
|
|
44123
|
-
type: "points",
|
|
44124
|
-
earned: 0,
|
|
44125
|
-
total: 1,
|
|
44126
|
-
message: null
|
|
44127
|
-
};
|
|
44128
|
-
}
|
|
44129
|
-
const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
|
|
44130
|
-
let message = null;
|
|
44131
|
-
const allCorrect = solution.every(function (rowSolution) {
|
|
44132
|
-
for (let i = 0; i < supplied.length; i++) {
|
|
44133
|
-
const rowSupplied = supplied[i];
|
|
44134
|
-
const correct = rowSupplied.every(function (cellSupplied, i) {
|
|
44135
|
-
const cellSolution = rowSolution[i];
|
|
44136
|
-
const validator = createValidator(cellSolution, {
|
|
44137
|
-
simplify: true
|
|
44138
|
-
});
|
|
44139
|
-
const result = validator(cellSupplied);
|
|
44140
|
-
if (result.message) {
|
|
44141
|
-
message = result.message;
|
|
44142
|
-
}
|
|
44143
|
-
return result.correct;
|
|
44144
|
-
});
|
|
44145
|
-
if (correct) {
|
|
44146
|
-
supplied.splice(i, 1);
|
|
44147
|
-
return true;
|
|
44148
|
-
}
|
|
44149
|
-
}
|
|
44150
|
-
return false;
|
|
44151
|
-
});
|
|
44152
|
-
return {
|
|
44153
|
-
type: "points",
|
|
44154
|
-
earned: allCorrect ? 1 : 0,
|
|
44155
|
-
total: 1,
|
|
44156
|
-
message
|
|
44157
|
-
};
|
|
44158
|
-
}
|
|
44159
|
-
|
|
44160
42354
|
const {
|
|
44161
42355
|
assert
|
|
44162
42356
|
} = InteractiveUtil;
|
|
@@ -44559,7 +42753,7 @@ const init = function init() {
|
|
|
44559
42753
|
|
|
44560
42754
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44561
42755
|
const libName = "@khanacademy/perseus";
|
|
44562
|
-
const libVersion = "
|
|
42756
|
+
const libVersion = "51.0.0";
|
|
44563
42757
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44564
42758
|
|
|
44565
42759
|
const apiVersion = {
|
|
@@ -46520,7 +44714,6 @@ const getValidWidgetIds = perseusItem => {
|
|
|
46520
44714
|
return keys(widgets).filter(id => isWidgetIdInContent(perseusItem, id));
|
|
46521
44715
|
};
|
|
46522
44716
|
|
|
46523
|
-
const deepEq = Util.deepEq;
|
|
46524
44717
|
function isRealJSONParse(jsonParse) {
|
|
46525
44718
|
const randomPhrase = buildRandomPhrase();
|
|
46526
44719
|
const randomHintPhrase = buildRandomPhrase();
|
|
@@ -46577,7 +44770,7 @@ function isRealJSONParse(jsonParse) {
|
|
|
46577
44770
|
const testJSON = buildTestData(testingObject.replace(/"/g, '\\"'));
|
|
46578
44771
|
const parsedTestJSON = jsonParse(testJSON);
|
|
46579
44772
|
const parsedTestItemData = parsedTestJSON.data.assessmentItem.item.itemData;
|
|
46580
|
-
return
|
|
44773
|
+
return approximateDeepEqual(parsedTestItemData, testingObject);
|
|
46581
44774
|
}
|
|
46582
44775
|
function buildRandomString(capitalize = false) {
|
|
46583
44776
|
let randomString = "";
|
|
@@ -46777,7 +44970,9 @@ function object(schema) {
|
|
|
46777
44970
|
for (const [prop, propParser] of Object.entries(schema)) {
|
|
46778
44971
|
const result = propParser(rawValue[prop], ctx.forSubtree(prop));
|
|
46779
44972
|
if (isSuccess(result)) {
|
|
46780
|
-
|
|
44973
|
+
if (result.value !== undefined || prop in rawValue) {
|
|
44974
|
+
ret[prop] = result.value;
|
|
44975
|
+
}
|
|
46781
44976
|
} else {
|
|
46782
44977
|
mismatches.push(...result.detail);
|
|
46783
44978
|
}
|
|
@@ -48386,7 +46581,6 @@ const EditorJsonify = {
|
|
|
48386
46581
|
* Main entry point
|
|
48387
46582
|
*/
|
|
48388
46583
|
const GrapherUtil = {
|
|
48389
|
-
allTypes,
|
|
48390
46584
|
DEFAULT_GRAPHER_PROPS,
|
|
48391
46585
|
chooseType,
|
|
48392
46586
|
defaultPlotProps,
|
|
@@ -48401,5 +46595,5 @@ const ScoringUtil = {
|
|
|
48401
46595
|
* Types
|
|
48402
46596
|
*/
|
|
48403
46597
|
|
|
48404
|
-
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords,
|
|
46598
|
+
export { ApiOptions, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, dependencies as Dependencies, EditorJsonify, Expression, GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, JiptParagraphs, KhanColors, context as LoadingContext, Log, Matrix$1 as MatrixWidget, NumericInput$1 as NumericInput, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ScoringUtil, ref as ServerItemRenderer, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, widgets$1 as Widgets, addWidget, apiVersion, bodyXsmallBold, components, containerSizeClass, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAngleCoords, getAnswerFromUserInput, getAnswersFromWidgets, getCircleCoords, getCorrectAnswerForWidgetId, getImagesWithoutAltData, getInteractiveBoxFromSizeClass, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getQuadraticCoords, getSegmentCoords, getSinusoidCoords, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, iconChevronDown, iconTrash, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isFailure, isItemRenderableByVersion, isSuccess, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, ItemVersion as itemVersion, libVersion, makeSafeUrl, mathOnlyParser, mediaQueries, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parseDataFromJSONP, parsePerseusItem, preprocessTex, registerAllWidgetsForTesting, scorePerseusItem, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
|
|
48405
46599
|
//# sourceMappingURL=index.js.map
|