@khanacademy/perseus 22.1.0 → 22.2.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.css CHANGED
@@ -4172,6 +4172,7 @@ html body .mq-editable-field .mq-selection .mq-nthroot-container * {
4172
4172
 
4173
4173
  --mafs-origin-color: var(--mafs-fg);
4174
4174
  --mafs-line-color: #555;
4175
+ --mafs-line-stroke-dash-style: 4, 3;
4175
4176
  --grid-line-subdivision-color: #222;
4176
4177
 
4177
4178
  --mafs-red: #f11d0e;
package/dist/es/index.js CHANGED
@@ -28677,6 +28677,163 @@ const lockedFigureColors = {
28677
28677
  };
28678
28678
  const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
28679
28679
 
28680
+ function pathBuilder() {
28681
+ return new PathBuilder();
28682
+ }
28683
+ class PathBuilder {
28684
+ constructor() {
28685
+ this.path = [];
28686
+ this.scaleFactor = 1;
28687
+ }
28688
+ build() {
28689
+ return this.path.map(scaleCommandBy(this.scaleFactor)).map(commandToString).join("");
28690
+ }
28691
+
28692
+ // add a move (M) command to the path
28693
+ move(x, y) {
28694
+ this.path.push({
28695
+ action: "M",
28696
+ coords: [x, y]
28697
+ });
28698
+ return this;
28699
+ }
28700
+
28701
+ // add a curve (C) command to the path
28702
+ curve(control1X, control1Y, control2X, control2Y, endX, endY) {
28703
+ this.path.push({
28704
+ action: "C",
28705
+ coords: [control1X, control1Y, control2X, control2Y, endX, endY]
28706
+ });
28707
+ return this;
28708
+ }
28709
+
28710
+ // Scale all coordinates of the path by a factor
28711
+ scale(factor) {
28712
+ this.scaleFactor *= factor;
28713
+ return this;
28714
+ }
28715
+ }
28716
+ function commandToString(command) {
28717
+ return `${command.action}${command.coords.join(" ")}`;
28718
+ }
28719
+ function scaleCommandBy(scaleFactor) {
28720
+ return command => _extends({}, command, {
28721
+ coords: command.coords.map(c => c * scaleFactor)
28722
+ });
28723
+ }
28724
+
28725
+ // We use the pathBuilder here to scale up the SVG path coordinates used
28726
+ // elsewhere for arrowheads. We scale in the path itself instead of using a CSS
28727
+ // transform because a transform would also scale up the stroke weight, and we
28728
+ // don't want that.
28729
+ const arrowPath = pathBuilder().move(-3, 4).curve(-2.75, 2.5, 0, 0.25, 0.75, 0).curve(0, -0.25, -2.75, -2.5, -3, -4).scale(1.4).build();
28730
+ function Arrowhead(props) {
28731
+ var _props$color;
28732
+ const [point] = useTransformVectorsToPixels(props.tip);
28733
+ return /*#__PURE__*/React.createElement("g", {
28734
+ transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
28735
+ }, /*#__PURE__*/React.createElement("g", {
28736
+ transform: "translate(-1.5)"
28737
+ }, /*#__PURE__*/React.createElement("path", {
28738
+ d: arrowPath,
28739
+ fill: "none",
28740
+ style: {
28741
+ stroke: (_props$color = props.color) != null ? _props$color : "inherit"
28742
+ },
28743
+ strokeLinejoin: "round",
28744
+ strokeLinecap: "round"
28745
+ })));
28746
+ }
28747
+
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]) {
28791
+ return Math.atan2(y, x) * 180 / Math.PI;
28792
+ }
28793
+
28794
+ /**
28795
+ * Given a ray and a rectangular box, find the point where the ray intersects
28796
+ * the edge of the box. Assumes the `initialPoint` is inside the box.
28797
+ * @param initialPoint - The starting point of the ray.
28798
+ * @param throughPoint - A point that the ray passes through. Must be different from initialPoint.
28799
+ * @param box - The box with which to intersect the ray, in the form [[xMin, xMax], [yMin, yMax]]
28800
+ */
28801
+ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
28802
+ const [[xMin, xMax], [yMin, yMax]] = box;
28803
+ const [aX, aY] = throughPoint;
28804
+ const [bX, bY] = initialPoint;
28805
+ const yDiff = bY - aY;
28806
+ const xDiff = bX - aX;
28807
+ const slope = yDiff / xDiff;
28808
+ const yAtXMin = slope * (xMin - aX) + aY;
28809
+ const yAtXMax = slope * (xMax - aX) + aY;
28810
+ const xAtYMin = (yMin - aY) / slope + aX;
28811
+ const xAtYMax = (yMax - aY) / slope + aX;
28812
+
28813
+ // clock analogy to describe quadrants
28814
+ switch (true) {
28815
+ // 12 o'clock to 2:59
28816
+ case yDiff > 0 && xDiff >= 0:
28817
+ return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
28818
+ // 3 o'clock to 5:59
28819
+ case yDiff <= 0 && xDiff > 0:
28820
+ // xAtYMin evaluates to -Infinity here, so we use absolute value
28821
+ return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
28822
+ // 9 o'clock to 11:59
28823
+ case yDiff >= 0 && xDiff < 0:
28824
+ return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
28825
+ // 6 o'clock to 8:59
28826
+ case yDiff < 0 && xDiff <= 0:
28827
+ return xAtYMin < xMin ? [xMin, yAtXMin] : [xAtYMin, yMin];
28828
+ default:
28829
+ return [xMax, yAtXMax];
28830
+ }
28831
+ };
28832
+ const getLines = points => points.map((point, i) => {
28833
+ const next = points[(i + 1) % points.length];
28834
+ return [point, next];
28835
+ });
28836
+
28680
28837
  const LockedLine = props => {
28681
28838
  const {
28682
28839
  color: color$1,
@@ -28684,11 +28841,27 @@ const LockedLine = props => {
28684
28841
  kind,
28685
28842
  points,
28686
28843
  showStartPoint,
28687
- showEndPoint
28844
+ showEndPoint,
28845
+ range
28688
28846
  } = props;
28689
28847
  const [point1, point2] = points;
28690
28848
  let line;
28691
- if (kind === "line" || kind === "segment") {
28849
+ if (kind === "ray") {
28850
+ // Rays extend to the end of the graph in one direction.
28851
+ const endExtend = getIntersectionOfRayWithBox(point2.coord, point1.coord, range);
28852
+ line = /*#__PURE__*/React.createElement(Vector, {
28853
+ tail: point1.coord,
28854
+ tip: endExtend,
28855
+ color: lockedFigureColors[color$1],
28856
+ 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
28862
+ }
28863
+ });
28864
+ } else {
28692
28865
  const LineType = kind === "segment" ? Line$3.Segment : Line$3.ThroughPoints;
28693
28866
  line = /*#__PURE__*/React.createElement(LineType, {
28694
28867
  point1: point1.coord,
@@ -28696,12 +28869,9 @@ const LockedLine = props => {
28696
28869
  color: lockedFigureColors[color$1],
28697
28870
  style: lineStyle
28698
28871
  });
28699
- } else {
28700
- // TODO(LEMS-1928): Implement rays
28701
- return null;
28702
28872
  }
28703
28873
  return /*#__PURE__*/React.createElement("g", {
28704
- className: "locked-line"
28874
+ className: kind === "ray" ? "locked-ray" : "locked-line"
28705
28875
  }, line, showStartPoint && /*#__PURE__*/React.createElement(Point$2, {
28706
28876
  x: point1.coord[0],
28707
28877
  y: point1.coord[1],
@@ -28750,14 +28920,16 @@ const GraphLockedLayer = props => {
28750
28920
  lockedFigures
28751
28921
  } = props;
28752
28922
  return /*#__PURE__*/React.createElement(React.Fragment, null, lockedFigures.map((figure, index) => {
28753
- let Figure;
28754
28923
  switch (figure.type) {
28755
28924
  case "point":
28756
- Figure = LockedPoint;
28757
- break;
28925
+ return /*#__PURE__*/React.createElement(LockedPoint, _extends({
28926
+ key: `point-${index}`
28927
+ }, figure));
28758
28928
  case "line":
28759
- Figure = LockedLine;
28760
- break;
28929
+ return /*#__PURE__*/React.createElement(LockedLine, _extends({
28930
+ key: `line-${index}`,
28931
+ range: props.range
28932
+ }, figure));
28761
28933
  default:
28762
28934
  /**
28763
28935
  * Devlopment-time future-proofing: This should
@@ -28767,9 +28939,6 @@ const GraphLockedLayer = props => {
28767
28939
  */
28768
28940
  throw new UnreachableCaseError(figure);
28769
28941
  }
28770
- return /*#__PURE__*/React.createElement(Figure, _extends({
28771
- key: `${figure.type}-${index}`
28772
- }, figure));
28773
28942
  }));
28774
28943
  };
28775
28944
 
@@ -28851,161 +29020,6 @@ function snap(snapStep, point) {
28851
29020
  return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
28852
29021
  }
28853
29022
 
28854
- /**
28855
- * Given a ray and a rectangular box, find the point where the ray intersects
28856
- * the edge of the box. Assumes the `initialPoint` is inside the box.
28857
- * @param initialPoint - The starting point of the ray.
28858
- * @param throughPoint - A point that the ray passes through. Must be different from initialPoint.
28859
- * @param box - The box with which to intersect the ray, in the form [[xMin, xMax], [yMin, yMax]]
28860
- */
28861
- const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
28862
- const [[xMin, xMax], [yMin, yMax]] = box;
28863
- const [aX, aY] = throughPoint;
28864
- const [bX, bY] = initialPoint;
28865
- const yDiff = bY - aY;
28866
- const xDiff = bX - aX;
28867
- const slope = yDiff / xDiff;
28868
- const yAtXMin = slope * (xMin - aX) + aY;
28869
- const yAtXMax = slope * (xMax - aX) + aY;
28870
- const xAtYMin = (yMin - aY) / slope + aX;
28871
- const xAtYMax = (yMax - aY) / slope + aX;
28872
-
28873
- // clock analogy to describe quadrants
28874
- switch (true) {
28875
- // 12 o'clock to 2:59
28876
- case yDiff > 0 && xDiff >= 0:
28877
- return xAtYMax > xMax ? [xMax, yAtXMax] : [xAtYMax, yMax];
28878
- // 3 o'clock to 5:59
28879
- case yDiff <= 0 && xDiff > 0:
28880
- // xAtYMin evaluates to -Infinity here, so we use absolute value
28881
- return Math.abs(xAtYMin) > xMax ? [xMax, yAtXMax] : [xAtYMin, yMin];
28882
- // 9 o'clock to 11:59
28883
- case yDiff >= 0 && xDiff < 0:
28884
- return xAtYMax < xMin ? [xMin, yAtXMin] : [xAtYMax, yMax];
28885
- // 6 o'clock to 8:59
28886
- case yDiff < 0 && xDiff <= 0:
28887
- return xAtYMin < xMin ? [xMin, yAtXMin] : [xAtYMin, yMin];
28888
- default:
28889
- return [xMax, yAtXMax];
28890
- }
28891
- };
28892
- const getLines = points => points.map((point, i) => {
28893
- const next = points[(i + 1) % points.length];
28894
- return [point, next];
28895
- });
28896
-
28897
- function SVGLine(props) {
28898
- const {
28899
- start,
28900
- end,
28901
- style,
28902
- className,
28903
- testId
28904
- } = props;
28905
- return /*#__PURE__*/React.createElement("line", {
28906
- x1: start[0],
28907
- y1: start[1],
28908
- x2: end[0],
28909
- y2: end[1],
28910
- style: style,
28911
- className: `${className}`,
28912
- "data-testid": testId
28913
- });
28914
- }
28915
-
28916
- function pathBuilder() {
28917
- return new PathBuilder();
28918
- }
28919
- class PathBuilder {
28920
- constructor() {
28921
- this.path = [];
28922
- this.scaleFactor = 1;
28923
- }
28924
- build() {
28925
- return this.path.map(scaleCommandBy(this.scaleFactor)).map(commandToString).join("");
28926
- }
28927
-
28928
- // add a move (M) command to the path
28929
- move(x, y) {
28930
- this.path.push({
28931
- action: "M",
28932
- coords: [x, y]
28933
- });
28934
- return this;
28935
- }
28936
-
28937
- // add a curve (C) command to the path
28938
- curve(control1X, control1Y, control2X, control2Y, endX, endY) {
28939
- this.path.push({
28940
- action: "C",
28941
- coords: [control1X, control1Y, control2X, control2Y, endX, endY]
28942
- });
28943
- return this;
28944
- }
28945
-
28946
- // Scale all coordinates of the path by a factor
28947
- scale(factor) {
28948
- this.scaleFactor *= factor;
28949
- return this;
28950
- }
28951
- }
28952
- function commandToString(command) {
28953
- return `${command.action}${command.coords.join(" ")}`;
28954
- }
28955
- function scaleCommandBy(scaleFactor) {
28956
- return command => _extends({}, command, {
28957
- coords: command.coords.map(c => c * scaleFactor)
28958
- });
28959
- }
28960
-
28961
- // We use the pathBuilder here to scale up the SVG path coordinates used
28962
- // elsewhere for arrowheads. We scale in the path itself instead of using a CSS
28963
- // transform because a transform would also scale up the stroke weight, and we
28964
- // don't want that.
28965
- const arrowPath = pathBuilder().move(-3, 4).curve(-2.75, 2.5, 0, 0.25, 0.75, 0).curve(0, -0.25, -2.75, -2.5, -3, -4).scale(1.4).build();
28966
- function Arrowhead(props) {
28967
- var _props$color;
28968
- const [point] = useTransformVectorsToPixels(props.tip);
28969
- return /*#__PURE__*/React.createElement("g", {
28970
- transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
28971
- }, /*#__PURE__*/React.createElement("g", {
28972
- transform: "translate(-1.5)"
28973
- }, /*#__PURE__*/React.createElement("path", {
28974
- d: arrowPath,
28975
- fill: "none",
28976
- style: {
28977
- stroke: (_props$color = props.color) != null ? _props$color : "inherit"
28978
- },
28979
- strokeLinejoin: "round",
28980
- strokeLinecap: "round"
28981
- })));
28982
- }
28983
-
28984
- function Vector(props) {
28985
- const {
28986
- tail,
28987
- tip,
28988
- color
28989
- } = props;
28990
- const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
28991
- const direction = vec.sub(tip, tail);
28992
- return /*#__PURE__*/React.createElement("g", {
28993
- style: {
28994
- stroke: color,
28995
- strokeWidth: 2
28996
- }
28997
- }, /*#__PURE__*/React.createElement(SVGLine, {
28998
- start: tailPx,
28999
- end: tipPx
29000
- }), /*#__PURE__*/React.createElement(Arrowhead, {
29001
- tip: tip,
29002
- angle: angleDegrees(direction)
29003
- }));
29004
- }
29005
- function angleDegrees([x, y]) {
29006
- return Math.atan2(y, x) * 180 / Math.PI;
29007
- }
29008
-
29009
29023
  const defaultStroke = "var(--movable-line-stroke-color)";
29010
29024
  const MovableLine = props => {
29011
29025
  const {
@@ -30953,7 +30967,8 @@ const MafsGraph = props => {
30953
30967
  containerSizeClass: props.containerSizeClass,
30954
30968
  markings: props.markings
30955
30969
  }), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
30956
- lockedFigures: props.lockedFigures
30970
+ lockedFigures: props.lockedFigures,
30971
+ range: props.range
30957
30972
  }), renderGraph({
30958
30973
  state,
30959
30974
  dispatch
@@ -43054,7 +43069,7 @@ const init = function init(options) {
43054
43069
 
43055
43070
  // This file is processed by a Rollup plugin (replace) to inject the production
43056
43071
  const libName = "@khanacademy/perseus";
43057
- const libVersion = "22.1.0";
43072
+ const libVersion = "22.2.0";
43058
43073
  addLibraryVersionToPerseusDebug(libName, libVersion);
43059
43074
 
43060
43075
  const apiVersion = {