@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/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';
@@ -28664,15 +28664,10 @@ const ItemExtras = [
28664
28664
 
28665
28665
  const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
28666
28666
  const lockedFigureColors = {
28667
- blue: "#3D7586",
28668
28667
  green: "#447A53",
28669
- gray: "#5D5F66",
28670
28668
  grayH: "#3B3D45",
28671
- grayI: "#21242C",
28672
28669
  purple: "#594094",
28673
- purpleD: "#8351E8",
28674
28670
  pink: "#B25071",
28675
- orange: "#946700",
28676
28671
  red: "#D92916"
28677
28672
  };
28678
28673
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
@@ -28731,6 +28726,7 @@ function Arrowhead(props) {
28731
28726
  var _props$color;
28732
28727
  const [point] = useTransformVectorsToPixels(props.tip);
28733
28728
  return /*#__PURE__*/React.createElement("g", {
28729
+ className: "interactive-graph-arrowhead",
28734
28730
  transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
28735
28731
  }, /*#__PURE__*/React.createElement("g", {
28736
28732
  transform: "translate(-1.5)"
@@ -28745,49 +28741,7 @@ function Arrowhead(props) {
28745
28741
  })));
28746
28742
  }
28747
28743
 
28748
- function SVGLine(props) {
28749
- const {
28750
- start,
28751
- end,
28752
- style,
28753
- className,
28754
- testId
28755
- } = props;
28756
- return /*#__PURE__*/React.createElement("line", {
28757
- x1: start[0],
28758
- y1: start[1],
28759
- x2: end[0],
28760
- y2: end[1],
28761
- style: style,
28762
- className: className,
28763
- "data-testid": testId
28764
- });
28765
- }
28766
-
28767
- function Vector(props) {
28768
- const {
28769
- tail,
28770
- tip,
28771
- color,
28772
- style
28773
- } = props;
28774
- const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
28775
- const direction = vec.sub(tip, tail);
28776
- return /*#__PURE__*/React.createElement("g", {
28777
- style: {
28778
- stroke: color,
28779
- strokeWidth: 2
28780
- }
28781
- }, /*#__PURE__*/React.createElement(SVGLine, {
28782
- start: tailPx,
28783
- end: tipPx,
28784
- style: style
28785
- }), /*#__PURE__*/React.createElement(Arrowhead, {
28786
- tip: tip,
28787
- angle: angleDegrees(direction)
28788
- }));
28789
- }
28790
- function angleDegrees([x, y]) {
28744
+ function calculateAngleInDegrees([x, y]) {
28791
28745
  return Math.atan2(y, x) * 180 / Math.PI;
28792
28746
  }
28793
28747
 
@@ -28829,10 +28783,57 @@ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
28829
28783
  return [xMax, yAtXMax];
28830
28784
  }
28831
28785
  };
28832
- const getLines = points => points.map((point, i) => {
28833
- const next = points[(i + 1) % points.length];
28834
- return [point, next];
28835
- });
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
+ }
28836
28837
 
28837
28838
  const LockedLine = props => {
28838
28839
  const {
@@ -28848,27 +28849,38 @@ const LockedLine = props => {
28848
28849
  let line;
28849
28850
  if (kind === "ray") {
28850
28851
  // Rays extend to the end of the graph in one direction.
28851
- const endExtend = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28852
+ const extendedPoint = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28852
28853
  line = /*#__PURE__*/React.createElement(Vector, {
28853
28854
  tail: point1.coord,
28854
- tip: endExtend,
28855
+ tip: extendedPoint,
28855
28856
  color: lockedFigureColors[color$1],
28856
28857
  style: {
28857
- strokeDasharray: lineStyle === "dashed" ?
28858
- // TODO(lems-1930): Uncomment this line when the
28859
- // dashed style is updated in Mafs.
28860
- // ? "var(--mafs-line-stroke-dash-style)"
28861
- "4, 3" : undefined
28858
+ strokeDasharray: lineStyle === "dashed" ? "var(--mafs-line-stroke-dash-style)" : undefined
28862
28859
  }
28863
28860
  });
28864
28861
  } else {
28865
28862
  const LineType = kind === "segment" ? Line$3.Segment : Line$3.ThroughPoints;
28866
- 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, {
28867
28879
  point1: point1.coord,
28868
28880
  point2: point2.coord,
28869
28881
  color: lockedFigureColors[color$1],
28870
28882
  style: lineStyle
28871
- });
28883
+ }), endArrowHead);
28872
28884
  }
28873
28885
  return /*#__PURE__*/React.createElement("g", {
28874
28886
  className: kind === "ray" ? "locked-ray" : "locked-line"
@@ -28974,6 +28986,20 @@ function movePoint(index, destination) {
28974
28986
  destination
28975
28987
  };
28976
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
+ }
28977
29003
  const CHANGE_SNAP_STEP = "change-snap-step";
28978
29004
  function changeSnapStep(snapStep) {
28979
29005
  return {
@@ -29579,6 +29605,272 @@ const PolygonGraph = props => {
29579
29605
  })));
29580
29606
  };
29581
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
+
29582
29874
  function PointGraph(props) {
29583
29875
  const {
29584
29876
  dispatch
@@ -30380,6 +30672,10 @@ function interactiveGraphReducer(state, action) {
30380
30672
  return doMoveAll(state, action);
30381
30673
  case MOVE_POINT:
30382
30674
  return doMovePoint(state, action);
30675
+ case MOVE_CENTER:
30676
+ return doMoveCenter(state, action);
30677
+ case MOVE_RADIUS_POINT:
30678
+ return doMoveRadiusPoint(state, action);
30383
30679
  case CHANGE_SNAP_STEP:
30384
30680
  return doChangeSnapStep(state, action);
30385
30681
  case CHANGE_RANGE:
@@ -30515,6 +30811,65 @@ function doMovePoint(state, action) {
30515
30811
  throw new Error("The movePoint action is only for point and polygon graphs");
30516
30812
  }
30517
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
+ }
30518
30873
  function doChangeSnapStep(state, action) {
30519
30874
  if (
30520
30875
  // Deep equality check since these are arrays
@@ -30626,222 +30981,6 @@ function setAtIndex(args) {
30626
30981
  return copy;
30627
30982
  }
30628
30983
 
30629
- function initializeGraphState(params) {
30630
- const {
30631
- graph,
30632
- step,
30633
- snapStep,
30634
- range,
30635
- markings
30636
- } = params;
30637
- const shared = {
30638
- hasBeenInteractedWith: false,
30639
- range,
30640
- snapStep,
30641
- markings
30642
- };
30643
- switch (graph.type) {
30644
- case "segment":
30645
- return _extends({}, shared, {
30646
- type: "segment",
30647
- coords: getDefaultSegments({
30648
- graph,
30649
- step,
30650
- range
30651
- })
30652
- });
30653
- case "linear":
30654
- case "linear-system":
30655
- case "ray":
30656
- return _extends({}, shared, {
30657
- type: graph.type,
30658
- // Linear and ray graphs have a single tuple of points, while a
30659
- // linear system has two tuples of points.
30660
- coords: getLineCoords({
30661
- graph,
30662
- range,
30663
- step
30664
- })
30665
- });
30666
- case "polygon":
30667
- return _extends({}, shared, {
30668
- type: "polygon",
30669
- showAngles: Boolean(graph.showAngles),
30670
- showSides: Boolean(graph.showSides),
30671
- coords: getPolygonCoords({
30672
- graph,
30673
- range,
30674
- step
30675
- })
30676
- });
30677
- case "point":
30678
- return _extends({}, shared, {
30679
- type: graph.type,
30680
- coords: getDefaultPoints({
30681
- graph,
30682
- step,
30683
- range
30684
- })
30685
- });
30686
- case "angle":
30687
- case "circle":
30688
- case "sinusoid":
30689
- case "quadratic":
30690
- throw new Error("Mafs not yet implemented for graph type: " + graph.type);
30691
- default:
30692
- throw new UnreachableCaseError(graph);
30693
- }
30694
- }
30695
- const getDefaultPoints = ({
30696
- graph,
30697
- range,
30698
- step
30699
- }) => {
30700
- var _graph$coords;
30701
- const numPoints = graph.numPoints || 1;
30702
- let coords = (_graph$coords = graph.coords) == null ? void 0 : _graph$coords.slice();
30703
- if (coords) {
30704
- return coords;
30705
- }
30706
- switch (numPoints) {
30707
- case 1:
30708
- // Back in the day, one point's coords were in graph.coord
30709
- coords = [graph.coord || [0, 0]];
30710
- break;
30711
- case 2:
30712
- coords = [[-5, 0], [5, 0]];
30713
- break;
30714
- case 3:
30715
- coords = [[-5, 0], [0, 0], [5, 0]];
30716
- break;
30717
- case 4:
30718
- coords = [[-6, 0], [-2, 0], [2, 0], [6, 0]];
30719
- break;
30720
- case 5:
30721
- coords = [[-6, 0], [-3, 0], [0, 0], [3, 0], [6, 0]];
30722
- break;
30723
- case 6:
30724
- coords = [[-5, 0], [-3, 0], [-1, 0], [1, 0], [3, 0], [5, 0]];
30725
- break;
30726
- default:
30727
- coords = [];
30728
- break;
30729
- }
30730
- // Transform coords from their -10 to 10 space to 0 to 1
30731
- // because of the old graph.coord, and also it's easier.
30732
- const newCoords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
30733
- return normalizePoints(range, step, newCoords);
30734
- };
30735
-
30736
- // TS v4 doesn't narrow return types, while v5 does.
30737
- // Instead of updating to v5, using generic type to relate input and output types.
30738
- function getGradableGraph(state, initialGraph) {
30739
- if (!state.hasBeenInteractedWith) {
30740
- return _extends({}, initialGraph);
30741
- }
30742
- if (initialGraph.type === "linear-system" && state.type === "linear-system") {
30743
- return _extends({}, initialGraph, {
30744
- coords: state.coords
30745
- });
30746
- }
30747
- if (state.type === "segment" && initialGraph.type === "segment") {
30748
- return _extends({}, initialGraph, {
30749
- coords: state.coords
30750
- });
30751
- }
30752
- if (state.type === "linear" && initialGraph.type === "linear") {
30753
- return _extends({}, initialGraph, {
30754
- coords: state.coords[0]
30755
- });
30756
- }
30757
- if (state.type === "ray" && initialGraph.type === "ray") {
30758
- return _extends({}, initialGraph, {
30759
- coords: state.coords[0]
30760
- });
30761
- }
30762
- if (state.type === "polygon" && initialGraph.type === "polygon") {
30763
- return _extends({}, initialGraph, {
30764
- coords: state.coords
30765
- });
30766
- }
30767
- if (state.type === "point" && initialGraph.type === "point") {
30768
- return _extends({}, initialGraph, {
30769
- coords: state.coords
30770
- });
30771
- }
30772
- throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
30773
- }
30774
- const getDefaultSegments = ({
30775
- graph,
30776
- range,
30777
- step
30778
- }) => {
30779
- const ys = n => {
30780
- switch (n) {
30781
- case 2:
30782
- return [5, -5];
30783
- case 3:
30784
- return [5, 0, -5];
30785
- case 4:
30786
- return [6, 2, -2, -6];
30787
- case 5:
30788
- return [6, 3, 0, -3, -6];
30789
- case 6:
30790
- return [5, 3, 1, -1, -3, -5];
30791
- default:
30792
- return [5];
30793
- }
30794
- };
30795
- const defaultRange = [[-10, 10], [-10, 10]];
30796
- return ys(graph.numSegments).map(y => {
30797
- let endpoints = [[-5, y], [5, y]];
30798
- endpoints = normalizeCoords(endpoints, defaultRange);
30799
- endpoints = normalizePoints(range, step, endpoints);
30800
- return endpoints;
30801
- });
30802
- };
30803
- const defaultLinearCoords = [[[0.25, 0.75], [0.75, 0.75]], [[0.25, 0.25], [0.75, 0.25]]];
30804
- const getLineCoords = ({
30805
- graph,
30806
- range,
30807
- step
30808
- }) => {
30809
- var _graph$coords2;
30810
- return (
30811
- // Return two lines for a linear system, one for a ray or linear
30812
- ((_graph$coords2 = graph.coords) != null ? _graph$coords2 : graph.type === "linear-system") ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
30813
- );
30814
- };
30815
- const getPolygonCoords = ({
30816
- graph,
30817
- range,
30818
- step
30819
- }) => {
30820
- var _graph$coords3;
30821
- let coords = (_graph$coords3 = graph.coords) == null ? void 0 : _graph$coords3.slice();
30822
- if (coords) {
30823
- return coords;
30824
- }
30825
- const n = graph.numSides || 3;
30826
- if (n === "unlimited") {
30827
- coords = [];
30828
- } else {
30829
- const angle = 2 * Math.PI / n;
30830
- const offset = (1 / n - 1 / 2) * Math.PI;
30831
-
30832
- // TODO(alex): Generalize this to more than just triangles so that
30833
- // all polygons have whole number side lengths if snapping to sides
30834
- const radius = graph.snapTo === "sides" ? Math.sqrt(3) / 3 * 7 : 4;
30835
-
30836
- // Generate coords of a regular polygon with n sides
30837
- coords = [...Array(n).keys()].map(i => [radius * Math.cos(i * angle + offset), radius * Math.sin(i * angle + offset)]);
30838
- }
30839
- coords = normalizeCoords(coords, [[-10, 10], [-10, 10]]);
30840
- const snapToGrid = !["angles", "sides"].includes(graph.snapTo || "");
30841
- coords = normalizePoints(range, step, coords, /* noSnap */!snapToGrid);
30842
- return coords;
30843
- };
30844
-
30845
30984
  const renderGraph = props => {
30846
30985
  const {
30847
30986
  state,
@@ -30878,20 +31017,42 @@ const renderGraph = props => {
30878
31017
  dispatch: dispatch
30879
31018
  });
30880
31019
  case "circle":
30881
- throw new Error("the circle graph type is not yet implemented");
31020
+ return /*#__PURE__*/React.createElement(CircleGraph, {
31021
+ graphState: state,
31022
+ dispatch: dispatch
31023
+ });
30882
31024
  default:
30883
31025
  return new UnreachableCaseError(type);
30884
31026
  }
30885
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
+ }
30886
31043
  const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
30887
31044
  const [state, dispatch] = React.useReducer(interactiveGraphReducer, props, initializeGraphState);
30888
31045
  useImperativeHandle(ref, () => ({
30889
31046
  getUserInput: () => getGradableGraph(state, props.graph)
30890
31047
  }));
30891
- return /*#__PURE__*/React.createElement(MafsGraph, _extends({
31048
+ function onChange(next) {
31049
+ props.onChange(mafsStateToInteractiveGraph(next));
31050
+ }
31051
+ return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
30892
31052
  state: state,
30893
- dispatch: dispatch
30894
- }, props));
31053
+ dispatch: dispatch,
31054
+ onChange: onChange
31055
+ }));
30895
31056
  });
30896
31057
  const MafsGraph = props => {
30897
31058
  const {
@@ -30933,6 +31094,7 @@ const MafsGraph = props => {
30933
31094
  labels
30934
31095
  }
30935
31096
  }, /*#__PURE__*/React.createElement(View, {
31097
+ className: "mafs-graph",
30936
31098
  style: {
30937
31099
  width,
30938
31100
  height,
@@ -43069,7 +43231,7 @@ const init = function init(options) {
43069
43231
 
43070
43232
  // This file is processed by a Rollup plugin (replace) to inject the production
43071
43233
  const libName = "@khanacademy/perseus";
43072
- const libVersion = "22.2.0";
43234
+ const libVersion = "22.4.0";
43073
43235
  addLibraryVersionToPerseusDebug(libName, libVersion);
43074
43236
 
43075
43237
  const apiVersion = {