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) => (