Skip to content

Added donut chart functionality via new axes.pie parameter #10944

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions doc/users/next_whats_new/donut_chart.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Doughnut Charts
-----------------------------------

A doughnut chart is essentially a pie chart with an area of the center cut out. It is one of the most aesthetically pleasing to look at charts that there is. In Matplotlib, it is apparent there there is no consolidated method of drawing a doughnut chart. With our changes we solve this problem.
Copy link
Contributor

@anntzer anntzer Apr 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not make judgment calls about the esthetics of donuts. Also be consistent doughnut/donut.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Donuts need to be gustatory, not aestetic!


A parameter, donut, is added to the pie class. Donut is a dictionary with three possible parameters: breaks, width, and callout. Breaks specify where to break the dataset into multiple layers, width specifies how much of each slice to show and callout specifies the text in the middle of the donut chart.

… code block:: python
import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()

# Set values for the chart
radius = 1
colors = ['blue', 'red', 'green', 'orange', 'purple', 'yellow']
labels = ['A', 'B', 'C', 'D', 'E', 'F']

# Set the breaks
callout = 'Comparison \n 50%'
breaks = [1, 3]
width = 0.3

# Draw the chart
pie, _ = ax.pie([75, 25, 15, 85, 45, 55], radius=radius, colors=colors, labels=labels,
donut={'breaks': breaks, 'width': width, 'callout': callout})
ax.axis('equal')


43 changes: 38 additions & 5 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2579,7 +2579,7 @@ def pie(self, x, explode=None, labels=None, colors=None,
autopct=None, pctdistance=0.6, shadow=False, labeldistance=1.1,
startangle=None, radius=None, counterclock=True,
wedgeprops=None, textprops=None, center=(0, 0),
frame=False, rotatelabels=False):
frame=False, rotatelabels=False, donut=None):
"""
Plot a pie chart.

Expand Down Expand Up @@ -2654,6 +2654,9 @@ def pie(self, x, explode=None, labels=None, colors=None,
rotatelabels : bool, optional, default: False
Rotate each label to the angle of the corresponding slice if true.

donut : dict, optional, default: 0
Dict of arguments to alter chart apperence to that of a donut graph.

Returns
-------
patches : list
Expand Down Expand Up @@ -2716,17 +2719,38 @@ def get_next_color():
slices = []
autotexts = []

dw = 1
breaks = []
break_ind = 0
layer_lvl = 0
num_layers = 1

if donut:
# donut width set based on number of 'breaks and 'width' parameter
num_layers = len(donut.get('breaks', [])) + 1
dw = 1/(num_layers + 1) * (donut.get('width', 1))
breaks = donut.get('breaks', [-1])
# draw the callout text in center of chart
t = self.text(center[0], center[1],
donut.get('callout', ''),
size=rcParams['xtick.labelsize'],
horizontalalignment='center',
verticalalignment='center',
wrap = 'true')
texts.append(t)

i = 0
for frac, label, expl in zip(x, labels, explode):
x, y = center
theta2 = (theta1 + frac) if counterclock else (theta1 - frac)
theta2 = (theta1 + (frac * num_layers)) if counterclock else (theta1 - (frac * num_layers))
thetam = 2 * np.pi * 0.5 * (theta1 + theta2)
x += expl * math.cos(thetam)
y += expl * math.sin(thetam)

w = mpatches.Wedge((x, y), radius, 360. * min(theta1, theta2),
w = mpatches.Wedge((x, y), radius * (1 - dw * layer_lvl), 360. * min(theta1, theta2),
360. * max(theta1, theta2),
facecolor=get_next_color(),
width=dw,
**wedgeprops)
slices.append(w)
self.add_patch(w)
Expand All @@ -2741,8 +2765,8 @@ def get_next_color():
shad.set_label('_nolegend_')
self.add_patch(shad)

xt = x + labeldistance * radius * math.cos(thetam)
yt = y + labeldistance * radius * math.sin(thetam)
xt = x + labeldistance * (radius * (1 - dw * layer_lvl) - (0.17 * layer_lvl)) * math.cos(thetam)
yt = y + labeldistance * (radius * (1 - dw * layer_lvl) - (0.17 * layer_lvl)) * math.sin(thetam)
label_alignment_h = xt > 0 and 'left' or 'right'
label_alignment_v = 'center'
label_rotation = 'horizontal'
Expand All @@ -2759,6 +2783,15 @@ def get_next_color():

texts.append(t)

# for donut charts, move to the next donut layer according to
# breakpoints set in dict parameter. Reset theta2 to 0.
if len(breaks) > 0 and i == breaks[break_ind]:
layer_lvl += 1
break_ind += 1
theta2 = 0
if break_ind >= len(breaks):
break_ind = 0;

if autopct is not None:
xt = x + pctdistance * radius * math.cos(thetam)
yt = y + pctdistance * radius * math.sin(thetam)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading