Skip to content
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

[ENH]: Different edgecolor and hatch color in bar plot #26074

Open
99991 opened this issue Jun 5, 2023 · 10 comments · May be fixed by #26993 or #28104
Open

[ENH]: Different edgecolor and hatch color in bar plot #26074

99991 opened this issue Jun 5, 2023 · 10 comments · May be fixed by #26993 or #28104

Comments

@99991
Copy link

99991 commented Jun 5, 2023

Problem

Colored edges look ugly:

colored hatch

I want bars with black edges and colored hatch:

colored hatch, black edge

My current workaround is to draw the bars twice, once with colored hatch and then a second time only with black edges:

import matplotlib.pyplot as plt
import numpy as np

width = 0.35
x = np.arange(4) + 1
y_red = [1, 3, 1, 4]
y_blue = [2, 2, 4, 1]

plt.bar(x - 0.5 * width, y_red, width, label="Height of red bars", hatch="////", facecolor=(0, 0, 0, 0), edgecolor="red")
plt.bar(x - 0.5 * width, y_red, width, facecolor=(0, 0, 0, 0), edgecolor="black")

plt.bar(x + 0.5 * width, y_blue, width, label="Height of blue bars", hatch=r"\\", facecolor=(0, 0, 0, 0), edgecolor="blue")
plt.bar(x + 0.5 * width, y_blue, width, facecolor=(0, 0, 0, 0), edgecolor="black")

plt.xticks(x)
plt.yticks([0, 1, 2, 3, 4])
plt.legend()
plt.savefig("hatch.png")
plt.show()

This workaround is not optimal because the legend is wrong.

Proposed solution

I propose a hatchcolor parameter which is independent of edgecolor.

@oscargus
Copy link
Contributor

oscargus commented Jun 5, 2023

There is partial support for this in the code base. The backends support a separate hatch color and collections store the hatch color as self._hatch_color. There is also an rcParam hatch.color that can be used (but will be overwritten if edgecolor is set).

If anyone wants to give it a go, please remember that it cannot break the current behavior. I assume adding a keyword argument to bar which is None by default and is only used when set is the way to go. This will mean that hatch.color will still not be used when edgecolor is set.

There is also a (long-term) idea to completely rework the hatch handling by introducing "hatch styles", but no one has had the time to do this.

@99991
Copy link
Author

99991 commented Jun 5, 2023

The backends support a separate hatch color and collections store the hatch color as self._hatch_color.

Thank you for the hint! It has helped me to find the following workaround that meets my requirements. I am unsure whether to leave this issue open in case someone wants to implement a "proper" solution.

import matplotlib.pyplot as plt
import numpy as np

width = 0.35
x = np.arange(4) + 1
y_red = [1, 3, 1, 4]
y_blue = [2, 2, 4, 1]

collection = plt.bar(x - 0.5 * width, y_red, width, label="Height of red bars", hatch="////", facecolor=(0, 0, 0, 0), edgecolor="black")
for patch in collection.patches:
    patch._hatch_color = (1.0, 0.0, 0.0, 1.0)

collection = plt.bar(x + 0.5 * width, y_blue, width, label="Height of blue bars", hatch=r"\\", facecolor=(0, 0, 0, 0), edgecolor="black")
for patch in collection.patches:
    patch._hatch_color = (0.0, 0.0, 1.0, 1.0)

plt.xticks(x)
plt.yticks([0, 1, 2, 3, 4])
plt.legend()
plt.savefig("hatch.png")
plt.show()

hatch

@story645
Copy link
Member

story645 commented Jun 5, 2023

I assume adding a keyword argument to bar which is None by default and is only used when set is the way to go

Um, if we do this (and I'm very pro, edit: give or take a proper hatch API), I think it should be a keyword on patch and propagated up

@artemshekh
Copy link
Contributor

What is a logic behind this code?

def _set_edgecolor(self, color):
set_hatch_color = True
if color is None:
if (mpl.rcParams['patch.force_edgecolor'] or
not self._fill or self._edge_default):
color = mpl.rcParams['patch.edgecolor']
else:
color = 'none'
set_hatch_color = False

'set_hatch_color = False' allows to change simultaneously hatchcolor through rcParams and edgecolor.

@Vashesh08
Copy link
Contributor

Vashesh08 commented Sep 3, 2023

I have worked on this issue. Please review it

import matplotlib.pyplot as plt
import numpy as np

width = 0.35
x = np.arange(4) + 1
y_red = [1, 3, 1, 4]
y_blue = [2, 2, 4, 1]

plt.bar(x - 0.5 * width, y_red, width, label="Height of red bars", hatch="////", facecolor=(0, 0, 0, 0), edgecolor="black", hatchcolor="red")

plt.bar(x + 0.5 * width, y_blue, width, label="Height of blue bars", hatch=r"\\", facecolor=(0, 0, 0, 0), edgecolor="black", hatchcolor="blue")

plt.xticks(x)
plt.yticks([0, 1, 2, 3, 4])
plt.legend()
plt.savefig("hatch.png")
plt.show()

The code can be edited in the following way to provide a different hatchcolor and edgecolor

image

@99991
Copy link
Author

99991 commented Sep 3, 2023

I have worked on this issue. Please review it

I got the following error with your latest commit at the time, but it seems that new commits are still being made.

python3 example.py 
Traceback (most recent call last):
  File "/data/example.py", line 9, in <module>
    plt.bar(x - 0.5 * width, y_red, width, label="Height of red bars", hatch="////", facecolor=(0, 0, 0, 0), edgecolor="black", hatchcolor="red")
  File "/data/lib/matplotlib/pyplot.py", line 2686, in bar
    return gca().bar(
  File "/data/lib/matplotlib/__init__.py", line 1465, in inner
    return func(ax, *map(sanitize_sequence, args), **kwargs)
  File "/data/lib/matplotlib/axes/_axes.py", line 2528, in bar
    r._internal_update(kwargs)
  File "/data/lib/matplotlib/artist.py", line 1218, in _internal_update
    return self._update_props(
  File "/data/lib/matplotlib/artist.py", line 1192, in _update_props
    raise AttributeError(
AttributeError: Rectangle.set() got an unexpected keyword argument 'hatchcolor'

@Vashesh08
Copy link
Contributor

Hi Thomas @99991 , can you try again Issue26074 branch and try. I already resolved this issue before pushing in the first commit.

In the newer commits, I am just solving the flaky issues (like empty line with extra whitespace) .

@99991
Copy link
Author

99991 commented Sep 3, 2023

I was able to successfully run your code with this branch. It produced a satisfactory hatched bar plot. The legend is correct as well.

hatch

@Vashesh08
Copy link
Contributor

I'm not sure why the PR did not show the changes earlier. Maybe all commits were not pushed. If anyone can go through it again, it would be great. Thanks!

It shows in the PR that it is connected with the same branch.(Attached Screenshot)
image

@Vashesh08 Vashesh08 linked a pull request Oct 4, 2023 that will close this issue
5 tasks
@matchyc
Copy link

matchyc commented Feb 2, 2024

@Vashesh08 Great PR, reviewers should review this code, and this function should be merged into matplotlib.

@Impaler343 Impaler343 linked a pull request Apr 22, 2024 that will close this issue
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment