Skip to content

[Bug]: Subplots aren't updating with fig.canvas.draw_idle(). #27216

Closed

Description

@Jacfger

Bug summary

fig.canvas.draw_idle() in a key event callback doesn't update all subplots (in the example there're 9). No matter what function I used. I've tried fig.canvas.draw(), plt.pause(), fig.canvas.flush_events().

Code for reproduction

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.widgets import LassoSelector
from matplotlib.path import Path
import matplotlib

print(matplotlib.get_backend())


class SelectFromSharedCollection:
    mask = None

    def __init__(self, ax, collection, alpha_other=0.3):
        self.ax, self.canvas = ax, ax.figure.canvas
        self.collection = collection
        self.alpha_other = alpha_other

        self.xys = collection.get_offsets()
        self.Npts = len(self.xys)

        # Ensure that we have separate colors for each object
        self.fc = collection.get_facecolors()
        if len(self.fc) == 0:
            raise ValueError("Collection must have a facecolor")
        elif len(self.fc) == 1:
            self.fc = np.tile(self.fc, (self.Npts, 1))

        self.lasso = LassoSelector(ax, onselect=self.onselect)
        self.ind = []

    def onselect(self, verts):
        path = Path(verts)
        SelectFromSharedCollection.last_path = path
        SelectFromSharedCollection.mask = path.contains_points(self.xys)
        self.ind = np.nonzero(SelectFromSharedCollection.mask)[0]
        self.fc[:, -1] = self.alpha_other
        self.fc[self.ind, -1] = 1
        self.collection.set_facecolors(self.fc)
        self.canvas.draw_idle()

    def disconnect(self):
        self.lasso.disconnect_events()
        self.fc[:, -1] = 1
        self.collection.set_facecolors(self.fc)
        self.canvas.draw_idle()

    def accept(self, event):
        if event.key == "enter":
            print(SelectFromSharedCollection.mask)
            self.fc[~SelectFromSharedCollection.mask] = [0.0, 0.5, 0, 0.1]
            self.fc[SelectFromSharedCollection.mask] = [0.5, 0, 0, 1]
            self.canvas.draw_idle()


fig, axes = plt.subplots(3, 3)
axes = axes.flatten()

figures = {}


def accept(event):
    match event.key:
        case "enter":
            for perplexity, figure in figures.items():
                print(f"Perplexity: {perplexity}")
                figure["selector"].accept(event)
            plt.pause(0.5)
            fig.canvas.draw()
            fig.canvas.flush_events()
        case _:
            pass


for ax, perplexity in zip(axes, range(20, 200, 20)):
    X_embedded = np.random.rand(50, 2)
    pts = ax.scatter(X_embedded[:, 0], X_embedded[:, 1], s=1)
    figures[perplexity] = {
        "ax": ax,
        "selector": SelectFromSharedCollection(ax, pts),
    }

fig.canvas.mpl_connect("key_press_event", accept)
plt.show()

Actual outcome

After selecting points from one of the subplots, pressing enter doesn't do anything. Until individual subplots are clicked on, then the plots update properly. But every subplots has to be clicked to update.

Expected outcome

The plots just update themselves after hitting the "Enter" keys.

Additional information

No response

Operating system

macOS Ventura 13.5

Matplotlib Version

3.7.2, 3.8.0 (I tried both)

Matplotlib Backend

MacOSX

Python version

3.11.4

Jupyter version

wasn't using jupyter

Installation

pip

Activity

  1. greglucas commented on Oct 29, 2023

    @greglucas
    Contributor

    This is a pretty complicated example to follow. I believe the issue is that you aren't updating the data in the other subplots, so there is no new data for your events to "draw". But, it is a bit unclear what you're expecting to see with this example. I'd suggest trying to narrow it down to just two subplots with minimal data if you can which might show the issue more clearly.

  2. Jacfger commented on Oct 29, 2023

    @Jacfger
    Author

    So in short my code basically highlight lasso selected point with the enter key. And assume there's an index for eacb points, other plots highlight the corresponding points as well, which is done by updating the attribute facecolor of the collection (return object from scatter).

    I'll try it tmr but what do you mean by data update? Is updating facecolor also a data update to the subplot?

  3. greglucas commented on Oct 29, 2023

    @greglucas
    Contributor

    There is a lot going on in that example, which makes it hard for someone without any context to follow what is going on. I'm just suggesting to boil it down to a simpler example to follow, which might make the analysis easier. I think what is going on is that you aren't updating the data in the other plots how you think you are, but again that is not easy to follow without some added effort of digging into it.

    Here is a more minimal example that simply swaps facecolors of items in two collections when pressing enter. You should see the blue and red scatter points change every time you hit enter.

    import matplotlib.pyplot as plt
    
    
    fig, (ax1, ax2) = plt.subplots(nrows=2)
    
    x = [0, 1, 2]
    
    coll1 = ax1.scatter(x, x, c=["r", "g", "b"])
    coll2 = ax2.scatter(x, x, c=["b", "g", "r"])
    
    def accept(event):
        match event.key:
            case "enter":
                print("SWAPPING COLORS")
                # swap facecolors
                fc1 = coll1.get_facecolors()
                fc2 = coll2.get_facecolors()
                coll1.set_facecolors(fc2)
                coll2.set_facecolors(fc1)
                fig.canvas.draw_idle()
            case _:
                pass
    
    fig.canvas.mpl_connect("key_press_event", accept)
    plt.show()
  4. tacaswell commented on Oct 30, 2023

    @tacaswell
    Member

    90% sure that the issue is each of the lasso selectors is using blitting (useblit=True is the default) and because there are naive to each other the last one "wins".

  5. Jacfger commented on Oct 31, 2023

    @Jacfger
    Author

    After comparing your example and mine I found out what I missed was set_facecolors(). It was called in onselect(), that's why clicking the subplots would trigger it, but not right after I press enter. Somehow I had the impression that set_facecolors() wasn't needed as the returned object from get_facecolors() seemed to be modifiable.

    Sorry for my stupid mistakes. Yikes.

    Thank you very much for your kind help and examples. This would probably be much harder to find out especially without @greglucas example.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions