Skip to content

[ Interactive Graph | Exponential Graph ] PR3: Rendering Logic#3393

Merged
SonicScrewdriver merged 11 commits intomainfrom
LEMS-3711/exponential-pr3
Mar 23, 2026
Merged

[ Interactive Graph | Exponential Graph ] PR3: Rendering Logic#3393
SonicScrewdriver merged 11 commits intomainfrom
LEMS-3711/exponential-pr3

Conversation

@SonicScrewdriver
Copy link
Copy Markdown
Contributor

@SonicScrewdriver SonicScrewdriver commented Mar 19, 2026

Summary:

This PR was created with the help of AI, albeit with heavy oversight and review. We had to make a new PR because the original history was fully messed up.

This is part of a series of PRs implementing the exponential graph type for
the Interactive Graph widget:

PR1 – type definitions and schema
PR2 – state management
▶️ PR3 – rendering & accessibility (this PR)
PR4 – scoring
PR5 – editor support

Issue: LEMS-3711

  • Added rendering and accessibility support for the exponential graph type

  • Implements the full interactive rendering of the exponential graph, including
    draggable curve points, a draggable asymptote line, the plotted curve, and
    screen reader descriptions. Also extracts two reusable components
    (MovableAsymptote and AsymptoteDragHandle) intended to be shared with
    the upcoming logarithm graph type.

Changes:

  • graphs/exponential.tsx — New file implementing renderExponentialGraph.
    Renders two MovablePoint components for the curve, a draggable asymptote
    line via useDraggable, and a Plot of f(x) = a·eᵇˣ + c using coefficients
    from getExponentialCoefficients. Caches the last valid coefficients via a
    ref to handle transient invalid states mid-drag. Dispatches
    actions.exponential.movePoint and actions.exponential.moveCenter on
    interaction.
  • graphs/components/asymptote-drag-handle.tsx — New reusable visual
    component rendering the asymptote pill handle with orientation dots.
    Accepts orientation: "horizontal" | "vertical" for reuse with future
    vertical-asymptote graph types (e.g. logarithm).
  • graphs/components/movable-asymptote.tsx — New reusable interactive
    component wrapping SVGLine and AsymptoteDragHandle with full drag,
    hover, and keyboard support via useDraggable. Exports
    constrainAsymptoteKeyboard for testability.
  • mafs-styles.css — Adds .movable-asymptote-handle* CSS classes for
    the asymptote pill styling.
  • mafs-graph.tsx — Wires up renderExponentialGraph in
    renderGraphElements, replacing the "Not implemented" stub from PR1.
  • interactive-graph.tsx — Implements getExponentialEquationString,
    replacing the empty-string stub from PR1.
  • interactive-graph-ai-utils.ts — Adds ExponentialGraphOptions,
    ExponentialUserInput, and wires them into getGraphOptionsForProps /
    getUserInput.
  • strings.ts — Adds i18n strings for exponential graph screen reader
    descriptions (graph label, point descriptions, asymptote description).
  • interactive-graph.stories.tsx — Adds an exponential graph story.
  • graphs/exponential.test.tsx — Tests for rendering, dragging curve
    points, asymptote dragging, keyboard movement, and screen reader output.
  • graphs/components/movable-asymptote.test.tsx — Tests for
    MovableAsymptote rendering and constrainAsymptoteKeyboard behavior.

Test plan:

  • pnpm test packages/perseus — all new rendering and component tests pass
  • pnpm tsc — no new type errors
  • pnpm lint — no lint errors
  • Exponential graph renders and is interactive in Storybook
  • Screen reader descriptions are correct
  • No existing interactive graph tests regress

Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 19, 2026

🗄️ Schema Change: No Changes ✅

@SonicScrewdriver SonicScrewdriver changed the title docs(changeset): Rendering logic for new Exponential Graph [ Interactive Graph | Exponential Graph ] PR3: Rendering Logic Mar 19, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 19, 2026

Size Change: +1.98 kB (+0.4%)

Total Size: 493 kB

Filename Size Change
packages/perseus/dist/es/index.js 193 kB +1.73 kB (+0.9%)
packages/perseus/dist/es/strings.js 8.09 kB +250 B (+3.19%)
ℹ️ View Unchanged
Filename Size
packages/kas/dist/es/index.js 20.5 kB
packages/keypad-context/dist/es/index.js 1 kB
packages/kmath/dist/es/index.js 6.21 kB
packages/math-input/dist/es/index.js 98.5 kB
packages/math-input/dist/es/strings.js 1.61 kB
packages/perseus-core/dist/es/index.item-splitting.js 11.9 kB
packages/perseus-core/dist/es/index.js 25 kB
packages/perseus-editor/dist/es/index.js 101 kB
packages/perseus-linter/dist/es/index.js 9.02 kB
packages/perseus-score/dist/es/index.js 9.5 kB
packages/perseus-utils/dist/es/index.js 403 B
packages/pure-markdown/dist/es/index.js 1.39 kB
packages/simple-markdown/dist/es/index.js 6.71 kB

compressed-size-action

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 19, 2026

npm Snapshot: Published

Good news!! We've packaged up the latest commit from this PR (77c478b) and published it to npm. You
can install it using the tag PR3393.

Example:

pnpm add @khanacademy/perseus@PR3393

If you are working in Khan Academy's frontend, you can run the below command.

./dev/tools/bump_perseus_version.ts -t PR3393

If you are working in Khan Academy's webapp, you can run the below command.

./dev/tools/bump_perseus_version.js -t PR3393

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 19, 2026

🛠️ Item Splitting: No Changes ✅

Copy link
Copy Markdown
Contributor

@ivyolamit ivyolamit left a comment

Choose a reason for hiding this comment

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

@SonicScrewdriver implementation looks logical to me, i just have few non-blocking suggestions.

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.

Can we add a test for this component especially it has 2 behavior depending on the orientation.

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.

Sure! It seemed simple enough that I wasn't sure it was worth the test bloat, but I can add one!

);
}

// Horizontal drag handle — wide pill with horizontal dot grid.
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.

do we still need this comment?

Suggested change
// Horizontal drag handle — wide pill with horizontal dot grid.

r: 1.25px;
}

.MafsView .movable-asymptote-handle-focus-ring {
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.

suggestion: Group these and add a comment that these are all used for the asymptote for exponent and logarithm

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.

these new styling are specific to the drag handle and movable asymptote, do we want these here or close to the component?

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.

This is where all the component styles are, so I think it makes sense to keep it in this file. If we want to move all the styles, it'd make more sense to move them collectively later.

}

.MafsView .movable-asymptote-handle-ring {
fill: #fff;
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.

Suggested change
fill: #fff;
fill: var(--wb-semanticColor-core-border-knockout-default);

}

.MafsView .movable-asymptote-handle-dot {
fill: #fff;
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.

Suggested change
fill: #fff;
fill: var(--wb-semanticColor-core-border-knockout-default);


.MafsView .movable-asymptote-handle-dot {
fill: #fff;
r: 1px;
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.

Suggested change
r: 1px;
r: var(--wb-sizing-size_010);

@SonicScrewdriver SonicScrewdriver force-pushed the LEMS-3711/exponential-pr2 branch from 6a28c3d to 58a0135 Compare March 23, 2026 22:34
@SonicScrewdriver SonicScrewdriver changed the base branch from LEMS-3711/exponential-pr2 to main March 23, 2026 22:55
@SonicScrewdriver SonicScrewdriver force-pushed the LEMS-3711/exponential-pr3 branch from 8b31dc5 to 77c478b Compare March 23, 2026 23:01
@SonicScrewdriver SonicScrewdriver merged commit 9f29bc7 into main Mar 23, 2026
12 of 13 checks passed
@SonicScrewdriver SonicScrewdriver deleted the LEMS-3711/exponential-pr3 branch March 23, 2026 23:08
ivyolamit added a commit that referenced this pull request Mar 24, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @khanacademy/perseus-editor@30.0.0

### Major Changes

- [#3332](#3332)
[`604b3a6c25`](604b3a6)
Thanks [@benchristel](https://github.com/benchristel)! - The `options`
parameter of the `serialize` method of `EditorPage` and `Editor` has
been removed.


- [#3386](#3386)
[`7e76fbbc2f`](7e76fbb)
Thanks [@benchristel](https://github.com/benchristel)! - The `serialize`
methods of classes in `@khanacademy/perseus-editor` no longer use arrow
function syntax. Callers should not unbind them from the class instance.

Additionally, the `Editor` component no longer accepts a `replace` prop
(used for hints), and its serialize method no longer returns `replace`.
The `replace` prop was only used in `serialize`. Users of the `Editor`
component should manage hints' `replace` setting themselves.

### Minor Changes

- [#3395](#3395)
[`97223334ea`](9722333)
Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! -
Implementation of Editor support for Exponential Graph


- [#3352](#3352)
[`b681e00a4f`](b681e00)
Thanks [@handeyeco](https://github.com/handeyeco)! - Add editor support
for AbsoluteValue


- [#3348](#3348)
[`b1557c2a73`](b1557c2)
Thanks [@handeyeco](https://github.com/handeyeco)! - Add schema for
AbsoluteValue graph


- [#3345](#3345)
[`dde985f3b5`](dde985f)
Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent type
definitions, this is the initial implementation for supporting Tangent
graph in Interactive Graph


- [#3358](#3358)
[`8c503171b1`](8c50317)
Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent graph
option in the Interactive Graph Editor


- [#3376](#3376)
[`8aa0a77886`](8aa0a77)
Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! -
Creation of new Types, Schema, and Kmath utilities for Exponential Graph

### Patch Changes

- [#3396](#3396)
[`35fa9133db`](35fa913)
Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (CX) | Add a
linter warning for images with no size


- [#3390](#3390)
[`d22c50dc2a`](d22c50d)
Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (CX) | Make
the 125 character alt text warning less aggressive


- [#3372](#3372)
[`3cdb09813d`](3cdb098)
Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (UX) |
Upscale Graphies within Explore Image Modal


- [#3391](#3391)
[`2f285ee161`](2f285ee)
Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (CX) | Add
character counter to alt text field


- [#3374](#3374)
[`cd73c99ba3`](cd73c99)
Thanks [@ivyolamit](https://github.com/ivyolamit)! - Remove incorrect
usage of the feature flag setting in one of the test

- Updated dependencies
\[[`f18c0d9b6f`](f18c0d9),
[`a022e751d6`](a022e75),
[`35fa9133db`](35fa913),
[`54db3fd4bd`](54db3fd),
[`97223334ea`](9722333),
[`027a5edbda`](027a5ed),
[`ae0538d0a7`](ae0538d),
[`005e13d784`](005e13d),
[`3cdb09813d`](3cdb098),
[`afcff9f96f`](afcff9f),
[`75f184e5a7`](75f184e),
[`4b2a7c85db`](4b2a7c8),
[`5e1acd01f8`](5e1acd0),
[`b681e00a4f`](b681e00),
[`d99f1c0259`](d99f1c0),
[`54eee35d65`](54eee35),
[`b1557c2a73`](b1557c2),
[`dde985f3b5`](dde985f),
[`56e7dbe9a2`](56e7dbe),
[`85f9cd46fc`](85f9cd4),
[`8c503171b1`](8c50317),
[`3aca3dcdf4`](3aca3dc),
[`9f29bc7161`](9f29bc7),
[`7034844845`](7034844),
[`8aa0a77886`](8aa0a77),
[`003aca7612`](003aca7)]:
    -   @khanacademy/perseus-linter@4.9.0
    -   @khanacademy/perseus-score@8.4.0
    -   @khanacademy/perseus-core@23.7.0
    -   @khanacademy/perseus@76.1.0
    -   @khanacademy/kmath@2.3.0
    -   @khanacademy/keypad-context@3.2.40
    -   @khanacademy/math-input@26.4.10

## @khanacademy/kmath@2.3.0

### Minor Changes

- [#3351](#3351)
[`005e13d784`](005e13d)
Thanks [@handeyeco](https://github.com/handeyeco)! - Add scoring for
AbsoluteValue


- [#3347](#3347)
[`d99f1c0259`](d99f1c0)
Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add the tangent
math utilities to kmath for supporting Tangent graph in Interactive
Graph


- [#3376](#3376)
[`8aa0a77886`](8aa0a77)
Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! -
Creation of new Types, Schema, and Kmath utilities for Exponential Graph

### Patch Changes

- Updated dependencies
\[[`54db3fd4bd`](54db3fd),
[`ae0538d0a7`](ae0538d),
[`005e13d784`](005e13d),
[`b1557c2a73`](b1557c2),
[`dde985f3b5`](dde985f),
[`8aa0a77886`](8aa0a77)]:
    -   @khanacademy/perseus-core@23.7.0

## @khanacademy/perseus@76.1.0

### Minor Changes

- [#3350](#3350)
[`75f184e5a7`](75f184e)
Thanks [@handeyeco](https://github.com/handeyeco)! - Implement
AbsoluteValue rendering


- [#3354](#3354)
[`4b2a7c85db`](4b2a7c8)
Thanks [@ivyolamit](https://github.com/ivyolamit)! - Created the tangent
graph visual component, add Storybook coverage, SR strings, and equation
string for supporting Tangent graph in Interactive Graph


- [#3353](#3353)
[`5e1acd01f8`](5e1acd0)
Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent graph
state management and reducer for supporting Tangent graph in Interactive
Graph


- [#3352](#3352)
[`b681e00a4f`](b681e00)
Thanks [@handeyeco](https://github.com/handeyeco)! - Add editor support
for AbsoluteValue


- [#3348](#3348)
[`b1557c2a73`](b1557c2)
Thanks [@handeyeco](https://github.com/handeyeco)! - Add schema for
AbsoluteValue graph


- [#3345](#3345)
[`dde985f3b5`](dde985f)
Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent type
definitions, this is the initial implementation for supporting Tangent
graph in Interactive Graph


- [#3349](#3349)
[`56e7dbe9a2`](56e7dbe)
Thanks [@handeyeco](https://github.com/handeyeco)! - Add state
management for AbsoluteValue


- [#3377](#3377)
[`85f9cd46fc`](85f9cd4)
Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! -
Implementation of state management logic for new Exponential graph


- [#3358](#3358)
[`8c503171b1`](8c50317)
Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent graph
option in the Interactive Graph Editor


- [#3393](#3393)
[`9f29bc7161`](9f29bc7)
Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! -
Rendering logic for new Exponential Graph


- [#3376](#3376)
[`8aa0a77886`](8aa0a77)
Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! -
Creation of new Types, Schema, and Kmath utilities for Exponential Graph

### Patch Changes

- [#3329](#3329)
[`027a5edbda`](027a5ed)
Thanks [@Myranae](https://github.com/Myranae)! - Fix image bug by
batching setState calls in setupGraphie


- [#3372](#3372)
[`3cdb09813d`](3cdb098)
Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (UX) |
Upscale Graphies within Explore Image Modal


- [#3365](#3365)
[`afcff9f96f`](afcff9f)
Thanks [@jeremywiebe](https://github.com/jeremywiebe)! - Improve
ordering of Props type for `Renderer` component


- [#3367](#3367)
[`54eee35d65`](54eee35)
Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (UX) | Show
image in explore modal even when size is undefined


- [#3407](#3407)
[`3aca3dcdf4`](3aca3dc)
Thanks [@Myranae](https://github.com/Myranae)! - Improve a11y with
graded group set


- [#3385](#3385)
[`003aca7612`](003aca7)
Thanks [@Myranae](https://github.com/Myranae)! - Small fix to prevent
pip duplication in Graded Group Sets

- Updated dependencies
\[[`f18c0d9b6f`](f18c0d9),
[`a022e751d6`](a022e75),
[`35fa9133db`](35fa913),
[`54db3fd4bd`](54db3fd),
[`97223334ea`](9722333),
[`ae0538d0a7`](ae0538d),
[`005e13d784`](005e13d),
[`d99f1c0259`](d99f1c0),
[`b1557c2a73`](b1557c2),
[`dde985f3b5`](dde985f),
[`7034844845`](7034844),
[`8aa0a77886`](8aa0a77)]:
    -   @khanacademy/perseus-linter@4.9.0
    -   @khanacademy/perseus-score@8.4.0
    -   @khanacademy/perseus-core@23.7.0
    -   @khanacademy/kmath@2.3.0
    -   @khanacademy/keypad-context@3.2.40
    -   @khanacademy/math-input@26.4.10

## @khanacademy/perseus-core@23.7.0

### Minor Changes

- [#3405](#3405)
[`54db3fd4bd`](54db3fd)
Thanks [@benchristel](https://github.com/benchristel)! -
`@khanacademy/perseus-core` now exports a
`removeOrphanedWidgetsFromPerseusItem` function, which removes
unreferenced widgets from a `PerseusItem`'s question and hints.


- [#3351](#3351)
[`005e13d784`](005e13d)
Thanks [@handeyeco](https://github.com/handeyeco)! - Add scoring for
AbsoluteValue


- [#3348](#3348)
[`b1557c2a73`](b1557c2)
Thanks [@handeyeco](https://github.com/handeyeco)! - Add schema for
AbsoluteValue graph


- [#3345](#3345)
[`dde985f3b5`](dde985f)
Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent type
definitions, this is the initial implementation for supporting Tangent
graph in Interactive Graph


- [#3376](#3376)
[`8aa0a77886`](8aa0a77)
Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! -
Creation of new Types, Schema, and Kmath utilities for Exponential Graph

### Patch Changes

- [#3357](#3357)
[`ae0538d0a7`](ae0538d)
Thanks [@jeremywiebe](https://github.com/jeremywiebe)! - Improve code
documentation for all data-schema and user-input types

## @khanacademy/perseus-linter@4.9.0

### Minor Changes

- [#3381](#3381)
[`f18c0d9b6f`](f18c0d9)
Thanks [@anakaren-rojas](https://github.com/anakaren-rojas)! - Adds new
linters for parsed objects


- [#3395](#3395)
[`97223334ea`](9722333)
Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! -
Implementation of Editor support for Exponential Graph

### Patch Changes

- [#3396](#3396)
[`35fa9133db`](35fa913)
Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (CX) | Add a
linter warning for images with no size

- Updated dependencies
\[[`54db3fd4bd`](54db3fd),
[`ae0538d0a7`](ae0538d),
[`005e13d784`](005e13d),
[`d99f1c0259`](d99f1c0),
[`b1557c2a73`](b1557c2),
[`dde985f3b5`](dde985f),
[`8aa0a77886`](8aa0a77)]:
    -   @khanacademy/perseus-core@23.7.0
    -   @khanacademy/kmath@2.3.0

## @khanacademy/perseus-score@8.4.0

### Minor Changes

- [#3356](#3356)
[`a022e751d6`](a022e75)
Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent graph
scoring to support the Tangent graph in Interactive Graph


- [#3351](#3351)
[`005e13d784`](005e13d)
Thanks [@handeyeco](https://github.com/handeyeco)! - Add scoring for
AbsoluteValue


- [#3394](#3394)
[`7034844845`](7034844)
Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! -
Implementation of new scoring logic for Exponential Graph

### Patch Changes

- Updated dependencies
\[[`54db3fd4bd`](54db3fd),
[`ae0538d0a7`](ae0538d),
[`005e13d784`](005e13d),
[`d99f1c0259`](d99f1c0),
[`b1557c2a73`](b1557c2),
[`dde985f3b5`](dde985f),
[`8aa0a77886`](8aa0a77)]:
    -   @khanacademy/perseus-core@23.7.0
    -   @khanacademy/kmath@2.3.0

## @khanacademy/keypad-context@3.2.40

### Patch Changes

- Updated dependencies
\[[`54db3fd4bd`](54db3fd),
[`ae0538d0a7`](ae0538d),
[`005e13d784`](005e13d),
[`b1557c2a73`](b1557c2),
[`dde985f3b5`](dde985f),
[`8aa0a77886`](8aa0a77)]:
    -   @khanacademy/perseus-core@23.7.0

## @khanacademy/math-input@26.4.10

### Patch Changes

- Updated dependencies
\[[`54db3fd4bd`](54db3fd),
[`ae0538d0a7`](ae0538d),
[`005e13d784`](005e13d),
[`b1557c2a73`](b1557c2),
[`dde985f3b5`](dde985f),
[`8aa0a77886`](8aa0a77)]:
    -   @khanacademy/perseus-core@23.7.0
    -   @khanacademy/keypad-context@3.2.40
catandthemachines pushed a commit that referenced this pull request Apr 1, 2026
### Summary:
_This PR was created with the help of AI, albeit with heavy oversight and review. We had to make a new PR because the original history was fully messed up._

This is part of a series of PRs implementing the exponential graph type for
the Interactive Graph widget:

   PR1 – type definitions and schema
   PR2 – state management
▶️ PR3 – rendering & accessibility (this PR)
   PR4 – scoring
   PR5 – editor support

**Issue:** LEMS-3711

- Added rendering and accessibility support for the exponential graph type

- Implements the full interactive rendering of the exponential graph, including
  draggable curve points, a draggable asymptote line, the plotted curve, and
  screen reader descriptions. Also extracts two reusable components
  (`MovableAsymptote` and `AsymptoteDragHandle`) intended to be shared with
  the upcoming logarithm graph type.

### Changes:

- **graphs/exponential.tsx** — New file implementing `renderExponentialGraph`.
  Renders two `MovablePoint` components for the curve, a draggable asymptote
  line via `useDraggable`, and a `Plot` of f(x) = a·eᵇˣ + c using coefficients
  from `getExponentialCoefficients`. Caches the last valid coefficients via a
  ref to handle transient invalid states mid-drag. Dispatches
  `actions.exponential.movePoint` and `actions.exponential.moveCenter` on
  interaction.
- **graphs/components/asymptote-drag-handle.tsx** — New reusable visual
  component rendering the asymptote pill handle with orientation dots.
  Accepts `orientation: "horizontal" | "vertical"` for reuse with future
  vertical-asymptote graph types (e.g. logarithm).
- **graphs/components/movable-asymptote.tsx** — New reusable interactive
  component wrapping `SVGLine` and `AsymptoteDragHandle` with full drag,
  hover, and keyboard support via `useDraggable`. Exports
  `constrainAsymptoteKeyboard` for testability.
- **mafs-styles.css** — Adds `.movable-asymptote-handle*` CSS classes for
  the asymptote pill styling.
- **mafs-graph.tsx** — Wires up `renderExponentialGraph` in
  `renderGraphElements`, replacing the "Not implemented" stub from PR1.
- **interactive-graph.tsx** — Implements `getExponentialEquationString`,
  replacing the empty-string stub from PR1.
- **interactive-graph-ai-utils.ts** — Adds `ExponentialGraphOptions`,
  `ExponentialUserInput`, and wires them into `getGraphOptionsForProps` /
  `getUserInput`.
- **strings.ts** — Adds i18n strings for exponential graph screen reader
  descriptions (graph label, point descriptions, asymptote description).
- **interactive-graph.stories.tsx** — Adds an exponential graph story.
- **graphs/exponential.test.tsx** — Tests for rendering, dragging curve
  points, asymptote dragging, keyboard movement, and screen reader output.
- **graphs/components/movable-asymptote.test.tsx** — Tests for
  `MovableAsymptote` rendering and `constrainAsymptoteKeyboard` behavior.

### Test plan:
- [ ] `pnpm test packages/perseus` — all new rendering and component tests pass
- [ ] `pnpm tsc` — no new type errors
- [ ] `pnpm lint` — no lint errors
- [ ] Exponential graph renders and is interactive in Storybook
- [ ] Screen reader descriptions are correct
- [ ] No existing interactive graph tests regress

Author: SonicScrewdriver

Reviewers: claude[bot], ivyolamit, SonicScrewdriver

Required Reviewers:

Approved By: ivyolamit

Checks: ⏭️  1 check has been skipped, ✅ 10 checks were successful

Pull Request URL: #3393
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants