@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/es/index.js CHANGED
@@ -29,7 +29,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
29
29
  import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
30
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$4, Point as Point$2, useMovable, Text as Text$1, Polygon, Circle as Circle$1, Coordinates, Mafs } from 'mafs';
32
+ import { vec, Line as Line$4, Point as Point$2, useMovable, Text as Text$1, Polygon, 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';
@@ -28474,6 +28474,8 @@ Graph.defaultProps = {
28474
28474
 
28475
28475
  const defaultGraphConfig = {
28476
28476
  range: [[0, 1], [0, 1]],
28477
+ tickStep: [1, 1],
28478
+ gridStep: [1, 1],
28477
28479
  snapStep: [1, 1],
28478
28480
  markings: "none",
28479
28481
  showTooltips: false,
@@ -28488,6 +28490,7 @@ function useGraphConfig() {
28488
28490
  }
28489
28491
 
28490
28492
  const matrixBuilder = vec.matrixBuilder;
28493
+
28491
28494
  // When converting vectors, we don't need to translate because a vector
28492
28495
  // is like a pair of x and y distances, which are not bound to specific graph locations.
28493
28496
  function vectorsToPixels(vectors, graphState, translation = [0, 0]) {
@@ -28736,7 +28739,7 @@ function Arrowhead(props) {
28736
28739
  const [point] = useTransformVectorsToPixels(props.tip);
28737
28740
  return /*#__PURE__*/React.createElement("g", {
28738
28741
  className: "interactive-graph-arrowhead",
28739
- transform: `translate(${point[0]} ${point[1]}) rotate(${-props.angle})`
28742
+ transform: `translate(${point[0]} ${point[1]}) rotate(${props.angle})`
28740
28743
  }, /*#__PURE__*/React.createElement("g", {
28741
28744
  transform: "translate(-1.5)"
28742
28745
  }, /*#__PURE__*/React.createElement("path", {
@@ -28746,7 +28749,8 @@ function Arrowhead(props) {
28746
28749
  stroke: (_props$color = props.color) != null ? _props$color : "inherit"
28747
28750
  },
28748
28751
  strokeLinejoin: "round",
28749
- strokeLinecap: "round"
28752
+ strokeLinecap: "round",
28753
+ strokeWidth: "2px"
28750
28754
  })));
28751
28755
  }
28752
28756
 
@@ -28782,10 +28786,7 @@ const getIntersectionOfRayWithBox = (initialPoint, throughPoint, box) => {
28782
28786
  case isBetween(xAtYExtreme, xMin, xMax):
28783
28787
  return [xAtYExtreme, yExtreme];
28784
28788
  default:
28785
- // This default case is only reachable if the input is invalid
28786
- // (initialPoint is outside the graph bounds, or initialPoint and
28787
- // throughPoint are the same).
28788
- return [0, 0];
28789
+ return [xExtreme, yExtreme];
28789
28790
  }
28790
28791
  };
28791
28792
  const getLines = points => {
@@ -28825,7 +28826,7 @@ function Vector(props) {
28825
28826
  style
28826
28827
  } = props;
28827
28828
  const [tailPx, tipPx] = useTransformVectorsToPixels(tail, tip);
28828
- const direction = vec.sub(tip, tail);
28829
+ const direction = vec.sub(tipPx, tailPx);
28829
28830
  const angle = calculateAngleInDegrees(direction);
28830
28831
  return /*#__PURE__*/React.createElement("g", {
28831
28832
  style: {
@@ -28869,7 +28870,9 @@ const LockedLine = props => {
28869
28870
  } else {
28870
28871
  const LineType = kind === "segment" ? Line$4.Segment : Line$4.ThroughPoints;
28871
28872
  let arrowTip = kind === "segment" ? point2.coord : getIntersectionOfRayWithBox(point1.coord, point2.coord, range);
28872
- const direction = vec.sub(point2.coord, point1.coord);
28873
+ // eslint-disable-next-line react-hooks/rules-of-hooks
28874
+ const [tailPx, tipPx] = useTransformVectorsToPixels(point2.coord, point1.coord);
28875
+ const direction = vec.sub(tailPx, tipPx);
28873
28876
  let angle = calculateAngleInDegrees(direction);
28874
28877
  const startArrowHead = kind !== "segment" && /*#__PURE__*/React.createElement(Arrowhead, {
28875
28878
  angle: angle,
@@ -29867,19 +29870,24 @@ const getLineCoords = ({
29867
29870
  range,
29868
29871
  step
29869
29872
  }) => {
29870
- var _graph$coords2;
29871
- return (
29872
- // Return two lines for a linear system, one for a ray or linear
29873
- ((_graph$coords2 = graph.coords) != null ? _graph$coords2 : graph.type === "linear-system") ? defaultLinearCoords.map(collinear => normalizePoints(range, step, collinear)) : [normalizePoints(range, step, defaultLinearCoords[0])]
29874
- );
29873
+ // Return two lines for a linear system, one for a ray or linear
29874
+ switch (graph.type) {
29875
+ case "linear-system":
29876
+ return defaultLinearCoords.map(points => normalizePoints(range, step, points));
29877
+ case "linear":
29878
+ case "ray":
29879
+ return [normalizePoints(range, step, defaultLinearCoords[0])];
29880
+ default:
29881
+ throw new UnreachableCaseError(graph);
29882
+ }
29875
29883
  };
29876
29884
  const getPolygonCoords = ({
29877
29885
  graph,
29878
29886
  range,
29879
29887
  step
29880
29888
  }) => {
29881
- var _graph$coords3;
29882
- let coords = (_graph$coords3 = graph.coords) == null ? void 0 : _graph$coords3.slice();
29889
+ var _graph$coords2;
29890
+ let coords = (_graph$coords2 = graph.coords) == null ? void 0 : _graph$coords2.slice();
29883
29891
  if (coords) {
29884
29892
  return coords;
29885
29893
  }
@@ -29965,11 +29973,12 @@ function MovableCircle(props) {
29965
29973
  cy: centerPx[1],
29966
29974
  rx: radiiPx[0] + 3,
29967
29975
  ry: radiiPx[1] + 3
29968
- }), /*#__PURE__*/React.createElement(Circle$1, {
29969
- center: center,
29970
- radius: radius,
29971
- fillOpacity: 0,
29972
- color: color.blue
29976
+ }), /*#__PURE__*/React.createElement("ellipse", {
29977
+ className: "circle",
29978
+ cx: centerPx[0],
29979
+ cy: centerPx[1],
29980
+ rx: radiiPx[0],
29981
+ ry: radiiPx[1]
29973
29982
  }), /*#__PURE__*/React.createElement(DragHandle, {
29974
29983
  center: center
29975
29984
  }));
@@ -30010,6 +30019,258 @@ function crossProduct(as, bs) {
30010
30019
  return result;
30011
30020
  }
30012
30021
 
30022
+ // Universal padding for the axis ticks to ensure that the labels
30023
+ // have enough space to render without overlapping the tick lines
30024
+ const tickPadding = 5;
30025
+ const AxisTickLabels = () => {
30026
+ const graphConfig = useGraphConfig();
30027
+ const {
30028
+ tickStep,
30029
+ range
30030
+ } = graphConfig;
30031
+ const [xMin, xMax] = range[0];
30032
+ const [yMin, yMax] = range[1];
30033
+ const yTickStep = tickStep[1];
30034
+ const xTickStep = tickStep[0];
30035
+ const yGridTicks = generateTickLocations$1(yTickStep, yMin, yMax);
30036
+ const xGridTicks = generateTickLocations$1(xTickStep, xMin, xMax);
30037
+ return /*#__PURE__*/React.createElement("div", {
30038
+ className: "axis-tick-labels"
30039
+ }, /*#__PURE__*/React.createElement(YGridAxis, {
30040
+ axisTicks: yGridTicks,
30041
+ graphConfig: graphConfig
30042
+ }), /*#__PURE__*/React.createElement(XGridAxis, {
30043
+ axisTicks: xGridTicks,
30044
+ graphConfig: graphConfig
30045
+ }));
30046
+ };
30047
+
30048
+ // Generate the necessary styles and ticks for the y-axis tick labels container
30049
+ const YGridAxis = props => {
30050
+ const {
30051
+ axisTicks,
30052
+ graphConfig
30053
+ } = props;
30054
+ const {
30055
+ width,
30056
+ height
30057
+ } = graphConfig;
30058
+
30059
+ // First get the label height by calculating the difference between the first two y-axis labels
30060
+ const yTopPoint = pointToPixel([0, axisTicks[0]], graphConfig);
30061
+ const ySecondPoint = pointToPixel([0, axisTicks[1]], graphConfig);
30062
+ const yLabelHeight = ySecondPoint[1] - yTopPoint[1];
30063
+
30064
+ // When rendering purely positive y-axis labels, we want to adjust the left position
30065
+ // from the lowest calculated tick, as yMin may be negative
30066
+ const yLowestTick = axisTicks[axisTicks.length - 1];
30067
+ const yLeftAdjustment = Math.min(Math.max(yTopPoint[0] - tickPadding, 0), width);
30068
+
30069
+ // We only want to render the 0 label on the y-axis if it is left of the grid
30070
+ const yAxisIsLeftOfGrid = yLeftAdjustment === 0;
30071
+
30072
+ // If the y-axis is right of the graph's viewbox/range,
30073
+ // we want to adjust both the positioning and text alignment
30074
+ const yAxisRightOfGrid = yLeftAdjustment === width;
30075
+
30076
+ // Determine the default spacing for the labels based on whether the y-axis is right of the grid
30077
+ const defaultSpacing = yAxisRightOfGrid ? 0.5 : 0;
30078
+
30079
+ // We also want to adjust the spacing if there are no negative y-axis labels as the extra space is not needed
30080
+ const marginSpacing = yLowestTick >= 0 ? defaultSpacing * 2 : defaultSpacing;
30081
+
30082
+ // Determine the text alignment based on whether the y-axis is right of the grid
30083
+ // We need to cast this as a const as it is used in the inline styles
30084
+ const textAlign = yAxisRightOfGrid ? "left" : "right";
30085
+ const yAxisStyles = {
30086
+ left: `calc(${yLeftAdjustment}px - ${marginSpacing}em)`,
30087
+ top: `calc(${yTopPoint[1]}px - 0.5em)`,
30088
+ height,
30089
+ textAlign,
30090
+ "--y-axis-label-height": yLabelHeight + "px"
30091
+ };
30092
+ const classNames = yAxisRightOfGrid ? "y-axis-tick-labels y-axis-right-of-grid" : "y-axis-tick-labels ";
30093
+ return /*#__PURE__*/React.createElement("div", {
30094
+ className: classNames,
30095
+ "data-testid": "y-axis-tick-labels",
30096
+ style: yAxisStyles
30097
+ }, axisTicks.map(label => {
30098
+ return /*#__PURE__*/React.createElement(AxisTickLabel, {
30099
+ label: label,
30100
+ axis: "y",
30101
+ key: `y-grid-tick-${label}`,
30102
+ graphConfig: graphConfig,
30103
+ axisOutOfBounds: yAxisIsLeftOfGrid
30104
+ });
30105
+ }));
30106
+ };
30107
+
30108
+ // Generate the necessary styles and ticks for the x-axis tick labels container
30109
+ const XGridAxis = props => {
30110
+ const {
30111
+ axisTicks,
30112
+ graphConfig
30113
+ } = props;
30114
+ const {
30115
+ width,
30116
+ height
30117
+ } = graphConfig;
30118
+
30119
+ // Determine label width by calculating the difference between the first two x-axis labels
30120
+ const xFirstPoint = pointToPixel([axisTicks[0], 0], graphConfig);
30121
+ const xSecondPoint = pointToPixel([axisTicks[1], 0], graphConfig);
30122
+ const xLabelWidth = Math.abs(xSecondPoint[0] - xFirstPoint[0]);
30123
+
30124
+ // Determine the top position of the x-axis labels based on
30125
+ // whether the x-axis is above, within, or below the graph
30126
+ const xAxisAboveGraph = xFirstPoint[1] < 0;
30127
+ const xTopAdjustment = xAxisAboveGraph ? 0 : Math.min(xFirstPoint[1] + tickPadding, height);
30128
+
30129
+ // Determine whether the x-axis is outside of the graph's viewbox/range
30130
+ // This is used to conditionally render the 0 label on the x-axis
30131
+ const xAxisOutOfBounds = xTopAdjustment === height || xAxisAboveGraph;
30132
+
30133
+ // Determine the available space to the right of the x-axis labels
30134
+ const availableSpaceRight = width - xFirstPoint[0];
30135
+
30136
+ // Calculate the total combined width of ALL the x-axis labels
30137
+ const axisTicksWidth = axisTicks.length * xLabelWidth;
30138
+
30139
+ // Determine if we need to increase the width of the axis past the graph width
30140
+ const calculatedWidth = axisTicksWidth > width ? axisTicksWidth : width;
30141
+
30142
+ // Determine the right adjustment for the x-axis labels based on the available space
30143
+ // We're adjusting the right as the flex-direction is row-reverse
30144
+ const xRightAdjustment = availableSpaceRight - xLabelWidth / 2;
30145
+ const xAxisStyles = {
30146
+ top: xTopAdjustment,
30147
+ right: xRightAdjustment,
30148
+ width: calculatedWidth,
30149
+ "--x-axis-label-width": xLabelWidth + "px"
30150
+ };
30151
+ return /*#__PURE__*/React.createElement("div", {
30152
+ className: `x-axis-tick-labels ${xAxisAboveGraph && "x-axis-top-of-grid"}`,
30153
+ "data-testid": "x-axis-tick-labels",
30154
+ style: xAxisStyles
30155
+ }, axisTicks.map(label => {
30156
+ return /*#__PURE__*/React.createElement(AxisTickLabel, {
30157
+ axis: "x",
30158
+ label: label,
30159
+ key: `x-grid-tick-${label}`,
30160
+ graphConfig: graphConfig,
30161
+ axisOutOfBounds: xAxisOutOfBounds
30162
+ });
30163
+ }));
30164
+ };
30165
+
30166
+ // Generate the individual tick label elements
30167
+ const AxisTickLabel = ({
30168
+ label,
30169
+ graphConfig,
30170
+ axisOutOfBounds,
30171
+ axis
30172
+ }) => {
30173
+ const {
30174
+ gridStep,
30175
+ tickStep
30176
+ } = graphConfig;
30177
+ // Determine the point on the axis based on the axis type
30178
+ const pointOnAxis = axis === "x" ? [label, 0] : [0, label];
30179
+ const pixelPoint = pointToPixel(pointOnAxis, graphConfig);
30180
+
30181
+ // Remove the negative sign from the label string as it is handled in the data-content attribute
30182
+ const labelString = Math.abs(label).toString();
30183
+
30184
+ // Determine the relevant edge of the graph based on the axis type
30185
+ const graphEdge = axis === "x" ? graphConfig.width : graphConfig.height;
30186
+
30187
+ // Determine the vector index based on the axis type
30188
+ const vectorIndex = axis === "x" ? 0 : 1;
30189
+
30190
+ // Determine whether or not to render the tick label text
30191
+ const shouldShowLabel = showTickLabel({
30192
+ label,
30193
+ axisOutOfBounds,
30194
+ graphEdge,
30195
+ position: pixelPoint[vectorIndex],
30196
+ gridStep: gridStep[vectorIndex],
30197
+ tickStep: tickStep[vectorIndex]
30198
+ });
30199
+
30200
+ // Determine whether or not to render a negative symbol for the tick label
30201
+ // as the negative sign is handled in the data-content attribute in order to
30202
+ // maintain the correct spacing between the labels
30203
+ const dataContent = label < 0 && shouldShowLabel ? "−" : null;
30204
+ return /*#__PURE__*/React.createElement("span", {
30205
+ className: `${axis}-axis-tick-label`,
30206
+ "data-content": dataContent
30207
+ }, shouldShowLabel && labelString);
30208
+ };
30209
+
30210
+ // Determine whether or not to show the tick label text
30211
+ const showTickLabel = ({
30212
+ gridStep,
30213
+ tickStep,
30214
+ label,
30215
+ graphEdge,
30216
+ position,
30217
+ axisOutOfBounds
30218
+ }) => {
30219
+ let showLabel = true;
30220
+
30221
+ // We only want to show the initial negative tick labels (e.g. -1) on each
30222
+ // axis if the tickStep > gridStep, to ensure that these labels do not overlap.
30223
+ // e.g. If gridStep = 1 and tickStep = 2, there are 2 grid lines for every 1 tick,
30224
+ // which allows enough room for both these tick labels to render.
30225
+ showLabel = tickStep > gridStep ? true : label !== -tickStep;
30226
+
30227
+ // If the tick label position is greater than the relevant edge of the graph, don't render it
30228
+ if (position <= -graphEdge || position >= graphEdge) {
30229
+ showLabel = false;
30230
+ }
30231
+
30232
+ // If the label is 0, we only want to show it if the axis is to the left of the graph's viewbox/range
30233
+ // otherwise it will overlap with graph axis labels
30234
+ if (label === 0 && !axisOutOfBounds) {
30235
+ showLabel = false;
30236
+ }
30237
+ return showLabel;
30238
+ };
30239
+
30240
+ // Generate the tick labels for an axis based on the tick step
30241
+ function generateTickLocations$1(tickStep, min, max) {
30242
+ const positiveTicks = [];
30243
+ const negativeTicks = [];
30244
+ const ticks = [];
30245
+
30246
+ // Add 0 if it is applicable.
30247
+ // This will be conditionally rendered using showTickLabel.
30248
+ if (min <= 0 && max >= 0) {
30249
+ positiveTicks.push(0);
30250
+ }
30251
+
30252
+ // Add ticks on the positive axis
30253
+ // Start at the first tick after 0 or the minimum value,
30254
+ // taking into account that the graph may be zoomed in on the positive axis
30255
+ let i = Math.max(min, 0) + tickStep;
30256
+ for (i; min < i && i < max; i += tickStep) {
30257
+ positiveTicks.push(i);
30258
+ }
30259
+
30260
+ // Add ticks on the negative axis
30261
+ // Start at the first tick before 0 or the maximum value,
30262
+ // taking into account that the graph may be zoomed in on the negative axis
30263
+ i = Math.min(max, 0) - tickStep;
30264
+ for (i; i > min; i -= tickStep) {
30265
+ negativeTicks.push(i);
30266
+ }
30267
+
30268
+ // Reverse the ticks so that they are in the correct render order
30269
+ positiveTicks.reverse();
30270
+ ticks.push(...positiveTicks, ...negativeTicks);
30271
+ return ticks;
30272
+ }
30273
+
30013
30274
  function PointGraph(props) {
30014
30275
  const {
30015
30276
  dispatch
@@ -30037,11 +30298,11 @@ function AxisArrows() {
30037
30298
  }), /*#__PURE__*/React__default.createElement(Arrowhead, {
30038
30299
  color: axisColor,
30039
30300
  tip: [0, yMin],
30040
- angle: 270
30301
+ angle: 90
30041
30302
  }), /*#__PURE__*/React__default.createElement(Arrowhead, {
30042
30303
  color: axisColor,
30043
30304
  tip: [0, yMax],
30044
- angle: 90
30305
+ angle: 270
30045
30306
  }));
30046
30307
  }
30047
30308
 
@@ -30050,81 +30311,119 @@ const tickStyle = {
30050
30311
  stroke: "black",
30051
30312
  strokeWidth: 1
30052
30313
  };
30053
-
30054
- // We only want to show the initial negative tick labels (e.g. -1) on each
30055
- // axis if the tickStep > gridStep, to ensure that these labels do not overlap.
30056
- // e.g. If gridStep = 1 and tickStep = 2, there are 2 grid lines for every 1 tick,
30057
- // which allows enough room for both these tick labels to render.
30058
- const showTickLabel = (gridStep, tickStep, label) => {
30059
- const showLabel = tickStep > gridStep ? true : label !== -tickStep;
30060
- return showLabel;
30061
- };
30062
30314
  const YGridTick = ({
30063
30315
  y,
30064
- gridStep,
30065
- tickStep
30316
+ graphInfo
30066
30317
  }) => {
30067
- const pointOnAxis = [0, y];
30318
+ let xPointOnAxis = 0;
30319
+
30320
+ // If the graph is zoomed in, we want to make sure the ticks are still visible
30321
+ // even if they are outside the graph's range.
30322
+ if (graphInfo.range[0][0] > 0) {
30323
+ // If the graph is on the positive side of the x-axis, lock the ticks to the left side of the graph
30324
+ xPointOnAxis = graphInfo.range[0][0];
30325
+ }
30326
+ if (graphInfo.range[0][1] < 0) {
30327
+ // If the graph is on the negative side of the x-axis, lock the ticks to the right side of the graph
30328
+ xPointOnAxis = graphInfo.range[0][1];
30329
+ }
30330
+ const pointOnAxis = [xPointOnAxis, y];
30068
30331
  const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
30332
+
30333
+ // If the tick is on the edge of the graph's range, don't render it
30334
+ if (yPosition === -graphInfo.height || yPosition === graphInfo.height + 20) {
30335
+ return null;
30336
+ }
30337
+
30338
+ // Position of the start of the tick
30339
+ const x1 = xPosition - tickSize / 2;
30340
+ const y1 = yPosition;
30341
+
30342
+ // Position of the end of the tick
30343
+ const x2 = xPosition + tickSize / 2;
30344
+ const y2 = yPosition;
30069
30345
  return /*#__PURE__*/React.createElement("g", {
30070
30346
  className: "y-axis-ticks"
30071
30347
  }, /*#__PURE__*/React.createElement("line", {
30072
- x1: xPosition - tickSize / 2,
30073
- y1: yPosition,
30074
- x2: xPosition + tickSize / 2,
30075
- y2: yPosition,
30348
+ x1: x1,
30349
+ y1: y1,
30350
+ x2: x2,
30351
+ y2: y2,
30076
30352
  style: tickStyle
30077
- }), showTickLabel(gridStep, tickStep, y) && /*#__PURE__*/React.createElement("text", {
30078
- height: 20,
30079
- width: 50,
30080
- textAnchor: "end",
30081
- x: xPosition - 10,
30082
- y: yPosition + 5
30083
- }, y.toString()));
30353
+ }));
30084
30354
  };
30085
30355
  const XGridTick = ({
30086
30356
  x,
30087
- gridStep,
30088
- tickStep
30357
+ graphInfo
30089
30358
  }) => {
30090
- const pointOnAxis = [x, 0];
30359
+ let yPointOnAxis = 0;
30360
+ // If the graph is zoomed in, we want to make sure the ticks are still visible
30361
+ // even if they are outside the graph's range.
30362
+ if (graphInfo.range[1][0] > 0) {
30363
+ // If the graph is on the positive side of the y-axis, lock the ticks to the top of the graph
30364
+ yPointOnAxis = graphInfo.range[1][0];
30365
+ }
30366
+ if (graphInfo.range[1][1] < 0) {
30367
+ // If the graph is on the negative side of the x-axis, lock the ticks to the bottom of the graph
30368
+ yPointOnAxis = graphInfo.range[1][1];
30369
+ }
30370
+ const pointOnAxis = [x, yPointOnAxis];
30091
30371
  const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
30372
+
30373
+ // If the tick is on the edge of the graph's range, don't render it
30374
+ if (xPosition === -graphInfo.width / 2 || xPosition === graphInfo.width) {
30375
+ return null;
30376
+ }
30377
+
30378
+ // Position of the start of the tick
30379
+ const x1 = xPosition;
30380
+ const y1 = yPosition + tickSize / 2;
30381
+
30382
+ // Position of the end of the tick
30383
+ const x2 = xPosition;
30384
+ const y2 = yPosition - tickSize / 2;
30092
30385
  return /*#__PURE__*/React.createElement("g", {
30093
30386
  className: "x-axis-ticks"
30094
30387
  }, /*#__PURE__*/React.createElement("line", {
30095
- x1: xPosition,
30096
- y1: yPosition + tickSize / 2,
30097
- x2: xPosition,
30098
- y2: yPosition - tickSize / 2,
30388
+ x1: x1,
30389
+ y1: y1,
30390
+ x2: x2,
30391
+ y2: y2,
30099
30392
  style: tickStyle
30100
- }), showTickLabel(gridStep, tickStep, x) && /*#__PURE__*/React.createElement("text", {
30101
- height: 20,
30102
- width: 50,
30103
- textAnchor: "middle",
30104
- x: xPosition,
30105
- y: yPosition + 25
30106
- }, x.toString()));
30393
+ }));
30107
30394
  };
30108
30395
  function generateTickLocations(tickStep, min, max) {
30109
30396
  const ticks = [];
30110
30397
 
30111
30398
  // Add ticks in the positive direction
30112
- for (let i = 0 + tickStep; i < max; i += tickStep) {
30113
- ticks.push(i);
30399
+ for (let _i = 0 + tickStep; _i < max; _i += tickStep) {
30400
+ ticks.push(_i);
30114
30401
  }
30115
30402
 
30116
30403
  // Add ticks in the negative direction
30117
- for (let i = 0 - tickStep; i > min; i -= tickStep) {
30404
+ // Start at the first tick after 0 or the maximum value if it is negative
30405
+ let i = Math.min(max, 0) - tickStep;
30406
+ for (i; i > min; i -= tickStep) {
30118
30407
  ticks.push(i);
30119
30408
  }
30120
30409
  return ticks;
30121
30410
  }
30122
- const AxisTicks = props => {
30123
- const range = props.range;
30411
+ const AxisTicks = () => {
30412
+ const {
30413
+ tickStep,
30414
+ range,
30415
+ width,
30416
+ height
30417
+ } = useGraphConfig();
30418
+ const graphInfo = {
30419
+ range,
30420
+ width,
30421
+ height
30422
+ };
30124
30423
  const [xMin, xMax] = range[0];
30125
30424
  const [yMin, yMax] = range[1];
30126
- const yTickStep = props.tickStep[1];
30127
- const xTickStep = props.tickStep[0];
30425
+ const yTickStep = tickStep[1];
30426
+ const xTickStep = tickStep[0];
30128
30427
  const yGridTicks = generateTickLocations(yTickStep, yMin, yMax);
30129
30428
  const xGridTicks = generateTickLocations(xTickStep, xMin, xMax);
30130
30429
  return /*#__PURE__*/React.createElement("g", {
@@ -30133,15 +30432,13 @@ const AxisTicks = props => {
30133
30432
  return /*#__PURE__*/React.createElement(YGridTick, {
30134
30433
  y: y,
30135
30434
  key: `y-grid-tick-${y}`,
30136
- gridStep: props.gridStep[0],
30137
- tickStep: yTickStep
30435
+ graphInfo: graphInfo
30138
30436
  });
30139
30437
  }), xGridTicks.map(x => {
30140
30438
  return /*#__PURE__*/React.createElement(XGridTick, {
30141
30439
  x: x,
30142
30440
  key: `x-grid-tick-${x}`,
30143
- gridStep: props.gridStep[1],
30144
- tickStep: xTickStep
30441
+ graphInfo: graphInfo
30145
30442
  });
30146
30443
  }));
30147
30444
  };
@@ -30169,11 +30466,7 @@ const Grid = props => {
30169
30466
  yAxis: axisOptions(props, 1)
30170
30467
  }),
30171
30468
  // Only render the axis ticks and arrows if the markings are set to a full "graph"
30172
- props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, {
30173
- range: props.range,
30174
- tickStep: props.tickStep,
30175
- gridStep: props.gridStep
30176
- }), /*#__PURE__*/React.createElement(AxisArrows, null)));
30469
+ props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)));
30177
30470
  };
30178
30471
 
30179
30472
  // Displays a stylized open eye: lint warnings are visible
@@ -31220,11 +31513,14 @@ const MafsGraph = props => {
31220
31513
  labels
31221
31514
  } = props;
31222
31515
  const [width, height] = props.box;
31516
+ const tickStep = props.step;
31223
31517
  return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
31224
31518
  value: {
31225
31519
  range: state.range,
31226
31520
  snapStep: state.snapStep,
31227
31521
  markings: props.markings,
31522
+ tickStep: tickStep,
31523
+ gridStep: props.gridStep,
31228
31524
  showTooltips: !!props.showTooltips,
31229
31525
  graphDimensionsInPixels: props.box,
31230
31526
  width,
@@ -31238,7 +31534,9 @@ const MafsGraph = props => {
31238
31534
  height,
31239
31535
  position: "relative",
31240
31536
  padding: "25px 25px 0 0",
31241
- boxSizing: "content-box"
31537
+ boxSizing: "content-box",
31538
+ marginLeft: "20px",
31539
+ marginBottom: "20px"
31242
31540
  }
31243
31541
  }, /*#__PURE__*/React.createElement(LegacyGrid, {
31244
31542
  box: props.box,
@@ -31249,7 +31547,7 @@ const MafsGraph = props => {
31249
31547
  bottom: 0,
31250
31548
  left: 0
31251
31549
  }
31252
- }, props.markings === "graph" && /*#__PURE__*/React.createElement(AxisLabels, null), /*#__PURE__*/React.createElement(Mafs, {
31550
+ }, props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null), /*#__PURE__*/React.createElement(AxisTickLabels, null)), /*#__PURE__*/React.createElement(Mafs, {
31253
31551
  preserveAspectRatio: false,
31254
31552
  viewBox: {
31255
31553
  x: state.range[0],
@@ -43369,7 +43667,7 @@ const init = function init(options) {
43369
43667
 
43370
43668
  // This file is processed by a Rollup plugin (replace) to inject the production
43371
43669
  const libName = "@khanacademy/perseus";
43372
- const libVersion = "22.5.1";
43670
+ const libVersion = "22.6.0";
43373
43671
  addLibraryVersionToPerseusDebug(libName, libVersion);
43374
43672
 
43375
43673
  const apiVersion = {