Search code examples
pythonpandasmatplotlibrandombar-chart

Using Random.sample with hatches in broken bars doesn't work


I want to draw different random hatches (from a predefined set) for each broken bar. Like this: https://i.sstatic.net/rfNLf.png

Below is the full code example:

import matplotlib.pyplot as plt
import pandas as pd
import random

result = pd.DataFrame([['Bill', 1972, 1974],
                       ['Bill', 1976, 1978],
                       ['Bill', 1967, 1971],
                       ['Danny', 1969, 1975],
                       ['Danny', 1976, 1977],
                       ['James', 1971, 1972],
                       ['Marshall', 1967, 1975]],
                      columns=['Person', 'Year_start', 'Year_left']).\
    sort_values(['Year_start', 'Year_left'], ascending=[True, True]).\
    reset_index(drop=True)

fig, ax = plt.subplots()

height = 0.5
names = []
colors = ['tab:blue', 'tab:green', 'tab:red', 'yellow', 'brown', 'black', 'tab:orange', 'aquamarine']
hatches = ['/', 'o', '+', '-', '*', 'O', 'x', 'X', '|']

bc = result.groupby('Person')['Person'].count().max()
# this is broken barh's max count in this dataframe that will be used further in loop (in order to avoid using constants)
print(bc)

for y, (name, g) in enumerate(result.groupby('Person', sort=False)):
    ax.broken_barh(list(zip(g['Year_start'],
                            g['Year_left'] - g['Year_start'])),
                   (y - height / 2, height),
                   facecolors=random.sample(colors, k=bc),
                   hatch=random.sample(hatches, k=bc)
                   )
    names.append(name)
ax.set_ylim(0 - height, len(names) - 1 + height)
ax.set_xlim(result['Year_start'].min() - 1, result['Year_left'].max() + 1)
ax.set_yticks(range(len(names)), names)
ax.set_yticklabels(names)

print(result)

ax.grid(True)
plt.show()

While it perfectly works with the colors:

facecolors=random.sample(colors, k=bc)

it does not work with the hatches in the same loop:

hatch=random.sample(hatches, k=bc)

hatches are not displayed (image below), which is very strange. Is this something that is not supported for hatches in particular, or did I made a mistake? Many thanks!

enter image description here


Solution

  • While it perfectly works with the colors, it does not work with the hatches in the same loop.. Is this something that is not supported for hatches in particular ?

    I think the answer is in the docstring of set_color :

    Set the facecolor(s) of the collection. c can be a color (all patches have same color), or a sequence of colors; if it is a sequence the patches will cycle through the sequence.

    Source : [code]

    It means that Matplotlib can handle different face colors for each individual bar by cycling through the provided sequence of colors but it is not the same for the hatches. So, for the hatches, you need to use random.choice(hatches) instead of random.sample(hatches, k=bc) because the former returns a single value while the latter returns a sequence/list.

    Output :

    enter image description here

    That being said, if you wanna assign a different fcolor/hatch of every piece of bar, you can sample both fcolors/hatches in // this way (well, one of the ways):

    names = []
    
    for y, (name, g) in enumerate(result.groupby('Person', sort=False)):
        for (start, width), facecolor, hatch in zip(
            zip(g['Year_start'], g['Year_left'] - g['Year_start']),
            random.sample(colors, k=bc),
            random.sample(hatches, k=bc)
        ):
            ax.broken_barh(
                [(start, width)], (y - height / 2, height),
                facecolors=facecolor, hatch=hatch)
        
        names.append(name)
    

    Output :

    enter image description here