Replot the same figure using matlibplot - python

I have the following code snippet:
import numpy as np
import matplotlib.pyplot as plt
N = 25
M = 10
def aFuncation(x):
return np.random.normal(100*np.exp(-x), 10.0)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
xs = np.zeros(N)
ys = np.zeros(N)
stds = np.zeros(N)
for n in range(N):
avgC = np.zeros(M)
for m in range(M):
Cost = aFuncation(n)
avgC[m] = Cost
xs[n] = n
ys[n] = np.mean(avgC)
stds[n] = np.std(avgC)
# Plot the time series
ax.clear()
ax.fill_between(xs[:n], ys[:n] - stds[:n], ys[:n] + stds[:n], alpha=0.3, color='black')
ax.plot(xs[:n], ys[:n], color='red')
ax.set_xlim([0, N + 1])
ax.set_xlabel('Number of evolutions')
ax.set_ylabel('Expected future cost')
plt.show()
I am not sure why the plot is not updated after the first iteration (n=1). I have read several posts related to this topic. The posts suggest the use of plt.ion(), plt.clf(), plt.draw(), etc. Whenever more I read about this topic, I become more confused. I am unfortunately unable to find a solution to this problem.

Related

Colored gradient on multiple lines from a csv in 3D plot?

Disclaimer: I'm a total newb to this, 2nd day so pls bear with me, thank you in advance!
So, I managed to get my 3D plot to have multiple lines, but I would like to give them some color gradients. I've managed to get it onto one example line, but I cannot convert it to my own plots.
My plots come from a .csv
I followed this question for the gradients: https://stackoverflow.com/a/8505774/20387853 (Answer by Yann) but I can't seem to understand how to merge the two for i in range bits (one from my old code with the new code) (if it even can be?)
I also dont understand ax.plot(x[i:i+2],y[i:i+2]) so I couldn't adjust this like I thought I could.
SO ATM i have two scripts
Script 1 - in which I'm trying to merge my two data sets.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import sys
import pandas
points = pandas.read_csv('D:Documents\PYTHON_FILES/test3d.csv')
def highResPoints(x,y,factor=10):
# r is the distance spanned between pairs of points
r = [0]
for i in range(1,len(x)):
dx = x[i]-x[i-1]
dy = y[i]-y[i-1]
r.append(np.sqrt(dx*dx+dy*dy))
r = np.array(r)
# rtot is a cumulative sum of r, it's used to save time
rtot = []
for i in range(len(r)):
rtot.append(r[0:i].sum())
rtot.append(r.sum())
dr = rtot[-1]/(NPOINTS*RESFACT-1)
xmod=[x[0]]
ymod=[y[0]]
rPos = 0 # current point on walk along data
rcount = 1
while rPos < r.sum():
x1,x2 = x[rcount-1],x[rcount]
y1,y2 = y[rcount-1],y[rcount]
dpos = rPos-rtot[rcount]
theta = np.arctan2((x2-x1),(y2-y1))
rx = np.sin(theta)*dpos+x1
ry = np.cos(theta)*dpos+y1
xmod.append(rx)
ymod.append(ry)
rPos+=dr
while rPos > rtot[rcount+1]:
rPos = rtot[rcount+1]
rcount+=1
if rcount>rtot[-1]:
break
return xmod,ymod
#CONSTANTS
NPOINTS = 10
COLOR='red'
RESFACT=10
MAP='winter' # choose carefully, or color transitions will not appear smoooth
cm = plt.get_cmap(MAP)
################ These are old data sets, just to use for this example
x = points['x'].values
y = points['y'].values
z = points['z'].values
x2 = points['x2'].values
y2 = points['y2'].values
z2 = points['z2'].values
fig = plt.figure()
#ax1 = fig.add_subplot(111,projection='3d') # regular resolution color map
ax = fig.add_subplot(111, projection='3d')
ax.plot(x, y, z, c='red',marker='v', linewidth=1.0, markersize=2)
ax.plot(x2, y2, z2, c='blue', marker='o', linewidth=1.0, markersize=2)
ax.set_prop_cycle(color=[cm(1.*i/(NPOINTS-1)) for i in range(NPOINTS-1)])
for i in range(NPOINTS-1):
#ax1.plot(x[i:i+2],y[i:i+2])
ax.plot(x[i:i+2],y[i:i+2])
########################The part I want to merge in
#for i in range(1, 5):
#if i == 1: i = '' #x is your first value not x1
#ax.plot(points[f"x{i}"], points[f"y{i}"], points[f"z{i}"], c='red', marker='o', linewidth=1.0, markersize=2)
#########################
fig.savefig('colorgradienttest.png')
plt.show()
[Link to Image]
I want to make the blue and red lines have a color gradient like the example 3rd line (markers are not important)
Script 2 - to which I want to apply the gradient (the one with the .csv)
from mpl_toolkits.mplot3d import Axes3D
import sys
import matplotlib.pyplot as plt
import pandas
import numpy as np
points = pandas.read_csv('D:Documents\PYTHON_FILES/test3d.csv')
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
#OPTION 1 - not sure why this isn't working for me so Im not using it yet
#for idx in range(29):
# suffix = '' if idx == 0 else str(idx + 1) # ranges start at 0
# x = points[f"x{suffix}"].values
# y = points[f"y{suffix}"].values
# z = points[f"z{suffix}"].values
#ax.plot(x, y, z, c='red', marker='o', linewidth=1.0, markersize=2)
#OPTION 2 - current approach <<<<<<<<<<<<<<<< want to apply gradient to this segment
for i in range(1, 5):
if i == 1: i = '' #x is your first value not x1
ax.plot(points[f"x{i}"], points[f"y{i}"], points[f"z{i}"], c='red', marker='o', linewidth=1.0, markersize=2)
plt.show()

