Skip to content

[Interactive Graph] Add tangent graph rendering, SR strings, and equation string#3354

Open
ivyolamit wants to merge 3 commits intoLEMS-3955/tangent-state-managementfrom
LEMS-3955/add-tangent-graph-component
Open

[Interactive Graph] Add tangent graph rendering, SR strings, and equation string#3354
ivyolamit wants to merge 3 commits intoLEMS-3955/tangent-state-managementfrom
LEMS-3955/add-tangent-graph-component

Conversation

@ivyolamit
Copy link
Contributor

@ivyolamit ivyolamit commented Mar 13, 2026

Summary:

PR series to add tangent graph support to the Interactive Graph widget:

  1. Foundation — Add tangent graph type definitions and data schema
  2. Math layer — Add tangent math utilities to kmath
  3. State management — Reducer, actions, initialization, and test data
  4. ▶️ Rendering — The tangent graph component, Storybook story, and AI utils
  5. Scoring — Add tangent scoring to the scoring package
  6. Editor — Add tangent to answer type

This is the fourth PR in a series to add tangent graph support to the Interactive Graph widget (LEMS-3955). It adds the rendering layer — the Mafs component, accessibility strings, equation string generation, and Storybook coverage.


Created the tangent graph visual component, add Storybook coverage, SR strings, and equation string for supporting Tangent graph in Interactive Graph.

  • Adds the tangent graph visual component (tangent.tsx) with renderTangentGraph(), computeTangent(), keyboard constraints, and screen reader descriptions
  • Adds 5 screen reader strings for tangent graph accessibility (srTangentGraph, srTangentInflectionPoint, srTangentSecondPoint, srTangentDescription, srTangentInteractiveElements)
  • Replaces the mafs-graph.tsx placeholder with real renderTangentGraph() call
  • Replaces the interactive-graph.tsx equation string placeholder with getTangentEquationString()
  • Adds Tangent Storybook story
  • 7 new tests for the tangent graph component
Implementation notes

Tangent component follows the sinusoid pattern. tangent.tsx mirrors sinusoid.tsx structurally: two movable control points, coefficient calculation with a ref-based fallback for invalid states, and the same keyboard constraint logic that prevents same-x points.

Asymptote handling (vertical line bug fix). Mafs Plot.OfX renders a single SVG <path> that draws vertical lines across discontinuities at asymptotes. To fix this, the tangent curve is split into segments between asymptotes:

  • getAsymptotePositions() computes asymptote x-positions within the visible range: x = (c + π/2 + nπ) / b
  • getPlotSegments() splits the x-range into segments between asymptotes with a small epsilon margin (0.01)
  • Each segment is rendered as a separate Plot.OfX with a domain prop, so Mafs never draws across a discontinuity
  • computeTangent() also returns NaN near asymptotes as a defensive backup. The proximity formula was corrected from the POC — the POC's ((arg / Math.PI + 0.5) % 1) - 0.5 measures distance from zero crossings (inflection points), not asymptotes. The corrected formula ((arg - Math.PI/2) / Math.PI) % 1 correctly targets asymptotes at arg = π/2 + nπ.
  • This approach was validated in the POC (commit 204f3f2)

Two getTangentCoefficients functions exist. The one in tangent.tsx returns NamedTangentCoefficient | undefined (named object with undefined fallback for same-x points) for rendering use. The one in kmath/coefficients.ts returns TangentCoefficient (numeric tuple, returns Infinity for same-x) for scoring use. The UI prevents the same-x case via the reducer's same-x guard, so the difference only matters as a defensive measure.

Screen reader descriptions. The tangent graph uses "inflection point" for the first control point (where the curve crosses the midline) and "control point" for the second point (a quarter-period away). This differs from sinusoid which uses "midline intersection" and "maximum/minimum point" — tangent doesn't have a meaningful max/min since it approaches ±∞.

Equation string. getTangentEquationString() formats y = a*tan(b*x - c) + d using the same pattern as getSinusoidEquationString() but using getTangentCoefficients from kmath.

No feature flag gate in rendering. The tangent graph renders unconditionally once the graph type is set to "tangent". The feature flag gate is in the editor (PR 6), which controls whether content creators can select "tangent" as a graph type. This follows the existing pattern — no other graph types check feature flags at render time.

References

Co-Authored by Claude Code (Opus)

Issue: LEMS-3955

Test plan:

  • pnpm tsc — no type errors
  • pnpm lint — no lint errors
  • pnpm prettier . --check — formatting clean
  • pnpm knip — no unused exports
  • SR tests pass (aria labels for graph, inflection point, control point, description, interactive elements)
  • Coefficient calculation test passes
  • Tangent computation test passes
  • Invalid coefficient test passes (same-x returns undefined)
  • Keyboard constraint test passes (avoids same-x)
  • Tangent story renders in Storybook (pnpm storybook)

…tangent graph visual component, add Storybook coverage, SR strings, and equation string for supporting Tangent graph in Interactive Graph
…nt graph rendering, SR strings, and equation string
@ivyolamit ivyolamit self-assigned this Mar 13, 2026
@github-actions github-actions bot added schema-change Attached to PRs when we detect Perseus Schema changes in it item-splitting-change and removed schema-change Attached to PRs when we detect Perseus Schema changes in it labels Mar 13, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 13, 2026

🗄️ Schema Change: No Changes ✅

@github-actions
Copy link
Contributor

github-actions bot commented Mar 13, 2026

Size Change: +1.05 kB (+0.22%)

Total Size: 487 kB

Filename Size Change
packages/perseus/dist/es/index.js 188 kB +861 B (+0.46%)
packages/perseus/dist/es/strings.js 7.66 kB +185 B (+2.48%)
ℹ️ View Unchanged
Filename Size
packages/kas/dist/es/index.js 20.8 kB
packages/keypad-context/dist/es/index.js 1 kB
packages/kmath/dist/es/index.js 6.03 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 100 kB
packages/perseus-linter/dist/es/index.js 8.82 kB
packages/perseus-score/dist/es/index.js 9.26 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
Contributor

github-actions bot commented Mar 13, 2026

🛠️ Item Splitting: No Changes ✅

@github-actions
Copy link
Contributor

github-actions bot commented Mar 13, 2026

npm Snapshot: Published

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

Example:

pnpm add @khanacademy/perseus@PR3354

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

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

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

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

@ivyolamit
Copy link
Contributor Author

Summary of the tangent implementation

PR Scope Note
1 Foundation — Add tangent graph type definitions and data schema #3345 for review
2 Math layer — Add tangent math utilities to kmath #3347 for review
3 State management — Reducer, actions, initialization, and test data #3353 base PR (for review)
4 Rendering — The tangent graph component, Storybook story, and AI utils #3354 this PR
5 Scoring — Add tangent scoring to the scoring package 🔜
6 Editor — Add tangent to answer type 🔜

@ivyolamit ivyolamit marked this pull request as ready for review March 13, 2026 22:30
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.

1 participant