I am trying to create the animation in this video using Python. But I stuck on the very first step. Till now I've created a Circle and a point rotating around its circumference. My code is given below. Now I want to plot the y values corresponding to x=np.arange(0, I*np.pi, 0.01) along the x-axis (as shown in update() function in the code). For this I have to define another function to plot these x and y and pass that function inside a new animation.FuncAnimation().
Is there any way to plot everything using only the update() function?
Note I have found a code of this animation in here. But it is written in Java!
My Code
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
W = 6.5
H = 2
radius = 1
I = 2
T = 3
N = 2
plt.style.use(['ggplot', 'dark_background'])
def create_circle(x, y, r):
circle = plt.Circle((x, y), radius=r, fill=False, alpha=0.7, color='w')
return circle
def create_animation():
fig = plt.figure()
ax = plt.axes(xlim=(-2, W + 2), ylim=(-H, H))
circle = create_circle(0, 0, radius)
ax.add_patch(circle)
line1, = ax.plot(0, 1, marker='o', markersize=3, color='pink', alpha=0.7)
def update(theta):
x = radius * np.cos(theta)
y = radius * np.sin(theta)
line1.set_data([0, x], [0, y])
return line1,
anim = []
anim.append(animation.FuncAnimation(fig, update,
frames=np.arange(0, I * np.pi, 0.01),
interval=10, repeat=True))
# anim.append(animation.FuncAnimation(fig, update_line, len(x),
# fargs=[x, y, line, line1], interval=10))
plt.grid(False)
plt.gca().set_aspect('equal')
plt.gca().spines['left'].set_visible(False)
plt.gca().spines['top'].set_visible(False)
plt.gca().spines['right'].set_visible(False)
plt.gca().spines['bottom'].set_visible(False)
plt.gca().set_xticks([])
plt.gca().set_yticks([])
plt.show()
if __name__ == '__main__':
create_animation()
Edit. I've improved the task by defining a global variable pos and changing the update() function in the following manner ...The animation now looks better but still having bugs!
Improved Portion
plot, = ax.plot([], [], color='w', alpha=0.7)
level = np.arange(0, I * np.pi, 0.01)
num = []
frames = []
for key, v in enumerate(level):
num.append(key)
frames.append(v)
def update(theta):
global pos
x = radius * np.cos(theta)
y = radius * np.sin(theta)
wave.append(y)
plot.set_data(np.flip(level[:pos] + T), wave[:pos])
line1.set_data([0, x], [0, y])
pos += 1
return line1, plot,
Edit Till now I've done the following:
def update(theta):
global pos
x, y = 0, 0
for i in range(N):
prev_x = x
prev_y = y
n = 2 * i + 1
rad = radius * (4 / (n * np.pi))
x += rad * np.cos(n * theta)
y += rad * np.sin(n * theta)
wave.append(y)
circle = create_circle(prev_x, prev_y, rad)
ax.add_patch(circle)
plot.set_data(np.flip(level[:pos] + T), wave[:pos])
line2.set_data([x, T], [y, y])
line1.set_data([prev_x, x], [prev_y, y])
pos += 1
return line1, plot, line2,
Output
Please help to correct this animation. Or, is there any efficient way to do this animation?
Edit Well, now the animation is partially working. But there is a little issue: In my code (inside the definition of update()) I have to add circles centered at (prev_x, prev_y) of radius defined as rad for each frame. For this reason I try to use a for loop in the definition of update() but then all the circles remains in the figure (see the output below). But I want one circle in each frame with the centre and radius as mentioned above. Also the same problem is with the plot. I try to use ax.clear() inside the for loop but it didn't work.
Related
My code is a fair bit more advanced, but in simple terms I am looking to place and rotate an image of a plane along a path using matplotlib. Ideally I would be able to select the angle and how far along the path the image should be placed. Any ideas? My ideal output would be something like this (ignoring the coordinates I already fixed that in my real code).
Image of Norway used:
Code
import matplotlib.pyplot as plt
import matplotlib.image as img
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def x2map(x, x_scale):
return x * x_scale
def y2map(y, y_scale):
return (1 - y) * y_scale
if __name__ == "__main__":
image_url = "Norge2.png"
# Obtains the scaling for the figure
map = img.imread(image_url)
fig, ax = plt.subplots()
im = ax.imshow(map)
_, x_scale = plt.xlim()
y_scale, _ = plt.ylim()
# Fixes the axis to 0-1 and 0-1
positions_x = [i * x_scale / 10 for i in range(0, 11)]
positions_y = [i * y_scale / 10 for i in range(0, 11)]
labels = [i / 10 for i in range(0, 11)]
ax.set_xticks(positions_x)
ax.set_xticklabels([i / 10 for i in range(0, 11)])
ax.set_yticks(positions_y)
ax.set_yticklabels([(10 - i) / 10 for i in range(0, 11)])
route_color = "red"
route_ls = "-"
city_marker ="o"
city_color = "red"
A = [x2map(0.125,x_scale), y2map(0.14,y_scale)]
B = [x2map(0.772,x_scale), y2map(0.92,y_scale)]
plt.plot(
[A[0], B[0]], [A[1], B[1]], marker='o', color=route_color, ls=route_ls
)
plt.show()
In python how to generate a random pair of points (x,y) that lies inside a circle of radius r.
Basically the x and y should satisfy the condition x^2 + y^2 = r^2.
To generate uniformly distributed point inside origin-centered circle of radius r, you can generate two uniform values t,u in range 0..1 and use the next formula:
import math, random
r = 4
t = random.random()
u = random.random()
x = r * math.sqrt(t) * math.cos(2 * math.pi * u)
y = r * math.sqrt(t) * math.sin(2 * math.pi * u)
print (x,y)
Using numpy to generate more than one point at a time:
import numpy as np
import matplotlib.pyplot as plt
n_samples = 1000
r = 4
# make a simple unit circle
theta = np.linspace(0, 2*np.pi, n_samples)
a, b = r * np.cos(theta), r * np.sin(theta)
t = np.random.uniform(0, 1, size=n_samples)
u = np.random.uniform(0, 1, size=n_samples)
x = r*np.sqrt(t) * np.cos(2*np.pi*u)
y = r*np.sqrt(t) * np.sin(2*np.pi*u)
# Plotting
plt.figure(figsize=(7,7))
plt.plot(a, b, linestyle='-', linewidth=2, label='Circle', color='red')
plt.scatter(x, y, marker='o', label='Samples')
plt.ylim([-r*1.5,r*1.5])
plt.xlim([-r*1.5,r*1.5])
plt.grid()
plt.legend(loc='upper right')
plt.show(block=True)
which results in:
I want to draw a semicircle using matplotlib.
Here I have a court
import numpy as np
import matplotlib.pyplot as plt
x_asix = np.array([0,0,100,100, 0])
y_asix = np.array([0,100,100,0, 0])
x_coordenates = np.concatenate([ x_asix])
y_coordenates = np.concatenate([y_asix])
plt.plot(x_coordenates, y_coordenates)
See image here:
I want to add one semicircle that stars at point (0,50) with radius = 10.
The result should be something like this:
Here is a function that draws semicircles, using numpy:
import matplotlib.pyplot as plt
import numpy as np
def generate_semicircle(center_x, center_y, radius, stepsize=0.1):
"""
generates coordinates for a semicircle, centered at center_x, center_y
"""
x = np.arange(center_x, center_x+radius+stepsize, stepsize)
y = np.sqrt(radius**2 - x**2)
# since each x value has two corresponding y-values, duplicate x-axis.
# [::-1] is required to have the correct order of elements for plt.plot.
x = np.concatenate([x,x[::-1]])
# concatenate y and flipped y.
y = np.concatenate([y,-y[::-1]])
return x, y + center_y
example:
x,y = generate_semicircle(0,50,10, 0.1)
plt.plot(x, y)
plt.show()
You could simply use the equation of the ellipse, to easily draw the portion of the ellipse you are interested in.
If you want to draw the part of the ellipse you have in your image, unfortunately you cannot simply write it as: y = f(x), but you can use the common trick of plotting x = f(y) instead:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 1)
ax.set_aspect('equal')
x_asix = np.array([0,0,100,100, 0])
y_asix = np.array([0,100,100,0, 0])
x_coordenates = np.concatenate([ x_asix])
y_coordenates = np.concatenate([y_asix])
ax.plot(x_coordenates, y_coordenates)
# ((x - x0) / a) ** 2 + ((y - y0) / b) ** 2 == 1
a = 20
b = 15
x0 = 50
y0 = 0
x = np.linspace(-a + x0, a + x0)
y = b * np.sqrt(1 - ((x - x0) / a) ** 2) + y0
ax.plot(y, x)
I am trying to create an image where the x axis is the width, and y axis is the height of the image. And where each point can be given a color based on a RBG mapping. From looking at imshow() from Matplotlib I guess I need to create a meshgrid on the form (NxMx3) where 3 is a tuple or something similar with the rbg colors.
But so far I have not managed to understand how to do that. Lets say I have this example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
x_min = 1
x_max = 5
y_min = 1
y_max = 5
Nx = 5 #number of steps for x axis
Ny = 5 #number of steps for y axis
x = np.linspace(x_min, x_max, Nx)
y = np.linspace(y_min, y_max, Ny)
#Can then create a meshgrid using this to get the x and y axis system
xx, yy = np.meshgrid(x, y)
#imagine I have some funcion that does someting based on the x and y values
def somefunc(x_value, y_value):
#do something and return rbg based on that
return x_value + y_value
res = somefunc(xx, yy)
cmap = LinearSegmentedColormap.from_list('mycmap', ['white', 'blue', 'black'])
plt.figure(dpi=100)
plt.imshow(res, cmap=cmap, interpolation='bilinear')
plt.show()
And this creates a plot, but what would I have to do if my goal was to give spesific rbg values based on x and y values inside somefunc and make the resulting numpy array into a N x M x 3 array
I tried to make the somefunc function return a tuple of rbg values to use (r, b g) but that does not seem to work
It will of course completely depend on what you want to do with the values you supply to the function. So let's assume you just want to put the x values as the red channel and the y values as the blue channel, this could look like
def somefunc(x_value, y_value):
return np.dstack((x_value/5., np.zeros_like(x_value), y_value/5.))
Complete example:
import numpy as np
import matplotlib.pyplot as plt
x_min = 1
x_max = 5
y_min = 1
y_max = 5
Nx = 5 #number of steps for x axis
Ny = 5 #number of steps for y axis
x = np.linspace(x_min, x_max, Nx)
y = np.linspace(y_min, y_max, Ny)
#Can then create a meshgrid using this to get the x and y axis system
xx, yy = np.meshgrid(x, y)
#imagine I have some funcion that does someting based on the x and y values
def somefunc(x_value, y_value):
return np.dstack((x_value/5., np.zeros_like(x_value), y_value/5.))
res = somefunc(xx, yy)
plt.figure(dpi=100)
plt.imshow(res)
plt.show()
If you already have a (more complicated) function that returns an RGB tuple you may loop over the grid to fill an empty array with the values of the function.
#If you already have some function that returns an RGB tuple
def somefunc(x_value, y_value):
if x_value > 2 and y_value < 3:
return np.array(((y_value+1)/4., (y_value+2)/5., 0.43))
elif x_value <=2:
return np.array((y_value/5., (x_value+3)/5., 0.0))
else:
return np.array((x_value/5., (y_value+5)/10., 0.89))
# you may loop over the grid to fill a new array with those values
res = np.zeros((xx.shape[0],xx.shape[1],3))
for i in range(xx.shape[0]):
for j in range(xx.shape[1]):
res[i,j,:] = somefunc(xx[i,j],yy[i,j])
plt.figure(dpi=100)
plt.imshow(res)
I need your help. Please consider the code below, which plots a sinusoid using pylab in IPython. A slider below the axis enables the user to adjust the frequency of the sinusoid interactively.
%pylab
# setup figure
fig, ax = subplots(1)
fig.subplots_adjust(left=0.25, bottom=0.25)
# add a slider
axcolor = 'lightgoldenrodyellow'
ax_freq = axes([0.3, 0.13, 0.5, 0.03], axisbg=axcolor)
s_freq = Slider(ax_freq, 'Frequency [Hz]', 0, 100, valinit=a0)
# plot
g = linspace(0, 1, 100)
f0 = 1
sig = sin(2*pi*f0*t)
myline, = ax.plot(sig)
# update plot
def update(value):
f = s_freq.val
new_data = sin(2*pi*f*t)
myline.set_ydata(new_data) # crucial line
fig.canvas.draw_idle()
s_freq.on_changed(update)
Instead of the above, I need to plot the signal as vertical lines, ranging from the amplitude of each point in t to the x-axis. Thus, my first idea was to use vlines instead of plot in line 15:
myline = ax.vlines(range(len(sig)), 0, sig)
This solution works in the non-interactive case. The problem is, plot returns an matplotlib.lines.Line2D object, which provides the set_ydata method to update data interactively. The object returned by vlines is of type matplotlib.collections.LineCollection and does not provide such a method.
My question: how do I update a LineCollection interactively?
Using #Aaron Voelker's comment of using set_segments and wrapping it up in a function:
def update_vlines(*, h, x, ymin=None, ymax=None):
seg_old = h.get_segments()
if ymin is None:
ymin = seg_old[0][0, 1]
if ymax is None:
ymax = seg_old[0][1, 1]
seg_new = [np.array([[xx, ymin],
[xx, ymax]]) for xx in x]
h.set_segments(seg_new)
Analog for hlines:
def update_hlines(*, h, y, xmin=None, xmax=None):
seg_old = h.get_segments()
if xmin is None:
xmin = seg_old[0][0, 0]
if xmax is None:
xmax = seg_old[0][1, 0]
seg_new = [np.array([[xmin, yy],
[xmax, yy]]) for yy in y]
h.set_segments(seg_new)
I will give examples for vlines here.
If you have multiple lines, #scleronomic solution works perfect. You also might prefer one-liner:
myline.set_segments([np.array([[x, x_min], [x, x_max]]) for x in xx])
If you need to update only maximums, then you can do this:
def update_maxs(vline):
vline[:,1] = x_min, x_max
return vline
myline.set_segments(list(map(update_maxs, x.get_segments())))
Also this example could be useful: LINK