From 56aff8f3bd010d7a903130c0afaa9c3be6e2d63f Mon Sep 17 00:00:00 2001 From: ellvix Date: Tue, 10 Feb 2026 09:03:03 -0700 Subject: [PATCH 1/6] chore: fixes for violin, smooth, and line plots --- .claude/settings.local.json | 7 +++++ examples/violin.html | 20 ++------------ src/command/describe.ts | 55 +++++++++++++++++++++++-------------- 3 files changed, 44 insertions(+), 38 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..bbf2e31d8 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(npm run build:*)" + ] + } +} diff --git a/examples/violin.html b/examples/violin.html index e78873ff9..dab0185f0 100644 --- a/examples/violin.html +++ b/examples/violin.html @@ -6,23 +6,7 @@
- +
@@ -576,4 +560,4 @@
- \ No newline at end of file + diff --git a/src/command/describe.ts b/src/command/describe.ts index 06c98f395..6cff0dd19 100644 --- a/src/command/describe.ts +++ b/src/command/describe.ts @@ -265,11 +265,6 @@ export class AnnouncePositionCommand extends DescribeCommand { * Executes the command to announce the current position. */ public execute(): void { - // Check if speech is off - if (this.textService.isOff()) { - return; - } - // Get current state const state = this.context.state; @@ -297,13 +292,21 @@ export class AnnouncePositionCommand extends DescribeCommand { ) { this.announceSegmentedBarPosition(state, x, cols); } else if (traceType === TraceType.SMOOTH) { - // Violin KDE plots: y=violin index, x=position within violin - this.announceViolinPosition(y, rows, x, cols); + if (rows > 1) { + // Multi-violin plots: y=violin index, x=position within violin + this.announceMultiViolinPosition(y, rows, x, cols); + } else { + // Single smooth/violin plot: 1D position within the curve + this.announceSmoothPosition(x, cols); + } } // Check for multi plots (multiline, panel, layer, facet) else if (traceType === TraceType.LINE && state.groupCount && state.groupCount > 1) { // Multi-line plots: x=line index, y=position within line this.announceMultiLinePosition(x, rows, y, cols); + } else if (traceType === TraceType.LINE) { + // Single line plot: y=position within line, cols=total points + this.announce1DPosition(y, cols); } // Default position announcement else if (this.is2DPlot(rows, cols)) { @@ -327,7 +330,7 @@ export class AnnouncePositionCommand extends DescribeCommand { const position = x + 1; const total = cols; - if (this.textService.isTerse()) { + if (this.textService.isTerse() || this.textService.isOff()) { const percent = cols > 1 ? Math.round((x / (cols - 1)) * 100) : 0; this.textViewModel.update(`${percent}%`); } else { @@ -342,7 +345,7 @@ export class AnnouncePositionCommand extends DescribeCommand { const colPos = x + 1; const rowPos = y + 1; - if (this.textService.isTerse()) { + if (this.textService.isTerse() || this.textService.isOff()) { const colPercent = cols > 1 ? Math.round((x / (cols - 1)) * 100) : 0; const rowPercent = rows > 1 ? Math.round((y / (rows - 1)) * 100) : 0; this.textViewModel.update(`${colPercent}%, ${rowPercent}%`); @@ -367,7 +370,7 @@ export class AnnouncePositionCommand extends DescribeCommand { const totalBoxes = braille.values.length; const position = boxIndex + 1; - if (this.textService.isTerse()) { + if (this.textService.isTerse() || this.textService.isOff()) { const percent = totalBoxes > 1 ? Math.round((boxIndex / (totalBoxes - 1)) * 100) : 0; this.textViewModel.update(`${percent}%, ${section.toLowerCase()}`); } else { @@ -389,7 +392,7 @@ export class AnnouncePositionCommand extends DescribeCommand { const totalCandles = braille.values[0].length; const position = candleIndex + 1; - if (this.textService.isTerse()) { + if (this.textService.isTerse() || this.textService.isOff()) { const percent = totalCandles > 1 ? Math.round((candleIndex / (totalCandles - 1)) * 100) : 0; this.textViewModel.update(`${percent}%, ${section.toLowerCase()}`); } else { @@ -406,7 +409,7 @@ export class AnnouncePositionCommand extends DescribeCommand { const position = x + 1; const total = cols; - if (this.textService.isTerse()) { + if (this.textService.isTerse() || this.textService.isOff()) { const percent = cols > 1 ? Math.round((x / (cols - 1)) * 100) : 0; this.textViewModel.update(`${percent}%, ${level}`); } else { @@ -415,10 +418,22 @@ export class AnnouncePositionCommand extends DescribeCommand { } /** - * Announces position for violin plots. - * Shows which violin (row) and position within that violin (col). + * Announces position for smooth/violin plots. + * Treats as 1D plot - only announces position within the curve. + */ + private announceSmoothPosition( + posIndex: number, + totalPos: number, + ): void { + // Smooth plots are 1D - just use position within the curve + this.announce1DPosition(posIndex, totalPos); + } + + /** + * Announces position for multi-violin plots. + * Shows which violin and position within that violin. */ - private announceViolinPosition( + private announceMultiViolinPosition( violinIndex: number, totalViolins: number, posIndex: number, @@ -426,13 +441,13 @@ export class AnnouncePositionCommand extends DescribeCommand { ): void { const violinPos = violinIndex + 1; const pos = posIndex + 1; + const violinPrefix = `Violin ${violinPos} of ${totalViolins}`; - if (this.textService.isTerse()) { - const violinPercent = totalViolins > 1 ? Math.round((violinIndex / (totalViolins - 1)) * 100) : 0; + if (this.textService.isTerse() || this.textService.isOff()) { const posPercent = totalPos > 1 ? Math.round((posIndex / (totalPos - 1)) * 100) : 0; - this.textViewModel.update(`${violinPercent}%, ${posPercent}%`); + this.textViewModel.update(`${violinPrefix}, ${posPercent}%`); } else { - this.textViewModel.update(`Position is ${violinPos} of ${totalViolins}, ${pos} of ${totalPos}`); + this.textViewModel.update(`${violinPrefix}, Position is ${pos} of ${totalPos}`); } } @@ -450,7 +465,7 @@ export class AnnouncePositionCommand extends DescribeCommand { const pos = posIndex + 1; const plotPrefix = `Plot ${linePos} of ${totalLines}`; - if (this.textService.isTerse()) { + if (this.textService.isTerse() || this.textService.isOff()) { const posPercent = totalPos > 1 ? Math.round((posIndex / (totalPos - 1)) * 100) : 0; this.textViewModel.update(`${plotPrefix}, ${posPercent}%`); } else { From 99ed8e20f6e3aed6c1faeb02df00f0b2d3d0cb31 Mon Sep 17 00:00:00 2001 From: ellvix Date: Mon, 16 Feb 2026 21:10:35 -0700 Subject: [PATCH 2/6] chore: added notification if text mode is off --- .claude/settings.local.json | 3 ++- src/command/describe.ts | 5 +++++ src/command/factory.ts | 3 ++- src/command/toggle.ts | 6 +++++- src/controller.ts | 1 + src/state/viewModel/textViewModel.ts | 18 ++++++++++++++++++ 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index bbf2e31d8..e48049aa4 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,8 @@ { "permissions": { "allow": [ - "Bash(npm run build:*)" + "Bash(npm run build:*)", + "Bash(npm test:*)" ] } } diff --git a/src/command/describe.ts b/src/command/describe.ts index 6cff0dd19..6eda0bb24 100644 --- a/src/command/describe.ts +++ b/src/command/describe.ts @@ -274,6 +274,11 @@ export class AnnouncePositionCommand extends DescribeCommand { return; } + // Warn if text mode is off instead of announcing position + if (this.textViewModel.warnIfTextOff()) { + return; + } + // Get position from audio.panning (contains x, y, rows, cols) const { panning } = state.audio; const { x, y, rows, cols } = panning; diff --git a/src/command/factory.ts b/src/command/factory.ts index 266f0c78a..80b0b07e5 100644 --- a/src/command/factory.ts +++ b/src/command/factory.ts @@ -253,10 +253,11 @@ export class CommandFactory { ); case 'ACTIVATE_FIGURE_LABEL_SCOPE': + return new ToggleScopeCommand(this.context, Scope.FIGURE_LABEL, this.textViewModel); case 'DEACTIVATE_FIGURE_LABEL_SCOPE': return new ToggleScopeCommand(this.context, Scope.FIGURE_LABEL); case 'ACTIVATE_TRACE_LABEL_SCOPE': - return new ToggleScopeCommand(this.context, Scope.TRACE_LABEL); + return new ToggleScopeCommand(this.context, Scope.TRACE_LABEL, this.textViewModel); case 'DEACTIVATE_TRACE_LABEL_SCOPE': return new ToggleScopeCommand(this.context, Scope.TRACE); case 'AUTOPLAY_UPWARD': diff --git a/src/command/toggle.ts b/src/command/toggle.ts index 7e3d10d29..35ab40649 100644 --- a/src/command/toggle.ts +++ b/src/command/toggle.ts @@ -293,21 +293,25 @@ export class CommandPaletteSelectCommand implements Command { export class ToggleScopeCommand implements Command { private readonly context: Context; private readonly scope: Scope; + private readonly textViewModel?: TextViewModel; /** * Creates an instance of ToggleScopeCommand. * @param {Context} context - The application context. * @param {Scope} scope - The scope to toggle. + * @param {TextViewModel} [textViewModel] - Optional text view model for text-off warnings. */ - public constructor(context: Context, scope: Scope) { + public constructor(context: Context, scope: Scope, textViewModel?: TextViewModel) { this.context = context; this.scope = scope; + this.textViewModel = textViewModel; } /** * Toggles the specified scope in the context. */ public execute(): void { + this.textViewModel?.warnIfTextOff(); this.context.toggleScope(this.scope); } } diff --git a/src/controller.ts b/src/controller.ts index 5fa5153fc..ae97bbef6 100644 --- a/src/controller.ts +++ b/src/controller.ts @@ -126,6 +126,7 @@ export class Controller implements Disposable { this.textService, this.notificationService, this.autoplayService, + this.audioService, ); this.brailleViewModel = new BrailleViewModel(store, this.brailleService); this.goToExtremaViewModel = new GoToExtremaViewModel( diff --git a/src/state/viewModel/textViewModel.ts b/src/state/viewModel/textViewModel.ts index f853f2e25..8019dc768 100644 --- a/src/state/viewModel/textViewModel.ts +++ b/src/state/viewModel/textViewModel.ts @@ -1,4 +1,5 @@ import type { PayloadAction } from '@reduxjs/toolkit'; +import type { AudioService } from '@service/audio'; import type { AutoplayService } from '@service/autoplay'; import type { NotificationService } from '@service/notification'; import type { TextService } from '@service/text'; @@ -54,6 +55,7 @@ const { update, announceText, toggle, notify, clearMessage, reset } = textSlice. * ViewModel for managing text display, announcements, and notifications. */ export class TextViewModel extends AbstractViewModel { + private readonly audioService: AudioService; private readonly textService: TextService; /** @@ -62,14 +64,17 @@ export class TextViewModel extends AbstractViewModel { * @param text - Service for managing text formatting and updates * @param notification - Service for handling notification messages * @param autoplay - Service for managing autoplay functionality + * @param audio - Audio service for playing warning tones */ public constructor( store: AppStore, text: TextService, notification: NotificationService, autoplay: AutoplayService, + audio: AudioService, ) { super(store); + this.audioService = audio; this.textService = text; this.registerListeners(notification, autoplay); } @@ -155,6 +160,19 @@ export class TextViewModel extends AbstractViewModel { public setAnnounce(enabled: boolean): void { this.store.dispatch(announceText(enabled)); } + + /** + * Warns the user if text mode is off by announcing a message and playing a warning tone. + * @returns True if text mode is off and the warning was issued, false otherwise + */ + public warnIfTextOff(): boolean { + if (!this.textService.isOff()) { + return false; + } + this.notify('Text mode is off. To enable, press the T key.'); + this.audioService.playWarningTone(); + return true; + } } export default textSlice.reducer; From 946a39790b88f5bbda36ee92c7f5b4b168f3d164 Mon Sep 17 00:00:00 2001 From: ellvix Date: Wed, 18 Feb 2026 21:33:18 -0700 Subject: [PATCH 3/6] chore: updated text for scatterplot so it sounds better --- src/command/describe.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/command/describe.ts b/src/command/describe.ts index 6eda0bb24..a765bf48f 100644 --- a/src/command/describe.ts +++ b/src/command/describe.ts @@ -313,6 +313,10 @@ export class AnnouncePositionCommand extends DescribeCommand { // Single line plot: y=position within line, cols=total points this.announce1DPosition(y, cols); } + else if (traceType === TraceType.SCATTER) { + // Scatter plot: use x/y for column/row position, but don't include 'Position' as it sounds weird + this.announceScatter(x, y, rows, cols); + } // Default position announcement else if (this.is2DPlot(rows, cols)) { this.announce2DPosition(x, y, rows, cols); @@ -477,4 +481,22 @@ export class AnnouncePositionCommand extends DescribeCommand { this.textViewModel.update(`${plotPrefix}, Position is ${pos} of ${totalPos}`); } } + /** + * Announces position for 2D plots (e.g., heatmaps). + */ + private announceScatter(x: number, y: number, rows: number, cols: number): void { + const colPos = x + 1; + const rowPos = y + 1; + + if (this.textService.isTerse() || this.textService.isOff()) { + const colPercent = cols > 1 ? Math.round((x / (cols - 1)) * 100) : 0; + const rowPercent = rows > 1 ? Math.round((y / (rows - 1)) * 100) : 0; + this.textViewModel.update(`${colPercent}%, ${rowPercent}%`); + } else { + this.textViewModel.update( + `Column ${colPos} of ${cols}, row ${rowPos} of ${rows}`, + ); + } + } + } From 5d09d525cb3ccf5f3ce4b39a7bf031bd3e53d14a Mon Sep 17 00:00:00 2001 From: ellvix Date: Mon, 23 Feb 2026 20:31:13 -0700 Subject: [PATCH 4/6] chore: change variable names from 'describe' to 'announce' for better accuracy --- src/command/factory.ts | 14 +++++++------- src/service/keybinding.ts | 26 +++++++++++++------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/command/factory.ts b/src/command/factory.ts index 80b0b07e5..d8928defe 100644 --- a/src/command/factory.ts +++ b/src/command/factory.ts @@ -225,13 +225,13 @@ export class CommandFactory { return new CommandPaletteSelectCommand(this.commandPaletteViewModel); case 'COMMAND_PALETTE_CLOSE': return new CommandPaletteCloseCommand(this.commandPaletteViewModel); - case 'DESCRIBE_X': + case 'ANNOUNCE_X': return new DescribeXCommand(this.context, this.textViewModel); - case 'DESCRIBE_Y': + case 'ANNOUNCE_Y': return new DescribeYCommand(this.context, this.textViewModel); - case 'DESCRIBE_FILL': + case 'ANNOUNCE_FILL': return new DescribeFillCommand(this.context, this.textViewModel); - case 'DESCRIBE_POINT': + case 'ANNOUNCE_POINT': return new DescribePointCommand( this.context, this.audioService, @@ -239,11 +239,11 @@ export class CommandFactory { this.brailleViewModel, this.textViewModel, ); - case 'DESCRIBE_TITLE': + case 'ANNOUNCE_TITLE': return new DescribeTitleCommand(this.context, this.textViewModel); - case 'DESCRIBE_SUBTITLE': + case 'ANNOUNCE_SUBTITLE': return new DescribeSubtitleCommand(this.context, this.textViewModel); - case 'DESCRIBE_CAPTION': + case 'ANNOUNCE_CAPTION': return new DescribeCaptionCommand(this.context, this.textViewModel); case 'ANNOUNCE_POSITION': return new AnnouncePositionCommand( diff --git a/src/service/keybinding.ts b/src/service/keybinding.ts index f99df58fa..907c0d7eb 100644 --- a/src/service/keybinding.ts +++ b/src/service/keybinding.ts @@ -55,7 +55,7 @@ const BRAILLE_KEYMAP = { TOGGLE_SETTINGS: `${Platform.ctrl}+,`, // Description - DESCRIBE_POINT: `space`, + ANNOUNCE_POINT: `space`, ANNOUNCE_POSITION: `p`, // rotor functionality @@ -78,9 +78,9 @@ const FIGURE_LABEL_KEYMAP = { DEACTIVATE_FIGURE_LABEL_SCOPE: `escape`, // Description - DESCRIBE_TITLE: `t`, - DESCRIBE_SUBTITLE: `s`, - DESCRIBE_CAPTION: `c`, + ANNOUNCE_TITLE: `t`, + ANNOUNCE_SUBTITLE: `s`, + ANNOUNCE_CAPTION: `c`, // Misc TOGGLE_HELP: `${Platform.ctrl}+/`, @@ -101,8 +101,8 @@ const SUBPLOT_KEYMAP = { ACTIVATE_FIGURE_LABEL_SCOPE: `l`, // Description - DESCRIBE_TITLE: `t`, - DESCRIBE_POINT: `space`, + ANNOUNCE_TITLE: `t`, + ANNOUNCE_POINT: `space`, ANNOUNCE_POSITION: `p`, // Navigation @@ -133,12 +133,12 @@ const TRACE_LABEL_KEYMAP = { DEACTIVATE_TRACE_LABEL_SCOPE: `escape`, // Description - DESCRIBE_X: `x`, - DESCRIBE_Y: `y`, - DESCRIBE_FILL: `f`, - DESCRIBE_TITLE: `t`, - DESCRIBE_SUBTITLE: `s`, - DESCRIBE_CAPTION: `c`, + ANNOUNCE_X: `x`, + ANNOUNCE_Y: `y`, + ANNOUNCE_FILL: `f`, + ANNOUNCE_TITLE: `t`, + ANNOUNCE_SUBTITLE: `s`, + ANNOUNCE_CAPTION: `c`, // Misc TOGGLE_HELP: `${Platform.ctrl}+/`, @@ -214,7 +214,7 @@ const TRACE_KEYMAP = { TOGGLE_SETTINGS: `${Platform.ctrl}+,`, // Description - DESCRIBE_POINT: `space`, + ANNOUNCE_POINT: `space`, ANNOUNCE_POSITION: `p`, // Go To functionality From 7b62560087596b6d6f61b8963651d17f1d8d07e2 Mon Sep 17 00:00:00 2001 From: ellvix Date: Wed, 11 Mar 2026 21:18:48 -0600 Subject: [PATCH 5/6] chore: updated describe name to announce --- src/command/describe.ts | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/command/describe.ts b/src/command/describe.ts index 4638f6c06..739dcf163 100644 --- a/src/command/describe.ts +++ b/src/command/describe.ts @@ -12,14 +12,14 @@ import { Scope } from '@type/event'; /** * Abstract base class for describe commands. */ -abstract class DescribeCommand implements Command { +abstract class AnnounceCommand implements Command { protected readonly context: Context; protected readonly textViewModel: TextViewModel; protected readonly audioService: AudioService; protected readonly textService: TextService; /** - * Creates an instance of DescribeCommand. + * Creates an instance of AnnounceCommand. * @param {Context} context - The application context. * @param {TextViewModel} textViewModel - The text view model. * @param {AudioService} audioService - The audio service. @@ -54,9 +54,9 @@ abstract class DescribeCommand implements Command { /** * Command to describe the X-axis label. */ -export class DescribeXCommand extends DescribeCommand { +export class AnnounceXCommand extends AnnounceCommand { /** - * Creates an instance of DescribeXCommand. + * Creates an instance of AnnounceXCommand. * @param {Context} context - The application context. * @param {TextViewModel} textViewModel - The text view model. * @param {AudioService} audioService - The audio service. @@ -90,9 +90,9 @@ export class DescribeXCommand extends DescribeCommand { /** * Command to describe the Y-axis label. */ -export class DescribeYCommand extends DescribeCommand { +export class AnnounceYCommand extends AnnounceCommand { /** - * Creates an instance of DescribeYCommand. + * Creates an instance of AnnounceYCommand. * @param {Context} context - The application context. * @param {TextViewModel} textViewModel - The text view model. * @param {AudioService} audioService - The audio service. @@ -126,9 +126,9 @@ export class DescribeYCommand extends DescribeCommand { /** * Command to describe the fill property. */ -export class DescribeFillCommand extends DescribeCommand { +export class AnnounceFillCommand extends AnnounceCommand { /** - * Creates an instance of DescribeFillCommand. + * Creates an instance of AnnounceFillCommand. * @param {Context} context - The application context. * @param {TextViewModel} textViewModel - The text view model. * @param {AudioService} audioService - The audio service. @@ -162,9 +162,9 @@ export class DescribeFillCommand extends DescribeCommand { /** * Command to describe the title of the figure or subplot. */ -export class DescribeTitleCommand extends DescribeCommand { +export class AnnounceTitleCommand extends AnnounceCommand { /** - * Creates an instance of DescribeTitleCommand. + * Creates an instance of AnnounceTitleCommand. * @param {Context} context - The application context. * @param {TextViewModel} textViewModel - The text view model. * @param {AudioService} audioService - The audio service. @@ -253,9 +253,9 @@ export class DescribeTitleCommand extends DescribeCommand { /** * Command to describe the subtitle of the figure. */ -export class DescribeSubtitleCommand extends DescribeCommand { +export class AnnounceSubtitleCommand extends AnnounceCommand { /** - * Creates an instance of DescribeSubtitleCommand. + * Creates an instance of AnnounceSubtitleCommand. * @param {Context} context - The application context. * @param {TextViewModel} textViewModel - The text view model. * @param {AudioService} audioService - The audio service. @@ -291,9 +291,9 @@ export class DescribeSubtitleCommand extends DescribeCommand { /** * Command to describe the caption of the figure. */ -export class DescribeCaptionCommand extends DescribeCommand { +export class AnnounceCaptionCommand extends AnnounceCommand { /** - * Creates an instance of DescribeCaptionCommand. + * Creates an instance of AnnounceCaptionCommand. * @param {Context} context - The application context. * @param {TextViewModel} textViewModel - The text view model. * @param {AudioService} audioService - The audio service. @@ -329,13 +329,13 @@ export class DescribeCaptionCommand extends DescribeCommand { /** * Command to describe the current point with audio, braille, and highlight. */ -export class DescribePointCommand extends DescribeCommand { +export class AnnouncePointCommand extends AnnounceCommand { private readonly audio: AudioService; private readonly brailleViewModel: BrailleViewModel; private readonly highlight: HighlightService; /** - * Creates an instance of DescribePointCommand. + * Creates an instance of AnnouncePointCommand. * @param {Context} context - The application context. * @param {AudioService} audioService - The audio service. * @param {HighlightService} highlightService - The highlight service. @@ -382,7 +382,7 @@ export class DescribePointCommand extends DescribeCommand { * Command to announce the current position in the chart. * Formats output based on text mode (terse/verbose) and chart type. */ -export class AnnouncePositionCommand extends DescribeCommand { +export class AnnouncePositionCommand extends AnnounceCommand { /** * Creates an instance of AnnouncePositionCommand. * @param {Context} context - The application context. From 44262ce2e69add3090d5ad27880e31a064de18ee Mon Sep 17 00:00:00 2001 From: ellvix Date: Wed, 11 Mar 2026 21:34:51 -0600 Subject: [PATCH 6/6] chore: fixed position data on lineplots --- src/command/describe.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/command/describe.ts b/src/command/describe.ts index 739dcf163..6763c2546 100644 --- a/src/command/describe.ts +++ b/src/command/describe.ts @@ -445,11 +445,8 @@ export class AnnouncePositionCommand extends AnnounceCommand { } // Check for multi plots (multiline, panel, layer, facet) else if (traceType === TraceType.LINE && state.groupCount && state.groupCount > 1) { - // Multi-line plots: x=line index, y=position within line - this.announceMultiLinePosition(x, rows, y, cols); - } else if (traceType === TraceType.LINE) { - // Single line plot: y=position within line, cols=total points - this.announce1DPosition(y, cols); + // Multi-line plots: x=position in the line, y=line index + this.announceMultiLinePosition(x, cols, y, rows); } else if (traceType === TraceType.SCATTER) { // Scatter plot: use x/y for column/row position, but don't include 'Position' as it sounds weird @@ -603,10 +600,10 @@ export class AnnouncePositionCommand extends AnnounceCommand { * Always shows "Plot X of Y" prefix, followed by position within the line. */ private announceMultiLinePosition( - lineIndex: number, - totalLines: number, posIndex: number, totalPos: number, + lineIndex: number, + totalLines: number, ): void { const linePos = lineIndex + 1; const pos = posIndex + 1;