@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/es/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/es/index.js
CHANGED
|
@@ -29,7 +29,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
|
|
|
29
29
|
import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
|
|
30
30
|
import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
|
|
31
31
|
import debounce$2 from 'lodash.debounce';
|
|
32
|
-
import { vec, Line as Line$4, Point as Point$2, useMovable, Text as Text$1, Polygon, Coordinates, Mafs } from 'mafs';
|
|
32
|
+
import { vec, Circle as Circle$1, Line as Line$4, Point as Point$2, useMovable, Text as Text$1, Polygon, Plot as Plot$3, Coordinates, Mafs } from 'mafs';
|
|
33
33
|
import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
|
|
34
34
|
import { Body, LabelSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
|
|
35
35
|
import questionIcon from '@phosphor-icons/core/regular/question.svg';
|
|
@@ -1142,8 +1142,6 @@ const ApiOptions = {
|
|
|
1142
1142
|
// drawing area (scratchpad) should be allowed/disallowed.
|
|
1143
1143
|
// Previously handled by `Khan.scratchpad.enable/disable`
|
|
1144
1144
|
setDrawingAreaAvailable: PropTypes.func,
|
|
1145
|
-
// Whether to use the Draft.js editor or the legacy textarea
|
|
1146
|
-
useDraftEditor: PropTypes.bool,
|
|
1147
1145
|
// The color used for the hint progress indicator (eg. 1 / 3)
|
|
1148
1146
|
hintProgressColor: PropTypes.string,
|
|
1149
1147
|
// Whether this Renderer is allowed to auto-scroll the rest of the
|
|
@@ -1153,8 +1151,6 @@ const ApiOptions = {
|
|
|
1153
1151
|
//
|
|
1154
1152
|
// Defaults to `false`.
|
|
1155
1153
|
canScrollPage: PropTypes.bool,
|
|
1156
|
-
// Whether or not we are rendering content inside of a modal.
|
|
1157
|
-
inModal: PropTypes.bool,
|
|
1158
1154
|
// Whether to enable the cross-out feature on multiple-choice radio
|
|
1159
1155
|
// widgets. This allows users to note which answers they believe to
|
|
1160
1156
|
// be incorrect, to find the answer by process of elimination.
|
|
@@ -1190,9 +1186,7 @@ const ApiOptions = {
|
|
|
1190
1186
|
}
|
|
1191
1187
|
},
|
|
1192
1188
|
setDrawingAreaAvailable: function () {},
|
|
1193
|
-
useDraftEditor: false,
|
|
1194
1189
|
canScrollPage: false,
|
|
1195
|
-
inModal: false,
|
|
1196
1190
|
crossOutEnabled: false,
|
|
1197
1191
|
editorChangeDelay: 0
|
|
1198
1192
|
}
|
|
@@ -17436,16 +17430,8 @@ class SvgImage extends React.Component {
|
|
|
17436
17430
|
// Props to send to all images
|
|
17437
17431
|
const imageProps = {
|
|
17438
17432
|
alt: this.props.alt,
|
|
17439
|
-
title: this.props.title
|
|
17440
|
-
tabIndex: "0"
|
|
17433
|
+
title: this.props.title
|
|
17441
17434
|
};
|
|
17442
|
-
// If alt text is present, AND overrideAriaHidden is true,
|
|
17443
|
-
// include aria-hidden to avoid screen reader from crawling texts
|
|
17444
|
-
// both from the div meant for screen readers (see svg-image.jsx)
|
|
17445
|
-
// and alt attribute.
|
|
17446
|
-
if (this.props.alt && this.props.overrideAriaHidden) {
|
|
17447
|
-
imageProps["aria-hidden"] = true;
|
|
17448
|
-
}
|
|
17449
17435
|
const width = this.props.width && this.props.width * this.props.scale;
|
|
17450
17436
|
const height = this.props.height && this.props.height * this.props.scale;
|
|
17451
17437
|
const dimensions = {
|
|
@@ -24950,8 +24936,6 @@ var CSProgram$1 = {
|
|
|
24950
24936
|
hidden: true
|
|
24951
24937
|
};
|
|
24952
24938
|
|
|
24953
|
-
// TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
|
|
24954
|
-
|
|
24955
24939
|
class Definition extends React.Component {
|
|
24956
24940
|
constructor(...args) {
|
|
24957
24941
|
super(...args);
|
|
@@ -25166,8 +25150,6 @@ var Dropdown$1 = {
|
|
|
25166
25150
|
|
|
25167
25151
|
/* eslint-disable react/sort-comp */
|
|
25168
25152
|
|
|
25169
|
-
// TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
|
|
25170
|
-
|
|
25171
25153
|
// transform = _.identity
|
|
25172
25154
|
|
|
25173
25155
|
function mediaQueryIsMatched(mediaQuery) {
|
|
@@ -27371,7 +27353,7 @@ class ImageWidget extends React.Component {
|
|
|
27371
27353
|
|
|
27372
27354
|
render() {
|
|
27373
27355
|
let image;
|
|
27374
|
-
|
|
27356
|
+
const alt = this.props.caption === this.props.alt ? "" : this.props.alt;
|
|
27375
27357
|
const {
|
|
27376
27358
|
apiOptions
|
|
27377
27359
|
} = this.props;
|
|
@@ -27382,29 +27364,7 @@ class ImageWidget extends React.Component {
|
|
|
27382
27364
|
setAssetStatus
|
|
27383
27365
|
}) => /*#__PURE__*/React.createElement(SvgImage, {
|
|
27384
27366
|
src: url,
|
|
27385
|
-
alt:
|
|
27386
|
-
/* alt text is formatted in a sr-only
|
|
27387
|
-
div next to the image in addition to
|
|
27388
|
-
the alt attribute.
|
|
27389
|
-
If there is no alt text at all,
|
|
27390
|
-
we don't put an alt attribute on
|
|
27391
|
-
the image, so that screen readers
|
|
27392
|
-
know there's something they can't
|
|
27393
|
-
read there :(.
|
|
27394
|
-
NOTE: React <=0.13 (maybe later)
|
|
27395
|
-
has a bug where it won't ever
|
|
27396
|
-
remove an attribute, so if this
|
|
27397
|
-
alt node is ever defined it's
|
|
27398
|
-
not removed. This is sort of
|
|
27399
|
-
dangerous, but we usually re-key
|
|
27400
|
-
new renderers so that they're
|
|
27401
|
-
rendered from scratch anyways,
|
|
27402
|
-
so this shouldn't be a problem
|
|
27403
|
-
in practice right now, although
|
|
27404
|
-
it will exhibit weird behaviour
|
|
27405
|
-
while editing. */
|
|
27406
|
-
this.props.alt,
|
|
27407
|
-
overrideAriaHidden: true,
|
|
27367
|
+
alt: alt,
|
|
27408
27368
|
width: backgroundImage.width,
|
|
27409
27369
|
height: backgroundImage.height,
|
|
27410
27370
|
preloader: apiOptions.imagePreloader,
|
|
@@ -27420,16 +27380,6 @@ class ImageWidget extends React.Component {
|
|
|
27420
27380
|
setAssetStatus: setAssetStatus
|
|
27421
27381
|
}));
|
|
27422
27382
|
}
|
|
27423
|
-
if (this.props.alt) {
|
|
27424
|
-
alt = /*#__PURE__*/React.createElement("span", {
|
|
27425
|
-
className: "perseus-sr-only"
|
|
27426
|
-
}, /*#__PURE__*/React.createElement(Renderer, {
|
|
27427
|
-
content: this.props.alt,
|
|
27428
|
-
apiOptions: apiOptions,
|
|
27429
|
-
linterContext: this.props.linterContext,
|
|
27430
|
-
strings: this.context.strings
|
|
27431
|
-
}));
|
|
27432
|
-
}
|
|
27433
27383
|
|
|
27434
27384
|
// For mobile we combine an image's title and caption.
|
|
27435
27385
|
if (apiOptions.isMobile) {
|
|
@@ -27496,7 +27446,7 @@ class ImageWidget extends React.Component {
|
|
|
27496
27446
|
style: {
|
|
27497
27447
|
maxWidth: backgroundImage.width
|
|
27498
27448
|
}
|
|
27499
|
-
}, image,
|
|
27449
|
+
}, image, titleAndCaption);
|
|
27500
27450
|
}
|
|
27501
27451
|
let title;
|
|
27502
27452
|
let caption;
|
|
@@ -27528,7 +27478,7 @@ class ImageWidget extends React.Component {
|
|
|
27528
27478
|
style: {
|
|
27529
27479
|
maxWidth: backgroundImage.width
|
|
27530
27480
|
}
|
|
27531
|
-
}, title, image,
|
|
27481
|
+
}, title, image, caption);
|
|
27532
27482
|
}
|
|
27533
27483
|
static validate(userInput, rubric) {
|
|
27534
27484
|
return {
|
|
@@ -28577,7 +28527,6 @@ function AxisLabels() {
|
|
|
28577
28527
|
|
|
28578
28528
|
// Range is replaced within this file with Interval, but it is used elsewhere
|
|
28579
28529
|
// and exported from the package, so we need to keep it around.
|
|
28580
|
-
// TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
|
|
28581
28530
|
const ItemExtras = [
|
|
28582
28531
|
// The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
|
|
28583
28532
|
"calculator",
|
|
@@ -28684,6 +28633,28 @@ const lockedFigureColors = {
|
|
|
28684
28633
|
};
|
|
28685
28634
|
const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
|
|
28686
28635
|
|
|
28636
|
+
const LockedCircle = props => {
|
|
28637
|
+
const {
|
|
28638
|
+
center,
|
|
28639
|
+
radius,
|
|
28640
|
+
color,
|
|
28641
|
+
fillStyle,
|
|
28642
|
+
strokeStyle
|
|
28643
|
+
} = props;
|
|
28644
|
+
const fill = {
|
|
28645
|
+
none: 0,
|
|
28646
|
+
solid: 1,
|
|
28647
|
+
translucent: 0.4
|
|
28648
|
+
};
|
|
28649
|
+
return /*#__PURE__*/React.createElement(Circle$1, {
|
|
28650
|
+
center: center,
|
|
28651
|
+
radius: radius,
|
|
28652
|
+
fillOpacity: fill[fillStyle],
|
|
28653
|
+
strokeStyle: strokeStyle,
|
|
28654
|
+
color: lockedFigureColors[color]
|
|
28655
|
+
});
|
|
28656
|
+
};
|
|
28657
|
+
|
|
28687
28658
|
function pathBuilder() {
|
|
28688
28659
|
return new PathBuilder();
|
|
28689
28660
|
}
|
|
@@ -28953,6 +28924,10 @@ const GraphLockedLayer = props => {
|
|
|
28953
28924
|
key: `line-${index}`,
|
|
28954
28925
|
range: props.range
|
|
28955
28926
|
}, figure));
|
|
28927
|
+
case "circle":
|
|
28928
|
+
return /*#__PURE__*/React.createElement(LockedCircle, _extends({
|
|
28929
|
+
key: `circle-${index}`
|
|
28930
|
+
}, figure));
|
|
28956
28931
|
default:
|
|
28957
28932
|
/**
|
|
28958
28933
|
* Devlopment-time future-proofing: This should
|
|
@@ -29742,9 +29717,17 @@ function initializeGraphState(params) {
|
|
|
29742
29717
|
center: [0, 0],
|
|
29743
29718
|
radiusPoint: [1, 0]
|
|
29744
29719
|
});
|
|
29745
|
-
case "angle":
|
|
29746
|
-
case "sinusoid":
|
|
29747
29720
|
case "quadratic":
|
|
29721
|
+
return _extends({}, shared, {
|
|
29722
|
+
type: graph.type,
|
|
29723
|
+
coords: getQuadraticCoords(graph, range, step)
|
|
29724
|
+
});
|
|
29725
|
+
case "sinusoid":
|
|
29726
|
+
return _extends({}, shared, {
|
|
29727
|
+
type: graph.type,
|
|
29728
|
+
coords: getSinusoidCoords(graph, range, step)
|
|
29729
|
+
});
|
|
29730
|
+
case "angle":
|
|
29748
29731
|
throw new Error("Mafs not yet implemented for graph type: " + graph.type);
|
|
29749
29732
|
default:
|
|
29750
29733
|
throw new UnreachableCaseError(graph);
|
|
@@ -29833,6 +29816,16 @@ function getGradableGraph(state, initialGraph) {
|
|
|
29833
29816
|
radius: getRadius(state)
|
|
29834
29817
|
});
|
|
29835
29818
|
}
|
|
29819
|
+
if (state.type === "quadratic" && initialGraph.type === "quadratic") {
|
|
29820
|
+
return _extends({}, initialGraph, {
|
|
29821
|
+
coords: state.coords
|
|
29822
|
+
});
|
|
29823
|
+
}
|
|
29824
|
+
if (state.type === "sinusoid" && initialGraph.type === "sinusoid") {
|
|
29825
|
+
return _extends({}, initialGraph, {
|
|
29826
|
+
coords: state.coords
|
|
29827
|
+
});
|
|
29828
|
+
}
|
|
29836
29829
|
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
29837
29830
|
}
|
|
29838
29831
|
const getDefaultSegments = ({
|
|
@@ -29910,6 +29903,16 @@ const getPolygonCoords = ({
|
|
|
29910
29903
|
coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
|
|
29911
29904
|
return coords;
|
|
29912
29905
|
};
|
|
29906
|
+
const getSinusoidCoords = (graph, range, step) => {
|
|
29907
|
+
let coords = [[0.5, 0.5], [0.65, 0.6]];
|
|
29908
|
+
coords = normalizePoints(range, step, coords, true);
|
|
29909
|
+
return coords;
|
|
29910
|
+
};
|
|
29911
|
+
const getQuadraticCoords = (graph, range, step) => {
|
|
29912
|
+
let coords = [[0.25, 0.75], [0.5, 0.25], [0.75, 0.75]];
|
|
29913
|
+
coords = normalizePoints(range, step, coords, true);
|
|
29914
|
+
return coords;
|
|
29915
|
+
};
|
|
29913
29916
|
|
|
29914
29917
|
/**
|
|
29915
29918
|
* determine radius of a circle graph
|
|
@@ -30019,6 +30022,138 @@ function crossProduct(as, bs) {
|
|
|
30019
30022
|
return result;
|
|
30020
30023
|
}
|
|
30021
30024
|
|
|
30025
|
+
function QuadraticGraph(props) {
|
|
30026
|
+
const {
|
|
30027
|
+
dispatch,
|
|
30028
|
+
graphState
|
|
30029
|
+
} = props;
|
|
30030
|
+
const {
|
|
30031
|
+
coords
|
|
30032
|
+
} = graphState;
|
|
30033
|
+
|
|
30034
|
+
// The coefficients are used to calculate the quadratic equation, plot the graph, and to indicate
|
|
30035
|
+
// to content creators the currently selected "correct answer". ex: y = 0.200x^2 + 0.000x + 0.000
|
|
30036
|
+
// While we should technically never have invalid coordinates, we want to ensure that we have a fallback
|
|
30037
|
+
const coeffRef = React.useRef([0, 0, 0]);
|
|
30038
|
+
const coeffs = getQuadraticCoefficients(coords);
|
|
30039
|
+
if (coeffs !== undefined) {
|
|
30040
|
+
coeffRef.current = coeffs;
|
|
30041
|
+
}
|
|
30042
|
+
|
|
30043
|
+
// Destructure the coefficients for calculating the quadratic equation
|
|
30044
|
+
const [a, b, c] = coeffRef.current;
|
|
30045
|
+
|
|
30046
|
+
// Calculate the y value based on the current x value
|
|
30047
|
+
const y = x => (a * x + b) * x + c;
|
|
30048
|
+
const handleOnMove = (destination, elementId) => {
|
|
30049
|
+
// If the destination is invalid, we want do not want to move the point
|
|
30050
|
+
const validDestination = isValidDestination(destination, elementId, coords);
|
|
30051
|
+
if (validDestination === false) {
|
|
30052
|
+
return;
|
|
30053
|
+
}
|
|
30054
|
+
dispatch(movePoint(elementId, destination));
|
|
30055
|
+
};
|
|
30056
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$3.OfX, {
|
|
30057
|
+
y: y,
|
|
30058
|
+
color: color.blue
|
|
30059
|
+
}), coords.map((coord, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
|
|
30060
|
+
key: "point-" + i,
|
|
30061
|
+
point: coord,
|
|
30062
|
+
onMove: destination => handleOnMove(destination, i)
|
|
30063
|
+
})));
|
|
30064
|
+
}
|
|
30065
|
+
|
|
30066
|
+
// Ensure that we are only snapping to coordinates that result in a valid quadratic equation
|
|
30067
|
+
const isValidDestination = (destination, elementId, coords) => {
|
|
30068
|
+
// Set up the new coords and check if the quadratic coefficients are valid
|
|
30069
|
+
const newCoords = [...coords];
|
|
30070
|
+
newCoords[elementId] = destination;
|
|
30071
|
+
const QuadraticCoefficients = getQuadraticCoefficients(newCoords);
|
|
30072
|
+
|
|
30073
|
+
// If the new destination results in an invalid quadratic equation, we don't want to move the point
|
|
30074
|
+
if (QuadraticCoefficients === undefined) {
|
|
30075
|
+
return false;
|
|
30076
|
+
}
|
|
30077
|
+
return true;
|
|
30078
|
+
};
|
|
30079
|
+
|
|
30080
|
+
// Get the quadratic coefficients from the 3 control points
|
|
30081
|
+
// These equations were originally set up in 2013 and may require some
|
|
30082
|
+
// additional comments to help clarify the quadratic formula manipulations
|
|
30083
|
+
// Origin: https://phabricator.khanacademy.org/D2413
|
|
30084
|
+
const getQuadraticCoefficients = coords => {
|
|
30085
|
+
const p1 = coords[0];
|
|
30086
|
+
const p2 = coords[1];
|
|
30087
|
+
const p3 = coords[2];
|
|
30088
|
+
|
|
30089
|
+
// If the denominator is 0, we are going to return undefined as we are
|
|
30090
|
+
// unable to calculate the quadratic coefficients when they hit infinity
|
|
30091
|
+
const denom = (p1[0] - p2[0]) * (p1[0] - p3[0]) * (p2[0] - p3[0]);
|
|
30092
|
+
if (denom === 0) {
|
|
30093
|
+
return;
|
|
30094
|
+
}
|
|
30095
|
+
const a = (p3[0] * (p2[1] - p1[1]) + p2[0] * (p1[1] - p3[1]) + p1[0] * (p3[1] - p2[1])) / denom;
|
|
30096
|
+
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;
|
|
30097
|
+
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;
|
|
30098
|
+
return [a, b, c];
|
|
30099
|
+
};
|
|
30100
|
+
|
|
30101
|
+
function SinusoidGraph(props) {
|
|
30102
|
+
const {
|
|
30103
|
+
dispatch,
|
|
30104
|
+
graphState
|
|
30105
|
+
} = props;
|
|
30106
|
+
|
|
30107
|
+
// Destructure the coordinates from the graph state
|
|
30108
|
+
// Note: The order of the coordinates is important:
|
|
30109
|
+
// The coords[0] is the root and the coords[1] is the first peak
|
|
30110
|
+
const {
|
|
30111
|
+
coords
|
|
30112
|
+
} = graphState;
|
|
30113
|
+
|
|
30114
|
+
// Get the coefficients for calculating the quadratic equation
|
|
30115
|
+
const coeffs = getSinusoidCoefficients(coords);
|
|
30116
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$3.OfX, {
|
|
30117
|
+
y: x => computeSine(x, coeffs),
|
|
30118
|
+
color: color.blue
|
|
30119
|
+
}), coords.map((coord, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
|
|
30120
|
+
key: "point-" + i,
|
|
30121
|
+
point: coord,
|
|
30122
|
+
onMove: destination => dispatch(movePoint(i, destination))
|
|
30123
|
+
})));
|
|
30124
|
+
}
|
|
30125
|
+
|
|
30126
|
+
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
30127
|
+
const computeSine = function computeSine(x,
|
|
30128
|
+
// x-coordinate
|
|
30129
|
+
sinusoidCoefficients) {
|
|
30130
|
+
// Break down the coefficients for the sine function to improve readability
|
|
30131
|
+
const {
|
|
30132
|
+
amplitude: a,
|
|
30133
|
+
angularFrequency: b,
|
|
30134
|
+
phase: c,
|
|
30135
|
+
verticalOffset: d
|
|
30136
|
+
} = sinusoidCoefficients;
|
|
30137
|
+
return a * Math.sin(b * x - c) + d;
|
|
30138
|
+
};
|
|
30139
|
+
const getSinusoidCoefficients = coords => {
|
|
30140
|
+
// It's assumed that p1 is the root and p2 is the first peak
|
|
30141
|
+
const p1 = coords[0];
|
|
30142
|
+
const p2 = coords[1];
|
|
30143
|
+
|
|
30144
|
+
// Resulting coefficients are canonical for this sine curve
|
|
30145
|
+
const amplitude = p2[1] - p1[1];
|
|
30146
|
+
const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
|
|
30147
|
+
const phase = p1[0] * angularFrequency;
|
|
30148
|
+
const verticalOffset = p1[1];
|
|
30149
|
+
return {
|
|
30150
|
+
amplitude,
|
|
30151
|
+
angularFrequency,
|
|
30152
|
+
phase,
|
|
30153
|
+
verticalOffset
|
|
30154
|
+
};
|
|
30155
|
+
};
|
|
30156
|
+
|
|
30022
30157
|
// Universal padding for the axis ticks to ensure that the labels
|
|
30023
30158
|
// have enough space to render without overlapping the tick lines
|
|
30024
30159
|
const tickPadding = 5;
|
|
@@ -30178,9 +30313,6 @@ const AxisTickLabel = ({
|
|
|
30178
30313
|
const pointOnAxis = axis === "x" ? [label, 0] : [0, label];
|
|
30179
30314
|
const pixelPoint = pointToPixel(pointOnAxis, graphConfig);
|
|
30180
30315
|
|
|
30181
|
-
// Remove the negative sign from the label string as it is handled in the data-content attribute
|
|
30182
|
-
const labelString = Math.abs(label).toString();
|
|
30183
|
-
|
|
30184
30316
|
// Determine the relevant edge of the graph based on the axis type
|
|
30185
30317
|
const graphEdge = axis === "x" ? graphConfig.width : graphConfig.height;
|
|
30186
30318
|
|
|
@@ -30196,15 +30328,9 @@ const AxisTickLabel = ({
|
|
|
30196
30328
|
gridStep: gridStep[vectorIndex],
|
|
30197
30329
|
tickStep: tickStep[vectorIndex]
|
|
30198
30330
|
});
|
|
30199
|
-
|
|
30200
|
-
// Determine whether or not to render a negative symbol for the tick label
|
|
30201
|
-
// as the negative sign is handled in the data-content attribute in order to
|
|
30202
|
-
// maintain the correct spacing between the labels
|
|
30203
|
-
const dataContent = label < 0 && shouldShowLabel ? "−" : null;
|
|
30204
30331
|
return /*#__PURE__*/React.createElement("span", {
|
|
30205
|
-
className: `${axis}-axis-tick-label
|
|
30206
|
-
|
|
30207
|
-
}, shouldShowLabel && labelString);
|
|
30332
|
+
className: `${axis}-axis-tick-label`
|
|
30333
|
+
}, shouldShowLabel && label);
|
|
30208
30334
|
};
|
|
30209
30335
|
|
|
30210
30336
|
// Determine whether or not to show the tick label text
|
|
@@ -31153,6 +31279,8 @@ function doMoveControlPoint(state, action) {
|
|
|
31153
31279
|
throw new Error("FIXME implement circle reducer");
|
|
31154
31280
|
case "point":
|
|
31155
31281
|
case "polygon":
|
|
31282
|
+
case "quadratic":
|
|
31283
|
+
case "sinusoid":
|
|
31156
31284
|
throw new Error(`Don't use moveControlPoint for ${state.type} graphs. Use movePoint instead!`);
|
|
31157
31285
|
default:
|
|
31158
31286
|
throw new UnreachableCaseError(state);
|
|
@@ -31223,8 +31351,46 @@ function doMoveAll(state, action) {
|
|
|
31223
31351
|
}
|
|
31224
31352
|
function doMovePoint(state, action) {
|
|
31225
31353
|
switch (state.type) {
|
|
31226
|
-
case "point":
|
|
31227
31354
|
case "polygon":
|
|
31355
|
+
case "point":
|
|
31356
|
+
{
|
|
31357
|
+
return _extends({}, state, {
|
|
31358
|
+
hasBeenInteractedWith: true,
|
|
31359
|
+
coords: setAtIndex({
|
|
31360
|
+
array: state.coords,
|
|
31361
|
+
index: action.index,
|
|
31362
|
+
newValue: snap(state.snapStep, bound$1({
|
|
31363
|
+
snapStep: state.snapStep,
|
|
31364
|
+
range: state.range,
|
|
31365
|
+
point: action.destination
|
|
31366
|
+
}))
|
|
31367
|
+
})
|
|
31368
|
+
});
|
|
31369
|
+
}
|
|
31370
|
+
case "sinusoid":
|
|
31371
|
+
{
|
|
31372
|
+
// First, we need to verify that the new coordinates are not on the same vertical line
|
|
31373
|
+
// If they are, we don't want to move the point
|
|
31374
|
+
const destination = action.destination;
|
|
31375
|
+
const newCoords = [...state.coords];
|
|
31376
|
+
newCoords[action.index] = action.destination;
|
|
31377
|
+
if (newCoords[0][0] === newCoords[1][0]) {
|
|
31378
|
+
return state;
|
|
31379
|
+
}
|
|
31380
|
+
return _extends({}, state, {
|
|
31381
|
+
hasBeenInteractedWith: true,
|
|
31382
|
+
coords: setAtIndex({
|
|
31383
|
+
array: state.coords,
|
|
31384
|
+
index: action.index,
|
|
31385
|
+
newValue: snap(state.snapStep, bound$1({
|
|
31386
|
+
snapStep: state.snapStep,
|
|
31387
|
+
range: state.range,
|
|
31388
|
+
point: destination
|
|
31389
|
+
}))
|
|
31390
|
+
})
|
|
31391
|
+
});
|
|
31392
|
+
}
|
|
31393
|
+
case "quadratic":
|
|
31228
31394
|
{
|
|
31229
31395
|
return _extends({}, state, {
|
|
31230
31396
|
hasBeenInteractedWith: true,
|
|
@@ -31240,7 +31406,7 @@ function doMovePoint(state, action) {
|
|
|
31240
31406
|
});
|
|
31241
31407
|
}
|
|
31242
31408
|
default:
|
|
31243
|
-
throw new Error("The movePoint action is only for point and polygon graphs");
|
|
31409
|
+
throw new Error("The movePoint action is only for point, quadratic, and polygon graphs");
|
|
31244
31410
|
}
|
|
31245
31411
|
}
|
|
31246
31412
|
function doMoveCenter(state, action) {
|
|
@@ -31453,6 +31619,16 @@ const renderGraph = props => {
|
|
|
31453
31619
|
graphState: state,
|
|
31454
31620
|
dispatch: dispatch
|
|
31455
31621
|
});
|
|
31622
|
+
case "quadratic":
|
|
31623
|
+
return /*#__PURE__*/React.createElement(QuadraticGraph, {
|
|
31624
|
+
graphState: state,
|
|
31625
|
+
dispatch: dispatch
|
|
31626
|
+
});
|
|
31627
|
+
case "sinusoid":
|
|
31628
|
+
return /*#__PURE__*/React.createElement(SinusoidGraph, {
|
|
31629
|
+
graphState: state,
|
|
31630
|
+
dispatch: dispatch
|
|
31631
|
+
});
|
|
31456
31632
|
default:
|
|
31457
31633
|
return new UnreachableCaseError(type);
|
|
31458
31634
|
}
|
|
@@ -43667,7 +43843,7 @@ const init = function init(options) {
|
|
|
43667
43843
|
|
|
43668
43844
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
43669
43845
|
const libName = "@khanacademy/perseus";
|
|
43670
|
-
const libVersion = "
|
|
43846
|
+
const libVersion = "23.1.0";
|
|
43671
43847
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
43672
43848
|
|
|
43673
43849
|
const apiVersion = {
|