Introduction
Spirograph toy that is used to produce complex patterns using plastic cogs and colored pens. A fractal is a curve, that is developed using a recurring pattern that repeats itself infinitely on a low scale. Fractals are used for modeling structures (such as snowflakes) or for describing partly chaotic phenomena.
Spirograph can be used to draw various fractals. Some of them are given below
You can visit benice-equation-blogspot.in for more fractals design with their parametric equation. Some of them are given below
Mathematics behind the curtain
These are the two parametric equation to form a spirograph fractals, to understand these equations you have to consider a generalized figure of spirograph.
For the mathematics part you can refer to Wiki although i'll try to explain a little of that mathematics in a short here. If we are interested behind the maths then you can check out the referred links. So as of now, these various curve can be drawn by using a parametric equation and varying some values of that equation we can get different fractals. So here's the parametric equation:
x(t) = R[(1-k)cost + lkcos(((1-k)/k)t)],
y(t) = R[(1-k)sint - lksin(((1-k)/k)t).
where,
k = r/R
R is a scaling parameter and does not affect the structure of the Spirograph.
and,
l = p/r
So, now let's try to implement this in code.
Python3
#importing the required librariesimportrandom,argparseimportmathimportturtlefromPILimportImagefromdatetimeimportdatetimefromfractionsimportgcd# A class that draws a spirographclassSpiro:# constructordef__init__(self,xc,yc,col,R,r,l):# create own turtleself.t=turtle.Turtle()# set cursor shapeself.t.shape('turtle')# set step in degreesself.step=5# set drawing complete flagself.drawingComplete=False# set parametersself.setparams(xc,yc,col,R,r,l)# initiatize drawingself.restart()# set parametersdefsetparams(self,xc,yc,col,R,r,l):# spirograph parametersself.xc=xcself.yc=ycself.R=int(R)self.r=int(r)self.l=lself.col=col# reduce r/R to smallest form by dividing with GCDgcdVal=gcd(self.r,self.R)self.nRot=self.r//gcdVal# get ratio of radiiself.k=r/float(R)# set colorself.t.color(*col)# current angleself.a=0# restart drawingdefrestart(self):# set flagself.drawingComplete=False# show turtleself.t.showturtle()# go to first pointself.t.up()R,k,l=self.R,self.k,self.la=0.0x=R*((1-k)*math.cos(a)+l*k*math.cos((1-k)*a/k))y=R*((1-k)*math.sin(a)-l*k*math.sin((1-k)*a/k))self.t.setpos(self.xc+x,self.yc+y)self.t.down()# draw the whole thingdefdraw(self):# draw rest of pointsR,k,l=self.R,self.k,self.lforiinrange(0,360*self.nRot+1,self.step):a=math.radians(i)x=R*((1-k)*math.cos(a)+l*k*math.cos((1-k)*a/k))y=R*((1-k)*math.sin(a)-l*k*math.sin((1-k)*a/k))self.t.setpos(self.xc+x,self.yc+y)# done - hide turtleself.t.hideturtle()# update by one stepdefupdate(self):# skip if doneifself.drawingComplete:return# increment angleself.a+=self.step# draw stepR,k,l=self.R,self.k,self.l# set anglea=math.radians(self.a)x=self.R*((1-k)*math.cos(a)+l*k*math.cos((1-k)*a/k))y=self.R*((1-k)*math.sin(a)-l*k*math.sin((1-k)*a/k))self.t.setpos(self.xc+x,self.yc+y)# check if drawing is complete and set flagifself.a>=360*self.nRot:self.drawingComplete=True# done - hide turtleself.t.hideturtle()# clear everythingdefclear(self):self.t.clear()# A class for animating spirographsclassSpiroAnimator:# constructordef__init__(self,N):# timer value in millisecondsself.deltaT=10# get window dimensionsself.width=turtle.window_width()self.height=turtle.window_height()# create spiro objectsself.spiros=[]foriinrange(N):# generate random parametersrparams=self.genRandomParams()# set spiro paramsspiro=Spiro(*rparams)self.spiros.append(spiro)# call timerturtle.ontimer(self.update,self.deltaT)# restart sprio drawingdefrestart(self):forspiroinself.spiros:# clearspiro.clear()# generate random parametersrparams=self.genRandomParams()# set spiro paramsspiro.setparams(*rparams)# restart drawingspiro.restart()# generate random parametersdefgenRandomParams(self):width,height=self.width,self.heightR=random.randint(50,min(width,height)//2)r=random.randint(10,9*R//10)l=random.uniform(0.1,0.9)xc=random.randint(-width//2,width//2)yc=random.randint(-height//2,height//2)col=(random.random(),random.random(),random.random())return(xc,yc,col,R,r,l)defupdate(self):# update all spirosnComplete=0forspiroinself.spiros:# updatespiro.update()# count completed onesifspiro.drawingComplete:nComplete+=1# if all spiros are complete, restartifnComplete==len(self.spiros):self.restart()# call timerturtle.ontimer(self.update,self.deltaT)# toggle turtle on/offdeftoggleTurtles(self):forspiroinself.spiros:ifspiro.t.isvisible():spiro.t.hideturtle()else:spiro.t.showturtle()# save spiros to imagedefsaveDrawing():# hide turtleturtle.hideturtle()# generate unique file namedateStr=(datetime.now()).strftime("%d%b%Y-%H%M%S")fileName='spiro-'+dateStrprint('saving drawing to %s.eps/png'%fileName)# get tkinter canvascanvas=turtle.getcanvas()# save postscipt imagecanvas.postscript(file=fileName+'.eps')# use PIL to convert to PNGimg=Image.open(fileName+'.eps')img.save(fileName+'.png','png')# show turtleturtle.showturtle()# main() functiondefmain():# use sys.argv if neededprint('generating spirograph...')# create parserdescStr="""This program draws spirographs using the Turtle module. When run with no arguments, this program draws random spirographs. Terminology: R: radius of outer circle. r: radius of inner circle. l: ratio of hole distance to r. """parser=argparse.ArgumentParser(description=descStr)# add expected argumentsparser.add_argument('--sparams',nargs=3,dest='sparams',required=False,help="The three arguments in sparams: R, r, l.")# parse argsargs=parser.parse_args()# set to 80% screen widthturtle.setup(width=0.8)# set cursor shapeturtle.shape('turtle')# set titleturtle.title("Spirographs!")# add key handler for saving imagesturtle.onkey(saveDrawing,"s")# start listening turtle.listen()# hide main turtle cursorturtle.hideturtle()# checks args and drawifargs.sparams:params=[float(x)forxinargs.sparams]# draw spirograph with given parameters# black by defaultcol=(0.0,0.0,0.0)spiro=Spiro(0,0,col,*params)spiro.draw()else:# create animator objectspiroAnim=SpiroAnimator(4)# add key handler to toggle turtle cursorturtle.onkey(spiroAnim.toggleTurtles,"t")# add key handler to restart animationturtle.onkey(spiroAnim.restart,"space")# start turtle main loopturtle.mainloop()# call mainif__name__=='__main__':main()
Output:
The above program draws 4 different kinds of spirograph fractals, try to generate other fractals and then upload your github links in the comment. I'll be happy to help you out if any error comes up.