0

When making a scatter plot in matplotlib, I found that when I change the size of the dots, they become transparent. How can I prevent this from happening?

I've already tried alpha=1, facecolor='k' but none of these seem to work

Example code

X = np.random.normal(0,1,5000)
Y = np.random.normal(0,1,5000)

fig, ax = plt.subplots(1,2, figsize=(6,3), dpi=288)

ax[0].scatter(X,Y, s=.01, c='k')
ax[1].scatter(X,Y, s=1, c='k')
plt.show()

Result:

Result

Notice how the larger dots are opaque and filled in, whilst the smaller ones seem transparent, and have a white facecolor. How do I get the smaller ones to be filled in as well?

1 Answers1

2

If you want the size to scale correctly, and print down to the pixel limit, the dpi and figure size need to be adjusted. Then s will behave correctly, starting with the smallest pixel being s=1. You also probably want to change the marker from the default. Try this:

#divide dpi by four, multiply figsize components by four to keep same area
fig, ax = plt.subplots(1,2, figsize=(24,12), dpi=72)

ax[0].scatter(X,Y, s=1, marker=".", c='k')
ax[1].scatter(X,Y, s=100, marker=".", c='k')

This will make the axes labels smaller, however.

Edit: We can also ensure that the s-value is never sub-minimal-pixel size, as pointed out in comments. Here's a function that allows one to play around with these settings using this:

def scat_rand(size=(24,12), scale=72, smin=1, smax=100, sedge=True, min_pix=1):
    X = np.random.normal(0,1,5000)
    Y = np.random.normal(0,1,5000)
    fig, ax = plt.subplots(1,2, figsize=(size), dpi=scale)

    slimit = (min_pix * 72 / fig.dpi) ** 2
    if smin < slimit + .0001:
        smin = slimit
        print(f"smin too small, reset to {slimit}")

    if sedge:
        ax[0].scatter(X,Y, s=smin, c='k')
        ax[1].scatter(X,Y, s=smax, c='k')
    else:
        ax[0].scatter(X,Y, s=smin, c='k', edgecolor='none')
        ax[1].scatter(X,Y, s=smax, c='k', edgecolor='none')
    plt.suptitle(f"Pixel= {size[0] * scale} x {size[1] * scale}", fontsize=14) 
    plt.show()

Here's a little driver if you want to play around with it:

if __name__ == "__main__":
    temp = int(input("Enter size(1-24): "))
    size = (2 * temp, temp)
    scale = int(input("Enter dpi scale (72, 144, 288, 576, 1152): "))
    limit = (72 / scale) ** 2
    smin = float(input(f"Enter smallest point (limit = {limit}): "))
    smax = float(input("Enter largest point: "))
    edge = input("edgecolor (y/n): ")
    if edge == 'y':
        sedge = True
    else: 
        sedge = False
    scat_rand(size, scale, smin, smax, sedge)

See also: Relationship between dpi and figure size

neutrino_logic
  • 1,289
  • 1
  • 6
  • 11
  • This is a bit confusing. You don't need to set the dpi to 72 to get the desired result. Rather the first problem is that the points have an edgecolor. So set that to `edgecolor="none"`. Then make sure the size of the scatter *can* actually be rendered, meaning it needs to be at least one pixel large. That would result in `scatter(X,Y, s=(1*72/fig.dpi)**2, marker="o", c='k', edgecolor="none")` – ImportanceOfBeingErnest Oct 06 '19 at 14:14
  • @ImportanceOfBeingErnest, thanks that does provide a nice way of finding the correct one-pixel limit. Edited the answer. – neutrino_logic Oct 06 '19 at 17:59