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

[Bug]: Using "." as markers create wrong-sized legend_elements for scatter plot #21848

Open
tobiscode opened this issue Dec 2, 2021 · 8 comments

Comments

@tobiscode
Copy link

Bug summary

If Axes.scatter() is being used to create a scatter plot with marker=".", the legend entries created by PathCollection.legend_elements() have the wrong sizes.

Code for reproduction

# imports
import matplotlib.pyplot as plt

# create figure
fig = plt.figure()
ax = fig.add_subplot()
points = ax.scatter([-2, 0, -2], [-1, 0, 1], s=[10, 50, 100], marker=".")

# add legend
handles, labels = points.legend_elements(prop="sizes", num=None)
ax.legend(handles, labels)

# show
plt.show()

Actual outcome

image

Expected outcome

This is if setting marker="o":

image

Additional information

No response

Operating system

Linux

Matplotlib Version

3.4.3 and 3.5.0

Matplotlib Backend

QtAgg

Python version

3.9.7

Jupyter version

No response

Installation

conda

@jklymak
Copy link
Member

jklymak commented Dec 2, 2021

They are probably coming out wrong because your dpi changes. '.' attempts to fill in a pixel, but pixels are sneaky and change their size. Better to not use '.' if you care about exact sizes.

@tobiscode
Copy link
Author

tobiscode commented Dec 2, 2021

They are probably coming out wrong because your dpi changes. '.' attempts to fill in a pixel, but pixels are sneaky and change their size. Better to not use '.' if you care about exact sizes.

I'm not actually sure what "." actually attempts to draw, since the pixel marker according to matplotlib.markers should be a comma: ",".

But even if it's supposed to fill in a certain number of pixels, shouldn't those numbers of pixels be the same in the legend and the plot?

@QuLogic
Copy link
Member

QuLogic commented Dec 3, 2021

I'm not sure this is specific to .; the o result looks slightly off as well.

@jklymak
Copy link
Member

jklymak commented Dec 3, 2021

I think that 'o' is an optical illusion - it looks fine when you line them up.

The problem here is the peculiarity of how '.' is created - it is really just a circle with the size reduced by 0.5. I don't understand why this was done, or why this marker was even created. It doesn't seem worth the bother of tracking this reduction into the Line2D object. One could rewrite '.' to do the right thing without relying on circle, but - I would tend to say that if you want the legend to work, just use 'o' since it is exactly the same as '.'

@timhoffm
Copy link
Member

timhoffm commented Dec 3, 2021

Should we then disallow '.' as scatter marker?

@mwaskom
Copy link

mwaskom commented Dec 3, 2021

Perhaps some relevant context here on how the concept of "marker size" across different marker shapes is rather complicated: #15703

@jklymak
Copy link
Member

jklymak commented Dec 3, 2021

Sure, but those markers should be consistent in the legend. The problem here is that the size gets halved for '.' but it is still a circle, so when the legend gets made, the marker is sized like it is 'o'.

@QuLogic
Copy link
Member

QuLogic commented Dec 4, 2021

OK, but the half size reduction for '.' is set by reducing the transform on MarkerStyle._transform from the original circle one. But this transform is used for all the triangle directions (as long as they're not half-filled) and that seems to be okay?

For reference, here are all filled styles:
filled
Not all of them match exactly, but that's most likely from snapping and antialiasing (importing into GIMP and thresholding exactly white pixels shows that some markers differ by 1 pixel or so, while many are exact same size.) But from what I can tell, both 100 diamonds are not the same size (17x17 in legend vs 22x22 in axes).

And for unfilled styles:
unfilled
In addition to the 'o', all the numbered styles are very wrong in the legend.

Test code
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.markers import MarkerStyle


print(mpl.__version__)


def do_plot(ax, marker, fillstyle):
    points = ax.scatter([0, 0, 0], [1, 0, -1], s=[10, 50, 100],
                        marker=MarkerStyle(marker, fillstyle=fillstyle))

    # add legend
    handles, labels = points.legend_elements(prop="sizes", num=None,
                                             color='C0')
    ax.legend(handles, labels, loc='right')
    ax.text(-0.5, 0, f'{marker!r} {fillstyle}', va='center', ha='center')

    ax.set(xticks=[], xlim=(-1, 1), yticks=[], ylim=(-2, 2))


# Filled markers
fig, ax = plt.subplots(len(MarkerStyle.filled_markers),
                       len(MarkerStyle.fillstyles),
                       figsize=(12, 10.8))
for i, marker in enumerate(MarkerStyle.filled_markers):
    for j, fillstyle in enumerate(MarkerStyle.fillstyles):
        do_plot(ax[i, j], marker, fillstyle)
fig.supylabel('Filled markers', ha='right')
fig.subplots_adjust(left=0.02, right=1, bottom=0, top=1, wspace=0, hspace=0)

# Unfilled markers
unfilled_markers = sorted(
    (k for k, v in MarkerStyle.markers.items()
     if k not in MarkerStyle.filled_markers and v != 'nothing'),
    key=str)

unfilled_count = len(unfilled_markers)
nrows = round(unfilled_count / len(MarkerStyle.fillstyles) + 0.5)
figheight = 10.8 / len(MarkerStyle.filled_markers) * nrows
fig, ax = plt.subplots(nrows, len(MarkerStyle.fillstyles),
                       figsize=(12, figheight))
for i, marker in enumerate(unfilled_markers):
    do_plot(ax.flat[i], marker, 'full')
for a in ax.flat[unfilled_count:]:
    a.remove()
fig.supylabel('Unfilled markers', ha='right')
fig.subplots_adjust(left=0.02, right=1, bottom=0, top=1, wspace=0, hspace=0)

plt.show()

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