Skip to content

Fix ConnectionOffsetMode.Rectangle producing NaN offsets for horizontal connections - #286

Open
marko1olo wants to merge 1 commit into
miroiu:masterfrom
marko1olo:fix/rectangle-offset-mode-nan
Open

marko1olo wants to merge 1 commit into
miroiu:masterfrom
marko1olo:fix/rectangle-offset-mode-nan

Conversation

@marko1olo

Copy link
Copy Markdown
Contributor

Closes #284.

Selecting ConnectionOffsetMode.Rectangle without also changing the offset made horizontal connections disappear: the offset vector came out NaN, so the whole geometry was non-finite and nothing was drawn. No exception, no warning.

The face-selection test in GetRectangleModeOffset decides which side of the offset rectangle the connection leaves through:

if (offset.Width * 2d * Math.Abs(delta.Y) < offset.Height * 2d * Math.Abs(delta.X))

The default offset is new Size(14, 0) (BoxValue.ConnectionOffset), so Height is zero. For a horizontal connection both sides of that comparison are zero — 14 * 2 * 0 against 0 * 2 * 1 — and < is false, so it took the else branch, which is the one that needs a vertical component to scale against:

  • result.Y = Math.Sign(0) * 0 → 0
  • angle = Math.Atan2(0, 1) → 0, so 1.0 / Math.Tan(0) → Infinity
  • Infinity * 0 → NaN

The mirrored direction was wrong too, but quietly: for a right-to-left connection angle is Math.PI and Math.Tan(Math.PI) is -1.2246e-16 rather than exactly zero, so the same expression yields -0 — finite, but no offset applied at all. So of the two horizontal directions one produced NaN and one produced nothing, and which you got depended on floating-point rounding.

The guard returns what the if branch would have produced for that case: offset.Width along X, in the direction of travel.

On the shape of the fix

The underlying cause is arguably the comparison being strict, so a degenerate zero-height offset lands in the wrong branch. Making it <= reaches the same answer and is a smaller diff. I kept the explicit guard because it is provably unchanged everywhere except where the result was NaN or -0, whereas <= alters branch selection for any input where the two products are exactly equal — a set I have not characterised. Happy to switch to <= if you prefer it; the behaviour I measured is identical on the cases I tested.

Proof

The repo has no test project, so this is a console app that references Nodify.csproj, drives the geometry and reads the result two independent ways — the offset vector and every control point of the produced Geometry, checking each with double.IsFinite. One accessor cannot fake success.

Before:

[FAIL] Rectangle, horizontal delta, DEFAULT offset
         way1 offset vector = (NaN, 0)
         way2 geometry      = 9 pts, 4 non-finite, bounds=Empty
[FAIL] Rectangle, zero delta (source==target), DEFAULT offset
         way1 offset vector = (NaN, 0)
[ok  ] Rectangle, horizontal delta (negative), DEFAULT offset
         way1 offset vector = (-0, 0)

After, all finite, and the diagonal and vertical controls byte-identical to before.

dotnet build Nodify.sln → Ошибок: 0, and no CS warnings under Nodify/. Changelog entry added.

Possible drawbacks

This is a deliberate behaviour change on a case that did not crash. A right-to-left horizontal connection previously got a silently-dropped -0 offset and now gets a real -offset.Width, which is a visible 14px shift in that configuration. Anyone who had settled on Rectangle plus a zero-height offset and was relying on the resulting no-op would see connections move. I think that is the right trade, since the other direction was drawing nothing at all, but it is a change and not purely a fix.

Cases where delta.Y != 0 are byte-identical before and after — verified by diffing the harness output rather than by inspection.

Only net10.0-windows was executed at runtime; the other seven target frameworks are compile-verified through the solution build. net472/net48 are pinned to LangVersion 8.0 and the added ternary is C# 7 syntax, so it compiles there, but no runtime check ran on them. And the geometry is proven numerically — I have not confirmed how it looks in a rendered window.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ConnectionOffsetMode.Rectangle computes a NaN offset for horizontal connections with the default offset

1 participant