-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
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
base: main
Are you sure you want to change the base?
Conversation
4f260a4
to
d870cca
Compare
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. |
There was a problem hiding this 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.
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. |
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. |
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. |
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. |
d870cca
to
5d7b820
Compare
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. |
flake8:
|
bar_container = BarContainer(patches, errorbar, datavalues=datavalues, | ||
orientation=orientation, label=label) | ||
if data_empty: | ||
bar_container = BarContainer([], [], datavalues=[], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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=[], |
Was this working after changing to an empty list in |
The legend actually uses a custom handler for matplotlib/lib/matplotlib/legend.py Lines 779 to 780 in a364dc5
which does: matplotlib/lib/matplotlib/legend_handler.py Lines 40 to 43 in a364dc5
So my suggestion (on top of this PR) would be something like (plus/minus decisions like naming or putting it in 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: |
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. |
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 withnan
-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:
PR Checklist
Tests and Styling
pytest
passes).flake8-docstrings
and runflake8 --docstring-convention=all
).Documentation
doc/users/next_whats_new/
(follow instructions in README.rst there).doc/api/next_api_changes/
(follow instructions in README.rst there).