Skip to content

marker edges overlap with marker #13360

Closed

Description

@krrk

Bug report

Bug summary

Marker edge overlaps with marker and the edge appears to be double the width specified. This becomes obvious when you use alpha transparency and a very larger point marker with a large edge width or in the case where I noticed it a small figure (about 2 inches wide) with the default rcParams.

Code for reproduction

Case 1: very large point with a large edge width

plt.figure()
plt.plot(1, '.', ms=200, mew=50, alpha=0.5)

Case 2: very small figure with default rcParams: markersize = 8, markeredgewidth=1.0

plt.figure(figsize=(1,1), dpi=600)
plt.plot(1, '.', alpha=0.5)

Actual outcome
In this figure I have markersize=200 and markeredgewith=50. There is an ugly ring and it looks as if the marker edges are two times wider (100 instead of 50) than I asked.
figure_1
Now in this figure I have markersize=200 and markeredgewidth=99. There is a single pixel in the center that is faded blue thus the markeredges are definitely twice what I asked for. If I set the markeredgewith to 100 I get a solid core.
figure_2

Expected outcome
I would expect that the marker edge would line up perfectly with the marker core. In this plot I set markersize to 300 (200 plus 2*50 edges) to get the same size as the original without the ugly ring.
figure_3

Matplotlib version

  • Operating system: macOS 10.13
  • Matplotlib version: 3.0.2
  • Matplotlib backend: both with Qt and PGF backends
  • Python version: 3.7.2
  • matplotlib installed via pip

Activity

  1. timhoffm commented on Feb 6, 2019

    @timhoffm
    Member

    1. Overlapping not an issue

    The line is drawn along the edge of the marker. It's common practice in vector graphics that half of the line width is to either side of the drawing path. The "ring" is the overlaid color of the filled area and the edge. This is also standard behavior see e.g. this red circle with a semi-transparent green edge produced in inkscape:

    grafik

    2. Sizes are incorrect

    What actually does not work correctly are the sizes, e.g.

    plt.figure()
    plt.plot([0, 1], [1.7, 1.7], '-', color='grey', lw=100, solid_capstyle='butt')
    plt.plot(1.7, '.', ms=200, mew=0, mec='r')
    plt.plot(1, '.', ms=200, mew=50, mec='r', alpha=0.5)
    plt.xlim(-0.3, 1)
    plt.ylim(0.4, 2.1)
    

    grafik

    1. The line lw=100 and the marker ms=200 have the same size.
    2. mew = 1/4 ms but the plotted edgewidth is 1/2 the marker diameter.
  2. krrk commented on Feb 6, 2019

    @krrk
    ContributorAuthor

    I see, that makes sense. I agree that the sizes are incorrect. In inkscape if the stroke width is 1/4 of the object size that stroke overlays half on the object and half off thus adding exactly its width to the objects width (unlike in matplotlib which currently adds 2*mew to the marker size).

  3. QuLogic commented on Feb 6, 2019

    @QuLogic
    Member

    What actually does not work correctly are the sizes, e.g.

    That's because your example is using '.', or "point" markers, which are intentionally smaller. Using 'o', or "circle" markers, ms=100 comes out the same size as lw=100.

  4. QuLogic commented on Feb 6, 2019

    @QuLogic
    Member

    Note: the actual sizes are rather undocumented except if you look at the table here (which is really just an implicit thing, and not explicitly spelled out.)

  5. krrk commented on Feb 28, 2019

    @krrk
    ContributorAuthor

    @QuLogic So I see now that if using 'o' as the marker then matplotlib matches Inkscape's behavior. The line width is the same as the marker size and the marker edge overlaps half-on/half-off. In that case I believe it to be a bit strange that matplotlib includes markers like '.' that break this behavior. Also it does seem that the '.' marker is currently "broken" in that the marker edge doesn't scale by half its value as does the marker size. In code I'm saying plt.plot(1, 1, 'o', ms=50, mew=0) and plt.plot(1, 1, '.', ms=100, mew=0) are identical yet plt.plot(1, 1, 'o', ms=50, mew=10) and plt.plot(1, 1, '.', ms=100, mew=20) are not. For the point marker the marker size is scaled by 1/2 but the marker edge width is not. Why is that?

  6. ImportanceOfBeingErnest commented on Mar 1, 2019

    @ImportanceOfBeingErnest
    Member

    The "." marker is a "o" marker, scaled by a factor of 0.5.
    The edge is applied after the scaling. So the edgewidth is the same in both cases.
    Meaning, each of the following produces a circle with 60 points diameter

    plt.plot(..., 'o', ms=60, mew=0)
    plt.plot(..., 'o', ms=50, mew=10)
    
    plt.plot(..., '.', ms=120, mew=0)
    plt.plot(..., '.', ms=100, mew=10)
    

    As for the reason for the factor of 0.5, I can only speculate: If you specify ms=1 and no edgewidth, the default edgewidth of 1 point will lead to a 1pt large dot in the figure.
    The logic breaks of course once you use the default (or any other) markersize (e.g. ".", ms=5, mew=1 will be a dot with 3.5pt diameter).
    Anyways, that logic got introduced 14 years ago and before that it looks like it was even less comprehensible.

    I think anyone is welcome to document the factor of 0.5 in the marker documentation.

  7. normanius commented on Aug 16, 2019

    @normanius

    Just out of curiosity: why should it be good that the edge overlaps the marker filling by half? An edge is an edge, and the current behavior is counter intuitive, especially if the edge color is the same as the fill color.

    I know that this is a very old feature and users have gotten used to it. Adding a bit of control could not harm, though. How about a parameter markeredgeoffset with the default choice reproducing the current behavior, a value 'aligned' (or similar) ensuring that the outer ring does not overlap, and scalar values controlling the offset of the outer ring?

  8. timhoffm commented on Aug 16, 2019

    @timhoffm
    Member

    As written above #13360 (comment), the edge overlapping the marker half is common practice.

    It‘s also much simpler to perform: The edge is a line drawn at the shape coordinates. If you put the line to the inner or outer side of the shape, it‘s point coordinates become dependent on the line width and even worse on the angles of the connecting segments. And even if done correctly, there may be interpolation artifacts at the interface if the body and the line do not overlap.

  9. ImportanceOfBeingErnest commented on Aug 16, 2019

    @ImportanceOfBeingErnest
    Member

    I think it's not only common practice but it's really the only way to unambiguously define an edge. If you consider the below shape consisting of 4 points, and start to do an "outer" edge, it will soon become an "inner" edge for some part of the shape.

    image

  10. timhoffm commented on Aug 16, 2019

    @timhoffm
    Member

    Closing as intended behavior.

  11. mzechmeister commented on Feb 17, 2022

    @mzechmeister

    Plotting the points twice once without edge and once without face is a workaround:

    import matplotlib.pyplot as plt
    plt.figure()
    plt.plot(0, 'og', ms=100, mew=10, alpha=0.5)
    plt.plot(1, 'og', ms=100, mew=10, alpha=0.5, mec='None')
    plt.plot(1, 'og', ms=100-10, mew=10, alpha=0.5, mfc='none')
    plt.show()

    image
    But a legend will become tricky.

  12. timhoffm commented on Feb 17, 2022

    @timhoffm
    Member

    Another option is to only make the face color transparent and keep the edge solid. This is likely acceptable appearance in many cases:

    import matplotlib.pyplot as plt
    from matplotlib.colors import to_rgba
    plt.plot([-0.04, 0.04], [0, 0], 'r', lw=5)
    plt.plot(0, 'o', ms=100, mew=10, mfc=to_rgba('g', alpha=0.5), mec='g')
    

    grafik

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions