Skip to content

Commit

Permalink
[Interactive Graph Editor] Implement the UI for adding start coords f…
Browse files Browse the repository at this point in the history
…or circle graphs (#1453)

## Summary:
Add the UI to specify start coords for Circle graph type.

- Add the circle graph type to `start-coor-settings.tsx`
- Create a `start-coords-circle.tsx` file with the main UI
- Update the circle graph type so that `startCoords` includes both
  the center and the radius.
- Update tests

Issue: https://khanacademy.atlassian.net/browse/LEMS-2206

## Test plan:
`yarn jest`

Storybook
- Go to http://localhost:6006/?path=/story/perseuseditor-widgets-interactive-graph--interactive-graph-circle-with-starting-coords
- Confirm that the preview shows the circle at the already set coords + radius
- Confirm that the UI reflects these coords + radius
- Press "Use default start coordinates"
- Confirm that the circle goes to [0, 0] with a radius of 2.

- Go to http://localhost:6006/?path=/story/perseuseditor-editorpage--demo
- Create an interactive graph widget
- Change the graph to circle type
- Confirm that the start coords UI shows the center at [0, 0] with radius 2

<img width="384" alt="Screenshot 2024-07-25 at 4 29 34 PM" src="https://github.com/user-attachments/assets/8756f8f9-6a4c-40c0-b609-de10d7844612">

Author: nishasy

Reviewers: nishasy, benchristel, SonicScrewdriver, Myranae

Required Reviewers:

Approved By: benchristel

Checks: ✅ codecov/project, ✅ codecov/patch, ✅ Upload Coverage (ubuntu-latest, 20.x), ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Jest Coverage (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ gerald

Pull Request URL: #1453
  • Loading branch information
nishasy authored Jul 30, 2024
1 parent 0a118ca commit 79a09d6
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 13 deletions.
6 changes: 6 additions & 0 deletions .changeset/proud-parents-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@khanacademy/perseus": minor
"@khanacademy/perseus-editor": minor
---

[Interactive Graph Editor] Implement the UI for adding start coords for circle graphs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {testDependencies} from "../../../../../testing/test-dependencies";
import {clone} from "../../util/object-utils";
import StartCoordsSettings from "../start-coords-settings";

import type {CollinearTuple, Range} from "@khanacademy/perseus";
import type {CollinearTuple, Coord, Range} from "@khanacademy/perseus";
import type {UserEvent} from "@testing-library/user-event";

const defaultProps = {
Expand Down Expand Up @@ -329,4 +329,66 @@ describe("StartCoordSettings", () => {
},
);
});

describe("circle graph", () => {
test("shows the start coordinates UI", () => {
// Arrange

// Act
render(
<StartCoordsSettings
{...defaultProps}
type="circle"
onChange={() => {}}
/>,
{wrapper: RenderStateRoot},
);

// Assert
expect(screen.getByText("Start coordinates")).toBeInTheDocument();
expect(screen.getByText("Center:")).toBeInTheDocument();
expect(screen.getByText("x")).toBeInTheDocument();
expect(screen.getByText("y")).toBeInTheDocument();
});

test.each`
coordIndex | coord
${0} | ${"x"}
${1} | ${"y"}
`(
`calls onChange when $coord coord is changed`,
async ({coordIndex, coord}) => {
// Arrange
const onChangeMock = jest.fn();

const start = {center: [2, 2], radius: 5} satisfies {
center: Coord;
radius: number;
};

// Act
render(
<StartCoordsSettings
{...defaultProps}
type="circle"
startCoords={start}
onChange={onChangeMock}
/>,
{wrapper: RenderStateRoot},
);

// Assert
const input = screen.getByRole("spinbutton", {
name: coord,
});
await userEvent.clear(input);
await userEvent.type(input, "101");

const expectedCoords = clone(start);
expectedCoords.center[coordIndex] = 101;

expect(onChangeMock).toHaveBeenLastCalledWith(expectedCoords);
},
);
});
});
15 changes: 15 additions & 0 deletions packages/perseus-editor/src/components/__tests__/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,19 @@ describe("getDefaultGraphStartCoords", () => {
],
]);
});

test("should get default start coords for a circle graph", () => {
// Arrange
const graph: PerseusGraphType = {type: "circle"};
const range = [
[-10, 10],
[-10, 10],
] satisfies [Range, Range];
const step = [1, 1] satisfies [number, number];

// Act
const defaultCoords = getDefaultGraphStartCoords(graph, range, step);

expect(defaultCoords).toEqual({center: [0, 0], radius: 2});
});
});
77 changes: 77 additions & 0 deletions packages/perseus-editor/src/components/start-coords-circle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {View} from "@khanacademy/wonder-blocks-core";
import {TextField} from "@khanacademy/wonder-blocks-form";
import {Strut} from "@khanacademy/wonder-blocks-layout";
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
import {LabelLarge} from "@khanacademy/wonder-blocks-typography";
import {StyleSheet} from "aphrodite";
import * as React from "react";

import CoordinatePairInput from "./coordinate-pair-input";

import type {Coord, PerseusGraphType} from "@khanacademy/perseus";

type Props = {
startCoords: {
center: Coord;
radius: number;
};
// center: number;
onChange: (startCoords: PerseusGraphType["startCoords"]) => void;
};

const StartCoordsCircle = (props: Props) => {
const {startCoords, onChange} = props;

return (
<View style={styles.tile}>
{/* Center */}
<View style={styles.row}>
<LabelLarge>Center:</LabelLarge>
<Strut size={spacing.small_12} />
<CoordinatePairInput
coord={startCoords.center}
labels={["x", "y"]}
onChange={(coord) =>
onChange({center: coord, radius: startCoords.radius})
}
/>
</View>
<Strut size={spacing.small_12} />

{/* Radius */}
<View style={styles.row}>
<LabelLarge>Radius:</LabelLarge>
<Strut size={spacing.small_12} />
<TextField
value={startCoords.radius.toString()}
type="number"
onChange={(value) => {
onChange({
center: startCoords.center,
radius: parseFloat(value),
});
}}
style={styles.textField}
/>
</View>
</View>
);
};

const styles = StyleSheet.create({
tile: {
backgroundColor: color.fadedBlue8,
marginTop: spacing.xSmall_8,
padding: spacing.small_12,
borderRadius: spacing.xSmall_8,
},
row: {
flexDirection: "row",
alignItems: "center",
},
textField: {
width: spacing.xxxLarge_64,
},
});

export default StartCoordsCircle;
14 changes: 14 additions & 0 deletions packages/perseus-editor/src/components/start-coords-settings.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {vector as kvector} from "@khanacademy/kmath";
import {
getCircleCoords,
getLineCoords,
getLinearSystemCoords,
getSegmentCoords,
Expand All @@ -11,6 +13,7 @@ import arrowCounterClockwise from "@phosphor-icons/core/bold/arrow-counter-clock
import * as React from "react";

import Heading from "./heading";
import StartCoordsCircle from "./start-coords-circle";
import StartCoordsLine from "./start-coords-line";
import StartCoordsMultiline from "./start-coords-multiline";
import {getDefaultGraphStartCoords} from "./util";
Expand Down Expand Up @@ -55,6 +58,17 @@ const StartCoordsSettingsInner = (props: Props) => {
onChange={onChange}
/>
);
case "circle":
const circleCoords = getCircleCoords(props);
const radius = kvector.length(
kvector.subtract(circleCoords.radiusPoint, circleCoords.center),
);
return (
<StartCoordsCircle
startCoords={{center: circleCoords.center, radius}}
onChange={onChange}
/>
);
default:
return null;
}
Expand Down
12 changes: 12 additions & 0 deletions packages/perseus-editor/src/components/util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {vector as kvector} from "@khanacademy/kmath";
import {
getCircleCoords,
getLineCoords,
getLinearSystemCoords,
getSegmentCoords,
Expand Down Expand Up @@ -168,6 +170,16 @@ export function getDefaultGraphStartCoords(
range,
step,
);
case "circle":
const startCoords = getCircleCoords({
...graph,
startCoords: undefined,
});
const radius = kvector.length(
kvector.subtract(startCoords.radiusPoint, startCoords.center),
);

return {center: startCoords.center, radius};
default:
return undefined;
}
Expand Down
1 change: 1 addition & 0 deletions packages/perseus/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export {
export {convertWidgetNameToEnum} from "./util/widget-enum-utils";
export {addWidget, QUESTION_WIDGETS} from "./util/snowman-utils";
export {
getCircleCoords,
getLineCoords,
getLinearSystemCoords,
getSegmentCoords,
Expand Down
5 changes: 4 additions & 1 deletion packages/perseus/src/perseus-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,10 @@ export type PerseusGraphTypeCircle = {
center?: Coord;
radius?: number;
// The initial coordinates the graph renders with.
startCoords?: Coord;
startCoords?: {
center: Coord;
radius: number;
};
} & PerseusGraphTypeCommon;

export type PerseusGraphTypeLinear = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ export const rayWithStartingCoordsQuestion: PerseusRenderer =

export const circleWithStartingCoordsQuestion: PerseusRenderer =
interactiveGraphQuestionBuilder()
.withCircle({startCoords: [9, 9]})
.withCircle({startCoords: {center: [9, 9], radius: 5}})
.build();

export const quadraticWithStartingCoordsQuestion: PerseusRenderer =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,12 +424,22 @@ describe("InteractiveGraphQuestionBuilder", () => {

it("creates a circle graph with options", () => {
const question: PerseusRenderer = interactiveGraphQuestionBuilder()
.withCircle({center: [-1, -1], radius: 3, startCoords: [9, 9]})
.withCircle({
center: [-1, -1],
radius: 3,
startCoords: {
center: [9, 9],
radius: 5,
},
})
.build();
const graph = question.widgets["interactive-graph 1"];
expect(graph.options).toEqual(
expect.objectContaining({
graph: {type: "circle", startCoords: [9, 9], radius: 5},
graph: {
type: "circle",
startCoords: {center: [9, 9], radius: 5},
},
correct: {type: "circle", radius: 3, center: [-1, -1]},
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,10 @@ class InteractiveGraphQuestionBuilder {
withCircle(options?: {
center?: Coord;
radius?: number;
startCoords?: Coord;
startCoords?: {
center: Coord;
radius: number;
};
}): InteractiveGraphQuestionBuilder {
this.interactiveFigureConfig = new CircleGraphConfig(options);
return this;
Expand Down Expand Up @@ -528,14 +531,20 @@ class RayGraphConfig implements InteractiveFigureConfig {
}

class CircleGraphConfig implements InteractiveFigureConfig {
private startCoords?: Coord;
private startCoords?: {
center: Coord;
radius: number;
};
private correctCenter: Coord;
private correctRadius: number;

constructor(options?: {
center?: Coord;
radius?: number;
startCoords?: Coord;
startCoords?: {
center: Coord;
radius: number;
};
}) {
this.startCoords = options?.startCoords;
this.correctCenter = options?.center ?? [0, 0];
Expand All @@ -552,7 +561,10 @@ class CircleGraphConfig implements InteractiveFigureConfig {

graph(): PerseusGraphType {
if (this.startCoords) {
return {type: "circle", startCoords: this.startCoords, radius: 5};
return {
type: "circle",
startCoords: this.startCoords,
};
}

return {type: "circle"};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ function getQuadraticCoords(
return normalizePoints(range, step, defaultCoords, true);
}

function getCircleCoords(graph: PerseusGraphTypeCircle): {
export function getCircleCoords(graph: PerseusGraphTypeCircle): {
center: Coord;
radiusPoint: Coord;
} {
Expand All @@ -378,10 +378,13 @@ function getCircleCoords(graph: PerseusGraphTypeCircle): {
};
}

if (graph.startCoords) {
if (graph.startCoords?.center && graph.startCoords.radius) {
return {
center: graph.startCoords,
radiusPoint: vec.add(graph.startCoords, [2, 0]),
center: graph.startCoords.center,
radiusPoint: vec.add(graph.startCoords.center, [
graph.startCoords.radius,
0,
]),
};
}

Expand Down

0 comments on commit 79a09d6

Please sign in to comment.