@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/es/index.js
CHANGED
|
@@ -32,7 +32,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
|
|
|
32
32
|
import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
|
|
33
33
|
import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
|
|
34
34
|
import debounce$2 from 'lodash.debounce';
|
|
35
|
-
import { vec, Coordinates, Ellipse, Plot as Plot$3, Line as Line$4, Point as Point$2, Polygon,
|
|
35
|
+
import { vec, Coordinates, Ellipse, Plot as Plot$3, Line as Line$4, Point as Point$2, Polygon, Text as Text$1, useTransformContext, Mafs } from 'mafs';
|
|
36
36
|
import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
|
|
37
37
|
import questionIcon from '@phosphor-icons/core/regular/question.svg';
|
|
38
38
|
import { useDrag } from '@use-gesture/react';
|
|
@@ -238,7 +238,15 @@ const mockStrings = {
|
|
|
238
238
|
simulationLocaleWarning: "Sorry, this simulation isn't available in your language.",
|
|
239
239
|
addPoint: "Add Point",
|
|
240
240
|
removePoint: "Remove Point",
|
|
241
|
-
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph"
|
|
241
|
+
graphKeyboardPrompt: "Press Shift + Enter to interact with the graph",
|
|
242
|
+
srPointAtCoordinates: ({
|
|
243
|
+
x,
|
|
244
|
+
y
|
|
245
|
+
}) => `Point at ${x} comma ${y}`,
|
|
246
|
+
srInteractiveElements: ({
|
|
247
|
+
elements
|
|
248
|
+
}) => `Interactive elements: ${elements}`,
|
|
249
|
+
srNoInteractiveElements: "No interactive elements"
|
|
242
250
|
};
|
|
243
251
|
|
|
244
252
|
/**
|
|
@@ -246,11 +254,13 @@ const mockStrings = {
|
|
|
246
254
|
* used inside the Perseus package.
|
|
247
255
|
*
|
|
248
256
|
*/
|
|
249
|
-
|
|
250
|
-
const PerseusI18nContext = /*#__PURE__*/React.createContext(process.env.NODE_ENV === "test" || process.env.STORYBOOK ? {
|
|
257
|
+
const mockPerseusI18nContext = {
|
|
251
258
|
strings: mockStrings,
|
|
252
259
|
locale: "en"
|
|
253
|
-
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// @ts-expect-error - TS2322 - Type 'Context<{ strings: {}; locale: string; }>' is not assignable to type 'Context<I18nContextType>'.
|
|
263
|
+
const PerseusI18nContext = /*#__PURE__*/React.createContext(process.env.NODE_ENV === "test" || process.env.STORYBOOK ? mockPerseusI18nContext :
|
|
254
264
|
// We want to return null here, not an empty object, so that we
|
|
255
265
|
// are will throw an error when attempting to access the
|
|
256
266
|
// undefined locale or strings, making it easier to debug.
|
|
@@ -531,7 +541,7 @@ class InnerMathInput extends React.Component {
|
|
|
531
541
|
closeButtonVisible: true,
|
|
532
542
|
style: styles$E.popoverContent
|
|
533
543
|
}, /*#__PURE__*/React.createElement(DesktopKeypad, _extends({
|
|
534
|
-
onAnalyticsEvent: this.props.
|
|
544
|
+
onAnalyticsEvent: this.props.onAnalyticsEvent,
|
|
535
545
|
extraKeys: this.props.extraKeys,
|
|
536
546
|
onClickKey: this.handleKeypadPress,
|
|
537
547
|
cursorContext: this.state.cursorContext,
|
|
@@ -705,6 +715,9 @@ const getDependencies = () => {
|
|
|
705
715
|
const DependenciesContext = /*#__PURE__*/React.createContext({
|
|
706
716
|
analytics: {
|
|
707
717
|
onAnalyticsEvent: async () => {}
|
|
718
|
+
},
|
|
719
|
+
useVideo: () => {
|
|
720
|
+
throw new Error("useVideo dependency not provided in Perseus dependencies");
|
|
708
721
|
}
|
|
709
722
|
});
|
|
710
723
|
|
|
@@ -958,7 +971,7 @@ class TextListEditor extends React.Component {
|
|
|
958
971
|
}
|
|
959
972
|
TextListEditor.propTypes = {
|
|
960
973
|
options: PropTypes.array,
|
|
961
|
-
layout: PropTypes.
|
|
974
|
+
layout: PropTypes.oneOf(["horizontal", "vertical"]),
|
|
962
975
|
onChange: PropTypes.func.isRequired
|
|
963
976
|
};
|
|
964
977
|
TextListEditor.defaultProps = {
|
|
@@ -966,6 +979,8 @@ TextListEditor.defaultProps = {
|
|
|
966
979
|
layout: "horizontal"
|
|
967
980
|
};
|
|
968
981
|
|
|
982
|
+
const EMPTY_ARRAY = [];
|
|
983
|
+
|
|
969
984
|
/**
|
|
970
985
|
* Stub Tag Editor.
|
|
971
986
|
*
|
|
@@ -979,7 +994,6 @@ TextListEditor.defaultProps = {
|
|
|
979
994
|
* It also gives a nicer interface for the group metadata editor
|
|
980
995
|
* in local demo mode.
|
|
981
996
|
*/
|
|
982
|
-
const EMPTY_ARRAY = [];
|
|
983
997
|
class StubTagEditor extends React.Component {
|
|
984
998
|
render() {
|
|
985
999
|
return /*#__PURE__*/React.createElement("div", null, this.props.showTitle && /*#__PURE__*/React.createElement("div", {
|
|
@@ -2408,7 +2422,7 @@ class Expression extends React.Component {
|
|
|
2408
2422
|
}, cb);
|
|
2409
2423
|
}
|
|
2410
2424
|
render() {
|
|
2411
|
-
var _this$props$keypadCon, _this$props$analytics2;
|
|
2425
|
+
var _this$props$keypadCon, _this$props$analytics2, _this$props$analytics3;
|
|
2412
2426
|
if (this.props.apiOptions.customKeypad) {
|
|
2413
2427
|
return /*#__PURE__*/React.createElement(View, {
|
|
2414
2428
|
className: css(styles$D.mobileLabelInputWrapper)
|
|
@@ -2481,9 +2495,7 @@ class Expression extends React.Component {
|
|
|
2481
2495
|
hasError: this.state.showErrorStyle,
|
|
2482
2496
|
ariaLabel: this.props.ariaLabel || this.context.strings.mathInputBox,
|
|
2483
2497
|
extraKeys: (_this$props$keypadCon = this.props.keypadConfiguration) == null ? void 0 : _this$props$keypadCon.extraKeys,
|
|
2484
|
-
|
|
2485
|
-
onAnalyticsEvent: async function () {}
|
|
2486
|
-
}
|
|
2498
|
+
onAnalyticsEvent: (_this$props$analytics2 = (_this$props$analytics3 = this.props.analytics) == null ? void 0 : _this$props$analytics3.onAnalyticsEvent) != null ? _this$props$analytics2 : async function () {}
|
|
2487
2499
|
}))));
|
|
2488
2500
|
}
|
|
2489
2501
|
}
|
|
@@ -14800,11 +14812,7 @@ _.extend(MovableLine$4.prototype, {
|
|
|
14800
14812
|
* A stripped version of Icon.jsx from webapp. Takes an SVG icon and renders it
|
|
14801
14813
|
* inline like Font Awesome did.
|
|
14802
14814
|
*
|
|
14803
|
-
*
|
|
14804
|
-
* webapp's `icon-paths.js` which is a reference file for all the SVG paths
|
|
14805
|
-
* that we've used. You'll need to copy the object from that file into
|
|
14806
|
-
* whichever file you're using the icon and explicitly pass it in to the
|
|
14807
|
-
* <InlineIcon/> React component.
|
|
14815
|
+
* You can refer to `icon-paths.ts` for all available SVG icons.
|
|
14808
14816
|
*
|
|
14809
14817
|
* We assume that the viewBox is cropped and aligned to (0, 0), but icons can
|
|
14810
14818
|
* be defined differently. At some point we might want to add these attributes
|
|
@@ -14812,13 +14820,14 @@ _.extend(MovableLine$4.prototype, {
|
|
|
14812
14820
|
*
|
|
14813
14821
|
* Sample usage:
|
|
14814
14822
|
*
|
|
14815
|
-
*
|
|
14823
|
+
* ```
|
|
14824
|
+
* const editIcon = {
|
|
14816
14825
|
* 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",
|
|
14817
14826
|
* width: 100,
|
|
14818
14827
|
* height: 78.912,
|
|
14819
|
-
*
|
|
14820
|
-
*
|
|
14821
|
-
*
|
|
14828
|
+
* };
|
|
14829
|
+
* <InlineIcon {...editIcon} />
|
|
14830
|
+
* ```
|
|
14822
14831
|
*/
|
|
14823
14832
|
const InlineIcon = ({
|
|
14824
14833
|
path,
|
|
@@ -14841,11 +14850,7 @@ const InlineIcon = ({
|
|
|
14841
14850
|
}));
|
|
14842
14851
|
|
|
14843
14852
|
/**
|
|
14844
|
-
* Icon paths to be used with `inline-icon.
|
|
14845
|
-
*
|
|
14846
|
-
* These paths are taken directly from webapp's `icon-paths.js`. Unlike the
|
|
14847
|
-
* webapp equivalent, these can be directly required within Perseus files since
|
|
14848
|
-
* this is all bundled together anyway.
|
|
14853
|
+
* Icon paths to be used with `inline-icon.tsx` and `icon.tsx`.
|
|
14849
14854
|
*/
|
|
14850
14855
|
|
|
14851
14856
|
const iconCheck = {
|
|
@@ -16791,6 +16796,15 @@ const Status = {
|
|
|
16791
16796
|
LOADED: "loaded",
|
|
16792
16797
|
FAILED: "failed"
|
|
16793
16798
|
};
|
|
16799
|
+
/**
|
|
16800
|
+
* Component to display an image (or other React components) while the desired
|
|
16801
|
+
* image is loading.
|
|
16802
|
+
*
|
|
16803
|
+
* Derived from
|
|
16804
|
+
* https://github.com/hzdg/react-imageloader/blob/master/src/index.js
|
|
16805
|
+
* to better suit our environment/build tools. Additionally, this one does
|
|
16806
|
+
* not introduce a wrapper element, which makes styling easier.
|
|
16807
|
+
*/
|
|
16794
16808
|
class ImageLoader extends React.Component {
|
|
16795
16809
|
constructor(props) {
|
|
16796
16810
|
super(props);
|
|
@@ -20920,7 +20934,7 @@ const Triangle = props => {
|
|
|
20920
20934
|
width: 0,
|
|
20921
20935
|
position: "absolute",
|
|
20922
20936
|
left: props.left,
|
|
20923
|
-
top: props
|
|
20937
|
+
top: props.top,
|
|
20924
20938
|
borderLeft: borderLeft,
|
|
20925
20939
|
borderRight: borderRight,
|
|
20926
20940
|
borderTop: borderTop,
|
|
@@ -21001,6 +21015,47 @@ const HORIZONTAL_ALIGNMNENTS = {
|
|
|
21001
21015
|
arrowLeft: arrowSize => -arrowSize - 2
|
|
21002
21016
|
}
|
|
21003
21017
|
};
|
|
21018
|
+
/**
|
|
21019
|
+
* DEPRECATED! Use Wonder Blocks tooltip instead.
|
|
21020
|
+
*
|
|
21021
|
+
* A generic tooltip library for React.js
|
|
21022
|
+
*
|
|
21023
|
+
* ```
|
|
21024
|
+
* import Tooltip from "./tooltip";
|
|
21025
|
+
* <Tooltip
|
|
21026
|
+
* className="class-for-tooltip-contents"
|
|
21027
|
+
* horizontalPosition={HoriziontalDirection.Left}
|
|
21028
|
+
* horizontalAlign={HoriziontalDirection.Left}
|
|
21029
|
+
* verticalPosition={VerticalDirection.Top}
|
|
21030
|
+
* arrowSize={10} // arrow size in pixels
|
|
21031
|
+
* borderColor="#ccc" // color of the border for the tooltip
|
|
21032
|
+
* show={true} // whether the tooltip should currently be visible
|
|
21033
|
+
* targetContainerStyle={targetContainerStyle}
|
|
21034
|
+
* >
|
|
21035
|
+
* <TargetElementOfTheTooltip />
|
|
21036
|
+
* <TooltipContents1 />
|
|
21037
|
+
* <TooltipContents2 />
|
|
21038
|
+
* </Tooltip>
|
|
21039
|
+
* ```
|
|
21040
|
+
*
|
|
21041
|
+
* To show/hide the tooltip, the parent component should call the
|
|
21042
|
+
* `.show()` and `.hide()` methods of the tooltip when appropriate.
|
|
21043
|
+
* (These are usually set up as handlers of events on the target element.)
|
|
21044
|
+
*
|
|
21045
|
+
* Notes:
|
|
21046
|
+
* `className` should not specify a border; that is handled by `borderColor`
|
|
21047
|
+
* so that the arrow and tooltip match
|
|
21048
|
+
*
|
|
21049
|
+
* ```
|
|
21050
|
+
* __,,--``\\
|
|
21051
|
+
* _,,-''`` \\ ,
|
|
21052
|
+
* '----------_.------'-.___|\__
|
|
21053
|
+
* _.--''`` `)__ )__ @\__
|
|
21054
|
+
* ( .. ''---/___,,E/__,E'------`
|
|
21055
|
+
* `-''`''
|
|
21056
|
+
* Here be dragons.
|
|
21057
|
+
* ```
|
|
21058
|
+
*/
|
|
21004
21059
|
class Tooltip extends React.Component {
|
|
21005
21060
|
constructor(...args) {
|
|
21006
21061
|
super(...args);
|
|
@@ -21227,10 +21282,7 @@ class InputWithExamples extends React.Component {
|
|
|
21227
21282
|
return "- " + example;
|
|
21228
21283
|
}).join("\n");
|
|
21229
21284
|
const showExamples = this.props.shouldShowExamples && this.state.showExamples;
|
|
21230
|
-
return /*#__PURE__*/React.createElement(Tooltip
|
|
21231
|
-
// eslint-disable-next-line react/no-string-refs
|
|
21232
|
-
, {
|
|
21233
|
-
ref: "tooltip",
|
|
21285
|
+
return /*#__PURE__*/React.createElement(Tooltip, {
|
|
21234
21286
|
className: "perseus-formats-tooltip preview-measure",
|
|
21235
21287
|
horizontalPosition: HorizontalDirection.Left,
|
|
21236
21288
|
horizontalAlign: HorizontalDirection.Left,
|
|
@@ -24747,6 +24799,37 @@ const BASE_ICON_SIZE = 10;
|
|
|
24747
24799
|
// If you pass in an object you need to specify an SVG path to
|
|
24748
24800
|
// render along with the width and height of the path (to ensure
|
|
24749
24801
|
// that it gets scaled correctly).
|
|
24802
|
+
/**
|
|
24803
|
+
* An SVG Icon
|
|
24804
|
+
*
|
|
24805
|
+
* This component is designed to take in SVG paths and render icons based upon
|
|
24806
|
+
* them. If you are looking for an icon that we've used before you should look
|
|
24807
|
+
* in `icon-paths.js` which is a reference file for all the SVG paths that
|
|
24808
|
+
* we've used. You'll need to copy the object from that file into whichever
|
|
24809
|
+
* file you're using the icon and explicitly pass it in to the <Icon/> React
|
|
24810
|
+
* component.
|
|
24811
|
+
*
|
|
24812
|
+
* Sample usage:
|
|
24813
|
+
*
|
|
24814
|
+
* ```
|
|
24815
|
+
* const dropdownIcon = `M5,6L0,0L10,0`;
|
|
24816
|
+
* <Icon icon={dropdownIcon} />
|
|
24817
|
+
* ```
|
|
24818
|
+
*
|
|
24819
|
+
* Or:
|
|
24820
|
+
*
|
|
24821
|
+
* ```
|
|
24822
|
+
* const dropdownIcon = {
|
|
24823
|
+
* path: `M5,6L0,0L10,0`,
|
|
24824
|
+
* height: 10,
|
|
24825
|
+
* width: 10,
|
|
24826
|
+
* };
|
|
24827
|
+
* <Icon icon={dropdownIcon} size={20} />
|
|
24828
|
+
* ```
|
|
24829
|
+
*
|
|
24830
|
+
* If you want to add an entirely new icon please read the note inside
|
|
24831
|
+
* the `icon-paths.ts` file.
|
|
24832
|
+
*/
|
|
24750
24833
|
class Icon extends React.Component {
|
|
24751
24834
|
render() {
|
|
24752
24835
|
const {
|
|
@@ -24969,7 +25052,7 @@ function ChoiceInner(props) {
|
|
|
24969
25052
|
} = usePerseusI18n();
|
|
24970
25053
|
const letter = getChoiceLetter(pos, strings);
|
|
24971
25054
|
if (!showCorrectness) {
|
|
24972
|
-
return letter;
|
|
25055
|
+
return /*#__PURE__*/React.createElement("span", null, letter);
|
|
24973
25056
|
}
|
|
24974
25057
|
if (correct) {
|
|
24975
25058
|
return /*#__PURE__*/React.createElement(InlineIcon, _extends({}, iconCheck, {
|
|
@@ -25039,7 +25122,6 @@ const ChoiceIcon = function ChoiceIcon(props) {
|
|
|
25039
25122
|
visible: focused || hovered,
|
|
25040
25123
|
multipleSelect: multipleSelect
|
|
25041
25124
|
}, /*#__PURE__*/React.createElement("div", {
|
|
25042
|
-
// @ts-expect-error - TS2322 - Type '{ backgroundColor: string | null | undefined; borderColor: string; color: string; borderRadius: number; }' is not assignable to type 'Properties<string | number, string & {}>'.
|
|
25043
25125
|
style: dynamicStyles,
|
|
25044
25126
|
"data-testid": "choice-icon__library-choice-icon",
|
|
25045
25127
|
className: css(styles$s.circle, showCorrectness && correct && styles$s.circleCorrect, showCorrectness && !correct && styles$s.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$s.circleIncorrectAnswered)
|
|
@@ -30873,6 +30955,10 @@ const HiddenIcon = () => /*#__PURE__*/React.createElement("svg", {
|
|
|
30873
30955
|
}, /*#__PURE__*/React.createElement("path", {
|
|
30874
30956
|
d: "M0 0h24v24H0z"
|
|
30875
30957
|
}))));
|
|
30958
|
+
/**
|
|
30959
|
+
* A "heads-up display" (HUD) indicator that includes a short message (usually
|
|
30960
|
+
* used for linting errors). The indicator can be disabled.
|
|
30961
|
+
*/
|
|
30876
30962
|
const HUD = ({
|
|
30877
30963
|
message,
|
|
30878
30964
|
enabled,
|
|
@@ -31093,18 +31179,24 @@ const {
|
|
|
31093
31179
|
const toNumericString = KhanMath.toNumericString;
|
|
31094
31180
|
const getNumericFormat = KhanMath.getNumericFormat;
|
|
31095
31181
|
|
|
31096
|
-
|
|
31182
|
+
/**
|
|
31183
|
+
* An input box that accepts only numeric strings
|
|
31097
31184
|
*
|
|
31098
|
-
* Calls onChange(value, format) for valid numbers.
|
|
31099
|
-
*
|
|
31185
|
+
* Calls `onChange(value, format)` for valid numbers.
|
|
31186
|
+
*
|
|
31187
|
+
* Reverts to the current value `onBlur` or on [ENTER],
|
|
31100
31188
|
* but maintains the format (i.e. 3/2, 1 1/2, 150%)
|
|
31101
|
-
*
|
|
31102
|
-
*
|
|
31103
|
-
*
|
|
31104
|
-
*
|
|
31105
|
-
* If
|
|
31106
|
-
*
|
|
31107
|
-
*
|
|
31189
|
+
*
|
|
31190
|
+
* Accepts empty input and sends it to `onChange` as `null` if no numeric
|
|
31191
|
+
* placeholder is set.
|
|
31192
|
+
*
|
|
31193
|
+
* If given a `checkValidity` function, will turn the background/outline red
|
|
31194
|
+
* when invalid.
|
|
31195
|
+
*
|
|
31196
|
+
* If `useArrowKeys` is set to `true`, up/down arrows will increment/decrement
|
|
31197
|
+
* integers.
|
|
31198
|
+
*
|
|
31199
|
+
* Optionally takes a `size` (`"mini"`, `"small"`,` `"normal"`)
|
|
31108
31200
|
*/
|
|
31109
31201
|
class NumberInput extends React.Component {
|
|
31110
31202
|
constructor(...args) {
|
|
@@ -31263,7 +31355,7 @@ NumberInput.propTypes = {
|
|
|
31263
31355
|
onChange: PropTypes.func.isRequired,
|
|
31264
31356
|
onFormatChange: PropTypes.func,
|
|
31265
31357
|
checkValidity: PropTypes.func,
|
|
31266
|
-
size: PropTypes.
|
|
31358
|
+
size: PropTypes.oneOf(["mini", "small", "normal"]),
|
|
31267
31359
|
label: PropTypes.oneOf(["put your labels outside your inputs!"])
|
|
31268
31360
|
};
|
|
31269
31361
|
NumberInput.defaultProps = {
|
|
@@ -31277,8 +31369,8 @@ NumberInput.defaultProps = {
|
|
|
31277
31369
|
|
|
31278
31370
|
const truth = () => true;
|
|
31279
31371
|
|
|
31280
|
-
|
|
31281
|
-
*
|
|
31372
|
+
/**
|
|
31373
|
+
* A minor abstraction on top of `NumberInput` for ranges
|
|
31282
31374
|
*/
|
|
31283
31375
|
class RangeInput extends React.Component {
|
|
31284
31376
|
constructor(...args) {
|
|
@@ -32129,6 +32221,327 @@ function reinitialize(params) {
|
|
|
32129
32221
|
};
|
|
32130
32222
|
}
|
|
32131
32223
|
|
|
32224
|
+
/**
|
|
32225
|
+
* Wrapper with Mafs transform styles for SVG elements.
|
|
32226
|
+
* Scales and translates the SVG element to match the Mafs view.
|
|
32227
|
+
* Use if you do not need/want to use the hook in `use-transform.ts`.
|
|
32228
|
+
*/
|
|
32229
|
+
const MafsCssTransformWrapper = ({
|
|
32230
|
+
children
|
|
32231
|
+
}) => /*#__PURE__*/React.createElement("g", {
|
|
32232
|
+
style: {
|
|
32233
|
+
transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
|
|
32234
|
+
}
|
|
32235
|
+
}, children);
|
|
32236
|
+
|
|
32237
|
+
const _excluded$1 = ["children"];
|
|
32238
|
+
const TextLabel = _ref => {
|
|
32239
|
+
let {
|
|
32240
|
+
children
|
|
32241
|
+
} = _ref,
|
|
32242
|
+
rest = _objectWithoutPropertiesLoose(_ref, _excluded$1);
|
|
32243
|
+
return /*#__PURE__*/React__default.createElement(Text$1, _extends({
|
|
32244
|
+
size: 16,
|
|
32245
|
+
svgTextProps: {
|
|
32246
|
+
filter: "url(#background)",
|
|
32247
|
+
fontWeight: "bold"
|
|
32248
|
+
}
|
|
32249
|
+
}, rest), children);
|
|
32250
|
+
};
|
|
32251
|
+
const SvgDefs = () => /*#__PURE__*/React__default.createElement("defs", null, /*#__PURE__*/React__default.createElement("filter", {
|
|
32252
|
+
id: "background",
|
|
32253
|
+
x: "-5%",
|
|
32254
|
+
width: "110%",
|
|
32255
|
+
y: "0%",
|
|
32256
|
+
height: "100%"
|
|
32257
|
+
}, /*#__PURE__*/React__default.createElement("feFlood", {
|
|
32258
|
+
floodColor: "#FFF",
|
|
32259
|
+
floodOpacity: "0.64"
|
|
32260
|
+
}), /*#__PURE__*/React__default.createElement("feComposite", {
|
|
32261
|
+
operator: "over",
|
|
32262
|
+
in: "SourceGraphic"
|
|
32263
|
+
})));
|
|
32264
|
+
|
|
32265
|
+
const PolygonAngle = ({
|
|
32266
|
+
centerPoint,
|
|
32267
|
+
endPoints,
|
|
32268
|
+
range,
|
|
32269
|
+
polygonLines,
|
|
32270
|
+
showAngles,
|
|
32271
|
+
snapTo
|
|
32272
|
+
}) => {
|
|
32273
|
+
const [centerX, centerY] = centerPoint;
|
|
32274
|
+
const areClockwise = clockwise$1([centerPoint, ...endPoints]);
|
|
32275
|
+
const [[startX, startY], [endX, endY]] = areClockwise ? endPoints : endPoints.reverse();
|
|
32276
|
+
const radius = 0.3;
|
|
32277
|
+
const a = vec.dist(centerPoint, endPoints[0]);
|
|
32278
|
+
const b = vec.dist(centerPoint, endPoints[1]);
|
|
32279
|
+
const c = vec.dist(endPoints[0], endPoints[1]);
|
|
32280
|
+
|
|
32281
|
+
// Law of cosines
|
|
32282
|
+
const angle = Math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b));
|
|
32283
|
+
const y1 = centerY + (startY - centerY) / a * radius;
|
|
32284
|
+
const x2 = centerX + (endX - centerX) / b * radius;
|
|
32285
|
+
const x1 = centerX + (startX - centerX) / a * radius;
|
|
32286
|
+
const y2 = centerY + (endY - centerY) / b * radius;
|
|
32287
|
+
|
|
32288
|
+
// Fourth point that would create a rhomboid
|
|
32289
|
+
// Used to calculate the inside of the angle and to create square
|
|
32290
|
+
const [x3, y3] = vec.add(centerPoint, vec.add(vec.sub([x1, y1], centerPoint), vec.sub([x2, y2], centerPoint)));
|
|
32291
|
+
if (!showAngles) {
|
|
32292
|
+
return isRightPolygonAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
32293
|
+
start: [x1, y1],
|
|
32294
|
+
vertex: [x2, y2],
|
|
32295
|
+
end: [x3, y3]
|
|
32296
|
+
}) : null;
|
|
32297
|
+
}
|
|
32298
|
+
|
|
32299
|
+
// Midpoint betwen ends of arc
|
|
32300
|
+
const isOutside = shouldDrawArcOutside([x3, y3], centerPoint, range, polygonLines);
|
|
32301
|
+
const largeArcFlag = isOutside ? 1 : 0;
|
|
32302
|
+
const sweepFlag = isOutside ? 1 : 0;
|
|
32303
|
+
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
32304
|
+
let angleInDegrees = angle * (180 / Math.PI);
|
|
32305
|
+
// If we have triggered "largArcFlag", the angle should be greater than 180
|
|
32306
|
+
if (isOutside) {
|
|
32307
|
+
angleInDegrees = 360 - angleInDegrees;
|
|
32308
|
+
}
|
|
32309
|
+
|
|
32310
|
+
// Only want to show whole numbers when in angles mode
|
|
32311
|
+
const angleLabelNumber = parseFloat(angleInDegrees.toFixed(snapTo === "angles" ? 0 : 1));
|
|
32312
|
+
const angleLabel = Number.isInteger(angleLabelNumber) ? angleLabelNumber : "≈ " + angleLabelNumber;
|
|
32313
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("filter", {
|
|
32314
|
+
id: "background",
|
|
32315
|
+
x: "-5%",
|
|
32316
|
+
width: "110%",
|
|
32317
|
+
y: "0%",
|
|
32318
|
+
height: "100%"
|
|
32319
|
+
}, /*#__PURE__*/React.createElement("feFlood", {
|
|
32320
|
+
floodColor: "#FFF",
|
|
32321
|
+
floodOpacity: "0.5"
|
|
32322
|
+
}), /*#__PURE__*/React.createElement("feComposite", {
|
|
32323
|
+
operator: "over",
|
|
32324
|
+
in: "SourceGraphic"
|
|
32325
|
+
}))), !isOutside && isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
32326
|
+
start: [x1, y1],
|
|
32327
|
+
vertex: [x2, y2],
|
|
32328
|
+
end: [x3, y3]
|
|
32329
|
+
}) : /*#__PURE__*/React.createElement(Arc, {
|
|
32330
|
+
arc: arc
|
|
32331
|
+
}), /*#__PURE__*/React.createElement(TextLabel, {
|
|
32332
|
+
x: x3,
|
|
32333
|
+
y: y3,
|
|
32334
|
+
attach: y3 - centerY > 0 ? "s" : "n",
|
|
32335
|
+
attachDistance: Math.abs(y3 - centerY) < 0.2 || vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
|
|
32336
|
+
}, angleLabel, "\xB0"));
|
|
32337
|
+
};
|
|
32338
|
+
const Angle = ({
|
|
32339
|
+
vertex,
|
|
32340
|
+
coords,
|
|
32341
|
+
showAngles,
|
|
32342
|
+
allowReflexAngles,
|
|
32343
|
+
range
|
|
32344
|
+
}) => {
|
|
32345
|
+
// Check if the points are clockwise or not, depending on whether we allow reflex angles
|
|
32346
|
+
const areClockwise = clockwise$1([...coords, vertex]);
|
|
32347
|
+
const shouldReverseCoords = areClockwise && !allowReflexAngles;
|
|
32348
|
+
|
|
32349
|
+
// Reverse the coordinates accordingly to ensure the angle is calculated correctly
|
|
32350
|
+
const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
|
|
32351
|
+
|
|
32352
|
+
// Calculate the angles between the two points
|
|
32353
|
+
const startAngle = getAngleFromVertex(clockwiseCoords[0], vertex);
|
|
32354
|
+
const endAngle = getAngleFromVertex(clockwiseCoords[1], vertex);
|
|
32355
|
+
const angle = (startAngle + 360 - endAngle) % 360;
|
|
32356
|
+
|
|
32357
|
+
// Check if the angle is reflexive
|
|
32358
|
+
const isReflexive = angle > 180;
|
|
32359
|
+
|
|
32360
|
+
// Break out the necessary points for the arc calculation
|
|
32361
|
+
const [centerX, centerY] = vertex;
|
|
32362
|
+
const [point1, point2] = clockwiseCoords;
|
|
32363
|
+
const [startX, startY] = point1;
|
|
32364
|
+
const [endX, endY] = point2;
|
|
32365
|
+
const a = vec.dist(vertex, point1);
|
|
32366
|
+
const b = vec.dist(vertex, point2);
|
|
32367
|
+
|
|
32368
|
+
// Set the radius of the arc
|
|
32369
|
+
const radius = 2;
|
|
32370
|
+
|
|
32371
|
+
// Calculate the end points of the arc
|
|
32372
|
+
const y1 = centerY + (startY - centerY) / a * radius;
|
|
32373
|
+
const x2 = centerX + (endX - centerX) / b * radius;
|
|
32374
|
+
const x1 = centerX + (startX - centerX) / a * radius;
|
|
32375
|
+
const y2 = centerY + (endY - centerY) / b * radius;
|
|
32376
|
+
|
|
32377
|
+
// Used to calculate the inside of the angle and to create square
|
|
32378
|
+
const [x3, y3] = vec.add(vertex, vec.add(vec.sub([x1, y1], vertex), vec.sub([x2, y2], vertex)));
|
|
32379
|
+
|
|
32380
|
+
// Determine whether the arc should be drawn outside the angle
|
|
32381
|
+
const isOutside = shouldDrawArcOutside([x3, y3], vertex, range, [[vertex, point1], [vertex, point2]]);
|
|
32382
|
+
|
|
32383
|
+
// Determine the flags for the arc
|
|
32384
|
+
// If the angle is outside OR reflexive, we want to draw a large arc
|
|
32385
|
+
// If the angle is outside AND reflexive, we want to sweep the arc
|
|
32386
|
+
const largeArcFlag = isOutside || isReflexive ? 1 : 0;
|
|
32387
|
+
const sweepFlag = isOutside && isReflexive ? 1 : 0;
|
|
32388
|
+
|
|
32389
|
+
// Create the SVG path for the arc
|
|
32390
|
+
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
32391
|
+
|
|
32392
|
+
// We only ever want to show whole angles
|
|
32393
|
+
const angleLabel = parseFloat(angle.toFixed(0)); // Only want to show whole angles
|
|
32394
|
+
|
|
32395
|
+
// Calculate the text position based on the angle and whether we allow reflex angles
|
|
32396
|
+
// Let's try the angle bisector method to find the midpoint of the arc
|
|
32397
|
+
const [textX, textY] = calculateBisectorPoint(point1, point2, vertex, isReflexive, allowReflexAngles, radius);
|
|
32398
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("filter", {
|
|
32399
|
+
id: "background",
|
|
32400
|
+
x: "-5%",
|
|
32401
|
+
width: "110%",
|
|
32402
|
+
y: "0%",
|
|
32403
|
+
height: "100%"
|
|
32404
|
+
}, /*#__PURE__*/React.createElement("feFlood", {
|
|
32405
|
+
floodColor: "#FFF",
|
|
32406
|
+
floodOpacity: "0.5"
|
|
32407
|
+
}), /*#__PURE__*/React.createElement("feComposite", {
|
|
32408
|
+
operator: "over",
|
|
32409
|
+
in: "SourceGraphic"
|
|
32410
|
+
}))), isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
32411
|
+
start: [x1, y1],
|
|
32412
|
+
vertex: [x2, y2],
|
|
32413
|
+
end: [x3, y3],
|
|
32414
|
+
className: "arc-right-angle"
|
|
32415
|
+
}) : /*#__PURE__*/React.createElement(Arc, {
|
|
32416
|
+
arc: arc,
|
|
32417
|
+
className: "angle-arc"
|
|
32418
|
+
}), showAngles && /*#__PURE__*/React.createElement(TextLabel, {
|
|
32419
|
+
x: textX,
|
|
32420
|
+
y: textY,
|
|
32421
|
+
color: color.blue
|
|
32422
|
+
}, angleLabel, "\xB0"));
|
|
32423
|
+
};
|
|
32424
|
+
|
|
32425
|
+
/**
|
|
32426
|
+
* This is broken out into its own component so that it can be used for an early return
|
|
32427
|
+
* (see line 55 or https://github.com/Khan/perseus/blob/84bbd882b11d16871e1b813a0b901f3b903d5479/packages/perseus/src/widgets/interactive-graphs/graphs/components/angle.tsx#L53-L57)
|
|
32428
|
+
*/
|
|
32429
|
+
const RightAngleSquare = ({
|
|
32430
|
+
start: [x1, y1],
|
|
32431
|
+
vertex: [x2, y2],
|
|
32432
|
+
end: [x3, y3],
|
|
32433
|
+
className
|
|
32434
|
+
}) => /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
32435
|
+
d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
|
|
32436
|
+
strokeWidth: 0.02,
|
|
32437
|
+
fill: "none",
|
|
32438
|
+
className: className
|
|
32439
|
+
}));
|
|
32440
|
+
|
|
32441
|
+
// We're conditionally adding the class name here so that we can style the arc differently
|
|
32442
|
+
// based on whether it's an angle or a polygon angle
|
|
32443
|
+
const Arc = ({
|
|
32444
|
+
arc,
|
|
32445
|
+
className
|
|
32446
|
+
}) => {
|
|
32447
|
+
return /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
32448
|
+
d: arc,
|
|
32449
|
+
strokeWidth: 0.02,
|
|
32450
|
+
fill: "none",
|
|
32451
|
+
className: className
|
|
32452
|
+
}));
|
|
32453
|
+
};
|
|
32454
|
+
const isRightPolygonAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
|
|
32455
|
+
const isRightAngle = angle => Math.round(angle) === 90;
|
|
32456
|
+
|
|
32457
|
+
/**
|
|
32458
|
+
* Determines if an angle is an inside (false) or outside (true) angle.
|
|
32459
|
+
* They way, we know to flip the `largeArc` and `sweepArc` flags.
|
|
32460
|
+
* Uses the priciple that a ray from a point inside a polygon will intersect
|
|
32461
|
+
* with an odd number of lines, while a ray from a point outside the polygon
|
|
32462
|
+
* will intersect with an even number of lines.
|
|
32463
|
+
* https://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon
|
|
32464
|
+
*/
|
|
32465
|
+
const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
|
|
32466
|
+
// Create a ray from the midpoint (inside angle) to the edge of the range
|
|
32467
|
+
const rangeIntersectionPoint = getIntersectionOfRayWithBox(midpoint, vertex, range);
|
|
32468
|
+
let lineIntersectionCount = 0;
|
|
32469
|
+
polygonLines.forEach(line => segmentsIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
|
|
32470
|
+
|
|
32471
|
+
// If the number of intersections is even, the angle is inside the polygon
|
|
32472
|
+
return !isEven(lineIntersectionCount);
|
|
32473
|
+
};
|
|
32474
|
+
const isEven = n => n % 2 === 0;
|
|
32475
|
+
|
|
32476
|
+
// This function calculates the bisector point of an angle formed by two points
|
|
32477
|
+
// and a vertex. It is used to position the angle label so that it is always
|
|
32478
|
+
// outside the arc at the exact midpoint of the angle.
|
|
32479
|
+
// Reflex angles make use of polar coordinates to determine the correct direction
|
|
32480
|
+
function calculateBisectorPoint(point1, point2, vertex, isReflex, allowReflex, arcRadius) {
|
|
32481
|
+
const [originX, originY] = vertex;
|
|
32482
|
+
const [x1, y1] = point1;
|
|
32483
|
+
const [x2, y2] = point2;
|
|
32484
|
+
|
|
32485
|
+
// Convert the Cartesian Coordinates to vectors
|
|
32486
|
+
const vectorA = [x1 - originX, y1 - originY];
|
|
32487
|
+
const vectorB = [x2 - originX, y2 - originY];
|
|
32488
|
+
|
|
32489
|
+
// Calculate the average angle of the vectors
|
|
32490
|
+
// so that we can point the bisector in the correct direction using polar coordinates
|
|
32491
|
+
const angleA = Math.atan2(vectorA[1], vectorA[0]);
|
|
32492
|
+
const angleB = Math.atan2(vectorB[1], vectorB[0]);
|
|
32493
|
+
|
|
32494
|
+
// Calculate the average of these two angles
|
|
32495
|
+
let averageAngle = (angleA + angleB) / 2;
|
|
32496
|
+
const angleRadians = Math.abs(angleA - angleB);
|
|
32497
|
+
|
|
32498
|
+
// Adjust the average angle to point in the correct direction
|
|
32499
|
+
// and to account for reflex angles and a bug with 180 degrees
|
|
32500
|
+
// that occurs due to our angleOffsetDegrees option in the graph
|
|
32501
|
+
if (allowReflex) {
|
|
32502
|
+
// If the resulting angles are less than 180 degrees,
|
|
32503
|
+
// or if the user has flipped the points on the interactive element
|
|
32504
|
+
// ensure that we have the correct direction for the reflex angle
|
|
32505
|
+
if (angleRadians <= Math.PI && isReflex || angleB > angleA) {
|
|
32506
|
+
averageAngle += Math.PI; // Add 180 degrees
|
|
32507
|
+
}
|
|
32508
|
+
} else {
|
|
32509
|
+
// If the resulting angles are greater than 180 degrees, adjust
|
|
32510
|
+
// to get the correct angle, as we already know that the angle is not reflexive
|
|
32511
|
+
// This fixes a bug that occurs at 180 degrees due to our angleOffsetDegrees option in the graph
|
|
32512
|
+
if (angleRadians > Math.PI) {
|
|
32513
|
+
averageAngle -= Math.PI; // Subract 180 degrees
|
|
32514
|
+
}
|
|
32515
|
+
}
|
|
32516
|
+
|
|
32517
|
+
// Convert the average angle back to Cartesian coordinates
|
|
32518
|
+
const sum = [Math.cos(averageAngle), Math.sin(averageAngle)];
|
|
32519
|
+
|
|
32520
|
+
// Calculate the magnitude of the sum to normalize it
|
|
32521
|
+
const sumMagnitude = Math.sqrt(sum[0] ** 2 + sum[1] ** 2);
|
|
32522
|
+
const bisectorDirection = [sum[0] / sumMagnitude, sum[1] / sumMagnitude];
|
|
32523
|
+
|
|
32524
|
+
// Calculate the initial distance of the bisector direction from the origin
|
|
32525
|
+
const initialDistance = Math.sqrt(bisectorDirection[0] ** 2 + bisectorDirection[1] ** 2);
|
|
32526
|
+
|
|
32527
|
+
// Determine the minimum radius to ensure that the text is always outside the arc
|
|
32528
|
+
const requiredDistance = arcRadius * 1.75;
|
|
32529
|
+
let radius = requiredDistance / initialDistance;
|
|
32530
|
+
|
|
32531
|
+
// Apply radius only if the initial distance is less than required
|
|
32532
|
+
if (initialDistance >= requiredDistance) {
|
|
32533
|
+
radius = 1; // Use the unit vector as is
|
|
32534
|
+
}
|
|
32535
|
+
|
|
32536
|
+
// Scale the bisector direction by the radius
|
|
32537
|
+
const bisectorPoint = [bisectorDirection[0] * radius, bisectorDirection[1] * radius];
|
|
32538
|
+
|
|
32539
|
+
// Add the vertex to the bisector point to get the final position
|
|
32540
|
+
// to ensure that the angle label moves with the interactive element
|
|
32541
|
+
const scaledBisector = vec.add(bisectorPoint, vertex);
|
|
32542
|
+
return scaledBisector;
|
|
32543
|
+
}
|
|
32544
|
+
|
|
32132
32545
|
/**
|
|
32133
32546
|
* 44 is touch best practice and AAA compliant for WCAG
|
|
32134
32547
|
* https://www.w3.org/WAI/WCAG21/Understanding/target-size.html
|
|
@@ -32334,6 +32747,14 @@ function range(min, max, step = 1) {
|
|
|
32334
32747
|
return result;
|
|
32335
32748
|
}
|
|
32336
32749
|
|
|
32750
|
+
function srFormatNumber(a, locale) {
|
|
32751
|
+
// adding zero here converts negative zero to positive zero.
|
|
32752
|
+
return (0 + a).toLocaleString(locale, {
|
|
32753
|
+
maximumFractionDigits: 3,
|
|
32754
|
+
useGrouping: false // no thousands separators
|
|
32755
|
+
});
|
|
32756
|
+
}
|
|
32757
|
+
|
|
32337
32758
|
// The hitbox size of 48px by 48px is preserved from the legacy interactive
|
|
32338
32759
|
// graph.
|
|
32339
32760
|
const hitboxSizePx = 48;
|
|
@@ -32448,6 +32869,10 @@ function useControlPoint(params) {
|
|
|
32448
32869
|
onBlur = noop$1,
|
|
32449
32870
|
forwardedRef = noop$1
|
|
32450
32871
|
} = params;
|
|
32872
|
+
const {
|
|
32873
|
+
strings,
|
|
32874
|
+
locale
|
|
32875
|
+
} = usePerseusI18n();
|
|
32451
32876
|
const [focused, setFocused] = useState(false);
|
|
32452
32877
|
const focusableHandleRef = useRef(null);
|
|
32453
32878
|
useDraggable({
|
|
@@ -32473,6 +32898,15 @@ function useControlPoint(params) {
|
|
|
32473
32898
|
className: "movable-point__focusable-handle",
|
|
32474
32899
|
tabIndex: disableKeyboardInteraction ? -1 : 0,
|
|
32475
32900
|
ref: focusableHandleRef,
|
|
32901
|
+
role: "button",
|
|
32902
|
+
"aria-label": strings.srPointAtCoordinates({
|
|
32903
|
+
x: srFormatNumber(point[X], locale),
|
|
32904
|
+
y: srFormatNumber(point[Y], locale)
|
|
32905
|
+
})
|
|
32906
|
+
// aria-live="assertive" causes the new location of the point to be
|
|
32907
|
+
// announced immediately on move.
|
|
32908
|
+
,
|
|
32909
|
+
"aria-live": "assertive",
|
|
32476
32910
|
onFocus: event => {
|
|
32477
32911
|
onFocus(event);
|
|
32478
32912
|
setFocused(true);
|
|
@@ -32653,25 +33087,338 @@ function trimRange(range, graphDimensionsInPixels) {
|
|
|
32653
33087
|
return inset([graphUnitsToTrimX, graphUnitsToTrimY], range);
|
|
32654
33088
|
}
|
|
32655
33089
|
|
|
32656
|
-
const
|
|
33090
|
+
const MovablePoint$1 = /*#__PURE__*/React.forwardRef((props, pointRef) => {
|
|
32657
33091
|
const {
|
|
32658
|
-
|
|
33092
|
+
visiblePoint,
|
|
33093
|
+
focusableHandle
|
|
33094
|
+
} = useControlPoint(_extends({}, props, {
|
|
33095
|
+
forwardedRef: pointRef
|
|
33096
|
+
}));
|
|
33097
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, focusableHandle, visiblePoint);
|
|
33098
|
+
});
|
|
33099
|
+
|
|
33100
|
+
function renderAngleGraph(state, dispatch) {
|
|
33101
|
+
return {
|
|
33102
|
+
graph: /*#__PURE__*/React.createElement(AngleGraph, {
|
|
33103
|
+
graphState: state,
|
|
33104
|
+
dispatch: dispatch
|
|
33105
|
+
}),
|
|
33106
|
+
interactiveElementsDescription: null
|
|
33107
|
+
};
|
|
33108
|
+
}
|
|
33109
|
+
function AngleGraph(props) {
|
|
33110
|
+
const {
|
|
33111
|
+
dispatch,
|
|
33112
|
+
graphState
|
|
32659
33113
|
} = props;
|
|
32660
33114
|
const {
|
|
32661
|
-
|
|
32662
|
-
} =
|
|
32663
|
-
|
|
32664
|
-
|
|
32665
|
-
|
|
32666
|
-
|
|
32667
|
-
|
|
32668
|
-
|
|
32669
|
-
|
|
32670
|
-
|
|
33115
|
+
graphDimensionsInPixels
|
|
33116
|
+
} = useGraphConfig();
|
|
33117
|
+
const {
|
|
33118
|
+
coords,
|
|
33119
|
+
showAngles,
|
|
33120
|
+
range,
|
|
33121
|
+
allowReflexAngles,
|
|
33122
|
+
snapDegrees
|
|
33123
|
+
} = graphState;
|
|
33124
|
+
|
|
33125
|
+
// Break the coords into the two end points and the center point
|
|
33126
|
+
const endPoints = [coords[0], coords[2]];
|
|
33127
|
+
const centerPoint = coords[1];
|
|
33128
|
+
|
|
33129
|
+
// Convert the vectors to pixels for rendering the svg lines
|
|
33130
|
+
const angleLines = [[centerPoint, endPoints[0]], [centerPoint, endPoints[1]]];
|
|
33131
|
+
const linePixelCoords = [useTransformVectorsToPixels(centerPoint, endPoints[0]), useTransformVectorsToPixels(centerPoint, endPoints[1])];
|
|
33132
|
+
|
|
33133
|
+
// Create the SVG lines for the angle
|
|
33134
|
+
const svgLines = linePixelCoords.map(([startPtPx, endPtPx], i) => {
|
|
33135
|
+
const trimmedRange = trimRange(range, graphDimensionsInPixels);
|
|
33136
|
+
const endExtend = getIntersectionOfRayWithBox(angleLines[i][0], angleLines[i][1], trimmedRange);
|
|
33137
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
33138
|
+
key: `line-${i}`
|
|
33139
|
+
}, /*#__PURE__*/React.createElement(SVGLine, {
|
|
33140
|
+
start: startPtPx,
|
|
33141
|
+
end: endPtPx,
|
|
33142
|
+
style: {
|
|
33143
|
+
stroke: "var(--movable-line-stroke-color)",
|
|
33144
|
+
strokeWidth: "var(--movable-line-stroke-weight)"
|
|
33145
|
+
}
|
|
33146
|
+
}), /*#__PURE__*/React.createElement(Vector, {
|
|
33147
|
+
tail: angleLines[i][1],
|
|
33148
|
+
tip: endExtend,
|
|
33149
|
+
color: "var(--movable-line-stroke-color)"
|
|
33150
|
+
}));
|
|
33151
|
+
});
|
|
33152
|
+
|
|
33153
|
+
// Create the angle indicator parameters
|
|
33154
|
+
const angleParams = {
|
|
33155
|
+
vertex: centerPoint,
|
|
33156
|
+
coords: endPoints,
|
|
33157
|
+
allowReflexAngles: allowReflexAngles || false,
|
|
33158
|
+
// Whether to allow reflex angles or not
|
|
33159
|
+
snapDegrees: snapDegrees || 1,
|
|
33160
|
+
// The multiple of degrees to snap to
|
|
33161
|
+
range: range,
|
|
33162
|
+
showAngles: showAngles || false // Whether to show the angle or not
|
|
33163
|
+
};
|
|
33164
|
+
|
|
33165
|
+
// Render the lines, angle, and then movable points
|
|
33166
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, svgLines, /*#__PURE__*/React.createElement(Angle, angleParams), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33167
|
+
point: coords[1],
|
|
33168
|
+
constrain: p => p,
|
|
33169
|
+
onMove: destination => dispatch(actions.angle.movePoint(1, destination))
|
|
33170
|
+
}), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33171
|
+
point: coords[0],
|
|
33172
|
+
constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
|
|
33173
|
+
onMove: destination => dispatch(actions.angle.movePoint(0, destination))
|
|
33174
|
+
}), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33175
|
+
point: coords[2],
|
|
33176
|
+
constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
|
|
33177
|
+
onMove: destination => dispatch(actions.angle.movePoint(2, destination))
|
|
33178
|
+
}));
|
|
33179
|
+
}
|
|
33180
|
+
const positiveX = [1, 0];
|
|
33181
|
+
const negativeX = [-1, 0];
|
|
33182
|
+
const positiveY = [0, 1];
|
|
33183
|
+
const negativeY = [0, -1];
|
|
33184
|
+
function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
33185
|
+
var _findIntersectionOfRa, _findIntersectionOfRa2, _findIntersectionOfRa3, _findIntersectionOfRa4;
|
|
33186
|
+
const currentAngle = calculateAngleInDegrees(vec.sub(sidePoint, vertex));
|
|
33187
|
+
|
|
33188
|
+
// Find the rays that start at the current point and point up, down, left
|
|
33189
|
+
// and right.
|
|
33190
|
+
const leftRay = [sidePoint, vec.add(sidePoint, negativeX)];
|
|
33191
|
+
const rightRay = [sidePoint, vec.add(sidePoint, positiveX)];
|
|
33192
|
+
const upRay = [sidePoint, vec.add(sidePoint, positiveY)];
|
|
33193
|
+
const downRay = [sidePoint, vec.add(sidePoint, negativeY)];
|
|
33194
|
+
|
|
33195
|
+
// find the angles that lie one snap step clockwise and counter-clockwise
|
|
33196
|
+
// from the current angle. These are the angles to which the side can be
|
|
33197
|
+
// moved.
|
|
33198
|
+
const oneStepCounterClockwise = currentAngle + snapDegrees;
|
|
33199
|
+
const oneStepClockwise = currentAngle - snapDegrees;
|
|
33200
|
+
|
|
33201
|
+
// find the rays that start from the vertex and point in the direction of
|
|
33202
|
+
// the angles we just computed.
|
|
33203
|
+
const counterClockwiseRay = [vertex, vec.add(vertex, polar(1, oneStepCounterClockwise))];
|
|
33204
|
+
const clockwiseRay = [vertex, vec.add(vertex, polar(1, oneStepClockwise))];
|
|
33205
|
+
|
|
33206
|
+
// find the intersections of those rays with the horizontal and vertical
|
|
33207
|
+
// rays extending from the sidePoint. These intersections are the points to
|
|
33208
|
+
// which sidePoint can move that will result in a rotation of `snapDegrees`.
|
|
33209
|
+
const left = (_findIntersectionOfRa = findIntersectionOfRays(leftRay, counterClockwiseRay)) != null ? _findIntersectionOfRa : findIntersectionOfRays(leftRay, clockwiseRay);
|
|
33210
|
+
const right = (_findIntersectionOfRa2 = findIntersectionOfRays(rightRay, counterClockwiseRay)) != null ? _findIntersectionOfRa2 : findIntersectionOfRays(rightRay, clockwiseRay);
|
|
33211
|
+
const up = (_findIntersectionOfRa3 = findIntersectionOfRays(upRay, counterClockwiseRay)) != null ? _findIntersectionOfRa3 : findIntersectionOfRays(upRay, clockwiseRay);
|
|
33212
|
+
const down = (_findIntersectionOfRa4 = findIntersectionOfRays(downRay, counterClockwiseRay)) != null ? _findIntersectionOfRa4 : findIntersectionOfRays(downRay, clockwiseRay);
|
|
33213
|
+
return {
|
|
33214
|
+
up: up != null ? up : sidePoint,
|
|
33215
|
+
down: down != null ? down : sidePoint,
|
|
33216
|
+
left: left != null ? left : sidePoint,
|
|
33217
|
+
right: right != null ? right : sidePoint
|
|
33218
|
+
};
|
|
33219
|
+
}
|
|
33220
|
+
|
|
33221
|
+
function getGradableGraph(state, initialGraph) {
|
|
33222
|
+
if (!state.hasBeenInteractedWith) {
|
|
33223
|
+
return _extends({}, initialGraph);
|
|
33224
|
+
}
|
|
33225
|
+
if (initialGraph.type === "linear-system" && state.type === "linear-system") {
|
|
33226
|
+
return _extends({}, initialGraph, {
|
|
33227
|
+
coords: state.coords
|
|
33228
|
+
});
|
|
33229
|
+
}
|
|
33230
|
+
if (state.type === "segment" && initialGraph.type === "segment") {
|
|
33231
|
+
return _extends({}, initialGraph, {
|
|
33232
|
+
coords: state.coords
|
|
33233
|
+
});
|
|
33234
|
+
}
|
|
33235
|
+
if (state.type === "linear" && initialGraph.type === "linear") {
|
|
33236
|
+
return _extends({}, initialGraph, {
|
|
33237
|
+
coords: state.coords
|
|
33238
|
+
});
|
|
33239
|
+
}
|
|
33240
|
+
if (state.type === "ray" && initialGraph.type === "ray") {
|
|
33241
|
+
return _extends({}, initialGraph, {
|
|
33242
|
+
coords: state.coords
|
|
33243
|
+
});
|
|
33244
|
+
}
|
|
33245
|
+
if (state.type === "polygon" && initialGraph.type === "polygon") {
|
|
33246
|
+
return _extends({}, initialGraph, {
|
|
33247
|
+
coords: state.coords
|
|
33248
|
+
});
|
|
33249
|
+
}
|
|
33250
|
+
if (state.type === "point" && initialGraph.type === "point") {
|
|
33251
|
+
return _extends({}, initialGraph, {
|
|
33252
|
+
coords: state.coords
|
|
33253
|
+
});
|
|
33254
|
+
}
|
|
33255
|
+
if (state.type === "circle" && initialGraph.type === "circle") {
|
|
33256
|
+
return _extends({}, initialGraph, {
|
|
33257
|
+
center: state.center,
|
|
33258
|
+
radius: getRadius(state)
|
|
33259
|
+
});
|
|
33260
|
+
}
|
|
33261
|
+
if (state.type === "quadratic" && initialGraph.type === "quadratic") {
|
|
33262
|
+
return _extends({}, initialGraph, {
|
|
33263
|
+
coords: state.coords
|
|
33264
|
+
});
|
|
33265
|
+
}
|
|
33266
|
+
if (state.type === "sinusoid" && initialGraph.type === "sinusoid") {
|
|
33267
|
+
return _extends({}, initialGraph, {
|
|
33268
|
+
coords: state.coords
|
|
33269
|
+
});
|
|
33270
|
+
}
|
|
33271
|
+
if (state.type === "angle" && initialGraph.type === "angle") {
|
|
33272
|
+
// We're going to reverse the coords for scoring if the angle is clockwise and
|
|
33273
|
+
// we don't allow reflex angles. This is because the angle is largely defined
|
|
33274
|
+
// by the order of the points in the coords array, and we want to maintain
|
|
33275
|
+
// the same angle scoring with the legacy graph (which had a bug).
|
|
33276
|
+
// (LEMS-2190): When we remove the legacy graph, move this logic to the scoring function.
|
|
33277
|
+
const areClockwise = clockwise$1([state.coords[0], state.coords[2], state.coords[1]]);
|
|
33278
|
+
const shouldReverseCoords = areClockwise && !state.allowReflexAngles;
|
|
33279
|
+
const coords = shouldReverseCoords ? state.coords.slice().reverse() : state.coords;
|
|
33280
|
+
return _extends({}, initialGraph, {
|
|
33281
|
+
coords,
|
|
33282
|
+
allowReflexAngles: state.allowReflexAngles
|
|
33283
|
+
});
|
|
33284
|
+
}
|
|
33285
|
+
if (state.type === "none" && initialGraph.type === "none") {
|
|
33286
|
+
return {
|
|
33287
|
+
type: "none"
|
|
33288
|
+
};
|
|
33289
|
+
}
|
|
33290
|
+
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
33291
|
+
}
|
|
33292
|
+
|
|
33293
|
+
/**
|
|
33294
|
+
* determine radius of a circle graph
|
|
33295
|
+
*
|
|
33296
|
+
* @param graph - the graph object containing the circle
|
|
33297
|
+
* @returns the radius of the circle
|
|
33298
|
+
*/
|
|
33299
|
+
function getRadius(graph) {
|
|
33300
|
+
const [centerX, centerY] = graph.center;
|
|
33301
|
+
const [edgeX, edgeY] = graph.radiusPoint;
|
|
33302
|
+
return Math.sqrt(Math.pow(edgeX - centerX, 2) + Math.pow(edgeY - centerY, 2));
|
|
33303
|
+
}
|
|
33304
|
+
|
|
33305
|
+
function renderCircleGraph(state, dispatch) {
|
|
33306
|
+
return {
|
|
33307
|
+
graph: /*#__PURE__*/React.createElement(CircleGraph, {
|
|
33308
|
+
graphState: state,
|
|
33309
|
+
dispatch: dispatch
|
|
33310
|
+
}),
|
|
33311
|
+
interactiveElementsDescription: null
|
|
33312
|
+
};
|
|
33313
|
+
}
|
|
33314
|
+
function CircleGraph(props) {
|
|
33315
|
+
const {
|
|
33316
|
+
dispatch,
|
|
33317
|
+
graphState
|
|
33318
|
+
} = props;
|
|
33319
|
+
const {
|
|
33320
|
+
center,
|
|
33321
|
+
radiusPoint
|
|
33322
|
+
} = graphState;
|
|
33323
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableCircle, {
|
|
33324
|
+
center: center,
|
|
33325
|
+
radius: getRadius(graphState),
|
|
33326
|
+
onMove: c => dispatch(actions.circle.moveCenter(c))
|
|
33327
|
+
}), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33328
|
+
point: radiusPoint,
|
|
33329
|
+
cursor: "ew-resize",
|
|
33330
|
+
onMove: newRadiusPoint => {
|
|
33331
|
+
dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
|
|
32671
33332
|
}
|
|
32672
|
-
}))
|
|
32673
|
-
}
|
|
33333
|
+
}));
|
|
33334
|
+
}
|
|
33335
|
+
function MovableCircle(props) {
|
|
33336
|
+
const {
|
|
33337
|
+
center,
|
|
33338
|
+
radius,
|
|
33339
|
+
onMove
|
|
33340
|
+
} = props;
|
|
33341
|
+
const {
|
|
33342
|
+
snapStep,
|
|
33343
|
+
disableKeyboardInteraction
|
|
33344
|
+
} = useGraphConfig();
|
|
33345
|
+
const draggableRef = useRef(null);
|
|
33346
|
+
const {
|
|
33347
|
+
dragging
|
|
33348
|
+
} = useDraggable({
|
|
33349
|
+
gestureTarget: draggableRef,
|
|
33350
|
+
point: center,
|
|
33351
|
+
onMove,
|
|
33352
|
+
constrainKeyboardMovement: p => snap(snapStep, p)
|
|
33353
|
+
});
|
|
33354
|
+
const [centerPx] = useTransformVectorsToPixels(center);
|
|
33355
|
+
const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
|
|
33356
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
33357
|
+
ref: draggableRef,
|
|
33358
|
+
tabIndex: disableKeyboardInteraction ? -1 : 0,
|
|
33359
|
+
className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
|
|
33360
|
+
}, /*#__PURE__*/React.createElement("ellipse", {
|
|
33361
|
+
className: "focus-ring",
|
|
33362
|
+
cx: centerPx[X],
|
|
33363
|
+
cy: centerPx[Y],
|
|
33364
|
+
rx: radiiPx[X] + 3,
|
|
33365
|
+
ry: radiiPx[Y] + 3
|
|
33366
|
+
}), /*#__PURE__*/React.createElement("ellipse", {
|
|
33367
|
+
className: "circle",
|
|
33368
|
+
cx: centerPx[X],
|
|
33369
|
+
cy: centerPx[Y],
|
|
33370
|
+
rx: radiiPx[X],
|
|
33371
|
+
ry: radiiPx[Y]
|
|
33372
|
+
}), /*#__PURE__*/React.createElement(DragHandle, {
|
|
33373
|
+
center: center
|
|
33374
|
+
}));
|
|
33375
|
+
}
|
|
33376
|
+
const dragHandleDimensions = [24, 14];
|
|
33377
|
+
const dragHandleDotPositions = crossProduct([-4.4, 0, 4.4], [-2.1, 2.1]);
|
|
33378
|
+
function DragHandle(props) {
|
|
33379
|
+
const {
|
|
33380
|
+
center
|
|
33381
|
+
} = props;
|
|
33382
|
+
const cornerRadius = Math.min(...dragHandleDimensions) / 2;
|
|
33383
|
+
const [centerPx] = useTransformVectorsToPixels(center);
|
|
33384
|
+
const topLeft = vec.sub(centerPx, vec.scale(dragHandleDimensions, 0.5));
|
|
33385
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("rect", {
|
|
33386
|
+
className: "movable-circle-handle",
|
|
33387
|
+
x: topLeft[X],
|
|
33388
|
+
y: topLeft[Y],
|
|
33389
|
+
width: dragHandleDimensions[X],
|
|
33390
|
+
height: dragHandleDimensions[Y],
|
|
33391
|
+
rx: cornerRadius,
|
|
33392
|
+
ry: cornerRadius
|
|
33393
|
+
}), dragHandleDotPositions.map(offsetPx => {
|
|
33394
|
+
const [xPx, yPx] = vec.add(offsetPx, centerPx);
|
|
33395
|
+
return /*#__PURE__*/React.createElement("circle", {
|
|
33396
|
+
key: `circle-${xPx}-${yPx}`,
|
|
33397
|
+
className: "movable-circle-handle-dot",
|
|
33398
|
+
cx: xPx,
|
|
33399
|
+
cy: yPx
|
|
33400
|
+
});
|
|
33401
|
+
}));
|
|
33402
|
+
}
|
|
33403
|
+
function crossProduct(as, bs) {
|
|
33404
|
+
const result = [];
|
|
33405
|
+
for (const a of as) {
|
|
33406
|
+
for (const b of bs) {
|
|
33407
|
+
result.push([a, b]);
|
|
33408
|
+
}
|
|
33409
|
+
}
|
|
33410
|
+
return result;
|
|
33411
|
+
}
|
|
32674
33412
|
|
|
33413
|
+
function renderLinearGraph(state, dispatch) {
|
|
33414
|
+
return {
|
|
33415
|
+
graph: /*#__PURE__*/React.createElement(LinearGraph, {
|
|
33416
|
+
graphState: state,
|
|
33417
|
+
dispatch: dispatch
|
|
33418
|
+
}),
|
|
33419
|
+
interactiveElementsDescription: null
|
|
33420
|
+
};
|
|
33421
|
+
}
|
|
32675
33422
|
const LinearGraph = (props, key) => {
|
|
32676
33423
|
const {
|
|
32677
33424
|
dispatch
|
|
@@ -32698,6 +33445,15 @@ const LinearGraph = (props, key) => {
|
|
|
32698
33445
|
});
|
|
32699
33446
|
};
|
|
32700
33447
|
|
|
33448
|
+
function renderLinearSystemGraph(state, dispatch) {
|
|
33449
|
+
return {
|
|
33450
|
+
graph: /*#__PURE__*/React.createElement(LinearSystemGraph, {
|
|
33451
|
+
graphState: state,
|
|
33452
|
+
dispatch: dispatch
|
|
33453
|
+
}),
|
|
33454
|
+
interactiveElementsDescription: null
|
|
33455
|
+
};
|
|
33456
|
+
}
|
|
32701
33457
|
const LinearSystemGraph = props => {
|
|
32702
33458
|
const {
|
|
32703
33459
|
dispatch
|
|
@@ -32720,359 +33476,121 @@ const LinearSystemGraph = props => {
|
|
|
32720
33476
|
})), ";");
|
|
32721
33477
|
};
|
|
32722
33478
|
|
|
32723
|
-
|
|
33479
|
+
function renderPointGraph(state, dispatch) {
|
|
33480
|
+
return {
|
|
33481
|
+
graph: /*#__PURE__*/React.createElement(PointGraph, {
|
|
33482
|
+
graphState: state,
|
|
33483
|
+
dispatch: dispatch
|
|
33484
|
+
}),
|
|
33485
|
+
interactiveElementsDescription: /*#__PURE__*/React.createElement(PointGraphDescription, {
|
|
33486
|
+
state: state
|
|
33487
|
+
})
|
|
33488
|
+
};
|
|
33489
|
+
}
|
|
33490
|
+
function PointGraph(props) {
|
|
33491
|
+
const numPoints = props.graphState.numPoints;
|
|
33492
|
+
if (numPoints === "unlimited") {
|
|
33493
|
+
return UnlimitedPointGraph(props);
|
|
33494
|
+
}
|
|
33495
|
+
return LimitedPointGraph(props);
|
|
33496
|
+
}
|
|
33497
|
+
function LimitedPointGraph(props) {
|
|
32724
33498
|
const {
|
|
32725
33499
|
dispatch
|
|
32726
33500
|
} = props;
|
|
33501
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33502
|
+
key: i,
|
|
33503
|
+
point: point,
|
|
33504
|
+
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
|
|
33505
|
+
})));
|
|
33506
|
+
}
|
|
33507
|
+
function UnlimitedPointGraph(props) {
|
|
32727
33508
|
const {
|
|
32728
|
-
|
|
32729
|
-
} = props
|
|
32730
|
-
const
|
|
32731
|
-
const
|
|
32732
|
-
|
|
32733
|
-
|
|
32734
|
-
|
|
32735
|
-
|
|
32736
|
-
|
|
32737
|
-
|
|
32738
|
-
|
|
32739
|
-
|
|
32740
|
-
|
|
32741
|
-
|
|
32742
|
-
|
|
32743
|
-
|
|
32744
|
-
|
|
32745
|
-
/**
|
|
32746
|
-
* Wrapper with Mafs transform styles for SVG elements.
|
|
32747
|
-
* Scales and translates the SVG element to match the Mafs view.
|
|
32748
|
-
* Use if you do not need/want to use the hook in `use-transform.ts`.
|
|
32749
|
-
*/
|
|
32750
|
-
const MafsCssTransformWrapper = ({
|
|
32751
|
-
children
|
|
32752
|
-
}) => /*#__PURE__*/React.createElement("g", {
|
|
32753
|
-
style: {
|
|
32754
|
-
transform: `var(--mafs-view-transform) var(--mafs-user-transform)`
|
|
32755
|
-
}
|
|
32756
|
-
}, children);
|
|
32757
|
-
|
|
32758
|
-
const _excluded$1 = ["children"];
|
|
32759
|
-
const TextLabel = _ref => {
|
|
32760
|
-
let {
|
|
32761
|
-
children
|
|
32762
|
-
} = _ref,
|
|
32763
|
-
rest = _objectWithoutPropertiesLoose(_ref, _excluded$1);
|
|
32764
|
-
return /*#__PURE__*/React__default.createElement(Text$1, _extends({
|
|
32765
|
-
size: 16,
|
|
32766
|
-
svgTextProps: {
|
|
32767
|
-
filter: "url(#background)",
|
|
32768
|
-
fontWeight: "bold"
|
|
33509
|
+
dispatch
|
|
33510
|
+
} = props;
|
|
33511
|
+
const graphConfig = useGraphConfig();
|
|
33512
|
+
const {
|
|
33513
|
+
range: [[minX, maxX], [minY, maxY]]
|
|
33514
|
+
} = graphConfig;
|
|
33515
|
+
const width = maxX - minX;
|
|
33516
|
+
const height = maxY - minY;
|
|
33517
|
+
const [[widthPx, heightPx]] = useTransformDimensionsToPixels([width, height]);
|
|
33518
|
+
const [[left, top]] = useTransformVectorsToPixels([minX, maxY]);
|
|
33519
|
+
const itemsRef = React.useRef([]);
|
|
33520
|
+
React.useEffect(() => {
|
|
33521
|
+
const focusedIndex = props.graphState.focusedPointIndex;
|
|
33522
|
+
if (focusedIndex != null) {
|
|
33523
|
+
var _itemsRef$current$foc;
|
|
33524
|
+
(_itemsRef$current$foc = itemsRef.current[focusedIndex]) == null || _itemsRef$current$foc.focus();
|
|
32769
33525
|
}
|
|
32770
|
-
},
|
|
32771
|
-
|
|
32772
|
-
|
|
32773
|
-
|
|
32774
|
-
|
|
32775
|
-
|
|
32776
|
-
|
|
32777
|
-
|
|
32778
|
-
|
|
32779
|
-
|
|
32780
|
-
|
|
32781
|
-
|
|
32782
|
-
|
|
32783
|
-
|
|
32784
|
-
|
|
32785
|
-
|
|
32786
|
-
const PolygonAngle = ({
|
|
32787
|
-
centerPoint,
|
|
32788
|
-
endPoints,
|
|
32789
|
-
range,
|
|
32790
|
-
polygonLines,
|
|
32791
|
-
showAngles,
|
|
32792
|
-
snapTo
|
|
32793
|
-
}) => {
|
|
32794
|
-
const [centerX, centerY] = centerPoint;
|
|
32795
|
-
const areClockwise = clockwise$1([centerPoint, ...endPoints]);
|
|
32796
|
-
const [[startX, startY], [endX, endY]] = areClockwise ? endPoints : endPoints.reverse();
|
|
32797
|
-
const radius = 0.3;
|
|
32798
|
-
const a = vec.dist(centerPoint, endPoints[0]);
|
|
32799
|
-
const b = vec.dist(centerPoint, endPoints[1]);
|
|
32800
|
-
const c = vec.dist(endPoints[0], endPoints[1]);
|
|
32801
|
-
|
|
32802
|
-
// Law of cosines
|
|
32803
|
-
const angle = Math.acos((a ** 2 + b ** 2 - c ** 2) / (2 * a * b));
|
|
32804
|
-
const y1 = centerY + (startY - centerY) / a * radius;
|
|
32805
|
-
const x2 = centerX + (endX - centerX) / b * radius;
|
|
32806
|
-
const x1 = centerX + (startX - centerX) / a * radius;
|
|
32807
|
-
const y2 = centerY + (endY - centerY) / b * radius;
|
|
32808
|
-
|
|
32809
|
-
// Fourth point that would create a rhomboid
|
|
32810
|
-
// Used to calculate the inside of the angle and to create square
|
|
32811
|
-
const [x3, y3] = vec.add(centerPoint, vec.add(vec.sub([x1, y1], centerPoint), vec.sub([x2, y2], centerPoint)));
|
|
32812
|
-
if (!showAngles) {
|
|
32813
|
-
return isRightPolygonAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
32814
|
-
start: [x1, y1],
|
|
32815
|
-
vertex: [x2, y2],
|
|
32816
|
-
end: [x3, y3]
|
|
32817
|
-
}) : null;
|
|
32818
|
-
}
|
|
32819
|
-
|
|
32820
|
-
// Midpoint betwen ends of arc
|
|
32821
|
-
const isOutside = shouldDrawArcOutside([x3, y3], centerPoint, range, polygonLines);
|
|
32822
|
-
const largeArcFlag = isOutside ? 1 : 0;
|
|
32823
|
-
const sweepFlag = isOutside ? 1 : 0;
|
|
32824
|
-
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
32825
|
-
let angleInDegrees = angle * (180 / Math.PI);
|
|
32826
|
-
// If we have triggered "largArcFlag", the angle should be greater than 180
|
|
32827
|
-
if (isOutside) {
|
|
32828
|
-
angleInDegrees = 360 - angleInDegrees;
|
|
32829
|
-
}
|
|
32830
|
-
|
|
32831
|
-
// Only want to show whole numbers when in angles mode
|
|
32832
|
-
const angleLabelNumber = parseFloat(angleInDegrees.toFixed(snapTo === "angles" ? 0 : 1));
|
|
32833
|
-
const angleLabel = Number.isInteger(angleLabelNumber) ? angleLabelNumber : "≈ " + angleLabelNumber;
|
|
32834
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("filter", {
|
|
32835
|
-
id: "background",
|
|
32836
|
-
x: "-5%",
|
|
32837
|
-
width: "110%",
|
|
32838
|
-
y: "0%",
|
|
32839
|
-
height: "100%"
|
|
32840
|
-
}, /*#__PURE__*/React.createElement("feFlood", {
|
|
32841
|
-
floodColor: "#FFF",
|
|
32842
|
-
floodOpacity: "0.5"
|
|
32843
|
-
}), /*#__PURE__*/React.createElement("feComposite", {
|
|
32844
|
-
operator: "over",
|
|
32845
|
-
in: "SourceGraphic"
|
|
32846
|
-
}))), !isOutside && isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
32847
|
-
start: [x1, y1],
|
|
32848
|
-
vertex: [x2, y2],
|
|
32849
|
-
end: [x3, y3]
|
|
32850
|
-
}) : /*#__PURE__*/React.createElement(Arc, {
|
|
32851
|
-
arc: arc
|
|
32852
|
-
}), /*#__PURE__*/React.createElement(TextLabel, {
|
|
32853
|
-
x: x3,
|
|
32854
|
-
y: y3,
|
|
32855
|
-
attach: y3 - centerY > 0 ? "s" : "n",
|
|
32856
|
-
attachDistance: Math.abs(y3 - centerY) < 0.2 || vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
|
|
32857
|
-
}, angleLabel, "\xB0"));
|
|
32858
|
-
};
|
|
32859
|
-
const Angle = ({
|
|
32860
|
-
vertex,
|
|
32861
|
-
coords,
|
|
32862
|
-
showAngles,
|
|
32863
|
-
allowReflexAngles,
|
|
32864
|
-
range
|
|
32865
|
-
}) => {
|
|
32866
|
-
// Check if the points are clockwise or not, depending on whether we allow reflex angles
|
|
32867
|
-
const areClockwise = clockwise$1([...coords, vertex]);
|
|
32868
|
-
const shouldReverseCoords = areClockwise && !allowReflexAngles;
|
|
32869
|
-
|
|
32870
|
-
// Reverse the coordinates accordingly to ensure the angle is calculated correctly
|
|
32871
|
-
const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
|
|
32872
|
-
|
|
32873
|
-
// Calculate the angles between the two points
|
|
32874
|
-
const startAngle = getAngleFromVertex(clockwiseCoords[0], vertex);
|
|
32875
|
-
const endAngle = getAngleFromVertex(clockwiseCoords[1], vertex);
|
|
32876
|
-
const angle = (startAngle + 360 - endAngle) % 360;
|
|
32877
|
-
|
|
32878
|
-
// Check if the angle is reflexive
|
|
32879
|
-
const isReflexive = angle > 180;
|
|
32880
|
-
|
|
32881
|
-
// Break out the necessary points for the arc calculation
|
|
32882
|
-
const [centerX, centerY] = vertex;
|
|
32883
|
-
const [point1, point2] = clockwiseCoords;
|
|
32884
|
-
const [startX, startY] = point1;
|
|
32885
|
-
const [endX, endY] = point2;
|
|
32886
|
-
const a = vec.dist(vertex, point1);
|
|
32887
|
-
const b = vec.dist(vertex, point2);
|
|
32888
|
-
|
|
32889
|
-
// Set the radius of the arc
|
|
32890
|
-
const radius = 2;
|
|
32891
|
-
|
|
32892
|
-
// Calculate the end points of the arc
|
|
32893
|
-
const y1 = centerY + (startY - centerY) / a * radius;
|
|
32894
|
-
const x2 = centerX + (endX - centerX) / b * radius;
|
|
32895
|
-
const x1 = centerX + (startX - centerX) / a * radius;
|
|
32896
|
-
const y2 = centerY + (endY - centerY) / b * radius;
|
|
32897
|
-
|
|
32898
|
-
// Used to calculate the inside of the angle and to create square
|
|
32899
|
-
const [x3, y3] = vec.add(vertex, vec.add(vec.sub([x1, y1], vertex), vec.sub([x2, y2], vertex)));
|
|
32900
|
-
|
|
32901
|
-
// Determine whether the arc should be drawn outside the angle
|
|
32902
|
-
const isOutside = shouldDrawArcOutside([x3, y3], vertex, range, [[vertex, point1], [vertex, point2]]);
|
|
32903
|
-
|
|
32904
|
-
// Determine the flags for the arc
|
|
32905
|
-
// If the angle is outside OR reflexive, we want to draw a large arc
|
|
32906
|
-
// If the angle is outside AND reflexive, we want to sweep the arc
|
|
32907
|
-
const largeArcFlag = isOutside || isReflexive ? 1 : 0;
|
|
32908
|
-
const sweepFlag = isOutside && isReflexive ? 1 : 0;
|
|
32909
|
-
|
|
32910
|
-
// Create the SVG path for the arc
|
|
32911
|
-
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
32912
|
-
|
|
32913
|
-
// We only ever want to show whole angles
|
|
32914
|
-
const angleLabel = parseFloat(angle.toFixed(0)); // Only want to show whole angles
|
|
32915
|
-
|
|
32916
|
-
// Calculate the text position based on the angle and whether we allow reflex angles
|
|
32917
|
-
// Let's try the angle bisector method to find the midpoint of the arc
|
|
32918
|
-
const [textX, textY] = calculateBisectorPoint(point1, point2, vertex, isReflexive, allowReflexAngles, radius);
|
|
32919
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("filter", {
|
|
32920
|
-
id: "background",
|
|
32921
|
-
x: "-5%",
|
|
32922
|
-
width: "110%",
|
|
32923
|
-
y: "0%",
|
|
32924
|
-
height: "100%"
|
|
32925
|
-
}, /*#__PURE__*/React.createElement("feFlood", {
|
|
32926
|
-
floodColor: "#FFF",
|
|
32927
|
-
floodOpacity: "0.5"
|
|
32928
|
-
}), /*#__PURE__*/React.createElement("feComposite", {
|
|
32929
|
-
operator: "over",
|
|
32930
|
-
in: "SourceGraphic"
|
|
32931
|
-
}))), isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
32932
|
-
start: [x1, y1],
|
|
32933
|
-
vertex: [x2, y2],
|
|
32934
|
-
end: [x3, y3],
|
|
32935
|
-
className: "arc-right-angle"
|
|
32936
|
-
}) : /*#__PURE__*/React.createElement(Arc, {
|
|
32937
|
-
arc: arc,
|
|
32938
|
-
className: "angle-arc"
|
|
32939
|
-
}), showAngles && /*#__PURE__*/React.createElement(TextLabel, {
|
|
32940
|
-
x: textX,
|
|
32941
|
-
y: textY,
|
|
32942
|
-
color: color.blue
|
|
32943
|
-
}, angleLabel, "\xB0"));
|
|
32944
|
-
};
|
|
32945
|
-
|
|
32946
|
-
/**
|
|
32947
|
-
* This is broken out into its own component so that it can be used for an early return
|
|
32948
|
-
* (see line 55 or https://github.com/Khan/perseus/blob/84bbd882b11d16871e1b813a0b901f3b903d5479/packages/perseus/src/widgets/interactive-graphs/graphs/components/angle.tsx#L53-L57)
|
|
32949
|
-
*/
|
|
32950
|
-
const RightAngleSquare = ({
|
|
32951
|
-
start: [x1, y1],
|
|
32952
|
-
vertex: [x2, y2],
|
|
32953
|
-
end: [x3, y3],
|
|
32954
|
-
className
|
|
32955
|
-
}) => /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
32956
|
-
d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
|
|
32957
|
-
strokeWidth: 0.02,
|
|
32958
|
-
fill: "none",
|
|
32959
|
-
className: className
|
|
32960
|
-
}));
|
|
32961
|
-
|
|
32962
|
-
// We're conditionally adding the class name here so that we can style the arc differently
|
|
32963
|
-
// based on whether it's an angle or a polygon angle
|
|
32964
|
-
const Arc = ({
|
|
32965
|
-
arc,
|
|
32966
|
-
className
|
|
32967
|
-
}) => {
|
|
32968
|
-
return /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
32969
|
-
d: arc,
|
|
32970
|
-
strokeWidth: 0.02,
|
|
32971
|
-
fill: "none",
|
|
32972
|
-
className: className
|
|
32973
|
-
}));
|
|
32974
|
-
};
|
|
32975
|
-
const isRightPolygonAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
|
|
32976
|
-
const isRightAngle = angle => Math.round(angle) === 90;
|
|
32977
|
-
|
|
32978
|
-
/**
|
|
32979
|
-
* Determines if an angle is an inside (false) or outside (true) angle.
|
|
32980
|
-
* They way, we know to flip the `largeArc` and `sweepArc` flags.
|
|
32981
|
-
* Uses the priciple that a ray from a point inside a polygon will intersect
|
|
32982
|
-
* with an odd number of lines, while a ray from a point outside the polygon
|
|
32983
|
-
* will intersect with an even number of lines.
|
|
32984
|
-
* https://stackoverflow.com/questions/217578/how-can-i-determine-whether-a-2d-point-is-within-a-polygon
|
|
32985
|
-
*/
|
|
32986
|
-
const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
|
|
32987
|
-
// Create a ray from the midpoint (inside angle) to the edge of the range
|
|
32988
|
-
const rangeIntersectionPoint = getIntersectionOfRayWithBox(midpoint, vertex, range);
|
|
32989
|
-
let lineIntersectionCount = 0;
|
|
32990
|
-
polygonLines.forEach(line => segmentsIntersect([vertex, rangeIntersectionPoint], line) && lineIntersectionCount++);
|
|
32991
|
-
|
|
32992
|
-
// If the number of intersections is even, the angle is inside the polygon
|
|
32993
|
-
return !isEven(lineIntersectionCount);
|
|
32994
|
-
};
|
|
32995
|
-
const isEven = n => n % 2 === 0;
|
|
32996
|
-
|
|
32997
|
-
// This function calculates the bisector point of an angle formed by two points
|
|
32998
|
-
// and a vertex. It is used to position the angle label so that it is always
|
|
32999
|
-
// outside the arc at the exact midpoint of the angle.
|
|
33000
|
-
// Reflex angles make use of polar coordinates to determine the correct direction
|
|
33001
|
-
function calculateBisectorPoint(point1, point2, vertex, isReflex, allowReflex, arcRadius) {
|
|
33002
|
-
const [originX, originY] = vertex;
|
|
33003
|
-
const [x1, y1] = point1;
|
|
33004
|
-
const [x2, y2] = point2;
|
|
33005
|
-
|
|
33006
|
-
// Convert the Cartesian Coordinates to vectors
|
|
33007
|
-
const vectorA = [x1 - originX, y1 - originY];
|
|
33008
|
-
const vectorB = [x2 - originX, y2 - originY];
|
|
33009
|
-
|
|
33010
|
-
// Calculate the average angle of the vectors
|
|
33011
|
-
// so that we can point the bisector in the correct direction using polar coordinates
|
|
33012
|
-
const angleA = Math.atan2(vectorA[1], vectorA[0]);
|
|
33013
|
-
const angleB = Math.atan2(vectorB[1], vectorB[0]);
|
|
33014
|
-
|
|
33015
|
-
// Calculate the average of these two angles
|
|
33016
|
-
let averageAngle = (angleA + angleB) / 2;
|
|
33017
|
-
const angleRadians = Math.abs(angleA - angleB);
|
|
33018
|
-
|
|
33019
|
-
// Adjust the average angle to point in the correct direction
|
|
33020
|
-
// and to account for reflex angles and a bug with 180 degrees
|
|
33021
|
-
// that occurs due to our angleOffsetDegrees option in the graph
|
|
33022
|
-
if (allowReflex) {
|
|
33023
|
-
// If the resulting angles are less than 180 degrees,
|
|
33024
|
-
// or if the user has flipped the points on the interactive element
|
|
33025
|
-
// ensure that we have the correct direction for the reflex angle
|
|
33026
|
-
if (angleRadians <= Math.PI && isReflex || angleB > angleA) {
|
|
33027
|
-
averageAngle += Math.PI; // Add 180 degrees
|
|
33526
|
+
}, [props.graphState.focusedPointIndex, itemsRef]);
|
|
33527
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("rect", {
|
|
33528
|
+
style: {
|
|
33529
|
+
fill: "rgba(0,0,0,0)",
|
|
33530
|
+
cursor: "crosshair"
|
|
33531
|
+
},
|
|
33532
|
+
width: widthPx,
|
|
33533
|
+
height: heightPx,
|
|
33534
|
+
x: left,
|
|
33535
|
+
y: top,
|
|
33536
|
+
onClick: event => {
|
|
33537
|
+
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
33538
|
+
const x = event.clientX - elementRect.x;
|
|
33539
|
+
const y = event.clientY - elementRect.y;
|
|
33540
|
+
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
33541
|
+
dispatch(actions.pointGraph.addPoint(graphCoordinates[0]));
|
|
33028
33542
|
}
|
|
33029
|
-
}
|
|
33030
|
-
|
|
33031
|
-
|
|
33032
|
-
|
|
33033
|
-
|
|
33034
|
-
|
|
33543
|
+
}), props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33544
|
+
key: i,
|
|
33545
|
+
point: point,
|
|
33546
|
+
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
|
|
33547
|
+
ref: ref => {
|
|
33548
|
+
itemsRef.current[i] = ref;
|
|
33549
|
+
},
|
|
33550
|
+
onFocus: () => {
|
|
33551
|
+
dispatch(actions.pointGraph.focusPoint(i));
|
|
33552
|
+
},
|
|
33553
|
+
onClick: () => {
|
|
33554
|
+
dispatch(actions.pointGraph.clickPoint(i));
|
|
33035
33555
|
}
|
|
33036
|
-
}
|
|
33037
|
-
|
|
33038
|
-
|
|
33039
|
-
|
|
33040
|
-
|
|
33041
|
-
//
|
|
33042
|
-
|
|
33043
|
-
const
|
|
33044
|
-
|
|
33045
|
-
// Calculate the initial distance of the bisector direction from the origin
|
|
33046
|
-
const initialDistance = Math.sqrt(bisectorDirection[0] ** 2 + bisectorDirection[1] ** 2);
|
|
33047
|
-
|
|
33048
|
-
// Determine the minimum radius to ensure that the text is always outside the arc
|
|
33049
|
-
const requiredDistance = arcRadius * 1.75;
|
|
33050
|
-
let radius = requiredDistance / initialDistance;
|
|
33051
|
-
|
|
33052
|
-
// Apply radius only if the initial distance is less than required
|
|
33053
|
-
if (initialDistance >= requiredDistance) {
|
|
33054
|
-
radius = 1; // Use the unit vector as is
|
|
33055
|
-
}
|
|
33056
|
-
|
|
33057
|
-
// Scale the bisector direction by the radius
|
|
33058
|
-
const bisectorPoint = [bisectorDirection[0] * radius, bisectorDirection[1] * radius];
|
|
33059
|
-
|
|
33060
|
-
// Add the vertex to the bisector point to get the final position
|
|
33061
|
-
// to ensure that the angle label moves with the interactive element
|
|
33062
|
-
const scaledBisector = vec.add(bisectorPoint, vertex);
|
|
33063
|
-
return scaledBisector;
|
|
33556
|
+
})));
|
|
33557
|
+
}
|
|
33558
|
+
function PointGraphDescription({
|
|
33559
|
+
state
|
|
33560
|
+
}) {
|
|
33561
|
+
// PointGraphDescription needs to `usePerseusI18n`, so it has to be a
|
|
33562
|
+
// component rather than a function that simply returns a string.
|
|
33563
|
+
const i18n = usePerseusI18n();
|
|
33564
|
+
return describePointGraph(state, i18n);
|
|
33064
33565
|
}
|
|
33065
33566
|
|
|
33066
|
-
|
|
33567
|
+
// Exported for testing
|
|
33568
|
+
function describePointGraph(state, i18n) {
|
|
33067
33569
|
const {
|
|
33068
|
-
|
|
33069
|
-
|
|
33070
|
-
} =
|
|
33071
|
-
|
|
33570
|
+
strings,
|
|
33571
|
+
locale
|
|
33572
|
+
} = i18n;
|
|
33573
|
+
if (state.coords.length === 0) {
|
|
33574
|
+
return strings.srNoInteractiveElements;
|
|
33575
|
+
}
|
|
33576
|
+
const pointDescriptions = state.coords.map(([x, y]) => strings.srPointAtCoordinates({
|
|
33577
|
+
x: srFormatNumber(x, locale),
|
|
33578
|
+
y: srFormatNumber(y, locale)
|
|
33072
33579
|
}));
|
|
33073
|
-
return
|
|
33074
|
-
|
|
33580
|
+
return strings.srInteractiveElements({
|
|
33581
|
+
elements: pointDescriptions.join(", ")
|
|
33582
|
+
});
|
|
33583
|
+
}
|
|
33075
33584
|
|
|
33585
|
+
function renderPolygonGraph(state, dispatch) {
|
|
33586
|
+
return {
|
|
33587
|
+
graph: /*#__PURE__*/React.createElement(PolygonGraph, {
|
|
33588
|
+
graphState: state,
|
|
33589
|
+
dispatch: dispatch
|
|
33590
|
+
}),
|
|
33591
|
+
interactiveElementsDescription: null
|
|
33592
|
+
};
|
|
33593
|
+
}
|
|
33076
33594
|
const LimitedPolygonGraph = props => {
|
|
33077
33595
|
const [hovered, setHovered] = React.useState(false);
|
|
33078
33596
|
// This is more so required for the re-rendering that occurs when state
|
|
@@ -33356,189 +33874,15 @@ const PolygonGraph = props => {
|
|
|
33356
33874
|
return numSides === "unlimited" ? UnlimitedPolygonGraph(props) : LimitedPolygonGraph(props);
|
|
33357
33875
|
};
|
|
33358
33876
|
|
|
33359
|
-
function
|
|
33360
|
-
|
|
33361
|
-
|
|
33362
|
-
|
|
33363
|
-
|
|
33364
|
-
|
|
33365
|
-
|
|
33366
|
-
|
|
33367
|
-
}
|
|
33368
|
-
if (state.type === "segment" && initialGraph.type === "segment") {
|
|
33369
|
-
return _extends({}, initialGraph, {
|
|
33370
|
-
coords: state.coords
|
|
33371
|
-
});
|
|
33372
|
-
}
|
|
33373
|
-
if (state.type === "linear" && initialGraph.type === "linear") {
|
|
33374
|
-
return _extends({}, initialGraph, {
|
|
33375
|
-
coords: state.coords
|
|
33376
|
-
});
|
|
33377
|
-
}
|
|
33378
|
-
if (state.type === "ray" && initialGraph.type === "ray") {
|
|
33379
|
-
return _extends({}, initialGraph, {
|
|
33380
|
-
coords: state.coords
|
|
33381
|
-
});
|
|
33382
|
-
}
|
|
33383
|
-
if (state.type === "polygon" && initialGraph.type === "polygon") {
|
|
33384
|
-
return _extends({}, initialGraph, {
|
|
33385
|
-
coords: state.coords
|
|
33386
|
-
});
|
|
33387
|
-
}
|
|
33388
|
-
if (state.type === "point" && initialGraph.type === "point") {
|
|
33389
|
-
return _extends({}, initialGraph, {
|
|
33390
|
-
coords: state.coords
|
|
33391
|
-
});
|
|
33392
|
-
}
|
|
33393
|
-
if (state.type === "circle" && initialGraph.type === "circle") {
|
|
33394
|
-
return _extends({}, initialGraph, {
|
|
33395
|
-
center: state.center,
|
|
33396
|
-
radius: getRadius(state)
|
|
33397
|
-
});
|
|
33398
|
-
}
|
|
33399
|
-
if (state.type === "quadratic" && initialGraph.type === "quadratic") {
|
|
33400
|
-
return _extends({}, initialGraph, {
|
|
33401
|
-
coords: state.coords
|
|
33402
|
-
});
|
|
33403
|
-
}
|
|
33404
|
-
if (state.type === "sinusoid" && initialGraph.type === "sinusoid") {
|
|
33405
|
-
return _extends({}, initialGraph, {
|
|
33406
|
-
coords: state.coords
|
|
33407
|
-
});
|
|
33408
|
-
}
|
|
33409
|
-
if (state.type === "angle" && initialGraph.type === "angle") {
|
|
33410
|
-
// We're going to reverse the coords for scoring if the angle is clockwise and
|
|
33411
|
-
// we don't allow reflex angles. This is because the angle is largely defined
|
|
33412
|
-
// by the order of the points in the coords array, and we want to maintain
|
|
33413
|
-
// the same angle scoring with the legacy graph (which had a bug).
|
|
33414
|
-
// (LEMS-2190): When we remove the legacy graph, move this logic to the scoring function.
|
|
33415
|
-
const areClockwise = clockwise$1([state.coords[0], state.coords[2], state.coords[1]]);
|
|
33416
|
-
const shouldReverseCoords = areClockwise && !state.allowReflexAngles;
|
|
33417
|
-
const coords = shouldReverseCoords ? state.coords.slice().reverse() : state.coords;
|
|
33418
|
-
return _extends({}, initialGraph, {
|
|
33419
|
-
coords,
|
|
33420
|
-
allowReflexAngles: state.allowReflexAngles
|
|
33421
|
-
});
|
|
33422
|
-
}
|
|
33423
|
-
if (state.type === "none" && initialGraph.type === "none") {
|
|
33424
|
-
return {
|
|
33425
|
-
type: "none"
|
|
33426
|
-
};
|
|
33427
|
-
}
|
|
33428
|
-
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
33429
|
-
}
|
|
33430
|
-
|
|
33431
|
-
/**
|
|
33432
|
-
* determine radius of a circle graph
|
|
33433
|
-
*
|
|
33434
|
-
* @param graph - the graph object containing the circle
|
|
33435
|
-
* @returns the radius of the circle
|
|
33436
|
-
*/
|
|
33437
|
-
function getRadius(graph) {
|
|
33438
|
-
const [centerX, centerY] = graph.center;
|
|
33439
|
-
const [edgeX, edgeY] = graph.radiusPoint;
|
|
33440
|
-
return Math.sqrt(Math.pow(edgeX - centerX, 2) + Math.pow(edgeY - centerY, 2));
|
|
33441
|
-
}
|
|
33442
|
-
|
|
33443
|
-
function CircleGraph(props) {
|
|
33444
|
-
const {
|
|
33445
|
-
dispatch,
|
|
33446
|
-
graphState
|
|
33447
|
-
} = props;
|
|
33448
|
-
const {
|
|
33449
|
-
center,
|
|
33450
|
-
radiusPoint
|
|
33451
|
-
} = graphState;
|
|
33452
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MovableCircle, {
|
|
33453
|
-
center: center,
|
|
33454
|
-
radius: getRadius(graphState),
|
|
33455
|
-
onMove: c => dispatch(actions.circle.moveCenter(c))
|
|
33456
|
-
}), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33457
|
-
point: radiusPoint,
|
|
33458
|
-
cursor: "ew-resize",
|
|
33459
|
-
onMove: newRadiusPoint => {
|
|
33460
|
-
dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
|
|
33461
|
-
}
|
|
33462
|
-
}));
|
|
33463
|
-
}
|
|
33464
|
-
function MovableCircle(props) {
|
|
33465
|
-
const {
|
|
33466
|
-
center,
|
|
33467
|
-
radius,
|
|
33468
|
-
onMove
|
|
33469
|
-
} = props;
|
|
33470
|
-
const {
|
|
33471
|
-
snapStep,
|
|
33472
|
-
disableKeyboardInteraction
|
|
33473
|
-
} = useGraphConfig();
|
|
33474
|
-
const draggableRef = useRef(null);
|
|
33475
|
-
const {
|
|
33476
|
-
dragging
|
|
33477
|
-
} = useDraggable({
|
|
33478
|
-
gestureTarget: draggableRef,
|
|
33479
|
-
point: center,
|
|
33480
|
-
onMove,
|
|
33481
|
-
constrainKeyboardMovement: p => snap(snapStep, p)
|
|
33482
|
-
});
|
|
33483
|
-
const [centerPx] = useTransformVectorsToPixels(center);
|
|
33484
|
-
const [radiiPx] = useTransformDimensionsToPixels([radius, radius]);
|
|
33485
|
-
return /*#__PURE__*/React.createElement("g", {
|
|
33486
|
-
ref: draggableRef,
|
|
33487
|
-
tabIndex: disableKeyboardInteraction ? -1 : 0,
|
|
33488
|
-
className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
|
|
33489
|
-
}, /*#__PURE__*/React.createElement("ellipse", {
|
|
33490
|
-
className: "focus-ring",
|
|
33491
|
-
cx: centerPx[X],
|
|
33492
|
-
cy: centerPx[Y],
|
|
33493
|
-
rx: radiiPx[X] + 3,
|
|
33494
|
-
ry: radiiPx[Y] + 3
|
|
33495
|
-
}), /*#__PURE__*/React.createElement("ellipse", {
|
|
33496
|
-
className: "circle",
|
|
33497
|
-
cx: centerPx[X],
|
|
33498
|
-
cy: centerPx[Y],
|
|
33499
|
-
rx: radiiPx[X],
|
|
33500
|
-
ry: radiiPx[Y]
|
|
33501
|
-
}), /*#__PURE__*/React.createElement(DragHandle, {
|
|
33502
|
-
center: center
|
|
33503
|
-
}));
|
|
33504
|
-
}
|
|
33505
|
-
const dragHandleDimensions = [24, 14];
|
|
33506
|
-
const dragHandleDotPositions = crossProduct([-4.4, 0, 4.4], [-2.1, 2.1]);
|
|
33507
|
-
function DragHandle(props) {
|
|
33508
|
-
const {
|
|
33509
|
-
center
|
|
33510
|
-
} = props;
|
|
33511
|
-
const cornerRadius = Math.min(...dragHandleDimensions) / 2;
|
|
33512
|
-
const [centerPx] = useTransformVectorsToPixels(center);
|
|
33513
|
-
const topLeft = vec.sub(centerPx, vec.scale(dragHandleDimensions, 0.5));
|
|
33514
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("rect", {
|
|
33515
|
-
className: "movable-circle-handle",
|
|
33516
|
-
x: topLeft[X],
|
|
33517
|
-
y: topLeft[Y],
|
|
33518
|
-
width: dragHandleDimensions[X],
|
|
33519
|
-
height: dragHandleDimensions[Y],
|
|
33520
|
-
rx: cornerRadius,
|
|
33521
|
-
ry: cornerRadius
|
|
33522
|
-
}), dragHandleDotPositions.map(offsetPx => {
|
|
33523
|
-
const [xPx, yPx] = vec.add(offsetPx, centerPx);
|
|
33524
|
-
return /*#__PURE__*/React.createElement("circle", {
|
|
33525
|
-
key: `circle-${xPx}-${yPx}`,
|
|
33526
|
-
className: "movable-circle-handle-dot",
|
|
33527
|
-
cx: xPx,
|
|
33528
|
-
cy: yPx
|
|
33529
|
-
});
|
|
33530
|
-
}));
|
|
33531
|
-
}
|
|
33532
|
-
function crossProduct(as, bs) {
|
|
33533
|
-
const result = [];
|
|
33534
|
-
for (const a of as) {
|
|
33535
|
-
for (const b of bs) {
|
|
33536
|
-
result.push([a, b]);
|
|
33537
|
-
}
|
|
33538
|
-
}
|
|
33539
|
-
return result;
|
|
33877
|
+
function renderQuadraticGraph(state, dispatch) {
|
|
33878
|
+
return {
|
|
33879
|
+
graph: /*#__PURE__*/React.createElement(QuadraticGraph, {
|
|
33880
|
+
graphState: state,
|
|
33881
|
+
dispatch: dispatch
|
|
33882
|
+
}),
|
|
33883
|
+
interactiveElementsDescription: null
|
|
33884
|
+
};
|
|
33540
33885
|
}
|
|
33541
|
-
|
|
33542
33886
|
function QuadraticGraph(props) {
|
|
33543
33887
|
const {
|
|
33544
33888
|
dispatch,
|
|
@@ -33593,6 +33937,74 @@ const getQuadraticCoefficients$1 = coords => {
|
|
|
33593
33937
|
return [a, b, c];
|
|
33594
33938
|
};
|
|
33595
33939
|
|
|
33940
|
+
function renderRayGraph(state, dispatch) {
|
|
33941
|
+
return {
|
|
33942
|
+
graph: /*#__PURE__*/React.createElement(RayGraph, {
|
|
33943
|
+
graphState: state,
|
|
33944
|
+
dispatch: dispatch
|
|
33945
|
+
}),
|
|
33946
|
+
interactiveElementsDescription: null
|
|
33947
|
+
};
|
|
33948
|
+
}
|
|
33949
|
+
const RayGraph = props => {
|
|
33950
|
+
const {
|
|
33951
|
+
dispatch
|
|
33952
|
+
} = props;
|
|
33953
|
+
const {
|
|
33954
|
+
coords: line
|
|
33955
|
+
} = props.graphState;
|
|
33956
|
+
const handleMoveLine = delta => dispatch(actions.ray.moveRay(delta));
|
|
33957
|
+
const handleMovePoint = (pointIndex, newPoint) => dispatch(actions.ray.movePoint(pointIndex, newPoint));
|
|
33958
|
+
|
|
33959
|
+
// Ray graphs only have one line
|
|
33960
|
+
return /*#__PURE__*/React.createElement(MovableLine, {
|
|
33961
|
+
points: line,
|
|
33962
|
+
onMoveLine: handleMoveLine,
|
|
33963
|
+
onMovePoint: handleMovePoint,
|
|
33964
|
+
extend: {
|
|
33965
|
+
start: false,
|
|
33966
|
+
end: true
|
|
33967
|
+
}
|
|
33968
|
+
});
|
|
33969
|
+
};
|
|
33970
|
+
|
|
33971
|
+
function renderSegmentGraph(state, dispatch) {
|
|
33972
|
+
return {
|
|
33973
|
+
graph: /*#__PURE__*/React.createElement(SegmentGraph, {
|
|
33974
|
+
graphState: state,
|
|
33975
|
+
dispatch: dispatch
|
|
33976
|
+
}),
|
|
33977
|
+
interactiveElementsDescription: null
|
|
33978
|
+
};
|
|
33979
|
+
}
|
|
33980
|
+
const SegmentGraph = props => {
|
|
33981
|
+
const {
|
|
33982
|
+
dispatch
|
|
33983
|
+
} = props;
|
|
33984
|
+
const {
|
|
33985
|
+
coords: segments
|
|
33986
|
+
} = props.graphState;
|
|
33987
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, segments == null ? void 0 : segments.map((segment, i) => /*#__PURE__*/React.createElement(MovableLine, {
|
|
33988
|
+
key: i,
|
|
33989
|
+
points: segment,
|
|
33990
|
+
onMoveLine: delta => {
|
|
33991
|
+
dispatch(actions.segment.moveLine(i, delta));
|
|
33992
|
+
},
|
|
33993
|
+
onMovePoint: (endpointIndex, destination) => {
|
|
33994
|
+
dispatch(actions.segment.movePointInFigure(i, endpointIndex, destination));
|
|
33995
|
+
}
|
|
33996
|
+
})));
|
|
33997
|
+
};
|
|
33998
|
+
|
|
33999
|
+
function renderSinusoidGraph(state, dispatch) {
|
|
34000
|
+
return {
|
|
34001
|
+
graph: /*#__PURE__*/React.createElement(SinusoidGraph, {
|
|
34002
|
+
graphState: state,
|
|
34003
|
+
dispatch: dispatch
|
|
34004
|
+
}),
|
|
34005
|
+
interactiveElementsDescription: null
|
|
34006
|
+
};
|
|
34007
|
+
}
|
|
33596
34008
|
function SinusoidGraph(props) {
|
|
33597
34009
|
const {
|
|
33598
34010
|
dispatch,
|
|
@@ -33668,187 +34080,6 @@ const getSinusoidCoefficients$1 = coords => {
|
|
|
33668
34080
|
};
|
|
33669
34081
|
};
|
|
33670
34082
|
|
|
33671
|
-
function AngleGraph(props) {
|
|
33672
|
-
const {
|
|
33673
|
-
dispatch,
|
|
33674
|
-
graphState
|
|
33675
|
-
} = props;
|
|
33676
|
-
const {
|
|
33677
|
-
graphDimensionsInPixels
|
|
33678
|
-
} = useGraphConfig();
|
|
33679
|
-
const {
|
|
33680
|
-
coords,
|
|
33681
|
-
showAngles,
|
|
33682
|
-
range,
|
|
33683
|
-
allowReflexAngles,
|
|
33684
|
-
snapDegrees
|
|
33685
|
-
} = graphState;
|
|
33686
|
-
|
|
33687
|
-
// Break the coords into the two end points and the center point
|
|
33688
|
-
const endPoints = [coords[0], coords[2]];
|
|
33689
|
-
const centerPoint = coords[1];
|
|
33690
|
-
|
|
33691
|
-
// Convert the vectors to pixels for rendering the svg lines
|
|
33692
|
-
const angleLines = [[centerPoint, endPoints[0]], [centerPoint, endPoints[1]]];
|
|
33693
|
-
const linePixelCoords = [useTransformVectorsToPixels(centerPoint, endPoints[0]), useTransformVectorsToPixels(centerPoint, endPoints[1])];
|
|
33694
|
-
|
|
33695
|
-
// Create the SVG lines for the angle
|
|
33696
|
-
const svgLines = linePixelCoords.map(([startPtPx, endPtPx], i) => {
|
|
33697
|
-
const trimmedRange = trimRange(range, graphDimensionsInPixels);
|
|
33698
|
-
const endExtend = getIntersectionOfRayWithBox(angleLines[i][0], angleLines[i][1], trimmedRange);
|
|
33699
|
-
return /*#__PURE__*/React.createElement("g", {
|
|
33700
|
-
key: `line-${i}`
|
|
33701
|
-
}, /*#__PURE__*/React.createElement(SVGLine, {
|
|
33702
|
-
start: startPtPx,
|
|
33703
|
-
end: endPtPx,
|
|
33704
|
-
style: {
|
|
33705
|
-
stroke: "var(--movable-line-stroke-color)",
|
|
33706
|
-
strokeWidth: "var(--movable-line-stroke-weight)"
|
|
33707
|
-
}
|
|
33708
|
-
}), /*#__PURE__*/React.createElement(Vector, {
|
|
33709
|
-
tail: angleLines[i][1],
|
|
33710
|
-
tip: endExtend,
|
|
33711
|
-
color: "var(--movable-line-stroke-color)"
|
|
33712
|
-
}));
|
|
33713
|
-
});
|
|
33714
|
-
|
|
33715
|
-
// Create the angle indicator parameters
|
|
33716
|
-
const angleParams = {
|
|
33717
|
-
vertex: centerPoint,
|
|
33718
|
-
coords: endPoints,
|
|
33719
|
-
allowReflexAngles: allowReflexAngles || false,
|
|
33720
|
-
// Whether to allow reflex angles or not
|
|
33721
|
-
snapDegrees: snapDegrees || 1,
|
|
33722
|
-
// The multiple of degrees to snap to
|
|
33723
|
-
range: range,
|
|
33724
|
-
showAngles: showAngles || false // Whether to show the angle or not
|
|
33725
|
-
};
|
|
33726
|
-
|
|
33727
|
-
// Render the lines, angle, and then movable points
|
|
33728
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, svgLines, /*#__PURE__*/React.createElement(Angle, angleParams), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33729
|
-
point: coords[1],
|
|
33730
|
-
constrain: p => p,
|
|
33731
|
-
onMove: destination => dispatch(actions.angle.movePoint(1, destination))
|
|
33732
|
-
}), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33733
|
-
point: coords[0],
|
|
33734
|
-
constrain: getAngleSideConstraint(coords[0], coords[1], snapDegrees || 1),
|
|
33735
|
-
onMove: destination => dispatch(actions.angle.movePoint(0, destination))
|
|
33736
|
-
}), /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33737
|
-
point: coords[2],
|
|
33738
|
-
constrain: getAngleSideConstraint(coords[2], coords[1], snapDegrees || 1),
|
|
33739
|
-
onMove: destination => dispatch(actions.angle.movePoint(2, destination))
|
|
33740
|
-
}));
|
|
33741
|
-
}
|
|
33742
|
-
const positiveX = [1, 0];
|
|
33743
|
-
const negativeX = [-1, 0];
|
|
33744
|
-
const positiveY = [0, 1];
|
|
33745
|
-
const negativeY = [0, -1];
|
|
33746
|
-
function getAngleSideConstraint(sidePoint, vertex, snapDegrees) {
|
|
33747
|
-
var _findIntersectionOfRa, _findIntersectionOfRa2, _findIntersectionOfRa3, _findIntersectionOfRa4;
|
|
33748
|
-
const currentAngle = calculateAngleInDegrees(vec.sub(sidePoint, vertex));
|
|
33749
|
-
|
|
33750
|
-
// Find the rays that start at the current point and point up, down, left
|
|
33751
|
-
// and right.
|
|
33752
|
-
const leftRay = [sidePoint, vec.add(sidePoint, negativeX)];
|
|
33753
|
-
const rightRay = [sidePoint, vec.add(sidePoint, positiveX)];
|
|
33754
|
-
const upRay = [sidePoint, vec.add(sidePoint, positiveY)];
|
|
33755
|
-
const downRay = [sidePoint, vec.add(sidePoint, negativeY)];
|
|
33756
|
-
|
|
33757
|
-
// find the angles that lie one snap step clockwise and counter-clockwise
|
|
33758
|
-
// from the current angle. These are the angles to which the side can be
|
|
33759
|
-
// moved.
|
|
33760
|
-
const oneStepCounterClockwise = currentAngle + snapDegrees;
|
|
33761
|
-
const oneStepClockwise = currentAngle - snapDegrees;
|
|
33762
|
-
|
|
33763
|
-
// find the rays that start from the vertex and point in the direction of
|
|
33764
|
-
// the angles we just computed.
|
|
33765
|
-
const counterClockwiseRay = [vertex, vec.add(vertex, polar(1, oneStepCounterClockwise))];
|
|
33766
|
-
const clockwiseRay = [vertex, vec.add(vertex, polar(1, oneStepClockwise))];
|
|
33767
|
-
|
|
33768
|
-
// find the intersections of those rays with the horizontal and vertical
|
|
33769
|
-
// rays extending from the sidePoint. These intersections are the points to
|
|
33770
|
-
// which sidePoint can move that will result in a rotation of `snapDegrees`.
|
|
33771
|
-
const left = (_findIntersectionOfRa = findIntersectionOfRays(leftRay, counterClockwiseRay)) != null ? _findIntersectionOfRa : findIntersectionOfRays(leftRay, clockwiseRay);
|
|
33772
|
-
const right = (_findIntersectionOfRa2 = findIntersectionOfRays(rightRay, counterClockwiseRay)) != null ? _findIntersectionOfRa2 : findIntersectionOfRays(rightRay, clockwiseRay);
|
|
33773
|
-
const up = (_findIntersectionOfRa3 = findIntersectionOfRays(upRay, counterClockwiseRay)) != null ? _findIntersectionOfRa3 : findIntersectionOfRays(upRay, clockwiseRay);
|
|
33774
|
-
const down = (_findIntersectionOfRa4 = findIntersectionOfRays(downRay, counterClockwiseRay)) != null ? _findIntersectionOfRa4 : findIntersectionOfRays(downRay, clockwiseRay);
|
|
33775
|
-
return {
|
|
33776
|
-
up: up != null ? up : sidePoint,
|
|
33777
|
-
down: down != null ? down : sidePoint,
|
|
33778
|
-
left: left != null ? left : sidePoint,
|
|
33779
|
-
right: right != null ? right : sidePoint
|
|
33780
|
-
};
|
|
33781
|
-
}
|
|
33782
|
-
|
|
33783
|
-
function LimitedPointGraph(props) {
|
|
33784
|
-
const {
|
|
33785
|
-
dispatch
|
|
33786
|
-
} = props;
|
|
33787
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33788
|
-
key: i,
|
|
33789
|
-
point: point,
|
|
33790
|
-
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination))
|
|
33791
|
-
})));
|
|
33792
|
-
}
|
|
33793
|
-
function UnlimitedPointGraph(props) {
|
|
33794
|
-
const {
|
|
33795
|
-
dispatch
|
|
33796
|
-
} = props;
|
|
33797
|
-
const graphConfig = useGraphConfig();
|
|
33798
|
-
const {
|
|
33799
|
-
range: [[minX, maxX], [minY, maxY]]
|
|
33800
|
-
} = graphConfig;
|
|
33801
|
-
const width = maxX - minX;
|
|
33802
|
-
const height = maxY - minY;
|
|
33803
|
-
const [[widthPx, heightPx]] = useTransformDimensionsToPixels([width, height]);
|
|
33804
|
-
const [[left, top]] = useTransformVectorsToPixels([minX, maxY]);
|
|
33805
|
-
const itemsRef = React.useRef([]);
|
|
33806
|
-
React.useEffect(() => {
|
|
33807
|
-
const focusedIndex = props.graphState.focusedPointIndex;
|
|
33808
|
-
if (focusedIndex != null) {
|
|
33809
|
-
var _itemsRef$current$foc;
|
|
33810
|
-
(_itemsRef$current$foc = itemsRef.current[focusedIndex]) == null || _itemsRef$current$foc.focus();
|
|
33811
|
-
}
|
|
33812
|
-
}, [props.graphState.focusedPointIndex, itemsRef]);
|
|
33813
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("rect", {
|
|
33814
|
-
style: {
|
|
33815
|
-
fill: "rgba(0,0,0,0)",
|
|
33816
|
-
cursor: "crosshair"
|
|
33817
|
-
},
|
|
33818
|
-
width: widthPx,
|
|
33819
|
-
height: heightPx,
|
|
33820
|
-
x: left,
|
|
33821
|
-
y: top,
|
|
33822
|
-
onClick: event => {
|
|
33823
|
-
const elementRect = event.currentTarget.getBoundingClientRect();
|
|
33824
|
-
const x = event.clientX - elementRect.x;
|
|
33825
|
-
const y = event.clientY - elementRect.y;
|
|
33826
|
-
const graphCoordinates = pixelsToVectors([[x, y]], graphConfig);
|
|
33827
|
-
dispatch(actions.pointGraph.addPoint(graphCoordinates[0]));
|
|
33828
|
-
}
|
|
33829
|
-
}), props.graphState.coords.map((point, i) => /*#__PURE__*/React.createElement(MovablePoint$1, {
|
|
33830
|
-
key: i,
|
|
33831
|
-
point: point,
|
|
33832
|
-
onMove: destination => dispatch(actions.pointGraph.movePoint(i, destination)),
|
|
33833
|
-
ref: ref => {
|
|
33834
|
-
itemsRef.current[i] = ref;
|
|
33835
|
-
},
|
|
33836
|
-
onFocus: () => {
|
|
33837
|
-
dispatch(actions.pointGraph.focusPoint(i));
|
|
33838
|
-
},
|
|
33839
|
-
onClick: () => {
|
|
33840
|
-
dispatch(actions.pointGraph.clickPoint(i));
|
|
33841
|
-
}
|
|
33842
|
-
})));
|
|
33843
|
-
}
|
|
33844
|
-
function PointGraph(props) {
|
|
33845
|
-
const numPoints = props.graphState.numPoints;
|
|
33846
|
-
if (numPoints === "unlimited") {
|
|
33847
|
-
return UnlimitedPointGraph(props);
|
|
33848
|
-
}
|
|
33849
|
-
return LimitedPointGraph(props);
|
|
33850
|
-
}
|
|
33851
|
-
|
|
33852
34083
|
const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
|
|
33853
34084
|
|
|
33854
34085
|
// The vector from the center of the protractor to the top left corner of the
|
|
@@ -34013,6 +34244,7 @@ const MafsGraph = props => {
|
|
|
34013
34244
|
const tickStep = props.step;
|
|
34014
34245
|
const uniqueId = React.useId();
|
|
34015
34246
|
const descriptionId = `interactive-graph-description-${uniqueId}`;
|
|
34247
|
+
const interactiveElementsDescriptionId = `interactive-graph-interactive-elements-description-${uniqueId}`;
|
|
34016
34248
|
const graphRef = React.useRef(null);
|
|
34017
34249
|
const {
|
|
34018
34250
|
analytics
|
|
@@ -34047,6 +34279,13 @@ const MafsGraph = props => {
|
|
|
34047
34279
|
}
|
|
34048
34280
|
});
|
|
34049
34281
|
});
|
|
34282
|
+
const {
|
|
34283
|
+
graph,
|
|
34284
|
+
interactiveElementsDescription
|
|
34285
|
+
} = renderGraphElements({
|
|
34286
|
+
state,
|
|
34287
|
+
dispatch
|
|
34288
|
+
});
|
|
34050
34289
|
return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
|
|
34051
34290
|
value: {
|
|
34052
34291
|
range: state.range,
|
|
@@ -34078,7 +34317,7 @@ const MafsGraph = props => {
|
|
|
34078
34317
|
handleKeyboardEvent(event, state, dispatch);
|
|
34079
34318
|
},
|
|
34080
34319
|
"aria-label": fullGraphAriaLabel,
|
|
34081
|
-
"aria-describedby": fullGraphAriaDescription
|
|
34320
|
+
"aria-describedby": describedByIds(fullGraphAriaDescription && descriptionId, interactiveElementsDescription && interactiveElementsDescriptionId),
|
|
34082
34321
|
ref: graphRef,
|
|
34083
34322
|
tabIndex: 0,
|
|
34084
34323
|
onFocus: event => {
|
|
@@ -34090,12 +34329,12 @@ const MafsGraph = props => {
|
|
|
34090
34329
|
}, fullGraphAriaDescription && /*#__PURE__*/React.createElement(View, {
|
|
34091
34330
|
id: descriptionId,
|
|
34092
34331
|
tabIndex: -1,
|
|
34093
|
-
|
|
34094
|
-
|
|
34095
|
-
|
|
34096
|
-
|
|
34097
|
-
|
|
34098
|
-
},
|
|
34332
|
+
className: "mafs-sr-only"
|
|
34333
|
+
}, fullGraphAriaDescription), interactiveElementsDescription && /*#__PURE__*/React.createElement(View, {
|
|
34334
|
+
id: interactiveElementsDescriptionId,
|
|
34335
|
+
tabIndex: -1,
|
|
34336
|
+
className: "mafs-sr-only"
|
|
34337
|
+
}, interactiveElementsDescription), /*#__PURE__*/React.createElement(LegacyGrid, {
|
|
34099
34338
|
box: props.box,
|
|
34100
34339
|
backgroundImage: props.backgroundImage
|
|
34101
34340
|
}), /*#__PURE__*/React.createElement(View, {
|
|
@@ -34146,10 +34385,7 @@ const MafsGraph = props => {
|
|
|
34146
34385
|
zoom: false,
|
|
34147
34386
|
width: width,
|
|
34148
34387
|
height: height
|
|
34149
|
-
}, /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.showProtractor && /*#__PURE__*/React.createElement(Protractor, null),
|
|
34150
|
-
state,
|
|
34151
|
-
dispatch
|
|
34152
|
-
}))))), interactionPrompt && /*#__PURE__*/React.createElement(View, {
|
|
34388
|
+
}, /*#__PURE__*/React.createElement("svg", nestedSVGAttributes, props.showProtractor && /*#__PURE__*/React.createElement(Protractor, null), graph)))), interactionPrompt && /*#__PURE__*/React.createElement(View, {
|
|
34153
34389
|
style: {
|
|
34154
34390
|
textAlign: "center",
|
|
34155
34391
|
backgroundColor: "white",
|
|
@@ -34382,7 +34618,7 @@ const calculateNestedSVGCoords = (range, width, height) => {
|
|
|
34382
34618
|
viewboxY
|
|
34383
34619
|
};
|
|
34384
34620
|
};
|
|
34385
|
-
const
|
|
34621
|
+
const renderGraphElements = props => {
|
|
34386
34622
|
const {
|
|
34387
34623
|
state,
|
|
34388
34624
|
dispatch
|
|
@@ -34392,62 +34628,41 @@ const renderGraph = props => {
|
|
|
34392
34628
|
} = state;
|
|
34393
34629
|
switch (type) {
|
|
34394
34630
|
case "angle":
|
|
34395
|
-
return
|
|
34396
|
-
graphState: state,
|
|
34397
|
-
dispatch: dispatch
|
|
34398
|
-
});
|
|
34631
|
+
return renderAngleGraph(state, dispatch);
|
|
34399
34632
|
case "segment":
|
|
34400
|
-
return
|
|
34401
|
-
graphState: state,
|
|
34402
|
-
dispatch: dispatch
|
|
34403
|
-
});
|
|
34633
|
+
return renderSegmentGraph(state, dispatch);
|
|
34404
34634
|
case "linear-system":
|
|
34405
|
-
return
|
|
34406
|
-
graphState: state,
|
|
34407
|
-
dispatch: dispatch
|
|
34408
|
-
});
|
|
34635
|
+
return renderLinearSystemGraph(state, dispatch);
|
|
34409
34636
|
case "linear":
|
|
34410
|
-
return
|
|
34411
|
-
graphState: state,
|
|
34412
|
-
dispatch: dispatch
|
|
34413
|
-
});
|
|
34637
|
+
return renderLinearGraph(state, dispatch);
|
|
34414
34638
|
case "ray":
|
|
34415
|
-
return
|
|
34416
|
-
graphState: state,
|
|
34417
|
-
dispatch: dispatch
|
|
34418
|
-
});
|
|
34639
|
+
return renderRayGraph(state, dispatch);
|
|
34419
34640
|
case "polygon":
|
|
34420
|
-
return
|
|
34421
|
-
graphState: state,
|
|
34422
|
-
dispatch: dispatch
|
|
34423
|
-
});
|
|
34641
|
+
return renderPolygonGraph(state, dispatch);
|
|
34424
34642
|
case "point":
|
|
34425
|
-
return
|
|
34426
|
-
graphState: state,
|
|
34427
|
-
dispatch: dispatch
|
|
34428
|
-
});
|
|
34643
|
+
return renderPointGraph(state, dispatch);
|
|
34429
34644
|
case "circle":
|
|
34430
|
-
return
|
|
34431
|
-
graphState: state,
|
|
34432
|
-
dispatch: dispatch
|
|
34433
|
-
});
|
|
34645
|
+
return renderCircleGraph(state, dispatch);
|
|
34434
34646
|
case "quadratic":
|
|
34435
|
-
return
|
|
34436
|
-
graphState: state,
|
|
34437
|
-
dispatch: dispatch
|
|
34438
|
-
});
|
|
34647
|
+
return renderQuadraticGraph(state, dispatch);
|
|
34439
34648
|
case "sinusoid":
|
|
34440
|
-
return
|
|
34441
|
-
graphState: state,
|
|
34442
|
-
dispatch: dispatch
|
|
34443
|
-
});
|
|
34649
|
+
return renderSinusoidGraph(state, dispatch);
|
|
34444
34650
|
case "none":
|
|
34445
|
-
return
|
|
34651
|
+
return {
|
|
34652
|
+
graph: null,
|
|
34653
|
+
interactiveElementsDescription: null
|
|
34654
|
+
};
|
|
34446
34655
|
default:
|
|
34447
34656
|
throw new UnreachableCaseError(type);
|
|
34448
34657
|
}
|
|
34449
34658
|
};
|
|
34450
34659
|
|
|
34660
|
+
// Returns a space-separated string like "foo bar" given several optional
|
|
34661
|
+
// string IDs. If all args are falsy, returns undefined.
|
|
34662
|
+
function describedByIds(...args) {
|
|
34663
|
+
return args.filter(Boolean).join(" ") || undefined;
|
|
34664
|
+
}
|
|
34665
|
+
|
|
34451
34666
|
// Converts the state of a StatefulMafsGraph back to the format used to
|
|
34452
34667
|
// represent graph state in the widget JSON.
|
|
34453
34668
|
//
|
|
@@ -43657,7 +43872,7 @@ const VideoTranscriptLink = props => {
|
|
|
43657
43872
|
} = props;
|
|
43658
43873
|
const {
|
|
43659
43874
|
useVideo
|
|
43660
|
-
} =
|
|
43875
|
+
} = useDependencies();
|
|
43661
43876
|
const [id, kind] = IS_URL$1.test(location) ? [getYoutubeId(location), "YOUTUBE_ID"] : [location, "READABLE_ID"];
|
|
43662
43877
|
// The result value conforms to the wonder-blocks-data `Result` type
|
|
43663
43878
|
// which is used by our GraphQL framework.
|
|
@@ -43831,7 +44046,7 @@ const init = function init(options) {
|
|
|
43831
44046
|
|
|
43832
44047
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
43833
44048
|
const libName = "@khanacademy/perseus";
|
|
43834
|
-
const libVersion = "
|
|
44049
|
+
const libVersion = "41.0.0";
|
|
43835
44050
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
43836
44051
|
|
|
43837
44052
|
const apiVersion = {
|