Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fast-points-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@khanacademy/perseus-editor": minor
---

Creation of new Vector Graph Editor
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable max-lines */
// Duplicated from packages/perseus/src/widgets/interactive-graphs/interactive-graph-question-builder.ts
// This is to have similar test data for packages/perseus-editor

Expand Down Expand Up @@ -302,6 +303,14 @@ class InteractiveGraphQuestionBuilder {
return this;
}

withVector(options?: {
coords?: CollinearTuple;
startCoords?: CollinearTuple;
}): InteractiveGraphQuestionBuilder {
this.interactiveFigureConfig = new VectorGraphConfig(options);
return this;
}

withPolygon(
snapTo?: SnapTo,
options?: {
Expand Down Expand Up @@ -842,6 +851,30 @@ class ExponentialGraphConfig implements InteractiveFigureConfig {
}
}

class VectorGraphConfig implements InteractiveFigureConfig {
private coords?: CollinearTuple;
private startCoords?: CollinearTuple;

constructor(options?: {
coords?: CollinearTuple;
startCoords?: CollinearTuple;
}) {
this.coords = options?.coords;
this.startCoords = options?.startCoords;
}

correct(): PerseusGraphType {
return {
type: "vector",
coords: this.coords,
};
}

graph(): PerseusGraphType {
return {type: "vector", startCoords: this.startCoords};
}
}

class PolygonGraphConfig implements InteractiveFigureConfig {
private snapTo: SnapTo;
private match?: "similar" | "congruent" | "approx";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ export const sinusoidMinimalQuestion: PerseusRenderer =
export const exponentialMinimalQuestion: PerseusRenderer =
interactiveGraphQuestionBuilder().withExponential().build();

export const vectorMinimalQuestion: PerseusRenderer =
interactiveGraphQuestionBuilder().withVector().build();

export const segmentWithLockedFigures: PerseusRenderer =
interactiveGraphQuestionBuilder()
.addLockedPointAt(-7, -7, {labels: [{text: "A"}], ariaLabel: "Point A"})
Expand Down
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooopppss I think not all new graph types has editor stories 🙈
let's create a follow-up tasks for it

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
segmentWithLockedFigures,
segmentWithStartingCoordsQuestion,
exponentialMinimalQuestion,
vectorMinimalQuestion,
sinusoidMinimalQuestion,
sinusoidWithStartingCoordsAndPiTicksQuestion,
unlimitedPolygonWithCorrectAnswerQuestion,
Expand Down Expand Up @@ -121,6 +122,10 @@ export const InteractiveGraphExponential = (): React.ReactElement => {
);
};

export const InteractiveGraphVector = (): React.ReactElement => {
return <EditorPageWithStorybookPreview question={vectorMinimalQuestion} />;
};

export const InteractiveGraphSinusoid = (): React.ReactElement => {
return (
<EditorPageWithStorybookPreview question={sinusoidMinimalQuestion} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const GraphTypeSelector = (props: GraphTypeSelectorProps) => {
"interactive-graph-logarithm",
);

const showVector = isFeatureOn(
{apiOptions: props.apiOptions},
"interactive-graph-vector",
);

return (
<SingleSelect
selectedValue={props.graphType}
Expand Down Expand Up @@ -63,6 +68,7 @@ const GraphTypeSelector = (props: GraphTypeSelectorProps) => {
<OptionItem value="polygon" label="Polygon" />
<OptionItem value="segment" label="Line Segment(s)" />
<OptionItem value="ray" label="Ray" />
{showVector && <OptionItem value="vector" label="Vector" />}
<OptionItem value="angle" label="Angle" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if there was a reason adding the Angle at the last, but can we rearrange this, looks weird Angle is at the bottom 😓

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree! I wonder if we should have this be a separate ticket to discuss with content. My inclination is just to make everything alphabetical, but perhaps they would prefer a different order according to the graph types frequency of use?

</SingleSelect>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {components} from "@khanacademy/perseus";
import {OptionItem, SingleSelect} from "@khanacademy/wonder-blocks-dropdown";
import * as React from "react";
import invariant from "tiny-invariant";

import styles from "../interactive-graph-editor.module.css";
import LabeledRow from "../locked-figures/labeled-row";

import type {Props as EditorProps} from "../interactive-graph-editor";
import type {PerseusGraphTypeVector} from "@khanacademy/perseus-core";

const {InfoTip} = components;

interface Props {
correct: PerseusGraphTypeVector;
onChange: (props: Partial<EditorProps>) => void;
}

export default function VectorAnswerOptions({correct, onChange}: Props) {
return (
<LabeledRow label="Student answer must">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I noticed when doing the POC yesterday in Matthew's interactive but not scored PR, the font of a label beside a dropdown is not consistent with the rest of the editor. I'm not sure if it was intentional, but in the POC i did made it consistent. I'll leave that up to you if you'll update that here, it's a trivial thing and not a blocker.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooo I'll look into this to make sure I've implemented it how we've set up the feature previously. I was pretty sure I copied this logic exactly from the Polygon Graph, but I could have missed something!

<SingleSelect
selectedValue={correct.match || "exact"}
onChange={(newValue) => {
invariant(
correct.type === "vector",
`Expected graph type to be vector, but got ${correct.type}`,
);
onChange({
correct: {
...correct,
match: newValue as PerseusGraphTypeVector["match"],
},
});
}}
// Never uses placeholder, always has value
placeholder=""
className={styles.singleSelectShort}
>
<OptionItem value="exact" label="match exactly" />
<OptionItem value="congruent" label="be congruent" />
</SingleSelect>
<InfoTip>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The font in the info tip here looks smaller and not consistent styling with the screen reader tree.

Congruent Screen reader tree
Image Image

I can't pin point where it is defined in screen reader tree code, it looks the same how you implemented here <InfoTip> 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm less inclined to worry about this if it's a deep dive, but I'll give it a quick look to see if I can notice anything different about my implementation! :)

<p>
Congruency requires only that the vector has the same
direction and magnitude. An exact match implies congruency,
but also requires that the tail and tip are in the same
position on the grid.
</p>
</InfoTip>
</LabeledRow>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import InteractiveGraphSettings from "./components/interactive-graph-settings";
import InteractiveGraphSRTree from "./components/interactive-graph-sr-tree";
import PolygonAnswerOptions from "./components/polygon-answer-options";
import SegmentCountSelector from "./components/segment-count-selector";
import VectorAnswerOptions from "./components/vector-answer-options";
import LabeledRow from "./locked-figures/labeled-row";
import LockedFiguresSection from "./locked-figures/locked-figures-section";
import StartCoordsSettings from "./start-coords/start-coords-settings";
Expand Down Expand Up @@ -457,6 +458,12 @@ class InteractiveGraphEditor extends React.Component<Props> {
onChange={this.props.onChange}
/>
)}
{this.props.correct?.type === "vector" && (
<VectorAnswerOptions
correct={this.props.correct}
onChange={this.props.onChange}
/>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an observation, probably it's the intended behavior of Correct Answer section the coordinates will not show up at initial render. I noticed this in Absolute Value. But it would be nice to see the initial coordinates like other graphs. (See (5.000, 5.000) on the right.)

Initial After Moving
Image Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! Yeah I fully agree, I'll update this so that it's always visible.

{this.props.correct?.type === "segment" && (
<SegmentCountSelector
correct={this.props.correct}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getSegmentCoords,
getSinusoidCoords,
getTangentCoords,
getVectorCoords,
} from "@khanacademy/perseus";
import Button from "@khanacademy/wonder-blocks-button";
import {View} from "@khanacademy/wonder-blocks-core";
Expand Down Expand Up @@ -140,6 +141,15 @@ const StartCoordsSettingsInner = (props: Props) => {
/>
);
}
case "vector": {
const vectorCoords = getVectorCoords(props, range, step);
return (
<StartCoordsLine
startCoords={vectorCoords}
onChange={onChange}
/>
);
}
case "tangent":
const tangentCoords = getTangentCoords(props, range, step);
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ type GraphTypesThatHaveStartCoords =
| {type: "sinusoid"}
| {type: "exponential"}
| {type: "logarithm"}
| {type: "tangent"};
| {type: "tangent"}
| {type: "vector"};

export type StartCoords = Extract<
PerseusGraphType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getSegmentCoords,
getSinusoidCoords,
getTangentCoords,
getVectorCoords,
} from "@khanacademy/perseus";
import {UnreachableCaseError} from "@khanacademy/wonder-stuff-core";

Expand Down Expand Up @@ -47,6 +48,12 @@ export function getDefaultGraphStartCoords(
range,
step,
);
case "vector":
return getVectorCoords(
{...graph, startCoords: undefined},
range,
step,
);
case "segment":
return getSegmentCoords(
{...graph, startCoords: undefined},
Expand Down