Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Bug Fix for Legend Label Removal Issue in ErrorbarContainer Object #25396

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
wants to merge 4 commits into from

Conversation

Higgs32584
Copy link
Contributor

@Higgs32584 Higgs32584 commented Mar 5, 2023

PR Summary

Patch for this error:
#25274 [Bug]: .remove() on ErrorbarContainer object does not remove the corresponding item from the legend

Continuation on failed #25386, I had some issues with github and codespaces :(, still getting used to using such a large repo. Turns out that creating a codespace not directly through the branch of the thing you are trying to commit is blocked, so I was running around trying to figure out what was wrong with the push permissions.

Pretty much just removes the label when removed is called on an object, you can read more about the bug under the issue #25274 , I also added a test case modeled after the bug.

what is changed in this patch.
#container.py
set the label to blank when removed called, which removes item from the legend

#container_test.py
test case for container.py, test
The test case is checking that when an object is removed from a legend using the "remove()" method, it is no longer present in the legend. The test case creates an errorbar plot with a marker "foo", removes "foo", adds another errorbar plot with a marker "bar", and then checks that "foo" is not present in the legend and that there is only one handle in the legend.

PR Checklist##

Documentation and Tests

  • added test case to container_test.py
  • whitespace complaint

@Higgs32584
Copy link
Contributor Author

@rcomer I believe you have already investigated this issue, so I here is the final result, with test cases!!

@jklymak
Copy link
Member

jklymak commented Mar 6, 2023

@Higgs32584 Would you consider amending the title to something descriptive, and the PR description to discuss the changes made here?

@Higgs32584 Higgs32584 changed the title fixed issue#25274 Bug Fix for Legend Label Removal Issue in ErrorbarContainer Object Mar 6, 2023
@Higgs32584
Copy link
Contributor Author

alright added some descriptions @jklymak

Copy link
Member

@rcomer rcomer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Higgs32584, I just have a minor comment on the test. See also the flake8 checker, which requires an extra line before the test.


ax.errorbar([1.1], [1.1], [1], marker="o", label="bar")

plt.legend()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is not needed I think.

Also, stylistically it would be good to remove some of the blank lines here. Try to use blank lines to separate things into related blocks of code. So maybe the lines above this (setting up) could be one block, and the lines below (the actual checks) could be another.

Functionally, this test seems good to me 👍

@Higgs32584
Copy link
Contributor Author

@rcomer alright I fixed it up. Thank you again!

@Higgs32584
Copy link
Contributor Author

What is the status of this? Thank you!

@melissawm
Copy link
Member

@Higgs32584 I think now we're just waiting for more reviews/feedback. Feel free to ping again if there's no movement in, say, a week 🙂

@Higgs32584
Copy link
Contributor Author

@melissawm Ok, thanks! I'm looking forward to saying that I completed an OS contribution :)

@@ -26,6 +26,7 @@ def remove(self):
self, scalarp=lambda x: isinstance(x, Artist)):
if c is not None:
c.remove()
self.set_label("")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is OK - it's a bit strange that legend is treating a Container as an Artist, but "remove" doesn't remove the Artist from consideration by legend. Setting the label to be empty accomplishes legend ignoring the Container, but at this point the Container should not be showing up in the list of Artists.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, you would think, right? But I guess for some reason, it still shows up in the legend unless you specifically set the label to empty. Weird, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - but I think this would be better if it chased down why that doesn't happen. Just blanking the label papers over the problem, but isn't a very robust fix. For instance you could imagine at some point we decide to include legend entries for artists with empty label strings.

Copy link
Contributor Author

@Higgs32584 Higgs32584 Mar 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jklymak, the bug has nothing to do with auto-updating; as the test cases show, the auto-updating likely has to do with the flattening of containers.

It likely has something to do with flattening the nested containers, which would probably be of greater risk for something else happening since "cbook.flatten" is referenced so much. I looked into other ways of removing it from the legend, but it seems there are stringent Object-Oriented protocols that I did not want to violate in my pull request. This seemed most in line with the current syntax of the rest of the function. If you query how many labels are in the value, the label is also absent, and the length is one, so there is not just a blank over.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is that the container is apparently being left in the list of artists on the axis, and it should not be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I understand. Where would you recommend looking?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd look at how we get the list of handles in _get_legend_handles and decide why the the Collection has not been removed from ax._children. Note I'm not suggesting changing anything in legend, probably just in Collection.remove

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at it more, I'd just put a bunch of print statements in container.remvoe and try and figure out why self._remove_method(self) is either not being called or not working

@Higgs32584 Higgs32584 requested review from jklymak and rcomer and removed request for jklymak and rcomer March 15, 2023 04:34
Copy link
Member

@jklymak jklymak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, however, I think this is a bandaid on the real problem, which is that the Container is not being removed from the list of Artists on the Axes. If the Artist is properly removed, it will never be querried for the legend.

@tacaswell
Copy link
Member

Containers are not properly "in the tree", but are stored independently (for reasons and fixing that is a big project), however the correct fix here is for the container to remove itself from

self.containers = []

Not sure if the best way is to just reach up to the parent Axes and remove itself or if we should do the same remove method injection we do with artists.

@Higgs32584
Copy link
Contributor Author

Higgs32584 commented Mar 24, 2023

Ok, thank you! Where is the we should do the same remove method injection we do with artists?

Also, I realized this code also this correctly changes the legend:


from matplotlib import pyplot

if __name__ == "__main__":
    
    ax = pyplot.gca()

    obj1 = ax.errorbar([1], [1], [1], marker="o", label="foo")

    pyplot.legend()
    #pyplot.savefig("test_errorbar_1.pdf") # it has "foo" in the legend as expected
    ax.containers.remove(obj1)
    ax.errorbar([2.1], [1.1], [1], marker="o", label="bar2")
    ax.errorbar([3.1], [1.1], [1], marker="o", label="ba")
    print(dir(pyplot.legend()))
    #pyplot.savefig("test_errorbar_2.pdf") # it has both "foo" and "bar" in the legend

@Higgs32584
Copy link
Contributor Author

Where is the remove method injection we do with artists?

@Higgs32584
Copy link
Contributor Author

Can someone help me out with this? I am having a lot of trouble trying to figure it out

@jklymak
Copy link
Member

jklymak commented Mar 25, 2023

The legend consults a list of artists on the axes and the error bars are still in that list though all their children have been removed. Error bars remove method should remove itself from that list.

@Higgs32584
Copy link
Contributor Author

Unfortunately I cannot figure it out. I spent a lot of time trying to decipher everything, but now I am procrastinating on other things. I will get my first commit eventually!

@Higgs32584 Higgs32584 closed this Mar 25, 2023
@tacaswell
Copy link
Member

Following up (too late) for anyone who finds this later, the remove method is injected into the Artist as part of the add_artist method:

def add_artist(self, a):
"""
Add an `.Artist` to the Axes; return the artist.
Use `add_artist` only for artists for which there is no dedicated
"add" method; and if necessary, use a method such as `update_datalim`
to manually update the dataLim if the artist is to be included in
autoscaling.
If no ``transform`` has been specified when creating the artist (e.g.
``artist.get_transform() == None``) then the transform is set to
``ax.transData``.
"""
a.axes = self
self._children.append(a)
a._remove_method = self._children.remove
self._set_artist_props(a)
a.set_clip_path(self.patch)
self.stale = True
return a

@melissawm
Copy link
Member

Thanks @Higgs32584 - for quick questions and specific guidance, feel free to join our gitter room. We also have an incubator room specific to new contributors, and folks will be happy to engage. It can be harder to get quick feedback on GitHub because of the (very) high number of notifications we all get daily. We also host new contributor meetings that you are welcome to join - the next one is set to Tuesday, April 4. Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

Successfully merging this pull request may close these issues.

5 participants