@khanacademy/perseus 24.2.0 → 24.3.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 +7 -0
- package/dist/es/index.js +654 -130
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +677 -141
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +1 -0
- package/dist/perseus-types.d.ts +17 -2
- package/dist/server-item-renderer.d.ts +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/widgets/expression.d.ts +6 -0
- package/dist/widgets/interactive-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/angle.d.ts +5 -0
- package/dist/widgets/interactive-graphs/graphs/components/{angle.d.ts → angle-indicators.d.ts} +12 -2
- package/dist/widgets/interactive-graphs/graphs/components/movable-line.d.ts +11 -0
- package/dist/widgets/interactive-graphs/graphs/components/text-label.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/index.d.ts +1 -0
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +3 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-function.d.ts +4 -0
- package/dist/widgets/interactive-graphs/mafs-supported-graph-types.d.ts +1 -1
- package/dist/widgets/interactive-graphs/math/angles.d.ts +3 -0
- package/dist/widgets/interactive-graphs/math/box.d.ts +4 -0
- package/dist/widgets/interactive-graphs/math/clamp.d.ts +1 -0
- package/dist/widgets/interactive-graphs/math/coordinates.d.ts +2 -0
- package/dist/widgets/interactive-graphs/math/index.d.ts +6 -0
- package/dist/widgets/interactive-graphs/math/interval.d.ts +5 -0
- package/dist/widgets/interactive-graphs/math/snap.d.ts +2 -0
- package/dist/widgets/interactive-graphs/types.d.ts +9 -1
- package/dist/widgets/interactive-graphs/utils.d.ts +0 -2
- package/dist/widgets/label-image.d.ts +2 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9068,7 +9068,7 @@ var Tex$1 = {
|
|
|
9068
9068
|
const {
|
|
9069
9069
|
processMath: processMath$1
|
|
9070
9070
|
} = Tex$1;
|
|
9071
|
-
function polar(r, th) {
|
|
9071
|
+
function polar$1(r, th) {
|
|
9072
9072
|
if (typeof r === "number") {
|
|
9073
9073
|
r = [r, r];
|
|
9074
9074
|
}
|
|
@@ -9404,8 +9404,8 @@ class Graphie$1 {
|
|
|
9404
9404
|
endAngle = (endAngle % 360 + 360) % 360;
|
|
9405
9405
|
const cent = this.scalePoint(center);
|
|
9406
9406
|
const radii = this.scaleVector(radius);
|
|
9407
|
-
const startVector = polar(radius, startAngle);
|
|
9408
|
-
const endVector = polar(radius, endAngle);
|
|
9407
|
+
const startVector = polar$1(radius, startAngle);
|
|
9408
|
+
const endVector = polar$1(radius, endAngle);
|
|
9409
9409
|
|
|
9410
9410
|
// We round the coordinates to make testing easier, because trig
|
|
9411
9411
|
// operations return unround numbers.
|
|
@@ -13834,7 +13834,7 @@ ___default["default"].extend(MovableAngle.prototype, {
|
|
|
13834
13834
|
let angle = GraphUtils.findAngle(newPoint, vertex);
|
|
13835
13835
|
angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
|
|
13836
13836
|
const distance = GraphUtils.getDistance(newPoint, vertex);
|
|
13837
|
-
return kmath.vector.add(vertex, polar(distance, angle));
|
|
13837
|
+
return kmath.vector.add(vertex, polar$1(distance, angle));
|
|
13838
13838
|
}
|
|
13839
13839
|
return true;
|
|
13840
13840
|
};
|
|
@@ -16794,7 +16794,7 @@ const Path = GraphieClasses.createClass({
|
|
|
16794
16794
|
this.path.toFront();
|
|
16795
16795
|
}
|
|
16796
16796
|
});
|
|
16797
|
-
const Arc = GraphieClasses.createSimpleClass((graphie, props) => {
|
|
16797
|
+
const Arc$1 = GraphieClasses.createSimpleClass((graphie, props) => {
|
|
16798
16798
|
let center = props.center;
|
|
16799
16799
|
let radius = props.radius;
|
|
16800
16800
|
if (props.unscaled) {
|
|
@@ -16810,7 +16810,7 @@ const Rect$1 = GraphieClasses.createSimpleClass((graphie, props) => {
|
|
|
16810
16810
|
return graphie.rect(props.x, props.y, props.width, props.height, props.style);
|
|
16811
16811
|
});
|
|
16812
16812
|
var Movables = {
|
|
16813
|
-
Arc: Arc,
|
|
16813
|
+
Arc: Arc$1,
|
|
16814
16814
|
Circle: Circle,
|
|
16815
16815
|
Label: Label$1,
|
|
16816
16816
|
Line: Line$3,
|
|
@@ -28657,6 +28657,92 @@ const useTransformDimensionsToPixels = function () {
|
|
|
28657
28657
|
return dimensionsToPixels(dimens, graphState);
|
|
28658
28658
|
};
|
|
28659
28659
|
|
|
28660
|
+
// Restricts `value` to be between `min` and `max`.
|
|
28661
|
+
function clamp(value, min, max) {
|
|
28662
|
+
if (value < min) {
|
|
28663
|
+
return min;
|
|
28664
|
+
}
|
|
28665
|
+
if (value > max) {
|
|
28666
|
+
return max;
|
|
28667
|
+
}
|
|
28668
|
+
return value;
|
|
28669
|
+
}
|
|
28670
|
+
|
|
28671
|
+
// Rounds each component of `point` to the nearest multiple of the
|
|
28672
|
+
// corresponding component of `snapStep`. E.g. if the snap step is [2, 3],
|
|
28673
|
+
// the x-coord of the point will be rounded to the nearest multiple of 2, and
|
|
28674
|
+
// the y-coord of the point will be rounded to the nearest multiple of 3.
|
|
28675
|
+
function snap(snapStep, point) {
|
|
28676
|
+
const [requestedX, requestedY] = point;
|
|
28677
|
+
const [snapX, snapY] = snapStep;
|
|
28678
|
+
return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
|
|
28679
|
+
}
|
|
28680
|
+
|
|
28681
|
+
// Use X and Y to index into arrays that represent coordinate pairs.
|
|
28682
|
+
// e.g. point[X]
|
|
28683
|
+
const X = 0;
|
|
28684
|
+
const Y = 1;
|
|
28685
|
+
|
|
28686
|
+
// Use MIN and MAX to index into arrays that represent intervals.
|
|
28687
|
+
// e.g. range[MAX]
|
|
28688
|
+
const MIN = 0;
|
|
28689
|
+
const MAX = 1;
|
|
28690
|
+
function size(_ref) {
|
|
28691
|
+
let [min, max] = _ref;
|
|
28692
|
+
return max - min;
|
|
28693
|
+
}
|
|
28694
|
+
|
|
28695
|
+
// Removes an equal amount from each end of the interval.
|
|
28696
|
+
//
|
|
28697
|
+
// If the given amount cannot be removed (because the interval is too small),
|
|
28698
|
+
// half the interval's size is removed from each end instead, so the interval
|
|
28699
|
+
// ends up with size = 0, and both MAX and MIN equal to its former midpoint.
|
|
28700
|
+
//
|
|
28701
|
+
// If `amount` is negative, the interval is expanded instead.
|
|
28702
|
+
function trim(amount, interval) {
|
|
28703
|
+
if (amount * 2 > size(interval)) {
|
|
28704
|
+
const middle = average(...interval);
|
|
28705
|
+
return [middle, middle];
|
|
28706
|
+
}
|
|
28707
|
+
return [interval[MIN] + amount, interval[MAX] - amount];
|
|
28708
|
+
}
|
|
28709
|
+
function average(a, b) {
|
|
28710
|
+
return (a + b) / 2;
|
|
28711
|
+
}
|
|
28712
|
+
|
|
28713
|
+
// Restricts the `point` to be within `box`, by clamping each coordinate to the
|
|
28714
|
+
// corresponding interval.
|
|
28715
|
+
function clampToBox(box, point) {
|
|
28716
|
+
return [clamp(point[X], ...box[X]), clamp(point[Y], ...box[Y])];
|
|
28717
|
+
}
|
|
28718
|
+
|
|
28719
|
+
// Reduces the size of `box`, trimming each corner by the given positive
|
|
28720
|
+
// `amount`. If a component of `amount` is negative, the box is expanded in
|
|
28721
|
+
// that dimension instead.
|
|
28722
|
+
function inset(amount, box) {
|
|
28723
|
+
return [trim(amount[X], box[X]), trim(amount[Y], box[Y])];
|
|
28724
|
+
}
|
|
28725
|
+
|
|
28726
|
+
// This file contains helper functions for working with angles.
|
|
28727
|
+
function polar(r, th) {
|
|
28728
|
+
if (typeof r === "number") {
|
|
28729
|
+
r = [r, r];
|
|
28730
|
+
}
|
|
28731
|
+
th = th * Math.PI / 180;
|
|
28732
|
+
return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
|
|
28733
|
+
}
|
|
28734
|
+
const findAngle = (point1, point2, vertex) => {
|
|
28735
|
+
if (vertex === undefined) {
|
|
28736
|
+
const x = point1[0] - point2[0];
|
|
28737
|
+
const y = point1[1] - point2[1];
|
|
28738
|
+
if (!x && !y) {
|
|
28739
|
+
return 0;
|
|
28740
|
+
}
|
|
28741
|
+
return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
|
|
28742
|
+
}
|
|
28743
|
+
return findAngle(point1, vertex) - findAngle(point2, vertex);
|
|
28744
|
+
};
|
|
28745
|
+
|
|
28660
28746
|
function AxisLabels() {
|
|
28661
28747
|
const {
|
|
28662
28748
|
range,
|
|
@@ -28664,8 +28750,8 @@ function AxisLabels() {
|
|
|
28664
28750
|
width,
|
|
28665
28751
|
height
|
|
28666
28752
|
} = useGraphConfig();
|
|
28667
|
-
const yAxisLabelLocation = [0, range[
|
|
28668
|
-
const xAxisLabelLocation = [range[
|
|
28753
|
+
const yAxisLabelLocation = [0, range[Y][MAX]];
|
|
28754
|
+
const xAxisLabelLocation = [range[X][MAX], 0];
|
|
28669
28755
|
const [xAxisLabelText, yAxisLabelText] = labels;
|
|
28670
28756
|
const graphInfo = {
|
|
28671
28757
|
range,
|
|
@@ -28836,6 +28922,32 @@ const LockedEllipse = props => {
|
|
|
28836
28922
|
});
|
|
28837
28923
|
};
|
|
28838
28924
|
|
|
28925
|
+
const LockedFunction = props => {
|
|
28926
|
+
const {
|
|
28927
|
+
color,
|
|
28928
|
+
strokeStyle,
|
|
28929
|
+
directionalAxis,
|
|
28930
|
+
domain
|
|
28931
|
+
} = props;
|
|
28932
|
+
const plotProps = {
|
|
28933
|
+
color: lockedFigureColors[color],
|
|
28934
|
+
style: strokeStyle,
|
|
28935
|
+
domain
|
|
28936
|
+
};
|
|
28937
|
+
const equation = props.equationParsed || KAS__namespace.parse(props.equation).expr;
|
|
28938
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
28939
|
+
className: "locked-function"
|
|
28940
|
+
}, directionalAxis === "x" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, _extends({
|
|
28941
|
+
y: x => equation.eval({
|
|
28942
|
+
x
|
|
28943
|
+
})
|
|
28944
|
+
}, plotProps)), directionalAxis === "y" && /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfY, _extends({
|
|
28945
|
+
x: y => equation.eval({
|
|
28946
|
+
y
|
|
28947
|
+
})
|
|
28948
|
+
}, plotProps)));
|
|
28949
|
+
};
|
|
28950
|
+
|
|
28839
28951
|
function pathBuilder() {
|
|
28840
28952
|
return new PathBuilder();
|
|
28841
28953
|
}
|
|
@@ -28934,7 +29046,7 @@ function Arrowhead(props) {
|
|
|
28934
29046
|
const [point] = useTransformVectorsToPixels(props.tip);
|
|
28935
29047
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
28936
29048
|
className: "interactive-graph-arrowhead",
|
|
28937
|
-
transform: `translate(${point[
|
|
29049
|
+
transform: `translate(${point[X]} ${point[Y]}) rotate(${props.angle})`
|
|
28938
29050
|
}, /*#__PURE__*/React__namespace.createElement("g", {
|
|
28939
29051
|
transform: "translate(-1.5)"
|
|
28940
29052
|
}, /*#__PURE__*/React__namespace.createElement("path", {
|
|
@@ -29004,10 +29116,10 @@ function SVGLine(props) {
|
|
|
29004
29116
|
testId
|
|
29005
29117
|
} = props;
|
|
29006
29118
|
return /*#__PURE__*/React__namespace.createElement("line", {
|
|
29007
|
-
x1: start[
|
|
29008
|
-
y1: start[
|
|
29009
|
-
x2: end[
|
|
29010
|
-
y2: end[
|
|
29119
|
+
x1: start[X],
|
|
29120
|
+
y1: start[Y],
|
|
29121
|
+
x2: end[X],
|
|
29122
|
+
y2: end[Y],
|
|
29011
29123
|
style: style,
|
|
29012
29124
|
className: className,
|
|
29013
29125
|
"data-testid": testId
|
|
@@ -29092,8 +29204,8 @@ const LockedLine = props => {
|
|
|
29092
29204
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
29093
29205
|
className: kind === "ray" ? "locked-ray" : "locked-line"
|
|
29094
29206
|
}, line, showPoint1 && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
|
|
29095
|
-
x: point1.coord[
|
|
29096
|
-
y: point1.coord[
|
|
29207
|
+
x: point1.coord[X],
|
|
29208
|
+
y: point1.coord[Y],
|
|
29097
29209
|
svgCircleProps: {
|
|
29098
29210
|
style: {
|
|
29099
29211
|
fill: point1.filled ? lockedFigureColors[point1.color] : wonderBlocksTokens.color.white,
|
|
@@ -29102,8 +29214,8 @@ const LockedLine = props => {
|
|
|
29102
29214
|
}
|
|
29103
29215
|
}
|
|
29104
29216
|
}), showPoint2 && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
|
|
29105
|
-
x: point2.coord[
|
|
29106
|
-
y: point2.coord[
|
|
29217
|
+
x: point2.coord[X],
|
|
29218
|
+
y: point2.coord[Y],
|
|
29107
29219
|
svgCircleProps: {
|
|
29108
29220
|
style: {
|
|
29109
29221
|
fill: point2.filled ? lockedFigureColors[point2.color] : wonderBlocksTokens.color.white,
|
|
@@ -29151,8 +29263,8 @@ const LockedPolygon = props => {
|
|
|
29151
29263
|
color: lockedFigureColors[color]
|
|
29152
29264
|
}), showVertices && points.map((point, index) => /*#__PURE__*/React__namespace.createElement(mafs.Point, {
|
|
29153
29265
|
key: `locked-polygon-point-${index}`,
|
|
29154
|
-
x: point[
|
|
29155
|
-
y: point[
|
|
29266
|
+
x: point[X],
|
|
29267
|
+
y: point[Y],
|
|
29156
29268
|
color: lockedFigureColors[color]
|
|
29157
29269
|
})));
|
|
29158
29270
|
};
|
|
@@ -29199,6 +29311,10 @@ const GraphLockedLayer = props => {
|
|
|
29199
29311
|
return /*#__PURE__*/React__namespace.createElement(LockedPolygon, _extends({
|
|
29200
29312
|
key: `polygon-${index}`
|
|
29201
29313
|
}, figure));
|
|
29314
|
+
case "function":
|
|
29315
|
+
return /*#__PURE__*/React__namespace.createElement(LockedFunction, _extends({
|
|
29316
|
+
key: `function-${index}`
|
|
29317
|
+
}, figure));
|
|
29202
29318
|
default:
|
|
29203
29319
|
/**
|
|
29204
29320
|
* Devlopment-time future-proofing: This should
|
|
@@ -29291,24 +29407,18 @@ const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coor
|
|
|
29291
29407
|
coords.map((coord, i) => {
|
|
29292
29408
|
const axisRange = range[i];
|
|
29293
29409
|
if (noSnap) {
|
|
29294
|
-
return axisRange[
|
|
29410
|
+
return axisRange[MIN] + size(axisRange) * coord;
|
|
29295
29411
|
}
|
|
29296
29412
|
const axisStep = step[i];
|
|
29297
|
-
const nSteps = Math.floor((axisRange
|
|
29413
|
+
const nSteps = Math.floor(size(axisRange) / axisStep);
|
|
29298
29414
|
const tick = Math.round(coord * nSteps);
|
|
29299
|
-
return axisRange[
|
|
29415
|
+
return axisRange[MIN] + axisStep * tick;
|
|
29300
29416
|
}));
|
|
29301
29417
|
|
|
29302
29418
|
// same as normalizeCoords in interactive-graph.tsx
|
|
29303
29419
|
const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
|
|
29304
|
-
|
|
29305
|
-
return (coord + ranges[i][1]) / extent;
|
|
29420
|
+
return (coord + ranges[i][1]) / size(ranges[i]);
|
|
29306
29421
|
}));
|
|
29307
|
-
function snap(snapStep, point) {
|
|
29308
|
-
const [requestedX, requestedY] = point;
|
|
29309
|
-
const [snapX, snapY] = snapStep;
|
|
29310
|
-
return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
|
|
29311
|
-
}
|
|
29312
29422
|
|
|
29313
29423
|
// Returns the closest point to the given `point` that is within the graph
|
|
29314
29424
|
// bounds given in `state`.
|
|
@@ -29318,19 +29428,8 @@ function bound$1(_ref) {
|
|
|
29318
29428
|
range,
|
|
29319
29429
|
point
|
|
29320
29430
|
} = _ref;
|
|
29321
|
-
const
|
|
29322
|
-
|
|
29323
|
-
const [[minX, maxX], [minY, maxY]] = range;
|
|
29324
|
-
return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
|
|
29325
|
-
}
|
|
29326
|
-
function clamp(value, min, max) {
|
|
29327
|
-
if (value < min) {
|
|
29328
|
-
return min;
|
|
29329
|
-
}
|
|
29330
|
-
if (value > max) {
|
|
29331
|
-
return max;
|
|
29332
|
-
}
|
|
29333
|
-
return value;
|
|
29431
|
+
const boundingBox = inset(snapStep, range);
|
|
29432
|
+
return clampToBox(boundingBox, point);
|
|
29334
29433
|
}
|
|
29335
29434
|
|
|
29336
29435
|
var isProduction = process.env.NODE_ENV === 'production';
|
|
@@ -29400,8 +29499,8 @@ function useDraggable(args) {
|
|
|
29400
29499
|
metaKey,
|
|
29401
29500
|
shiftKey
|
|
29402
29501
|
} = state;
|
|
29403
|
-
const direction = [yDownDirection[
|
|
29404
|
-
const span = Math.abs(direction[
|
|
29502
|
+
const direction = [yDownDirection[X], -yDownDirection[Y]];
|
|
29503
|
+
const span = Math.abs(direction[X]) ? xSpan : ySpan;
|
|
29405
29504
|
let divisions = 50;
|
|
29406
29505
|
if (altKey || metaKey) {
|
|
29407
29506
|
divisions = 200;
|
|
@@ -29512,8 +29611,10 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
|
|
|
29512
29611
|
})?.[0] ?? "blue";
|
|
29513
29612
|
const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""} ${focusClass(focusBehavior)}`;
|
|
29514
29613
|
const [[x, y]] = useTransformVectorsToPixels(point);
|
|
29515
|
-
|
|
29516
|
-
|
|
29614
|
+
|
|
29615
|
+
// TODO(benchristel): destructure range in one line
|
|
29616
|
+
const [xMin, xMax] = range[X];
|
|
29617
|
+
const [yMin, yMax] = range[Y];
|
|
29517
29618
|
const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
|
|
29518
29619
|
const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
|
|
29519
29620
|
const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
|
|
@@ -29570,7 +29671,7 @@ const MovablePointView = /*#__PURE__*/React.forwardRef((props, hitboxRef) => {
|
|
|
29570
29671
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
|
|
29571
29672
|
autoUpdate: true,
|
|
29572
29673
|
backgroundColor: wbColorName,
|
|
29573
|
-
content: `(${point[
|
|
29674
|
+
content: `(${point[X]}, ${point[Y]})`,
|
|
29574
29675
|
contentStyle: {
|
|
29575
29676
|
color: "white"
|
|
29576
29677
|
}
|
|
@@ -29755,16 +29856,7 @@ function trimRange(range, graphDimensionsInPixels) {
|
|
|
29755
29856
|
const graphUnitsPerPixelY = size(yRange) / pixelsTall;
|
|
29756
29857
|
const graphUnitsToTrimX = pixelsToTrim * graphUnitsPerPixelX;
|
|
29757
29858
|
const graphUnitsToTrimY = pixelsToTrim * graphUnitsPerPixelY;
|
|
29758
|
-
return [
|
|
29759
|
-
}
|
|
29760
|
-
function trim(interval, amount) {
|
|
29761
|
-
if (size(interval) < amount * 2) {
|
|
29762
|
-
return [0, 0];
|
|
29763
|
-
}
|
|
29764
|
-
return [interval[0] + amount, interval[1] - amount];
|
|
29765
|
-
}
|
|
29766
|
-
function size(interval) {
|
|
29767
|
-
return interval[1] - interval[0];
|
|
29859
|
+
return inset([graphUnitsToTrimX, graphUnitsToTrimY], range);
|
|
29768
29860
|
}
|
|
29769
29861
|
|
|
29770
29862
|
const SegmentGraph = props => {
|
|
@@ -29899,7 +29991,7 @@ const SvgDefs = () => /*#__PURE__*/React__default["default"].createElement("defs
|
|
|
29899
29991
|
in: "SourceGraphic"
|
|
29900
29992
|
})));
|
|
29901
29993
|
|
|
29902
|
-
const
|
|
29994
|
+
const PolygonAngle = _ref => {
|
|
29903
29995
|
let {
|
|
29904
29996
|
centerPoint,
|
|
29905
29997
|
endPoints,
|
|
@@ -29927,7 +30019,7 @@ const Angle = _ref => {
|
|
|
29927
30019
|
// Used to calculate the inside of the angle and to create square
|
|
29928
30020
|
const [x3, y3] = mafs.vec.add(centerPoint, mafs.vec.add(mafs.vec.sub([x1, y1], centerPoint), mafs.vec.sub([x2, y2], centerPoint)));
|
|
29929
30021
|
if (!showAngles) {
|
|
29930
|
-
return
|
|
30022
|
+
return isRightPolygonAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
|
|
29931
30023
|
start: [x1, y1],
|
|
29932
30024
|
vertex: [x2, y2],
|
|
29933
30025
|
end: [x3, y3]
|
|
@@ -29964,35 +30056,138 @@ const Angle = _ref => {
|
|
|
29964
30056
|
start: [x1, y1],
|
|
29965
30057
|
vertex: [x2, y2],
|
|
29966
30058
|
end: [x3, y3]
|
|
29967
|
-
}) : /*#__PURE__*/React__namespace.createElement(
|
|
29968
|
-
|
|
29969
|
-
|
|
29970
|
-
fill: "none"
|
|
29971
|
-
})), /*#__PURE__*/React__namespace.createElement(TextLabel, {
|
|
30059
|
+
}) : /*#__PURE__*/React__namespace.createElement(Arc, {
|
|
30060
|
+
arc: arc
|
|
30061
|
+
}), /*#__PURE__*/React__namespace.createElement(TextLabel, {
|
|
29972
30062
|
x: x3,
|
|
29973
30063
|
y: y3,
|
|
29974
30064
|
attach: y3 - centerY > 0 ? "s" : "n",
|
|
29975
30065
|
attachDistance: Math.abs(y3 - centerY) < 0.2 || mafs.vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
|
|
29976
30066
|
}, angleLabel, "\xB0"));
|
|
29977
30067
|
};
|
|
30068
|
+
const Angle = _ref2 => {
|
|
30069
|
+
let {
|
|
30070
|
+
vertex,
|
|
30071
|
+
coords,
|
|
30072
|
+
showAngles,
|
|
30073
|
+
allowReflexAngles,
|
|
30074
|
+
range
|
|
30075
|
+
} = _ref2;
|
|
30076
|
+
// Check if the points are clockwise or not, depending on whether we allow reflex angles
|
|
30077
|
+
const areClockwise = clockwise$1([...coords, vertex]);
|
|
30078
|
+
const shouldReverseCoords = areClockwise && !allowReflexAngles;
|
|
30079
|
+
|
|
30080
|
+
// Reverse the coordinates accordingly to ensure the angle is calculated correctly
|
|
30081
|
+
const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
|
|
30082
|
+
|
|
30083
|
+
// Calculate the angles between the two points
|
|
30084
|
+
const startAngle = findAngle(clockwiseCoords[0], vertex);
|
|
30085
|
+
const endAngle = findAngle(clockwiseCoords[1], vertex);
|
|
30086
|
+
const angle = (startAngle + 360 - endAngle) % 360;
|
|
30087
|
+
|
|
30088
|
+
// Check if the angle is reflexive
|
|
30089
|
+
const isReflexive = angle > 180;
|
|
30090
|
+
|
|
30091
|
+
// Break out the necessary points for the arc calculation
|
|
30092
|
+
const [centerX, centerY] = vertex;
|
|
30093
|
+
const [point1, point2] = clockwiseCoords;
|
|
30094
|
+
const [startX, startY] = point1;
|
|
30095
|
+
const [endX, endY] = point2;
|
|
30096
|
+
const a = mafs.vec.dist(vertex, point1);
|
|
30097
|
+
const b = mafs.vec.dist(vertex, point2);
|
|
30098
|
+
|
|
30099
|
+
// Set the radius of the arc
|
|
30100
|
+
const radius = 2;
|
|
30101
|
+
|
|
30102
|
+
// Calculate the end points of the arc
|
|
30103
|
+
const y1 = centerY + (startY - centerY) / a * radius;
|
|
30104
|
+
const x2 = centerX + (endX - centerX) / b * radius;
|
|
30105
|
+
const x1 = centerX + (startX - centerX) / a * radius;
|
|
30106
|
+
const y2 = centerY + (endY - centerY) / b * radius;
|
|
30107
|
+
|
|
30108
|
+
// Used to calculate the inside of the angle and to create square
|
|
30109
|
+
const [x3, y3] = mafs.vec.add(vertex, mafs.vec.add(mafs.vec.sub([x1, y1], vertex), mafs.vec.sub([x2, y2], vertex)));
|
|
30110
|
+
|
|
30111
|
+
// Determine whether the arc should be drawn outside the angle
|
|
30112
|
+
const isOutside = shouldDrawArcOutside([x3, y3], vertex, range, [[vertex, point1], [vertex, point2]]);
|
|
30113
|
+
|
|
30114
|
+
// Determine the flags for the arc
|
|
30115
|
+
// If the angle is outside OR reflexive, we want to draw a large arc
|
|
30116
|
+
// If the angle is outside AND reflexive, we want to sweep the arc
|
|
30117
|
+
const largeArcFlag = isOutside || isReflexive ? 1 : 0;
|
|
30118
|
+
const sweepFlag = isOutside && isReflexive ? 1 : 0;
|
|
30119
|
+
|
|
30120
|
+
// Create the SVG path for the arc
|
|
30121
|
+
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
30122
|
+
|
|
30123
|
+
// We only ever want to show whole angles
|
|
30124
|
+
const angleLabel = parseFloat(angle.toFixed(0)); // Only want to show whole angles
|
|
30125
|
+
|
|
30126
|
+
// Calculate the text position based on the angle and whether we allow reflex angles
|
|
30127
|
+
// Let's try the angle bisector method to find the midpoint of the arc
|
|
30128
|
+
const [textX, textY] = calculateBisectorPoint(point1, point2, vertex, isReflexive, allowReflexAngles, radius);
|
|
30129
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("defs", null, /*#__PURE__*/React__namespace.createElement("filter", {
|
|
30130
|
+
id: "background",
|
|
30131
|
+
x: "-5%",
|
|
30132
|
+
width: "110%",
|
|
30133
|
+
y: "0%",
|
|
30134
|
+
height: "100%"
|
|
30135
|
+
}, /*#__PURE__*/React__namespace.createElement("feFlood", {
|
|
30136
|
+
floodColor: "#FFF",
|
|
30137
|
+
floodOpacity: "0.5"
|
|
30138
|
+
}), /*#__PURE__*/React__namespace.createElement("feComposite", {
|
|
30139
|
+
operator: "over",
|
|
30140
|
+
in: "SourceGraphic"
|
|
30141
|
+
}))), isRightAngle(angle) ? /*#__PURE__*/React__namespace.createElement(RightAngleSquare, {
|
|
30142
|
+
start: [x1, y1],
|
|
30143
|
+
vertex: [x2, y2],
|
|
30144
|
+
end: [x3, y3],
|
|
30145
|
+
className: "arc-right-angle"
|
|
30146
|
+
}) : /*#__PURE__*/React__namespace.createElement(Arc, {
|
|
30147
|
+
arc: arc,
|
|
30148
|
+
className: "angle-arc"
|
|
30149
|
+
}), showAngles && /*#__PURE__*/React__namespace.createElement(TextLabel, {
|
|
30150
|
+
x: textX,
|
|
30151
|
+
y: textY,
|
|
30152
|
+
color: wonderBlocksTokens.color.blue
|
|
30153
|
+
}, angleLabel, "\xB0"));
|
|
30154
|
+
};
|
|
29978
30155
|
|
|
29979
30156
|
/**
|
|
29980
30157
|
* This is broken out into its own component so that it can be used for an early return
|
|
29981
30158
|
* (see line 55 or https://github.com/Khan/perseus/blob/84bbd882b11d16871e1b813a0b901f3b903d5479/packages/perseus/src/widgets/interactive-graphs/graphs/components/angle.tsx#L53-L57)
|
|
29982
30159
|
*/
|
|
29983
|
-
const RightAngleSquare =
|
|
30160
|
+
const RightAngleSquare = _ref3 => {
|
|
29984
30161
|
let {
|
|
29985
30162
|
start: [x1, y1],
|
|
29986
30163
|
vertex: [x2, y2],
|
|
29987
|
-
end: [x3, y3]
|
|
29988
|
-
|
|
30164
|
+
end: [x3, y3],
|
|
30165
|
+
className
|
|
30166
|
+
} = _ref3;
|
|
29989
30167
|
return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
|
|
29990
30168
|
d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
|
|
29991
30169
|
strokeWidth: 0.02,
|
|
29992
|
-
fill: "none"
|
|
30170
|
+
fill: "none",
|
|
30171
|
+
className: className
|
|
29993
30172
|
}));
|
|
29994
30173
|
};
|
|
29995
|
-
|
|
30174
|
+
|
|
30175
|
+
// We're conditionally adding the class name here so that we can style the arc differently
|
|
30176
|
+
// based on whether it's an angle or a polygon angle
|
|
30177
|
+
const Arc = _ref4 => {
|
|
30178
|
+
let {
|
|
30179
|
+
arc,
|
|
30180
|
+
className
|
|
30181
|
+
} = _ref4;
|
|
30182
|
+
return /*#__PURE__*/React__namespace.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React__namespace.createElement("path", {
|
|
30183
|
+
d: arc,
|
|
30184
|
+
strokeWidth: 0.02,
|
|
30185
|
+
fill: "none",
|
|
30186
|
+
className: className
|
|
30187
|
+
}));
|
|
30188
|
+
};
|
|
30189
|
+
const isRightPolygonAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
|
|
30190
|
+
const isRightAngle = angle => Math.round(angle) === 90;
|
|
29996
30191
|
|
|
29997
30192
|
/**
|
|
29998
30193
|
* Determines if an angle is an inside (false) or outside (true) angle.
|
|
@@ -30014,9 +30209,9 @@ const shouldDrawArcOutside = (midpoint, vertex, range, polygonLines) => {
|
|
|
30014
30209
|
|
|
30015
30210
|
// https://stackoverflow.com/a/24392281/7347484
|
|
30016
30211
|
// The "intersects" function in geometry doesn't seem to work for this use case
|
|
30017
|
-
const linesIntersect = (
|
|
30018
|
-
let [[a, b], [c, d]] =
|
|
30019
|
-
let [[p, q], [r, s]] =
|
|
30212
|
+
const linesIntersect = (_ref5, _ref6) => {
|
|
30213
|
+
let [[a, b], [c, d]] = _ref5;
|
|
30214
|
+
let [[p, q], [r, s]] = _ref6;
|
|
30020
30215
|
const determinant = (c - a) * (s - q) - (r - p) * (d - b);
|
|
30021
30216
|
if (determinant === 0) {
|
|
30022
30217
|
return false;
|
|
@@ -30028,6 +30223,75 @@ const linesIntersect = (_ref3, _ref4) => {
|
|
|
30028
30223
|
};
|
|
30029
30224
|
const isEven = n => n % 2 === 0;
|
|
30030
30225
|
|
|
30226
|
+
// This function calculates the bisector point of an angle formed by two points
|
|
30227
|
+
// and a vertex. It is used to position the angle label so that it is always
|
|
30228
|
+
// outside the arc at the exact midpoint of the angle.
|
|
30229
|
+
// Reflex angles make use of polar coordinates to determine the correct direction
|
|
30230
|
+
function calculateBisectorPoint(point1, point2, vertex, isReflex, allowReflex, arcRadius) {
|
|
30231
|
+
const [originX, originY] = vertex;
|
|
30232
|
+
const [x1, y1] = point1;
|
|
30233
|
+
const [x2, y2] = point2;
|
|
30234
|
+
|
|
30235
|
+
// Convert the Cartesian Coordinates to vectors
|
|
30236
|
+
const vectorA = [x1 - originX, y1 - originY];
|
|
30237
|
+
const vectorB = [x2 - originX, y2 - originY];
|
|
30238
|
+
|
|
30239
|
+
// Calculate the average angle of the vectors
|
|
30240
|
+
// so that we can point the bisector in the correct direction using polar coordinates
|
|
30241
|
+
const angleA = Math.atan2(vectorA[1], vectorA[0]);
|
|
30242
|
+
const angleB = Math.atan2(vectorB[1], vectorB[0]);
|
|
30243
|
+
|
|
30244
|
+
// Calculate the average of these two angles
|
|
30245
|
+
let averageAngle = (angleA + angleB) / 2;
|
|
30246
|
+
const angleRadians = Math.abs(angleA - angleB);
|
|
30247
|
+
|
|
30248
|
+
// Adjust the average angle to point in the correct direction
|
|
30249
|
+
// and to account for reflex angles and a bug with 180 degrees
|
|
30250
|
+
// that occurs due to our angleOffsetDegrees option in the graph
|
|
30251
|
+
if (allowReflex) {
|
|
30252
|
+
// If the resulting angles are less than 180 degrees,
|
|
30253
|
+
// or if the user has flipped the points on the interactive element
|
|
30254
|
+
// ensure that we have the correct direction for the reflex angle
|
|
30255
|
+
if (angleRadians <= Math.PI && isReflex || angleB > angleA) {
|
|
30256
|
+
averageAngle += Math.PI; // Add 180 degrees
|
|
30257
|
+
}
|
|
30258
|
+
} else {
|
|
30259
|
+
// If the resulting angles are greater than 180 degrees, adjust
|
|
30260
|
+
// to get the correct angle, as we already know that the angle is not reflexive
|
|
30261
|
+
// This fixes a bug that occurs at 180 degrees due to our angleOffsetDegrees option in the graph
|
|
30262
|
+
if (angleRadians > Math.PI) {
|
|
30263
|
+
averageAngle -= Math.PI; // Subract 180 degrees
|
|
30264
|
+
}
|
|
30265
|
+
}
|
|
30266
|
+
|
|
30267
|
+
// Convert the average angle back to Cartesian coordinates
|
|
30268
|
+
const sum = [Math.cos(averageAngle), Math.sin(averageAngle)];
|
|
30269
|
+
|
|
30270
|
+
// Calculate the magnitude of the sum to normalize it
|
|
30271
|
+
const sumMagnitude = Math.sqrt(sum[0] ** 2 + sum[1] ** 2);
|
|
30272
|
+
const bisectorDirection = [sum[0] / sumMagnitude, sum[1] / sumMagnitude];
|
|
30273
|
+
|
|
30274
|
+
// Calculate the initial distance of the bisector direction from the origin
|
|
30275
|
+
const initialDistance = Math.sqrt(bisectorDirection[0] ** 2 + bisectorDirection[1] ** 2);
|
|
30276
|
+
|
|
30277
|
+
// Determine the minimum radius to ensure that the text is always outside the arc
|
|
30278
|
+
const requiredDistance = arcRadius * 1.75;
|
|
30279
|
+
let radius = requiredDistance / initialDistance;
|
|
30280
|
+
|
|
30281
|
+
// Apply radius only if the initial distance is less than required
|
|
30282
|
+
if (initialDistance >= requiredDistance) {
|
|
30283
|
+
radius = 1; // Use the unit vector as is
|
|
30284
|
+
}
|
|
30285
|
+
|
|
30286
|
+
// Scale the bisector direction by the radius
|
|
30287
|
+
const bisectorPoint = [bisectorDirection[0] * radius, bisectorDirection[1] * radius];
|
|
30288
|
+
|
|
30289
|
+
// Add the vertex to the bisector point to get the final position
|
|
30290
|
+
// to ensure that the angle label moves with the interactive element
|
|
30291
|
+
const scaledBisector = mafs.vec.add(bisectorPoint, vertex);
|
|
30292
|
+
return scaledBisector;
|
|
30293
|
+
}
|
|
30294
|
+
|
|
30031
30295
|
const StyledMovablePoint = props => {
|
|
30032
30296
|
const {
|
|
30033
30297
|
snapStep
|
|
@@ -30108,7 +30372,7 @@ const PolygonGraph = props => {
|
|
|
30108
30372
|
if (!pt1 || !pt2) {
|
|
30109
30373
|
return null;
|
|
30110
30374
|
}
|
|
30111
|
-
return /*#__PURE__*/React__namespace.createElement(
|
|
30375
|
+
return /*#__PURE__*/React__namespace.createElement(PolygonAngle, {
|
|
30112
30376
|
key: "angle-" + i,
|
|
30113
30377
|
centerPoint: point,
|
|
30114
30378
|
endPoints: [pt1, pt2],
|
|
@@ -30212,6 +30476,13 @@ function getGradableGraph(state, initialGraph) {
|
|
|
30212
30476
|
coords: state.coords
|
|
30213
30477
|
};
|
|
30214
30478
|
}
|
|
30479
|
+
if (state.type === "angle" && initialGraph.type === "angle") {
|
|
30480
|
+
return {
|
|
30481
|
+
...initialGraph,
|
|
30482
|
+
coords: state.coords,
|
|
30483
|
+
allowReflexAngles: state.allowReflexAngles
|
|
30484
|
+
};
|
|
30485
|
+
}
|
|
30215
30486
|
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
30216
30487
|
}
|
|
30217
30488
|
|
|
@@ -30274,16 +30545,16 @@ function MovableCircle(props) {
|
|
|
30274
30545
|
className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
|
|
30275
30546
|
}, /*#__PURE__*/React__namespace.createElement("ellipse", {
|
|
30276
30547
|
className: "focus-ring",
|
|
30277
|
-
cx: centerPx[
|
|
30278
|
-
cy: centerPx[
|
|
30279
|
-
rx: radiiPx[
|
|
30280
|
-
ry: radiiPx[
|
|
30548
|
+
cx: centerPx[X],
|
|
30549
|
+
cy: centerPx[Y],
|
|
30550
|
+
rx: radiiPx[X] + 3,
|
|
30551
|
+
ry: radiiPx[Y] + 3
|
|
30281
30552
|
}), /*#__PURE__*/React__namespace.createElement("ellipse", {
|
|
30282
30553
|
className: "circle",
|
|
30283
|
-
cx: centerPx[
|
|
30284
|
-
cy: centerPx[
|
|
30285
|
-
rx: radiiPx[
|
|
30286
|
-
ry: radiiPx[
|
|
30554
|
+
cx: centerPx[X],
|
|
30555
|
+
cy: centerPx[Y],
|
|
30556
|
+
rx: radiiPx[X],
|
|
30557
|
+
ry: radiiPx[Y]
|
|
30287
30558
|
}), /*#__PURE__*/React__namespace.createElement(DragHandle, {
|
|
30288
30559
|
center: center
|
|
30289
30560
|
}));
|
|
@@ -30299,10 +30570,10 @@ function DragHandle(props) {
|
|
|
30299
30570
|
const topLeft = mafs.vec.sub(centerPx, mafs.vec.scale(dragHandleDimensions, 0.5));
|
|
30300
30571
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement("rect", {
|
|
30301
30572
|
className: "movable-circle-handle",
|
|
30302
|
-
x: topLeft[
|
|
30303
|
-
y: topLeft[
|
|
30304
|
-
width: dragHandleDimensions[
|
|
30305
|
-
height: dragHandleDimensions[
|
|
30573
|
+
x: topLeft[X],
|
|
30574
|
+
y: topLeft[Y],
|
|
30575
|
+
width: dragHandleDimensions[X],
|
|
30576
|
+
height: dragHandleDimensions[Y],
|
|
30306
30577
|
rx: cornerRadius,
|
|
30307
30578
|
ry: cornerRadius
|
|
30308
30579
|
}), dragHandleDotPositions.map(offsetPx => {
|
|
@@ -30437,15 +30708,15 @@ const getSinusoidCoefficients = coords => {
|
|
|
30437
30708
|
const p2 = coords[1];
|
|
30438
30709
|
|
|
30439
30710
|
// If the x-coordinates are the same, we are unable to calculate the coefficients
|
|
30440
|
-
if (p2[
|
|
30711
|
+
if (p2[X] === p1[X]) {
|
|
30441
30712
|
return;
|
|
30442
30713
|
}
|
|
30443
30714
|
|
|
30444
30715
|
// Resulting coefficients are canonical for this sine curve
|
|
30445
|
-
const amplitude = p2[
|
|
30446
|
-
const angularFrequency = Math.PI / (2 * (p2[
|
|
30447
|
-
const phase = p1[
|
|
30448
|
-
const verticalOffset = p1[
|
|
30716
|
+
const amplitude = p2[Y] - p1[Y];
|
|
30717
|
+
const angularFrequency = Math.PI / (2 * (p2[X] - p1[X]));
|
|
30718
|
+
const phase = p1[X] * angularFrequency;
|
|
30719
|
+
const verticalOffset = p1[Y];
|
|
30449
30720
|
return {
|
|
30450
30721
|
amplitude,
|
|
30451
30722
|
angularFrequency,
|
|
@@ -30454,6 +30725,75 @@ const getSinusoidCoefficients = coords => {
|
|
|
30454
30725
|
};
|
|
30455
30726
|
};
|
|
30456
30727
|
|
|
30728
|
+
function AngleGraph(props) {
|
|
30729
|
+
const {
|
|
30730
|
+
dispatch,
|
|
30731
|
+
graphState
|
|
30732
|
+
} = props;
|
|
30733
|
+
const {
|
|
30734
|
+
graphDimensionsInPixels
|
|
30735
|
+
} = useGraphConfig();
|
|
30736
|
+
const {
|
|
30737
|
+
coords,
|
|
30738
|
+
showAngles,
|
|
30739
|
+
range,
|
|
30740
|
+
allowReflexAngles,
|
|
30741
|
+
angleOffsetDeg,
|
|
30742
|
+
snapDegrees
|
|
30743
|
+
} = graphState;
|
|
30744
|
+
|
|
30745
|
+
// Break the coords into the two end points and the center point
|
|
30746
|
+
const endPoints = [coords[0], coords[2]];
|
|
30747
|
+
const centerPoint = coords[1];
|
|
30748
|
+
|
|
30749
|
+
// Convert the vectors to pixels for rendering the svg lines
|
|
30750
|
+
const angleLines = [[centerPoint, endPoints[0]], [centerPoint, endPoints[1]]];
|
|
30751
|
+
const linePixelCoords = [useTransformVectorsToPixels(centerPoint, endPoints[0]), useTransformVectorsToPixels(centerPoint, endPoints[1])];
|
|
30752
|
+
|
|
30753
|
+
// Create the SVG lines for the angle
|
|
30754
|
+
const svgLines = linePixelCoords.map((_ref, i) => {
|
|
30755
|
+
let [startPtPx, endPtPx] = _ref;
|
|
30756
|
+
const trimmedRange = trimRange(range, graphDimensionsInPixels);
|
|
30757
|
+
const endExtend = getIntersectionOfRayWithBox(angleLines[i][0], angleLines[i][1], trimmedRange);
|
|
30758
|
+
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
30759
|
+
key: `line-${i}`
|
|
30760
|
+
}, /*#__PURE__*/React__namespace.createElement(SVGLine, {
|
|
30761
|
+
start: startPtPx,
|
|
30762
|
+
end: endPtPx,
|
|
30763
|
+
style: {
|
|
30764
|
+
stroke: "var(--movable-line-stroke-color)",
|
|
30765
|
+
strokeWidth: "var(--movable-line-stroke-weight)"
|
|
30766
|
+
}
|
|
30767
|
+
}), /*#__PURE__*/React__namespace.createElement(Vector, {
|
|
30768
|
+
tail: angleLines[i][1],
|
|
30769
|
+
tip: endExtend,
|
|
30770
|
+
color: "var(--movable-line-stroke-color)"
|
|
30771
|
+
}));
|
|
30772
|
+
});
|
|
30773
|
+
|
|
30774
|
+
// Create the angle indicator parameters
|
|
30775
|
+
const angleParams = {
|
|
30776
|
+
vertex: centerPoint,
|
|
30777
|
+
coords: endPoints,
|
|
30778
|
+
allowReflexAngles: allowReflexAngles || false,
|
|
30779
|
+
// Whether to allow reflex angles or not
|
|
30780
|
+
angleOffsetDeg: angleOffsetDeg || 0,
|
|
30781
|
+
// The angle offset from the x-axis
|
|
30782
|
+
snapDegrees: snapDegrees || 1,
|
|
30783
|
+
// The multiple of degrees to snap to
|
|
30784
|
+
range: range,
|
|
30785
|
+
showAngles: showAngles || false // Whether to show the angle or not
|
|
30786
|
+
};
|
|
30787
|
+
|
|
30788
|
+
// Render the lines, angle, and then movable points
|
|
30789
|
+
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, svgLines, /*#__PURE__*/React__namespace.createElement(Angle, angleParams), coords.map((point, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
|
|
30790
|
+
key: "point-" + i,
|
|
30791
|
+
snapTo: "angles",
|
|
30792
|
+
point: point,
|
|
30793
|
+
onMove: destination => dispatch(movePoint(i, destination))
|
|
30794
|
+
})));
|
|
30795
|
+
}
|
|
30796
|
+
|
|
30457
30797
|
// Universal padding for the axis ticks to ensure that the labels
|
|
30458
30798
|
// have enough space to render without overlapping the tick lines
|
|
30459
30799
|
const tickPadding = 5;
|
|
@@ -30463,10 +30803,12 @@ const AxisTickLabels = () => {
|
|
|
30463
30803
|
tickStep,
|
|
30464
30804
|
range
|
|
30465
30805
|
} = graphConfig;
|
|
30466
|
-
|
|
30467
|
-
|
|
30468
|
-
const
|
|
30469
|
-
const
|
|
30806
|
+
|
|
30807
|
+
// TODO(benchristel): use destructuring here
|
|
30808
|
+
const [xMin, xMax] = range[X];
|
|
30809
|
+
const [yMin, yMax] = range[Y];
|
|
30810
|
+
const yTickStep = tickStep[Y];
|
|
30811
|
+
const xTickStep = tickStep[X];
|
|
30470
30812
|
const yGridTicks = generateTickLocations$1(yTickStep, yMin, yMax);
|
|
30471
30813
|
const xGridTicks = generateTickLocations$1(xTickStep, xMin, xMax);
|
|
30472
30814
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
@@ -30748,13 +31090,13 @@ const YGridTick = _ref => {
|
|
|
30748
31090
|
|
|
30749
31091
|
// If the graph is zoomed in, we want to make sure the ticks are still visible
|
|
30750
31092
|
// even if they are outside the graph's range.
|
|
30751
|
-
if (graphInfo.range[
|
|
31093
|
+
if (graphInfo.range[X][MIN] > 0) {
|
|
30752
31094
|
// If the graph is on the positive side of the x-axis, lock the ticks to the left side of the graph
|
|
30753
|
-
xPointOnAxis = graphInfo.range[
|
|
31095
|
+
xPointOnAxis = graphInfo.range[X][MIN];
|
|
30754
31096
|
}
|
|
30755
|
-
if (graphInfo.range[
|
|
31097
|
+
if (graphInfo.range[X][MAX] < 0) {
|
|
30756
31098
|
// If the graph is on the negative side of the x-axis, lock the ticks to the right side of the graph
|
|
30757
|
-
xPointOnAxis = graphInfo.range[
|
|
31099
|
+
xPointOnAxis = graphInfo.range[X][MAX];
|
|
30758
31100
|
}
|
|
30759
31101
|
const pointOnAxis = [xPointOnAxis, y];
|
|
30760
31102
|
const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
|
|
@@ -30789,13 +31131,13 @@ const XGridTick = _ref2 => {
|
|
|
30789
31131
|
let yPointOnAxis = 0;
|
|
30790
31132
|
// If the graph is zoomed in, we want to make sure the ticks are still visible
|
|
30791
31133
|
// even if they are outside the graph's range.
|
|
30792
|
-
if (graphInfo.range[
|
|
31134
|
+
if (graphInfo.range[Y][MIN] > 0) {
|
|
30793
31135
|
// If the graph is on the positive side of the y-axis, lock the ticks to the top of the graph
|
|
30794
|
-
yPointOnAxis = graphInfo.range[
|
|
31136
|
+
yPointOnAxis = graphInfo.range[Y][MIN];
|
|
30795
31137
|
}
|
|
30796
|
-
if (graphInfo.range[
|
|
31138
|
+
if (graphInfo.range[Y][MAX] < 0) {
|
|
30797
31139
|
// If the graph is on the negative side of the x-axis, lock the ticks to the bottom of the graph
|
|
30798
|
-
yPointOnAxis = graphInfo.range[
|
|
31140
|
+
yPointOnAxis = graphInfo.range[Y][MAX];
|
|
30799
31141
|
}
|
|
30800
31142
|
const pointOnAxis = [x, yPointOnAxis];
|
|
30801
31143
|
const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
|
|
@@ -30850,10 +31192,12 @@ const AxisTicks = () => {
|
|
|
30850
31192
|
width,
|
|
30851
31193
|
height
|
|
30852
31194
|
};
|
|
30853
|
-
|
|
30854
|
-
|
|
30855
|
-
const
|
|
30856
|
-
const
|
|
31195
|
+
|
|
31196
|
+
// TODO(benchristel): destructure these in one line
|
|
31197
|
+
const [xMin, xMax] = range[X];
|
|
31198
|
+
const [yMin, yMax] = range[Y];
|
|
31199
|
+
const yTickStep = tickStep[Y];
|
|
31200
|
+
const xTickStep = tickStep[X];
|
|
30857
31201
|
const yGridTicks = generateTickLocations(yTickStep, yMin, yMax);
|
|
30858
31202
|
const xGridTicks = generateTickLocations(xTickStep, xMin, xMax);
|
|
30859
31203
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
@@ -30892,8 +31236,8 @@ const axisOptions = (props, axisIndex) => {
|
|
|
30892
31236
|
};
|
|
30893
31237
|
const Grid = props => {
|
|
30894
31238
|
return props.markings === "none" ? null : /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Coordinates.Cartesian, {
|
|
30895
|
-
xAxis: axisOptions(props,
|
|
30896
|
-
yAxis: axisOptions(props,
|
|
31239
|
+
xAxis: axisOptions(props, X),
|
|
31240
|
+
yAxis: axisOptions(props, Y)
|
|
30897
31241
|
}),
|
|
30898
31242
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
30899
31243
|
props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)));
|
|
@@ -31501,7 +31845,7 @@ const LegacyGrid = _ref => {
|
|
|
31501
31845
|
height
|
|
31502
31846
|
} = backgroundImage ?? {};
|
|
31503
31847
|
if (url && typeof url === "string") {
|
|
31504
|
-
const scale = box[
|
|
31848
|
+
const scale = box[X] / interactiveSizes$1.defaultBoxSize;
|
|
31505
31849
|
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
31506
31850
|
style: {
|
|
31507
31851
|
position: "absolute",
|
|
@@ -31569,9 +31913,9 @@ function Protractor() {
|
|
|
31569
31913
|
const angle = calculateAngleInDegrees(rotationHandleOffset) - calculateAngleInDegrees(centerToRotationHandle);
|
|
31570
31914
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
31571
31915
|
ref: draggableRef,
|
|
31572
|
-
transform: `translate(${topLeftPx[
|
|
31916
|
+
transform: `translate(${topLeftPx[X]}, ${topLeftPx[Y]}), rotate(${angle})`,
|
|
31573
31917
|
style: {
|
|
31574
|
-
transformOrigin: `${-centerToTopLeft[
|
|
31918
|
+
transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`
|
|
31575
31919
|
}
|
|
31576
31920
|
}, /*#__PURE__*/React__namespace.createElement("image", {
|
|
31577
31921
|
href: protractorImage
|
|
@@ -31745,7 +32089,19 @@ function initializeGraphState(params) {
|
|
|
31745
32089
|
coords: getSinusoidCoords(graph, range, step)
|
|
31746
32090
|
};
|
|
31747
32091
|
case "angle":
|
|
31748
|
-
|
|
32092
|
+
return {
|
|
32093
|
+
...shared,
|
|
32094
|
+
type: graph.type,
|
|
32095
|
+
showAngles: Boolean(graph.showAngles),
|
|
32096
|
+
coords: getAngleCoords({
|
|
32097
|
+
graph,
|
|
32098
|
+
range,
|
|
32099
|
+
step
|
|
32100
|
+
}),
|
|
32101
|
+
angleOffsetDeg: Number(graph.angleOffsetDeg),
|
|
32102
|
+
allowReflexAngles: Boolean(graph.allowReflexAngles),
|
|
32103
|
+
snapDegrees: Number(graph.snapDegrees)
|
|
32104
|
+
};
|
|
31749
32105
|
default:
|
|
31750
32106
|
throw new wonderStuffCore.UnreachableCaseError(graph);
|
|
31751
32107
|
}
|
|
@@ -31877,6 +32233,43 @@ function getCircleCoords(graph) {
|
|
|
31877
32233
|
radiusPoint: [2, 0]
|
|
31878
32234
|
};
|
|
31879
32235
|
}
|
|
32236
|
+
const getAngleCoords = params => {
|
|
32237
|
+
const {
|
|
32238
|
+
graph,
|
|
32239
|
+
range,
|
|
32240
|
+
step
|
|
32241
|
+
} = params;
|
|
32242
|
+
if (graph.coords) {
|
|
32243
|
+
return graph.coords;
|
|
32244
|
+
}
|
|
32245
|
+
const {
|
|
32246
|
+
snapDegrees,
|
|
32247
|
+
angleOffsetDeg
|
|
32248
|
+
} = graph;
|
|
32249
|
+
const snap = snapDegrees || 1;
|
|
32250
|
+
let angle = snap;
|
|
32251
|
+
while (angle < 20) {
|
|
32252
|
+
angle += snap;
|
|
32253
|
+
}
|
|
32254
|
+
angle = angle * Math.PI / 180;
|
|
32255
|
+
const offset = (angleOffsetDeg || 0) * Math.PI / 180;
|
|
32256
|
+
let defaultCoords = [[0.85, 0.5], [0.5, 0.5]];
|
|
32257
|
+
defaultCoords = normalizePoints(range, step, defaultCoords, true);
|
|
32258
|
+
|
|
32259
|
+
// @ts-expect-error - TS2345 - Argument of type 'number[]' is not assignable to parameter of type 'readonly Coord[]'. | TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
|
|
32260
|
+
const radius = magnitude(vector(...defaultCoords));
|
|
32261
|
+
|
|
32262
|
+
// We're adding a placeholder for the third point to appease ts and so that we
|
|
32263
|
+
// can calculate it after we've adjusted the first point by the angleOffsetDeg
|
|
32264
|
+
const coords = [...defaultCoords, [0, 0]];
|
|
32265
|
+
|
|
32266
|
+
// Adjust the lower point by angleOffsetDeg degrees
|
|
32267
|
+
coords[0] = [coords[1][0] + radius * Math.cos(offset), coords[1][1] + radius * Math.sin(offset)];
|
|
32268
|
+
// Position the upper point angle radians from the
|
|
32269
|
+
// lower point
|
|
32270
|
+
coords[2] = [coords[1][0] + radius * Math.cos(angle + offset), coords[1][1] + radius * Math.sin(angle + offset)];
|
|
32271
|
+
return coords;
|
|
32272
|
+
};
|
|
31880
32273
|
|
|
31881
32274
|
function interactiveGraphReducer(state, action) {
|
|
31882
32275
|
switch (action.type) {
|
|
@@ -31940,6 +32333,7 @@ function doMoveControlPoint(state, action) {
|
|
|
31940
32333
|
coords: newCoords
|
|
31941
32334
|
};
|
|
31942
32335
|
}
|
|
32336
|
+
case "angle":
|
|
31943
32337
|
case "circle":
|
|
31944
32338
|
throw new Error("FIXME implement circle reducer");
|
|
31945
32339
|
case "point":
|
|
@@ -32044,12 +32438,32 @@ function doMoveAll(state, action) {
|
|
|
32044
32438
|
}
|
|
32045
32439
|
function doMovePoint(state, action) {
|
|
32046
32440
|
switch (state.type) {
|
|
32441
|
+
case "angle":
|
|
32442
|
+
// If the index is 1, we are moving the vertex of the angle,
|
|
32443
|
+
// which will move the other two points as well
|
|
32444
|
+
if (action.index === 1) {
|
|
32445
|
+
const updatedCoords = boundAndSnapAngleVertex(state, action);
|
|
32446
|
+
return {
|
|
32447
|
+
...state,
|
|
32448
|
+
hasBeenInteractedWith: true,
|
|
32449
|
+
coords: updatedCoords
|
|
32450
|
+
};
|
|
32451
|
+
}
|
|
32452
|
+
return {
|
|
32453
|
+
...state,
|
|
32454
|
+
hasBeenInteractedWith: true,
|
|
32455
|
+
coords: setAtIndex({
|
|
32456
|
+
array: state.coords,
|
|
32457
|
+
index: action.index,
|
|
32458
|
+
newValue: boundAndSnapAngleEndPoints(action.destination, state, action.index)
|
|
32459
|
+
})
|
|
32460
|
+
};
|
|
32047
32461
|
case "polygon":
|
|
32048
32462
|
let newValue;
|
|
32049
32463
|
if (state.snapTo === "sides") {
|
|
32050
32464
|
newValue = boundAndSnapToSides(action.destination, state, action.index);
|
|
32051
32465
|
} else if (state.snapTo === "angles") {
|
|
32052
|
-
newValue =
|
|
32466
|
+
newValue = boundAndSnapToPolygonAngle(action.destination, state, action.index);
|
|
32053
32467
|
} else {
|
|
32054
32468
|
newValue = boundAndSnapToGrid(action.destination, state);
|
|
32055
32469
|
}
|
|
@@ -32090,7 +32504,7 @@ function doMovePoint(state, action) {
|
|
|
32090
32504
|
// vertical line. If they are, then we don't want to move the point
|
|
32091
32505
|
const newCoords = [...state.coords];
|
|
32092
32506
|
newCoords[action.index] = boundDestination;
|
|
32093
|
-
if (newCoords[0][
|
|
32507
|
+
if (newCoords[0][X] === newCoords[1][X]) {
|
|
32094
32508
|
return state;
|
|
32095
32509
|
}
|
|
32096
32510
|
return {
|
|
@@ -32150,13 +32564,13 @@ function doMoveCenter(state, action) {
|
|
|
32150
32564
|
// if it otherwise would be off the chart
|
|
32151
32565
|
// ex: if the handle is on the right and we move the center
|
|
32152
32566
|
// to the rightmost position, move the handle to the left
|
|
32153
|
-
const [xMin, xMax] = state.range[
|
|
32567
|
+
const [xMin, xMax] = state.range[X];
|
|
32154
32568
|
const [radX] = newRadiusPoint;
|
|
32155
32569
|
if (radX < xMin || radX > xMax) {
|
|
32156
|
-
const xJumpDist = (radX - constrainedCenter[
|
|
32570
|
+
const xJumpDist = (radX - constrainedCenter[X]) * 2;
|
|
32157
32571
|
const possibleNewX = radX - xJumpDist;
|
|
32158
32572
|
if (possibleNewX >= xMin && possibleNewX <= xMax) {
|
|
32159
|
-
newRadiusPoint[
|
|
32573
|
+
newRadiusPoint[X] = possibleNewX;
|
|
32160
32574
|
}
|
|
32161
32575
|
}
|
|
32162
32576
|
return {
|
|
@@ -32174,11 +32588,11 @@ function doMoveRadiusPoint(state, action) {
|
|
|
32174
32588
|
switch (state.type) {
|
|
32175
32589
|
case "circle":
|
|
32176
32590
|
{
|
|
32177
|
-
const [xMin, xMax] = state.range[
|
|
32591
|
+
const [xMin, xMax] = state.range[X];
|
|
32178
32592
|
const nextRadiusPoint = [
|
|
32179
32593
|
// Constrain to graph range
|
|
32180
32594
|
// The +0 is to convert -0 to +0
|
|
32181
|
-
|
|
32595
|
+
clamp(action.destination[X] + 0, xMin, xMax), state.center[1]];
|
|
32182
32596
|
if (___default["default"].isEqual(nextRadiusPoint, state.center)) {
|
|
32183
32597
|
return state;
|
|
32184
32598
|
}
|
|
@@ -32216,10 +32630,10 @@ function doChangeRange(state, action) {
|
|
|
32216
32630
|
}
|
|
32217
32631
|
const getDeltaVertex = (maxMoves, minMoves, delta) => {
|
|
32218
32632
|
const [deltaX, deltaY] = delta;
|
|
32219
|
-
const maxXMove = Math.min(...maxMoves.map(move => move[
|
|
32220
|
-
const maxYMove = Math.min(...maxMoves.map(move => move[
|
|
32221
|
-
const minXMove = Math.max(...minMoves.map(move => move[
|
|
32222
|
-
const minYMove = Math.max(...minMoves.map(move => move[
|
|
32633
|
+
const maxXMove = Math.min(...maxMoves.map(move => move[X]));
|
|
32634
|
+
const maxYMove = Math.min(...maxMoves.map(move => move[Y]));
|
|
32635
|
+
const minXMove = Math.max(...minMoves.map(move => move[X]));
|
|
32636
|
+
const minYMove = Math.max(...minMoves.map(move => move[Y]));
|
|
32223
32637
|
const dx = clamp(deltaX, minXMove, maxXMove);
|
|
32224
32638
|
const dy = clamp(deltaY, minYMove, maxYMove);
|
|
32225
32639
|
return [dx, dy];
|
|
@@ -32253,11 +32667,130 @@ function boundAndSnapToGrid(point, _ref) {
|
|
|
32253
32667
|
point
|
|
32254
32668
|
}));
|
|
32255
32669
|
}
|
|
32256
|
-
function
|
|
32670
|
+
function boundAndSnapAngleVertex(_ref2, _ref3) {
|
|
32257
32671
|
let {
|
|
32258
32672
|
range,
|
|
32259
|
-
coords
|
|
32673
|
+
coords,
|
|
32674
|
+
snapStep
|
|
32260
32675
|
} = _ref2;
|
|
32676
|
+
let {
|
|
32677
|
+
destination
|
|
32678
|
+
} = _ref3;
|
|
32679
|
+
// Needed to prevent updating the original coords before the checks for
|
|
32680
|
+
// degenerate triangles and overlapping sides
|
|
32681
|
+
const coordsCopy = [...coords];
|
|
32682
|
+
|
|
32683
|
+
// Get the current and upcoming positions of the vertex
|
|
32684
|
+
const startingVertex = coordsCopy[1];
|
|
32685
|
+
const newVertex = bound$1({
|
|
32686
|
+
snapStep,
|
|
32687
|
+
range,
|
|
32688
|
+
point: snap(snapStep, destination)
|
|
32689
|
+
});
|
|
32690
|
+
|
|
32691
|
+
// Get the vector from the starting vertex to the new vertex
|
|
32692
|
+
const delta = mafs.vec.add(newVertex, reverseVector(startingVertex));
|
|
32693
|
+
|
|
32694
|
+
// Apply the delta to each of the other two points so that the angle is maintained
|
|
32695
|
+
let valid = true;
|
|
32696
|
+
const newPoints = {};
|
|
32697
|
+
for (const i of [0, 2]) {
|
|
32698
|
+
const oldPoint = coordsCopy[i];
|
|
32699
|
+
let newPoint = mafs.vec.add(oldPoint, delta);
|
|
32700
|
+
let angle = findAngle(newVertex, newPoint);
|
|
32701
|
+
angle *= Math.PI / 180;
|
|
32702
|
+
newPoint = constrainToBoundsOnAngle(newPoint, angle, range, snapStep);
|
|
32703
|
+
newPoints[i] = newPoint;
|
|
32704
|
+
|
|
32705
|
+
// Check if the new point is too close to the vertex
|
|
32706
|
+
if (tooClose(newVertex, newPoint)) {
|
|
32707
|
+
valid = false;
|
|
32708
|
+
}
|
|
32709
|
+
}
|
|
32710
|
+
|
|
32711
|
+
// Update the vertex after snapping to the snapStep
|
|
32712
|
+
newPoints[1] = newVertex;
|
|
32713
|
+
// Only move points if all new positions are valid
|
|
32714
|
+
if (valid) {
|
|
32715
|
+
Object.entries(newPoints).forEach(_ref4 => {
|
|
32716
|
+
let [i, newPoint] = _ref4;
|
|
32717
|
+
coordsCopy[i] = newPoint;
|
|
32718
|
+
});
|
|
32719
|
+
}
|
|
32720
|
+
return coordsCopy;
|
|
32721
|
+
}
|
|
32722
|
+
|
|
32723
|
+
// This function is used to ensure that the vertex of
|
|
32724
|
+
// an angle is not too close to the other points
|
|
32725
|
+
function tooClose(point1, point2, range) {
|
|
32726
|
+
const safeDistance = 2;
|
|
32727
|
+
const distance = mafs.vec.dist(point1, point2);
|
|
32728
|
+
return distance < safeDistance;
|
|
32729
|
+
}
|
|
32730
|
+
function constrainToBoundsOnAngle(point, angle, range, snapStep) {
|
|
32731
|
+
// We're subtracting the snapStep from the lower bound and adding it to the upper bound
|
|
32732
|
+
// to ensure that the point is within the bounds of the graph even after snapping to the nearest degree
|
|
32733
|
+
const lower = [range[0][0] + snapStep[0], range[1][0] + snapStep[0]];
|
|
32734
|
+
const upper = [range[0][1] - snapStep[1], range[1][1] - snapStep[1]];
|
|
32735
|
+
let result = point;
|
|
32736
|
+
if (result[0] < lower[0]) {
|
|
32737
|
+
result = [lower[0], result[1] + (lower[0] - result[0]) * Math.tan(angle)];
|
|
32738
|
+
} else if (result[0] > upper[0]) {
|
|
32739
|
+
result = [upper[0], result[1] - (result[0] - upper[0]) * Math.tan(angle)];
|
|
32740
|
+
}
|
|
32741
|
+
if (result[1] < lower[1]) {
|
|
32742
|
+
result = [result[0] + (lower[1] - result[1]) / Math.tan(angle), lower[1]];
|
|
32743
|
+
} else if (result[1] > upper[1]) {
|
|
32744
|
+
result = [result[0] - (result[1] - upper[1]) / Math.tan(angle), upper[1]];
|
|
32745
|
+
}
|
|
32746
|
+
return result;
|
|
32747
|
+
}
|
|
32748
|
+
function boundAndSnapAngleEndPoints(destinationPoint, _ref5, index) {
|
|
32749
|
+
let {
|
|
32750
|
+
range,
|
|
32751
|
+
coords,
|
|
32752
|
+
snapDegrees,
|
|
32753
|
+
angleOffsetDeg,
|
|
32754
|
+
snapStep
|
|
32755
|
+
} = _ref5;
|
|
32756
|
+
const snap = snapDegrees || 1;
|
|
32757
|
+
const offsetDegrees = angleOffsetDeg || 0;
|
|
32758
|
+
|
|
32759
|
+
// Needed to prevent updating the original coords before the checks for
|
|
32760
|
+
// degenerate triangles and overlapping sides
|
|
32761
|
+
const coordsCopy = [...coords];
|
|
32762
|
+
|
|
32763
|
+
// We want to subtract or add the snapStep to the lower and upper bounds
|
|
32764
|
+
// respectively to ensure that the point is within the bounds of the graph
|
|
32765
|
+
// even after snapping to the nearest degree
|
|
32766
|
+
const angleRange = [[range[0][0] + snapStep[0], range[0][1] - snapStep[0]], [range[1][0] + snapStep[1], range[1][1] - snapStep[1]]];
|
|
32767
|
+
|
|
32768
|
+
// Takes the destination point and makes sure it is within the bounds of the graph
|
|
32769
|
+
// SnapStep is [0, 0] because we don't want to snap these points to the grid at all
|
|
32770
|
+
const boundPoint = bound$1({
|
|
32771
|
+
snapStep: [0, 0],
|
|
32772
|
+
range: angleRange,
|
|
32773
|
+
point: destinationPoint
|
|
32774
|
+
});
|
|
32775
|
+
coordsCopy[index] = boundPoint;
|
|
32776
|
+
|
|
32777
|
+
// Get the vertex of the angle
|
|
32778
|
+
const vertex = coords[1];
|
|
32779
|
+
|
|
32780
|
+
// Gets the angle between the coords and the vertex
|
|
32781
|
+
let angle = findAngle(coordsCopy[index], vertex);
|
|
32782
|
+
|
|
32783
|
+
// Snap the angle to the nearest multiple of snapDegrees (if provided)
|
|
32784
|
+
angle = Math.round((angle - offsetDegrees) / snap) * snap + offsetDegrees;
|
|
32785
|
+
const distance = mafs.vec.dist(coordsCopy[index], vertex);
|
|
32786
|
+
const snappedValue = mafs.vec.add(vertex, polar(distance, angle));
|
|
32787
|
+
return snappedValue;
|
|
32788
|
+
}
|
|
32789
|
+
function boundAndSnapToPolygonAngle(destinationPoint, _ref6, index) {
|
|
32790
|
+
let {
|
|
32791
|
+
range,
|
|
32792
|
+
coords
|
|
32793
|
+
} = _ref6;
|
|
32261
32794
|
const startingPoint = coords[index];
|
|
32262
32795
|
|
|
32263
32796
|
// Needed to prevent updating the original coords before the checks for
|
|
@@ -32285,7 +32818,7 @@ function boundAndSnapToAngle(destinationPoint, _ref2, index) {
|
|
|
32285
32818
|
angles[rel(j)] = Math.round(angles[rel(j)]);
|
|
32286
32819
|
});
|
|
32287
32820
|
const getAngle = function (a, vertex, b) {
|
|
32288
|
-
const angle =
|
|
32821
|
+
const angle = findAngle(coordsCopy[rel(a)], coordsCopy[rel(b)], coordsCopy[rel(vertex)]);
|
|
32289
32822
|
return (angle + 360) % 360;
|
|
32290
32823
|
};
|
|
32291
32824
|
const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
|
|
@@ -32313,7 +32846,7 @@ function boundAndSnapToAngle(destinationPoint, _ref2, index) {
|
|
|
32313
32846
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
32314
32847
|
|
|
32315
32848
|
// Angle at the second vertex of the polygon
|
|
32316
|
-
const outerAngle =
|
|
32849
|
+
const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
|
|
32317
32850
|
|
|
32318
32851
|
// Uses the length of the side of the polygon (radial coordinate)
|
|
32319
32852
|
// and the angle between the first and second sides of the
|
|
@@ -32321,11 +32854,11 @@ function boundAndSnapToAngle(destinationPoint, _ref2, index) {
|
|
|
32321
32854
|
const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
32322
32855
|
return kmath.vector.add(coordsCopy[rel(-1)], offset);
|
|
32323
32856
|
}
|
|
32324
|
-
function boundAndSnapToSides(destinationPoint,
|
|
32857
|
+
function boundAndSnapToSides(destinationPoint, _ref7, index) {
|
|
32325
32858
|
let {
|
|
32326
32859
|
range,
|
|
32327
32860
|
coords
|
|
32328
|
-
} =
|
|
32861
|
+
} = _ref7;
|
|
32329
32862
|
const startingPoint = coords[index];
|
|
32330
32863
|
|
|
32331
32864
|
// Needed to prevent updating the original coords before the checks for
|
|
@@ -32364,7 +32897,7 @@ function boundAndSnapToSides(destinationPoint, _ref3, index) {
|
|
|
32364
32897
|
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
32365
32898
|
|
|
32366
32899
|
// Angle at the second vertex of the polygon
|
|
32367
|
-
const outerAngle =
|
|
32900
|
+
const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
|
|
32368
32901
|
|
|
32369
32902
|
// Returns true if the points form a counter-clockwise turn;
|
|
32370
32903
|
// a.k.a if the point is on the left or right of the polygon.
|
|
@@ -32382,12 +32915,12 @@ function boundAndSnapToSides(destinationPoint, _ref3, index) {
|
|
|
32382
32915
|
}
|
|
32383
32916
|
|
|
32384
32917
|
// Returns the vector from the given point to the top-right corner of the graph when snapped to the grid
|
|
32385
|
-
function maxMove(
|
|
32918
|
+
function maxMove(_ref8) {
|
|
32386
32919
|
let {
|
|
32387
32920
|
snapStep,
|
|
32388
32921
|
range,
|
|
32389
32922
|
point
|
|
32390
|
-
} =
|
|
32923
|
+
} = _ref8;
|
|
32391
32924
|
const topRight = bound$1({
|
|
32392
32925
|
snapStep,
|
|
32393
32926
|
range,
|
|
@@ -32397,12 +32930,12 @@ function maxMove(_ref4) {
|
|
|
32397
32930
|
}
|
|
32398
32931
|
|
|
32399
32932
|
// Returns the vector from the given point to the bottom-left corner of the graph when snapped to the grid
|
|
32400
|
-
function minMove(
|
|
32933
|
+
function minMove(_ref9) {
|
|
32401
32934
|
let {
|
|
32402
32935
|
snapStep,
|
|
32403
32936
|
range,
|
|
32404
32937
|
point
|
|
32405
|
-
} =
|
|
32938
|
+
} = _ref9;
|
|
32406
32939
|
const bottomLeft = bound$1({
|
|
32407
32940
|
snapStep,
|
|
32408
32941
|
range,
|
|
@@ -32444,6 +32977,11 @@ const renderGraph = props => {
|
|
|
32444
32977
|
type
|
|
32445
32978
|
} = state;
|
|
32446
32979
|
switch (type) {
|
|
32980
|
+
case "angle":
|
|
32981
|
+
return /*#__PURE__*/React__namespace.createElement(AngleGraph, {
|
|
32982
|
+
graphState: state,
|
|
32983
|
+
dispatch: dispatch
|
|
32984
|
+
});
|
|
32447
32985
|
case "segment":
|
|
32448
32986
|
return /*#__PURE__*/React__namespace.createElement(SegmentGraph, {
|
|
32449
32987
|
graphState: state,
|
|
@@ -32607,8 +33145,8 @@ const MafsGraph = props => {
|
|
|
32607
33145
|
}, 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, {
|
|
32608
33146
|
preserveAspectRatio: false,
|
|
32609
33147
|
viewBox: {
|
|
32610
|
-
x: state.range[
|
|
32611
|
-
y: state.range[
|
|
33148
|
+
x: state.range[X],
|
|
33149
|
+
y: state.range[Y],
|
|
32612
33150
|
padding: 0
|
|
32613
33151
|
},
|
|
32614
33152
|
pan: false,
|
|
@@ -33492,7 +34030,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
|
|
|
33492
34030
|
// Solve for side by using the law of sines
|
|
33493
34031
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
33494
34032
|
const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
|
|
33495
|
-
const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
34033
|
+
const offset = polar$1(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
33496
34034
|
return this.graphie?.addPoints(coords[rel(-1)], offset);
|
|
33497
34035
|
}
|
|
33498
34036
|
// @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
|
|
@@ -33516,7 +34054,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
|
|
|
33516
34054
|
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
33517
34055
|
const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
|
|
33518
34056
|
const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
|
|
33519
|
-
const offset = polar(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
34057
|
+
const offset = polar$1(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
33520
34058
|
return this.graphie?.addPoints(coords[rel(-1)], offset);
|
|
33521
34059
|
}
|
|
33522
34060
|
// Snap to grid (already done)
|
|
@@ -34123,11 +34661,9 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
34123
34661
|
const radius = magnitude(vector(...coords));
|
|
34124
34662
|
|
|
34125
34663
|
// Adjust the lower point by angleOffsetDeg degrees
|
|
34126
|
-
// @ts-expect-error - TS2542 - Index signature in type 'readonly Coord[]' only permits reading.
|
|
34127
34664
|
coords[0] = [coords[1][0] + radius * Math.cos(offset), coords[1][1] + radius * Math.sin(offset)];
|
|
34128
34665
|
// Position the upper point angle radians from the
|
|
34129
34666
|
// lower point
|
|
34130
|
-
// @ts-expect-error - TS2542 - Index signature in type 'readonly Coord[]' only permits reading.
|
|
34131
34667
|
coords[2] = [coords[1][0] + radius * Math.cos(angle + offset), coords[1][1] + radius * Math.sin(angle + offset)];
|
|
34132
34668
|
return coords;
|
|
34133
34669
|
}
|
|
@@ -44747,7 +45283,7 @@ const init = function (options) {
|
|
|
44747
45283
|
|
|
44748
45284
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44749
45285
|
const libName = "@khanacademy/perseus";
|
|
44750
|
-
const libVersion = "24.
|
|
45286
|
+
const libVersion = "24.3.0";
|
|
44751
45287
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44752
45288
|
|
|
44753
45289
|
const apiVersion = {
|