@khanacademy/perseus 22.5.0 → 22.6.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
@@ -28758,6 +28758,8 @@ class Graph extends React__namespace.Component {
28758
28758
 
28759
28759
  const defaultGraphConfig = {
28760
28760
  range: [[0, 1], [0, 1]],
28761
+ tickStep: [1, 1],
28762
+ gridStep: [1, 1],
28761
28763
  snapStep: [1, 1],
28762
28764
  markings: "none",
28763
28765
  showTooltips: false,
@@ -28772,6 +28774,7 @@ function useGraphConfig() {
28772
28774
  }
28773
28775
 
28774
28776
  const matrixBuilder = mafs.vec.matrixBuilder;
28777
+
28775
28778
  // When converting vectors, we don't need to translate because a vector
28776
28779
  // is like a pair of x and y distances, which are not bound to specific graph locations.
28777
28780
  function vectorsToPixels(vectors, graphState) {
@@ -29025,7 +29028,7 @@ function Arrowhead(props) {
29025
29028
  const [point] = useTransformVectorsToPixels(props.tip);
29026
29029
  return /*#__PURE__*/React__namespace.createElement("g", {
29027
29030
  className: "interactive-graph-arrowhead",
29028
- transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
29031
+ transform: `translate(${point[0]} ${point[1]}) rotate(${props.angle})`
29029
29032
  }, /*#__PURE__*/React__namespace.createElement("g", {
29030
29033
  transform: "translate(-1.5)"
29031
29034
  }, /*#__PURE__*/React__namespace.createElement("path", {
@@ -29035,7 +29038,8 @@ function Arrowhead(props) {
29035
29038
  stroke: props.color ?? "inherit"
29036
29039
  },
29037
29040
  strokeLinejoin: "round",
29038
- strokeLinecap: "round"
29041
+ strokeLinecap: "round",
29042
+ strokeWidth: "2px"
29039
29043
  })));
29040
29044
  }
29041
29045
 
@@ -29072,10 +29076,7 @@ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
29072
29076
  case isBetween(xAtYExtreme, xMin, xMax):
29073
29077
  return [xAtYExtreme, yExtreme];
29074
29078
  default:
29075
- // This default case is only reachable if the input is invalid
29076
- // (initialPoint is outside the graph bounds, or initialPoint and
29077
- // throughPoint are the same).
29078
- return [0, 0];
29079
+ return [xExtreme, yExtreme];
29079
29080
  }
29080
29081
  };
29081
29082
  const getLines = points => {
@@ -29115,7 +29116,7 @@ function Vector(props) {
29115
29116
  style
29116
29117
  } = props;
29117
29118
  const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
29118
- const direction = mafs.vec.sub(tip, tail);
29119
+ const direction = mafs.vec.sub(tipPx, tailPx);
29119
29120
  const angle = calculateAngleInDegrees(direction);
29120
29121
  return /*#__PURE__*/React__namespace.createElement("g", {
29121
29122
  style: {
@@ -29159,7 +29160,9 @@ const LockedLine = props => {
29159
29160
  } else {
29160
29161
  const LineType = kind === "segment" ? mafs.Line.Segment : mafs.Line.ThroughPoints;
29161
29162
  let arrowTip = kind === "segment" ? point2.coord : getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
29162
- const direction = mafs.vec.sub(point2.coord, point1.coord);
29163
+ // eslint-disable-next-line react-hooks/rules-of-hooks
29164
+ const [tailPx, tipPx] = useTransformVectorsToPixels(point2.coord, point1.coord);
29165
+ const direction = mafs.vec.sub(tailPx, tipPx);
29163
29166
  let angle = calculateAngleInDegrees(direction);
29164
29167
  const startArrowHead = kind !== "segment" && /*#__PURE__*/React__namespace.createElement(Arrowhead, {
29165
29168
  angle: angle,
@@ -29984,14 +29987,12 @@ function initializeGraphState(params) {
29984
29987
  graph,
29985
29988
  step,
29986
29989
  snapStep,
29987
- range,
29988
- markings
29990
+ range
29989
29991
  } = params;
29990
29992
  const shared = {
29991
29993
  hasBeenInteractedWith: false,
29992
29994
  range,
29993
- snapStep,
29994
- markings
29995
+ snapStep
29995
29996
  };
29996
29997
  switch (graph.type) {
29997
29998
  case "segment":
@@ -30186,10 +30187,16 @@ const getLineCoords = _ref3 => {
30186
30187
  range,
30187
30188
  step
30188
30189
  } = _ref3;
30189
- return (
30190
- // Return two lines for a linear system, one for a ray or linear
30191
- graph.coords ?? graph.type === "linear-system" ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
30192
- );
30190
+ // Return two lines for a linear system, one for a ray or linear
30191
+ switch (graph.type) {
30192
+ case "linear-system":
30193
+ return defaultLinearCoords.map(points => normalizePoints(range, step, points));
30194
+ case "linear":
30195
+ case "ray":
30196
+ return [normalizePoints(range, step, defaultLinearCoords[0])];
30197
+ default:
30198
+ throw new wonderStuffCore.UnreachableCaseError(graph);
30199
+ }
30193
30200
  };
30194
30201
  const getPolygonCoords = _ref4 => {
30195
30202
  let {
@@ -30283,11 +30290,12 @@ function MovableCircle(props) {
30283
30290
  cy: centerPx[1],
30284
30291
  rx: radiiPx[0] + 3,
30285
30292
  ry: radiiPx[1] + 3
30286
- }), /*#__PURE__*/React__namespace.createElement(mafs.Circle, {
30287
- center: center,
30288
- radius: radius,
30289
- fillOpacity: 0,
30290
- color: wonderBlocksTokens.color.blue
30293
+ }), /*#__PURE__*/React__namespace.createElement("ellipse", {
30294
+ className: "circle",
30295
+ cx: centerPx[0],
30296
+ cy: centerPx[1],
30297
+ rx: radiiPx[0],
30298
+ ry: radiiPx[1]
30291
30299
  }), /*#__PURE__*/React__namespace.createElement(DragHandle, {
30292
30300
  center: center
30293
30301
  }));
@@ -30328,6 +30336,260 @@ function crossProduct(as, bs) {
30328
30336
  return result;
30329
30337
  }
30330
30338
 
30339
+ // Universal padding for the axis ticks to ensure that the labels
30340
+ // have enough space to render without overlapping the tick lines
30341
+ const tickPadding = 5;
30342
+ const AxisTickLabels = () => {
30343
+ const graphConfig = useGraphConfig();
30344
+ const {
30345
+ tickStep,
30346
+ range
30347
+ } = graphConfig;
30348
+ const [xMin, xMax] = range[0];
30349
+ const [yMin, yMax] = range[1];
30350
+ const yTickStep = tickStep[1];
30351
+ const xTickStep = tickStep[0];
30352
+ const yGridTicks = generateTickLocations$1(yTickStep, yMin, yMax);
30353
+ const xGridTicks = generateTickLocations$1(xTickStep, xMin, xMax);
30354
+ return /*#__PURE__*/React__namespace.createElement("div", {
30355
+ className: "axis-tick-labels"
30356
+ }, /*#__PURE__*/React__namespace.createElement(YGridAxis, {
30357
+ axisTicks: yGridTicks,
30358
+ graphConfig: graphConfig
30359
+ }), /*#__PURE__*/React__namespace.createElement(XGridAxis, {
30360
+ axisTicks: xGridTicks,
30361
+ graphConfig: graphConfig
30362
+ }));
30363
+ };
30364
+
30365
+ // Generate the necessary styles and ticks for the y-axis tick labels container
30366
+ const YGridAxis = props => {
30367
+ const {
30368
+ axisTicks,
30369
+ graphConfig
30370
+ } = props;
30371
+ const {
30372
+ width,
30373
+ height
30374
+ } = graphConfig;
30375
+
30376
+ // First get the label height by calculating the difference between the first two y-axis labels
30377
+ const yTopPoint = pointToPixel([0, axisTicks[0]], graphConfig);
30378
+ const ySecondPoint = pointToPixel([0, axisTicks[1]], graphConfig);
30379
+ const yLabelHeight = ySecondPoint[1] - yTopPoint[1];
30380
+
30381
+ // When rendering purely positive y-axis labels, we want to adjust the left position
30382
+ // from the lowest calculated tick, as yMin may be negative
30383
+ const yLowestTick = axisTicks[axisTicks.length - 1];
30384
+ const yLeftAdjustment = Math.min(Math.max(yTopPoint[0] - tickPadding, 0), width);
30385
+
30386
+ // We only want to render the 0 label on the y-axis if it is left of the grid
30387
+ const yAxisIsLeftOfGrid = yLeftAdjustment === 0;
30388
+
30389
+ // If the y-axis is right of the graph's viewbox/range,
30390
+ // we want to adjust both the positioning and text alignment
30391
+ const yAxisRightOfGrid = yLeftAdjustment === width;
30392
+
30393
+ // Determine the default spacing for the labels based on whether the y-axis is right of the grid
30394
+ const defaultSpacing = yAxisRightOfGrid ? 0.5 : 0;
30395
+
30396
+ // We also want to adjust the spacing if there are no negative y-axis labels as the extra space is not needed
30397
+ const marginSpacing = yLowestTick >= 0 ? defaultSpacing * 2 : defaultSpacing;
30398
+
30399
+ // Determine the text alignment based on whether the y-axis is right of the grid
30400
+ // We need to cast this as a const as it is used in the inline styles
30401
+ const textAlign = yAxisRightOfGrid ? "left" : "right";
30402
+ const yAxisStyles = {
30403
+ left: `calc(${yLeftAdjustment}px - ${marginSpacing}em)`,
30404
+ top: `calc(${yTopPoint[1]}px - 0.5em)`,
30405
+ height,
30406
+ textAlign,
30407
+ "--y-axis-label-height": yLabelHeight + "px"
30408
+ };
30409
+ const classNames = yAxisRightOfGrid ? "y-axis-tick-labels y-axis-right-of-grid" : "y-axis-tick-labels ";
30410
+ return /*#__PURE__*/React__namespace.createElement("div", {
30411
+ className: classNames,
30412
+ "data-testid": "y-axis-tick-labels",
30413
+ style: yAxisStyles
30414
+ }, axisTicks.map(label => {
30415
+ return /*#__PURE__*/React__namespace.createElement(AxisTickLabel, {
30416
+ label: label,
30417
+ axis: "y",
30418
+ key: `y-grid-tick-${label}`,
30419
+ graphConfig: graphConfig,
30420
+ axisOutOfBounds: yAxisIsLeftOfGrid
30421
+ });
30422
+ }));
30423
+ };
30424
+
30425
+ // Generate the necessary styles and ticks for the x-axis tick labels container
30426
+ const XGridAxis = props => {
30427
+ const {
30428
+ axisTicks,
30429
+ graphConfig
30430
+ } = props;
30431
+ const {
30432
+ width,
30433
+ height
30434
+ } = graphConfig;
30435
+
30436
+ // Determine label width by calculating the difference between the first two x-axis labels
30437
+ const xFirstPoint = pointToPixel([axisTicks[0], 0], graphConfig);
30438
+ const xSecondPoint = pointToPixel([axisTicks[1], 0], graphConfig);
30439
+ const xLabelWidth = Math.abs(xSecondPoint[0] - xFirstPoint[0]);
30440
+
30441
+ // Determine the top position of the x-axis labels based on
30442
+ // whether the x-axis is above, within, or below the graph
30443
+ const xAxisAboveGraph = xFirstPoint[1] < 0;
30444
+ const xTopAdjustment = xAxisAboveGraph ? 0 : Math.min(xFirstPoint[1] + tickPadding, height);
30445
+
30446
+ // Determine whether the x-axis is outside of the graph's viewbox/range
30447
+ // This is used to conditionally render the 0 label on the x-axis
30448
+ const xAxisOutOfBounds = xTopAdjustment === height || xAxisAboveGraph;
30449
+
30450
+ // Determine the available space to the right of the x-axis labels
30451
+ const availableSpaceRight = width - xFirstPoint[0];
30452
+
30453
+ // Calculate the total combined width of ALL the x-axis labels
30454
+ const axisTicksWidth = axisTicks.length * xLabelWidth;
30455
+
30456
+ // Determine if we need to increase the width of the axis past the graph width
30457
+ const calculatedWidth = axisTicksWidth > width ? axisTicksWidth : width;
30458
+
30459
+ // Determine the right adjustment for the x-axis labels based on the available space
30460
+ // We're adjusting the right as the flex-direction is row-reverse
30461
+ const xRightAdjustment = availableSpaceRight - xLabelWidth / 2;
30462
+ const xAxisStyles = {
30463
+ top: xTopAdjustment,
30464
+ right: xRightAdjustment,
30465
+ width: calculatedWidth,
30466
+ "--x-axis-label-width": xLabelWidth + "px"
30467
+ };
30468
+ return /*#__PURE__*/React__namespace.createElement("div", {
30469
+ className: `x-axis-tick-labels ${xAxisAboveGraph && "x-axis-top-of-grid"}`,
30470
+ "data-testid": "x-axis-tick-labels",
30471
+ style: xAxisStyles
30472
+ }, axisTicks.map(label => {
30473
+ return /*#__PURE__*/React__namespace.createElement(AxisTickLabel, {
30474
+ axis: "x",
30475
+ label: label,
30476
+ key: `x-grid-tick-${label}`,
30477
+ graphConfig: graphConfig,
30478
+ axisOutOfBounds: xAxisOutOfBounds
30479
+ });
30480
+ }));
30481
+ };
30482
+
30483
+ // Generate the individual tick label elements
30484
+ const AxisTickLabel = _ref => {
30485
+ let {
30486
+ label,
30487
+ graphConfig,
30488
+ axisOutOfBounds,
30489
+ axis
30490
+ } = _ref;
30491
+ const {
30492
+ gridStep,
30493
+ tickStep
30494
+ } = graphConfig;
30495
+ // Determine the point on the axis based on the axis type
30496
+ const pointOnAxis = axis === "x" ? [label, 0] : [0, label];
30497
+ const pixelPoint = pointToPixel(pointOnAxis, graphConfig);
30498
+
30499
+ // Remove the negative sign from the label string as it is handled in the data-content attribute
30500
+ const labelString = Math.abs(label).toString();
30501
+
30502
+ // Determine the relevant edge of the graph based on the axis type
30503
+ const graphEdge = axis === "x" ? graphConfig.width : graphConfig.height;
30504
+
30505
+ // Determine the vector index based on the axis type
30506
+ const vectorIndex = axis === "x" ? 0 : 1;
30507
+
30508
+ // Determine whether or not to render the tick label text
30509
+ const shouldShowLabel = showTickLabel({
30510
+ label,
30511
+ axisOutOfBounds,
30512
+ graphEdge,
30513
+ position: pixelPoint[vectorIndex],
30514
+ gridStep: gridStep[vectorIndex],
30515
+ tickStep: tickStep[vectorIndex]
30516
+ });
30517
+
30518
+ // Determine whether or not to render a negative symbol for the tick label
30519
+ // as the negative sign is handled in the data-content attribute in order to
30520
+ // maintain the correct spacing between the labels
30521
+ const dataContent = label < 0 && shouldShowLabel ? "−" : null;
30522
+ return /*#__PURE__*/React__namespace.createElement("span", {
30523
+ className: `${axis}-axis-tick-label`,
30524
+ "data-content": dataContent
30525
+ }, shouldShowLabel && labelString);
30526
+ };
30527
+
30528
+ // Determine whether or not to show the tick label text
30529
+ const showTickLabel = _ref2 => {
30530
+ let {
30531
+ gridStep,
30532
+ tickStep,
30533
+ label,
30534
+ graphEdge,
30535
+ position,
30536
+ axisOutOfBounds
30537
+ } = _ref2;
30538
+ let showLabel = true;
30539
+
30540
+ // We only want to show the initial negative tick labels (e.g. -1) on each
30541
+ // axis if the tickStep > gridStep, to ensure that these labels do not overlap.
30542
+ // e.g. If gridStep = 1 and tickStep = 2, there are 2 grid lines for every 1 tick,
30543
+ // which allows enough room for both these tick labels to render.
30544
+ showLabel = tickStep > gridStep ? true : label !== -tickStep;
30545
+
30546
+ // If the tick label position is greater than the relevant edge of the graph, don't render it
30547
+ if (position <= -graphEdge || position >= graphEdge) {
30548
+ showLabel = false;
30549
+ }
30550
+
30551
+ // If the label is 0, we only want to show it if the axis is to the left of the graph's viewbox/range
30552
+ // otherwise it will overlap with graph axis labels
30553
+ if (label === 0 && !axisOutOfBounds) {
30554
+ showLabel = false;
30555
+ }
30556
+ return showLabel;
30557
+ };
30558
+
30559
+ // Generate the tick labels for an axis based on the tick step
30560
+ function generateTickLocations$1(tickStep, min, max) {
30561
+ const positiveTicks = [];
30562
+ const negativeTicks = [];
30563
+ const ticks = [];
30564
+
30565
+ // Add 0 if it is applicable.
30566
+ // This will be conditionally rendered using showTickLabel.
30567
+ if (min <= 0 && max >= 0) {
30568
+ positiveTicks.push(0);
30569
+ }
30570
+
30571
+ // Add ticks on the positive axis
30572
+ // Start at the first tick after 0 or the minimum value,
30573
+ // taking into account that the graph may be zoomed in on the positive axis
30574
+ let i = Math.max(min, 0) + tickStep;
30575
+ for (i; min < i && i < max; i += tickStep) {
30576
+ positiveTicks.push(i);
30577
+ }
30578
+
30579
+ // Add ticks on the negative axis
30580
+ // Start at the first tick before 0 or the maximum value,
30581
+ // taking into account that the graph may be zoomed in on the negative axis
30582
+ i = Math.min(max, 0) - tickStep;
30583
+ for (i; i > min; i -= tickStep) {
30584
+ negativeTicks.push(i);
30585
+ }
30586
+
30587
+ // Reverse the ticks so that they are in the correct render order
30588
+ positiveTicks.reverse();
30589
+ ticks.push(...positiveTicks, ...negativeTicks);
30590
+ return ticks;
30591
+ }
30592
+
30331
30593
  function PointGraph(props) {
30332
30594
  const {
30333
30595
  dispatch
@@ -30355,11 +30617,11 @@ function AxisArrows() {
30355
30617
  }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
30356
30618
  color: axisColor,
30357
30619
  tip: [0, yMin],
30358
- angle: 270
30620
+ angle: 90
30359
30621
  }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
30360
30622
  color: axisColor,
30361
30623
  tip: [0, yMax],
30362
- angle: 90
30624
+ angle: 270
30363
30625
  }));
30364
30626
  }
30365
30627
 
@@ -30368,62 +30630,88 @@ const tickStyle = {
30368
30630
  stroke: "black",
30369
30631
  strokeWidth: 1
30370
30632
  };
30371
-
30372
- // We only want to show the initial negative tick labels (e.g. -1) on each
30373
- // axis if the tickStep > gridStep, to ensure that these labels do not overlap.
30374
- // e.g. If gridStep = 1 and tickStep = 2, there are 2 grid lines for every 1 tick,
30375
- // which allows enough room for both these tick labels to render.
30376
- const showTickLabel = (gridStep, tickStep, label) => {
30377
- const showLabel = tickStep > gridStep ? true : label !== -tickStep;
30378
- return showLabel;
30379
- };
30380
30633
  const YGridTick = _ref => {
30381
30634
  let {
30382
30635
  y,
30383
- gridStep,
30384
- tickStep
30636
+ graphInfo
30385
30637
  } = _ref;
30386
- const pointOnAxis = [0, y];
30638
+ let xPointOnAxis = 0;
30639
+
30640
+ // If the graph is zoomed in, we want to make sure the ticks are still visible
30641
+ // even if they are outside the graph's range.
30642
+ if (graphInfo.range[0][0] > 0) {
30643
+ // If the graph is on the positive side of the x-axis, lock the ticks to the left side of the graph
30644
+ xPointOnAxis = graphInfo.range[0][0];
30645
+ }
30646
+ if (graphInfo.range[0][1] < 0) {
30647
+ // If the graph is on the negative side of the x-axis, lock the ticks to the right side of the graph
30648
+ xPointOnAxis = graphInfo.range[0][1];
30649
+ }
30650
+ const pointOnAxis = [xPointOnAxis, y];
30387
30651
  const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
30652
+
30653
+ // If the tick is on the edge of the graph's range, don't render it
30654
+ if (yPosition === -graphInfo.height || yPosition === graphInfo.height + 20) {
30655
+ return null;
30656
+ }
30657
+
30658
+ // Position of the start of the tick
30659
+ const x1 = xPosition - tickSize / 2;
30660
+ const y1 = yPosition;
30661
+
30662
+ // Position of the end of the tick
30663
+ const x2 = xPosition + tickSize / 2;
30664
+ const y2 = yPosition;
30388
30665
  return /*#__PURE__*/React__namespace.createElement("g", {
30389
30666
  className: "y-axis-ticks"
30390
30667
  }, /*#__PURE__*/React__namespace.createElement("line", {
30391
- x1: xPosition - tickSize / 2,
30392
- y1: yPosition,
30393
- x2: xPosition + tickSize / 2,
30394
- y2: yPosition,
30668
+ x1: x1,
30669
+ y1: y1,
30670
+ x2: x2,
30671
+ y2: y2,
30395
30672
  style: tickStyle
30396
- }), showTickLabel(gridStep, tickStep, y) && /*#__PURE__*/React__namespace.createElement("text", {
30397
- height: 20,
30398
- width: 50,
30399
- textAnchor: "end",
30400
- x: xPosition - 10,
30401
- y: yPosition + 5
30402
- }, y.toString()));
30673
+ }));
30403
30674
  };
30404
30675
  const XGridTick = _ref2 => {
30405
30676
  let {
30406
30677
  x,
30407
- gridStep,
30408
- tickStep
30678
+ graphInfo
30409
30679
  } = _ref2;
30410
- const pointOnAxis = [x, 0];
30680
+ let yPointOnAxis = 0;
30681
+ // If the graph is zoomed in, we want to make sure the ticks are still visible
30682
+ // even if they are outside the graph's range.
30683
+ if (graphInfo.range[1][0] > 0) {
30684
+ // If the graph is on the positive side of the y-axis, lock the ticks to the top of the graph
30685
+ yPointOnAxis = graphInfo.range[1][0];
30686
+ }
30687
+ if (graphInfo.range[1][1] < 0) {
30688
+ // If the graph is on the negative side of the x-axis, lock the ticks to the bottom of the graph
30689
+ yPointOnAxis = graphInfo.range[1][1];
30690
+ }
30691
+ const pointOnAxis = [x, yPointOnAxis];
30411
30692
  const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
30693
+
30694
+ // If the tick is on the edge of the graph's range, don't render it
30695
+ if (xPosition === -graphInfo.width / 2 || xPosition === graphInfo.width) {
30696
+ return null;
30697
+ }
30698
+
30699
+ // Position of the start of the tick
30700
+ const x1 = xPosition;
30701
+ const y1 = yPosition + tickSize / 2;
30702
+
30703
+ // Position of the end of the tick
30704
+ const x2 = xPosition;
30705
+ const y2 = yPosition - tickSize / 2;
30412
30706
  return /*#__PURE__*/React__namespace.createElement("g", {
30413
30707
  className: "x-axis-ticks"
30414
30708
  }, /*#__PURE__*/React__namespace.createElement("line", {
30415
- x1: xPosition,
30416
- y1: yPosition + tickSize / 2,
30417
- x2: xPosition,
30418
- y2: yPosition - tickSize / 2,
30709
+ x1: x1,
30710
+ y1: y1,
30711
+ x2: x2,
30712
+ y2: y2,
30419
30713
  style: tickStyle
30420
- }), showTickLabel(gridStep, tickStep, x) && /*#__PURE__*/React__namespace.createElement("text", {
30421
- height: 20,
30422
- width: 50,
30423
- textAnchor: "middle",
30424
- x: xPosition,
30425
- y: yPosition + 25
30426
- }, x.toString()));
30714
+ }));
30427
30715
  };
30428
30716
  function generateTickLocations(tickStep, min, max) {
30429
30717
  const ticks = [];
@@ -30434,17 +30722,29 @@ function generateTickLocations(tickStep, min, max) {
30434
30722
  }
30435
30723
 
30436
30724
  // Add ticks in the negative direction
30437
- for (let i = 0 - tickStep; i > min; i -= tickStep) {
30725
+ // Start at the first tick after 0 or the maximum value if it is negative
30726
+ let i = Math.min(max, 0) - tickStep;
30727
+ for (i; i > min; i -= tickStep) {
30438
30728
  ticks.push(i);
30439
30729
  }
30440
30730
  return ticks;
30441
30731
  }
30442
- const AxisTicks = props => {
30443
- const range = props.range;
30732
+ const AxisTicks = () => {
30733
+ const {
30734
+ tickStep,
30735
+ range,
30736
+ width,
30737
+ height
30738
+ } = useGraphConfig();
30739
+ const graphInfo = {
30740
+ range,
30741
+ width,
30742
+ height
30743
+ };
30444
30744
  const [xMin, xMax] = range[0];
30445
30745
  const [yMin, yMax] = range[1];
30446
- const yTickStep = props.tickStep[1];
30447
- const xTickStep = props.tickStep[0];
30746
+ const yTickStep = tickStep[1];
30747
+ const xTickStep = tickStep[0];
30448
30748
  const yGridTicks = generateTickLocations(yTickStep, yMin, yMax);
30449
30749
  const xGridTicks = generateTickLocations(xTickStep, xMin, xMax);
30450
30750
  return /*#__PURE__*/React__namespace.createElement("g", {
@@ -30453,15 +30753,13 @@ const AxisTicks = props => {
30453
30753
  return /*#__PURE__*/React__namespace.createElement(YGridTick, {
30454
30754
  y: y,
30455
30755
  key: `y-grid-tick-${y}`,
30456
- gridStep: props.gridStep[0],
30457
- tickStep: yTickStep
30756
+ graphInfo: graphInfo
30458
30757
  });
30459
30758
  }), xGridTicks.map(x => {
30460
30759
  return /*#__PURE__*/React__namespace.createElement(XGridTick, {
30461
30760
  x: x,
30462
30761
  key: `x-grid-tick-${x}`,
30463
- gridStep: props.gridStep[1],
30464
- tickStep: xTickStep
30762
+ graphInfo: graphInfo
30465
30763
  });
30466
30764
  }));
30467
30765
  };
@@ -30489,11 +30787,7 @@ const Grid = props => {
30489
30787
  yAxis: axisOptions(props, 1)
30490
30788
  }),
30491
30789
  // Only render the axis ticks and arrows if the markings are set to a full "graph"
30492
- props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, {
30493
- range: props.range,
30494
- tickStep: props.tickStep,
30495
- gridStep: props.gridStep
30496
- }), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)));
30790
+ props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)));
30497
30791
  };
30498
30792
 
30499
30793
  // Displays a stylized open eye: lint warnings are visible
@@ -31519,35 +31813,22 @@ function mafsStateToInteractiveGraph(state) {
31519
31813
  };
31520
31814
  }
31521
31815
  const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref) => {
31816
+ const {
31817
+ onChange
31818
+ } = props;
31522
31819
  const [state, dispatch] = React__namespace.useReducer(interactiveGraphReducer, props, initializeGraphState);
31523
31820
  React.useImperativeHandle(ref, () => ({
31524
31821
  getUserInput: () => getGradableGraph(state, props.graph)
31525
31822
  }));
31526
- function onChange(next) {
31527
- props.onChange(mafsStateToInteractiveGraph(next));
31528
- }
31529
- return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
31530
- state: state,
31531
- dispatch: dispatch,
31532
- onChange: onChange
31533
- }));
31534
- });
31535
- const MafsGraph = props => {
31536
- const {
31537
- state,
31538
- dispatch,
31539
- labels
31540
- } = props;
31541
- const [width, height] = props.box;
31542
31823
  const prevState = React.useRef(state);
31543
31824
  React.useEffect(() => {
31544
31825
  if (prevState.current !== state) {
31545
- props.onChange({
31826
+ onChange(mafsStateToInteractiveGraph({
31546
31827
  graph: state
31547
- });
31828
+ }));
31548
31829
  }
31549
31830
  prevState.current = state;
31550
- }, [props, state]);
31831
+ }, [onChange, state]);
31551
31832
 
