@khanacademy/perseus 24.2.0 → 24.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/index.css +7 -0
- package/dist/es/index.js +654 -130
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +7 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +677 -141
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +1 -0
- package/dist/perseus-types.d.ts +17 -2
- package/dist/server-item-renderer.d.ts +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/widgets/expression.d.ts +6 -0
- package/dist/widgets/interactive-graph.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/angle.d.ts +5 -0
- package/dist/widgets/interactive-graphs/graphs/components/{angle.d.ts → angle-indicators.d.ts} +12 -2
- package/dist/widgets/interactive-graphs/graphs/components/movable-line.d.ts +11 -0
- package/dist/widgets/interactive-graphs/graphs/components/text-label.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/index.d.ts +1 -0
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +3 -1
- package/dist/widgets/interactive-graphs/locked-figures/locked-function.d.ts +4 -0
- package/dist/widgets/interactive-graphs/mafs-supported-graph-types.d.ts +1 -1
- package/dist/widgets/interactive-graphs/math/angles.d.ts +3 -0
- package/dist/widgets/interactive-graphs/math/box.d.ts +4 -0
- package/dist/widgets/interactive-graphs/math/clamp.d.ts +1 -0
- package/dist/widgets/interactive-graphs/math/coordinates.d.ts +2 -0
- package/dist/widgets/interactive-graphs/math/index.d.ts +6 -0
- package/dist/widgets/interactive-graphs/math/interval.d.ts +5 -0
- package/dist/widgets/interactive-graphs/math/snap.d.ts +2 -0
- package/dist/widgets/interactive-graphs/types.d.ts +9 -1
- package/dist/widgets/interactive-graphs/utils.d.ts +0 -2
- package/dist/widgets/label-image.d.ts +2 -0
- package/package.json +2 -2
package/dist/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,10 +30247,10 @@ 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 => {
|
|
@@ -30118,15 +30385,15 @@ const getSinusoidCoefficients = coords => {
|
|
|
30118
30385
|
const p2 = coords[1];
|
|
30119
30386
|
|
|
30120
30387
|
// If the x-coordinates are the same, we are unable to calculate the coefficients
|
|
30121
|
-
if (p2[
|
|
30388
|
+
if (p2[X] === p1[X]) {
|
|
30122
30389
|
return;
|
|
30123
30390
|
}
|
|
30124
30391
|
|
|
30125
30392
|
// Resulting coefficients are canonical for this sine curve
|
|
30126
|
-
const amplitude = p2[
|
|
30127
|
-
const angularFrequency = Math.PI / (2 * (p2[
|
|
30128
|
-
const phase = p1[
|
|
30129
|
-
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];
|
|
30130
30397
|
return {
|
|
30131
30398
|
amplitude,
|
|
30132
30399
|
angularFrequency,
|
|
@@ -30135,6 +30402,74 @@ const getSinusoidCoefficients = coords => {
|
|
|
30135
30402
|
};
|
|
30136
30403
|
};
|
|
30137
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
|
+
|
|
30138
30473
|
// Universal padding for the axis ticks to ensure that the labels
|
|
30139
30474
|
// have enough space to render without overlapping the tick lines
|
|
30140
30475
|
const tickPadding = 5;
|
|
@@ -30144,10 +30479,12 @@ const AxisTickLabels = () => {
|
|
|
30144
30479
|
tickStep,
|
|
30145
30480
|
range
|
|
30146
30481
|
} = graphConfig;
|
|
30147
|
-
|
|
30148
|
-
|
|
30149
|
-
const
|
|
30150
|
-
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];
|
|
30151
30488
|
const yGridTicks = generateTickLocations$1(yTickStep, yMin, yMax);
|
|
30152
30489
|
const xGridTicks = generateTickLocations$1(xTickStep, xMin, xMax);
|
|
30153
30490
|
return /*#__PURE__*/React.createElement("div", {
|
|
@@ -30426,13 +30763,13 @@ const YGridTick = ({
|
|
|
30426
30763
|
|
|
30427
30764
|
// If the graph is zoomed in, we want to make sure the ticks are still visible
|
|
30428
30765
|
// even if they are outside the graph's range.
|
|
30429
|
-
if (graphInfo.range[
|
|
30766
|
+
if (graphInfo.range[X][MIN] > 0) {
|
|
30430
30767
|
// If the graph is on the positive side of the x-axis, lock the ticks to the left side of the graph
|
|
30431
|
-
xPointOnAxis = graphInfo.range[
|
|
30768
|
+
xPointOnAxis = graphInfo.range[X][MIN];
|
|
30432
30769
|
}
|
|
30433
|
-
if (graphInfo.range[
|
|
30770
|
+
if (graphInfo.range[X][MAX] < 0) {
|
|
30434
30771
|
// If the graph is on the negative side of the x-axis, lock the ticks to the right side of the graph
|
|
30435
|
-
xPointOnAxis = graphInfo.range[
|
|
30772
|
+
xPointOnAxis = graphInfo.range[X][MAX];
|
|
30436
30773
|
}
|
|
30437
30774
|
const pointOnAxis = [xPointOnAxis, y];
|
|
30438
30775
|
const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
|
|
@@ -30466,13 +30803,13 @@ const XGridTick = ({
|
|
|
30466
30803
|
let yPointOnAxis = 0;
|
|
30467
30804
|
// If the graph is zoomed in, we want to make sure the ticks are still visible
|
|
30468
30805
|
// even if they are outside the graph's range.
|
|
30469
|
-
if (graphInfo.range[
|
|
30806
|
+
if (graphInfo.range[Y][MIN] > 0) {
|
|
30470
30807
|
// If the graph is on the positive side of the y-axis, lock the ticks to the top of the graph
|
|
30471
|
-
yPointOnAxis = graphInfo.range[
|
|
30808
|
+
yPointOnAxis = graphInfo.range[Y][MIN];
|
|
30472
30809
|
}
|
|
30473
|
-
if (graphInfo.range[
|
|
30810
|
+
if (graphInfo.range[Y][MAX] < 0) {
|
|
30474
30811
|
// If the graph is on the negative side of the x-axis, lock the ticks to the bottom of the graph
|
|
30475
|
-
yPointOnAxis = graphInfo.range[
|
|
30812
|
+
yPointOnAxis = graphInfo.range[Y][MAX];
|
|
30476
30813
|
}
|
|
30477
30814
|
const pointOnAxis = [x, yPointOnAxis];
|
|
30478
30815
|
const [[xPosition, yPosition]] = useTransformVectorsToPixels(pointOnAxis);
|
|
@@ -30527,10 +30864,12 @@ const AxisTicks = () => {
|
|
|
30527
30864
|
width,
|
|
30528
30865
|
height
|
|
30529
30866
|
};
|
|
30530
|
-
|
|
30531
|
-
|
|
30532
|
-
const
|
|
30533
|
-
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];
|
|
30534
30873
|
const yGridTicks = generateTickLocations(yTickStep, yMin, yMax);
|
|
30535
30874
|
const xGridTicks = generateTickLocations(xTickStep, xMin, xMax);
|
|
30536
30875
|
return /*#__PURE__*/React.createElement("g", {
|
|
@@ -30569,8 +30908,8 @@ const axisOptions = (props, axisIndex) => {
|
|
|
30569
30908
|
};
|
|
30570
30909
|
const Grid = props => {
|
|
30571
30910
|
return props.markings === "none" ? null : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Coordinates.Cartesian, {
|
|
30572
|
-
xAxis: axisOptions(props,
|
|
30573
|
-
yAxis: axisOptions(props,
|
|
30911
|
+
xAxis: axisOptions(props, X),
|
|
30912
|
+
yAxis: axisOptions(props, Y)
|
|
30574
30913
|
}),
|
|
30575
30914
|
// Only render the axis ticks and arrows if the markings are set to a full "graph"
|
|
30576
30915
|
props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisTicks, null), /*#__PURE__*/React.createElement(AxisArrows, null)));
|
|
@@ -31179,7 +31518,7 @@ const LegacyGrid = ({
|
|
|
31179
31518
|
height
|
|
31180
31519
|
} = backgroundImage != null ? backgroundImage : {};
|
|
31181
31520
|
if (url && typeof url === "string") {
|
|
31182
|
-
const scale = box[
|
|
31521
|
+
const scale = box[X] / interactiveSizes$1.defaultBoxSize;
|
|
31183
31522
|
return /*#__PURE__*/React.createElement(View, {
|
|
31184
31523
|
style: {
|
|
31185
31524
|
position: "absolute",
|
|
@@ -31244,9 +31583,9 @@ function Protractor() {
|
|
|
31244
31583
|
const angle = calculateAngleInDegrees(rotationHandleOffset) - calculateAngleInDegrees(centerToRotationHandle);
|
|
31245
31584
|
return /*#__PURE__*/React.createElement("g", {
|
|
31246
31585
|
ref: draggableRef,
|
|
31247
|
-
transform: `translate(${topLeftPx[
|
|
31586
|
+
transform: `translate(${topLeftPx[X]}, ${topLeftPx[Y]}), rotate(${angle})`,
|
|
31248
31587
|
style: {
|
|
31249
|
-
transformOrigin: `${-centerToTopLeft[
|
|
31588
|
+
transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`
|
|
31250
31589
|
}
|
|
31251
31590
|
}, /*#__PURE__*/React.createElement("image", {
|
|
31252
31591
|
href: protractorImage
|
|
@@ -31411,7 +31750,18 @@ function initializeGraphState(params) {
|
|
|
31411
31750
|
coords: getSinusoidCoords(graph, range, step)
|
|
31412
31751
|
});
|
|
31413
31752
|
case "angle":
|
|
31414
|
-
|
|
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
|
+
});
|
|
31415
31765
|
default:
|
|
31416
31766
|
throw new UnreachableCaseError(graph);
|
|
31417
31767
|
}
|
|
@@ -31545,6 +31895,43 @@ function getCircleCoords(graph) {
|
|
|
31545
31895
|
radiusPoint: [2, 0]
|
|
31546
31896
|
};
|
|
31547
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
|
+
};
|
|
31548
31935
|
|
|
31549
31936
|
function interactiveGraphReducer(state, action) {
|
|
31550
31937
|
switch (action.type) {
|
|
@@ -31606,6 +31993,7 @@ function doMoveControlPoint(state, action) {
|
|
|
31606
31993
|
coords: newCoords
|
|
31607
31994
|
});
|
|
31608
31995
|
}
|
|
31996
|
+
case "angle":
|
|
31609
31997
|
case "circle":
|
|
31610
31998
|
throw new Error("FIXME implement circle reducer");
|
|
31611
31999
|
case "point":
|
|
@@ -31707,12 +32095,30 @@ function doMoveAll(state, action) {
|
|
|
31707
32095
|
}
|
|
31708
32096
|
function doMovePoint(state, action) {
|
|
31709
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
|
+
});
|
|
31710
32116
|
case "polygon":
|
|
31711
32117
|
let newValue;
|
|
31712
32118
|
if (state.snapTo === "sides") {
|
|
31713
32119
|
newValue = boundAndSnapToSides(action.destination, state, action.index);
|
|
31714
32120
|
} else if (state.snapTo === "angles") {
|
|
31715
|
-
newValue =
|
|
32121
|
+
newValue = boundAndSnapToPolygonAngle(action.destination, state, action.index);
|
|
31716
32122
|
} else {
|
|
31717
32123
|
newValue = boundAndSnapToGrid(action.destination, state);
|
|
31718
32124
|
}
|
|
@@ -31751,7 +32157,7 @@ function doMovePoint(state, action) {
|
|
|
31751
32157
|
// vertical line. If they are, then we don't want to move the point
|
|
31752
32158
|
const _newCoords = [...state.coords];
|
|
31753
32159
|
_newCoords[action.index] = boundDestination;
|
|
31754
|
-
if (_newCoords[0][
|
|
32160
|
+
if (_newCoords[0][X] === _newCoords[1][X]) {
|
|
31755
32161
|
return state;
|
|
31756
32162
|
}
|
|
31757
32163
|
return _extends({}, state, {
|
|
@@ -31809,13 +32215,13 @@ function doMoveCenter(state, action) {
|
|
|
31809
32215
|
// if it otherwise would be off the chart
|
|
31810
32216
|
// ex: if the handle is on the right and we move the center
|
|
31811
32217
|
// to the rightmost position, move the handle to the left
|
|
31812
|
-
const [xMin, xMax] = state.range[
|
|
32218
|
+
const [xMin, xMax] = state.range[X];
|
|
31813
32219
|
const [radX] = newRadiusPoint;
|
|
31814
32220
|
if (radX < xMin || radX > xMax) {
|
|
31815
|
-
const xJumpDist = (radX - constrainedCenter[
|
|
32221
|
+
const xJumpDist = (radX - constrainedCenter[X]) * 2;
|
|
31816
32222
|
const possibleNewX = radX - xJumpDist;
|
|
31817
32223
|
if (possibleNewX >= xMin && possibleNewX <= xMax) {
|
|
31818
|
-
newRadiusPoint[
|
|
32224
|
+
newRadiusPoint[X] = possibleNewX;
|
|
31819
32225
|
}
|
|
31820
32226
|
}
|
|
31821
32227
|
return _extends({}, state, {
|
|
@@ -31832,11 +32238,11 @@ function doMoveRadiusPoint(state, action) {
|
|
|
31832
32238
|
switch (state.type) {
|
|
31833
32239
|
case "circle":
|
|
31834
32240
|
{
|
|
31835
|
-
const [xMin, xMax] = state.range[
|
|
32241
|
+
const [xMin, xMax] = state.range[X];
|
|
31836
32242
|
const nextRadiusPoint = [
|
|
31837
32243
|
// Constrain to graph range
|
|
31838
32244
|
// The +0 is to convert -0 to +0
|
|
31839
|
-
|
|
32245
|
+
clamp(action.destination[X] + 0, xMin, xMax), state.center[1]];
|
|
31840
32246
|
if (_.isEqual(nextRadiusPoint, state.center)) {
|
|
31841
32247
|
return state;
|
|
31842
32248
|
}
|
|
@@ -31871,10 +32277,10 @@ function doChangeRange(state, action) {
|
|
|
31871
32277
|
}
|
|
31872
32278
|
const getDeltaVertex = (maxMoves, minMoves, delta) => {
|
|
31873
32279
|
const [deltaX, deltaY] = delta;
|
|
31874
|
-
const maxXMove = Math.min(...maxMoves.map(move => move[
|
|
31875
|
-
const maxYMove = Math.min(...maxMoves.map(move => move[
|
|
31876
|
-
const minXMove = Math.max(...minMoves.map(move => move[
|
|
31877
|
-
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]));
|
|
31878
32284
|
const dx = clamp(deltaX, minXMove, maxXMove);
|
|
31879
32285
|
const dy = clamp(deltaY, minYMove, maxYMove);
|
|
31880
32286
|
return [dx, dy];
|
|
@@ -31905,7 +32311,122 @@ function boundAndSnapToGrid(point, {
|
|
|
31905
32311
|
point
|
|
31906
32312
|
}));
|
|
31907
32313
|
}
|
|
31908
|
-
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, {
|
|
31909
32430
|
range,
|
|
31910
32431
|
coords
|
|
31911
32432
|
}, index) {
|
|
@@ -31936,7 +32457,7 @@ function boundAndSnapToAngle(destinationPoint, {
|
|
|
31936
32457
|
angles[rel(j)] = Math.round(angles[rel(j)]);
|
|
31937
32458
|
});
|
|
31938
32459
|
const getAngle = function getAngle(a, vertex, b) {
|
|
31939
|
-
const angle =
|
|
32460
|
+
const angle = findAngle(coordsCopy[rel(a)], coordsCopy[rel(b)], coordsCopy[rel(vertex)]);
|
|
31940
32461
|
return (angle + 360) % 360;
|
|
31941
32462
|
};
|
|
31942
32463
|
const innerAngles = [angles[rel(-1)] - getAngle(-2, -1, 1), angles[rel(1)] - getAngle(-1, 1, 2)];
|
|
@@ -31964,7 +32485,7 @@ function boundAndSnapToAngle(destinationPoint, {
|
|
|
31964
32485
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
31965
32486
|
|
|
31966
32487
|
// Angle at the second vertex of the polygon
|
|
31967
|
-
const outerAngle =
|
|
32488
|
+
const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
|
|
31968
32489
|
|
|
31969
32490
|
// Uses the length of the side of the polygon (radial coordinate)
|
|
31970
32491
|
// and the angle between the first and second sides of the
|
|
@@ -32014,7 +32535,7 @@ function boundAndSnapToSides(destinationPoint, {
|
|
|
32014
32535
|
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
32015
32536
|
|
|
32016
32537
|
// Angle at the second vertex of the polygon
|
|
32017
|
-
const outerAngle =
|
|
32538
|
+
const outerAngle = findAngle(coordsCopy[rel(1)], coordsCopy[rel(-1)]);
|
|
32018
32539
|
|
|
32019
32540
|
// Returns true if the points form a counter-clockwise turn;
|
|
32020
32541
|
// a.k.a if the point is on the left or right of the polygon.
|
|
@@ -32092,6 +32613,11 @@ const renderGraph = props => {
|
|
|
32092
32613
|
type
|
|
32093
32614
|
} = state;
|
|
32094
32615
|
switch (type) {
|
|
32616
|
+
case "angle":
|
|
32617
|
+
return /*#__PURE__*/React.createElement(AngleGraph, {
|
|
32618
|
+
graphState: state,
|
|
32619
|
+
dispatch: dispatch
|
|
32620
|
+
});
|
|
32095
32621
|
case "segment":
|
|
32096
32622
|
return /*#__PURE__*/React.createElement(SegmentGraph, {
|
|
32097
32623
|
graphState: state,
|
|
@@ -32251,8 +32777,8 @@ const MafsGraph = props => {
|
|
|
32251
32777
|
}, props.markings === "graph" && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AxisLabels, null), /*#__PURE__*/React.createElement(AxisTickLabels, null)), /*#__PURE__*/React.createElement(Mafs, {
|
|
32252
32778
|
preserveAspectRatio: false,
|
|
32253
32779
|
viewBox: {
|
|
32254
|
-
x: state.range[
|
|
32255
|
-
y: state.range[
|
|
32780
|
+
x: state.range[X],
|
|
32781
|
+
y: state.range[Y],
|
|
32256
32782
|
padding: 0
|
|
32257
32783
|
},
|
|
32258
32784
|
pan: false,
|
|
@@ -33093,7 +33619,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
33093
33619
|
// Solve for side by using the law of sines
|
|
33094
33620
|
const side = Math.sin(innerAngles[1] * Math.PI / 180) / Math.sin(innerAngles[2] * Math.PI / 180) * knownSide;
|
|
33095
33621
|
const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
|
|
33096
|
-
const offset = polar(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
33622
|
+
const offset = polar$1(side, outerAngle + (onLeft ? 1 : -1) * innerAngles[0]);
|
|
33097
33623
|
return (_this$graphie5 = this.graphie) == null ? void 0 : _this$graphie5.addPoints(coords[rel(-1)], offset);
|
|
33098
33624
|
}
|
|
33099
33625
|
// @ts-expect-error - TS2339 - Property 'snapTo' does not exist on type 'PerseusGraphType'.
|
|
@@ -33118,7 +33644,7 @@ class LegacyInteractiveGraph extends React.Component {
|
|
|
33118
33644
|
const innerAngle = lawOfCosines(sides[0], sides[2], sides[1]);
|
|
33119
33645
|
const outerAngle = GraphUtils.findAngle(coords[rel(1)], coords[rel(-1)]);
|
|
33120
33646
|
const onLeft = sign(ccw(coords[rel(-1)], coords[rel(1)], coords[i])) === 1;
|
|
33121
|
-
const offset = polar(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
33647
|
+
const offset = polar$1(sides[0], outerAngle + (onLeft ? 1 : -1) * innerAngle);
|
|
33122
33648
|
return (_this$graphie6 = this.graphie) == null ? void 0 : _this$graphie6.addPoints(coords[rel(-1)], offset);
|
|
33123
33649
|
}
|
|
33124
33650
|
// Snap to grid (already done)
|
|
@@ -33779,11 +34305,9 @@ class InteractiveGraph extends React.Component {
|
|
|
33779
34305
|
const radius = magnitude(vector(...coords));
|
|
33780
34306
|
|
|
33781
34307
|
// Adjust the lower point by angleOffsetDeg degrees
|
|
33782
|
-
// @ts-expect-error - TS2542 - Index signature in type 'readonly Coord[]' only permits reading.
|
|
33783
34308
|
coords[0] = [coords[1][0] + radius * Math.cos(offset), coords[1][1] + radius * Math.sin(offset)];
|
|
33784
34309
|
// Position the upper point angle radians from the
|
|
33785
34310
|
// lower point
|
|
33786
|
-
// @ts-expect-error - TS2542 - Index signature in type 'readonly Coord[]' only permits reading.
|
|
33787
34311
|
coords[2] = [coords[1][0] + radius * Math.cos(angle + offset), coords[1][1] + radius * Math.sin(angle + offset)];
|
|
33788
34312
|
return coords;
|
|
33789
34313
|
}
|
|
@@ -44396,7 +44920,7 @@ const init = function init(options) {
|
|
|
44396
44920
|
|
|
44397
44921
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44398
44922
|
const libName = "@khanacademy/perseus";
|
|
44399
|
-
const libVersion = "24.
|
|
44923
|
+
const libVersion = "24.3.0";
|
|
44400
44924
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44401
44925
|
|
|
44402
44926
|
const apiVersion = {
|