Monte Carlo simulation programming exercise [duplicate]

I can evaluate the value of pi using different data points by Python. But for each repeat I want to plot the scatter plot like this:
My python code for finding pi using monte carlo method is :
from random import *
from math import sqrt
inside=0
n=10**6
for i in range(0,n):
x=random()
y=random()
if sqrt(x*x+y*y)<=1:
inside+=1
pi=4*inside/n
print (pi)
If you get errors about the backend use this:
import matplotlib as mp
mp.use('Tkagg')
Which will set the backend to TkAgg, which uses the Tkinter user interface toolkit.
import numpy as np
import matplotlib.pyplot as plt
n=1e3
x = 1-2*np.random.random(int(n))
y = 1-2.*np.random.random(int(n))
insideX, insideY = x[(x*x+y*y)<=1],y[(x*x+y*y)<=1]
outsideX, outsideY = x[(x*x+y*y)>1],y[(x*x+y*y)>1]
fig, ax = plt.subplots(1)
ax.scatter(insideX, insideY, c='b', alpha=0.8, edgecolor=None)
ax.scatter(outsideX, outsideY, c='r', alpha=0.8, edgecolor=None)
ax.set_aspect('equal')
fig.show()
To further elaborate Robbie's code:
import numpy as np
import matplotlib.pyplot as plt
n = 1000
xy = np.random.uniform(-1, 1, 2 * n).reshape((2, n))
in_marker = xy[0]**2 + xy[1]**2 <= 1
pi = np.sum(in_marker) / n * 4
in_xy = xy[:, in_marker]
out_xy = xy[:, ~in_marker]
fig, ax = plt.subplots(1)
ax.scatter(*in_xy,c='b')
ax.scatter(*out_xy,c='r')
ax.set_aspect('equal')
fig.show()
building from your code, this may get you started:
import matplotlib.pyplot as plt
from random import random
inside = 0
n = 10**3
x_inside = []
y_inside = []
x_outside = []
y_outside = []
for _ in range(n):
x = random()
y = random()
if x**2+y**2 <= 1:
inside += 1
x_inside.append(x)
y_inside.append(y)
else:
x_outside.append(x)
y_outside.append(y)
pi = 4*inside/n
print(pi)
fig, ax = plt.subplots()
ax.set_aspect('equal')
ax.scatter(x_inside, y_inside, color='g', marker='s')
ax.scatter(x_outside, y_outside, color='r', marker='s')
fig.show()
although i prefer this answer that uses numpy from the start...
Here is a variation on hiro protagonist's code, using random.uniform() to allow for random numbers between -1.0 and 1.0, allowing all the points to be plotted, and not just 1/4 of it (not the most elegant code, but it is spelled-out to learn the basics of the Monte Carlo Simulation):
import matplotlib.pyplot as plt
import random
inside = 0
n = 10**3
x_inside = []
y_inside = []
x_outside = []
y_outside = []
for _ in range(n):
x = random.uniform(-1.0,1.0)
y = random.uniform(-1.0,1.0)
if x**2+y**2 <= 1:
inside += 1
x_inside.append(x)
y_inside.append(y)
else:
x_outside.append(x)
y_outside.append(y)
To estimate pi, the points in the circle correspond to the area of the circle enclosing it (pi*radius^2) and the total points correspond to the area of the square enclosing it (2*radius)^2. So this translates into:
(points in the circle)/(total points) = (pi*radius^2)/(2*radius)^2
Solving for pi, the equation becomes:
pi=4*(points in the circle)/(total points)
pi = 4*inside/n
print(pi)
Plot the points inside and outside the circle:
fig, ax = plt.subplots()
ax.set_aspect('equal')
ax.scatter(x_inside, y_inside, color='g', marker='s')
ax.scatter(x_outside, y_outside, color='r', marker='s')
fig.show()
Plot of points inside and outside the circle

Is there a simple way to animate several points in the same plot with FuncAnimation?

I was trying to do an animation for fun using the mathematical solution for the pendulum. I know that my script is a little bit messy but I would really hope to improve. Below is my attempt to produce an animation using FuncAnimation from Matplotlib. I generated the data information for 3 points but my gif only shows one.
I read the documentation examples and some answers here similar to my problem but I really did not understand how to apply a simple solution in my case. I also saw that it was possible to use a scatter plot for this case but still I was not able to do it.
I would be grateful if you could recommend me a solution considering that I am a newbie using Python. And any other recommendation regarding the script will be kindly appreciated.
import matplotlib.pyplot as plt
import numpy as np
from scipy.integrate import odeint
from matplotlib.animation import FuncAnimation
%matplotlib inline
def solution(g, length, n, initial_angle, initial_velocity):
# g = 9.81
# l = 9.81
# n = 8
def model(u, t):
return (u[1], - (g / length) * np.sin(u[0] * np.pi / 180))
# Initial angle, and initial velocity
theta0 = [initial_angle, initial_velocity]
# Desired time interval
time = np.arange(0, 2 * n * np.pi, 0.1)
solution = odeint(model, theta0, time)
return solution[:,0]
data = []
for i in range(1, 4):
data.append(solution(9.81, 9.81 * i / 3, n, -17.5, 0))
data = np.array(data)
x_temp = np.sin(data * np.pi / 180)
y_temp = - np.cos(data * np.pi / 180)
# Coordinates for 3 pendulums
for i in range(0, 3):
x_temp[i] = (9.81 * i / 3) * x_temp[i]
y_temp[i] = (9.81 * i / 3) * y_temp[i]
# minimums and maximums based on the longest pendulum
x_min = x_temp[2].min()
x_max = x_temp[2].max()
y_min = y_temp[2].min()
fig, ax = plt.subplots()
ax = plt.axes(xlim=(x_min - 0.5, x_max + 0.5), ylim=(y_min - 0.5, 0))
point, = ax.plot([],[], 'go', lw=3)
# def init():
# point.set_data([], [])
# return point,
def animation_frames(i, x, y):
# point.set_data(x, y)
for j in range(0,3):
point.set_data(x[j][i], y[j][i])
return point,
animation = FuncAnimation(fig, animation_frames, frames=len(x_temp[0]), fargs=(x_temp, y_temp), interval=10)
animation.save('simple_pendulum.gif', writer='imagemagick')
Edit
I generated a simple case as recommended. The lists to be used just before calling the sublopts function are:
x_tem, y_temp
(array([[-0.98330796, -0.97717458, -0.95882857, -0.92843441, -0.88627296,
-0.83275034, -0.76840839, -0.69393471, -0.61017043, -0.51811348,
-0.41891573, -0.31387291, -0.20440652],
[-1.96661593, -1.9604803 , -1.94210039, -1.91155767, -1.86898966,
-1.81459239, -1.74862365, -1.67140657, -1.58333345, -1.48486936,
-1.37655514, -1.25900948, -1.13292978],
[-2.94992389, -2.94378752, -2.92539636, -2.89480454, -2.85210308,
-2.79742099, -2.73092681, -2.65283041, -2.56338497, -2.46288906,
-2.35168869, -2.23017916, -2.0988066 ]]),
array([[-3.11865443, -3.12058165, -3.12626739, -3.13542813, -3.14760548,
-3.16218704, -3.17843492, -3.19552102, -3.21256783, -3.22869299,
-3.2430556 , -3.2549015 , -3.26360506],
[-6.23730886, -6.2392401 , -6.24498567, -6.25440223, -6.26725439,
-6.28322007, -6.30189776, -6.32281583, -6.34544365, -6.36920427,
-6.39348856, -6.41767054, -6.44112336],
[-9.35596329, -9.35789587, -9.36366147, -9.37316418, -9.38624568,
-9.40268769, -9.42221517, -9.44450056, -9.46916879, -9.49580315,
-9.52395193, -9.55313566, -9.58285505]]))
I don't know exactly what your goal is but I tried to imitate your example of animating three points independently as close as possible. I only added different color and marker features, so that the points can be better distinguished:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
n=100
x_temp = [np.linspace(-10, -8, n),
np.linspace(-9, -6, n),
np.linspace(-6, -10, n)]
y_temp = [np.sin(x_temp[0]),
np.cos(x_temp[1]),
np.sin(x_temp[2])]
fig, ax = plt.subplots()
ax = plt.axes(xlim=(-11, -5), ylim=(- 1.5, 1.5))
points = []
for j, (col, mar) in enumerate(zip(["green", "blue", "red"], ["o", "x", "s"])):
newpoint, = ax.plot(x_temp[j][0], y_temp[j][0], color=col, marker=mar)
points.append(newpoint)
def animation_frames(i):
for j in range(0,3):
points[j].set_data(x_temp[j][i], y_temp[j][i])
animation = FuncAnimation(fig, animation_frames, frames=len(x_temp[0]), interval=30)
plt.show()
Sample output:

