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

make_scatter_glow picks wrong collection and also does not update all scatters with correct color #33

Open
IruNikZe opened this issue Nov 29, 2023 · 1 comment

Comments

@IruNikZe
Copy link

Hello,

I'm reporting this for Python 3.9.13 with matplotlib 3.8.1

Problem Description

I tried to run the command make_scatter_glow on a plot where I plotted a few scatter points before I also added lines, a fill_between, and many more. When I ran make_scatter_glow at the end, I got the following error:

      File "...", line 222, in make_scatter_glow
        x, y = scatterpoints.get_offsets().data.T
    AttributeError: 'memoryview' object has no attribute 'T'

Besides, I noticed that it only makes the first scatter series glow (if it detects one) and that the color of the glow does not match the color of the scatter series if it does not originate from a colormap, but a static color. When I get the plotting of multiple series right, it still looks like:
grafik
for

# Imports
import numpy as np
import matplotlib.pyplot as plt

# Create a figure and axes
fig, ax = plt.subplots()
# scattering three random series
_ = ax.scatter(x=[1, 2, 3], y=[1, 2, 3]) # has default blue glow and thus works out by first look
_ = ax.scatter(x=[1, 2, 3], y=[3, 2, 1]) # glow did not show at first; when fixed the glow was blue as well
_ = ax.scatter( # glow did not show at first; when fixed the glow had the correct colors for each point
    x = np.random.rand(10),
    y = np.random.rand(10),
    c = np.arange(10),
)

Suspected Cause

I'm not 100% familiar with Axes.collection in matplotlib, but I think the error is coming from the way how the collection is picked because it by default takes the last one. I marked the problematic line together with the ones causing the wrong color and the missing glows with a ⚠️.

def make_scatter_glow(
    ax: Optional[plt.Axes] = None,
    n_glow_lines: int = 10,
    diff_dotwidth: float = 1.2,
    alpha: float = 0.3,
) -> None:
    """Add glow effect to dots in scatter plot.

    Each plot is redrawn 10 times with increasing width to create glow effect."""
    if not ax:
        ax = plt.gca()

    scatterpoints = ax.collections[-1] # <-- ⚠️ I ASSUME THIS CAUSES THE ISSUE OF THE CRASH OF THE NEXT LINE
    x, y = scatterpoints.get_offsets().data.T
    dot_color = scatterpoints.get_array() # <-- ⚠️ THIS RETURNS None IF NOT FROM A COLORMAP AND THUS CAUSES WRONG COLOR
    dot_size = scatterpoints.get_sizes()

    alpha = alpha/n_glow_lines

    for i in range(1, n_glow_lines):
        plt.scatter(x, y, s=dot_size*(diff_dotwidth**i), c=dot_color, alpha=alpha)
        # ⚠️ THIS DOES NOT SEEM TO PLOT ANY SERIES BUT THE FIRST ONE (IF ONE CAN BE DETECTED)

Proposed Fix

I could work out a solution that first collects the information of all scatter series and then plots the glows one by one:

def make_scatter_glow(
    ax: Optional[plt.Axes] = None,
    n_glow_lines: int = 10,
    diff_dotwidth: float = 1.2,
    alpha: float = 0.3,
) -> None:
    """Add glow effect to dots in scatter plot.

    Each plot is redrawn 10 times with increasing width to create glow effect."""
    if not ax:
        ax = plt.gca()


    # first, the details of each scatter series are stored in a list
    alpha = alpha / n_glow_lines
    scatter_series = []
    for collection in ax.collections:
        if isinstance(collection, mpl.collections.PathCollection): # True for scatter series
            dot_colors = collection.get_array()
            if dot_colors is None: # the "if not" does not work because this can be a NumPy-Array which has no such comparison
                dot_colors = collection.get_facecolors()
                
            scatter_details = {
                'offsets': collection.get_offsets().data.T,
                'dot_colors': dot_colors,
                'sizes': collection.get_sizes()
            }
            scatter_series.append(scatter_details)

    # then, the scatter series are augmented by the glow effect
    for series in scatter_series:
        x, y = series['offsets']
        dot_colors = series['dot_colors']
        dot_size = series['sizes']

        # Apply glow
        for i in range(0, n_glow_lines):
            ax.scatter(x, y, s=dot_size*(diff_dotwidth**i), c=dot_colors, alpha=alpha)

Now, with this function, there are no crashes even though multiple collections are present (lines, scatter, etc. at arbitrary order), the color is correct, and all glows are applied.
grafik

I did not start a pull request since matplotlib details are a bit outside my domain 😅

@ethanlchristensen
Copy link

ethanlchristensen commented Dec 19, 2023

@IruNikZe I noticed this too when implementing glow effects for 3D scatters. eaa1429

A similar idea to the one you stated is to call is to simply use your own function for making the axis glow (which looks like you have already done). This is how I went about it.

# function to make 2d or 3d scatters glow
def make_collection_glow(ax, collection, mode="3d"):
    if mode == "3d":
        x, y, z = collection._offsets3d
    elif mode == "2d":
        x, y = collection.get_offsets().data.T
    dot_color = collection.get_facecolor()
    dot_size = collection.get_sizes()
    alpha = 0.2 / 10
    if mode == "3d":
        for _ in range(1, 10):
            ax.scatter(x, y, z, s=dot_size*(1.2**_), c=dot_color, alpha=alpha)
    elif mode == "2d":
        for _ in range(1, 10):
            ax.scatter(x, y, s=dot_size*(1.2**_), c=dot_color, alpha=alpha)


# after you have created the scatter
for idx, collection in enumerate(ax.collections):
        make_collection_glow(ax, collection, mode="2d")

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

2 participants