Animating two figures with different data units using matplotlib - python

Trying to plot two separate animations, i.e. in different windows as separate figures. Running this code for me rightly creates two windows, but animates the data on the second figure at the same time. Closing figure 1 results in only the intended data for figure 2 being animated, removing the overlap from the data intended for figure 1. Closing figure 2 results in only the intended data for figure 1 being animated, removing the overlap from the data intended for figure 2.
Minimum code below:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
dx, dv, N, Nb, decp = 2, 1.5, 100, 12, int(1)
Pd = np.zeros([N + 1, 2 * Nb])
Vd = np.zeros([N + 1, 2 * Nb])
Pd[:, 1] = 4
Vd[:, 3] = 2
t = np.zeros(N + 1)
t[0] = 0
for i in range(0, N):
t[i + 1] = (i + 1) * 0.1
Px = []
for i in range(0, (2 * Nb)):
PX = dx * (-Nb + i) / 4
Px.append(PX)
lblx = []
for i in range(0, int((Nb / 2) + 1)):
if i == (Nb / 4):
LBL = r"$\mu_x$"
lblx.append(LBL)
else:
LBL = r"${0}\sigma_x$".format(-(Nb / 4) + i)
lblx.append(LBL)
Pv = []
for i in range(0, (2 * Nb)):
PV = dv * (-Nb + i) / 4
Pv.append(PV)
lblv = []
for i in range(0, int((Nb / 2) + 1)):
if i == (Nb / 4):
LBL = r"$\mu_v$"
lblv.append(LBL)
else:
LBL = r"${0}\sigma_v$".format(-(Nb / 4) + i)
lblv.append(LBL)
fig1 = plt.figure(figsize=(8,6))
def animatex(i):
fig1.clear()
plt.bar(Px, Pd[i, :], width = dx / 4, align = 'edge', color = 'b', \
label = 't = {} seconds'.format(round(t[i], decp)))
s_ticks = np.arange(-3 * dx, (3 + 1) * dx, dx)
plt.xticks(s_ticks, lblx)
plt.ylim(0, np.max(Pd))
plt.xlim(-3 * dx, 3 * dx)
plt.legend()
plt.draw()
anix = FuncAnimation(fig1, animatex, repeat = True, interval = 200, frames = N + 1)
fig2 = plt.figure(figsize=(8,6))
def animatev(i):
fig2.clear()
plt.bar(Pv, Vd[i, :], width = dv / 4, align = 'edge', color = 'b', \
label = 't = {} seconds'.format(round(t[i], decp)))
s_ticks = np.arange(-3 * dv, (3 + 1) * dv, dv)
plt.xticks(s_ticks, lblv)
plt.ylim(0, np.max(Vd))
plt.xlim(-3 * dv, 3 * dv)
plt.legend()
plt.draw()
aniv = FuncAnimation(fig2, animatev, repeat = True, interval = 200, frames = N + 1)
plt.show()
As is probably clear, they are two bar plots, with different vertical and horizontal dimensions. I've seen some solutions for these kinds of problems where the data shares an axis through a shared variable, but here they are not (as can be seen).
For this minimum code, the solution involves having the two bars, one in Pd and the other in Vd, being on their respective intended figures, not both on the second figure.
Let me know if there are any issues with the information here i.e. minimal code requirements not met, more information etc. and I will update.
Ignore any wayward writing style, it is not relevant.

Simplifying your code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
dx, dv, N, Nb, decp = 2, 1.5, 10, 12, int(1)
Px = np.arange(Nb)
Pd = np.random.randn(N, Nb)
Vd = np.random.randn(N, Nb)
fig1, ax1 = plt.subplots(figsize=(8, 6))
def animatex(i):
ax1.clear()
ax1.bar(Px, Pd[i, :], width=dx / 4, align='edge', color='b')
anix = FuncAnimation(fig1, animatex, repeat=True, interval=200, frames=N)
fig2, ax2 = plt.subplots(figsize=(8, 6))
def animatev(i):
ax2.clear()
ax2.bar(Px, Vd[i, :], width = dv / 4, align='edge', color='b')
aniv = FuncAnimation(fig2, animatev, repeat=True, interval=200, frames=N)
plt.show()
works fine for me. You can add the esthetic/data details back in...

Related

How to add common Tittle, label and legend in Matplotlib.pyplot?

