Search code examples
pythonpandasmatplotlibbar-chartlegend

How to consolidate labels in legend


So, the script below requests data from postgres database and draws a diagram. The requested data is a table with 4 columns (ID, Object, Percentage, Color).

The data:

result = [
    (1, 'Apple', 10, 'Red'),
    (2, 'Blueberry', 40, 'Blue'),
    (3, 'Cherry', 94, 'Red'), 
    (4, 'Orange', 68, 'Orange')
]
import pandas as pd
from matplotlib import pyplot as plt
import psycopg2
conn = psycopg2.connect(
    host="localhost",
    port="5432",
    database="db",
    user="user",
    password="123")
cur = conn.cursor()
cur.callproc("test_stored_procedure")
result = cur.fetchall()
cur.close()
conn.close()
print(result)

result = pd.DataFrame(result, columns=['ID', 'Object', 'Percentage', 'Color'])
fruits = result.Object
counts = result.Percentage
labels = result.Color
s = 'tab:'
bar_colors = [s + x for x in result.Color]

fig, ax = plt.subplots()

for x, y, c, lb in zip(fruits, counts, bar_colors, labels):
    ax.bar(x, y, color=c, label=lb)

ax.set_ylabel('fruit supply')
ax.set_title('Fruit supply by kind and color')
ax.legend(title='Fruit color', loc='upper left')

plt.show()

Result:

enter image description here

As you can see in the legend "Red" label is shown twice.

I tried several different examples of how to fix this, but unfortunately no one worked out. F.e.:

handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, labels)

Solution

  • Either make a dictionnary of the handles/labels to make unique combinations:

    hls = {l:h for h,l in zip(*ax.get_legend_handles_labels())}
    ax.legend(hls.values(), hls.keys(), loc='upper left', title='Fruit color')
    

    Output :

    enter image description here

    Or avoid the duplicates upfront by using plot and manually draw the legend :

    fig, ax = plt.subplots()
    
    result.plot(x="Object", y="Percentage", kind="bar", rot=0,
                     title="Fruit supply by kind and color",
                     color=result["Color"].radd("tab:"), ax=ax)
    
    labels = result["Color"].unique()
    
    handles = [plt.Rectangle((0, 0), 0, 0, color=c) for c in labels]
    
    ax.legend(
        handles, labels, ncol=1,
        handleheight=2, handlelength=3,
        loc="upper left", title="Fruit color"
    )
    
    ax.set_xlabel(None)