Skip to content

Commit

Permalink
View Locked Function in the Interactive Graph (#1383)
Browse files Browse the repository at this point in the history
## Summary:
Existing locked figures include lines that are straight. The need exists to also have lines that follow a function/equation. Plotting arbitrary functions as a locked figure will provide content creators with a powerful drawing tool for interactive graphs.

Issue: LEMS-1944

## Test plan:
1. Launch Storybook (yarn start)
1. Navigate to Perseus => Widgets => Interactive Graph => [Locked Functions](http://localhost:6006/?path=/docs/perseus-widgets-interactive-graph-locked-functions--docs)
1. Note that there are multiple stories available to demonstrate various functions and options

## Examples:
**Quadratic**
![Quadratic](https://github.com/Khan/perseus/assets/13896410/70abc39b-c1ec-482a-bb63-793626fe28a8)

**Sine**
![Sine](https://github.com/Khan/perseus/assets/13896410/c4617d53-5fda-47f4-8792-d3fb4a73942a)

**Logarithmic**
![Logarithmic](https://github.com/Khan/perseus/assets/13896410/af903a44-16ad-4d9c-bbf8-5ba95ccd4925)

Author: mark-fitzgerald

Reviewers: nishasy, mark-fitzgerald, #perseus

Required Reviewers:

Approved By: nishasy

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

Pull Request URL: #1383
  • Loading branch information
mark-fitzgerald authored Jul 2, 2024
1 parent d229cea commit 4b56e10
Show file tree
Hide file tree
Showing 13 changed files with 418 additions and 14 deletions.
6 changes: 6 additions & 0 deletions .changeset/brave-steaks-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@khanacademy/perseus": minor
"@khanacademy/perseus-editor": patch
---

View Locked Functions in the Interactive Graph
11 changes: 11 additions & 0 deletions packages/perseus-editor/src/components/__tests__/util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ describe("getDefaultFigureForType", () => {
strokeStyle: "solid",
});
});

test("should return a 'function' with default values", () => {
const figure = getDefaultFigureForType("function");
expect(figure).toEqual({
type: "function",
color: "grayH",
strokeStyle: "solid",
equation: "x^2",
directionalAxis: "x",
});
});
});

describe("degreeToRadian", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ const LockedFiguresSection = (props: Props) => {
return (
<View>
{figures?.map((figure, index) => {
if (figure.type === "function") {
// TODO(LEMS-1947): Add locked function settings.
// Remove this block once function locked figure settings are
// implemented.
return;
}
return (
<LockedFigureSettings
key={`${uniqueId}-locked-${figure}-${index}`}
Expand Down
10 changes: 10 additions & 0 deletions packages/perseus-editor/src/components/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
LockedEllipseType,
LockedVectorType,
LockedPolygonType,
LockedFunctionType,
} from "@khanacademy/perseus";

export function focusWithChromeStickyFocusBugWorkaround(element: Element) {
Expand Down Expand Up @@ -56,6 +57,7 @@ export function getDefaultFigureForType(type: "line"): LockedLineType;
export function getDefaultFigureForType(type: "vector"): LockedVectorType;
export function getDefaultFigureForType(type: "ellipse"): LockedEllipseType;
export function getDefaultFigureForType(type: "polygon"): LockedPolygonType;
export function getDefaultFigureForType(type: "function"): LockedFunctionType;
export function getDefaultFigureForType(type: LockedFigureType): LockedFigure;
export function getDefaultFigureForType(type: LockedFigureType): LockedFigure {
switch (type) {
Expand Down Expand Up @@ -114,6 +116,14 @@ export function getDefaultFigureForType(type: LockedFigureType): LockedFigure {
fillStyle: "none",
strokeStyle: "solid",
};
case "function":
return {
type: "function",
color: DEFAULT_COLOR,
strokeStyle: "solid",
equation: "x^2",
directionalAxis: "x",
};
default:
throw new UnreachableCaseError(type);
}
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 @@ -167,6 +167,7 @@ export type {
LockedVectorType,
LockedEllipseType,
LockedPolygonType,
LockedFunctionType,
PerseusGraphType,
PerseusAnswerArea,
PerseusExpressionWidgetOptions,
Expand Down
24 changes: 23 additions & 1 deletion packages/perseus/src/perseus-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import type {Interval, vec} from "mafs";
// Range is replaced within this file with Interval, but it is used elsewhere
// and exported from the package, so we need to keep it around.
export type Range = Interval;
export type Domain = {
min?: number;
max?: number;
};
export type Size = [number, number];
export type CollinearTuple = [vec.Vector2, vec.Vector2];
export type ShowSolutions = "all" | "selected" | "none";
Expand Down Expand Up @@ -679,7 +683,8 @@ export type LockedFigure =
| LockedLineType
| LockedVectorType
| LockedEllipseType
| LockedPolygonType;
| LockedPolygonType
| LockedFunctionType;
export type LockedFigureType = LockedFigure["type"];

export type LockedPointType = {
Expand Down Expand Up @@ -731,6 +736,23 @@ export type LockedPolygonType = {
strokeStyle: "solid" | "dashed";
};

export type LockedFunctionType = {
type: "function";
color: LockedFigureColor;
strokeStyle: "solid" | "dashed";
equation: string; // This is the user-defined equation (as it was typed)
equationParsed?: {
// This is the parsed (tokenized) version of the equation.
// Since the function that is passed to Mafs is executed many times,
// it would be expensive to have KAS parse the equation each time.
// This is parsed version is included to aid in performance.
// KAS doesn't have any types, so making this generic
[k: string]: any;
};
directionalAxis: "x" | "y";
domain?: Domain;
};

export type PerseusGraphType =
| PerseusGraphTypeAngle
| PerseusGraphTypeCircle
Expand Down
122 changes: 122 additions & 0 deletions packages/perseus/src/widgets/__stories__/locked-functions.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import * as React from "react";

import {RendererWithDebugUI} from "../../../../../testing/renderer-with-debug-ui";
import {segmentWithLockedFunction} from "../__testdata__/interactive-graph.testdata";

export default {
title: "Perseus/Widgets/Interactive Graph/Locked Functions",
};

const mafsOptions = {
apiOptions: {
flags: {
mafs: {
segment: true,
},
},
},
};

type StoryArgs = Record<any, any>;

export const DefaultSettings = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI
{...mafsOptions}
question={segmentWithLockedFunction()}
/>
);

export const StyledSettings = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI
{...mafsOptions}
question={segmentWithLockedFunction("x^2", {
color: "green",
strokeStyle: "dashed",
})}
/>
);

export const FunctionOfY = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI
{...mafsOptions}
question={segmentWithLockedFunction("y^2", {
directionalAxis: "y",
})}
/>
);

export const DomainRestrictedMin = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI
{...mafsOptions}
question={segmentWithLockedFunction("sin(x)", {
domain: {min: -5},
})}
/>
);

export const DomainRestrictedMax = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI
{...mafsOptions}
question={segmentWithLockedFunction("sin(x)", {
domain: {max: 5},
})}
/>
);

export const DomainRestrictedBoth = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI
{...mafsOptions}
question={segmentWithLockedFunction("sin(x)", {
domain: {min: -5, max: 5},
})}
/>
);

export const Quadratic = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI
{...mafsOptions}
question={segmentWithLockedFunction("x^2 + 2x + 3")}
/>
);

export const QubicPolynomial = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI
{...mafsOptions}
question={segmentWithLockedFunction("(1/3)x^3 - 2x^2 + 3x - 4")}
/>
);

export const Tangent = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI
{...mafsOptions}
question={segmentWithLockedFunction("tan(x)")}
/>
);

export const ArcTangent = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI
{...mafsOptions}
question={segmentWithLockedFunction("arctan(x)")}
/>
);

export const Logarithmic = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI
{...mafsOptions}
question={segmentWithLockedFunction("log(x)")}
/>
);

export const Exponent = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI
{...mafsOptions}
question={segmentWithLockedFunction("e^x")}
/>
);

export const AbsoluteValue = (args: StoryArgs): React.ReactElement => (
<RendererWithDebugUI
{...mafsOptions}
question={segmentWithLockedFunction("abs(x)")}
/>
);
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {interactiveGraphQuestionBuilder} from "../interactive-graphs/interactive

import type {Coord} from "../../interactive2/types";
import type {PerseusRenderer} from "../../perseus-types";
import type {LockedFunctionOptions} from "../interactive-graphs/interactive-graph-question-builder";

// Data for the interactive graph widget

Expand Down Expand Up @@ -2225,6 +2226,15 @@ export const segmentWithLockedPolygons: PerseusRenderer =
)
.build();

export const segmentWithLockedFunction = (
equation: string = "x^2",
options?: LockedFunctionOptions,
): PerseusRenderer => {
return interactiveGraphQuestionBuilder()
.addLockedFunction(equation, options)
.build();
};

export const segmentWithLockedFigures: PerseusRenderer =
interactiveGraphQuestionBuilder()
.addLockedPointAt(-7, -7)
Expand Down
Loading

0 comments on commit 4b56e10

Please sign in to comment.