Search code examples
pythonmatplotlibpie-chart

Preventing overlapping labels in a pie chart Python Matplotlib


I have a pie chart produced by the following code that crowds labels towards the top. Is there a better way to add labels and arrows so that there is no overlapping? I can't use a legend; I need the labels to be next to each slice.

I know in excel there is a "best fit" option that solves issues like this (Prevent overlapping of data labels in pie chart), but I'm wondering if there's any Python equivalent or way to equally space labels around the chart but maintain lines to slices

import matplotlib.pyplot as plt
import numpy as np

bbox_props=dict(boxstyle='square,pad=0.3',fc ='w',ec='k',lw=0.72)
kw=dict(xycoords='data',textcoords='data',arrowprops=dict(arrowstyle='-'),zorder=0,va='center')

fig1,ax1=plt.subplots()
labels=["first\n1.8%","second\n1.3%","third\n10.5%","fourth\n13.8%","fifth\n7.8%","sixth\n6.7%","seventh\n9.9%","eighth\n12.2%","ninth\n12.7%","tenth\n10.9%","eleventh\n7.6%","twelfth\n4.8%"]
values=[1.8,1.3,10.5,13.8,7.8,6.7,9.9,12.2,12.7,10.9,7.6,4.8]
wedges,texts=ax1.pie(values,explode=[0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01],labeldistance=1.2,startangle=90)
for i,p in enumerate(wedges):
    ang=(p.theta2-p.theta1)/2. +p.theta1
    y=np.sin(np.deg2rad(ang))
    x=np.cos(np.deg2rad(ang))
    horizontalalignment={-1:"right",1:"left"}[int(np.sign(x))]
    connectionstyle="angle,angleA=0,angleB={}".format(ang)
    kw["arrowprops"].update({"connectionstyle":connectionstyle})
    ax1.annotate(labels[i],xy=(x, y),xytext=(1.35*np.sign(x),1.4*y),
                horizontalalignment=horizontalalignment,**kw)
    
fig1.show()

enter image description here


Solution

  • We will not discuss whether it is suitable for visualization or not. Assuming that the order of the annotations can be different, we can reorder the numbers into the order of large and small, and change the order of the labels accordingly. This approach depends on the data and may be limited to this task. There may be a smarter way to reorder the data.

    import matplotlib.pyplot as plt
    import numpy as np
    
    bbox_props=dict(boxstyle='square,pad=0.3',fc ='w',ec='k',lw=0.72)
    kw=dict(xycoords='data',textcoords='data',arrowprops=dict(arrowstyle='-'),zorder=0,va='center')
    
    fig1,ax1=plt.subplots()
    labels=["first\n1.8%","second\n1.3%","third\n10.5%","fourth\n13.8%","fifth\n7.8%","sixth\n6.7%","seventh\n9.9%","eighth\n12.2%","ninth\n12.7%","tenth\n10.9%","eleventh\n7.6%","twelfth\n4.8%"]
    values=[1.8,1.3,10.5,13.8,7.8,6.7,9.9,12.2,12.7,10.9,7.6,4.8]
    # Add code
    annotate_dict = {k:v for k,v in zip(labels, values)}
    val = [[x,y] for x,y in zip(sorted(values, reverse=True),sorted(values))]
    values1 = sum(val, [])
    
    new_labels = []
    for v in values1[:len(values)]:
        for key, value in annotate_dict.items():
            if v == value:
                new_labels.append(key)
                
    wedges,texts=ax1.pie(values1[:len(values)],explode=[0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01,0.01],labeldistance=1.2,startangle=90)
    for i,p in enumerate(wedges):
        ang=(p.theta2-p.theta1)/2. +p.theta1
        y=np.sin(np.deg2rad(ang))
        x=np.cos(np.deg2rad(ang))
        horizontalalignment={-1:"right",1:"left"}[int(np.sign(x))]
        connectionstyle="angle,angleA=0,angleB={}".format(ang)
        kw["arrowprops"].update({"connectionstyle":connectionstyle})
        ax1.annotate(new_labels[i],xy=(x, y),xytext=(1.35*np.sign(x),1.4*y),
                    horizontalalignment=horizontalalignment,**kw)
        
    plt.show()
    

    enter image description here