@khanacademy/perseus 22.7.0 → 23.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.css +2 -14
- package/dist/es/index.js +249 -73
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +2 -14
- package/dist/index.d.ts +1 -1
- package/dist/index.js +254 -72
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +3 -6
- package/dist/perseus-types.d.ts +10 -3
- package/dist/server-item-renderer.d.ts +3 -2
- package/dist/types.d.ts +2 -9
- package/dist/widgets/definition.d.ts +0 -1
- package/dist/widgets/explanation.d.ts +0 -1
- package/dist/widgets/expression.d.ts +20 -26
- package/dist/widgets/interactive-graph.d.ts +2 -1
- package/dist/widgets/interactive-graphs/graphs/index.d.ts +2 -0
- package/dist/widgets/interactive-graphs/graphs/quadratic.d.ts +9 -0
- package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +14 -0
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +6 -1
- package/dist/widgets/interactive-graphs/locked-circle.d.ts +4 -0
- package/dist/widgets/interactive-graphs/mafs-supported-graph-types.d.ts +1 -1
- package/dist/widgets/interactive-graphs/types.d.ts +9 -1
- package/dist/widgets/label-image.d.ts +6 -4
- package/dist/widgets/matcher.d.ts +0 -2
- package/dist/widgets/sorter.d.ts +0 -2
- package/package.json +4 -4
package/dist/index.css
CHANGED
|
@@ -4177,6 +4177,7 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
|
|
|
4177
4177
|
--mafs-origin-color: var(--mafs-fg);
|
|
4178
4178
|
--mafs-line-color: #555;
|
|
4179
4179
|
--mafs-line-stroke-dash-style: 4, 3;
|
|
4180
|
+
--mafs-axis-stroke-width: 1px;
|
|
4180
4181
|
--grid-line-subdivision-color: #222;
|
|
4181
4182
|
|
|
4182
4183
|
--mafs-red: #f11d0e;
|
|
@@ -4488,7 +4489,7 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
|
|
|
4488
4489
|
}
|
|
4489
4490
|
|
|
4490
4491
|
.MafsView pattern g {
|
|
4491
|
-
stroke:
|
|
4492
|
+
stroke: rgba(33, 36, 44, 0.32);
|
|
4492
4493
|
}
|
|
4493
4494
|
.axis-tick-labels {
|
|
4494
4495
|
font-size: 14px;
|
|
@@ -4524,19 +4525,6 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
|
|
|
4524
4525
|
transform: translateY(calc(-100% + 0.25em));
|
|
4525
4526
|
}
|
|
4526
4527
|
|
|
4527
|
-
.x-axis-tick-labels span,
|
|
4528
|
-
.y-axis-tick-labels span {
|
|
4529
|
-
/* The negative sign is rendered as a pseudo-element to
|
|
4530
|
-
ensure that we are positioning all labels correctly */
|
|
4531
|
-
&::before {
|
|
4532
|
-
content: attr(data-content);
|
|
4533
|
-
position: absolute;
|
|
4534
|
-
/* We're translating the symbol up by 1px to ensure it's legible around gridlines. */
|
|
4535
|
-
transform: translate(-100%, -1px);
|
|
4536
|
-
font-weight: 600; /* This helps get the symbol to render crisply */
|
|
4537
|
-
}
|
|
4538
|
-
}
|
|
4539
|
-
|
|
4540
4528
|
.y-axis-tick-labels span {
|
|
4541
4529
|
display: inline-block;
|
|
4542
4530
|
height: var(--y-axis-label-height, 20px);
|
package/dist/index.d.ts
CHANGED
|
@@ -87,7 +87,7 @@ export type { ILogger, LogErrorOptions, ErrorKind } from "./logging/log";
|
|
|
87
87
|
export type { ServerItemRenderer as ServerItemRendererComponent } from "./server-item-renderer";
|
|
88
88
|
export type { Alignment, APIOptions, APIOptionsWithDefaults, ChangeHandler, DeviceType, DomInsertCheckFn, EditorMode, FocusPath, Hint, ImageDict, ImageUploader, JiptLabelStore, JiptRenderer, PerseusDependencies, PerseusScore, Version, VideoData, VideoKind, WidgetDict, WidgetExports, } from "./types";
|
|
89
89
|
export type { ParsedValue } from "./util";
|
|
90
|
-
export type { LockedFigure, LockedFigureColor, LockedFigureType, LockedPointType, LockedLineType, PerseusGraphType, PerseusAnswerArea, PerseusExpressionWidgetOptions, Range, Size, MathFormat, InputNumberWidget, // TODO(jeremy): remove?
|
|
90
|
+
export type { LockedFigure, LockedFigureColor, LockedFigureType, LockedPointType, LockedLineType, LockedCircleType, PerseusGraphType, PerseusAnswerArea, PerseusExpressionWidgetOptions, Range, Size, MathFormat, InputNumberWidget, // TODO(jeremy): remove?
|
|
91
91
|
PerseusImageBackground, PerseusInputNumberWidgetOptions, PerseusInteractiveGraphWidgetOptions, PerseusItem, PerseusPlotterWidgetOptions, PerseusPythonProgramWidgetOptions, PerseusRadioWidgetOptions, PerseusExampleWidgetOptions, PerseusExampleGraphieWidgetOptions, PerseusSimpleMarkdownTesterWidgetOptions, PerseusRenderer, PerseusWidget, PerseusWidgetsMap, } from "./perseus-types";
|
|
92
92
|
export type { Coord } from "./interactive2/types";
|
|
93
93
|
export type { MarkerType } from "./widgets/label-image/types";
|
package/dist/index.js
CHANGED
|
@@ -1245,8 +1245,6 @@ const ApiOptions = {
|
|
|
1245
1245
|
// drawing area (scratchpad) should be allowed/disallowed.
|
|
1246
1246
|
// Previously handled by `Khan.scratchpad.enable/disable`
|
|
1247
1247
|
setDrawingAreaAvailable: PropTypes__default["default"].func,
|
|
1248
|
-
// Whether to use the Draft.js editor or the legacy textarea
|
|
1249
|
-
useDraftEditor: PropTypes__default["default"].bool,
|
|
1250
1248
|
// The color used for the hint progress indicator (eg. 1 / 3)
|
|
1251
1249
|
hintProgressColor: PropTypes__default["default"].string,
|
|
1252
1250
|
// Whether this Renderer is allowed to auto-scroll the rest of the
|
|
@@ -1256,8 +1254,6 @@ const ApiOptions = {
|
|
|
1256
1254
|
//
|
|
1257
1255
|
// Defaults to `false`.
|
|
1258
1256
|
canScrollPage: PropTypes__default["default"].bool,
|
|
1259
|
-
// Whether or not we are rendering content inside of a modal.
|
|
1260
|
-
inModal: PropTypes__default["default"].bool,
|
|
1261
1257
|
// Whether to enable the cross-out feature on multiple-choice radio
|
|
1262
1258
|
// widgets. This allows users to note which answers they believe to
|
|
1263
1259
|
// be incorrect, to find the answer by process of elimination.
|
|
@@ -1293,9 +1289,7 @@ const ApiOptions = {
|
|
|
1293
1289
|
}
|
|
1294
1290
|
},
|
|
1295
1291
|
setDrawingAreaAvailable: function () {},
|
|
1296
|
-
useDraftEditor: false,
|
|
1297
1292
|
canScrollPage: false,
|
|
1298
|
-
inModal: false,
|
|
1299
1293
|
crossOutEnabled: false,
|
|
1300
1294
|
editorChangeDelay: 0
|
|
1301
1295
|
}
|
|
@@ -17604,16 +17598,8 @@ class SvgImage extends React__namespace.Component {
|
|
|
17604
17598
|
// Props to send to all images
|
|
17605
17599
|
const imageProps = {
|
|
17606
17600
|
alt: this.props.alt,
|
|
17607
|
-
title: this.props.title
|
|
17608
|
-
tabIndex: "0"
|
|
17601
|
+
title: this.props.title
|
|
17609
17602
|
};
|
|
17610
|
-
// If alt text is present, AND overrideAriaHidden is true,
|
|
17611
|
-
// include aria-hidden to avoid screen reader from crawling texts
|
|
17612
|
-
// both from the div meant for screen readers (see svg-image.jsx)
|
|
17613
|
-
// and alt attribute.
|
|
17614
|
-
if (this.props.alt && this.props.overrideAriaHidden) {
|
|
17615
|
-
imageProps["aria-hidden"] = true;
|
|
17616
|
-
}
|
|
17617
17603
|
const width = this.props.width && this.props.width * this.props.scale;
|
|
17618
17604
|
const height = this.props.height && this.props.height * this.props.scale;
|
|
17619
17605
|
const dimensions = {
|
|
@@ -25197,8 +25183,6 @@ var CSProgram$1 = {
|
|
|
25197
25183
|
hidden: true
|
|
25198
25184
|
};
|
|
25199
25185
|
|
|
25200
|
-
// TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
|
|
25201
|
-
|
|
25202
25186
|
class Definition extends React__namespace.Component {
|
|
25203
25187
|
static contextType = PerseusI18nContext;
|
|
25204
25188
|
static defaultProps = {
|
|
@@ -25410,8 +25394,6 @@ var Dropdown$1 = {
|
|
|
25410
25394
|
|
|
25411
25395
|
/* eslint-disable react/sort-comp */
|
|
25412
25396
|
|
|
25413
|
-
// TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
|
|
25414
|
-
|
|
25415
25397
|
// transform = _.identity
|
|
25416
25398
|
|
|
25417
25399
|
function mediaQueryIsMatched(mediaQuery) {
|
|
@@ -27659,7 +27641,7 @@ class ImageWidget extends React__namespace.Component {
|
|
|
27659
27641
|
|
|
27660
27642
|
render() {
|
|
27661
27643
|
let image;
|
|
27662
|
-
|
|
27644
|
+
const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
|
|
27663
27645
|
const {
|
|
27664
27646
|
apiOptions
|
|
27665
27647
|
} = this.props;
|
|
@@ -27672,29 +27654,7 @@ class ImageWidget extends React__namespace.Component {
|
|
|
27672
27654
|
} = _ref;
|
|
27673
27655
|
return /*#__PURE__*/React__namespace.createElement(SvgImage, {
|
|
27674
27656
|
src: url,
|
|
27675
|
-
alt:
|
|
27676
|
-
/* alt text is formatted in a sr-only
|
|
27677
|
-
div next to the image in addition to
|
|
27678
|
-
the alt attribute.
|
|
27679
|
-
If there is no alt text at all,
|
|
27680
|
-
we don't put an alt attribute on
|
|
27681
|
-
the image, so that screen readers
|
|
27682
|
-
know there's something they can't
|
|
27683
|
-
read there :(.
|
|
27684
|
-
NOTE: React <=0.13 (maybe later)
|
|
27685
|
-
has a bug where it won't ever
|
|
27686
|
-
remove an attribute, so if this
|
|
27687
|
-
alt node is ever defined it's
|
|
27688
|
-
not removed. This is sort of
|
|
27689
|
-
dangerous, but we usually re-key
|
|
27690
|
-
new renderers so that they're
|
|
27691
|
-
rendered from scratch anyways,
|
|
27692
|
-
so this shouldn't be a problem
|
|
27693
|
-
in practice right now, although
|
|
27694
|
-
it will exhibit weird behaviour
|
|
27695
|
-
while editing. */
|
|
27696
|
-
this.props.alt,
|
|
27697
|
-
overrideAriaHidden: true,
|
|
27657
|
+
alt: alt,
|
|
27698
27658
|
width: backgroundImage.width,
|
|
27699
27659
|
height: backgroundImage.height,
|
|
27700
27660
|
preloader: apiOptions.imagePreloader,
|
|
@@ -27711,16 +27671,6 @@ class ImageWidget extends React__namespace.Component {
|
|
|
27711
27671
|
});
|
|
27712
27672
|
});
|
|
27713
27673
|
}
|
|
27714
|
-
if (this.props.alt) {
|
|
27715
|
-
alt = /*#__PURE__*/React__namespace.createElement("span", {
|
|
27716
|
-
className: "perseus-sr-only"
|
|
27717
|
-
}, /*#__PURE__*/React__namespace.createElement(Renderer, {
|
|
27718
|
-
content: this.props.alt,
|
|
27719
|
-
apiOptions: apiOptions,
|
|
27720
|
-
linterContext: this.props.linterContext,
|
|
27721
|
-
strings: this.context.strings
|
|
27722
|
-
}));
|
|
27723
|
-
}
|
|
27724
27674
|
|
|
27725
27675
|
// For mobile we combine an image's title and caption.
|
|
27726
27676
|
if (apiOptions.isMobile) {
|
|
@@ -27787,7 +27737,7 @@ class ImageWidget extends React__namespace.Component {
|
|
|
27787
27737
|
style: {
|
|
27788
27738
|
maxWidth: backgroundImage.width
|
|
27789
27739
|
}
|
|
27790
|
-
}, image,
|
|
27740
|
+
}, image, titleAndCaption);
|
|
27791
27741
|
}
|
|
27792
27742
|
let title;
|
|
27793
27743
|
let caption;
|
|
@@ -27819,7 +27769,7 @@ class ImageWidget extends React__namespace.Component {
|
|
|
27819
27769
|
style: {
|
|
27820
27770
|
maxWidth: backgroundImage.width
|
|
27821
27771
|
}
|
|
27822
|
-
}, title, image,
|
|
27772
|
+
}, title, image, caption);
|
|
27823
27773
|
}
|
|
27824
27774
|
static validate(userInput, rubric) {
|
|
27825
27775
|
return {
|
|
@@ -28868,7 +28818,6 @@ function AxisLabels() {
|
|
|
28868
28818
|
|
|
28869
28819
|
// Range is replaced within this file with Interval, but it is used elsewhere
|
|
28870
28820
|
// and exported from the package, so we need to keep it around.
|
|
28871
|
-
// TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
|
|
28872
28821
|
const ItemExtras = [
|
|
28873
28822
|
// The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
|
|
28874
28823
|
"calculator",
|
|
@@ -28975,6 +28924,28 @@ const lockedFigureColors = {
|
|
|
28975
28924
|
};
|
|
28976
28925
|
const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
|
|
28977
28926
|
|
|
28927
|
+
const LockedCircle = props => {
|
|
28928
|
+
const {
|
|
28929
|
+
center,
|
|
28930
|
+
radius,
|
|
28931
|
+
color,
|
|
28932
|
+
fillStyle,
|
|
28933
|
+
strokeStyle
|
|
28934
|
+
} = props;
|
|
28935
|
+
const fill = {
|
|
28936
|
+
none: 0,
|
|
28937
|
+
solid: 1,
|
|
28938
|
+
translucent: 0.4
|
|
28939
|
+
};
|
|
28940
|
+
return /*#__PURE__*/React__namespace.createElement(mafs.Circle, {
|
|
28941
|
+
center: center,
|
|
28942
|
+
radius: radius,
|
|
28943
|
+
fillOpacity: fill[fillStyle],
|
|
28944
|
+
strokeStyle: strokeStyle,
|
|
28945
|
+
color: lockedFigureColors[color]
|
|
28946
|
+
});
|
|
28947
|
+
};
|
|
28948
|
+
|
|
28978
28949
|
function pathBuilder() {
|
|
28979
28950
|
return new PathBuilder();
|
|
28980
28951
|
}
|
|
@@ -29243,6 +29214,10 @@ const GraphLockedLayer = props => {
|
|
|
29243
29214
|
key: `line-${index}`,
|
|
29244
29215
|
range: props.range
|
|
29245
29216
|
}, figure));
|
|
29217
|
+
case "circle":
|
|
29218
|
+
return /*#__PURE__*/React__namespace.createElement(LockedCircle, _extends({
|
|
29219
|
+
key: `circle-${index}`
|
|
29220
|
+
}, figure));
|
|
29246
29221
|
default:
|
|
29247
29222
|
/**
|
|
29248
29223
|
* Devlopment-time future-proofing: This should
|
|
@@ -30048,9 +30023,19 @@ function initializeGraphState(params) {
|
|
|
30048
30023
|
center: [0, 0],
|
|
30049
30024
|
radiusPoint: [1, 0]
|
|
30050
30025
|
};
|
|
30051
|
-
case "angle":
|
|
30052
|
-
case "sinusoid":
|
|
30053
30026
|
case "quadratic":
|
|
30027
|
+
return {
|
|
30028
|
+
...shared,
|
|
30029
|
+
type: graph.type,
|
|
30030
|
+
coords: getQuadraticCoords(graph, range, step)
|
|
30031
|
+
};
|
|
30032
|
+
case "sinusoid":
|
|
30033
|
+
return {
|
|
30034
|
+
...shared,
|
|
30035
|
+
type: graph.type,
|
|
30036
|
+
coords: getSinusoidCoords(graph, range, step)
|
|
30037
|
+
};
|
|
30038
|
+
case "angle":
|
|
30054
30039
|
throw new Error("Mafs not yet implemented for graph type: " + graph.type);
|
|
30055
30040
|
default:
|
|
30056
30041
|
throw new wonderStuffCore.UnreachableCaseError(graph);
|
|
@@ -30148,6 +30133,18 @@ function getGradableGraph(state, initialGraph) {
|
|
|
30148
30133
|
radius: getRadius(state)
|
|
30149
30134
|
};
|
|
30150
30135
|
}
|
|
30136
|
+
if (state.type === "quadratic" && initialGraph.type === "quadratic") {
|
|
30137
|
+
return {
|
|
30138
|
+
...initialGraph,
|
|
30139
|
+
coords: state.coords
|
|
30140
|
+
};
|
|
30141
|
+
}
|
|
30142
|
+
if (state.type === "sinusoid" && initialGraph.type === "sinusoid") {
|
|
30143
|
+
return {
|
|
30144
|
+
...initialGraph,
|
|
30145
|
+
coords: state.coords
|
|
30146
|
+
};
|
|
30147
|
+
}
|
|
30151
30148
|
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
30152
30149
|
}
|
|
30153
30150
|
const getDefaultSegments = _ref2 => {
|
|
@@ -30227,6 +30224,16 @@ const getPolygonCoords = _ref4 => {
|
|
|
30227
30224
|
coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
|
|
30228
30225
|
return coords;
|
|
30229
30226
|
};
|
|
30227
|
+
const getSinusoidCoords = (graph, range, step) => {
|
|
30228
|
+
let coords = [[0.5, 0.5], [0.65, 0.6]];
|
|
30229
|
+
coords = normalizePoints(range, step, coords, true);
|
|
30230
|
+
return coords;
|
|
30231
|
+
};
|
|
30232
|
+
const getQuadraticCoords = (graph, range, step) => {
|
|
30233
|
+
let coords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
|
|
30234
|
+
coords = normalizePoints(range, step, coords, true);
|
|
30235
|
+
return coords;
|
|
30236
|
+
};
|
|
30230
30237
|
|
|
30231
30238
|
/**
|
|
30232
30239
|
* determine radius of a circle graph
|
|
@@ -30336,6 +30343,138 @@ function crossProduct(as, bs) {
|
|
|
30336
30343
|
return result;
|
|
30337
30344
|
}
|
|
30338
30345
|
|
|
30346
|
+
function QuadraticGraph(props) {
|
|
30347
|
+
const {
|
|
30348
|
+
dispatch,
|
|
30349
|
+
graphState
|
|
30350
|
+
} = props;
|
|
30351
|
+
const {
|
|
30352
|
+
coords
|
|
30353
|
+
} = graphState;
|
|
30354
|
+
|
|
30355
|
+
// The coefficients are used to calculate the quadratic equation, plot the graph, and to indicate
|
|
30356
|
+
// to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
|
|
30357
|
+
// While we should technically never have invalid coordinates, we want to ensure that we have a fallback
|
|
30358
|
+
const coeffRef = React__namespace.useRef([0, 0, 0]);
|
|
30359
|
+
const coeffs = getQuadraticCoefficients(coords);
|
|
30360
|
+
if (coeffs !== undefined) {
|
|
30361
|
+
coeffRef.current = coeffs;
|
|
30362
|
+
}
|
|
30363
|
+
|
|
30364
|
+
// Destructure the coefficients for calculating the quadratic equation
|
|
30365
|
+
const [a, b, c] = coeffRef.current;
|
|
30366
|
+
|
|
30367
|
+
// Calculate the y value based on the current x value
|
|
30368
|
+
const y = x => (a * x + b) * x + c;
|
|
30369
|
+
const handleOnMove = (destination, elementId) => {
|
|
30370
|
+
// If the destination is invalid, we want do not want to move the point
|
|
30371
|
+
const validDestination = isValidDestination(destination, elementId, coords);
|
|
30372
|
+
if (validDestination === false) {
|
|
30373
|
+
return;
|
|
30374
|
+
}
|
|
30375
|
+
dispatch(movePoint(elementId, destination));
|
|
30376
|
+
};
|
|
30377
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
|
|
30378
|
+
y: y,
|
|
30379
|
+
color: wonderBlocksTokens.color.blue
|
|
30380
|
+
}), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
|
|
30381
|
+
key: "point-" + i,
|
|
30382
|
+
point: coord,
|
|
30383
|
+
onMove: destination => handleOnMove(destination, i)
|
|
30384
|
+
})));
|
|
30385
|
+
}
|
|
30386
|
+
|
|
30387
|
+
// Ensure that we are only snapping to coordinates that result in a valid quadratic equation
|
|
30388
|
+
const isValidDestination = (destination, elementId, coords) => {
|
|
30389
|
+
// Set up the new coords and check if the quadratic coefficients are valid
|
|
30390
|
+
const newCoords = [...coords];
|
|
30391
|
+
newCoords[elementId] = destination;
|
|
30392
|
+
const QuadraticCoefficients = getQuadraticCoefficients(newCoords);
|
|
30393
|
+
|
|
30394
|
+
// If the new destination results in an invalid quadratic equation, we don't want to move the point
|
|
30395
|
+
if (QuadraticCoefficients === undefined) {
|
|
30396
|
+
return false;
|
|
30397
|
+
}
|
|
30398
|
+
return true;
|
|
30399
|
+
};
|
|
30400
|
+
|
|
30401
|
+
// Get the quadratic coefficients from the 3 control points
|
|
30402
|
+
// These equations were originally set up in 2013 and may require some
|
|
30403
|
+
// additional comments to help clarify the quadratic formula manipulations
|
|
30404
|
+
// Origin: https://phabricator.khanacademy.org/D2413
|
|
30405
|
+
const getQuadraticCoefficients = coords => {
|
|
30406
|
+
const p1 = coords[0];
|
|
30407
|
+
const p2 = coords[1];
|
|
30408
|
+
const p3 = coords[2];
|
|
30409
|
+
|
|
30410
|
+
// If the denominator is 0, we are going to return undefined as we are
|
|
30411
|
+
// unable to calculate the quadratic coefficients when they hit infinity
|
|
30412
|
+
const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
|
|
30413
|
+
if (denom === 0) {
|
|
30414
|
+
return;
|
|
30415
|
+
}
|
|
30416
|
+
const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
|
|
30417
|
+
const b = (p3[0] * p3[0] * (p1[1] - p2[1]) + p2[0] * p2[0] * (p3[1] - p1[1]) + p1[0] * p1[0] * (p2[1] - p3[1])) / denom;
|
|
30418
|
+
const c = (p2[0] * p3[0] * (p2[0] - p3[0]) * p1[1] + p3[0] * p1[0] * (p3[0] - p1[0]) * p2[1] + p1[0] * p2[0] * (p1[0] - p2[0]) * p3[1]) / denom;
|
|
30419
|
+
return [a, b, c];
|
|
30420
|
+
};
|
|
30421
|
+
|
|
30422
|
+
function SinusoidGraph(props) {
|
|
30423
|
+
const {
|
|
30424
|
+
dispatch,
|
|
30425
|
+
graphState
|
|
30426
|
+
} = props;
|
|
30427
|
+
|
|
30428
|
+
// Destructure the coordinates from the graph state
|
|
30429
|
+
// Note: The order of the coordinates is important:
|
|
30430
|
+
// The coords[0] is the root and the coords[1] is the first peak
|
|
30431
|
+
const {
|
|
30432
|
+
coords
|
|
30433
|
+
} = graphState;
|
|
30434
|
+
|
|
30435
|
+
// Get the coefficients for calculating the quadratic equation
|
|
30436
|
+
const coeffs = getSinusoidCoefficients(coords);
|
|
30437
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
|
|
30438
|
+
y: x => computeSine(x, coeffs),
|
|
30439
|
+
color: wonderBlocksTokens.color.blue
|
|
30440
|
+
}), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
|
|
30441
|
+
key: "point-" + i,
|
|
30442
|
+
point: coord,
|
|
30443
|
+
onMove: destination => dispatch(movePoint(i, destination))
|
|
30444
|
+
})));
|
|
30445
|
+
}
|
|
30446
|
+
|
|
30447
|
+
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
30448
|
+
const computeSine = function (x,
|
|
30449
|
+
// x-coordinate
|
|
30450
|
+
sinusoidCoefficients) {
|
|
30451
|
+
// Break down the coefficients for the sine function to improve readability
|
|
30452
|
+
const {
|
|
30453
|
+
amplitude: a,
|
|
30454
|
+
angularFrequency: b,
|
|
30455
|
+
phase: c,
|
|
30456
|
+
verticalOffset: d
|
|
30457
|
+
} = sinusoidCoefficients;
|
|
30458
|
+
return a * Math.sin(b * x - c) + d;
|
|
30459
|
+
};
|
|
30460
|
+
const getSinusoidCoefficients = coords => {
|
|
30461
|
+
// It's assumed that p1 is the root and p2 is the first peak
|
|
30462
|
+
const p1 = coords[0];
|
|
30463
|
+
const p2 = coords[1];
|
|
30464
|
+
|
|
30465
|
+
// Resulting coefficients are canonical for this sine curve
|
|
30466
|
+
const amplitude = p2[1] - p1[1];
|
|
30467
|
+
const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
|
|
30468
|
+
const phase = p1[0] * angularFrequency;
|
|
30469
|
+
const verticalOffset = p1[1];
|
|
30470
|
+
return {
|
|
30471
|
+
amplitude,
|
|
30472
|
+
angularFrequency,
|
|
30473
|
+
phase,
|
|
30474
|
+
verticalOffset
|
|
30475
|
+
};
|
|
30476
|
+
};
|
|
30477
|
+
|
|
30339
30478
|
// Universal padding for the axis ticks to ensure that the labels
|
|
30340
30479
|
// have enough space to render without overlapping the tick lines
|
|
30341
30480
|
const tickPadding = 5;
|
|
@@ -30496,9 +30635,6 @@ const AxisTickLabel = _ref => {
|
|
|
30496
30635
|
const pointOnAxis = axis === "x" ? [label, 0] : [0, label];
|
|
30497
30636
|
const pixelPoint = pointToPixel(pointOnAxis, graphConfig);
|
|
30498
30637
|
|
|
30499
|
-
// Remove the negative sign from the label string as it is handled in the data-content attribute
|
|
30500
|
-
const labelString = Math.abs(label).toString();
|
|
30501
|
-
|
|
30502
30638
|
// Determine the relevant edge of the graph based on the axis type
|
|
30503
30639
|
const graphEdge = axis === "x" ? graphConfig.width : graphConfig.height;
|
|
30504
30640
|
|
|
@@ -30514,15 +30650,9 @@ const AxisTickLabel = _ref => {
|
|
|
30514
30650
|
gridStep: gridStep[vectorIndex],
|
|
30515
30651
|
tickStep: tickStep[vectorIndex]
|
|
30516
30652
|
});
|
|
30517
|
-
|
|
30518
|
-
// Determine whether or not to render a negative symbol for the tick label
|
|
30519
|
-
// as the negative sign is handled in the data-content attribute in order to
|
|
30520
|
-
// maintain the correct spacing between the labels
|
|
30521
|
-
const dataContent = label < 0 && shouldShowLabel ? "−" : null;
|
|
30522
30653
|
return /*#__PURE__*/React__namespace.createElement("span", {
|
|
30523
|
-
className: `${axis}-axis-tick-label
|
|
30524
|
-
|
|
30525
|
-
}, shouldShowLabel && labelString);
|
|
30654
|
+
className: `${axis}-axis-tick-label`
|
|
30655
|
+
}, shouldShowLabel && label);
|
|
30526
30656
|
};
|
|
30527
30657
|
|
|
30528
30658
|
// Determine whether or not to show the tick label text
|
|
@@ -31477,6 +31607,8 @@ function doMoveControlPoint(state, action) {
|
|
|
31477
31607
|
throw new Error("FIXME implement circle reducer");
|
|
31478
31608
|
case "point":
|
|
31479
31609
|
case "polygon":
|
|
31610
|
+
case "quadratic":
|
|
31611
|
+
case "sinusoid":
|
|
31480
31612
|
throw new Error(`Don't use moveControlPoint for ${state.type} graphs. Use movePoint instead!`);
|
|
31481
31613
|
default:
|
|
31482
31614
|
throw new wonderStuffCore.UnreachableCaseError(state);
|
|
@@ -31549,8 +31681,48 @@ function doMoveAll(state, action) {
|
|
|
31549
31681
|
}
|
|
31550
31682
|
function doMovePoint(state, action) {
|
|
31551
31683
|
switch (state.type) {
|
|
31552
|
-
case "point":
|
|
31553
31684
|
case "polygon":
|
|
31685
|
+
case "point":
|
|
31686
|
+
{
|
|
31687
|
+
return {
|
|
31688
|
+
...state,
|
|
31689
|
+
hasBeenInteractedWith: true,
|
|
31690
|
+
coords: setAtIndex({
|
|
31691
|
+
array: state.coords,
|
|
31692
|
+
index: action.index,
|
|
31693
|
+
newValue: snap(state.snapStep, bound$1({
|
|
31694
|
+
snapStep: state.snapStep,
|
|
31695
|
+
range: state.range,
|
|
31696
|
+
point: action.destination
|
|
31697
|
+
}))
|
|
31698
|
+
})
|
|
31699
|
+
};
|
|
31700
|
+
}
|
|
31701
|
+
case "sinusoid":
|
|
31702
|
+
{
|
|
31703
|
+
// First, we need to verify that the new coordinates are not on the same vertical line
|
|
31704
|
+
// If they are, we don't want to move the point
|
|
31705
|
+
const destination = action.destination;
|
|
31706
|
+
const newCoords = [...state.coords];
|
|
31707
|
+
newCoords[action.index] = action.destination;
|
|
31708
|
+
if (newCoords[0][0] === newCoords[1][0]) {
|
|
31709
|
+
return state;
|
|
31710
|
+
}
|
|
31711
|
+
return {
|
|
31712
|
+
...state,
|
|
31713
|
+
hasBeenInteractedWith: true,
|
|
31714
|
+
coords: setAtIndex({
|
|
31715
|
+
array: state.coords,
|
|
31716
|
+
index: action.index,
|
|
31717
|
+
newValue: snap(state.snapStep, bound$1({
|
|
31718
|
+
snapStep: state.snapStep,
|
|
31719
|
+
range: state.range,
|
|
31720
|
+
point: destination
|
|
31721
|
+
}))
|
|
31722
|
+
})
|
|
31723
|
+
};
|
|
31724
|
+
}
|
|
31725
|
+
case "quadratic":
|
|
31554
31726
|
{
|
|
31555
31727
|
return {
|
|
31556
31728
|
...state,
|
|
@@ -31567,7 +31739,7 @@ function doMovePoint(state, action) {
|
|
|
31567
31739
|
};
|
|
31568
31740
|
}
|
|
31569
31741
|
default:
|
|
31570
|
-
throw new Error("The movePoint action is only for point and polygon graphs");
|
|
31742
|
+
throw new Error("The movePoint action is only for point, quadratic, and polygon graphs");
|
|
31571
31743
|
}
|
|
31572
31744
|
}
|
|
31573
31745
|
function doMoveCenter(state, action) {
|
|
@@ -31789,6 +31961,16 @@ const renderGraph = props => {
|
|
|
31789
31961
|
graphState: state,
|
|
31790
31962
|
dispatch: dispatch
|
|
31791
31963
|
});
|
|
31964
|
+
case "quadratic":
|
|
31965
|
+
return /*#__PURE__*/React__namespace.createElement(QuadraticGraph, {
|
|
31966
|
+
graphState: state,
|
|
31967
|
+
dispatch: dispatch
|
|
31968
|
+
});
|
|
31969
|
+
case "sinusoid":
|
|
31970
|
+
return /*#__PURE__*/React__namespace.createElement(SinusoidGraph, {
|
|
31971
|
+
graphState: state,
|
|
31972
|
+
dispatch: dispatch
|
|
31973
|
+
});
|
|
31792
31974
|
default:
|
|
31793
31975
|
return new wonderStuffCore.UnreachableCaseError(type);
|
|
31794
31976
|
}
|
|
@@ -44002,7 +44184,7 @@ const init = function (options) {
|
|
|
44002
44184
|
|
|
44003
44185
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44004
44186
|
const libName = "@khanacademy/perseus";
|
|
44005
|
-
const libVersion = "
|
|
44187
|
+
const libVersion = "23.1.0";
|
|
44006
44188
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44007
44189
|
|
|
44008
44190
|
const apiVersion = {
|