@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/es/index.css +69 -1
- package/dist/es/index.js +401 -106
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +69 -1
- package/dist/index.js +398 -100
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +1 -1
- package/dist/server-item-renderer.d.ts +1 -1
- package/dist/widgets/expression.d.ts +6 -6
- package/dist/widgets/interactive-graphs/axis-ticks.d.ts +1 -8
- package/dist/widgets/interactive-graphs/graphs/components/axis-tick-labels.d.ts +13 -0
- package/dist/widgets/interactive-graphs/graphs/use-transform.d.ts +2 -6
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +6 -0
- package/dist/widgets/interactive-graphs/mafs-graph.d.ts +12 -4
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-action.d.ts +5 -6
- package/dist/widgets/interactive-graphs/reducer/interactive-graph-state.d.ts +0 -1
- package/dist/widgets/interactive-graphs/reducer/use-graph-config.d.ts +3 -2
- package/dist/widgets/interactive-graphs/types.d.ts +5 -1
- package/dist/widgets/label-image.d.ts +2 -2
- package/package.json +2 -2
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,
|
|
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(${
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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,
|
|
@@ -29683,14 +29686,12 @@ function initializeGraphState(params) {
|
|
|
29683
29686
|
graph,
|
|
29684
29687
|
step,
|
|
29685
29688
|
snapStep,
|
|
29686
|
-
range
|
|
29687
|
-
markings
|
|
29689
|
+
range
|
|
29688
29690
|
} = params;
|
|
29689
29691
|
const shared = {
|
|
29690
29692
|
hasBeenInteractedWith: false,
|
|
29691
29693
|
range,
|
|
29692
|
-
snapStep
|
|
29693
|
-
markings
|
|
29694
|
+
snapStep
|
|
29694
29695
|
};
|
|
29695
29696
|
switch (graph.type) {
|
|
29696
29697
|
case "segment":
|
|
@@ -29869,19 +29870,24 @@ const getLineCoords = ({
|
|
|
29869
29870
|
range,
|
|
29870
29871
|
step
|
|
29871
29872
|
}) => {
|
|
29872
|
-
|
|
29873
|
-
|
|
29874
|
-
|
|
29875
|
-
|
|
29876
|
-
|
|
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
|
+
}
|
|
29877
29883
|
};
|
|
29878
29884
|
const getPolygonCoords = ({
|
|
29879
29885
|
graph,
|
|
29880
29886
|
range,
|
|
29881
29887
|
step
|
|
29882
29888
|
}) => {
|
|
29883
|
-
var _graph$
|
|
29884
|
-
let coords = (_graph$
|
|
29889
|
+
var _graph$coords2;
|
|
29890
|
+
let coords = (_graph$coords2 = graph.coords) == null ? void 0 : _graph$coords2.slice();
|
|
29885
29891
|
if (coords) {
|
|
29886
29892
|
return coords;
|
|
29887
29893
|
}
|
|
@@ -29967,11 +29973,12 @@ function MovableCircle(props) {
|
|
|
29967
29973
|
cy: centerPx[1],
|
|
29968
29974
|
rx: radiiPx[0] + 3,
|
|
29969
29975
|
ry: radiiPx[1] + 3
|
|
29970
|
-
}), /*#__PURE__*/React.createElement(
|
|
29971
|
-
|
|
29972
|
-
|
|
29973
|
-
|
|
29974
|
-
|
|
29976
|
+
}), /*#__PURE__*/React.createElement("ellipse", {
|
|
29977
|
+
className: "circle",
|
|
29978
|
+
cx: centerPx[0],
|
|
29979
|
+
cy: centerPx[1],
|
|
29980
|
+
rx: radiiPx[0],
|
|
29981
|
+
ry: radiiPx[1]
|
|
29975
29982
|
}), /*#__PURE__*/React.createElement(DragHandle, {
|
|
29976
29983
|
center: center
|
|
29977
29984
|
}));
|
|
@@ -30012,6 +30019,258 @@ function crossProduct(as, bs) {
|
|
|
30012
30019
|
return result;
|
|
30013
30020
|
}
|
|
30014
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
|
+
|
|
30015
30274
|
function PointGraph(props) {
|
|
30016
30275
|
const {
|
|
30017
30276
|
dispatch
|
|
@@ -30039,11 +30298,11 @@ function AxisArrows() {
|
|
|
30039
30298
|
}), /*#__PURE__*/React__default.createElement(Arrowhead, {
|
|
30040
30299
|
color: axisColor,
|
|
30041
30300
|
tip: [0, yMin],
|
|
30042
|
-
angle:
|
|
30301
|
+
angle: 90
|
|
30043
30302
|
}), /*#__PURE__*/React__default.createElement(Arrowhead, {
|
|
30044
30303
|
color: axisColor,
|
|
30045
30304
|
tip: [0, yMax],
|
|
30046
|
-
angle:
|
|
30305
|
+
angle: 270
|
|
30047
30306
|
}));
|
|
30048
30307
|
}
|
|
30049
30308
|
|
|
@@ -30052,81 +30311,119 @@ const tickStyle = {
|
|
|
30052
30311
|
stroke: "black",
|
|
30053
30312
|
strokeWidth: 1
|
|
30054
30313
|
};
|
|
30055
|
-
|
|
30056
|
-
// We only want to show the initial negative tick labels (e.g. -1) on each
|
|
30057
|
-
// axis if the tickStep > gridStep, to ensure that these labels do not overlap.
|
|
30058
|
-
// e.g. If gridStep = 1 and tickStep = 2, there are 2 grid lines for every 1 tick,
|
|
30059
|
-
// which allows enough room for both these tick labels to render.
|
|
30060
|
-
const showTickLabel = (gridStep, tickStep, label) => {
|
|
30061
|
-
const showLabel = tickStep > gridStep ? true : label !== -tickStep;
|
|
30062
|
-
return showLabel;
|
|
30063
|
-
};
|
|
30064
30314
|
const YGridTick = ({
|
|
30065
30315
|
y,
|
|
30066
|
-
|
|
30067
|
-
tickStep
|
|
30316
|
+
graphInfo
|
|
30068
30317
|
}) => {
|
|
30069
|
-
|
|
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];
|
|
30070
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;
|
|
30071
30345
|
return /*#__PURE__*/React.createElement("g", {
|
|
30072
30346
|
className: "y-axis-ticks"
|
|
30073
30347
|
}, /*#__PURE__*/React.createElement("line", {
|
|
30074
|
-
x1:
|
|
30075
|
-
y1:
|
|
30076
|
-
x2:
|
|
30077
|
-
y2:
|
|
30348
|
+
x1: x1,
|
|
30349
|
+
y1: y1,
|
|
30350
|
+
x2: x2,
|
|
30351
|
+
y2: y2,
|
|
30078
30352
|
style: tickStyle
|
|
30079
|
-
})
|
|
30080
|
-
height: 20,
|
|
30081
|
-
width: 50,
|
|
30082
|
-
textAnchor: "end",
|
|
30083
|
-
x: xPosition - 10,
|
|
30084
|
-
y: yPosition + 5
|
|
30085
|
-
}, y.toString()));
|
|
30353
|
+
}));
|
|
30086
30354
|
};
|
|
30087
30355
|
const XGridTick = ({
|
|
30088
30356
|
x,
|
|
30089
|
-
|
|
30090
|
-
tickStep
|
|
30357
|
+
graphInfo
|
|
30091
30358
|
}) => {
|
|
30092
|
-
|
|
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];
|
|
30093
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;
|
|
30094
30385
|
return /*#__PURE__*/React.createElement("g", {
|
|
30095
30386
|
className: "x-axis-ticks"
|
|
30096
30387
|
}, /*#__PURE__*/React.createElement("line", {
|
|
30097
|
-
x1:
|
|
30098
|
-
y1:
|
|
30099
|
-
x2:
|
|
30100
|
-
y2:
|
|
30388
|
+
x1: x1,
|
|
30389
|
+
y1: y1,
|
|
30390
|
+
x2: x2,
|
|
30391
|
+
y2: y2,
|
|
30101
30392
|
style: tickStyle
|
|
30102
|
-
})
|
|
30103
|
-
height: 20,
|
|
30104
|
-
width: 50,
|
|
30105
|
-
textAnchor: "middle",
|
|
30106
|
-
x: xPosition,
|
|
30107
|
-
y: yPosition + 25
|
|
30108
|
-
}, x.toString()));
|
|
30393
|
+
}));
|
|
30109
30394
|
};
|
|
30110
30395
|
function generateTickLocations(tickStep, min, max) {
|
|
30111
30396
|
const ticks = [];
|
|
30112
30397
|
|
|
30113
30398
|
// Add ticks in the positive direction
|
|
30114
|
-
for (let
|
|
30115
|
-
ticks.push(
|
|
30399
|
+
for (let _i = 0 + tickStep; _i < max; _i += tickStep) {
|
|
30400
|
+
ticks.push(_i);
|
|
30116
30401
|
}
|
|
30117
30402
|
|
|
30118
30403
|
// Add ticks in the negative direction
|
|
30119
|
-
|
|
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) {
|
|
30120
30407
|
ticks.push(i);
|
|
30121
30408
|
}
|
|
30122
30409
|
return ticks;
|
|
30123
30410
|
}
|
|
30124
|
-
const AxisTicks =
|
|
30125
|
-
const
|
|
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
|
+
};
|
|
30126
30423
|
const [xMin, xMax] = range[0];
|
|
30127
30424
|
const [yMin, yMax] = range[1];
|
|
30128
|
-
const yTickStep =
|
|
30129
|
-
const xTickStep =
|
|
30425
|
+
const yTickStep = tickStep[1];
|
|
30426
|
+
const xTickStep = tickStep[0];
|
|
30130
30427
|
const yGridTicks = generateTickLocations(yTickStep, yMin, yMax);
|
|
30131
30428
|
const xGridTicks = generateTickLocations(xTickStep, xMin, xMax);
|
|
30132
30429
|
return /*#__PURE__*/React.createElement("g", {
|
|
@@ -30135,15 +30432,13 @@ const AxisTicks = props => {
|
|
|
30135
30432
|
return /*#__PURE__*/React.createElement(YGridTick, {
|
|
30136
30433
|
y: y,
|
|
30137
30434
|
key: `y-grid-tick-${y}`,
|
|
30138
|
-
|
|
30139
|
-
tickStep: yTickStep
|
|
30435
|
+
graphInfo: graphInfo
|
|
30140
30436
|
});
|
|
30141
30437
|
}), xGridTicks.map(x => {
|
|
30142
30438
|
return /*#__PURE__*/React.createElement(XGridTick, {
|
|
30143
30439
|
x: x,
|
|
30144
30440
|
key: `x-grid-tick-${x}`,
|
|
30145
|
-
|
|
30146
|
-
tickStep: xTickStep
|
|
30441
|
+
graphInfo: graphInfo
|
|
30147
30442
|
});
|
|
30148
30443
|
}));
|
|
30149
30444
|
};
|
|
@@ -30171,11 +30466,7 @@ const Grid = props => {
|
|
|
30171
30466
|
yAxis: axisOptions(props, 1)
|
|
30172
30467
|
}),
|
|
30173
30468
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
30174
|
-
props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks,
|
|
30175
|
-
range: props.range,
|
|
30176
|
-
tickStep: props.tickStep,
|
|
30177
|
-
gridStep: props.gridStep
|
|
30178
|
-
}), /*#__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)));
|
|
30179
30470
|
};
|
|
30180
30471
|
|
|
30181
30472
|
// Displays a stylized open eye: lint warnings are visible
|
|
@@ -31182,35 +31473,22 @@ function mafsStateToInteractiveGraph(state) {
|
|
|
31182
31473
|
return _extends({}, state);
|
|
31183
31474
|
}
|
|
31184
31475
|
const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
31476
|
+
const {
|
|
31477
|
+
onChange
|
|
31478
|
+
} = props;
|
|
31185
31479
|
const [state, dispatch] = React.useReducer(interactiveGraphReducer, props, initializeGraphState);
|
|
31186
31480
|
useImperativeHandle(ref, () => ({
|
|
31187
31481
|
getUserInput: () => getGradableGraph(state, props.graph)
|
|
31188
31482
|
}));
|
|
31189
|
-
function onChange(next) {
|
|
31190
|
-
props.onChange(mafsStateToInteractiveGraph(next));
|
|
31191
|
-
}
|
|
31192
|
-
return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
|
|
31193
|
-
state: state,
|
|
31194
|
-
dispatch: dispatch,
|
|
31195
|
-
onChange: onChange
|
|
31196
|
-
}));
|
|
31197
|
-
});
|
|
31198
|
-
const MafsGraph = props => {
|
|
31199
|
-
const {
|
|
31200
|
-
state,
|
|
31201
|
-
dispatch,
|
|
31202
|
-
labels
|
|
31203
|
-
} = props;
|
|
31204
|
-
const [width, height] = props.box;
|
|
31205
31483
|
const prevState = useRef(state);
|
|
31206
31484
|
useEffect(() => {
|
|
31207
31485
|
if (prevState.current !== state) {
|
|
31208
|
-
|
|
31486
|
+
onChange(mafsStateToInteractiveGraph({
|
|
31209
31487
|
graph: state
|
|
31210
|
-
});
|
|
31488
|
+
}));
|
|
31211
31489
|
}
|
|
31212
31490
|
prevState.current = state;
|
|
31213
|
-
}, [
|
|
31491
|
+
}, [onChange, state]);
|
|
31214
31492
|
|
|
31215
31493
|
// Destructuring first to keep useEffect from making excess calls
|
|
31216
31494
|
const [xSnap, ySnap] = props.snapStep;
|
|
@@ -31223,11 +31501,26 @@ const MafsGraph = props => {
|
|
|
31223
31501
|
useEffect(() => {
|
|
31224
31502
|
dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
|
|
31225
31503
|
}, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
|
|
31504
|
+
return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
|
|
31505
|
+
state: state,
|
|
31506
|
+
dispatch: dispatch
|
|
31507
|
+
}));
|
|
31508
|
+
});
|
|
31509
|
+
const MafsGraph = props => {
|
|
31510
|
+
const {
|
|
31511
|
+
state,
|
|
31512
|
+
dispatch,
|
|
31513
|
+
labels
|
|
31514
|
+
} = props;
|
|
31515
|
+
const [width, height] = props.box;
|
|
31516
|
+
const tickStep = props.step;
|
|
31226
31517
|
return /*#__PURE__*/React.createElement(GraphConfigContext.Provider, {
|
|
31227
31518
|
value: {
|
|
31228
31519
|
range: state.range,
|
|
31229
31520
|
snapStep: state.snapStep,
|
|
31230
31521
|
markings: props.markings,
|
|
31522
|
+
tickStep: tickStep,
|
|
31523
|
+
gridStep: props.gridStep,
|
|
31231
31524
|
showTooltips: !!props.showTooltips,
|
|
31232
31525
|
graphDimensionsInPixels: props.box,
|
|
31233
31526
|
width,
|
|
@@ -31241,7 +31534,9 @@ const MafsGraph = props => {
|
|
|
31241
31534
|
height,
|
|
31242
31535
|
position: "relative",
|
|
31243
31536
|
padding: "25px 25px 0 0",
|
|
31244
|
-
boxSizing: "content-box"
|
|
31537
|
+
boxSizing: "content-box",
|
|
31538
|
+
marginLeft: "20px",
|
|
31539
|
+
marginBottom: "20px"
|
|
31245
31540
|
}
|
|
31246
31541
|
}, /*#__PURE__*/React.createElement(LegacyGrid, {
|
|
31247
31542
|
box: props.box,
|
|
@@ -31252,11 +31547,11 @@ const MafsGraph = props => {
|
|
|
31252
31547
|
bottom: 0,
|
|
31253
31548
|
left: 0
|
|
31254
31549
|
}
|
|
31255
|
-
}, 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, {
|
|
31256
31551
|
preserveAspectRatio: false,
|
|
31257
31552
|
viewBox: {
|
|
31258
|
-
x:
|
|
31259
|
-
y:
|
|
31553
|
+
x: state.range[0],
|
|
31554
|
+
y: state.range[1],
|
|
31260
31555
|
padding: 0
|
|
31261
31556
|
},
|
|
31262
31557
|
pan: false,
|
|
@@ -31266,12 +31561,12 @@ const MafsGraph = props => {
|
|
|
31266
31561
|
}, /*#__PURE__*/React.createElement(SvgDefs, null), /*#__PURE__*/React.createElement(Grid, {
|
|
31267
31562
|
tickStep: props.step,
|
|
31268
31563
|
gridStep: props.gridStep,
|
|
31269
|
-
range:
|
|
31564
|
+
range: state.range,
|
|
31270
31565
|
containerSizeClass: props.containerSizeClass,
|
|
31271
31566
|
markings: props.markings
|
|
31272
31567
|
}), props.lockedFigures && /*#__PURE__*/React.createElement(GraphLockedLayer, {
|
|
31273
31568
|
lockedFigures: props.lockedFigures,
|
|
31274
|
-
range:
|
|
31569
|
+
range: state.range
|
|
31275
31570
|
}), renderGraph({
|
|
31276
31571
|
state,
|
|
31277
31572
|
dispatch
|
|
@@ -43372,7 +43667,7 @@ const init = function init(options) {
|
|
|
43372
43667
|
|
|
43373
43668
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
43374
43669
|
const libName = "@khanacademy/perseus";
|
|
43375
|
-
const libVersion = "22.
|
|
43670
|
+
const libVersion = "22.6.0";
|
|
43376
43671
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
43377
43672
|
|
|
43378
43673
|
const apiVersion = {
|