MAIDR Documentation - v4.4.0
    Preparing search index...

    Class VolcanoTrace

    Trace implementation for volcano and Manhattan plots.

    A volcano puts effect size against significance; a Manhattan puts genomic position against it. Both are scatters, so the navigation, the pitch and the braille all transfer -- and both are read almost entirely through a threshold, which is what a scatter has no notion of.

    Point-by-point navigation is not a viable path here. These charts routinely carry tens of thousands of points of which a few dozen matter. A reader who has to walk them has not been given access to the chart; they have been given a very long list. So two things carry the weight:

    The summary on entry. "43 of 12,000 points above the significance threshold" is the first thing a sighted reader takes from the shape of the cloud, and the last thing a per-point reading would ever assemble.

    A rotor filter over the points that clear it, so the few dozen that matter are reachable in a few dozen keystrokes rather than twelve thousand.

    Identity is the payload. A reader told "x is 2.3, y is 14.1" has been given the two numbers the axes already describe and withheld the one thing they came for, which is which gene that is. The label travels as an aside, so it survives every one of the scatter's navigation modes and is never run through an axis formatter -- a gene name is not a value on an axis.

    The threshold is declared, never guessed. These charts sit on transformed axes whose conventions differ by field and by tool: -log10(p) at 1.3 for p < 0.05, at 7.3 for genome-wide significance, and a raw p axis runs the other way entirely. A guessed line would sort every point on the figure onto the wrong side of it, silently. A layer that declares none gets the scatter's reading and no claim about significance.

    Hierarchy (View Summary)

    Index

    Constructors

    Properties

    Accessors

    Methods

    Constructors

    Properties

    observers: Observer<TraceState>[]
    isWarning: boolean
    isComputingStateAt: boolean

    True while AbstractTrace.getStateAt computes state at a temporarily moved cursor. Enforces (structurally, not just by documentation) that state getters never notify observers.

    id: string
    type: TraceType
    title: string
    name: string | undefined

    What this layer is, when the producer named it. See MaidrLayer.name.

    xAxis: string
    yAxis: string
    z: string
    navigationService: NavigationService
    layer: MaidrLayer
    movable: MovablePlane
    supportsExtrema: false

    Abstract property that subclasses must implement to indicate extrema support

    highlightCenters:
        | {
            x: number;
            y: number;
            row: number;
            col: number;
            element: SVGElement;
        }[]
        | null
    flatPoints: FlatPoint[]

    Protected so a subclass can read the points themselves. A volcano plot needs each point's identity and whether it clears a threshold, neither of which is a coordinate.

    readingOrder: number[]
    readingPos: number[]

    Protected alongside readingOrder: it is the precomputed inverse of it, and a subclass filtering the reading order needs O(1) position lookups rather than a linear scan per keystroke.

    isInPointMode: boolean
    pointModeIndex: number

    Accessors

    • get isInitialEntry(): boolean

      Returns boolean

    • set isInitialEntry(value: boolean): void

      Parameters

      • value: boolean

      Returns void

    • get row(): number

      Returns number

    • set row(value: number): void

      Parameters

      • value: number

      Returns void

    • get col(): number

      Returns number

    • set col(value: number): void

      Parameters

      • value: number

      Returns void

    • get isEmptyAtCursor(): boolean

      Whether the cursor has no point to report.

      Read from dimension rather than from any one trace's data, because that is the one shape every trace already answers in. It is also evaluated at the cursorLineTrace returns the current series' length as cols — so this covers a ragged layer, where one series has points and another has none, and not only a layer that is empty throughout.

      Returns boolean

      True when there is nothing at (row, col) to describe

    • get state(): TraceState

      Gets the current state of the trace including audio, braille, text, and highlight information.

      Returns TraceState

      The current TraceState

    • get traceType(): TraceType

      The trace's chart type. Lightweight alternative to reading state.traceType, which eagerly computes the full audio/braille/text/ highlight state just to expose this one string.

      Returns TraceType

    • get highlightValues(): SVGElement[][] | null

      Returns the appropriate highlight elements based on current navigation mode.

      Returns SVGElement[][] | null

      SVG elements for X-based or Y-based highlighting depending on mode

    • get values(): number[][]

      Returns number[][]

    • get highlightedPointIndices(): readonly number[]

      The points the highlight currently covers, as indices into this layer's data array.

      This is highlight answered in a renderer-neutral currency. highlight resolves the same five navigation modes into SVGElements, which a canvas chart has none of — so a canvas adapter is handed back an index into the data array it supplied, and inverts it against its own extraction walk. That keeps the binning here, in the model that owns it: the adapter never learns what a grid cell or an x-bucket is.

      Deliberately not gated on SVG availability, where highlight falls back to out-of-bounds when the binder supplied no elements. A canvas chart has no elements by definition, and that is exactly the case this exists to serve.

      Returns an empty array when nothing is addressed, which a consumer reads as "clear the overlay".

      Returns readonly number[]

      Indices into layer.data, in no particular order.

    • get hasMultiPoints(): boolean

      Returns boolean

    • get outOfBoundsState(): TraceEmptyState

      Returns out-of-bounds state with the position the active navigation mode is actually on, so the boundary chime pans from where the user is.

      Grid, point and intersection mode each keep their own cursor; the base implementation pans by row/col, which in those modes is the stale cursor from before the mode was entered.

      Returns TraceEmptyState

    Methods

    • Gets safe row and column indices to prevent accessing undefined values

      Returns { row: number; col: number }

      Object with safe row and column indices

    • Base implementation of navigation in HIGHER and LOWER modes of ROTOR, default is no-op Needs to be implemented in Line, Bar, Heatmap, Candlestick

      Parameters

      • _direction: "left" | "right" | "up" | "down"
      • _type: "lower" | "higher"

      Returns boolean

    • Parameters

      • a: number

        Utility function to compare point values for rotor functionality

      • b: number
      • type: "lower" | "higher"

      Returns boolean

      boolean value

    • Moves up in rotor mode, optionally filtering by lower or higher values.

      Parameters

      • Optional_mode: "lower" | "higher"

        Optional mode for filtering (lower or higher)

      Returns boolean

      Error always - subclasses must override this method

    • Moves down in rotor mode, optionally filtering by lower or higher values.

      Parameters

      • Optional_mode: "lower" | "higher"

        Optional mode for filtering (lower or higher)

      Returns boolean

      Error always - subclasses must override this method

    • Moves left in rotor mode, optionally filtering by lower or higher values.

      Parameters

      • Optional_mode: "lower" | "higher"

        Optional mode for filtering (lower or higher)

      Returns boolean

      Error always - subclasses must override this method

    • Moves right in rotor mode, optionally filtering by lower or higher values.

      Parameters

      • Optional_mode: "lower" | "higher"

        Optional mode for filtering (lower or higher)

      Returns boolean

      Error always - subclasses must override this method

    • Get all highlight SVG elements for this trace Used by HighlightService for high contrast mode

      Returns SVGElement[]

      Array of all SVG elements, or empty array if none

    • Get all original (visible) SVG elements for this trace. These are the actual rendered elements, not the hidden clones used for highlighting. Used by HighlightService for high contrast mode color changes.

      Returns SVGElement[]

      Array of all original SVG elements, or empty array if none

    • Returns a human-readable label for this trace's chart type (e.g., 'Bar Chart', 'Scatter Plot') for display in the description modal. Falls back to the raw layer type if no mapping is registered.

      Returns string

    • Builds the axes object for the description state, including z only when the layer explicitly provides a z-axis label. Subclasses should call this instead of constructing the axes object inline so charts without a real z dimension don't surface the placeholder default.

      Returns { x?: string; y?: string; z?: string }

    • Moves the active point to the pointer location and returns directional guidance toward the nearest data geometry in a single call.

      Combining both operations avoids running findNearestPoint twice per pointermove event — important on dense plots where the scan is the hot path.

      Parameters

      • x: number

        Screen-space x position of the pointer/finger

      • y: number

        Screen-space y position of the pointer/finger

      Returns PointerGuidanceState | null

      Guidance state relative to nearest point, or null when unavailable

    • Checks if the specified coordinates are within bounds of the element.

      Parameters

      • x: number

        The x-coordinate

      • y: number

        The y-coordinate

      • element: NearestPoint

        Object containing the SVG element and its position

        • element: SVGElement
        • row: number
        • col: number
        • centerX: number
        • centerY: number

      Returns boolean

      True if the point is in bounds, false otherwise

    • Returns an empty object to avoid grouping scatter points by audio tone.

      Returns { groupIndex?: number }

      Empty object without groupIndex to maintain consistent audio feedback

    • Returns true if this trace supports compare (lower/higher value) navigation. Override to false for trace types that don't use compare modes (e.g., scatter, which is all we currently have).

      Returns boolean

    • Returns the display name for the default data navigation mode. Override to provide a trace-specific name (e.g., "ROW AND COLUMN NAVIGATION" for scatter).

      Returns string

    • Enters grid cell mode to navigate points within the current cell. Groups cell points by X coordinate (like COL mode) for navigation.

      Returns boolean

      true if entered successfully, false if no points in cell

    • Mirrors LineTrace's intersection rule: the mode is offered only when navigating across the data is meaningful. For scatter, that means a stack exists on either axis — either an x with multiple ys (column stack) or a y with multiple xs (row stack). Either base mode (COL / ROW) can enter the rotor and cycle through its corresponding stack, so the capability check is the OR of both.

      Returns boolean

    • Called by the rotor service when entering / leaving INTERSECTION_MODE. Preserve the active NavMode: entering from COL cycles through the y-stack at the current x; entering from ROW cycles through the x-stack at the current y. Force-switching modes here would silently re-anchor the user on a different point set — the bug fixed by this commit.

      No notifyStateUpdate(): mirrors setPointMode and setGridMode; the rotor announces the mode name and the next arrow press emits the focused state.

      Parameters

      • enabled: boolean

      Returns void

    • Walks one step forward through the current stack — y values at the current x in COL mode, x values at the current y in ROW mode. Returns false at the top of the stack so the rotor can announce the boundary; no chime (consistent with LineTrace's intersection bounds).

      Returns boolean

    • Returns true if this trace supports point-by-point navigation mode. Opt-in per trace type: override to return true for traces that expose individual data points navigable in reading order (left/right) and column-major order (up/down). Currently only ScatterTrace.

      Returns boolean

    • Enters or exits point-by-point navigation mode.

      On entry, the current point is seeded from whatever ROW_COL position the user was on so they keep their place in the data. When the previous mode highlighted a group of points (e.g. a column of points sharing an x in COL mode), we pick the first point in reading order from that group — highest y for COL mode, lowest x for ROW mode.

      On exit, no state needs to be unwound; the existing row/col indices are untouched while in point mode, so ROW_COL navigation resumes where it left off.

      Parameters

      • enabled: boolean

      Returns void

    • Converts SVG elements to center coordinates for proximity-based navigation.

      Returns { x: number; y: number; row: number; col: number; element: SVGElement }[] | null

      Array of center points with coordinates and indices, or null if unavailable

    • Finds the nearest scatter point to the given screen coordinates.

      Parameters

      • _x: number

        The x-coordinate in screen space

      • _y: number

        The y-coordinate in screen space

      Returns NearestPoint | null

      The nearest point with its element and indices, or null if unavailable

    • Reads state at an explicit cursor, with the trace-local navigation modes suspended for the duration.

      getStateAt moves row/col and reads the state getters, but point and intersection mode short-circuit those getters onto their own cursor — so a live-appended point would be announced as whichever point the user happens to be focused on. Suspending the flags makes the read positional again, which is what every caller of this method asks for.

      Parameters

      • row: number

        Row index to read at

      • col: number

        Column index to read at

      Returns TraceState

      The trace state at that position

    • Moves the trace to the nearest point when the pointer is within its bounds and the trace is not already focused on that point.

      onCurve is intentionally non-optional: the caller has already paid the cost of isPointInBounds to assemble guidance state, and forcing subclasses to accept the value makes the contract explicit so a future override cannot silently recompute (or worse, ignore) it.

      Subclasses override this to customise hover-driven navigation:

      • Box / ViolinBox no-op the move while still surfacing guidance.
      • Scatter switches into column navigation mode before delegating.

      Parameters

      Returns void

    • Offers the significant points when the chart declares a threshold.

      Withheld when no threshold is declared -- there is nothing to filter on -- and withheld when one is declared but nothing clears it, since a mode whose only possible answer is "none found" is worse than not offering it. That second case is a real reading of the chart, and the description says so in words.

      Returns readonly RotorFilterUnit[]

      The significant unit alongside whatever the scatter offers

    • Moves within an active rotor filter unit along the filtered axis.

      Called by RotorNavigationService when the current rotor mode is one of this trace's getRotorFilterUnits. Filter units navigate a single axis, so only left/right are dispatched here — the service announces up/down as unavailable without calling the model (matching intersection mode). Implementations should move to the nearest point matching the unit identified by key and notify observers, returning true; when no such point exists they should call notifyRotorBounds and return false.

      Default is a no-op that reports bounds, so a trace advertising a filter unit but forgetting to implement movement fails safe (announces "no point found") rather than moving unexpectedly.

      Parameters

      • key: string
      • direction: "left" | "right"

      Returns boolean

      True if the cursor moved, false otherwise