Skip to content

Line2D can snap incorrectly and request snap(x)(y) parameters #19054

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
alexiscoutinho opened this issue Dec 1, 2020 · 6 comments
Closed

Line2D can snap incorrectly and request snap(x)(y) parameters #19054

alexiscoutinho opened this issue Dec 1, 2020 · 6 comments
Labels
status: needs comment/discussion needs consensus on next step

Comments

@alexiscoutinho
Copy link
Contributor

alexiscoutinho commented Dec 1, 2020

Bug report

Bug summary

Axes.plot produces thin lines (1, 3, ... px) which may snap incorrectly relative to Rectangles depending on the dpi and Axes position, in other words, the width of the left and bottom margins. In such cases, non-snapped lines achieve the desired outcome.

Code for reproduction

from matplotlib.pyplot import figure
from matplotlib.patches import Rectangle

fig = figure(figsize=(10,10), dpi=155)
ax = fig.add_axes([0.09, 0.1, 0.8, 0.86])
ax.axis([0, 1240, 0, 1333])
ax.minorticks_on()
ax.grid(which='both', ls=':')

ax.add_patch(Rectangle((700,800), 200, 3, snap=True))
ax.add_patch(Rectangle((800,700), 3, 200, snap=True))
lw = 1 * 72 / 155
ax.plot([890,801.5,801.5], [801.5,801.5,890], c='lime', lw=lw, snap=True)
ax.plot([710,801.5,801.5], [801.5,801.5,710], c='red', lw=lw, snap=False)

fig.savefig("snap.png")

The Axes relative position of (0.09, 0.1) is chosen on purpose so that the left margin is 0.09 * 10 in * 155 dpi = 139.5 px and the bottom one is 155 px. Also note that the Axis are scaled to be 1 pixel : 1 unit.

Actual outcome

Zoom in to view individual pixels...

snap

Expected outcome

That the lime line snaps correctly in the middle of the blue Rectangles. Note how the horizontal non-snapped red line works as desired.

Feature request

To implement something equivalent to snapx and snapy following scalex and scaley trend. #15595 might change this trend though. This feature could be used to work around this issue, but I think the solutions should be independent. Will split this request into another issue if it's welcomed.

Matplotlib version

  • Operating system: Windows 10
  • Matplotlib version: 3.3.3
  • Matplotlib backend: module://ipykernel.pylab.backend_inline
  • Python version: 3.9.0

Python installed via official installer. Matplotlib installed via pip.

@alexiscoutinho alexiscoutinho changed the title Line2D snaps incorrectly relative to Rectangle and request snap(x)(y) parameters Line2D can snap incorrectly and request snap(x)(y) parameters Dec 4, 2020
@timhoffm
Copy link
Member

This is a numeric issue. If you replace the 801.5 y-values by 801.50000000001 everything looks right, i.e.

ax.plot([890,801.5,801.5], [801.50000000001,801.50000000001,890], c='lime', lw=lw, snap=True)

I'm not clear if we can do anything about this.

@timhoffm timhoffm added the status: needs comment/discussion needs consensus on next step label Dec 28, 2020
@anntzer
Copy link
Contributor

anntzer commented Dec 29, 2020

It's not clear we can do anything about it? The point of snapping is that there is a critical value below which everything gets snapped to one pixel row, and above which everything gets snapped to the next pixel row...

@tacaswell
Copy link
Member

xref #8265 for discussion and some code on floor vs round.

@Ernesto1971
Copy link

Ernesto1971 commented Dec 30, 2020 via email

@jklymak
Copy link
Member

jklymak commented Dec 30, 2020

I'm going to close, in the same spirit as #18945. Numbers right on the border of 0.5 are going to flop between either side arbitrarily, based on round off error.

@Ernesto1971 we can't change your subscription status on GitHub. You have to do that yourself.

@jklymak jklymak closed this as completed Dec 30, 2020
@alexiscoutinho
Copy link
Contributor Author

@timhoffm I think this is more than a numeric issue. @anntzer I found the thresholds and there are still some unanswered questions... The below thresholds are between the bottom/left pixel row/column and middle pixel row/column of the original blue cross for the x and y axis:

x (offsetting the vertical artists):
rectangle: left column < 801.0 <= middle column
snapped line: left column < 801.0 <= middle column
normal line: left column < ~801.5 < middle column (threshold determines where most of the opacity will be)

y (offsetting the horizontal artists):
rectangle: bottom row <= 800.5 < middle row
snapped line: bottom row <= 801.5 < middle row
normal line: bottom row < ~801.0 < middle row

@jklymak Even if we ignore the exact transition points (<= or <), bizarre snapping still occurs:

  • Why are the thresholds between snapped and not snapped artists for both axis offset by half? Based on Artist.set_snap, 1px lines between 2 pixels should be interpolated between them for not snapped lines. Since the same x or y value/region that maps to a pixel border (causing interpolation) could be used for both line types, why does the snapped line not interpret this same value as a threshold/border?
  • The x thresholds are offset from the y thresholds by +0.5, except the snapped line threshold, which is offset by -0.5, why? Furthermore, this causes the absurd case where a line at y=801.5 would be drawn outside a rectangle between y=801 and y=803.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: needs comment/discussion needs consensus on next step
Projects
None yet
Development

No branches or pull requests

6 participants