Skip to content

marker edges overlap with marker #13360

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
krrk opened this issue Feb 4, 2019 · 12 comments
Closed

marker edges overlap with marker #13360

krrk opened this issue Feb 4, 2019 · 12 comments

Comments

@krrk
Copy link
Contributor

krrk commented Feb 4, 2019

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
@timhoffm
Copy link
Member

timhoffm commented Feb 6, 2019

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.

@krrk
Copy link
Contributor Author

krrk commented Feb 6, 2019

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

@QuLogic
Copy link
Member

QuLogic commented Feb 6, 2019

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.

@QuLogic
Copy link
Member

QuLogic commented Feb 6, 2019

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

@krrk
Copy link
Contributor Author

krrk commented Feb 28, 2019

@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?

@ImportanceOfBeingErnest
Copy link
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.

@normanius
Copy link

normanius commented Aug 16, 2019

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?

@timhoffm
Copy link
Member

timhoffm commented Aug 16, 2019

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.

@ImportanceOfBeingErnest
Copy link
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

@timhoffm
Copy link
Member

Closing as intended behavior.

@mzechmeister
Copy link

mzechmeister commented Feb 17, 2022

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.

@timhoffm
Copy link
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
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants