Skip to content
Draft
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
7 changes: 7 additions & 0 deletions .changeset/large-crews-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@khanacademy/perseus": minor
"@khanacademy/perseus-core": minor
"@khanacademy/perseus-editor": minor
---

Render visible point labels on interactive graphs when the`perseus-enable-point-label-field` flag is on and the graph's`showPointLabels` field is true. Labels render via TeX in an HTML overlayso authors can include math (`$A$`, `$\theta$`, …) and stay outside theplotted region as a point is dragged toward an edge. Covers point,circle, angle, polygon, sinusoid, linear, linear-system, ray, andsegment in this release; remaining graph types follow in a per-graphseries. Existing content that sets `pointLabels` for screen-readerpurposes is unaffected — visible rendering requires both the flag and`showPointLabels: true`.
1 change: 1 addition & 0 deletions packages/perseus-core/src/feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/
const PerseusFeatureFlags = [
"input-number-to-numeric-input", // TODO(LEMS-4085): clean up feature flag
"perseus-enable-point-label-field", // TODO(AITQ-385): clean up feature flag
] as const;

export default PerseusFeatureFlags;
Expand Down
1 change: 1 addition & 0 deletions packages/perseus-editor/src/testing/feature-flags-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const DEFAULT_FEATURE_FLAGS = {
"interactive-graph-vector": false,
"interactive-graph-not-scored": false,
"input-number-to-numeric-input": false,
"perseus-enable-point-label-field": false, // TODO(AITQ-385): clean up feature flag
// ...add new flags here
};

Expand Down
1 change: 1 addition & 0 deletions packages/perseus/src/testing/feature-flags-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const DEFAULT_FEATURE_FLAGS = {
"interactive-graph-vector": false,
"interactive-graph-not-scored": false,
"input-number-to-numeric-input": false,
"perseus-enable-point-label-field": false, // TODO(AITQ-385): clean up feature flag
// ...add new flags here
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,16 @@ export const Point: Story = {
};

/**
* A point graph whose interactive point uses a custom screen-reader label ("T")
* via `pointLabels`, so the announcement matches the question prompt ("Plot point T …")
* instead of the generic "Point 1 …".
* A point graph whose interactive point uses a custom label ("T") via
* `pointLabels`. With `showPointLabels: true` (paired with the
* `perseus-enable-point-label-field` flag) the same string drives both
* the screen-reader announcement ("Plot point T …") and the visible
* on-canvas label.
*/
export const PointWithCustomLabel: Story = {
globals: {
featureFlags: ["perseus-enable-point-label-field"],
},
args: {
item: generateTestPerseusItem({
question: pointWithCustomLabelQuestion,
Expand Down Expand Up @@ -192,13 +197,16 @@ export const Polygon: Story = {
};

/**
* A polygon graph whose vertices use custom screen-reader
* labels ("A", "B", "C") via `pointLabels`, so the announcements
* match the question prompt instead of the generic "Point 1/2/3 …".
* Open with the Storybook a11y addon (or VoiceOver / JAWS) to verify
* each vertex announces "Point A / B / C at …".
* A polygon graph whose vertices use custom labels ("A", "B", "C") via
* `pointLabels`. With `showPointLabels: true` (paired with the
* `perseus-enable-point-label-field` flag) each vertex carries its
* letter both as the screen-reader announcement and as the visible
* on-canvas label.
*/
export const PolygonWithCustomLabels: Story = {
globals: {
featureFlags: ["perseus-enable-point-label-field"],
},
args: {
item: generateTestPerseusItem({
question: polygonWithCustomLabelsQuestion,
Expand Down Expand Up @@ -271,6 +279,9 @@ export const Exponential: Story = {
* unaffected — `pointLabels` only covers the curve's control points.
*/
export const ExponentialWithCustomLabels: Story = {
globals: {
featureFlags: ["perseus-enable-point-label-field"],
},
args: {
item: generateTestPerseusItem({
question: exponentialWithCustomLabelsQuestion,
Expand Down Expand Up @@ -311,6 +322,9 @@ export const Logarithm: Story = {
* announcement is unaffected.
*/
export const LogarithmWithCustomLabels: Story = {
globals: {
featureFlags: ["perseus-enable-point-label-field"],
},
args: {
item: generateTestPerseusItem({
question: logarithmWithCustomLabelsQuestion,
Expand All @@ -330,6 +344,9 @@ export const Tangent: Story = {
* point" semantic labels.
*/
export const TangentWithCustomLabels: Story = {
globals: {
featureFlags: ["perseus-enable-point-label-field"],
},
args: {
item: generateTestPerseusItem({
question: tangentWithCustomLabelsQuestion,
Expand All @@ -343,6 +360,9 @@ export const TangentWithCustomLabels: Story = {
* the SR announcement matches the prompt's naming convention.
*/
export const QuadraticWithCustomLabels: Story = {
globals: {
featureFlags: ["perseus-enable-point-label-field"],
},
args: {
item: generateTestPerseusItem({
question: quadraticWithCustomLabelsQuestion,
Expand All @@ -363,6 +383,9 @@ export const AbsoluteValue: Story = {
* the prompt's naming convention.
*/
export const AbsoluteValueWithCustomLabels: Story = {
globals: {
featureFlags: ["perseus-enable-point-label-field"],
},
args: {
item: generateTestPerseusItem({
question: absoluteValueWithCustomLabelsQuestion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import {X, Y} from "../math/coordinates";
import {actions} from "../reducer/interactive-graph-action";
import useGraphConfig from "../reducer/use-graph-config";
import {getEffectivePointLabels} from "../utils/point-labels";

import {usePointAriaLabel} from "./components/build-point-aria-label";
import {ClipToGraphBounds} from "./components/clip-to-graph-bounds";
Expand Down Expand Up @@ -46,8 +47,13 @@ function AbsoluteValueGraph(props: AbsoluteValueGraphProps) {
const id = React.useId();
const descriptionId = id + "-description";

const {coords, pointLabels, snapStep} = graphState;
const buildLabel = usePointAriaLabel(pointLabels);
const {coords, pointLabels, showPointLabels, snapStep} = graphState;
const effectiveLabels = getEffectivePointLabels(
showPointLabels,
pointLabels,
coords.length,
);
const buildLabel = usePointAriaLabel(effectiveLabels);

// Cache last valid coefficients to protect against transient invalid
// states that can occur mid-drag (e.g., both points on the same x).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {X, Y} from "../math";
import {findIntersectionOfRays} from "../math/geometry";
import {actions} from "../reducer/interactive-graph-action";
import useGraphConfig from "../reducer/use-graph-config";
import {getEffectivePointLabels} from "../utils/point-labels";

import {Angle} from "./components/angle-indicators";
import {usePointAriaLabel} from "./components/build-point-aria-label";
Expand Down Expand Up @@ -54,6 +55,7 @@ function AngleGraph(props: AngleGraphProps) {
const {
coords,
pointLabels,
showPointLabels,
showAngles,
range,
allowReflexAngles,
Expand All @@ -63,7 +65,12 @@ function AngleGraph(props: AngleGraphProps) {
// [2]=starting side. The MovablePoints below are rendered in a
// different order (vertex first), so each call site indexes pointLabels
// by the coords slot it is bound to.
const buildLabel = usePointAriaLabel(pointLabels);
const effectiveLabels = getEffectivePointLabels(
Comment thread
EmiliaPalaghita marked this conversation as resolved.
showPointLabels,
pointLabels,
coords.length,
);
const buildLabel = usePointAriaLabel(effectiveLabels);

// Break the coords into the two end points and the center point
const endPoints: [vec.Vector2, vec.Vector2] = [coords[0], coords[2]];
Expand Down
14 changes: 12 additions & 2 deletions packages/perseus/src/widgets/interactive-graphs/graphs/circle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {snap, X, Y} from "../math";
import {actions} from "../reducer/interactive-graph-action";
import {getRadius} from "../reducer/interactive-graph-state";
import useGraphConfig from "../reducer/use-graph-config";
import {getEffectivePointLabels} from "../utils/point-labels";

import {usePointAriaLabel} from "./components/build-point-aria-label";
import {ClipToGraphBounds} from "./components/clip-to-graph-bounds";
Expand Down Expand Up @@ -48,10 +49,19 @@ type CircleGraphProps = MafsGraphProps<CircleGraphState>;
// Exported for testing
export function CircleGraph(props: CircleGraphProps) {
const {dispatch, graphState} = props;
const {center, pointLabels, radiusPoint, snapStep} = graphState;
const {center, pointLabels, showPointLabels, radiusPoint, snapStep} =
graphState;

const {strings, locale} = usePerseusI18n();
const buildLabel = usePointAriaLabel(pointLabels);
// Circle only has one labelable point (the radius point at index 0); the
// center is a MovableCircle, not a MovablePoint, and is intentionally not
// overridden — see MovablePoint's ariaLabel below.
const effectiveLabels = getEffectivePointLabels(
showPointLabels,
pointLabels,
1,
);
const buildLabel = usePointAriaLabel(effectiveLabels);

const radius = getRadius(graphState);
const id = React.useId();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {default} from "./movable-point-labels-layer";
Loading
Loading