@khanacademy/perseus 22.5.1 → 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,
@@ -30184,10 +30187,16 @@ const getLineCoords = _ref3 => {
30184
30187
  range,
30185
30188
  step
30186
30189
  } = _ref3;
30187
- return (
30188
- // Return two lines for a linear system, one for a ray or linear
30189
- graph.coords ?? graph.type === "linear-system" ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
30190
- );
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
+ }
30191
30200
  };
30192
30201
  const getPolygonCoords = _ref4 => {
30193
30202
  let {
@@ -30281,11 +30290,12 @@ function MovableCircle(props) {
30281
30290
  cy: centerPx[1],
30282
30291
  rx: radiiPx[0] + 3,
30283
30292
  ry: radiiPx[1] + 3
30284
- }), /*#__PURE__*/React__namespace.createElement(mafs.Circle, {
30285
- center: center,
30286
- radius: radius,
30287
- fillOpacity: 0,
30288
- 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]
30289
30299
  }), /*#__PURE__*/React__namespace.createElement(DragHandle, {
30290
30300
  center: center
30291
30301
  }));
@@ -30326,6 +30336,260 @@ function crossProduct(as, bs) {
30326
30336
  return result;
30327
30337
  }
30328
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
+
30329
30593
  function PointGraph(props) {
30330
30594
  const {
30331
30595
  dispatch
@@ -30353,11 +30617,11 @@ function AxisArrows() {
30353
30617
  }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
30354
30618
  color: axisColor,
30355
30619
  tip: [0, yMin],
30356
- angle: 270
30620
+ angle: 90
30357
30621
  }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
30358
30622
  color: axisColor,
30359
30623
  tip: [0, yMax],
30360
- angle: 90
30624
+ angle: 270
30361
30625
  }));
30362
30626
  }
30363
30627
 
@@ -30366,62 +30630,88 @@ const tickStyle = {
30366
30630
  stroke: "black",
30367
30631
  strokeWidth: 1
30368
30632
  };
30369
-
30370
- // We only want to show the initial negative tick labels (e.g. -1) on each
30371
- // axis if the tickStep > gridStep, to ensure that these labels do not overlap.
30372
- // e.g. If gridStep = 1 and tickStep = 2, there are 2 grid lines for every 1 tick,
30373
- // which allows enough room for both these tick labels to render.
30374
- const showTickLabel = (gridStep, tickStep, label) => {
30375
- const showLabel = tickStep > gridStep ? true : label !== -tickStep;
30376
- return showLabel;
30377
- };
30378
30633
  const YGridTick = _ref => {
30379
30634
  let {
30380
30635
  y,
30381
- gridStep,
30382
- tickStep
30636
+ graphInfo
30383
30637
  } = _ref;
30384
- 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];
30385
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;
30386
30665
  return /*#__PURE__*/React__namespace.createElement("g", {
30387
30666
  className: "y-axis-ticks"
30388
30667
  }, /*#__PURE__*/React__namespace.createElement("line", {
30389
- x1: xPosition - tickSize / 2,
30390
- y1: yPosition,
30391
- x2: xPosition + tickSize / 2,
30392
- y2: yPosition,
30668
+ x1: x1,
30669
+ y1: y1,
30670
+ x2: x2,
30671
+ y2: y2,
30393
30672
  style: tickStyle
30394
- }), showTickLabel(gridStep, tickStep, y) && /*#__PURE__*/React__namespace.createElement("text", {
30395
- height: 20,
30396
- width: 50,
30397
- textAnchor: "end",
30398
- x: xPosition - 10,
30399
- y: yPosition + 5
30400
- }, y.toString()));
30673
+ }));
30401
30674
  };
30402
30675
  const XGridTick = _ref2 => {
30403
30676
  let {
30404
30677
  x,
30405
- gridStep,
30406
- tickStep
30678
+ graphInfo
30407
30679
  } = _ref2;
30408
- 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];
30409
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;
30410
30706
  return /*#__PURE__*/React__namespace.createElement("g", {
30411
30707
  className: "x-axis-ticks"
30412
30708
  }, /*#__PURE__*/React__namespace.createElement("line", {
30413
- x1: xPosition,
30414
- y1: yPosition + tickSize / 2,
30415
- x2: xPosition,
30416
- y2: yPosition - tickSize / 2,
30709
+ x1: x1,
30710
+ y1: y1,
30711
+ x2: x2,
30712
+ y2: y2,
30417
30713
  style: tickStyle
30418
- }), showTickLabel(gridStep, tickStep, x) && /*#__PURE__*/React__namespace.createElement("text", {
30419
- height: 20,
30420
- width: 50,
30421
- textAnchor: "middle",
30422
- x: xPosition,
30423
- y: yPosition + 25
30424
- }, x.toString()));
30714
+ }));
30425
30715
  };
30426
30716
  function generateTickLocations(tickStep, min, max) {
30427
30717
  const ticks = [];
@@ -30432,17 +30722,29 @@ function generateTickLocations(tickStep, min, max) {
30432
30722
  }
30433
30723
 
30434
30724
  // Add ticks in the negative direction
30435
- 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) {
30436
30728
  ticks.push(i);
30437
30729
  }
30438
30730
  return ticks;
30439
30731
  }
30440
- const AxisTicks = props => {
30441
- 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
+ };
30442
30744
  const [xMin, xMax] = range[0];
30443
30745
  const [yMin, yMax] = range[1];
30444
- const yTickStep = props.tickStep[1];
30445
- const xTickStep = props.tickStep[0];
30746
+ const yTickStep = tickStep[1];
30747
+ const xTickStep = tickStep[0];
30446
30748
  const yGridTicks = generateTickLocations(yTickStep, yMin, yMax);
30447
30749
  const xGridTicks = generateTickLocations(xTickStep, xMin, xMax);
30448
30750
  return /*#__PURE__*/React__namespace.createElement("g", {
@@ -30451,15 +30753,13 @@ const AxisTicks = props => {
30451
30753
  return /*#__PURE__*/React__namespace.createElement(YGridTick, {
30452
30754
  y: y,
30453
30755
  key: `y-grid-tick-${y}`,
30454
- gridStep: props.gridStep[0],
30455
- tickStep: yTickStep
30756
+ graphInfo: graphInfo
30456
30757
  });
30457
30758
  }), xGridTicks.map(x => {
30458
30759
  return /*#__PURE__*/React__namespace.createElement(XGridTick, {
30459
30760
  x: x,
30460
30761
  key: `x-grid-tick-${x}`,
30461
- gridStep: props.gridStep[1],
30462
- tickStep: xTickStep
30762
+ graphInfo: graphInfo
30463
30763
  });
30464
30764
  }));
30465
30765
  };
@@ -30487,11 +30787,7 @@ const Grid = props => {
30487
30787
  yAxis: axisOptions(props, 1)
30488
30788
  }),
30489
30789
  // Only render the axis ticks and arrows if the markings are set to a full "graph"
30490
- props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, {
30491
- range: props.range,
30492
- tickStep: props.tickStep,
30493
- gridStep: props.gridStep
30494
- }), /*#__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)));
30495
30791
  };
30496
30792
 
30497
30793
  // Displays a stylized open eye: lint warnings are visible
@@ -31557,11 +31853,14 @@ const MafsGraph = props => {
31557
31853
  labels
31558
31854
  } = props;
31559
31855
  const [width, height] = props.box;
31856
+ const tickStep = props.step;
31560
31857
  return /*#__PURE__*/React__namespace.createElement(GraphConfigContext.Provider, {
31561
31858
  value: {
31562
31859
  range: state.range,
31563
31860
  snapStep: state.snapStep,
31564
31861
  markings: props.markings,
31862
+ tickStep: tickStep,
31863
+ gridStep: props.gridStep,
31565
31864
  showTooltips: !!props.showTooltips,
31566
31865
  graphDimensionsInPixels: props.box,
31567
31866
  width,
@@ -31575,7 +31874,9 @@ const MafsGraph = props => {
31575
31874
  height,
31576
31875
  position: "relative",
31577
31876
  padding: "25px 25px 0 0",
31578
- boxSizing: "content-box"
31877
+ boxSizing: "content-box",
31878
+ marginLeft: "20px",
31879
+ marginBottom: "20px"
31579
31880
  }
31580
31881
  }, /*#__PURE__*/React__namespace.createElement(LegacyGrid, {
31581
31882
  box: props.box,
@@ -31586,7 +31887,7 @@ const MafsGraph = props => {
31586
31887
  bottom: 0,
31587
31888
  left: 0
31588
31889
  }
31589
- }, 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, {
31590
31891
  preserveAspectRatio: false,
31591
31892
  viewBox: {
31592
31893
  x: state.range[0],
@@ -43701,7 +44002,7 @@ const init = function (options) {
43701
44002
 
43702
44003
  // This file is processed by a Rollup plugin (replace) to inject the production
43703
44004
  const libName = "@khanacademy/perseus";
43704
- const libVersion = "22.5.1";
44005
+ const libVersion = "22.6.0";
43705
44006
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
43706
44007
 
43707
44008
  const apiVersion = {