Plot multiple subplots as animations - python

I have two separate subplots that I'm hoping to display as animations. For the subplots below, ax1 displays an animated scatter plot, while ax2 is a scatter now, I'm hoping to alter this to a line plot.
Please note: I've simplified the question to only display relevant info. However I'm hoping to keep the code similar to what it is now.
Below is my attempt:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import pandas as pd
DATA_LIMITS = [0, 15]
def datalimits(*data):
return DATA_LIMITS
fig = plt.figure(figsize=(10,18))
grid = plt.GridSpec(1, 3, wspace=0.4, hspace=0.3)
gridsize = (3, 2)
ax1 = plt.subplot2grid(gridsize, (0, 0), colspan=2, rowspan=2)
ax2 = plt.subplot2grid(gridsize, (2, 0), colspan=2, rowspan=2)
ax1.grid(False)
ax2.grid(False)
ax1.set_xlim(DATA_LIMITS)
ax1.set_ylim(DATA_LIMITS)
line_a, = ax1.plot([], [], 'o', c='red', alpha = 0.5, markersize=5,zorder=3)
line_b, = ax1.plot([], [], 'o', c='blue', alpha = 0.5, markersize=5,zorder=3)
lines=[line_a,line_b]
scat = ax1.scatter([], [], s=20, marker='o', c='white', alpha = 1,zorder=3)
scats=[scat]
line_d = ax2.plot([], [], 'o', c = 'k')
ax2.set_ylim(-6,6)
ax2.set_xlim(0,15)
def plots(tdf, xlim=None, ylim=None, fig=fig, ax=ax1):
df = tdf[1]
if xlim is None: xlim = datalimits(df['X'])
if ylim is None: ylim = datalimits(df['Y'])
for (group, gdf), group_line in zip(df.groupby('group'), lines+scats+line_d):
if group in ['A','B','D']:
group_line.set_data(*gdf[['X','Y']].values.T)
elif group in ['C']:
gdf['X'].values, gdf['Y'].values
scat.set_offsets(gdf[['X','Y']].values)
return [scat] + [line_a,line_b] + [line_d]
n = 9
time = range(n)
d = ({
'A1_X' : [13,14,12,13,11,12,13,12,11,10],
'A1_Y' : [6,6,7,7,7,8,8,8,9,10],
'A2_X' : [7,6,5,7,6,3,4,5,6,6],
'A2_Y' : [11,12,11,10,11,12,10,11,10,9],
'B1_X' : [8,9,8,7,6,7,5,6,7,6],
'B1_Y' : [3,4,3,2,3,4,2,1,2,3],
'B2_X' : [13,14,14,14,13,13,13,12,12,12],
'B2_Y' : [5,4,3,2,4,5,4,6,3,3],
'C1_X' : [5,6,7,5,6,5,6,5,6,5],
'C1_Y' : [10,11,10,11,12,11,10,8,7,6],
'D1_X' : [0,1,2,3,4,5,6,7,8,9],
'D1_Y' : [0,1,2,3,4,3,2,1,0,-1],
})
tuples = [((t, k.split('_')[0][0], int(k.split('_')[0][1:]), k.split('_')[1]), v[i])
for k,v in d.items() for i,t in enumerate(time) ]
df = pd.Series(dict(tuples)).unstack(-1)
df.index.names = ['time', 'group', 'id']
interval_ms = 1000
delay_ms = 2000
ani = animation.FuncAnimation(fig, plots, frames=df.groupby('time'), interval=interval_ms, repeat_delay=delay_ms,)
plt.show()

Edit 3: I've deleted all previous updates to keep things clean; you can still check them out in the edit history.
See if this code does what you want, changes are marked via comments:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import pandas as pd
import numpy as np #<< a new import is required
DATA_LIMITS = [0, 15]
def datalimits(*data):
return DATA_LIMITS
fig = plt.figure(figsize=(10,18))
grid = plt.GridSpec(1, 3, wspace=0.4, hspace=0.3)
gridsize = (3, 2)
ax1 = plt.subplot2grid(gridsize, (0, 0), colspan=2, rowspan=2)
ax2 = plt.subplot2grid(gridsize, (2, 0), colspan=2, rowspan=2)
ax1.grid(False)
ax2.grid(False)
ax1.set_xlim(DATA_LIMITS)
ax1.set_ylim(DATA_LIMITS)
line_a, = ax1.plot([], [], 'o', c='red', alpha = 0.5, markersize=5,zorder=3)
line_b, = ax1.plot([], [], 'o', c='blue', alpha = 0.5, markersize=5,zorder=3)
lines=[line_a,line_b]
scat = ax1.scatter([], [], s=20, marker='o', c='white', alpha = 1,zorder=3)
scats=[scat]
line_d = ax2.plot([], [], '-', c = 'k') ##<< using '-' makes this a line plot
ax2.set_ylim(-6,6)
ax2.set_xlim(0,15)
def plots(tdf, xlim=None, ylim=None, fig=fig, ax=ax1):
df = tdf[1]
if xlim is None: xlim = datalimits(df['X'])
if ylim is None: ylim = datalimits(df['Y'])
for (group, gdf), group_line in zip(df.groupby('group'), lines+scats+line_d):
if group in ['A','B']: #<< 'D' is moved to a new if case
group_line.set_data(*gdf[['X','Y']].values.T)
elif group in ['D']:
if tdf[0]==0: #<< use this to "reset the line" when the animation restarts
## or remove the if/else part here if you want continuous (over-)plotting
group_line.set_data([0,0])
else:
_x,_y=group_line.get_data()
_x=np.append(_x,gdf['X'].values)
_y=np.append(_y,gdf['Y'].values)
group_line.set_data([_x,_y])
elif group in ['C']:
gdf['X'].values, gdf['Y'].values
scat.set_offsets(gdf[['X','Y']].values)
return [scat] + [line_a,line_b] + [line_d]
n = 9
time = range(n)
d = ({
'A1_X' : [13,14,12,13,11,12,13,12,11,10],
'A1_Y' : [6,6,7,7,7,8,8,8,9,10],
'A2_X' : [7,6,5,7,6,3,4,5,6,6],
'A2_Y' : [11,12,11,10,11,12,10,11,10,9],
'B1_X' : [8,9,8,7,6,7,5,6,7,6],
'B1_Y' : [3,4,3,2,3,4,2,1,2,3],
'B2_X' : [13,14,14,14,13,13,13,12,12,12],
'B2_Y' : [5,4,3,2,4,5,4,6,3,3],
'C1_X' : [5,6,7,5,6,5,6,5,6,5],
'C1_Y' : [10,11,10,11,12,11,10,8,7,6],
'D1_X' : [0,1,2,3,4,5,6,7,8,9],
'D1_Y' : [0,1,2,3,4,3,2,1,0,-1],
})
tuples = [((t, k.split('_')[0][0], int(k.split('_')[0][1:]), k.split('_')[1]), v[i])
for k,v in d.items() for i,t in enumerate(time) ]
df = pd.Series(dict(tuples)).unstack(-1)
df.index.names = ['time', 'group', 'id']
interval_ms = 1000
delay_ms = 2000
ani = animation.FuncAnimation(fig, plots, frames=df.groupby('time'), interval=interval_ms, repeat_delay=delay_ms,)
plt.show()

Related

boxplot show max and min fliers results in TypeError: 'AxesSubplot' object is not subscriptable

I am preparing box plots with a whisker interval of [2,98]. The issue is that I am working with air quality data and have a large range of data points, so the outliers take up the entire figure and overshadow the boxplots. I would like to plot the max and min outliers only and have tried the method from Matplotlib boxplot show only max and min fliers, however, I get an error message that says TypeError: 'AxesSubplot' object is not subscriptable.
Here is my code:
fig,ax = plt.subplots(1, figsize=(8,6))
g = sns.boxplot(data=mda8, orient='v', width = 0.7, whis = (2,98))
fliers = g['fliers']
for fly in fliers:
fdata=fly.get_data
fly.set_data([fdata[0][0],fdata[0][-1],fdata[1][0],fdata[1][-1]])
xvalues = ['Niland', 'El Centro', 'Calexico']
plt.xticks(np.arange(3), xvalues, fontsize=12)
ax.set_ylabel('Ozone MDA8 (ppb)',fontsize=15)
ax.set_ylim(0,105)
plt.show()
Here's some sample data:
mda8 = pd.DataFrame({
'T1':[35.000000, 32.125000, 32.000000, 35.250000, 28.875000, 28.500000, 29.375000, 25.125000, 34.166667, 35.250000],
'T2':[28.375, 30.750, 33.250, 34.000, 32.875, 30.250, 29.875, 100.409, 29.625, 1.232],
'T3':[34.250, 102.232, 28.250, 33.000, 27.625, 21.500, 28.375, 30.250, 3.454, 33.750]})
I need help with plotting the max and min outliers only and am open to doing another method besides the one that I tried here.
EDIT here's the link to my csv file https://drive.google.com/file/d/1E3A0UAYCbSN53JXtfsbrA4i_Phci_JWf/view?usp=sharing
A possible approach could be:
hide the outliers plotted by seaborn.boxplot by passing showfliers = False parameter:
sns.boxplot(data=mda8, orient='v', width = 0.7, whis = (2,98), showfliers = False)
get the list of outliers for each column, find maximum and minimum and plot only them:
outliers = {col: list(stat['fliers']) for col in mda8.columns for stat in boxplot_stats(mda8[col])}
min_max_outliers = {key: [np.min(value), np.max(value)] if value != [] else [] for key, value in outliers.items()}
i = 0
for key, value in min_max_outliers.items():
if value != []:
ax.scatter([i, i], value, marker = 'd', facecolor = 'black')
i += 1
Complete Code
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from matplotlib.cbook import boxplot_stats
mda8 = pd.DataFrame({'T1': [35.000000, 32.125000, 32.000000, 35.250000, 28.875000, 28.500000, 29.375000, 25.125000, 34.166667, 35.250000],
'T2': [28.375, 30.750, 33.250, 34.000, 32.875, 30.250, 29.875, 100.409, 29.625, 1.232],
'T3': [34.250, 102.232, 28.250, 33.000, 27.625, 21.500, 28.375, 30.250, 3.454, 33.750]})
fig,ax = plt.subplots(1, figsize=(8,6))
sns.boxplot(data=mda8, orient='v', width = 0.7, whis = (2,98), showfliers = False)
outliers = {col: list(stat['fliers']) for col in mda8.columns for stat in boxplot_stats(mda8[col])}
min_max_outliers = {key: [np.min(value), np.max(value)] if value != [] else [] for key, value in outliers.items()}
i = 0
for key, value in min_max_outliers.items():
if value != []:
ax.scatter([i, i], value, marker = 'd', facecolor = 'black')
i += 1
xvalues = ['Niland', 'El Centro', 'Calexico']
plt.xticks(np.arange(3), xvalues, fontsize=12)
ax.set_ylabel('Ozone MDA8 (ppb)',fontsize=15)
ax.set_ylim(0,105)
plt.show()
EDIT
Working on the data your provided, if I plot them as they are:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
mda8 = pd.read_csv(r'data/MDA8_allregions.csv')
mda8 = mda8.drop(['date', 'date.1', 'date.2'], axis = 1)
fig, ax = plt.subplots(1, figsize = (8, 6))
sns.boxplot(data = mda8, orient = 'v', width = 0.7, whis = (2, 98), showfliers = True)
plt.show()
I get:
In the code above I change the parameter showfliers = False, in order to hide outliers.
Then, as suggested by JohanC in the comment, a simpler way to plot outliers is to plot min and max for each column:
for i, col in enumerate(mda8.columns, 0):
ax.scatter([i, i], [mda8[col].min(), mda8[col].max()], marker = 'd', facecolor = 'black')
Complete Code
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
mda8 = pd.read_csv(r'data/MDA8_allregions.csv')
mda8 = mda8.drop(['date', 'date.1', 'date.2'], axis = 1)
fig, ax = plt.subplots(1, figsize = (8, 6))
sns.boxplot(data = mda8, orient = 'v', width = 0.7, whis = (2, 98), showfliers = False)
for i, col in enumerate(mda8.columns, 0):
ax.scatter([i, i], [mda8[col].min(), mda8[col].max()], marker = 'd', facecolor = 'black')
plt.show()

Filtering of data in signal processing

I am processing the data from serial. I have to filter the data to remove ripples. I have tried with the following code. However, I can't get the expected results. Suggest me which type of filter I have to use?
def graph_plot():
plt.xlabel("samples")
plt.ylabel("data")
plt.xlim([0, 2048])
plt.ylim([0, 255])
return plt
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
ax.minorticks_on()
ax.grid(1, which = 'both', axis = 'y', markevery = 5)
actual_rx_data = rx_data[4:2052] #rx_data is input from serial
N = len(actual_rx_data)
rx_data = [actual_rx_data[i] for i in range (0, N)]
rx_data = np.reshape(rx_data, (2048, 1))
smoother = ConvolutionSmoother(window_len = 20, window_type = 'ones')
smoother.smooth(rx_data)
plt = graph_plot()
ax.plot(rx_data, color = 'red') #input
ax.plot(smoother.smooth_data[0], linewidth = 2, color = 'blue') #output
ax.clear()
Result obtained by the above code:
Expected results in blue color:

How to label Y ticklabels as group/category in seaborn clustermap?

I want to make a clustermap/heatmap of gene presence-absence data from patients where the genes will be grouped into categories (e.g chemotaxis, endotoxin etc) and labelled appropriately. I haven't found any such option in seaborn documentation. I know how to generate the heatmap, I just don't know how to label yticks as categories. Here is a sample (unrelated to my work) of what I want to achieve:
Here , yticklabels January, February and March are given group label winter and other yticklabels are also similarly labelled.
I've reproduced the example you gave in seaborn, adapting #Stein's answer from here.
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
from itertools import groupby
import datetime
import seaborn as sns
def test_table():
months = [datetime.date(2008, i+1, 1).strftime('%B') for i in range(12)]
seasons = ['Winter',]*3 + ['Spring',]*2 + ['Summer']*3 + ['Pre-Winter',]*4
tuples = list(zip(months, seasons))
index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])
d = {i: [np.random.randint(0,50) for _ in range(12)] for i in range(1950, 1960)}
df = pd.DataFrame(d, index=index)
return df
def add_line(ax, xpos, ypos):
line = plt.Line2D([ypos, ypos+ .2], [xpos, xpos], color='black', transform=ax.transAxes)
line.set_clip_on(False)
ax.add_line(line)
def label_len(my_index,level):
labels = my_index.get_level_values(level)
return [(k, sum(1 for i in g)) for k,g in groupby(labels)]
def label_group_bar_table(ax, df):
xpos = -.2
scale = 1./df.index.size
for level in range(df.index.nlevels):
pos = df.index.size
for label, rpos in label_len(df.index,level):
add_line(ax, pos*scale, xpos)
pos -= rpos
lypos = (pos + .5 * rpos)*scale
ax.text(xpos+.1, lypos, label, ha='center', transform=ax.transAxes)
add_line(ax, pos*scale , xpos)
xpos -= .2
df = test_table()
fig = plt.figure(figsize = (10, 10))
ax = fig.add_subplot(111)
sns.heatmap(df)
#Below 3 lines remove default labels
labels = ['' for item in ax.get_yticklabels()]
ax.set_yticklabels(labels)
ax.set_ylabel('')
label_group_bar_table(ax, df)
fig.subplots_adjust(bottom=.1*df.index.nlevels)
plt.show()
Gives:
Hope that helps.
I haven't tested this with seaborn yet, but the following works with vanilla matplotlib.
#!/usr/bin/env python
"""
Annotate a group of y-tick labels as such.
"""
import matplotlib.pyplot as plt
from matplotlib.transforms import TransformedBbox
def annotate_yranges(groups, ax=None):
"""
Annotate a group of consecutive yticklabels with a group name.
Arguments:
----------
groups : dict
Mapping from group label to an ordered list of group members.
ax : matplotlib.axes object (default None)
The axis instance to annotate.
"""
if ax is None:
ax = plt.gca()
label2obj = {ticklabel.get_text() : ticklabel for ticklabel in ax.get_yticklabels()}
for ii, (group, members) in enumerate(groups.items()):
first = members[0]
last = members[-1]
bbox0 = _get_text_object_bbox(label2obj[first], ax)
bbox1 = _get_text_object_bbox(label2obj[last], ax)
set_yrange_label(group, bbox0.y0 + bbox0.height/2,
bbox1.y0 + bbox1.height/2,
min(bbox0.x0, bbox1.x0),
-2,
ax=ax)
def set_yrange_label(label, ymin, ymax, x, dx=-0.5, ax=None, *args, **kwargs):
"""
Annotate a y-range.
Arguments:
----------
label : string
The label.
ymin, ymax : float, float
The y-range in data coordinates.
x : float
The x position of the annotation arrow endpoints in data coordinates.
dx : float (default -0.5)
The offset from x at which the label is placed.
ax : matplotlib.axes object (default None)
The axis instance to annotate.
"""
if not ax:
ax = plt.gca()
dy = ymax - ymin
props = dict(connectionstyle='angle, angleA=90, angleB=180, rad=0',
arrowstyle='-',
shrinkA=10,
shrinkB=10,
lw=1)
ax.annotate(label,
xy=(x, ymin),
xytext=(x + dx, ymin + dy/2),
annotation_clip=False,
arrowprops=props,
*args, **kwargs,
)
ax.annotate(label,
xy=(x, ymax),
xytext=(x + dx, ymin + dy/2),
annotation_clip=False,
arrowprops=props,
*args, **kwargs,
)
def _get_text_object_bbox(text_obj, ax):
# https://stackoverflow.com/a/35419796/2912349
transform = ax.transData.inverted()
# the figure needs to have been drawn once, otherwise there is no renderer?
plt.ion(); plt.show(); plt.pause(0.001)
bb = text_obj.get_window_extent(renderer = ax.get_figure().canvas.renderer)
# handle canvas resizing
return TransformedBbox(bb, transform)
if __name__ == '__main__':
import numpy as np
fig, ax = plt.subplots(1,1)
# so we have some extra space for the annotations
fig.subplots_adjust(left=0.3)
data = np.random.rand(10,10)
ax.imshow(data)
ticklabels = 'abcdefghij'
ax.set_yticks(np.arange(len(ticklabels)))
ax.set_yticklabels(ticklabels)
groups = {
'abc' : ('a', 'b', 'c'),
'def' : ('d', 'e', 'f'),
'ghij' : ('g', 'h', 'i', 'j')
}
annotate_yranges(groups)
plt.show()

matplotlib-axis-with-two-scales-shared-origin

Matplotlib axis with two scales shared origin
I have already tried to implement this the existing stackflow solution and my both x-axes are not aligning to 0.
My Code :
def align_xaxis(ax1, v1, ax2, v2):
"""adjust ax2 xlimit so that v2 in ax2 is aligned to v1 in ax1"""
x1, _ = ax1.transData.transform((v1, 0))
x2, _ = ax2.transData.transform((v2, 0))
inv = ax2.transData.inverted()
dx, _ = inv.transform((0, 0)) - inv.transform((x1-x2, 0))
minx, maxx = ax1.get_xlim()
ax2.set_xlim(minx+dx, maxx+dx)
def unrealized_profit_loss_graph(profit_loss):
plt.style.use('ggplot');
fig = plt.figure()
ax1 = fig.add_subplot(111);
ax2 = ax1.twiny()
profit_loss['total_G/l'].
plot(kind='barh',color=profit_loss.positive.map({True: 'g', False: 'r'}))
profit_loss['gain_loss_perc'].plot(kind='barh',color=profit_loss.positive.map({True: 'b', False: 'y'}))
ax1.set_xlabel('%', fontsize=12)
ax2.set_xlabel('$', fontsize=12);
align_xaxis(ax1,0,ax2,0)
plt.xlim(-5000, 20000)
plt.xticks(rotation=45);
plt.show();
I would like both x axes to align at 0.
Also to show the negative plus the positive of the ax1.
Working example :
def unrealized_profit_loss():
Profit_loss = Path("C:/Users/champ/Documents/Pers/Python/stock_dfs/Profit_loss_tranactions.xlsx")
df = pd.read_excel(Profit_loss, sheet_name='Unrealized')
current_prices_ROTH=price_data_ROTH.loc[price_data_ROTH.index[-1]] current_prices_Personal=price_data_Personal.loc[price_data_Personal.index[-1]]
df2 = pd.DataFrame({'Symbol':current_prices_ROTH.index, 'Prices':current_prices_ROTH.values})
df2 = pd.DataFrame({'Symbol':current_prices_Personal.index, 'Prices':current_prices_Personal.values})
da= pd.merge(df,df2, how='left',on=['Symbol','Symbol'])
da['gain_loss_perc']=round(((da['Prices']-da['Cost/share'])/da['Cost/share'])*100,2)
da['total_G/l']=round((da['Prices']*da['Quantity'])-(da['Cost Basis']),0)
da['Account_symbol'] = str(da['Account'])
da['Account_symbol'] = da.agg(lambda x: f"{x['Symbol']} - {x['Account']}", axis=1)
da = da.sort_values(by=['total_G/l'],ascending=True)
da.index = da['Account_symbol']
da['positive'] = da['total_G/l'] > 0
del da.index.name
return(da)
def unrealized_profit_loss_graph(profit_loss):
# graph the profit and loss
#fig, (ax1,ax2) = plt.subplots(1,2,sharex=False,sharey=True,figsize=(16,8));
#fig, ax1 = plt.subplots(1,1,figsize=(16,8));
plt.style.use('ggplot');
fig = plt.figure()
ax1 = fig.add_subplot(111);
ax1.set_title('Total G/L (UNREALIZED - IN THE MARKET)');
#ax1 = fig.add_subplot() # Create matplotlib axes
ax2 = ax1.twiny()
profit_loss['total_G/l'].plot(kind='barh',color=profit_loss.positive.map({True: 'g', False: 'r'}))
profit_loss['gain_loss_perc'].plot(kind='barh',color=profit_loss.positive.map({True: 'b', False: 'y'}))
ax1.set_xlabel('%', fontsize=12);
ax2.set_xlabel('$', fontsize=12);
plt.xlim(-5000, 20000);
plt.xticks(rotation=45);
align_xaxis(ax1,0,ax2,0);
plt.show();
# Profit and loss
profit_loss = unrealized_profit_loss()
p_l = unrealized_profit_loss_graph(profit_loss)
xls file I read from
You failed to provide a working example. Nevertheless, try the following: Pass the respective axis to the plot function and then try aligning
def unrealized_profit_loss_graph(profit_loss):
plt.style.use('ggplot')
fig = plt.figure()
ax1 = fig.add_subplot(111)
profit_loss['total_G/l'].plot(kind='barh',
color=profit_loss.positive.map({True: 'g', False: 'r'}),
ax=ax1)
ax2 = ax1.twiny()
profit_loss['gain_loss_perc'].plot(kind='barh',
color=profit_loss.positive.map({True: 'b', False: 'y'}),
ax=ax2)
ax1.set_xlabel('%', fontsize=12)
ax2.set_xlabel('$', fontsize=12)
plt.xlim(-5000, 20000)
plt.xticks(rotation=45)
align_xaxis(ax1,0,ax2,0)
plt.show();

Vary xytext to prevent overlapping annotations

So I've got some code that generates a donut chart but the problem is there are cases where the annotations overlap due to the values. Code and problem below.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
def donut_chart(val):
df_vals = pd.DataFrame.from_dict(val, orient='index')
labels = df_vals.index.tolist()
fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(aspect="equal"))
color = ['grey']*20
color[0] = 'red'
wedges, texts, junk = ax.pie(df_vals[0:4], counterclock = True,
wedgeprops=dict(width=0.6, linewidth = 2, edgecolor = 'w'),
startangle=90, colors=color,
autopct='%1.0f%%',
pctdistance=0.75,
textprops={'fontsize': 14})
bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="w", lw=0.72)
kw = dict(xycoords='data', textcoords='data', arrowprops=dict(arrowstyle="-"),
bbox=bbox_props, zorder=0, va="center")
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(int(ang))
kw["arrowprops"].update({"connectionstyle": connectionstyle})
ax.annotate(labels[i], xy=(x, y), xytext=(1.2*np.sign(x), 1.2*y),
horizontalalignment=horizontalalignment, **kw, size=14)
#centre_circle = plt.Circle((0,0),0.5, fc='white',linewidth=1.25)
#fig.gca().add_artist(centre_circle)
plt.axis('equal')
plt.show()
plt.close()
val = {'Label A':50, 'Label B':2, 'Label C':1, 'Label D':0.5}
donut_chart(val)
Problem:
What I'd like to do is create something like this:
The key appears to be varying the y value in the xytext so the labels don't overlap but I'm stuck on how this might be implemented or even whether it is possible.
Any ideas?
updated code
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
def donut_chart(val):
df_vals = pd.DataFrame.from_dict(val, orient='index')
labels = df_vals.index.tolist()
fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(aspect="equal"))
color = ['grey']*20
color[0] = 'red'
wedges, texts = ax.pie(df_vals[0:5], counterclock = True,
wedgeprops=dict(width=0.6, linewidth = 1, edgecolor = 'w'),
startangle=90, colors=color,
textprops={'fontsize': 14})
bbox_props = dict(boxstyle="square,pad=0", fc="w", ec="w", lw=0.72)
kw = dict(xycoords='data', textcoords='data', arrowprops=dict(arrowstyle="-"),
bbox=bbox_props, zorder=0, va="center")
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(int(ang))
kw["arrowprops"].update({"connectionstyle": connectionstyle})
ax.annotate(labels[i], xy=(x, y), xytext=((i/10) + 1.1*np.sign(x), (i/10) + y),
horizontalalignment=horizontalalignment, **kw, size=14)
#centre_circle = plt.Circle((0,0),0.5, fc='white',linewidth=1.25)
#fig.gca().add_artist(centre_circle)
plt.axis('equal')
plt.show()
plt.close()
val = {'Label A':50, 'Label B':2, 'Label C':0.2, 'Label D':0.2,'Label E':0.2}
donut_chart(val)
difference
ax.annotate(labels[i], xy=(x, y), xytext=((i/10) + 1.1*np.sign(x), (i/10) + y),
horizontalalignment=horizontalalignment, **kw, size=14)
output

Categories