@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/es/index.js CHANGED
@@ -26,10 +26,10 @@ import Button from '@khanacademy/wonder-blocks-button';
26
26
  import { Strut } from '@khanacademy/wonder-blocks-layout';
27
27
  import Banner from '@khanacademy/wonder-blocks-banner';
28
28
  import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-blocks-dropdown';
29
- import caretDown from '@phosphor-icons/core/assets/regular/caret-down.svg';
30
- import caretUp from '@phosphor-icons/core/assets/regular/caret-up.svg';
29
+ import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
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$3, Point as Point$2, useMovable, Text as Text$1, Polygon, Coordinates, Mafs } from 'mafs';
32
+ import { vec, Line as Line$3, Point as Point$2, useMovable, Text as Text$1, Polygon, Circle as Circle$1, 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';
@@ -28726,6 +28726,7 @@ function Arrowhead(props) {
28726
28726
  var _props$color;
28727
28727
  const [point] = useTransformVectorsToPixels(props.tip);
28728
28728
  return /*#__PURE__*/React.createElement("g", {
28729
+ className: "interactive-graph-arrowhead",
28729
28730
  transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
28730
28731
  }, /*#__PURE__*/React.createElement("g", {
28731
28732
  transform: "translate(-1.5)"
@@ -28740,49 +28741,7 @@ function Arrowhead(props) {
28740
28741
  })));
28741
28742
  }
28742
28743
 
28743
- function SVGLine(props) {
28744
- const {
28745
- start,
28746
- end,
28747
- style,
28748
- className,
28749
- testId
28750
- } = props;
28751
- return /*#__PURE__*/React.createElement("line", {
28752
- x1: start[0],
28753
- y1: start[1],
28754
- x2: end[0],
28755
- y2: end[1],
28756
- style: style,
28757
- className: className,
28758
- "data-testid": testId
28759
- });
28760
- }
28761
-
28762
- function Vector(props) {
28763
- const {
28764
- tail,
28765
- tip,
28766
- color,
28767
- style
28768
- } = props;
28769
- const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
28770
- const direction = vec.sub(tip, tail);
28771
- return /*#__PURE__*/React.createElement("g", {
28772
- style: {
28773
- stroke: color,
28774
- strokeWidth: 2
28775
- }
28776
- }, /*#__PURE__*/React.createElement(SVGLine, {
28777
- start: tailPx,
28778
- end: tipPx,
28779
- style: style
28780
- }), /*#__PURE__*/React.createElement(Arrowhead, {
28781
- tip: tip,
28782
- angle: angleDegrees(direction)
28783
- }));
28784
- }
28785
- function angleDegrees([x, y]) {
28744
+ function calculateAngleInDegrees([x, y]) {
28786
28745
  return Math.atan2(y, x) * 180 / Math.PI;
28787
28746
  }
28788
28747
 
@@ -28824,10 +28783,57 @@ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
28824
28783
  return [xMax, yAtXMax];
28825
28784
  }
28826
28785
  };
28827
- const getLines = points => points.map((point, i) => {
28828
- const next = points[(i + 1) % points.length];
28829
- return [point, next];
28830
- });
28786
+ const getLines = points => {
28787
+ return points.map((point, i) => {
28788
+ const next = points[(i + 1) % points.length];
28789
+ return [point, next];
28790
+ });
28791
+ };
28792
+
28793
+ function SVGLine(props) {
28794
+ const {
28795
+ start,
28796
+ end,
28797
+ style,
28798
+ className,
28799
+ testId
28800
+ } = props;
28801
+ return /*#__PURE__*/React.createElement("line", {
28802
+ x1: start[0],
28803
+ y1: start[1],
28804
+ x2: end[0],
28805
+ y2: end[1],
28806
+ style: style,
28807
+ className: className,
28808
+ "data-testid": testId
28809
+ });
28810
+ }
28811
+
28812
+ function Vector(props) {
28813
+ const {
28814
+ tail,
28815
+ tip,
28816
+ color,
28817
+ style
28818
+ } = props;
28819
+ const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
28820
+ const direction = vec.sub(tip, tail);
28821
+ const angle = calculateAngleInDegrees(direction);
28822
+ return /*#__PURE__*/React.createElement("g", {
28823
+ style: {
28824
+ stroke: color,
28825
+ strokeWidth: 2
28826
+ }
28827
+ }, /*#__PURE__*/React.createElement(SVGLine, {
28828
+ start: tailPx,
28829
+ end: tipPx,
28830
+ style: style
28831
+ }), /*#__PURE__*/React.createElement(Arrowhead, {
28832
+ angle: angle,
28833
+ tip: tip,
28834
+ color: color
28835
+ }));
28836
+ }
28831
28837
 
28832
28838
  const LockedLine = props => {
28833
28839
  const {
@@ -28843,27 +28849,38 @@ const LockedLine = props => {
28843
28849
  let line;
28844
28850
  if (kind === "ray") {
28845
28851
  // Rays extend to the end of the graph in one direction.
28846
- const endExtend = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28852
+ const extendedPoint = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28847
28853
  line = /*#__PURE__*/React.createElement(Vector, {
28848
28854
  tail: point1.coord,
28849
- tip: endExtend,
28855
+ tip: extendedPoint,
28850
28856
  color: lockedFigureColors[color$1],
28851
28857
  style: {
28852
- strokeDasharray: lineStyle === "dashed" ?
28853
- // TODO(lems-1930): Uncomment this line when the
28854
- // dashed style is updated in Mafs.
28855
- // ? "var(--mafs-line-stroke-dash-style)"
28856
- "4, 3" : undefined
28858
+ strokeDasharray: lineStyle === "dashed" ? "var(--mafs-line-stroke-dash-style)" : undefined
28857
28859
  }
28858
28860
  });
28859
28861
  } else {
28860
28862
  const LineType = kind === "segment" ? Line$3.Segment : Line$3.ThroughPoints;
28861
- line = /*#__PURE__*/React.createElement(LineType, {
28863
+ let arrowTip = kind === "segment" ? point2.coord : getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28864
+ const direction = vec.sub(point2.coord, point1.coord);
28865
+ let angle = calculateAngleInDegrees(direction);
28866
+ const startArrowHead = kind !== "segment" && /*#__PURE__*/React.createElement(Arrowhead, {
28867
+ angle: angle,
28868
+ tip: arrowTip,
28869
+ color: lockedFigureColors[color$1]
28870
+ });
28871
+ arrowTip = kind === "segment" ? point1.coord : getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
28872
+ angle = angle > 180 ? angle - 180 : angle + 180;
28873
+ const endArrowHead = kind !== "segment" && /*#__PURE__*/React.createElement(Arrowhead, {
28874
+ angle: angle,
28875
+ tip: arrowTip,
28876
+ color: lockedFigureColors[color$1]
28877
+ });
28878
+ line = /*#__PURE__*/React.createElement(React.Fragment, null, startArrowHead, /*#__PURE__*/React.createElement(LineType, {
28862
28879
  point1: point1.coord,
28863
28880
  point2: point2.coord,
28864
28881
  color: lockedFigureColors[color$1],
28865
28882
  style: lineStyle
28866
- });
28883
+ }), endArrowHead);
28867
28884
  }
28868
28885
  return /*#__PURE__*/React.createElement("g", {
28869
28886
  className: kind === "ray" ? "locked-ray" : "locked-line"
@@ -28969,6 +28986,20 @@ function movePoint(index, destination) {
28969
28986
  destination
28970
28987
  };
28971
28988
  }
28989
+ const MOVE_CENTER = "move-center";
28990
+ function moveCenter(destination) {
28991
+ return {
28992
+ type: MOVE_CENTER,
28993
+ destination
28994
+ };
28995
+ }
28996
+ const MOVE_RADIUS_POINT = "MOVE_RADIUS_POINT";
28997
+ function moveRadiusPoint(destination) {
28998
+ return {
28999
+ type: MOVE_RADIUS_POINT,
29000
+ destination
29001
+ };
29002
+ }
28972
29003
  const CHANGE_SNAP_STEP = "change-snap-step";
28973
29004
  function changeSnapStep(snapStep) {
28974
29005
  return {
@@ -29574,6 +29605,272 @@ const PolygonGraph = props => {
29574
29605
  })));
29575
29606
  };
29576
29607
 
29608
+ function initializeGraphState(params) {
29609
+ const {
29610
+ graph,
29611
+ step,
29612
+ snapStep,
29613
+ range,
29614
+ markings
29615
+ } = params;
29616
+ const shared = {
29617
+ hasBeenInteractedWith: false,
29618
+ range,
29619
+ snapStep,
29620
+ markings
29621
+ };
29622
+ switch (graph.type) {
29623
+ case "segment":
29624
+ return _extends({}, shared, {
29625
+ type: "segment",
29626
+ coords: getDefaultSegments({
29627
+ graph,
29628
+ step,
29629
+ range
29630
+ })
29631
+ });
29632
+ case "linear":
29633
+ case "linear-system":
29634
+ case "ray":
29635
+ return _extends({}, shared, {
29636
+ type: graph.type,
29637
+ // Linear and ray graphs have a single tuple of points, while a
29638
+ // linear system has two tuples of points.
29639
+ coords: getLineCoords({
29640
+ graph,
29641
+ range,
29642
+ step
29643
+ })
29644
+ });
29645
+ case "polygon":
29646
+ return _extends({}, shared, {
29647
+ type: "polygon",
29648
+ showAngles: Boolean(graph.showAngles),
29649
+ showSides: Boolean(graph.showSides),
29650
+ coords: getPolygonCoords({
29651
+ graph,
29652
+ range,
29653
+ step
29654
+ })
29655
+ });
29656
+ case "point":
29657
+ return _extends({}, shared, {
29658
+ type: graph.type,
29659
+ coords: getDefaultPoints({
29660
+ graph,
29661
+ step,
29662
+ range
29663
+ })
29664
+ });
29665
+ case "circle":
29666
+ return _extends({}, shared, {
29667
+ type: graph.type,
29668
+ center: [0, 0],
29669
+ radiusPoint: [1, 0]
29670
+ });
29671
+ case "angle":
29672
+ case "sinusoid":
29673
+ case "quadratic":
29674
+ throw new Error("Mafs not yet implemented for graph type: " + graph.type);
29675
+ default:
29676
+ throw new UnreachableCaseError(graph);
29677
+ }
29678
+ }
29679
+ const getDefaultPoints = ({
29680
+ graph,
29681
+ range,
29682
+ step
29683
+ }) => {
29684
+ var _graph$coords;
29685
+ const numPoints = graph.numPoints || 1;
29686
+ let coords = (_graph$coords = graph.coords) == null ? void 0 : _graph$coords.slice();
29687
+ if (coords) {
29688
+ return coords;
29689
+ }
29690
+ switch (numPoints) {
29691
+ case 1:
29692
+ // Back in the day, one point's coords were in graph.coord
29693
+ coords = [graph.coord || [0, 0]];
29694
+ break;
29695
+ case 2:
29696
+ coords = [[-5, 0], [5, 0]];
29697
+ break;
29698
+ case 3:
29699
+ coords = [[-5, 0], [0, 0], [5, 0]];
29700
+ break;
29701
+ case 4:
29702
+ coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
29703
+ break;
29704
+ case 5:
29705
+ coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
29706
+ break;
29707
+ case 6:
29708
+ coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
29709
+ break;
29710
+ default:
29711
+ coords = [];
29712
+ break;
29713
+ }
29714
+ // Transform coords from their -10 to 10 space to 0 to 1
29715
+ // because of the old graph.coord, and also it's easier.
29716
+ const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
29717
+ return normalizePoints(range, step, newCoords);
29718
+ };
29719
+
29720
+ // TS v4 doesn't narrow return types, while v5 does.
29721
+ // Instead of updating to v5, using generic type to relate input and output types.
29722
+ function getGradableGraph(state, initialGraph) {
29723
+ if (!state.hasBeenInteractedWith) {
29724
+ return _extends({}, initialGraph);
29725
+ }
29726
+ if (initialGraph.type === "linear-system" && state.type === "linear-system") {
29727
+ return _extends({}, initialGraph, {
29728
+ coords: state.coords
29729
+ });
29730
+ }
29731
+ if (state.type === "segment" && initialGraph.type === "segment") {
29732
+ return _extends({}, initialGraph, {
29733
+ coords: state.coords
29734
+ });
29735
+ }
29736
+ if (state.type === "linear" && initialGraph.type === "linear") {
29737
+ return _extends({}, initialGraph, {
29738
+ coords: state.coords[0]
29739
+ });
29740
+ }
29741
+ if (state.type === "ray" && initialGraph.type === "ray") {
29742
+ return _extends({}, initialGraph, {
29743
+ coords: state.coords[0]
29744
+ });
29745
+ }
29746
+ if (state.type === "polygon" && initialGraph.type === "polygon") {
29747
+ return _extends({}, initialGraph, {
29748
+ coords: state.coords
29749
+ });
29750
+ }
29751
+ if (state.type === "point" && initialGraph.type === "point") {
29752
+ return _extends({}, initialGraph, {
29753
+ coords: state.coords
29754
+ });
29755
+ }
29756
+ if (state.type === "circle" && initialGraph.type === "circle") {
29757
+ return _extends({}, initialGraph, {
29758
+ center: state.center,
29759
+ radius: getRadius(state)
29760
+ });
29761
+ }
29762
+ throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
29763
+ }
29764
+ const getDefaultSegments = ({
29765
+ graph,
29766
+ range,
29767
+ step
29768
+ }) => {
29769
+ const ys = n => {
29770
+ switch (n) {
29771
+ case 2:
29772
+ return [5, -5];
29773
+ case 3:
29774
+ return [5, 0, -5];
29775
+ case 4:
29776
+ return [6, 2, -2, -6];
29777
+ case 5:
29778
+ return [6, 3, 0, -3, -6];
29779
+ case 6:
29780
+ return [5, 3, 1, -1, -3, -5];
29781
+ default:
29782
+ return [5];
29783
+ }
29784
+ };
29785
+ const defaultRange = [[-10, 10], [-10, 10]];
29786
+ return ys(graph.numSegments).map(y => {
29787
+ let endpoints = [[-5, y], [5, y]];
29788
+ endpoints = normalizeCoords(endpoints, defaultRange);
29789
+ endpoints = normalizePoints(range, step, endpoints);
29790
+ return endpoints;
29791
+ });
29792
+ };
29793
+ const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
29794
+ const getLineCoords = ({
29795
+ graph,
29796
+ range,
29797
+ step
29798
+ }) => {
29799
+ var _graph$coords2;
29800
+ return (
29801
+ // Return two lines for a linear system, one for a ray or linear
29802
+ ((_graph$coords2 = graph.coords) != null ? _graph$coords2 : graph.type === "linear-system") ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
29803
+ );
29804
+ };
29805
+ const getPolygonCoords = ({
29806
+ graph,
29807
+ range,
29808
+ step
29809
+ }) => {
29810
+ var _graph$coords3;
29811
+ let coords = (_graph$coords3 = graph.coords) == null ? void 0 : _graph$coords3.slice();
29812
+ if (coords) {
29813
+ return coords;
29814
+ }
29815
+ const n = graph.numSides || 3;
29816
+ if (n === "unlimited") {
29817
+ coords = [];
29818
+ } else {
29819
+ const angle = 2 * Math.PI / n;
29820
+ const offset = (1 / n - 1 / 2) * Math.PI;
29821
+
29822
+ // TODO(alex): Generalize this to more than just triangles so that
29823
+ // all polygons have whole number side lengths if snapping to sides
29824
+ const radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7 : 4;
29825
+
29826
+ // Generate coords of a regular polygon with n sides
29827
+ coords = [...Array(n).keys()].map(i => [radius * Math.cos(i * angle + offset), radius * Math.sin(i * angle + offset)]);
29828
+ }
29829
+ coords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
29830
+ const snapToGrid = !["angles", "sides"].includes(graph.snapTo || "");
29831
+ coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
29832
+ return coords;
29833
+ };
29834
+
29835
+ /**
29836
+ * determine radius of a circle graph
29837
+ *
29838
+ * @param graph - the graph object containing the circle
29839
+ * @returns the radius of the circle
29840
+ */
29841
+ function getRadius(graph) {
29842
+ const [centerX, centerY] = graph.center;
29843
+ const [edgeX, edgeY] = graph.radiusPoint;
29844
+ return Math.sqrt(Math.pow(edgeX - centerX, 2) + Math.pow(edgeY - centerY, 2));
29845
+ }
29846
+
29847
+ function CircleGraph(props) {
29848
+ const {
29849
+ dispatch,
29850
+ graphState
29851
+ } = props;
29852
+ const {
29853
+ center,
29854
+ radiusPoint
29855
+ } = graphState;
29856
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Circle$1, {
29857
+ center: center,
29858
+ radius: getRadius(graphState),
29859
+ fillOpacity: 0,
29860
+ color: color.blue
29861
+ }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29862
+ point: center,
29863
+ onMove: newCenter => {
29864
+ dispatch(moveCenter(newCenter));
29865
+ }
29866
+ }), /*#__PURE__*/React.createElement(StyledMovablePoint, {
29867
+ point: radiusPoint,
29868
+ onMove: newRadiusPoint => {
29869
+ dispatch(moveRadiusPoint(newRadiusPoint));
29870
+ }
29871
+ }));
29872
+ }
29873
+
29577
29874
  function PointGraph(props) {
29578
29875
  const {
29579
29876
  dispatch
@@ -30375,6 +30672,10 @@ function interactiveGraphReducer(state, action) {
30375
30672
  return doMoveAll(state, action);
30376
30673
  case MOVE_POINT:
30377
30674
  return doMovePoint(state, action);
30675
+ case MOVE_CENTER:
30676
+ return doMoveCenter(state, action);
30677
+ case MOVE_RADIUS_POINT:
30678
+ return doMoveRadiusPoint(state, action);
30378
30679
  case CHANGE_SNAP_STEP:
30379
30680
  return doChangeSnapStep(state, action);
30380
30681
  case CHANGE_RANGE:
@@ -30510,6 +30811,65 @@ function doMovePoint(state, action) {
30510
30811
  throw new Error("The movePoint action is only for point and polygon graphs");
30511
30812
  }
30512
30813
  }
30814
+ function doMoveCenter(state, action) {
30815
+ switch (state.type) {
30816
+ case "circle":
30817
+ {
30818
+ // Constrain the center of the circle to the chart range
30819
+ const constrainedCenter = bound$1({
30820
+ snapStep: state.snapStep,
30821
+ range: state.range,
30822
+ point: action.destination
30823
+ });
30824
+
30825
+ // Reposition the radius point based on the new center
30826
+ // (spread to make sure we're not going to mutate anything)
30827
+ const newRadiusPoint = [...vec.add(state.radiusPoint, vec.sub(constrainedCenter, state.center))];
30828
+
30829
+ // Try to position the radius handle in a visible spot
30830
+ // if it otherwise would be off the chart
30831
+ // ex: if the handle is on the right and we move the center
30832
+ // to the rightmost position, move the handle to the left
30833
+ const [xMin, xMax] = state.range[0];
30834
+ const [radX] = newRadiusPoint;
30835
+ if (radX < xMin || radX > xMax) {
30836
+ const xJumpDist = (radX - constrainedCenter[0]) * 2;
30837
+ const possibleNewX = radX - xJumpDist;
30838
+ if (possibleNewX >= xMin && possibleNewX <= xMax) {
30839
+ newRadiusPoint[0] = possibleNewX;
30840
+ }
30841
+ }
30842
+ return _extends({}, state, {
30843
+ hasBeenInteractedWith: true,
30844
+ center: constrainedCenter,
30845
+ radiusPoint: newRadiusPoint
30846
+ });
30847
+ }
30848
+ default:
30849
+ throw new Error("The doMoveCenter action is only for circle graphs");
30850
+ }
30851
+ }
30852
+ function doMoveRadiusPoint(state, action) {
30853
+ switch (state.type) {
30854
+ case "circle":
30855
+ {
30856
+ const [xMin, xMax] = state.range[0];
30857
+ const nextRadiusPoint = [
30858
+ // Constrain to graph range
30859
+ // The +0 is to convert -0 to +0
30860
+ Math.min(Math.max(xMin, action.destination[0] + 0), xMax), state.center[1]];
30861
+ if (_.isEqual(nextRadiusPoint, state.center)) {
30862
+ return state;
30863
+ }
30864
+ return _extends({}, state, {
30865
+ hasBeenInteractedWith: true,
30866
+ radiusPoint: nextRadiusPoint
30867
+ });
30868
+ }
30869
+ default:
30870
+ throw new Error("The doMoveRadiusPoint action is only for circle graphs");
30871
+ }
30872
+ }
30513
30873
  function doChangeSnapStep(state, action) {
30514
30874
  if (
30515
30875
  // Deep equality check since these are arrays
@@ -30621,222 +30981,6 @@ function setAtIndex(args) {
30621
30981
  return copy;
30622
30982
  }
30623
30983
 
30624
- function initializeGraphState(params) {
30625
- const {
30626
- graph,
30627
- step,
30628
- snapStep,
30629
- range,
30630
- markings
30631
- } = params;
30632
- const shared = {
30633
- hasBeenInteractedWith: false,
30634
- range,
30635
- snapStep,
30636
- markings
30637
- };
30638
- switch (graph.type) {
30639
- case "segment":
30640
- return _extends({}, shared, {
30641
- type: "segment",
30642
- coords: getDefaultSegments({
30643
- graph,
30644
- step,
30645
- range
30646
- })
30647
- });
30648
- case "linear":
30649
- case "linear-system":
30650
- case "ray":
30651
- return _extends({}, shared, {
30652
- type: graph.type,
30653
- // Linear and ray graphs have a single tuple of points, while a
30654
- // linear system has two tuples of points.
30655
- coords: getLineCoords({
30656
- graph,
30657
- range,
30658
- step
30659
- })
30660
- });
30661
- case "polygon":
30662
- return _extends({}, shared, {
30663
- type: "polygon",
30664
- showAngles: Boolean(graph.showAngles),
30665
- showSides: Boolean(graph.showSides),
30666
- coords: getPolygonCoords({
30667
- graph,
30668
- range,
30669
- step
30670
- })
30671
- });
30672
- case "point":
30673
- return _extends({}, shared, {
30674
- type: graph.type,
30675
- coords: getDefaultPoints({
30676
- graph,
30677
- step,
30678
- range
30679
- })
30680
- });
30681
- case "angle":
30682
- case "circle":
30683
- case "sinusoid":
30684
- case "quadratic":
30685
- throw new Error("Mafs not yet implemented for graph type: " + graph.type);
30686
- default:
30687
- throw new UnreachableCaseError(graph);
30688
- }
30689
- }
30690
- const getDefaultPoints = ({
30691
- graph,
30692
- range,
30693
- step
30694
- }) => {
30695
- var _graph$coords;
30696
- const numPoints = graph.numPoints || 1;
30697
- let coords = (_graph$coords = graph.coords) == null ? void 0 : _graph$coords.slice();
30698
- if (coords) {
30699
- return coords;
30700
- }
30701
- switch (numPoints) {
30702
- case 1:
30703
- // Back in the day, one point's coords were in graph.coord
30704
- coords = [graph.coord || [0, 0]];
30705
- break;
30706
- case 2:
30707
- coords = [[-5, 0], [5, 0]];
30708
- break;
30709
- case 3:
30710
- coords = [[-5, 0], [0, 0], [5, 0]];
30711
- break;
30712
- case 4:
30713
- coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
30714
- break;
30715
- case 5:
30716
- coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
30717
- break;
30718
- case 6:
30719
- coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
30720
- break;
30721
- default:
30722
- coords = [];
30723
- break;
30724
- }
30725
- // Transform coords from their -10 to 10 space to 0 to 1
30726
- // because of the old graph.coord, and also it's easier.
30727
- const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
30728
- return normalizePoints(range, step, newCoords);
30729
- };
30730
-
30731
- // TS v4 doesn't narrow return types, while v5 does.
30732
- // Instead of updating to v5, using generic type to relate input and output types.
30733
- function getGradableGraph(state, initialGraph) {
30734
- if (!state.hasBeenInteractedWith) {
30735
- return _extends({}, initialGraph);
30736
- }
30737
- if (initialGraph.type === "linear-system" && state.type === "linear-system") {
30738
- return _extends({}, initialGraph, {
30739
- coords: state.coords
30740
- });
30741
- }
30742
- if (state.type === "segment" && initialGraph.type === "segment") {
30743
- return _extends({}, initialGraph, {
30744
- coords: state.coords
30745
- });
30746
- }
30747
- if (state.type === "linear" && initialGraph.type === "linear") {
30748
- return _extends({}, initialGraph, {
30749
- coords: state.coords[0]
30750
- });
30751
- }
30752
- if (state.type === "ray" && initialGraph.type === "ray") {
30753
- return _extends({}, initialGraph, {
30754
- coords: state.coords[0]
30755
- });
30756
- }
30757
- if (state.type === "polygon" && initialGraph.type === "polygon") {
30758
- return _extends({}, initialGraph, {
30759
- coords: state.coords
30760
- });
30761
- }
30762
- if (state.type === "point" && initialGraph.type === "point") {
30763
- return _extends({}, initialGraph, {
30764
- coords: state.coords
30765
- });
30766
- }
30767
- throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
30768
- }
30769
- const getDefaultSegments = ({
30770
- graph,
30771
- range,
30772
- step
30773
- }) => {
30774
- const ys = n => {
30775
- switch (n) {
30776
- case 2:
30777
- return [5, -5];
30778
- case 3:
30779
- return [5, 0, -5];
30780
- case 4:
30781
- return [6, 2, -2, -6];
30782
- case 5:
30783
- return [6, 3, 0, -3, -6];
30784
- case 6:
30785
- return [5, 3, 1, -1, -3, -5];
30786
- default:
30787
- return [5];
30788
- }
30789
- };
30790
- const defaultRange = [[-10, 10], [-10, 10]];
30791
- return ys(graph.numSegments).map(y => {
30792
- let endpoints = [[-5, y], [5, y]];
30793
- endpoints = normalizeCoords(endpoints, defaultRange);
30794
- endpoints = normalizePoints(range, step, endpoints);
30795
- return endpoints;
30796
- });
30797
- };
30798
- const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
30799
- const getLineCoords = ({
30800
- graph,
30801
- range,
30802
- step
30803
- }) => {
30804
- var _graph$coords2;
30805
- return (
30806
- // Return two lines for a linear system, one for a ray or linear
30807
- ((_graph$coords2 = graph.coords) != null ? _graph$coords2 : graph.type === "linear-system") ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
30808
- );
30809
- };
30810
- const getPolygonCoords = ({
30811
- graph,
30812
- range,
30813
- step
30814
- }) => {
30815
- var _graph$coords3;
30816
- let coords = (_graph$coords3 = graph.coords) == null ? void 0 : _graph$coords3.slice();
30817
- if (coords) {
30818
- return coords;
30819
- }
30820
- const n = graph.numSides || 3;
30821
- if (n === "unlimited") {
30822
- coords = [];
30823
- } else {
30824
- const angle = 2 * Math.PI / n;
30825
- const offset = (1 / n - 1 / 2) * Math.PI;
30826
-
30827
- // TODO(alex): Generalize this to more than just triangles so that
30828
- // all polygons have whole number side lengths if snapping to sides
30829
- const radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7 : 4;
30830
-
30831
- // Generate coords of a regular polygon with n sides
30832
- coords = [...Array(n).keys()].map(i => [radius * Math.cos(i * angle + offset), radius * Math.sin(i * angle + offset)]);
30833
- }
30834
- coords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
30835
- const snapToGrid = !["angles", "sides"].includes(graph.snapTo || "");
30836
- coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
30837
- return coords;
30838
- };
30839
-
30840
30984
  const renderGraph = props => {
30841
30985
  const {
30842
30986
  state,
@@ -30873,20 +31017,42 @@ const renderGraph = props => {
30873
31017
  dispatch: dispatch
30874
31018
  });
30875
31019
  case "circle":
30876
- throw new Error("the circle graph type is not yet implemented");
31020
+ return /*#__PURE__*/React.createElement(CircleGraph, {
31021
+ graphState: state,
31022
+ dispatch: dispatch
31023
+ });
30877
31024
  default:
30878
31025
  return new UnreachableCaseError(type);
30879
31026
  }
30880
31027
  };
31028
+
31029
+ // Rather than be tightly bound to how data was structured in
31030
+ // the legacy interactive graph, this lets us store state
31031
+ // however we want and we just transform it before handing it off
31032
+ // the the parent InteractiveGraph
31033
+ function mafsStateToInteractiveGraph(state) {
31034
+ if (state.graph.type === "circle") {
31035
+ return _extends({}, state, {
31036
+ graph: _extends({}, state.graph, {
31037
+ radius: getRadius(state.graph)
31038
+ })
31039
+ });
31040
+ }
31041
+ return _extends({}, state);
31042
+ }
30881
31043
  const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
30882
31044
  const [state, dispatch] = React.useReducer(interactiveGraphReducer, props, initializeGraphState);
30883
31045
  useImperativeHandle(ref, () => ({
30884
31046
  getUserInput: () => getGradableGraph(state, props.graph)
30885
31047
  }));
30886
- return /*#__PURE__*/React.createElement(MafsGraph, _extends({
31048
+ function onChange(next) {
31049
+ props.onChange(mafsStateToInteractiveGraph(next));
31050
+ }
31051
+ return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
30887
31052
  state: state,
30888
- dispatch: dispatch
30889
- }, props));
31053
+ dispatch: dispatch,
31054
+ onChange: onChange
31055
+ }));
30890
31056
  });
30891
31057
  const MafsGraph = props => {
30892
31058
  const {
@@ -43065,7 +43231,7 @@ const init = function init(options) {
43065
43231
 
43066
43232
  // This file is processed by a Rollup plugin (replace) to inject the production
43067
43233
  const libName = "@khanacademy/perseus";
43068
- const libVersion = "22.3.0";
43234
+ const libVersion = "22.4.0";
43069
43235
  addLibraryVersionToPerseusDebug(libName, libVersion);
43070
43236
 
43071
43237
  const apiVersion = {