Skip to content

Incremental updates to the SVG renderer #47

Description

@lucasfelixs

Context

The SVG renderer repaints everything on every change. In packages/svg/src/renderer.ts, paint() calls clearChildren(objectsG) and then drawEntity for every entity:

  • on every entities:changed, even when only one entity was updated;
  • on every selection:change, even when only two entities changed state.

During a drag, core.update runs on every pointer move, so the whole scene is rebuilt about 60 times per second. That is fine for small scenes, but it gets slow with hundreds or thousands of entities (a stadium seat map, a large warehouse).


What changes

Keep one <g> per entity and update only what changed. The entities:changed event already carries what's needed: { type: 'add' | 'update' | 'remove', ids }.

  • Keep a Map<EntityId, SVGGElement> in the renderer.
  • add → create the entity's <g>, call drawEntity into it, and insert it in its layer group at the right position (insertion order = z-order).
  • update → clear only that entity's <g> and call drawEntity again. If the layer changed, move the <g> to the new layer group.
  • remove → remove the <g> and the map entry.
  • selection:change → redraw only the entities whose selected state changed (diff the previous and current selection).
  • rerender() keeps doing a full repaint, as an escape hatch.

Selection overlay and resize handles can stay as they are in a first PR; they only draw selected entities.


Affected files

File Change
packages/svg/src/renderer.ts Incremental painting by event type
packages/svg/src/__tests__/ Tests for each event type and for z-order

The React adapter already renders with keyed elements (<g key={entity.id}>), so React's reconciliation covers part of this. Profiling it is a possible follow-up issue, not part of this one.


Invariants to preserve

  • Rendered output (DOM order, layer groups, data-spatial-layer attributes) is identical to a full repaint after any sequence of events.
  • Z-order: layer order first, then insertion order inside each layer.
  • drawEntity keeps the same signature and receives the same { g, selected } context.
  • rerender() and destroy() keep working.

Implementation checklist

  • Entity → <g> map and incremental add / update / remove
  • Selection diff: redraw only entities whose state changed
  • Tests: each event type, a layer change, z-order after add/remove, and "incremental output equals full repaint"
  • A spy test showing drawEntity is called only for the changed entity on update
  • Optional: a quick benchmark note in the PR (e.g. 2,000 entities, drag one)
  • Changeset (patch or minor for svg)
  • Run pnpm typecheck && pnpm test && pnpm lint
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions