@khanacademy/perseus 22.2.0 → 22.4.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/index.js CHANGED
@@ -27,8 +27,8 @@ var Button = require('@khanacademy/wonder-blocks-button');
27
27
  var wonderBlocksLayout = require('@khanacademy/wonder-blocks-layout');
28
28
  var Banner = require('@khanacademy/wonder-blocks-banner');
29
29
  var wonderBlocksDropdown = require('@khanacademy/wonder-blocks-dropdown');
30
- var caretDown = require('@phosphor-icons/core/assets/regular/caret-down.svg');
31
- var caretUp = require('@phosphor-icons/core/assets/regular/caret-up.svg');
30
+ var caretDown = require('@phosphor-icons/core/regular/caret-down.svg');
31
+ var caretUp = require('@phosphor-icons/core/regular/caret-up.svg');
32
32
  var debounce$1 = require('lodash.debounce');
33
33
  var mafs = require('mafs');
34
34
  var wonderBlocksIcon = require('@khanacademy/wonder-blocks-icon');
@@ -28952,15 +28952,10 @@ const ItemExtras = [
28952
28952
 
28953
28953
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
28954
28954
  const lockedFigureColors = {
28955
- blue: "#3D7586",
28956
28955
  green: "#447A53",
28957
- gray: "#5D5F66",
28958
28956
  grayH: "#3B3D45",
28959
- grayI: "#21242C",
28960
28957
  purple: "#594094",
28961
- purpleD: "#8351E8",
28962
28958
  pink: "#B25071",
28963
- orange: "#946700",
28964
28959
  red: "#D92916"
28965
28960
  };
28966
28961
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
@@ -29017,6 +29012,7 @@ const arrowPath = pathBuilder().move(-3, 4).curve(-2.75, 2.5, 0, 0.25, 0.75, 0).
29017
29012
  function Arrowhead(props) {
29018
29013
  const [point] = useTransformVectorsToPixels(props.tip);
29019
29014
  return /*#__PURE__*/React__namespace.createElement("g", {
29015
+ className: "interactive-graph-arrowhead",
29020
29016
  transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
29021
29017
  }, /*#__PURE__*/React__namespace.createElement("g", {
29022
29018
  transform: "translate(-1.5)"
@@ -29031,49 +29027,7 @@ function Arrowhead(props) {
29031
29027
  })));
29032
29028
  }
29033
29029
 
29034
- function SVGLine(props) {
29035
- const {
29036
- start,
29037
- end,
29038
- style,
29039
- className,
29040
- testId
29041
- } = props;
29042
- return /*#__PURE__*/React__namespace.createElement("line", {
29043
- x1: start[0],
29044
- y1: start[1],
29045
- x2: end[0],
29046
- y2: end[1],
29047
- style: style,
29048
- className: className,
29049
- "data-testid": testId
29050
- });
29051
- }
29052
-
29053
- function Vector(props) {
29054
- const {
29055
- tail,
29056
- tip,
29057
- color,
29058
- style
29059
- } = props;
29060
- const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
29061
- const direction = mafs.vec.sub(tip, tail);
29062
- return /*#__PURE__*/React__namespace.createElement("g", {
29063
- style: {
29064
- stroke: color,
29065
- strokeWidth: 2
29066
- }
29067
- }, /*#__PURE__*/React__namespace.createElement(SVGLine, {
29068
- start: tailPx,
29069
- end: tipPx,
29070
- style: style
29071
- }), /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29072
- tip: tip,
29073
- angle: angleDegrees(direction)
29074
- }));
29075
- }
29076
- function angleDegrees(_ref) {
29030
+ function calculateAngleInDegrees(_ref) {
29077
29031
  let [x, y] = _ref;
29078
29032
  return Math.atan2(y, x) * 180 / Math.PI;
29079
29033
  }
@@ -29116,10 +29070,57 @@ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
29116
29070
  return [xMax, yAtXMax];
29117
29071
  }
29118
29072
  };
29119
- const getLines = points => points.map((point, i) => {
29120
- const next = points[(i + 1) % points.length];
29121
- return [point, next];
29122
- });
29073
+ const getLines = points => {
29074
+ return points.map((point, i) => {
29075
+ const next = points[(i + 1) % points.length];
29076
+ return [point, next];
29077
+ });
29078
+ };
29079
+
29080
+ function SVGLine(props) {
29081
+ const {
29082
+ start,
29083
+ end,
29084
+ style,
29085
+ className,
29086
+ testId
29087
+ } = props;
29088
+ return /*#__PURE__*/React__namespace.createElement("line", {
29089
+ x1: start[0],
29090
+ y1: start[1],
29091
+ x2: end[0],
29092
+ y2: end[1],
29093
+ style: style,
29094
+ className: className,
29095
+ "data-testid": testId
29096
+ });
29097
+ }
29098
+
29099
+ function Vector(props) {
29100
+ const {
29101
+ tail,
29102
+ tip,
29103
+ color,
29104
+ style
29105
+ } = props;
29106
+ const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
29107
+ const direction = mafs.vec.sub(tip, tail);
29108
+ const angle = calculateAngleInDegrees(direction);
29109
+ return /*#__PURE__*/React__namespace.createElement("g", {
29110
+ style: {
29111
+ stroke: color,
29112
+ strokeWidth: 2
29113
+ }
29114
+ }, /*#__PURE__*/React__namespace.createElement(SVGLine, {
29115
+ start: tailPx,
29116
+ end: tipPx,
29117
+ style: style
29118
+ }), /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29119
+ angle: angle,
29120
+ tip: tip,
29121
+ color: color
29122
+ }));
29123
+ }
29123
29124
 
29124
29125
  const LockedLine = props => {
29125
29126
  const {
@@ -29135,27 +29136,38 @@ const LockedLine = props => {
29135
29136
  let line;
29136
29137
  if (kind === "ray") {
29137
29138
  // Rays extend to the end of the graph in one direction.
29138
- const endExtend = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29139
+ const extendedPoint = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29139
29140
  line = /*#__PURE__*/React__namespace.createElement(Vector, {
29140
29141
  tail: point1.coord,
29141
- tip: endExtend,
29142
+ tip: extendedPoint,
29142
29143
  color: lockedFigureColors[color],
29143
29144
  style: {
29144
- strokeDasharray: lineStyle === "dashed" ?
29145
- // TODO(lems-1930): Uncomment this line when the
29146
- // dashed style is updated in Mafs.
29147
- // ? "var(--mafs-line-stroke-dash-style)"
29148
- "4, 3" : undefined
29145
+ strokeDasharray: lineStyle === "dashed" ? "var(--mafs-line-stroke-dash-style)" : undefined
29149
29146
  }
29150
29147
  });
29151
29148
  } else {
29152
29149
  const LineType = kind === "segment" ? mafs.Line.Segment : mafs.Line.ThroughPoints;
29153
- line = /*#__PURE__*/React__namespace.createElement(LineType, {
29150
+ let arrowTip = kind === "segment" ? point2.coord : getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29151
+ const direction = mafs.vec.sub(point2.coord, point1.coord);
29152
+ let angle = calculateAngleInDegrees(direction);
29153
+ const startArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29154
+ angle: angle,
29155
+ tip: arrowTip,
29156
+ color: lockedFigureColors[color]
29157
+ });
29158
+ arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
29159
+ angle = angle > 180 ? angle - 180 : angle + 180;
29160
+ const endArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29161
+ angle: angle,
29162
+ tip: arrowTip,
29163
+ color: lockedFigureColors[color]
29164
+ });
29165
+ line = /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, startArrowHead, /*#__PURE__*/React__namespace.createElement(LineType, {
29154
29166
  point1: point1.coord,
29155
29167
  point2: point2.coord,
29156
29168
  color: lockedFigureColors[color],
29157
29169
  style: lineStyle
29158
- });
29170
+ }), endArrowHead);
29159
29171
  }
29160
29172
  return /*#__PURE__*/React__namespace.createElement("g", {
29161
29173
  className: kind === "ray" ? "locked-ray" : "locked-line"
@@ -29261,6 +29273,20 @@ function movePoint(index, destination) {
29261
29273
  destination
29262
29274
  };
29263
29275
  }
29276
+ const MOVE_CENTER = "move-center";
29277
+ function moveCenter(destination) {
29278
+ return {
29279
+ type: MOVE_CENTER,
29280
+ destination
29281
+ };
29282
+ }
29283
+ const MOVE_RADIUS_POINT = "MOVE_RADIUS_POINT";
29284
+ function moveRadiusPoint(destination) {
29285
+ return {
29286
+ type: MOVE_RADIUS_POINT,
29287
+ destination
29288
+ };
29289
+ }
29264
29290
  const CHANGE_SNAP_STEP = "change-snap-step";
29265
29291
  function changeSnapStep(snapStep) {
29266
29292
  return {
@@ -29876,6 +29902,287 @@ const PolygonGraph = props => {
29876
29902
  })));
29877
29903
  };
29878
29904
 
29905
+ function initializeGraphState(params) {
29906
+ const {
29907
+ graph,
29908
+ step,
29909
+ snapStep,
29910
+ range,
29911
+ markings
29912
+ } = params;
29913
+ const shared = {
29914
+ hasBeenInteractedWith: false,
29915
+ range,
29916
+ snapStep,
29917
+ markings
29918
+ };
29919
+ switch (graph.type) {
29920
+ case "segment":
29921
+ return {
29922
+ ...shared,
29923
+ type: "segment",
29924
+ coords: getDefaultSegments({
29925
+ graph,
29926
+ step,
29927
+ range
29928
+ })
29929
+ };
29930
+ case "linear":
29931
+ case "linear-system":
29932
+ case "ray":
29933
+ return {
29934
+ ...shared,
29935
+ type: graph.type,
29936
+ // Linear and ray graphs have a single tuple of points, while a
29937
+ // linear system has two tuples of points.
29938
+ coords: getLineCoords({
29939
+ graph,
29940
+ range,
29941
+ step
29942
+ })
29943
+ };
29944
+ case "polygon":
29945
+ return {
29946
+ ...shared,
29947
+ type: "polygon",
29948
+ showAngles: Boolean(graph.showAngles),
29949
+ showSides: Boolean(graph.showSides),
29950
+ coords: getPolygonCoords({
29951
+ graph,
29952
+ range,
29953
+ step
29954
+ })
29955
+ };
29956
+ case "point":
29957
+ return {
29958
+ ...shared,
29959
+ type: graph.type,
29960
+ coords: getDefaultPoints({
29961
+ graph,
29962
+ step,
29963
+ range
29964
+ })
29965
+ };
29966
+ case "circle":
29967
+ return {
29968
+ ...shared,
29969
+ type: graph.type,
29970
+ center: [0, 0],
29971
+ radiusPoint: [1, 0]
29972
+ };
29973
+ case "angle":
29974
+ case "sinusoid":
29975
+ case "quadratic":
29976
+ throw new Error("Mafs not yet implemented for graph type: " + graph.type);
29977
+ default:
29978
+ throw new wonderStuffCore.UnreachableCaseError(graph);
29979
+ }
29980
+ }
29981
+ const getDefaultPoints = _ref => {
29982
+ let {
29983
+ graph,
29984
+ range,
29985
+ step
29986
+ } = _ref;
29987
+ const numPoints = graph.numPoints || 1;
29988
+ let coords = graph.coords?.slice();
29989
+ if (coords) {
29990
+ return coords;
29991
+ }
29992
+ switch (numPoints) {
29993
+ case 1:
29994
+ // Back in the day, one point's coords were in graph.coord
29995
+ coords = [graph.coord || [0, 0]];
29996
+ break;
29997
+ case 2:
29998
+ coords = [[-5, 0], [5, 0]];
29999
+ break;
30000
+ case 3:
30001
+ coords = [[-5, 0], [0, 0], [5, 0]];
30002
+ break;
30003
+ case 4:
30004
+ coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
30005
+ break;
30006
+ case 5:
30007
+ coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
30008
+ break;
30009
+ case 6:
30010
+ coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
30011
+ break;
30012
+ default:
30013
+ coords = [];
30014
+ break;
30015
+ }
30016
+ // Transform coords from their -10 to 10 space to 0 to 1
30017
+ // because of the old graph.coord, and also it's easier.
30018
+ const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
30019
+ return normalizePoints(range, step, newCoords);
30020
+ };
30021
+
30022
+ // TS v4 doesn't narrow return types, while v5 does.
30023
+ // Instead of updating to v5, using generic type to relate input and output types.
30024
+ function getGradableGraph(state, initialGraph) {
30025
+ if (!state.hasBeenInteractedWith) {
30026
+ return {
30027
+ ...initialGraph
30028
+ };
30029
+ }
30030
+ if (initialGraph.type === "linear-system" && state.type === "linear-system") {
30031
+ return {
30032
+ ...initialGraph,
30033
+ coords: state.coords
30034
+ };
30035
+ }
30036
+ if (state.type === "segment" && initialGraph.type === "segment") {
30037
+ return {
30038
+ ...initialGraph,
30039
+ coords: state.coords
30040
+ };
30041
+ }
30042
+ if (state.type === "linear" && initialGraph.type === "linear") {
30043
+ return {
30044
+ ...initialGraph,
30045
+ coords: state.coords[0]
30046
+ };
30047
+ }
30048
+ if (state.type === "ray" && initialGraph.type === "ray") {
30049
+ return {
30050
+ ...initialGraph,
30051
+ coords: state.coords[0]
30052
+ };
30053
+ }
30054
+ if (state.type === "polygon" && initialGraph.type === "polygon") {
30055
+ return {
30056
+ ...initialGraph,
30057
+ coords: state.coords
30058
+ };
30059
+ }
30060
+ if (state.type === "point" && initialGraph.type === "point") {
30061
+ return {
30062
+ ...initialGraph,
30063
+ coords: state.coords
30064
+ };
30065
+ }
30066
+ if (state.type === "circle" && initialGraph.type === "circle") {
30067
+ return {
30068
+ ...initialGraph,
30069
+ center: state.center,
30070
+ radius: getRadius(state)
30071
+ };
30072
+ }
30073
+ throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
30074
+ }
30075
+ const getDefaultSegments = _ref2 => {
30076
+ let {
30077
+ graph,
30078
+ range,
30079
+ step
30080
+ } = _ref2;
30081
+ const ys = n => {
30082
+ switch (n) {
30083
+ case 2:
30084
+ return [5, -5];
30085
+ case 3:
30086
+ return [5, 0, -5];
30087
+ case 4:
30088
+ return [6, 2, -2, -6];
30089
+ case 5:
30090
+ return [6, 3, 0, -3, -6];
30091
+ case 6:
30092
+ return [5, 3, 1, -1, -3, -5];
30093
+ default:
30094
+ return [5];
30095
+ }
30096
+ };
30097
+ const defaultRange = [[-10, 10], [-10, 10]];
30098
+ return ys(graph.numSegments).map(y => {
30099
+ let endpoints = [[-5, y], [5, y]];
30100
+ endpoints = normalizeCoords(endpoints, defaultRange);
30101
+ endpoints = normalizePoints(range, step, endpoints);
30102
+ return endpoints;
30103
+ });
30104
+ };
30105
+ const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
30106
+ const getLineCoords = _ref3 => {
30107
+ let {
30108
+ graph,
30109
+ range,
30110
+ step
30111
+ } = _ref3;
30112
+ return (
30113
+ // Return two lines for a linear system, one for a ray or linear
30114
+ graph.coords ?? graph.type === "linear-system" ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
30115
+ );
30116
+ };
30117
+ const getPolygonCoords = _ref4 => {
30118
+ let {
30119
+ graph,
30120
+ range,
30121
+ step
30122
+ } = _ref4;
30123
+ let coords = graph.coords?.slice();
30124
+ if (coords) {
30125
+ return coords;
30126
+ }
30127
+ const n = graph.numSides || 3;
30128
+ if (n === "unlimited") {
30129
+ coords = [];
30130
+ } else {
30131
+ const angle = 2 * Math.PI / n;
30132
+ const offset = (1 / n - 1 / 2) * Math.PI;
30133
+
30134
+ // TODO(alex): Generalize this to more than just triangles so that
30135
+ // all polygons have whole number side lengths if snapping to sides
30136
+ const radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7 : 4;
30137
+
30138
+ // Generate coords of a regular polygon with n sides
30139
+ coords = [...Array(n).keys()].map(i => [radius * Math.cos(i * angle + offset), radius * Math.sin(i * angle + offset)]);
30140
+ }
30141
+ coords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
30142
+ const snapToGrid = !["angles", "sides"].includes(graph.snapTo || "");
30143
+ coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
30144
+ return coords;
30145
+ };
30146
+
30147
+ /**
30148
+ * determine radius of a circle graph
30149
+ *
30150
+ * @param graph - the graph object containing the circle
30151
+ * @returns the radius of the circle
30152
+ */
30153
+ function getRadius(graph) {
30154
+ const [centerX, centerY] = graph.center;
30155
+ const [edgeX, edgeY] = graph.radiusPoint;
30156
+ return Math.sqrt(Math.pow(edgeX - centerX, 2) + Math.pow(edgeY - centerY, 2));
30157
+ }
30158
+
30159
+ function CircleGraph(props) {
30160
+ const {
30161
+ dispatch,
30162
+ graphState
30163
+ } = props;
30164
+ const {
30165
+ center,
30166
+ radiusPoint
30167
+ } = graphState;
30168
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Circle, {
30169
+ center: center,
30170
+ radius: getRadius(graphState),
30171
+ fillOpacity: 0,
30172
+ color: wonderBlocksTokens.color.blue
30173
+ }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30174
+ point: center,
30175
+ onMove: newCenter => {
30176
+ dispatch(moveCenter(newCenter));
30177
+ }
30178
+ }), /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
30179
+ point: radiusPoint,
30180
+ onMove: newRadiusPoint => {
30181
+ dispatch(moveRadiusPoint(newRadiusPoint));
30182
+ }
30183
+ }));
30184
+ }
30185
+
29879
30186
  function PointGraph(props) {
29880
30187
  const {
29881
30188
  dispatch
@@ -30681,6 +30988,10 @@ function interactiveGraphReducer(state, action) {
30681
30988
  return doMoveAll(state, action);
30682
30989
  case MOVE_POINT:
30683
30990
  return doMovePoint(state, action);
30991
+ case MOVE_CENTER:
30992
+ return doMoveCenter(state, action);
30993
+ case MOVE_RADIUS_POINT:
30994
+ return doMoveRadiusPoint(state, action);
30684
30995
  case CHANGE_SNAP_STEP:
30685
30996
  return doChangeSnapStep(state, action);
30686
30997
  case CHANGE_RANGE:
@@ -30820,6 +31131,67 @@ function doMovePoint(state, action) {
30820
31131
  throw new Error("The movePoint action is only for point and polygon graphs");
30821
31132
  }
30822
31133
  }
31134
+ function doMoveCenter(state, action) {
31135
+ switch (state.type) {
31136
+ case "circle":
31137
+ {
31138
+ // Constrain the center of the circle to the chart range
31139
+ const constrainedCenter = bound$1({
31140
+ snapStep: state.snapStep,
31141
+ range: state.range,
31142
+ point: action.destination
31143
+ });
31144
+
31145
+ // Reposition the radius point based on the new center
31146
+ // (spread to make sure we're not going to mutate anything)
31147
+ const newRadiusPoint = [...mafs.vec.add(state.radiusPoint, mafs.vec.sub(constrainedCenter, state.center))];
31148
+
31149
+ // Try to position the radius handle in a visible spot
31150
+ // if it otherwise would be off the chart
31151
+ // ex: if the handle is on the right and we move the center
31152
+ // to the rightmost position, move the handle to the left
31153
+ const [xMin, xMax] = state.range[0];
31154
+ const [radX] = newRadiusPoint;
31155
+ if (radX < xMin || radX > xMax) {
31156
+ const xJumpDist = (radX - constrainedCenter[0]) * 2;
31157
+ const possibleNewX = radX - xJumpDist;
31158
+ if (possibleNewX >= xMin && possibleNewX <= xMax) {
31159
+ newRadiusPoint[0] = possibleNewX;
31160
+ }
31161
+ }
31162
+ return {
31163
+ ...state,
31164
+ hasBeenInteractedWith: true,
31165
+ center: constrainedCenter,
31166
+ radiusPoint: newRadiusPoint
31167
+ };
31168
+ }
31169
+ default:
31170
+ throw new Error("The doMoveCenter action is only for circle graphs");
31171
+ }
31172
+ }
31173
+ function doMoveRadiusPoint(state, action) {
31174
+ switch (state.type) {
31175
+ case "circle":
31176
+ {
31177
+ const [xMin, xMax] = state.range[0];
31178
+ const nextRadiusPoint = [
31179
+ // Constrain to graph range
31180
+ // The +0 is to convert -0 to +0
31181
+ Math.min(Math.max(xMin, action.destination[0] + 0), xMax), state.center[1]];
31182
+ if (___default["default"].isEqual(nextRadiusPoint, state.center)) {
31183
+ return state;
31184
+ }
31185
+ return {
31186
+ ...state,
31187
+ hasBeenInteractedWith: true,
31188
+ radiusPoint: nextRadiusPoint
31189
+ };
31190
+ }
31191
+ default:
31192
+ throw new Error("The doMoveRadiusPoint action is only for circle graphs");
31193
+ }
31194
+ }
30823
31195
  function doChangeSnapStep(state, action) {
30824
31196
  if (
30825
31197
  // Deep equality check since these are arrays
@@ -30938,235 +31310,6 @@ function setAtIndex(args) {
30938
31310
  return copy;
30939
31311
  }
30940
31312
 
30941
- function initializeGraphState(params) {
30942
- const {
30943
- graph,
30944
- step,
30945
- snapStep,
30946
- range,
30947
- markings
30948
- } = params;
30949
- const shared = {
30950
- hasBeenInteractedWith: false,
30951
- range,
30952
- snapStep,
30953
- markings
30954
- };
30955
- switch (graph.type) {
30956
- case "segment":
30957
- return {
30958
- ...shared,
30959
- type: "segment",
30960
- coords: getDefaultSegments({
30961
- graph,
30962
- step,
30963
- range
30964
- })
30965
- };
30966
- case "linear":
30967
- case "linear-system":
30968
- case "ray":
30969
- return {
30970
- ...shared,
30971
- type: graph.type,
30972
- // Linear and ray graphs have a single tuple of points, while a
30973
- // linear system has two tuples of points.
30974
- coords: getLineCoords({
30975
- graph,
30976
- range,
30977
- step
30978
- })
30979
- };
30980
- case "polygon":
30981
- return {
30982
- ...shared,
30983
- type: "polygon",
30984
- showAngles: Boolean(graph.showAngles),
30985
- showSides: Boolean(graph.showSides),
30986
- coords: getPolygonCoords({
30987
- graph,
30988
- range,
30989
- step
30990
- })
30991
- };
30992
- case "point":
30993
- return {
30994
- ...shared,
30995
- type: graph.type,
30996
- coords: getDefaultPoints({
30997
- graph,
30998
- step,
30999
- range
31000
- })
31001
- };
31002
- case "angle":
31003
- case "circle":
31004
- case "sinusoid":
31005
- case "quadratic":
31006
- throw new Error("Mafs not yet implemented for graph type: " + graph.type);
31007
- default:
31008
- throw new wonderStuffCore.UnreachableCaseError(graph);
31009
- }
31010
- }
31011
- const getDefaultPoints = _ref => {
31012
- let {
31013
- graph,
31014
- range,
31015
- step
31016
- } = _ref;
31017
- const numPoints = graph.numPoints || 1;
31018
- let coords = graph.coords?.slice();
31019
- if (coords) {
31020
- return coords;
31021
- }
31022
- switch (numPoints) {
31023
- case 1:
31024
- // Back in the day, one point's coords were in graph.coord
31025
- coords = [graph.coord || [0, 0]];
31026
- break;
31027
- case 2:
31028
- coords = [[-5, 0], [5, 0]];
31029
- break;
31030
- case 3:
31031
- coords = [[-5, 0], [0, 0], [5, 0]];
31032
- break;
31033
- case 4:
31034
- coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
31035
- break;
31036
- case 5:
31037
- coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
31038
- break;
31039
- case 6:
31040
- coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
31041
- break;
31042
- default:
31043
- coords = [];
31044
- break;
31045
- }
31046
- // Transform coords from their -10 to 10 space to 0 to 1
31047
- // because of the old graph.coord, and also it's easier.
31048
- const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
31049
- return normalizePoints(range, step, newCoords);
31050
- };
31051
-
31052
- // TS v4 doesn't narrow return types, while v5 does.
31053
- // Instead of updating to v5, using generic type to relate input and output types.
31054
- function getGradableGraph(state, initialGraph) {
31055
- if (!state.hasBeenInteractedWith) {
31056
- return {
31057
- ...initialGraph
31058
- };
31059
- }
31060
- if (initialGraph.type === "linear-system" && state.type === "linear-system") {
31061
- return {
31062
- ...initialGraph,
31063
- coords: state.coords
31064
- };
31065
- }
31066
- if (state.type === "segment" && initialGraph.type === "segment") {
31067
- return {
31068
- ...initialGraph,
31069
- coords: state.coords
31070
- };
31071
- }
31072
- if (state.type === "linear" && initialGraph.type === "linear") {
31073
- return {
31074
- ...initialGraph,
31075
- coords: state.coords[0]
31076
- };
31077
- }
31078
- if (state.type === "ray" && initialGraph.type === "ray") {
31079
- return {
31080
- ...initialGraph,
31081
- coords: state.coords[0]
31082
- };
31083
- }
31084
- if (state.type === "polygon" && initialGraph.type === "polygon") {
31085
- return {
31086
- ...initialGraph,
31087
- coords: state.coords
31088
- };
31089
- }
31090
- if (state.type === "point" && initialGraph.type === "point") {
31091
- return {
31092
- ...initialGraph,
31093
- coords: state.coords
31094
- };
31095
- }
31096
- throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
31097
- }
31098
- const getDefaultSegments = _ref2 => {
31099
- let {
31100
- graph,
31101
- range,
31102
- step
31103
- } = _ref2;
31104
- const ys = n => {
31105
- switch (n) {
31106
- case 2:
31107
- return [5, -5];
31108
- case 3:
31109
- return [5, 0, -5];
31110
- case 4:
31111
- return [6, 2, -2, -6];
31112
- case 5:
31113
- return [6, 3, 0, -3, -6];
31114
- case 6:
31115
- return [5, 3, 1, -1, -3, -5];
31116
- default:
31117
- return [5];
31118
- }
31119
- };
31120
- const defaultRange = [[-10, 10], [-10, 10]];
31121
- return ys(graph.numSegments).map(y => {
31122
- let endpoints = [[-5, y], [5, y]];
31123
- endpoints = normalizeCoords(endpoints, defaultRange);
31124
- endpoints = normalizePoints(range, step, endpoints);
31125
- return endpoints;
31126
- });
31127
- };
31128
- const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
31129
- const getLineCoords = _ref3 => {
31130
- let {
31131
- graph,
31132
- range,
31133
- step
31134
- } = _ref3;
31135
- return (
31136
- // Return two lines for a linear system, one for a ray or linear
31137
- graph.coords ?? graph.type === "linear-system" ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
31138
- );
31139
- };
31140
- const getPolygonCoords = _ref4 => {
31141
- let {
31142
- graph,
31143
- range,
31144
- step
31145
- } = _ref4;
31146
- let coords = graph.coords?.slice();
31147
- if (coords) {
31148
- return coords;
31149
- }
31150
- const n = graph.numSides || 3;
31151
- if (n === "unlimited") {
31152
- coords = [];
31153
- } else {
31154
- const angle = 2 * Math.PI / n;
31155
- const offset = (1 / n - 1 / 2) * Math.PI;
31156
-
31157
- // TODO(alex): Generalize this to more than just triangles so that
31158
- // all polygons have whole number side lengths if snapping to sides
31159
- const radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7 : 4;
31160
-
31161
- // Generate coords of a regular polygon with n sides
31162
- coords = [...Array(n).keys()].map(i => [radius * Math.cos(i * angle + offset), radius * Math.sin(i * angle + offset)]);
31163
- }
31164
- coords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
31165
- const snapToGrid = !["angles", "sides"].includes(graph.snapTo || "");
31166
- coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
31167
- return coords;
31168
- };
31169
-
31170
31313
  const renderGraph = props => {
31171
31314
  const {
31172
31315
  state,
@@ -31203,20 +31346,46 @@ const renderGraph = props => {
31203
31346
  dispatch: dispatch
31204
31347
  });
31205
31348
  case "circle":
31206
- throw new Error("the circle graph type is not yet implemented");
31349
+ return /*#__PURE__*/React__namespace.createElement(CircleGraph, {
31350
+ graphState: state,
31351
+ dispatch: dispatch
31352
+ });
31207
31353
  default:
31208
31354
  return new wonderStuffCore.UnreachableCaseError(type);
31209
31355
  }
31210
31356
  };
31357
+
31358
+ // Rather than be tightly bound to how data was structured in
31359
+ // the legacy interactive graph, this lets us store state
31360
+ // however we want and we just transform it before handing it off
31361
+ // the the parent InteractiveGraph
31362
+ function mafsStateToInteractiveGraph(state) {
31363
+ if (state.graph.type === "circle") {
31364
+ return {
31365
+ ...state,
31366
+ graph: {
31367
+ ...state.graph,
31368
+ radius: getRadius(state.graph)
31369
+ }
31370
+ };
31371
+ }
31372
+ return {
31373
+ ...state
31374
+ };
31375
+ }
31211
31376
  const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
31212
31377
  const [state, dispatch] = React__namespace.useReducer(interactiveGraphReducer, props, initializeGraphState);
31213
31378
  React.useImperativeHandle(ref, () => ({
31214
31379
  getUserInput: () => getGradableGraph(state, props.graph)
31215
31380
  }));
31216
- return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({
31381
+ function onChange(next) {
31382
+ props.onChange(mafsStateToInteractiveGraph(next));
31383
+ }
31384
+ return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
31217
31385
  state: state,
31218
- dispatch: dispatch
31219
- }, props));
31386
+ dispatch: dispatch,
31387
+ onChange: onChange
31388
+ }));
31220
31389
  });
31221
31390
  const MafsGraph = props => {
31222
31391
  const {
@@ -31258,6 +31427,7 @@ const MafsGraph = props => {
31258
31427
  labels
31259
31428
  }
31260
31429
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
31430
+ className: "mafs-graph",
31261
31431
  style: {
31262
31432
  width,
31263
31433
  height,
@@ -43389,7 +43559,7 @@ const init = function (options) {
43389
43559
 
43390
43560
  // This file is processed by a Rollup plugin (replace) to inject the production
43391
43561
  const libName = "@khanacademy/perseus";
43392
- const libVersion = "22.2.0";
43562
+ const libVersion = "22.4.0";
43393
43563
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
43394
43564
 
43395
43565
  const apiVersion = {