Skip to content

Orthogonal edges jump around when zooming #1051

Description

@olivergeorge

Describe the bug

When zooming in or out, orthogonal edges with user-authored waypoints jump around — flickering between the correct route and a diagonal depending on the exact zoom level.

To Reproduce

  1. Insert a source vertex at (870, 430) size (310, 70) — no exitX/exitY (floating port).
  2. Insert a target vertex at (450, 250) size (410, 80) — no entryX/entryY.
  3. Insert an edge between them with waypoints [(1025, 390), (655, 390)].
  4. graph.getView().setScale(0.75); graph.getView().revalidate();
  5. Read state.absolutePoints for the edge. (we expect 4 non-null points (S-route) but see 2 points (diagonal).

Also reproduces at scale = 0.8. Routes correctly at scale ∈ {0.5, 1.0, 1.5, 2.0}.

segment-bug-repro.html

Expected behavior

The route should not change shape as view.scale changes. Zoom should not alter the logical geometry.

Screenshots

Image

Environment

  • maxGraph version or commit: @maxgraph/core@0.22.0 and current main.
  • Desktop or mobile: Desktop.
  • OS and version: macOS 26.3.1.
  • Browser and version: Chrome 147.
  • Node/npm version: Node 20.19.5, npm 10.8.2.
  • Used frameworks: N/A.

Additional context

Analysis. Looking at Segment.ts, the "remove bends inside source/target terminal" cleanup (L281-299) compares result points (VIEW coords after pushPoint scales them at L65-66) against source / target (MODEL coords after scaleCellState divides by scale at L54-55). At fractional scale the spaces diverge, and a legitimate VIEW-space waypoint can land inside the MODEL-space terminal bounds — the cleanup then splices it out and the route collapses. Only seems to fire on edges with floating ports.

A patch that fixes it in my testing — comparing against the sourceScaled / targetScaled function parameters (VIEW coords, same space as result):

-  if (pts[0] == null && source != null) {
+  if (pts[0] == null && sourceScaled != null) {
     while (
       result.length > 1 &&
       result[1] != null &&
-      contains(source, result[1].x, result[1].y)
+      contains(sourceScaled, result[1].x, result[1].y)
     ) { result.splice(1, 1); }
   }
-  if (pts[lastInx] == null && target != null) {
+  if (pts[lastInx] == null && targetScaled != null) {
     while (
       result.length > 1 &&
       result[result.length - 1] != null &&
-      contains(target, result[result.length - 1].x, result[result.length - 1].y)
+      contains(targetScaled, result[result.length - 1].x, result[result.length - 1].y)
     ) { result.splice(result.length - 1, 1); }
   }

Drawio comparison. The identical code is in jgraph/drawio's internal mxGraph fork, unpatched — but drawio quantises every zoom path to 1% increments or coarser with a source comment citing "rounding errors for zoom steps" as the motivation. @maxgraph/core has no equivalent app-layer mitigation, so the bug is reachable through the default zoomIn button alone (which walks through 1.44, 1.73, 2.08, … at zoomFactor = 1.2).

Scope. OrthConnector and ManhattanConnector both delegate to SegmentConnector, so this affects segmentEdgeStyle, orthogonalEdgeStyle, and manhattanEdgeStyle.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions