diff --git a/.changeset/blue-lamps-invent.md b/.changeset/blue-lamps-invent.md
new file mode 100644
index 0000000000..6a5ef16942
--- /dev/null
+++ b/.changeset/blue-lamps-invent.md
@@ -0,0 +1,8 @@
+---
+"@khanacademy/perseus": minor
+"@khanacademy/perseus-core": minor
+"@khanacademy/perseus-editor": minor
+"@khanacademy/perseus-linter": minor
+---
+
+Add `showAngle` field to `LockedPolygonPointType`. This option controls the display of an angle label per-vertex. It defaults to false.
diff --git a/__docs__/sample-data.ts b/__docs__/sample-data.ts
index 8aeb3d9ca2..3a0c2d2081 100644
--- a/__docs__/sample-data.ts
+++ b/__docs__/sample-data.ts
@@ -84,11 +84,11 @@ export const graphExample: PerseusRenderer = {
weight: "thick",
fillStyle: "translucent",
points: [
- {coord: [7, -3]},
- {coord: [8, -5]},
- {coord: [4, -7]},
- {coord: [0, -7]},
- {coord: [2, -3]},
+ {coord: [7, -3], showAngle: false},
+ {coord: [8, -5], showAngle: false},
+ {coord: [4, -7], showAngle: false},
+ {coord: [0, -7], showAngle: false},
+ {coord: [2, -3], showAngle: false},
],
showVertices: false,
strokeStyle: "solid",
diff --git a/packages/perseus-core/src/data-schema.ts b/packages/perseus-core/src/data-schema.ts
index 6f3a6af83a..b3f9a64b29 100644
--- a/packages/perseus-core/src/data-schema.ts
+++ b/packages/perseus-core/src/data-schema.ts
@@ -1077,6 +1077,7 @@ export type LockedEllipseType = {
export type LockedPolygonPointType = {
coord: Coord;
+ showAngle: boolean;
};
export type LockedPolygonType = {
diff --git a/packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.test.ts b/packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.test.ts
index fdf24e5201..1f38dd958a 100644
--- a/packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.test.ts
+++ b/packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.test.ts
@@ -385,7 +385,7 @@ describe("parseInteractiveGraphWidget", () => {
);
});
- it("parses locked polygon points with coord field", () => {
+ it("parses locked polygon points with coord and showAngle fields", () => {
const result = parse(
{
type: "interactive-graph",
@@ -411,7 +411,7 @@ describe("parseInteractiveGraphWidget", () => {
{
type: "polygon",
points: [
- {coord: [0, 0]},
+ {coord: [0, 0], showAngle: true},
{coord: [1, 0]},
{coord: [1, 1]},
],
@@ -455,9 +455,9 @@ describe("parseInteractiveGraphWidget", () => {
{
type: "polygon",
points: [
- {coord: [0, 0]},
- {coord: [1, 0]},
- {coord: [1, 1]},
+ {coord: [0, 0], showAngle: true},
+ {coord: [1, 0], showAngle: false},
+ {coord: [1, 1], showAngle: false},
],
color: "blue",
showVertices: false,
@@ -542,9 +542,9 @@ describe("parseInteractiveGraphWidget", () => {
{
type: "polygon",
points: [
- {coord: [0, 0]},
- {coord: [1, 0]},
- {coord: [1, 1]},
+ {coord: [0, 0], showAngle: false},
+ {coord: [1, 0], showAngle: false},
+ {coord: [1, 1], showAngle: false},
],
color: "blue",
showVertices: false,
diff --git a/packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.ts b/packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.ts
index 3c92d9cc0e..e0d29ca978 100644
--- a/packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.ts
+++ b/packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.ts
@@ -258,6 +258,7 @@ const parseLockedEllipseType = object({
const parseLockedPolygonPointType = object({
coord: pairOfNumbers,
+ showAngle: defaulted(boolean, () => false),
});
const parseLockedPolygonPointsType = union(
@@ -266,7 +267,7 @@ const parseLockedPolygonPointsType = union(
// Normalize legacy representation of points as Coord[] to
// LockedPolygonPointType[].
pipeParsers(array(pairOfNumbers)).then(
- convert((coords) => coords.map((coord) => ({coord}))),
+ convert((coords) => coords.map((coord) => ({coord, showAngle: false}))),
).parser,
).parser;
diff --git a/packages/perseus-core/src/utils/generators/interactive-graph-widget-generator.test.ts b/packages/perseus-core/src/utils/generators/interactive-graph-widget-generator.test.ts
index a7ed5fc288..af474a8228 100644
--- a/packages/perseus-core/src/utils/generators/interactive-graph-widget-generator.test.ts
+++ b/packages/perseus-core/src/utils/generators/interactive-graph-widget-generator.test.ts
@@ -1180,15 +1180,9 @@ describe("generateIGLockedPolygon", () => {
expect(lockedPolygon).toEqual({
type: "polygon",
points: [
- {
- coord: [0, 2],
- },
- {
- coord: [-1, 0],
- },
- {
- coord: [1, 0],
- },
+ {coord: [0, 2], showAngle: false},
+ {coord: [-1, 0], showAngle: false},
+ {coord: [1, 0], showAngle: false},
],
color: "grayH",
showVertices: false,
@@ -1203,15 +1197,9 @@ describe("generateIGLockedPolygon", () => {
// Arrange, Act
const lockedPolygon = generateIGLockedPolygon({
points: [
- {
- coord: [1, 1],
- },
- {
- coord: [2, 2],
- },
- {
- coord: [3, 3],
- },
+ {coord: [1, 1], showAngle: false},
+ {coord: [2, 2], showAngle: true},
+ {coord: [3, 3], showAngle: false},
],
color: "blue",
showVertices: true,
@@ -1234,15 +1222,9 @@ describe("generateIGLockedPolygon", () => {
expect(lockedPolygon).toEqual({
type: "polygon",
points: [
- {
- coord: [1, 1],
- },
- {
- coord: [2, 2],
- },
- {
- coord: [3, 3],
- },
+ {coord: [1, 1], showAngle: false},
+ {coord: [2, 2], showAngle: true},
+ {coord: [3, 3], showAngle: false},
],
color: "blue",
showVertices: true,
diff --git a/packages/perseus-core/src/utils/get-default-figure-for-type.test.ts b/packages/perseus-core/src/utils/get-default-figure-for-type.test.ts
index 8ce8c98502..f8012c65ab 100644
--- a/packages/perseus-core/src/utils/get-default-figure-for-type.test.ts
+++ b/packages/perseus-core/src/utils/get-default-figure-for-type.test.ts
@@ -75,7 +75,11 @@ describe("getDefaultFigureForType", () => {
const figure = getDefaultFigureForType("polygon");
expect(figure).toEqual({
type: "polygon",
- points: [{coord: [0, 2]}, {coord: [-1, 0]}, {coord: [1, 0]}],
+ points: [
+ {coord: [0, 2], showAngle: false},
+ {coord: [-1, 0], showAngle: false},
+ {coord: [1, 0], showAngle: false},
+ ],
color: "grayH",
showVertices: false,
fillStyle: "none",
diff --git a/packages/perseus-core/src/utils/get-default-figure-for-type.ts b/packages/perseus-core/src/utils/get-default-figure-for-type.ts
index 4c6428d808..1aa2aaa849 100644
--- a/packages/perseus-core/src/utils/get-default-figure-for-type.ts
+++ b/packages/perseus-core/src/utils/get-default-figure-for-type.ts
@@ -77,7 +77,11 @@ export function getDefaultFigureForType(type: LockedFigureType): LockedFigure {
case "polygon":
return {
type: "polygon",
- points: [{coord: [0, 2]}, {coord: [-1, 0]}, {coord: [1, 0]}],
+ points: [
+ {coord: [0, 2], showAngle: false},
+ {coord: [-1, 0], showAngle: false},
+ {coord: [1, 0], showAngle: false},
+ ],
color: DEFAULT_COLOR,
showVertices: false,
fillStyle: "none",
diff --git a/packages/perseus-editor/src/__testdata__/interactive-graph.testdata.ts b/packages/perseus-editor/src/__testdata__/interactive-graph.testdata.ts
index 08cb8ecb26..e5dc56ff49 100644
--- a/packages/perseus-editor/src/__testdata__/interactive-graph.testdata.ts
+++ b/packages/perseus-editor/src/__testdata__/interactive-graph.testdata.ts
@@ -265,10 +265,10 @@ export const segmentWithLockedFigures: PerseusRenderer =
}),
generateIGLockedPolygon({
points: [
- {coord: [-9, 4]},
- {coord: [-6, 4]},
- {coord: [-6, 1]},
- {coord: [-9, 1]},
+ {coord: [-9, 4], showAngle: false},
+ {coord: [-6, 4], showAngle: false},
+ {coord: [-6, 1], showAngle: false},
+ {coord: [-9, 1], showAngle: false},
],
color: "pink",
labels: [
diff --git a/packages/perseus-editor/src/widgets/__tests__/interactive-graph-editor-locked-figures.test.tsx b/packages/perseus-editor/src/widgets/__tests__/interactive-graph-editor-locked-figures.test.tsx
index 9fc17d6561..75efab8fd3 100644
--- a/packages/perseus-editor/src/widgets/__tests__/interactive-graph-editor-locked-figures.test.tsx
+++ b/packages/perseus-editor/src/widgets/__tests__/interactive-graph-editor-locked-figures.test.tsx
@@ -1079,7 +1079,10 @@ describe("InteractiveGraphEditor locked figures", () => {
lockedFigures: [
expect.objectContaining({
type: "polygon",
- points: [...defaultPolygon.points, {coord: [0, 0]}],
+ points: [
+ ...defaultPolygon.points,
+ {coord: [0, 0], showAngle: false},
+ ],
}),
],
}),
@@ -1091,10 +1094,10 @@ describe("InteractiveGraphEditor locked figures", () => {
const onChangeMock = jest.fn();
const squarePolygonPoints = [
- {coord: [-9, 4]},
- {coord: [-6, 4]},
- {coord: [-6, 1]},
- {coord: [-9, 1]},
+ {coord: [-9, 4], showAngle: false},
+ {coord: [-6, 4], showAngle: false},
+ {coord: [-6, 1], showAngle: false},
+ {coord: [-9, 1], showAngle: false},
];
renderEditor({
@@ -1120,9 +1123,9 @@ describe("InteractiveGraphEditor locked figures", () => {
expect.objectContaining({
type: "polygon",
points: [
- {coord: [-6, 4]},
- {coord: [-6, 1]},
- {coord: [-9, 1]},
+ {coord: [-6, 4], showAngle: false},
+ {coord: [-6, 1], showAngle: false},
+ {coord: [-9, 1], showAngle: false},
],
}),
],
@@ -1153,9 +1156,9 @@ describe("InteractiveGraphEditor locked figures", () => {
expect.objectContaining({
type: "polygon",
points: [
- {coord: [7, 2]},
- {coord: [-1, 0]},
- {coord: [1, 0]},
+ {coord: [7, 2], showAngle: false},
+ {coord: [-1, 0], showAngle: false},
+ {coord: [1, 0], showAngle: false},
],
}),
],
@@ -1186,9 +1189,9 @@ describe("InteractiveGraphEditor locked figures", () => {
expect.objectContaining({
type: "polygon",
points: [
- {coord: [0, 7]},
- {coord: [-1, 0]},
- {coord: [1, 0]},
+ {coord: [0, 7], showAngle: false},
+ {coord: [-1, 0], showAngle: false},
+ {coord: [1, 0], showAngle: false},
],
}),
],
diff --git a/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-polygon-settings.test.tsx b/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-polygon-settings.test.tsx
index 8682dfc16d..09c9fd4071 100644
--- a/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-polygon-settings.test.tsx
+++ b/packages/perseus-editor/src/widgets/interactive-graph-editor/locked-figures/locked-polygon-settings.test.tsx
@@ -47,10 +47,10 @@ describe("LockedPolygonSettings", () => {
,
{
@@ -119,10 +119,10 @@ describe("LockedPolygonSettings", () => {
,
{
@@ -185,10 +185,10 @@ describe("LockedPolygonSettings", () => {
labels={[initialLabel]}
onChangeProps={onChangeSpy}
points={[
- {coord: [1, 1]},
- {coord: [2, 1]},
- {coord: [2, 2]},
- {coord: [1, 2]},
+ {coord: [1, 1], showAngle: false},
+ {coord: [2, 1], showAngle: false},
+ {coord: [2, 2], showAngle: false},
+ {coord: [1, 2], showAngle: false},
]}
/>,
{
@@ -204,10 +204,10 @@ describe("LockedPolygonSettings", () => {
expect(onChangeSpy).toHaveBeenCalledWith({
labels: [{...initialLabel, coord: [0, 1]}],
points: [
- {coord: [1, 2]},
- {coord: [2, 2]},
- {coord: [2, 3]},
- {coord: [1, 3]},
+ {coord: [1, 2], showAngle: false},
+ {coord: [2, 2], showAngle: false},
+ {coord: [2, 3], showAngle: false},
+ {coord: [1, 3], showAngle: false},
],
});
});
@@ -228,10 +228,10 @@ describe("LockedPolygonSettings", () => {
labels={[initialLabel]}
onChangeProps={onChangeSpy}
points={[
- {coord: [1, 1]},
- {coord: [2, 1]},
- {coord: [2, 2]},
- {coord: [1, 2]},
+ {coord: [1, 1], showAngle: false},
+ {coord: [2, 1], showAngle: false},
+ {coord: [2, 2], showAngle: false},
+ {coord: [1, 2], showAngle: false},
]}
/>,
{
@@ -247,10 +247,10 @@ describe("LockedPolygonSettings", () => {
expect(onChangeSpy).toHaveBeenCalledWith({
labels: [{...initialLabel, coord: [0, -1]}],
points: [
- {coord: [1, 0]},
- {coord: [2, 0]},
- {coord: [2, 1]},
- {coord: [1, 1]},
+ {coord: [1, 0], showAngle: false},
+ {coord: [2, 0], showAngle: false},
+ {coord: [2, 1], showAngle: false},
+ {coord: [1, 1], showAngle: false},
],
});
});
@@ -271,10 +271,10 @@ describe("LockedPolygonSettings", () => {
onChangeProps={onChangeSpy}
labels={[initialLabel]}
points={[
- {coord: [1, 1]},
- {coord: [2, 1]},
- {coord: [2, 2]},
- {coord: [1, 2]},
+ {coord: [1, 1], showAngle: false},
+ {coord: [2, 1], showAngle: false},
+ {coord: [2, 2], showAngle: false},
+ {coord: [1, 2], showAngle: false},
]}
/>,
{
@@ -290,10 +290,10 @@ describe("LockedPolygonSettings", () => {
expect(onChangeSpy).toHaveBeenCalledWith({
labels: [{...initialLabel, coord: [-1, 0]}],
points: [
- {coord: [0, 1]},
- {coord: [1, 1]},
- {coord: [1, 2]},
- {coord: [0, 2]},
+ {coord: [0, 1], showAngle: false},
+ {coord: [1, 1], showAngle: false},
+ {coord: [1, 2], showAngle: false},
+ {coord: [0, 2], showAngle: false},
],
});
});
@@ -314,10 +314,10 @@ describe("LockedPolygonSettings", () => {
onChangeProps={onChangeSpy}
labels={[initialLabel]}
points={[
- {coord: [1, 1]},
- {coord: [2, 1]},
- {coord: [2, 2]},
- {coord: [1, 2]},
+ {coord: [1, 1], showAngle: false},
+ {coord: [2, 1], showAngle: false},
+ {coord: [2, 2], showAngle: false},
+ {coord: [1, 2], showAngle: false},
]}
/>,
{
@@ -333,10 +333,10 @@ describe("LockedPolygonSettings", () => {
expect(onChangeSpy).toHaveBeenCalledWith({
labels: [{...initialLabel, coord: [1, 0]}],
points: [
- {coord: [2, 1]},
- {coord: [3, 1]},
- {coord: [3, 2]},
- {coord: [2, 2]},
+ {coord: [2, 1], showAngle: false},
+ {coord: [3, 1], showAngle: false},
+ {coord: [3, 2], showAngle: false},
+ {coord: [2, 2], showAngle: false},
],
});
});
@@ -364,6 +364,61 @@ describe("LockedPolygonSettings", () => {
expect(onChangeSpy).toHaveBeenCalledWith({weight: "thick"});
});
+ test("calls onChange when a point angle switch is toggled", async () => {
+ // Arrange
+ const onChangeSpy = jest.fn();
+ render(
+ ,
+ {
+ wrapper: RenderStateRoot,
+ },
+ );
+
+ // Act
+ const showAngleSwitch = screen.getByLabelText("show angle at B");
+ await userEvent.click(showAngleSwitch);
+
+ // Assert
+ expect(onChangeSpy).toHaveBeenCalledWith({
+ points: [
+ {coord: [0, 0], showAngle: false},
+ {coord: [1, 0], showAngle: true},
+ {coord: [1, 1], showAngle: false},
+ ],
+ });
+ });
+
+ test("calls onChange with showAngle false when a point is added", async () => {
+ // Arrange
+ const onChangeSpy = jest.fn();
+ render(
+ ,
+ {
+ wrapper: RenderStateRoot,
+ },
+ );
+
+ // Act
+ const addPointButton = screen.getByRole("button", {name: "Add point"});
+ await userEvent.click(addPointButton);
+
+ // Assert
+ expect(onChangeSpy).toHaveBeenCalledWith({
+ points: [...defaultProps.points, {coord: [0, 0], showAngle: false}],
+ });
+ });
+
describe("Labels", () => {
test("Renders a label when a label is provided", () => {
// Arrange
@@ -407,7 +462,10 @@ describe("LockedPolygonSettings", () => {
render(
({coord}))}
+ points={startingCoords.map((coord) => ({
+ coord,
+ showAngle: false,
+ }))}
labels={[
{
...defaultLabel,
@@ -433,7 +491,10 @@ describe("LockedPolygonSettings", () => {
// Assert
expect(onChangeProps).toHaveBeenCalledWith({
- points: expectedCoords.map((coord) => ({coord})),
+ points: expectedCoords.map((coord) => ({
+ coord,
+ showAngle: false,
+ })),
labels: [
{
...defaultLabel,
@@ -542,7 +603,11 @@ describe("LockedPolygonSettings", () => {
render(
{
render(
,
@@ -655,7 +724,11 @@ describe("LockedPolygonSettings", () => {
render(
{
render(
{
props.onChangeProps({points: newPoints});
}}
/>
+
+ {
+ const newPoints = [...points];
+ newPoints[index] = {
+ ...point,
+ showAngle: newValue,
+ };
+ props.onChangeProps({points: newPoints});
+ }}
+ />
{
// Only show the minus (delete) buttons if there are
// more than 3 points. 3 points is the minimum number
@@ -336,7 +350,10 @@ const LockedPolygonSettings = (props: Props) => {
startIcon={plusCircle}
onClick={() => {
props.onChangeProps({
- points: [...points, {coord: [0, 0]}],
+ points: [
+ ...points,
+ {coord: [0, 0], showAngle: false},
+ ],
});
}}
>
diff --git a/packages/perseus-linter/src/rules/interactive-graph-widget-error.test.ts b/packages/perseus-linter/src/rules/interactive-graph-widget-error.test.ts
index c7eb3894f4..1d17ca63c0 100644
--- a/packages/perseus-linter/src/rules/interactive-graph-widget-error.test.ts
+++ b/packages/perseus-linter/src/rules/interactive-graph-widget-error.test.ts
@@ -51,9 +51,9 @@ describe("interactive-graph-widget-error", () => {
lockedFigures: [
generateIGLockedPolygon({
points: [
- {coord: [0, 0]},
- {coord: [0, 0]},
- {coord: [0, 0]},
+ {coord: [0, 0], showAngle: false},
+ {coord: [0, 0], showAngle: false},
+ {coord: [0, 0], showAngle: false},
],
}),
],
@@ -153,9 +153,9 @@ describe("interactive-graph-widget-error", () => {
}),
generateIGLockedPolygon({
points: [
- {coord: [0, 0]},
- {coord: [0, 2]},
- {coord: [1, 1]},
+ {coord: [0, 0], showAngle: false},
+ {coord: [0, 2], showAngle: false},
+ {coord: [1, 1], showAngle: false},
],
}),
generateIGLockedEllipse({radius: [2, 2]}),
diff --git a/packages/perseus/src/widgets/interactive-graphs/__docs__/interactive-graph-initial-state-regression.stories.tsx b/packages/perseus/src/widgets/interactive-graphs/__docs__/interactive-graph-initial-state-regression.stories.tsx
index 43be5439a9..307b03a12d 100644
--- a/packages/perseus/src/widgets/interactive-graphs/__docs__/interactive-graph-initial-state-regression.stories.tsx
+++ b/packages/perseus/src/widgets/interactive-graphs/__docs__/interactive-graph-initial-state-regression.stories.tsx
@@ -539,10 +539,10 @@ function lockedFiguresWithWeight(
}),
generateIGLockedPolygon({
points: [
- {coord: [-7.5, -3.5]},
- {coord: [-6.5, -2.5]},
- {coord: [-5.5, -3.5]},
- {coord: [-6.5, -4.5]},
+ {coord: [-7.5, -3.5], showAngle: false},
+ {coord: [-6.5, -2.5], showAngle: false},
+ {coord: [-5.5, -3.5], showAngle: false},
+ {coord: [-6.5, -4.5], showAngle: false},
],
weight,
color: "pink",
diff --git a/packages/perseus/src/widgets/interactive-graphs/interactive-graph.test.tsx b/packages/perseus/src/widgets/interactive-graphs/interactive-graph.test.tsx
index 1b8c112413..355f7154de 100644
--- a/packages/perseus/src/widgets/interactive-graphs/interactive-graph.test.tsx
+++ b/packages/perseus/src/widgets/interactive-graphs/interactive-graph.test.tsx
@@ -1236,9 +1236,9 @@ describe("Interactive Graph", function () {
lockedFigures: [
generateIGLockedPolygon({
points: [
- {coord: [0, 0]},
- {coord: [0, 1]},
- {coord: [1, 1]},
+ {coord: [0, 0], showAngle: false},
+ {coord: [0, 1], showAngle: false},
+ {coord: [1, 1], showAngle: false},
],
weight: weight,
}),
@@ -1322,6 +1322,28 @@ describe("Interactive Graph", function () {
});
});
+ it("should render angle labels for selected locked polygon points", () => {
+ // Arrange
+ renderQuestion(
+ generateInteractiveGraphQuestion({
+ lockedFigures: [
+ generateIGLockedPolygon({
+ points: [
+ {coord: [0, 0], showAngle: true},
+ {coord: [1, 0], showAngle: false},
+ {coord: [1, 1], showAngle: false},
+ {coord: [0, 1], showAngle: false},
+ ],
+ }),
+ ],
+ }),
+ blankOptions,
+ );
+
+ // Assert
+ expect(screen.getByText("90°")).toBeInTheDocument();
+ });
+
it("should render a locked label within a locked polygon", async () => {
// Arrange
const {container} = renderQuestion(
@@ -1352,9 +1374,9 @@ describe("Interactive Graph", function () {
lockedFigures: [
generateIGLockedPolygon({
points: [
- {coord: [0, 0]},
- {coord: [0, 1]},
- {coord: [1, 1]},
+ {coord: [0, 0], showAngle: false},
+ {coord: [0, 1], showAngle: false},
+ {coord: [1, 1], showAngle: false},
],
ariaLabel: "Polygon A",
}),
@@ -1380,9 +1402,9 @@ describe("Interactive Graph", function () {
lockedFigures: [
generateIGLockedPolygon({
points: [
- {coord: [0, 0]},
- {coord: [0, 1]},
- {coord: [1, 1]},
+ {coord: [0, 0], showAngle: false},
+ {coord: [0, 1], showAngle: false},
+ {coord: [1, 1], showAngle: false},
],
}),
],
diff --git a/packages/perseus/src/widgets/interactive-graphs/interactive-graph.testdata.ts b/packages/perseus/src/widgets/interactive-graphs/interactive-graph.testdata.ts
index 0f1bee625f..aff3829ecb 100644
--- a/packages/perseus/src/widgets/interactive-graphs/interactive-graph.testdata.ts
+++ b/packages/perseus/src/widgets/interactive-graphs/interactive-graph.testdata.ts
@@ -861,14 +861,18 @@ export const segmentWithLockedPolygons: PerseusRenderer =
correct: generateIGSegmentGraph(),
lockedFigures: [
generateIGLockedPolygon({
- points: [{coord: [-3, 4]}, {coord: [-5, 1]}, {coord: [-1, 1]}],
+ points: [
+ {coord: [-3, 4], showAngle: false},
+ {coord: [-5, 1], showAngle: false},
+ {coord: [-1, 1], showAngle: false},
+ ],
}),
generateIGLockedPolygon({
points: [
- {coord: [1, 4]},
- {coord: [4, 4]},
- {coord: [4, 1]},
- {coord: [1, 1]},
+ {coord: [1, 4], showAngle: false},
+ {coord: [4, 4], showAngle: false},
+ {coord: [4, 1], showAngle: false},
+ {coord: [1, 1], showAngle: false},
],
color: "green",
showVertices: true,
@@ -877,11 +881,11 @@ export const segmentWithLockedPolygons: PerseusRenderer =
}),
generateIGLockedPolygon({
points: [
- {coord: [0, -1]},
- {coord: [-2, -3]},
- {coord: [-1, -5]},
- {coord: [1, -5]},
- {coord: [2, -3]},
+ {coord: [0, -1], showAngle: false},
+ {coord: [-2, -3], showAngle: false},
+ {coord: [-1, -5], showAngle: false},
+ {coord: [1, -5], showAngle: false},
+ {coord: [2, -3], showAngle: false},
],
color: "purple",
showVertices: false,
@@ -896,12 +900,20 @@ export const segmentWithLockedPolygonWhite: PerseusRenderer =
correct: generateIGSegmentGraph(),
lockedFigures: [
generateIGLockedPolygon({
- points: [{coord: [0, 3]}, {coord: [-3, 0]}, {coord: [3, 0]}],
+ points: [
+ {coord: [0, 3], showAngle: false},
+ {coord: [-3, 0], showAngle: false},
+ {coord: [3, 0], showAngle: false},
+ ],
color: "green",
fillStyle: "white",
}),
generateIGLockedPolygon({
- points: [{coord: [-5, 0]}, {coord: [-3, -1]}, {coord: [3, -1]}],
+ points: [
+ {coord: [-5, 0], showAngle: false},
+ {coord: [-3, -1], showAngle: false},
+ {coord: [3, -1], showAngle: false},
+ ],
color: "pink",
fillStyle: "translucent",
}),
@@ -1000,10 +1012,10 @@ export const staticGraphQuestion: PerseusRenderer =
}),
generateIGLockedPolygon({
points: [
- {coord: [-9, 4]},
- {coord: [-6, 4]},
- {coord: [-6, 1]},
- {coord: [-9, 1]},
+ {coord: [-9, 4], showAngle: false},
+ {coord: [-6, 4], showAngle: false},
+ {coord: [-6, 1], showAngle: false},
+ {coord: [-9, 1], showAngle: false},
],
color: "pink",
}),
@@ -1038,10 +1050,10 @@ export const staticGraphQuestionWithAnotherWidget: () => PerseusRenderer =
}),
generateIGLockedPolygon({
points: [
- {coord: [-9, 4]},
- {coord: [-6, 4]},
- {coord: [-6, 1]},
- {coord: [-9, 1]},
+ {coord: [-9, 4], showAngle: false},
+ {coord: [-6, 4], showAngle: false},
+ {coord: [-6, 1], showAngle: false},
+ {coord: [-9, 1], showAngle: false},
],
color: "pink",
}),
@@ -1156,7 +1168,11 @@ export const graphWithLabeledPolygon: PerseusRenderer =
correct: generateIGSegmentGraph(),
lockedFigures: [
generateIGLockedPolygon({
- points: [{coord: [0, 0]}, {coord: [4, 0]}, {coord: [2, 4]}],
+ points: [
+ {coord: [0, 0], showAngle: false},
+ {coord: [4, 0], showAngle: false},
+ {coord: [2, 4], showAngle: false},
+ ],
labels: [generateIGLockedLabel({text: "E", coord: [0, 0]})],
}),
],
diff --git a/packages/perseus/src/widgets/interactive-graphs/locked-figures/locked-polygon.tsx b/packages/perseus/src/widgets/interactive-graphs/locked-figures/locked-polygon.tsx
index 2514a95392..c8df255931 100644
--- a/packages/perseus/src/widgets/interactive-graphs/locked-figures/locked-polygon.tsx
+++ b/packages/perseus/src/widgets/interactive-graphs/locked-figures/locked-polygon.tsx
@@ -1,3 +1,4 @@
+import {geometry} from "@khanacademy/kmath";
import {
lockedFigureColors,
lockedFigureFillStyles,
@@ -6,12 +7,15 @@ import {semanticColor} from "@khanacademy/wonder-blocks-tokens";
import {Point, Polygon} from "mafs";
import * as React from "react";
+import {PolygonAngle} from "../graphs/components/angle-indicators";
import {X, Y} from "../math";
import {strokeWeights} from "./utils";
import type {LockedPolygonType} from "@khanacademy/perseus-core";
+const {clockwise} = geometry;
+
const LockedPolygon = (props: LockedPolygonType) => {
const {points, color, showVertices, fillStyle, strokeStyle, weight} = props;
@@ -42,6 +46,29 @@ const LockedPolygon = (props: LockedPolygonType) => {
},
}}
/>
+ {points.map((point, i) => {
+ const previous = points.at(i - 1);
+ const next = points.at((i + 1) % points.length);
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ if (!previous || !next) {
+ return null;
+ }
+
+ return (
+ coord),
+ )}
+ showAngles={point.showAngle}
+ // Locked polygons don't snap; "grid" is chosen as a
+ // default to preserve decimal angle labels.
+ snapTo="grid"
+ />
+ );
+ })}
{showVertices &&
points.map((point, index) => (