matplotlib
matplotlib copied to clipboard
[ENH]: Different edgecolor and hatch color in bar plot
Problem
Colored edges look ugly:
I want bars with black edges and colored hatch:
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
.
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.
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()
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
What is a logic behind this code? https://github.com/matplotlib/matplotlib/blob/0b8bd96d5ebce4ff4edcca8395973ccfa8bda842/lib/matplotlib/patches.py#L315-L323
'set_hatch_color = False' allows to change simultaneously hatchcolor through rcParams and edgecolor.
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
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'
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) .
I was able to successfully run your code with this branch. It produced a satisfactory hatched bar plot. The legend is correct as well.
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)
@Vashesh08 Great PR, reviewers should review this code, and this function should be merged into matplotlib.