@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/es/index.js
CHANGED
|
@@ -29,7 +29,7 @@ import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-block
|
|
|
29
29
|
import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
|
|
30
30
|
import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
|
|
31
31
|
import debounce$2 from 'lodash.debounce';
|
|
32
|
-
import { vec, Ellipse, Line as Line$4, Point as Point$2, Polygon, useTransformContext, Text as Text$1,
|
|
32
|
+
import { vec, Ellipse, Plot as Plot$3, Line as Line$4, Point as Point$2, Polygon, useTransformContext, Text as Text$1, Coordinates, Mafs } from 'mafs';
|
|
33
33
|
import { useDrag } from '@use-gesture/react';
|
|
34
34
|
import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
|
|
35
35
|
import { Body, LabelSmall, LabelMedium } from '@khanacademy/wonder-blocks-typography';
|
|
@@ -8969,7 +8969,7 @@ var Tex$1 = {
|
|
|
8969
8969
|
const {
|
|
8970
8970
|
processMath: processMath$1
|
|
8971
8971
|
} = Tex$1;
|
|
8972
|
-
function polar(r, th) {
|
|
8972
|
+
function polar$1(r, th) {
|
|
8973
8973
|
if (typeof r === "number") {
|
|
8974
8974
|
r = [r, r];
|
|
8975
8975
|
}
|
|
@@ -9458,8 +9458,8 @@ class Graphie$1 {
|
|
|
9458
9458
|
endAngle = (endAngle % 360 + 360) % 360;
|
|
9459
9459
|
const cent = this.scalePoint(center);
|
|
9460
9460
|
const radii = this.scaleVector(radius);
|
|
9461
|
-
const startVector = polar(radius, startAngle);
|
|
9462
|
-
const endVector = polar(radius, endAngle);
|
|
9461
|
+
const startVector = polar$1(radius, startAngle);
|
|
9462
|
+
const endVector = polar$1(radius, endAngle);
|
|
9463
9463
|
|
|
9464
9464
|
// We round the coordinates to make testing easier, because trig
|
|
9465
9465
|
// operations return unround numbers.
|
|
@@ -13732,7 +13732,7 @@ _.extend(MovableAngle.prototype, {
|
|
|
13732
13732
|
let angle = GraphUtils.findAngle(newPoint, vertex);
|
|
13733
13733
|
angle = Math.round((angle - snapOffset) / snap) * snap + snapOffset;
|
|
13734
13734
|
const distance = GraphUtils.getDistance(newPoint, vertex);
|
|
13735
|
-
return vector$1.add(vertex, polar(distance, angle));
|
|
13735
|
+
return vector$1.add(vertex, polar$1(distance, angle));
|
|
13736
13736
|
}
|
|
13737
13737
|
return true;
|
|
13738
13738
|
};
|
|
@@ -16648,7 +16648,7 @@ const Path = GraphieClasses.createClass({
|
|
|
16648
16648
|
this.path.toFront();
|
|
16649
16649
|
}
|
|
16650
16650
|
});
|
|
16651
|
-
const Arc = GraphieClasses.createSimpleClass((graphie, props) => {
|
|
16651
|
+
const Arc$1 = GraphieClasses.createSimpleClass((graphie, props) => {
|
|
16652
16652
|
let center = props.center;
|
|
16653
16653
|
let radius = props.radius;
|
|
16654
16654
|
if (props.unscaled) {
|
|
@@ -16664,7 +16664,7 @@ const Rect$1 = GraphieClasses.createSimpleClass((graphie, props) => {
|
|
|
16664
16664
|
return graphie.rect(props.x, props.y, props.width, props.height, props.style);
|
|
16665
16665
|
});
|
|
16666
16666
|
var Movables = {
|
|
16667
|
-
Arc: Arc,
|
|
16667
|
+
Arc: Arc$1,
|
|
16668
16668
|
Circle: Circle,
|
|
16669
16669
|
Label: Label$1,
|
|
16670
16670
|
Line: Line$3,
|
|
@@ -28363,6 +28363,91 @@ const useTransformDimensionsToPixels = (...dimens) => {
|
|
|
28363
28363
|
return dimensionsToPixels(dimens, graphState);
|
|
28364
28364
|
};
|
|
28365
28365
|
|
|
28366
|
+
// Restricts `value` to be between `min` and `max`.
|
|
28367
|
+
function clamp(value, min, max) {
|
|
28368
|
+
if (value < min) {
|
|
28369
|
+
return min;
|
|
28370
|
+
}
|
|
28371
|
+
if (value > max) {
|
|
28372
|
+
return max;
|
|
28373
|
+
}
|
|
28374
|
+
return value;
|
|
28375
|
+
}
|
|
28376
|
+
|
|
28377
|
+
// Rounds each component of `point` to the nearest multiple of the
|
|
28378
|
+
// corresponding component of `snapStep`. E.g. if the snap step is [2, 3],
|
|
28379
|
+
// the x-coord of the point will be rounded to the nearest multiple of 2, and
|
|
28380
|
+
// the y-coord of the point will be rounded to the nearest multiple of 3.
|
|
28381
|
+
function snap(snapStep, point) {
|
|
28382
|
+
const [requestedX, requestedY] = point;
|
|
28383
|
+
const [snapX, snapY] = snapStep;
|
|
28384
|
+
return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
|
|
28385
|
+
}
|
|
28386
|
+
|
|
28387
|
+
// Use X and Y to index into arrays that represent coordinate pairs.
|
|
28388
|
+
// e.g. point[X]
|
|
28389
|
+
const X = 0;
|
|
28390
|
+
const Y = 1;
|
|
28391
|
+
|
|
28392
|
+
// Use MIN and MAX to index into arrays that represent intervals.
|
|
28393
|
+
// e.g. range[MAX]
|
|
28394
|
+
const MIN = 0;
|
|
28395
|
+
const MAX = 1;
|
|
28396
|
+
function size([min, max]) {
|
|
28397
|
+
return max - min;
|
|
28398
|
+
}
|
|
28399
|
+
|
|
28400
|
+
// Removes an equal amount from each end of the interval.
|
|
28401
|
+
//
|
|
28402
|
+
// If the given amount cannot be removed (because the interval is too small),
|
|
28403
|
+
// half the interval's size is removed from each end instead, so the interval
|
|
28404
|
+
// ends up with size = 0, and both MAX and MIN equal to its former midpoint.
|
|
28405
|
+
//
|
|
28406
|
+
// If `amount` is negative, the interval is expanded instead.
|
|
28407
|
+
function trim(amount, interval) {
|
|
28408
|
+
if (amount * 2 > size(interval)) {
|
|
28409
|
+
const middle = average(...interval);
|
|
28410
|
+
return [middle, middle];
|
|
28411
|
+
}
|
|
28412
|
+
return [interval[MIN] + amount, interval[MAX] - amount];
|
|
28413
|
+
}
|
|
28414
|
+
function average(a, b) {
|
|
28415
|
+
return (a + b) / 2;
|
|
28416
|
+
}
|
|
28417
|
+
|
|
28418
|
+
// Restricts the `point` to be within `box`, by clamping each coordinate to the
|
|
28419
|
+
// corresponding interval.
|
|
28420
|
+
function clampToBox(box, point) {
|
|
28421
|
+
return [clamp(point[X], ...box[X]), clamp(point[Y], ...box[Y])];
|
|
28422
|
+
}
|
|
28423
|
+
|
|
28424
|
+
// Reduces the size of `box`, trimming each corner by the given positive
|
|
28425
|
+
// `amount`. If a component of `amount` is negative, the box is expanded in
|
|
28426
|
+
// that dimension instead.
|
|
28427
|
+
function inset(amount, box) {
|
|
28428
|
+
return [trim(amount[X], box[X]), trim(amount[Y], box[Y])];
|
|
28429
|
+
}
|
|
28430
|
+
|
|
28431
|
+
// This file contains helper functions for working with angles.
|
|
28432
|
+
function polar(r, th) {
|
|
28433
|
+
if (typeof r === "number") {
|
|
28434
|
+
r = [r, r];
|
|
28435
|
+
}
|
|
28436
|
+
th = th * Math.PI / 180;
|
|
28437
|
+
return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
|
|
28438
|
+
}
|
|
28439
|
+
const findAngle = (point1, point2, vertex) => {
|
|
28440
|
+
if (vertex === undefined) {
|
|
28441
|
+
const x = point1[0] - point2[0];
|
|
28442
|
+
const y = point1[1] - point2[1];
|
|
28443
|
+
if (!x && !y) {
|
|
28444
|
+
return 0;
|
|
28445
|
+
}
|
|
28446
|
+
return (180 + Math.atan2(-y, -x) * 180 / Math.PI + 360) % 360;
|
|
28447
|
+
}
|
|
28448
|
+
return findAngle(point1, vertex) - findAngle(point2, vertex);
|
|
28449
|
+
};
|
|
28450
|
+
|
|
28366
28451
|
function AxisLabels() {
|
|
28367
28452
|
const {
|
|
28368
28453
|
range,
|
|
@@ -28370,8 +28455,8 @@ function AxisLabels() {
|
|
|
28370
28455
|
width,
|
|
28371
28456
|
height
|
|
28372
28457
|
} = useGraphConfig();
|
|
28373
|
-
const yAxisLabelLocation = [0, range[
|
|
28374
|
-
const xAxisLabelLocation = [range[
|
|
28458
|
+
const yAxisLabelLocation = [0, range[Y][MAX]];
|
|
28459
|
+
const xAxisLabelLocation = [range[X][MAX], 0];
|
|
28375
28460
|
const [xAxisLabelText, yAxisLabelText] = labels;
|
|
28376
28461
|
const graphInfo = {
|
|
28377
28462
|
range,
|
|
@@ -28542,6 +28627,32 @@ const LockedEllipse = props => {
|
|
|
28542
28627
|
});
|
|
28543
28628
|
};
|
|
28544
28629
|
|
|
28630
|
+
const LockedFunction = props => {
|
|
28631
|
+
const {
|
|
28632
|
+
color,
|
|
28633
|
+
strokeStyle,
|
|
28634
|
+
directionalAxis,
|
|
28635
|
+
domain
|
|
28636
|
+
} = props;
|
|
28637
|
+
const plotProps = {
|
|
28638
|
+
color: lockedFigureColors[color],
|
|
28639
|
+
style: strokeStyle,
|
|
28640
|
+
domain
|
|
28641
|
+
};
|
|
28642
|
+
const equation = props.equationParsed || KAS.parse(props.equation).expr;
|
|
28643
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
28644
|
+
className: "locked-function"
|
|
28645
|
+
}, directionalAxis === "x" && /*#__PURE__*/React.createElement(Plot$3.OfX, _extends({
|
|
28646
|
+
y: x => equation.eval({
|
|
28647
|
+
x
|
|
28648
|
+
})
|
|
28649
|
+
}, plotProps)), directionalAxis === "y" && /*#__PURE__*/React.createElement(Plot$3.OfY, _extends({
|
|
28650
|
+
x: y => equation.eval({
|
|
28651
|
+
y
|
|
28652
|
+
})
|
|
28653
|
+
}, plotProps)));
|
|
28654
|
+
};
|
|
28655
|
+
|
|
28545
28656
|
function pathBuilder() {
|
|
28546
28657
|
return new PathBuilder();
|
|
28547
28658
|
}
|
|
@@ -28640,7 +28751,7 @@ function Arrowhead(props) {
|
|
|
28640
28751
|
const [point] = useTransformVectorsToPixels(props.tip);
|
|
28641
28752
|
return /*#__PURE__*/React.createElement("g", {
|
|
28642
28753
|
className: "interactive-graph-arrowhead",
|
|
28643
|
-
transform: `translate(${point[
|
|
28754
|
+
transform: `translate(${point[X]} ${point[Y]}) rotate(${props.angle})`
|
|
28644
28755
|
}, /*#__PURE__*/React.createElement("g", {
|
|
28645
28756
|
transform: "translate(-1.5)"
|
|
28646
28757
|
}, /*#__PURE__*/React.createElement("path", {
|
|
@@ -28709,10 +28820,10 @@ function SVGLine(props) {
|
|
|
28709
28820
|
testId
|
|
28710
28821
|
} = props;
|
|
28711
28822
|
return /*#__PURE__*/React.createElement("line", {
|
|
28712
|
-
x1: start[
|
|
28713
|
-
y1: start[
|
|
28714
|
-
x2: end[
|
|
28715
|
-
y2: end[
|
|
28823
|
+
x1: start[X],
|
|
28824
|
+
y1: start[Y],
|
|
28825
|
+
x2: end[X],
|
|
28826
|
+
y2: end[Y],
|
|
28716
28827
|
style: style,
|
|
28717
28828
|
className: className,
|
|
28718
28829
|
"data-testid": testId
|
|
@@ -28797,8 +28908,8 @@ const LockedLine = props => {
|
|
|
28797
28908
|
return /*#__PURE__*/React.createElement("g", {
|
|
28798
28909
|
className: kind === "ray" ? "locked-ray" : "locked-line"
|
|
28799
28910
|
}, line, showPoint1 && /*#__PURE__*/React.createElement(Point$2, {
|
|
28800
|
-
x: point1.coord[
|
|
28801
|
-
y: point1.coord[
|
|
28911
|
+
x: point1.coord[X],
|
|
28912
|
+
y: point1.coord[Y],
|
|
28802
28913
|
svgCircleProps: {
|
|
28803
28914
|
style: {
|
|
28804
28915
|
fill: point1.filled ? lockedFigureColors[point1.color] : color.white,
|
|
@@ -28807,8 +28918,8 @@ const LockedLine = props => {
|
|
|
28807
28918
|
}
|
|
28808
28919
|
}
|
|
28809
28920
|
}), showPoint2 && /*#__PURE__*/React.createElement(Point$2, {
|
|
28810
|
-
x: point2.coord[
|
|
28811
|
-
y: point2.coord[
|
|
28921
|
+
x: point2.coord[X],
|
|
28922
|
+
y: point2.coord[Y],
|
|
28812
28923
|
svgCircleProps: {
|
|
28813
28924
|
style: {
|
|
28814
28925
|
fill: point2.filled ? lockedFigureColors[point2.color] : color.white,
|
|
@@ -28856,8 +28967,8 @@ const LockedPolygon = props => {
|
|
|
28856
28967
|
color: lockedFigureColors[color]
|
|
28857
28968
|
}), showVertices && points.map((point, index) => /*#__PURE__*/React.createElement(Point$2, {
|
|
28858
28969
|
key: `locked-polygon-point-${index}`,
|
|
28859
|
-
x: point[
|
|
28860
|
-
y: point[
|
|
28970
|
+
x: point[X],
|
|
28971
|
+
y: point[Y],
|
|
28861
28972
|
color: lockedFigureColors[color]
|
|
28862
28973
|
})));
|
|
28863
28974
|
};
|
|
@@ -28904,6 +29015,10 @@ const GraphLockedLayer = props => {
|
|
|
28904
29015
|
return /*#__PURE__*/React.createElement(LockedPolygon, _extends({
|
|
28905
29016
|
key: `polygon-${index}`
|
|
28906
29017
|
}, figure));
|
|
29018
|
+
case "function":
|
|
29019
|
+
return /*#__PURE__*/React.createElement(LockedFunction, _extends({
|
|
29020
|
+
key: `function-${index}`
|
|
29021
|
+
}, figure));
|
|
28907
29022
|
default:
|
|
28908
29023
|
/**
|
|
28909
29024
|
* Devlopment-time future-proofing: This should
|
|
@@ -28996,24 +29111,18 @@ const normalizePoints = (range, step, coordsList, noSnap) => coordsList.map(coor
|
|
|
28996
29111
|
coords.map((coord, i) => {
|
|
28997
29112
|
const axisRange = range[i];
|
|
28998
29113
|
if (noSnap) {
|
|
28999
|
-
return axisRange[
|
|
29114
|
+
return axisRange[MIN] + size(axisRange) * coord;
|
|
29000
29115
|
}
|
|
29001
29116
|
const axisStep = step[i];
|
|
29002
|
-
const nSteps = Math.floor((axisRange
|
|
29117
|
+
const nSteps = Math.floor(size(axisRange) / axisStep);
|
|
29003
29118
|
const tick = Math.round(coord * nSteps);
|
|
29004
|
-
return axisRange[
|
|
29119
|
+
return axisRange[MIN] + axisStep * tick;
|
|
29005
29120
|
}));
|
|
29006
29121
|
|
|
29007
29122
|
// same as normalizeCoords in interactive-graph.tsx
|
|
29008
29123
|
const normalizeCoords = (coordsList, ranges) => coordsList.map(coords => coords.map((coord, i) => {
|
|
29009
|
-
|
|
29010
|
-
return (coord + ranges[i][1]) / extent;
|
|
29124
|
+
return (coord + ranges[i][1]) / size(ranges[i]);
|
|
29011
29125
|
}));
|
|
29012
|
-
function snap(snapStep, point) {
|
|
29013
|
-
const [requestedX, requestedY] = point;
|
|
29014
|
-
const [snapX, snapY] = snapStep;
|
|
29015
|
-
return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
|
|
29016
|
-
}
|
|
29017
29126
|
|
|
29018
29127
|
// Returns the closest point to the given `point` that is within the graph
|
|
29019
29128
|
// bounds given in `state`.
|
|
@@ -29022,19 +29131,8 @@ function bound$1({
|
|
|
29022
29131
|
range,
|
|
29023
29132
|
point
|
|
29024
29133
|
}) {
|
|
29025
|
-
const
|
|
29026
|
-
|
|
29027
|
-
const [[minX, maxX], [minY, maxY]] = range;
|
|
29028
|
-
return [clamp(requestedX, minX + snapX, maxX - snapX), clamp(requestedY, minY + snapY, maxY - snapY)];
|
|
29029
|
-
}
|
|
29030
|
-
function clamp(value, min, max) {
|
|
29031
|
-
if (value < min) {
|
|
29032
|
-
return min;
|
|
29033
|
-
}
|
|
29034
|
-
if (value > max) {
|
|
29035
|
-
return max;
|
|
29036
|
-
}
|
|
29037
|
-
return value;
|
|
29134
|
+
const boundingBox = inset(snapStep, range);
|
|
29135
|
+
return clampToBox(boundingBox, point);
|
|
29038
29136
|
}
|
|
29039
29137
|
|
|
29040
29138
|
var isProduction = process.env.NODE_ENV === 'production';
|
|
@@ -29104,8 +29202,8 @@ function useDraggable(args) {
|
|
|
29104
29202
|
metaKey,
|
|
29105
29203
|
shiftKey
|
|
29106
29204
|
} = state;
|
|
29107
|
-
const direction = [yDownDirection[
|
|
29108
|
-
const span = Math.abs(direction[
|
|
29205
|
+
const direction = [yDownDirection[X], -yDownDirection[Y]];
|
|
29206
|
+
const span = Math.abs(direction[X]) ? xSpan : ySpan;
|
|
29109
29207
|
let divisions = 50;
|
|
29110
29208
|
if (altKey || metaKey) {
|
|
29111
29209
|
divisions = 200;
|
|
@@ -29213,8 +29311,10 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
|
|
|
29213
29311
|
const wbColorName = (_Object$entries$find$ = (_Object$entries$find = Object.entries(color).find(([_, value]) => value === color$1)) == null ? void 0 : _Object$entries$find[0]) != null ? _Object$entries$find$ : "blue";
|
|
29214
29312
|
const pointClasses = `movable-point ${dragging ? "movable-point--dragging" : ""} ${focusClass(focusBehavior)}`;
|
|
29215
29313
|
const [[x, y]] = useTransformVectorsToPixels(point);
|
|
29216
|
-
|
|
29217
|
-
|
|
29314
|
+
|
|
29315
|
+
// TODO(benchristel): destructure range in one line
|
|
29316
|
+
const [xMin, xMax] = range[X];
|
|
29317
|
+
const [yMin, yMax] = range[Y];
|
|
29218
29318
|
const [[verticalStartX]] = useTransformVectorsToPixels([xMin, 0]);
|
|
29219
29319
|
const [[verticalEndX]] = useTransformVectorsToPixels([xMax, 0]);
|
|
29220
29320
|
const [[_, horizontalStartY]] = useTransformVectorsToPixels([0, yMin]);
|
|
@@ -29271,7 +29371,7 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
|
|
|
29271
29371
|
return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React.createElement(Tooltip$1, {
|
|
29272
29372
|
autoUpdate: true,
|
|
29273
29373
|
backgroundColor: wbColorName,
|
|
29274
|
-
content: `(${point[
|
|
29374
|
+
content: `(${point[X]}, ${point[Y]})`,
|
|
29275
29375
|
contentStyle: {
|
|
29276
29376
|
color: "white"
|
|
29277
29377
|
}
|
|
@@ -29456,16 +29556,7 @@ function trimRange(range, graphDimensionsInPixels) {
|
|
|
29456
29556
|
const graphUnitsPerPixelY = size(yRange) / pixelsTall;
|
|
29457
29557
|
const graphUnitsToTrimX = pixelsToTrim * graphUnitsPerPixelX;
|
|
29458
29558
|
const graphUnitsToTrimY = pixelsToTrim * graphUnitsPerPixelY;
|
|
29459
|
-
return [
|
|
29460
|
-
}
|
|
29461
|
-
function trim(interval, amount) {
|
|
29462
|
-
if (size(interval) < amount * 2) {
|
|
29463
|
-
return [0, 0];
|
|
29464
|
-
}
|
|
29465
|
-
return [interval[0] + amount, interval[1] - amount];
|
|
29466
|
-
}
|
|
29467
|
-
function size(interval) {
|
|
29468
|
-
return interval[1] - interval[0];
|
|
29559
|
+
return inset([graphUnitsToTrimX, graphUnitsToTrimY], range);
|
|
29469
29560
|
}
|
|
29470
29561
|
|
|
29471
29562
|
const SegmentGraph = props => {
|
|
@@ -29598,7 +29689,7 @@ const SvgDefs = () => /*#__PURE__*/React__default.createElement("defs", null, /*
|
|
|
29598
29689
|
in: "SourceGraphic"
|
|
29599
29690
|
})));
|
|
29600
29691
|
|
|
29601
|
-
const
|
|
29692
|
+
const PolygonAngle = ({
|
|
29602
29693
|
centerPoint,
|
|
29603
29694
|
endPoints,
|
|
29604
29695
|
range,
|
|
@@ -29625,7 +29716,7 @@ const Angle = ({
|
|
|
29625
29716
|
// Used to calculate the inside of the angle and to create square
|
|
29626
29717
|
const [x3, y3] = vec.add(centerPoint, vec.add(vec.sub([x1, y1], centerPoint), vec.sub([x2, y2], centerPoint)));
|
|
29627
29718
|
if (!showAngles) {
|
|
29628
|
-
return
|
|
29719
|
+
return isRightPolygonAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
29629
29720
|
start: [x1, y1],
|
|
29630
29721
|
vertex: [x2, y2],
|
|
29631
29722
|
end: [x3, y3]
|
|
@@ -29662,17 +29753,101 @@ const Angle = ({
|
|
|
29662
29753
|
start: [x1, y1],
|
|
29663
29754
|
vertex: [x2, y2],
|
|
29664
29755
|
end: [x3, y3]
|
|
29665
|
-
}) : /*#__PURE__*/React.createElement(
|
|
29666
|
-
|
|
29667
|
-
|
|
29668
|
-
fill: "none"
|
|
29669
|
-
})), /*#__PURE__*/React.createElement(TextLabel, {
|
|
29756
|
+
}) : /*#__PURE__*/React.createElement(Arc, {
|
|
29757
|
+
arc: arc
|
|
29758
|
+
}), /*#__PURE__*/React.createElement(TextLabel, {
|
|
29670
29759
|
x: x3,
|
|
29671
29760
|
y: y3,
|
|
29672
29761
|
attach: y3 - centerY > 0 ? "s" : "n",
|
|
29673
29762
|
attachDistance: Math.abs(y3 - centerY) < 0.2 || vec.dist([x3, y3], centerPoint) < 0.3 ? 20 : 10
|
|
29674
29763
|
}, angleLabel, "\xB0"));
|
|
29675
29764
|
};
|
|
29765
|
+
const Angle = ({
|
|
29766
|
+
vertex,
|
|
29767
|
+
coords,
|
|
29768
|
+
showAngles,
|
|
29769
|
+
allowReflexAngles,
|
|
29770
|
+
range
|
|
29771
|
+
}) => {
|
|
29772
|
+
// Check if the points are clockwise or not, depending on whether we allow reflex angles
|
|
29773
|
+
const areClockwise = clockwise$1([...coords, vertex]);
|
|
29774
|
+
const shouldReverseCoords = areClockwise && !allowReflexAngles;
|
|
29775
|
+
|
|
29776
|
+
// Reverse the coordinates accordingly to ensure the angle is calculated correctly
|
|
29777
|
+
const clockwiseCoords = shouldReverseCoords ? coords : coords.reverse();
|
|
29778
|
+
|
|
29779
|
+
// Calculate the angles between the two points
|
|
29780
|
+
const startAngle = findAngle(clockwiseCoords[0], vertex);
|
|
29781
|
+
const endAngle = findAngle(clockwiseCoords[1], vertex);
|
|
29782
|
+
const angle = (startAngle + 360 - endAngle) % 360;
|
|
29783
|
+
|
|
29784
|
+
// Check if the angle is reflexive
|
|
29785
|
+
const isReflexive = angle > 180;
|
|
29786
|
+
|
|
29787
|
+
// Break out the necessary points for the arc calculation
|
|
29788
|
+
const [centerX, centerY] = vertex;
|
|
29789
|
+
const [point1, point2] = clockwiseCoords;
|
|
29790
|
+
const [startX, startY] = point1;
|
|
29791
|
+
const [endX, endY] = point2;
|
|
29792
|
+
const a = vec.dist(vertex, point1);
|
|
29793
|
+
const b = vec.dist(vertex, point2);
|
|
29794
|
+
|
|
29795
|
+
// Set the radius of the arc
|
|
29796
|
+
const radius = 2;
|
|
29797
|
+
|
|
29798
|
+
// Calculate the end points of the arc
|
|
29799
|
+
const y1 = centerY + (startY - centerY) / a * radius;
|
|
29800
|
+
const x2 = centerX + (endX - centerX) / b * radius;
|
|
29801
|
+
const x1 = centerX + (startX - centerX) / a * radius;
|
|
29802
|
+
const y2 = centerY + (endY - centerY) / b * radius;
|
|
29803
|
+
|
|
29804
|
+
// Used to calculate the inside of the angle and to create square
|
|
29805
|
+
const [x3, y3] = vec.add(vertex, vec.add(vec.sub([x1, y1], vertex), vec.sub([x2, y2], vertex)));
|
|
29806
|
+
|
|
29807
|
+
// Determine whether the arc should be drawn outside the angle
|
|
29808
|
+
const isOutside = shouldDrawArcOutside([x3, y3], vertex, range, [[vertex, point1], [vertex, point2]]);
|
|
29809
|
+
|
|
29810
|
+
// Determine the flags for the arc
|
|
29811
|
+
// If the angle is outside OR reflexive, we want to draw a large arc
|
|
29812
|
+
// If the angle is outside AND reflexive, we want to sweep the arc
|
|
29813
|
+
const largeArcFlag = isOutside || isReflexive ? 1 : 0;
|
|
29814
|
+
const sweepFlag = isOutside && isReflexive ? 1 : 0;
|
|
29815
|
+
|
|
29816
|
+
// Create the SVG path for the arc
|
|
29817
|
+
const arc = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;
|
|
29818
|
+
|
|
29819
|
+
// We only ever want to show whole angles
|
|
29820
|
+
const angleLabel = parseFloat(angle.toFixed(0)); // Only want to show whole angles
|
|
29821
|
+
|
|
29822
|
+
// Calculate the text position based on the angle and whether we allow reflex angles
|
|
29823
|
+
// Let's try the angle bisector method to find the midpoint of the arc
|
|
29824
|
+
const [textX, textY] = calculateBisectorPoint(point1, point2, vertex, isReflexive, allowReflexAngles, radius);
|
|
29825
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("defs", null, /*#__PURE__*/React.createElement("filter", {
|
|
29826
|
+
id: "background",
|
|
29827
|
+
x: "-5%",
|
|
29828
|
+
width: "110%",
|
|
29829
|
+
y: "0%",
|
|
29830
|
+
height: "100%"
|
|
29831
|
+
}, /*#__PURE__*/React.createElement("feFlood", {
|
|
29832
|
+
floodColor: "#FFF",
|
|
29833
|
+
floodOpacity: "0.5"
|
|
29834
|
+
}), /*#__PURE__*/React.createElement("feComposite", {
|
|
29835
|
+
operator: "over",
|
|
29836
|
+
in: "SourceGraphic"
|
|
29837
|
+
}))), isRightAngle(angle) ? /*#__PURE__*/React.createElement(RightAngleSquare, {
|
|
29838
|
+
start: [x1, y1],
|
|
29839
|
+
vertex: [x2, y2],
|
|
29840
|
+
end: [x3, y3],
|
|
29841
|
+
className: "arc-right-angle"
|
|
29842
|
+
}) : /*#__PURE__*/React.createElement(Arc, {
|
|
29843
|
+
arc: arc,
|
|
29844
|
+
className: "angle-arc"
|
|
29845
|
+
}), showAngles && /*#__PURE__*/React.createElement(TextLabel, {
|
|
29846
|
+
x: textX,
|
|
29847
|
+
y: textY,
|
|
29848
|
+
color: color.blue
|
|
29849
|
+
}, angleLabel, "\xB0"));
|
|
29850
|
+
};
|
|
29676
29851
|
|
|
29677
29852
|
/**
|
|
29678
29853
|
* This is broken out into its own component so that it can be used for an early return
|
|
@@ -29681,13 +29856,30 @@ const Angle = ({
|
|
|
29681
29856
|
const RightAngleSquare = ({
|
|
29682
29857
|
start: [x1, y1],
|
|
29683
29858
|
vertex: [x2, y2],
|
|
29684
|
-
end: [x3, y3]
|
|
29859
|
+
end: [x3, y3],
|
|
29860
|
+
className
|
|
29685
29861
|
}) => /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
29686
29862
|
d: `M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,
|
|
29687
29863
|
strokeWidth: 0.02,
|
|
29688
|
-
fill: "none"
|
|
29864
|
+
fill: "none",
|
|
29865
|
+
className: className
|
|
29689
29866
|
}));
|
|
29690
|
-
|
|
29867
|
+
|
|
29868
|
+
// We're conditionally adding the class name here so that we can style the arc differently
|
|
29869
|
+
// based on whether it's an angle or a polygon angle
|
|
29870
|
+
const Arc = ({
|
|
29871
|
+
arc,
|
|
29872
|
+
className
|
|
29873
|
+
}) => {
|
|
29874
|
+
return /*#__PURE__*/React.createElement(MafsCssTransformWrapper, null, /*#__PURE__*/React.createElement("path", {
|
|
29875
|
+
d: arc,
|
|
29876
|
+
strokeWidth: 0.02,
|
|
29877
|
+
fill: "none",
|
|
29878
|
+
className: className
|
|
29879
|
+
}));
|
|
29880
|
+
};
|
|
29881
|
+
const isRightPolygonAngle = angle => Math.abs(angle - Math.PI / 2) < 0.01;
|
|
29882
|
+
const isRightAngle = angle => Math.round(angle) === 90;
|
|
29691
29883
|
|
|
29692
29884
|
/**
|
|
29693
29885
|
* Determines if an angle is an inside (false) or outside (true) angle.
|
|
@@ -29721,6 +29913,75 @@ const linesIntersect = ([[a, b], [c, d]], [[p, q], [r, s]]) => {
|
|
|
29721
29913
|
};
|
|
29722
29914
|
const isEven = n => n % 2 === 0;
|
|
29723
29915
|
|
|
29916
|
+
// This function calculates the bisector point of an angle formed by two points
|
|
29917
|
+
// and a vertex. It is used to position the angle label so that it is always
|
|
29918
|
+
// outside the arc at the exact midpoint of the angle.
|
|
29919
|
+
// Reflex angles make use of polar coordinates to determine the correct direction
|
|
29920
|
+
function calculateBisectorPoint(point1, point2, vertex, isReflex, allowReflex, arcRadius) {
|
|
29921
|
+
const [originX, originY] = vertex;
|
|
29922
|
+
const [x1, y1] = point1;
|
|
29923
|
+
const [x2, y2] = point2;
|
|
29924
|
+
|
|
29925
|
+
// Convert the Cartesian Coordinates to vectors
|
|
29926
|
+
const vectorA = [x1 - originX, y1 - originY];
|
|
29927
|
+
const vectorB = [x2 - originX, y2 - originY];
|
|
29928
|
+
|
|
29929
|
+
// Calculate the average angle of the vectors
|
|
29930
|
+
// so that we can point the bisector in the correct direction using polar coordinates
|
|
29931
|
+
const angleA = Math.atan2(vectorA[1], vectorA[0]);
|
|
29932
|
+
const angleB = Math.atan2(vectorB[1], vectorB[0]);
|
|
29933
|
+
|
|
29934
|
+
// Calculate the average of these two angles
|
|
29935
|
+
let averageAngle = (angleA + angleB) / 2;
|
|
29936
|
+
const angleRadians = Math.abs(angleA - angleB);
|
|
29937
|
+
|
|
29938
|
+
// Adjust the average angle to point in the correct direction
|
|
29939
|
+
// and to account for reflex angles and a bug with 180 degrees
|
|
29940
|
+
// that occurs due to our angleOffsetDegrees option in the graph
|
|
29941
|
+
if (allowReflex) {
|
|
29942
|
+
// If the resulting angles are less than 180 degrees,
|
|
29943
|
+
// or if the user has flipped the points on the interactive element
|
|
29944
|
+
// ensure that we have the correct direction for the reflex angle
|
|
29945
|
+
if (angleRadians <= Math.PI && isReflex || angleB > angleA) {
|
|
29946
|
+
averageAngle += Math.PI; // Add 180 degrees
|
|
29947
|
+
}
|
|
29948
|
+
} else {
|
|
29949
|
+
// If the resulting angles are greater than 180 degrees, adjust
|
|
29950
|
+
// to get the correct angle, as we already know that the angle is not reflexive
|
|
29951
|
+
// This fixes a bug that occurs at 180 degrees due to our angleOffsetDegrees option in the graph
|
|
29952
|
+
if (angleRadians > Math.PI) {
|
|
29953
|
+
averageAngle -= Math.PI; // Subract 180 degrees
|
|
29954
|
+
}
|
|
29955
|
+
}
|
|
29956
|
+
|
|
29957
|
+
// Convert the average angle back to Cartesian coordinates
|
|
29958
|
+
const sum = [Math.cos(averageAngle), Math.sin(averageAngle)];
|
|
29959
|
+
|
|
29960
|
+
// Calculate the magnitude of the sum to normalize it
|
|
29961
|
+
const sumMagnitude = Math.sqrt(sum[0] ** 2 + sum[1] ** 2);
|
|
29962
|
+
const bisectorDirection = [sum[0] / sumMagnitude, sum[1] / sumMagnitude];
|
|
29963
|
+
|
|
29964
|
+
// Calculate the initial distance of the bisector direction from the origin
|
|
29965
|
+
const initialDistance = Math.sqrt(bisectorDirection[0] ** 2 + bisectorDirection[1] ** 2);
|
|
29966
|
+
|
|
29967
|
+
// Determine the minimum radius to ensure that the text is always outside the arc
|
|
29968
|
+
const requiredDistance = arcRadius * 1.75;
|
|
29969
|
+
let radius = requiredDistance / initialDistance;
|
|
29970
|
+
|
|
29971
|
+
// Apply radius only if the initial distance is less than required
|
|
29972
|
+
if (initialDistance >= requiredDistance) {
|
|
29973
|
+
radius = 1; // Use the unit vector as is
|
|
29974
|
+
}
|
|
29975
|
+
|
|
29976
|
+
// Scale the bisector direction by the radius
|
|
29977
|
+
const bisectorPoint = [bisectorDirection[0] * radius, bisectorDirection[1] * radius];
|
|
29978
|
+
|
|
29979
|
+
// Add the vertex to the bisector point to get the final position
|
|
29980
|
+
// to ensure that the angle label moves with the interactive element
|
|
29981
|
+
const scaledBisector = vec.add(bisectorPoint, vertex);
|
|
29982
|
+
return scaledBisector;
|
|
29983
|
+
}
|
|
29984
|
+
|
|
29724
29985
|
const StyledMovablePoint = props => {
|
|
29725
29986
|
const {
|
|
29726
29987
|
snapStep
|
|
@@ -29801,7 +30062,7 @@ const PolygonGraph = props => {
|
|
|
29801
30062
|
if (!pt1 || !pt2) {
|
|
29802
30063
|
return null;
|
|
29803
30064
|
}
|
|
29804
|
-
return /*#__PURE__*/React.createElement(
|
|
30065
|
+
return /*#__PURE__*/React.createElement(PolygonAngle, {
|
|
29805
30066
|
key: "angle-" + i,
|
|
29806
30067
|
centerPoint: point,
|
|
29807
30068
|
endPoints: [pt1, pt2],
|
|
@@ -29893,6 +30154,12 @@ function getGradableGraph(state, initialGraph) {
|
|
|
29893
30154
|
coords: state.coords
|
|
29894
30155
|
});
|
|
29895
30156
|
}
|
|
30157
|
+
if (state.type === "angle" && initialGraph.type === "angle") {
|
|
30158
|
+
return _extends({}, initialGraph, {
|
|
30159
|
+
coords: state.coords,
|
|
30160
|
+
allowReflexAngles: state.allowReflexAngles
|
|
30161
|
+
});
|
|
30162
|
+
}
|
|
29896
30163
|
throw new Error("Mafs is not yet implemented for graph type: " + initialGraph.type);
|
|
29897
30164
|
}
|
|
29898
30165
|
|
|
@@ -29955,16 +30222,16 @@ function MovableCircle(props) {
|
|
|
29955
30222
|
className: `movable-circle ${dragging ? "movable-circle--dragging" : ""}`
|
|
29956
30223
|
}, /*#__PURE__*/React.createElement("ellipse", {
|
|
29957
30224
|
className: "focus-ring",
|
|
29958
|
-
cx: centerPx[
|
|
29959
|
-
cy: centerPx[
|
|
29960
|
-
rx: radiiPx[
|
|
29961
|
-
ry: radiiPx[
|
|
30225
|
+
cx: centerPx[X],
|
|
30226
|
+
cy: centerPx[Y],
|
|
30227
|
+
rx: radiiPx[X] + 3,
|
|
30228
|
+
ry: radiiPx[Y] + 3
|
|
29962
30229
|
}), /*#__PURE__*/React.createElement("ellipse", {
|
|
29963
30230
|
className: "circle",
|
|
29964
|
-
cx: centerPx[
|
|
29965
|
-
cy: centerPx[
|
|
29966
|
-
rx: radiiPx[
|
|
29967
|
-
ry: radiiPx[
|
|
30231
|
+
cx: centerPx[X],
|
|
30232
|
+
cy: centerPx[Y],
|
|
30233
|
+
rx: radiiPx[X],
|
|
30234
|
+
ry: radiiPx[Y]
|
|
29968
30235
|
}), /*#__PURE__*/React.createElement(DragHandle, {
|
|
29969
30236
|
center: center
|
|
29970
30237
|
}));
|
|
@@ -29980,15 +30247,16 @@ function DragHandle(props) {
|
|
|
29980
30247
|
const topLeft = vec.sub(centerPx, vec.scale(dragHandleDimensions, 0.5));
|
|
29981
30248
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("rect", {
|
|
29982
30249
|
className: "movable-circle-handle",
|
|
29983
|
-
x: topLeft[
|
|
29984
|
-
y: topLeft[
|
|
29985
|
-
width: dragHandleDimensions[
|
|
29986
|
-
height: dragHandleDimensions[
|
|
30250
|
+
x: topLeft[X],
|
|
30251
|
+
y: topLeft[Y],
|
|
30252
|
+
width: dragHandleDimensions[X],
|
|
30253
|
+
height: dragHandleDimensions[Y],
|
|
29987
30254
|
rx: cornerRadius,
|
|
29988
30255
|
ry: cornerRadius
|
|
29989
30256
|
}), dragHandleDotPositions.map(offsetPx => {
|
|
29990
30257
|
const [xPx, yPx] = vec.add(offsetPx, centerPx);
|
|
29991
30258
|
return /*#__PURE__*/React.createElement("circle", {
|
|
30259
|
+
key: `circle-${xPx}-${yPx}`,
|
|
29992
30260
|
className: "movable-circle-handle-dot",
|
|
29993
30261
|
cx: xPx,
|
|
29994
30262
|
cy: yPx
|
|
@@ -30117,15 +30385,15 @@ const getSinusoidCoefficients = coords => {
|
|
|
30117
30385
|
const p2 = coords[1];
|
|
30118
30386
|
|
|
30119
30387
|
// If the x-coordinates are the same, we are unable to calculate the coefficients
|
|
30120
|
-
if (p2[
|
|
30388
|
+
if (p2[X] === p1[X]) {
|
|
30121
30389
|
return;
|
|
30122
30390
|
}
|
|
30123
30391
|
|
|
30124
30392
|
// Resulting coefficients are canonical for this sine curve
|
|
30125
|
-
const amplitude = p2[
|
|
30126
|
-
const angularFrequency = Math.PI / (2 * (p2[
|
|
30127
|
-
const phase = p1[
|
|
30128
|
-
const verticalOffset = p1[
|
|
30393
|
+
const amplitude = p2[Y] - p1[Y];
|
|
30394
|
+
const angularFrequency = Math.PI / (2 * (p2[X] - p1[X]));
|
|
30395
|
+
const phase = p1[X] * angularFrequency;
|
|
30396
|
+
const verticalOffset = p1[Y];
|
|
30129
30397
|
return {
|
|
30130
30398
|
amplitude,
|
|
30131
30399
|
angularFrequency,
|
|
@@ -30134,6 +30402,74 @@ const getSinusoidCoefficients = coords => {
|
|
|
30134
30402
|
};
|
|
30135
30403
|
};
|
|
30136
30404
|
|
|
30405
|
+
function AngleGraph(props) {
|
|
30406
|
+
const {
|
|
30407
|
+
dispatch,
|
|
30408
|
+
graphState
|
|
30409
|
+
} = props;
|
|
30410
|
+
const {
|
|
30411
|
+
graphDimensionsInPixels
|
|
30412
|
+
} = useGraphConfig();
|
|
30413
|
+
const {
|
|
30414
|
+
coords,
|
|
30415
|
+
showAngles,
|
|
30416
|
+
range,
|
|
30417
|
+
allowReflexAngles,
|
|
30418
|
+
angleOffsetDeg,
|
|
30419
|
+
snapDegrees
|
|
30420
|
+
} = graphState;
|
|
30421
|
+
|
|
30422
|
+
// Break the coords into the two end points and the center point
|
|
30423
|
+
const endPoints = [coords[0], coords[2]];
|
|
30424
|
+
const centerPoint = coords[1];
|
|
30425
|
+
|
|
30426
|
+
// Convert the vectors to pixels for rendering the svg lines
|
|
30427
|
+
const angleLines = [[centerPoint, endPoints[0]], [centerPoint, endPoints[1]]];
|
|
30428
|
+
const linePixelCoords = [useTransformVectorsToPixels(centerPoint, endPoints[0]), useTransformVectorsToPixels(centerPoint, endPoints[1])];
|
|
30429
|
+
|
|
30430
|
+
// Create the SVG lines for the angle
|
|
30431
|
+
const svgLines = linePixelCoords.map(([startPtPx, endPtPx], i) => {
|
|
30432
|
+
const trimmedRange = trimRange(range, graphDimensionsInPixels);
|
|
30433
|
+
const endExtend = getIntersectionOfRayWithBox(angleLines[i][0], angleLines[i][1], trimmedRange);
|
|
30434
|
+
return /*#__PURE__*/React.createElement("g", {
|
|
30435
|
+
key: `line-${i}`
|
|
30436
|
+
}, /*#__PURE__*/React.createElement(SVGLine, {
|
|
30437
|
+
start: startPtPx,
|
|
30438
|
+
end: endPtPx,
|
|
30439
|
+
style: {
|
|
30440
|
+
stroke: "var(--movable-line-stroke-color)",
|
|
30441
|
+
strokeWidth: "var(--movable-line-stroke-weight)"
|
|
30442
|
+
}
|
|
30443
|
+
}), /*#__PURE__*/React.createElement(Vector, {
|
|
30444
|
+
tail: angleLines[i][1],
|
|
30445
|
+
tip: endExtend,
|
|
30446
|
+
color: "var(--movable-line-stroke-color)"
|
|
30447
|
+
}));
|
|
30448
|
+
});
|
|
30449
|
+
|
|
30450
|
+
// Create the angle indicator parameters
|
|
30451
|
+
const angleParams = {
|
|
30452
|
+
vertex: centerPoint,
|
|
30453
|
+
coords: endPoints,
|
|
30454
|
+
allowReflexAngles: allowReflexAngles || false,
|
|
30455
|
+
// Whether to allow reflex angles or not
|
|
30456
|
+
angleOffsetDeg: angleOffsetDeg || 0,
|
|
30457
|
+
// The angle offset from the x-axis
|
|
30458
|
+
snapDegrees: snapDegrees || 1,
|
|
30459
|
+
// The multiple of degrees to snap to
|
|
30460
|
+
range: range,
|
|
30461
|
+
showAngles: showAngles || false // Whether to show the angle or not
|
|
30462
|
+
};
|
|
30463
|
+
|
|
30464
|
+
// Render the lines, angle, and then movable points
|
|
30465
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, svgLines, /*#__PURE__*/React.createElement(Angle, angleParams), coords.map((point, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
|
|
30466
|
+
key: "point-" + i,
|
|
30467
|
+
snapTo: "angles",
|
|
30468
|
+
point: point,
|
|
30469
|
+
onMove: destination => dispatch(movePoint(i, destination))
|
|
30470
|
+
})));
|
|
30471
|
+
}
|
|
30472
|
+
|
|
30137
30473
|
// Universal padding for the axis ticks to ensure that the labels
|
|
30138
30474
|
// have enough space to render without overlapping the tick lines
|
|
30139
30475
|
const tickPadding = 5;
|
|
@@ -30143,10 +30479,12 @@ const AxisTickLabels = () => {
|
|
|
30143
30479
|
tickStep,
|
|
30144
30480
|
range
|
|
30145
30481
|
} = graphConfig;
|
|
30146
|
-
|
|
30147
|
-
|
|
30148
|
-
const
|
|
30149
|
-
const
|
|
30482
|
+
|
|
30483
|
+
// TODO(benchristel): use destructuring here
|
|
30484
|
+
const [xMin, xMax] = range[X];
|
|
30485
|
+
const [yMin, yMax] = range[Y];
|
|
30486
|
+
const yTickStep = tickStep[Y];
|
|
30487
|
+
const xTickStep = tickStep[X];
|
|
30150
30488
|
const yGridTicks = generateTickLocations$1(yTickStep, yMin, yMax);
|
|
30151
30489
|
const xGridTicks = generateTickLocations$1(xTickStep, xMin, xMax);
|
|
30152
30490
|
return /*#__PURE__*/React.createElement("div", {
|
|
@@ -30425,13 +30763,13 @@ const YGridTick = ({
|
|
|
30425
30763
|
|
|
30426
30764
|
// If the graph is zoomed in, we want to make sure the ticks are still visible
|
|
30427
30765
|
// even if they are outside the graph's range.
|
|
30428
|
-
if (graphInfo.range[
|
|
30766
|
+
if (graphInfo.range[X][MIN] > 0) {
|
|
30429
30767
|
// If the graph is on the positive side of the x-axis, lock the ticks to the left side of the graph
|
|
30430
|
-
xPointOnAxis = graphInfo.range[
|
|
30768
|
+
xPointOnAxis = graphInfo.range[X][MIN];
|
|
30431
30769
|
}
|
|
30432
|
-
if (graphInfo.range[
|
|
30770
|
+
if (graphInfo.range[X][MAX] < 0) {
|
|
30433
30771
|
// If the graph is on the negative side of the x-axis, lock the ticks to the right side of the graph
|
|
30434
|
-
xPointOnAxis = graphInfo.range[
|
|
30772
|
+
xPointOnAxis = graphInfo.range[X][MAX];
|
|
30435
30773
|
}
|
|
30436
30774
|
const pointOnAxis = [xPointOnAxis, y];
|
|
30437
30775
|
const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
|
|
@@ -30465,13 +30803,13 @@ const XGridTick = ({
|
|
|
30465
30803
|
let yPointOnAxis = 0;
|
|
30466
30804
|
// If the graph is zoomed in, we want to make sure the ticks are still visible
|
|
30467
30805
|
// even if they are outside the graph's range.
|
|
30468
|
-
if (graphInfo.range[
|
|
30806
|
+
if (graphInfo.range[Y][MIN] > 0) {
|
|
30469
30807
|
// If the graph is on the positive side of the y-axis, lock the ticks to the top of the graph
|
|
30470
|
-
yPointOnAxis = graphInfo.range[
|
|
30808
|
+
yPointOnAxis = graphInfo.range[Y][MIN];
|
|
30471
30809
|
}
|
|
30472
|
-
if (graphInfo.range[
|
|
30810
|
+
if (graphInfo.range[Y][MAX] < 0) {
|
|
30473
30811
|
// If the graph is on the negative side of the x-axis, lock the ticks to the bottom of the graph
|
|
30474
|
-
yPointOnAxis = graphInfo.range[
|
|
30812
|
+
yPointOnAxis = graphInfo.range[Y][MAX];
|
|
30475
30813
|
}
|
|
30476
30814
|
const pointOnAxis = [x, yPointOnAxis];
|
|
30477
30815
|
const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
|
|
@@ -30526,10 +30864,12 @@ const AxisTicks = () => {
|
|
|
30526
30864
|
width,
|
|
30527
30865
|
height
|
|
30528
30866
|
};
|
|
30529
|
-
|
|
30530
|
-
|
|
30531
|
-
const
|
|
30532
|
-
const
|
|
30867
|
+
|
|
30868
|
+
// TODO(benchristel): destructure these in one line
|
|
30869
|
+
const [xMin, xMax] = range[X];
|
|
30870
|
+
const [yMin, yMax] = range[Y];
|
|
30871
|
+
const yTickStep = tickStep[Y];
|
|
30872
|
+
const xTickStep = tickStep[X];
|
|
30533
30873
|
const yGridTicks = generateTickLocations(yTickStep, yMin, yMax);
|
|
30534
30874
|
const xGridTicks = generateTickLocations(xTickStep, xMin, xMax);
|
|
30535
30875
|
return /*#__PURE__*/React.createElement("g", {
|
|
@@ -30568,8 +30908,8 @@ const axisOptions = (props, axisIndex) => {
|
|
|
30568
30908
|
};
|
|
30569
30909
|
const Grid = props => {
|
|
30570
30910
|
return props.markings === "none" ? null : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Coordinates.Cartesian, {
|
|
30571
|
-
xAxis: axisOptions(props,
|
|
30572
|
-
yAxis: axisOptions(props,
|
|
30911
|
+
xAxis: axisOptions(props, X),
|
|
30912
|
+
yAxis: axisOptions(props, Y)
|
|
30573
30913
|
}),
|
|
30574
30914
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
30575
30915
|
props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)));
|
|
@@ -31178,7 +31518,7 @@ const LegacyGrid = ({
|
|
|
31178
31518
|
height
|
|
31179
31519
|
} = backgroundImage != null ? backgroundImage : {};
|
|
31180
31520
|
if (url && typeof url === "string") {
|
|
31181
|
-
const scale = box[
|
|
31521
|
+
const scale = box[X] / interactiveSizes$1.defaultBoxSize;
|
|
31182
31522
|
return /*#__PURE__*/React.createElement(View, {
|
|
31183
31523
|
style: {
|
|
31184
31524
|
position: "absolute",
|
|
@@ -31243,9 +31583,9 @@ function Protractor() {
|
|
|
31243
31583
|
const angle = calculateAngleInDegrees(rotationHandleOffset) - calculateAngleInDegrees(centerToRotationHandle);
|
|
31244
31584
|
return /*#__PURE__*/React.createElement("g", {
|
|
31245
31585
|
ref: draggableRef,
|
|
31246
|
-
transform: `translate(${topLeftPx[
|
|
31586
|
+
transform: `translate(${topLeftPx[X]}, ${topLeftPx[Y]}), rotate(${angle})`,
|
|
31247
31587
|
style: {
|
|
31248
|
-
transformOrigin: `${-centerToTopLeft[
|
|
31588
|
+
transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`
|
|
31249
31589
|
}
|
|
31250
31590
|
}, /*#__PURE__*/React.createElement("image", {
|
|
31251
31591
|
href: protractorImage
|
|
@@ -31410,7 +31750,18 @@ function initializeGraphState(params) {
|
|
|
31410
31750
|
coords: getSinusoidCoords(graph, range, step)
|
|
31411
31751
|
});
|
|
31412
31752
|
case "angle":
|
|
31413
|
-
|
|
31753
|
+
return _extends({}, shared, {
|
|
31754
|
+
type: graph.type,
|
|
31755
|
+
showAngles: Boolean(graph.showAngles),
|
|
31756
|
+
coords: getAngleCoords({
|
|
31757
|
+
graph,
|
|
31758
|
+
range,
|
|
31759
|
+
step
|
|
31760
|
+
}),
|
|
31761
|
+
angleOffsetDeg: Number(graph.angleOffsetDeg),
|
|
31762
|
+
allowReflexAngles: Boolean(graph.allowReflexAngles),
|
|
31763
|
+
snapDegrees: Number(graph.snapDegrees)
|
|
31764
|
+
});
|
|
31414
31765
|
default:
|
|
31415
31766
|
throw new UnreachableCaseError(graph);
|
|
31416
31767
|
}
|
|
@@ -31544,6 +31895,43 @@ function getCircleCoords(graph) {
|
|
|
31544
31895
|
radiusPoint: [2, 0]
|
|
31545
31896
|
};
|
|
31546
31897
|
}
|
|
31898
|
+
const getAngleCoords = params => {
|
|
31899
|
+
const {
|
|
31900
|
+
graph,
|
|
31901
|
+
range,
|
|
31902
|
+
step
|
|
31903
|
+
} = params;
|
|
31904
|
+
if (graph.coords) {
|
|
31905
|
+
return graph.coords;
|
|
31906
|
+
}
|
|
31907
|
+
const {
|
|
31908
|
+
snapDegrees,
|
|
31909
|
+
angleOffsetDeg
|
|
31910
|
+
} = graph;
|
|
31911
|
+
const snap = snapDegrees || 1;
|
|
31912
|
+
let angle = snap;
|
|
31913
|
+
while (angle < 20) {
|
|
31914
|
+
angle += snap;
|
|
31915
|
+
}
|
|
31916
|
+
angle = angle * Math.PI / 180;
|
|
31917
|
+
const offset = (angleOffsetDeg || 0) * Math.PI / 180;
|
|
31918
|
+
let defaultCoords = [[0.85, 0.5], [0.5, 0.5]];
|
|
31919
|
+
defaultCoords = normalizePoints(range, step, defaultCoords, true);
|
|
31920
|
+
|
|
31921
|
+
// @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.
|
|
31922
|
+
const radius = magnitude(vector(...defaultCoords));
|
|
31923
|
+
|
|
31924
|
+
// We're adding a placeholder for the third point to appease ts and so that we
|
|
31925
|
+
// can calculate it after we've adjusted the first point by the angleOffsetDeg
|
|
31926
|
+
const coords = [...defaultCoords, [0, 0]];
|
|
31927
|
+
|
|
31928
|
+
// Adjust the lower point by angleOffsetDeg degrees
|
|
31929
|
+
coords[0] = [coords[1][0] + radius * Math.cos(offset), coords[1][1] + radius * Math.sin(offset)];
|
|
31930
|
+
// Position the upper point angle radians from the
|
|
31931
|
+
// lower point
|
|
31932
|
+
coords[2] = [coords[1][0] + radius * Math.cos(angle + offset), coords[1][1] + radius * Math.sin(angle + offset)];
|
|
31933
|
+
return coords;
|
|
31934
|
+
};
|
|
31547
31935
|
|
|
31548
31936
|
function interactiveGraphReducer(state, action) {
|
|
31549
31937
|
switch (action.type) {
|
|
@@ -31605,6 +31993,7 @@ function doMoveControlPoint(state, action) {
|
|
|
31605
31993
|
coords: newCoords
|
|
31606
31994
|
});
|
|
31607
31995
|
}
|
|
31996
|
+
case "angle":
|
|
31608
31997
|
case "circle":
|
|
31609
31998
|
throw new Error("FIXME implement circle reducer");
|
|
31610
31999
|
case "point":
|
|
@@ -31706,12 +32095,30 @@ function doMoveAll(state, action) {
|
|
|
31706
32095
|
}
|
|
31707
32096
|
function doMovePoint(state, action) {
|
|
31708
32097
|
switch (state.type) {
|
|
32098
|
+
case "angle":
|
|
32099
|
+
// If the index is 1, we are moving the vertex of the angle,
|
|
32100
|
+
// which will move the other two points as well
|
|
32101
|
+
if (action.index === 1) {
|
|
32102
|
+
const updatedCoords = boundAndSnapAngleVertex(state, action);
|
|
32103
|
+
return _extends({}, state, {
|
|
32104
|
+
hasBeenInteractedWith: true,
|
|
32105
|
+
coords: updatedCoords
|
|
32106
|
+
});
|
|
32107
|
+
}
|
|
32108
|
+
return _extends({}, state, {
|
|
32109
|
+
hasBeenInteractedWith: true,
|
|
32110
|
+
coords: setAtIndex({
|
|
32111
|
+
array: state.coords,
|
|
32112
|
+
index: action.index,
|
|
32113
|
+
newValue: boundAndSnapAngleEndPoints(action.destination, state, action.index)
|
|
32114
|
+
})
|
|
32115
|
+
});
|
|
31709
32116
|
case "polygon":
|
|
31710
32117
|
let newValue;
|
|
31711
32118
|
if (state.snapTo === "sides") {
|
|
31712
32119
|
newValue = boundAndSnapToSides(action.destination, state, action.index);
|
|
31713
32120
|
} else if (state.snapTo === "angles") {
|
|
31714
|
-
newValue =
|
|
32121
|
+
newValue = boundAndSnapToPolygonAngle(action.destination, state, action.index);
|
|
31715
32122
|
} else {
|
|
31716
32123
|
newValue = boundAndSnapToGrid(action.destination, state);
|
|
31717
32124
|
}
|
|
@@ -31750,7 +32157,7 @@ function doMovePoint(state, action) {
|
|
|
31750
32157
|
// vertical line. If they are, then we don't want to move the point
|
|
31751
32158
|
const _newCoords = [...state.coords];
|
|
31752
32159
|
_newCoords[action.index] = boundDestination;
|
|
31753
|
-
if (_newCoords[0][
|
|
32160
|
+
if (_newCoords[0][X] === _newCoords[1][X]) {
|
|
31754
32161
|
return state;
|
|
31755
32162
|
}
|
|
31756
32163
|
return _extends({}, state, {
|
|
@@ -31808,13 +32215,13 @@ function doMoveCenter(state, action) {
|
|
|
31808
32215
|
// if it otherwise would be off the chart
|
|
31809
32216
|
// ex: if the handle is on the right and we move the center
|
|
31810
32217
|
// to the rightmost position, move the handle to the left
|
|
31811
|
-
const [xMin, xMax] = state.range[
|
|
32218
|
+
const [xMin, xMax] = state.range[X];
|
|
31812
32219
|
const [radX] = newRadiusPoint;
|
|
31813
32220
|
if (radX < xMin || radX > xMax) {
|
|
31814
|
-
const xJumpDist = (radX - constrainedCenter[
|
|
32221
|
+
const xJumpDist = (radX - constrainedCenter[X]) * 2;
|
|
31815
32222
|
const possibleNewX = radX - xJumpDist;
|
|
31816
32223
|
if (possibleNewX >= xMin && possibleNewX <= xMax) {
|
|
31817
|
-
newRadiusPoint[
|
|
32224
|
+
newRadiusPoint[X] = possibleNewX;
|
|
31818
32225
|
}
|
|
31819
32226
|
}
|
|
31820
32227
|
return _extends({}, state, {
|
|
@@ -31831,11 +32238,11 @@ function doMoveRadiusPoint(state, action) {
|
|
|
31831
32238
|
switch (state.type) {
|
|
31832
32239
|
case "circle":
|
|
31833
32240
|
{
|
|
31834
|
-
const [xMin, xMax] = state.range[
|
|
32241
|
+
const [xMin, xMax] = state.range[X];
|
|
31835
32242
|
const nextRadiusPoint = [
|
|
31836
32243
|
// Constrain to graph range
|
|
31837
32244
|
// The +0 is to convert -0 to +0
|
|
31838
|
-
|
|
32245
|
+
clamp(action.destination[X] + 0, xMin, xMax), state.center[1]];
|
|
31839
32246
|
if (_.isEqual(nextRadiusPoint, state.center)) {
|
|
31840
32247
|
return state;
|
|
31841
32248
|
}
|
|
@@ -31870,10 +32277,10 @@ function doChangeRange(state, action) {
|
|
|
31870
32277
|
}
|
|
31871
32278
|
const getDeltaVertex = (maxMoves, minMoves, delta) => {
|
|
31872
32279
|
const [deltaX, deltaY] = delta;
|
|
31873
|
-
const maxXMove = Math.min(...maxMoves.map(move => move[
|
|
31874
|
-
const maxYMove = Math.min(...maxMoves.map(move => move[
|
|
31875
|
-
const minXMove = Math.max(...minMoves.map(move => move[
|
|
31876
|
-
const minYMove = Math.max(...minMoves.map(move => move[
|
|
32280
|
+
const maxXMove = Math.min(...maxMoves.map(move => move[X]));
|
|
32281
|
+
const maxYMove = Math.min(...maxMoves.map(move => move[Y]));
|
|
32282
|
+
const minXMove = Math.max(...minMoves.map(move => move[X]));
|
|
32283
|
+
const minYMove = Math.max(...minMoves.map(move => move[Y]));
|
|
31877
32284
|
const dx = clamp(deltaX, minXMove, maxXMove);
|
|
31878
32285
|
const dy = clamp(deltaY, minYMove, maxYMove);
|
|
31879
32286
|
return [dx, dy];
|
|
@@ -31904,7 +32311,122 @@ function boundAndSnapToGrid(point, {
|
|
|
31904
32311
|
point
|
|
31905
32312
|
}));
|
|
31906
32313
|
}
|
|
31907
|
-
function
|
|
32314
|
+
function boundAndSnapAngleVertex({
|
|
32315
|
+
range,
|
|
32316
|
+
coords,
|
|
32317
|
+
snapStep
|
|
32318
|
+
}, {
|
|
32319
|
+
destination
|
|
32320
|
+
}) {
|
|
32321
|
+
// Needed to prevent updating the original coords before the checks for
|
|
32322
|
+
// degenerate triangles and overlapping sides
|
|
32323
|
+
const coordsCopy = [...coords];
|
|
32324
|
+
|
|
32325
|
+
// Get the current and upcoming positions of the vertex
|
|
32326
|
+
const startingVertex = coordsCopy[1];
|
|
32327
|
+
const newVertex = bound$1({
|
|
32328
|
+
snapStep,
|
|
32329
|
+
range,
|
|
32330
|
+
point: snap(snapStep, destination)
|
|
32331
|
+
});
|
|
32332
|
+
|
|
32333
|
+
// Get the vector from the starting vertex to the new vertex
|
|
32334
|
+
const delta = vec.add(newVertex, reverseVector(startingVertex));
|
|
32335
|
+
|
|
32336
|
+
// Apply the delta to each of the other two points so that the angle is maintained
|
|
32337
|
+
let valid = true;
|
|
32338
|
+
const newPoints = {};
|
|
32339
|
+
for (const i of [0, 2]) {
|
|
32340
|
+
const oldPoint = coordsCopy[i];
|
|
32341
|
+
let newPoint = vec.add(oldPoint, delta);
|
|
32342
|
+
let angle = findAngle(newVertex, newPoint);
|
|
32343
|
+
angle *= Math.PI / 180;
|
|
32344
|
+
newPoint = constrainToBoundsOnAngle(newPoint, angle, range, snapStep);
|
|
32345
|
+
newPoints[i] = newPoint;
|
|
32346
|
+
|
|
32347
|
+
// Check if the new point is too close to the vertex
|
|
32348
|
+
if (tooClose(newVertex, newPoint)) {
|
|
32349
|
+
valid = false;
|
|
32350
|
+
}
|
|
32351
|
+
}
|
|
32352
|
+
|
|
32353
|
+
// Update the vertex after snapping to the snapStep
|
|
32354
|
+
newPoints[1] = newVertex;
|
|
32355
|
+
// Only move points if all new positions are valid
|
|
32356
|
+
if (valid) {
|
|
32357
|
+
Object.entries(newPoints).forEach(([i, newPoint]) => {
|
|
32358
|
+
coordsCopy[i] = newPoint;
|
|
32359
|
+
});
|
|
32360
|
+
}
|
|
32361
|
+
return coordsCopy;
|
|
32362
|
+
}
|
|
32363
|
+
|
|
32364
|
+
// This function is used to ensure that the vertex of
|
|
32365
|
+
// an angle is not too close to the other points
|
|
32366
|
+
function tooClose(point1, point2, range) {
|
|
32367
|
+
const safeDistance = 2;
|
|
32368
|
+
const distance = vec.dist(point1, point2);
|
|
32369
|
+
return distance < safeDistance;
|
|
32370
|
+
}
|
|
32371
|
+
function constrainToBoundsOnAngle(point, angle, range, snapStep) {
|
|
32372
|
+
// We're subtracting the snapStep from the lower bound and adding it to the upper bound
|
|
32373
|
+
// to ensure that the point is within the bounds of the graph even after snapping to the nearest degree
|
|
32374
|
+
const lower = [range[0][0] + snapStep[0], range[1][0] + snapStep[0]];
|
|
32375
|
+
const upper = [range[0][1] - snapStep[1], range[1][1] - snapStep[1]];
|
|
32376
|
+
let result = point;
|
|
32377
|
+
if (result[0] < lower[0]) {
|
|
32378
|
+
result = [lower[0], result[1] + (lower[0] - result[0]) * Math.tan(angle)];
|
|
32379
|
+
} else if (result[0] > upper[0]) {
|
|
32380
|
+
result = [upper[0], result[1] - (result[0] - upper[0]) * Math.tan(angle)];
|
|
32381
|
+
}
|
|
32382
|
+
if (result[1] < lower[1]) {
|
|
32383
|
+
result = [result[0] + (lower[1] - result[1]) / Math.tan(angle), lower[1]];
|
|
32384
|
+
} else if (result[1] > upper[1]) {
|
|
32385
|
+
result = [result[0] - (result[1] - upper[1]) / Math.tan(angle), upper[1]];
|
|
32386
|
+
}
|
|
32387
|
+
return result;
|
|
32388
|
+
}
|
|
32389
|
+
function boundAndSnapAngleEndPoints(destinationPoint, {
|
|
32390
|
+
range,
|
|
32391
|
+
coords,
|
|
32392
|
+
snapDegrees,
|
|
32393
|
+
angleOffsetDeg,
|
|
32394
|
+
snapStep
|
|
32395
|
+
}, index) {
|
|
32396
|
+
const snap = snapDegrees || 1;
|
|
32397
|
+
const offsetDegrees = angleOffsetDeg || 0;
|
|
32398
|
+
|
|
32399
|
+
// Needed to prevent updating the original coords before the checks for
|
|
32400
|
+
// degenerate triangles and overlapping sides
|
|
32401
|
+
const coordsCopy = [...coords];
|
|
32402
|
+
|
|
32403
|
+
// We want to subtract or add the snapStep to the lower and upper bounds
|
|
32404
|
+
// respectively to ensure that the point is within the bounds of the graph
|
|
32405
|
+
// even after snapping to the nearest degree
|
|
32406
|
+
const angleRange = [[range[0][0] + snapStep[0], range[0][1] - snapStep[0]], [range[1][0] + snapStep[1], range[1][1] - snapStep[1]]];
|
|
32407
|
+
|
|
32408
|
+
// Takes the destination point and makes sure it is within the bounds of the graph
|
|
32409
|
+
// SnapStep is [0, 0] because we don't want to snap these points to the grid at all
|
|
32410
|
+
const boundPoint = bound$1({
|
|
32411
|
+
snapStep: [0, 0],
|
|
32412
|
+
range: angleRange,
|
|
32413
|
+
point: destinationPoint
|
|
32414
|
+
});
|
|
32415
|
+
coordsCopy[index] = boundPoint;
|
|
32416
|
+
|
|
32417
|
+
// Get the vertex of the angle
|
|
32418
|
+
const vertex = coords[1];
|
|
32419
|
+
|
|
32420
|
+
// Gets the angle between the coords and the vertex
|
|
32421
|
+
let angle = findAngle(coordsCopy[index], vertex);
|
|
32422
|
+
|
|
32423
|
+
// Snap the angle to the nearest multiple of snapDegrees (if provided)
|
|
32424
|
+
angle = Math.round((angle - offsetDegrees) / snap) * snap + offsetDegrees;
|
|
32425
|
+
const distance = vec.dist(coordsCopy[index], vertex);
|
|
32426
|
+
const snappedValue = vec.add(vertex, polar(distance, angle));
|
|
32427
|
+
return snappedValue;
|
|
32428
|
+
}
|
|
32429
|
+
function boundAndSnapToPolygonAngle(destinationPoint, {
|
|
31908
32430
|
range,
|
|
31909
32431
|
coords
|
|
31910
32432
|
}, index) {
|
|
@@ -31935,7 +32457,7 @@ function boundAndSnapToAngle(destinationPoint, {
|
|
|
31935
32457
|
angles[rel(j)] = Math.round(angles[rel(j)]);
|
|
31936
32458
|
});
|
|
31937
32459
|
const getAngle = function getAngle(a, vertex, b) {
|
|
31938
|
-
const angle =
|
|
32460
|
+
const angle = findAngle(coordsCopy[rel(a)], coordsCopy[rel(b)], coordsCopy[rel(vertex)]);
|
|
31939
32461
|
return (angle + 360) % 360;
|
|
31940
32462
|
};
|
|
31941
32463
|
const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
|
|
@@ -31963,7 +32485,7 @@ function boundAndSnapToAngle(destinationPoint, {
|
|
|
31963
32485
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
31964
32486
|
|
|
31965
32487
|
// Angle at the second vertex of the polygon
|
|
31966
|
-
const outerAngle =
|
|
32488
|
+
const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
|
|
31967
32489
|
|
|
31968
32490
|
// Uses the length of the side of the polygon (radial coordinate)
|
|
31969
32491
|
// and the angle between the first and second sides of the
|
|
@@ -32013,7 +32535,7 @@ function boundAndSnapToSides(destinationPoint, {
|
|
|
32013
32535
|
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
32014
32536
|
|
|
32015
32537
|
// Angle at the second vertex of the polygon
|
|
32016
|
-
const outerAngle =
|
|
32538
|
+
const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
|
|
32017
32539
|
|
|
32018
32540
|
// Returns true if the points form a counter-clockwise turn;
|
|
32019
32541
|
// a.k.a if the point is on the left or right of the polygon.
|
|
@@ -32091,6 +32613,11 @@ const renderGraph = props => {
|
|
|
32091
32613
|
type
|
|
32092
32614
|
} = state;
|
|
32093
32615
|
switch (type) {
|
|
32616
|
+
case "angle":
|
|
32617
|
+
return /*#__PURE__*/React.createElement(AngleGraph, {
|
|
32618
|
+
graphState: state,
|
|
32619
|
+
dispatch: dispatch
|
|
32620
|
+
});
|
|
32094
32621
|
case "segment":
|
|
32095
32622
|
return /*#__PURE__*/React.createElement(SegmentGraph, {
|
|
32096
32623
|
graphState: state,
|
|
@@ -32250,8 +32777,8 @@ const MafsGraph = props => {
|
|
|
32250
32777
|
}, props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null), /*#__PURE__*/React.createElement(AxisTickLabels, null)), /*#__PURE__*/React.createElement(Mafs, {
|
|
32251
32778
|
preserveAspectRatio: false,
|
|
32252
32779
|
viewBox: {
|
|
32253
|
-
x: state.range[
|
|
32254
|
-
y: state.range[
|
|
32780
|
+
x: state.range[X],
|
|
32781
|
+
y: state.range[Y],
|
|
32255
32782
|
padding: 0
|
|
32256
32783
|
},
|
|
32257
32784
|
pan: false,
|
|
@@ -33092,7 +33619,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
33092
33619
|
// Solve for side by using the law of sines
|
|
33093
33620
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
33094
33621
|
const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
|
|
33095
|
-
const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
33622
|
+
const offset = polar$1(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
33096
33623
|
return (_this$graphie5 = this.graphie) == null ? void 0 : _this$graphie5.addPoints(coords[rel(-1)], offset);
|
|
33097
33624
|
}
|
|
33098
33625
|
// @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
|
|
@@ -33117,7 +33644,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
33117
33644
|
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
33118
33645
|
const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
|
|
33119
33646
|
const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
|
|
33120
|
-
const offset = polar(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
33647
|
+
const offset = polar$1(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
33121
33648
|
return (_this$graphie6 = this.graphie) == null ? void 0 : _this$graphie6.addPoints(coords[rel(-1)], offset);
|
|
33122
33649
|
}
|
|
33123
33650
|
// Snap to grid (already done)
|
|
@@ -33778,11 +34305,9 @@ class InteractiveGraph extends React.Component {
|
|
|
33778
34305
|
const radius = magnitude(vector(...coords));
|
|
33779
34306
|
|
|
33780
34307
|
// Adjust the lower point by angleOffsetDeg degrees
|
|
33781
|
-
// @ts-expect-error - TS2542 - Index signature in type 'readonly Coord[]' only permits reading.
|
|
33782
34308
|
coords[0] = [coords[1][0] + radius * Math.cos(offset), coords[1][1] + radius * Math.sin(offset)];
|
|
33783
34309
|
// Position the upper point angle radians from the
|
|
33784
34310
|
// lower point
|
|
33785
|
-
// @ts-expect-error - TS2542 - Index signature in type 'readonly Coord[]' only permits reading.
|
|
33786
34311
|
coords[2] = [coords[1][0] + radius * Math.cos(angle + offset), coords[1][1] + radius * Math.sin(angle + offset)];
|
|
33787
34312
|
return coords;
|
|
33788
34313
|
}
|
|
@@ -44395,7 +44920,7 @@ const init = function init(options) {
|
|
|
44395
44920
|
|
|
44396
44921
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44397
44922
|
const libName = "@khanacademy/perseus";
|
|
44398
|
-
const libVersion = "24.
|
|
44923
|
+
const libVersion = "24.3.0";
|
|
44399
44924
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44400
44925
|
|
|
44401
44926
|
const apiVersion = {
|