Reputation: 1
I have this task: create a heart animation using polar coordinates. Start from drawing a heart and then add animation as the next step; use variable increase of the rendering angle. Then i need to build other figures in polar coordinates the same way. I also need to make the drawing function separate from the figure, so that it would be possible to change figure with minimal code changes
I tried running this, put the output looks really weird and the other figures don't look as expected when i change theta and r. I am not sure if it's possible to complete this task using tkinter or pygame or something like that. I would be happy if you could come up with any ideas to complete this.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
theta = np.linspace(0, 2*np.pi, 100)
r = 1 - (np.abs(theta) - 1) ** 2
line, = ax.plot([], [], color='red')
def init():
line.set_data([], [])
return line,
def animate(i):
current_theta = theta[:i]
current_r = r[:i]
x_polar = current_r * np.cos(current_theta)
y_polar = current_r * np.sin(current_theta)
line.set_data(x_polar, y_polar)
return line,
ani = animation.FuncAnimation(fig, animate, init_func=init, frames=len(theta), interval=50, blit=True)
plt.show()
Upvotes: -1
Views: 460
Reputation: 1
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
fig = plt.figure() ax = fig.add_subplot(111, polar=True)
theta = np.linspace(0, 2*np.pi, 100) r = 1 - (np.abs(theta) - 1) ** 2
line, = ax.plot([], [], color='red')
def init(): line.set_data([], []) return line,
def animate(i): current_theta = theta[:i] current_r = r[:i] x_polar = current_r * np.cos(current_theta) y_polar = current_r * np.sin(current_theta) line.set_data(x_polar, y_polar) return line,
ani = animation.FuncAnimation(fig, animate, init_func=init, frames=len(theta), interval=50, blit=True)
plt.show()
Upvotes: 0
Reputation: 9046
If you look at the plot on Desmos (link), you see that up to pi/2, the plot looks fine, but the tail end after pi/2 is not what you want. So, the first thing you need to do is to plot theta from 0 to pi/2.
You also want to plot a second half reflected across the x-axis. A y=f(-x) reflects f(x) across the x-axis. We can achieve this by converting the polar coordinates to cartesian like you do in the loop, and then creating the x vector of points as the x coordinates and the negative x coordinates. The y vector is just the y coordinates twice. To ensure the plot is smooth, you will want to reverse the second set of points (for both x and y) so it doesn't restart at the origin.
The code below does what I describe and I've put in code that plots it polar or cartesian coordinates.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
plt.close("all")
fig, ax = plt.subplots(subplot_kw=dict(projection="polar"))
theta = np.linspace(0, np.pi/2, 100)
r = 1 - (np.abs(theta) - 1) ** 2
x = r*np.cos(theta)
y = r*np.sin(theta)
x_data = np.concatenate((x, -x[::-1]))
y_data = np.concatenate((y, y[::-1]))
r_data = np.sqrt(x_data**2 + y_data**2)
theta_data = np.arctan2(y_data, x_data)
line, = ax.plot([], [], color='red')
def init():
line.set_data([], [])
return line,
def animate(i):
# line.set_data(x_data[:i], y_data[:i]) # cartesian
line.set_data(theta_data[:i], r_data[:i]) # polar
return line,
ani = animation.FuncAnimation(fig, animate, init_func=init, frames=len(x_data), interval=10, blit=True)
plt.show()
Polar animation:
Cartesian animation:
Upvotes: 0
Reputation: 91
I wrote another code:
# Import modules
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# ------------------------------------------ #
# Define the figure
fig = plt.figure(figsize=(16,16))
# Setting the axes projection as polar
ax = fig.add_subplot(111, projection='polar')
# Define the heart equation from https://pavpanchekha.com/blog/heart-polar-coordinates.html
t = np.linspace(0.0, 2.0*np.pi, 200, endpoint=True)
r = np.sin(t)*np.sqrt(np.abs(np.cos(t))) / (np.sin(t) + 7/5) - 2*np.sin(t) + 2
# Define a list that stores temporary data points
# [[theta], [radius]]
data_points = [[], []]
# Define the index of the last data point that needs to be plotted
# For example, if index = 5, data_points[0][-1] = t[5] and data_points[1][-1] = r[5]
index = 0
def animate(i):
# Global variables
global data_points, index
# Clear the plot
ax.clear()
# Remove tick labels
ax.set_xticklabels([])
ax.set_yticklabels([])
# Append a data point to the list
data_points[0].append(t[index])
data_points[1].append(r[index])
# Update the index
index += 1
# Reset the index and points
# if index is out of range
if (index == len(t)):
index = 0
data_points = [[], []]
# Plot the "heart"
plt.plot(data_points[0], data_points[1], color="red")
# Define the limits
ax.set_rlim(min(r), max(r))
# Remove border
#ax.axis("off")
# Calling the animation function
ani1 = FuncAnimation(fig, animate, interval=30)
# Display the plot
plt.show()
This yields: (You must increase the number of points in the theta array for a better smooth line)
Change your way of writing the code to mine... I hope this helps!
Upvotes: 0
Reputation: 731
It seems you are using the wrong expression for the radius. Your radius is proportional to the sqare of theta and becoming very negative with increasing theta which cannot be represented in a polar plot. Most likely you are missing sine or cosine. Playing around a bit I found r = 1 - (np.abs(np.cos(theta)) - 1) ** 2
to produce a heart shape.
In general though it seems odd to me to calculate theta and r, which are the coordinates of a polar plot and then doing something like a trasformation to cartesian coordinates (x_polar and y_polar) for your plot.
About the separating issue, move the calculation of the radius out of the animate function. The animate function should be responsible only for updating the plot:
def heart(theta):
return 1 - (np.abs(np.cos(theta)) - 1) ** 2
def cardiod(theta):
return 0.5 * (1 - np.sin(theta))
theta = np.linspace(0, 2*np.pi, 100)
r = heart(theta)
# transformation needed for your example
theta, r = r * np.cos(theta), r * np.sin(theta)
# test of the cardiod function; this function does not need any transformation afterwards
# r = cardiod(theta)
def animate(i):
current_theta = theta[:i]
current_r = r[:i]
line.set_data(current_theta, current_r)
return line,
I added a function for the Cardioid on Wikipedia, the 'heart curve', as an example for having several functions prepared.
Upvotes: 1