Skip to content

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

Closed

Description

@bderembl

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!

Activity

  1. efiring commented on Oct 25, 2017

    @efiring
    Member

    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.

  2. bderembl commented on Oct 25, 2017

    @bderembl
    Author

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

  3. jklymak commented on Oct 25, 2017

    @jklymak
    Member

    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.

  4. efiring commented on Oct 25, 2017

    @efiring
    Member

    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.

  5. jklymak commented on Oct 26, 2017

    @jklymak
    Member

    #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.

  6. jklymak commented on Oct 26, 2017

    @jklymak
    Member

    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.
  7. jklymak commented on Oct 26, 2017

    @jklymak
    Member

    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

  8. anntzer commented on Oct 26, 2017

    @anntzer
    Contributor

    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.

  9. jklymak commented on Oct 26, 2017

    @jklymak
    Member

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

  10. anntzer commented on Oct 26, 2017

    @anntzer
    Contributor

    @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).

  11. jklymak commented on Oct 26, 2017

    @jklymak
    Member

    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.

  12. QuLogic commented on Mar 4, 2023

    @QuLogic
    Member

    Closing as a duplicate of #1188.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions