Conversation
Text with wrap=True at an axis-aligned rotation on the matching figure edge collapsed to zero wrap width. In _get_dist_to_box the trig formula divides by cos(radians(90)) (~6e-17): normally that term's large value loses the min() to the sensible other side, but when the position sits exactly on the edge its numerator is also zero and the returned distance is zero. Short-circuit cardinal rotations. Also normalize rotation to [0, 360) with a small absolute tolerance so near-cardinal angles coming out of transform_angles (e.g. -90.00000000000003 via transform_rotates_text) still take the fast path instead of blowing up in the trig formula. Regression tests cover each cardinal rotation on its broken edge plus a transform_rotates_text case.
65d03b7 to
a8560a9
Compare
| rotation = rotation % 360 | ||
| # Short-circuit cardinal angles, otherwise cos(radians(90)) makes | ||
| # the trig formula below blow up when the text is on the edge. | ||
| tol = 1e-10 |
There was a problem hiding this comment.
Why choose a / exactly this tolerance?
There was a problem hiding this comment.
Picked it a few orders of magnitude above the float noise I saw, but with the existing % 360 in get_rotation, the tolerance isn't actually needed. Removing it.
|
All the added tests pass with the simpler patch that I put in the issue. Do you have examples that demonstrate the need for this more complicated approach? |
| """ | ||
| # Normalize rotation; transform_angles can return values outside | ||
| # [0, 360) with tiny float noise. | ||
| rotation = rotation % 360 |
There was a problem hiding this comment.
AFAICS this is already done within set_rotation or get_rotation.
There was a problem hiding this comment.
Yep, missed that. Dropping the % 360 line.
You're right, all the tests pass with your original patch. Since get_rotation already does % 360, the transform_angles noise is absorbed before it reaches here. Will simplify. |
transform_angles can return a tiny-negative value, and in Python (tiny_negative) % 360 float-rounds to exactly 360.0, breaking the [0, 360) range that the docstring promises. Clamp 360 back to 0 so callers can rely on the documented range. With that in place, the normalize + tolerance wrapper added to _get_dist_to_box in the previous commit is no longer needed. Reduce it to plain cardinal-angle short-circuits, per rcomer's suggestion in matplotlib#31537. Rework the transform_rotates_text regression test to compare wrapped output against a plain rotation=0 reference at the same position, instead of the weaker "contains a space" assertion.
Text placed exactly on a figure edge with an axis-aligned rotation and wrap=True collapses to zero wrap width, producing one word per line. Minimal reproducer from the issue:
Root cause is in
Text._get_dist_to_box: the trig formula divides bycos(radians(90))(~6e-17). Away from the edge the degenerate term evaluates to a huge number (non-zero distance over a tiny denominator) and losesmin()to the sensible other side. On the edge the numerator is zero too, so the result collapses to zero.The fix short-circuits the four cardinal rotations and normalizes rotation to
[0, 360)with a small tolerance, so that near-cardinal angles coming out oftransform_angles(e.g.-90.00000000000003whentransform_rotates_text=True) also take the fast path. Direction follows @rcomer's suggestion in the issue thread.closes #31537
PR checklist
test_wrap_on_figure_edge,test_wrap_on_figure_edge_transform_rotates_textinlib/matplotlib/tests/test_text.py)