31552
31833
  // Destructuring first to keep useEffect from making excess calls
31553
31834
  const [xSnap, ySnap] = props.snapStep;
@@ -31560,11 +31841,26 @@ const MafsGraph = props => {
31560
31841
  React.useEffect(() => {
31561
31842
  dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
31562
31843
  }, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
31844
+ return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
31845
+ state: state,
31846
+ dispatch: dispatch
31847
+ }));
31848
+ });
31849
+ const MafsGraph = props => {
31850
+ const {
31851
+ state,
31852
+ dispatch,
31853
+ labels
31854
+ } = props;
31855
+ const [width, height] = props.box;
31856
+ const tickStep = props.step;
31563
31857
  return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
31564
31858
  value: {
31565
31859
  range: state.range,
31566
31860
  snapStep: state.snapStep,
31567
31861
  markings: props.markings,
31862
+ tickStep: tickStep,
31863
+ gridStep: props.gridStep,
31568
31864
  showTooltips: !!props.showTooltips,
31569
31865
  graphDimensionsInPixels: props.box,
31570
31866
  width,
@@ -31578,7 +31874,9 @@ const MafsGraph = props => {
31578
31874
  height,
31579
31875
  position: "relative",
31580
31876
  padding: "25px 25px 0 0",
31581
- boxSizing: "content-box"
31877
+ boxSizing: "content-box",
31878
+ marginLeft: "20px",
31879
+ marginBottom: "20px"
31582
31880
  }
31583
31881
  }, /*#__PURE__*/React__namespace.createElement(LegacyGrid, {
31584
31882
  box: props.box,
@@ -31589,11 +31887,11 @@ const MafsGraph = props => {
31589
31887
  bottom: 0,
31590
31888
  left: 0
31591
31889
  }
31592
- }, props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(AxisLabels, null), /*#__PURE__*/React__namespace.createElement(mafs.Mafs, {
31890
+ }, props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisLabels, null), /*#__PURE__*/React__namespace.createElement(AxisTickLabels, null)), /*#__PURE__*/React__namespace.createElement(mafs.Mafs, {
31593
31891
  preserveAspectRatio: false,
31594
31892
  viewBox: {
31595
- x: props.range[0],
31596
- y: props.range[1],
31893
+ x: state.range[0],
31894
+ y: state.range[1],
31597
31895
  padding: 0
31598
31896
  },
31599
31897
  pan: false,
@@ -31603,12 +31901,12 @@ const MafsGraph = props => {
31603
31901
  }, /*#__PURE__*/React__namespace.createElement(SvgDefs, null), /*#__PURE__*/React__namespace.createElement(Grid, {
31604
31902
  tickStep: props.step,
31605
31903
  gridStep: props.gridStep,
31606
- range: props.range,
31904
+ range: state.range,
31607
31905
  containerSizeClass: props.containerSizeClass,
31608
31906
  markings: props.markings
31609
31907
  }), props.lockedFigures && /*#__PURE__*/React__namespace.createElement(GraphLockedLayer, {
31610
31908
  lockedFigures: props.lockedFigures,
31611
- range: props.range
31909
+ range: state.range
31612
31910
  }), renderGraph({
31613
31911
  state,
31614
31912
  dispatch
@@ -43704,7 +44002,7 @@ const init = function (options) {
43704
44002
 
43705
44003
  // This file is processed by a Rollup plugin (replace) to inject the production
43706
44004
  const libName = "@khanacademy/perseus";
43707
- const libVersion = "22.5.0";
44005
+ const libVersion = "22.6.0";
43708
44006
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
43709
44007
 
43710
44008
  const apiVersion = {