How to add "copy id" button in matplot graph window - python-3.x

I am working on product sell analysis project and for that i have created one donut chart for different products.Now my question is,how to add copy id button near the legends(or anywhere in the graph window) of doughnut chart so that user can copy product id directly from there
Expected output
Code
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6, 3), subplot_kw=dict(aspect="equal"))
products = ["id 11111",
"id 22222",
"id 33333",
"id 44444",
"id 55555",
"id 66666"]
data = [225, 90, 50, 60, 100, 5]
wedges, texts = ax.pie(data, wedgeprops=dict(width=0.5), startangle=-40)
bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
kw = dict(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(ang)
kw["arrowprops"].update({"connectionstyle": connectionstyle})
ax.annotate(products[i], xy=(x, y), xytext=(1.35*np.sign(x), 1.4*y),
horizontalalignment=horizontalalignment, **kw)
ax.set_title("Matplotlib Products Sell: A donut")
plt.show()
output

The following code allows you to click on the annotation box and copies the content to the clipboard.
I use pandas.io.clipboard for doing so, as per this answer.
import numpy as np
import matplotlib.pyplot as plt
from pandas.io.clipboard import copy
def onclick(event):
copy(event.artist.get_text())
fig, ax = plt.subplots(figsize=(6, 3), subplot_kw=dict(aspect="equal"))
cid = fig.canvas.mpl_connect('pick_event', onclick)
products = ["id 11111",
"id 22222",
"id 33333",
"id 44444",
"id 55555",
"id 66666"]
annotations = []
data = [225, 90, 50, 60, 100, 5]
wedges, texts = ax.pie(data, wedgeprops=dict(width=0.5), startangle=-40)
bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
kw = dict(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(ang)
kw["arrowprops"].update({"connectionstyle": connectionstyle})
ax.annotate(products[i], xy=(x, y), xytext=(1.35 * np.sign(x), 1.4 * y),
horizontalalignment=horizontalalignment, picker=True, **kw)
ax.set_title("Matplotlib Products Sell: A donut")
plt.show()

Related

Matplotlib update bbox position

I have a code to make a custom pie chart that moves the annotations when it detects a colision on their bboxes.
I want to move the bbox (and thus the text) to avoid the texting overlaping.
I already detect the colision but when I use the “set_points” method it doesn’t update my figure.
The code is as it follows:
fig, ax = plt.subplots(figsize=(6, 3), subplot_kw=dict(aspect="equal"))
wedges, texts = ax.pie(
np.array(values) / sum(values),
wedgeprops=dict(width=0.5),
startangle=startAngle,
colors=color_map,
)
# bbox_props=dict(facecolor='none', edgecolor='none')
bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
kw = dict(arrowprops=dict(arrowstyle="-"), bbox=bbox_props, zorder=0, va="center")
annot_bbox_list = []
for i, p in enumerate(wedges):
ang = (p.theta2 - p.theta1) / 2.0 + 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})
actual_annot = ax.annotate(
labels[i] + f" {values[i]:.1%}",
xy=(x, y),
xytext=(1.35 * np.sign(x), 1.4 * y),
horizontalalignment=horizontalalignment,
**kw,
)
actual_bbox = actual_annot.get_window_extent(renderer=fig.canvas.get_renderer())
for (annot, bbox) in annot_bbox_list:
intersect, move = bbox_intersection(bbox, actual_bbox, margin=10)
if intersect:
print(f'{annot.get_text()} e {actual_annot.get_text()} intersectam, movendo a segunda em {move}')
#fig.canvas.draw()
points = actual_bbox.get_points()#.copy()
print('pontos da caixa2 :\n {}'.format(points))
points[0][1] += move
points[1][1] += move
print('pontos novos da caixa2 :\n {}'.format(points))
renderer=fig.canvas.get_renderer()
#actual_annot.update_positions(renderer)
#fig.canvas.draw()
actual_bbox.set_points(points)
fig.draw(renderer)
annot_bbox_list.append((actual_annot, actual_bbox))
diff += np.sign(x)
dict_angulos[startAngle] = diff

Using colormap in cycle (python)

How to edit the for cycles under #ax5 and #ax6 to plot graphs in the same fashion? Now, the lower figure has no colour transit, as opposed to the upper one. The colour transit appears in the lower figure after increasing of dpi, however, some unwanted stuff also appears. Is there a scalling problem? Thank you
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.gridspec import GridSpec
import math
fig, ax = plt.subplots()
plt.rcParams["figure.figsize"] = [8, 8]
# Function for plotting parallels to curves
def get_parallels(length=.1):
px, py = [], []
for idx in range(len(x)-1):
x0, y0, xa, ya = x[idx], y[idx], x[idx+1], y[idx+1]
dx, dy = xa-x0, ya-y0
norm = math.hypot(dx, dy) * 1/length
dx /= norm
dy /= norm
px.append(x0-dy)
py.append(y0+dx)
return px, py
def offset(x,y, o):
""" Offset coordinates given by array x,y by o """
X = np.c_[x,y].T
m = np.array([[0,-1],[1,0]])
R = np.zeros_like(X)
S = X[:,2:]-X[:,:-2]
R[:,1:-1] = np.dot(m, S)
R[:,0] = np.dot(m, X[:,1]-X[:,0])
R[:,-1] = np.dot(m, X[:,-1]-X[:,-2])
On = R/np.sqrt(R[0,:]**2+R[1,:]**2)*o
Out = On+X
return Out[0,:], Out[1,:]
dpi = 20
def offset_curve(ax, x,y, o):
""" Offset array x,y in data coordinates
by o in points """
trans = ax.transData.transform
inv = ax.transData.inverted().transform
X = np.c_[x,y]
Xt = trans(X)
xto, yto = offset(Xt[:,0],Xt[:,1],o*dpi/72. )
Xto = np.c_[xto, yto]
Xo = inv(Xto)
return Xo[:,0], Xo[:,1]
fig = plt.figure(constrained_layout=True)
gs = GridSpec(3, 6, figure=fig)
ax5 = fig.add_subplot(gs[1, 3:6])
ax6 = fig.add_subplot(gs[2, :3])
ax7 = fig.add_subplot(gs[2, 3:6])
cmap = plt.get_cmap('Greys_r')
# ax5
x = np.linspace(-1, 1, 100)
y = -x**2
ax5.set_ylim(-1.02, 0.3)
width_l = ax5.get_ylim()[1] - ax5.get_ylim()[0]
for t in np.linspace(0, 1, 40):
length = -0.1*width_l*t
ax5.plot(*get_parallels(length=length), color=cmap(t/2 + 0.25))
# ax6
x = np.linspace(-3, 3, 100)
y = -(1/4*x**4 - 1.6*x**2)
ax6.plot(x, y)
ax6.set_xlim(ax6.get_xlim()[0]-0.5, ax6.get_xlim()[1]+0.5)
ax6.scatter(1/2*(ax6.get_xlim()[0] + ax6.get_xlim()[1]), 1.2, marker = 'o', s=900, facecolors='none')
lines = []
width_l = ax6.get_ylim()[1] - ax6.get_ylim()[0]
for t in np.linspace(0, 1, 40):
l, = ax6.plot(x, y - t * 0.1 * width_l, color=cmap(t/2 + 0.25))
lines.append(l)
def plot_rainbow(event=None):
x0 = x
y0 = y
for i in range(len(lines)):
xx, yy = offset_curve(ax, x0, y0, -width_l)
lines[i].set_data(xx, yy)
lines[i].set_linewidth(1.1*width_l)
x0 = xx
y0 = yy
plot_rainbow()
fig.canvas.mpl_connect("resize_event", plot_rainbow)
fig.canvas.mpl_connect("button_release_event", plot_rainbow)
plt.savefig('fig.pdf')

Annotate Percentage of Group within a Seaborn CountPlot

The below code gets the percentage of all collisions. However, I want to get the percentage within a group. E.G. Mid-Block (not related to intersection) has 2 labels, a 1(red) or a 2(green/blue). Currently, the percentages next to those bars are percentages of the whole (bar count / all collisions), but I need to display the percentage within just one y-axis label. E.G. for Mid-block (not related to intersection), bar count / all collisions within mid-block (not related to intersection). I do not know how to do this, so if someone could point me in the right direction or give me some code that I could study to understand, I'd be very grateful.
Thank you so much for your time.
plt.style.use('ggplot')
plt.figure(figsize = (20, 15))
ax = sb.countplot(y = "JUNCTIONTYPE", hue = "SEVERITYCODE", data = dfm)
plt.title('Number of Persons vs. Number of Collisions by Severity', fontsize = 30)
plt.xlabel('Number of Collisions', fontsize = 24)
plt.ylabel('Number of Persons', fontsize = 24)
plt.tick_params(labelsize=18);
plt.legend(fontsize = 18, title = "Severity", loc = 'lower right')
plt.text(5, 6, "Figure 8: Number of persons plotted against the number of collisions grouped by severity", fontsize = 16)
# labels = [item.get_text() for item in ax.get_yticklabels()]
# labels[0] = 'No'
# labels[1] = 'Yes'
# ax.set_yticklabels(labels)
for p in ax.patches:
width = p.get_width()
height = p.get_height()
x, y = p.get_xy()
ax.annotate(int(width),
((x + width), y),
xytext = (30, -25),
fontsize = 18,
color = '#000000',
textcoords = 'offset points',
ha = 'right',
va = 'center')
for p in ax.patches:
width = p.get_width()
height = p.get_height()
x, y = p.get_xy()
totals = []
for i in ax.patches:
totals.append(i.get_width())
total = sum(totals)
ax.text(width + 0.3, y + 0.38,
str(
round((width/total) * 100, 2))
+ '%',
fontsize=18)
You could pre-calculate the per-group percentage points and use the order in which seaborn / matplotlib draws the bars to reference them.
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
titanic = sns.load_dataset('titanic')
# prepare the dataset
df = (titanic
.groupby(["embark_town", "survived"])
.size()
.reset_index()
.replace({"survived": {0:"no", 1:"yes"}})
.rename(columns={0:"count"}))
# calculate survival % per town of embarkation
df["percent"] = (df
.groupby("embark_town")
.apply(lambda x: x["count"] / x["count"].sum()).values)
# sort the dataframe to match the drawing order
df.sort_values(by=["survived", "embark_town"], inplace=True)
# visualisation
plt.style.use('ggplot')
fig = sns.catplot(
x="count", y="embark_town", hue="survived",
kind="bar", data=df, height=4, aspect=2)
for i, bar in enumerate(fig.ax.patches):
height = bar.get_height()
fig.ax.annotate(
# reference the pre-calculated row in the dataframe
f"{df.iloc[i, 3] :.0%}",
xycoords="data",
xytext=(20, -15),
textcoords="offset points",
xy=(bar.get_width(), bar.get_y()),
ha='center', va='center')
# make space for annonations
plt.margins(x=0.2)
plt.show()

Graph not showing in principal component analysis using python

How to show the graph image in my output for Principal Component Analysis?
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure(figsize = (8, 8))
ax = fig.add_subplot(1, 1, 1)
ax.set_xlabel('Principal Component 1', fontsize = 15)
ax.set_ylabel('Principal Component 2', fontsize = 15)
ax.set_title('2 Component PCA', fontsize = 20)
targets = ['Iris - setosa', 'Iris - versicolor', 'Iris - virginica']
colors = ['r', 'g', 'b']
for target, color in zip(targets, colors):
indicesToKeep = finalDF['target'] == target
ax.scatter(finalDF.loc[indicesToKeep, 'Principal Component 1'],
finalDF.loc[indicesToKeep, 'Principal Component 2'],
c = color, s = 50)
ax.legend(targets)
ax.grid()
ax.show()
Here is the error:
AttributeError Traceback (most recent call last)
<ipython-input-21-d87d089bc5f1> in <module>
16 ax.legend(targets)
17 ax.grid()
---> 18 ax.show()
AttributeError: 'AxesSubplot' object has no attribute 'show'
How to show the graph as there is no plots out there and show attribute is not working?
Only correction here is: in last line replace axis.show() by fig.show()
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure(figsize = (8, 8))
ax = fig.add_subplot(1, 1, 1)
ax.set_xlabel('Principal Component 1', fontsize = 15)
ax.set_ylabel('Principal Component 2', fontsize = 15)
ax.set_title('2 Component PCA', fontsize = 20)
targets = ['Iris - setosa', 'Iris - versicolor', 'Iris - virginica']
colors = ['r', 'g', 'b']
for target, color in zip(targets, colors):
indicesToKeep = finalDF['target'] == target
ax.scatter(finalDF.loc[indicesToKeep, 'Principal Component 1'],
finalDF.loc[indicesToKeep, 'Principal Component 2'],
c = color, s = 50)
ax.legend(targets)
ax.grid()
fig.show()

How to add a subplot to a plot with control widgets

I would like to extend solution of the question "How to keep some text relative to the line into the plot when the plot changes" with two subplots controlled with a pair of widget sliders.
The code I have tried it makes no errors, but it does not show the Figure nor the controls.
from ipywidgets import widgets
from IPython.display import display
import matplotlib.pyplot as plt
import numpy as np
%matplotlib notebook
fig1, ax1 = plt.subplots(211)
line1, = ax1.semilogx([],[], label='Multipath')
hline1 = ax1.axhline(y = 0, linewidth=1.2, color='black',ls='--')
text1 = ax1.text(0, 0, "T Threshold",
verticalalignment='top', horizontalalignment='left',
transform=ax1.get_yaxis_transform(),
color='brown', fontsize=10)
ax1.set_xlabel('Separation Distance, r (m)')
ax1.set_ylabel('Received Power, $P_t$ (dBm)')
ax1.grid(True,which="both",ls=":")
ax1.legend()
fig2, ax2 = plt.subplots(212)
line2, = ax2.semilogx([],[], label='Monostatic Link')
hline2 = ax2.axhline(y = 0, linewidth=1.2, color='black',ls='--')
text2 = ax2.text(0, 0, "R Threshold",
verticalalignment='top', horizontalalignment='left',
transform=ax2.get_yaxis_transform(),
color='brown', fontsize=10)
ax2.set_xlabel('Separation Distance, r (m)')
ax2.set_ylabel('Received Power, $P_t$ (dBm)')
ax2.grid(True,which="both",ls=":")
ax2.legend()
def update_plot(h1, h2):
D = np.arange(0.5, 12.0, 0.0100)
r = np.sqrt((h1-h2)**2 + D**2)
freq = 865.7 #freq = 915 MHz
lmb = 300/freq
H = D**2/(D**2+2*h1*h2)
theta = 4*np.pi*h1*h2/(lmb*D)
q_e = H**2*(np.sin(theta))**2 + (1 - H*np.cos(theta))**2
q_e_rcn1 = 1
P_x_G = 4 # 4 Watt EIRP
sigma = 1.94
N_1 = np.random.normal(0,sigma,D.shape)
rnd = 10**(-N_1/10)
F = 10
y = 10*np.log10( 1000*(P_x_G*1.622*((lmb)**2) *0.5*1) / (((4*np.pi*r)**2) *1.2*1*F)*q_e*rnd*q_e_rcn1 )
line1.set_data(r,y)
hline1.set_ydata(-18)
text1.set_position((0.02, -18.5))
ax1.relim()
ax1.autoscale_view()
fig1.canvas.draw_idle()
######################################
rd =np.sqrt((h1-h2)**2 + D**2)
rd = np.sort(rd)
P_r=0.8
G_r=5 # 7dBi
q_e_rcn2 = 1
N_2 = np.random.normal(0, sigma*2, D.shape)
rnd_2 = 10**(-N_2/10)
F_2 = 126 # 21 dB for K=3dB and P_outage = 0.05
y = 10*np.log10( 1000*(P_r*(G_r*1.622)**2*(lmb)**4*0.5**2*0.25)/((4*np.pi*rd)**4*1.2**2*1**2*F_2)*
q_e**2*rnd*rnd_2*q_e_rcn1*q_e_rcn2 )
line2.set_data(rd,y)
hline2.set_ydata(-80)
text2.set_position((0.02, -80.5))
ax2.relim()
ax2.autoscale_view()
fig2.canvas.draw_idle()
r_height = widgets.FloatSlider(min=0.5, max=4, value=0.9, description= 'R_Height:')
t_height = widgets.FloatSlider(min=0.15, max=1.5, value=0.5, description= 'T_Height:')
widgets.interactive(update_plot, h1=r_height, h2=t_height)
In the second subplot, there would be a horizontal line at y=-80 with a text that should move in a similar way as the first subplot of the figure.
How could I add the second subplot using the same controls?
Regards.
This code does produce an error. The problem is that you are creating a figure with 211 (i.e. twohundredeleven) subplots. Those are stored in an array called ax1 and this array does not have a .semilogx method. Hence the error AttributeError: 'numpy.ndarray' object has no attribute 'semilogx'.
So, what you need instead is only two subplots, which you may directly unpack.
fig, (ax1, ax2) = plt.subplots(nrows=2)
The rest is basically adjusting the code for the new situation. It should then look like this:
from ipywidgets import widgets
from IPython.display import display
import matplotlib.pyplot as plt
import numpy as np
%matplotlib notebook
fig, (ax1, ax2) = plt.subplots(nrows=2)
line1, = ax1.semilogx([],[], label='Multipath')
hline1 = ax1.axhline(y = 0, linewidth=1.2, color='black',ls='--')
text1 = ax1.text(0, 0, "T Threshold",
verticalalignment='top', horizontalalignment='left',
transform=ax1.get_yaxis_transform(),
color='brown', fontsize=10)
ax1.set_xlabel('Separation Distance, r (m)')
ax1.set_ylabel('Received Power, $P_t$ (dBm)')
ax1.grid(True,which="both",ls=":")
ax1.legend()
line2, = ax2.semilogx([],[], label='Monostatic Link')
hline2 = ax2.axhline(y = 0, linewidth=1.2, color='black',ls='--')
text2 = ax2.text(0, 0, "R Threshold",
verticalalignment='top', horizontalalignment='left',
transform=ax2.get_yaxis_transform(),
color='brown', fontsize=10)
ax2.set_xlabel('Separation Distance, r (m)')
ax2.set_ylabel('Received Power, $P_t$ (dBm)')
ax2.grid(True,which="both",ls=":")
ax2.legend()
def update_plot(h1, h2):
D = np.arange(0.5, 12.0, 0.0100)
r = np.sqrt((h1-h2)**2 + D**2)
freq = 865.7 #freq = 915 MHz
lmb = 300/freq
H = D**2/(D**2+2*h1*h2)
theta = 4*np.pi*h1*h2/(lmb*D)
q_e = H**2*(np.sin(theta))**2 + (1 - H*np.cos(theta))**2
q_e_rcn1 = 1
P_x_G = 4 # 4 Watt EIRP
sigma = 1.94
N_1 = np.random.normal(0,sigma,D.shape)
rnd = 10**(-N_1/10)
F = 10
y = 10*np.log10( 1000*(P_x_G*1.622*((lmb)**2) *0.5*1) / (((4*np.pi*r)**2) *1.2*1*F)*q_e*rnd*q_e_rcn1 )
line1.set_data(r,y)
hline1.set_ydata(-18)
text1.set_position((0.02, -18.5))
ax1.relim()
ax1.autoscale_view()
######################################
rd =np.sqrt((h1-h2)**2 + D**2)
rd = np.sort(rd)
P_r=0.8
G_r=5 # 7dBi
q_e_rcn2 = 1
N_2 = np.random.normal(0, sigma*2, D.shape)
rnd_2 = 10**(-N_2/10)
F_2 = 126 # 21 dB for K=3dB and P_outage = 0.05
y = 10*np.log10( 1000*(P_r*(G_r*1.622)**2*(lmb)**4*0.5**2*0.25)/((4*np.pi*rd)**4*1.2**2*1**2*F_2)*
q_e**2*rnd*rnd_2*q_e_rcn1*q_e_rcn2 )
line2.set_data(rd,y)
hline2.set_ydata(-80)
text2.set_position((0.02, -80.5))
ax2.relim()
ax2.autoscale_view()
fig.canvas.draw_idle()
r_height = widgets.FloatSlider(min=0.5, max=4, value=0.9, description= 'R_Height:')
t_height = widgets.FloatSlider(min=0.15, max=1.5, value=0.5, description= 'T_Height:')
widgets.interactive(update_plot, h1=r_height, h2=t_height)

Resources