@khanacademy/perseus 24.1.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 +655 -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 +678 -141
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +2 -0
- package/dist/perseus-types.d.ts +17 -2
- package/dist/server-item-renderer.d.ts +2 -0
- package/dist/types.d.ts +1 -1
- package/dist/widgets/expression.d.ts +12 -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 +4 -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,15 +30570,16 @@ 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 => {
|
|
30309
30580
|
const [xPx, yPx] = mafs.vec.add(offsetPx, centerPx);
|
|
30310
30581
|
return /*#__PURE__*/React__namespace.createElement("circle", {
|
|
30582
|
+
key: `circle-${xPx}-${yPx}`,
|
|
30311
30583
|
className: "movable-circle-handle-dot",
|
|
30312
30584
|
cx: xPx,
|
|
30313
30585
|
cy: yPx
|
|
@@ -30436,15 +30708,15 @@ const getSinusoidCoefficients = coords => {
|
|
|
30436
30708
|
const p2 = coords[1];
|
|
30437
30709
|
|
|
30438
30710
|
// If the x-coordinates are the same, we are unable to calculate the coefficients
|
|
30439
|
-
if (p2[
|
|
30711
|
+
if (p2[X] === p1[X]) {
|
|
30440
30712
|
return;
|
|
30441
30713
|
}
|
|
30442
30714
|
|
|
30443
30715
|
// Resulting coefficients are canonical for this sine curve
|
|
30444
|
-
const amplitude = p2[
|
|
30445
|
-
const angularFrequency = Math.PI / (2 * (p2[
|
|
30446
|
-
const phase = p1[
|
|
30447
|
-
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];
|
|
30448
30720
|
return {
|
|
30449
30721
|
amplitude,
|
|
30450
30722
|
angularFrequency,
|
|
@@ -30453,6 +30725,75 @@ const getSinusoidCoefficients = coords => {
|
|
|
30453
30725
|
};
|
|
30454
30726
|
};
|
|
30455
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
|
+
|
|
30456
30797
|
// Universal padding for the axis ticks to ensure that the labels
|
|
30457
30798
|
// have enough space to render without overlapping the tick lines
|
|
30458
30799
|
const tickPadding = 5;
|
|
@@ -30462,10 +30803,12 @@ const AxisTickLabels = () => {
|
|
|
30462
30803
|
tickStep,
|
|
30463
30804
|
range
|
|
30464
30805
|
} = graphConfig;
|
|
30465
|
-
|
|
30466
|
-
|
|
30467
|
-
const
|
|
30468
|
-
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];
|
|
30469
30812
|
const yGridTicks = generateTickLocations$1(yTickStep, yMin, yMax);
|
|
30470
30813
|
const xGridTicks = generateTickLocations$1(xTickStep, xMin, xMax);
|
|
30471
30814
|
return /*#__PURE__*/React__namespace.createElement("div", {
|
|
@@ -30747,13 +31090,13 @@ const YGridTick = _ref => {
|
|
|
30747
31090
|
|
|
30748
31091
|
// If the graph is zoomed in, we want to make sure the ticks are still visible
|
|
30749
31092
|
// even if they are outside the graph's range.
|
|
30750
|
-
if (graphInfo.range[
|
|
31093
|
+
if (graphInfo.range[X][MIN] > 0) {
|
|
30751
31094
|
// If the graph is on the positive side of the x-axis, lock the ticks to the left side of the graph
|
|
30752
|
-
xPointOnAxis = graphInfo.range[
|
|
31095
|
+
xPointOnAxis = graphInfo.range[X][MIN];
|
|
30753
31096
|
}
|
|
30754
|
-
if (graphInfo.range[
|
|
31097
|
+
if (graphInfo.range[X][MAX] < 0) {
|
|
30755
31098
|
// If the graph is on the negative side of the x-axis, lock the ticks to the right side of the graph
|
|
30756
|
-
xPointOnAxis = graphInfo.range[
|
|
31099
|
+
xPointOnAxis = graphInfo.range[X][MAX];
|
|
30757
31100
|
}
|
|
30758
31101
|
const pointOnAxis = [xPointOnAxis, y];
|
|
30759
31102
|
const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
|
|
@@ -30788,13 +31131,13 @@ const XGridTick = _ref2 => {
|
|
|
30788
31131
|
let yPointOnAxis = 0;
|
|
30789
31132
|
// If the graph is zoomed in, we want to make sure the ticks are still visible
|
|
30790
31133
|
// even if they are outside the graph's range.
|
|
30791
|
-
if (graphInfo.range[
|
|
31134
|
+
if (graphInfo.range[Y][MIN] > 0) {
|
|
30792
31135
|
// If the graph is on the positive side of the y-axis, lock the ticks to the top of the graph
|
|
30793
|
-
yPointOnAxis = graphInfo.range[
|
|
31136
|
+
yPointOnAxis = graphInfo.range[Y][MIN];
|
|
30794
31137
|
}
|
|
30795
|
-
if (graphInfo.range[
|
|
31138
|
+
if (graphInfo.range[Y][MAX] < 0) {
|
|
30796
31139
|
// If the graph is on the negative side of the x-axis, lock the ticks to the bottom of the graph
|
|
30797
|
-
yPointOnAxis = graphInfo.range[
|
|
31140
|
+
yPointOnAxis = graphInfo.range[Y][MAX];
|
|
30798
31141
|
}
|
|
30799
31142
|
const pointOnAxis = [x, yPointOnAxis];
|
|
30800
31143
|
const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
|
|
@@ -30849,10 +31192,12 @@ const AxisTicks = () => {
|
|
|
30849
31192
|
width,
|
|
30850
31193
|
height
|
|
30851
31194
|
};
|
|
30852
|
-
|
|
30853
|
-
|
|
30854
|
-
const
|
|
30855
|
-
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];
|
|
30856
31201
|
const yGridTicks = generateTickLocations(yTickStep, yMin, yMax);
|
|
30857
31202
|
const xGridTicks = generateTickLocations(xTickStep, xMin, xMax);
|
|
30858
31203
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
@@ -30891,8 +31236,8 @@ const axisOptions = (props, axisIndex) => {
|
|
|
30891
31236
|
};
|
|
30892
31237
|
const Grid = props => {
|
|
30893
31238
|
return props.markings === "none" ? null : /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Coordinates.Cartesian, {
|
|
30894
|
-
xAxis: axisOptions(props,
|
|
30895
|
-
yAxis: axisOptions(props,
|
|
31239
|
+
xAxis: axisOptions(props, X),
|
|
31240
|
+
yAxis: axisOptions(props, Y)
|
|
30896
31241
|
}),
|
|
30897
31242
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
30898
31243
|
props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, null), /*#__PURE__*/React__namespace.createElement(AxisArrows, null)));
|
|
@@ -31500,7 +31845,7 @@ const LegacyGrid = _ref => {
|
|
|
31500
31845
|
height
|
|
31501
31846
|
} = backgroundImage ?? {};
|
|
31502
31847
|
if (url && typeof url === "string") {
|
|
31503
|
-
const scale = box[
|
|
31848
|
+
const scale = box[X] / interactiveSizes$1.defaultBoxSize;
|
|
31504
31849
|
return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
|
|
31505
31850
|
style: {
|
|
31506
31851
|
position: "absolute",
|
|
@@ -31568,9 +31913,9 @@ function Protractor() {
|
|
|
31568
31913
|
const angle = calculateAngleInDegrees(rotationHandleOffset) - calculateAngleInDegrees(centerToRotationHandle);
|
|
31569
31914
|
return /*#__PURE__*/React__namespace.createElement("g", {
|
|
31570
31915
|
ref: draggableRef,
|
|
31571
|
-
transform: `translate(${topLeftPx[
|
|
31916
|
+
transform: `translate(${topLeftPx[X]}, ${topLeftPx[Y]}), rotate(${angle})`,
|
|
31572
31917
|
style: {
|
|
31573
|
-
transformOrigin: `${-centerToTopLeft[
|
|
31918
|
+
transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`
|
|
31574
31919
|
}
|
|
31575
31920
|
}, /*#__PURE__*/React__namespace.createElement("image", {
|
|
31576
31921
|
href: protractorImage
|
|
@@ -31744,7 +32089,19 @@ function initializeGraphState(params) {
|
|
|
31744
32089
|
coords: getSinusoidCoords(graph, range, step)
|
|
31745
32090
|
};
|
|
31746
32091
|
case "angle":
|
|
31747
|
-
|
|
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
|
+
};
|
|
31748
32105
|
default:
|
|
31749
32106
|
throw new wonderStuffCore.UnreachableCaseError(graph);
|
|
31750
32107
|
}
|
|
@@ -31876,6 +32233,43 @@ function getCircleCoords(graph) {
|
|
|
31876
32233
|
radiusPoint: [2, 0]
|
|
31877
32234
|
};
|
|
31878
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
|
+
};
|
|
31879
32273
|
|
|
31880
32274
|
function interactiveGraphReducer(state, action) {
|
|
31881
32275
|
switch (action.type) {
|
|
@@ -31939,6 +32333,7 @@ function doMoveControlPoint(state, action) {
|
|
|
31939
32333
|
coords: newCoords
|
|
31940
32334
|
};
|
|
31941
32335
|
}
|
|
32336
|
+
case "angle":
|
|
31942
32337
|
case "circle":
|
|
31943
32338
|
throw new Error("FIXME implement circle reducer");
|
|
31944
32339
|
case "point":
|
|
@@ -32043,12 +32438,32 @@ function doMoveAll(state, action) {
|
|
|
32043
32438
|
}
|
|
32044
32439
|
function doMovePoint(state, action) {
|
|
32045
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
|
+
};
|
|
32046
32461
|
case "polygon":
|
|
32047
32462
|
let newValue;
|
|
32048
32463
|
if (state.snapTo === "sides") {
|
|
32049
32464
|
newValue = boundAndSnapToSides(action.destination, state, action.index);
|
|
32050
32465
|
} else if (state.snapTo === "angles") {
|
|
32051
|
-
newValue =
|
|
32466
|
+
newValue = boundAndSnapToPolygonAngle(action.destination, state, action.index);
|
|
32052
32467
|
} else {
|
|
32053
32468
|
newValue = boundAndSnapToGrid(action.destination, state);
|
|
32054
32469
|
}
|
|
@@ -32089,7 +32504,7 @@ function doMovePoint(state, action) {
|
|
|
32089
32504
|
// vertical line. If they are, then we don't want to move the point
|
|
32090
32505
|
const newCoords = [...state.coords];
|
|
32091
32506
|
newCoords[action.index] = boundDestination;
|
|
32092
|
-
if (newCoords[0][
|
|
32507
|
+
if (newCoords[0][X] === newCoords[1][X]) {
|
|
32093
32508
|
return state;
|
|
32094
32509
|
}
|
|
32095
32510
|
return {
|
|
@@ -32149,13 +32564,13 @@ function doMoveCenter(state, action) {
|
|
|
32149
32564
|
// if it otherwise would be off the chart
|
|
32150
32565
|
// ex: if the handle is on the right and we move the center
|
|
32151
32566
|
// to the rightmost position, move the handle to the left
|
|
32152
|
-
const [xMin, xMax] = state.range[
|
|
32567
|
+
const [xMin, xMax] = state.range[X];
|
|
32153
32568
|
const [radX] = newRadiusPoint;
|
|
32154
32569
|
if (radX < xMin || radX > xMax) {
|
|
32155
|
-
const xJumpDist = (radX - constrainedCenter[
|
|
32570
|
+
const xJumpDist = (radX - constrainedCenter[X]) * 2;
|
|
32156
32571
|
const possibleNewX = radX - xJumpDist;
|
|
32157
32572
|
if (possibleNewX >= xMin && possibleNewX <= xMax) {
|
|
32158
|
-
newRadiusPoint[
|
|
32573
|
+
newRadiusPoint[X] = possibleNewX;
|
|
32159
32574
|
}
|
|
32160
32575
|
}
|
|
32161
32576
|
return {
|
|
@@ -32173,11 +32588,11 @@ function doMoveRadiusPoint(state, action) {
|
|
|
32173
32588
|
switch (state.type) {
|
|
32174
32589
|
case "circle":
|
|
32175
32590
|
{
|
|
32176
|
-
const [xMin, xMax] = state.range[
|
|
32591
|
+
const [xMin, xMax] = state.range[X];
|
|
32177
32592
|
const nextRadiusPoint = [
|
|
32178
32593
|
// Constrain to graph range
|
|
32179
32594
|
// The +0 is to convert -0 to +0
|
|
32180
|
-
|
|
32595
|
+
clamp(action.destination[X] + 0, xMin, xMax), state.center[1]];
|
|
32181
32596
|
if (___default["default"].isEqual(nextRadiusPoint, state.center)) {
|
|
32182
32597
|
return state;
|
|
32183
32598
|
}
|
|
@@ -32215,10 +32630,10 @@ function doChangeRange(state, action) {
|
|
|
32215
32630
|
}
|
|
32216
32631
|
const getDeltaVertex = (maxMoves, minMoves, delta) => {
|
|
32217
32632
|
const [deltaX, deltaY] = delta;
|
|
32218
|
-
const maxXMove = Math.min(...maxMoves.map(move => move[
|
|
32219
|
-
const maxYMove = Math.min(...maxMoves.map(move => move[
|
|
32220
|
-
const minXMove = Math.max(...minMoves.map(move => move[
|
|
32221
|
-
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]));
|
|
32222
32637
|
const dx = clamp(deltaX, minXMove, maxXMove);
|
|
32223
32638
|
const dy = clamp(deltaY, minYMove, maxYMove);
|
|
32224
32639
|
return [dx, dy];
|
|
@@ -32252,11 +32667,130 @@ function boundAndSnapToGrid(point, _ref) {
|
|
|
32252
32667
|
point
|
|
32253
32668
|
}));
|
|
32254
32669
|
}
|
|
32255
|
-
function
|
|
32670
|
+
function boundAndSnapAngleVertex(_ref2, _ref3) {
|
|
32256
32671
|
let {
|
|
32257
32672
|
range,
|
|
32258
|
-
coords
|
|
32673
|
+
coords,
|
|
32674
|
+
snapStep
|
|
32259
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;
|
|
32260
32794
|
const startingPoint = coords[index];
|
|
32261
32795
|
|
|
32262
32796
|
// Needed to prevent updating the original coords before the checks for
|
|
@@ -32284,7 +32818,7 @@ function boundAndSnapToAngle(destinationPoint, _ref2, index) {
|
|
|
32284
32818
|
angles[rel(j)] = Math.round(angles[rel(j)]);
|
|
32285
32819
|
});
|
|
32286
32820
|
const getAngle = function (a, vertex, b) {
|
|
32287
|
-
const angle =
|
|
32821
|
+
const angle = findAngle(coordsCopy[rel(a)], coordsCopy[rel(b)], coordsCopy[rel(vertex)]);
|
|
32288
32822
|
return (angle + 360) % 360;
|
|
32289
32823
|
};
|
|
32290
32824
|
const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
|
|
@@ -32312,7 +32846,7 @@ function boundAndSnapToAngle(destinationPoint, _ref2, index) {
|
|
|
32312
32846
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
32313
32847
|
|
|
32314
32848
|
// Angle at the second vertex of the polygon
|
|
32315
|
-
const outerAngle =
|
|
32849
|
+
const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
|
|
32316
32850
|
|
|
32317
32851
|
// Uses the length of the side of the polygon (radial coordinate)
|
|
32318
32852
|
// and the angle between the first and second sides of the
|
|
@@ -32320,11 +32854,11 @@ function boundAndSnapToAngle(destinationPoint, _ref2, index) {
|
|
|
32320
32854
|
const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
32321
32855
|
return kmath.vector.add(coordsCopy[rel(-1)], offset);
|
|
32322
32856
|
}
|
|
32323
|
-
function boundAndSnapToSides(destinationPoint,
|
|
32857
|
+
function boundAndSnapToSides(destinationPoint, _ref7, index) {
|
|
32324
32858
|
let {
|
|
32325
32859
|
range,
|
|
32326
32860
|
coords
|
|
32327
|
-
} =
|
|
32861
|
+
} = _ref7;
|
|
32328
32862
|
const startingPoint = coords[index];
|
|
32329
32863
|
|
|
32330
32864
|
// Needed to prevent updating the original coords before the checks for
|
|
@@ -32363,7 +32897,7 @@ function boundAndSnapToSides(destinationPoint, _ref3, index) {
|
|
|
32363
32897
|
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
32364
32898
|
|
|
32365
32899
|
// Angle at the second vertex of the polygon
|
|
32366
|
-
const outerAngle =
|
|
32900
|
+
const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
|
|
32367
32901
|
|
|
32368
32902
|
// Returns true if the points form a counter-clockwise turn;
|
|
32369
32903
|
// a.k.a if the point is on the left or right of the polygon.
|
|
@@ -32381,12 +32915,12 @@ function boundAndSnapToSides(destinationPoint, _ref3, index) {
|
|
|
32381
32915
|
}
|
|
32382
32916
|
|
|
32383
32917
|
// Returns the vector from the given point to the top-right corner of the graph when snapped to the grid
|
|
32384
|
-
function maxMove(
|
|
32918
|
+
function maxMove(_ref8) {
|
|
32385
32919
|
let {
|
|
32386
32920
|
snapStep,
|
|
32387
32921
|
range,
|
|
32388
32922
|
point
|
|
32389
|
-
} =
|
|
32923
|
+
} = _ref8;
|
|
32390
32924
|
const topRight = bound$1({
|
|
32391
32925
|
snapStep,
|
|
32392
32926
|
range,
|
|
@@ -32396,12 +32930,12 @@ function maxMove(_ref4) {
|
|
|
32396
32930
|
}
|
|
32397
32931
|
|
|
32398
32932
|
// Returns the vector from the given point to the bottom-left corner of the graph when snapped to the grid
|
|
32399
|
-
function minMove(
|
|
32933
|
+
function minMove(_ref9) {
|
|
32400
32934
|
let {
|
|
32401
32935
|
snapStep,
|
|
32402
32936
|
range,
|
|
32403
32937
|
point
|
|
32404
|
-
} =
|
|
32938
|
+
} = _ref9;
|
|
32405
32939
|
const bottomLeft = bound$1({
|
|
32406
32940
|
snapStep,
|
|
32407
32941
|
range,
|
|
@@ -32443,6 +32977,11 @@ const renderGraph = props => {
|
|
|
32443
32977
|
type
|
|
32444
32978
|
} = state;
|
|
32445
32979
|
switch (type) {
|
|
32980
|
+
case "angle":
|
|
32981
|
+
return /*#__PURE__*/React__namespace.createElement(AngleGraph, {
|
|
32982
|
+
graphState: state,
|
|
32983
|
+
dispatch: dispatch
|
|
32984
|
+
});
|
|
32446
32985
|
case "segment":
|
|
32447
32986
|
return /*#__PURE__*/React__namespace.createElement(SegmentGraph, {
|
|
32448
32987
|
graphState: state,
|
|
@@ -32606,8 +33145,8 @@ const MafsGraph = props => {
|
|
|
32606
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, {
|
|
32607
33146
|
preserveAspectRatio: false,
|
|
32608
33147
|
viewBox: {
|
|
32609
|
-
x: state.range[
|
|
32610
|
-
y: state.range[
|
|
33148
|
+
x: state.range[X],
|
|
33149
|
+
y: state.range[Y],
|
|
32611
33150
|
padding: 0
|
|
32612
33151
|
},
|
|
32613
33152
|
pan: false,
|
|
@@ -33491,7 +34030,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
|
|
|
33491
34030
|
// Solve for side by using the law of sines
|
|
33492
34031
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
33493
34032
|
const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
|
|
33494
|
-
const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
34033
|
+
const offset = polar$1(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
33495
34034
|
return this.graphie?.addPoints(coords[rel(-1)], offset);
|
|
33496
34035
|
}
|
|
33497
34036
|
// @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
|
|
@@ -33515,7 +34054,7 @@ class LegacyInteractiveGraph extends React__namespace.Component {
|
|
|
33515
34054
|
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
33516
34055
|
const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
|
|
33517
34056
|
const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
|
|
33518
|
-
const offset = polar(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
34057
|
+
const offset = polar$1(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
33519
34058
|
return this.graphie?.addPoints(coords[rel(-1)], offset);
|
|
33520
34059
|
}
|
|
33521
34060
|
// Snap to grid (already done)
|
|
@@ -34122,11 +34661,9 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
34122
34661
|
const radius = magnitude(vector(...coords));
|
|
34123
34662
|
|
|
34124
34663
|
// Adjust the lower point by angleOffsetDeg degrees
|
|
34125
|
-
// @ts-expect-error - TS2542 - Index signature in type 'readonly Coord[]' only permits reading.
|
|
34126
34664
|
coords[0] = [coords[1][0] + radius * Math.cos(offset), coords[1][1] + radius * Math.sin(offset)];
|
|
34127
34665
|
// Position the upper point angle radians from the
|
|
34128
34666
|
// lower point
|
|
34129
|
-
// @ts-expect-error - TS2542 - Index signature in type 'readonly Coord[]' only permits reading.
|
|
34130
34667
|
coords[2] = [coords[1][0] + radius * Math.cos(angle + offset), coords[1][1] + radius * Math.sin(angle + offset)];
|
|
34131
34668
|
return coords;
|
|
34132
34669
|
}
|
|
@@ -44746,7 +45283,7 @@ const init = function (options) {
|
|
|
44746
45283
|
|
|
44747
45284
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44748
45285
|
const libName = "@khanacademy/perseus";
|
|
44749
|
-
const libVersion = "24.
|
|
45286
|
+
const libVersion = "24.3.0";
|
|
44750
45287
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44751
45288
|
|
|
44752
45289
|
const apiVersion = {
|