Real time live graphs in Jupyter Notebook

I have just started learning python to plot realtime gragh. I have tried solutions provided on stackoverflow but none of them are working. Below is my code and it isn't woorking. Please help
import numpy as np
import matplotlib.pyplot as plt
import pyautogui as pg
from matplotlib.animation import FuncAnimation
%matplotlib notebook
binSize = 512
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
def animate(i):
try:
while True:
x, y = pg.position()
f.append(x)
except KeyboardInterrupt:
print('')
# f.append(15)
if len(f)<binSize :
plt.cla()
plt.plot(f, color='c',LineWidth=1.5,label="Noisy")
else:
plt.cla()
plt.plot(f[-binSize:],color='c',LineWidth=1.5,label="Noisy")
ani = FuncAnimation(plt.gcf(),animate,interval=1);
So I have updated the code and trying to draw two subplots but after sometime
Upper graph stopped clearing the canvas (Mouse X coordinates)
Lower graph stopped updating the plot (FFT)
When data grows beyond the binSize, notebook freezes and plots update really slowly
%matplotlib notebook
binSize = 256
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
t = 0
dt = 1
fig,axs = plt.subplots(2,1)
def animate(i):
x, y = pg.position()
f.append(x)
n = len(f)
if n<binSize :
plt.sca(axs[0])
plt.cla()
plt.plot(f, color='c',LineWidth=1.5,label="MOUSE")
else:
fhat = np.fft.fft(f,binSize)
PSD = fhat*np.conj(fhat)/binSize
freq = (1/(dt*binSize))*np.arange(binSize)
L = np.arange(1,np.floor(binSize/2),dtype='int')
# update the code third time
axs[0].clear()
axs[0].plot(f[-binSize:], color='c',LineWidth=1.5,label="MOUSE")
# axs[0].xlim(0,binSize) # this stopped the FFT graph to be plotted
# plt.cla()
axs[1].clear()
axs[1].plot(freq[L],PSD[L],color='r',LineWidth=2,label="FFT")
# plt.xlim(t[0],t[-1])
# plt.legend()
# plt.sca(axs[1])
# plt.plot(freq[L],PSD[L],color='c',LineWidth=2,label="Mouse FFT")
# plt.xlim(0,300)
# plt.legend()
# plt.cla()
# plt.plot(f[-binSize:],color='c',LineWidth=1.5,label="Mouse")
ani = FuncAnimation(plt.gcf(),animate,interval=dt)
To make it faster you may reduce data like in other answer
f.pop(0)
I use also different method to update plot which works much faster on my computer.
I create empty plots at start
# needs `,` to get first element from list
p1, = axs[0].plot([], [], color='c', LineWidth=1.5, label="MOUSE")
p2, = axs[1].plot([], [], color='r', LineWidth=2, label="FFT")
and later only update data in plots without clear() and plot() again
xdata = range(len(f))
ydata = f
p1.set_data(xdata, ydata)
and
# replace data in plot
xdata = range(binSize)
ydata = f[-binSize:]
p1.set_data(xdata, ydata)
#p1.set_xdata(xdata)
#p1.set_ydata(ydata)
# replace data in plot
xdata = freq[:(binSize//2)]
ydata = PSD[:(binSize//2)]
p2.set_data(xdata, ydata)
It needs only to run code which rescale plot
# rescale view
axs[0].relim()
axs[0].autoscale_view(True,True,True)
axs[1].relim()
axs[1].autoscale_view(True,True,True)
animate() has to also return new plots
# return plots
return p1, p2
And FuncAnimation() has to blit them
ani = FuncAnimation(..., blit=True)
EDIT:
Animation works much, much faster also because I run it normally python script.py, not in Jupuyter Notebook
EDIT:
when I run normally I found one problem which I could find solution: it doesn't update values/ticks on axes. Jupyter Notebook doesn't have this problem.
import numpy as np
import matplotlib.pyplot as plt
import pyautogui as pg
from matplotlib.animation import FuncAnimation
%matplotlib notebook
binSize = 256
f = []
t = 0
dt = 1
fig, axs = plt.subplots(2, 1)
# needs `,` to get first element from list
p1, = axs[0].plot([], [], color='c', LineWidth=1.5, label="MOUSE")
p2, = axs[1].plot([], [], color='r', LineWidth=2, label="FFT")
freq = np.arange(binSize)/(dt*binSize)
def animate(i):
x, y = pg.position()
n = len(f)
if n < binSize :
f.append(x)
# replace data in plot
xdata = range(len(f))
ydata = f
p1.set_data(xdata, ydata)
#p1.set_xdata(xdata)
#p1.set_ydata(ydata)
else:
f.pop(0)
f.append(x)
fhat = np.fft.fft(f, binSize)
PSD = fhat * np.conj(fhat) / binSize
# replace data in plot
#xdata = range(binSize)
ydata = f[-binSize:]
#p1.set_data(xdata, ydata)
#p1.set_xdata(xdata)
p1.set_ydata(ydata)
# replace data in plot
xdata = freq[:(binSize//2)]
ydata = PSD[:(binSize//2)]
p2.set_data(xdata, ydata)
# rescale view
axs[0].relim()
axs[0].autoscale_view(True,True,True)
axs[1].relim()
axs[1].autoscale_view(True,True,True)
# return plots
return p1, p2
ani = FuncAnimation(plt.gcf(), animate, interval=dt, blit=True)
plt.show()
You should try this. Instead of clearing the plt clear axs[0] and so on. Also, instead of plotting on plt.plot, plot on axs[0].plot
%matplotlib notebook
binSize = 256
# fig(ax1,ax2) = plt.subplots(2,figsize=(12,6))
f = []
t = 0
dt = 1
fig,axs = plt.subplots(2,1)
plt.sca(axs[0])
plt.sca(axs[1])
def animate(i):
x, y = pg.position()
n = len(f)
if n<binSize :
f.append(x*100)
axs[0].clear()
axs[0].plot(f, color='c',LineWidth=1.5,label="MOUSE")
else:
f.pop(0)
f.append(x)
fhat = np.fft.fft(f,binSize)
PSD = fhat*np.conj(fhat)/binSize
freq = (1/(dt*binSize))*np.arange(binSize)
L = np.arange(1,np.floor(binSize/2),dtype='int') # index array of [1,2,3..... binsize/2] type int
axs[0].clear()
axs[0].plot(f[-binSize:], color='c',LineWidth=1.5,label="MOUSE")
axs[1].clear()
axs[1].plot(freq[L],PSD[L],color='r',LineWidth=2,label="FFT")
ani = FuncAnimation(plt.gcf(),animate,interval=dt)
plt.show()

Plotting Pi using Monte Carlo Method

I can evaluate the value of pi using different data points by Python. But for each repeat I want to plot the scatter plot like this:
My python code for finding pi using monte carlo method is :
from random import *
from math import sqrt
inside=0
n=10**6
for i in range(0,n):
x=random()
y=random()
if sqrt(x*x+y*y)<=1:
inside+=1
pi=4*inside/n
print (pi)
If you get errors about the backend use this:
import matplotlib as mp
mp.use('Tkagg')
Which will set the backend to TkAgg, which uses the Tkinter user interface toolkit.
import numpy as np
import matplotlib.pyplot as plt
n=1e3
x = 1-2*np.random.random(int(n))
y = 1-2.*np.random.random(int(n))
insideX, insideY = x[(x*x+y*y)<=1],y[(x*x+y*y)<=1]
outsideX, outsideY = x[(x*x+y*y)>1],y[(x*x+y*y)>1]
fig, ax = plt.subplots(1)
ax.scatter(insideX, insideY, c='b', alpha=0.8, edgecolor=None)
ax.scatter(outsideX, outsideY, c='r', alpha=0.8, edgecolor=None)
ax.set_aspect('equal')
fig.show()
To further elaborate Robbie's code:
import numpy as np
import matplotlib.pyplot as plt
n = 1000
xy = np.random.uniform(-1, 1, 2 * n).reshape((2, n))
in_marker = xy[0]**2 + xy[1]**2 <= 1
pi = np.sum(in_marker) / n * 4
in_xy = xy[:, in_marker]
out_xy = xy[:, ~in_marker]
fig, ax = plt.subplots(1)
ax.scatter(*in_xy,c='b')
ax.scatter(*out_xy,c='r')
ax.set_aspect('equal')
fig.show()
building from your code, this may get you started:
import matplotlib.pyplot as plt
from random import random
inside = 0
n = 10**3
x_inside = []
y_inside = []
x_outside = []
y_outside = []
for _ in range(n):
x = random()
y = random()
if x**2+y**2 <= 1:
inside += 1
x_inside.append(x)
y_inside.append(y)
else:
x_outside.append(x)
y_outside.append(y)
pi = 4*inside/n
print(pi)
fig, ax = plt.subplots()
ax.set_aspect('equal')
ax.scatter(x_inside, y_inside, color='g', marker='s')
ax.scatter(x_outside, y_outside, color='r', marker='s')
fig.show()
although i prefer this answer that uses numpy from the start...
Here is a variation on hiro protagonist's code, using random.uniform() to allow for random numbers between -1.0 and 1.0, allowing all the points to be plotted, and not just 1/4 of it (not the most elegant code, but it is spelled-out to learn the basics of the Monte Carlo Simulation):
import matplotlib.pyplot as plt
import random
inside = 0
n = 10**3
x_inside = []
y_inside = []
x_outside = []
y_outside = []
for _ in range(n):
x = random.uniform(-1.0,1.0)
y = random.uniform(-1.0,1.0)
if x**2+y**2 <= 1:
inside += 1
x_inside.append(x)
y_inside.append(y)
else:
x_outside.append(x)
y_outside.append(y)
To estimate pi, the points in the circle correspond to the area of the circle enclosing it (pi*radius^2) and the total points correspond to the area of the square enclosing it (2*radius)^2. So this translates into:
(points in the circle)/(total points) = (pi*radius^2)/(2*radius)^2
Solving for pi, the equation becomes:
pi=4*(points in the circle)/(total points)
pi = 4*inside/n
print(pi)
Plot the points inside and outside the circle:
fig, ax = plt.subplots()
ax.set_aspect('equal')
ax.scatter(x_inside, y_inside, color='g', marker='s')
ax.scatter(x_outside, y_outside, color='r', marker='s')
fig.show()
Plot of points inside and outside the circle

Categories

Resources