@khanacademy/perseus 40.0.0 → 41.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/components/hud.d.ts +4 -0
- package/dist/components/i18n-context.d.ts +4 -0
- package/dist/components/icon.d.ts +28 -27
- package/dist/components/image-loader.d.ts +9 -9
- package/dist/components/inline-icon.d.ts +6 -9
- package/dist/components/lint.d.ts +13 -0
- package/dist/components/math-input.d.ts +2 -2
- package/dist/components/multi-button-group.d.ts +26 -0
- package/dist/components/number-input.d.ts +19 -0
- package/dist/components/range-input.d.ts +3 -0
- package/dist/components/stub-tag-editor.d.ts +2 -2
- package/dist/components/svg-image.d.ts +30 -0
- package/dist/components/tooltip.d.ts +41 -30
- package/dist/es/index.css +6 -0
- package/dist/es/index.js +1034 -819
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +16 -2
- package/dist/es/strings.js.map +1 -1
- package/dist/icon-paths.d.ts +1 -5
- package/dist/index.css +6 -0
- package/dist/index.js +1062 -834
- package/dist/index.js.map +1 -1
- package/dist/strings.d.ts +8 -0
- package/dist/strings.js +22 -2
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +5 -5
- package/dist/widgets/expression/expression.d.ts +4 -4
- package/dist/widgets/interactive-graphs/graphs/angle.d.ts +2 -5
- package/dist/widgets/interactive-graphs/graphs/circle.d.ts +2 -5
- package/dist/widgets/interactive-graphs/graphs/linear-system.d.ts +2 -5
- package/dist/widgets/interactive-graphs/graphs/linear.d.ts +2 -5
- package/dist/widgets/interactive-graphs/graphs/point.d.ts +7 -5
- package/dist/widgets/interactive-graphs/graphs/polygon.d.ts +2 -7
- package/dist/widgets/interactive-graphs/graphs/quadratic.d.ts +2 -4
- package/dist/widgets/interactive-graphs/graphs/ray.d.ts +2 -5
- package/dist/widgets/interactive-graphs/graphs/screenreader-text.d.ts +1 -0
- package/dist/widgets/interactive-graphs/graphs/segment.d.ts +2 -5
- package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +2 -5
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/types.d.ts +7 -2
- package/dist/widgets/label-image/label-image.d.ts +2 -2
- package/package.json +2 -2
- package/dist/widgets/interactive-graphs/graphs/index.d.ts +0 -9
package/dist/index.js
CHANGED
|
@@ -367,7 +367,21 @@ const mockStrings = {
|
|
|
367
367
|
simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
|
|
368
368
|
addPoint: "Add Point",
|
|
369
369
|
removePoint: "Remove Point",
|
|
370
|
-
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph"
|
|
370
|
+
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
|
|
371
|
+
srPointAtCoordinates: _ref27 => {
|
|
372
|
+
let {
|
|
373
|
+
x,
|
|
374
|
+
y
|
|
375
|
+
} = _ref27;
|
|
376
|
+
return `Point at ${x} comma ${y}`;
|
|
377
|
+
},
|
|
378
|
+
srInteractiveElements: _ref28 => {
|
|
379
|
+
let {
|
|
380
|
+
elements
|
|
381
|
+
} = _ref28;
|
|
382
|
+
return `Interactive elements: ${elements}`;
|
|
383
|
+
},
|
|
384
|
+
srNoInteractiveElements: "No interactive elements"
|
|
371
385
|
};
|
|
372
386
|
|
|
373
387
|
/**
|
|
@@ -375,11 +389,13 @@ const mockStrings = {
|
|
|
375
389
|
* used inside the Perseus package.
|
|
376
390
|
*
|
|
377
391
|
*/
|
|
378
|
-
|
|
379
|
-
const PerseusI18nContext = /*#__PURE__*/React__namespace.createContext(process.env.NODE_ENV === "test" || process.env.STORYBOOK ? {
|
|
392
|
+
const mockPerseusI18nContext = {
|
|
380
393
|
strings: mockStrings,
|
|
381
394
|
locale: "en"
|
|
382
|
-
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// @ts-expect-error - TS2322 - Type 'Context<{ strings: {}; locale: string; }>' is not assignable to type 'Context<I18nContextType>'.
|
|
398
|
+
const PerseusI18nContext = /*#__PURE__*/React__namespace.createContext(process.env.NODE_ENV === "test" || process.env.STORYBOOK ? mockPerseusI18nContext :
|
|
383
399
|
// We want to return null here, not an empty object, so that we
|
|
384
400
|
// are will throw an error when attempting to access the
|
|
385
401
|
// undefined locale or strings, making it easier to debug.
|
|
@@ -664,7 +680,7 @@ class InnerMathInput extends React__namespace.Component {
|
|
|
664
680
|
closeButtonVisible: true,
|
|
665
681
|
style: styles$E.popoverContent
|
|
666
682
|
}, /*#__PURE__*/React__namespace.createElement(mathInput.DesktopKeypad, _extends({
|
|
667
|
-
onAnalyticsEvent: this.props.
|
|
683
|
+
onAnalyticsEvent: this.props.onAnalyticsEvent,
|
|
668
684
|
extraKeys: this.props.extraKeys,
|
|
669
685
|
onClickKey: this.handleKeypadPress,
|
|
670
686
|
cursorContext: this.state.cursorContext,
|
|
@@ -827,6 +843,9 @@ const getDependencies = () => {
|
|
|
827
843
|
const DependenciesContext = /*#__PURE__*/React__namespace.createContext({
|
|
828
844
|
analytics: {
|
|
829
845
|
onAnalyticsEvent: async () => {}
|
|
846
|
+
},
|
|
847
|
+
useVideo: () => {
|
|
848
|
+
throw new Error("useVideo dependency not provided in Perseus dependencies");
|
|
830
849
|
}
|
|
831
850
|
});
|
|
832
851
|
|
|
@@ -967,7 +986,7 @@ function getTextWidth(text) {
|
|
|
967
986
|
class TextListEditor extends React__namespace.Component {
|
|
968
987
|
static propTypes = {
|
|
969
988
|
options: PropTypes__default["default"].array,
|
|
970
|
-
layout: PropTypes__default["default"].
|
|
989
|
+
layout: PropTypes__default["default"].oneOf(["horizontal", "vertical"]),
|
|
971
990
|
onChange: PropTypes__default["default"].func.isRequired
|
|
972
991
|
};
|
|
973
992
|
static defaultProps = {
|
|
@@ -1085,6 +1104,8 @@ class TextListEditor extends React__namespace.Component {
|
|
|
1085
1104
|
}
|
|
1086
1105
|
}
|
|
1087
1106
|
|
|
1107
|
+
const EMPTY_ARRAY = [];
|
|
1108
|
+
|
|
1088
1109
|
/**
|
|
1089
1110
|
* Stub Tag Editor.
|
|
1090
1111
|
*
|
|
@@ -1098,7 +1119,6 @@ class TextListEditor extends React__namespace.Component {
|
|
|
1098
1119
|
* It also gives a nicer interface for the group metadata editor
|
|
1099
1120
|
* in local demo mode.
|
|
1100
1121
|
*/
|
|
1101
|
-
const EMPTY_ARRAY = [];
|
|
1102
1122
|
class StubTagEditor extends React__namespace.Component {
|
|
1103
1123
|
static propTypes = {
|
|
1104
1124
|
value: PropTypes__default["default"].arrayOf(PropTypes__default["default"].string),
|
|
@@ -2607,9 +2627,7 @@ class Expression extends React__namespace.Component {
|
|
|
2607
2627
|
hasError: this.state.showErrorStyle,
|
|
2608
2628
|
ariaLabel: this.props.ariaLabel || this.context.strings.mathInputBox,
|
|
2609
2629
|
extraKeys: this.props.keypadConfiguration?.extraKeys,
|
|
2610
|
-
|
|
2611
|
-
onAnalyticsEvent: async () => {}
|
|
2612
|
-
}
|
|
2630
|
+
onAnalyticsEvent: this.props.analytics?.onAnalyticsEvent ?? (async () => {})
|
|
2613
2631
|
}))));
|
|
2614
2632
|
}
|
|
2615
2633
|
}
|
|
@@ -14942,11 +14960,7 @@ ___default["default"].extend(MovableLine$4.prototype, {
|
|
|
14942
14960
|
* A stripped version of Icon.jsx from webapp. Takes an SVG icon and renders it
|
|
14943
14961
|
* inline like Font Awesome did.
|
|
14944
14962
|
*
|
|
14945
|
-
*
|
|
14946
|
-
* webapp's `icon-paths.js` which is a reference file for all the SVG paths
|
|
14947
|
-
* that we've used. You'll need to copy the object from that file into
|
|
14948
|
-
* whichever file you're using the icon and explicitly pass it in to the
|
|
14949
|
-
* <InlineIcon/> React component.
|
|
14963
|
+
* You can refer to `icon-paths.ts` for all available SVG icons.
|
|
14950
14964
|
*
|
|
14951
14965
|
* We assume that the viewBox is cropped and aligned to (0, 0), but icons can
|
|
14952
14966
|
* be defined differently. At some point we might want to add these attributes
|
|
@@ -14954,13 +14968,14 @@ ___default["default"].extend(MovableLine$4.prototype, {
|
|
|
14954
14968
|
*
|
|
14955
14969
|
* Sample usage:
|
|
14956
14970
|
*
|
|
14957
|
-
*
|
|
14971
|
+
* ```
|
|
14972
|
+
* const editIcon = {
|
|
14958
14973
|
* path: "M41.209 53.753l5.39 0l0 5.39l3.136 0l6.468-6.517-8.477-8.526-6.517 6.517l0 3.136zm33.173-34.937q-.882-.882-1.862.049l-19.6 19.6q-.931.98-.049 1.862t1.862-.049l19.6-19.6q.931-.98.049-1.862zm-38.563 45.668l0-16.121l37.632-37.632 16.17 16.121-37.632 37.632l-16.17 0zm43.022-12.397l0 10.633q-.049 6.713-4.753 11.417t-11.368 4.704l-46.599 0q-6.713 0-11.417-4.753t-4.704-11.368l0-46.599q0-6.664 4.753-11.417t11.368-4.704l46.599 0q3.528 0 6.566 1.372.833.392.98 1.323t-.49 1.617l-2.744 2.744q-.784.784-1.96.441t-2.352-.343l-46.599 0q-3.675 0-6.321 2.646t-2.646 6.321l0 46.599q0 3.675 2.646 6.321t6.321 2.646l46.599 0q3.675 0 6.321-2.646t2.646-6.321l0-7.056q0-.735.49-1.225l3.577-3.577q.833-.833 1.96-.392t1.127 1.617zm7.203-51.646q2.254 0 3.773 1.568l8.526 8.526q1.568 1.568 1.568 3.822t-1.568 3.773l-5.145 5.145-16.121-16.121 5.145-5.145q1.568-1.568 3.822-1.568z",
|
|
14959
14974
|
* width: 100,
|
|
14960
14975
|
* height: 78.912,
|
|
14961
|
-
*
|
|
14962
|
-
*
|
|
14963
|
-
*
|
|
14976
|
+
* };
|
|
14977
|
+
* <InlineIcon {...editIcon} />
|
|
14978
|
+
* ```
|
|
14964
14979
|
*/
|
|
14965
14980
|
const InlineIcon = _ref => {
|
|
14966
14981
|
let {
|
|
@@ -14987,11 +15002,7 @@ const InlineIcon = _ref => {
|
|
|
14987
15002
|
};
|
|
14988
15003
|
|
|
14989
15004
|
/**
|
|
14990
|
-
* Icon paths to be used with `inline-icon.
|
|
14991
|
-
*
|
|
14992
|
-
* These paths are taken directly from webapp's `icon-paths.js`. Unlike the
|
|
14993
|
-
* webapp equivalent, these can be directly required within Perseus files since
|
|
14994
|
-
* this is all bundled together anyway.
|
|
15005
|
+
* Icon paths to be used with `inline-icon.tsx` and `icon.tsx`.
|
|
14995
15006
|
*/
|
|
14996
15007
|
|
|
14997
15008
|
const iconCheck = {
|
|
@@ -16954,6 +16965,15 @@ const Status = {
|
|
|
16954
16965
|
LOADED: "loaded",
|
|
16955
16966
|
FAILED: "failed"
|
|
16956
16967
|
};
|
|
16968
|
+
/**
|
|
16969
|
+
* Component to display an image (or other React components) while the desired
|
|
16970
|
+
* image is loading.
|
|
16971
|
+
*
|
|
16972
|
+
* Derived from
|
|
16973
|
+
* https://github.com/hzdg/react-imageloader/blob/master/src/index.js
|
|
16974
|
+
* to better suit our environment/build tools. Additionally, this one does
|
|
16975
|
+
* not introduce a wrapper element, which makes styling easier.
|
|
16976
|
+
*/
|
|
16957
16977
|
class ImageLoader extends React__namespace.Component {
|
|
16958
16978
|
img;
|
|
16959
16979
|
constructor(props) {
|
|
@@ -21158,7 +21178,7 @@ const Triangle = props => {
|
|
|
21158
21178
|
width: 0,
|
|
21159
21179
|
position: "absolute",
|
|
21160
21180
|
left: props.left,
|
|
21161
|
-
top: props
|
|
21181
|
+
top: props.top,
|
|
21162
21182
|
borderLeft: borderLeft,
|
|
21163
21183
|
borderRight: borderRight,
|
|
21164
21184
|
borderTop: borderTop,
|
|
@@ -21239,6 +21259,47 @@ const HORIZONTAL_ALIGNMNENTS = {
|
|
|
21239
21259
|
arrowLeft: arrowSize => -arrowSize - 2
|
|
21240
21260
|
}
|
|
21241
21261
|
};
|
|
21262
|
+
/**
|
|
21263
|
+
* DEPRECATED! Use Wonder Blocks tooltip instead.
|
|
21264
|
+
*
|
|
21265
|
+
* A generic tooltip library for React.js
|
|
21266
|
+
*
|
|
21267
|
+
* ```
|
|
21268
|
+
* import Tooltip from "./tooltip";
|
|
21269
|
+
* <Tooltip
|
|
21270
|
+
* className="class-for-tooltip-contents"
|
|
21271
|
+
* horizontalPosition={HoriziontalDirection.Left}
|
|
21272
|
+
* horizontalAlign={HoriziontalDirection.Left}
|
|
21273
|
+
* verticalPosition={VerticalDirection.Top}
|
|
21274
|
+
* arrowSize={10} // arrow size in pixels
|
|
21275
|
+
* borderColor="#ccc" // color of the border for the tooltip
|
|
21276
|
+
* show={true} // whether the tooltip should currently be visible
|
|
21277
|
+
* targetContainerStyle={targetContainerStyle}
|
|
21278
|
+
* >
|
|
21279
|
+
* <TargetElementOfTheTooltip />
|
|
21280
|
+
* <TooltipContents1 />
|
|
21281
|
+
* <TooltipContents2 />
|
|
21282
|
+
* </Tooltip>
|
|
21283
|
+
* ```
|
|
21284
|
+
*
|
|
21285
|
+
* To show/hide the tooltip, the parent component should call the
|
|
21286
|
+
* `.show()` and `.hide()` methods of the tooltip when appropriate.
|
|
21287
|
+
* (These are usually set up as handlers of events on the target element.)
|
|
21288
|
+
*
|
|
21289
|
+
* Notes:
|
|
21290
|
+
* `className` should not specify a border; that is handled by `borderColor`
|
|
21291
|
+
* so that the arrow and tooltip match
|
|
21292
|
+
*
|
|
21293
|
+
* ```
|
|
21294
|
+
* __,,--``\\
|
|
21295
|
+
* _,,-''`` \\ ,
|
|
21296
|
+
* '----------_.------'-.___|\__
|
|
21297
|
+
* _.--''`` `)__ )__ @\__
|
|
21298
|
+
* ( .. ''---/___,,E/__,E'------`
|
|
21299
|
+
* `-''`''
|
|
21300
|
+
* Here be dragons.
|
|
21301
|
+
* ```
|
|
21302
|
+
*/
|
|
21242
21303
|
class Tooltip extends React__namespace.Component {
|
|
21243
21304
|
static defaultProps = {
|
|
21244
21305
|
className: "",
|
|
@@ -21466,10 +21527,7 @@ class InputWithExamples extends React__namespace.Component {
|
|
|
21466
21527
|
return "- " + example;
|
|
21467
21528
|
}).join("\n");
|
|
21468
21529
|
const showExamples = this.props.shouldShowExamples && this.state.showExamples;
|
|
21469
|
-
return /*#__PURE__*/React__namespace.createElement(Tooltip
|
|
21470
|
-
// eslint-disable-next-line react/no-string-refs
|
|
21471
|
-
, {
|
|
21472
|
-
ref: "tooltip",
|
|
21530
|
+
return /*#__PURE__*/React__namespace.createElement(Tooltip, {
|
|
21473
21531
|
className: "perseus-formats-tooltip preview-measure",
|
|
21474
21532
|
horizontalPosition: HorizontalDirection.Left,
|
|
21475
21533
|
horizontalAlign: HorizontalDirection.Left,
|
|
@@ -24966,6 +25024,37 @@ const BASE_ICON_SIZE = 10;
|
|
|
24966
25024
|
// If you pass in an object you need to specify an SVG path to
|
|
24967
25025
|
// render along with the width and height of the path (to ensure
|
|
24968
25026
|
// that it gets scaled correctly).
|
|
25027
|
+
/**
|
|
25028
|
+
* An SVG Icon
|
|
25029
|
+
*
|
|
25030
|
+
* This component is designed to take in SVG paths and render icons based upon
|
|
25031
|
+
* them. If you are looking for an icon that we've used before you should look
|
|
25032
|
+
* in `icon-paths.js` which is a reference file for all the SVG paths that
|
|
25033
|
+
* we've used. You'll need to copy the object from that file into whichever
|
|
25034
|
+
* file you're using the icon and explicitly pass it in to the <Icon/> React
|
|
25035
|
+
* component.
|
|
25036
|
+
*
|
|
25037
|
+
* Sample usage:
|
|
25038
|
+
*
|
|
25039
|
+
* ```
|
|
25040
|
+
* const dropdownIcon = `M5,6L0,0L10,0`;
|
|
25041
|
+
* <Icon icon={dropdownIcon} />
|
|
25042
|
+
* ```
|
|
25043
|
+
*
|
|
25044
|
+
* Or:
|
|
25045
|
+
*
|
|
25046
|
+
* ```
|
|
25047
|
+
* const dropdownIcon = {
|
|
25048
|
+
* path: `M5,6L0,0L10,0`,
|
|
25049
|
+
* height: 10,
|
|
25050
|
+
* width: 10,
|
|
25051
|
+
* };
|
|
25052
|
+
* <Icon icon={dropdownIcon} size={20} />
|
|
25053
|
+
* ```
|
|
25054
|
+
*
|
|
25055
|
+
* If you want to add an entirely new icon please read the note inside
|
|
25056
|
+
* the `icon-paths.ts` file.
|
|
25057
|
+
*/
|
|
24969
25058
|
class Icon extends React__namespace.Component {
|
|
24970
25059
|
static defaultProps = {
|
|
24971
25060
|
color: "currentColor"
|
|
@@ -25189,7 +25278,7 @@ function ChoiceInner(props) {
|
|
|
25189
25278
|
} = usePerseusI18n();
|
|
25190
25279
|
const letter = getChoiceLetter(pos, strings);
|
|
25191
25280
|
if (!showCorrectness) {
|
|
25192
|
-
return letter;
|
|
25281
|
+
return /*#__PURE__*/React__namespace.createElement("span", null, letter);
|
|
25193
25282
|
}
|
|
25194
25283
|
if (correct) {
|
|
25195
25284
|
return /*#__PURE__*/React__namespace.createElement(InlineIcon, _extends({}, iconCheck, {
|
|
@@ -25259,7 +25348,6 @@ const ChoiceIcon = function (props) {
|
|
|
25259
25348
|
visible: focused || hovered,
|
|
25260
25349
|
multipleSelect: multipleSelect
|
|
25261
25350
|
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
25262
|
-
// @ts-expect-error - TS2322 - Type '{ backgroundColor: string | null | undefined; borderColor: string; color: string; borderRadius: number; }' is not assignable to type 'Properties<string | number, string & {}>'.
|
|
25263
25351
|
style: dynamicStyles,
|
|
25264
25352
|
"data-testid": "choice-icon__library-choice-icon",
|
|
25265
25353
|
className: aphrodite.css(styles$s.circle, showCorrectness && correct && styles$s.circleCorrect, showCorrectness && !correct && styles$s.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$s.circleIncorrectAnswered)
|
|
@@ -31173,6 +31261,10 @@ const HiddenIcon = () => /*#__PURE__*/React__namespace.createElement("svg", {
|
|
|
31173
31261
|
}, /*#__PURE__*/React__namespace.createElement("path", {
|
|
31174
31262
|
d: "M0 0h24v24H0z"
|
|
31175
31263
|
}))));
|
|
31264
|
+
/**
|
|
31265
|
+
* A "heads-up display" (HUD) indicator that includes a short message (usually
|
|
31266
|
+
* used for linting errors). The indicator can be disabled.
|
|
31267
|
+
*/
|
|
31176
31268
|
const HUD = _ref => {
|
|
31177
31269
|
let {
|
|
31178
31270
|
message,
|
|
@@ -31386,18 +31478,24 @@ const {
|
|
|
31386
31478
|
const toNumericString = KhanMath.toNumericString;
|
|
31387
31479
|
const getNumericFormat = KhanMath.getNumericFormat;
|
|
31388
31480
|
|
|
31389
|
-
|
|
31481
|
+
/**
|
|
31482
|
+
* An input box that accepts only numeric strings
|
|
31390
31483
|
*
|
|
31391
|
-
* Calls onChange(value, format) for valid numbers.
|
|
31392
|
-
*
|
|
31484
|
+
* Calls `onChange(value, format)` for valid numbers.
|
|
31485
|
+
*
|
|
31486
|
+
* Reverts to the current value `onBlur` or on [ENTER],
|
|
31393
31487
|
* but maintains the format (i.e. 3/2, 1 1/2, 150%)
|
|
31394
|
-
*
|
|
31395
|
-
*
|
|
31396
|
-
*
|
|
31397
|
-
*
|
|
31398
|
-
* If
|
|
31399
|
-
*
|
|
31400
|
-
*
|
|
31488
|
+
*
|
|
31489
|
+
* Accepts empty input and sends it to `onChange` as `null` if no numeric
|
|
31490
|
+
* placeholder is set.
|
|
31491
|
+
*
|
|
31492
|
+
* If given a `checkValidity` function, will turn the background/outline red
|
|
31493
|
+
* when invalid.
|
|
31494
|
+
*
|
|
31495
|
+
* If `useArrowKeys` is set to `true`, up/down arrows will increment/decrement
|
|
31496
|
+
* integers.
|
|
31497
|
+
*
|
|
31498
|
+
* Optionally takes a `size` (`"mini"`, `"small"`,` `"normal"`)
|
|
31401
31499
|
*/
|
|
31402
31500
|
class NumberInput extends React__namespace.Component {
|
|
31403
31501
|
static contextType = PerseusI18nContext;
|
|
@@ -31408,7 +31506,7 @@ class NumberInput extends React__namespace.Component {
|
|
|
31408
31506
|
onChange: PropTypes__default["default"].func.isRequired,
|
|
31409
31507
|
onFormatChange: PropTypes__default["default"].func,
|
|
31410
31508
|
checkValidity: PropTypes__default["default"].func,
|
|
31411
|
-
size: PropTypes__default["default"].
|
|
31509
|
+
size: PropTypes__default["default"].oneOf(["mini", "small", "normal"]),
|
|
31412
31510
|
label: PropTypes__default["default"].oneOf(["put your labels outside your inputs!"])
|
|
31413
31511
|
};
|
|
31414
31512
|
static defaultProps = {
|
|
@@ -31578,8 +31676,8 @@ class NumberInput extends React__namespace.Component {
|
|
|
31578
31676
|
|
|
31579
31677
|
const truth = () => true;
|
|
31580
31678
|
|
|
31581
|
-
|
|
31582
|
-
*
|
|
31679
|
+
/**
|
|
31680
|
+
* A minor abstraction on top of `NumberInput` for ranges
|
|
31583
31681
|
*/
|
|
31584
31682
|
class RangeInput extends React__namespace.Component {
|
|
31585
31683
|
static propTypes = {
|
|
@@ -32423,6 +32521,335 @@ function reinitialize(params) {
|
|
|
32423
32521
|
};
|
|
32424
32522
|
}
|
|
32425
32523
|
|
|
32524
|
+
/**
|
|
32525
|
+
* Wrapper with Mafs transform styles for SVG elements.
|
|
32526
|
+
* Scales and translates the SVG element to match the Mafs view.
|
|
32527
|
+
* Use if you do not need/want to use the hook in `use-transform.ts`.
|
|
32528
|
+
*/
|
|
32529
|
+
const MafsCssTransformWrapper = _ref => {
|
|
32530
|
+
let {
|
|
32531
|
+
children
|
|
32532
|
+
} = _ref;
|
|
32533
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
32534
|
+
style: {
|
|
32535
|
+
transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
|
|
32536
|
+
}
|
|
32537
|
+
}, children);
|
|
32538
|
+
};
|
|
32539
|
+
|
|
32540
|
+
const TextLabel = _ref => {
|
|
32541
|
+
let {
|
|
32542
|
+
children,
|
|
32543
|
+
...rest
|
|
32544
|
+
} = _ref;
|
|
32545
|
+
return /*#__PURE__*/React__default["default"].createElement(mafs.Text, _extends({
|
|
32546
|
+
size: 16,
|
|
32547
|
+
svgTextProps: {
|
|
32548
|
+
filter: "url(#background)",
|
|
32549
|
+
fontWeight: "bold"
|
|
32550
|
+
}
|
|
32551
|
+
}, rest), children);
|
|
32552
|
+
};
|
|
32553
|
+
const SvgDefs = () => /*#__PURE__*/React__default["default"].createElement("defs", null, /*#__PURE__*/React__default["default"].createElement("filter", {
|
|
32554
|
+
id: "background",
|
|
32555
|
+
x: "-5%",
|
|
32556
|
+
width: "110%",
|
|
32557
|
+
y: "0%",
|
|
32558
|
+
height: "100%"
|
|
32559
|
+
}, /*#__PURE__*/React__default["default"].createElement("feFlood", {
|
|
32560
|
+
floodColor: "#FFF",
|
|
32561
|
+
floodOpacity: "0.64"
|
|
32562
|
+
}), /*#__PURE__*/React__default["default"].createElement("feComposite", {
|
|
32563
|
+
operator: "over",
|
|
32564
|
+
in: "SourceGraphic"
|
|
32565
|
+
})));
|
|
32566
|
+
|
|
32567
|
+
const PolygonAngle = _ref => {
|
|
32568
|
+
let {
|
|
32569
|
+
centerPoint,
|
|
32570
|
+
endPoints,
|
|
32571
|
+
range,
|
|
32572
|
+
polygonLines,
|
|
32573
|
+
showAngles,
|
|
32574
|
+
snapTo
|
|
32575
|
+
} = _ref;
|
|
32576
|
+
const [centerX, centerY] = centerPoint;
|
|
32577
|
+
const areClockwise = clockwise$1([centerPoint, ...endPoints]);
|
|
32578
|
+
const [[startX, startY], [endX, endY]] = areClockwise ? endPoints : endPoints.reverse();
|
|
32579
|
+
const radius = 0.3;
|
|
32580
|
+
const a = mafs.vec.dist(centerPoint, endPoints[0]);
|
|
32581
|
+
const b = mafs.vec.dist(centerPoint, endPoints[1]);
|
|
32582
|
+
const c = mafs.vec.dist(endPoints[0], endPoints[1]);
|
|
32583
|
+
|
|
32584
|
+
// Law of cosines
|
|
32585
|
+
const angle = Math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b));
|
|
32586
|
+
const y1 = centerY + (startY - centerY) / a * radius;
|
|
32587
|
+
const x2 = centerX + (endX - centerX) / b * radius;
|
|
32588
|
+
const x1 = centerX + (startX - centerX) / a * radius;
|
|
32589
|
+
const y2 = centerY + (endY - centerY) / b * radius;
|
|
32590
|
+
|
|
32591
|
+
// Fourth point that would create a rhomboid
|
|
32592
|
+
// Used to calculate the inside of the angle and to create square
|
|
32593
|
+
const [x3, y3] = mafs.vec.add(centerPoint, mafs.vec.add(mafs.vec.sub([x1, y1], centerPoint), mafs.vec.sub([x2, y2], centerPoint)));
|
|
32594
|
+
if (!showAngles) {
|
|
32595
|
+
return isRightPolygonAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
|
|
32596
|
+
start: [x1, y1],
|
|
32597
|
+
vertex: [x2, y2],
|
|
32598
|
+
end: [x3, y3]
|
|
32599
|
+
}) : null;
|
|
32600
|
+
}
|
|
32601
|
+
|
|
32602
|
+
// Midpoint betwen ends of arc
|
|
32603
|
+
const isOutside = shouldDrawArcOutside([x3, y3], centerPoint, range, polygonLines);
|
|
32604
|
+
const largeArcFlag = isOutside ? 1 : 0;
|
|
32605
|
+
const sweepFlag = isOutside ? 1 : 0;
|
|
32606
|
+
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
32607
|
+
let angleInDegrees = angle * (180 / Math.PI);
|
|
32608
|
+
// If we have triggered "largArcFlag", the angle should be greater than 180
|
|
32609
|
+
if (isOutside) {
|
|
32610
|
+
angleInDegrees = 360 - angleInDegrees;
|
|
32611
|
+
}
|
|
32612
|
+
|
|
32613
|
+
// Only want to show whole numbers when in angles mode
|
|
32614
|
+
const angleLabelNumber = parseFloat(angleInDegrees.toFixed(snapTo === "angles" ? 0 : 1));
|
|
32615
|
+
const angleLabel = Number.isInteger(angleLabelNumber) ? angleLabelNumber : "≈ " + angleLabelNumber;
|
|
32616
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("filter", {
|
|
32617
|
+
id: "background",
|
|
32618
|
+
x: "-5%",
|
|
32619
|
+
width: "110%",
|
|
32620
|
+
y: "0%",
|
|
32621
|
+
height: "100%"
|
|
32622
|
+
}, /*#__PURE__*/React__namespace.createElement("feFlood", {
|
|
32623
|
+
floodColor: "#FFF",
|
|
32624
|
+
floodOpacity: "0.5"
|
|
32625
|
+
}), /*#__PURE__*/React__namespace.createElement("feComposite", {
|
|
32626
|
+
operator: "over",
|
|
32627
|
+
in: "SourceGraphic"
|
|
32628
|
+
}))), !isOutside && isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
|
|
32629
|
+
start: [x1, y1],
|
|
32630
|
+
vertex: [x2, y2],
|
|
32631
|
+
end: [x3, y3]
|
|
32632
|
+
}) : /*#__PURE__*/React__namespace.createElement(Arc, {
|
|
32633
|
+
arc: arc
|
|
32634
|
+
}), /*#__PURE__*/React__namespace.createElement(TextLabel, {
|
|
32635
|
+
x: x3,
|
|
32636
|
+
y: y3,
|
|
32637
|
+
attach: y3 - centerY > 0 ? "s" : "n",
|
|
32638
|
+
attachDistance: Math.abs(y3 - centerY) < 0.2 || mafs.vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
|
|
32639
|
+
}, angleLabel, "\xB0"));
|
|
32640
|
+
};
|
|
32641
|
+
const Angle = _ref2 => {
|
|
32642
|
+
let {
|
|
32643
|
+
vertex,
|
|
32644
|
+
coords,
|
|
32645
|
+
showAngles,
|
|
32646
|
+
allowReflexAngles,
|
|
32647
|
+
range
|
|
32648
|
+
} = _ref2;
|
|
32649
|
+
// Check if the points are clockwise or not, depending on whether we allow reflex angles
|
|
32650
|
+
const areClockwise = clockwise$1([...coords, vertex]);
|
|
32651
|
+
const shouldReverseCoords = areClockwise && !allowReflexAngles;
|
|
32652
|
+
|
|
32653
|
+
// Reverse the coordinates accordingly to ensure the angle is calculated correctly
|
|
32654
|
+
const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
|
|
32655
|
+
|
|
32656
|
+
// Calculate the angles between the two points
|
|
32657
|
+
const startAngle = getAngleFromVertex(clockwiseCoords[0], vertex);
|
|
32658
|
+
const endAngle = getAngleFromVertex(clockwiseCoords[1], vertex);
|
|
32659
|
+
const angle = (startAngle + 360 - endAngle) % 360;
|
|
32660
|
+
|
|
32661
|
+
// Check if the angle is reflexive
|
|
32662
|
+
const isReflexive = angle > 180;
|
|
32663
|
+
|
|
32664
|
+
// Break out the necessary points for the arc calculation
|
|
32665
|
+
const [centerX, centerY] = vertex;
|
|
32666
|
+
const [point1, point2] = clockwiseCoords;
|
|
32667
|
+
const [startX, startY] = point1;
|
|
32668
|
+
const [endX, endY] = point2;
|
|
32669
|
+
const a = mafs.vec.dist(vertex, point1);
|
|
32670
|
+
const b = mafs.vec.dist(vertex, point2);
|
|
32671
|
+
|
|
32672
|
+
// Set the radius of the arc
|
|
32673
|
+
const radius = 2;
|
|
32674
|
+
|
|
32675
|
+
// Calculate the end points of the arc
|
|
32676
|
+
const y1 = centerY + (startY - centerY) / a * radius;
|
|
32677
|
+
const x2 = centerX + (endX - centerX) / b * radius;
|
|
32678
|
+
const x1 = centerX + (startX - centerX) / a * radius;
|
|
32679
|
+
const y2 = centerY + (endY - centerY) / b * radius;
|
|
32680
|
+
|
|
32681
|
+
// Used to calculate the inside of the angle and to create square
|
|
32682
|
+
const [x3, y3] = mafs.vec.add(vertex, mafs.vec.add(mafs.vec.sub([x1, y1], vertex), mafs.vec.sub([x2, y2], vertex)));
|
|
32683
|
+
|
|
32684
|
+
// Determine whether the arc should be drawn outside the angle
|
|
32685
|
+
const isOutside = shouldDrawArcOutside([x3, y3], vertex, range, [[vertex, point1], [vertex, point2]]);
|
|
32686
|
+
|
|
32687
|
+
// Determine the flags for the arc
|
|
32688
|
+
// If the angle is outside OR reflexive, we want to draw a large arc
|
|
32689
|
+
// If the angle is outside AND reflexive, we want to sweep the arc
|
|
32690
|
+
const largeArcFlag = isOutside || isReflexive ? 1 : 0;
|
|
32691
|
+
const sweepFlag = isOutside && isReflexive ? 1 : 0;
|
|
32692
|
+
|
|
32693
|
+
// Create the SVG path for the arc
|
|
32694
|
+
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
32695
|
+
|
|
32696
|
+
// We only ever want to show whole angles
|
|
32697
|
+
const angleLabel = parseFloat(angle.toFixed(0)); // Only want to show whole angles
|
|
32698
|
+
|
|
32699
|
+
// Calculate the text position based on the angle and whether we allow reflex angles
|
|
32700
|
+
// Let's try the angle bisector method to find the midpoint of the arc
|
|
32701
|
+
const [textX, textY] = calculateBisectorPoint(point1, point2, vertex, isReflexive, allowReflexAngles, radius);
|
|
32702
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("filter", {
|
|
32703
|
+
id: "background",
|
|
32704
|
+
x: "-5%",
|
|
32705
|
+
width: "110%",
|
|
32706
|
+
y: "0%",
|
|
32707
|
+
height: "100%"
|
|
32708
|
+
}, /*#__PURE__*/React__namespace.createElement("feFlood", {
|
|
32709
|
+
floodColor: "#FFF",
|
|
32710
|
+
floodOpacity: "0.5"
|
|
32711
|
+
}), /*#__PURE__*/React__namespace.createElement("feComposite", {
|
|
32712
|
+
operator: "over",
|
|
32713
|
+
in: "SourceGraphic"
|
|
32714
|
+
}))), isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
|
|
32715
|
+
start: [x1, y1],
|
|
32716
|
+
vertex: [x2, y2],
|
|
32717
|
+
end: [x3, y3],
|
|
32718
|
+
className: "arc-right-angle"
|
|
32719
|
+
}) : /*#__PURE__*/React__namespace.createElement(Arc, {
|
|
32720
|
+
arc: arc,
|
|
32721
|
+
className: "angle-arc"
|
|
32722
|
+
}), showAngles && /*#__PURE__*/React__namespace.createElement(TextLabel, {
|
|
32723
|
+
x: textX,
|
|
32724
|
+
y: textY,
|
|
32725
|
+
color: wonderBlocksTokens.color.blue
|
|
32726
|
+
}, angleLabel, "\xB0"));
|
|
32727
|
+
};
|
|
32728
|
+
|
|
32729
|
+
/**
|
|
32730
|
+
* This is broken out into its own component so that it can be used for an early return
|
|
32731
|
+
* (see line 55 or https://github.com/Khan/perseus/blob/84bbd882b11d16871e1b813a0b901f3b903d5479/packages/perseus/src/widgets/interactive-graphs/graphs/components/angle.tsx#L53-L57)
|
|
32732
|
+
*/
|
|
32733
|
+
const RightAngleSquare = _ref3 => {
|
|
32734
|
+
let {
|
|
32735
|
+
start: [x1, y1],
|
|
32736
|
+
vertex: [x2, y2],
|
|
32737
|
+
end: [x3, y3],
|
|
32738
|
+
className
|
|
32739
|
+
} = _ref3;
|
|
32740
|
+
return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
|
|
32741
|
+
d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
|
|
32742
|
+
strokeWidth: 0.02,
|
|
32743
|
+
fill: "none",
|
|
32744
|
+
className: className
|
|
32745
|
+
}));
|
|
32746
|
+
};
|
|
32747
|
+
|
|
32748
|
+
// We're conditionally adding the class name here so that we can style the arc differently
|
|
32749
|
+
// based on whether it's an angle or a polygon angle
|
|
32750
|
+
const Arc = _ref4 => {
|
|
32751
|
+
let {
|
|
32752
|
+
arc,
|
|
32753
|
+
className
|
|
32754
|
+
} = _ref4;
|
|
32755
|
+
return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
|
|
32756
|
+
d: arc,
|
|
32757
|
+
strokeWidth: 0.02,
|
|
32758
|
+
fill: "none",
|
|
32759
|
+
className: className
|
|
32760
|
+
}));
|
|
32761
|
+
};
|
|
32762
|
+
const isRightPolygonAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
|
|
32763
|
+
const isRightAngle = angle => Math.round(angle) === 90;
|
|
32764
|
+
|
|
32765
|
+
/**
|
|
32766
|
+
* Determines if an angle is an inside (false) or outside (true) angle.
|
|
32767
|
+
* They way, we know to flip the `largeArc` and `sweepArc` flags.
|
|
32768
|
+
* Uses the priciple that a ray from a point inside a polygon will intersect
|
|
32769
|
+
* with an odd number of lines, while a ray from a point outside the polygon
|
|
32770
|
+
* will intersect with an even number of lines.
|
|
32771
|
+
* https://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon
|
|
32772
|
+
*/
|
|
32773
|
+
const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
|
|
32774
|
+
// Create a ray from the midpoint (inside angle) to the edge of the range
|
|
32775
|
+
const rangeIntersectionPoint = getIntersectionOfRayWithBox(midpoint, vertex, range);
|
|
32776
|
+
let lineIntersectionCount = 0;
|
|
32777
|
+
polygonLines.forEach(line => segmentsIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
|
|
32778
|
+
|
|
32779
|
+
// If the number of intersections is even, the angle is inside the polygon
|
|
32780
|
+
return !isEven(lineIntersectionCount);
|
|
32781
|
+
};
|
|
32782
|
+
const isEven = n => n % 2 === 0;
|
|
32783
|
+
|
|
32784
|
+
// This function calculates the bisector point of an angle formed by two points
|
|
32785
|
+
// and a vertex. It is used to position the angle label so that it is always
|
|
32786
|
+
// outside the arc at the exact midpoint of the angle.
|
|
32787
|
+
// Reflex angles make use of polar coordinates to determine the correct direction
|
|
32788
|
+
function calculateBisectorPoint(point1, point2, vertex, isReflex, allowReflex, arcRadius) {
|
|
32789
|
+
const [originX, originY] = vertex;
|
|
32790
|
+
const [x1, y1] = point1;
|
|
32791
|
+
const [x2, y2] = point2;
|
|
32792
|
+
|
|
32793
|
+
// Convert the Cartesian Coordinates to vectors
|
|
32794
|
+
const vectorA = [x1 - originX, y1 - originY];
|
|
32795
|
+
const vectorB = [x2 - originX, y2 - originY];
|
|
32796
|
+
|
|
32797
|
+
// Calculate the average angle of the vectors
|
|
32798
|
+
// so that we can point the bisector in the correct direction using polar coordinates
|
|
32799
|
+
const angleA = Math.atan2(vectorA[1], vectorA[0]);
|
|
32800
|
+
const angleB = Math.atan2(vectorB[1], vectorB[0]);
|
|
32801
|
+
|
|
32802
|
+
// Calculate the average of these two angles
|
|
32803
|
+
let averageAngle = (angleA + angleB) / 2;
|
|
32804
|
+
const angleRadians = Math.abs(angleA - angleB);
|
|
32805
|
+
|
|
32806
|
+
// Adjust the average angle to point in the correct direction
|
|
32807
|
+
// and to account for reflex angles and a bug with 180 degrees
|
|
32808
|
+
// that occurs due to our angleOffsetDegrees option in the graph
|
|
32809
|
+
if (allowReflex) {
|
|
32810
|
+
// If the resulting angles are less than 180 degrees,
|
|
32811
|
+
// or if the user has flipped the points on the interactive element
|
|
32812
|
+
// ensure that we have the correct direction for the reflex angle
|
|
32813
|
+
if (angleRadians <= Math.PI && isReflex || angleB > angleA) {
|
|
32814
|
+
averageAngle += Math.PI; // Add 180 degrees
|
|
32815
|
+
}
|
|
32816
|
+
} else {
|
|
32817
|
+
// If the resulting angles are greater than 180 degrees, adjust
|
|
32818
|
+
// to get the correct angle, as we already know that the angle is not reflexive
|
|
32819
|
+
// This fixes a bug that occurs at 180 degrees due to our angleOffsetDegrees option in the graph
|
|
32820
|
+
if (angleRadians > Math.PI) {
|
|
32821
|
+
averageAngle -= Math.PI; // Subract 180 degrees
|
|
32822
|
+
}
|
|
32823
|
+
}
|
|
32824
|
+
|
|
32825
|
+
// Convert the average angle back to Cartesian coordinates
|
|
32826
|
+
const sum = [Math.cos(averageAngle), Math.sin(averageAngle)];
|
|
32827
|
+
|
|
32828
|
+
// Calculate the magnitude of the sum to normalize it
|
|
32829
|
+
const sumMagnitude = Math.sqrt(sum[0] ** 2 + sum[1] ** 2);
|
|
32830
|
+
const bisectorDirection = [sum[0] / sumMagnitude, sum[1] / sumMagnitude];
|
|
32831
|
+
|
|
32832
|
+
// Calculate the initial distance of the bisector direction from the origin
|
|
32833
|
+
const initialDistance = Math.sqrt(bisectorDirection[0] ** 2 + bisectorDirection[1] ** 2);
|
|
32834
|
+
|
|
32835
|
+
// Determine the minimum radius to ensure that the text is always outside the arc
|
|
32836
|
+
const requiredDistance = arcRadius * 1.75;
|
|
32837
|
+
let radius = requiredDistance / initialDistance;
|
|
32838
|
+
|
|
32839
|
+
// Apply radius only if the initial distance is less than required
|
|
32840
|
+
if (initialDistance >= requiredDistance) {
|
|
32841
|
+
radius = 1; // Use the unit vector as is
|
|
32842
|
+
}
|
|
32843
|
+
|
|
32844
|
+
// Scale the bisector direction by the radius
|
|
32845
|
+
const bisectorPoint = [bisectorDirection[0] * radius, bisectorDirection[1] * radius];
|
|
32846
|
+
|
|
32847
|
+
// Add the vertex to the bisector point to get the final position
|
|
32848
|
+
// to ensure that the angle label moves with the interactive element
|
|
32849
|
+
const scaledBisector = mafs.vec.add(bisectorPoint, vertex);
|
|
32850
|
+
return scaledBisector;
|
|
32851
|
+
}
|
|
32852
|
+
|
|
32426
32853
|
/**
|
|
32427
32854
|
* 44 is touch best practice and AAA compliant for WCAG
|
|
32428
32855
|
* https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
|
|
@@ -32630,6 +33057,14 @@ function range(min, max) {
|
|
|
32630
33057
|
return result;
|
|
32631
33058
|
}
|
|
32632
33059
|
|
|
33060
|
+
function srFormatNumber(a, locale) {
|
|
33061
|
+
// adding zero here converts negative zero to positive zero.
|
|
33062
|
+
return (0 + a).toLocaleString(locale, {
|
|
33063
|
+
maximumFractionDigits: 3,
|
|
33064
|
+
useGrouping: false // no thousands separators
|
|
33065
|
+
});
|
|
33066
|
+
}
|
|
33067
|
+
|
|
32633
33068
|
// The hitbox size of 48px by 48px is preserved from the legacy interactive
|
|
32634
33069
|
// graph.
|
|
32635
33070
|
const hitboxSizePx = 48;
|
|
@@ -32749,6 +33184,10 @@ function useControlPoint(params) {
|
|
|
32749
33184
|
onBlur = noop$1,
|
|
32750
33185
|
forwardedRef = noop$1
|
|
32751
33186
|
} = params;
|
|
33187
|
+
const {
|
|
33188
|
+
strings,
|
|
33189
|
+
locale
|
|
33190
|
+
} = usePerseusI18n();
|
|
32752
33191
|
const [focused, setFocused] = React.useState(false);
|
|
32753
33192
|
const focusableHandleRef = React.useRef(null);
|
|
32754
33193
|
useDraggable({
|
|
@@ -32774,6 +33213,15 @@ function useControlPoint(params) {
|
|
|
32774
33213
|
className: "movable-point__focusable-handle",
|
|
32775
33214
|
tabIndex: disableKeyboardInteraction ? -1 : 0,
|
|
32776
33215
|
ref: focusableHandleRef,
|
|
33216
|
+
role: "button",
|
|
33217
|
+
"aria-label": strings.srPointAtCoordinates({
|
|
33218
|
+
x: srFormatNumber(point[X], locale),
|
|
33219
|
+
y: srFormatNumber(point[Y], locale)
|
|
33220
|
+
})
|
|
33221
|
+
// aria-live="assertive" causes the new location of the point to be
|
|
33222
|
+
// announced immediately on move.
|
|
33223
|
+
,
|
|
33224
|
+
"aria-live": "assertive",
|
|
32777
33225
|
onFocus: event => {
|
|
32778
33226
|
onFocus(event);
|
|
32779
33227
|
setFocused(true);
|
|
@@ -32953,25 +33401,351 @@ function trimRange(range, graphDimensionsInPixels) {
|
|
|
32953
33401
|
return inset([graphUnitsToTrimX, graphUnitsToTrimY], range);
|
|
32954
33402
|
}
|
|
32955
33403
|
|
|
32956
|
-
const
|
|
33404
|
+
const MovablePoint$1 = /*#__PURE__*/React__namespace.forwardRef((props, pointRef) => {
|
|
32957
33405
|
const {
|
|
32958
|
-
|
|
33406
|
+
visiblePoint,
|
|
33407
|
+
focusableHandle
|
|
33408
|
+
} = useControlPoint({
|
|
33409
|
+
...props,
|
|
33410
|
+
forwardedRef: pointRef
|
|
33411
|
+
});
|
|
33412
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, focusableHandle, visiblePoint);
|
|
33413
|
+
});
|
|
33414
|
+
|
|
33415
|
+
function renderAngleGraph(state, dispatch) {
|
|
33416
|
+
return {
|
|
33417
|
+
graph: /*#__PURE__*/React__namespace.createElement(AngleGraph, {
|
|
33418
|
+
graphState: state,
|
|
33419
|
+
dispatch: dispatch
|
|
33420
|
+
}),
|
|
33421
|
+
interactiveElementsDescription: null
|
|
33422
|
+
};
|
|
33423
|
+
}
|
|
33424
|
+
function AngleGraph(props) {
|
|
33425
|
+
const {
|
|
33426
|
+
dispatch,
|
|
33427
|
+
graphState
|
|
32959
33428
|
} = props;
|
|
32960
33429
|
const {
|
|
32961
|
-
|
|
32962
|
-
} =
|
|
32963
|
-
|
|
32964
|
-
|
|
32965
|
-
|
|
32966
|
-
|
|
32967
|
-
|
|
32968
|
-
|
|
32969
|
-
|
|
32970
|
-
|
|
33430
|
+
graphDimensionsInPixels
|
|
33431
|
+
} = useGraphConfig();
|
|
33432
|
+
const {
|
|
33433
|
+
coords,
|
|
33434
|
+
showAngles,
|
|
33435
|
+
range,
|
|
33436
|
+
allowReflexAngles,
|
|
33437
|
+
snapDegrees
|
|
33438
|
+
} = graphState;
|
|
33439
|
+
|
|
33440
|
+
// Break the coords into the two end points and the center point
|
|
33441
|
+
const endPoints = [coords[0], coords[2]];
|
|
33442
|
+
const centerPoint = coords[1];
|
|
33443
|
+
|
|
33444
|
+
// Convert the vectors to pixels for rendering the svg lines
|
|
33445
|
+
const angleLines = [[centerPoint, endPoints[0]], [centerPoint, endPoints[1]]];
|
|
33446
|
+
const linePixelCoords = [useTransformVectorsToPixels(centerPoint, endPoints[0]), useTransformVectorsToPixels(centerPoint, endPoints[1])];
|
|
33447
|
+
|
|
33448
|
+
// Create the SVG lines for the angle
|
|
33449
|
+
const svgLines = linePixelCoords.map((_ref, i) => {
|
|
33450
|
+
let [startPtPx, endPtPx] = _ref;
|
|
33451
|
+
const trimmedRange = trimRange(range, graphDimensionsInPixels);
|
|
33452
|
+
const endExtend = getIntersectionOfRayWithBox(angleLines[i][0], angleLines[i][1], trimmedRange);
|
|
33453
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
33454
|
+
key: `line-${i}`
|
|
33455
|
+
}, /*#__PURE__*/React__namespace.createElement(SVGLine, {
|
|
33456
|
+
start: startPtPx,
|
|
33457
|
+
end: endPtPx,
|
|
33458
|
+
style: {
|
|
33459
|
+
stroke: "var(--movable-line-stroke-color)",
|
|
33460
|
+
strokeWidth: "var(--movable-line-stroke-weight)"
|
|
33461
|
+
}
|
|
33462
|
+
}), /*#__PURE__*/React__namespace.createElement(Vector, {
|
|
33463
|
+
tail: angleLines[i][1],
|
|
33464
|
+
tip: endExtend,
|
|
33465
|
+
color: "var(--movable-line-stroke-color)"
|
|
33466
|
+
}));
|
|
33467
|
+
});
|
|
33468
|
+
|
|
33469
|
+
// Create the angle indicator parameters
|
|
33470
|
+
const angleParams = {
|
|
33471
|
+
vertex: centerPoint,
|
|
33472
|
+
coords: endPoints,
|
|
33473
|
+
allowReflexAngles: allowReflexAngles || false,
|
|
33474
|
+
// Whether to allow reflex angles or not
|
|
33475
|
+
snapDegrees: snapDegrees || 1,
|
|
33476
|
+
// The multiple of degrees to snap to
|
|
33477
|
+
range: range,
|
|
33478
|
+
showAngles: showAngles || false // Whether to show the angle or not
|
|
33479
|
+
};
|
|
33480
|
+
|
|
33481
|
+
// Render the lines, angle, and then movable points
|
|
33482
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, svgLines, /*#__PURE__*/React__namespace.createElement(Angle, angleParams), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
33483
|
+
point: coords[1],
|
|
33484
|
+
constrain: p => p,
|
|
33485
|
+
onMove: destination => dispatch(actions.angle.movePoint(1, destination))
|
|
33486
|
+
}), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
33487
|
+
point: coords[0],
|
|
33488
|
+
constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
|
|
33489
|
+
onMove: destination => dispatch(actions.angle.movePoint(0, destination))
|
|
33490
|
+
}), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
33491
|
+
point: coords[2],
|
|
33492
|
+
constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
|
|
33493
|
+
onMove: destination => dispatch(actions.angle.movePoint(2, destination))
|
|
33494
|
+
}));
|
|
33495
|
+
}
|
|
33496
|
+
const positiveX = [1, 0];
|
|
33497
|
+
const negativeX = [-1, 0];
|
|
33498
|
+
const positiveY = [0, 1];
|
|
33499
|
+
const negativeY = [0, -1];
|
|
33500
|
+
function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
33501
|
+
const currentAngle = calculateAngleInDegrees(mafs.vec.sub(sidePoint, vertex));
|
|
33502
|
+
|
|
33503
|
+
// Find the rays that start at the current point and point up, down, left
|
|
33504
|
+
// and right.
|
|
33505
|
+
const leftRay = [sidePoint, mafs.vec.add(sidePoint, negativeX)];
|
|
33506
|
+
const rightRay = [sidePoint, mafs.vec.add(sidePoint, positiveX)];
|
|
33507
|
+
const upRay = [sidePoint, mafs.vec.add(sidePoint, positiveY)];
|
|
33508
|
+
const downRay = [sidePoint, mafs.vec.add(sidePoint, negativeY)];
|
|
33509
|
+
|
|
33510
|
+
// find the angles that lie one snap step clockwise and counter-clockwise
|
|
33511
|
+
// from the current angle. These are the angles to which the side can be
|
|
33512
|
+
// moved.
|
|
33513
|
+
const oneStepCounterClockwise = currentAngle + snapDegrees;
|
|
33514
|
+
const oneStepClockwise = currentAngle - snapDegrees;
|
|
33515
|
+
|
|
33516
|
+
// find the rays that start from the vertex and point in the direction of
|
|
33517
|
+
// the angles we just computed.
|
|
33518
|
+
const counterClockwiseRay = [vertex, mafs.vec.add(vertex, polar(1, oneStepCounterClockwise))];
|
|
33519
|
+
const clockwiseRay = [vertex, mafs.vec.add(vertex, polar(1, oneStepClockwise))];
|
|
33520
|
+
|
|
33521
|
+
// find the intersections of those rays with the horizontal and vertical
|
|
33522
|
+
// rays extending from the sidePoint. These intersections are the points to
|
|
33523
|
+
// which sidePoint can move that will result in a rotation of `snapDegrees`.
|
|
33524
|
+
const left = findIntersectionOfRays(leftRay, counterClockwiseRay) ?? findIntersectionOfRays(leftRay, clockwiseRay);
|
|
33525
|
+
const right = findIntersectionOfRays(rightRay, counterClockwiseRay) ?? findIntersectionOfRays(rightRay, clockwiseRay);
|
|
33526
|
+
const up = findIntersectionOfRays(upRay, counterClockwiseRay) ?? findIntersectionOfRays(upRay, clockwiseRay);
|
|
33527
|
+
const down = findIntersectionOfRays(downRay, counterClockwiseRay) ?? findIntersectionOfRays(downRay, clockwiseRay);
|
|
33528
|
+
return {
|
|
33529
|
+
up: up ?? sidePoint,
|
|
33530
|
+
down: down ?? sidePoint,
|
|
33531
|
+
left: left ?? sidePoint,
|
|
33532
|
+
right: right ?? sidePoint
|
|
33533
|
+
};
|
|
33534
|
+
}
|
|
33535
|
+
|
|
33536
|
+
function getGradableGraph(state, initialGraph) {
|
|
33537
|
+
if (!state.hasBeenInteractedWith) {
|
|
33538
|
+
return {
|
|
33539
|
+
...initialGraph
|
|
33540
|
+
};
|
|
33541
|
+
}
|
|
33542
|
+
if (initialGraph.type === "linear-system" && state.type === "linear-system") {
|
|
33543
|
+
return {
|
|
33544
|
+
...initialGraph,
|
|
33545
|
+
coords: state.coords
|
|
33546
|
+
};
|
|
33547
|
+
}
|
|
33548
|
+
if (state.type === "segment" && initialGraph.type === "segment") {
|
|
33549
|
+
return {
|
|
33550
|
+
...initialGraph,
|
|
33551
|
+
coords: state.coords
|
|
33552
|
+
};
|
|
33553
|
+
}
|
|
33554
|
+
if (state.type === "linear" && initialGraph.type === "linear") {
|
|
33555
|
+
return {
|
|
33556
|
+
...initialGraph,
|
|
33557
|
+
coords: state.coords
|
|
33558
|
+
};
|
|
33559
|
+
}
|
|
33560
|
+
if (state.type === "ray" && initialGraph.type === "ray") {
|
|
33561
|
+
return {
|
|
33562
|
+
...initialGraph,
|
|
33563
|
+
coords: state.coords
|
|
33564
|
+
};
|
|
33565
|
+
}
|
|
33566
|
+
if (state.type === "polygon" && initialGraph.type === "polygon") {
|
|
33567
|
+
return {
|
|
33568
|
+
...initialGraph,
|
|
33569
|
+
coords: state.coords
|
|
33570
|
+
};
|
|
33571
|
+
}
|
|
33572
|
+
if (state.type === "point" && initialGraph.type === "point") {
|
|
33573
|
+
return {
|
|
33574
|
+
...initialGraph,
|
|
33575
|
+
coords: state.coords
|
|
33576
|
+
};
|
|
33577
|
+
}
|
|
33578
|
+
if (state.type === "circle" && initialGraph.type === "circle") {
|
|
33579
|
+
return {
|
|
33580
|
+
...initialGraph,
|
|
33581
|
+
center: state.center,
|
|
33582
|
+
radius: getRadius(state)
|
|
33583
|
+
};
|
|
33584
|
+
}
|
|
33585
|
+
if (state.type === "quadratic" && initialGraph.type === "quadratic") {
|
|
33586
|
+
return {
|
|
33587
|
+
...initialGraph,
|
|
33588
|
+
coords: state.coords
|
|
33589
|
+
};
|
|
33590
|
+
}
|
|
33591
|
+
if (state.type === "sinusoid" && initialGraph.type === "sinusoid") {
|
|
33592
|
+
return {
|
|
33593
|
+
...initialGraph,
|
|
33594
|
+
coords: state.coords
|
|
33595
|
+
};
|
|
33596
|
+
}
|
|
33597
|
+
if (state.type === "angle" && initialGraph.type === "angle") {
|
|
33598
|
+
// We're going to reverse the coords for scoring if the angle is clockwise and
|
|
33599
|
+
// we don't allow reflex angles. This is because the angle is largely defined
|
|
33600
|
+
// by the order of the points in the coords array, and we want to maintain
|
|
33601
|
+
// the same angle scoring with the legacy graph (which had a bug).
|
|
33602
|
+
// (LEMS-2190): When we remove the legacy graph, move this logic to the scoring function.
|
|
33603
|
+
const areClockwise = clockwise$1([state.coords[0], state.coords[2], state.coords[1]]);
|
|
33604
|
+
const shouldReverseCoords = areClockwise && !state.allowReflexAngles;
|
|
33605
|
+
const coords = shouldReverseCoords ? state.coords.slice().reverse() : state.coords;
|
|
33606
|
+
return {
|
|
33607
|
+
...initialGraph,
|
|
33608
|
+
coords,
|
|
33609
|
+
allowReflexAngles: state.allowReflexAngles
|
|
33610
|
+
};
|
|
33611
|
+
}
|
|
33612
|
+
if (state.type === "none" && initialGraph.type === "none") {
|
|
33613
|
+
return {
|
|
33614
|
+
type: "none"
|
|
33615
|
+
};
|
|
33616
|
+
}
|
|
33617
|
+
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
33618
|
+
}
|
|
33619
|
+
|
|
33620
|
+
/**
|
|
33621
|
+
* determine radius of a circle graph
|
|
33622
|
+
*
|
|
33623
|
+
* @param graph - the graph object containing the circle
|
|
33624
|
+
* @returns the radius of the circle
|
|
33625
|
+
*/
|
|
33626
|
+
function getRadius(graph) {
|
|
33627
|
+
const [centerX, centerY] = graph.center;
|
|
33628
|
+
const [edgeX, edgeY] = graph.radiusPoint;
|
|
33629
|
+
return Math.sqrt(Math.pow(edgeX - centerX, 2) + Math.pow(edgeY - centerY, 2));
|
|
33630
|
+
}
|
|
33631
|
+
|
|
33632
|
+
function renderCircleGraph(state, dispatch) {
|
|
33633
|
+
return {
|
|
33634
|
+
graph: /*#__PURE__*/React__namespace.createElement(CircleGraph, {
|
|
33635
|
+
graphState: state,
|
|
33636
|
+
dispatch: dispatch
|
|
33637
|
+
}),
|
|
33638
|
+
interactiveElementsDescription: null
|
|
33639
|
+
};
|
|
33640
|
+
}
|
|
33641
|
+
function CircleGraph(props) {
|
|
33642
|
+
const {
|
|
33643
|
+
dispatch,
|
|
33644
|
+
graphState
|
|
33645
|
+
} = props;
|
|
33646
|
+
const {
|
|
33647
|
+
center,
|
|
33648
|
+
radiusPoint
|
|
33649
|
+
} = graphState;
|
|
33650
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableCircle, {
|
|
33651
|
+
center: center,
|
|
33652
|
+
radius: getRadius(graphState),
|
|
33653
|
+
onMove: c => dispatch(actions.circle.moveCenter(c))
|
|
33654
|
+
}), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
33655
|
+
point: radiusPoint,
|
|
33656
|
+
cursor: "ew-resize",
|
|
33657
|
+
onMove: newRadiusPoint => {
|
|
33658
|
+
dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
|
|
32971
33659
|
}
|
|
32972
|
-
}))
|
|
32973
|
-
}
|
|
33660
|
+
}));
|
|
33661
|
+
}
|
|
33662
|
+
function MovableCircle(props) {
|
|
33663
|
+
const {
|
|
33664
|
+
center,
|
|
33665
|
+
radius,
|
|
33666
|
+
onMove
|
|
33667
|
+
} = props;
|
|
33668
|
+
const {
|
|
33669
|
+
snapStep,
|
|
33670
|
+
disableKeyboardInteraction
|
|
33671
|
+
} = useGraphConfig();
|
|
33672
|
+
const draggableRef = React.useRef(null);
|
|
33673
|
+
const {
|
|
33674
|
+
dragging
|
|
33675
|
+
} = useDraggable({
|
|
33676
|
+
gestureTarget: draggableRef,
|
|
33677
|
+
point: center,
|
|
33678
|
+
onMove,
|
|
33679
|
+
constrainKeyboardMovement: p => snap(snapStep, p)
|
|
33680
|
+
});
|
|
33681
|
+
const [centerPx] = useTransformVectorsToPixels(center);
|
|
33682
|
+
const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
|
|
33683
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
33684
|
+
ref: draggableRef,
|
|
33685
|
+
tabIndex: disableKeyboardInteraction ? -1 : 0,
|
|
33686
|
+
className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
|
|
33687
|
+
}, /*#__PURE__*/React__namespace.createElement("ellipse", {
|
|
33688
|
+
className: "focus-ring",
|
|
33689
|
+
cx: centerPx[X],
|
|
33690
|
+
cy: centerPx[Y],
|
|
33691
|
+
rx: radiiPx[X] + 3,
|
|
33692
|
+
ry: radiiPx[Y] + 3
|
|
33693
|
+
}), /*#__PURE__*/React__namespace.createElement("ellipse", {
|
|
33694
|
+
className: "circle",
|
|
33695
|
+
cx: centerPx[X],
|
|
33696
|
+
cy: centerPx[Y],
|
|
33697
|
+
rx: radiiPx[X],
|
|
33698
|
+
ry: radiiPx[Y]
|
|
33699
|
+
}), /*#__PURE__*/React__namespace.createElement(DragHandle, {
|
|
33700
|
+
center: center
|
|
33701
|
+
}));
|
|
33702
|
+
}
|
|
33703
|
+
const dragHandleDimensions = [24, 14];
|
|
33704
|
+
const dragHandleDotPositions = crossProduct([-4.4, 0, 4.4], [-2.1, 2.1]);
|
|
33705
|
+
function DragHandle(props) {
|
|
33706
|
+
const {
|
|
33707
|
+
center
|
|
33708
|
+
} = props;
|
|
33709
|
+
const cornerRadius = Math.min(...dragHandleDimensions) / 2;
|
|
33710
|
+
const [centerPx] = useTransformVectorsToPixels(center);
|
|
33711
|
+
const topLeft = mafs.vec.sub(centerPx, mafs.vec.scale(dragHandleDimensions, 0.5));
|
|
33712
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
|
|
33713
|
+
className: "movable-circle-handle",
|
|
33714
|
+
x: topLeft[X],
|
|
33715
|
+
y: topLeft[Y],
|
|
33716
|
+
width: dragHandleDimensions[X],
|
|
33717
|
+
height: dragHandleDimensions[Y],
|
|
33718
|
+
rx: cornerRadius,
|
|
33719
|
+
ry: cornerRadius
|
|
33720
|
+
}), dragHandleDotPositions.map(offsetPx => {
|
|
33721
|
+
const [xPx, yPx] = mafs.vec.add(offsetPx, centerPx);
|
|
33722
|
+
return /*#__PURE__*/React__namespace.createElement("circle", {
|
|
33723
|
+
key: `circle-${xPx}-${yPx}`,
|
|
33724
|
+
className: "movable-circle-handle-dot",
|
|
33725
|
+
cx: xPx,
|
|
33726
|
+
cy: yPx
|
|
33727
|
+
});
|
|
33728
|
+
}));
|
|
33729
|
+
}
|
|
33730
|
+
function crossProduct(as, bs) {
|
|
33731
|
+
const result = [];
|
|
33732
|
+
for (const a of as) {
|
|
33733
|
+
for (const b of bs) {
|
|
33734
|
+
result.push([a, b]);
|
|
33735
|
+
}
|
|
33736
|
+
}
|
|
33737
|
+
return result;
|
|
33738
|
+
}
|
|
32974
33739
|
|
|
33740
|
+
function renderLinearGraph(state, dispatch) {
|
|
33741
|
+
return {
|
|
33742
|
+
graph: /*#__PURE__*/React__namespace.createElement(LinearGraph, {
|
|
33743
|
+
graphState: state,
|
|
33744
|
+
dispatch: dispatch
|
|
33745
|
+
}),
|
|
33746
|
+
interactiveElementsDescription: null
|
|
33747
|
+
};
|
|
33748
|
+
}
|
|
32975
33749
|
const LinearGraph = (props, key) => {
|
|
32976
33750
|
const {
|
|
32977
33751
|
dispatch
|
|
@@ -32998,6 +33772,15 @@ const LinearGraph = (props, key) => {
|
|
|
32998
33772
|
});
|
|
32999
33773
|
};
|
|
33000
33774
|
|
|
33775
|
+
function renderLinearSystemGraph(state, dispatch) {
|
|
33776
|
+
return {
|
|
33777
|
+
graph: /*#__PURE__*/React__namespace.createElement(LinearSystemGraph, {
|
|
33778
|
+
graphState: state,
|
|
33779
|
+
dispatch: dispatch
|
|
33780
|
+
}),
|
|
33781
|
+
interactiveElementsDescription: null
|
|
33782
|
+
};
|
|
33783
|
+
}
|
|
33001
33784
|
const LinearSystemGraph = props => {
|
|
33002
33785
|
const {
|
|
33003
33786
|
dispatch
|
|
@@ -33020,368 +33803,124 @@ const LinearSystemGraph = props => {
|
|
|
33020
33803
|
})), ";");
|
|
33021
33804
|
};
|
|
33022
33805
|
|
|
33023
|
-
|
|
33806
|
+
function renderPointGraph(state, dispatch) {
|
|
33807
|
+
return {
|
|
33808
|
+
graph: /*#__PURE__*/React__namespace.createElement(PointGraph, {
|
|
33809
|
+
graphState: state,
|
|
33810
|
+
dispatch: dispatch
|
|
33811
|
+
}),
|
|
33812
|
+
interactiveElementsDescription: /*#__PURE__*/React__namespace.createElement(PointGraphDescription, {
|
|
33813
|
+
state: state
|
|
33814
|
+
})
|
|
33815
|
+
};
|
|
33816
|
+
}
|
|
33817
|
+
function PointGraph(props) {
|
|
33818
|
+
const numPoints = props.graphState.numPoints;
|
|
33819
|
+
if (numPoints === "unlimited") {
|
|
33820
|
+
return UnlimitedPointGraph(props);
|
|
33821
|
+
}
|
|
33822
|
+
return LimitedPointGraph(props);
|
|
33823
|
+
}
|
|
33824
|
+
function LimitedPointGraph(props) {
|
|
33024
33825
|
const {
|
|
33025
33826
|
dispatch
|
|
33026
33827
|
} = props;
|
|
33828
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
33829
|
+
key: i,
|
|
33830
|
+
point: point,
|
|
33831
|
+
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
|
|
33832
|
+
})));
|
|
33833
|
+
}
|
|
33834
|
+
function UnlimitedPointGraph(props) {
|
|
33027
33835
|
const {
|
|
33028
|
-
|
|
33029
|
-
} = props
|
|
33030
|
-
const
|
|
33031
|
-
const
|
|
33032
|
-
|
|
33033
|
-
|
|
33034
|
-
|
|
33035
|
-
|
|
33036
|
-
|
|
33037
|
-
|
|
33038
|
-
|
|
33039
|
-
|
|
33040
|
-
|
|
33836
|
+
dispatch
|
|
33837
|
+
} = props;
|
|
33838
|
+
const graphConfig = useGraphConfig();
|
|
33839
|
+
const {
|
|
33840
|
+
range: [[minX, maxX], [minY, maxY]]
|
|
33841
|
+
} = graphConfig;
|
|
33842
|
+
const width = maxX - minX;
|
|
33843
|
+
const height = maxY - minY;
|
|
33844
|
+
const [[widthPx, heightPx]] = useTransformDimensionsToPixels([width, height]);
|
|
33845
|
+
const [[left, top]] = useTransformVectorsToPixels([minX, maxY]);
|
|
33846
|
+
const itemsRef = React__namespace.useRef([]);
|
|
33847
|
+
React__namespace.useEffect(() => {
|
|
33848
|
+
const focusedIndex = props.graphState.focusedPointIndex;
|
|
33849
|
+
if (focusedIndex != null) {
|
|
33850
|
+
itemsRef.current[focusedIndex]?.focus();
|
|
33041
33851
|
}
|
|
33042
|
-
});
|
|
33043
|
-
|
|
33044
|
-
|
|
33045
|
-
/**
|
|
33046
|
-
* Wrapper with Mafs transform styles for SVG elements.
|
|
33047
|
-
* Scales and translates the SVG element to match the Mafs view.
|
|
33048
|
-
* Use if you do not need/want to use the hook in `use-transform.ts`.
|
|
33049
|
-
*/
|
|
33050
|
-
const MafsCssTransformWrapper = _ref => {
|
|
33051
|
-
let {
|
|
33052
|
-
children
|
|
33053
|
-
} = _ref;
|
|
33054
|
-
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
33852
|
+
}, [props.graphState.focusedPointIndex, itemsRef]);
|
|
33853
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
|
|
33055
33854
|
style: {
|
|
33056
|
-
|
|
33855
|
+
fill: "rgba(0,0,0,0)",
|
|
33856
|
+
cursor: "crosshair"
|
|
33857
|
+
},
|
|
33858
|
+
width: widthPx,
|
|
33859
|
+
height: heightPx,
|
|
33860
|
+
x: left,
|
|
33861
|
+
y: top,
|
|
33862
|
+
onClick: event => {
|
|
33863
|
+
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
33864
|
+
const x = event.clientX - elementRect.x;
|
|
33865
|
+
const y = event.clientY - elementRect.y;
|
|
33866
|
+
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
33867
|
+
dispatch(actions.pointGraph.addPoint(graphCoordinates[0]));
|
|
33057
33868
|
}
|
|
33058
|
-
},
|
|
33059
|
-
|
|
33060
|
-
|
|
33061
|
-
|
|
33062
|
-
|
|
33063
|
-
|
|
33064
|
-
|
|
33065
|
-
|
|
33066
|
-
|
|
33067
|
-
|
|
33068
|
-
|
|
33069
|
-
|
|
33070
|
-
fontWeight: "bold"
|
|
33869
|
+
}), props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
33870
|
+
key: i,
|
|
33871
|
+
point: point,
|
|
33872
|
+
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
|
|
33873
|
+
ref: ref => {
|
|
33874
|
+
itemsRef.current[i] = ref;
|
|
33875
|
+
},
|
|
33876
|
+
onFocus: () => {
|
|
33877
|
+
dispatch(actions.pointGraph.focusPoint(i));
|
|
33878
|
+
},
|
|
33879
|
+
onClick: () => {
|
|
33880
|
+
dispatch(actions.pointGraph.clickPoint(i));
|
|
33071
33881
|
}
|
|
33072
|
-
}
|
|
33073
|
-
}
|
|
33074
|
-
|
|
33075
|
-
id: "background",
|
|
33076
|
-
x: "-5%",
|
|
33077
|
-
width: "110%",
|
|
33078
|
-
y: "0%",
|
|
33079
|
-
height: "100%"
|
|
33080
|
-
}, /*#__PURE__*/React__default["default"].createElement("feFlood", {
|
|
33081
|
-
floodColor: "#FFF",
|
|
33082
|
-
floodOpacity: "0.64"
|
|
33083
|
-
}), /*#__PURE__*/React__default["default"].createElement("feComposite", {
|
|
33084
|
-
operator: "over",
|
|
33085
|
-
in: "SourceGraphic"
|
|
33086
|
-
})));
|
|
33087
|
-
|
|
33088
|
-
const PolygonAngle = _ref => {
|
|
33882
|
+
})));
|
|
33883
|
+
}
|
|
33884
|
+
function PointGraphDescription(_ref) {
|
|
33089
33885
|
let {
|
|
33090
|
-
|
|
33091
|
-
endPoints,
|
|
33092
|
-
range,
|
|
33093
|
-
polygonLines,
|
|
33094
|
-
showAngles,
|
|
33095
|
-
snapTo
|
|
33886
|
+
state
|
|
33096
33887
|
} = _ref;
|
|
33097
|
-
|
|
33098
|
-
|
|
33099
|
-
const
|
|
33100
|
-
|
|
33101
|
-
const a = mafs.vec.dist(centerPoint, endPoints[0]);
|
|
33102
|
-
const b = mafs.vec.dist(centerPoint, endPoints[1]);
|
|
33103
|
-
const c = mafs.vec.dist(endPoints[0], endPoints[1]);
|
|
33104
|
-
|
|
33105
|
-
// Law of cosines
|
|
33106
|
-
const angle = Math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b));
|
|
33107
|
-
const y1 = centerY + (startY - centerY) / a * radius;
|
|
33108
|
-
const x2 = centerX + (endX - centerX) / b * radius;
|
|
33109
|
-
const x1 = centerX + (startX - centerX) / a * radius;
|
|
33110
|
-
const y2 = centerY + (endY - centerY) / b * radius;
|
|
33111
|
-
|
|
33112
|
-
// Fourth point that would create a rhomboid
|
|
33113
|
-
// Used to calculate the inside of the angle and to create square
|
|
33114
|
-
const [x3, y3] = mafs.vec.add(centerPoint, mafs.vec.add(mafs.vec.sub([x1, y1], centerPoint), mafs.vec.sub([x2, y2], centerPoint)));
|
|
33115
|
-
if (!showAngles) {
|
|
33116
|
-
return isRightPolygonAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
|
|
33117
|
-
start: [x1, y1],
|
|
33118
|
-
vertex: [x2, y2],
|
|
33119
|
-
end: [x3, y3]
|
|
33120
|
-
}) : null;
|
|
33121
|
-
}
|
|
33122
|
-
|
|
33123
|
-
// Midpoint betwen ends of arc
|
|
33124
|
-
const isOutside = shouldDrawArcOutside([x3, y3], centerPoint, range, polygonLines);
|
|
33125
|
-
const largeArcFlag = isOutside ? 1 : 0;
|
|
33126
|
-
const sweepFlag = isOutside ? 1 : 0;
|
|
33127
|
-
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
33128
|
-
let angleInDegrees = angle * (180 / Math.PI);
|
|
33129
|
-
// If we have triggered "largArcFlag", the angle should be greater than 180
|
|
33130
|
-
if (isOutside) {
|
|
33131
|
-
angleInDegrees = 360 - angleInDegrees;
|
|
33132
|
-
}
|
|
33133
|
-
|
|
33134
|
-
// Only want to show whole numbers when in angles mode
|
|
33135
|
-
const angleLabelNumber = parseFloat(angleInDegrees.toFixed(snapTo === "angles" ? 0 : 1));
|
|
33136
|
-
const angleLabel = Number.isInteger(angleLabelNumber) ? angleLabelNumber : "≈ " + angleLabelNumber;
|
|
33137
|
-
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("filter", {
|
|
33138
|
-
id: "background",
|
|
33139
|
-
x: "-5%",
|
|
33140
|
-
width: "110%",
|
|
33141
|
-
y: "0%",
|
|
33142
|
-
height: "100%"
|
|
33143
|
-
}, /*#__PURE__*/React__namespace.createElement("feFlood", {
|
|
33144
|
-
floodColor: "#FFF",
|
|
33145
|
-
floodOpacity: "0.5"
|
|
33146
|
-
}), /*#__PURE__*/React__namespace.createElement("feComposite", {
|
|
33147
|
-
operator: "over",
|
|
33148
|
-
in: "SourceGraphic"
|
|
33149
|
-
}))), !isOutside && isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
|
|
33150
|
-
start: [x1, y1],
|
|
33151
|
-
vertex: [x2, y2],
|
|
33152
|
-
end: [x3, y3]
|
|
33153
|
-
}) : /*#__PURE__*/React__namespace.createElement(Arc, {
|
|
33154
|
-
arc: arc
|
|
33155
|
-
}), /*#__PURE__*/React__namespace.createElement(TextLabel, {
|
|
33156
|
-
x: x3,
|
|
33157
|
-
y: y3,
|
|
33158
|
-
attach: y3 - centerY > 0 ? "s" : "n",
|
|
33159
|
-
attachDistance: Math.abs(y3 - centerY) < 0.2 || mafs.vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
|
|
33160
|
-
}, angleLabel, "\xB0"));
|
|
33161
|
-
};
|
|
33162
|
-
const Angle = _ref2 => {
|
|
33163
|
-
let {
|
|
33164
|
-
vertex,
|
|
33165
|
-
coords,
|
|
33166
|
-
showAngles,
|
|
33167
|
-
allowReflexAngles,
|
|
33168
|
-
range
|
|
33169
|
-
} = _ref2;
|
|
33170
|
-
// Check if the points are clockwise or not, depending on whether we allow reflex angles
|
|
33171
|
-
const areClockwise = clockwise$1([...coords, vertex]);
|
|
33172
|
-
const shouldReverseCoords = areClockwise && !allowReflexAngles;
|
|
33173
|
-
|
|
33174
|
-
// Reverse the coordinates accordingly to ensure the angle is calculated correctly
|
|
33175
|
-
const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
|
|
33176
|
-
|
|
33177
|
-
// Calculate the angles between the two points
|
|
33178
|
-
const startAngle = getAngleFromVertex(clockwiseCoords[0], vertex);
|
|
33179
|
-
const endAngle = getAngleFromVertex(clockwiseCoords[1], vertex);
|
|
33180
|
-
const angle = (startAngle + 360 - endAngle) % 360;
|
|
33181
|
-
|
|
33182
|
-
// Check if the angle is reflexive
|
|
33183
|
-
const isReflexive = angle > 180;
|
|
33184
|
-
|
|
33185
|
-
// Break out the necessary points for the arc calculation
|
|
33186
|
-
const [centerX, centerY] = vertex;
|
|
33187
|
-
const [point1, point2] = clockwiseCoords;
|
|
33188
|
-
const [startX, startY] = point1;
|
|
33189
|
-
const [endX, endY] = point2;
|
|
33190
|
-
const a = mafs.vec.dist(vertex, point1);
|
|
33191
|
-
const b = mafs.vec.dist(vertex, point2);
|
|
33192
|
-
|
|
33193
|
-
// Set the radius of the arc
|
|
33194
|
-
const radius = 2;
|
|
33195
|
-
|
|
33196
|
-
// Calculate the end points of the arc
|
|
33197
|
-
const y1 = centerY + (startY - centerY) / a * radius;
|
|
33198
|
-
const x2 = centerX + (endX - centerX) / b * radius;
|
|
33199
|
-
const x1 = centerX + (startX - centerX) / a * radius;
|
|
33200
|
-
const y2 = centerY + (endY - centerY) / b * radius;
|
|
33201
|
-
|
|
33202
|
-
// Used to calculate the inside of the angle and to create square
|
|
33203
|
-
const [x3, y3] = mafs.vec.add(vertex, mafs.vec.add(mafs.vec.sub([x1, y1], vertex), mafs.vec.sub([x2, y2], vertex)));
|
|
33204
|
-
|
|
33205
|
-
// Determine whether the arc should be drawn outside the angle
|
|
33206
|
-
const isOutside = shouldDrawArcOutside([x3, y3], vertex, range, [[vertex, point1], [vertex, point2]]);
|
|
33207
|
-
|
|
33208
|
-
// Determine the flags for the arc
|
|
33209
|
-
// If the angle is outside OR reflexive, we want to draw a large arc
|
|
33210
|
-
// If the angle is outside AND reflexive, we want to sweep the arc
|
|
33211
|
-
const largeArcFlag = isOutside || isReflexive ? 1 : 0;
|
|
33212
|
-
const sweepFlag = isOutside && isReflexive ? 1 : 0;
|
|
33213
|
-
|
|
33214
|
-
// Create the SVG path for the arc
|
|
33215
|
-
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
33216
|
-
|
|
33217
|
-
// We only ever want to show whole angles
|
|
33218
|
-
const angleLabel = parseFloat(angle.toFixed(0)); // Only want to show whole angles
|
|
33219
|
-
|
|
33220
|
-
// Calculate the text position based on the angle and whether we allow reflex angles
|
|
33221
|
-
// Let's try the angle bisector method to find the midpoint of the arc
|
|
33222
|
-
const [textX, textY] = calculateBisectorPoint(point1, point2, vertex, isReflexive, allowReflexAngles, radius);
|
|
33223
|
-
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("filter", {
|
|
33224
|
-
id: "background",
|
|
33225
|
-
x: "-5%",
|
|
33226
|
-
width: "110%",
|
|
33227
|
-
y: "0%",
|
|
33228
|
-
height: "100%"
|
|
33229
|
-
}, /*#__PURE__*/React__namespace.createElement("feFlood", {
|
|
33230
|
-
floodColor: "#FFF",
|
|
33231
|
-
floodOpacity: "0.5"
|
|
33232
|
-
}), /*#__PURE__*/React__namespace.createElement("feComposite", {
|
|
33233
|
-
operator: "over",
|
|
33234
|
-
in: "SourceGraphic"
|
|
33235
|
-
}))), isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
|
|
33236
|
-
start: [x1, y1],
|
|
33237
|
-
vertex: [x2, y2],
|
|
33238
|
-
end: [x3, y3],
|
|
33239
|
-
className: "arc-right-angle"
|
|
33240
|
-
}) : /*#__PURE__*/React__namespace.createElement(Arc, {
|
|
33241
|
-
arc: arc,
|
|
33242
|
-
className: "angle-arc"
|
|
33243
|
-
}), showAngles && /*#__PURE__*/React__namespace.createElement(TextLabel, {
|
|
33244
|
-
x: textX,
|
|
33245
|
-
y: textY,
|
|
33246
|
-
color: wonderBlocksTokens.color.blue
|
|
33247
|
-
}, angleLabel, "\xB0"));
|
|
33248
|
-
};
|
|
33249
|
-
|
|
33250
|
-
/**
|
|
33251
|
-
* This is broken out into its own component so that it can be used for an early return
|
|
33252
|
-
* (see line 55 or https://github.com/Khan/perseus/blob/84bbd882b11d16871e1b813a0b901f3b903d5479/packages/perseus/src/widgets/interactive-graphs/graphs/components/angle.tsx#L53-L57)
|
|
33253
|
-
*/
|
|
33254
|
-
const RightAngleSquare = _ref3 => {
|
|
33255
|
-
let {
|
|
33256
|
-
start: [x1, y1],
|
|
33257
|
-
vertex: [x2, y2],
|
|
33258
|
-
end: [x3, y3],
|
|
33259
|
-
className
|
|
33260
|
-
} = _ref3;
|
|
33261
|
-
return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
|
|
33262
|
-
d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
|
|
33263
|
-
strokeWidth: 0.02,
|
|
33264
|
-
fill: "none",
|
|
33265
|
-
className: className
|
|
33266
|
-
}));
|
|
33267
|
-
};
|
|
33268
|
-
|
|
33269
|
-
// We're conditionally adding the class name here so that we can style the arc differently
|
|
33270
|
-
// based on whether it's an angle or a polygon angle
|
|
33271
|
-
const Arc = _ref4 => {
|
|
33272
|
-
let {
|
|
33273
|
-
arc,
|
|
33274
|
-
className
|
|
33275
|
-
} = _ref4;
|
|
33276
|
-
return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
|
|
33277
|
-
d: arc,
|
|
33278
|
-
strokeWidth: 0.02,
|
|
33279
|
-
fill: "none",
|
|
33280
|
-
className: className
|
|
33281
|
-
}));
|
|
33282
|
-
};
|
|
33283
|
-
const isRightPolygonAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
|
|
33284
|
-
const isRightAngle = angle => Math.round(angle) === 90;
|
|
33285
|
-
|
|
33286
|
-
/**
|
|
33287
|
-
* Determines if an angle is an inside (false) or outside (true) angle.
|
|
33288
|
-
* They way, we know to flip the `largeArc` and `sweepArc` flags.
|
|
33289
|
-
* Uses the priciple that a ray from a point inside a polygon will intersect
|
|
33290
|
-
* with an odd number of lines, while a ray from a point outside the polygon
|
|
33291
|
-
* will intersect with an even number of lines.
|
|
33292
|
-
* https://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon
|
|
33293
|
-
*/
|
|
33294
|
-
const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
|
|
33295
|
-
// Create a ray from the midpoint (inside angle) to the edge of the range
|
|
33296
|
-
const rangeIntersectionPoint = getIntersectionOfRayWithBox(midpoint, vertex, range);
|
|
33297
|
-
let lineIntersectionCount = 0;
|
|
33298
|
-
polygonLines.forEach(line => segmentsIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
|
|
33299
|
-
|
|
33300
|
-
// If the number of intersections is even, the angle is inside the polygon
|
|
33301
|
-
return !isEven(lineIntersectionCount);
|
|
33302
|
-
};
|
|
33303
|
-
const isEven = n => n % 2 === 0;
|
|
33304
|
-
|
|
33305
|
-
// This function calculates the bisector point of an angle formed by two points
|
|
33306
|
-
// and a vertex. It is used to position the angle label so that it is always
|
|
33307
|
-
// outside the arc at the exact midpoint of the angle.
|
|
33308
|
-
// Reflex angles make use of polar coordinates to determine the correct direction
|
|
33309
|
-
function calculateBisectorPoint(point1, point2, vertex, isReflex, allowReflex, arcRadius) {
|
|
33310
|
-
const [originX, originY] = vertex;
|
|
33311
|
-
const [x1, y1] = point1;
|
|
33312
|
-
const [x2, y2] = point2;
|
|
33313
|
-
|
|
33314
|
-
// Convert the Cartesian Coordinates to vectors
|
|
33315
|
-
const vectorA = [x1 - originX, y1 - originY];
|
|
33316
|
-
const vectorB = [x2 - originX, y2 - originY];
|
|
33317
|
-
|
|
33318
|
-
// Calculate the average angle of the vectors
|
|
33319
|
-
// so that we can point the bisector in the correct direction using polar coordinates
|
|
33320
|
-
const angleA = Math.atan2(vectorA[1], vectorA[0]);
|
|
33321
|
-
const angleB = Math.atan2(vectorB[1], vectorB[0]);
|
|
33322
|
-
|
|
33323
|
-
// Calculate the average of these two angles
|
|
33324
|
-
let averageAngle = (angleA + angleB) / 2;
|
|
33325
|
-
const angleRadians = Math.abs(angleA - angleB);
|
|
33326
|
-
|
|
33327
|
-
// Adjust the average angle to point in the correct direction
|
|
33328
|
-
// and to account for reflex angles and a bug with 180 degrees
|
|
33329
|
-
// that occurs due to our angleOffsetDegrees option in the graph
|
|
33330
|
-
if (allowReflex) {
|
|
33331
|
-
// If the resulting angles are less than 180 degrees,
|
|
33332
|
-
// or if the user has flipped the points on the interactive element
|
|
33333
|
-
// ensure that we have the correct direction for the reflex angle
|
|
33334
|
-
if (angleRadians <= Math.PI && isReflex || angleB > angleA) {
|
|
33335
|
-
averageAngle += Math.PI; // Add 180 degrees
|
|
33336
|
-
}
|
|
33337
|
-
} else {
|
|
33338
|
-
// If the resulting angles are greater than 180 degrees, adjust
|
|
33339
|
-
// to get the correct angle, as we already know that the angle is not reflexive
|
|
33340
|
-
// This fixes a bug that occurs at 180 degrees due to our angleOffsetDegrees option in the graph
|
|
33341
|
-
if (angleRadians > Math.PI) {
|
|
33342
|
-
averageAngle -= Math.PI; // Subract 180 degrees
|
|
33343
|
-
}
|
|
33344
|
-
}
|
|
33345
|
-
|
|
33346
|
-
// Convert the average angle back to Cartesian coordinates
|
|
33347
|
-
const sum = [Math.cos(averageAngle), Math.sin(averageAngle)];
|
|
33348
|
-
|
|
33349
|
-
// Calculate the magnitude of the sum to normalize it
|
|
33350
|
-
const sumMagnitude = Math.sqrt(sum[0] ** 2 + sum[1] ** 2);
|
|
33351
|
-
const bisectorDirection = [sum[0] / sumMagnitude, sum[1] / sumMagnitude];
|
|
33352
|
-
|
|
33353
|
-
// Calculate the initial distance of the bisector direction from the origin
|
|
33354
|
-
const initialDistance = Math.sqrt(bisectorDirection[0] ** 2 + bisectorDirection[1] ** 2);
|
|
33355
|
-
|
|
33356
|
-
// Determine the minimum radius to ensure that the text is always outside the arc
|
|
33357
|
-
const requiredDistance = arcRadius * 1.75;
|
|
33358
|
-
let radius = requiredDistance / initialDistance;
|
|
33359
|
-
|
|
33360
|
-
// Apply radius only if the initial distance is less than required
|
|
33361
|
-
if (initialDistance >= requiredDistance) {
|
|
33362
|
-
radius = 1; // Use the unit vector as is
|
|
33363
|
-
}
|
|
33364
|
-
|
|
33365
|
-
// Scale the bisector direction by the radius
|
|
33366
|
-
const bisectorPoint = [bisectorDirection[0] * radius, bisectorDirection[1] * radius];
|
|
33367
|
-
|
|
33368
|
-
// Add the vertex to the bisector point to get the final position
|
|
33369
|
-
// to ensure that the angle label moves with the interactive element
|
|
33370
|
-
const scaledBisector = mafs.vec.add(bisectorPoint, vertex);
|
|
33371
|
-
return scaledBisector;
|
|
33888
|
+
// PointGraphDescription needs to `usePerseusI18n`, so it has to be a
|
|
33889
|
+
// component rather than a function that simply returns a string.
|
|
33890
|
+
const i18n = usePerseusI18n();
|
|
33891
|
+
return describePointGraph(state, i18n);
|
|
33372
33892
|
}
|
|
33373
33893
|
|
|
33374
|
-
|
|
33894
|
+
// Exported for testing
|
|
33895
|
+
function describePointGraph(state, i18n) {
|
|
33375
33896
|
const {
|
|
33376
|
-
|
|
33377
|
-
|
|
33378
|
-
} =
|
|
33379
|
-
|
|
33380
|
-
|
|
33897
|
+
strings,
|
|
33898
|
+
locale
|
|
33899
|
+
} = i18n;
|
|
33900
|
+
if (state.coords.length === 0) {
|
|
33901
|
+
return strings.srNoInteractiveElements;
|
|
33902
|
+
}
|
|
33903
|
+
const pointDescriptions = state.coords.map(_ref2 => {
|
|
33904
|
+
let [x, y] = _ref2;
|
|
33905
|
+
return strings.srPointAtCoordinates({
|
|
33906
|
+
x: srFormatNumber(x, locale),
|
|
33907
|
+
y: srFormatNumber(y, locale)
|
|
33908
|
+
});
|
|
33381
33909
|
});
|
|
33382
|
-
return
|
|
33383
|
-
|
|
33910
|
+
return strings.srInteractiveElements({
|
|
33911
|
+
elements: pointDescriptions.join(", ")
|
|
33912
|
+
});
|
|
33913
|
+
}
|
|
33384
33914
|
|
|
33915
|
+
function renderPolygonGraph(state, dispatch) {
|
|
33916
|
+
return {
|
|
33917
|
+
graph: /*#__PURE__*/React__namespace.createElement(PolygonGraph, {
|
|
33918
|
+
graphState: state,
|
|
33919
|
+
dispatch: dispatch
|
|
33920
|
+
}),
|
|
33921
|
+
interactiveElementsDescription: null
|
|
33922
|
+
};
|
|
33923
|
+
}
|
|
33385
33924
|
const LimitedPolygonGraph = props => {
|
|
33386
33925
|
const [hovered, setHovered] = React__namespace.useState(false);
|
|
33387
33926
|
// This is more so required for the re-rendering that occurs when state
|
|
@@ -33663,201 +34202,15 @@ const PolygonGraph = props => {
|
|
|
33663
34202
|
return numSides === "unlimited" ? UnlimitedPolygonGraph(props) : LimitedPolygonGraph(props);
|
|
33664
34203
|
};
|
|
33665
34204
|
|
|
33666
|
-
function
|
|
33667
|
-
|
|
33668
|
-
|
|
33669
|
-
|
|
33670
|
-
|
|
33671
|
-
|
|
33672
|
-
|
|
33673
|
-
|
|
33674
|
-
...initialGraph,
|
|
33675
|
-
coords: state.coords
|
|
33676
|
-
};
|
|
33677
|
-
}
|
|
33678
|
-
if (state.type === "segment" && initialGraph.type === "segment") {
|
|
33679
|
-
return {
|
|
33680
|
-
...initialGraph,
|
|
33681
|
-
coords: state.coords
|
|
33682
|
-
};
|
|
33683
|
-
}
|
|
33684
|
-
if (state.type === "linear" && initialGraph.type === "linear") {
|
|
33685
|
-
return {
|
|
33686
|
-
...initialGraph,
|
|
33687
|
-
coords: state.coords
|
|
33688
|
-
};
|
|
33689
|
-
}
|
|
33690
|
-
if (state.type === "ray" && initialGraph.type === "ray") {
|
|
33691
|
-
return {
|
|
33692
|
-
...initialGraph,
|
|
33693
|
-
coords: state.coords
|
|
33694
|
-
};
|
|
33695
|
-
}
|
|
33696
|
-
if (state.type === "polygon" && initialGraph.type === "polygon") {
|
|
33697
|
-
return {
|
|
33698
|
-
...initialGraph,
|
|
33699
|
-
coords: state.coords
|
|
33700
|
-
};
|
|
33701
|
-
}
|
|
33702
|
-
if (state.type === "point" && initialGraph.type === "point") {
|
|
33703
|
-
return {
|
|
33704
|
-
...initialGraph,
|
|
33705
|
-
coords: state.coords
|
|
33706
|
-
};
|
|
33707
|
-
}
|
|
33708
|
-
if (state.type === "circle" && initialGraph.type === "circle") {
|
|
33709
|
-
return {
|
|
33710
|
-
...initialGraph,
|
|
33711
|
-
center: state.center,
|
|
33712
|
-
radius: getRadius(state)
|
|
33713
|
-
};
|
|
33714
|
-
}
|
|
33715
|
-
if (state.type === "quadratic" && initialGraph.type === "quadratic") {
|
|
33716
|
-
return {
|
|
33717
|
-
...initialGraph,
|
|
33718
|
-
coords: state.coords
|
|
33719
|
-
};
|
|
33720
|
-
}
|
|
33721
|
-
if (state.type === "sinusoid" && initialGraph.type === "sinusoid") {
|
|
33722
|
-
return {
|
|
33723
|
-
...initialGraph,
|
|
33724
|
-
coords: state.coords
|
|
33725
|
-
};
|
|
33726
|
-
}
|
|
33727
|
-
if (state.type === "angle" && initialGraph.type === "angle") {
|
|
33728
|
-
// We're going to reverse the coords for scoring if the angle is clockwise and
|
|
33729
|
-
// we don't allow reflex angles. This is because the angle is largely defined
|
|
33730
|
-
// by the order of the points in the coords array, and we want to maintain
|
|
33731
|
-
// the same angle scoring with the legacy graph (which had a bug).
|
|
33732
|
-
// (LEMS-2190): When we remove the legacy graph, move this logic to the scoring function.
|
|
33733
|
-
const areClockwise = clockwise$1([state.coords[0], state.coords[2], state.coords[1]]);
|
|
33734
|
-
const shouldReverseCoords = areClockwise && !state.allowReflexAngles;
|
|
33735
|
-
const coords = shouldReverseCoords ? state.coords.slice().reverse() : state.coords;
|
|
33736
|
-
return {
|
|
33737
|
-
...initialGraph,
|
|
33738
|
-
coords,
|
|
33739
|
-
allowReflexAngles: state.allowReflexAngles
|
|
33740
|
-
};
|
|
33741
|
-
}
|
|
33742
|
-
if (state.type === "none" && initialGraph.type === "none") {
|
|
33743
|
-
return {
|
|
33744
|
-
type: "none"
|
|
33745
|
-
};
|
|
33746
|
-
}
|
|
33747
|
-
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
33748
|
-
}
|
|
33749
|
-
|
|
33750
|
-
/**
|
|
33751
|
-
* determine radius of a circle graph
|
|
33752
|
-
*
|
|
33753
|
-
* @param graph - the graph object containing the circle
|
|
33754
|
-
* @returns the radius of the circle
|
|
33755
|
-
*/
|
|
33756
|
-
function getRadius(graph) {
|
|
33757
|
-
const [centerX, centerY] = graph.center;
|
|
33758
|
-
const [edgeX, edgeY] = graph.radiusPoint;
|
|
33759
|
-
return Math.sqrt(Math.pow(edgeX - centerX, 2) + Math.pow(edgeY - centerY, 2));
|
|
33760
|
-
}
|
|
33761
|
-
|
|
33762
|
-
function CircleGraph(props) {
|
|
33763
|
-
const {
|
|
33764
|
-
dispatch,
|
|
33765
|
-
graphState
|
|
33766
|
-
} = props;
|
|
33767
|
-
const {
|
|
33768
|
-
center,
|
|
33769
|
-
radiusPoint
|
|
33770
|
-
} = graphState;
|
|
33771
|
-
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(MovableCircle, {
|
|
33772
|
-
center: center,
|
|
33773
|
-
radius: getRadius(graphState),
|
|
33774
|
-
onMove: c => dispatch(actions.circle.moveCenter(c))
|
|
33775
|
-
}), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
33776
|
-
point: radiusPoint,
|
|
33777
|
-
cursor: "ew-resize",
|
|
33778
|
-
onMove: newRadiusPoint => {
|
|
33779
|
-
dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
|
|
33780
|
-
}
|
|
33781
|
-
}));
|
|
33782
|
-
}
|
|
33783
|
-
function MovableCircle(props) {
|
|
33784
|
-
const {
|
|
33785
|
-
center,
|
|
33786
|
-
radius,
|
|
33787
|
-
onMove
|
|
33788
|
-
} = props;
|
|
33789
|
-
const {
|
|
33790
|
-
snapStep,
|
|
33791
|
-
disableKeyboardInteraction
|
|
33792
|
-
} = useGraphConfig();
|
|
33793
|
-
const draggableRef = React.useRef(null);
|
|
33794
|
-
const {
|
|
33795
|
-
dragging
|
|
33796
|
-
} = useDraggable({
|
|
33797
|
-
gestureTarget: draggableRef,
|
|
33798
|
-
point: center,
|
|
33799
|
-
onMove,
|
|
33800
|
-
constrainKeyboardMovement: p => snap(snapStep, p)
|
|
33801
|
-
});
|
|
33802
|
-
const [centerPx] = useTransformVectorsToPixels(center);
|
|
33803
|
-
const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
|
|
33804
|
-
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
33805
|
-
ref: draggableRef,
|
|
33806
|
-
tabIndex: disableKeyboardInteraction ? -1 : 0,
|
|
33807
|
-
className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
|
|
33808
|
-
}, /*#__PURE__*/React__namespace.createElement("ellipse", {
|
|
33809
|
-
className: "focus-ring",
|
|
33810
|
-
cx: centerPx[X],
|
|
33811
|
-
cy: centerPx[Y],
|
|
33812
|
-
rx: radiiPx[X] + 3,
|
|
33813
|
-
ry: radiiPx[Y] + 3
|
|
33814
|
-
}), /*#__PURE__*/React__namespace.createElement("ellipse", {
|
|
33815
|
-
className: "circle",
|
|
33816
|
-
cx: centerPx[X],
|
|
33817
|
-
cy: centerPx[Y],
|
|
33818
|
-
rx: radiiPx[X],
|
|
33819
|
-
ry: radiiPx[Y]
|
|
33820
|
-
}), /*#__PURE__*/React__namespace.createElement(DragHandle, {
|
|
33821
|
-
center: center
|
|
33822
|
-
}));
|
|
33823
|
-
}
|
|
33824
|
-
const dragHandleDimensions = [24, 14];
|
|
33825
|
-
const dragHandleDotPositions = crossProduct([-4.4, 0, 4.4], [-2.1, 2.1]);
|
|
33826
|
-
function DragHandle(props) {
|
|
33827
|
-
const {
|
|
33828
|
-
center
|
|
33829
|
-
} = props;
|
|
33830
|
-
const cornerRadius = Math.min(...dragHandleDimensions) / 2;
|
|
33831
|
-
const [centerPx] = useTransformVectorsToPixels(center);
|
|
33832
|
-
const topLeft = mafs.vec.sub(centerPx, mafs.vec.scale(dragHandleDimensions, 0.5));
|
|
33833
|
-
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
|
|
33834
|
-
className: "movable-circle-handle",
|
|
33835
|
-
x: topLeft[X],
|
|
33836
|
-
y: topLeft[Y],
|
|
33837
|
-
width: dragHandleDimensions[X],
|
|
33838
|
-
height: dragHandleDimensions[Y],
|
|
33839
|
-
rx: cornerRadius,
|
|
33840
|
-
ry: cornerRadius
|
|
33841
|
-
}), dragHandleDotPositions.map(offsetPx => {
|
|
33842
|
-
const [xPx, yPx] = mafs.vec.add(offsetPx, centerPx);
|
|
33843
|
-
return /*#__PURE__*/React__namespace.createElement("circle", {
|
|
33844
|
-
key: `circle-${xPx}-${yPx}`,
|
|
33845
|
-
className: "movable-circle-handle-dot",
|
|
33846
|
-
cx: xPx,
|
|
33847
|
-
cy: yPx
|
|
33848
|
-
});
|
|
33849
|
-
}));
|
|
33850
|
-
}
|
|
33851
|
-
function crossProduct(as, bs) {
|
|
33852
|
-
const result = [];
|
|
33853
|
-
for (const a of as) {
|
|
33854
|
-
for (const b of bs) {
|
|
33855
|
-
result.push([a, b]);
|
|
33856
|
-
}
|
|
33857
|
-
}
|
|
33858
|
-
return result;
|
|
34205
|
+
function renderQuadraticGraph(state, dispatch) {
|
|
34206
|
+
return {
|
|
34207
|
+
graph: /*#__PURE__*/React__namespace.createElement(QuadraticGraph, {
|
|
34208
|
+
graphState: state,
|
|
34209
|
+
dispatch: dispatch
|
|
34210
|
+
}),
|
|
34211
|
+
interactiveElementsDescription: null
|
|
34212
|
+
};
|
|
33859
34213
|
}
|
|
33860
|
-
|
|
33861
34214
|
function QuadraticGraph(props) {
|
|
33862
34215
|
const {
|
|
33863
34216
|
dispatch,
|
|
@@ -33912,6 +34265,74 @@ const getQuadraticCoefficients$1 = coords => {
|
|
|
33912
34265
|
return [a, b, c];
|
|
33913
34266
|
};
|
|
33914
34267
|
|
|
34268
|
+
function renderRayGraph(state, dispatch) {
|
|
34269
|
+
return {
|
|
34270
|
+
graph: /*#__PURE__*/React__namespace.createElement(RayGraph, {
|
|
34271
|
+
graphState: state,
|
|
34272
|
+
dispatch: dispatch
|
|
34273
|
+
}),
|
|
34274
|
+
interactiveElementsDescription: null
|
|
34275
|
+
};
|
|
34276
|
+
}
|
|
34277
|
+
const RayGraph = props => {
|
|
34278
|
+
const {
|
|
34279
|
+
dispatch
|
|
34280
|
+
} = props;
|
|
34281
|
+
const {
|
|
34282
|
+
coords: line
|
|
34283
|
+
} = props.graphState;
|
|
34284
|
+
const handleMoveLine = delta => dispatch(actions.ray.moveRay(delta));
|
|
34285
|
+
const handleMovePoint = (pointIndex, newPoint) => dispatch(actions.ray.movePoint(pointIndex, newPoint));
|
|
34286
|
+
|
|
34287
|
+
// Ray graphs only have one line
|
|
34288
|
+
return /*#__PURE__*/React__namespace.createElement(MovableLine, {
|
|
34289
|
+
points: line,
|
|
34290
|
+
onMoveLine: handleMoveLine,
|
|
34291
|
+
onMovePoint: handleMovePoint,
|
|
34292
|
+
extend: {
|
|
34293
|
+
start: false,
|
|
34294
|
+
end: true
|
|
34295
|
+
}
|
|
34296
|
+
});
|
|
34297
|
+
};
|
|
34298
|
+
|
|
34299
|
+
function renderSegmentGraph(state, dispatch) {
|
|
34300
|
+
return {
|
|
34301
|
+
graph: /*#__PURE__*/React__namespace.createElement(SegmentGraph, {
|
|
34302
|
+
graphState: state,
|
|
34303
|
+
dispatch: dispatch
|
|
34304
|
+
}),
|
|
34305
|
+
interactiveElementsDescription: null
|
|
34306
|
+
};
|
|
34307
|
+
}
|
|
34308
|
+
const SegmentGraph = props => {
|
|
34309
|
+
const {
|
|
34310
|
+
dispatch
|
|
34311
|
+
} = props;
|
|
34312
|
+
const {
|
|
34313
|
+
coords: segments
|
|
34314
|
+
} = props.graphState;
|
|
34315
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, segments?.map((segment, i) => /*#__PURE__*/React__namespace.createElement(MovableLine, {
|
|
34316
|
+
key: i,
|
|
34317
|
+
points: segment,
|
|
34318
|
+
onMoveLine: delta => {
|
|
34319
|
+
dispatch(actions.segment.moveLine(i, delta));
|
|
34320
|
+
},
|
|
34321
|
+
onMovePoint: (endpointIndex, destination) => {
|
|
34322
|
+
dispatch(actions.segment.movePointInFigure(i, endpointIndex, destination));
|
|
34323
|
+
}
|
|
34324
|
+
})));
|
|
34325
|
+
};
|
|
34326
|
+
|
|
34327
|
+
function renderSinusoidGraph(state, dispatch) {
|
|
34328
|
+
return {
|
|
34329
|
+
graph: /*#__PURE__*/React__namespace.createElement(SinusoidGraph, {
|
|
34330
|
+
graphState: state,
|
|
34331
|
+
dispatch: dispatch
|
|
34332
|
+
}),
|
|
34333
|
+
interactiveElementsDescription: null
|
|
34334
|
+
};
|
|
34335
|
+
}
|
|
33915
34336
|
function SinusoidGraph(props) {
|
|
33916
34337
|
const {
|
|
33917
34338
|
dispatch,
|
|
@@ -33987,186 +34408,6 @@ const getSinusoidCoefficients$1 = coords => {
|
|
|
33987
34408
|
};
|
|
33988
34409
|
};
|
|
33989
34410
|
|
|
33990
|
-
function AngleGraph(props) {
|
|
33991
|
-
const {
|
|
33992
|
-
dispatch,
|
|
33993
|
-
graphState
|
|
33994
|
-
} = props;
|
|
33995
|
-
const {
|
|
33996
|
-
graphDimensionsInPixels
|
|
33997
|
-
} = useGraphConfig();
|
|
33998
|
-
const {
|
|
33999
|
-
coords,
|
|
34000
|
-
showAngles,
|
|
34001
|
-
range,
|
|
34002
|
-
allowReflexAngles,
|
|
34003
|
-
snapDegrees
|
|
34004
|
-
} = graphState;
|
|
34005
|
-
|
|
34006
|
-
// Break the coords into the two end points and the center point
|
|
34007
|
-
const endPoints = [coords[0], coords[2]];
|
|
34008
|
-
const centerPoint = coords[1];
|
|
34009
|
-
|
|
34010
|
-
// Convert the vectors to pixels for rendering the svg lines
|
|
34011
|
-
const angleLines = [[centerPoint, endPoints[0]], [centerPoint, endPoints[1]]];
|
|
34012
|
-
const linePixelCoords = [useTransformVectorsToPixels(centerPoint, endPoints[0]), useTransformVectorsToPixels(centerPoint, endPoints[1])];
|
|
34013
|
-
|
|
34014
|
-
// Create the SVG lines for the angle
|
|
34015
|
-
const svgLines = linePixelCoords.map((_ref, i) => {
|
|
34016
|
-
let [startPtPx, endPtPx] = _ref;
|
|
34017
|
-
const trimmedRange = trimRange(range, graphDimensionsInPixels);
|
|
34018
|
-
const endExtend = getIntersectionOfRayWithBox(angleLines[i][0], angleLines[i][1], trimmedRange);
|
|
34019
|
-
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
34020
|
-
key: `line-${i}`
|
|
34021
|
-
}, /*#__PURE__*/React__namespace.createElement(SVGLine, {
|
|
34022
|
-
start: startPtPx,
|
|
34023
|
-
end: endPtPx,
|
|
34024
|
-
style: {
|
|
34025
|
-
stroke: "var(--movable-line-stroke-color)",
|
|
34026
|
-
strokeWidth: "var(--movable-line-stroke-weight)"
|
|
34027
|
-
}
|
|
34028
|
-
}), /*#__PURE__*/React__namespace.createElement(Vector, {
|
|
34029
|
-
tail: angleLines[i][1],
|
|
34030
|
-
tip: endExtend,
|
|
34031
|
-
color: "var(--movable-line-stroke-color)"
|
|
34032
|
-
}));
|
|
34033
|
-
});
|
|
34034
|
-
|
|
34035
|
-
// Create the angle indicator parameters
|
|
34036
|
-
const angleParams = {
|
|
34037
|
-
vertex: centerPoint,
|
|
34038
|
-
coords: endPoints,
|
|
34039
|
-
allowReflexAngles: allowReflexAngles || false,
|
|
34040
|
-
// Whether to allow reflex angles or not
|
|
34041
|
-
snapDegrees: snapDegrees || 1,
|
|
34042
|
-
// The multiple of degrees to snap to
|
|
34043
|
-
range: range,
|
|
34044
|
-
showAngles: showAngles || false // Whether to show the angle or not
|
|
34045
|
-
};
|
|
34046
|
-
|
|
34047
|
-
// Render the lines, angle, and then movable points
|
|
34048
|
-
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, svgLines, /*#__PURE__*/React__namespace.createElement(Angle, angleParams), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
34049
|
-
point: coords[1],
|
|
34050
|
-
constrain: p => p,
|
|
34051
|
-
onMove: destination => dispatch(actions.angle.movePoint(1, destination))
|
|
34052
|
-
}), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
34053
|
-
point: coords[0],
|
|
34054
|
-
constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
|
|
34055
|
-
onMove: destination => dispatch(actions.angle.movePoint(0, destination))
|
|
34056
|
-
}), /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
34057
|
-
point: coords[2],
|
|
34058
|
-
constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
|
|
34059
|
-
onMove: destination => dispatch(actions.angle.movePoint(2, destination))
|
|
34060
|
-
}));
|
|
34061
|
-
}
|
|
34062
|
-
const positiveX = [1, 0];
|
|
34063
|
-
const negativeX = [-1, 0];
|
|
34064
|
-
const positiveY = [0, 1];
|
|
34065
|
-
const negativeY = [0, -1];
|
|
34066
|
-
function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
34067
|
-
const currentAngle = calculateAngleInDegrees(mafs.vec.sub(sidePoint, vertex));
|
|
34068
|
-
|
|
34069
|
-
// Find the rays that start at the current point and point up, down, left
|
|
34070
|
-
// and right.
|
|
34071
|
-
const leftRay = [sidePoint, mafs.vec.add(sidePoint, negativeX)];
|
|
34072
|
-
const rightRay = [sidePoint, mafs.vec.add(sidePoint, positiveX)];
|
|
34073
|
-
const upRay = [sidePoint, mafs.vec.add(sidePoint, positiveY)];
|
|
34074
|
-
const downRay = [sidePoint, mafs.vec.add(sidePoint, negativeY)];
|
|
34075
|
-
|
|
34076
|
-
// find the angles that lie one snap step clockwise and counter-clockwise
|
|
34077
|
-
// from the current angle. These are the angles to which the side can be
|
|
34078
|
-
// moved.
|
|
34079
|
-
const oneStepCounterClockwise = currentAngle + snapDegrees;
|
|
34080
|
-
const oneStepClockwise = currentAngle - snapDegrees;
|
|
34081
|
-
|
|
34082
|
-
// find the rays that start from the vertex and point in the direction of
|
|
34083
|
-
// the angles we just computed.
|
|
34084
|
-
const counterClockwiseRay = [vertex, mafs.vec.add(vertex, polar(1, oneStepCounterClockwise))];
|
|
34085
|
-
const clockwiseRay = [vertex, mafs.vec.add(vertex, polar(1, oneStepClockwise))];
|
|
34086
|
-
|
|
34087
|
-
// find the intersections of those rays with the horizontal and vertical
|
|
34088
|
-
// rays extending from the sidePoint. These intersections are the points to
|
|
34089
|
-
// which sidePoint can move that will result in a rotation of `snapDegrees`.
|
|
34090
|
-
const left = findIntersectionOfRays(leftRay, counterClockwiseRay) ?? findIntersectionOfRays(leftRay, clockwiseRay);
|
|
34091
|
-
const right = findIntersectionOfRays(rightRay, counterClockwiseRay) ?? findIntersectionOfRays(rightRay, clockwiseRay);
|
|
34092
|
-
const up = findIntersectionOfRays(upRay, counterClockwiseRay) ?? findIntersectionOfRays(upRay, clockwiseRay);
|
|
34093
|
-
const down = findIntersectionOfRays(downRay, counterClockwiseRay) ?? findIntersectionOfRays(downRay, clockwiseRay);
|
|
34094
|
-
return {
|
|
34095
|
-
up: up ?? sidePoint,
|
|
34096
|
-
down: down ?? sidePoint,
|
|
34097
|
-
left: left ?? sidePoint,
|
|
34098
|
-
right: right ?? sidePoint
|
|
34099
|
-
};
|
|
34100
|
-
}
|
|
34101
|
-
|
|
34102
|
-
function LimitedPointGraph(props) {
|
|
34103
|
-
const {
|
|
34104
|
-
dispatch
|
|
34105
|
-
} = props;
|
|
34106
|
-
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
34107
|
-
key: i,
|
|
34108
|
-
point: point,
|
|
34109
|
-
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
|
|
34110
|
-
})));
|
|
34111
|
-
}
|
|
34112
|
-
function UnlimitedPointGraph(props) {
|
|
34113
|
-
const {
|
|
34114
|
-
dispatch
|
|
34115
|
-
} = props;
|
|
34116
|
-
const graphConfig = useGraphConfig();
|
|
34117
|
-
const {
|
|
34118
|
-
range: [[minX, maxX], [minY, maxY]]
|
|
34119
|
-
} = graphConfig;
|
|
34120
|
-
const width = maxX - minX;
|
|
34121
|
-
const height = maxY - minY;
|
|
34122
|
-
const [[widthPx, heightPx]] = useTransformDimensionsToPixels([width, height]);
|
|
34123
|
-
const [[left, top]] = useTransformVectorsToPixels([minX, maxY]);
|
|
34124
|
-
const itemsRef = React__namespace.useRef([]);
|
|
34125
|
-
React__namespace.useEffect(() => {
|
|
34126
|
-
const focusedIndex = props.graphState.focusedPointIndex;
|
|
34127
|
-
if (focusedIndex != null) {
|
|
34128
|
-
itemsRef.current[focusedIndex]?.focus();
|
|
34129
|
-
}
|
|
34130
|
-
}, [props.graphState.focusedPointIndex, itemsRef]);
|
|
34131
|
-
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
|
|
34132
|
-
style: {
|
|
34133
|
-
fill: "rgba(0,0,0,0)",
|
|
34134
|
-
cursor: "crosshair"
|
|
34135
|
-
},
|
|
34136
|
-
width: widthPx,
|
|
34137
|
-
height: heightPx,
|
|
34138
|
-
x: left,
|
|
34139
|
-
y: top,
|
|
34140
|
-
onClick: event => {
|
|
34141
|
-
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
34142
|
-
const x = event.clientX - elementRect.x;
|
|
34143
|
-
const y = event.clientY - elementRect.y;
|
|
34144
|
-
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
34145
|
-
dispatch(actions.pointGraph.addPoint(graphCoordinates[0]));
|
|
34146
|
-
}
|
|
34147
|
-
}), props.graphState.coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(MovablePoint$1, {
|
|
34148
|
-
key: i,
|
|
34149
|
-
point: point,
|
|
34150
|
-
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
|
|
34151
|
-
ref: ref => {
|
|
34152
|
-
itemsRef.current[i] = ref;
|
|
34153
|
-
},
|
|
34154
|
-
onFocus: () => {
|
|
34155
|
-
dispatch(actions.pointGraph.focusPoint(i));
|
|
34156
|
-
},
|
|
34157
|
-
onClick: () => {
|
|
34158
|
-
dispatch(actions.pointGraph.clickPoint(i));
|
|
34159
|
-
}
|
|
34160
|
-
})));
|
|
34161
|
-
}
|
|
34162
|
-
function PointGraph(props) {
|
|
34163
|
-
const numPoints = props.graphState.numPoints;
|
|
34164
|
-
if (numPoints === "unlimited") {
|
|
34165
|
-
return UnlimitedPointGraph(props);
|
|
34166
|
-
}
|
|
34167
|
-
return LimitedPointGraph(props);
|
|
34168
|
-
}
|
|
34169
|
-
|
|
34170
34411
|
const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
|
|
34171
34412
|
|
|
34172
34413
|
// The vector from the center of the protractor to the top left corner of the
|
|
@@ -34330,6 +34571,7 @@ const MafsGraph = props => {
|
|
|
34330
34571
|
const tickStep = props.step;
|
|
34331
34572
|
const uniqueId = React__namespace.useId();
|
|
34332
34573
|
const descriptionId = `interactive-graph-description-${uniqueId}`;
|
|
34574
|
+
const interactiveElementsDescriptionId = `interactive-graph-interactive-elements-description-${uniqueId}`;
|
|
34333
34575
|
const graphRef = React__namespace.useRef(null);
|
|
34334
34576
|
const {
|
|
34335
34577
|
analytics
|
|
@@ -34364,6 +34606,13 @@ const MafsGraph = props => {
|
|
|
34364
34606
|
}
|
|
34365
34607
|
});
|
|
34366
34608
|
});
|
|
34609
|
+
const {
|
|
34610
|
+
graph,
|
|
34611
|
+
interactiveElementsDescription
|
|
34612
|
+
} = renderGraphElements({
|
|
34613
|
+
state,
|
|
34614
|
+
dispatch
|
|
34615
|
+
});
|
|
34367
34616
|
return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
|
|
34368
34617
|
value: {
|
|
34369
34618
|
range: state.range,
|
|
@@ -34395,7 +34644,7 @@ const MafsGraph = props => {
|
|
|
34395
34644
|
handleKeyboardEvent(event, state, dispatch);
|
|
34396
34645
|
},
|
|
34397
34646
|
"aria-label": fullGraphAriaLabel,
|
|
34398
|
-
"aria-describedby": fullGraphAriaDescription
|
|
34647
|
+
"aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId),
|
|
34399
34648
|
ref: graphRef,
|
|
34400
34649
|
tabIndex: 0,
|
|
34401
34650
|
onFocus: event => {
|
|
@@ -34407,12 +34656,12 @@ const MafsGraph = props => {
|
|
|
34407
34656
|
}, fullGraphAriaDescription && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
34408
34657
|
id: descriptionId,
|
|
34409
34658
|
tabIndex: -1,
|
|
34410
|
-
|
|
34411
|
-
|
|
34412
|
-
|
|
34413
|
-
|
|
34414
|
-
|
|
34415
|
-
},
|
|
34659
|
+
className: "mafs-sr-only"
|
|
34660
|
+
}, fullGraphAriaDescription), interactiveElementsDescription && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
34661
|
+
id: interactiveElementsDescriptionId,
|
|
34662
|
+
tabIndex: -1,
|
|
34663
|
+
className: "mafs-sr-only"
|
|
34664
|
+
}, interactiveElementsDescription), /*#__PURE__*/React__namespace.createElement(LegacyGrid, {
|
|
34416
34665
|
box: props.box,
|
|
34417
34666
|
backgroundImage: props.backgroundImage
|
|
34418
34667
|
}), /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
@@ -34463,10 +34712,7 @@ const MafsGraph = props => {
|
|
|
34463
34712
|
zoom: false,
|
|
34464
34713
|
width: width,
|
|
34465
34714
|
height: height
|
|
34466
|
-
}, /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null),
|
|
34467
|
-
state,
|
|
34468
|
-
dispatch
|
|
34469
|
-
}))))), interactionPrompt && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
34715
|
+
}, /*#__PURE__*/React__namespace.createElement("svg", nestedSVGAttributes, props.showProtractor && /*#__PURE__*/React__namespace.createElement(Protractor, null), graph)))), interactionPrompt && /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
34470
34716
|
style: {
|
|
34471
34717
|
textAlign: "center",
|
|
34472
34718
|
backgroundColor: "white",
|
|
@@ -34698,7 +34944,7 @@ const calculateNestedSVGCoords = (range, width, height) => {
|
|
|
34698
34944
|
viewboxY
|
|
34699
34945
|
};
|
|
34700
34946
|
};
|
|
34701
|
-
const
|
|
34947
|
+
const renderGraphElements = props => {
|
|
34702
34948
|
const {
|
|
34703
34949
|
state,
|
|
34704
34950
|
dispatch
|
|
@@ -34708,62 +34954,44 @@ const renderGraph = props => {
|
|
|
34708
34954
|
} = state;
|
|
34709
34955
|
switch (type) {
|
|
34710
34956
|
case "angle":
|
|
34711
|
-
return
|
|
34712
|
-
graphState: state,
|
|
34713
|
-
dispatch: dispatch
|
|
34714
|
-
});
|
|
34957
|
+
return renderAngleGraph(state, dispatch);
|
|
34715
34958
|
case "segment":
|
|
34716
|
-
return
|
|
34717
|
-
graphState: state,
|
|
34718
|
-
dispatch: dispatch
|
|
34719
|
-
});
|
|
34959
|
+
return renderSegmentGraph(state, dispatch);
|
|
34720
34960
|
case "linear-system":
|
|
34721
|
-
return
|
|
34722
|
-
graphState: state,
|
|
34723
|
-
dispatch: dispatch
|
|
34724
|
-
});
|
|
34961
|
+
return renderLinearSystemGraph(state, dispatch);
|
|
34725
34962
|
case "linear":
|
|
34726
|
-
return
|
|
34727
|
-
graphState: state,
|
|
34728
|
-
dispatch: dispatch
|
|
34729
|
-
});
|
|
34963
|
+
return renderLinearGraph(state, dispatch);
|
|
34730
34964
|
case "ray":
|
|
34731
|
-
return
|
|
34732
|
-
graphState: state,
|
|
34733
|
-
dispatch: dispatch
|
|
34734
|
-
});
|
|
34965
|
+
return renderRayGraph(state, dispatch);
|
|
34735
34966
|
case "polygon":
|
|
34736
|
-
return
|
|
34737
|
-
graphState: state,
|
|
34738
|
-
dispatch: dispatch
|
|
34739
|
-
});
|
|
34967
|
+
return renderPolygonGraph(state, dispatch);
|
|
34740
34968
|
case "point":
|
|
34741
|
-
return
|
|
34742
|
-
graphState: state,
|
|
34743
|
-
dispatch: dispatch
|
|
34744
|
-
});
|
|
34969
|
+
return renderPointGraph(state, dispatch);
|
|
34745
34970
|
case "circle":
|
|
34746
|
-
return
|
|
34747
|
-
graphState: state,
|
|
34748
|
-
dispatch: dispatch
|
|
34749
|
-
});
|
|
34971
|
+
return renderCircleGraph(state, dispatch);
|
|
34750
34972
|
case "quadratic":
|
|
34751
|
-
return
|
|
34752
|
-
graphState: state,
|
|
34753
|
-
dispatch: dispatch
|
|
34754
|
-
});
|
|
34973
|
+
return renderQuadraticGraph(state, dispatch);
|
|
34755
34974
|
case "sinusoid":
|
|
34756
|
-
return
|
|
34757
|
-
graphState: state,
|
|
34758
|
-
dispatch: dispatch
|
|
34759
|
-
});
|
|
34975
|
+
return renderSinusoidGraph(state, dispatch);
|
|
34760
34976
|
case "none":
|
|
34761
|
-
return
|
|
34977
|
+
return {
|
|
34978
|
+
graph: null,
|
|
34979
|
+
interactiveElementsDescription: null
|
|
34980
|
+
};
|
|
34762
34981
|
default:
|
|
34763
34982
|
throw new wonderStuffCore.UnreachableCaseError(type);
|
|
34764
34983
|
}
|
|
34765
34984
|
};
|
|
34766
34985
|
|
|
34986
|
+
// Returns a space-separated string like "foo bar" given several optional
|
|
34987
|
+
// string IDs. If all args are falsy, returns undefined.
|
|
34988
|
+
function describedByIds() {
|
|
34989
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
34990
|
+
args[_key] = arguments[_key];
|
|
34991
|
+
}
|
|
34992
|
+
return args.filter(Boolean).join(" ") || undefined;
|
|
34993
|
+
}
|
|
34994
|
+
|
|
34767
34995
|
// Converts the state of a StatefulMafsGraph back to the format used to
|
|
34768
34996
|
// represent graph state in the widget JSON.
|
|
34769
34997
|
//
|
|
@@ -44008,7 +44236,7 @@ const VideoTranscriptLink = props => {
|
|
|
44008
44236
|
} = props;
|
|
44009
44237
|
const {
|
|
44010
44238
|
useVideo
|
|
44011
|
-
} =
|
|
44239
|
+
} = useDependencies();
|
|
44012
44240
|
const [id, kind] = IS_URL$1.test(location) ? [getYoutubeId(location), "YOUTUBE_ID"] : [location, "READABLE_ID"];
|
|
44013
44241
|
// The result value conforms to the wonder-blocks-data `Result` type
|
|
44014
44242
|
// which is used by our GraphQL framework.
|
|
@@ -44184,7 +44412,7 @@ const init = function (options) {
|
|
|
44184
44412
|
|
|
44185
44413
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44186
44414
|
const libName = "@khanacademy/perseus";
|
|
44187
|
-
const libVersion = "
|
|
44415
|
+
const libVersion = "41.0.0";
|
|
44188
44416
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44189
44417
|
|
|
44190
44418
|
const apiVersion = {
|