0

I would like to add percentage values for each of the labels and layers within my three-layered donut plot.

The following code generates the three layer donut plot and legend appropriately. However, it messes up the display of the percentage values (see output figure at bottom of this question).

Current solutions at stack overflow or elsewhere only work for adding percentage values to pie/donut chart (eg: How do I use matplotlib autopct? ) but I have three layers/levels to my pie/donut chart. My code follows below:

import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
fig.set_figheight(7)
fig.set_figwidth(22)

ax1 = fig.add_subplot(121)

first_labels = ["Bonjovi", "Superman", "Darius_royale", "Stargazer_1991_viz", "Obligatory_Vis1", "Gertrude", 'Trimbel', "Olio", "Iniwaka"]
first_sizes = [2000, 300, 200, 100, 100, 150, 40, 30, 700]

second_sizes = [1000, 200, 200, 400, 500, 40, 1, 1, 1000]

third_sizes = [500, 300, 400, 500, 400, 100, 5, 2, 800]

flatui = (sns.diverging_palette(20, 250, n=8))

bigger = plt.pie(first_sizes, colors=flatui, startangle=90, frame=True, radius = 1,
                 wedgeprops={'edgecolor':'k'}, autopct = '%1.1%%f')

smaller = plt.pie(second_sizes, colors=flatui, radius=0.9, startangle=90,
                 wedgeprops={'edgecolor':'k'}, autopct = '%1.1%%f')

smallest = plt.pie(third_sizes, colors=flatui, radius=0.8, startangle=90, 
                  wedgeprops={'edgecolor':'k'}, autopct = '%1.1%%f')

centre_circle = plt.Circle((0, 0), 0.7, color='white', linewidth=0)

plt.gca().add_artist(centre_circle)

# add legend to current ax:
plt.gca().legend(first_labels, loc='center right', bbox_to_anchor=(1,0,.4,1), frameon = True)

plt.show();

The results of the above code look as follows:enter image description here

Can somebody please guide me on how to get the percentage values to display neatly within each donut ring?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
veg2020
  • 956
  • 10
  • 27
  • 1
    Does this answer your question? [How do I use matplotlib autopct?](https://stackoverflow.com/questions/6170246/how-do-i-use-matplotlib-autopct) – Matts Dec 26 '19 at 01:34
  • Unfortunately, the suggested solution only works if a pie/donut chart had one layer/level. I have three levels in my donut chart above. Thus, the link does not answer my question. – veg2020 Dec 26 '19 at 14:55
  • This format string `'%1.1%%f'` doesn't seem right. I guess it should be `'%4.2f%%'` (note the position of the `f`). – accdias Dec 27 '19 at 01:13
  • And if the problem you are trying to solve is the position of the percentiles, then you are looking for `pctdistance` parameter of `matplotlib.pie()`. – accdias Dec 27 '19 at 01:27
  • I got a good visual with `pctdistance`s of 1, .9, and .8, for each pie, respectively. – accdias Dec 27 '19 at 01:33

1 Answers1

2

Summarizing my comments, here is the code from where I got a better graphic output and, I believe, what you are looking for:

import matplotlib.pyplot as plt
import numpy as np


# Example function that you can call from pie(autopct=autopct)
def autopct(pct):
    if pct > 0.5:
        return f'{pct:.2f}%'
    else:
        return ''


fig = plt.figure()
fig.set_figheight(7)
fig.set_figwidth(22)

ax1 = fig.add_subplot(121)

first_labels = (
    'Bonjovi',
    'Superman',
    'Darius Royale',
    'Stargazer 1991 Viz',
    'Obligatory Vis1',
    'Gertrude',
    'Trimbel',
    'Olio',
    'Iniwaka'
)
first_sizes = (2000, 300, 200, 100, 100, 150, 40, 30, 700)
second_sizes = (1000, 200, 200, 400, 500, 40, 1, 1, 1000)
third_sizes = (500, 300, 400, 500, 400, 100, 5, 2, 800)

flatui = (sns.diverging_palette(20, 250, n=8))

bigger = plt.pie(
    first_sizes,
    colors=flatui,
    startangle=90,
    frame=True,
    radius=1,
    wedgeprops={'edgecolor':'k'},
#    autopct='%.2f%%',
    autopct=autopct,
    pctdistance=1
)

smaller = plt.pie(
    second_sizes,
    colors=flatui,
    radius=0.9,
    startangle=90,
    wedgeprops={'edgecolor':'k'},
#    autopct='%.2f%%',
    autopct=autopct,
    pctdistance=.9
)

smallest = plt.pie(
    third_sizes,
    colors=flatui,
    radius=0.8,
    startangle=90,
    wedgeprops={'edgecolor':'k'},
#    autopct='%.2f%%',
    autopct=autopct,
    pctdistance=.8
)

centre_circle = plt.Circle((0, 0), 0.7, color='white', linewidth=0)

plt.gca().add_artist(centre_circle)

# add legend to current ax:
plt.gca().legend(
    first_labels,
    loc='center right',
    bbox_to_anchor=(1,0,.4,1),
    frameon=True
)

plt.show()

You will need to tweak pctdistance until you are satisfied with the result.

EDIT:

After researching a little I wrote this better (IMHO) version:

import matplotlib.pyplot as plt


fig, ax = plt.subplots()
ax.axis('equal')

sizes = dict(
    first = (2000, 300, 200, 100, 100, 150, 40, 30, 700),
    second = (1000, 200, 200, 400, 500, 40, 1, 1, 1000),
    third = (500, 300, 400, 500, 400, 100, 5, 2, 800)
)

percentiles = dict(
    first = [x*100/sum(sizes['first']) for x in sizes['first']],
    second = [x*100/sum(sizes['second']) for x in sizes['second']],
    third = [x*100/sum(sizes['third']) for x in sizes['third']]
)

labels = dict(
    first = [f"{x:.2f}%" if x >.5 else '' for x in percentiles['first']],
    second = [f"{x:.2f}%" if x >.5 else '' for x in percentiles['second']],
    third = [f"{x:.2f}%" if x >.5 else '' for x in percentiles['third']]
)

width = 0.35
radius = 1.5

first, _ = ax.pie(
    sizes['first'],
    startangle=90,
    radius=radius,
    labeldistance=.9,
    labels=labels['first'],
    rotatelabels=True
)

second, _ = ax.pie(
    sizes['second'],
    radius=radius - width,
    startangle=90,
    labeldistance=.9,
    labels=labels['second'],
    rotatelabels=True
)

third, _ = ax.pie(
    sizes['third'],
    radius=radius - 2 * width,
    startangle=90,
    labeldistance=.9,
    labels=labels['third'],
    rotatelabels=True
)

plt.setp(first + second + third, width=width, edgecolor='white')

plt.show()

accdias
  • 5,160
  • 3
  • 19
  • 31
  • Thank you for explaining the pctdistance modifier, Accdias! The is almost there - but the visual isnt quite ideal yet! Some values overlap somewhat....Is there a way to only display those values with > than a certain % (eg: 3% or higher) or rotate the values inside the label you think? – veg2020 Dec 27 '19 at 01:45
  • You can pass a function for `autopct` and then you can have your function returning a string based on the condition you want. About rotating the labels, I guess we don't have that function. I'm going to check the manual. – accdias Dec 27 '19 at 01:48
  • Can you provide a simple example in code for the function you are thinking about? I think rotation is unnecessary if one can set a criterion. One simple fix to improve the visual appeal is to simply increase the thickness of each donut layer to 0.2 units instead of 0.1 units. This allowed me more control to use the pct distance and fit the % values into the center of each donut/level within. – veg2020 Dec 27 '19 at 01:54
  • Check the examples [here](https://code-examples.net/en/q/5e2686). I also found the parameter `rotatelabels` but it doesn't seem to have any effect on the resulting graphic. – accdias Dec 27 '19 at 01:59
  • This is superhelpful accdias - I will accept your answer and probably post a separate question for only printing those values with greater than a certain cut-off value. – veg2020 Dec 27 '19 at 02:05
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/204914/discussion-between-sb2020-and-accdias). – veg2020 Dec 27 '19 at 02:18