@khanacademy/perseus 22.3.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');
@@ -29012,6 +29012,7 @@ const arrowPath = pathBuilder().move(-3, 4).curve(-2.75, 2.5, 0, 0.25, 0.75, 0).
29012
29012
  function Arrowhead(props) {
29013
29013
  const [point] = useTransformVectorsToPixels(props.tip);
29014
29014
  return /*#__PURE__*/React__namespace.createElement("g", {
29015
+ className: "interactive-graph-arrowhead",
29015
29016
  transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
29016
29017
  }, /*#__PURE__*/React__namespace.createElement("g", {
29017
29018
  transform: "translate(-1.5)"
@@ -29026,49 +29027,7 @@ function Arrowhead(props) {
29026
29027
  })));
29027
29028
  }
29028
29029
 
29029
- function SVGLine(props) {
29030
- const {
29031
- start,
29032
- end,
29033
- style,
29034
- className,
29035
- testId
29036
- } = props;
29037
- return /*#__PURE__*/React__namespace.createElement("line", {
29038
- x1: start[0],
29039
- y1: start[1],
29040
- x2: end[0],
29041
- y2: end[1],
29042
- style: style,
29043
- className: className,
29044
- "data-testid": testId
29045
- });
29046
- }
29047
-
29048
- function Vector(props) {
29049
- const {
29050
- tail,
29051
- tip,
29052
- color,
29053
- style
29054
- } = props;
29055
- const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
29056
- const direction = mafs.vec.sub(tip, tail);
29057
- return /*#__PURE__*/React__namespace.createElement("g", {
29058
- style: {
29059
- stroke: color,
29060
- strokeWidth: 2
29061
- }
29062
- }, /*#__PURE__*/React__namespace.createElement(SVGLine, {
29063
- start: tailPx,
29064
- end: tipPx,
29065
- style: style
29066
- }), /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29067
- tip: tip,
29068
- angle: angleDegrees(direction)
29069
- }));
29070
- }
29071
- function angleDegrees(_ref) {
29030
+ function calculateAngleInDegrees(_ref) {
29072
29031
  let [x, y] = _ref;
29073
29032
  return Math.atan2(y, x) * 180 / Math.PI;
29074
29033
  }
@@ -29111,10 +29070,57 @@ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
29111
29070
  return [xMax, yAtXMax];
29112
29071
  }
29113
29072
  };
29114
- const getLines = points => points.map((point, i) => {
29115
- const next = points[(i + 1) % points.length];
29116
- return [point, next];
29117
- });
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
+ }
29118
29124
 
29119
29125
  const LockedLine = props => {
29120
29126
  const {
@@ -29130,27 +29136,38 @@ const LockedLine = props => {
29130
29136
  let line;
29131
29137
  if (kind === "ray") {
29132
29138
  // Rays extend to the end of the graph in one direction.
29133
- const endExtend = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29139
+ const extendedPoint = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
29134
29140
  line = /*#__PURE__*/React__namespace.createElement(Vector, {
29135
29141
  tail: point1.coord,
29136
- tip: endExtend,
29142
+ tip: extendedPoint,
29137
29143
  color: lockedFigureColors[color],
29138
29144
  style: {
29139
- strokeDasharray: lineStyle === "dashed" ?
29140
- // TODO(lems-1930): Uncomment this line when the
29141
- // dashed style is updated in Mafs.
29142
- // ? "var(--mafs-line-stroke-dash-style)"
29143
- "4, 3" : undefined
29145
+ strokeDasharray: lineStyle === "dashed" ? "var(--mafs-line-stroke-dash-style)" : undefined
29144
29146
  }
29145
29147
  });
29146
29148
  } else {
29147
29149
  const LineType = kind === "segment" ? mafs.Line.Segment : mafs.Line.ThroughPoints;
29148
- 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, {
29149
29166
  point1: point1.coord,
29150
29167
  point2: point2.coord,
29151
29168
  color: lockedFigureColors[color],
29152
29169
  style: lineStyle
29153
- });
29170
+ }), endArrowHead);
29154
29171
  }
29155
29172
  return /*#__PURE__*/React__namespace.createElement("g", {
29156
29173
  className: kind === "ray" ? "locked-ray" : "locked-line"
@@ -29256,6 +29273,20 @@ function movePoint(index, destination) {
29256
29273
  destination
29257
29274
  };
29258
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
+ }
29259
29290
  const CHANGE_SNAP_STEP = "change-snap-step";
29260
29291
  function changeSnapStep(snapStep) {
29261
29292
  return {
@@ -29871,6 +29902,287 @@ const PolygonGraph = props => {
29871
29902
  })));
29872
29903
  };
29873
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
+
29874
30186
  function PointGraph(props) {
29875
30187
  const {
29876
30188
  dispatch
@@ -30676,6 +30988,10 @@ function interactiveGraphReducer(state, action) {
30676
30988
  return doMoveAll(state, action);
30677
30989
  case MOVE_POINT:
30678
30990
  return doMovePoint(state, action);
30991
+ case MOVE_CENTER:
30992
+ return doMoveCenter(state, action);
30993
+ case MOVE_RADIUS_POINT:
30994
+ return doMoveRadiusPoint(state, action);
30679
30995
  case CHANGE_SNAP_STEP:
30680
30996
  return doChangeSnapStep(state, action);
30681
30997
  case CHANGE_RANGE:
@@ -30815,6 +31131,67 @@ function doMovePoint(state, action) {
30815
31131
  throw new Error("The movePoint action is only for point and polygon graphs");
30816
31132
  }
30817
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
+ }
30818
31195
  function doChangeSnapStep(state, action) {
30819
31196
  if (
30820
31197
  // Deep equality check since these are arrays
@@ -30933,235 +31310,6 @@ function setAtIndex(args) {
30933
31310
  return copy;
30934
31311
  }
30935
31312
 
30936
- function initializeGraphState(params) {
30937
- const {
30938
- graph,
30939
- step,
30940
- snapStep,
30941
- range,
30942
- markings
30943
- } = params;
30944
- const shared = {
30945
- hasBeenInteractedWith: false,
30946
- range,
30947
- snapStep,
30948
- markings
30949
- };
30950
- switch (graph.type) {
30951
- case "segment":
30952
- return {
30953
- ...shared,
30954
- type: "segment",
30955
- coords: getDefaultSegments({
30956
- graph,
30957
- step,
30958
- range
30959
- })
30960
- };
30961
- case "linear":
30962
- case "linear-system":
30963
- case "ray":
30964
- return {
30965
- ...shared,
30966
- type: graph.type,
30967
- // Linear and ray graphs have a single tuple of points, while a
30968
- // linear system has two tuples of points.
30969
- coords: getLineCoords({
30970
- graph,
30971
- range,
30972
- step
30973
- })
30974
- };
30975
- case "polygon":
30976
- return {
30977
- ...shared,
30978
- type: "polygon",
30979
- showAngles: Boolean(graph.showAngles),
30980
- showSides: Boolean(graph.showSides),
30981
- coords: getPolygonCoords({
30982
- graph,
30983
- range,
30984
- step
30985
- })
30986
- };
30987
- case "point":
30988
- return {
30989
- ...shared,
30990
- type: graph.type,
30991
- coords: getDefaultPoints({
30992
- graph,
30993
- step,
30994
- range
30995
- })
30996
- };
30997
- case "angle":
30998
- case "circle":
30999
- case "sinusoid":
31000
- case "quadratic":
31001
- throw new Error("Mafs not yet implemented for graph type: " + graph.type);
31002
- default:
31003
- throw new wonderStuffCore.UnreachableCaseError(graph);
31004
- }
31005
- }
31006
- const getDefaultPoints = _ref => {
31007
- let {
31008
- graph,
31009
- range,
31010
- step
31011
- } = _ref;
31012
- const numPoints = graph.numPoints || 1;
31013
- let coords = graph.coords?.slice();
31014
- if (coords) {
31015
- return coords;
31016
- }
31017
- switch (numPoints) {
31018
- case 1:
31019
- // Back in the day, one point's coords were in graph.coord
31020
- coords = [graph.coord || [0, 0]];
31021
- break;
31022
- case 2:
31023
- coords = [[-5, 0], [5, 0]];
31024
- break;
31025
- case 3:
31026
- coords = [[-5, 0], [0, 0], [5, 0]];
31027
- break;
31028
- case 4:
31029
- coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
31030
- break;
31031
- case 5:
31032
- coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
31033
- break;
31034
- case 6:
31035
- coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
31036
- break;
31037
- default:
31038
- coords = [];
31039
- break;
31040
- }
31041
- // Transform coords from their -10 to 10 space to 0 to 1
31042
- // because of the old graph.coord, and also it's easier.
31043
- const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
31044
- return normalizePoints(range, step, newCoords);
31045
- };
31046
-
31047
- // TS v4 doesn't narrow return types, while v5 does.
31048
- // Instead of updating to v5, using generic type to relate input and output types.
31049
- function getGradableGraph(state, initialGraph) {
31050
- if (!state.hasBeenInteractedWith) {
31051
- return {
31052
- ...initialGraph
31053
- };
31054
- }
31055
- if (initialGraph.type === "linear-system" && state.type === "linear-system") {
31056
- return {
31057
- ...initialGraph,
31058
- coords: state.coords
31059
- };
31060
- }
31061
- if (state.type === "segment" && initialGraph.type === "segment") {
31062
- return {
31063
- ...initialGraph,
31064
- coords: state.coords
31065
- };
31066
- }
31067
- if (state.type === "linear" && initialGraph.type === "linear") {
31068
- return {
31069
- ...initialGraph,
31070
- coords: state.coords[0]
31071
- };
31072
- }
31073
- if (state.type === "ray" && initialGraph.type === "ray") {
31074
- return {
31075
- ...initialGraph,
31076
- coords: state.coords[0]
31077
- };
31078
- }
31079
- if (state.type === "polygon" && initialGraph.type === "polygon") {
31080
- return {
31081
- ...initialGraph,
31082
- coords: state.coords
31083
- };
31084
- }
31085
- if (state.type === "point" && initialGraph.type === "point") {
31086
- return {
31087
- ...initialGraph,
31088
- coords: state.coords
31089
- };
31090
- }
31091
- throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
31092
- }
31093
- const getDefaultSegments = _ref2 => {
31094
- let {
31095
- graph,
31096
- range,
31097
- step
31098
- } = _ref2;
31099
- const ys = n => {
31100
- switch (n) {
31101
- case 2:
31102
- return [5, -5];
31103
- case 3:
31104
- return [5, 0, -5];
31105
- case 4:
31106
- return [6, 2, -2, -6];
31107
- case 5:
31108
- return [6, 3, 0, -3, -6];
31109
- case 6:
31110
- return [5, 3, 1, -1, -3, -5];
31111
- default:
31112
- return [5];
31113
- }
31114
- };
31115
- const defaultRange = [[-10, 10], [-10, 10]];
31116
- return ys(graph.numSegments).map(y => {
31117
- let endpoints = [[-5, y], [5, y]];
31118
- endpoints = normalizeCoords(endpoints, defaultRange);
31119
- endpoints = normalizePoints(range, step, endpoints);
31120
- return endpoints;
31121
- });
31122
- };
31123
- const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
31124
- const getLineCoords = _ref3 => {
31125
- let {
31126
- graph,
31127
- range,
31128
- step
31129
- } = _ref3;
31130
- return (
31131
- // Return two lines for a linear system, one for a ray or linear
31132
- graph.coords ?? graph.type === "linear-system" ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
31133
- );
31134
- };
31135
- const getPolygonCoords = _ref4 => {
31136
- let {
31137
- graph,
31138
- range,
31139
- step
31140
- } = _ref4;
31141
- let coords = graph.coords?.slice();
31142
- if (coords) {
31143
- return coords;
31144
- }
31145
- const n = graph.numSides || 3;
31146
- if (n === "unlimited") {
31147
- coords = [];
31148
- } else {
31149
- const angle = 2 * Math.PI / n;
31150
- const offset = (1 / n - 1 / 2) * Math.PI;
31151
-
31152
- // TODO(alex): Generalize this to more than just triangles so that
31153
- // all polygons have whole number side lengths if snapping to sides
31154
- const radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7 : 4;
31155
-
31156
- // Generate coords of a regular polygon with n sides
31157
- coords = [...Array(n).keys()].map(i => [radius * Math.cos(i * angle + offset), radius * Math.sin(i * angle + offset)]);
31158
- }
31159
- coords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
31160
- const snapToGrid = !["angles", "sides"].includes(graph.snapTo || "");
31161
- coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
31162
- return coords;
31163
- };
31164
-
31165
31313
  const renderGraph = props => {
31166
31314
  const {
31167
31315
  state,
@@ -31198,20 +31346,46 @@ const renderGraph = props => {
31198
31346
  dispatch: dispatch
31199
31347
  });
31200
31348
  case "circle":
31201
- 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
+ });
31202
31353
  default:
31203
31354
  return new wonderStuffCore.UnreachableCaseError(type);
31204
31355
  }
31205
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
+ }
31206
31376
  const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
31207
31377
  const [state, dispatch] = React__namespace.useReducer(interactiveGraphReducer, props, initializeGraphState);
31208
31378
  React.useImperativeHandle(ref, () => ({
31209
31379
  getUserInput: () => getGradableGraph(state, props.graph)
31210
31380
  }));
31211
- 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, {
31212
31385
  state: state,
31213
- dispatch: dispatch
31214
- }, props));
31386
+ dispatch: dispatch,
31387
+ onChange: onChange
31388
+ }));
31215
31389
  });
31216
31390
  const MafsGraph = props => {
31217
31391
  const {
@@ -43385,7 +43559,7 @@ const init = function (options) {
43385
43559
 
43386
43560
  // This file is processed by a Rollup plugin (replace) to inject the production
43387
43561
  const libName = "@khanacademy/perseus";
43388
- const libVersion = "22.3.0";
43562
+ const libVersion = "22.4.0";
43389
43563
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
43390
43564
 
43391
43565
  const apiVersion = {