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
Related
I have built a python script to plot an image file, it works fine. When I want to plot multiple image files within a range, I am able to create multiple image files. The issue I am facing - the first image files comes out fine. The second, third and there after all image files are overlapped with the previous image files. What is that I am doing wrong. Appreciate some help, I am new to Python.
df4 = ((df3 + 1).cumprod() - 1)
for row_no in range(2, 10):
df5 = df4.iloc[:row_no,:]
rcParams['figure.figsize']=25,10
plt.scatter(df5.mean(), df5.std())
plt.vlines(df5['SPY'].mean(), ymin = -0.01, ymax = 0.10, colors = 'blue')
plt.hlines(df5['SPY'].std(), xmin = -0.10, xmax = 0.10, colors = 'blue')
plt.tight_layout()
plt.xlabel('Expected returns')
plt.ylabel('Risk')
plt.title("Watchlist Today Performance")
plt.xlim([-0.1, 0.1])
plt.ylim([-0.01, 0.1])
for label, x, y in zip(df5.columns, df5.mean(), df5.std()):
#plt.annotate(label, xy = (x, y), xytext = (20, -20), textcoords = 'offset points', ha = 'right', va = 'bottom', bbox = dict(boxstyle = 'round,pad=0.5', fc = 'red', alpha = 0.5), arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0'))
plt.annotate(label, xy = (x, y), xytext = (20, -20), textcoords = 'offset points', ha = 'right', va = 'bottom', bbox = dict(boxstyle = 'round,pad=0.5', fc = 'red', alpha = 0.5))
plt.savefig('/content/drive/MyDrive/Images/jan26/image.png')
i = 0
while os.path.exists("/content/drive/MyDrive/Images/jan26/image-%s.png" % i):
i += 1
with open('/content/drive/MyDrive/Images/jan26/image.png', 'rb') as f:
data = f.read()
with open('/content/drive/MyDrive/Images/jan26/image-%s.png' % i, 'wb') as f:
f.write(data)
First Image file
Fifth Image File
Final Image File
How to fill between two lines with different x and y? Now, the filling is for two y functions with the common x-axis, which is not true. When I tried x1, x2, y1, y2 I have got a worse result than displayed below.
import matplotlib.pyplot as plt
import numpy as np
from numpy import exp, sin
def g(y):
amp = 0.6
return amp*exp(-2.5*y)*sin(9.8*y)
def g_e(y):
amp = 0.66
return amp*exp(-2.5*y_e)*sin(8.1*y_e)
y = np.linspace(0, 0.83, 501)
y_e = np.linspace(0, 1.08, 501)
values = g(y)
values_e = g_e(y)
theta = np.radians(-65.9)
c, s = np.cos(theta), np.sin(theta)
rot_matrix = np.array(((c, s), (-s, c)))
xy = np.array([y, values]).T # rot_matrix
theta_e = np.radians(-60)
c_e, s_e = np.cos(theta_e), np.sin(theta_e)
rot_matrix_e = np.array(((c_e, s_e), (-s_e, c_e)))
xy_e = np.array([y, values_e]).T # rot_matrix_e
fig, ax = plt.subplots(figsize=(5,5))
ax.axis('equal')
x_shift = 0.59
y_shift = 0.813
x_shift_e = 0.54
y_shift_e = 0.83
ax.plot(xy[:, 0]+x_shift, xy[:, 1]+y_shift, c='red')
ax.plot(xy_e[:, 0]+x_shift_e, xy_e[:, 1]+y_shift_e, c='black')
ax.fill_between(xy[:, 0]+x_shift, xy[:, 1]+y_shift, xy_e[:, 1]+y_shift_e)
plt.show()
Script for additional question:
for i in range(len(x)-1):
for j in range(i-1):
xs_ys = intersection(x[i],x[i+1],x[j],x[j+1],y[i],y[i+1],y[j],y[j+1])
if xs_ys in not None:
xs.append(xs_ys[0])
ys.append(xs_ys[1])
I got an error:
if xs_ys in not None:
^
SyntaxError: invalid syntax
Here is an approach creating a "polygon" by concatenating the reverse of one curve to the other curve. ax.fill() can be used to fill the polygon. Note that fill_between() can look strange when the x-values aren't nicely ordered (as is the case here after the rotation). Also, the mirror function fill_betweenx() wouldn't be adequate in this case.
import matplotlib.pyplot as plt
import numpy as np
def g(y):
amp = 0.6
return amp * np.exp(-2.5 * y) * np.sin(9.8 * y)
def g_e(y):
amp = 0.66
return amp * np.exp(-2.5 * y_e) * np.sin(8.1 * y_e)
y = np.linspace(0, 0.83, 501)
y_e = np.linspace(0, 1.08, 501)
values = g(y)
values_e = g_e(y)
theta = np.radians(-65.9)
c, s = np.cos(theta), np.sin(theta)
rot_matrix = np.array(((c, s), (-s, c)))
xy = np.array([y, values]).T # rot_matrix
theta_e = np.radians(-60)
c_e, s_e = np.cos(theta_e), np.sin(theta_e)
rot_matrix_e = np.array(((c_e, s_e), (-s_e, c_e)))
xy_e = np.array([y, values_e]).T # rot_matrix_e
fig, ax = plt.subplots(figsize=(5, 5))
ax.axis('equal')
x_shift = 0.59
y_shift = 0.813
x_shift_e = 0.54
y_shift_e = 0.83
xf = np.concatenate([xy[:, 0] + x_shift, xy_e[::-1, 0] + x_shift_e])
yf = np.concatenate([xy[:, 1] + y_shift, xy_e[::-1, 1] + y_shift_e])
ax.plot(xy[:, 0] + x_shift, xy[:, 1] + y_shift, c='red')
ax.plot(xy_e[:, 0] + x_shift_e, xy_e[:, 1] + y_shift_e, c='black')
ax.fill(xf, yf, color='dodgerblue', alpha=0.3)
plt.show()
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')
How to edit this code to have the same width and colour map as in the following figure? The script is based on this question.
import numpy as np
import matplotlib.pyplot as plt
dpi = 100
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,:]
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]
x = np.linspace(-3, 3, 100)
y = -(1/4*x**4 - 1.6*x**2)
fig, ax=plt.subplots(figsize=(4,2.5), dpi=dpi)
cmap = plt.get_cmap('Greys_r')
lw = 2.
lines = []
width_l = ax.get_ylim()[1] - ax.get_ylim()[0]
for t in np.linspace(0, 1, 40):
l, = ax.plot(x, y - t * 0.1 * width_l, color=cmap(t/2 + 0.25))
lines.append(l)
def plot_rainbow(event=None):
# initialization of lists
xr, yr = 6*[None], 6*[None]
xr[0],yr[0] = offset_curve(ax, x,y, lw/2.)
xr[1],yr[1] = offset_curve(ax, x,y, -lw/2.)
xr[2],yr[2] = offset_curve(ax, xr[0],yr[0], lw)
xr[3],yr[3] = offset_curve(ax, xr[1],yr[1], -lw)
xr[4],yr[4] = offset_curve(ax, xr[2],yr[2], lw)
xr[5],yr[5] = offset_curve(ax, xr[3],yr[3], -lw)
for i in range(6):
lines[i].set_data(xr[i], yr[i])
plot_rainbow()
fig.canvas.mpl_connect("resize_event", plot_rainbow)
fig.canvas.mpl_connect("button_release_event", plot_rainbow)
plt.show()
The figure above was created by the following script:
import numpy as np
import matplotlib.pyplot as plt
import math
dpi = 100
# 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
fig, ax=plt.subplots(figsize=(4,2.5), dpi=dpi)
cmap = plt.get_cmap('Greys_r')
x = np.linspace(-1, 1, 100)
y = -x**2
ax.set_ylim(-1.02, 0.3)
ax.scatter(1/2*(ax.get_xlim()[0] + ax.get_xlim()[1]), 0.145, marker = 'o', s=900, facecolors='none')
width_l = ax.get_ylim()[1] - ax.get_ylim()[0]
for t in np.linspace(0, 1, 40):
length = -0.1*width_l*t
ax.plot(*get_parallels(length=length), color=cmap(t/2 + 0.25))
plt.tight_layout()
plt.show()
Several curves are plotted in camp and the length is set.
I would like to have the same "shadow" for the curve in the first scrip. How to do that, please?
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()