How to properly edit the legend using the ax function in python - python

I have the following code, it outputs a persistence diagram for random points on the plane. Everything is almost exactly how I want it, my last issue is that I would love to have a horizontal red line in the legend to the left of "current radius = #" for my graph on the right (this is ax2 in the code), I am also getting the following error message every time the ax2.legend(loc = 'lower right', title = f'current radius = {i}') line runs.
"No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.'
Anyone know how I might be able to fix this issue?
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from celluloid import Camera
import numpy as np
# center of the original circle
h = 0 # x-coord of center
k = 0 # y-coord of center
# a randomly determined selection of angles
theta = np.array( [1 * np.pi / 6, 2 * np.pi / 3, 4 * np.pi / 5,
1 * np.pi / 5, 2 * np.pi / 5, 4 * np.pi / 7,
1 * np.pi / 4, 2 * np.pi / 9, 4 * np.pi / 3,
1 * np.pi / 3, 3 * np.pi / 2, 5 * np.pi / 9,
1 * np.pi / 2, 3 * np.pi / 8, 5 * np.pi / 4,
1 * np.pi / 1, 3 * np.pi / 7, 5 * np.pi / 3,
11 * np.pi / 6, 11 * np.pi / 10, 7 * np.pi / 6,
11 * np.pi / 7, 7 * np.pi / 4, 1 * np.pi / 10] )
# calculating coordinates
r = np.linspace(.1,2,24) # the vector of radii that will spread the original points out
x = h + r * np.cos(theta)
y = k + r * np.sin(theta)
# diagram set up
radii = np.linspace(0,1,50) # non truncated list of radii
trunc_radii = np.around(radii, 3) # truncated list of radii
death_list = np.array( [.041, .061, .102, .122, .143, # list of radii that cause deaths
.163, .184, .224, .245, .286,
.306, .327, .408, .429, .694] )
plotted_deaths = [] # plotted deaths so far
fig, (ax1, ax2) = plt.subplots(1,2)
camera = Camera(fig)
for i in trunc_radii :
ax1.scatter(x, y, color = 'green', marker = 'o', s = 15)
for xx,yy in zip(x,y) :
cir = mpatches.Circle((xx, yy), i, color = 'r',fill = True, zorder = 0)
ax1.add_patch(cir)
ax1.set_aspect('equal', adjustable='datalim')
ax1.set_title('Random Points in the Plane')
ax1.set(xlabel='x-axis', ylabel='y-axis')
ax1.set_xlim([-2.25, 2.25])
ax1.set_ylim([-2.25, 2.25])
a = np.arange(0,1.1,.1)
b = a
ax2.plot(a,b, color = 'b')
ax2.axhline(y = i, color = 'r', linestyle = '-')
if i in death_list :
plotted_deaths.append(i)
for j in plotted_deaths :
ax2.plot(0, j, marker = "o", markersize = 5, markeredgecolor = "green", markerfacecolor = "green")
ax2.set_aspect('equal', adjustable='datalim')
ax2.set_title('0-Dimensional Persistence Diagram')
ax2.set(xlabel='Birth', ylabel='Death')
ax2.legend(loc = 'lower right', title = f'current radius = {i}')
fig.set_figheight(6)
fig.set_figwidth(12)
camera.snap()
animation = camera.animate()
# animation.save('0-D persistence diagram for random points on the plane.gif')
plt.show()
And here is a picture of the output:

In such cases it can make sense to add the legend manually.
This way you get full control of what the legend will show!
import matplotlib.pyplot as plt
f, ax = plt.subplots()
# just remember the last plotted line of the loop
for i in range(10):
l, = ax.plot([i]*2, c="g", lw=2)
l2, = ax.plot([-i]*2, c="r", lw=2)
# you can also always add a custom artist to the legend!
custom_legend_line = plt.Line2D([], [], c="b", ls="--")
# create a custom legend
handles = [l, l2, custom_legend_line]
labels = ["first line", "second line", "custom_legend_line"]
ax.legend(handles, labels, loc="upper center")

Related

Animating two figures with different data units using matplotlib

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...

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 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)

What is the best solution to move the 6 different blue balls with pyGTK?

what would be the best recommendation in order to move the 6 blue balls around the 3D field
a) with a pygtk and threading matplot for the plot window? (menue in pygtk where the new position is given manually and the balls would move after a "Click Go")
b) or something else?
It seems that matplot is slow: 4s after starting the attached python script, the plot windows open and everything appear.
Moving later manually the window (rotation) is very slow too (it is mandatory, I have access to that manual rotation function of the plot window in order to observe the position of the balls in the 3D field).
Perhaps another library or programm should be used?
For the reason 6 additional balls (light grey) must be added, it will become probably slow. The 3D rendering dont has to be good (just a bit non transparent 3D).
Any recommendation is welcome before I go too far in that pygtk and matplotlib direction.
The programm is for hobby sport and will help beginners to know where to have their position in a field and how to move (3D.. NOT 2D)
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import matplotlib.axes as axs
import numpy as np
import math
from pylab import figure
# parameter in m
#swimminpool_width
s_w = 10
#swimmingpool_length
s_l = 18
#swimmingpool_depth
s_d = 4
#exchange lane width
el_w = 3
# ball radius
b_r = 0.53 / (2 * math.pi)
if __name__ == '__main__':
# basket at the swimmingpool bottom in the middle
x_basket1 = s_w / 2
y_basket1 = 0.24
# basket at the swimmingpool bottom in the middle
x_basket2 = s_w / 2
y_basket2 = s_l - 0.24
#
fig = plt.figure()
ax1 = fig.add_subplot (111,projection='3d')
# field
xG = [0,10,10,0,0, 0,10,10,10,10,10, 0, 0,0, 0,10]
yG = [0, 0, 0,0,0,18,18, 0, 0,18,18,18,18,0,18,18]
zG = [0, 0, 4,4,0, 0, 0, 0, 4, 4, 0, 0, 4,4, 4, 4]
ax1.plot_wireframe (xG,yG,zG,colors= (0,0,1,1)) # blue line game area
# exchange area
xW = [10,13,13,10,10,10,13,13,13,13,13,10,10,10,10,13]
yW = [0, 0, 0, 0, 0,18,18, 0, 0,18,18,18,18, 0,18,18]
zW = [0, 0, 4, 4, 0, 0, 0, 0, 4, 4, 0, 0, 4, 4, 4, 4]
ax1.plot_wireframe (xW,yW,zW,colors= (0,1,1,1)) # light blue line exchange area
#
ax1.set_xlabel('Wide')
ax1.set_ylabel('Length')
ax1.set_zlabel('Depth')
#
# Make data for sphere 80cm radius = player1
# pos Player 1
Pos_xP1 = 1
Pos_yP1 = 1
Pos_zP1 = 4
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
xP1 = Pos_xP1+ 0.4 * np.outer(np.cos(u), np.sin(v))
yP1 = Pos_yP1+ 0.4 * np.outer(np.sin(u), np.sin(v))
zP1 = Pos_zP1+ 0.4 * np.outer(np.ones(np.size(u)), np.cos(v))
# Plot the surface
ax1.plot_surface(xP1, yP1, zP1,color= (0,0,1,1))
#mark it
i=1
ax1.text(Pos_xP1, Pos_yP1, Pos_zP1, '%s' % (str(i)), size=20,color='k')
#
# Make data for sphere 80cm radius = player2
# pos Player 2 (use later lists)?
Pos_xP2 = 2.5
Pos_yP2 = 1
Pos_zP2 = 4
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
xP2 = Pos_xP2+ 0.4 * np.outer(np.cos(u), np.sin(v))
yP2 = Pos_yP2+ 0.4 * np.outer(np.sin(u), np.sin(v))
zP2 = Pos_zP2+ 0.4 * np.outer(np.ones(np.size(u)), np.cos(v))
# Plot the surface
ax1.plot_surface(xP2, yP2, zP2,color= (0,0,1,1))
#mark it
i=2
ax1.text(Pos_xP2, Pos_yP2, Pos_zP2, '%s' % (str(i)), size=20,color='k')
#
# Make data for sphere 80cm radius = player3
# pos Player 3
Pos_xP3 = 4
Pos_yP3 = 1
Pos_zP3 = 4
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
xP3 = Pos_xP3+ 0.4 * np.outer(np.cos(u), np.sin(v))
yP3 = Pos_yP3+ 0.4 * np.outer(np.sin(u), np.sin(v))
zP3 = Pos_zP3+ 0.4 * np.outer(np.ones(np.size(u)), np.cos(v))
# Plot the surface
ax1.plot_surface(xP3, yP3, zP3,color= (0,0,1,1))
#mark it
i=3
ax1.text(Pos_xP3, Pos_yP3, Pos_zP3, '%s' % (str(i)), size=20,color='k')
#
# Make data for sphere 80cm radius = player4
# pos Player 4
Pos_xP4 = 5.5
Pos_yP4 = 1
Pos_zP4 = 4
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
xP4 = Pos_xP4+ 0.4 * np.outer(np.cos(u), np.sin(v))
yP4 = Pos_yP4+ 0.4 * np.outer(np.sin(u), np.sin(v))
zP4 = Pos_zP4+ 0.4 * np.outer(np.ones(np.size(u)), np.cos(v))
# Plot the surface
ax1.plot_surface(xP4, yP4, zP4,color= (0,0,1,1))
#mark it
i=4
ax1.text(Pos_xP4, Pos_yP4, Pos_zP4, '%s' % (str(i)), size=20,color='k')
#
# Make data for sphere 80cm radius = player5
# pos Player 5
Pos_xP5 = 7
Pos_yP5 = 1
Pos_zP5 = 4
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
xP5 = Pos_xP5+ 0.4 * np.outer(np.cos(u), np.sin(v))
yP5 = Pos_yP5+ 0.4 * np.outer(np.sin(u), np.sin(v))
zP5 = Pos_zP5+ 0.4 * np.outer(np.ones(np.size(u)), np.cos(v))
# Plot the surface
ax1.plot_surface(xP5, yP5, zP5,color= (0,0,1,1))
#mark it
i=5
ax1.text(Pos_xP5, Pos_yP5, Pos_zP5, '%s' % (str(i)), size=20,color='k')
#
# Make data for sphere 80cm radius = player6
# pos Player 6
Pos_xP6 = 8.5
Pos_yP6 = 1
Pos_zP6 = 4
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
xP6 = Pos_xP6+ 0.4 * np.outer(np.cos(u), np.sin(v))
yP6 = Pos_yP6+ 0.4 * np.outer(np.sin(u), np.sin(v))
zP6 = Pos_zP6+ 0.4 * np.outer(np.ones(np.size(u)), np.cos(v))
# Plot the surface
ax1.plot_surface(xP6, yP6, zP6,color= (0,0,1,1))
#mark it
i=6
ax1.text(Pos_xP6, Pos_yP6, Pos_zP6, '%s' % (str(i)), size=20,color='k')
#
#
# Make data for sphere ball
posx_ball = 5
posy_ball = 9
posz_ball = b_r
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
x_ball = posx_ball + b_r * np.outer(np.cos(u), np.sin(v))
y_ball = posy_ball + b_r * np.outer(np.sin(u), np.sin(v))
z_ball = posz_ball + b_r * np.outer(np.ones(np.size(u)), np.cos(v))
# Plot the surface
ax1.plot_surface(x_ball, y_ball, z_ball, color=(1, 0, 0, 1))
#
#use a factor for having y = x in factor
ax1.set_aspect(aspect=0.222)
#
# define the basket1
t = np.linspace(0, np.pi * 2, 16)
#bottom
ax1.plot(x_basket1+0.24*np.cos(t), y_basket1+0.24*np.sin(t), 0, linewidth=1, color='black')
ax1.plot(x_basket1+0.16*np.cos(t), y_basket1+0.16*np.sin(t), 0, linewidth=1, color='black')
#top
ax1.plot(x_basket1+0.24*np.cos(t), y_basket1+0.24*np.sin(t), 0.45, linewidth=1, color='black')
# side bars
A=0
while A < 16:
xBar = [x_basket1+ 0.16 * math.sin(A*22.5*np.pi/180),x_basket1+ 0.24 * math.sin(A*22.5*np.pi/180)]
yBar = [y_basket1+ 0.16 * math.cos(A*22.5*np.pi/180),y_basket1+ 0.24 * math.cos(A*22.5*np.pi/180)]
zBar = [0,0.45]
ax1.plot(xBar,yBar,zBar,color='black')
A = A+1
# define the basket2
t = np.linspace(0, np.pi * 2, 16)
# bottom
ax1.plot(x_basket2 + 0.24 * np.cos(t), y_basket2 + 0.24 * np.sin(t), 0, linewidth=1, color='black')
ax1.plot(x_basket2 + 0.16 * np.cos(t), y_basket2 + 0.16 * np.sin(t), 0, linewidth=1, color='black')
# top
ax1.plot(x_basket2 + 0.24 * np.cos(t), y_basket2 + 0.24 * np.sin(t), 0.45, linewidth=1, color='black')
# side bars
A = 0
while A < 16:
xBar = [x_basket2 + 0.16 * math.sin(A * 22.5 * np.pi / 180),x_basket2 + 0.24 * math.sin(A * 22.5 * np.pi / 180)]
yBar = [y_basket2 + 0.16 * math.cos(A * 22.5 * np.pi / 180),y_basket2 + 0.24 * math.cos(A * 22.5 * np.pi / 180)]
zBar = [0, 0.45]
ax1.plot(xBar, yBar, zBar, color='black')
A = A + 1
#
plt.show()
I'm not sure why this has been downvoted, seems a reasonable question. However, matplotlib is not intended to be a solution for fast plotting (see this answer) and has limited support for 3D, so certainly consider another library for this.
That said, if you are only using 6 balls then I think you can make this work. I'd suggest cleaning up your code using functions for repeated code, for example defining,
def draw_ball(x, y, z, label="", color=(0,0,1,1)):
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
xP1 = x+ 0.4 * np.outer(np.cos(u), np.sin(v))
yP1 = y+ 0.4 * np.outer(np.sin(u), np.sin(v))
zP1 = z+ 0.4 * np.outer(np.ones(np.size(u)), np.cos(v))
# Plot the surface
b = ax1.plot_surface(xP1, yP1, zP1, color= color)
#mark it
t = ax1.text(x, y, z, '%s' % label, size=20, color='k')
return b, t
would allow you to set everything up with just,
players = []
for i in range(6):
players.append(draw_ball(1+i*1.5, 1, 4, label=str(i+1)))
That said, you'll find that drawing spheres with plot surface/text is too slow, instead I'd recommend using a scatter plot, with some transparency, and changing the data each time as follows,
#Instead, get all positions and plot as a single scatter collection
pos = []
for i in range(6):
pos.append([1+i*1.5, 1, 4])
#Define numpy array which is faster to work with
pos = np.array(pos)
s = ax1.scatter(pos[:,0], pos[:,1], pos[:,2], s=100, alpha = 0.5)
where pos is a 6 by 3 array for all players ball locations.
This can then be updated as follows, from this answer, when pos changes,
s._offsets3d = juggle_axes(pos[:,0], pos[:,1], pos[:,2], 'z')
It should be more efficient to update the whole collection (6 players by 3) all in one go. Adding annotation can be done following the excellent example by #Luchko.
To give you an idea how this all works together, try running the following code,
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import matplotlib.axes as axs
import numpy as np
import math
from mpl_toolkits.mplot3d.proj3d import proj_transform
from matplotlib.text import Annotation
#based on https://stackoverflow.com/questions/10374930/matplotlib-annotating-a-3d-scatter-plot#34139293
class Annotation3D(Annotation):
'''Annotate the point xyz with text s'''
def __init__(self, s, xyz, *args, **kwargs):
Annotation.__init__(self,s, xy=(0,0), *args, **kwargs)
self._verts3d = xyz
def draw(self, renderer):
xs3d, ys3d, zs3d = self._verts3d
xs, ys, zs = proj_transform(xs3d, ys3d, zs3d, renderer.M)
self.xy=(xs,ys)
Annotation.draw(self, renderer)
def annotate3D(ax, s, *args, **kwargs):
'''add anotation text s to to Axes3d ax'''
tag = Annotation3D(s, *args, **kwargs)
ax.add_artist(tag)
def draw_ball(x, y, z, label="", color=(0,0,1,1)):
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
xP1 = x+ 0.4 * np.outer(np.cos(u), np.sin(v))
yP1 = y+ 0.4 * np.outer(np.sin(u), np.sin(v))
zP1 = z+ 0.4 * np.outer(np.ones(np.size(u)), np.cos(v))
# Plot the surface
b = ax1.plot_surface(xP1, yP1, zP1, color= color)
#mark it
t = ax1.text(x, y, z, '%s' % label, size=20, color='k')
return b, t
def draw_basket(x, y, z, h, color='black'):
# define the basket1
t = np.linspace(0, np.pi * 2, 16)
#bottom
ax1.plot(x+0.24*np.cos(t), y+0.24*np.sin(t), z, linewidth=1, color=color)
ax1.plot(x+0.16*np.cos(t), y+0.16*np.sin(t), z, linewidth=1, color=color)
#top
ax1.plot(x+0.24*np.cos(t), y+0.24*np.sin(t), z+h, linewidth=1, color=color)
# side bars
A=0
while A < 16:
xBar = [x+ 0.16 * math.sin(A*22.5*np.pi/180),x+ 0.24 * math.sin(A*22.5*np.pi/180)]
yBar = [y+ 0.16 * math.cos(A*22.5*np.pi/180),y+ 0.24 * math.cos(A*22.5*np.pi/180)]
zBar = [0,0.45]
ax1.plot(xBar, yBar, zBar, color=color)
A = A+1
# parameter in m
#swimminpool_width
s_w = 10
#swimmingpool_length
s_l = 18
#swimmingpool_depth
s_d = 4
#exchange lane width
el_w = 3
# ball radius
b_r = 0.53 / (2 * math.pi)
if __name__ == '__main__':
# basket at the swimmingpool bottom in the middle
x_basket1 = s_w / 2
y_basket1 = 0.24
# basket at the swimmingpool bottom in the middle
x_basket2 = s_w / 2
y_basket2 = s_l - 0.24
fig = plt.figure()
ax1 = fig.add_subplot(111,projection='3d')
# field
xG = [0,10,10,0,0, 0,10,10,10,10,10, 0, 0,0, 0,10]
yG = [0, 0, 0,0,0,18,18, 0, 0,18,18,18,18,0,18,18]
zG = [0, 0, 4,4,0, 0, 0, 0, 4, 4, 0, 0, 4,4, 4, 4]
ax1.plot_wireframe (xG,yG,zG,colors= (0,0,1,1)) # blue line game area
# exchange area
xW = [10,13,13,10,10,10,13,13,13,13,13,10,10,10,10,13]
yW = [0, 0, 0, 0, 0,18,18, 0, 0,18,18,18,18, 0,18,18]
zW = [0, 0, 4, 4, 0, 0, 0, 0, 4, 4, 0, 0, 4, 4, 4, 4]
ax1.plot_wireframe (xW,yW,zW,colors= (0,1,1,1)) # light blue line exchange area
ax1.set_xlabel('Wide')
ax1.set_ylabel('Length')
ax1.set_zlabel('Depth')
#use a factor for having y = x in factor
ax1.set_aspect(aspect=0.222)
# Make data for sphere ball
draw_ball(8.5, 1, b_r, label="")
# define the basket1
draw_basket(x_basket1, y_basket1, 0., 0.45)
# define the basket2
draw_basket(x_basket2, y_basket2, 0., 0.45)
#Instead, get all positions and plot as a single scatter collection
pos = []
for i in range(6):
pos.append([1+i*1.5, 1, 4])
#Define numpy array which is faster to work with
pos = np.array(pos)
s = ax1.scatter(pos[:,0], pos[:,1], pos[:,2], s=100, alpha = 0.5)
#Add labels
for j, xyz_ in enumerate(pos):
annotate3D(ax1, s=str(j), xyz=xyz_, fontsize=10, xytext=(-3,3),
textcoords='offset points', ha='right',va='bottom')
#Use interactive mode for quick animation
plt.ion()
plt.show()
# Do 100 steps and add random change to positions
for step in range(100):
for i in range(pos.shape[0]):
pos[i,0] += 0.1*np.random.randn(1)
pos[i,1] += 0.1*np.random.randn(1)
pos[i,2] += 0.1*np.random.randn(1)
s._offsets3d = pos[:,0], pos[:,1], pos[:,2]
plt.pause(0.01)
this appears to allow quick rotation and plotting, at least for me,
#!/usr/bin/python3.5
"""
/* game uwr: field and basket design test module
* Copyright (C) Creative Commons Alike V4.0
* No warranty
*/
"""
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import juggle_axes
import matplotlib.axes as axs
import numpy as np
import math
from mpl_toolkits.mplot3d.proj3d import proj_transform
from matplotlib.text import Annotation
# parameter in m
#swimminpool_width
s_w = 10
#swimmingpool_length
s_l = 18
#swimmingpool_depth
s_d = 4
#exchange lane width
el_w = 3
# ball radius
b_r = 0.53 / (2 * math.pi)
# coordinate depth_pb1 player blue nb 1
# previous1 and previous2 is array pos_p1_b and array pos_p2_b
# current is array pos_b ..
# target is (from menue gtk) 1m length and 1m deeper
depth_pb1 = 2
length_pb1 = 2
side_pb1 = ((s_w/6)/2)+0*(s_w/6)
text_speed_pb1 = "low"
#based on https://stackoverflow.com/questions/10374930/matplotlib-annotating-a-3d-scatter-plot#34139293
class Annotation3D(Annotation):
'''Annotate the point xyz with text s'''
def __init__(self, s, xyz, *args, **kwargs):
Annotation.__init__(self,s, xy=(0,0), *args, **kwargs)
self._verts3d = xyz
def draw(self, renderer):
xs3d, ys3d, zs3d = self._verts3d
xs, ys, zs = proj_transform(xs3d, ys3d, zs3d, renderer.M)
self.xy=(xs,ys)
Annotation.draw(self, renderer)
def annotate3D(ax, s, *args, **kwargs):
'''add anotation text s to to Axes3d ax'''
tag = Annotation3D(s, *args, **kwargs)
ax.add_artist(tag)
def draw_basket(x, y, z, h, color='black'):
# define the basket1
t = np.linspace(0, np.pi * 2, 16)
#bottom
ax1.plot(x+0.24*np.cos(t), y+0.24*np.sin(t), z, linewidth=1, color=color)
ax1.plot(x+0.16*np.cos(t), y+0.16*np.sin(t), z, linewidth=1, color=color)
#top
ax1.plot(x+0.24*np.cos(t), y+0.24*np.sin(t), z+h, linewidth=1, color=color)
# side bars
A=0
while A < 16:
xBar = [x+ 0.16 * math.sin(A*22.5*np.pi/180),x+ 0.24 * math.sin(A*22.5*np.pi/180)]
yBar = [y+ 0.16 * math.cos(A*22.5*np.pi/180),y+ 0.24 * math.cos(A*22.5*np.pi/180)]
zBar = [0,0.45]
ax1.plot(xBar, yBar, zBar, color=color)
A = A+1
def draw_ball(x, y, z, label="", color=(1,0,0,1)):
global b_r
u = np.linspace(0, 2 * np.pi, 50)
v = np.linspace(0, np.pi, 50)
xP1 = x+ b_r * np.outer(np.cos(u), np.sin(v))
yP1 = y+ b_r * np.outer(np.sin(u), np.sin(v))
zP1 = z+ b_r * np.outer(np.ones(np.size(u)), np.cos(v))
# Plot the surface
b = ax1.plot_surface(xP1, yP1, zP1, color= color)
#mark it
t = ax1.text(x, y, z, '%s' % label, size=20, color='k')
# return b, t
if __name__ == '__main__':
# basket at the swimmingpool bottom in the middle
x_basket1 = s_w / 2
y_basket1 = 0.24
# basket at the swimmingpool bottom in the middle
x_basket2 = s_w / 2
y_basket2 = s_l - 0.24
fig = plt.figure()
ax1 = fig.add_subplot(111,projection='3d')
# field
xG = [0,10,10,0,0, 0,10,10,10,10,10, 0, 0,0, 0,10]
yG = [0, 0, 0,0,0,18,18, 0, 0,18,18,18,18,0,18,18]
zG = [0, 0, 4,4,0, 0, 0, 0, 4, 4, 0, 0, 4,4, 4, 4]
ax1.plot_wireframe (xG,yG,zG,colors= (0,0,1,1)) # blue line game area
# exchange area
xW = [10,13,13,10,10,10,13,13,13,13,13,10,10,10,10,13]
yW = [0, 0, 0, 0, 0,18,18, 0, 0,18,18,18,18, 0,18,18]
zW = [0, 0, 4, 4, 0, 0, 0, 0, 4, 4, 0, 0, 4, 4, 4, 4]
ax1.plot_wireframe (xW,yW,zW,colors= (0,1,1,1)) # light blue line exchange area
ax1.set_xlabel('Wide')
ax1.set_ylabel('Length')
ax1.set_zlabel('Water')
# draw the 2 lines which show the depth
xG1 = [0, 10]
yG1 = [4, 4]
zG1 = [0, 0]
ax1.plot_wireframe(xG1, yG1, zG1, colors=(0, 0, 1, 1),linestyle=':') # blue line
xG2 = [0, 10]
yG2 = [14, 14]
zG2 = [0, 0]
ax1.plot_wireframe(xG2, yG2, zG2, colors=(0, 0, 1, 1),linestyle=':') # blue line
#
# put the axis fix
ax1.set_xlim3d(0, 13)
ax1.set_ylim3d(0, 18)
ax1.set_zlim3d(0, 4)
#
# use a factor for having y = x in factor
ax1.set_aspect(aspect=0.222)
#
# sphere red ball for playing
draw_ball(5, 9, b_r, label="")
#
# define the basket1
draw_basket(x_basket1, y_basket1, 0., 0.45)
# define the basket2
draw_basket(x_basket2, y_basket2, 0., 0.45)
#get all positions and plot as a single scatter collection at all player position blue
pos_b = []
for i in range(6):
# distribute the players at the side with the same distance
# at game start
pos_b.append([((s_w/6)/2)+i*(s_w/6),1, s_d])
#Define numpy array which is faster to work with
pos_b = np.array(pos_b)
# s parameter below is the surface of the scatter point 100 is 80cm diam by non zooming
# c blue
p_b = ax1.scatter(pos_b[:,0], pos_b[:,1], pos_b[:,2], s=400, alpha = 0.5, c=(0, 0, 1, 1))
#Add labels
for j, xyz_ in enumerate(pos_b):
annotate3D(ax1, s=str(j+1), xyz=xyz_, fontsize=10, xytext=(-3,3),
textcoords='offset points', ha='right',va='bottom')
#
#get all positions and plot as a single scatter collection at all player position white
pos_w = []
for i in range(6):
# distribute the players at the side with the same distance
# at game start
pos_w.append([((s_w/6)/2)+i*(s_w/6), (s_l-1), s_d])
#Define numpy array which is faster to work with
pos_w = np.array(pos_w)
# s parameter below is the surface of the scatter point 100 is 80cm diam by non zooming
# c="lightgrey", alpha=0.5 half transparent
p_w = ax1.scatter(pos_w[:,0], pos_w[:,1], pos_w[:,2], s=400, alpha = 0.5, c="darkgrey")
#Add labels
for j, xyz_ in enumerate(pos_w):
annotate3D(ax1, s=str(j+1), xyz=xyz_, fontsize=10, xytext=(-3,3),
textcoords='offset points', ha='right',va='bottom')
#
#
#Use interactive mode for quick animation
plt.ion()
plt.show()
# Linear move in 10 steps
# Do 10 steps and add change to positions in homogenous speed along distance of each player
# delta_pos_pb1_x = side_pb1 - pos_b[0,0]
# delta_pos_pb1_y = length_pb1 - pos_b[0,1]
# delta_pos_pb1_z = depth_pb1 - pos_b[0,2]
delta_pos_pb1_x = 3
delta_pos_pb1_y = 3
delta_pos_pb1_z = -3
delta_pos_pb2_x = 3
delta_pos_pb2_y = 0.5
delta_pos_pb2_z = -3.5
for step in range(10):
pos_b[0,0] += 0.1 * delta_pos_pb1_x
pos_b[0,1] += 0.1 * delta_pos_pb1_y
pos_b[0,2] += 0.1 * delta_pos_pb1_z
pos_b[1, 0] += 0.1 * delta_pos_pb2_x
pos_b[1, 1] += 0.1 * delta_pos_pb2_y
pos_b[1, 2] += 0.1 * delta_pos_pb2_z
# for i in range(pos_b.shape[0]):
# pos_b[i,0]+= 0.01*delta_pos_pb1_x
# pos_b[i,1]+= 0.01*delta_pos_pb1_y
# pos_b[i,2]+= 0.01*delta_pos_pb1_z
# plt.pause(1.0)
# plt.draw()
# s._offsets3d = juggle_axes(pos_b[:,0], pos_b[:,1], pos_b[:,2], 'z')
p_b._offsets3d = pos_b[:, 0], pos_b[:, 1], pos_b[:, 2]
plt.pause(0.001)

Matplotlib plot pulse propagation in 3d

I'd like to plot pulse propagation in such a way at each step, it plots the pulse shape. In other words, I want a serie of x-z plots, for each values of y. Something like this (without color):
How can I do this using matplotlib (or Mayavi)? Here is what I did so far:
def drawPropagation(beta2, C, z):
""" beta2 in ps / km
C is chirp
z is an array of z positions """
T = numpy.linspace(-10, 10, 100)
sx = T.size
sy = z.size
T = numpy.tile(T, (sy, 1))
z = numpy.tile(z, (sx, 1)).T
U = 1 / numpy.sqrt(1 - 1j*beta2*z * (1 + 1j * C)) * numpy.exp(- 0.5 * (1 + 1j * C) * T * T / (1 - 1j*beta2*z*(1 + 1j*C)))
fig = pyplot.figure()
ax = fig.add_subplot(1,1,1, projection='3d')
surf = ax.plot_wireframe(T, z, abs(U))
Change to:
ax.plot_wireframe(T, z, abs(U), cstride=1000)
and call:
drawPropagation(1.0, 1.0, numpy.linspace(-2, 2, 10))
will create the following graph:
If you need the curve been filled with white color:
import numpy
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import pyplot
from matplotlib.collections import PolyCollection
def drawPropagation(beta2, C, z):
""" beta2 in ps / km
C is chirp
z is an array of z positions """
T = numpy.linspace(-10, 10, 100)
sx = T.size
sy = z.size
T = numpy.tile(T, (sy, 1))
z = numpy.tile(z, (sx, 1)).T
U = 1 / numpy.sqrt(1 - 1j*beta2*z * (1 + 1j * C)) * numpy.exp(- 0.5 * (1 + 1j * C) * T * T / (1 - 1j*beta2*z*(1 + 1j*C)))
fig = pyplot.figure()
ax = fig.add_subplot(1,1,1, projection='3d')
U = numpy.abs(U)
verts = []
for i in xrange(T.shape[0]):
verts.append(zip(T[i, :], U[i, :]))
poly = PolyCollection(verts, facecolors=(1,1,1,1), edgecolors=(0,0,1,1))
ax.add_collection3d(poly, zs=z[:, 0], zdir='y')
ax.set_xlim3d(numpy.min(T), numpy.max(T))
ax.set_ylim3d(numpy.min(z), numpy.max(z))
ax.set_zlim3d(numpy.min(U), numpy.max(U))
drawPropagation(1.0, 1.0, numpy.linspace(-2, 2, 10))
pyplot.show()

Categories