@khanacademy/perseus 36.1.1 → 36.1.2

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/index.d.ts CHANGED
@@ -68,6 +68,7 @@ export { getWidgetTypeByWidgetId, contentHasWidgetType, getWidgetIdsFromContent,
68
68
  export { convertWidgetNameToEnum } from "./util/widget-enum-utils";
69
69
  export { addWidget, QUESTION_WIDGETS } from "./util/snowman-utils";
70
70
  export { getCircleCoords, getLineCoords, getLinearSystemCoords, getPointCoords, getPolygonCoords, getSegmentCoords, getSinusoidCoords, getQuadraticCoords, getAngleCoords, } from "./widgets/interactive-graphs/reducer/initialize-graph-state";
71
+ export { getClockwiseAngle } from "./widgets/interactive-graphs/math";
71
72
  export { makeSafeUrl } from "./widgets/phet-simulation";
72
73
  /**
73
74
  * Mixins
package/dist/index.js CHANGED
@@ -10176,7 +10176,10 @@ const GraphUtils = {
10176
10176
  return kmath.point.distanceToPoint(point1, point2);
10177
10177
  },
10178
10178
  // Find the angle in degrees between two or three points
10179
- findAngle: function (point1, point2, vertex) {
10179
+ // This function is deprecated as it has several issues calculating
10180
+ // correctly when dealing with reflex angles or the position of point2
10181
+ // (LEMS-2202) Remove this function while removing the Legacy Interactive Graph.
10182
+ findAngleDeprecated: function (point1, point2, vertex) {
10180
10183
  if (vertex === undefined) {
10181
10184
  const x = point1[0] - point2[0];
10182
10185
  const y = point1[1] - point2[1];
@@ -10185,7 +10188,7 @@ const GraphUtils = {
10185
10188
  }
10186
10189
  return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
10187
10190
  }
10188
- return GraphUtils.findAngle(point1, vertex) - GraphUtils.findAngle(point2, vertex);
10191
+ return GraphUtils.findAngleDeprecated(point1, vertex) - GraphUtils.findAngleDeprecated(point2, vertex);
10189
10192
  },
10190
10193
  graphs: {}
10191
10194
  };
@@ -11202,8 +11205,8 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11202
11205
  p1 = options.point3;
11203
11206
  p3 = options.point1;
11204
11207
  }
11205
- const startAngle = GraphUtils.findAngle(p1, vertex);
11206
- const endAngle = GraphUtils.findAngle(p3, vertex);
11208
+ const startAngle = GraphUtils.findAngleDeprecated(p1, vertex);
11209
+ const endAngle = GraphUtils.findAngleDeprecated(p3, vertex);
11207
11210
  const angle = (endAngle + 360 - startAngle) % 360;
11208
11211
  const halfAngle = (startAngle + angle / 2) % 360;
11209
11212
  const sPadding = 5 * options.pushOut;
@@ -11370,15 +11373,15 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11370
11373
  let angle = 135;
11371
11374
  let halfAngle;
11372
11375
  if (p1 && p3) {
11373
- const startAngle = GraphUtils.findAngle(p1, vertex);
11374
- const endAngle = GraphUtils.findAngle(p3, vertex);
11376
+ const startAngle = GraphUtils.findAngleDeprecated(p1, vertex);
11377
+ const endAngle = GraphUtils.findAngleDeprecated(p3, vertex);
11375
11378
  angle = (endAngle + 360 - startAngle) % 360;
11376
11379
  halfAngle = (startAngle + angle / 2 + 180) % 360;
11377
11380
  } else if (p1) {
11378
- const parallelAngle = GraphUtils.findAngle(vertex, p1);
11381
+ const parallelAngle = GraphUtils.findAngleDeprecated(vertex, p1);
11379
11382
  halfAngle = parallelAngle + 90;
11380
11383
  } else if (p3) {
11381
- const parallelAngle = GraphUtils.findAngle(p3, vertex);
11384
+ const parallelAngle = GraphUtils.findAngleDeprecated(p3, vertex);
11382
11385
  halfAngle = parallelAngle + 90;
11383
11386
  } else {
11384
11387
  // Standalone point
@@ -11584,7 +11587,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11584
11587
  const vertex = constraints.fixedAngle.vertex.coord || constraints.fixedAngle.vertex;
11585
11588
  const ref = constraints.fixedAngle.ref.coord || constraints.fixedAngle.ref;
11586
11589
  const distPoint = constraints.fixedDistance.point.coord || constraints.fixedDistance.point;
11587
- const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngle(ref, vertex)) * Math.PI / 180;
11590
+ const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngleDeprecated(ref, vertex)) * Math.PI / 180;
11588
11591
  const length = constraints.fixedDistance.dist;
11589
11592
  newCoord[0] = length * Math.cos(constrainedAngle) + distPoint[0];
11590
11593
  newCoord[1] = length * Math.sin(constrainedAngle) + distPoint[1];
@@ -11593,8 +11596,8 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11593
11596
  } else if (typeof constraints.fixedAngle.angle === "number") {
11594
11597
  const vertex = constraints.fixedAngle.vertex.coord || constraints.fixedAngle.vertex;
11595
11598
  const ref = constraints.fixedAngle.ref.coord || constraints.fixedAngle.ref;
11596
- const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngle(ref, vertex)) * Math.PI / 180;
11597
- const angle = GraphUtils.findAngle(coord, vertex) * Math.PI / 180;
11599
+ const constrainedAngle = (constraints.fixedAngle.angle + GraphUtils.findAngleDeprecated(ref, vertex)) * Math.PI / 180;
11600
+ const angle = GraphUtils.findAngleDeprecated(coord, vertex) * Math.PI / 180;
11598
11601
  const distance = GraphUtils.getDistance(coord, vertex);
11599
11602
  let length = distance * Math.cos(constrainedAngle - angle);
11600
11603
  length = length < 1.0 ? 1.0 : length;
@@ -11604,7 +11607,7 @@ ___default["default"].extend(GraphUtils.Graphie.prototype, {
11604
11607
  // distance is constrained
11605
11608
  } else if (typeof constraints.fixedDistance.dist === "number") {
11606
11609
  const distPoint = constraints.fixedDistance.point.coord || constraints.fixedDistance.point;
11607
- let angle = GraphUtils.findAngle(coord, distPoint);
11610
+ let angle = GraphUtils.findAngleDeprecated(coord, distPoint);
11608
11611
  const length = constraints.fixedDistance.dist;
11609
11612
  angle = angle * Math.PI / 180;
11610
11613
  newCoord[0] = length * Math.cos(angle) + distPoint[0];
@@ -13804,7 +13807,7 @@ ___default["default"].extend(MovableAngle.prototype, {
13804
13807
  ___default["default"].each([0, 2], function (i) {
13805
13808
  const oldPoint = points[i].coord;
13806
13809
  let newPoint = kmath.vector.add(oldPoint, delta);
13807
- let angle = GraphUtils.findAngle(newVertex, newPoint);
13810
+ let angle = GraphUtils.findAngleDeprecated(newVertex, newPoint);
13808
13811
  angle *= Math.PI / 180;
13809
13812
  newPoint = graphie.constrainToBoundsOnAngle(newPoint, 10, angle);
13810
13813
  newPoints[i] = newPoint;
@@ -13837,7 +13840,7 @@ ___default["default"].extend(MovableAngle.prototype, {
13837
13840
  return false;
13838
13841
  }
13839
13842
  if (snap) {
13840
- let angle = GraphUtils.findAngle(newPoint, vertex);
13843
+ let angle = GraphUtils.findAngleDeprecated(newPoint, vertex);
13841
13844
  angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
13842
13845
  const distance = GraphUtils.getDistance(newPoint, vertex);
13843
13846
  return kmath.vector.add(vertex, polar$1(distance, angle));
@@ -13925,7 +13928,7 @@ ___default["default"].extend(MovableAngle.prototype, {
13925
13928
  * coords when interpreted in a clockwise direction.
13926
13929
  */
13927
13930
  _getClockwiseAngle: function (coords) {
13928
- const rawAngle = GraphUtils.findAngle(
13931
+ const rawAngle = GraphUtils.findAngleDeprecated(
13929
13932
  // The order of these is "weird" to match what a clockwise
13930
13933
  // order is in graphie.labelAngle
13931
13934
  coords[2],
@@ -30975,16 +30978,40 @@ function polar(r, th) {
30975
30978
  th = th * Math.PI / 180;
30976
30979
  return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
30977
30980
  }
30978
- const findAngle = (point1, point2, vertex) => {
30979
- if (vertex === undefined) {
30980
- const x = point1[0] - point2[0];
30981
- const y = point1[1] - point2[1];
30982
- if (!x && !y) {
30983
- return 0;
30984
- }
30985
- return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
30981
+ // This function calculates the angle between two points and an optional vertex.
30982
+ // If the vertex is not provided, the angle is measured between the two points.
30983
+ // This does not account for reflex angles or clockwise position.
30984
+ const getAngleFromVertex = (point, vertex) => {
30985
+ const x = point[0] - vertex[0];
30986
+ const y = point[1] - vertex[1];
30987
+ if (!x && !y) {
30988
+ return 0;
30986
30989
  }
30987
- return findAngle(point1, vertex) - findAngle(point2, vertex);
30990
+ return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
30991
+ };
30992
+
30993
+ // This function calculates the clockwise angle between three points,
30994
+ // and is used to generate the labels and equation strings of the
30995
+ // current angle for the interactive graph.
30996
+ const getClockwiseAngle = function (coords) {
30997
+ let allowReflexAngles = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
30998
+ const coordsCopy = [...coords];
30999
+ // The coords are saved as [point1, vertex, point2] in the interactive graph
31000
+ const areClockwise = clockwise$1([coordsCopy[0], coordsCopy[2], coordsCopy[1]]);
31001
+
31002
+ // We may need to reverse the coordinates if we allow
31003
+ // reflex angles and the points are not in clockwise order.
31004
+ const shouldReverseCoords = !areClockwise || allowReflexAngles;
31005
+
31006
+ // Reverse the coordinates accordingly to ensure the angle is calculated correctly
31007
+ const clockwiseCoords = shouldReverseCoords ? coordsCopy.reverse() : coordsCopy;
31008
+
31009
+ // Calculate the angles between the two points and get the difference
31010
+ // between the two angles to get the clockwise angle.
31011
+ const startAngle = getAngleFromVertex(clockwiseCoords[0], clockwiseCoords[1]);
31012
+ const endAngle = getAngleFromVertex(clockwiseCoords[2], clockwiseCoords[1]);
31013
+ const angle = (startAngle + 360 - endAngle) % 360;
31014
+ return angle;
30988
31015
  };
30989
31016
 
30990
31017
  const defaultGraphConfig = {
@@ -33416,8 +33443,8 @@ const Angle = _ref2 => {
33416
33443
  const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
33417
33444
 
33418
33445
  // Calculate the angles between the two points
33419
- const startAngle = findAngle(clockwiseCoords[0], vertex);
33420
- const endAngle = findAngle(clockwiseCoords[1], vertex);
33446
+ const startAngle = getAngleFromVertex(clockwiseCoords[0], vertex);
33447
+ const endAngle = getAngleFromVertex(clockwiseCoords[1], vertex);
33421
33448
  const angle = (startAngle + 360 - endAngle) % 360;
33422
33449
 
33423
33450
  // Check if the angle is reflexive
@@ -34506,7 +34533,9 @@ const MafsGraph = props => {
34506
34533
  analytics.onAnalyticsEvent({
34507
34534
  type: "perseus:interactive-graph-widget:rendered",
34508
34535
  payload: {
34509
- type
34536
+ type,
34537
+ widgetType: "INTERACTIVE_GRAPH",
34538
+ widgetId: "interactive-graph"
34510
34539
  }
34511
34540
  });
34512
34541
  });
@@ -35793,7 +35822,7 @@ function boundAndSnapAngleVertex(_ref2, _ref3) {
35793
35822
  for (const i of [0, 2]) {
35794
35823
  const oldPoint = coordsCopy[i];
35795
35824
  let newPoint = mafs.vec.add(oldPoint, delta);
35796
- let angle = findAngle(newVertex, newPoint);
35825
+ let angle = getAngleFromVertex(newVertex, newPoint);
35797
35826
  angle *= Math.PI / 180;
35798
35827
  newPoint = constrainToBoundsOnAngle(newPoint, angle, range, snapStep);
35799
35828
  newPoints[i] = newPoint;
@@ -35866,7 +35895,7 @@ function boundAndSnapAngleEndPoints(destinationPoint, _ref5, index) {
35866
35895
  const vertex = coords[1];
35867
35896
 
35868
35897
  // Gets the angle between the coords and the vertex
35869
- let angle = findAngle(coordsCopy[index], vertex);
35898
+ let angle = getAngleFromVertex(coordsCopy[index], vertex);
35870
35899
 
35871
35900
  // Snap the angle to the nearest multiple of snapDegrees (if provided)
35872
35901
  angle = Math.round((angle - offsetDegrees) / snap) * snap + offsetDegrees;
@@ -35913,8 +35942,8 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
35913
35942
  angles[rel(j)] = Math.round(angles[rel(j)]);
35914
35943
  });
35915
35944
  const getAngle = function (a, vertex, b) {
35916
- const angle = findAngle(coordsCopy[rel(a)], coordsCopy[rel(b)], coordsCopy[rel(vertex)]);
35917
- return (angle + 360) % 360;
35945
+ const angle = getClockwiseAngle([coordsCopy[rel(a)], coordsCopy[rel(vertex)], coordsCopy[rel(b)]]);
35946
+ return angle;
35918
35947
  };
35919
35948
  const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
35920
35949
  innerAngles[2] = 180 - (innerAngles[0] + innerAngles[1]);
@@ -35941,7 +35970,7 @@ function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
35941
35970
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
35942
35971
 
35943
35972
  // Angle at the second vertex of the polygon
35944
- const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35973
+ const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35945
35974
 
35946
35975
  // Uses the length of the side of the polygon (radial coordinate)
35947
35976
  // and the angle between the first and second sides of the
@@ -35992,7 +36021,7 @@ function boundAndSnapToSides(destinationPoint, _ref7, index) {
35992
36021
  const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
35993
36022
 
35994
36023
  // Angle at the second vertex of the polygon
35995
- const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
36024
+ const outerAngle = getAngleFromVertex(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
35996
36025
 
35997
36026
  // Returns true if the points form a counter-clockwise turn;
35998
36027
  // a.k.a if the point is on the left or right of the polygon.
@@ -36101,7 +36130,8 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
36101
36130
  const numPoints = graph.type === "point" ? graph.numPoints : null;
36102
36131
  const numSides = graph.type === "polygon" ? graph.numSides : null;
36103
36132
  const snapTo = graph.type === "polygon" ? graph.snapTo : null;
36104
- const showAngles = graph.type === "polygon" ? graph.showAngles : null;
36133
+ const showAngles = graph.type === "polygon" || graph.type === "angle" ? graph.showAngles : null;
36134
+ const allowReflexAngles = graph.type === "angle" ? graph.allowReflexAngles : null;
36105
36135
  const showSides = graph.type === "polygon" ? graph.showSides : null;
36106
36136
  const startCoords = "startCoords" in graph ? graph.startCoords : undefined;
36107
36137
  const originalPropsRef = React.useRef(props);
@@ -36114,7 +36144,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
36114
36144
  if (latestPropsRef.current !== originalPropsRef.current) {
36115
36145
  dispatch(reinitialize(latestPropsRef.current));
36116
36146
  }
36117
- }, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, startCoords]);
36147
+ }, [graph.type, numPoints, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef, startCoords, allowReflexAngles]);
36118
36148
 
36119
36149
  // If the graph is static, it always displays the correct answer. This is
36120
36150
  // standard behavior for Perseus widgets (e.g. compare the Radio widget).
@@ -36302,17 +36332,15 @@ function interactiveGraphValidator(userInput, rubric) {
36302
36332
  } else if (userInput.type === "angle" && rubric.correct.type === "angle") {
36303
36333
  const guess = userInput.coords;
36304
36334
  const correct = rubric.correct.coords;
36335
+ const allowReflexAngles = rubric.correct.allowReflexAngles;
36305
36336
  let match;
36306
36337
  if (rubric.correct.match === "congruent") {
36307
36338
  const angles = ___default["default"].map([guess, correct], function (coords) {
36308
- const angle = GraphUtils.findAngle(
36309
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
36310
- coords[2],
36311
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
36312
- coords[0],
36313
- // @ts-expect-error - TS2532 - Object is possibly 'undefined'.
36314
- coords[1]);
36315
- return (angle + 360) % 360;
36339
+ if (!coords) {
36340
+ return false;
36341
+ }
36342
+ const angle = getClockwiseAngle(coords, allowReflexAngles);
36343
+ return angle;
36316
36344
  });
36317
36345
  // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
36318
36346
  match = eq$1(...angles);
@@ -37215,7 +37243,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
37215
37243
  angles[rel(j)] = Math.round(angles[rel(j)]);
37216
37244
  });
37217
37245
  const getAngle = function (a, vertex, b) {
37218
- const angle = GraphUtils.findAngle(coords[rel(a)], coords[rel(b)], coords[rel(vertex)]);
37246
+ const angle = GraphUtils.findAngleDeprecated(coords[rel(a)], coords[rel(b)], coords[rel(vertex)]);
37219
37247
  return (angle + 360) % 360;
37220
37248
  };
37221
37249
  const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
@@ -37234,7 +37262,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
37234
37262
 
37235
37263
  // Solve for side by using the law of sines
37236
37264
  const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
37237
- const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
37265
+ const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
37238
37266
  const offset = polar$1(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
37239
37267
  return this.graphie?.addPoints(coords[rel(-1)], offset);
37240
37268
  }
@@ -37257,7 +37285,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
37257
37285
 
37258
37286
  // Solve for angle by using the law of cosines
37259
37287
  const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
37260
- const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
37288
+ const outerAngle = GraphUtils.findAngleDeprecated(coords[rel(1)], coords[rel(-1)]);
37261
37289
  const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
37262
37290
  const offset = polar$1(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
37263
37291
  return this.graphie?.addPoints(coords[rel(-1)], offset);
@@ -37981,7 +38009,8 @@ class InteractiveGraph extends React__namespace.Component {
37981
38009
  throw makeInvalidTypeError("getAngleEquationString", "angle");
37982
38010
  }
37983
38011
  const coords = InteractiveGraph.getAngleCoords(props.graph, props);
37984
- const angle = GraphUtils.findAngle(coords[2], coords[0], coords[1]);
38012
+ const allowReflexAngles = props.graph.allowReflexAngles;
38013
+ const angle = getClockwiseAngle(coords, allowReflexAngles);
37985
38014
  return angle.toFixed(0) + "\u00B0 angle" + " at (" + coords[1].join(", ") + ")";
37986
38015
  }
37987
38016
  static validate(userInput, rubric) {
@@ -45309,7 +45338,7 @@ const init = function (options) {
45309
45338
 
45310
45339
  // This file is processed by a Rollup plugin (replace) to inject the production
45311
45340
  const libName = "@khanacademy/perseus";
45312
- const libVersion = "36.1.1";
45341
+ const libVersion = "36.1.2";
45313
45342
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
45314
45343
 
45315
45344
  const apiVersion = {
@@ -48942,6 +48971,7 @@ exports.getAngleCoords = getAngleCoords;
48942
48971
  exports.getAnswerFromUserInput = getAnswerFromUserInput;
48943
48972
  exports.getAnswersFromWidgets = getAnswersFromWidgets;
48944
48973
  exports.getCircleCoords = getCircleCoords;
48974
+ exports.getClockwiseAngle = getClockwiseAngle;
48945
48975
  exports.getCorrectAnswerForWidgetId = getCorrectAnswerForWidgetId;
48946
48976
  exports.getImagesWithoutAltData = getImagesWithoutAltData;
48947
48977
  exports.getLineCoords = getLineCoords;