Search code examples
pandasmatplotlibchartsstacked

Name stacked bars after legend entry on Pandas/Matplotlib


I have a stacked bar chart that works really well for what I'm looking for. My problem is handling the labels.

I can label every single stacked bar after its value (number), but I'm looking to label it after its name (on the legend).

Does anyone have an idea on how to solve this?

ps.: Unfortunately I can't post images yet.

I have something like this:
    ####
    #15#
    ####
    oooo        ####
    oooo        #35#
    o55o        ####
    oooo        ####
    oooo        o12o


And need like this:
    ####
    #### A
    ####
    oooo        ####
    oooo B      #### A
    oooo        ####
    oooo        oooo B

Solution

  • Based on Dex answer I came up with a solution. Using patches, it will get every single bar from the chart. The bars are ordenated by rows. So if you have a 4x3 dataframe:

       zero  um  dois
    0     a   b     c
    1     d   e     f
    2     g   h     i
    3     j   k     l
    

    bars.patches will have each column after the other: [a,d,g,j,b,e,h,k,c,f,i,l]

    So, every 4 items (rows), it restarts. To do that, we can use the the mod function (%) based on the number of rows on the df:

    i % len(df.index)  == 0   #moves position counter to the next column name
    

    The code ended up like this:

    import pandas as pd
    import numpy as np
    
    # Some data
    x  = np.array(['zero', 'um', 'dois'])
    y = np.array([[3, 4, 8],[2, 2, 4],[6, 7, 8]])
    
    df = pd.DataFrame(y, columns = x)
    
    print(df)
    
       zero  um  dois
    0     3   4     8
    1     2   2     4
    2     6   7     8
    
    
    
    title = 'Chart Title'
    bars = df.plot.bar(ax = ax, stacked = True, title = title, legend = False)
    
    plt.xlabel('x axis label')
    
    pos = -1
    for i, bar in enumerate(bars.patches):     #runs through every single bar on the chart
        if i % len(df.index) == 0:             #based on lenght of the index, gets which label 
            pos += 1                           #to use from the columns. Returning to the 
                                               #first after completing a row
            
        xloc = bar.get_x()                     
        yloc = bar.get_y() + bar.get_height() / 2
        if bar.get_height() > 30:
            ax.annotate(str(df.columns[pos]), xy = (xloc, yloc), va='center', ha='left')
    
                            #df.columns[pos] will get the correct column name
    
    
    

    So, no matter the size of the dataframe, it will plot the column names next to the bars

    chart example: https://i.sstatic.net/2iHau.png