Skip to content

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

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
Jacfger opened this issue Oct 27, 2023 · 5 comments
Closed

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

Jacfger opened this issue Oct 27, 2023 · 5 comments
Labels
status: needs clarification Issues that need more information to resolve.

Comments

@Jacfger
Copy link

Jacfger commented Oct 27, 2023

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

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

@Jacfger
Copy link
Author

Jacfger commented Oct 29, 2023

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?

@greglucas
Copy link
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()

@jklymak jklymak added the status: needs clarification Issues that need more information to resolve. label Oct 29, 2023
@tacaswell
Copy link
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".

@Jacfger
Copy link
Author

Jacfger commented Oct 31, 2023

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
Labels
status: needs clarification Issues that need more information to resolve.
Projects
None yet
Development

No branches or pull requests

4 participants