Animate label with bar chart - matplotlib - python

The code below animates a bar chart and associated label values. The issue I'm having is positioning the label when the integer is negative. Specifically, I want the label to be positioned on top of the bar, not inside it. It's working for the first frame but the subsequent frames of animation revert back to plotting the label inside the bar chart for negative integers.
def autolabel(rects, ax):
# Get y-axis height to calculate label position from.
ts = []
(y_bottom, y_top) = ax.get_ylim()
y_height = y_top - y_bottom
for rect in rects:
height = 0
if rect.get_y() < 0:
height = rect.get_y()
else:
height = rect.get_height()
p_height = (height / y_height)
if p_height > 0.95:
label_position = height - (y_height * 0.05) if (height > -0.01) else height + (y_height * 0.05)
else:
label_position = height + (y_height * 0.01) if (height > -0.01) else height - (y_height * 0.05)
t = ax.text(rect.get_x() + rect.get_width() / 2., label_position,
'%d' % int(height),
ha='center', va='bottom')
ts.append(t)
return ts
def gradientbars(bars, ax, cmap, vmin, vmax):
g = np.linspace(vmin,vmax,100)
grad = np.vstack([g,g]).T
xmin,xmax = ax.get_xlim()
ymin,ymax = ax.get_ylim()
ims = []
for bar in bars:
bar.set_facecolor('none')
im = ax.imshow(grad, aspect="auto", zorder=0, cmap=cmap, vmin=vmin, vmax=vmax, extent=(xmin,xmax,ymin,ymax))
im.set_clip_path(bar)
ims.append(im)
return ims
vmin = -6
vmax = 6
cmap = 'PRGn'
data = np.random.randint(-5,5, size=(10, 4))
x = [chr(ord('A')+i) for i in range(4)]
fig, ax = plt.subplots()
ax.grid(False)
ax.set_ylim(vmin, vmax)
rects = ax.bar(x,data[0])
labels = autolabel(rects, ax)
imgs = gradientbars(rects, ax, cmap=cmap, vmin=vmin, vmax=vmax)
def animate(i):
for rect,label,img,yi in zip(rects, labels, imgs, data[i]):
rect.set_height(yi)
label.set_text('%d'%int(yi))
label.set_y(yi)
img.set_clip_path(rect)
anim = animation.FuncAnimation(fig, animate, frames = len(data), interval = 500)
plt.show()

It's working for the first frame.
You call autolabel(rects, ax) in the first plot, so the label is well placed.
The subsequent frames of animation revert back to plotting the label inside the bar chart for negative integers.
The label position of subsequent frames is set by label.set_y(yi). yi is from data[i], you didn't consider the negative value here.
I create a function named get_label_position(height) to calculate the right label position for give height. It uses a global variable y_height. And call this function before label.set_y().
import matplotlib.pyplot as plt
from matplotlib import animation
import pandas as pd
import numpy as np
def get_label_position(height):
p_height = (height / y_height)
label_position = 0
if p_height > 0.95:
label_position = height - (y_height * 0.05) if (height > -0.01) else height + (y_height * 0.05)
else:
label_position = height + (y_height * 0.01) if (height > -0.01) else height - (y_height * 0.05)
return label_position
def autolabel(rects, ax):
# Get y-axis height to calculate label position from.
ts = []
(y_bottom, y_top) = ax.get_ylim()
y_height = y_top - y_bottom
for rect in rects:
height = 0
if rect.get_y() < 0:
height = rect.get_y()
else:
height = rect.get_height()
p_height = (height / y_height)
if p_height > 0.95:
label_position = height - (y_height * 0.05) if (height > -0.01) else height + (y_height * 0.05)
else:
label_position = height + (y_height * 0.01) if (height > -0.01) else height - (y_height * 0.05)
t = ax.text(rect.get_x() + rect.get_width() / 2., label_position,
'%d' % int(height),
ha='center', va='bottom')
ts.append(t)
return ts
def gradientbars(bars, ax, cmap, vmin, vmax):
g = np.linspace(vmin,vmax,100)
grad = np.vstack([g,g]).T
xmin,xmax = ax.get_xlim()
ymin,ymax = ax.get_ylim()
ims = []
for bar in bars:
bar.set_facecolor('none')
im = ax.imshow(grad, aspect="auto", zorder=0, cmap=cmap, vmin=vmin, vmax=vmax, extent=(xmin,xmax,ymin,ymax))
im.set_clip_path(bar)
ims.append(im)
return ims
vmin = -6
vmax = 6
cmap = 'PRGn'
data = np.random.randint(-5,5, size=(10, 4))
x = [chr(ord('A')+i) for i in range(4)]
fig, ax = plt.subplots()
ax.grid(False)
ax.set_ylim(vmin, vmax)
rects = ax.bar(x,data[0])
labels = autolabel(rects, ax)
imgs = gradientbars(rects, ax, cmap=cmap, vmin=vmin, vmax=vmax)
(y_bottom, y_top) = ax.get_ylim()
y_height = y_top - y_bottom
def animate(i):
for rect,label,img,yi in zip(rects, labels, imgs, data[i]):
rect.set_height(yi)
label.set_text('%d'%int(yi))
label.set_y(get_label_position(yi))
img.set_clip_path(rect)
anim = animation.FuncAnimation(fig, animate, frames = len(data), interval = 500)
plt.show()

Related

chart_pie index to generate

I need help to generate this graph, especially with domains limits
and the arrow indicating the domain
all I can do is generate the domains name but not the limits and arrow
The following code will produce something like what you require:
from matplotlib import pyplot as plt
from matplotlib.patches import Wedge
import numpy as np
labels = ["Obésité\nmassive", "Obésité", "Surpoids", "Normal", "Maigreur"]
innerlabels = [">40", "30 à 40", "25 à 30", "18,5 à 25", "< 18,5"]
colours = ["red", "darkorange", "orange", "green", "blue"]
fig, ax = plt.subplots(figsize=(6, 6), dpi=200)
theta = 0
dtheta = 180 / len(labels)
width = 0.35
def pol2cart(rho, phi):
x = rho * np.cos(phi)
y = rho * np.sin(phi)
return(x, y)
patches = []
for i in range(len(labels)):
# outer wedge
wedge = Wedge(0, r=1, width=width, theta1=theta, theta2=(theta + dtheta), fc=colours[i], alpha=0.6, edgecolor="whitesmoke")
ax.add_patch(wedge)
# inner wedge
wedge = Wedge(0, r=1 - width, width=width, theta1=theta, theta2=(theta + dtheta), fc=colours[i], edgecolor="whitesmoke")
ax.add_patch(wedge)
theta += dtheta
# add text label
tr = 1 - (width / 2)
ta = theta - dtheta / 2
x, y = pol2cart(tr, np.deg2rad(ta))
textangle = -np.fmod(90 - ta, 180)
ax.text(x, y, labels[i], rotation=textangle, va="center", ha="center", color="white", fontweight="bold")
# inner labels
tr = (1 - width) - (width / 2)
x, y = pol2cart(tr, np.deg2rad(ta))
textangle = -np.fmod(90 - ta, 180)
ax.text(x, y, innerlabels[i], rotation=textangle, va="center", ha="center", color="white")
ax.set_xlim([-1, 1])
ax.set_ylim([0, 1])
ax.set_axis_off()
ax.set_aspect("equal")
def bmiposition(bmi):
"""
Get angular position of BMI arrow.
"""
from scipy.interpolate import interp1d
bmiranges = [(0, 18.5), (18.5, 25), (25, 30), (30, 40), (40, 80)]
angrange = [(180 - dtheta * i, 180 - dtheta * (i + 1)) for i in range(len(bmiranges))]
interpfuncs = []
for i in range(len(bmiranges)):
interpfuncs.append(interp1d(bmiranges[i], angrange[i], kind="linear"))
bmiang = np.piecewise(
bmi,
[bmiranges[i][0] < bmi <= bmiranges[i][1] for i in range(len(bmiranges))],
interpfuncs,
)
return bmiang
bmi = 22.5 # set BMI
# add arrow
pos = bmiposition(bmi) # get BMI angle
x, y = pol2cart(0.25, np.deg2rad(pos))
ax.arrow(0, 0, x, y, head_length=0.125, width=0.025, fc="k")
ax.plot(0, 0, 'ko', ms=10) # circle at origin
giving:

Unable to label inside ring pie chart

The following code :
import matplotlib.pyplot as plt
import numpy as np
def autopct_format(values):
def my_format(pct):
total = sum(values)
val = int(round(pct*total/100.0)/1000000)
if val == 0:
return ""
else:
return '{:.1f}%\n({v:,d})'.format(pct, v=val)
return my_format
fig, ax = plt.subplots(1,1,figsize=(10,10),dpi=100,layout="constrained")
ax.axis('equal')
width = 0.3
#Color
A, B, C=[plt.cm.Blues, plt.cm.Reds, plt.cm.Greens]
#OUTSIDE
cin = [A(0.5),A(0.4),A(0.3),B(0.5),B(0.4),B(0.3),B(0.2),B(0.1), C(0.5),C(0.4),C(0.3)]
Labels_Smalls = ['groupA', 'groupB', 'groupC']
labels = ['A.1', 'A.2', 'A.3', 'B.1', 'B.2', 'C.1', 'C.2', 'C.3',
'C.4', 'C.5']
Sizes_Detail = [4,3,5,6,5,10,5,5,4,6]
Sizes = [12,11,30]
pie2, _ ,junk = ax.pie(Sizes_Detail ,radius=1,
labels=labels,labeldistance=0.85,
autopct=autopct_format(Sizes_Detail) ,pctdistance = 1.15,
colors=cin)
for ea, eb in zip(pie2, _):
mang =(ea.theta1 + ea.theta2)/2
tourner = 360 - mang
eb.set_rotation(mang+tourner) # rotate the label by (mean_angle + 270)
eb.set_va("center")
eb.set_ha("center")
plt.setp(pie2, width=width, edgecolor='white')
#INSIDE
pie, _, junk = ax.pie(Sizes, radius=1-width,
autopct=autopct_format(Sizes) ,pctdistance = 0.8,
colors = [A(0.6), B(0.6), C(0.6)])
plt.setp(pie, width=width, edgecolor='white')
plt.margins(0,0)
bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.72)
kw = dict(arrowprops=dict(arrowstyle="-"),
bbox=bbox_props, zorder=3, va="center")
for i, p in enumerate(pie):
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(Labels_Smalls[i], xy=(x, y), xytext=(1.35*np.sign(x), 1.4*y),
horizontalalignment=horizontalalignment, **kw)
for ea, eb in zip(pie, _):
mang =(ea.theta1 + ea.theta2)/2. # get mean_angle of the wedge
#print(mang, eb.get_rotation())
tourner = 360 - mang
eb.set_rotation(mang+tourner) # rotate the label by (mean_angle + 270)
eb.set_va("center")
eb.set_ha("center")
gives the following output :
But It does only link the outside pie and not the inside one, so how would I do the same chart but with the arrow linking the inside pie to the bbox ?

Animated arrows in matplotlib gives IndexError

I was trying to animate arrow and used the script from the following answer. Plus my animation has scatter-points and text too.
This is the script that I am using:-
from my_func import Pitch
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
## Pitch for plotting the football-pitch in the background
pitch = Pitch(line_color='grey', pitch_color='#121212', orientation='horizontal')
fig, ax = pitch.create_pitch()
x_start, y_start = (50, 35)
x_end, y_end = (90, 45)
x_1, y_1, x_2, y_2 = 50.55, 35.1375, 89.45, 44.8625
x = np.linspace(x_1, x_2, 20)
y = np.linspace(y_1, y_2, 20)
sc_1 = ax.scatter([], [], color="crimson", zorder=4, s=150, alpha=0.7, edgecolor="w")
sc_2 = ax.scatter([], [], color="crimson", zorder=4, s=150, alpha=0.7, edgecolor="w")
title = ax.text(50, 65, "", bbox={'facecolor':'w', 'alpha':0.5, 'pad':5}, ha="center")
def animate(i):
if i == 1:
sc_1.set_offsets([x_start, y_start])
title.set_text("Start of Action 01")
if i == 2:
plt.pause(0.5)
title.set_text("Action 02")
## plot arrow
## haven't included ax.cla() as it was removing the pitch in the background
if i <= len(x):
patch = plt.Arrow(x_start, y_start, x[i] - x_start, y[i] - y_start)
ax.add_patch(patch)
## plot scatter point
if i > len(x):
plt.pause(0.2)
title.set_text("Action 03")
sc_2.set_offsets([x_end, y_end])
return sc_1, patch, sc_2, title,
ani = animation.FuncAnimation(
fig=fig, func=animate, interval=50, blit=True)
plt.show()
The result is showing a little bit of animation and then giving me the following error:
File "test.py", line 33, in animate
patch = plt.Arrow(x_start, y_start, x[i] - x_start, y[i] - y_start)
IndexError: index 20 is out of bounds for axis 0 with size 20
[1] 14116 abort (core dumped) python test.py
I am a begineer in matplotlib animations and don't know how to solve this error what should I change in my code to remove the error and generate the animated output.
The modifications are as follows: 1) plt.axes() is added to ax. 2) The return value of the animation function is set to ax. 3) The number of objects to be animated is 20, so frames=20 is used.
# from my_func import Pitch
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
## Pitch for plotting the football-pitch in the background
# pitch = Pitch(line_color='grey', pitch_color='#121212', orientation='horizontal')
# fig, ax = pitch.create_pitch()
fig,ax = plt.subplots()
x_start, y_start = 50, 35
x_end, y_end = 90, 45
x_1, y_1, x_2, y_2 = 50.55, 35.1375, 89.45, 44.8625
x = np.linspace(x_1, x_2, 20)
y = np.linspace(y_1, y_2, 20)
ax = plt.axes(xlim=(50, 90), ylim=(35, 45))
sc_1 = ax.scatter([], [], color="crimson", zorder=1, s=150, alpha=0.7, edgecolor="w")
sc_2 = ax.scatter([], [], color="crimson", zorder=1, s=150, alpha=0.7, edgecolor="w")
title = ax.text(50, 65, "", bbox={'facecolor':'w', 'alpha':0.5, 'pad':5}, ha="center")
def animate(i):
if i == 1:
sc_1.set_offsets([x_start, y_start])
title.set_text("Start of Action 01")
if i == 2:
plt.pause(0.5)
title.set_text("Action 02")
## plot arrow
## haven't included ax.cla() as it was removing the pitch in the background
if i <= len(x):
ax.clear()
#patch = plt.Arrow(x_start, y_start, x[i] - x_start, y[i] - y_start)
#ax.add_patch(patch)
ax.arrow(x_start, y_start, x[i]-x_start, y[i]-y_start, head_width=1, head_length=1, fc='black', ec='black')
ax.set(xlim=(50,90), ylim=(35,45))
## plot scatter point
if i == len(x)-1:
plt.pause(0.2)
title.set_text("Action 03")
sc_2.set_offsets([x_end, y_end])
return sc_1, sc_2, title, ax
ani = animation.FuncAnimation(fig=fig, func=animate, frames=20, interval=200, repeat=False, blit=True)
plt.show()

Is there a way in matplotlib to check which artists are in the currently displayed area of the axes?

I have a program with an interactive figure where occasionally many artists are drawn. In this figure, you can also zoom and pan using the mouse. However, the performace during zooming an panning is not very good because every artist is always redrawn. Is there a way to check which artists are in the currently displayed area and only redraw those? (In the example below the perfomace is still relatively good, but it can be made arbitrarily worse by using more or more complex artists)
I had a similar performace problem with the hover method that whenever it was called it ran canvas.draw() at the end. But as you can see I found a neat workaround for that by making use of caching and restoring the background of the axes (based on this). This significantly improved the performace and now even with many artists it runs very smooth. Maybe there is a similar way of doing this but for the pan and zoom method?
Sorry for the long code sample, most of it is not directly relevant for the question but necessary for a working example to highlight the issue.
EDIT
I updated the MWE to something that is more representative of my actual code.
import numpy as np
import sys
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import \
FigureCanvasQTAgg
import matplotlib.patheffects as PathEffects
from matplotlib.text import Annotation
from matplotlib.collections import LineCollection
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QDialog
def check_limits(base_xlim, base_ylim, new_xlim, new_ylim):
if new_xlim[0] < base_xlim[0]:
overlap = base_xlim[0] - new_xlim[0]
new_xlim[0] = base_xlim[0]
if new_xlim[1] + overlap > base_xlim[1]:
new_xlim[1] = base_xlim[1]
else:
new_xlim[1] += overlap
if new_xlim[1] > base_xlim[1]:
overlap = new_xlim[1] - base_xlim[1]
new_xlim[1] = base_xlim[1]
if new_xlim[0] - overlap < base_xlim[0]:
new_xlim[0] = base_xlim[0]
else:
new_xlim[0] -= overlap
if new_ylim[1] < base_ylim[1]:
overlap = base_ylim[1] - new_ylim[1]
new_ylim[1] = base_ylim[1]
if new_ylim[0] + overlap > base_ylim[0]:
new_ylim[0] = base_ylim[0]
else:
new_ylim[0] += overlap
if new_ylim[0] > base_ylim[0]:
overlap = new_ylim[0] - base_ylim[0]
new_ylim[0] = base_ylim[0]
if new_ylim[1] - overlap < base_ylim[1]:
new_ylim[1] = base_ylim[1]
else:
new_ylim[1] -= overlap
return new_xlim, new_ylim
class FigureCanvas(FigureCanvasQTAgg):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bg_cache = None
def draw(self):
ax = self.figure.axes[0]
hid_annotation = False
if ax.annot.get_visible():
ax.annot.set_visible(False)
hid_annotation = True
hid_highlight = False
if ax.last_artist:
ax.last_artist.set_path_effects([PathEffects.Normal()])
hid_highlight = True
super().draw()
self.bg_cache = self.copy_from_bbox(self.figure.bbox)
if hid_highlight:
ax.last_artist.set_path_effects(
[PathEffects.withStroke(
linewidth=7, foreground="c", alpha=0.4
)]
)
ax.draw_artist(ax.last_artist)
if hid_annotation:
ax.annot.set_visible(True)
ax.draw_artist(ax.annot)
if hid_highlight:
self.update()
def position(t_, coeff, var=0.1):
x_ = np.random.normal(np.polyval(coeff[:, 0], t_), var)
y_ = np.random.normal(np.polyval(coeff[:, 1], t_), var)
return x_, y_
class Data:
def __init__(self, times):
self.length = np.random.randint(1, 20)
self.t = np.sort(
np.random.choice(times, size=self.length, replace=False)
)
self.vel = [np.random.uniform(-2, 2), np.random.uniform(-2, 2)]
self.accel = [np.random.uniform(-0.01, 0.01), np.random.uniform(-0.01,
0.01)]
x0, y0 = np.random.uniform(0, 1000, 2)
self.x, self.y = position(
self.t, np.array([self.accel, self.vel, [x0, y0]])
)
class Test(QDialog):
def __init__(self):
super().__init__()
self.fig, self.ax = plt.subplots()
self.canvas = FigureCanvas(self.fig)
self.artists = []
self.zoom_factor = 1.5
self.x_press = None
self.y_press = None
self.annot = Annotation(
"", xy=(0, 0), xytext=(-20, 20), textcoords="offset points",
bbox=dict(boxstyle="round", fc="w", alpha=0.7), color='black',
arrowprops=dict(arrowstyle="->"), zorder=6, visible=False,
annotation_clip=False, in_layout=False,
)
self.annot.set_clip_on(False)
setattr(self.ax, 'annot', self.annot)
self.ax.add_artist(self.annot)
self.last_artist = None
setattr(self.ax, 'last_artist', self.last_artist)
self.image = np.random.uniform(0, 100, 1000000).reshape((1000, 1000))
self.ax.imshow(self.image, cmap='gray', interpolation='nearest')
self.times = np.linspace(0, 20)
for i in range(1000):
data = Data(self.times)
points = np.array([data.x, data.y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
z = np.linspace(0, 1, data.length)
norm = plt.Normalize(z.min(), z.max())
lc = LineCollection(
segments, cmap='autumn', norm=norm, alpha=1,
linewidths=2, picker=8, capstyle='round',
joinstyle='round'
)
setattr(lc, 'data_id', i)
lc.set_array(z)
self.ax.add_artist(lc)
self.artists.append(lc)
self.default_xlim = self.ax.get_xlim()
self.default_ylim = self.ax.get_ylim()
self.canvas.draw()
self.cid_motion = self.fig.canvas.mpl_connect(
'motion_notify_event', self.motion_event
)
self.cid_button = self.fig.canvas.mpl_connect(
'button_press_event', self.pan_press
)
self.cid_zoom = self.fig.canvas.mpl_connect(
'scroll_event', self.zoom
)
layout = QVBoxLayout()
layout.addWidget(self.canvas)
self.setLayout(layout)
def zoom(self, event):
if event.inaxes == self.ax:
scale_factor = np.power(self.zoom_factor, -event.step)
xdata = event.xdata
ydata = event.ydata
cur_xlim = self.ax.get_xlim()
cur_ylim = self.ax.get_ylim()
x_left = xdata - cur_xlim[0]
x_right = cur_xlim[1] - xdata
y_top = ydata - cur_ylim[0]
y_bottom = cur_ylim[1] - ydata
new_xlim = [
xdata - x_left * scale_factor, xdata + x_right * scale_factor
]
new_ylim = [
ydata - y_top * scale_factor, ydata + y_bottom * scale_factor
]
# intercept new plot parameters if they are out of bounds
new_xlim, new_ylim = check_limits(
self.default_xlim, self.default_ylim, new_xlim, new_ylim
)
if cur_xlim != tuple(new_xlim) or cur_ylim != tuple(new_ylim):
self.ax.set_xlim(new_xlim)
self.ax.set_ylim(new_ylim)
self.canvas.draw_idle()
def motion_event(self, event):
if event.button == 1:
self.pan_move(event)
else:
self.hover(event)
def pan_press(self, event):
if event.inaxes == self.ax:
self.x_press = event.xdata
self.y_press = event.ydata
def pan_move(self, event):
if event.inaxes == self.ax:
xdata = event.xdata
ydata = event.ydata
cur_xlim = self.ax.get_xlim()
cur_ylim = self.ax.get_ylim()
dx = xdata - self.x_press
dy = ydata - self.y_press
new_xlim = [cur_xlim[0] - dx, cur_xlim[1] - dx]
new_ylim = [cur_ylim[0] - dy, cur_ylim[1] - dy]
# intercept new plot parameters that are out of bound
new_xlim, new_ylim = check_limits(
self.default_xlim, self.default_ylim, new_xlim, new_ylim
)
if cur_xlim != tuple(new_xlim) or cur_ylim != tuple(new_ylim):
self.ax.set_xlim(new_xlim)
self.ax.set_ylim(new_ylim)
self.canvas.draw_idle()
def update_annot(self, event, artist):
self.ax.annot.xy = (event.xdata, event.ydata)
text = f'Data #{artist.data_id}'
self.ax.annot.set_text(text)
self.ax.annot.set_visible(True)
self.ax.draw_artist(self.ax.annot)
def hover(self, event):
vis = self.ax.annot.get_visible()
if event.inaxes == self.ax:
ind = 0
cont = None
while (
ind in range(len(self.artists))
and not cont
):
artist = self.artists[ind]
cont, _ = artist.contains(event)
if cont and artist is not self.ax.last_artist:
if self.ax.last_artist is not None:
self.canvas.restore_region(self.canvas.bg_cache)
self.ax.last_artist.set_path_effects(
[PathEffects.Normal()]
)
self.ax.last_artist = None
artist.set_path_effects(
[PathEffects.withStroke(
linewidth=7, foreground="c", alpha=0.4
)]
)
self.ax.last_artist = artist
self.ax.draw_artist(self.ax.last_artist)
self.update_annot(event, self.ax.last_artist)
ind += 1
if vis and not cont and self.ax.last_artist:
self.canvas.restore_region(self.canvas.bg_cache)
self.ax.last_artist.set_path_effects([PathEffects.Normal()])
self.ax.last_artist = None
self.ax.annot.set_visible(False)
elif vis:
self.canvas.restore_region(self.canvas.bg_cache)
self.ax.last_artist.set_path_effects([PathEffects.Normal()])
self.ax.last_artist = None
self.ax.annot.set_visible(False)
self.canvas.update()
self.canvas.flush_events()
if __name__ == '__main__':
app = QApplication(sys.argv)
test = Test()
test.show()
sys.exit(app.exec_())
You can find which artists are in the current area of the axes if you focus on the data the artists are plotting.
For example if you put your points data (a and b arrays) in a numpy array like this:
self.points = np.random.randint(0, 100, (1000, 2))
you can get the list of points inside the current x and y limits:
xmin, xmax = self.ax.get_xlim()
ymin, ymax = self.ax.get_ylim()
p = self.points
indices_of_visible_points = (np.argwhere((p[:, 0] > xmin) & (p[:, 0] < xmax) & (p[:, 1] > ymin) & (p[:, 1] < ymax))).flatten()
you can use indices_of_visible_points to index your related self.artists list

My circles are not matching up with my planets orbits

My planets orbits are not matching up with my circular paths im plotting of the orbits, I have matched in my for loop the distance for every planet with the distance they are from the center.(image is attached)
from pylab import *
from matplotlib.animation import *
Here is where I am defining the circes I will plot to show the paths of the orbits at the "def circle..."
def circle(x0,y0,R,N) :
''' create circle, given center x0,y0, input radius R,
and number of points N, then
output arrays of x and y coordinates '''
theta = linspace(0.0,2.0*pi,N)
x = R * cos(theta) + x0
y = R * sin(theta) + y0
return x,y
a= array([.39,.72,1,1.52,5.20,9.54,19.2,30.1]) #astronomical units of #the planets in order of the planets(i.e mercury, venus, earth,mars...)
period = a**(3.0/2.0)
I am taking the distane of the planets and inputting them in array, to be able to use a for loop to graph the circles.
#distance of the planets
d= array([390,790,980, 1520,5200,9540,19200,30100])#same order of the #planets as well
#attributes of the sun
x0 =0
y0 = 0
r_s=70*1.2#actual earth radius is 695e6
#radius of the planets
r_Ear = 63.781#e in m {for all the planets}
r_Merc= 24
r_Ven = 60
r_Mars= 33.9
r_Jup = 700
r_Sat = 582
r_Ura = 253
r_Nep = 246
the actual distance of the planets
#Distance of the planets
d_Ear = 1000#152
d_Merc= 390#70
d_Ven = 790#109
d_Mars= 1520#249
d_Jup = 5200#816
d_Sat = 9540#1514
d_Ura = 19200#3003
d_Nep = 30100#4545
fig = plt.figure()
ax = plt.axes(xlim=(-1e4-5000, 1e4+5000), ylim=(-1e4-5000, 1e4+5000), aspect=True)
This is where I am looping over, to plot 8 circles. Theres a link here to see the plot I have generated.
for i in range(8) :
x, y = circle(0.0,0.0,d[i],10000) # orbit
plot(x,y,':k')
[enter image description here][1]
The rest are the patches of the planets and the FuncAnimation.
Sun = plt.Circle((x0, y0), radius=r_s, ec='yellow', fc='yellow', lw=3)
Mercury = plt.Circle((0, 0), radius=r_Merc, ec='brown', fc='brown', lw=3)
Venus = plt.Circle((0, 0), radius=r_Ven, ec='brown', fc='brown', lw=3)
Earth = plt.Circle((0, 0), radius=r_Ear, ec='black', fc='black', lw=3)
Mars = plt.Circle((0, 0), radius=r_Mars, ec='brown', fc='brown', lw=3)
Jupiter = plt.Circle((0, 0), radius=r_Jup, ec='green', fc='green', lw=3)
Saturn = plt.Circle((0, 0), radius=r_Sat, ec='green', fc='green', lw=3)
Uranus = plt.Circle((0, 0), radius=r_Ura, ec='green', fc='green', lw=3)
Neptune = plt.Circle((0, 0), radius=r_Nep, ec='green', fc='green', lw=3)
ax.add_patch(Sun)
def init():
ax.add_patch(Earth)
ax.add_patch(Mercury)
ax.add_patch(Venus)
ax.add_patch(Mars)
ax.add_patch(Jupiter)
ax.add_patch(Saturn)
ax.add_patch(Uranus)
ax.add_patch(Neptune)
return Mercury, Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune,
def animate(i):
theta = radians(i)
mx = d_Merc*np.cos(theta/period[0]) - d_Merc*np.sin(theta/period[0])
my = d_Merc*np.sin(theta/period[0]) + d_Merc*np.cos(theta/period[0])
vx = d_Ven *np.cos(theta/period[1]) - d_Ven*np.sin(theta/period[1])
vy = d_Ven *np.cos(theta/period[1]) + d_Ven*np.sin(theta/period[1])
ex = d_Ear*np.cos(theta/period[2]) - d_Ear*np.sin(theta/period[2])
ey = d_Ear*np.sin(theta/period[2]) + d_Ear*np.cos(theta/period[2])
Mx = d_Mars*np.cos(theta/period[3]) - d_Mars*np.sin(theta/period[3])
My = d_Mars*np.sin(theta/period[3]) + d_Mars*np.cos(theta/period[3])
Jx = d_Jup*np.cos(theta/period[4]) - d_Jup*np.sin(theta/period[4])
Jy = d_Jup*np.sin(theta/period[4]) + d_Jup*np.cos(theta/period[4])
Sx = d_Sat*np.cos(theta/period[5]) - d_Sat*np.sin(theta/period[5])
Sy = d_Sat*np.sin(theta/period[5]) + d_Sat*np.cos(theta/period[5])
Ux = d_Ura*np.cos(theta/period[6]) - d_Ura*np.sin(theta/period[6])
Uy = d_Ura*np.sin(theta/period[6]) + d_Ura*np.cos(theta/period[6])
Nx = d_Nep*np.cos(theta/period[7]) - d_Nep*np.sin(theta/period[7])
Ny = d_Nep*np.sin(theta/period[7]) + d_Nep*np.cos(theta/period[7])
Mercury.center = (mx, my)
Mercury._angle = i
Venus.center = (vx, vy)
Venus._angle = i
Earth.center = (ex, ey)
Earth._angle = i
Mars.center = (Mx, My)
Mars.angle =i
Jupiter.center = (Jx, Jy)
Jupiter._angle = i
Saturn.center = (Sx, Sy)
Saturn._angle = i
Uranus.center = (Ux, Uy)
Uranus.angle = i
Neptune.center = (Nx, Ny)
Neptune._angle = i
return Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune,
anim = FuncAnimation(fig, animate, init_func=init, frames=1080,
interval=25, blit=True)
plt.show()
I'm not following your math on this:
mx = d_Merc*np.cos(theta/period[0]) - d_Merc*np.sin(theta/period[0])
my = d_Merc*np.sin(theta/period[0]) + d_Merc*np.cos(theta/period[0])
You'll be more correct if you change that to this:
mx = d_Merc*np.cos(theta/period[0])
my = d_Merc*np.sin(theta/period[0])
And even more correct if you take the size of the planet into account
mx = (d_Merc+(r_Merc/2))*np.cos(theta/period[0])
my = (d_Merc+(r_Merc/2))*np.sin(theta/period[0])
That will fix your basic issue I think.
Beyond that:
d_Ear differs from Earth's value in the d array. 980 vs. 1000.
vy = d_Ven *np.cos(theta/period[1]) is not correct. That needs
to be vy = d_Ven *np.sin(theta/period[1])
As far as the code goes, you may consider using a few dictionaries to
avoid repetition like with your d array.

Categories

Resources