diff --git a/.changeset/short-badgers-wonder.md b/.changeset/short-badgers-wonder.md
new file mode 100644
index 0000000000..68bea28b58
--- /dev/null
+++ b/.changeset/short-badgers-wonder.md
@@ -0,0 +1,6 @@
+---
+"@khanacademy/perseus": patch
+"@khanacademy/perseus-editor": patch
+---
+
+[Interactive Graph] Update the interactive graph builder with all currently migrated graph types
diff --git a/packages/perseus-editor/src/__stories__/editor-page.stories.tsx b/packages/perseus-editor/src/__stories__/editor-page.stories.tsx
index 286452723b..d57fabbb7d 100644
--- a/packages/perseus-editor/src/__stories__/editor-page.stories.tsx
+++ b/packages/perseus-editor/src/__stories__/editor-page.stories.tsx
@@ -7,7 +7,17 @@ import {StyleSheet} from "aphrodite";
import * as React from "react";
import {EditorPage} from "..";
-import {segmentWithLockedFigures} from "../../../perseus/src/widgets/__testdata__/interactive-graph.testdata";
+import {
+ circleWithStartingCoordsQuestion,
+ linearSystemWithStartingCoordsQuestion,
+ linearWithStartingCoordsQuestion,
+ quadraticWithStartingCoordsQuestion,
+ rayWithStartingCoordsQuestion,
+ segmentWithLockedFigures,
+ segmentWithStartingCoordsQuestion,
+ segmentsWithStartingCoordsQuestion,
+ sinusoidWithStartingCoordsQuestion,
+} from "../../../perseus/src/widgets/__testdata__/interactive-graph.testdata";
import {registerAllWidgetsAndEditorsForTesting} from "../util/register-all-widgets-and-editors-for-testing";
import EditorPageWithStorybookPreview from "./editor-page-with-storybook-preview";
@@ -32,6 +42,77 @@ export const Demo = (): React.ReactElement => {
return ;
};
+export const InteractiveGraphSegmentWithStartingCoords =
+ (): React.ReactElement => {
+ return (
+
+ );
+ };
+
+export const InteractiveGraphSegmentsWithStartingCoords =
+ (): React.ReactElement => {
+ return (
+
+ );
+ };
+
+export const InteractiveGraphLinearWithStartingCoords =
+ (): React.ReactElement => {
+ return (
+
+ );
+ };
+
+export const InteractiveGraphLinearSystemWithStartingCoords =
+ (): React.ReactElement => {
+ return (
+
+ );
+ };
+
+export const InteractiveGraphRayWithStartingCoords = (): React.ReactElement => {
+ return (
+
+ );
+};
+
+export const InteractiveGraphCircleWithStartingCoords =
+ (): React.ReactElement => {
+ return (
+
+ );
+ };
+
+export const InteractiveGraphQuadraticWithStartingCoords =
+ (): React.ReactElement => {
+ return (
+
+ );
+ };
+
+export const InteractiveGraphSinusoidWithStartingCoords =
+ (): React.ReactElement => {
+ return (
+
+ );
+ };
+
export const MafsWithLockedFiguresCurrent = (): React.ReactElement => {
return (
(
);
diff --git a/packages/perseus/src/widgets/__testdata__/interactive-graph.testdata.ts b/packages/perseus/src/widgets/__testdata__/interactive-graph.testdata.ts
index 52c801d9bc..eef3e1a1ae 100644
--- a/packages/perseus/src/widgets/__testdata__/interactive-graph.testdata.ts
+++ b/packages/perseus/src/widgets/__testdata__/interactive-graph.testdata.ts
@@ -1091,6 +1091,80 @@ export const segmentQuestion: PerseusRenderer = {
},
};
+export const segmentWithStartingCoordsQuestion: PerseusRenderer =
+ interactiveGraphQuestionBuilder()
+ .withSegments([
+ [
+ [0, 0],
+ [2, 2],
+ ],
+ ])
+ .build();
+
+export const segmentsWithStartingCoordsQuestion: PerseusRenderer =
+ interactiveGraphQuestionBuilder()
+ .withSegments([
+ [
+ [0, 0],
+ [2, 2],
+ ],
+ [
+ [0, 2],
+ [2, 0],
+ ],
+ ])
+ .build();
+
+export const linearWithStartingCoordsQuestion: PerseusRenderer =
+ interactiveGraphQuestionBuilder()
+ .withLinear([
+ [3, 0],
+ [3, 3],
+ ])
+ .build();
+
+export const linearSystemWithStartingCoordsQuestion: PerseusRenderer =
+ interactiveGraphQuestionBuilder()
+ .withLinearSystem([
+ [
+ [-3, 0],
+ [-3, 3],
+ ],
+ [
+ [3, 0],
+ [3, 3],
+ ],
+ ])
+ .build();
+
+export const rayWithStartingCoordsQuestion: PerseusRenderer =
+ interactiveGraphQuestionBuilder()
+ .withRay([
+ [3, 0],
+ [3, 3],
+ ])
+ .build();
+
+export const circleWithStartingCoordsQuestion: PerseusRenderer =
+ interactiveGraphQuestionBuilder().withCircle([9, 9]).build();
+
+export const quadraticWithStartingCoordsQuestion: PerseusRenderer =
+ interactiveGraphQuestionBuilder()
+ .withQuadratic([
+ [-1, -1],
+ [0, 0],
+ [1, -1],
+ ])
+ .build();
+
+export const sinusoidWithStartingCoordsQuestion: PerseusRenderer =
+ interactiveGraphQuestionBuilder()
+ .withSinusoid([
+ [0, 0],
+ [1, -1],
+ ])
+ .build();
+
export const segmentWithLockedPointsQuestion: PerseusRenderer = {
content:
"Line segment $\\overline{OG}$ is rotated $180^\\circ$ about the point $(-2,4)$. \n\n**Draw the image of this rotation using the interactive graph.**\n\n*The direction of a rotation by a positive angle is counter-clockwise.* \n\n[[☃ interactive-graph 1]]\n\n",
diff --git a/packages/perseus/src/widgets/interactive-graphs/interactive-graph-question-builder.test.ts b/packages/perseus/src/widgets/interactive-graphs/interactive-graph-question-builder.test.ts
index 755867bc14..bdc0c40f34 100644
--- a/packages/perseus/src/widgets/interactive-graphs/interactive-graph-question-builder.test.ts
+++ b/packages/perseus/src/widgets/interactive-graphs/interactive-graph-question-builder.test.ts
@@ -68,9 +68,9 @@ describe("InteractiveGraphQuestionBuilder", () => {
expect(graph.options.step).toEqual([7, 8]);
});
- it("creates a segment graph with a specified number of segments", () => {
+ it("creates a default segment graph with a specified number of segments", () => {
const question: PerseusRenderer = interactiveGraphQuestionBuilder()
- .withSegments(3)
+ .withNumSegments(3)
.build();
const graph = question.widgets["interactive-graph 1"];
@@ -93,6 +93,258 @@ describe("InteractiveGraphQuestionBuilder", () => {
);
});
+ it("creates a segment graph with start coords", () => {
+ const question: PerseusRenderer = interactiveGraphQuestionBuilder()
+ .withSegments([
+ [
+ [0, 0],
+ [2, 2],
+ ],
+ ])
+ .build();
+ const graph = question.widgets["interactive-graph 1"];
+
+ expect(graph.options).toEqual(
+ expect.objectContaining({
+ graph: {
+ type: "segment",
+ numSegments: 1,
+ coords: [
+ [
+ [0, 0],
+ [2, 2],
+ ],
+ ],
+ },
+ correct: {
+ type: "segment",
+ numSegments: 1,
+ coords: [
+ [
+ [-7, 7],
+ [2, 5],
+ ],
+ ],
+ },
+ }),
+ );
+ });
+
+ it("creates a graph with multiple segments with start coords", () => {
+ const question: PerseusRenderer = interactiveGraphQuestionBuilder()
+ .withSegments([
+ [
+ [0, 0],
+ [2, 2],
+ ],
+ [
+ [3, 3],
+ [5, 5],
+ ],
+ ])
+ .build();
+ const graph = question.widgets["interactive-graph 1"];
+
+ expect(graph.options).toEqual(
+ expect.objectContaining({
+ graph: {
+ type: "segment",
+ numSegments: 2,
+ coords: [
+ [
+ [0, 0],
+ [2, 2],
+ ],
+ [
+ [3, 3],
+ [5, 5],
+ ],
+ ],
+ },
+ correct: {
+ type: "segment",
+ numSegments: 2,
+ coords: [
+ [
+ [-7, 7],
+ [2, 5],
+ ],
+ [
+ [-7, 7],
+ [2, 5],
+ ],
+ ],
+ },
+ }),
+ );
+ });
+
+ it("creates a linear graph", () => {
+ const question: PerseusRenderer = interactiveGraphQuestionBuilder()
+ .withLinear()
+ .build();
+
+ const graph = question.widgets["interactive-graph 1"];
+ expect(graph.options).toEqual(
+ expect.objectContaining({
+ graph: {type: "linear"},
+ correct: {
+ type: "linear",
+ coords: [
+ [-10, -5],
+ [10, 5],
+ ],
+ },
+ }),
+ );
+ });
+
+ it("creates a linear graph with start coords", () => {
+ const question: PerseusRenderer = interactiveGraphQuestionBuilder()
+ .withLinear([
+ [3, 0],
+ [3, 3],
+ ])
+ .build();
+ const graph = question.widgets["interactive-graph 1"];
+ expect(graph.options).toEqual(
+ expect.objectContaining({
+ graph: {
+ type: "linear",
+ coords: [
+ [3, 0],
+ [3, 3],
+ ],
+ },
+ correct: {
+ type: "linear",
+ coords: [
+ [-10, -5],
+ [10, 5],
+ ],
+ },
+ }),
+ );
+ });
+
+ it("creates a linear system graph", () => {
+ const question: PerseusRenderer = interactiveGraphQuestionBuilder()
+ .withLinearSystem()
+ .build();
+ const graph = question.widgets["interactive-graph 1"];
+ expect(graph.options).toEqual(
+ expect.objectContaining({
+ graph: {type: "linear-system"},
+ correct: {
+ type: "linear-system",
+ coords: [
+ [
+ [-10, -5],
+ [10, 5],
+ ],
+ [
+ [-10, 5],
+ [10, -5],
+ ],
+ ],
+ },
+ }),
+ );
+ });
+
+ it("creates a linear system graph with start coords", () => {
+ const question: PerseusRenderer = interactiveGraphQuestionBuilder()
+ .withLinearSystem([
+ [
+ [-3, 0],
+ [-3, 3],
+ ],
+ [
+ [3, 0],
+ [3, 3],
+ ],
+ ])
+ .build();
+ const graph = question.widgets["interactive-graph 1"];
+ expect(graph.options).toEqual(
+ expect.objectContaining({
+ graph: {
+ type: "linear-system",
+ coords: [
+ [
+ [-3, 0],
+ [-3, 3],
+ ],
+ [
+ [3, 0],
+ [3, 3],
+ ],
+ ],
+ },
+ correct: {
+ type: "linear-system",
+ coords: [
+ [
+ [-10, -5],
+ [10, 5],
+ ],
+ [
+ [-10, 5],
+ [10, -5],
+ ],
+ ],
+ },
+ }),
+ );
+ });
+
+ it("creates a ray graph", () => {
+ const question: PerseusRenderer = interactiveGraphQuestionBuilder()
+ .withRay()
+ .build();
+ const graph = question.widgets["interactive-graph 1"];
+ expect(graph.options).toEqual(
+ expect.objectContaining({
+ graph: {type: "ray"},
+ correct: {
+ type: "ray",
+ coords: [
+ [-10, -5],
+ [10, 5],
+ ],
+ },
+ }),
+ );
+ });
+
+ it("creates a ray graph with start coords", () => {
+ const question: PerseusRenderer = interactiveGraphQuestionBuilder()
+ .withRay([
+ [3, 0],
+ [3, 3],
+ ])
+ .build();
+ const graph = question.widgets["interactive-graph 1"];
+ expect(graph.options).toEqual(
+ expect.objectContaining({
+ graph: {
+ type: "ray",
+ coords: [
+ [3, 0],
+ [3, 3],
+ ],
+ },
+ correct: {
+ type: "ray",
+ coords: [
+ [-10, -5],
+ [10, 5],
+ ],
+ },
+ }),
+ );
+ });
+
it("creates a circle graph", () => {
const question: PerseusRenderer = interactiveGraphQuestionBuilder()
.withCircle()
@@ -106,6 +358,117 @@ describe("InteractiveGraphQuestionBuilder", () => {
);
});
+ it("creates a circle graph with start coords", () => {
+ const question: PerseusRenderer = interactiveGraphQuestionBuilder()
+ .withCircle([9, 9])
+ .build();
+ const graph = question.widgets["interactive-graph 1"];
+ expect(graph.options).toEqual(
+ expect.objectContaining({
+ graph: {type: "circle", center: [9, 9], radius: 5},
+ correct: {type: "circle", radius: 5, center: [0, 0]},
+ }),
+ );
+ });
+
+ it("creates a quadratic graph", () => {
+ const question: PerseusRenderer = interactiveGraphQuestionBuilder()
+ .withQuadratic()
+ .build();
+ const graph = question.widgets["interactive-graph 1"];
+ expect(graph.options).toEqual(
+ expect.objectContaining({
+ graph: {type: "quadratic"},
+ correct: {
+ type: "quadratic",
+ coords: [
+ [-10, 5],
+ [10, 5],
+ [0, -5],
+ ],
+ },
+ }),
+ );
+ });
+
+ it("creates a quadratic graph with start coords", () => {
+ const question: PerseusRenderer = interactiveGraphQuestionBuilder()
+ .withQuadratic([
+ [-1, -1],
+ [0, 0],
+ [1, -1],
+ ])
+ .build();
+ const graph = question.widgets["interactive-graph 1"];
+ expect(graph.options).toEqual(
+ expect.objectContaining({
+ graph: {
+ type: "quadratic",
+ coords: [
+ [-1, -1],
+ [0, 0],
+ [1, -1],
+ ],
+ },
+ correct: {
+ type: "quadratic",
+ coords: [
+ [-10, 5],
+ [10, 5],
+ [0, -5],
+ ],
+ },
+ }),
+ );
+ });
+
+ it("creates a sinusoid graph", () => {
+ const question: PerseusRenderer = interactiveGraphQuestionBuilder()
+ .withSinusoid()
+ .build();
+ const graph = question.widgets["interactive-graph 1"];
+ expect(graph.options).toEqual(
+ expect.objectContaining({
+ graph: {type: "sinusoid"},
+ correct: {
+ type: "sinusoid",
+ coords: [
+ [-10, 5],
+ [10, 5],
+ ],
+ },
+ }),
+ );
+ });
+
+ it("creates a sinusoid graph with start coords", () => {
+ const question: PerseusRenderer = interactiveGraphQuestionBuilder()
+ .withSinusoid([
+ [0, 0],
+ [1, -1],
+ ])
+ .build();
+ const graph = question.widgets["interactive-graph 1"];
+ expect(graph.options).toEqual(
+ expect.objectContaining({
+ graph: {
+ type: "sinusoid",
+ coords: [
+ [0, 0],
+ [1, -1],
+ ],
+ },
+ correct: {
+ type: "sinusoid",
+ coords: [
+ [-10, 5],
+ [10, 5],
+ ],
+ },
+ }),
+ );
+ });
+
it("creates a polygon graph", () => {
const question: PerseusRenderer = interactiveGraphQuestionBuilder()
.withPolygon("grid")
diff --git a/packages/perseus/src/widgets/interactive-graphs/interactive-graph-question-builder.ts b/packages/perseus/src/widgets/interactive-graphs/interactive-graph-question-builder.ts
index 78022194b3..420e43ba45 100644
--- a/packages/perseus/src/widgets/interactive-graphs/interactive-graph-question-builder.ts
+++ b/packages/perseus/src/widgets/interactive-graphs/interactive-graph-question-builder.ts
@@ -9,7 +9,9 @@ import type {
PerseusGraphType,
PerseusRenderer,
LockedPolygonType,
+ CollinearTuple,
} from "../../perseus-types";
+import type {Coord} from "@khanacademy/perseus";
import type {Interval, vec} from "mafs";
export function interactiveGraphQuestionBuilder(): InteractiveGraphQuestionBuilder {
@@ -97,13 +99,54 @@ class InteractiveGraphQuestionBuilder {
return this;
}
- withSegments(numSegments: number): InteractiveGraphQuestionBuilder {
+ withSegments(
+ startCoords: CollinearTuple[],
+ ): InteractiveGraphQuestionBuilder {
+ this.interactiveFigureConfig = new SegmentGraphConfig(
+ startCoords.length,
+ startCoords,
+ );
+ return this;
+ }
+
+ withNumSegments(numSegments: number): InteractiveGraphQuestionBuilder {
this.interactiveFigureConfig = new SegmentGraphConfig(numSegments);
return this;
}
- withCircle(): InteractiveGraphQuestionBuilder {
- this.interactiveFigureConfig = new CircleGraphConfig();
+ withLinear(startCoords?: CollinearTuple): InteractiveGraphQuestionBuilder {
+ this.interactiveFigureConfig = new LinearConfig(startCoords);
+ return this;
+ }
+
+ withLinearSystem(
+ startCoords?: CollinearTuple[],
+ ): InteractiveGraphQuestionBuilder {
+ this.interactiveFigureConfig = new LinearSystemConfig(startCoords);
+ return this;
+ }
+
+ withRay(startCoords?: CollinearTuple): InteractiveGraphQuestionBuilder {
+ this.interactiveFigureConfig = new RayConfig(startCoords);
+ return this;
+ }
+
+ withCircle(startCoords?: Coord): InteractiveGraphQuestionBuilder {
+ this.interactiveFigureConfig = new CircleGraphConfig(startCoords);
+ return this;
+ }
+
+ withQuadratic(
+ startCoords?: [Coord, Coord, Coord],
+ ): InteractiveGraphQuestionBuilder {
+ this.interactiveFigureConfig = new QuadraticConfig(startCoords);
+ return this;
+ }
+
+ withSinusoid(
+ startCoords?: [Coord, Coord],
+ ): InteractiveGraphQuestionBuilder {
+ this.interactiveFigureConfig = new SinusoidGraphConfig(startCoords);
return this;
}
@@ -219,8 +262,11 @@ interface InteractiveFigureConfig {
class SegmentGraphConfig implements InteractiveFigureConfig {
private numSegments: number;
- constructor(numSegments: number) {
+ private startCoords?: CollinearTuple[];
+
+ constructor(numSegments: number, startCoords?: CollinearTuple[]) {
this.numSegments = numSegments;
+ this.startCoords = startCoords;
}
correct(): PerseusGraphType {
@@ -235,20 +281,151 @@ class SegmentGraphConfig implements InteractiveFigureConfig {
}
graph(): PerseusGraphType {
- return {type: "segment", numSegments: this.numSegments};
+ return {
+ type: "segment",
+ numSegments: this.numSegments,
+ coords: this.startCoords,
+ };
+ }
+}
+
+class LinearConfig implements InteractiveFigureConfig {
+ private startCoords?: CollinearTuple;
+
+ constructor(startCoords?: CollinearTuple) {
+ this.startCoords = startCoords;
+ }
+
+ correct(): PerseusGraphType {
+ return {
+ type: "linear",
+ coords: [
+ [-10, -5],
+ [10, 5],
+ ],
+ };
+ }
+
+ graph(): PerseusGraphType {
+ return {type: "linear", coords: this.startCoords};
+ }
+}
+
+class LinearSystemConfig implements InteractiveFigureConfig {
+ private startCoords?: CollinearTuple[];
+
+ constructor(startCoords?: CollinearTuple[]) {
+ this.startCoords = startCoords;
+ }
+
+ correct(): PerseusGraphType {
+ return {
+ type: "linear-system",
+ coords: [
+ [
+ [-10, -5],
+ [10, 5],
+ ],
+ [
+ [-10, 5],
+ [10, -5],
+ ],
+ ],
+ };
+ }
+
+ graph(): PerseusGraphType {
+ return {type: "linear-system", coords: this.startCoords};
+ }
+}
+
+class RayConfig implements InteractiveFigureConfig {
+ private startCoords?: CollinearTuple;
+
+ constructor(startCoords?: CollinearTuple) {
+ this.startCoords = startCoords;
+ }
+
+ correct(): PerseusGraphType {
+ return {
+ type: "ray",
+ coords: [
+ [-10, -5],
+ [10, 5],
+ ],
+ };
+ }
+
+ graph(): PerseusGraphType {
+ return {type: "ray", coords: this.startCoords};
}
}
class CircleGraphConfig implements InteractiveFigureConfig {
+ private startCoords?: Coord;
+
+ constructor(startCoords?: Coord) {
+ this.startCoords = startCoords;
+ }
+
correct(): PerseusGraphType {
return {type: "circle", radius: 5, center: [0, 0]};
}
graph(): PerseusGraphType {
+ if (this.startCoords) {
+ return {type: "circle", center: this.startCoords, radius: 5};
+ }
+
return {type: "circle"};
}
}
+class QuadraticConfig implements InteractiveFigureConfig {
+ private startCoords?: [Coord, Coord, Coord];
+
+ constructor(startCoords?: [Coord, Coord, Coord]) {
+ this.startCoords = startCoords;
+ }
+
+ correct(): PerseusGraphType {
+ return {
+ type: "quadratic",
+ coords: [
+ [-10, 5],
+ [10, 5],
+ [0, -5],
+ ],
+ };
+ }
+
+ graph(): PerseusGraphType {
+ return {type: "quadratic", coords: this.startCoords};
+ }
+}
+
+class SinusoidGraphConfig implements InteractiveFigureConfig {
+ private startCoords?: [Coord, Coord];
+
+ constructor(startCoords?: [Coord, Coord]) {
+ this.startCoords = startCoords;
+ }
+
+ correct(): PerseusGraphType {
+ return {
+ type: "sinusoid",
+ coords: [
+ [-10, 5],
+ [10, 5],
+ ],
+ };
+ }
+
+ graph(): PerseusGraphType {
+ return {type: "sinusoid", coords: this.startCoords};
+ }
+}
+
class PolygonGraphConfig implements InteractiveFigureConfig {
private snapTo: "grid" | "angles" | "sides";
constructor(snapTo: "grid" | "angles" | "sides") {