Skip to content

FIX: Fixed bug with legend color and empty bars. Closes #21506. #22182

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

oscargus
Copy link
Member

PR Summary

As no Rectangle object was created when no data is provided to bar, the colors ended up incorrect. In this PR, data is created with nan-values to not give any visible object, but still create a Rectangle.

Not really sure what a good way to test this is, but I added a test that there is one patch returned in the BarContainer.

This is the result for the non-working example in #21506:

image

PR Checklist

Tests and Styling

  • Has pytest style unit tests (and pytest passes).
  • Is Flake 8 compliant (install flake8-docstrings and run flake8 --docstring-convention=all).

Documentation

  • New features are documented, with examples if plot related.
  • New features have an entry in doc/users/next_whats_new/ (follow instructions in README.rst there).
  • [N/A] API changes documented in doc/api/next_api_changes/ (follow instructions in README.rst there).
  • [N/A] Documentation is sphinx and numpydoc compliant (the docs should build without error).

@oscargus oscargus marked this pull request as ready for review January 10, 2022 19:12
@oscargus
Copy link
Member Author

oscargus commented Jan 10, 2022

I do not understand this. It works on my local machine (although I get an image comparison error). Trying some variants...

Edit: Seems OK now.

@oscargus oscargus changed the title Fixed bug with legend color and empty bars. Closes #21506. FiX: Fixed bug with legend color and empty bars. Closes #21506. Jan 10, 2022
@oscargus oscargus changed the title FiX: Fixed bug with legend color and empty bars. Closes #21506. FIX: Fixed bug with legend color and empty bars. Closes #21506. Jan 10, 2022
Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

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

I'm only 30% convinced this is the right solution. This breaks the invariant that the bar container patches and datavalues contains as many elements as input values.

Can you try to exercise the same code path, including creating the Rectangle, but not adding it to the returned BarContainer? I suppose the best one can do is a data_empty flag and do

if data_empty:
    bar_container = BarContainer([], [], datavalues=[],
                                 orientation=orientation, label=label)
else:
    bar_container = BarContainer(patches, errorbar, datavalues=datavalues,
                                 orientation=orientation, label=label)

There may still be pitfalls with this approach as well.

@oscargus oscargus marked this pull request as draft January 11, 2022 10:57
@tacaswell
Copy link
Member

I share @timhoffm 's concerns.

It may be worth looking at the code that extracts the properties to mirror and see if we can stash some of the style on the container and retrieve it from there.

@oscargus
Copy link
Member Author

I haven't had time to look into it in detail yet, but I've been thinking about it and I agree that it probably is better to add the properties to the container in case it is empty.

@timhoffm
Copy link
Member

timhoffm commented Jan 14, 2022

Please check the following: I think the container does not matter for the legend, what counts is the objects that are added to the Axes (e.g. self.add_patch(r)). One can still create the Artists for an empty dataset just don't add it to the returned container. - Basically what you're doing already but returning an empty container. I don't think we have to somehow artificially add properties to container.

@tacaswell
Copy link
Member

We do walk the container list (that is how error bars get in), I may be miss-remembering the details. If Tim is right about how we discover the patches for the legend, lets not teach more things about the containers.

@oscargus
Copy link
Member Author

Just remembered this one.

It seems like creating the rectangle but not adding it doesn't help. I've pushed the latest version, but I guess the added test will fail now.

@timhoffm
Copy link
Member

flake8:

./lib/matplotlib/axes/_axes.py:2460:80: E501 line too long (82 > 79 characters)

bar_container = BarContainer(patches, errorbar, datavalues=datavalues,
orientation=orientation, label=label)
if data_empty:
bar_container = BarContainer([], [], datavalues=[],
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
bar_container = BarContainer([], [], datavalues=[],
# we've added a dummy Rectangle to the Axes above in case of
# empty data to keep the legend correct. However, we do not want
# to return it here. There are no values and thus there should be
# no patches in the BarContainer.
bar_container = BarContainer([], [], datavalues=[],

@QuLogic
Copy link
Member

QuLogic commented Apr 3, 2025

Was this working after changing to an empty list in BarContainer? I tried rebasing, but it wasn't working AFAICT.

@QuLogic
Copy link
Member

QuLogic commented Apr 3, 2025

The legend actually uses a custom handler for BarContainer:

BarContainer: legend_handler.HandlerPatch(
update_func=legend_handler.update_from_first_child),

which does:
def update_from_first_child(tgt, src):
first_child = next(iter(src.get_children()), None)
if first_child is not None:
tgt.update_from(first_child)

So my suggestion (on top of this PR) would be something like (plus/minus decisions like naming or putting it in __init__, etc.):

diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py
index 47a76e1f40..ff17ce5443 100644
--- a/lib/matplotlib/axes/_axes.py
+++ b/lib/matplotlib/axes/_axes.py
@@ -2598,8 +2598,9 @@ class Axes(_AxesBase):
                 r.sticky_edges.y.append(b)
             else:  # horizontal
                 r.sticky_edges.x.append(l)
-            self.add_patch(r)
-            patches.append(r)
+            if not data_empty:
+                self.add_patch(r)
+                patches.append(r)
 
         if xerr is not None or yerr is not None:
             if orientation == 'vertical':
@@ -2626,9 +2627,11 @@ class Axes(_AxesBase):
         else:  # horizontal
             datavalues = width
 
         bar_container = BarContainer(patches, errorbar, datavalues=datavalues,
                                      orientation=orientation,
                                      label=bar_container_label)
+        if data_empty:
+            bar_container._style_child = r
         self.add_container(bar_container)
 
         if tick_labels is not None:
diff --git a/lib/matplotlib/legend_handler.py b/lib/matplotlib/legend_handler.py
index 263945b050..84fdbeec58 100644
--- a/lib/matplotlib/legend_handler.py
+++ b/lib/matplotlib/legend_handler.py
@@ -41,6 +41,8 @@ def update_from_first_child(tgt, src):
     first_child = next(iter(src.get_children()), None)
     if first_child is not None:
         tgt.update_from(first_child)
+    elif hasattr(src, '_style_child'):
+        tgt.update_from(src._style_child)
 
 
 class HandlerBase:

@oscargus
Copy link
Member Author

oscargus commented Apr 3, 2025

Was this working after changing to an empty list in BarContainer? I tried rebasing, but it wasn't working AFAICT.

I do not recall. I think it did, at least in the sense that it produced the plot above. Trying to think back, I think at least it advances the color cycle? But something else might have changed?

But this was one of my first PRs to Matplotlib and apparently it wasn't the right approach then, so it's been in the back of my head to one day try to do it the right way.

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

Successfully merging this pull request may close these issues.

4 participants