matplotlib icon indicating copy to clipboard operation
matplotlib copied to clipboard

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

Open 99991 opened this issue 1 year ago • 10 comments

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.

99991 avatar Jun 05 '23 07:06 99991

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.

oscargus avatar Jun 05 '23 10:06 oscargus

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

99991 avatar Jun 05 '23 10:06 99991

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

story645 avatar Jun 05 '23 18:06 story645

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.

artemshekh avatar Jun 22 '23 19:06 artemshekh

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

Vashesh08 avatar Sep 03 '23 11:09 Vashesh08

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'

99991 avatar Sep 03 '23 12:09 99991

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) .

Vashesh08 avatar Sep 03 '23 12:09 Vashesh08

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

99991 avatar Sep 03 '23 12:09 99991

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 avatar Sep 03 '23 14:09 Vashesh08

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

matchyc avatar Feb 02 '24 02:02 matchyc