My following code can't add a common title, common legend also a common x and y label. When I add title, legend in between the for loop it works but its not working for the whole figure?
I want to add a common title, common legend, and shared x and y label in this plot. What is the best way to do that?
My code
#position vs Time
h = 0.05
nrow, ncol, count = 2,4,1
plt.figure(figsize=(20,10))
plt.title("Common Title") #does not working
with plt.style.context('ggplot'):
for inv in range(8):
plt.subplot(nrow, ncol, count)
x = [1]
v = [0]
rk4_x = [1] #position
rk4_v = [0] #velocity
t = np.arange(0, 30, 0.1)
for i in range(len(t)-1):
k1 = h*v[i]
l1 = h*-(x[i])
k2 = h*(v[i]+l1)
l2 = h*-(x[i]+k1)
x_value = x[i]+0.5*(k1+k2)
x.append(x_value)
v_value = v[i]+0.5*(l1+l2)
v.append(v_value)
k1 = h*rk4_v[i]
l1 = h*-(rk4_x[i])
k2 = h*(rk4_v[i]+0.5*l1)
l2 = h*-(rk4_x[i]+0.5*k1)
k3 = h*(rk4_v[i]+0.5*l2)
l3 = h*-(rk4_x[i]+0.5*k2)
k4 = h*(rk4_v[i]+l3)
l4 = h*-(rk4_x[i]+k3)
x_value = rk4_x[i]+(1/6)*(k1+2*k2+2*k3+k4)
rk4_x.append(x_value)
v_value = rk4_v[i]+(1/6)*(l1+2*l2+2*l3+l4)
rk4_v.append(v_value)
# with plt.style.context('ggplot'):
# plt.figure()
plt.plot(t, x,'r', label= "Position vs Time RK2")
plt.plot(t, rk4_x, 'k--', label = "Position vs Time RK4")
plt.ylim(-1.5, 1.5)
h = round(h,3)
plt.title("h="+str(h), fontsize= '11')
plt.grid('on')
plt.tick_params(labelcolor='none', which='both', top=False, bottom=False, left=False, right=False)
plt.xlabel('Time')
plt.ylabel('Position')
plt.legend()
h = h + 0.05
count = count+1
Use subplots with fig, axs. See the document on matplotlib.pyplot.subplots
And you should use Numpy array not the python list to speed up the numerical calculation. (Not this example, but generally).
Also, If the amount of calculation increases, it seems necessary to make the formula simple. Like x[i+1] = (1 - 0.5 * h**2) * x[i]+ h * v[i]
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Agg')
plt.style.use('ggplot')
def get_x_v(h, t):
x = np.ones_like(t)
v = np.zeros_like(t)
rk4_x = np.ones_like(t)
rk4_v = np.zeros_like(t)
for i, _ in enumerate(t[:-1]):
k1 = h * (v[i])
l1 = h * (x[i]) * -1
k2 = h * (v[i] + l1)
l2 = h * (x[i] + k1) * -1
x[i+1] = x[i] + 0.5 * (k1 + k2)
v[i+1] = v[i] + 0.5 * (l1 + l2)
rk1 = h * (rk4_v[i])
rl1 = h * (rk4_x[i]) * -1
rk2 = h * (rk4_v[i] + 0.5 * rl1)
rl2 = h * (rk4_x[i] + 0.5 * rk1) * -1
rk3 = h * (rk4_v[i] + 0.5 * rl2)
rl3 = h * (rk4_x[i] + 0.5 * rk2) * -1
rk4 = h * (rk4_v[i] + rl3)
rl4 = h * (rk4_x[i] + rk3) * -1
rk4_x[i+1] = rk4_x[i]+(1/6)*(rk1+2*rk2+2*rk3+rk4)
rk4_v[i+1] = rk4_v[i]+(1/6)*(rl1+2*rl2+2*rl3+rl4)
return x, v, rk4_x, rk4_v
h = 0.05
ncol, nrow = 4, 2
fig, axs = plt.subplots(nrows=nrow, ncols=ncol, figsize=(20, 10))
fig.suptitle('Common Title', y=0.94, fontsize=20)
fig.supxlabel('Common X Label', y=0.05, fontsize=14)
t = np.arange(0, 30, 0.1)
for ax in axs.flat:
x, v, rk4_x, rk_v = get_x_v(h, t)
ax.plot(t, x, 'r', label="Position vs Time RK2")
ax.plot(t, rk4_x, 'k--', label="Position vs Time RK4")
ax.set_ylim(-1.5, 1.5)
ax.set_title('h={0}'.format(str(round(h,3))), fontsize='11')
ax.grid('on')
ax.tick_params(labelcolor='none', which='both',
top=False, bottom=False, left=False, right=False)
ax.set_xlabel('Time')
ax.set_ylabel('Position')
ax.legend(loc=1)
ax.set_facecolor('gray')
h += 0.05
plt.savefig('test.png')

How to avoid plotting a line through a given point in Matplotlib?

I made an animation of an orbit that plots an orbit using an initial altitude and velocity.
Here is the code:
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import ode
import matplotlib.animation as animation
plt.style.use('dark_background')
mu_earth = 398600
# Planet radius, km
R = 6371
fig = plt.figure()
ax1 = fig.add_subplot(111)
orbit_alt = 500
v_mag = 7.7 # km/s
# time step (delta-time)
dt = 10
satellite_size = 100
r_mag = R + orbit_alt
v_esc = np.sqrt(2*mu_earth*1e9/(R*1000 + orbit_alt*1000))
earth = plt.Circle((0, 0), 6371, color='blue')
ax1.add_artist(earth)
def diff_eq(t, y):
rx, ry, vx, vy = y # state vectors
r = np.array([rx, ry])
radial_vector = np.linalg.norm(r)
ax, ay = -r * mu_earth / radial_vector ** 3
return [vx, vy, ax, ay]
# Initial states
r0 = [r_mag, 0]
v0 = [0, v_mag]
specific_energy = (v_mag * 1000 * v_mag * 1000) / 2 - ((mu_earth * 1e9) / ((R + orbit_alt) * 1000))
semi_major = -mu_earth * 1e9 / (2 * specific_energy)
orbit_period = 2 * np.pi * np.sqrt(semi_major ** 3 / (mu_earth * 1e9))
if v_mag * 1000 < v_esc:
tspan = 5 * orbit_period
else:
tspan = 1500000
eccen = ((v_mag * 1000) ** 2 * (r_mag)*1000)/(mu_earth*1e9) - 1
perigee = (semi_major * (1 - np.abs(eccen)))/1000
apogee = (semi_major * (1 + np.abs(eccen)))/1000
n_steps = int(np.ceil(tspan/dt))
ts = np.zeros((n_steps, 1))
ys = np.zeros((n_steps, 4))
ys0 = r0 + v0
ts[0] = 0
ys[0] = ys0
solver = ode(diff_eq)
solver.set_integrator('lsoda')
solver.set_initial_value(ys0, 0)
rs = ys[:, :2]
def animate(i):
solver.integrate(solver.t + dt)
ts[i] = solver.t
ys[i] = solver.y
point = plt.Circle((ys[i][0], ys[i][1]), satellite_size, facecolor=(1, 1, 1))
ax1.add_artist(point)
satellite, = ax1.plot(rs[:, 0], rs[:, 1], color='r', alpha = 1)
return satellite, point
ax1.set_xlim([-apogee, apogee])
ax1.set_ylim([-apogee, apogee])
ax1.set_xlabel('X (km)')
ax1.set_ylabel('Y (km)')
ax1.set_title("ORBIT SIMULATION")
plt.gca().set_aspect('equal', adjustable='box')
ani = animation.FuncAnimation(fig, animate, interval=0.01, blit=True)
plt.show()
And here is a screen shot of the output:
I want to omit the line that goes to the origin (0, 0). That line is a part of the satellite line object. What I believe is happening is that the origin is considered the first point, and the satellite is the second point, and matplotlib is connecting them. So how could that line leading to the middle be omitted?
The main problem is that your data is a full vector of zeros and your code plots the full dataset. Change from rs[:, 0] to rs[:, 0][:i] to only plot the points that have been simulated so far.
Second consideration is that FuncAnimation is usually used to update data already plotted. See small change below in animate(i).
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import ode
import matplotlib.animation as animation
plt.style.use('dark_background')
mu_earth = 398600
# Planet radius, km
R = 6371
fig = plt.figure()
ax1 = fig.add_subplot(111)
orbit_alt = 500
v_mag = 7.7 # km/s
# time step (delta-time)
dt = 10
satellite_size = 100
r_mag = R + orbit_alt
v_esc = np.sqrt(2*mu_earth*1e9/(R*1000 + orbit_alt*1000))
earth = plt.Circle((0, 0), 6371, color='blue')
ax1.add_artist(earth)
def diff_eq(t, y):
rx, ry, vx, vy = y # state vectors
r = np.array([rx, ry])
radial_vector = np.linalg.norm(r)
ax, ay = -r * mu_earth / radial_vector ** 3
return [vx, vy, ax, ay]
# Initial states
r0 = [r_mag, 0]
v0 = [0, v_mag]
specific_energy = (v_mag * 1000 * v_mag * 1000) / 2 - ((mu_earth * 1e9) / ((R + orbit_alt) * 1000))
semi_major = -mu_earth * 1e9 / (2 * specific_energy)
orbit_period = 2 * np.pi * np.sqrt(semi_major ** 3 / (mu_earth * 1e9))
if v_mag * 1000 < v_esc:
tspan = 5 * orbit_period
else:
tspan = 1500000
eccen = ((v_mag * 1000) ** 2 * (r_mag)*1000)/(mu_earth*1e9) - 1
perigee = (semi_major * (1 - np.abs(eccen)))/1000
apogee = (semi_major * (1 + np.abs(eccen)))/1000
n_steps = int(np.ceil(tspan/dt))
ts = np.zeros((n_steps, 1))
ys = np.zeros((n_steps, 4))
ys0 = r0 + v0
ts[0] = 0
ys[0] = ys0
solver = ode(diff_eq)
solver.set_integrator('lsoda')
solver.set_initial_value(ys0, 0)
rs = ys[:, :2]
orbit, = ax1.plot(rs[:, 0], rs[:, 1], color='r', alpha=1)
point = plt.Circle((ys[0][0], ys[0][1]), satellite_size, facecolor=(1, 1, 1))
ax1.add_artist(point)
def animate(i):
solver.integrate(solver.t + dt)
ts[i] = solver.t
ys[i] = solver.y
orbit.set_data(rs[:, 0][:i], rs[:, 1][:i])
point.set_center((ys[i][0], ys[i][1]))
return orbit, point
ax1.set_xlim([-apogee, apogee])
ax1.set_ylim([-apogee, apogee])
ax1.set_xlabel('X (km)')
ax1.set_ylabel('Y (km)')
ax1.set_title("ORBIT SIMULATION")
plt.gca().set_aspect('equal', adjustable='box')
ani = animation.FuncAnimation(fig, animate, interval=10, blit=True)
plt.show()

how to make line plots with foreground and background?

How in the world do you make line plots with multiple lines in one plot, but where each line is set into the background, almost as if it is 3D?
See example pictures below:
In R you can get pretty close like this:
library(ggridges)
set.seed(69)
df <- data.frame(x = as.vector(sapply(14:10, function(i) rnorm(30, i, 2))),
group = rep(letters[1:5], each = 30))
ggplot(df, aes(x, y = group, fill = group)) +
geom_vline(aes(xintercept = 10), size = 2, color = "#5078be") +
geom_density_ridges(size = 2, aes(color = group)) +
geom_vline(aes(xintercept = 3), size = 2) +
scale_fill_manual(values = c("#8f4b4a", "#c08f33", "#e2baba", "#ffe2ae", "#83a8f1")) +
scale_colour_manual(values = c("#5c1a08", "#8c5b01", "#af8987", "#d2ab83", "#5078be")) +
theme_void() +
theme(legend.position = "none")
If you want to do that kind of plot (a ridgeline plot) in R check out the ggridges package. This will show you a pile of cool examples:
browseVignettes("ggridges")
You can do that with a z-axis. You can still adjust the design and hide the axes and so on as you like.
from scipy import exp
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits import mplot3d
def gaus(x, a, x0, sigma):
return a*exp(-(x-x0)**2/(2*sigma**2))
if __name__ == '__main__':
x = np.array([i for i in range(0, 100)])
y1 = gaus(x, 1, 50, 5)
y2 = gaus(x, 1, 45, 12)
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.view_init(-90, 90)
ax.plot3D(x, y1, 100)
ax.plot3D(x, y2, 1)
plt.show()

How to fill in a shape composed of multiple curves?

I'm attempting to fill in an area enclosed by four curves but can't seem to get the fill I want: I'm using fill_between() but that can only handle two lines at most. Here's the code I currently have:
fig,ax = plt.subplots()
x = np.arange(start = -80,stop = 80,step = 1e-3)
yArc = np.sqrt(80**2 - x**2)
yLL = -2*x - 10
yRL = 2*x - 10
yTop = 0*x + 150
ax.fill_between(x,yArc,yTop,where = yArc<yTop,color = 'red',alpha = 0.8)
plt.show()
Here's the output:
Note: In case you can't see, there is a horizontal line just about where the free-throw line is.
You can use set operations between different filters to fill the central part first,
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x = np.arange(start=-80, stop=80, step=1e-3)
yArc = np.sqrt(80 ** 2 - x ** 2)
yLL = -2 * x - 10
yRL = 2 * x - 10
yTop = 0 * x + 150
plt.plot(x, yArc, "r")
plt.plot(x, yLL, "b")
plt.plot(x, yRL, "g")
plt.plot(x, yTop, "k")
filt_1 = yArc < yTop
filt_2 = yArc > yLL
filt_3 = yArc > yRL
filt = filt_1 & filt_2 & filt_3
ax.fill_between(x, yArc, yTop, where=filt, color="orange", alpha=0.8)
plt.show()
Producing:
And finally fill the two remaining right triangles, see for example:
matplotlib: use fill_between to make coloured triangles
The most obvious way to tackle this, is to create a bottom curve as the maximum of the three curves:
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x = np.arange(start=-80, stop=80, step=1e-3)
yArc = np.sqrt(80 ** 2 - x ** 2)
yLL = -2 * x - 10
yRL = 2 * x - 10
yTop = 0 * x + 150
plt.plot(x, yArc, "r")
plt.plot(x, yLL, "b")
plt.plot(x, yRL, "g")
plt.plot(x, yTop, "k")
yBottom = np.max([yArc, yLL, yRL], axis=0)
ax.fill_between(x, yBottom, yTop, where=yBottom <= yTop, color="orange", alpha=0.8)

Add colorbar labels as text on scatter plot

I have a scatter plot generated using:
x = list(auto['umap1'])
y = list(auto['umap2'])
final_df2 = pd.DataFrame(list(zip(x,y,communities)), columns =['x', 'y', 'cluster'])
no_clusters = max(communities)
cluster_list = list(range (min(communities), no_clusters+1))
fig2, ax = plt.subplots(figsize = (20,15))
plt.scatter(x,y, c=final_df2['cluster'], cmap=plt.cm.get_cmap('hsv', max(cluster_list)), s = 0.5)
plt.title('Phenograph on UMAP - All Markers (auto)', fontsize=15)
plt.xlabel('umap_1', fontsize=15)
plt.ylabel('umap_2', fontsize=15)
plt.colorbar(extend='both',ticks = range(max(cluster_list)))
plt.show()
I wanted to know how can I add the colorbar labels (numbers from 1-31) to the actual clusters on the graph (as text) that each one corresponds to. This is because it is quite hard to tell this from the colours as they loop back to red.
I tried:
n = list(final_df2['cluster'])
for i, txt in enumerate(n):
ax.annotate(txt, (y[i], x[i]))
But this is giving me no luck.
Your code for the annotations is writing an annotation for each and every dot. This just ends in a sea of numbers.
Somehow, you should find a kind of center for each cluster, for example by averaging all the points that belong to the same cluster.
Then, you use the coordinates of the center to position the text. You can give it a background to make it easier to read.
As I don't have your data, the code below simulates some points already around a center.
from matplotlib import pyplot as plt
import pandas as pd
import numpy as np
# calculate some random points to serve as cluster centers; run a few steps of a relaxing algorithm to separate them a bit
def random_distibuted_centers():
cx = np.random.uniform(-10, 10, MAX_CLUST + 1)
cy = np.random.uniform(-10, 10, MAX_CLUST + 1)
for _ in range(10):
for i in range(1, MAX_CLUST + 1):
for j in range(1, MAX_CLUST + 1):
if i != j:
dist = np.linalg.norm([cx[i] - cx[j], cy[i] - cy[j]])
if dist < 4:
cx[i] += 0.4 * (cx[i] - cx[j]) / dist
cy[i] += 0.4 * (cy[i] - cy[j]) / dist
return cx, cy
N = 1000
MAX_CLUST = 31
cx, cy = random_distibuted_centers()
# for demonstration purposes, just generate some random points around the centers
x = np.concatenate( [np.random.normal(cx[i], 2, N) for i in range(1,MAX_CLUST+1)])
y = np.concatenate( [np.random.normal(cy[i], 2, N) for i in range(1,MAX_CLUST+1)])
communities = np.repeat(range(1,MAX_CLUST+1), N)
final_df2 = pd.DataFrame({'x':x, 'y':y, 'cluster': communities})
no_clusters = max(communities)
cluster_list = list(range (min(communities), no_clusters+1))
fig2, ax = plt.subplots(figsize = (20,15))
plt.scatter(x,y, c=final_df2['cluster'], cmap=plt.cm.get_cmap('hsv', max(cluster_list)), s=0.5)
plt.title('Phenograph on UMAP - All Markers (auto)', fontsize=15)
plt.xlabel('umap_1', fontsize=15)
plt.ylabel('umap_2', fontsize=15)
plt.colorbar(extend='both',ticks = cluster_list)
bbox_props = dict(boxstyle="circle,pad=0.3", fc="white", ec="black", lw=2, alpha=0.9)
for i in range(1,MAX_CLUST+1):
ax.annotate(i, xy=(cx[i], cy[i]), ha='center', va='center', bbox=bbox_props)
plt.show()

Categories