Skip to content

[Perf] Pointer moves: screen-CTM read and interaction-point rebuild on every move, and no early-input option #203

Open

Description

@toomingos

Hover cost and hover latency both come from the same move handler.

A. Work per move, even when focus does not change

Measured in Chrome (radar, 3 polygons, custom focus.resolve, 300 moves inside one polygon, 3 runs): 0.13-0.18 ms per move in the handler. The strategy itself is 0.004-0.011 ms. The same chart drawn with per-polygon mouseenter costs 0.035-0.05 ms.

B. No way to use earlier pointer input

Chrome aligns pointermove to the frame. On a line chart at 1440 px, about 8 ms of the 9.7 ms median from input to the next painted frame is this wait. pointerrawupdate arrives 0.5-2 ms after the input, but the renderer only listens to pointermove. Driving setControlledFocus from it makes the owner 'controlled', so pointer leave no longer clears focus (see the comment on #189).

Proposal

  • A: cache the inverse CTM until a resize or scene update, and let a strategy say the pointer is still inside the current focus (for example contains(x, y)) so the controller skips the rebuild.
  • B: an option such as pointerEvent: 'pointermove' | 'pointerrawupdate' (falling back where unsupported), or a public method that applies a pointer sample with the same leave and tooltip handling as the move handler.

Related: #190 (what a strategy can see). Checked against 57774e14a1a86081eddb3a23523724a40da05640 (v1.1.0).

Activity

  1. changed the title [-]Pointer moves: every move re-reads the screen CTM and rebuilds the interaction points, and focus waits for the aligned `pointermove`[/-] [+][Perf] Pointer moves: screen-CTM read and interaction-point rebuild on every move, and no early-input option[/+] on Oct 13, 2026
  2. tannerlinsley commented on Oct 14, 2026

    @tannerlinsley
    Member

    Thanks @toomingos, the viewport-filter part is merged in #225. It skips mark ownership lookup for points already inside the viewport and avoids reading unrelated channel values for offscreen points.

    In seven paired Chromium trials, 200 filters of 1,000 points improved from 17.1 to 1.4 ms for visible points and 16.8 to 6.3 ms for offscreen points. This measures filtering only, not total pointer latency. The fix passes 2,237 tests, maintained example builds, and a 432-case ownership matrix in Chromium, Firefox and WebKit. The measured bundle costs were approved, with maximum increases of 0.009 kB gzip and 0.117 kB Brotli across the entry-point audit.

    Keeping this open for the screen-CTM work and early-input option. The patch Changeset is merged, but it has not been published to npm yet.

  3. tannerlinsley commented on Oct 14, 2026

    @tannerlinsley
    Member

    I checked the proposed resize/scene-update cache in Chromium, Firefox and WebKit. It would leave stale pointer coordinates in all three:

    • Translating an ancestor by (40, 30) changes a client (100, 100) sample to chart (60, 70).
    • Adding a 75 px sibling above the chart changes it to (100, 25).
    • Scrolling the page by 50 px changes it to (100, 150).

    The SVG's width and height stay unchanged in each case, and no chart scene update is needed. A cache that only watches chart size and scene updates keeps returning (100, 100).

    So the remaining coordinate optimization needs to handle ancestor layout, transforms and scrolling too. I'm keeping the fresh matrix read for now. This does not affect the viewport-filter improvement already merged in #225.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions