Rotating circle going around the circumference of a larger circle on Matplotlib? - python

I'm trying to use matplotlib to make a program that will use any two circles (any radius) and the adjacent circle will rotate around the main larger circle. I've looked at matplotlib.animation and it doesn't seem to work. Apparently animations just won't work with shapes?
Here's my code so far (I've deleted the animations subroutine as they seem to just brick the program)
import matplotlib.pyplot as plt
import matplotlib.animation
firstcircle = int(input("please input radius for the largest circle"))
secondcircle = int(input("please input radius for the adjacent circle"))
largest = int(firstcircle*2+secondcircle*2)
difference = int(0-(largest))
difference2 = int(0+(largest))
def createList(r1, r2):
return [item for item in range(r1, r2+1)]
x = (createList(difference,difference2))
y = (createList(difference,difference2))
print(difference)
print(difference2)
def circle():
theta = np.linspace(0, 2*np.pi, 100)
r = np.sqrt(firstcircle**2)
x1 = r*np.cos(theta)
x2 = r*np.sin(theta)
theta2 = np.linspace(0, 2*np.pi, 100)
r1 = np.sqrt(secondcircle**2)
x3 = r1*np.cos(theta2)+firstcircle+secondcircle
x4 = r1*np.sin(theta2)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.spines['left'].set_position('center')
ax.spines['bottom'].set_position('center')
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
ax.plot(x1,x2)
circlemove ,= ax.plot(x3,x4)
ax.set_aspect(1)
plt.tight_layout()
plt.xlim(difference,difference2)
plt.ylim(difference,difference2)
plt.grid(linestyle='--')
plt.show()
circle()

You can use Matplotlib FuncAnimation with a function as argument (update in the code below) to call at each new frame. Use theta2 as argument to the update function (trough the frames parameter in the FuncAnimation) and use it together with the already declared variables x3 and x4 to give the perception of the smaller circle progressing around the larger one. Next, use set_data to allow the circlemove object to display different data points for each new frame.
from matplotlib.animation import FuncAnimation
...
...
plt.xlim(difference,difference2)
plt.ylim(difference,difference2)
plt.grid(linestyle='--')
def update(angle):
r_a = r1 + firstcircle
x_a = x3 + r_a * np.cos(angle) - firstcircle - secondcircle
y_a = x4 + r_a * np.sin(angle)
circlemove.set_data(x_a, y_a)
return circlemove
anim = FuncAnimation(fig, update, frames=theta2, repeat=True)
plt.show()
circle()

Related

Python mouse click event.xdata using twinx()

I am using canvas.mpl_connect mouse click listener for my e.g. 100x100 contourf plot with xlim from 0 to 99. Doing so I get e.g [x,y]= 10,20 as desired. However I have to display a second x-axis with different coordinates (e.g. xlim from 0.01 to 1) but I dont want event.xdata to return the coordinates in the style of the second axis. Is there a possibility to do so?
You could use the transformations in matplotlib. You would want to convert from the data-coordinates in ax2 to display coordinates (which are universal between the two axes) and then into data coordinates for ax1. Helpfully, you can combine transformations.
For example:
import matplotlib.pyplot as plt
import numpy as np
fig, ax1 = plt.subplots(1)
# First axis, with x-values going from 0 to 100
x1 = np.linspace(0, 100, 101)
y1 = np.sin(2 * np.pi * x1 / max(x1))
ax1.plot(x1, y1, 'b.-')
# Second axis, x values going from 0 to 1
ax2 = ax1.twiny()
x2 = np.linspace(0, 1, 11)
y2 = np.cos(2 * np.pi * x2 / max(x2))
ax2.plot(x2, y2, 'r.-')
# Create a combined transform from ax2 data to ax1 data
combinedTransform = ax2.transData + ax1.transData.inverted()
def onclick(event):
# event has x and y in data coordinates for ax2:
pt_data2 = (event.xdata, event.ydata)
# Convert them into data coordinates for ax1:
pt_data1 = combinedTransform.transform(pt_data2)
# ...
cid = fig.canvas.mpl_connect('button_press_event', onclick)
It feels like there would be a nicer way (somehow tell the event listener which axis you want the xdata and ydata to be valid for, but I don't know it. Sorry)
thanks, I implemented something like this as well. The problem is that there is actually no direct linear transformation behind my data. I solved the issue by just calling the second axis in a function after I finished setting the marker or choosing points. It's not beautiful but should be fine for a Master's thesis!

struggling in calling multiple interactive functions for a graph using ipywidgets

I'm looking to have a main image upon which I draw either spirals, ellipses etc with variables that change the shape on the imposed drawing. The main image also needs to have a contrast variable.
My code currently looks like this;
###############################################BASIC FIGURE PLOT####################################
plt.figure(figsize=(24,24))
#interact
def spiral(Spiral=False,n=2000,x1=50,y1=50,z1=50,k1=300):
if Spiral == False:
x = 0;
y = 0;
plt.scatter(x,y,s = 3, c = 'black');
else:
angle = np.linspace(x1,y1*1*np.pi, n)
radius = np.linspace(z1,k1,n)
x = radius * np.cos(angle) + 150
y = radius * np.sin(angle) + 150
plt.scatter(x,y,s = 3, c = 'black');
#interact
def contrast(vuc=(0.2,1,0.01),vlc=(0.1,1,0.01)):
vu = np.quantile(qphi, vuc);
vl = np.quantile(qphi, vlc);
print("upper =",vu, " lower=",vl);
plt.imshow(qphi, origin='lower',vmin=vl,vmax=vu);
plt.show()
This produces two plots;
visible here
One plot which creates a spiral I can edit freely and one plot that is the main image with variable contrast.
Any advise on how to combine the two plots would be much appreciated; Thank you!
There are several ways to approach controlling a matplotlib plot using ipywidgets. Below I've created the output I think you're looking for using each of the options. The methods are listed in what feels like the natural order of discovery, however, I would recommend trying them in this order: 4, 2, 1, 3
Approach 1 - inline backend
If you use %matplotlib inline then matplotlib figures will not be interactive and you will need to recreate the entire plot every time
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact
# load fake image data
from matplotlib import cbook
img = plt.imread(cbook.get_sample_data("grace_hopper.jpg")).mean(axis=-1)
#interact
def graph(
Spiral=True,
n=2000,
x1=50,
y1=50,
z1=50,
k1=300,
vlc=(0.1, 1, 0.01),
vuc=(0.1, 1, 0.01),
):
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12,5))
if Spiral == False:
x = 0
y = 0
else:
angle = np.linspace(x1, y1 * 1 * np.pi, n)
radius = np.linspace(z1, k1, n)
x = radius * np.cos(angle) + 150
y = radius * np.sin(angle) + 150
ax1.scatter(x, y, s=3, color="k")
vu = np.quantile(img, vuc)
vl = np.quantile(img, vlc)
ax2.imshow(img, vmin=vl, vmax=vu)
Approach 2 - interactive backend + cla
You can use one of the interactive maptlotlib backends to avoid having to completely regenerate the figure every time you change. To do this the first approach is to simply clear the axes everytime the sliders change using the cla method.
This will work with either %matplotlib notebook or %matplotlib ipympl. The former will only work in jupyter notebook and the latter will work in both jupyter notebook and juptyerlab. (Installation info for ipympl here: https://github.com/matplotlib/ipympl#installation)
%matplotlib ipympl
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact, interactive, interactive_output
# load fake image data
from matplotlib import cbook
img = plt.imread(cbook.get_sample_data("grace_hopper.jpg")).mean(axis=-1)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12,5))
#interact
def graph(
Spiral=True,
n=2000,
x1=50,
y1=50,
z1=50,
k1=300,
vlc=(0.1, 1, 0.01),
vuc=(0.1, 1, 0.01),
):
ax1.cla()
ax2.cla()
if Spiral == False:
x = 0
y = 0
else:
angle = np.linspace(x1, y1 * 1 * np.pi, n)
radius = np.linspace(z1, k1, n)
x = radius * np.cos(angle) + 150
y = radius * np.sin(angle) + 150
ax1.scatter(x, y, s=3, color="k")
vu = np.quantile(img, vuc)
vl = np.quantile(img, vlc)
ax2.imshow(img, vmin=vl, vmax=vu)
Approach 3 - interactive backend + set_data
Totally clearing the axes can be inefficient when you are plotting larger datasets or have some parts of the plot that you want to persist from one interaction to the next. So you can instead use the set_data and set_offsets methods to update what you have already drawn.
%matplotlib ipympl
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact, interactive, interactive_output
# load fake image data
from matplotlib import cbook
img = plt.imread(cbook.get_sample_data("grace_hopper.jpg")).mean(axis=-1)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12,5))
scat = ax1.scatter([0]*2000,[0]*2000,s=3, color='k')
im = ax2.imshow(img)
out = widgets.Output()
display(out)
#interact
def graph(
Spiral=True,
n=2000,
x1=50,
y1=50,
z1=50,
k1=300,
vlc=(0.1, 1, 0.01),
vuc=(0.1, 1, 0.01),
):
if Spiral == False:
x = 0
y = 0
else:
angle = np.linspace(x1, y1 * 1 * np.pi, n)
radius = np.linspace(z1, k1, n)
x = radius * np.cos(angle) + 150
y = radius * np.sin(angle) + 150
scat.set_offsets(np.c_[x, y])
# correctly scale the x and y limits
ax1.dataLim = scat.get_datalim(ax1.transData)
ax1.autoscale_view()
vu = np.quantile(img, vuc)
vl = np.quantile(img, vlc)
im.norm.vmin = vl
im.norm.vmax = vu
Approach 4 - mpl_interactions
Using set_offsets and equivalent set_data will be the most performant solution, but can also be tricky to figure out how to get it work and even trickier to remember. To make it easier I've creted a library (mpl-interactions) that automates the boilerplate of approach 3.
In addition to being easy and performant this has the advantage that you aren't responsible for updating the plots, only for returning the correct values. Which then has the ancillary benefit that now functions like spiral can be used in other parts of your code as they just return values rather than handle plotting.
The other advantage is that mpl-interactions can also create matplotlib widgets so this is the only approach that will also work outside of a notebook.
%matplotlib ipympl
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np
import mpl_interactions.ipyplot as iplt
img = plt.imread(cbook.get_sample_data("grace_hopper.jpg")).mean(axis=-1)
# define the functions to be plotted
def spiral(Spiral=False, n=2000, x1=50, y1=50, z1=50, k1=300):
if Spiral == False:
x = 0
y = 0
return x, y
else:
angle = np.linspace(x1, y1 * 1 * np.pi, n)
radius = np.linspace(z1, k1, n)
x = radius * np.cos(angle) + 150
y = radius * np.sin(angle) + 150
return x, y
def vmin(vuc, vlc):
return np.quantile(img, vlc)
def vmax(vlc, vuc):
return np.quantile(img, vuc)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
controls = iplt.scatter(
spiral,
Spiral={(True, False)},
n=np.arange(1800, 2200),
x1=(25, 75),
y1=(25, 75),
z1=(25, 75),
k1=(200, 400),
parametric=True,
s=3,
c="black",
ax=ax1,
)
controls = iplt.imshow(
img,
vmin=vmin,
vmax=vmax,
vuc=(0.1, 1, 1000),
vlc=(0.1, 1, 1000),
controls=controls[None],
ax=ax2,
)

Revolution solid with MatplotLib Python

I'm trying to create a bottle as a revolution solid using MatplotLib. I've got this points:
Image of the coordinates
Which in terms of coordinates are:
coords = [(0.00823433249299356, 0.06230346394288128),
(0.04086905251958573, 0.0648935210878489),
(0.08386400112604843, 0.0648935210878489),
(0.11753474401062763, 0.06541153251684242),
(0.14239929260231693, 0.05712334965294601),
(0.19109236692770842, 0.05401528107898486),
(0.2278711783862488, 0.05142522393401722),
(0.24133947554008045, 0.04158300678314021)]
The polynomial (more or less accurate) is:
Lambda(x, -19493.7965633925*x**6 + 13024.3747084876*x**5 - 3228.16456296349*x**4 + 368.816080918066*x**3 - 20.500262217588*x**2 + 0.545840273670868*x + 0.0590464366057008)
Which I get by:
# Getting the polynomial:
z = np.polyfit(xdata, ydata, 6)
# Being xdata and ydata the 2 vector from the coordinates
x = sp.symbols('x', real=True)
P = sp.Lambda(x,sum((a*x**i for i,a in enumerate(z[::-1]))))
print(P)
The point describe the outline of the bottle (cast your imagination) being the bottle in the plane XY.
How can I get, from that curve, a solid of revolution that recreates a bottle?
My objective is to be able to rotate the generator curve and create a solid of revolution, what I've tried is:
# Create the polynomial
pol = sp.lambdify(x,P(x),"numpy")
# Create the matrix of points
X = np.linspace(xdata[0], xdata[-1], 50)
Y = pol(X)
X, Y = np.meshgrid(X, Y)
# As long as a bottle is no more than a big amount of small cylinders, my
# equation should be more or less like:
# Z = x**2 + y** -R**2
# So we create here the equation
Z = X**2 + Y**2 - (Y - 0.0115)**2
# We create the #D figure
fig = plt.figure()
ax = plt.axes(projection="3d")
# And we representate it
surf = ax.plot_surface(X, Y, Z)
# We change the labels
ax.set_xlabel('$x$')
ax.set_ylabel('$y$')
ax.set_zlabel('$z$')
# And show the figure
plt.show()
The problem is that what I get is no longer a bottle (and I think is because how I'm using the plot_surface (I don't get very well how to use it by reading the documentation).
What I got is:
Image of the plotting. First I thought that was a problem related to the zoom, but I changed it and the figure is the same
I'll reference unutbu's answer to a similar question.
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as axes3d
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
# grab more points between your coordinates, say 100 points
u = np.linspace(0.00823433249299356, 0.24133947554008045, 100)
def polynomial(x):
return -19493.7965633925*x**6 + 13024.3747084876*x**5 - 3228.16456296349*x**4 + 368.816080918066*x**3 - 20.500262217588*x**2 + 0.545840273670868*x + 0.0590464366057008
v = np.linspace(0, 2*np.pi, 60)
U, V = np.meshgrid(u, v)
X = U
Y1 = polynomial(X)*np.cos(V)
Z1 = polynomial(X)*np.sin(V)
# Revolving around the axis
Y2 = 0*np.cos(V)
Z2 = 0*np.sin(V)
ax.plot_surface(X, Y1, Z1, alpha=0.3, color='red', rstride=6, cstride=12)
ax.plot_surface(X, Y2, Z2, alpha=0.3, color='blue', rstride=6, cstride=12)
# set the limits of the axes
ax.set_xlim3d(-0.3, 0.3)
ax.set_ylim3d(-0.3, 0.3)
ax.set_zlim3d(-0.3, 0.3)
plt.show()

Connector patch between subplots with animation not visible (matplotlib)

I am using an artist animation method with 5 subplots. There is one static plot on the left, with 3 smaller animated imshow plots to the right (the colorbar is the 5th). I have successfully used ConnectionPatch to connect subplots to show where the data is coming from, but only on static plots. No matter what I try, I can't seem to get the patches to show up on the animation. I've tried to include the patch in the image artist list, tried to update the figure with the artist instead of the axis (which I guess doesn't make much sense), among other things. It will be very difficult to extract a working example due to the complexity of the plot, but maybe someone has a tip.
Could setting the facecolor to 'white' with the animation savefig_kwargs be covering up the connector lines? If so, how do I change the z order of the patch/facecolor?
Without a minimal working example, I can only tell you that it is possible to use a ConnectionPatch in an animation. However, as seen below, one has to recreate it for every frame.
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(0)
import matplotlib.gridspec as gridspec
from matplotlib.patches import ConnectionPatch
import matplotlib.animation
plt.rcParams["figure.figsize"] = np.array([6,3.6])*0.7
x = np.linspace(-3,3)
X,Y = np.meshgrid(x,x)
f = lambda x,y: (1 - x / 2. + x ** 5 + y ** 3) * np.exp(-x ** 2 - y ** 2)+1.5
Z = f(X,Y)
bins=np.linspace(Z.min(), Z.max(), 16)
cols = plt.cm.PuOr((bins[:-1]-Z.min())/(Z.max()-Z.min()))
gs = gridspec.GridSpec(2, 2, height_ratios=[34,53], width_ratios=[102,53])
fig = plt.figure()
ax=fig.add_subplot(gs[:,0])
ax2=fig.add_subplot(gs[0,1])
ax3=fig.add_subplot(gs[1,1])
ax.imshow(Z, cmap="PuOr")
rec = plt.Rectangle([-.5,-.5], width=9, height=9, edgecolor="crimson", fill=False, lw=2)
conp = ConnectionPatch(xyA=[-0.5,0.5], xyB=[9.5,4], coordsA="data", coordsB="data",
axesA=ax3, axesB=ax, arrowstyle="-|>", zorder=25, shrinkA=0, shrinkB=1,
mutation_scale=20, fc="w", ec="crimson", lw=2)
ax3.add_artist(conp)
ax.add_artist(rec)
im = ax3.imshow(Z[:9,:9], cmap="PuOr", vmin=Z.min(), vmax=Z.max())
ticks = np.array([0,4,8])
ax3.set_yticks(ticks); ax3.set_xticks(ticks)
ax2.hist(Z[:9,:9].flatten(), bins=bins)
def ins(px,py):
global rec, conp, histpatches
ll = [px-.5,py-.5]
rec.set_xy(ll)
conp.remove()
conp = ConnectionPatch(xyA=[-0.5,0.5], xyB=[px+9.5,py+4], coordsA="data", coordsB="data",
axesA=ax3, axesB=ax, arrowstyle="-|>", zorder=25, shrinkA=0, shrinkB=1,
mutation_scale=20, fc="w", ec="crimson", lw=2)
ax3.add_patch(conp)
data = Z[px:px+9,py:py+9]
im.set_data(data)
ax3.set_xticklabels(ticks+px)
ax3.set_yticklabels(ticks+py)
ax2.clear()
ax2.set_ylim(0,60)
h, b_, patches = ax2.hist(data.flatten(), bins=bins, ec="k", fc="#f1a142")
[pat.set_color(cols[i]) for i, pat in enumerate(patches)]
def func(p):
px,py = p
ins(px, py)
phi = np.linspace(0.,2*np.pi)
r = np.sin(2*phi)*20+np.pi/2
xr = (r*np.cos(phi)).astype(np.int8)
yr = (r*np.sin(phi)).astype(np.int8)
plt.subplots_adjust(top=0.93,bottom=0.11,left=0.04,right=0.96,hspace=0.26,wspace=0.15)
frames = np.c_[xr+20, yr+20]
ani = matplotlib.animation.FuncAnimation(fig, func, frames=frames, interval=300, repeat=True)
plt.show()

Animation with matplotlib where points are dynamically added to a graph

I've written a simple code which generates random points (x0, y0) between certain values using a while loop. After the coordinates of each point are set, that point is drawn in an empty graph which is showed at the end of the while loop.
However, I would like to set up an animation with matplotlib which would allow me to see the initial graph and the points progressively added to it as the code is calculating them. I've looked for some examples but the ones I found are mainly concerned with waves and so on and I guess I need a slightly different approach.
This is the basic code:
from numpy import *
from pylab import *
import random
figure(figsize=(8,6), dpi=150)
x = np.linspace(-1, 4.5, 250)
h=5
a=0.5
b=4
ylim(-0.5,5.5)
xlim(-1,5.0)
i= 0
while i< 500:
R1 = random.random()
R2 = random.random()
x0 = (b - a)*R1 + a
y0 = h*R2
scatter(x0, y0, 10, color="red")
i = i + 1
show()
Thanks for your help!
EDIT: ANIMATION CODE
import numpy as np
import matplotlib.pyplot as plt
from pylab import *
import matplotlib.animation as animation
import random
fig = plt.figure(figsize=(8,6), dpi=150)
x = np.linspace(-2, 4.5, 250)
h=4
a=1
b=3
hlines(y=h, xmin=1, xmax=3, linewidth=1.5)
vlines(x=a, ymin=0, ymax=4, linewidth=1.5)
vlines(x=b, ymin=0, ymax=4, linewidth=1.5)
ylim(-2.5,10.5)
xlim(-2.5,4.5)
grid()
def data_gen():
i = 0
while i< 1:
R1 = random.random()
R2 = random.random()
x0 = (b - a)*R1 + a
y0 = h*R2
i = i + 1
yield x0, y0
line, = plot([], [], linestyle='none', marker='o', color='r')
ax = gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))
xdata, ydata = [], []
def run(data):
x0,y0 = data
xdata.append(x0)
ydata.append(y0)
line.set_data(xdata, ydata)
return line,
ani = animation.FuncAnimation(fig, run, data_gen, blit=True, interval=0.5,
repeat=False)
plt.show()
I do not know if this is exactly what you are looking for; in any case, you can generate random points inside the run function and there plot them. You do not need neither blit = True nor clear the axis from one frame to another.
Here is my code:
from pylab import *
from matplotlib.animation import FuncAnimation
import random
fig = plt.figure(figsize=(8,6), dpi=150)
x = np.linspace(-2, 4.5, 250)
h=4
a=1
b=3
hlines(y=h, xmin=a, xmax=b, linewidth=1.5)
vlines(x=a, ymin=0, ymax=h, linewidth=1.5)
vlines(x=b, ymin=0, ymax=h, linewidth=1.5)
ylim(-2.5,10.5)
xlim(-2.5,4.5)
grid()
ax = gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))
def run(i):
R1 = random.random()
R2 = random.random()
x0 = (b - a)*R1 + a
y0 = h*R2
ax.scatter(x0, y0, 10, color='red')
ani = FuncAnimation(fig = fig, func = run, frames = 500, interval = 10, repeat = False)
plt.show()
which produces this animation:
(I cut this animation to 100 points in order to get a lighter file, less than 2 MB; the code above produces an animation wiht 500 points)

Categories