Skip to content

Colorbar fill not aligned with containing box #17103

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
brunobeltran opened this issue Apr 11, 2020 · 18 comments
Closed

Colorbar fill not aligned with containing box #17103

brunobeltran opened this issue Apr 11, 2020 · 18 comments

Comments

@brunobeltran
Copy link
Contributor

Bug report

Bug summary

Definitely a low-priority bug, but at some figure sizes, there seems to be an issue with the colorbar not being drawn at the correct position in some backends (seems like PDF and SVG are affected, but not Agg).

The colorbar "fill" (the actual colored bit) will not line up with the box that contains it (the black marker edge that makes the rectangle that is filled by the color bar, see below). Seems like some sort of rounding issue.

I have a more "dramatic" example, but still need to distill it to a minimum working example, as it is a plot of proprietary data.

Code for reproduction

import matplotlib.pyplot as plt
import matplotlib.cm

fig, ax = plt.subplots(figsize=(2, 1.5))

plt.plot()

sm = matplotlib.cm.ScalarMappable()
sm.set_array([])
plt.colorbar(sm)

plt.savefig('/home/bbeltr1/Downloads/test.svg')
plt.savefig('/home/bbeltr1/Downloads/test.pdf')
plt.savefig('/home/bbeltr1/Downloads/test.png', dpi=800)

Actual outcome

SVG:
test svg

PDF:
test pdf

PNG:
test

Expected outcome

The PNG output has the expected outcome.

Matplotlib version

  • Operating system: Debian Jessie
  • Matplotlib version: master
  • Matplotlib backend (print(matplotlib.get_backend())):
  • Python version: 3.7
  • Jupyter version (if applicable):
  • Other libraries:
@ImportanceOfBeingErnest
Copy link
Member

Might this be a problem with the viewer?

For me, pdf looks like this in Foxit Viewer:

image
and svg like this in Inkscape:

image

and like this in Firefox:

image

@jklymak
Copy link
Member

jklymak commented Apr 11, 2020

I can't reproduce in Preview nor Adobe Acrobat (on Mac).

@brunobeltran
Copy link
Contributor Author

Interesting, I can reproduce it in Chrome but by default "qpdfview" doesn't show the problem until I zoom in. Chrome looks like the below (smaller gap than in Inkscape, but still a gap) in both PDF and SVG

2020-04-12-194859_613x259_scrot

Whereas "qpdfview" looks like this:
2020-04-12-195128_887x668_scrot

Until I zoom REALLY far in, then it looks like
2020-04-12-195158_818x478_scrot

@brunobeltran
Copy link
Contributor Author

Also Foxit on my system, still shows the alignment off. But you can only see it if you zoom in a bit.

2020-04-12-234607_1475x660_scrot

@brunobeltran
Copy link
Contributor Author

I can't reproduce in Preview nor Adobe Acrobat (on Mac).

@jklymak My roommate on a mac can reproduce this by zooming into the bottom of the bar in Preview. The gap renders only after a certain zoom level.

@timhoffm
Copy link
Member

Maybe a similar rendering artifact like #5694.

@brunobeltran
Copy link
Contributor Author

brunobeltran commented Apr 13, 2020

Maybe a similar rendering artifact like #5694.

I suspect not actually, because I can make a better example that also "overflows" the top of the bar instead of just "underflowing" the top. Sorry that it took awhile to get this:

Better Example

import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox

# [ax.get_position() for ax in proprietary_fig.axes]
ax_bbox = Bbox([[0.1974965228858249, 0.203249633462262], [0.8130847727044617, 0.8860326455421427]])
cax_bbox = Bbox([[0.8515590383181265, 0.203249633462262], [0.8753374421595627, 0.8860326455421427]])

# figsize=proprietary_fig.get_size_inches()
fig = plt.figure(figsize=[2.27, 1.58108728])

ax = fig.add_axes([ax_bbox.x0, ax_bbox.y0, ax_bbox.width, ax_bbox.height])
cax = fig.add_axes([cax_bbox.x0, cax_bbox.y0, cax_bbox.width, cax_bbox.height])

cb = plt.colorbar(sm, cax=cax)

plt.savefig('/home/bbeltr1/Downloads/test-no-plot-no-labels.svg')

Output

Screenshot from Foxit Reader on Debian Jessie. Circles obviously made in post (in Gimp) for emphasis.

2020-04-13-022012_114x474_scrot

@brunobeltran
Copy link
Contributor Author

For the above colorbar, opening the SVG shows that the "box" ends up being called <svg:g id="patch_8"> and is drawn as

M 139.17881,90.700695 V 90.397074 13.277469 12.973848 h 3.88634 v 0.303621 77.119605 0.303621 z

which is functionally equivalent to just

M 139.17881,90.700695 V 12.973848 h 3.88634 v 77.726847 z

whereas the image is a...well....svg:image with x=139, y=-12, width=4, height=78 (and transform=matrix(1, 0, 0, -1, 0, 78)).

This suggests the issue is actually the PNG position is being rounded to the nearest pixel, whereas the SVG path data is being kept as floats.

By zooming in, it is clear that the bar is slightly to the left (139 instead of 139.17...), is slightly to wide (4 instead of 3.886...), etc.

@ImportanceOfBeingErnest
Copy link
Member

When I run the new example code, I can still not reproduce the issue. I do get

transform="scale(1 -1)translate(0 -77.76)" height="77.76" width="4.32" x="138.96" y="-12.96"

in the svg file.

Here are the two files I obtain (zipped, for github to me upload them):
test.zip

@brunobeltran
Copy link
Contributor Author

brunobeltran commented Apr 13, 2020

Okay, thanks everyone for your patience here, after games of reinstalling-everything-in-a-fresh-venv , I think I've determined that this is a Jupyter-specific bug.

The following produces a "correct" plot, as reported by @ImportanceOfBeingErnest:

$ python -m venv venv
$ source venv/bin/activate
$ pip install matplotlib jupyter
$ cat make_plot.py
import matplotlib.cm
import matplotlib.pyplot as plt
from matplotlib.transforms import Bbox

# [ax.get_position() for ax in proprietary_fig.axes]
ax_bbox = Bbox([[0.1974965228858249, 0.203249633462262], [0.8130847727044617, 0.8860326455421427]])
cax_bbox = Bbox([[0.8515590383181265, 0.203249633462262], [0.8753374421595627, 0.8860326455421427]])

# figsize=proprietary_fig.get_size_inches()
fig = plt.figure(figsize=[2.27,1.58108728])

ax = fig.add_axes([ax_bbox.x0, ax_bbox.y0, ax_bbox.width, ax_bbox.height])
cax = fig.add_axes([cax_bbox.x0, cax_bbox.y0, cax_bbox.width, cax_bbox.height])

sm = matplotlib.cm.ScalarMappable()
sm.set_array([])

cb = plt.colorbar(sm, cax=cax)

plt.savefig('./test-plot.svg')
$ python make_plot.py

However, opening a new jupyter notebook and executing

%run make_plot.py

produces a PDF with the rounding issue.

@ImportanceOfBeingErnest
Copy link
Member

Ok, this does not mean that it's a bug with jupyter necessarily. After all, it's still matplotlib producing the output. But most probably there are some settings being made (e.g. if using IPykernel's inline backend) that change the numbers. It might then be possible to construct an example which shows the same problem outside of jupyter as well.

@QuLogic
Copy link
Member

QuLogic commented Apr 14, 2020

Looks like #6827?

@brunobeltran
Copy link
Contributor Author

Jupyter does set the DPI itself actually....maybe I'll dig into this, seems like it is probably the same issue.

@cover-me
Copy link
Contributor

cover-me commented Apr 15, 2020

For the above colorbar, opening the SVG shows that the "box" ends up being called <svg:g id="patch_8"> and is drawn as

M 139.17881,90.700695 V 90.397074 13.277469 12.973848 h 3.88634 v 0.303621 77.119605 0.303621 z

which is functionally equivalent to just

M 139.17881,90.700695 V 12.973848 h 3.88634 v 77.726847 z

whereas the image is a...well....svg:image with x=139, y=-12, width=4, height=78 (and transform=matrix(1, 0, 0, -1, 0, 78)).

This suggests the issue is actually the PNG position is being rounded to the nearest pixel, whereas the SVG path data is being kept as floats.

By zooming in, it is clear that the bar is slightly to the left (139 instead of 139.17...), is slightly to wide (4 instead of 3.886...), etc.

I can reproduce the problem with Chrome + Jupyter notebook. Matplotlib version 3.2.0.

The SVG result:

Screenshot 2020-04-15 at 18 34 08

However, the white space goes away after I use a larger figure size (2*1.5->3*2.25)

Screenshot 2020-04-15 at 18 38 07

Maybe this is an issue due to rounding.

@cover-me
Copy link
Contributor

Image positions are rounded to 72/dpi in SVG files. The default dpi in Matplotlib is 100 or so. For Jupyter notebook, dpi=72, so the position is rounded to integers. Increase the DPI would increase the precision of positions. plt.savefig('test.svg') -> plt.savefig('test.svg',dpi=100)

@jklymak
Copy link
Member

jklymak commented Apr 16, 2020

Interesting - do the images need to be snapped like that?

@QuLogic
Copy link
Member

QuLogic commented Apr 16, 2020

I'm fairly certain this is a duplicate of #6827 then.

@drcege
Copy link

drcege commented Mar 27, 2024

Thank you all! Manually setting dpi in the savefig function solved the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants