Skip to content

[Bug]: Updating the facecolors of a collection before its first draw has no effect #24479

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

Open
anntzer opened this issue Nov 16, 2022 · 4 comments

Comments

@anntzer
Copy link
Contributor

anntzer commented Nov 16, 2022

Bug summary

Create e.g. a pathcollection (a scatter plot) not mapping any array, but with directly set colors; then update one of the facecolors before the first draw of the collection. This change has no effect and gets cancelled.

Code for reproduction

import matplotlib.pyplot as plt

sc1 = plt.scatter([0, 1, 2], [0, 1, 2], c=["r", "g", "b"])
sc2 = plt.scatter([0, 1, 2], [2, 3, 4], c=["r", "g", "b"])
# Update sc1 before the first draw.
sc1.get_facecolors()[2] = (0, 0, 0, 1)  # i.e. black.
plt.gcf().canvas.draw()
# Update sc2 after the first draw.
sc2.get_facecolors()[2] = (0, 0, 0, 1)  # i.e. black

plt.show()

Actual outcome

test
Note that the third point of sc1 is incorrectly blue, whereas the third point of sc2 is correctly black.

Expected outcome

Both third points are black.

Additional information

First noted by @chahak13 in #24474 (comment).
I haven't done a full bisect but did check that this broke at some point between 3.3.4 and 3.4.0, and I suspect #18480 introduced the issue (in the changes related to update_scalarmappable); attn @efiring.

Operating system

fedora37

Matplotlib Version

3.7.0.dev538+g9b4985e185

Matplotlib Backend

qtagg

Python version

3.10

Jupyter version

ENOSUCHLIB

Installation

git checkout

@efiring
Copy link
Member

efiring commented Nov 17, 2022

This is not a bug. There was never any intention that one could change the colors by mutating the output of get_facecolors(). If you want to change the colors, you need to use the setter.

import matplotlib.pyplot as plt

sc1 = plt.scatter([0, 1, 2], [0, 1, 2], c=["r", "g", "b"])
# Update sc1 before the first draw.
fc = sc1.get_facecolors()
fc[0] = (0, 0, 0, 1)  # i.e. black. instead of red
sc1.set_facecolors(fc)
plt.gcf().canvas.draw()

plt.show()

@anntzer
Copy link
Contributor Author

anntzer commented Nov 17, 2022

Ah, thanks for pointing this out.
Do you think get_facecolors() could return e.g. a read-only view on the colors to prevent the users from getting this wrong? Or at least the limitation should be documented. (Note that the limitation is somewhat subtle, as directly mutating the array will sometimes work (e.g. after a draw has been triggered, AFAICT).

@ksunden
Copy link
Member

ksunden commented Nov 17, 2022

Just for slightly more concrete discussion of the implementation, the primary thing the setter does (in regards to why just editing the getter's output is insufficient) is store the direct input to self._original_facecolor before actually computing the RGBA array

This is done so that the colors can be recomputed according to what users expect.

Recomputing happens in e.g. set_alpha, but also in update_scalarmappable, which is called directly from draw.

By using the getter, editing and then using the setter, the values used to recompute colors are updated as well.

@Jacfger
Copy link

Jacfger commented Oct 31, 2023

So while I understand the functions name implies the functions behavior but I believe it might be better to state the difference explicitly as what I had stated in #27233

Would that be a good change? As mentioned in the issue I was attempting to change that but also there were a lot of get and set functions, so just to ask for some opinion

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

4 participants