Skip to content
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

Hide contour linestroke on pyplot.contourf to get only fills #9574

Closed
bderembl opened this issue Oct 25, 2017 · 12 comments
Closed

Hide contour linestroke on pyplot.contourf to get only fills #9574

bderembl opened this issue Oct 25, 2017 · 12 comments

Comments

@bderembl
Copy link

if you save a contourf in pdf format, you see the thin white line contours.
This is well described in this question and I think the fix makes sense too.
https://stackoverflow.com/a/32911283

is it possible to implement it with an extra argument in contourf?
thanks!

@efiring
Copy link
Member

efiring commented Oct 25, 2017

The problem is that it is not a real solution--it fails if alpha is not 1--and it actually distorts the plot because the finite-width edges extend the color beyond the true boundaries of each patch. The problems can be minimized by carefully fiddling with the patch collection line width, but it is impossible to pick a single value for line width that will always be optimal. It depends on the renderer that is ultimately used, and the dpi used at the time by that renderer.

@bderembl
Copy link
Author

I see. in my experience, the distortion is very minimal though.
I know matlab handles this with the shading flat option

@jklymak
Copy link
Member

jklymak commented Oct 25, 2017

This is an interesting one. I think as @efiring has alluded to in the past, this is an issue with PDF renderers. And 100 appologies if this has been covered before...

But....

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mplp

fig, ax = plt.subplots()

ax.set_axis_off()

x = np.arange(-100.,100.)/100.
y = x
X,Y = np.meshgrid(x, y)
z = np.exp(-X**2 - Y**2)
levels = np.linspace(0, 1, 20)

cnt = plt.contourf(x, y, z, levels=levels, cmap="ocean")

for c in cnt.collections:
    c.set_edgecolor("face")
    c.set_linewidth(0.000000000001)
plt.savefig("test.eps")

works. and I hope we can agree that a linewidth of 0.0000000001 isn't distorting anything. And I guess I don't know what the issue is w/ alpha, but....

In the eps we have

0.000 setlinewidth
1 setlinejoin
0 setlinecap
[] 0 setdash
0.000 0.306 0.129 setrgbcolor
gsave
357.1 266.1 57.6 38.02 clipbox
59.394573 38.016 m
61.189146 38.016 l
62.983719 38.016 l
64.778291 38.016 l
...ETC
cl
gsave
fill
grestore
stroke
grestore

And the ghost lines disappear (at least in my pdf viewers).

If we don't put the fake linewidth in, then the

stroke
grestore

lines in test.eps disappear...

Note that 0.000 setlinewidth has been called for the paths. Unfortunately, with matplotlib if we set c.set_linewidth(0.0) the stroke never gets called (I guess someone was optimizing).

Sooooo, not an expert on eps/pdf at all, but I think most pdf viewers want the stroke in there, even if linewidth=0.

@efiring
Copy link
Member

efiring commented Oct 25, 2017

Yes, we decided long ago that we wanted zero-linewidth to mean "don't stroke", instead of the postscript meaning of "stroke with the minimum linewidth (presumably one dot)".

This whole problem keeps coming up, every year or two, and more than once I have conducted tests to see if I could find a general solution. I think the most recent conversation was in connection with hexbin; someone who is good at github searching might want to find this, and possibly other issues where it has been discussed. On those previous occasions I have found using a tiny linewidth to not work uniformly. In a quick test just now, it appears to work for pdf and eps using evince or firefox as the viewer, but not for svg on firefox. More testing is in order. If it turns out to commonly work for pdf, and to do no major damage in the cases where it doesn't help, then I would be happy to make it standard. I'm not sure what would be the proper way to implement it, though--by changing the behavior of stroke with zero linewidth inside the backend, or by setting the default linewidth.

The problem with alpha <1 occurs when stroking the outline causes overlaps; they then show up as dark boundaries.

@jklymak
Copy link
Member

jklymak commented Oct 26, 2017

#5151 certainly is caused by the same issue.
#7500, #7185 wrt hexbin and edgecolor='face'.

I think a good solution is to let contourf have edgecolors and linewidths kwargs just like contour and default edgecolors to face and allow linewidths=0 to happen, and suggest making small line widths if the exact contour position is crucial? That leaves it largely in the users hands to get something good.

@jklymak
Copy link
Member

jklymak commented Oct 26, 2017

OK, a few notes which I'm sure other folks already know:

  • pcolormesh has this problem (of course)
  • its an adjacency problem.
  • its because the edges of the paths are not quite matching: If you set the axes facecolor to black, the lines turn black.
  • it is definitely a renderer bug, and not actually matplotlib's. If you zoom in on the lines in the pdf they keep getting smaller and smaller.

@jklymak
Copy link
Member

jklymak commented Oct 26, 2017

Right now:

hexbin: edgecolor='face' is default, but "None" can be specified.

pcolormesh: edgecolor=None where None means default to rcParams. 'face' and 'None' are possible. However, I don't see what rcParams is being referred to in the docs! So, the pcolormesh issue is fixable, though not by default as I can see...

contourf doesn't allow edgecolor or linewidths. I think it should at least allow edgecolors like pcolormesh does, and I think it should default to 'face', but allow 'None'. I don't see how pcolormesh sets the linewidth, but looking at an eps, it appears to be set to 0.1

@anntzer
Copy link
Contributor

anntzer commented Oct 26, 2017

This is a fairly difficult to solve problem in general. See e.g. https://computergraphics.stackexchange.com/questions/1824/what-is-illustrators-vector-rasterization-process.

It's not, strictly speaking, because the edges of the polygons are not matching. It's because the renderer only sees one patch at a time. When it antialiases the pixels on the edges of the polygon, the only thing it can do is assign partial occupancy by using partial transparency. When the renderer sees the second polygon, it will do the same. So an edge pixel which was supposed to have, say, left 50% red and right 50% green gets rendered as (white background) (50% transparency red) (50% transparency green), and thus the white "leaks through". This is known as a conflation artefact (I think due to the "conflation" between coverage and alpha).

AFAIK the only way to solve this is to emit your polygons as mesh objects, so that the renderer actually "knows" that the two edges are supposed to be the same (and even then you need a renderer that actually knows how to use that information). See also #7841.

@jklymak
Copy link
Member

jklymak commented Oct 26, 2017

Cool! Given that, I still think we should allow a line to be added!

@anntzer
Copy link
Contributor

anntzer commented Oct 26, 2017

@efiring I guess that setting the default linewidth to a tiny positive value (np.nexafter(0, 1) is probably the easiest). If I understand correctly, any nonzero value will tell postscript to draw an as-thin-as-possible line. On raster backends, such tiny lines will just not appear at all due, well, to rasterization (it may cause a bit of performance drop, or perhaps not -- perhaps worth checking).

@jklymak
Copy link
Member

jklymak commented Oct 26, 2017

You can also see https://github.com/jklymak/contourfIssues/blob/master/Readme.rst where I try to work through this.

Note it is totally a viewer issue. If you turn anti-aliasing if images off you don’t see these lines in the PDF.

@QuLogic
Copy link
Member

QuLogic commented Mar 4, 2023

Closing as a duplicate of #1188.

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

5 participants