diff --git a/packages/perseus-core/src/data-schema.ts b/packages/perseus-core/src/data-schema.ts index e10cbea5686..f6dbdd835b4 100644 --- a/packages/perseus-core/src/data-schema.ts +++ b/packages/perseus-core/src/data-schema.ts @@ -1173,6 +1173,13 @@ export type PerseusGraphTypePoint = { startCoords?: Coord[]; /** Used instead of `coords` in some old graphs that have only one point. */ coord?: Coord; + /** + * Custom display names for each point, used in screen reader labels. + * If provided, pointNames[i] replaces the default "Point {i+1}" label. + * Example: ["A", "T"] → "Point A at 3 comma 4" instead of + * "Point 1 at 3 comma 4" + */ + pointNames?: string[]; }; export type PerseusGraphTypePolygon = { diff --git a/packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.ts b/packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.ts index 7413af98424..2a9bfe0478a 100644 --- a/packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.ts +++ b/packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.ts @@ -72,6 +72,7 @@ const parsePerseusGraphTypePoint = object({ coords: optional(nullable(array(pairOfNumbers))), startCoords: optional(array(pairOfNumbers)), coord: optional(pairOfNumbers), + pointNames: optional(array(string)), }); const parsePerseusGraphTypePolygon = object({ diff --git a/packages/perseus-editor/src/widgets/interactive-graph-editor/interactive-graph-editor.tsx b/packages/perseus-editor/src/widgets/interactive-graph-editor/interactive-graph-editor.tsx index a47213aef5f..6e8f132e7a4 100644 --- a/packages/perseus-editor/src/widgets/interactive-graph-editor/interactive-graph-editor.tsx +++ b/packages/perseus-editor/src/widgets/interactive-graph-editor/interactive-graph-editor.tsx @@ -179,6 +179,22 @@ class InteractiveGraphEditor extends React.Component { this.props.onChange({graph: graph}); }; + changePointNames = (pointNames: string[]) => { + if (this.props.graph?.type !== "point") { + return; + } + + const graph = { + ...this.props.graph, + pointNames, + }; + const correct = { + ...this.props.correct, + pointNames, + }; + this.props.onChange({graph, correct}); + }; + // serialize() is what makes copy/paste work. All the properties included // in the serialization json are included when, for example, a graph // is copied from the question editor and pasted into the hint editor @@ -214,6 +230,11 @@ class InteractiveGraphEditor extends React.Component { type: correct.type, startCoords: this.props.graph && getStartCoords(this.props.graph), + // Preserve custom point names from editor config + ...(this.props.graph?.type === "point" && + this.props.graph.pointNames && { + pointNames: this.props.graph.pointNames, + }), }, correct: correct, }); @@ -225,6 +246,7 @@ class InteractiveGraphEditor extends React.Component { "numPoints", "numSides", "numSegments", + "pointNames", "showAngles", "showSides", "snapTo", @@ -435,6 +457,7 @@ class InteractiveGraphEditor extends React.Component { range={this.props.range} step={this.props.step} onChange={this.changeStartCoords} + onChangePointNames={this.changePointNames} /> )} `Point ${num} at ${x} comma ${y}.` + ``` + +2. **`sequenceNumber` prop** on `MovablePoint` (`packages/perseus/src/widgets/interactive-graphs/graphs/components/movable-point.tsx:18-26`): + - 1-indexed integer, resets per interactive figure + - Passed to `useControlPoint` which builds the aria-label + +3. **Aria-label generation** (`packages/perseus/src/widgets/interactive-graphs/graphs/components/use-control-point.tsx:93-100`): + ```ts + const pointAriaLabel = + ariaLabel || + strings.srPointAtCoordinates({ + num: sequenceNumber, + x: srFormatNumber(point[X], locale), + y: srFormatNumber(point[Y], locale), + }); + ``` + Note: A custom `ariaLabel` prop already overrides the default — this is the extension point used by the POC. + +4. **Editor UI** shows "Point 1:", "Point 2:", etc. in: + - Start coords settings (`packages/perseus-editor/src/widgets/interactive-graph-editor/start-coords/start-coords-point.tsx`) + - SR tree visualization (`packages/perseus-editor/src/widgets/interactive-graph-editor/components/interactive-graph-sr-tree.tsx`) + +### Graph types that use point naming + +| Graph Type | # Points | Naming Source | File | +|---|---|---|---| +| **Point** | 1+ (or unlimited) | `sequenceNumber={i + 1}` | `graphs/point.tsx` | +| **Polygon** | 3+ (or unlimited) | `sequenceNumber={i + 1}` | `graphs/polygon.tsx` | +| **Angle** | 3 (fixed) | Hard-coded 1, 2, 3 + custom ariaLabels | `graphs/angle.tsx` | +| **Line/Ray** | 2 per line | Via `MovableLine`, sequenceNumber 1 & 2 | `graphs/linear.tsx`, `graphs/ray.tsx` | +| **Segment** | 2 per segment | Via `MovableLine` | `graphs/segment.tsx` | +| **Linear System** | 2 per line | `srLinearSystemPoint` string | `graphs/linear-system.tsx` | +| **Circle** | 1 | sequenceNumber 1 | `graphs/circle.tsx` | +| **Quadratic** | 2-3 | `srQuadraticPoint*` strings | `graphs/quadratic.tsx` | +| **Sinusoid** | 2 | sequenceNumber 1 & 2 | `graphs/sinusoid.tsx` | + +--- + +## POC Implementation (Completed) + +### Scope + +The `point` graph type only. Other graph types (polygon, line, etc.) can be addressed in follow-up work using the same pattern. + +### Architecture Decision: Props Over Reducer State + +**Key learning from POC iteration:** `pointNames` must NOT be stored in the reducer state (`PointGraphState`). Instead, it flows directly through React props from the widget config to the renderer. + +**Why:** Storing `pointNames` in the reducer requires `reinitialize` to update the state when names change. `reinitialize` fires in a `useEffect`, which causes a timing issue — the SR tree reads the DOM's aria-labels in its own `useEffect` before the graph has re-rendered with the new state. By passing `pointNames` directly through props, changes take effect in the same render cycle. + +**Data flow (implemented):** +``` +Editor: this.props.graph.pointNames + this.props.correct.pointNames + → InteractiveGraph widget: userInput (which is correct) + → StatefulMafsGraph: props.graph (which is userInput) + → MafsGraph: props.graph passed to renderGraphElements + → renderGraphElements extracts pointNames from graphOptions + → renderPointGraph(state, dispatch, i18n, pointNames) + → PointGraph component receives pointNames as prop + → MovablePoint receives custom ariaLabel +``` + +### Files Modified (9 files) + +#### 1. Data Schema — `packages/perseus-core/src/data-schema.ts` +Added `pointNames?: string[]` to `PerseusGraphTypePoint`: +```ts +export type PerseusGraphTypePoint = { + type: "point"; + numPoints?: number | "unlimited"; + coords?: Coord[] | null; + startCoords?: Coord[]; + coord?: Coord; + pointNames?: string[]; // NEW +}; +``` + +#### 2. Parser — `packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.ts` +Added `pointNames: optional(array(string))` to `parsePerseusGraphTypePoint`. + +#### 3. String Type — `packages/perseus/src/strings.ts` +Changed `srPointAtCoordinates` `num` parameter type from `number` to `number | string` so custom names can be passed. + +#### 4. Graph Renderer — `packages/perseus/src/widgets/interactive-graphs/graphs/point.tsx` +- `renderPointGraph` now accepts an optional `pointNames?: string[]` parameter +- Passes `pointNames` as a prop to the `PointGraph` component +- `LimitedPointGraph` and `UnlimitedPointGraph` read `pointNames` from props (NOT from state) +- When a custom name exists at index `i`, builds a custom `ariaLabel` using `strings.srPointAtCoordinates({num: customName, ...})` and passes it to `MovablePoint` +- When no custom name exists, passes `ariaLabel={undefined}` which falls back to the default `sequenceNumber` behavior +- `getPointGraphDescription` also accepts `pointNames` and uses custom names in the SR element description string + +#### 5. Mafs Graph — `packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx` +- Added `graph: PerseusGraphType` to `MafsGraphProps` type (it was already passed via spread from `StatefulMafsGraph` but not declared in the type) +- `renderGraphElements` now receives and passes through `graphOptions` +- For the `"point"` case, extracts `pointNames` from `graphOptions` and passes it to `renderPointGraph` + +#### 6. Test Utils — `packages/perseus/src/widgets/interactive-graphs/utils.ts` +Added `graph: {type: "segment"}` to `getBaseMafsGraphPropsForTests()` to satisfy the new `MafsGraphProps.graph` field. + +#### 7. Editor — `packages/perseus-editor/src/widgets/interactive-graph-editor/interactive-graph-editor.tsx` +- Added `changePointNames` method that updates BOTH `graph` and `correct` with the new `pointNames` (both must be updated — `correct` becomes the widget's `userInput`, `graph` is the editor's state) +- Passes `onChangePointNames={this.changePointNames}` to `StartCoordsSettings` +- `serialize()` includes `pointNames` in the serialized graph: added to the `_.each` list of keys copied from `correct`, and also explicitly spread from `this.props.graph` into the serialized `graph` object + +#### 8. Start Coords Settings — `packages/perseus-editor/src/widgets/interactive-graph-editor/start-coords/start-coords-settings.tsx` +- Added `onChangePointNames?` to `Props` type +- Threads `pointNames` and `onChangePointNames` through to `StartCoordsPoint` (only for `type === "point"`) + +#### 9. Start Coords Point UI — `packages/perseus-editor/src/widgets/interactive-graph-editor/start-coords/start-coords-point.tsx` +- Added `pointNames?: string[]` and `onChangePointNames?` to `Props` +- Layout: "Point {name}:" title on top, input fields row below +- Name input uses `LabelMedium tag="label"` with "name" label, matching the `CoordinatePairInput` styling pattern +- `TextField` with `placeholder={index + 1}` so default numbering is visible when empty +- Title dynamically shows custom name (e.g., "Point T:") or falls back to index (e.g., "Point 1:") + +### SR Tree — No Changes Needed +`packages/perseus-editor/src/widgets/interactive-graph-editor/components/interactive-graph-sr-tree.tsx` reads aria-labels directly from the DOM. Since `pointNames` changes flow through `correct` (which is in the `useEffect` dependency array), the tree re-reads the DOM after the graph re-renders with updated aria-labels. + +--- + +## Remaining Work for Production + +### Tests to Write +1. **`point.tsx` tests** — Verify that `renderPointGraph` with `pointNames` produces correct aria-labels on `MovablePoint` elements +2. **`point.tsx` tests** — Verify `getPointGraphDescription` includes custom names in the description string +3. **`start-coords-point.tsx` tests** — Verify the name input renders and calls `onChangePointNames` on input +4. **`interactive-graph-editor.tsx` tests** — Verify `changePointNames` updates both `graph` and `correct` +5. **`interactive-graph-editor.tsx` tests** — Verify `serialize()` includes `pointNames` in output + +### Storybook +- Add a story demonstrating custom point names in the editor +- Add a story showing the SR tree with custom names + +### Edge Cases to Handle + +| Case | Current POC Behavior | Production Recommendation | +|---|---|---| +| `pointNames` not provided | Falls back to "Point 1", etc. (working) | No change needed | +| `pointNames` shorter than `numPoints` | Named points use custom names; remaining use index numbers (working) | No change needed | +| `pointNames` has empty string at index | Falls back to index number for that point (working) | No change needed | +| Unlimited points mode | Custom names apply to points at matching indices; extras use numbers (working) | No change needed | +| Duplicate point names | Allowed (no validation) | Consider adding an editor warning | +| Non-ASCII names (e.g., Greek letters) | Works — template literals handle Unicode | No change needed | +| `numPoints` changes after names are set | `pointNames` array may be longer/shorter than new count | Consider trimming/extending in `changePointNames` or the editor | + +### Polish +- Consider adding a tooltip or info text explaining the "name" field in the editor +- Consider visual point labels on the graph (showing "T" next to the point, not just SR) + +--- + +## Key Files Reference + +| File | Role | +|---|---| +| `packages/perseus-core/src/data-schema.ts` | `PerseusGraphTypePoint` type with `pointNames` field | +| `packages/perseus-core/src/parse-perseus-json/perseus-parsers/interactive-graph-widget.ts` | Parser for `pointNames` | +| `packages/perseus/src/strings.ts` | `srPointAtCoordinates` string template (`num: number \| string`) | +| `packages/perseus/src/widgets/interactive-graphs/graphs/point.tsx` | Point graph renderer — builds custom aria-labels | +| `packages/perseus/src/widgets/interactive-graphs/graphs/components/movable-point.tsx` | `MovablePoint` component — accepts `ariaLabel` prop | +| `packages/perseus/src/widgets/interactive-graphs/graphs/components/use-control-point.tsx` | Builds aria-label from `ariaLabel` prop or `sequenceNumber` fallback | +| `packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx` | Routes `pointNames` from widget props to graph renderer | +| `packages/perseus-editor/src/widgets/interactive-graph-editor/interactive-graph-editor.tsx` | Editor — `changePointNames`, serialization | +| `packages/perseus-editor/src/widgets/interactive-graph-editor/start-coords/start-coords-settings.tsx` | Threads props to `StartCoordsPoint` | +| `packages/perseus-editor/src/widgets/interactive-graph-editor/start-coords/start-coords-point.tsx` | Editor UI — name input per point | +| `packages/perseus-editor/src/widgets/interactive-graph-editor/components/interactive-graph-sr-tree.tsx` | SR tree — reads DOM aria-labels (no changes needed) | + +--- + +## Future Work (Out of Scope) + +- Custom point names for other graph types (polygon, segment, line, etc.) — same pattern: add `pointNames` to the graph type, thread through `renderGraphElements` +- Visual point labels on the graph itself (not just SR — showing "T" next to the point) +- Locked figure point naming (for non-interactive reference points) +- Migration tooling if we want to backfill existing content with custom names diff --git a/packages/perseus-editor/src/widgets/interactive-graph-editor/start-coords/start-coords-point.tsx b/packages/perseus-editor/src/widgets/interactive-graph-editor/start-coords/start-coords-point.tsx index ec88c2210a1..6012326f72c 100644 --- a/packages/perseus-editor/src/widgets/interactive-graph-editor/start-coords/start-coords-point.tsx +++ b/packages/perseus-editor/src/widgets/interactive-graph-editor/start-coords/start-coords-point.tsx @@ -1,7 +1,8 @@ import {View} from "@khanacademy/wonder-blocks-core"; +import {TextField} from "@khanacademy/wonder-blocks-form"; import {Strut} from "@khanacademy/wonder-blocks-layout"; import {semanticColor, spacing} from "@khanacademy/wonder-blocks-tokens"; -import {LabelLarge} from "@khanacademy/wonder-blocks-typography"; +import {LabelLarge, LabelMedium} from "@khanacademy/wonder-blocks-typography"; import {StyleSheet} from "aphrodite"; import * as React from "react"; @@ -11,28 +12,55 @@ import type {Coord} from "@khanacademy/perseus"; type Props = { startCoords: Coord[]; + pointNames?: string[]; onChange: (startCoords: Coord[]) => void; + onChangePointNames?: (pointNames: string[]) => void; }; const StartCoordsPoint = (props: Props) => { - const {startCoords, onChange} = props; + const {startCoords, pointNames, onChange, onChangePointNames} = props; return ( <> {startCoords.map((coord, index) => { + const customName = pointNames?.[index] || ""; + const displayName = customName || `${index + 1}`; return ( - {`Point ${index + 1}:`} - - { - const newStartCoords = [...startCoords]; - newStartCoords[index] = newCoord; - onChange(newStartCoords); - }} - /> + {`Point ${displayName}:`} + + + + name + + { + const newPointNames = [ + ...(pointNames || []), + ]; + // Ensure the array is long enough + while (newPointNames.length <= index) { + newPointNames.push(""); + } + newPointNames[index] = newName; + onChangePointNames?.(newPointNames); + }} + style={styles.nameTextField} + /> + + + { + const newStartCoords = [...startCoords]; + newStartCoords[index] = newCoord; + onChange(newStartCoords); + }} + /> + ); })} @@ -46,9 +74,20 @@ const styles = StyleSheet.create({ marginTop: spacing.xSmall_8, padding: spacing.small_12, borderRadius: spacing.xSmall_8, + flexDirection: "column", + }, + inputsRow: { flexDirection: "row", alignItems: "center", }, + row: { + display: "flex", + flexDirection: "row", + alignItems: "center", + }, + nameTextField: { + width: spacing.xxxLarge_64, + }, }); export default StartCoordsPoint; diff --git a/packages/perseus-editor/src/widgets/interactive-graph-editor/start-coords/start-coords-settings.tsx b/packages/perseus-editor/src/widgets/interactive-graph-editor/start-coords/start-coords-settings.tsx index ef3e6408259..23e0a734ef6 100644 --- a/packages/perseus-editor/src/widgets/interactive-graph-editor/start-coords/start-coords-settings.tsx +++ b/packages/perseus-editor/src/widgets/interactive-graph-editor/start-coords/start-coords-settings.tsx @@ -36,10 +36,12 @@ type Props = PerseusGraphType & { step: [x: number, y: number]; allowReflexAngles?: boolean; onChange: (startCoords: StartCoords) => void; + onChangePointNames?: (pointNames: string[]) => void; }; const StartCoordsSettingsInner = (props: Props) => { - const {type, range, step, allowReflexAngles, onChange} = props; + const {type, range, step, allowReflexAngles, onChange, onChangePointNames} = + props; switch (type) { // Graphs with startCoords of type CollinearTuple @@ -107,7 +109,11 @@ const StartCoordsSettingsInner = (props: Props) => { return ( ); case "angle": diff --git a/packages/perseus/src/strings.ts b/packages/perseus/src/strings.ts index 9587f6f4b4a..48953e77c53 100644 --- a/packages/perseus/src/strings.ts +++ b/packages/perseus/src/strings.ts @@ -134,7 +134,7 @@ export type PerseusStrings = { x, y, }: { - num: number; + num: number | string; x: string; y: string; }) => string; diff --git a/packages/perseus/src/widgets/interactive-graphs/graphs/point.tsx b/packages/perseus/src/widgets/interactive-graphs/graphs/point.tsx index 264bd963233..af555c6513f 100644 --- a/packages/perseus/src/widgets/interactive-graphs/graphs/point.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/graphs/point.tsx @@ -1,6 +1,7 @@ import {useTimeout} from "@khanacademy/wonder-blocks-timing"; import * as React from "react"; +import {usePerseusI18n} from "../../../components/i18n-context"; import {actions} from "../reducer/interactive-graph-action"; import useGraphConfig from "../reducer/use-graph-config"; import {getCSSZoomFactor} from "../utils"; @@ -23,14 +24,27 @@ export function renderPointGraph( state: PointGraphState, dispatch: Dispatch, i18n: I18nContextType, + pointNames?: string[], ): InteractiveGraphElementSuite { return { - graph: , - interactiveElementsDescription: getPointGraphDescription(state, i18n), + graph: ( + + ), + interactiveElementsDescription: getPointGraphDescription( + state, + i18n, + pointNames, + ), }; } -type Props = MafsGraphProps; +type Props = MafsGraphProps & { + pointNames?: string[]; +}; type StatefulProps = Props & { graphConfig: GraphConfig; pointsRef: React.MutableRefObject<(SVGElement | null)[]>; @@ -78,27 +92,43 @@ function PointGraph(props: Props) { } function LimitedPointGraph(statefulProps: StatefulProps) { - const {dispatch} = statefulProps; + const {dispatch, pointNames} = statefulProps; + const {strings, locale} = usePerseusI18n(); return ( <> - {statefulProps.graphState.coords.map((point, i) => ( - - dispatch(actions.pointGraph.movePoint(i, destination)) - } - /> - ))} + {statefulProps.graphState.coords.map((point, i) => { + const customName = pointNames?.[i]; + const ariaLabel = customName + ? strings.srPointAtCoordinates({ + num: customName, + x: srFormatNumber(point[0], locale), + y: srFormatNumber(point[1], locale), + }) + : undefined; + return ( + + dispatch( + actions.pointGraph.movePoint(i, destination), + ) + } + /> + ); + })} ); } function UnlimitedPointGraph(statefulProps: StatefulProps) { - const {dispatch, graphConfig, pointsRef, top, left} = statefulProps; + const {dispatch, graphConfig, pointsRef, top, left, pointNames} = + statefulProps; const {coords} = statefulProps.graphState; + const {strings, locale} = usePerseusI18n(); // When users drag a point on iOS Safari, the browser fires a click event after the mouseup // at the original click location, which would add an unwanted new point. We track drag @@ -149,33 +179,46 @@ function UnlimitedPointGraph(statefulProps: StatefulProps) { dispatch(actions.pointGraph.addPoint(graphCoordinates[0])); }} /> - {coords.map((point, i) => ( - { - dragEndCallbackTimer.clear(); - setIsCurrentlyDragging(true); - }} - onMove={(destination) => { - dispatch(actions.pointGraph.movePoint(i, destination)); - }} - onDragEnd={() => { - // Start timer to reset drag state after delay - dragEndCallbackTimer.set(); - }} - ref={(ref) => { - pointsRef.current[i] = ref; - }} - onFocus={() => { - dispatch(actions.pointGraph.focusPoint(i)); - }} - onClick={() => { - dispatch(actions.pointGraph.clickPoint(i)); - }} - /> - ))} + {coords.map((point, i) => { + const customName = pointNames?.[i]; + const ariaLabel = customName + ? strings.srPointAtCoordinates({ + num: customName, + x: srFormatNumber(point[0], locale), + y: srFormatNumber(point[1], locale), + }) + : undefined; + return ( + { + dragEndCallbackTimer.clear(); + setIsCurrentlyDragging(true); + }} + onMove={(destination) => { + dispatch( + actions.pointGraph.movePoint(i, destination), + ); + }} + onDragEnd={() => { + // Start timer to reset drag state after delay + dragEndCallbackTimer.set(); + }} + ref={(ref) => { + pointsRef.current[i] = ref; + }} + onFocus={() => { + dispatch(actions.pointGraph.focusPoint(i)); + }} + onClick={() => { + dispatch(actions.pointGraph.clickPoint(i)); + }} + /> + ); + })} ); } @@ -184,6 +227,7 @@ function UnlimitedPointGraph(statefulProps: StatefulProps) { export function getPointGraphDescription( state: PointGraphState, i18n: {strings: PerseusStrings; locale: string}, + pointNames?: string[], ): string { const {strings, locale} = i18n; @@ -193,7 +237,7 @@ export function getPointGraphDescription( const pointDescriptions = state.coords.map(([x, y], index) => strings.srPointAtCoordinates({ - num: index + 1, + num: pointNames?.[index] || index + 1, x: srFormatNumber(x, locale), y: srFormatNumber(y, locale), }), diff --git a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx index f6aa6e91de5..49257c958ee 100644 --- a/packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx +++ b/packages/perseus/src/widgets/interactive-graphs/mafs-graph.tsx @@ -64,6 +64,7 @@ import type { } from "./types"; import type {I18nContextType} from "../../components/i18n-context"; import type {PerseusStrings} from "../../strings"; +import type {PerseusGraphType} from "@khanacademy/perseus-core"; import type {vec} from "mafs"; import "mafs/core.css"; @@ -74,6 +75,7 @@ const GRAPH_LEFT_MARGIN = 20; export type MafsGraphProps = { box: [number, number]; backgroundImage?: InteractiveGraphProps["backgroundImage"]; + graph: PerseusGraphType; lockedFigures: InteractiveGraphProps["lockedFigures"]; step: InteractiveGraphProps["step"]; gridStep: [x: number, y: number]; @@ -155,6 +157,7 @@ export const MafsGraph = (props: MafsGraphProps) => { dispatch, i18n, markings: props.markings, + graphOptions: props.graph, }); const disableInteraction = readOnly || !!props.static; @@ -742,8 +745,10 @@ const renderGraphElements = (props: { // coordinates of the graph elements. We don't want to mention the // coordinates if the graph is not on a coordinate plane (no axes). markings: InteractiveGraphProps["markings"]; + // The original graph options from the widget config. + graphOptions: PerseusGraphType; }): InteractiveGraphElementSuite => { - const {state, dispatch, i18n, markings} = props; + const {state, dispatch, i18n, markings, graphOptions} = props; const {type} = state; switch (type) { case "angle": @@ -758,8 +763,13 @@ const renderGraphElements = (props: { return renderRayGraph(state, dispatch, i18n); case "polygon": return renderPolygonGraph(state, dispatch, i18n, markings); - case "point": - return renderPointGraph(state, dispatch, i18n); + case "point": { + const pointNames = + graphOptions.type === "point" + ? graphOptions.pointNames + : undefined; + return renderPointGraph(state, dispatch, i18n, pointNames); + } case "circle": return renderCircleGraph(state, dispatch, i18n); case "quadratic": diff --git a/packages/perseus/src/widgets/interactive-graphs/utils.ts b/packages/perseus/src/widgets/interactive-graphs/utils.ts index 8d88c2a24c9..7a84162e52b 100644 --- a/packages/perseus/src/widgets/interactive-graphs/utils.ts +++ b/packages/perseus/src/widgets/interactive-graphs/utils.ts @@ -155,6 +155,7 @@ export function getBaseMafsGraphPropsForTests(): MafsGraphProps { box: [400, 400], step: [1, 1], gridStep: [1, 1], + graph: {type: "segment"}, markings: "graph", containerSizeClass: "small", showTooltips: false,